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

[インデックス 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/templatetext/template の間の機能的な一貫性を確保することにあります。text/template は汎用的なテキスト生成のためのテンプレートパッケージであり、html/template はHTML出力に特化し、クロスサイトスクリプティング (XSS) などのセキュリティ脆弱性から保護するための自動エスケープ機能を提供します。

コミットメッセージにある "Fixes #3296" は、GitHubのIssue #3296に関連する修正であることを示しています。このIssueは、html/templatetext/template と同様の Templates() メソッドがないこと、および text/template で提供されているエスケープ関数(HTMLEscape, JSEscape など)が html/template から直接利用できないことに関するものでした。

開発者は、両パッケージ間で一貫したAPIを提供することで、ユーザーがどちらのパッケージを使用しても同様の操作感を得られるようにすることを目指しました。特に、Templates() メソッドは、定義されているすべてのテンプレートをプログラム的に取得し、操作するために重要です。また、エスケープ関数を html/template から直接エクスポートすることで、ユーザーが text/template を別途インポートすることなく、HTMLやJavaScriptの特定の部分を手動でエスケープする必要がある場合に、より簡単にアクセスできるようにします。これは、自動エスケープが適用されない特定のシナリオや、より細かい制御が必要な場合に役立ちます。

前提知識の解説

Go言語の text/templatehtml/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エスケープ: <&lt; に、>&gt; に、&&amp; に、"&quot; に変換するなど。
  • JavaScriptエスケープ: JavaScript文字列リテラル内に挿入されるデータを安全にするためのエスケープ。
  • URLクエリエスケープ: URLのクエリパラメータとして使用されるデータを安全にするためのエスケープ。

html/template はこれらのエスケープを自動的に行いますが、text/template は行いません。しかし、text/template パッケージには、これらのエスケープを手動で行うためのヘルパー関数(HTMLEscape, JSEscape, URLQueryEscaper など)が提供されています。

技術的詳細

このコミットは、主に以下の2つの機能追加に焦点を当てています。

  1. Template.Templates() メソッドの追加:

    • text/template パッケージには既に Templates() メソッドが存在し、これは現在のテンプレートセットに属するすべての名前付きテンプレートのスライスを返します。
    • html/template にも同様の機能を提供することで、ユーザーは html/template を使用している場合でも、プログラム的に定義済みのテンプレートを列挙し、アクセスできるようになります。これは、デバッグ、テンプレートの動的な選択、またはテンプレート構造の分析に役立ちます。
    • 実装としては、Template 構造体が内部的に保持している nameSpaceset マップ(名前とテンプレートのポインタをマッピング)から、すべてのテンプレートをスライスにコピーして返します。この際、内部マップが直接外部に公開されないように、新しいスライスを作成して返しています。これは、マップの同時変更による競合状態を防ぎ、カプセル化を維持するためのGoの慣用的なパターンです。
  2. *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/templatetext/template のスーパーセットとして、より完全な機能セットを提供し、両パッケージ間のAPIの一貫性を高めることを目的としています。

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

このコミットでは、以下の3つのファイルが変更されています。

  1. src/pkg/html/template/clone_test.go:

    • TestTemplates という新しいテスト関数が追加されています。これは、Template.Templates() メソッドが正しく動作し、すべての定義済みテンプレートを返すことを検証します。
  2. src/pkg/html/template/escape.go:

    • text/template パッケージのエスケープ関数を呼び出すためのフォワーディング関数が多数追加されています。これらは HTMLEscape, HTMLEscapeString, HTMLEscaper, JSEscape, JSEscapeString, JSEscaper, URLQueryEscaper です。
  3. 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)): nameSpaceset マップ(テンプレート名と *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.HTMLEscapetext/template.HTMLEscape を呼び出します。これにより、html/template をインポートするだけで、これらのエスケープ関数にアクセスできるようになり、APIの利便性が向上します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (text/template, html/template パッケージ)
  • GitHubのGoリポジトリのIssue #3296
  • Go言語のテンプレートに関する一般的な情報源
  • クロスサイトスクリプティング (XSS) に関するセキュリティ情報
  • Go言語の sync.Mutex に関するドキュメント