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

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

このコミットは、Go言語の実験的なHTMLパーサー(exp/htmlパッケージ)において、外部コンテンツ(foreign content、例えばSVGやMathML)内のテキストに含まれるNULL文字(\x00、U+0000)の処理方法を修正するものです。具体的には、NULL文字を単に削除するのではなく、Unicodeの置換文字(U+FFFD)に置き換えるように変更しています。これにより、HTML5の仕様に準拠し、セキュリティと互換性を向上させています。

コミット

commit eff32f573b19b83283785c1df4539232c39fdba0
Author: Andrew Balholm <andybalholm@gmail.com>
Date:   Sun Jul 29 16:29:49 2012 +1000

    exp/html: replace NUL with U+FFFD in text in foreign content
    
    Pass 5 additional tests.
    
    R=nigeltao
    CC=golang-dev
    https://golang.org/cl/6452055

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

https://github.com/golang/go/commit/eff32f573b19b83283785c1df4539232c39fdba0

元コミット内容

このコミットの目的は、exp/htmlパッケージにおいて、外部コンテンツ(foreign content)内のテキストデータにNULL文字(\x00)が出現した場合の処理を改善することです。以前はNULL文字を単に削除していましたが、HTML5の仕様ではこれをUnicodeの置換文字(U+FFFD)に置き換えることが求められています。この変更により、5つの追加テストがパスするようになりました。

変更の背景

HTML5の仕様では、パーサーが入力ストリーム内でNULL文字(U+0000)を検出した場合、ほとんどのケースでそれをUnicodeの置換文字(U+FFFD)に置き換えることが義務付けられています。これは主にセキュリティ上の理由と、古いC言語ベースの実装がNULL終端文字列をどのように扱っていたかに関連する潜在的な脆弱性を防ぐためです。

特に、SVG(Scalable Vector Graphics)やMathML(Mathematical Markup Language)のような「外部コンテンツ(foreign content)」がHTMLドキュメント内に埋め込まれている場合、これらのコンテンツ内のテキストデータにNULL文字が含まれると、ブラウザやパーサーの実装によって挙動が異なる可能性がありました。HTML5の厳格なルールに従うことで、パーサーの予測可能性と堅牢性を高め、クロスサイトスクリプティング(XSS)などの潜在的な攻撃ベクトルを減少させることができます。

このコミットは、Go言語のHTMLパーサーがHTML5仕様に完全に準拠し、より安全で互換性の高い動作をするようにするための修正の一環です。

前提知識の解説

HTML5パーシングとトークン化

HTML5のパースプロセスは、入力ストリームをトークンに分解する「トークン化」と、それらのトークンからDOMツリーを構築する「ツリー構築」の2つの主要なフェーズに分かれます。このコミットが関連するのは、主にトークン化フェーズ、特にテキストデータの処理です。

NULL文字(U+0000, \x00

NULL文字は、ASCIIコードで0x00、UnicodeでU+0000に割り当てられている制御文字です。C言語などでは文字列の終端を示すために使われることが多く、歴史的に様々なセキュリティ問題の原因となってきました。Webコンテンツにおいては、意図しない挙動を引き起こす可能性があるため、HTML5仕様では特別な扱いが規定されています。

Unicode置換文字(U+FFFD)

U+FFFDは「REPLACEMENT CHARACTER」と呼ばれるUnicode文字です。これは、エンコーディングエラーや、不正な文字シーケンス、または処理できない文字が検出された場合に、その文字の代わりに表示されるプレースホルダーとして使用されます。HTML5パーシングにおいてNULL文字がU+FFFDに置き換えられるのは、NULL文字が不正な文字として扱われるためです。

外部コンテンツ(Foreign Content)

HTML5における「外部コンテンツ」とは、HTML名前空間ではないXML名前空間に属する要素を指します。最も一般的な例はSVG(Scalable Vector Graphics)とMathML(Mathematical Markup Language)です。これらのコンテンツはHTMLドキュメント内に埋め込まれますが、独自のパースルールやDOM構造を持ちます。外部コンテンツ内のテキストノードの処理は、通常のHTMLテキストノードとは異なる場合があります。

framesetOKフラグ

HTMLパーサー内部で使用されるフラグの一つで、frameset要素がまだ許可されているかどうかを示すものです。HTML5では、frameset要素は特定の条件下でのみ許可され、例えば、body要素や特定の種類のテキストノードが既に存在する場合は許可されません。このフラグは、パーサーがframeset要素を適切に処理するための状態管理に役立ちます。

技術的詳細

このコミットの核心は、src/pkg/exp/html/parse.goファイル内のparseForeignContent関数におけるNULL文字の処理ロジックの変更です。

  1. whitespaceOrNUL定数の導入: const whitespaceOrNUL = whitespace + "\x00" これは、既存のwhitespace定数にNULL文字(\x00)を追加した新しい定数です。これにより、空白文字とNULL文字の両方を一括してトリムする操作が可能になります。

  2. framesetOKフラグの更新ロジックの変更: 変更前: p.framesetOK = strings.TrimLeft(p.tok.Data, whitespace) == "" 変更後: p.framesetOK = strings.TrimLeft(p.tok.Data, whitespaceOrNUL) == "" framesetOKフラグの更新において、トークンデータ(p.tok.Data)の左側から空白文字をトリムする際に、NULL文字もトリムの対象に含めるようになりました。これは、NULL文字が空白文字と同様に、frameset要素の許可状態に影響を与える可能性があるというHTML5の挙動に合わせたものです。

  3. NULL文字の置換ロジックの変更: 変更前: p.tok.Data = strings.Replace(p.tok.Data, "\x00", "", -1) 変更後: p.tok.Data = strings.Replace(p.tok.Data, "\x00", "\ufffd", -1) これが最も重要な変更点です。以前は、外部コンテンツ内のテキストデータからNULL文字を単に削除していました。しかし、HTML5の仕様に準拠するため、NULL文字をUnicodeの置換文字U+FFFDに置き換えるように修正されました。strings.Replace関数の最後の引数-1は、すべての出現箇所を置換することを意味します。

これらの変更により、パーサーはHTML5の仕様に厳密に従い、外部コンテンツ内のNULL文字を適切に処理し、より堅牢で予測可能な動作を実現します。

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

--- a/src/pkg/exp/html/parse.go
+++ b/src/pkg/exp/html/parse.go
@@ -1846,14 +1846,16 @@ func afterAfterFramesetIM(p *parser) bool {
 	return true
 }
 
+const whitespaceOrNUL = whitespace + "\x00"
+
 // Section 12.2.5.5.
 func parseForeignContent(p *parser) bool {
 	switch p.tok.Type {
 	case TextToken:
-\t\tp.tok.Data = strings.Replace(p.tok.Data, "\x00", "", -1)
 		if p.framesetOK {
-\t\t\tp.framesetOK = strings.TrimLeft(p.tok.Data, whitespace) == ""
+\t\t\tp.framesetOK = strings.TrimLeft(p.tok.Data, whitespaceOrNUL) == ""
 		}
+\t\tp.tok.Data = strings.Replace(p.tok.Data, "\x00", "\ufffd", -1)
 		p.addText(p.tok.Data)
 	case CommentToken:
 		p.addChild(&Node{

また、テストログファイルsrc/pkg/exp/html/testlogs/plain-text-unsafe.dat.logも更新されています。

--- a/src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log
+++ b/src/pkg/exp/html/testlogs/plain-text-unsafe.dat.log
@@ -12,13 +12,13 @@ PASS "<svg><![CDATA[\x00filler\x00text\x00]]>"
 PASS "<body><!\x00>"
 PASS "<body><!\x00filler\x00text>"
 PASS "<body><svg><foreignObject>\x00filler\x00text"
-FAIL "<svg>\x00filler\x00text"
-FAIL "<svg>\x00<frameset>"
-FAIL "<svg>\x00 <frameset>"
-FAIL "<svg>\x00a<frameset>"
+PASS "<svg>\x00filler\x00text"
+PASS "<svg>\x00<frameset>"
+PASS "<svg>\x00 <frameset>"
+PASS "<svg>\x00a<frameset>"
 PASS "<svg>\x00</svg><frameset>"
 PASS "<svg>\x00 </svg><frameset>"
-FAIL "<svg>\x00a</svg><frameset>"
+PASS "<svg>\x00a</svg><frameset>"
 PASS "<svg><path></path></svg><frameset>"
 PASS "<svg><p><frameset>"
 PASS "<!DOCTYPE html><pre>\r\n\r\nA</pre>"

コアとなるコードの解説

parseForeignContent関数は、HTMLパーサーが外部コンテンツ(例えばSVGやMathML)の内部をパースする際に呼び出される主要な関数です。この関数は、現在のトークンのタイプに基づいて異なる処理を行います。

変更された部分は、TextToken(テキストノード)が検出された場合の処理ブロックです。

  1. whitespaceOrNUL定数: const whitespaceOrNUL = whitespace + "\x00" この行は、空白文字とNULL文字の両方を含む新しい文字列定数を定義しています。これは、後続のstrings.TrimLeft関数で使用され、テキストデータの先頭からこれらの文字を効率的に除去するために使われます。

  2. framesetOKの更新: if p.framesetOK { p.framesetOK = strings.TrimLeft(p.tok.Data, whitespaceOrNUL) == "" } このコードは、framesetOKフラグがまだtrueの場合に、現在のテキストトークンデータが空白文字とNULL文字のみで構成されているかどうかをチェックします。もしそうであれば、framesetOKtrueのまま維持されます。これは、HTML5の仕様で、frameset要素が許可される条件の一つとして、先行するテキストノードが実質的に空である(空白文字やNULL文字のみで構成される)場合があるためです。以前はNULL文字を考慮していなかったため、このチェックが不正確になる可能性がありました。

  3. NULL文字の置換: p.tok.Data = strings.Replace(p.tok.Data, "\x00", "\ufffd", -1) この行がこのコミットの最も重要な変更です。外部コンテンツ内のテキストデータ(p.tok.Data)に含まれるすべてのNULL文字(\x00)を、Unicodeの置換文字(\ufffd)に置き換えています。これにより、HTML5の仕様に厳密に準拠し、NULL文字が不正な文字として適切に処理されるようになります。

これらの変更により、Go言語のHTMLパーサーは、外部コンテンツ内のNULL文字の処理に関して、より正確で標準に準拠した動作をするようになります。テストログの変更は、この修正が実際に以前失敗していたテストケースをパスさせる効果があったことを示しています。

関連リンク

参考にした情報源リンク