Home | History | Annotate | Download | only in template
      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 package template
      6 
      7 import (
      8 	"bytes"
      9 	"fmt"
     10 	"strings"
     11 	"testing"
     12 )
     13 
     14 func 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.
    404 type stringer struct {
    405 	v int
    406 }
    407 
    408 func (s *stringer) String() string {
    409 	return fmt.Sprintf("string=%d", s.v)
    410 }
    411 
    412 type errorer struct {
    413 	v int
    414 }
    415 
    416 func (s *errorer) Error() string {
    417 	return fmt.Sprintf("error=%d", s.v)
    418 }
    419 
    420 func 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
    443 func 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 	// 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.
    452 	want := new(bytes.Buffer)
    453 	data := struct{ E string }{E: fmt.Sprint(nil)}
    454 	tmpl.Execute(want, data)
    455 
    456 	if !bytes.Equal(want.Bytes(), got.Bytes()) {
    457 		t.Errorf("expected %q got %q", string(want.Bytes()), string(got.Bytes()))
    458 	}
    459 }
    460