Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10266] ファイルの概要

このコミットは、Go言語のnet/httpパッケージにおけるコンテンツスニッフィング機能のバグ修正に関するものです。具体的には、HTTPレスポンスのコンテンツタイプを自動判別する際に、DOS形式の改行コード(CRLF)を含むHTMLデータを正しくHTMLとして認識できない問題が修正されました。これは、ホワイトスペースを検出する関数内のタイプミス(\nの代わりに\rであるべき箇所)が原因でした。

コミット

  • コミットハッシュ: 75af79b9b59548c3177b7a0307d6ab75fbbd87a2
  • Author: David Symonds dsymonds@golang.org
  • Date: Mon Nov 7 11:55:33 2011 +1100

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/75af79b9b59548c3177b7a0307d6ab75fbbd87a2

元コミット内容

net/http: fix whitespace handling in sniffer.

A single character typo ("\n" instead of "\r") meant that
HTML data using DOS line breaks (CRLF) was not detected as HTML.

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/5365041

変更の背景

このコミットの背景には、Go言語のnet/httpパッケージが提供するコンテンツスニッフィング機能の不具合がありました。コンテンツスニッフィングとは、HTTPレスポンスのContent-Typeヘッダが指定されていない場合や、指定されたタイプが不明確な場合に、レスポンスボディの先頭バイトを検査してそのコンテンツタイプ(例: text/html, image/pngなど)を推測するプロセスです。

問題は、net/http/sniff.go内のisWS関数に存在しました。この関数は、コンテンツスニッフィングの際に、データの先頭にあるホワイトスペース(空白文字)をスキップするために使用されます。しかし、この関数が認識するホワイトスペース文字のリストにタイプミスがあり、キャリッジリターン(\r)の代わりにラインフィード(\n)が誤って含まれていました。

これにより、特にWindows環境で一般的なDOS形式の改行コード(CRLF: \r\n)で始まるHTMLデータが、正しくHTMLとして検出されないという問題が発生していました。\rがホワイトスペースとして認識されないため、スニッファは\rをHTMLの開始タグの一部と誤解し、結果としてコンテンツタイプをtext/htmlと判別できませんでした。この修正は、このタイプミスを訂正し、CRLFを含むHTMLデータも適切に処理できるようにすることを目的としています。

前提知識の解説

1. net/httpパッケージ

Go言語の標準ライブラリであるnet/httpパッケージは、HTTPクライアントとサーバーの実装を提供します。Webアプリケーションの構築やHTTPリクエストの送信など、ネットワーク通信の基盤となります。このパッケージには、HTTPヘッダの解析、リクエストのルーティング、レスポンスの生成など、HTTPプロトコルを扱うための様々な機能が含まれています。

2. コンテンツスニッフィング (Content Sniffing)

コンテンツスニッフィングは、MIMEタイプスニッフィングとも呼ばれ、HTTPレスポンスのContent-Typeヘッダが欠落しているか、または一般的なMIMEタイプ(例: application/octet-stream)が指定されている場合に、ブラウザや他のクライアントがコンテンツの実際のタイプを推測する技術です。これは、セキュリティ上のリスク(例: 実行可能なスクリプトが画像として扱われる)や、コンテンツの誤った表示につながる可能性があるため、慎重に実装される必要があります。Goのnet/httpパッケージのDetectContentType関数は、このスニッフィングロジックを提供します。

3. CRLF (Carriage Return Line Feed)

CRLFは、テキストファイルやネットワークプロトコルにおける改行コードの一種です。

  • CR (Carriage Return): キャリッジリターン(\r、ASCIIコード13)。カーソルを行の先頭に戻す制御文字です。
  • LF (Line Feed): ラインフィード(\n、ASCIIコード10)。カーソルを次の行に移動させる制御文字です。

Windowsシステムでは、改行は通常CRLF(\r\n)の組み合わせで表現されます。一方、Unix/LinuxシステムではLF(\n)のみが使用され、古いMacシステムではCR(\r)のみが使用されていました。HTTPプロトコルでは、ヘッダとボディの区切りなど、多くの場所でCRLFが使用されます。

4. ホワイトスペース (Whitespace)

プログラミングやテキスト処理において、ホワイトスペースとは、表示されないがテキストのレイアウトに影響を与える文字の総称です。一般的なホワイトスペース文字には、スペース( )、タブ(\t)、ラインフィード(\n)、キャリッジリターン(\r)、フォームフィード(\x0C\f)などがあります。コンテンツスニッフィングでは、データの先頭にあるこれらのホワイトスペースをスキップして、実際のコンテンツの開始位置を特定することが重要です。

5. bytes.IndexByte

Go言語のbytesパッケージに含まれるIndexByte関数は、バイトスライス([]byte)内で特定のバイトが最初に現れるインデックスを返します。もしそのバイトが見つからない場合は-1を返します。このコミットでは、isWS関数内で、与えられたバイトがホワイトスペース文字のリストに含まれているかを効率的にチェックするために使用されています。

技術的詳細

