Skip to content

Commit c5cb484

Browse files
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 <[email protected]> Reviewed-by: Daniel Martí <[email protected]> Reviewed-by: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent a67c481 commit c5cb484

File tree

5 files changed

+37
-7
lines changed

5 files changed

+37
-7
lines changed

src/html/template/content.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,17 @@ func stringify(args ...interface{}) (string, contentType) {
169169
return string(s), contentTypeSrcset
170170
}
171171
}
172-
for i, arg := range args {
172+
i := 0
173+
for _, arg := range args {
174+
// We skip untyped nil arguments for backward compatibility.
175+
// Without this they would be output as <nil>, escaped.
176+
// See issue 25875.
177+
if arg == nil {
178+
continue
179+
}
180+
173181
args[i] = indirectToStringerOrError(arg)
182+
i++
174183
}
175-
return fmt.Sprint(args...), contentTypePlain
184+
return fmt.Sprint(args[:i]...), contentTypePlain
176185
}

src/html/template/content_test.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -447,10 +447,9 @@ func TestEscapingNilNonemptyInterfaces(t *testing.T) {
447447
testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand
448448
tmpl.Execute(got, testData)
449449

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

456455
if !bytes.Equal(want.Bytes(), got.Bytes()) {

src/html/template/doc.go

+3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ In this case it becomes
7070
where urlescaper, attrescaper, and htmlescaper are aliases for internal escaping
7171
functions.
7272
73+
For these internal escaping functions, if an action pipeline evaluates to
74+
a nil interface value, it is treated as though it were an empty string.
75+
7376
Errors
7477
7578
See the documentation of ErrorCode for details.

src/html/template/escape_test.go

+19-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ func TestEscape(t *testing.T) {
3535
A, E []string
3636
B, M json.Marshaler
3737
N int
38-
Z *int
38+
U interface{} // untyped nil
39+
Z *int // typed nil
3940
W HTML
4041
}{
4142
F: false,
@@ -48,6 +49,7 @@ func TestEscape(t *testing.T) {
4849
N: 42,
4950
B: &badMarshaler{},
5051
M: &goodMarshaler{},
52+
U: nil,
5153
Z: nil,
5254
W: HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
5355
}
@@ -113,6 +115,16 @@ func TestEscape(t *testing.T) {
113115
"{{.T}}",
114116
"true",
115117
},
118+
{
119+
"untypedNilValue",
120+
"{{.U}}",
121+
"",
122+
},
123+
{
124+
"typedNilValue",
125+
"{{.Z}}",
126+
"&lt;nil&gt;",
127+
},
116128
{
117129
"constant",
118130
`<a href="/search?q={{"'a<b'"}}">`,
@@ -199,10 +211,15 @@ func TestEscape(t *testing.T) {
199211
`<button onclick='alert( true )'>`,
200212
},
201213
{
202-
"jsNilValue",
214+
"jsNilValueTyped",
203215
"<button onclick='alert(typeof{{.Z}})'>",
204216
`<button onclick='alert(typeof null )'>`,
205217
},
218+
{
219+
"jsNilValueUntyped",
220+
"<button onclick='alert(typeof{{.U}})'>",
221+
`<button onclick='alert(typeof null )'>`,
222+
},
206223
{
207224
"jsObjValue",
208225
"<button onclick='alert({{.A}})'>",

src/text/template/exec_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ var execTests = []execTest{
448448
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
449449
"&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
450450
{"html", `{{html .PS}}`, "a string", tVal, true},
451+
{"html typed nil", `{{html .NIL}}`, "&lt;nil&gt;", tVal, true},
452+
{"html untyped nil", `{{html .Empty0}}`, "&lt;no value&gt;", tVal, true},
451453

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

0 commit comments

Comments
 (0)