Go 语言

Go 语言教程 Go 语言环境安装 Go 语言结构 Go 语言基础语法 Go 语言数据类型 Go 语言变量 Go 语言常量 Go 语言运算符 Go 语言条件语句 Go 语言 if 语句 Go 语言 if...else 语句 Go 语言 if 语句嵌套 Go 语言 switch 语句 Go 语言 select 语句 Go 语言循环语句 Go 语言 for 循环 Go 语言循环嵌套 Go 语言 break 语句 Go 语言 continue 语句 Go 语言 goto 语句 Go 语言函数 Go 语言函数值传递值 Go 语言函数引用传递值 Go 语言函数作为值 Go 语言函数闭包 Go 语言函数方法 Go 语言变量作用域 Go 语言数组 Go 语言多维数组 Go 语言向函数传递数组 Go 语言指针 Go 语言指针数组 Go 语言指向指针的指针 Go 语言指针作为函数参数 Go 语言结构体 Go 语言切片(Slice) Go 语言范围(Range) Go 语言Map(集合) Go 语言递归函数 Go 语言类型转换 Go 语言接口 Go 错误处理 Go 语言开发工具Go 语言标准库

Go 语言标准库


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", "<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;!

Contexts

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

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

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

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

Errors

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

A fuller picture

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

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>

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

Typed Strings

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

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

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

模板:

Hello, {{.}}!

可以采用如下调用:

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

来输出:

Hello, <b>World</b>!

而不是:

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

如果{{.}}是一个内建类型字符串就会产生该输出。

Security Model

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

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

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

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

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

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

