template

package
v1.21.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 31, 2023 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

パッケージテンプレート(html/template)は、コードインジェクションに対して安全なHTML出力を生成するための データ駆動型テンプレートを実装します。それは text/template と同じインターフェースを提供し、出力がHTMLの場合は text/template の代わりに使用すべきです。

ここでのドキュメンテーションは、パッケージのセキュリティ機能に焦点を当てています。 テンプレート自体のプログラミングについての情報は、text/template のドキュメンテーションを参照してください。

はじめに

このパッケージは[text/template]をラップして、そのテンプレートAPIを共有して HTMLテンプレートを安全に解析し実行できます。

tmpl, err := template.New("name").Parse(...)
// エラーチェックは省略
err = tmpl.Execute(out, data)

成功した場合、tmplは今後、インジェクションに対して安全になります。それ以外の場合、errはErrorCodeのドキュメントで定義されたエラーです。

HTMLテンプレートは、データ値をHTMLドキュメントに安全に埋め込むためにエンコードするべきプレーンテキストとして扱います。エスケープは文脈に依存するため、JavaScript、CSS、URIの文脈内にアクションが現れることがあります。

このパッケージが使用するセキュリティモデルは、テンプレートの作者が信頼できると仮定し、 一方でExecuteのデータパラメータは信頼できないと仮定します。詳細は以下に提供されています。

import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")

は以下を生成します

Hello, <script>alert('you have been pwned')</script>!

しかし、html/templateの文脈自動エスケープでは

import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")

は安全な、エスケープされたHTML出力を生成します

Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!

コンテキスト

このパッケージはHTML、CSS、JavaScript、およびURIを理解します。それは各単純なアクションパイプラインに サニタイジング関数を追加するので、以下の抜粋が与えられた場合

<a href="/search?q={{.}}">{{.}}</a>

パース時に、必要に応じてエスケープ関数を追加するため、各{{.}}が上書きされます。 この場合、それは次のようになります。

<a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a>

ここで、urlescaper、attrescaper、およびhtmlescaperは、内部エスケープ関数のエイリアスです。

これらの内部エスケープ関数については、アクションパイプラインがnilインターフェース値を評価すると、 それは空の文字列であるかのように扱われます。

名前空間とデータ属性

名前空間を持つ属性は、名前空間がないかのように扱われます。 以下の抜粋が与えられた場合

<a my:href="{{.}}"></a>

パース時に、属性はまるでそれがただの"href"であるかのように扱われます。 したがって、パース時にテンプレートは次のようになります:

<a my:href="{{. | urlescaper | attrescaper}}"></a>

同様に、"data-"プレフィックスを持つ属性は、まるでそれらが"data-"プレフィックスを持っていないかのように扱われます。したがって、以下が与えられた場合

<a data-href="{{.}}"></a>

パース時に、これは次のようになります。

<a data-href="{{. | urlescaper | attrescaper}}"></a>

属性が名前空間と"data-"プレフィックスの両方を持っている場合、コンテキストを決定するときには名前空間のみが削除されます。例えば

<a my:data-href="{{.}}"></a>

これは、"my:data-href"がただの"data-href"であるかのように、そして"href"であるかのように("data-"プレフィックスも無視される場合)扱われます。したがって、パース時には次のようになります。

<a my:data-href="{{. | attrescaper}}"></a>

特別なケースとして、"xmlns"名前空間を持つ属性は常にURLを含んでいるとして扱われます。以下の抜粋が与えられた場合

<a xmlns:title="{{.}}"></a>
<a xmlns:href="{{.}}"></a>
<a xmlns:onclick="{{.}}"></a>

パース時に、それらは次のようになります。

<a xmlns:title="{{. | urlescaper | attrescaper}}"></a>
<a xmlns:href="{{. | urlescaper | attrescaper}}"></a>
<a xmlns:onclick="{{. | urlescaper | attrescaper}}"></a>

エラー

詳細はErrorCodeのドキュメンテーションを参照してください。

より詳細な情報

このパッケージのコメントの残りの部分は、最初の読み込み時にスキップしても構いません。これには、 エスケープの文脈とエラーメッセージを理解するために必要な詳細が含まれています。ほとんどのユーザーは これらの詳細を理解する必要はありません。

Contexts

{{.}}が`O'Reilly: How are <i>you</i>?`と仮定すると、以下の表は 左側の文脈で{{.}}がどのように表示されるかを示しています。

Context                          {{.}} After
{{.}}                            O'Reilly: How are &lt;i&gt;you&lt;/i&gt;?
<a title='{{.}}'>                O&#39;Reilly: How are you?
<a href="/{{.}}">                O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}">              O&#39;Reilly%3a%20How%20are%3ci%3e...%3f
<a onx='f("{{.}}")'>             O\x27Reilly: How are \x3ci\x3eyou...?
<a onx='f({{.}})'>               "O\x27Reilly: How are \x3ci\x3eyou...?"
<a onx='pattern = /{{.}}/;'>     O\x27Reilly: How are \x3ci\x3eyou...\x3f

安全でないコンテキストで使用された場合、その値はフィルタリングされる可能性があります:

Context                          {{.}} After
<a href="{{.}}">                 #ZgotmplZ

なぜなら "O'Reilly:" は "http:" のような許可されたプロトコルではないからです。

もし {{.}} が無害な単語、`left`であるなら、それはより広範に現れることができます。

Context                              {{.}} After
{{.}}                                left
<a title='{{.}}'>                    left
<a href='{{.}}'>                     left
<a href='/{{.}}'>                    left
<a href='?dir={{.}}'>                left
<a style="border-{{.}}: 4px">        left
<a style="align: {{.}}">             left
<a style="background: '{{.}}'>       left
<a style="background: url('{{.}}')>  left
<style>p.{{.}} {color:red}</style>   left

