Skip to content

Commit

Permalink
html/template: ignore untyped nil arguments to default escapers
Browse files Browse the repository at this point in the history
CL 95215 changed text/template so that untyped nil arguments were no
longer ignored, but were instead passed to functions as expected.
This had an unexpected effect on html/template, where all data is
implicitly passed to functions: originally untyped nil arguments were
not passed and were thus effectively ignored, but after CL 95215 they
were passed and were printed, typically as an escaped version of "<nil>".

This CL restores some of the behavior of html/template by ignoring
untyped nil arguments passed implicitly to escaper functions.

While eliminating one change to html/template relative to earlier
releases, this unfortunately introduces a different one: originally
values of interface type with the value nil were printed as an escaped
version of "<nil>". With this CL they are ignored as though they were
untyped nil values. My judgement is that this is a less common case.
We'll see.

This CL adds some tests of typed and untyped nil values to
html/template and text/template to capture the current behavior.

Updates #18716
Fixes #25875

Change-Id: I5912983ca32b31ece29e929e72d503b54d7b0cac
Reviewed-on: https://go-review.googlesource.com/121815
Run-TryBot: Ian Lance Taylor <[email protected]>
Reviewed-by: Daniel Martí <[email protected]>
Reviewed-by: Russ Cox <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
  • Loading branch information
ianlancetaylor committed Jul 9, 2018
1 parent a67c481 commit c5cb484
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 7 deletions.
13 changes: 11 additions & 2 deletions src/html/template/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,17 @@ func stringify(args ...interface{}) (string, contentType) {
return string(s), contentTypeSrcset
}
}
for i, arg := range args {
i := 0
for _, arg := range args {
// We skip untyped nil arguments for backward compatibility.
// Without this they would be output as <nil>, escaped.
// See issue 25875.
if arg == nil {
continue
}

args[i] = indirectToStringerOrError(arg)
i++
}
return fmt.Sprint(args...), contentTypePlain
return fmt.Sprint(args[:i]...), contentTypePlain
}
5 changes: 2 additions & 3 deletions src/html/template/content_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,9 @@ func TestEscapingNilNonemptyInterfaces(t *testing.T) {
testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand
tmpl.Execute(got, testData)

// Use this data instead of just hard-coding "&lt;nil&gt;" to avoid
// dependencies on the html escaper and the behavior of fmt w.r.t. nil.
// A non-empty interface should print like an empty interface.
want := new(bytes.Buffer)
data := struct{ E string }{E: fmt.Sprint(nil)}
data := struct{ E interface{} }{}
tmpl.Execute(want, data)

if !bytes.Equal(want.Bytes(), got.Bytes()) {
Expand Down
3 changes: 3 additions & 0 deletions src/html/template/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ In this case it becomes
where urlescaper, attrescaper, and htmlescaper are aliases for internal escaping
functions.
For these internal escaping functions, if an action pipeline evaluates to
a nil interface value, it is treated as though it were an empty string.
Errors
See the documentation of ErrorCode for details.
Expand Down
21 changes: 19 additions & 2 deletions src/html/template/escape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ func TestEscape(t *testing.T) {
A, E []string
B, M json.Marshaler
N int
Z *int
U interface{} // untyped nil
Z *int // typed nil
W HTML
}{
F: false,
Expand All @@ -48,6 +49,7 @@ func TestEscape(t *testing.T) {
N: 42,
B: &badMarshaler{},
M: &goodMarshaler{},
U: nil,
Z: nil,
W: HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
}
Expand Down Expand Up @@ -113,6 +115,16 @@ func TestEscape(t *testing.T) {
"{{.T}}",
"true",
},
{
"untypedNilValue",
"{{.U}}",
"",
},
{
"typedNilValue",
"{{.Z}}",
"&lt;nil&gt;",
},
{
"constant",
`<a href="/search?q={{"'a<b'"}}">`,
Expand Down Expand Up @@ -199,10 +211,15 @@ func TestEscape(t *testing.T) {
`<button onclick='alert( true )'>`,
},
{
"jsNilValue",
"jsNilValueTyped",
"<button onclick='alert(typeof{{.Z}})'>",
`<button onclick='alert(typeof null )'>`,
},
{
"jsNilValueUntyped",
"<button onclick='alert(typeof{{.U}})'>",
`<button onclick='alert(typeof null )'>`,
},
{
"jsObjValue",
"<button onclick='alert({{.A}})'>",
Expand Down
2 changes: 2 additions & 0 deletions src/text/template/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ var execTests = []execTest{
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
"&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
{"html", `{{html .PS}}`, "a string", tVal, true},
{"html typed nil", `{{html .NIL}}`, "&lt;nil&gt;", tVal, true},
{"html untyped nil", `{{html .Empty0}}`, "&lt;no value&gt;", tVal, true},

// JavaScript.
{"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true},
Expand Down

0 comments on commit c5cb484

Please sign in to comment.