Go语言标准库 >>


  • type ErrorCode
  • type Error
  • 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
  • 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;
        //   <script>f()
        // 讨论:
        //   执行模板必须生成HTML的一个文档片段,以未闭合标签结束的模板都会引发本错误。
        //   不用在HTML上下文或者生成不完整片段的模板不应直接执行。
        //   {{define "main"}} <script&rt;{{template "helper"}}</script> {{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正则表达式插入字符集
        // 示例:
        //     <script>var pattern = /foo[{{.Chars}}]/</script&rt;
        // 讨论:
        //   html/template不支持向JS正则表达式里插入字面值字符集
        ErrPartialCharset
        // 部分转义序列尚未支持
        // 示例:
        //   <script>alert("\{{.X}}")</script&rt;
        // 讨论:
        //   html/template包不支持紧跟在反斜杠后面的action
        //   这一般是错误的,有更好的解决方法,例如:
        //     <script>alert("{{.X}}")</script&rt;
        //   可以工作,如果{{.X}}是部分转义序列,如"xA0",
        //   可以将整个序列标记为安全文本:JSStr(`\xA0`)
        ErrPartialEscape
        // range循环的重入口出错,导致ErrRangeLoopReentry
        // 示例:
        //   <script>var x = [{{range .}}'{{.}},{{end}}]</script&rt;
        // 讨论:
        //   如果range的迭代部分导致其结束于上一次循环的另一上下文,将不会有唯一的上下文环境
        //   此例中,缺少一个引号,因此无法确定{{.}}是存在于一个JS字符串里,还是一个JS值文本里。
        //   第二次迭代生成类似下面的输出:
        //     <script>var x = ['firstValue,'secondValue]</script&rt;
        ErrRangeLoopReentry
        // 斜杠可以开始一个除法或者正则表达式
        // 示例:
        //   <script&rt;
        //     {{if .C}}var x = 1{{end}}
        //     /-{{.N}}/i.test(x) ? doThis : doThat();
        //   </script&rt;
        // 讨论:
        //   上例可以生成`var x = 1/-2/i.test(s)...`,其中第一个斜杠作为除号;
        //   或者它也可以生成`/-2/i.test(s)`,其中第一个斜杠生成一个正则表达式字面值
        //   检查分支中是否缺少分号,或者使用括号来明确你的意图
        ErrSlashAmbig
    )

    我们为转义模板时的所有错误都定义了错误码,但经过转义修正的模板仍可能在运行时出错:

    输出"ZgotmplZ"的例子:

    <img src="{{.X}}">
    其中{{.X}}执行结果为`javascript:...`

    讨论:

    "ZgotmplZ"是一个特殊值,表示运行时在CSS或URL上下文环境生成的不安全内容。本例的输出为:
      <img src="#ZgotmplZ">
    如果数据来源可信,请转换内容类型来避免被滤除:URL(`javascript:...`)

    type Error

    type Error struct {
        // ErrorCode描述错误的种类
        ErrorCode ErrorCode
        // Name是发生错误的模板的名字
        Name string
        // Line是错误位置在模板原文中的行号或者0
        Line int
        // Description是供调试者阅读的错误描述
        Description string
    }

    Error描述在模板转义时出现的错误。

    func (*Error) Error

    func (e *Error) Error() string

    func HTMLEscape

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

    函数向w中写入b的HTML转义等价表示。

    func HTMLEscapeString

    func HTMLEscapeString(s string) string

    返回s的HTML转义等价表示字符串。

    func HTMLEscaper

    func HTMLEscaper(args ...interface{}) string

    函数返回其所有参数文本表示的HTML转义等价表示字符串。

    func JSEscape

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

    函数向w中写入b的JavaScript转义等价表示。

    func JSEscapeString

    func JSEscapeString(s string) string

    返回s的JavaScript转义等价表示字符串。

    func JSEscaper

    func JSEscaper(args ...interface{}) string

    函数返回其所有参数文本表示的JavaScript转义等价表示字符串。

    func URLQueryEscaper

    func URLQueryEscaper(args ...interface{}) string

    函数返回其所有参数文本表示的可以嵌入URL查询的转义等价表示字符串。

    type FuncMap

    type FuncMap map[string]interface{}

    FuncMap类型定义了函数名字符串到函数的映射,每个函数都必须有1到2个返回值,如果有2个则后一个必须是error接口类型;如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用者该错误。该类型拷贝自text/template包的同名类型,因此不需要导入该包以使用该类型。

    type HTML

    type HTML string

    HTML用于封装一个已知安全的HTML文档片段。它不应被第三方使用,也不能用于含有未闭合的标签或注释的HTML文本。该类型适用于封装一个效果良好的HTML生成器生成的HTML文本或者本包模板的输出的文本。

    type HTMLAttr

    type HTMLAttr string

    HTMLAttr用来封装一个来源可信的HTML属性,如` dir="ltr"`。

    type JS

    type JS string

    JS用于封装一个已知安全的EcmaScript5表达式,如`(x + y * z())`。模板作者有责任确保封装的字符串不会破坏原有的语义,也不能包含有歧义的声明或表达式,如"{ foo: bar() }\n['foo']()",这一句既是合法的表达式也是语义完全不同的合法程序。

    type JSStr

    type JSStr string

    JSStr用于封装一个打算嵌入JavaScript表达式中的字符序列,该字符串必须匹配一系列StringCharacters:

    StringCharacter :: 除了`\`和行终止符的SourceCharacter | EscapeSequence

    注意不允许换行,JSStr("foo\\nbar")是可以的,但JSStr("foo\\\nbar")不可以。

    type URL

    type URL string

    URL用来封装一个已知安全的URL或URL子字符串(参见RFC 3986

    形如`javascript:checkThatFormNotEditedBeforeLeavingPage()`的来源可信的URL应写进页面里,但一般动态的`javascript:` URL排除在外(不写进页面),因为它们是频繁使用的注入向量。

    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)`

    参见:http://www.w3.org/TR/css3-syntax/#parsing 

    以及:https://web.archive.org/web/20090211114933/http://w3.org/TR/css3-syntax#style

    type Template

    type Template struct {
        // 底层的模板解析树,会更新为HTML安全的
        Tree *parse.Tree
        // 内含隐藏或非导出字段
    }

    Template类型是text/template包的Template类型的特化版本,用于生成安全的HTML文本片段。

    func Must

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

    Must函数用于包装返回(*Template, error)的函数/方法调用,它会在err非nil时panic,一般用于变量初始化:

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

    func New

    func New(name string) *Template

    创建一个名为name的模板。

    func ParseFiles

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

    ParseFiles函数创建一个模板并解析filenames指定的文件里的模板定义。返回的模板的名字是第一个文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。至少要提供一个文件。如果发生错误,会停止解析并返回nil。

    func ParseGlob

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

    ParseGlob创建一个模板并解析匹配pattern的文件(参见glob规则)里的模板定义。返回的模板的名字是第一个匹配的文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。至少要存在一个匹配的文件。如果发生错误,会停止解析并返回nil。ParseGlob等价于使用匹配pattern的文件的列表为参数调用ParseFiles。

    func (*Template) Name

    func (t *Template) Name() string

    返回模板t的名字。

    func (*Template) Delims

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

    Delims方法用于设置action的分界字符串,应用于之后的Parse、ParseFiles、ParseGlob方法。嵌套模板定义会继承这种分界符设置。空字符串分界符表示相应的默认分界符:{{或}}。返回值就是t,以便进行链式调用。

    func (*Template) Funcs

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

    Funcs方法向模板t的函数字典里加入参数funcMap内的键值对。如果funcMap某个键值对的值不是函数类型或者返回值不符合要求会panic。但是,可以对t函数列表的成员进行重写。方法返回t以便进行链式调用。

    func (*Template) Clone

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

    Clone方法返回模板的一个副本,包括所有相关联的模板。模板的底层表示树并未拷贝,而是拷贝了命名空间,因此拷贝调用Parse方法不会修改原模板的命名空间。Clone方法用于准备模板的公用部分,向拷贝中加入其他关联模板后再进行使用。

    如果t已经执行过了,会返回错误。

    func (*Template) Lookup

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

    Lookup方法返回与t关联的名为name的模板,如果没有这个模板会返回nil。

    func (*Template) Templates

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

    Templates方法返回与t相关联的模板的切片,包括t自己。

    func (*Template) New

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

    New方法创建一个和t关联的名字为name的模板并返回它。这种可以传递的关联允许一个模板使用template action调用另一个模板。

    func (*Template) AddParseTree

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

    AddParseTree方法使用name和tree创建一个模板并使它和t相关联。

    如果t已经执行过了,会返回错误。

    func (*Template) Parse

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

    Parse方法将字符串text解析为模板。嵌套定义的模板会关联到最顶层的t。Parse可以多次调用,但只有第一次调用可以包含空格、注释和模板定义之外的文本。如果后面的调用在解析后仍剩余文本会引发错误、返回nil且丢弃剩余文本;如果解析得到的模板已有相关联的同名模板,会覆盖掉原模板。

    func (*Template) ParseFiles

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

    ParseGlob方法解析filenames指定的文件里的模板定义并将解析结果与t关联。如果发生错误,会停止解析并返回nil,否则返回(t, nil)。至少要提供一个文件。

    func (*Template) ParseGlob

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

    ParseFiles方法解析匹配pattern的文件里的模板定义并将解析结果与t关联。如果发生错误,会停止解析并返回nil,否则返回(t, nil)。至少要存在一个匹配的文件。

    func (*Template) Execute

    func (t *Template) Execute(wr io.Writer, data interface{}) error

    Execute方法将解析好的模板应用到data上,并将输出写入wr。如果执行时出现错误,会停止执行,但有可能已经写入wr部分数据。模板可以安全的并发执行。

    func (*Template) ExecuteTemplate

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

    ExecuteTemplate方法类似Execute,但是使用名为name的t关联的模板产生输出。