Commit c5cb4843 authored by Ian Lance Taylor's avatar Ian Lance Taylor

html/template: ignore untyped nil arguments to default escapers

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 <iant@golang.org>
Reviewed-by: 's avatarDaniel Martí <mvdan@mvdan.cc>
Reviewed-by: 's avatarRuss Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent a67c481f
...@@ -169,8 +169,17 @@ func stringify(args ...interface{}) (string, contentType) { ...@@ -169,8 +169,17 @@ func stringify(args ...interface{}) (string, contentType) {
return string(s), contentTypeSrcset 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) args[i] = indirectToStringerOrError(arg)
i++
} }
return fmt.Sprint(args...), contentTypePlain return fmt.Sprint(args[:i]...), contentTypePlain
} }
...@@ -447,10 +447,9 @@ func TestEscapingNilNonemptyInterfaces(t *testing.T) { ...@@ -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 testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand
tmpl.Execute(got, testData) tmpl.Execute(got, testData)
// Use this data instead of just hard-coding "&lt;nil&gt;" to avoid // A non-empty interface should print like an empty interface.
// dependencies on the html escaper and the behavior of fmt w.r.t. nil.
want := new(bytes.Buffer) want := new(bytes.Buffer)
data := struct{ E string }{E: fmt.Sprint(nil)} data := struct{ E interface{} }{}
tmpl.Execute(want, data) tmpl.Execute(want, data)
if !bytes.Equal(want.Bytes(), got.Bytes()) { if !bytes.Equal(want.Bytes(), got.Bytes()) {
......
...@@ -70,6 +70,9 @@ In this case it becomes ...@@ -70,6 +70,9 @@ In this case it becomes
where urlescaper, attrescaper, and htmlescaper are aliases for internal escaping where urlescaper, attrescaper, and htmlescaper are aliases for internal escaping
functions. 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 Errors
See the documentation of ErrorCode for details. See the documentation of ErrorCode for details.
......
...@@ -35,7 +35,8 @@ func TestEscape(t *testing.T) { ...@@ -35,7 +35,8 @@ func TestEscape(t *testing.T) {
A, E []string A, E []string
B, M json.Marshaler B, M json.Marshaler
N int N int
Z *int U interface{} // untyped nil
Z *int // typed nil
W HTML W HTML
}{ }{
F: false, F: false,
...@@ -48,6 +49,7 @@ func TestEscape(t *testing.T) { ...@@ -48,6 +49,7 @@ func TestEscape(t *testing.T) {
N: 42, N: 42,
B: &badMarshaler{}, B: &badMarshaler{},
M: &goodMarshaler{}, M: &goodMarshaler{},
U: nil,
Z: nil, Z: nil,
W: HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`), W: HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
} }
...@@ -113,6 +115,16 @@ func TestEscape(t *testing.T) { ...@@ -113,6 +115,16 @@ func TestEscape(t *testing.T) {
"{{.T}}", "{{.T}}",
"true", "true",
}, },
{
"untypedNilValue",
"{{.U}}",
"",
},
{
"typedNilValue",
"{{.Z}}",
"&lt;nil&gt;",
},
{ {
"constant", "constant",
`<a href="/search?q={{"'a<b'"}}">`, `<a href="/search?q={{"'a<b'"}}">`,
...@@ -199,10 +211,15 @@ func TestEscape(t *testing.T) { ...@@ -199,10 +211,15 @@ func TestEscape(t *testing.T) {
`<button onclick='alert( true )'>`, `<button onclick='alert( true )'>`,
}, },
{ {
"jsNilValue", "jsNilValueTyped",
"<button onclick='alert(typeof{{.Z}})'>", "<button onclick='alert(typeof{{.Z}})'>",
`<button onclick='alert(typeof null )'>`, `<button onclick='alert(typeof null )'>`,
}, },
{
"jsNilValueUntyped",
"<button onclick='alert(typeof{{.U}})'>",
`<button onclick='alert(typeof null )'>`,
},
{ {
"jsObjValue", "jsObjValue",
"<button onclick='alert({{.A}})'>", "<button onclick='alert({{.A}})'>",
......
...@@ -448,6 +448,8 @@ var execTests = []execTest{ ...@@ -448,6 +448,8 @@ var execTests = []execTest{
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`, {"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
"&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true}, "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
{"html", `{{html .PS}}`, "a string", tVal, 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. // JavaScript.
{"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true}, {"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment