Skip to content

Commit 882a640

Browse files
Samuel Tanrsc
Samuel Tan
authored andcommitted
html/template: only search identifier nodes for predefined escapers
Predefined escapers (i.e. "html" and "urlquery") should only occur in Identifier nodes, and never in Field or Chain nodes, since these are global functions that return string values (see inline comments for more details). Therefore, skip Chain and Field nodes when searching for predefined escapers in template pipelines. Also, make a non-functional change two existing test cases to avoid giving the impression that it is valid to reference a field of a predefined escaper. Fixes #20323 Change-Id: I34f722f443c778699fcdd575dc3e0fd1fd6f2eb3 Reviewed-on: https://go-review.googlesource.com/43296 Reviewed-by: Samuel Tan <[email protected]> Reviewed-by: Mike Samuel <[email protected]> Reviewed-by: Russ Cox <[email protected]> Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 2c3c8c4 commit 882a640

File tree

2 files changed

+56
-26
lines changed

2 files changed

+56
-26
lines changed

src/html/template/escape.go

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -139,20 +139,6 @@ func (e *escaper) escape(c context, n parse.Node) context {
139139
panic("escaping " + n.String() + " is unimplemented")
140140
}
141141

142-
// allIdents returns the names of the identifiers under the Ident field of the node,
143-
// which might be a singleton (Identifier) or a slice (Field or Chain).
144-
func allIdents(node parse.Node) []string {
145-
switch node := node.(type) {
146-
case *parse.IdentifierNode:
147-
return []string{node.Ident}
148-
case *parse.FieldNode:
149-
return node.Ident
150-
case *parse.ChainNode:
151-
return node.Field
152-
}
153-
return nil
154-
}
155-
156142
// escapeAction escapes an action template node.
157143
func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
158144
if len(n.Pipe.Decl) != 0 {
@@ -162,14 +148,24 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
162148
c = nudge(c)
163149
// Check for disallowed use of predefined escapers in the pipeline.
164150
for pos, idNode := range n.Pipe.Cmds {
165-
for _, ident := range allIdents(idNode.Args[0]) {
166-
if _, ok := predefinedEscapers[ident]; ok {
167-
if pos < len(n.Pipe.Cmds)-1 ||
168-
c.state == stateAttr && c.delim == delimSpaceOrTagEnd && ident == "html" {
169-
return context{
170-
state: stateError,
171-
err: errorf(ErrPredefinedEscaper, n, n.Line, "predefined escaper %q disallowed in template", ident),
172-
}
151+
node, ok := idNode.Args[0].(*parse.IdentifierNode)
152+
if !ok {
153+
// A predefined escaper "esc" will never be found as an identifier in a
154+
// Chain or Field node, since:
155+
// - "esc.x ..." is invalid, since predefined escapers return strings, and
156+
// strings do not have methods, keys or fields.
157+
// - "... .esc" is invalid, since predefined escapers are global functions,
158+
// not methods or fields of any types.
159+
// Therefore, it is safe to ignore these two node types.
160+
continue
161+
}
162+
ident := node.Ident
163+
if _, ok := predefinedEscapers[ident]; ok {
164+
if pos < len(n.Pipe.Cmds)-1 ||
165+
c.state == stateAttr && c.delim == delimSpaceOrTagEnd && ident == "html" {
166+
return context{
167+
state: stateError,
168+
err: errorf(ErrPredefinedEscaper, n, n.Line, "predefined escaper %q disallowed in template", ident),
173169
}
174170
}
175171
}

src/html/template/escape_test.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,40 @@ func TestEscape(t *testing.T) {
685685
}
686686
}
687687

688+
func TestEscapeMap(t *testing.T) {
689+
data := map[string]string{
690+
"html": `<h1>Hi!</h1>`,
691+
"urlquery": `http://www.foo.com/index.html?title=main`,
692+
}
693+
for _, test := range [...]struct {
694+
desc, input, output string
695+
}{
696+
// covering issue 20323
697+
{
698+
"field with predefined escaper name 1",
699+
`{{.html | print}}`,
700+
`&lt;h1&gt;Hi!&lt;/h1&gt;`,
701+
},
702+
// covering issue 20323
703+
{
704+
"field with predefined escaper name 2",
705+
`{{.urlquery | print}}`,
706+
`http://www.foo.com/index.html?title=main`,
707+
},
708+
} {
709+
tmpl := Must(New("").Parse(test.input))
710+
b := new(bytes.Buffer)
711+
if err := tmpl.Execute(b, data); err != nil {
712+
t.Errorf("%s: template execution failed: %s", test.desc, err)
713+
continue
714+
}
715+
if w, g := test.output, b.String(); w != g {
716+
t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.desc, w, g)
717+
continue
718+
}
719+
}
720+
}
721+
688722
func TestEscapeSet(t *testing.T) {
689723
type dataItem struct {
690724
Children []*dataItem
@@ -1595,14 +1629,14 @@ func TestEnsurePipelineContains(t *testing.T) {
15951629
},
15961630
{
15971631
// covering issue 10801
1598-
"{{.X | js.x }}",
1599-
".X | js.x | urlquery | html",
1632+
"{{.X | println.x }}",
1633+
".X | println.x | urlquery | html",
16001634
[]string{"urlquery", "html"},
16011635
},
16021636
{
16031637
// covering issue 10801
1604-
"{{.X | (print 12 | js).x }}",
1605-
".X | (print 12 | js).x | urlquery | html",
1638+
"{{.X | (print 12 | println).x }}",
1639+
".X | (print 12 | println).x | urlquery | html",
16061640
[]string{"urlquery", "html"},
16071641
},
16081642
// The following test cases ensure that the merging of internal escapers

0 commit comments

Comments
 (0)