非文字列の値はJavaScriptの文脈で使用できます。 もし {{.}} が

struct{A,B string}{ "foo", "bar" }

エスケープされたテンプレート内で

<script>var pair = {{.}};</script>

その後、テンプレートの出力は次のようになります。

<script>var pair = {"A": "foo", "B": "bar"};</script>

JavaScriptの文脈で埋め込むために非文字列コンテンツがどのようにマーシャルされるかを理解するために、jsonパッケージを参照してください。

型付けされた文字列

デフォルトでは、このパッケージはすべてのパイプラインがプレーンテキストの文字列を生成すると仮定します。 それは、そのプレーンテキスト文字列を適切な文脈で正しく安全に埋め込むために必要なエスケープパイプラインステージを追加します。

データ値がプレーンテキストでない場合、そのタイプでマークすることで、それが過度にエスケープされないようにすることができます。

Types HTML, JS, URL, and others from content.go can carry safe content that is exempted from escaping.

テンプレート

Hello, {{.}}!

は以下のように呼び出すことができます

tmpl.Execute(out, template.HTML(`<b>World</b>`))

これにより

Hello, <b>World</b>!

が生成されます。

これは、{{.}}が通常の文字列であった場合に生成される

Hello, &lt;b&gt;World&lt;b&gt;!

とは異なります。

セキュリティモデル

https://rawgit.com/mikesamuel/sanitized-jquery-templates/trunk/safetemplate.html#problem_definition は、このパッケージが使用する「安全」を定義しています。

このパッケージは、テンプレートの作者が信頼できると仮定し、Executeのデータパラメータは信頼できないと仮定し、信頼できないデータに対して以下のプロパティを保持しようとします:

Structure Preservation Property: "... テンプレートの作者が安全なテンプレート言語でHTMLタグを書くとき、 ブラウザは出力の対応する部分を、信頼できないデータの値に関係なくタグとして解釈します。 同様に、属性の境界やJSとCSSの文字列の境界などの他の構造についても同様です。"

Code Effect Property: "... テンプレートの出力をページに注入する結果として実行されるのは、 テンプレートの作者によって指定されたコードのみであり、 同じ結果として実行されるすべてのコードもテンプレートの作者によって指定されるべきです。"

Least Surprise Property: "HTML、CSS、JavaScriptに精通し、コンテキストに応じた自動エスケープが行われることを知っている開発者(またはコードレビュアー)は、{{.}}を見て、どのようなサニタイゼーションが行われるかを正しく推測することができるべきです。"

最小驚きの原則の結果として、ECMAScript 6のテンプレートリテラル内のテンプレートアクションはデフォルトで無効になっています。 これらのリテラル内での文字列補間の処理はかなり複雑で、それをサポートする明確な安全な方法がありません。 ECMAScript 6のテンプレートリテラル内でテンプレートアクションを再度有効にするには、GODEBUG=jstmpllitinterp=1環境変数を使用します。

Example
package main

import (
	"github.com/shogo82148/std/html/template"
	"github.com/shogo82148/std/log"
	"github.com/shogo82148/std/os"
)

func main() {
	const tpl = `
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>{{.Title}}</title>
	</head>
	<body>
		{{range .Items}}<div>{{ . }}</div>{{else}}<div><strong>no rows</strong></div>{{end}}
	</body>
</html>`

	check := func(err error) {
		if err != nil {
			log.Fatal(err)
		}
	}
	t, err := template.New("webpage").Parse(tpl)
	check(err)

	data := struct {
		Title string
		Items []string
	}{
		Title: "My page",
		Items: []string{
			"My photos",
			"My blog",
		},
	}

	err = t.Execute(os.Stdout, data)
	check(err)

	noItems := struct {
		Title string
		Items []string
	}{
		Title: "My another page",
		Items: []string{},
	}

	err = t.Execute(os.Stdout, noItems)
	check(err)

}
Output:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>My page</title>
	</head>
	<body>
		<div>My photos</div><div>My blog</div>
	</body>
</html>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>My another page</title>
	</head>
	<body>
		<div><strong>no rows</strong></div>
	</body>
</html>
Example (Autoescaping)
package main

import (
	"github.com/shogo82148/std/html/template"
	"github.com/shogo82148/std/log"
	"github.com/shogo82148/std/os"
)

func main() {
	check := func(err error) {
		if err != nil {
			log.Fatal(err)
		}
	}
	t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
	check(err)
	err = t.ExecuteTemplate(os.Stdout, "T", "<script>alert('you have been pwned')</script>")
	check(err)
}
Output:

Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!
Example (Escape)
package main

import (
	"github.com/shogo82148/std/fmt"
	"github.com/shogo82148/std/html/template"
	"github.com/shogo82148/std/os"
)

func main() {
	const s = `"Fran & Freddie's Diner" <tasty@example.com>`
	v := []any{`"Fran & Freddie's Diner"`, ' ', `<tasty@example.com>`}

	fmt.Println(template.HTMLEscapeString(s))
	template.HTMLEscape(os.Stdout, []byte(s))
	fmt.Fprintln(os.Stdout, "")
	fmt.Println(template.HTMLEscaper(v...))

	fmt.Println(template.JSEscapeString(s))
	template.JSEscape(os.Stdout, []byte(s))
	fmt.Fprintln(os.Stdout, "")
	fmt.Println(template.JSEscaper(v...))

	fmt.Println(template.URLQueryEscaper(v...))

}
Output:

&#34;Fran &amp; Freddie&#39;s Diner&#34; &lt;tasty@example.com&gt;
&#34;Fran &amp; Freddie&#39;s Diner&#34; &lt;tasty@example.com&gt;
&#34;Fran &amp; Freddie&#39;s Diner&#34;32&lt;tasty@example.com&gt;
\"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E
\"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E
\"Fran \u0026 Freddie\'s Diner\"32\u003Ctasty@example.com\u003E
%22Fran+%26+Freddie%27s+Diner%2232%3Ctasty%40example.com%3E

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func HTMLEscape

func HTMLEscape(w io.Writer, b []byte)

HTMLEscapeは、プレーンテキストデータbのエスケープされたHTML相当をwに書き込みます。

func HTMLEscapeString

func HTMLEscapeString(s string) string

HTMLEscapeStringは、プレーンテキストデータsのエスケープされたHTML相当を返します。

func HTMLEscaper

func HTMLEscaper(args ...any) string

HTMLEscaperは、その引数のテキスト表現のエスケープされたHTML相当を返します。

func IsTrue added in v1.6.0

func IsTrue(val any) (truth, ok bool)

IsTrueは、値がその型のゼロでない「真」であるか、 そして値が意味のある真偽値を持っているかどうかを報告します。 これはifやその他のアクションで使用される真実の定義です。

func JSEscape

func JSEscape(w io.Writer, b []byte)

JSEscapeは、プレーンテキストデータbのエスケープされたJavaScript相当をwに書き込みます。

func JSEscapeString

func JSEscapeString(s string) string

JSEscapeStringは、プレーンテキストデータsのエスケープされたJavaScript相当を返します。

func JSEscaper

func JSEscaper(args ...any) string

JSEscaperは、その引数のテキスト表現のエスケープされたJavaScript相当を返します。

func URLQueryEscaper

func URLQueryEscaper(args ...any) string

URLQueryEscaperは、その引数のテキスト表現のエスケープされた値を、 URLクエリに埋め込むのに適した形式で返します。

Types

type CSS

type CSS string

CSSは、以下のいずれかに一致する既知の安全なコンテンツをカプセル化します:

  1. CSS3のスタイルシートの生成、例えば `p { color: purple }`。
  2. CSS3のルールの生成、例えば `a[href=~"https:"].foo#bar`。
  3. CSS3の宣言の生成、例えば `color: red; margin: 2px`。
  4. CSS3の値の生成、例えば `rgba(0, 0, 255, 127)`。

https://www.w3.org/TR/css3-syntax/#parsing および https://web.archive.org/web/20090211114933/http://w3.org/TR/css3-syntax#style を参照してください。

このタイプの使用はセキュリティリスクを伴います: カプセル化されたコンテンツは信頼できるソースから来るべきであり、 それはテンプレートの出力にそのまま含まれます。

type Error

type Error struct {
	// ErrorCodeはエラーの種類を説明します。
	ErrorCode ErrorCode
	// Nodeは問題を引き起こしたノードです(もし分かる場合)。
	// nilでない場合、NameとLineを上書きします。
	Node parse.Node
	// Nameはエラーが発生したテンプレートの名前です。
	Name string
	// Lineはテンプレートソース内のエラーの行番号、または0です。
	Line int
	// Descriptionは問題の人間が読める説明です。
	Description string
}

Errorは、テンプレートのエスケープ処理中に遭遇した問題を説明します。

func (*Error) Error

func (e *Error) Error() string

type ErrorCode

type ErrorCode int

ErrorCodeはエラーの種類を表すコードです。