Goのnet/httpパッケージにおけるコンテンツスニッフィングは、DetectContentType関数によって行われます。この関数は、入力されたバイトスライス(通常はHTTPレスポンスボディの先頭部分)を検査し、その内容に基づいて適切なMIMEタイプを返します。

スニッフィングのプロセスでは、まずデータの先頭にあるホワイトスペースをスキップします。これは、HTMLドキュメントが<!DOCTYPE html><html>タグの前に空白や改行を含むことが一般的であるためです。このホワイトスペースの検出を担当するのがisWS関数です。

元のisWS関数の実装は以下のようになっていました。

func isWS(b byte) bool {
    return bytes.IndexByte([]byte("\t\n\x0C\n "), b) != -1
}

このコードでは、ホワイトスペースとしてタブ(\t)、ラインフィード(\n)、フォームフィード(\x0C)、そして通常のスペース( )を認識していました。しかし、ここで問題となったのは、\nが2回含まれている点と、キャリッジリターン(\r)が欠落している点です。

特に、\rがホワイトスペースとして認識されないことが、DOS形式の改行コード(CRLF: \r\n)で始まるHTMLデータの検出に影響を与えました。例えば、データが\r\n<html>...という形式で始まる場合、isWS関数は最初の\rをホワイトスペースとしてスキップせず、それをコンテンツの一部と見なしてしまいます。これにより、<html>タグが期待される位置で見つからず、結果としてtext/htmlとして正しく検出されませんでした。

このコミットでは、isWS関数内のホワイトスペース文字リストから余分な\nを削除し、代わりに\rを追加することで、この問題を解決しています。これにより、CRLFで始まるHTMLデータも適切にホワイトスペースとしてスキップされ、その後のHTMLタグが正しく解析されるようになります。

また、この修正を検証するために、sniff_test.goに新しいテストケースが追加されました。このテストケースは、CRLFで始まるHTMLデータが正しくtext/html; charset=utf-8として検出されることを確認します。

コアとなるコードの変更箇所

このコミットによって変更されたファイルは以下の2つです。

  1. src/pkg/net/http/sniff.go
  2. src/pkg/net/http/sniff_test.go

src/pkg/net/http/sniff.go の変更点

--- a/src/pkg/net/http/sniff.go
+++ b/src/pkg/net/http/sniff.go
@@ -38,7 +38,7 @@ func DetectContentType(data []byte) string {
 }
 
 func isWS(b byte) bool {
-	return bytes.IndexByte([]byte("\t\n\x0C\n "), b) != -1
+	return bytes.IndexByte([]byte("\t\n\x0C\r "), b) != -1
 }
 
 type sniffSig interface {

src/pkg/net/http/sniff_test.go の変更点

--- a/src/pkg/net/http/sniff_test.go
+++ b/src/pkg/net/http/sniff_test.go
@@ -26,6 +26,7 @@ var sniffTests = []struct {
 	{"HTML document #1", []byte(`<HtMl><bOdY>blah blah blah</body></html>`), "text/html; charset=utf-8"},
 	{"HTML document #2", []byte(`<HTML></HTML>`), "text/html; charset=utf-8"},
 	{"HTML document #3 (leading whitespace)", []byte(`   <!DOCTYPE HTML>...`), "text/html; charset=utf-8"},
+\t{"HTML document #4 (leading CRLF)", []byte("\r\n<html>..."), "text/html; charset=utf-8"},
 
 	{"Plain text", []byte(`This is not HTML. It has ☃ though.`), "text/plain; charset=utf-8"},
 

コアとなるコードの解説

src/pkg/net/http/sniff.go の変更

isWS関数は、与えられたバイトbがホワイトスペース文字であるかどうかを判定します。 変更前: bytes.IndexByte([]byte("\t\n\x0C\n "), b) このバイトスライスには、タブ(\t)、ラインフィード(\n)、フォームフィード(\x0C)、そして通常のスペース( )が含まれていました。しかし、\nが重複しており、キャリッジリターン(\r)が欠落していました。

変更後: bytes.IndexByte([]byte("\t\n\x0C\r "), b) この修正により、バイトスライスから重複していた\nが削除され、代わりにキャリッジリターン(\r)が追加されました。これにより、isWS関数はCRLF形式の改行コードに含まれる\rも正しくホワイトスペースとして認識できるようになり、コンテンツスニッフィングがより堅牢になりました。

src/pkg/net/http/sniff_test.go の変更

sniff_test.goには、sniffTestsというテストケースのスライスが定義されており、様々なコンテンツタイプのスニッフィングが正しく行われるかを確認しています。

追加されたテストケース: {"HTML document #4 (leading CRLF)", []byte("\r\n<html>..."), "text/html; charset=utf-8"}

この新しいテストケースは、\r\n(CRLF)で始まるHTML文字列が、期待通りtext/html; charset=utf-8として検出されることを検証します。このテストの追加は、isWS関数の修正が意図した通りに機能し、CRLFを含むHTMLデータが正しく処理されることを保証するために不可欠です。

関連リンク

参考にした情報源リンク