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