const (
	// OKはエラーがないことを示します。
	OK ErrorCode = iota

	// ErrAmbigContext: "...はURL内の曖昧なコンテキストに現れます"
	// 例:
	//   <a href="
	//      {{if .C}}
	//        /path/
	//      {{else}}
	//        /search?q=
	//      {{end}}
	//      {{.X}}
	//   ">
	// 議論:
	//   {{.X}}は曖昧なURLコンテキストにあります。なぜなら、{{.C}}によって、
	//   URLの接尾辞かクエリパラメータのどちらかになる可能性があるからです。
	//   {{.X}}を条件の中に移動すると曖昧さがなくなります:
	//   <a href="{{if .C}}/path/{{.X}}{{else}}/search?q={{.X}}">
	ErrAmbigContext

	// ErrBadHTML: "スペース、属性名、またはタグの終わりを期待していましたが、...が得られました",
	//   "...は引用符で囲まれていない属性内にあります", "...は属性名内にあります"
	// 例:
	//   <a href = /search?q=foo>
	//   <href=foo>
	//   <form na<e=...>
	//   <option selected<
	// 議論:
	//   これは、HTML要素のタイプミスが原因であることが多いですが、一部のルーンは、
	//   パーサーの曖昧さを引き起こす可能性があるため、タグ名、属性名、引用符で囲まれていない属性値で禁止されています。
	//   すべての属性を引用符で囲むのが最善の方針です。
	ErrBadHTML

	// ErrBranchEnd: "{{if}}の分岐が異なるコンテキストで終わります"
	// 例:
	//   {{if .C}}<a href="{{end}}{{.X}}
	// 議論:
	//   パッケージhtml/templateは、{{if}}、{{range}}、または{{with}}を通じて各パスを静的に調べ、
	//   その後のパイプラインをエスケープします。例は曖昧です。なぜなら、{{.X}}はHTMLテキストノードであるか、
	//   HTML属性のURLプレフィックスである可能性があるからです。{{.X}}のコンテキストは、それをどのようにエスケープするかを
	//   理解するために使用されますが、そのコンテキストは実行時の{{.C}}の値に依存し、それは静的には知られていません。
	//
	//   問題は通常、引用符や角括弧が欠けているなどの問題であり、または、2つのコンテキストをif、range、withの
	//   異なる分岐にリファクタリングすることで回避できます。問題が空であるべきではないコレクションに対する{{range}}にある場合、
	//   ダミーの{{else}}を追加すると役立つことがあります。
	ErrBranchEnd

	// ErrEndContext: "...は非テキストコンテキストで終わります: ..."
	// 例:
	//   <div
	//   <div title="閉じ引用符なし>
	//   <script>f()
	// 議論:
	//   実行されたテンプレートはHTMLのDocumentFragmentを生成するべきです。
	//   閉じタグなしで終わるテンプレートはこのエラーを引き起こします。
	//   HTMLコンテキストで使用すべきでないテンプレート、または不完全なFragmentを生成するテンプレートは、
	//   直接実行すべきではありません。
	//
	//   {{define "main"}} <script>{{template "helper"}}</script> {{end}}
	//   {{define "helper"}} document.write(' <div title=" ') {{end}}
	//
	//   "helper"は有効なドキュメントフラグメントを生成しないため、直接実行すべきではありません。
	ErrEndContext

	// ErrNoSuchTemplate: "そのようなテンプレートは存在しません ..."
	// 例:
	//   {{define "main"}}<div {{template "attrs"}}>{{end}}
	//   {{define "attrs"}}href="{{.URL}}"{{end}}
	// 議論:
	//   パッケージhtml/templateはテンプレート呼び出しを見てコンテキストを計算します。
	//   ここでは、"attrs"の{{.URL}}は"main"から呼び出されたときにURLとして扱われなければなりませんが、
	//   "main"が解析されたときに"attrs"が定義されていない場合、このエラーが発生します。
	ErrNoSuchTemplate

	// ErrOutputContext: "テンプレート...の出力コンテキストを計算できません"
	// 例:
	//   {{define "t"}}{{if .T}}{{template "t" .T}}{{end}}{{.H}}",{{end}}
	// 議論:
	//   再帰的なテンプレートは、開始したときと同じコンテキストで終わらないため、
	//   信頼性のある出力コンテキストを計算することはできません。
	//   名前付きテンプレートのタイプミスを探してみてください。
	//   もしテンプレートが名前付きの開始コンテキストで呼び出されるべきでないなら、
	//   予期しないコンテキストでそのテンプレートへの呼び出しを探してみてください。
	//   再帰的なテンプレートを再帰的でないようにリファクタリングすることも考えてみてください。
	ErrOutputContext

	// ErrPartialCharset: "未完成のJS正規表現文字セットが...に存在します"
	// 例:
	//     <script>var pattern = /foo[{{.Chars}}]/</script>
	// 議論:
	//   パッケージhtml/templateは、正規表現リテラルの文字セットへの補間をサポートしていません。
	ErrPartialCharset

	// ErrPartialEscape: "未完成のエスケープシーケンスが...に存在します"
	// 例:
	//   <script>alert("\{{.X}}")</script>
	// 議論:
	//   パッケージhtml/templateは、バックスラッシュの後に続くアクションをサポートしていません。
	//   これは通常、エラーであり、より良い解決策があります。例えば、
	//     <script>alert("{{.X}}")</script>
	//   は動作するはずで、もし{{.X}}が"xA0"のような部分的なエスケープシーケンスであれば、
	//   全体を安全なコンテンツとしてマークします:JSStr(`\xA0`)
	ErrPartialEscape

	// ErrRangeLoopReentry: "範囲ループの再入時に: ..."
	// 例:
	//   <script>var x = [{{range .}}'{{.}},{{end}}]</script>
	// 議論:
	//   範囲を通じた反復が、以前のパスと異なるコンテキストで終わるような場合、単一のコンテキストは存在しません。
	//   例では、引用符が欠けているため、{{.}}がJS文字列の内部にあるのか、JS値のコンテキストにあるのかが明確ではありません。
	//   2回目の反復では、次のようなものが生成されます。
	//
	//     <script>var x = ['firstValue,'secondValue]</script>
	ErrRangeLoopReentry

	// ErrSlashAmbig: "'/'は除算または正規表現を開始する可能性があります"
	// 例:
	//   <script>
	//     {{if .C}}var x = 1{{end}}
	//     /-{{.N}}/i.test(x) ? doThis : doThat();
	//   </script>
	// 議論:
	//   上記の例では、最初の'/'が数学的な除算演算子である`var x = 1/-2/i.test(s)...`を生成するか、
	//   最初の'/'が正規表現リテラルを開始する`/-2/i.test(s)`を生成する可能性があります。
	//   分岐内のセミコロンが欠けていないか確認し、どちらの解釈を意図しているか明確にするために
	//   括弧を追加することを検討してみてください。
	ErrSlashAmbig

	// ErrPredefinedEscaper: "テンプレートで禁止されている事前定義されたエスケーパー..."
	// 例:
	//   <div class={{. | html}}>Hello<div>
	// 議論:
	//   パッケージhtml/templateは、すべてのパイプラインをコンテキストに応じてエスケープして、
	//   コードインジェクションに対して安全なHTML出力を生成します。事前定義されたエスケーパー"html"または"urlquery"を
	//   使用してパイプライン出力を手動でエスケープすることは不要であり、Go 1.8以前ではエスケープされたパイプライン出力の
	//   正確さや安全性に影響を与える可能性があります。
	//
	//   ほとんどの場合、例えば上記の例のような場合、このエラーはパイプラインから事前定義されたエスケーパーを単純に削除し、
	//   コンテキスト自動エスケーパーがパイプラインのエスケープを処理することで解決できます。他の場合、事前定義されたエスケーパーが
	//   パイプラインの中間に存在し、後続のコマンドがエスケープされた入力を期待する場合、例えば
	//     {{.X | html | makeALink}}
	//   ここでmakeALinkは
	//     return `<a href="`+input+`">link</a>`
	//   を行う場合、周囲のテンプレートをリファクタリングしてコンテキスト自動エスケーパーを利用するように考えてみてください。つまり、
	//     <a href="{{.X}}">link</a>
	//
	//   Go 1.9以降への移行を容易にするために、"html"と"urlquery"はパイプラインの最後のコマンドとして引き続き許可されます。
	//   ただし、パイプラインが引用符で囲まれていない属性値のコンテキストで発生する場合、"html"は禁止されます。
	//   新しいテンプレートでは"html"と"urlquery"を全く使用しないようにしてください。
	ErrPredefinedEscaper

	// ErrJSTemplate: "...はJSテンプレートリテラル内に存在します"
	// 例:
	//     <script>var tmpl = `{{.Interp}}`</script>
	// 議論:
	//   パッケージhtml/templateは、JSテンプレートリテラル内のアクションをサポートしていません。
	ErrJSTemplate
)

テンプレートをエスケープする際に現れる各エラーに対してコードを定義していますが、 エスケープされたテンプレートは実行時にも失敗する可能性があります。

出力: "ZgotmplZ" 例:

<img src="{{.X}}">
ここで {{.X}} は `javascript:...` に評価されます

議論:

"ZgotmplZ" は、実行時に安全でないコンテンツがCSSまたはURLのコンテキストに到達したことを示す特別な値です。
例の出力は
  <img src="#ZgotmplZ">
になります。
データが信頼できるソースから来る場合は、フィルタリングから免除するためにコンテンツタイプを使用します:URL(`javascript:...`)。

type FuncMap

type FuncMap = template.FuncMap

type HTML

type HTML string

HTMLは、既知の安全なHTMLドキュメントフラグメントをカプセル化します。 それは、第三者からのHTMLや、閉じられていないタグやコメントが含まれるHTMLには使用すべきではありません。 信頼できるHTMLサニタイザの出力と、このパッケージによってエスケープされたテンプレートは、HTMLでの使用に適しています。

このタイプの使用はセキュリティリスクを伴います: カプセル化されたコンテンツは信頼できるソースから来るべきであり、 それはテンプレートの出力にそのまま含まれます。

type HTMLAttr

type HTMLAttr string

HTMLAttrは、信頼できるソースからのHTML属性をカプセル化します。 例えば、` dir="ltr"`。

このタイプの使用はセキュリティリスクを伴います: カプセル化されたコンテンツは信頼できるソースから来るべきであり、 それはテンプレートの出力にそのまま含まれます。

type JS

type JS string

JSは、例えば `(x + y * z())` のような、既知の安全なEcmaScript5の式をカプセル化します。 テンプレートの作者は、型付けされた式が意図した優先順位を壊さないこと、そして "{ foo: bar() }\n['foo']()" のような式を渡すときのように、 ステートメント/式の曖昧性がないことを確認する責任があります。 これは、非常に異なる意味を持つ有効な式と有効なプログラムの両方です。

このタイプの使用はセキュリティリスクを伴います: カプセル化されたコンテンツは信頼できるソースから来るべきであり、 それはテンプレートの出力にそのまま含まれます。

有効だが信頼できないJSONを含めるためにJSを使用することは安全ではありません。 安全な代替手段は、json.UnmarshalでJSONを解析し、 結果のオブジェクトをテンプレートに渡すことです。これは、JavaScriptのコンテキストで提示されるときに、 サニタイズされたJSONに変換されます。

type JSStr

type JSStr string

JSStrは、JavaScriptの式のクォートの間に埋め込むことを意図した一連の文字をカプセル化します。 文字列は一連のStringCharactersに一致しなければなりません:

