package template

import “html/template”

template包(html/template)实现了数据驱动的模板,用于生成可对抗代码注入的安全HTML输出。本包提供了和text/template包相同的接口,无论何时当输出是HTML的时候都应使用本包。

此处的文档关注本包的安全特性。至于如何使用模板,请参照text/template包。

Introduction

本包是对text/template包的包装,两个包提供的模板API几无差别,可以安全的随意替换两包。

tmpl, err := template.New(“name”).Parse(…) // 省略错误检测 err = tmpl.Execute(out, data) 如果成功创建了tmpl,tmpl现在是注入安全的了。否则err将返回ErrorCode里定义的某个错误。即使成功生成了模板,执行时仍可能导致ErrorCode里定义的错误。

HTML模板将数据视为明文文本,必须经过编码以便安全的嵌入HTML文档。转义操作会参考上下文,因此action可以出现在JavaScript、CSS、URI上下文环境里。

本包使用的安全模型假设模板的作者是可信任的,但用于执行的数据不可信。更多细节参见下面。

示例:

import “text/template” … t, err := template.New(“foo”).Parse({{define "T"}}Hello, {{.}}!{{end}}) err = t.ExecuteTemplate(out, “T”, “”) 生成:

Hello, ! 但在html/template包里会根据上下文自动转义:

import “html/template” … t, err := template.New(“foo”).Parse({{define "T"}}Hello, {{.}}!{{end}}) err = t.ExecuteTemplate(out, “T”, “”) 生成安全的转义后HTML输出:

Hello, <script>alert(‘you have been pwned’)</script>! Contexts

本包可以理解HTML、CSS、JavaScript和URI。它会给每一个简单的action pipeline都添加处理函数,如下例:

{{.}} 在解析时每个{{.}}都会在必要时重写添加转义函数,此例中会修改为:

{{. | html}} Errors

细节请参见ErrorCode类型的文档。

A fuller picture

本包剩余部分的注释第一次阅读时可以跳过;这些部分包括理解转码文本和错误信息的必要细节。多数使用者无需理解这些细节。

Contexts

假设{{.}}是O'Reilly: How are <i>you</i>?,下表展示了{{.}}用于左侧模板时的输出:

Context {{.}} After {{.}} O’Reilly: How are <i>you</i>? O’Reilly: How are you? O’Reilly: How are %3ci%3eyou%3c/i%3e? O’Reilly%3a%20How%20are%3ci%3e…%3f O\x27Reilly: How are \x3ci\x3eyou…? “O\x27Reilly: How are \x3ci\x3eyou…?” O\x27Reilly: How are \x3ci\x3eyou…\x3f 如果用在不安全的上下文里,值就可能被过滤掉:

Context {{.}} After #ZgotmplZ 因为”O’Reilly:”不是一个可以接受的协议名,如”http:”。

如果{{.}}是一个无害的词汇,如left,那么它就可以出现在更多地方。

Context {{.}} After {{.}} left left left left left left left <a style=”background: ‘{{.}}’> left <a style=”background: url(‘{{.}}’)> left

left

如果{{.}}是非字符串类型的值,可以用于JavaScript上下文环境里:

struct{A,B string}{ “foo”, “bar” } 将该值应用在在转义后的模板里:

模板输出为:

请参见json包来理解非字符串内容是如何序列化并嵌入JavaScript里的。

Typed Strings

本包默认所有的pipeline都生成明文字符串,它会在必要时添加转义pipeline阶段以安全并正确的将明文字符串嵌入输出的文本里。

当用于执行的数据不是明文字符串时,你可以通过显式改变数据的类型以避免其被错误的转义。

类型HTML、JS、URL和其他content.go里定义的类型可以保持不被转义的安全内容。

模板:

Hello, {{.}}! 可以采用如下调用:

tmpl.Execute(out, HTML(<b>World</b>)) 来输出:

Hello, World! 而不是:

Hello, <b>World<b>! 如果{{.}}是一个内建类型字符串就会产生该输出。

Security Model

本包里安全的定义参加如下网页:

http://js-quasis-libraries-and-repl.googlecode.com/svn/trunk/safetemplate.html#problem_definition

本包假设模板作者可信而执行数据不可信,目标是在保证安全性的前提下保证效率:

结构保留特性:“……当模板作者用安全的模板语言写了一个HTML标签时,不管数据的值为何浏览器都会将输出的相应部分解释为标签,该情况在其他结构里也成立,如属性边界以及JS和CSS边界。”

代码影响特性:“……只有模板作者指定的代码能作为注入模板输出到页面的结果执行,所有模板作者指定的代码都应如此。”

最少惊讶特性:“一个熟悉HTML、CSS、JS的开发者(或代码阅读者),应可以正确的推断出{{.}}会如何转义。”

