...

Source file src/html/template/content_test.go

Documentation: html/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\u0021`),
		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\u0021`,
		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\u0021`,
	 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\u0021`,
	 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\u0021`,
	 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\u0021"`,
	 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\u0021&#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  				`\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
	 175  				`a[href =~ \u0022\/\/example.com\u0022]#foo`,
	 176  				`Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
	 177  				` dir=\u0022ltr\u0022`,
	 178  				`c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
	 179  				// Escape sequence not over-escaped.
	 180  				`Hello, World \u0026 O\u0027Reilly\u0021`,
	 181  				`greeting=H%69,\u0026addressee=(World)`,
	 182  				`greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
	 183  				`,foo\/,`,
	 184  			},
	 185  		},
	 186  		{
	 187  			`<script type="text/javascript">alert("{{.}}")</script>`,
	 188  			[]string{
	 189  				`\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
	 190  				`a[href =~ \u0022\/\/example.com\u0022]#foo`,
	 191  				`Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
	 192  				` dir=\u0022ltr\u0022`,
	 193  				`c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
	 194  				// Escape sequence not over-escaped.
	 195  				`Hello, World \u0026 O\u0027Reilly\u0021`,
	 196  				`greeting=H%69,\u0026addressee=(World)`,
	 197  				`greeting=H%69,\u0026addressee=(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\u0021"`,
	 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\u0021`,
	 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  				`\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
	 237  				`a[href =~ \u0022\/\/example.com\u0022]#foo`,
	 238  				`Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
	 239  				` dir=\u0022ltr\u0022`,
	 240  				`c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
	 241  				// Escape sequence not over-escaped.
	 242  				`Hello, World \u0026 O\u0027Reilly\u0021`,
	 243  				`greeting=H%69,\u0026addressee=(World)`,
	 244  				`greeting=H%69,\u0026addressee=(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%5cu0021`,
	 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%5cu0021`,
	 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 myStringer struct {
	 405  	v int
	 406  }
	 407  
	 408  func (s *myStringer) 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 := &myStringer{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  	// 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  

View as plain text