content_test.go (13958B)
1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 //go:build go1.13 && !windows
6 // +build go1.13,!windows
7
8 package template
9
10 import (
11 "bytes"
12 "fmt"
13 htmltemplate "html/template"
14 "strings"
15 "testing"
16 )
17
18 func TestTypedContent(t *testing.T) {
19 data := []any{
20 `<b> "foo%" O'Reilly &bar;`,
21 htmltemplate.CSS(`a[href =~ "//example.com"]#foo`),
22 htmltemplate.HTML(`Hello, <b>World</b> &tc!`),
23 htmltemplate.HTMLAttr(` dir="ltr"`),
24 htmltemplate.JS(`c && alert("Hello, World!");`),
25 htmltemplate.JSStr(`Hello, World & O'Reilly\u0021`),
26 htmltemplate.URL(`greeting=H%69,&addressee=(World)`),
27 htmltemplate.Srcset(`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`),
28 htmltemplate.URL(`,foo/,`),
29 }
30
31 // For each content sensitive escaper, see how it does on
32 // each of the typed strings above.
33 tests := []struct {
34 // A template containing a single {{.}}.
35 input string
36 want []string
37 }{
38 {
39 `<style>{{.}} { color: blue }</style>`,
40 []string{
41 `ZgotmplZ`,
42 // Allowed but not escaped.
43 `a[href =~ "//example.com"]#foo`,
44 `ZgotmplZ`,
45 `ZgotmplZ`,
46 `ZgotmplZ`,
47 `ZgotmplZ`,
48 `ZgotmplZ`,
49 `ZgotmplZ`,
50 `ZgotmplZ`,
51 },
52 },
53 {
54 `<div style="{{.}}">`,
55 []string{
56 `ZgotmplZ`,
57 // Allowed and HTML escaped.
58 `a[href =~ "//example.com"]#foo`,
59 `ZgotmplZ`,
60 `ZgotmplZ`,
61 `ZgotmplZ`,
62 `ZgotmplZ`,
63 `ZgotmplZ`,
64 `ZgotmplZ`,
65 `ZgotmplZ`,
66 },
67 },
68 {
69 `{{.}}`,
70 []string{
71 `<b> "foo%" O'Reilly &bar;`,
72 `a[href =~ "//example.com"]#foo`,
73 // Not escaped.
74 `Hello, <b>World</b> &tc!`,
75 ` dir="ltr"`,
76 `c && alert("Hello, World!");`,
77 `Hello, World & O'Reilly\u0021`,
78 `greeting=H%69,&addressee=(World)`,
79 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
80 `,foo/,`,
81 },
82 },
83 {
84 `<a{{.}}>`,
85 []string{
86 `ZgotmplZ`,
87 `ZgotmplZ`,
88 `ZgotmplZ`,
89 // Allowed and HTML escaped.
90 ` dir="ltr"`,
91 `ZgotmplZ`,
92 `ZgotmplZ`,
93 `ZgotmplZ`,
94 `ZgotmplZ`,
95 `ZgotmplZ`,
96 },
97 },
98 {
99 `<a title={{.}}>`,
100 []string{
101 `<b> "foo%" O'Reilly &bar;`,
102 `a[href =~ "//example.com"]#foo`,
103 // Tags stripped, spaces escaped, entity not re-escaped.
104 `Hello, World &tc!`,
105 ` dir="ltr"`,
106 `c && alert("Hello, World!");`,
107 `Hello, World & O'Reilly\u0021`,
108 `greeting=H%69,&addressee=(World)`,
109 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
110 `,foo/,`,
111 },
112 },
113 {
114 `<a title='{{.}}'>`,
115 []string{
116 `<b> "foo%" O'Reilly &bar;`,
117 `a[href =~ "//example.com"]#foo`,
118 // Tags stripped, entity not re-escaped.
119 `Hello, World &tc!`,
120 ` dir="ltr"`,
121 `c && alert("Hello, World!");`,
122 `Hello, World & O'Reilly\u0021`,
123 `greeting=H%69,&addressee=(World)`,
124 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
125 `,foo/,`,
126 },
127 },
128 {
129 `<textarea>{{.}}</textarea>`,
130 []string{
131 `<b> "foo%" O'Reilly &bar;`,
132 `a[href =~ "//example.com"]#foo`,
133 // Angle brackets escaped to prevent injection of close tags, entity not re-escaped.
134 `Hello, <b>World</b> &tc!`,
135 ` dir="ltr"`,
136 `c && alert("Hello, World!");`,
137 `Hello, World & O'Reilly\u0021`,
138 `greeting=H%69,&addressee=(World)`,
139 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
140 `,foo/,`,
141 },
142 },
143 {
144 `<script>alert({{.}})</script>`,
145 []string{
146 `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
147 `"a[href =~ \"//example.com\"]#foo"`,
148 `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
149 `" dir=\"ltr\""`,
150 // Not escaped.
151 `c && alert("Hello, World!");`,
152 // Escape sequence not over-escaped.
153 `"Hello, World & O'Reilly\u0021"`,
154 `"greeting=H%69,\u0026addressee=(World)"`,
155 `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
156 `",foo/,"`,
157 },
158 },
159 {
160 `<button onclick="alert({{.}})">`,
161 []string{
162 `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
163 `"a[href =~ \"//example.com\"]#foo"`,
164 `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
165 `" dir=\"ltr\""`,
166 // Not JS escaped but HTML escaped.
167 `c && alert("Hello, World!");`,
168 // Escape sequence not over-escaped.
169 `"Hello, World & O'Reilly\u0021"`,
170 `"greeting=H%69,\u0026addressee=(World)"`,
171 `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
172 `",foo/,"`,
173 },
174 },
175 {
176 `<script>alert("{{.}}")</script>`,
177 []string{
178 `\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
179 `a[href =~ \u0022\/\/example.com\u0022]#foo`,
180 `Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
181 ` dir=\u0022ltr\u0022`,
182 `c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
183 // Escape sequence not over-escaped.
184 `Hello, World \u0026 O\u0027Reilly\u0021`,
185 `greeting=H%69,\u0026addressee=(World)`,
186 `greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
187 `,foo\/,`,
188 },
189 },
190 {
191 `<script type="text/javascript">alert("{{.}}")</script>`,
192 []string{
193 `\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
194 `a[href =~ \u0022\/\/example.com\u0022]#foo`,
195 `Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
196 ` dir=\u0022ltr\u0022`,
197 `c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
198 // Escape sequence not over-escaped.
199 `Hello, World \u0026 O\u0027Reilly\u0021`,
200 `greeting=H%69,\u0026addressee=(World)`,
201 `greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
202 `,foo\/,`,
203 },
204 },
205 {
206 `<script type="text/javascript">alert({{.}})</script>`,
207 []string{
208 `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
209 `"a[href =~ \"//example.com\"]#foo"`,
210 `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
211 `" dir=\"ltr\""`,
212 // Not escaped.
213 `c && alert("Hello, World!");`,
214 // Escape sequence not over-escaped.
215 `"Hello, World & O'Reilly\u0021"`,
216 `"greeting=H%69,\u0026addressee=(World)"`,
217 `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
218 `",foo/,"`,
219 },
220 },
221 {
222 // Not treated as JS. The output is same as for <div>{{.}}</div>
223 `<script type="text/template">{{.}}</script>`,
224 []string{
225 `<b> "foo%" O'Reilly &bar;`,
226 `a[href =~ "//example.com"]#foo`,
227 // Not escaped.
228 `Hello, <b>World</b> &tc!`,
229 ` dir="ltr"`,
230 `c && alert("Hello, World!");`,
231 `Hello, World & O'Reilly\u0021`,
232 `greeting=H%69,&addressee=(World)`,
233 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
234 `,foo/,`,
235 },
236 },
237 {
238 `<button onclick='alert("{{.}}")'>`,
239 []string{
240 `\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
241 `a[href =~ \u0022\/\/example.com\u0022]#foo`,
242 `Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
243 ` dir=\u0022ltr\u0022`,
244 `c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
245 // Escape sequence not over-escaped.
246 `Hello, World \u0026 O\u0027Reilly\u0021`,
247 `greeting=H%69,\u0026addressee=(World)`,
248 `greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
249 `,foo\/,`,
250 },
251 },
252 {
253 `<a href="?q={{.}}">`,
254 []string{
255 `%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
256 `a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
257 `Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
258 `%20dir%3d%22ltr%22`,
259 `c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
260 `Hello%2c%20World%20%26%20O%27Reilly%5cu0021`,
261 // Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is done.
262 `greeting=H%69,&addressee=%28World%29`,
263 `greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
264 `,foo/,`,
265 },
266 },
267 {
268 `<style>body { background: url('?img={{.}}') }</style>`,
269 []string{
270 `%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
271 `a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
272 `Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
273 `%20dir%3d%22ltr%22`,
274 `c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
275 `Hello%2c%20World%20%26%20O%27Reilly%5cu0021`,
276 // Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is not done.
277 `greeting=H%69,&addressee=%28World%29`,
278 `greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
279 `,foo/,`,
280 },
281 },
282 {
283 `<img srcset="{{.}}">`,
284 []string{
285 `#ZgotmplZ`,
286 `#ZgotmplZ`,
287 // Commas are not esacped
288 `Hello,#ZgotmplZ`,
289 // Leading spaces are not percent escapes.
290 ` dir=%22ltr%22`,
291 // Spaces after commas are not percent escaped.
292 `#ZgotmplZ, World!%22%29;`,
293 `Hello,#ZgotmplZ`,
294 `greeting=H%69%2c&addressee=%28World%29`,
295 // Metadata is not escaped.
296 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
297 `%2cfoo/%2c`,
298 },
299 },
300 {
301 `<img srcset={{.}}>`,
302 []string{
303 `#ZgotmplZ`,
304 `#ZgotmplZ`,
305 `Hello,#ZgotmplZ`,
306 // Spaces are HTML escaped not %-escaped
307 ` dir=%22ltr%22`,
308 `#ZgotmplZ, World!%22%29;`,
309 `Hello,#ZgotmplZ`,
310 `greeting=H%69%2c&addressee=%28World%29`,
311 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
312 // Commas are escaped.
313 `%2cfoo/%2c`,
314 },
315 },
316 {
317 `<img srcset="{{.}} 2x, https://golang.org/ 500.5w">`,
318 []string{
319 `#ZgotmplZ`,
320 `#ZgotmplZ`,
321 `Hello,#ZgotmplZ`,
322 ` dir=%22ltr%22`,
323 `#ZgotmplZ, World!%22%29;`,
324 `Hello,#ZgotmplZ`,
325 `greeting=H%69%2c&addressee=%28World%29`,
326 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
327 `%2cfoo/%2c`,
328 },
329 },
330 {
331 `<img srcset="http://godoc.org/ {{.}}, https://golang.org/ 500.5w">`,
332 []string{
333 `#ZgotmplZ`,
334 `#ZgotmplZ`,
335 `Hello,#ZgotmplZ`,
336 ` dir=%22ltr%22`,
337 `#ZgotmplZ, World!%22%29;`,
338 `Hello,#ZgotmplZ`,
339 `greeting=H%69%2c&addressee=%28World%29`,
340 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
341 `%2cfoo/%2c`,
342 },
343 },
344 {
345 `<img srcset="http://godoc.org/?q={{.}} 2x, https://golang.org/ 500.5w">`,
346 []string{
347 `#ZgotmplZ`,
348 `#ZgotmplZ`,
349 `Hello,#ZgotmplZ`,
350 ` dir=%22ltr%22`,
351 `#ZgotmplZ, World!%22%29;`,
352 `Hello,#ZgotmplZ`,
353 `greeting=H%69%2c&addressee=%28World%29`,
354 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
355 `%2cfoo/%2c`,
356 },
357 },
358 {
359 `<img srcset="http://godoc.org/ 2x, {{.}} 500.5w">`,
360 []string{
361 `#ZgotmplZ`,
362 `#ZgotmplZ`,
363 `Hello,#ZgotmplZ`,
364 ` dir=%22ltr%22`,
365 `#ZgotmplZ, World!%22%29;`,
366 `Hello,#ZgotmplZ`,
367 `greeting=H%69%2c&addressee=%28World%29`,
368 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
369 `%2cfoo/%2c`,
370 },
371 },
372 {
373 `<img srcset="http://godoc.org/ 2x, https://golang.org/ {{.}}">`,
374 []string{
375 `#ZgotmplZ`,
376 `#ZgotmplZ`,
377 `Hello,#ZgotmplZ`,
378 ` dir=%22ltr%22`,
379 `#ZgotmplZ, World!%22%29;`,
380 `Hello,#ZgotmplZ`,
381 `greeting=H%69%2c&addressee=%28World%29`,
382 `greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
383 `%2cfoo/%2c`,
384 },
385 },
386 }
387
388 for _, test := range tests {
389 tmpl := Must(New("x").Parse(test.input))
390 pre := strings.Index(test.input, "{{.}}")
391 post := len(test.input) - (pre + 5)
392 var b bytes.Buffer
393 for i, x := range data {
394 b.Reset()
395 if err := tmpl.Execute(&b, x); err != nil {
396 t.Errorf("%q with %v: %s", test.input, x, err)
397 continue
398 }
399 if want, got := test.want[i], b.String()[pre:b.Len()-post]; want != got {
400 t.Errorf("%q with %v:\nwant\n\t%q,\ngot\n\t%q\n", test.input, x, want, got)
401 continue
402 }
403 }
404 }
405 }
406
407 // Test that we print using the String method. Was issue 3073.
408 type myStringer struct {
409 v int
410 }
411
412 func (s *myStringer) String() string {
413 return fmt.Sprintf("string=%d", s.v)
414 }
415
416 type errorer struct {
417 v int
418 }
419
420 func (s *errorer) Error() string {
421 return fmt.Sprintf("error=%d", s.v)
422 }
423
424 func TestStringer(t *testing.T) {
425 s := &myStringer{3}
426 b := new(bytes.Buffer)
427 tmpl := Must(New("x").Parse("{{.}}"))
428 if err := tmpl.Execute(b, s); err != nil {
429 t.Fatal(err)
430 }
431 var expect = "string=3"
432 if b.String() != expect {
433 t.Errorf("expected %q got %q", expect, b.String())
434 }
435 e := &errorer{7}
436 b.Reset()
437 if err := tmpl.Execute(b, e); err != nil {
438 t.Fatal(err)
439 }
440 expect = "error=7"
441 if b.String() != expect {
442 t.Errorf("expected %q got %q", expect, b.String())
443 }
444 }
445
446 // https://golang.org/issue/5982
447 func TestEscapingNilNonemptyInterfaces(t *testing.T) {
448 tmpl := Must(New("x").Parse("{{.E}}"))
449
450 got := new(bytes.Buffer)
451 testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand
452 tmpl.Execute(got, testData)
453
454 // A non-empty interface should print like an empty interface.
455 want := new(bytes.Buffer)
456 data := struct{ E any }{}
457 tmpl.Execute(want, data)
458
459 if !bytes.Equal(want.Bytes(), got.Bytes()) {
460 t.Errorf("expected %q got %q", string(want.Bytes()), string(got.Bytes()))
461 }
462 }