StringCharacter :: SourceCharacter ただし `\` または LineTerminator は除く
                 | EscapeSequence

LineContinuationsは許可されていません。 JSStr("foo\\nbar")は問題ありませんが、JSStr("foo\\\nbar")は許可されていません。

このタイプの使用はセキュリティリスクを伴います: カプセル化されたコンテンツは信頼できるソースから来るべきであり、 それはテンプレートの出力にそのまま含まれます。

type Srcset added in v1.10.0

type Srcset string

Srcsetは、既知の安全なsrcset属性をカプセル化します (https://w3c.github.io/html/semantics-embedded-content.html#element-attrdef-img-srcset を参照)。

このタイプの使用はセキュリティリスクを伴います: カプセル化されたコンテンツは信頼できるソースから来るべきであり、 それはテンプレートの出力にそのまま含まれます。

type Template

type Template struct {

	// The underlying template's parse tree, updated to be HTML-safe.
	Tree *parse.Tree
	// contains filtered or unexported fields
}

Templateは、安全なHTMLドキュメントフラグメントを生成する"text/template"からの特化したTemplateです。

Example (Block)
package main

import (
	"github.com/shogo82148/std/html/template"
	"github.com/shogo82148/std/log"
	"github.com/shogo82148/std/os"
	"github.com/shogo82148/std/strings"
)

func main() {
	const (
		master  = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
		overlay = `{{define "list"}} {{join . ", "}}{{end}} `
	)
	var (
		funcs     = template.FuncMap{"join": strings.Join}
		guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"}
	)
	masterTmpl, err := template.New("master").Funcs(funcs).Parse(master)
	if err != nil {
		log.Fatal(err)
	}
	overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay)
	if err != nil {
		log.Fatal(err)
	}
	if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
		log.Fatal(err)
	}
	if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
		log.Fatal(err)
	}
}
Output:

Names:
- Gamora
- Groot
- Nebula
- Rocket
- Star-Lord
Names: Gamora, Groot, Nebula, Rocket, Star-Lord
Example (Glob)

ここでは、ディレクトリから一連のテンプレートをロードする方法を示しています。

// ここでは、一時ディレクトリを作成し、それをサンプルの
// テンプレート定義ファイルで満たします。通常、テンプレートファイルはすでに
// プログラムが知っている何らかの場所に存在します。
dir := createTestDir([]templateFile{
	// T0.tmplは、単にT1を呼び出すだけのプレーンなテンプレートファイルです。
	{"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
	// T1.tmplは、T2を呼び出すテンプレート、T1を定義します。
	{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
	// T2.tmplは、テンプレートT2を定義します。
	{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
// テストの後片付けをします。これも例として実行する際の特性です。
defer os.RemoveAll(dir)

// patternは、すべてのテンプレートファイルを見つけるために使用されるグロブパターンです。
pattern := filepath.Join(dir, "*.tmpl")

// ここからが実際の例です。
// T0.tmplは最初にマッチした名前なので、それが開始テンプレートとなり、
// ParseGlobによって返される値となります。
tmpl := template.Must(template.ParseGlob(pattern))

err := tmpl.Execute(os.Stdout, nil)
if err != nil {
	log.Fatalf("template execution: %s", err)
}
Output:

T0 invokes T1: (T1 invokes T2: (This is T2))
Example (Helpers)

この例では、いくつかのテンプレートを共有し、それらを異なるコンテキストで使用する方法を示しています。 このバリアントでは、既存のテンプレートバンドルに手動で複数のドライバーテンプレートを追加します。

// ここでは、一時ディレクトリを作成し、それをサンプルの
// テンプレート定義ファイルで満たします。通常、テンプレートファイルはすでに
// プログラムが知っている何らかの場所に存在します。
dir := createTestDir([]templateFile{
	// T1.tmplは、T2を呼び出すテンプレート、T1を定義します。
	{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
	// T2.tmplは、テンプレートT2を定義します。
	{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
// テストの後片付けをします。これも例として実行する際の特性です。
defer os.RemoveAll(dir)

// patternは、すべてのテンプレートファイルを見つけるために使用されるグロブパターンです。
pattern := filepath.Join(dir, "*.tmpl")

// ここからが実際の例です。
// ヘルパーをロードします。
templates := template.Must(template.ParseGlob(pattern))
// 明示的なテンプレート定義を使用して、一連のテンプレートに1つのドライバーテンプレートを追加します。
_, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
if err != nil {
	log.Fatal("parsing driver1: ", err)
}
// 別のドライバーテンプレートを追加します。
_, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
if err != nil {
	log.Fatal("parsing driver2: ", err)
}
// 実行前にすべてのテンプレートをロードします。このパッケージはそのような振る舞いを必要としませんが、
// html/templateのエスケープはそれを必要とするので、それは良い習慣です。
err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
if err != nil {
	log.Fatalf("driver1 execution: %s", err)
}
err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
if err != nil {
	log.Fatalf("driver2 execution: %s", err)
}
Output:

Driver 1 calls T1: (T1 invokes T2: (This is T2))
Driver 2 calls T2: (This is T2)
Example (Parsefiles)

ここでは、異なるディレクトリ内のファイルから一連のテンプレートをロードする方法を示しています。

// ここでは、異なる一時ディレクトリを作成し、それらをサンプルの
// テンプレート定義ファイルで満たします。通常、テンプレートファイルはすでに
// プログラムが知っている何らかの場所に存在します。
dir1 := createTestDir([]templateFile{
	// T1.tmplは、単にT2を呼び出すだけのプレーンなテンプレートファイルです。
	{"T1.tmpl", `T1 invokes T2: ({{template "T2"}})`},
})

dir2 := createTestDir([]templateFile{
	// T2.tmplは、テンプレートT2を定義します。
	{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})

// テストの後片付けをします。これも例として実行する際の特性です。
defer func(dirs ...string) {
	for _, dir := range dirs {
		os.RemoveAll(dir)
	}
}(dir1, dir2)

// ここからが実際の例です。
// dir1/T0とdir2/T2だけをパースしましょう
paths := []string{
	filepath.Join(dir1, "T1.tmpl"),
	filepath.Join(dir2, "T2.tmpl"),
}
tmpl := template.Must(template.ParseFiles(paths...))

err := tmpl.Execute(os.Stdout, nil)
if err != nil {
	log.Fatalf("template execution: %s", err)
}
Output:

T1 invokes T2: (This is T2)
Example (Share)

この例では、一連のヘルパーテンプレートと異なる一連のヘルパーテンプレートを使用して、 一つのグループのドライバーテンプレートを使用する方法を示しています。

// ここでは、一時ディレクトリを作成し、それをサンプルの
// テンプレート定義ファイルで満たします。通常、テンプレートファイルはすでに
// プログラムが知っている何らかの場所に存在します。
dir := createTestDir([]templateFile{
	// T0.tmplは、単にT1を呼び出すだけのプレーンなテンプレートファイルです。
	{"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
	// T1.tmplは、T2を呼び出すテンプレート、T1を定義します。T2は定義されていないことに注意してください。
	{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
})
// テストの後片付けをします。これも例として実行する際の特性です。
defer os.RemoveAll(dir)

// patternは、すべてのテンプレートファイルを見つけるために使用されるグロブパターンです。
pattern := filepath.Join(dir, "*.tmpl")

// ここからが実際の例です。
// ドライバーをロードします。
drivers := template.Must(template.ParseGlob(pattern))

// T2テンプレートの実装を定義する必要があります。まず、ドライバーをクローンし、
// 次にT2の定義をテンプレート名前空間に追加します。

// 1. ヘルパーセットをクローンして、それらを実行するための新しい名前空間を作成します。
first, err := drivers.Clone()
if err != nil {
	log.Fatal("cloning helpers: ", err)
}
// 2. T2のバージョンAを定義し、それを解析します。
_, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
if err != nil {
	log.Fatal("parsing T2: ", err)
}

// 今度は、T2の別のバージョンを使用して、全体を繰り返します。
// 1. ドライバーをクローンします。
second, err := drivers.Clone()
if err != nil {
	log.Fatal("cloning drivers: ", err)
}
// 2. Define T2, version B, and parse it.
_, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
if err != nil {
	log.Fatal("parsing T2: ", err)
}

// テンプレートを逆の順序で実行して、
// 最初のテンプレートが2番目のテンプレートに影響されないことを確認します。
err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
if err != nil {
	log.Fatalf("second execution: %s", err)
}
err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
if err != nil {
	log.Fatalf("first: execution: %s", err)
}
Output:

T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))

func Must

func Must(t *Template, err error) *Template

Mustは、(*Template, error)を返す関数への呼び出しをラップし、 エラーが非nilの場合にパニックを起こすヘルパーです。これは変数の初期化での使用を意図しています。 例えば、

var t = template.Must(template.New("name").Parse("html"))

func New

func New(name string) *Template

Newは、指定された名前を持つ新しいHTMLテンプレートを割り当てます。

func ParseFS added in v1.16.0

func ParseFS(fs fs.FS, patterns ...string) (*Template, error)

ParseFSはParseFilesやParseGlobと似ていますが、ホストのオペレーティングシステムのファイルシステムではなく、 ファイルシステムfsから読み取ります。 それはグロブパターンのリストを受け入れます。 (ほとんどのファイル名は、自分自身のみにマッチするグロブパターンとして機能することに注意してください。)

func ParseFiles

func ParseFiles(filenames ...string) (*Template, error)

ParseFilesは新しいテンプレートを作成し、 指定されたファイルからテンプレート定義を解析します。返されるテンプレートの名前は、 最初のファイルの(ベース)名と(解析された)内容になります。少なくとも一つのファイルが必要です。 エラーが発生した場合、解析は停止し、返される*Templateはnilになります。

異なるディレクトリにある同じ名前の複数のファイルを解析するとき、 最後に指定されたものが結果となります。 例えば、ParseFiles("a/foo", "b/foo")は "b/foo" を "foo" という名前のテンプレートとして保存し、 "a/foo" は利用できません。

func ParseGlob

func ParseGlob(pattern string) (*Template, error)

ParseGlobは新しいテンプレートを作成し、パターンによって識別されたファイルから テンプレート定義を解析します。ファイルはfilepath.Matchのセマンティクスに従ってマッチし、 パターンは少なくとも一つのファイルとマッチしなければなりません。 返されるテンプレートの名前は、パターンによって最初にマッチしたファイルの(ベース)名と (解析された)内容になります。ParseGlobは、パターンにマッチしたファイルのリストで ParseFilesを呼び出すのと同等です。

異なるディレクトリにある同じ名前の複数のファイルを解析するとき、 最後に指定されたものが結果となります。

func (*Template) AddParseTree

func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)

AddParseTreeは、名前とパースツリーを持つ新しいテンプレートを作成し、 それをtに関連付けます。

tまたは関連するテンプレートがすでに実行されている場合、エラーを返します。

func (*Template) Clone

func (t *Template) Clone() (*Template, error)

Cloneは、テンプレートの複製を返します。これには、すべての関連テンプレートも含まれます。 実際の表現はコピーされませんが、関連テンプレートの名前空間はコピーされるため、 コピーでのParseへのさらなる呼び出しは、コピーにテンプレートを追加しますが、元のテンプレートには追加しません。 Cloneは、共通のテンプレートを準備し、それらを他のテンプレートのバリアント定義とともに使用するために使用できます。 バリアントは、クローンが作成された後に追加します。

tがすでに実行されている場合、エラーを返します。

func (*Template) DefinedTemplates added in v1.6.0

func (t *Template) DefinedTemplates() string

DefinedTemplatesは、定義されたテンプレートのリストを返します。 それは文字列 "; defined templates are: " で始まります。もし定義されたテンプレートがなければ、 空の文字列を返します。エラーメッセージを生成するために使用されます。

func (*Template) Delims

func (t *Template) Delims(left, right string) *Template

Delimsは、アクションのデリミタを指定された文字列に設定します。これは、 その後のParse、ParseFiles、またはParseGlobへの呼び出しで使用されます。ネストしたテンプレート 定義はこの設定を継承します。空のデリミタは、対応するデフォルトを表します: {{ または }}。 戻り値はテンプレートなので、呼び出しはチェーンできます。

Example
package main

import (
	"github.com/shogo82148/std/html/template"
	"github.com/shogo82148/std/log"
	"github.com/shogo82148/std/os"
)

func main() {
	const text = "<<.Greeting>> {{.Name}}"

	data := struct {
		Greeting string
		Name     string
	}{
		Greeting: "Hello",
		Name:     "Joe",
	}

	t := template.Must(template.New("tpl").Delims("<<", ">>").Parse(text))

	err := t.Execute(os.Stdout, data)
	if err != nil {
		log.Fatal(err)
	}

}
Output:

Hello {{.Name}}

func (*Template) Execute

func (t *Template) Execute(wr io.Writer, data any) error

Executeは、解析されたテンプレートを指定されたデータオブジェクトに適用し、 出力をwrに書き込みます。 テンプレートの実行中またはその出力の書き込み中にエラーが発生した場合、 実行は停止しますが、部分的な結果はすでに出力ライターに書き込まれている可能性があります。 テンプレートは並行して安全に実行できますが、並行実行がWriterを共有する場合、 出力が交互になる可能性があります。

func (*Template) ExecuteTemplate

func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error

ExecuteTemplateは、指定された名前を持つtに関連付けられたテンプレートを 指定されたデータオブジェクトに適用し、出力をwrに書き込みます。 テンプレートの実行中またはその出力の書き込み中にエラーが発生した場合、 実行は停止しますが、部分的な結果はすでに出力ライターに書き込まれている可能性があります。 テンプレートは並行して安全に実行できますが、並行実行がWriterを共有する場合、 出力が交互になる可能性があります。

func (*Template) Funcs

func (t *Template) Funcs(funcMap FuncMap) *Template

Funcsは引数のマップの要素をテンプレートの関数マップに追加します。 これはテンプレートが解析される前に呼び出す必要があります。 マップの値が適切な戻り値型を持つ関数でない場合、パニックを起こします。ただし、 マップの要素を上書きすることは合法です。戻り値はテンプレートなので、 呼び出しはチェーンできます。

func (*Template) Lookup

func (t *Template) Lookup(name string) *Template

Lookupは、tに関連付けられた指定された名前のテンプレートを返します。 もし該当するテンプレートがなければ、nilを返します。

func (*Template) Name

func (t *Template) Name() string

Nameはテンプレートの名前を返します。

func (*Template) New

func (t *Template) New(name string) *Template

Newは、指定された名前を持つ新しいHTMLテンプレートを割り当て、 それを与えられたテンプレートと同じデリミタと関連付けます。この関連付けは推移的で、 一つのテンプレートが{{template}}アクションで別のテンプレートを呼び出すことを可能にします。

指定された名前を持つテンプレートがすでに存在する場合、新しいHTMLテンプレートは それを置き換えます。既存のテンプレートはリセットされ、tとの関連付けが解除されます。

func (*Template) Option added in v1.5.0

func (t *Template) Option(opt ...string) *Template

Optionは、テンプレートのオプションを設定します。オプションは 文字列で記述され、単純な文字列または "key=value" の形式を取ります。オプション文字列には 最大で一つの等号が含まれます。オプション文字列が認識できない、または無効な場合、 Optionはパニックを起こします。

既知のオプション:

missingkey: マップが存在しないキーでインデックス付けされた場合の、実行中の振る舞いを制御します。

"missingkey=default" または "missingkey=invalid"
	デフォルトの振る舞い: 何もせずに実行を続けます。
	印刷される場合、インデックス操作の結果は文字列
	"<no value>" です。
"missingkey=zero"
	操作はマップタイプの要素のゼロ値を返します。
"missingkey=error"
	エラーで直ちに実行が停止します。

func (*Template) Parse

func (t *Template) Parse(text string) (*Template, error)

Parseは、tのテンプレートボディとしてテキストを解析します。 テキスト内の名前付きテンプレート定義 ({{define ...}}または{{block ...}}ステートメント) は、 tに関連付けられた追加のテンプレートを定義し、t自体の定義からは削除されます。

テンプレートは、tまたは関連するテンプレートのExecuteが初めて使用される前に、 Parseを連続して呼び出すことで再定義できます。 ボディが空白とコメントのみで構成されるテンプレート定義は空とみなされ、 既存のテンプレートのボディを置き換えません。 これにより、Parseを使用して新しい名前付きテンプレート定義を追加することができますが、 メインのテンプレートボディを上書きすることはありません。

func (*Template) ParseFS added in v1.16.0

func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error)

ParseFSはParseFilesやParseGlobと似ていますが、ホストのオペレーティングシステムのファイルシステムではなく、 ファイルシステムfsから読み取ります。 それはグロブパターンのリストを受け入れます。 (ほとんどのファイル名は、自分自身のみにマッチするグロブパターンとして機能することに注意してください。)

func (*Template) ParseFiles

func (t *Template) ParseFiles(filenames ...string) (*Template, error)

ParseFilesは指定されたファイルを解析し、結果として得られるテンプレートを tに関連付けます。エラーが発生した場合、解析は停止し、返されるテンプレートはnilになります。 それ以外の場合、それはtです。少なくとも一つのファイルが必要です。

異なるディレクトリにある同じ名前の複数のファイルを解析するとき、 最後に指定されたものが結果となります。

ParseFilesは、tまたは関連するテンプレートがすでに実行されている場合、エラーを返します。

func (*Template) ParseGlob

func (t *Template) ParseGlob(pattern string) (*Template, error)

ParseGlobは、パターンによって識別されたファイルのテンプレート定義を解析し、 結果として得られるテンプレートをtに関連付けます。ファイルはfilepath.Matchのセマンティクスに従ってマッチし、 パターンは少なくとも一つのファイルとマッチしなければなりません。 ParseGlobは、パターンにマッチしたファイルのリストでt.ParseFilesを呼び出すのと同等です。

異なるディレクトリにある同じ名前の複数のファイルを解析するとき、 最後に指定されたものが結果となります。

ParseGlobは、tまたは関連するテンプレートがすでに実行されている場合、エラーを返します。

func (*Template) Templates

func (t *Template) Templates() []*Template

Templatesは、t自体を含む、tに関連付けられたテンプレートのスライスを返します。

type URL

type URL string

URLは、既知の安全なURLまたはURL部分文字列(RFC 3986を参照)をカプセル化します。 信頼できるソースからの`javascript:checkThatFormNotEditedBeforeLeavingPage()`のようなURLは ページに含まれるべきですが、デフォルトでは動的な`javascript:` URLは、 頻繁に悪用されるインジェクションベクトルであるためフィルタリングされます。

このタイプの使用はセキュリティリスクを伴います: カプセル化されたコンテンツは信頼できるソースから来るべきであり、 それはテンプレートの出力にそのまま含まれます。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL