summaryrefslogtreecommitdiff
path: root/libgo/go/html
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/html')
-rw-r--r--libgo/go/html/template/attr.go3
-rw-r--r--libgo/go/html/template/doc.go4
-rw-r--r--libgo/go/html/template/error.go28
-rw-r--r--libgo/go/html/template/escape.go240
-rw-r--r--libgo/go/html/template/escape_test.go192
-rw-r--r--libgo/go/html/template/js.go2
-rw-r--r--libgo/go/html/template/template.go20
-rw-r--r--libgo/go/html/template/transition.go4
8 files changed, 354 insertions, 139 deletions
diff --git a/libgo/go/html/template/attr.go b/libgo/go/html/template/attr.go
index d65d340073d..7438f51f6a9 100644
--- a/libgo/go/html/template/attr.go
+++ b/libgo/go/html/template/attr.go
@@ -135,9 +135,8 @@ var attrTypeMap = map[string]contentType{
}
// attrType returns a conservative (upper-bound on authority) guess at the
-// type of the named attribute.
+// type of the lowercase named attribute.
func attrType(name string) contentType {
- name = strings.ToLower(name)
if strings.HasPrefix(name, "data-") {
// Strip data- so that custom attribute heuristics below are
// widely applied.
diff --git a/libgo/go/html/template/doc.go b/libgo/go/html/template/doc.go
index cb898127432..35d171c3fca 100644
--- a/libgo/go/html/template/doc.go
+++ b/libgo/go/html/template/doc.go
@@ -65,8 +65,10 @@ functions to each simple action pipeline, so given the excerpt
At parse time each {{.}} is overwritten to add escaping functions as necessary.
In this case it becomes
- <a href="/search?q={{. | urlquery}}">{{. | html}}</a>
+ <a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a>
+where urlescaper, attrescaper, and htmlescaper are aliases for internal escaping
+functions.
Errors
diff --git a/libgo/go/html/template/error.go b/libgo/go/html/template/error.go
index cbcaf92e4ab..0e527063ea6 100644
--- a/libgo/go/html/template/error.go
+++ b/libgo/go/html/template/error.go
@@ -183,6 +183,34 @@ const (
// Look for missing semicolons inside branches, and maybe add
// parentheses to make it clear which interpretation you intend.
ErrSlashAmbig
+
+ // ErrPredefinedEscaper: "predefined escaper ... disallowed in template"
+ // Example:
+ // <div class={{. | html}}>Hello<div>
+ // Discussion:
+ // Package html/template already contextually escapes all pipelines to
+ // produce HTML output safe against code injection. Manually escaping
+ // pipeline output using the predefined escapers "html" or "urlquery" is
+ // unnecessary, and may affect the correctness or safety of the escaped
+ // pipeline output in Go 1.8 and earlier.
+ //
+ // In most cases, such as the given example, this error can be resolved by
+ // simply removing the predefined escaper from the pipeline and letting the
+ // contextual autoescaper handle the escaping of the pipeline. In other
+ // instances, where the predefined escaper occurs in the middle of a
+ // pipeline where subsequent commands expect escaped input, e.g.
+ // {{.X | html | makeALink}}
+ // where makeALink does
+ // return `<a href="`+input+`">link</a>`
+ // consider refactoring the surrounding template to make use of the
+ // contextual autoescaper, i.e.
+ // <a href="{{.X}}">link</a>
+ //
+ // To ease migration to Go 1.9 and beyond, "html" and "urlquery" will
+ // continue to be allowed as the last command in a pipeline. However, if the
+ // pipeline occurs in an unquoted attribute value context, "html" is
+ // disallowed. Avoid using "html" and "urlquery" entirely in new templates.
+ ErrPredefinedEscaper
)
func (e *Error) Error() string {
diff --git a/libgo/go/html/template/escape.go b/libgo/go/html/template/escape.go
index 0e7d2be143b..b51a37039bd 100644
--- a/libgo/go/html/template/escape.go
+++ b/libgo/go/html/template/escape.go
@@ -19,8 +19,7 @@ import (
// been modified. Otherwise the named templates have been rendered
// unusable.
func escapeTemplate(tmpl *Template, node parse.Node, name string) error {
- e := newEscaper(tmpl)
- c, _ := e.escapeTree(context{}, node, name, 0)
+ c, _ := tmpl.esc.escapeTree(context{}, node, name, 0)
var err error
if c.err != nil {
err, c.err.Name = c.err, name
@@ -36,7 +35,7 @@ func escapeTemplate(tmpl *Template, node parse.Node, name string) error {
}
return err
}
- e.commit()
+ tmpl.esc.commit()
if t := tmpl.set[name]; t != nil {
t.escapeErr = escapeOK
t.Tree = t.text.Tree
@@ -44,6 +43,21 @@ func escapeTemplate(tmpl *Template, node parse.Node, name string) error {
return nil
}
+// evalArgs formats the list of arguments into a string. It is equivalent to
+// fmt.Sprint(args...), except that it deferences all pointers.
+func evalArgs(args ...interface{}) string {
+ // Optimization for simple common case of a single string argument.
+ if len(args) == 1 {
+ if s, ok := args[0].(string); ok {
+ return s
+ }
+ }
+ for i, arg := range args {
+ args[i] = indirectToStringerOrError(arg)
+ }
+ return fmt.Sprint(args...)
+}
+
// funcMap maps command names to functions that render their inputs safe.
var funcMap = template.FuncMap{
"_html_template_attrescaper": attrEscaper,
@@ -60,22 +74,14 @@ var funcMap = template.FuncMap{
"_html_template_urlescaper": urlEscaper,
"_html_template_urlfilter": urlFilter,
"_html_template_urlnormalizer": urlNormalizer,
-}
-
-// equivEscapers matches contextual escapers to equivalent template builtins.
-var equivEscapers = map[string]string{
- "_html_template_attrescaper": "html",
- "_html_template_htmlescaper": "html",
- "_html_template_nospaceescaper": "html",
- "_html_template_rcdataescaper": "html",
- "_html_template_urlescaper": "urlquery",
- "_html_template_urlnormalizer": "urlquery",
+ "_eval_args_": evalArgs,
}
// escaper collects type inferences about templates and changes needed to make
// templates injection safe.
type escaper struct {
- tmpl *Template
+ // ns is the nameSpace that this escaper is associated with.
+ ns *nameSpace
// output[templateName] is the output context for a templateName that
// has been mangled to include its input context.
output map[string]context
@@ -92,10 +98,10 @@ type escaper struct {
textNodeEdits map[*parse.TextNode][]byte
}
-// newEscaper creates a blank escaper for the given set.
-func newEscaper(t *Template) *escaper {
- return &escaper{
- t,
+// makeEscaper creates a blank escaper for the given set.
+func makeEscaper(n *nameSpace) escaper {
+ return escaper{
+ n,
map[string]context{},
map[string]*template.Template{},
map[string]bool{},
@@ -140,6 +146,30 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
return c
}
c = nudge(c)
+ // Check for disallowed use of predefined escapers in the pipeline.
+ for pos, idNode := range n.Pipe.Cmds {
+ node, ok := idNode.Args[0].(*parse.IdentifierNode)
+ if !ok {
+ // A predefined escaper "esc" will never be found as an identifier in a
+ // Chain or Field node, since:
+ // - "esc.x ..." is invalid, since predefined escapers return strings, and
+ // strings do not have methods, keys or fields.
+ // - "... .esc" is invalid, since predefined escapers are global functions,
+ // not methods or fields of any types.
+ // Therefore, it is safe to ignore these two node types.
+ continue
+ }
+ ident := node.Ident
+ if _, ok := predefinedEscapers[ident]; ok {
+ if pos < len(n.Pipe.Cmds)-1 ||
+ c.state == stateAttr && c.delim == delimSpaceOrTagEnd && ident == "html" {
+ return context{
+ state: stateError,
+ err: errorf(ErrPredefinedEscaper, n, n.Line, "predefined escaper %q disallowed in template", ident),
+ }
+ }
+ }
+ }
s := make([]string, 0, 3)
switch c.state {
case stateError:
@@ -204,75 +234,98 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
return c
}
-// allIdents returns the names of the identifiers under the Ident field of the node,
-// which might be a singleton (Identifier) or a slice (Field or Chain).
-func allIdents(node parse.Node) []string {
- switch node := node.(type) {
- case *parse.IdentifierNode:
- return []string{node.Ident}
- case *parse.FieldNode:
- return node.Ident
- case *parse.ChainNode:
- return node.Field
- }
- return nil
-}
-
-// ensurePipelineContains ensures that the pipeline has commands with
-// the identifiers in s in order.
-// If the pipeline already has some of the sanitizers, do not interfere.
-// For example, if p is (.X | html) and s is ["escapeJSVal", "html"] then it
-// has one matching, "html", and one to insert, "escapeJSVal", to produce
-// (.X | escapeJSVal | html).
+// ensurePipelineContains ensures that the pipeline ends with the commands with
+// the identifiers in s in order. If the pipeline ends with a predefined escaper
+// (i.e. "html" or "urlquery"), merge it with the identifiers in s.
func ensurePipelineContains(p *parse.PipeNode, s []string) {
if len(s) == 0 {
+ // Do not rewrite pipeline if we have no escapers to insert.
return
}
- n := len(p.Cmds)
- // Find the identifiers at the end of the command chain.
- idents := p.Cmds
- for i := n - 1; i >= 0; i-- {
- if cmd := p.Cmds[i]; len(cmd.Args) != 0 {
- if _, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
- continue
- }
- }
- idents = p.Cmds[i+1:]
- }
- dups := 0
- for _, idNode := range idents {
- for _, ident := range allIdents(idNode.Args[0]) {
- if escFnsEq(s[dups], ident) {
- dups++
- if dups == len(s) {
- return
+ // Precondition: p.Cmds contains at most one predefined escaper and the
+ // escaper will be present at p.Cmds[len(p.Cmds)-1]. This precondition is
+ // always true because of the checks in escapeAction.
+ pipelineLen := len(p.Cmds)
+ if pipelineLen > 0 {
+ lastCmd := p.Cmds[pipelineLen-1]
+ if idNode, ok := lastCmd.Args[0].(*parse.IdentifierNode); ok {
+ if esc := idNode.Ident; predefinedEscapers[esc] {
+ // Pipeline ends with a predefined escaper.
+ if len(p.Cmds) == 1 && len(lastCmd.Args) > 1 {
+ // Special case: pipeline is of the form {{ esc arg1 arg2 ... argN }},
+ // where esc is the predefined escaper, and arg1...argN are its arguments.
+ // Convert this into the equivalent form
+ // {{ _eval_args_ arg1 arg2 ... argN | esc }}, so that esc can be easily
+ // merged with the escapers in s.
+ lastCmd.Args[0] = parse.NewIdentifier("_eval_args_").SetTree(nil).SetPos(lastCmd.Args[0].Position())
+ p.Cmds = appendCmd(p.Cmds, newIdentCmd(esc, p.Position()))
+ pipelineLen++
}
- }
- }
- }
- newCmds := make([]*parse.CommandNode, n-len(idents), n+len(s)-dups)
- copy(newCmds, p.Cmds)
- // Merge existing identifier commands with the sanitizers needed.
- for _, idNode := range idents {
- pos := idNode.Args[0].Position()
- for _, ident := range allIdents(idNode.Args[0]) {
- i := indexOfStr(ident, s, escFnsEq)
- if i != -1 {
- for _, name := range s[:i] {
- newCmds = appendCmd(newCmds, newIdentCmd(name, pos))
+ // If any of the commands in s that we are about to insert is equivalent
+ // to the predefined escaper, use the predefined escaper instead.
+ dup := false
+ for i, escaper := range s {
+ if escFnsEq(esc, escaper) {
+ s[i] = idNode.Ident
+ dup = true
+ }
+ }
+ if dup {
+ // The predefined escaper will already be inserted along with the
+ // escapers in s, so do not copy it to the rewritten pipeline.
+ pipelineLen--
}
- s = s[i+1:]
}
}
- newCmds = appendCmd(newCmds, idNode)
}
- // Create any remaining sanitizers.
+ // Rewrite the pipeline, creating the escapers in s at the end of the pipeline.
+ newCmds := make([]*parse.CommandNode, pipelineLen, pipelineLen+len(s))
+ copy(newCmds, p.Cmds)
for _, name := range s {
newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position()))
}
p.Cmds = newCmds
}
+// predefinedEscapers contains template predefined escapers that are equivalent
+// to some contextual escapers. Keep in sync with equivEscapers.
+var predefinedEscapers = map[string]bool{
+ "html": true,
+ "urlquery": true,
+}
+
+// equivEscapers matches contextual escapers to equivalent predefined
+// template escapers.
+var equivEscapers = map[string]string{
+ // The following pairs of HTML escapers provide equivalent security
+ // guarantees, since they all escape '\000', '\'', '"', '&', '<', and '>'.
+ "_html_template_attrescaper": "html",
+ "_html_template_htmlescaper": "html",
+ "_html_template_rcdataescaper": "html",
+ // These two URL escapers produce URLs safe for embedding in a URL query by
+ // percent-encoding all the reserved characters specified in RFC 3986 Section
+ // 2.2
+ "_html_template_urlescaper": "urlquery",
+ // These two functions are not actually equivalent; urlquery is stricter as it
+ // escapes reserved characters (e.g. '#'), while _html_template_urlnormalizer
+ // does not. It is therefore only safe to replace _html_template_urlnormalizer
+ // with urlquery (this happens in ensurePipelineContains), but not the otherI've
+ // way around. We keep this entry around to preserve the behavior of templates
+ // written before Go 1.9, which might depend on this substitution taking place.
+ "_html_template_urlnormalizer": "urlquery",
+}
+
+// escFnsEq reports whether the two escaping functions are equivalent.
+func escFnsEq(a, b string) bool {
+ if e := equivEscapers[a]; e != "" {
+ a = e
+ }
+ if e := equivEscapers[b]; e != "" {
+ b = e
+ }
+ return a == b
+}
+
// redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
// for all x.
var redundantFuncs = map[string]map[string]bool{
@@ -318,17 +371,6 @@ func indexOfStr(s string, strs []string, eq func(a, b string) bool) int {
return -1
}
-// escFnsEq reports whether the two escaping functions are equivalent.
-func escFnsEq(a, b string) bool {
- if e := equivEscapers[a]; e != "" {
- a = e
- }
- if e := equivEscapers[b]; e != "" {
- b = e
- }
- return a == b
-}
-
// newIdentCmd produces a command containing a single identifier node.
func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode {
return &parse.CommandNode{
@@ -449,13 +491,13 @@ func (e *escaper) escapeList(c context, n *parse.ListNode) context {
// It returns the best guess at an output context, and the result of the filter
// which is the same as whether e was updated.
func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
- e1 := newEscaper(e.tmpl)
+ e1 := makeEscaper(e.ns)
// Make type inferences available to f.
for k, v := range e.output {
e1.output[k] = v
}
c = e1.escapeList(c, n)
- ok := filter != nil && filter(e1, c)
+ ok := filter != nil && filter(&e1, c)
if ok {
// Copy inferences and edits from e1 back into e.
for k, v := range e1.output {
@@ -504,7 +546,7 @@ func (e *escaper) escapeTree(c context, node parse.Node, name string, line int)
if t == nil {
// Two cases: The template exists but is empty, or has never been mentioned at
// all. Distinguish the cases in the error messages.
- if e.tmpl.set[name] != nil {
+ if e.ns.set[name] != nil {
return context{
state: stateError,
err: errorf(ErrNoSuchTemplate, node, line, "%q is an incomplete or empty template", name),
@@ -624,7 +666,7 @@ func (e *escaper) escapeText(c context, n *parse.TextNode) context {
// the entire comment is considered to be a
// LineTerminator for purposes of parsing by
// the syntactic grammar."
- if bytes.IndexAny(s[written:i1], "\n\r\u2028\u2029") != -1 {
+ if bytes.ContainsAny(s[written:i1], "\n\r\u2028\u2029") {
b.WriteByte('\n')
} else {
b.WriteByte(' ')
@@ -752,8 +794,11 @@ func (e *escaper) commit() {
for name := range e.output {
e.template(name).Funcs(funcMap)
}
+ // Any template from the name space associated with this escaper can be used
+ // to add derived templates to the underlying text/template name space.
+ tmpl := e.arbitraryTemplate()
for _, t := range e.derived {
- if _, err := e.tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil {
+ if _, err := tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil {
panic("error adding derived template")
}
}
@@ -766,17 +811,34 @@ func (e *escaper) commit() {
for n, s := range e.textNodeEdits {
n.Text = s
}
+ // Reset state that is specific to this commit so that the same changes are
+ // not re-applied to the template on subsequent calls to commit.
+ e.called = make(map[string]bool)
+ e.actionNodeEdits = make(map[*parse.ActionNode][]string)
+ e.templateNodeEdits = make(map[*parse.TemplateNode]string)
+ e.textNodeEdits = make(map[*parse.TextNode][]byte)
}
// template returns the named template given a mangled template name.
func (e *escaper) template(name string) *template.Template {
- t := e.tmpl.text.Lookup(name)
+ // Any template from the name space associated with this escaper can be used
+ // to look up templates in the underlying text/template name space.
+ t := e.arbitraryTemplate().text.Lookup(name)
if t == nil {
t = e.derived[name]
}
return t
}
+// arbitraryTemplate returns an arbitrary template from the name space
+// associated with e and panics if no templates are found.
+func (e *escaper) arbitraryTemplate() *Template {
+ for _, t := range e.ns.set {
+ return t
+ }
+ panic("no templates in name space")
+}
+
// Forwarding functions so that clients need only import this package
// to reach the general escaping functions of text/template.
diff --git a/libgo/go/html/template/escape_test.go b/libgo/go/html/template/escape_test.go
index f6ace496e7b..f5a4ce17364 100644
--- a/libgo/go/html/template/escape_test.go
+++ b/libgo/go/html/template/escape_test.go
@@ -359,7 +359,7 @@ func TestEscape(t *testing.T) {
{
"styleStrEncodedProtocolEncoded",
`<a style="background: '{{"javascript\\3a alert(1337)"}}'">`,
- // The CSS string 'javascript\\3a alert(1337)' does not contains a colon.
+ // The CSS string 'javascript\\3a alert(1337)' does not contain a colon.
`<a style="background: 'javascript\\3a alert\28 1337\29 '">`,
},
{
@@ -685,6 +685,40 @@ func TestEscape(t *testing.T) {
}
}
+func TestEscapeMap(t *testing.T) {
+ data := map[string]string{
+ "html": `<h1>Hi!</h1>`,
+ "urlquery": `http://www.foo.com/index.html?title=main`,
+ }
+ for _, test := range [...]struct {
+ desc, input, output string
+ }{
+ // covering issue 20323
+ {
+ "field with predefined escaper name 1",
+ `{{.html | print}}`,
+ `&lt;h1&gt;Hi!&lt;/h1&gt;`,
+ },
+ // covering issue 20323
+ {
+ "field with predefined escaper name 2",
+ `{{.urlquery | print}}`,
+ `http://www.foo.com/index.html?title=main`,
+ },
+ } {
+ tmpl := Must(New("").Parse(test.input))
+ b := new(bytes.Buffer)
+ if err := tmpl.Execute(b, data); err != nil {
+ t.Errorf("%s: template execution failed: %s", test.desc, err)
+ continue
+ }
+ if w, g := test.output, b.String(); w != g {
+ t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.desc, w, g)
+ continue
+ }
+ }
+}
+
func TestEscapeSet(t *testing.T) {
type dataItem struct {
Children []*dataItem
@@ -970,8 +1004,33 @@ func TestErrors(t *testing.T) {
`<a=foo>`,
`: expected space, attr name, or end of tag, but got "=foo>"`,
},
+ {
+ `Hello, {{. | urlquery | print}}!`,
+ // urlquery is disallowed if it is not the last command in the pipeline.
+ `predefined escaper "urlquery" disallowed in template`,
+ },
+ {
+ `Hello, {{. | html | print}}!`,
+ // html is disallowed if it is not the last command in the pipeline.
+ `predefined escaper "html" disallowed in template`,
+ },
+ {
+ `Hello, {{html . | print}}!`,
+ // A direct call to html is disallowed if it is not the last command in the pipeline.
+ `predefined escaper "html" disallowed in template`,
+ },
+ {
+ `<div class={{. | html}}>Hello<div>`,
+ // html is disallowed in a pipeline that is in an unquoted attribute context,
+ // even if it is the last command in the pipeline.
+ `predefined escaper "html" disallowed in template`,
+ },
+ {
+ `Hello, {{. | urlquery | html}}!`,
+ // html is allowed since it is the last command in the pipeline, but urlquery is not.
+ `predefined escaper "urlquery" disallowed in template`,
+ },
}
-
for _, test := range tests {
buf := new(bytes.Buffer)
tmpl, err := New("z").Parse(test.input)
@@ -1396,6 +1455,16 @@ func TestEscapeText(t *testing.T) {
`<script type="text/template">`,
context{state: stateText},
},
+ // covering issue 19968
+ {
+ `<script type="TEXT/JAVASCRIPT">`,
+ context{state: stateJS, element: elementScript},
+ },
+ // covering issue 19965
+ {
+ `<script TYPE="text/template">`,
+ context{state: stateText},
+ },
{
`<script type="notjs">`,
context{state: stateText},
@@ -1495,7 +1564,7 @@ func TestEscapeText(t *testing.T) {
}
for _, test := range tests {
- b, e := []byte(test.input), newEscaper(nil)
+ b, e := []byte(test.input), makeEscaper(nil)
c := e.escapeText(context{}, &parse.TextNode{NodeType: parse.NodeText, Text: b})
if !test.output.eq(c) {
t.Errorf("input %q: want context\n\t%v\ngot\n\t%v", test.input, test.output, c)
@@ -1529,24 +1598,24 @@ func TestEnsurePipelineContains(t *testing.T) {
[]string{"html"},
},
{
- "{{.X | html}}",
- ".X | html | urlquery",
- []string{"urlquery"},
+ "{{html .X}}",
+ "_eval_args_ .X | html | urlquery",
+ []string{"html", "urlquery"},
},
{
- "{{.X | html | urlquery}}",
- ".X | html | urlquery",
- []string{"urlquery"},
+ "{{html .X .Y .Z}}",
+ "_eval_args_ .X .Y .Z | html | urlquery",
+ []string{"html", "urlquery"},
},
{
- "{{.X | html | urlquery}}",
- ".X | html | urlquery",
- []string{"html", "urlquery"},
+ "{{.X | print}}",
+ ".X | print | urlquery",
+ []string{"urlquery"},
},
{
- "{{.X | html | urlquery}}",
- ".X | html | urlquery",
- []string{"html"},
+ "{{.X | print | urlquery}}",
+ ".X | print | urlquery",
+ []string{"urlquery"},
},
{
"{{.X | urlquery}}",
@@ -1554,50 +1623,69 @@ func TestEnsurePipelineContains(t *testing.T) {
[]string{"html", "urlquery"},
},
{
- "{{.X | html | print}}",
- ".X | urlquery | html | print",
+ "{{.X | print 2 | .f 3}}",
+ ".X | print 2 | .f 3 | urlquery | html",
[]string{"urlquery", "html"},
},
{
- "{{($).X | html | print}}",
- "($).X | urlquery | html | print",
+ // covering issue 10801
+ "{{.X | println.x }}",
+ ".X | println.x | urlquery | html",
[]string{"urlquery", "html"},
},
{
- "{{.X | print 2 | .f 3}}",
- ".X | print 2 | .f 3 | urlquery | html",
+ // covering issue 10801
+ "{{.X | (print 12 | println).x }}",
+ ".X | (print 12 | println).x | urlquery | html",
[]string{"urlquery", "html"},
},
+ // The following test cases ensure that the merging of internal escapers
+ // with the predefined "html" and "urlquery" escapers is correct.
{
- "{{.X | html | print 2 | .f 3}}",
- ".X | urlquery | html | print 2 | .f 3",
- []string{"urlquery", "html"},
+ "{{.X | urlquery}}",
+ ".X | _html_template_urlfilter | urlquery",
+ []string{"_html_template_urlfilter", "_html_template_urlnormalizer"},
},
{
- // covering issue 10801
- "{{.X | js.x }}",
- ".X | js.x | urlquery | html",
- []string{"urlquery", "html"},
+ "{{.X | urlquery}}",
+ ".X | urlquery | _html_template_urlfilter | _html_template_cssescaper",
+ []string{"_html_template_urlfilter", "_html_template_cssescaper"},
},
{
- // covering issue 10801
- "{{.X | (print 12 | js).x }}",
- ".X | (print 12 | js).x | urlquery | html",
- []string{"urlquery", "html"},
+ "{{.X | urlquery}}",
+ ".X | urlquery",
+ []string{"_html_template_urlnormalizer"},
+ },
+ {
+ "{{.X | urlquery}}",
+ ".X | urlquery",
+ []string{"_html_template_urlescaper"},
+ },
+ {
+ "{{.X | html}}",
+ ".X | html",
+ []string{"_html_template_htmlescaper"},
+ },
+ {
+ "{{.X | html}}",
+ ".X | html",
+ []string{"_html_template_rcdataescaper"},
},
}
for i, test := range tests {
tmpl := template.Must(template.New("test").Parse(test.input))
action, ok := (tmpl.Tree.Root.Nodes[0].(*parse.ActionNode))
if !ok {
- t.Errorf("#%d: First node is not an action: %s", i, test.input)
+ t.Errorf("First node is not an action: %s", test.input)
continue
}
pipe := action.Pipe
+ originalIDs := make([]string, len(test.ids))
+ copy(originalIDs, test.ids)
ensurePipelineContains(pipe, test.ids)
got := pipe.String()
if got != test.output {
- t.Errorf("#%d: %s, %v: want\n\t%s\ngot\n\t%s", i, test.input, test.ids, test.output, got)
+ t.Errorf("#%d: %s, %v: want\n\t%s\ngot\n\t%s", i, test.input, originalIDs, test.output, got)
}
}
}
@@ -1606,10 +1694,8 @@ func TestEscapeMalformedPipelines(t *testing.T) {
tests := []string{
"{{ 0 | $ }}",
"{{ 0 | $ | urlquery }}",
- "{{ 0 | $ | urlquery | html }}",
"{{ 0 | (nil) }}",
"{{ 0 | (nil) | html }}",
- "{{ 0 | (nil) | html | urlquery }}",
}
for _, test := range tests {
var b bytes.Buffer
@@ -1761,6 +1847,40 @@ func TestErrorOnUndefined(t *testing.T) {
}
}
+// This covers issue #20842.
+func TestIdempotentExecute(t *testing.T) {
+ tmpl := Must(New("").
+ Parse(`{{define "main"}}<body>{{template "hello"}}</body>{{end}}`))
+ Must(tmpl.
+ Parse(`{{define "hello"}}Hello, {{"Ladies & Gentlemen!"}}{{end}}`))
+ got := new(bytes.Buffer)
+ var err error
+ // Ensure that "hello" produces the same output when executed twice.
+ want := "Hello, Ladies &amp; Gentlemen!"
+ for i := 0; i < 2; i++ {
+ err = tmpl.ExecuteTemplate(got, "hello", nil)
+ if err != nil {
+ t.Errorf("unexpected error: %s", err)
+ }
+ if got.String() != want {
+ t.Errorf("after executing template \"hello\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want)
+ }
+ got.Reset()
+ }
+ // Ensure that the implicit re-execution of "hello" during the execution of
+ // "main" does not cause the output of "hello" to change.
+ err = tmpl.ExecuteTemplate(got, "main", nil)
+ if err != nil {
+ t.Errorf("unexpected error: %s", err)
+ }
+ // If the HTML escaper is added again to the action {{"Ladies & Gentlemen!"}},
+ // we would expected to see the ampersand overescaped to "&amp;amp;".
+ want = "<body>Hello, Ladies &amp; Gentlemen!</body>"
+ if got.String() != want {
+ t.Errorf("after executing template \"main\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want)
+ }
+}
+
func BenchmarkEscapedExecute(b *testing.B) {
tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`))
var buf bytes.Buffer
diff --git a/libgo/go/html/template/js.go b/libgo/go/html/template/js.go
index 6434fa3be63..239395f8d3a 100644
--- a/libgo/go/html/template/js.go
+++ b/libgo/go/html/template/js.go
@@ -372,7 +372,7 @@ func isJSType(mimeType string) bool {
// https://tools.ietf.org/html/rfc7231#section-3.1.1
// https://tools.ietf.org/html/rfc4329#section-3
// https://www.ietf.org/rfc/rfc4627.txt
-
+ mimeType = strings.ToLower(mimeType)
// discard parameters
if i := strings.Index(mimeType, ";"); i >= 0 {
mimeType = mimeType[:i]
diff --git a/libgo/go/html/template/template.go b/libgo/go/html/template/template.go
index b313a6b104a..6a661bf6e5d 100644
--- a/libgo/go/html/template/template.go
+++ b/libgo/go/html/template/template.go
@@ -36,6 +36,7 @@ type nameSpace struct {
mu sync.Mutex
set map[string]*Template
escaped bool
+ esc escaper
}
// Templates returns a slice of the templates associated with t, including t
@@ -112,7 +113,8 @@ func (t *Template) escape() error {
// If an error occurs executing the template or writing its output,
// execution stops, but partial results may already have been written to
// the output writer.
-// A template may be executed safely in parallel.
+// A template may be executed safely in parallel, although if parallel
+// executions share a Writer the output may be interleaved.
func (t *Template) Execute(wr io.Writer, data interface{}) error {
if err := t.escape(); err != nil {
return err
@@ -125,7 +127,8 @@ func (t *Template) Execute(wr io.Writer, data interface{}) error {
// If an error occurs executing the template or writing its output,
// execution stops, but partial results may already have been written to
// the output writer.
-// A template may be executed safely in parallel.
+// A template may be executed safely in parallel, although if parallel
+// executions share a Writer the output may be interleaved.
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
tmpl, err := t.lookupAndEscapeTemplate(name)
if err != nil {
@@ -248,13 +251,13 @@ func (t *Template) Clone() (*Template, error) {
if err != nil {
return nil, err
}
+ ns := &nameSpace{set: make(map[string]*Template)}
+ ns.esc = makeEscaper(ns)
ret := &Template{
nil,
textClone,
textClone.Tree,
- &nameSpace{
- set: make(map[string]*Template),
- },
+ ns,
}
ret.set[ret.Name()] = ret
for _, x := range textClone.Templates() {
@@ -277,13 +280,13 @@ func (t *Template) Clone() (*Template, error) {
// New allocates a new HTML template with the given name.
func New(name string) *Template {
+ ns := &nameSpace{set: make(map[string]*Template)}
+ ns.esc = makeEscaper(ns)
tmpl := &Template{
nil,
template.New(name),
nil,
- &nameSpace{
- set: make(map[string]*Template),
- },
+ ns,
}
tmpl.set[name] = tmpl
return tmpl
@@ -325,6 +328,7 @@ func (t *Template) Name() string {
type FuncMap map[string]interface{}
// Funcs adds the elements of the argument map to the template's function map.
+// It must be called before the template is parsed.
// It panics if a value in the map is not a function with appropriate return
// type. However, it is legal to overwrite elements of the map. The return
// value is the template, so calls can be chained.
diff --git a/libgo/go/html/template/transition.go b/libgo/go/html/template/transition.go
index 4a4716d7820..df7ac2289b4 100644
--- a/libgo/go/html/template/transition.go
+++ b/libgo/go/html/template/transition.go
@@ -106,7 +106,7 @@ func tTag(c context, s []byte) (context, int) {
}, len(s)
}
- attrName := string(s[i:j])
+ attrName := strings.ToLower(string(s[i:j]))
if c.element == elementScript && attrName == "type" {
attr = attrScriptType
} else {
@@ -246,7 +246,7 @@ func tAttr(c context, s []byte) (context, int) {
// tURL is the context transition function for the URL state.
func tURL(c context, s []byte) (context, int) {
- if bytes.IndexAny(s, "#?") >= 0 {
+ if bytes.ContainsAny(s, "#?") {
c.urlPart = urlPartQueryOrFrag
} else if len(s) != eatWhiteSpace(s, 0) && c.urlPart == urlPartNone {
// HTML5 uses "Valid URL potentially surrounded by spaces" for