Index 返回首页

type ErrorCode type Error func (e *Error) Error() string func HTMLEscape(w io.Writer, b []byte) func HTMLEscapeString(s string) string func HTMLEscaper(args …interface{}) string func JSEscape(w io.Writer, b []byte) func JSEscapeString(s string) string func JSEscaper(args …interface{}) string func URLQueryEscaper(args …interface{}) string type FuncMap type HTML type HTMLAttr type JS type JSStr type CSS type URL type Template func Must(t *Template, err error) *Template func New(name string) *Template func ParseFiles(filenames …string) (*Template, error) func ParseGlob(pattern string) (*Template, error) func (t *Template) Name() string func (t *Template) Delims(left, right string) *Template func (t *Template) Funcs(funcMap FuncMap) *Template func (t *Template) Clone() (*Template, error) func (t *Template) Lookup(name string) *Template func (t *Template) Templates() []*Template func (t *Template) New(name string) *Template func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) func (t *Template) Parse(src string) (*Template, error) func (t *Template) ParseFiles(filenames …string) (*Template, error) func (t *Template) ParseGlob(pattern string) (*Template, error) func (t *Template) Execute(wr io.Writer, data interface{}) error func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error type ErrorCode type ErrorCode int ErrorCode是代表错误种类的错误码。

const ( // OK表示没有出错 OK ErrorCode = iota // 当上下文环境有歧义时导致ErrAmbigContext: // 举例: // <a href=”{{if .C}}/path/{{else}}/search?q={{end}}{{.X}}”&rt; // 说明: // {{.X}}的URL上下文环境有歧义,因为根据{{.C}}的值, // 它可以是URL的后缀,或者是查询的参数。 // 将{{.X}}移动到如下情况可以消除歧义: // <a href=”{{if .C}}/path/{{.X}}{{else}}/search?q={{.X}}{{end}}”&rt; ErrAmbigContext // 期望空白、属性名、标签结束标志而没有时,标签名或无引号标签值包含非法字符时, // 会导致ErrBadHTML;举例: // <a href = /search?q=foo&rt; // <href=foo&rt; // <form na<e=…&rt; // <option selected< // 讨论: // 一般是因为HTML元素输入了错误的标签名、属性名或者未用引号的属性值,导致解析失败 // 将所有的属性都用引号括起来是最好的策略 ErrBadHTML // {{if}}等分支不在相同上下文开始和结束时,导致ErrBranchEnd // 示例: // {{if .C}}<a href=”{{end}}{{.X}} // 讨论: // html/template包会静态的检验{{if}}、{{range}}或{{with}}的每一个分支, // 以对后续的pipeline进行转义。该例出现了歧义,{{.X}}可能是HTML文本节点, // 或者是HTML属性值的URL的前缀,{{.X}}的上下文环境可以确定如何转义,但该 // 上下文环境却是由运行时{{.C}}的值决定的,不能在编译期获知。 // 这种问题一般是因为缺少引号或者角括号引起的,另一些则可以通过重构将两个上下文 // 放进if、range、with的不同分支里来避免,如果问题出现在参数长度一定非0的 // {{range}}的分支里,可以通过添加无效{{else}}分支解决。 ErrBranchEnd // 如果以非文本上下文结束,则导致ErrEndContext // 示例: // <div // <div title=”no close quote&rt; // {{end}} // {{define “helper”}} document.write(‘ <div title=” ‘) {{end}} // 模板”helper”不能生成合法的文档片段,所以不直接执行,用js生成。 ErrEndContext // 调用不存在的模板时导致ErrNoSuchTemplate // 示例: // {{define “main”}}<div {{template “attrs”}}&rt;{{end}} // {{define “attrs”}}href=”{{.URL}}”{{end}} // 讨论: // html/template包略过模板调用计算上下文环境。 // 此例中,当被”main”模板调用时,”attrs”模板的{{.URL}}必须视为一个URL; // 但如果解析”main”时,”attrs”还未被定义,就会导致本错误 ErrNoSuchTemplate // 不能计算输出位置的上下文环境时,导致ErrOutputContext // 示例: // {{define “t”}}{{if .T}}{{template “t” .T}}{{end}}{{.H}}”,{{end}} // 讨论: // 一个递归的模板,其起始和结束的上下文环境不同时; // 不能计算出可信的输出位置上下文环境时,就可能导致本错误。 // 检查各个命名模板是否有错误; // 如果模板不应在命名的起始上下文环境调用,检查在不期望上下文环境中对该模板的调用; // 或者将递归模板重构为非递归模板; ErrOutputContext // 尚未支持JS正则表达式插入字符集 // 示例: //