[インデックス 12694] ファイルの概要
このコミットは、Go言語の標準ライブラリである html/template
パッケージに、text/template
パッケージとのインターフェースの整合性を高めるための機能追加と修正を導入します。具体的には、テンプレートのリストを取得する Templates()
メソッドと、HTML/JavaScriptのエスケープ関数を直接利用できるようにするフォワーディング関数が追加されています。
コミット
commit 49be7f7d0d5c8be7db5a038ff10cece702796fa7
Author: Rob Pike <r@golang.org>
Date: Tue Mar 20 14:38:07 2012 +1100
html/template: add Templates and *Escape functions
to bring it in line with text/template's interface.
Fixes #3296.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5843066
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/49be7f7d0d5c8be7db5a038ff10cece702796fa7
元コミット内容
html/template: add Templates and *Escape functions
to bring it in line with text/template's interface.
Fixes #3296.
変更の背景
このコミットの主な背景は、Go言語のテンプレートエンジンにおける html/template
と text/template
の間の機能的な一貫性を確保することにあります。text/template
は汎用的なテキスト生成のためのテンプレートパッケージであり、html/template
はHTML出力に特化し、クロスサイトスクリプティング (XSS) などのセキュリティ脆弱性から保護するための自動エスケープ機能を提供します。
コミットメッセージにある "Fixes #3296" は、GitHubのIssue #3296に関連する修正であることを示しています。このIssueは、html/template
に text/template
と同様の Templates()
メソッドがないこと、および text/template
で提供されているエスケープ関数(HTMLEscape
, JSEscape
など)が html/template
から直接利用できないことに関するものでした。
開発者は、両パッケージ間で一貫したAPIを提供することで、ユーザーがどちらのパッケージを使用しても同様の操作感を得られるようにすることを目指しました。特に、Templates()
メソッドは、定義されているすべてのテンプレートをプログラム的に取得し、操作するために重要です。また、エスケープ関数を html/template
から直接エクスポートすることで、ユーザーが text/template
を別途インポートすることなく、HTMLやJavaScriptの特定の部分を手動でエスケープする必要がある場合に、より簡単にアクセスできるようにします。これは、自動エスケープが適用されない特定のシナリオや、より細かい制御が必要な場合に役立ちます。
前提知識の解説
Go言語の text/template
と html/template
パッケージ
Go言語には、テキストベースの出力を生成するための強力なテンプレートエンジンが標準ライブラリとして提供されています。
text/template
: このパッケージは、任意のテキスト形式の出力を生成するための汎用的なテンプレートエンジンです。設定ファイル、コード生成、プレーンテキストのレポートなど、様々な用途に使用できます。セキュリティ上の考慮事項は、生成されるテキストの内容に依存します。html/template
: このパッケージはtext/template
を基盤としていますが、HTML出力を安全に生成することに特化しています。最も重要な機能は、クロスサイトスクリプティング (XSS) 攻撃を防ぐための自動エスケープ機能です。テンプレート内でユーザー提供のデータがHTML要素の属性、JavaScriptコード、URLなどに挿入される際、html/template
は自動的に適切なエスケープ処理を施し、悪意のあるスクリプトの実行を防ぎます。これにより、開発者はセキュリティを意識することなく、動的なHTMLページを構築できます。
テンプレートの構造と定義
Goのテンプレートは、{{...}}
で囲まれたアクション(例: {{.Name}}
, {{range .Items}}
, {{define "name"}}
)と、プレーンテキストで構成されます。
define
アクション:{{define "name"}}...{{end}}
は、名前付きのテンプレートを定義するために使用されます。これにより、テンプレートをモジュール化し、再利用可能な部品として管理できます。template
アクション:{{template "name"}}
は、別の名前付きテンプレートを現在のテンプレートに埋め込むために使用されます。
エスケープ処理
Webアプリケーションにおいて、ユーザーからの入力や外部データがHTMLページに直接表示される場合、悪意のあるスクリプトが埋め込まれる可能性があります(XSS攻撃)。これを防ぐために、特殊文字をHTMLエンティティに変換する「エスケープ」処理が必要です。
- HTMLエスケープ:
<
を<
に、>
を>
に、&
を&
に、"
を"
に変換するなど。 - JavaScriptエスケープ: JavaScript文字列リテラル内に挿入されるデータを安全にするためのエスケープ。
- URLクエリエスケープ: URLのクエリパラメータとして使用されるデータを安全にするためのエスケープ。
html/template
はこれらのエスケープを自動的に行いますが、text/template
は行いません。しかし、text/template
パッケージには、これらのエスケープを手動で行うためのヘルパー関数(HTMLEscape
, JSEscape
, URLQueryEscaper
など)が提供されています。
技術的詳細
このコミットは、主に以下の2つの機能追加に焦点を当てています。
-
Template.Templates()
メソッドの追加:text/template
パッケージには既にTemplates()
メソッドが存在し、これは現在のテンプレートセットに属するすべての名前付きテンプレートのスライスを返します。html/template
にも同様の機能を提供することで、ユーザーはhtml/template
を使用している場合でも、プログラム的に定義済みのテンプレートを列挙し、アクセスできるようになります。これは、デバッグ、テンプレートの動的な選択、またはテンプレート構造の分析に役立ちます。- 実装としては、
Template
構造体が内部的に保持しているnameSpace
のset
マップ(名前とテンプレートのポインタをマッピング)から、すべてのテンプレートをスライスにコピーして返します。この際、内部マップが直接外部に公開されないように、新しいスライスを作成して返しています。これは、マップの同時変更による競合状態を防ぎ、カプセル化を維持するためのGoの慣用的なパターンです。
-
*Escape
フォワーディング関数の追加:text/template
パッケージには、HTMLEscape
,JSEscape
,URLQueryEscaper
などのエスケープヘルパー関数が直接提供されています。- このコミットでは、
html/template
パッケージのトップレベルに、これらのtext/template
のエスケープ関数を呼び出すだけのフォワーディング関数を追加しています。 - これにより、
html/template
をインポートするだけで、text/template
を別途インポートすることなく、これらのエスケープ関数を利用できるようになります。これは、html/template
の自動エスケープ機能が適用されない特定のコンテキスト(例: JavaScriptコード内で動的に生成される文字列の一部を手動でエスケープする場合)で、開発者が明示的にエスケープ処理を行いたい場合に便利です。 - 追加された関数は以下の通りです:
HTMLEscape(w io.Writer, b []byte)
HTMLEscapeString(s string) string
HTMLEscaper(args ...interface{}) string
JSEscape(w io.Writer, b []byte)
JSEscapeString(s string) string
JSEscaper(args ...interface{}) string
URLQueryEscaper(args ...interface{}) string
これらの変更は、html/template
が text/template
のスーパーセットとして、より完全な機能セットを提供し、両パッケージ間のAPIの一貫性を高めることを目的としています。
コアとなるコードの変更箇所
このコミットでは、以下の3つのファイルが変更されています。
-
src/pkg/html/template/clone_test.go
:TestTemplates
という新しいテスト関数が追加されています。これは、Template.Templates()
メソッドが正しく動作し、すべての定義済みテンプレートを返すことを検証します。
-
src/pkg/html/template/escape.go
:text/template
パッケージのエスケープ関数を呼び出すためのフォワーディング関数が多数追加されています。これらはHTMLEscape
,HTMLEscapeString
,HTMLEscaper
,JSEscape
,JSEscapeString
,JSEscaper
,URLQueryEscaper
です。
-
src/pkg/html/template/template.go
:Template
型にTemplates() []*Template
メソッドが追加されています。このメソッドは、テンプレートに関連付けられたすべてのテンプレート(自身を含む)のスライスを返します。
コアとなるコードの解説
src/pkg/html/template/template.go
の変更
// Templates returns a slice of the templates associated with t, including t
// itself.
func (t *Template) Templates() []*Template {
ns := t.nameSpace
ns.mu.Lock()
defer ns.mu.Unlock()
// Return a slice so we don't expose the map.
m := make([]*Template, 0, len(ns.set))
for _, v := range ns.set {
m = append(m, v)
}
return m
}
func (t *Template) Templates() []*Template
:Template
型のメソッドとしてTemplates
が追加されています。これは、*Template
型のスライスを返します。ns := t.nameSpace
: 現在のテンプレートt
が属する名前空間 (nameSpace
構造体) を取得します。nameSpace
は、このテンプレートセット内で定義されているすべてのテンプレートを管理しています。ns.mu.Lock()
とdefer ns.mu.Unlock()
:nameSpace
構造体にはsync.Mutex
型のmu
フィールドが含まれており、これはテンプレートセットへのアクセスを同期するために使用されます。Lock()
でロックを取得し、defer Unlock()
で関数が終了する際に必ずロックを解放するようにしています。これにより、複数のゴルーチンが同時にテンプレートセットにアクセスしようとした場合の競合状態を防ぎます。m := make([]*Template, 0, len(ns.set))
:nameSpace
のset
マップ(テンプレート名と*Template
のマッピング)のサイズと同じ容量を持つ*Template
型のスライスm
を作成します。for _, v := range ns.set { m = append(m, v) }
:set
マップ内のすべての*Template
ポインタを、新しく作成したスライスm
に追加します。return m
: テンプレートのスライスを返します。マップを直接返すのではなく、スライスにコピーして返すことで、内部のマップ構造が外部に漏洩するのを防ぎ、安全性を高めています。
src/pkg/html/template/escape.go
の変更
// Forwarding functions so that clients need only import this package
// to reach the general escaping functions of text/template.
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
func HTMLEscape(w io.Writer, b []byte) {
template.HTMLEscape(w, b)
}
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
func HTMLEscapeString(s string) string {
return template.HTMLEscapeString(s)
}
// HTMLEscaper returns the escaped HTML equivalent of the textual
// representation of its arguments.
func HTMLEscaper(args ...interface{}) string {
return template.HTMLEscaper(args...)
}
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
func JSEscape(w io.Writer, b []byte) {
template.JSEscape(w, b)
}
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
func JSEscapeString(s string) string {
return template.JSEscapeString(s)
}
// JSEscaper returns the escaped JavaScript equivalent of the textual
// representation of its arguments.
func JSEscaper(args ...interface{}) string {
return template.JSEscaper(args...)
}
// URLQueryEscaper returns the escaped value of the textual representation of
// its arguments in a form suitable for embedding in a URL query.
func URLQueryEscaper(args ...interface{}) string {
return template.URLQueryEscaper(args...)
}
これらの関数はすべて、対応する text/template
パッケージの関数を単に呼び出すだけのフォワーディング(転送)関数です。例えば、html/template.HTMLEscape
は text/template.HTMLEscape
を呼び出します。これにより、html/template
をインポートするだけで、これらのエスケープ関数にアクセスできるようになり、APIの利便性が向上します。
関連リンク
- Go言語の
text/template
パッケージ公式ドキュメント: https://pkg.go.dev/text/template - Go言語の
html/template
パッケージ公式ドキュメント: https://pkg.go.dev/html/template - Go Issue #3296:
html/template
needsTemplates()
and*Escape
functions: https://github.com/golang/go/issues/3296
参考にした情報源リンク
- Go言語の公式ドキュメント (
text/template
,html/template
パッケージ) - GitHubのGoリポジトリのIssue #3296
- Go言語のテンプレートに関する一般的な情報源
- クロスサイトスクリプティング (XSS) に関するセキュリティ情報
- Go言語の
sync.Mutex
に関するドキュメント