[インデックス 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つです。
src/pkg/net/http/sniff.go
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データが正しく処理されることを保証するために不可欠です。
関連リンク
- Go CL 5365041: https://golang.org/cl/5365041
参考にした情報源リンク
- Go
net/http
package documentation: https://pkg.go.dev/net/http - Go
bytes
package documentation: https://pkg.go.dev/bytes - MIME type sniffing: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#mime_sniffing
- CRLF: https://en.wikipedia.org/wiki/Newline#Representations
- Whitespace character: https://en.wikipedia.org/wiki/Whitespace_character