[インデックス 12181] ファイルの概要
このコミットは、Go言語の標準ライブラリ html/template
パッケージにおける DOCTYPE
宣言の解析ロジックを修正するものです。具体的には、DOCTYPE
宣言がケースインセンシティブ(大文字・小文字を区別しない)にチェックされるように変更されました。これにより、HTMLの仕様に準拠し、様々な大文字・小文字の組み合わせで記述された DOCTYPE
宣言が正しく認識されるようになります。
コミット
commit c05c3a9d1180e6d449049d9ed96c46b65837bb29
Author: Scott Lawrence <bytbox@gmail.com>
Date: Fri Feb 24 11:32:33 2012 +1100
html/template: make doctype check case-insensitive
Fixes #3094.
R=golang-dev, rsc, nigeltao
CC=golang-dev
https://golang.org/cl/5687065
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c05c3a9d1180e6d449049d9ed96c46b65837bb29
元コミット内容
html/template: make doctype check case-insensitive
Fixes #3094.
変更の背景
この変更は、Go言語の html/template
パッケージが DOCTYPE
宣言を処理する際に、大文字・小文字を厳密に区別してしまうという問題(Issue #3094)を解決するために行われました。HTMLの仕様では、DOCTYPE
宣言は大文字・小文字を区別しない(ケースインセンシティブ)と定められています。しかし、当時の html/template
の実装では、例えば <!DOCTYPE html>
は認識できても <!doctype html>
や <!DoCtYpE hTmL>
のような記述は正しく認識されず、エスケープ処理が意図しない形で適用されてしまう可能性がありました。
この問題は、テンプレートエンジンが生成するHTMLの正確性に関わるものであり、特にセキュリティ上の観点からも重要です。html/template
パッケージは、クロスサイトスクリプティング(XSS)などの脆弱性からアプリケーションを保護するために、自動エスケープ機能を提供しています。DOCTYPE
宣言が正しく認識されないと、このエスケープ処理が適切に機能せず、セキュリティ上のリスクが生じる可能性がありました。
このコミットは、この不整合を解消し、html/template
がHTMLの標準に完全に準拠するようにすることで、より堅牢で安全なウェブアプリケーション開発を支援することを目的としています。
前提知識の解説
html/template
パッケージ
html/template
はGo言語の標準ライブラリの一つで、HTMLテンプレートを安全に生成するためのパッケージです。このパッケージの主要な機能は、出力されるHTMLコンテンツに対して自動的にエスケープ処理を施すことで、クロスサイトスクリプティング(XSS)攻撃などのウェブ脆弱性を防ぐことにあります。開発者は、ユーザー入力やデータベースからのデータを直接HTMLに埋め込む際に、手動でエスケープ処理を行う必要がなく、安全なウェブページを構築できます。
DOCTYPE
宣言
DOCTYPE
宣言は、HTMLドキュメントの冒頭に記述される特別な命令で、そのドキュメントがどのHTMLまたはXHTMLのバージョンに準拠しているかをウェブブラウザに伝えます。例えば、<!DOCTYPE html>
は、そのドキュメントがHTML5の仕様に準拠していることを示します。ブラウザは DOCTYPE
宣言を読み取ることで、ドキュメントを「標準モード」または「互換モード(Quirks Mode)」のどちらでレンダリングするかを決定します。標準モードでは、ブラウザは最新のWeb標準に厳密に従ってページをレンダリングし、互換モードでは古いブラウザの挙動を模倣します。DOCTYPE
宣言はHTMLの構文規則上、大文字・小文字を区別しない(ケースインセンシティブ)とされています。
ケースインセンシティブ(Case-Insensitive)
ケースインセンシティブとは、文字列の比較やマッチングにおいて、大文字と小文字を区別しないことを指します。例えば、「Hello」と「hello」はケースインセンシティブな比較では同じものと見なされます。HTMLの多くの要素名や属性名、そして DOCTYPE
宣言はケースインセンシティブに扱われるべきです。
bytes.HasPrefix
Go言語の bytes
パッケージに含まれる関数で、func HasPrefix(s, prefix []byte) bool
というシグネチャを持ちます。これは、バイトスライス s
がバイトスライス prefix
で始まるかどうかをチェックします。この関数は、厳密なバイト列の一致をチェックするため、デフォルトではケースセンシティブです。
bytes.ToUpper
Go言語の bytes
パッケージに含まれる関数で、func ToUpper(s []byte) []byte
というシグネチャを持ちます。これは、与えられたバイトスライス s
内のすべてのASCII文字を大文字に変換した新しいバイトスライスを返します。この関数を使用することで、ケースセンシティブな比較を行う前に、文字列を統一されたケース(例えばすべて大文字)に変換し、実質的にケースインセンシティブな比較を実現できます。
技術的詳細
このコミットの技術的な核心は、html/template
パッケージ内の escape.go
ファイルにおける DOCTYPE
宣言のチェック方法の変更にあります。
変更前は、s[j:]
というバイトスライス(現在の走査位置から文字列の最後まで)が doctypeBytes
(<!DOCTYPE html>
のバイト表現)と直接 bytes.HasPrefix
で比較されていました。この bytes.HasPrefix
はケースセンシティブな比較を行うため、<!DOCTYPE html>
と完全に一致する形式でなければ DOCTYPE
宣言として認識されませんでした。
変更後は、bytes.ToUpper(s[j:])
が導入されました。これにより、s[j:]
の内容がまずすべて大文字に変換されてから、doctypeBytes
と bytes.HasPrefix
で比較されるようになりました。doctypeBytes
自体も <!DOCTYPE html>
の大文字形式で定義されているため、入力された DOCTYPE
宣言が <!doctype html>
であろうと <!DoCtYpE hTmL>
であろうと、bytes.ToUpper
によってすべて大文字に変換され、正しく <!DOCTYPE HTML>
と比較されるようになります。
この修正により、html/template
はHTMLの仕様に準拠し、DOCTYPE
宣言のケースインセンシティブな性質を正しく扱えるようになりました。これにより、テンプレートエンジンが生成するHTMLの堅牢性が向上し、意図しないエスケープ処理の適用を防ぐことができます。
また、escape_test.go
に新しいテストケースが追加され、<!doCtYPE htMl>
のような混合ケースの DOCTYPE
宣言が正しく処理されることを検証しています。これは、変更が期待通りに機能し、回帰バグがないことを保証するために非常に重要です。
コアとなるコードの変更箇所
--- a/src/pkg/html/template/escape.go
+++ b/src/pkg/html/template/escape.go
@@ -593,7 +593,7 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context {
for j := i; j < end; j++ {
- if s[j] == '<' && !bytes.HasPrefix(s[j:], doctypeBytes) {
+ if s[j] == '<' && !bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes) {
b.Write(s[written:j])
b.WriteString("<")
written = j + 1
--- a/src/pkg/html/template/escape_test.go
+++ b/src/pkg/html/template/escape_test.go
@@ -431,6 +431,11 @@ func TestEscape(t *testing.T) {
"<!DOCTYPE html>Hello, World!",
"<!DOCTYPE html>Hello, World!",
},
+ {
+ "HTML doctype not case-insensitive",
+ "<!doCtYPE htMl>Hello, World!",
+ "<!doCtYPE htMl>Hello, World!",
+ },
{
"No doctype injection",
`<!{{"DOCTYPE"}}`,
コアとなるコードの解説
変更の中心は src/pkg/html/template/escape.go
の以下の行です。
- if s[j] == '<' && !bytes.HasPrefix(s[j:], doctypeBytes) {
+ if s[j] == '<' && !bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes) {
この if
文は、HTMLコンテンツを走査中に <
文字を見つけた際に、それが DOCTYPE
宣言の開始であるかどうかをチェックしています。
s[j] == '<'
:現在の文字が<
であることを確認します。!bytes.HasPrefix(...)
:続く部分がDOCTYPE
宣言ではないことを確認します。もしDOCTYPE
宣言であれば、その部分はエスケープする必要がないため、!
で否定しています。
変更前は bytes.HasPrefix(s[j:], doctypeBytes)
となっており、s[j:]
(現在の位置から文字列の終わりまでの部分文字列)が doctypeBytes
(<!DOCTYPE html>
のバイト表現)と完全に一致するかどうかをケースセンシティブに比較していました。
変更後は bytes.HasPrefix(bytes.ToUpper(s[j:]), doctypeBytes)
となっています。ここで bytes.ToUpper(s[j:])
が追加されています。これは、s[j:]
の内容をすべて大文字に変換した新しいバイトスライスを生成し、その結果に対して bytes.HasPrefix
を適用します。これにより、元の文字列が <!doctype html>
であろうと <!DoCtYpE hTmL>
であろうと、bytes.ToUpper
によって <!DOCTYPE HTML>
に変換され、doctypeBytes
との比較がケースインセンシティブに行われるようになります。
この修正により、html/template
はHTMLの仕様に準拠し、DOCTYPE
宣言の様々な大文字・小文字の組み合わせを正しく認識できるようになりました。
また、src/pkg/html/template/escape_test.go
に追加されたテストケースは、この変更が正しく機能することを検証しています。
{
"HTML doctype not case-insensitive",
"<!doCtYPE htMl>Hello, World!",
"<!doCtYPE htMl>Hello, World!",
},
このテストケースは、入力文字列 <!doCtYPE htMl>Hello, World!
が、html/template
によって処理された後も <!doCtYPE htMl>Hello, World!
のままであることを期待しています。つまり、<!doCtYPE htMl>
が正しく DOCTYPE
宣言として認識され、その後の Hello, World!
がエスケープされずにそのまま出力されることを確認しています。これは、DOCTYPE
宣言がケースインセンシティブに扱われるようになったことの直接的な検証となります。
関連リンク
- Go Issue #3094: https://github.com/golang/go/issues/3094
- Go CL 5687065: https://golang.org/cl/5687065
参考にした情報源リンク
- Web search results for "Go issue 3094": https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG5zpJsxrcFU2alTFD8jNWbkY3AJHgTL-NmYlUcWxTDHP0b1l6i-zFDTawjKeg1QJbUh_sZt8QNmwGaO8aEQ5bIFvIuQ4Gb1JqLprPIRFBHFOcVqc9yVVp1eBzCraB2JVY5gA==
- Go
bytes
package documentation (forHasPrefix
andToUpper
): https://pkg.go.dev/bytes (一般的なGoのドキュメントとして参照) - HTML
DOCTYPE
declaration: https://developer.mozilla.org/ja/docs/Glossary/Doctype (一般的なHTMLの知識として参照)