...

Source file src/html/template/escape_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  	"encoding/json"
		10  	"fmt"
		11  	"os"
		12  	"strings"
		13  	"testing"
		14  	"text/template"
		15  	"text/template/parse"
		16  )
		17  
		18  type badMarshaler struct{}
		19  
		20  func (x *badMarshaler) MarshalJSON() ([]byte, error) {
		21  	// Keys in valid JSON must be double quoted as must all strings.
		22  	return []byte("{ foo: 'not quite valid JSON' }"), nil
		23  }
		24  
		25  type goodMarshaler struct{}
		26  
		27  func (x *goodMarshaler) MarshalJSON() ([]byte, error) {
		28  	return []byte(`{ "<foo>": "O'Reilly" }`), nil
		29  }
		30  
		31  func TestEscape(t *testing.T) {
		32  	data := struct {
		33  		F, T		bool
		34  		C, G, H string
		35  		A, E		[]string
		36  		B, M		json.Marshaler
		37  		N			 int
		38  		U			 interface{} // untyped nil
		39  		Z			 *int				// typed nil
		40  		W			 HTML
		41  	}{
		42  		F: false,
		43  		T: true,
		44  		C: "<Cincinnati>",
		45  		G: "<Goodbye>",
		46  		H: "<Hello>",
		47  		A: []string{"<a>", "<b>"},
		48  		E: []string{},
		49  		N: 42,
		50  		B: &badMarshaler{},
		51  		M: &goodMarshaler{},
		52  		U: nil,
		53  		Z: nil,
		54  		W: HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
		55  	}
		56  	pdata := &data
		57  
		58  	tests := []struct {
		59  		name	 string
		60  		input	string
		61  		output string
		62  	}{
		63  		{
		64  			"if",
		65  			"{{if .T}}Hello{{end}}, {{.C}}!",
		66  			"Hello, &lt;Cincinnati&gt;!",
		67  		},
		68  		{
		69  			"else",
		70  			"{{if .F}}{{.H}}{{else}}{{.G}}{{end}}!",
		71  			"&lt;Goodbye&gt;!",
		72  		},
		73  		{
		74  			"overescaping1",
		75  			"Hello, {{.C | html}}!",
		76  			"Hello, &lt;Cincinnati&gt;!",
		77  		},
		78  		{
		79  			"overescaping2",
		80  			"Hello, {{html .C}}!",
		81  			"Hello, &lt;Cincinnati&gt;!",
		82  		},
		83  		{
		84  			"overescaping3",
		85  			"{{with .C}}{{$msg := .}}Hello, {{$msg}}!{{end}}",
		86  			"Hello, &lt;Cincinnati&gt;!",
		87  		},
		88  		{
		89  			"assignment",
		90  			"{{if $x := .H}}{{$x}}{{end}}",
		91  			"&lt;Hello&gt;",
		92  		},
		93  		{
		94  			"withBody",
		95  			"{{with .H}}{{.}}{{end}}",
		96  			"&lt;Hello&gt;",
		97  		},
		98  		{
		99  			"withElse",
	 100  			"{{with .E}}{{.}}{{else}}{{.H}}{{end}}",
	 101  			"&lt;Hello&gt;",
	 102  		},
	 103  		{
	 104  			"rangeBody",
	 105  			"{{range .A}}{{.}}{{end}}",
	 106  			"&lt;a&gt;&lt;b&gt;",
	 107  		},
	 108  		{
	 109  			"rangeElse",
	 110  			"{{range .E}}{{.}}{{else}}{{.H}}{{end}}",
	 111  			"&lt;Hello&gt;",
	 112  		},
	 113  		{
	 114  			"nonStringValue",
	 115  			"{{.T}}",
	 116  			"true",
	 117  		},
	 118  		{
	 119  			"untypedNilValue",
	 120  			"{{.U}}",
	 121  			"",
	 122  		},
	 123  		{
	 124  			"typedNilValue",
	 125  			"{{.Z}}",
	 126  			"&lt;nil&gt;",
	 127  		},
	 128  		{
	 129  			"constant",
	 130  			`<a href="/search?q={{"'a<b'"}}">`,
	 131  			`<a href="/search?q=%27a%3cb%27">`,
	 132  		},
	 133  		{
	 134  			"multipleAttrs",
	 135  			"<a b=1 c={{.H}}>",
	 136  			"<a b=1 c=&lt;Hello&gt;>",
	 137  		},
	 138  		{
	 139  			"urlStartRel",
	 140  			`<a href='{{"/foo/bar?a=b&c=d"}}'>`,
	 141  			`<a href='/foo/bar?a=b&amp;c=d'>`,
	 142  		},
	 143  		{
	 144  			"urlStartAbsOk",
	 145  			`<a href='{{"http://example.com/foo/bar?a=b&c=d"}}'>`,
	 146  			`<a href='http://example.com/foo/bar?a=b&amp;c=d'>`,
	 147  		},
	 148  		{
	 149  			"protocolRelativeURLStart",
	 150  			`<a href='{{"//example.com:8000/foo/bar?a=b&c=d"}}'>`,
	 151  			`<a href='//example.com:8000/foo/bar?a=b&amp;c=d'>`,
	 152  		},
	 153  		{
	 154  			"pathRelativeURLStart",
	 155  			`<a href="{{"/javascript:80/foo/bar"}}">`,
	 156  			`<a href="/javascript:80/foo/bar">`,
	 157  		},
	 158  		{
	 159  			"dangerousURLStart",
	 160  			`<a href='{{"javascript:alert(%22pwned%22)"}}'>`,
	 161  			`<a href='#ZgotmplZ'>`,
	 162  		},
	 163  		{
	 164  			"dangerousURLStart2",
	 165  			`<a href='	{{"javascript:alert(%22pwned%22)"}}'>`,
	 166  			`<a href='	#ZgotmplZ'>`,
	 167  		},
	 168  		{
	 169  			"nonHierURL",
	 170  			`<a href={{"mailto:Muhammed \"The Greatest\" Ali <[email protected]>"}}>`,
	 171  			`<a href=mailto:Muhammed%20%22The%20Greatest%22%20Ali%20%[email protected]%3e>`,
	 172  		},
	 173  		{
	 174  			"urlPath",
	 175  			`<a href='http://{{"javascript:80"}}/foo'>`,
	 176  			`<a href='http://javascript:80/foo'>`,
	 177  		},
	 178  		{
	 179  			"urlQuery",
	 180  			`<a href='/search?q={{.H}}'>`,
	 181  			`<a href='/search?q=%3cHello%3e'>`,
	 182  		},
	 183  		{
	 184  			"urlFragment",
	 185  			`<a href='/faq#{{.H}}'>`,
	 186  			`<a href='/faq#%3cHello%3e'>`,
	 187  		},
	 188  		{
	 189  			"urlBranch",
	 190  			`<a href="{{if .F}}/foo?a=b{{else}}/bar{{end}}">`,
	 191  			`<a href="/bar">`,
	 192  		},
	 193  		{
	 194  			"urlBranchConflictMoot",
	 195  			`<a href="{{if .T}}/foo?a={{else}}/bar#{{end}}{{.C}}">`,
	 196  			`<a href="/foo?a=%3cCincinnati%3e">`,
	 197  		},
	 198  		{
	 199  			"jsStrValue",
	 200  			"<button onclick='alert({{.H}})'>",
	 201  			`<button onclick='alert(&#34;\u003cHello\u003e&#34;)'>`,
	 202  		},
	 203  		{
	 204  			"jsNumericValue",
	 205  			"<button onclick='alert({{.N}})'>",
	 206  			`<button onclick='alert( 42 )'>`,
	 207  		},
	 208  		{
	 209  			"jsBoolValue",
	 210  			"<button onclick='alert({{.T}})'>",
	 211  			`<button onclick='alert( true )'>`,
	 212  		},
	 213  		{
	 214  			"jsNilValueTyped",
	 215  			"<button onclick='alert(typeof{{.Z}})'>",
	 216  			`<button onclick='alert(typeof null )'>`,
	 217  		},
	 218  		{
	 219  			"jsNilValueUntyped",
	 220  			"<button onclick='alert(typeof{{.U}})'>",
	 221  			`<button onclick='alert(typeof null )'>`,
	 222  		},
	 223  		{
	 224  			"jsObjValue",
	 225  			"<button onclick='alert({{.A}})'>",
	 226  			`<button onclick='alert([&#34;\u003ca\u003e&#34;,&#34;\u003cb\u003e&#34;])'>`,
	 227  		},
	 228  		{
	 229  			"jsObjValueScript",
	 230  			"<script>alert({{.A}})</script>",
	 231  			`<script>alert(["\u003ca\u003e","\u003cb\u003e"])</script>`,
	 232  		},
	 233  		{
	 234  			"jsObjValueNotOverEscaped",
	 235  			"<button onclick='alert({{.A | html}})'>",
	 236  			`<button onclick='alert([&#34;\u003ca\u003e&#34;,&#34;\u003cb\u003e&#34;])'>`,
	 237  		},
	 238  		{
	 239  			"jsStr",
	 240  			"<button onclick='alert(&quot;{{.H}}&quot;)'>",
	 241  			`<button onclick='alert(&quot;\u003cHello\u003e&quot;)'>`,
	 242  		},
	 243  		{
	 244  			"badMarshaler",
	 245  			`<button onclick='alert(1/{{.B}}in numbers)'>`,
	 246  			`<button onclick='alert(1/ /* json: error calling MarshalJSON for type *template.badMarshaler: invalid character &#39;f&#39; looking for beginning of object key string */null in numbers)'>`,
	 247  		},
	 248  		{
	 249  			"jsMarshaler",
	 250  			`<button onclick='alert({{.M}})'>`,
	 251  			`<button onclick='alert({&#34;\u003cfoo\u003e&#34;:&#34;O&#39;Reilly&#34;})'>`,
	 252  		},
	 253  		{
	 254  			"jsStrNotUnderEscaped",
	 255  			"<button onclick='alert({{.C | urlquery}})'>",
	 256  			// URL escaped, then quoted for JS.
	 257  			`<button onclick='alert(&#34;%3CCincinnati%3E&#34;)'>`,
	 258  		},
	 259  		{
	 260  			"jsRe",
	 261  			`<button onclick='alert(/{{"foo+bar"}}/.test(""))'>`,
	 262  			`<button onclick='alert(/foo\u002bbar/.test(""))'>`,
	 263  		},
	 264  		{
	 265  			"jsReBlank",
	 266  			`<script>alert(/{{""}}/.test(""));</script>`,
	 267  			`<script>alert(/(?:)/.test(""));</script>`,
	 268  		},
	 269  		{
	 270  			"jsReAmbigOk",
	 271  			`<script>{{if true}}var x = 1{{end}}</script>`,
	 272  			// The {if} ends in an ambiguous jsCtx but there is
	 273  			// no slash following so we shouldn't care.
	 274  			`<script>var x = 1</script>`,
	 275  		},
	 276  		{
	 277  			"styleBidiKeywordPassed",
	 278  			`<p style="dir: {{"ltr"}}">`,
	 279  			`<p style="dir: ltr">`,
	 280  		},
	 281  		{
	 282  			"styleBidiPropNamePassed",
	 283  			`<p style="border-{{"left"}}: 0; border-{{"right"}}: 1in">`,
	 284  			`<p style="border-left: 0; border-right: 1in">`,
	 285  		},
	 286  		{
	 287  			"styleExpressionBlocked",
	 288  			`<p style="width: {{"expression(alert(1337))"}}">`,
	 289  			`<p style="width: ZgotmplZ">`,
	 290  		},
	 291  		{
	 292  			"styleTagSelectorPassed",
	 293  			`<style>{{"p"}} { color: pink }</style>`,
	 294  			`<style>p { color: pink }</style>`,
	 295  		},
	 296  		{
	 297  			"styleIDPassed",
	 298  			`<style>p{{"#my-ID"}} { font: Arial }</style>`,
	 299  			`<style>p#my-ID { font: Arial }</style>`,
	 300  		},
	 301  		{
	 302  			"styleClassPassed",
	 303  			`<style>p{{".my_class"}} { font: Arial }</style>`,
	 304  			`<style>p.my_class { font: Arial }</style>`,
	 305  		},
	 306  		{
	 307  			"styleQuantityPassed",
	 308  			`<a style="left: {{"2em"}}; top: {{0}}">`,
	 309  			`<a style="left: 2em; top: 0">`,
	 310  		},
	 311  		{
	 312  			"stylePctPassed",
	 313  			`<table style=width:{{"100%"}}>`,
	 314  			`<table style=width:100%>`,
	 315  		},
	 316  		{
	 317  			"styleColorPassed",
	 318  			`<p style="color: {{"#8ff"}}; background: {{"#000"}}">`,
	 319  			`<p style="color: #8ff; background: #000">`,
	 320  		},
	 321  		{
	 322  			"styleObfuscatedExpressionBlocked",
	 323  			`<p style="width: {{"	e\\78preS\x00Sio/**/n(alert(1337))"}}">`,
	 324  			`<p style="width: ZgotmplZ">`,
	 325  		},
	 326  		{
	 327  			"styleMozBindingBlocked",
	 328  			`<p style="{{"-moz-binding(alert(1337))"}}: ...">`,
	 329  			`<p style="ZgotmplZ: ...">`,
	 330  		},
	 331  		{
	 332  			"styleObfuscatedMozBindingBlocked",
	 333  			`<p style="{{"	-mo\\7a-B\x00I/**/nding(alert(1337))"}}: ...">`,
	 334  			`<p style="ZgotmplZ: ...">`,
	 335  		},
	 336  		{
	 337  			"styleFontNameString",
	 338  			`<p style='font-family: "{{"Times New Roman"}}"'>`,
	 339  			`<p style='font-family: "Times New Roman"'>`,
	 340  		},
	 341  		{
	 342  			"styleFontNameString",
	 343  			`<p style='font-family: "{{"Times New Roman"}}", "{{"sans-serif"}}"'>`,
	 344  			`<p style='font-family: "Times New Roman", "sans-serif"'>`,
	 345  		},
	 346  		{
	 347  			"styleFontNameUnquoted",
	 348  			`<p style='font-family: {{"Times New Roman"}}'>`,
	 349  			`<p style='font-family: Times New Roman'>`,
	 350  		},
	 351  		{
	 352  			"styleURLQueryEncoded",
	 353  			`<p style="background: url(/img?name={{"O'Reilly Animal(1)<2>.png"}})">`,
	 354  			`<p style="background: url(/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png)">`,
	 355  		},
	 356  		{
	 357  			"styleQuotedURLQueryEncoded",
	 358  			`<p style="background: url('/img?name={{"O'Reilly Animal(1)<2>.png"}}')">`,
	 359  			`<p style="background: url('/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png')">`,
	 360  		},
	 361  		{
	 362  			"styleStrQueryEncoded",
	 363  			`<p style="background: '/img?name={{"O'Reilly Animal(1)<2>.png"}}'">`,
	 364  			`<p style="background: '/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png'">`,
	 365  		},
	 366  		{
	 367  			"styleURLBadProtocolBlocked",
	 368  			`<a style="background: url('{{"javascript:alert(1337)"}}')">`,
	 369  			`<a style="background: url('#ZgotmplZ')">`,
	 370  		},
	 371  		{
	 372  			"styleStrBadProtocolBlocked",
	 373  			`<a style="background: '{{"vbscript:alert(1337)"}}'">`,
	 374  			`<a style="background: '#ZgotmplZ'">`,
	 375  		},
	 376  		{
	 377  			"styleStrEncodedProtocolEncoded",
	 378  			`<a style="background: '{{"javascript\\3a alert(1337)"}}'">`,
	 379  			// The CSS string 'javascript\\3a alert(1337)' does not contain a colon.
	 380  			`<a style="background: 'javascript\\3a alert\28 1337\29 '">`,
	 381  		},
	 382  		{
	 383  			"styleURLGoodProtocolPassed",
	 384  			`<a style="background: url('{{"http://oreilly.com/O'Reilly Animals(1)<2>;{}.html"}}')">`,
	 385  			`<a style="background: url('http://oreilly.com/O%27Reilly%20Animals%281%29%3c2%3e;%7b%7d.html')">`,
	 386  		},
	 387  		{
	 388  			"styleStrGoodProtocolPassed",
	 389  			`<a style="background: '{{"http://oreilly.com/O'Reilly Animals(1)<2>;{}.html"}}'">`,
	 390  			`<a style="background: 'http\3a\2f\2foreilly.com\2fO\27Reilly Animals\28 1\29\3c 2\3e\3b\7b\7d.html'">`,
	 391  		},
	 392  		{
	 393  			"styleURLEncodedForHTMLInAttr",
	 394  			`<a style="background: url('{{"/search?img=foo&size=icon"}}')">`,
	 395  			`<a style="background: url('/search?img=foo&amp;size=icon')">`,
	 396  		},
	 397  		{
	 398  			"styleURLNotEncodedForHTMLInCdata",
	 399  			`<style>body { background: url('{{"/search?img=foo&size=icon"}}') }</style>`,
	 400  			`<style>body { background: url('/search?img=foo&size=icon') }</style>`,
	 401  		},
	 402  		{
	 403  			"styleURLMixedCase",
	 404  			`<p style="background: URL(#{{.H}})">`,
	 405  			`<p style="background: URL(#%3cHello%3e)">`,
	 406  		},
	 407  		{
	 408  			"stylePropertyPairPassed",
	 409  			`<a style='{{"color: red"}}'>`,
	 410  			`<a style='color: red'>`,
	 411  		},
	 412  		{
	 413  			"styleStrSpecialsEncoded",
	 414  			`<a style="font-family: '{{"/**/'\";:// \\"}}', &quot;{{"/**/'\";:// \\"}}&quot;">`,
	 415  			`<a style="font-family: '\2f**\2f\27\22\3b\3a\2f\2f	\\', &quot;\2f**\2f\27\22\3b\3a\2f\2f	\\&quot;">`,
	 416  		},
	 417  		{
	 418  			"styleURLSpecialsEncoded",
	 419  			`<a style="border-image: url({{"/**/'\";:// \\"}}), url(&quot;{{"/**/'\";:// \\"}}&quot;), url('{{"/**/'\";:// \\"}}'), 'http://www.example.com/?q={{"/**/'\";:// \\"}}''">`,
	 420  			`<a style="border-image: url(/**/%27%22;://%20%5c), url(&quot;/**/%27%22;://%20%5c&quot;), url('/**/%27%22;://%20%5c'), 'http://www.example.com/?q=%2f%2a%2a%2f%27%22%3b%3a%2f%2f%20%5c''">`,
	 421  		},
	 422  		{
	 423  			"HTML comment",
	 424  			"<b>Hello, <!-- name of world -->{{.C}}</b>",
	 425  			"<b>Hello, &lt;Cincinnati&gt;</b>",
	 426  		},
	 427  		{
	 428  			"HTML comment not first < in text node.",
	 429  			"<<!-- -->!--",
	 430  			"&lt;!--",
	 431  		},
	 432  		{
	 433  			"HTML normalization 1",
	 434  			"a < b",
	 435  			"a &lt; b",
	 436  		},
	 437  		{
	 438  			"HTML normalization 2",
	 439  			"a << b",
	 440  			"a &lt;&lt; b",
	 441  		},
	 442  		{
	 443  			"HTML normalization 3",
	 444  			"a<<!-- --><!-- -->b",
	 445  			"a&lt;b",
	 446  		},
	 447  		{
	 448  			"HTML doctype not normalized",
	 449  			"<!DOCTYPE html>Hello, World!",
	 450  			"<!DOCTYPE html>Hello, World!",
	 451  		},
	 452  		{
	 453  			"HTML doctype not case-insensitive",
	 454  			"<!doCtYPE htMl>Hello, World!",
	 455  			"<!doCtYPE htMl>Hello, World!",
	 456  		},
	 457  		{
	 458  			"No doctype injection",
	 459  			`<!{{"DOCTYPE"}}`,
	 460  			"&lt;!DOCTYPE",
	 461  		},
	 462  		{
	 463  			"Split HTML comment",
	 464  			"<b>Hello, <!-- name of {{if .T}}city -->{{.C}}{{else}}world -->{{.W}}{{end}}</b>",
	 465  			"<b>Hello, &lt;Cincinnati&gt;</b>",
	 466  		},
	 467  		{
	 468  			"JS line comment",
	 469  			"<script>for (;;) { if (c()) break// foo not a label\n" +
	 470  				"foo({{.T}});}</script>",
	 471  			"<script>for (;;) { if (c()) break\n" +
	 472  				"foo( true );}</script>",
	 473  		},
	 474  		{
	 475  			"JS multiline block comment",
	 476  			"<script>for (;;) { if (c()) break/* foo not a label\n" +
	 477  				" */foo({{.T}});}</script>",
	 478  			// Newline separates break from call. If newline
	 479  			// removed, then break will consume label leaving
	 480  			// code invalid.
	 481  			"<script>for (;;) { if (c()) break\n" +
	 482  				"foo( true );}</script>",
	 483  		},
	 484  		{
	 485  			"JS single-line block comment",
	 486  			"<script>for (;;) {\n" +
	 487  				"if (c()) break/* foo a label */foo;" +
	 488  				"x({{.T}});}</script>",
	 489  			// Newline separates break from call. If newline
	 490  			// removed, then break will consume label leaving
	 491  			// code invalid.
	 492  			"<script>for (;;) {\n" +
	 493  				"if (c()) break foo;" +
	 494  				"x( true );}</script>",
	 495  		},
	 496  		{
	 497  			"JS block comment flush with mathematical division",
	 498  			"<script>var a/*b*//c\nd</script>",
	 499  			"<script>var a /c\nd</script>",
	 500  		},
	 501  		{
	 502  			"JS mixed comments",
	 503  			"<script>var a/*b*///c\nd</script>",
	 504  			"<script>var a \nd</script>",
	 505  		},
	 506  		{
	 507  			"CSS comments",
	 508  			"<style>p// paragraph\n" +
	 509  				`{border: 1px/* color */{{"#00f"}}}</style>`,
	 510  			"<style>p\n" +
	 511  				"{border: 1px #00f}</style>",
	 512  		},
	 513  		{
	 514  			"JS attr block comment",
	 515  			`<a onclick="f(&quot;&quot;); /* alert({{.H}}) */">`,
	 516  			// Attribute comment tests should pass if the comments
	 517  			// are successfully elided.
	 518  			`<a onclick="f(&quot;&quot;); /* alert() */">`,
	 519  		},
	 520  		{
	 521  			"JS attr line comment",
	 522  			`<a onclick="// alert({{.G}})">`,
	 523  			`<a onclick="// alert()">`,
	 524  		},
	 525  		{
	 526  			"CSS attr block comment",
	 527  			`<a style="/* color: {{.H}} */">`,
	 528  			`<a style="/* color:	*/">`,
	 529  		},
	 530  		{
	 531  			"CSS attr line comment",
	 532  			`<a style="// color: {{.G}}">`,
	 533  			`<a style="// color: ">`,
	 534  		},
	 535  		{
	 536  			"HTML substitution commented out",
	 537  			"<p><!-- {{.H}} --></p>",
	 538  			"<p></p>",
	 539  		},
	 540  		{
	 541  			"Comment ends flush with start",
	 542  			"<!--{{.}}--><script>/*{{.}}*///{{.}}\n</script><style>/*{{.}}*///{{.}}\n</style><a onclick='/*{{.}}*///{{.}}' style='/*{{.}}*///{{.}}'>",
	 543  			"<script> \n</script><style> \n</style><a onclick='/**///' style='/**///'>",
	 544  		},
	 545  		{
	 546  			"typed HTML in text",
	 547  			`{{.W}}`,
	 548  			`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`,
	 549  		},
	 550  		{
	 551  			"typed HTML in attribute",
	 552  			`<div title="{{.W}}">`,
	 553  			`<div title="&iexcl;Hello, O&#39;World!">`,
	 554  		},
	 555  		{
	 556  			"typed HTML in script",
	 557  			`<button onclick="alert({{.W}})">`,
	 558  			`<button onclick="alert(&#34;\u0026iexcl;\u003cb class=\&#34;foo\&#34;\u003eHello\u003c/b\u003e, \u003ctextarea\u003eO&#39;World\u003c/textarea\u003e!&#34;)">`,
	 559  		},
	 560  		{
	 561  			"typed HTML in RCDATA",
	 562  			`<textarea>{{.W}}</textarea>`,
	 563  			`<textarea>&iexcl;&lt;b class=&#34;foo&#34;&gt;Hello&lt;/b&gt;, &lt;textarea&gt;O&#39;World&lt;/textarea&gt;!</textarea>`,
	 564  		},
	 565  		{
	 566  			"range in textarea",
	 567  			"<textarea>{{range .A}}{{.}}{{end}}</textarea>",
	 568  			"<textarea>&lt;a&gt;&lt;b&gt;</textarea>",
	 569  		},
	 570  		{
	 571  			"No tag injection",
	 572  			`{{"10$"}}<{{"script src,evil.org/pwnd.js"}}...`,
	 573  			`10$&lt;script src,evil.org/pwnd.js...`,
	 574  		},
	 575  		{
	 576  			"No comment injection",
	 577  			`<{{"!--"}}`,
	 578  			`&lt;!--`,
	 579  		},
	 580  		{
	 581  			"No RCDATA end tag injection",
	 582  			`<textarea><{{"/textarea "}}...</textarea>`,
	 583  			`<textarea>&lt;/textarea ...</textarea>`,
	 584  		},
	 585  		{
	 586  			"optional attrs",
	 587  			`<img class="{{"iconClass"}}"` +
	 588  				`{{if .T}} id="{{"<iconId>"}}"{{end}}` +
	 589  				// Double quotes inside if/else.
	 590  				` src=` +
	 591  				`{{if .T}}"?{{"<iconPath>"}}"` +
	 592  				`{{else}}"images/cleardot.gif"{{end}}` +
	 593  				// Missing space before title, but it is not a
	 594  				// part of the src attribute.
	 595  				`{{if .T}}title="{{"<title>"}}"{{end}}` +
	 596  				// Quotes outside if/else.
	 597  				` alt="` +
	 598  				`{{if .T}}{{"<alt>"}}` +
	 599  				`{{else}}{{if .F}}{{"<title>"}}{{end}}` +
	 600  				`{{end}}"` +
	 601  				`>`,
	 602  			`<img class="iconClass" id="&lt;iconId&gt;" src="?%3ciconPath%3e"title="&lt;title&gt;" alt="&lt;alt&gt;">`,
	 603  		},
	 604  		{
	 605  			"conditional valueless attr name",
	 606  			`<input{{if .T}} checked{{end}} name=n>`,
	 607  			`<input checked name=n>`,
	 608  		},
	 609  		{
	 610  			"conditional dynamic valueless attr name 1",
	 611  			`<input{{if .T}} {{"checked"}}{{end}} name=n>`,
	 612  			`<input checked name=n>`,
	 613  		},
	 614  		{
	 615  			"conditional dynamic valueless attr name 2",
	 616  			`<input {{if .T}}{{"checked"}} {{end}}name=n>`,
	 617  			`<input checked name=n>`,
	 618  		},
	 619  		{
	 620  			"dynamic attribute name",
	 621  			`<img on{{"load"}}="alert({{"loaded"}})">`,
	 622  			// Treated as JS since quotes are inserted.
	 623  			`<img onload="alert(&#34;loaded&#34;)">`,
	 624  		},
	 625  		{
	 626  			"bad dynamic attribute name 1",
	 627  			// Allow checked, selected, disabled, but not JS or
	 628  			// CSS attributes.
	 629  			`<input {{"onchange"}}="{{"doEvil()"}}">`,
	 630  			`<input ZgotmplZ="doEvil()">`,
	 631  		},
	 632  		{
	 633  			"bad dynamic attribute name 2",
	 634  			`<div {{"sTyle"}}="{{"color: expression(alert(1337))"}}">`,
	 635  			`<div ZgotmplZ="color: expression(alert(1337))">`,
	 636  		},
	 637  		{
	 638  			"bad dynamic attribute name 3",
	 639  			// Allow title or alt, but not a URL.
	 640  			`<img {{"src"}}="{{"javascript:doEvil()"}}">`,
	 641  			`<img ZgotmplZ="javascript:doEvil()">`,
	 642  		},
	 643  		{
	 644  			"bad dynamic attribute name 4",
	 645  			// Structure preservation requires values to associate
	 646  			// with a consistent attribute.
	 647  			`<input checked {{""}}="Whose value am I?">`,
	 648  			`<input checked ZgotmplZ="Whose value am I?">`,
	 649  		},
	 650  		{
	 651  			"dynamic element name",
	 652  			`<h{{3}}><table><t{{"head"}}>...</h{{3}}>`,
	 653  			`<h3><table><thead>...</h3>`,
	 654  		},
	 655  		{
	 656  			"bad dynamic element name",
	 657  			// Dynamic element names are typically used to switch
	 658  			// between (thead, tfoot, tbody), (ul, ol), (th, td),
	 659  			// and other replaceable sets.
	 660  			// We do not currently easily support (ul, ol).
	 661  			// If we do change to support that, this test should
	 662  			// catch failures to filter out special tag names which
	 663  			// would violate the structure preservation property --
	 664  			// if any special tag name could be substituted, then
	 665  			// the content could be raw text/RCDATA for some inputs
	 666  			// and regular HTML content for others.
	 667  			`<{{"script"}}>{{"doEvil()"}}</{{"script"}}>`,
	 668  			`&lt;script>doEvil()&lt;/script>`,
	 669  		},
	 670  		{
	 671  			"srcset bad URL in second position",
	 672  			`<img srcset="{{"/not-an-image#,javascript:alert(1)"}}">`,
	 673  			// The second URL is also filtered.
	 674  			`<img srcset="/not-an-image#,#ZgotmplZ">`,
	 675  		},
	 676  		{
	 677  			"srcset buffer growth",
	 678  			`<img srcset={{",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}}>`,
	 679  			`<img srcset=,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,>`,
	 680  		},
	 681  	}
	 682  
	 683  	for _, test := range tests {
	 684  		tmpl := New(test.name)
	 685  		tmpl = Must(tmpl.Parse(test.input))
	 686  		// Check for bug 6459: Tree field was not set in Parse.
	 687  		if tmpl.Tree != tmpl.text.Tree {
	 688  			t.Errorf("%s: tree not set properly", test.name)
	 689  			continue
	 690  		}
	 691  		b := new(bytes.Buffer)
	 692  		if err := tmpl.Execute(b, data); err != nil {
	 693  			t.Errorf("%s: template execution failed: %s", test.name, err)
	 694  			continue
	 695  		}
	 696  		if w, g := test.output, b.String(); w != g {
	 697  			t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.name, w, g)
	 698  			continue
	 699  		}
	 700  		b.Reset()
	 701  		if err := tmpl.Execute(b, pdata); err != nil {
	 702  			t.Errorf("%s: template execution failed for pointer: %s", test.name, err)
	 703  			continue
	 704  		}
	 705  		if w, g := test.output, b.String(); w != g {
	 706  			t.Errorf("%s: escaped output for pointer: want\n\t%q\ngot\n\t%q", test.name, w, g)
	 707  			continue
	 708  		}
	 709  		if tmpl.Tree != tmpl.text.Tree {
	 710  			t.Errorf("%s: tree mismatch", test.name)
	 711  			continue
	 712  		}
	 713  	}
	 714  }
	 715  
	 716  func TestEscapeMap(t *testing.T) {
	 717  	data := map[string]string{
	 718  		"html":		 `<h1>Hi!</h1>`,
	 719  		"urlquery": `http://www.foo.com/index.html?title=main`,
	 720  	}
	 721  	for _, test := range [...]struct {
	 722  		desc, input, output string
	 723  	}{
	 724  		// covering issue 20323
	 725  		{
	 726  			"field with predefined escaper name 1",
	 727  			`{{.html | print}}`,
	 728  			`&lt;h1&gt;Hi!&lt;/h1&gt;`,
	 729  		},
	 730  		// covering issue 20323
	 731  		{
	 732  			"field with predefined escaper name 2",
	 733  			`{{.urlquery | print}}`,
	 734  			`http://www.foo.com/index.html?title=main`,
	 735  		},
	 736  	} {
	 737  		tmpl := Must(New("").Parse(test.input))
	 738  		b := new(bytes.Buffer)
	 739  		if err := tmpl.Execute(b, data); err != nil {
	 740  			t.Errorf("%s: template execution failed: %s", test.desc, err)
	 741  			continue
	 742  		}
	 743  		if w, g := test.output, b.String(); w != g {
	 744  			t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.desc, w, g)
	 745  			continue
	 746  		}
	 747  	}
	 748  }
	 749  
	 750  func TestEscapeSet(t *testing.T) {
	 751  	type dataItem struct {
	 752  		Children []*dataItem
	 753  		X				string
	 754  	}
	 755  
	 756  	data := dataItem{
	 757  		Children: []*dataItem{
	 758  			{X: "foo"},
	 759  			{X: "<bar>"},
	 760  			{
	 761  				Children: []*dataItem{
	 762  					{X: "baz"},
	 763  				},
	 764  			},
	 765  		},
	 766  	}
	 767  
	 768  	tests := []struct {
	 769  		inputs map[string]string
	 770  		want	 string
	 771  	}{
	 772  		// The trivial set.
	 773  		{
	 774  			map[string]string{
	 775  				"main": ``,
	 776  			},
	 777  			``,
	 778  		},
	 779  		// A template called in the start context.
	 780  		{
	 781  			map[string]string{
	 782  				"main": `Hello, {{template "helper"}}!`,
	 783  				// Not a valid top level HTML template.
	 784  				// "<b" is not a full tag.
	 785  				"helper": `{{"<World>"}}`,
	 786  			},
	 787  			`Hello, &lt;World&gt;!`,
	 788  		},
	 789  		// A template called in a context other than the start.
	 790  		{
	 791  			map[string]string{
	 792  				"main": `<a onclick='a = {{template "helper"}};'>`,
	 793  				// Not a valid top level HTML template.
	 794  				// "<b" is not a full tag.
	 795  				"helper": `{{"<a>"}}<b`,
	 796  			},
	 797  			`<a onclick='a = &#34;\u003ca\u003e&#34;<b;'>`,
	 798  		},
	 799  		// A recursive template that ends in its start context.
	 800  		{
	 801  			map[string]string{
	 802  				"main": `{{range .Children}}{{template "main" .}}{{else}}{{.X}} {{end}}`,
	 803  			},
	 804  			`foo &lt;bar&gt; baz `,
	 805  		},
	 806  		// A recursive helper template that ends in its start context.
	 807  		{
	 808  			map[string]string{
	 809  				"main":	 `{{template "helper" .}}`,
	 810  				"helper": `{{if .Children}}<ul>{{range .Children}}<li>{{template "main" .}}</li>{{end}}</ul>{{else}}{{.X}}{{end}}`,
	 811  			},
	 812  			`<ul><li>foo</li><li>&lt;bar&gt;</li><li><ul><li>baz</li></ul></li></ul>`,
	 813  		},
	 814  		// Co-recursive templates that end in its start context.
	 815  		{
	 816  			map[string]string{
	 817  				"main":	 `<blockquote>{{range .Children}}{{template "helper" .}}{{end}}</blockquote>`,
	 818  				"helper": `{{if .Children}}{{template "main" .}}{{else}}{{.X}}<br>{{end}}`,
	 819  			},
	 820  			`<blockquote>foo<br>&lt;bar&gt;<br><blockquote>baz<br></blockquote></blockquote>`,
	 821  		},
	 822  		// A template that is called in two different contexts.
	 823  		{
	 824  			map[string]string{
	 825  				"main":	 `<button onclick="title='{{template "helper"}}'; ...">{{template "helper"}}</button>`,
	 826  				"helper": `{{11}} of {{"<100>"}}`,
	 827  			},
	 828  			`<button onclick="title='11 of \u003c100\u003e'; ...">11 of &lt;100&gt;</button>`,
	 829  		},
	 830  		// A non-recursive template that ends in a different context.
	 831  		// helper starts in jsCtxRegexp and ends in jsCtxDivOp.
	 832  		{
	 833  			map[string]string{
	 834  				"main":	 `<script>var x={{template "helper"}}/{{"42"}};</script>`,
	 835  				"helper": "{{126}}",
	 836  			},
	 837  			`<script>var x= 126 /"42";</script>`,
	 838  		},
	 839  		// A recursive template that ends in a similar context.
	 840  		{
	 841  			map[string]string{
	 842  				"main":			`<script>var x=[{{template "countdown" 4}}];</script>`,
	 843  				"countdown": `{{.}}{{if .}},{{template "countdown" . | pred}}{{end}}`,
	 844  			},
	 845  			`<script>var x=[ 4 , 3 , 2 , 1 , 0 ];</script>`,
	 846  		},
	 847  		// A recursive template that ends in a different context.
	 848  		/*
	 849  			{
	 850  				map[string]string{
	 851  					"main":	 `<a href="/foo{{template "helper" .}}">`,
	 852  					"helper": `{{if .Children}}{{range .Children}}{{template "helper" .}}{{end}}{{else}}?x={{.X}}{{end}}`,
	 853  				},
	 854  				`<a href="/foo?x=foo?x=%3cbar%3e?x=baz">`,
	 855  			},
	 856  		*/
	 857  	}
	 858  
	 859  	// pred is a template function that returns the predecessor of a
	 860  	// natural number for testing recursive templates.
	 861  	fns := FuncMap{"pred": func(a ...interface{}) (interface{}, error) {
	 862  		if len(a) == 1 {
	 863  			if i, _ := a[0].(int); i > 0 {
	 864  				return i - 1, nil
	 865  			}
	 866  		}
	 867  		return nil, fmt.Errorf("undefined pred(%v)", a)
	 868  	}}
	 869  
	 870  	for _, test := range tests {
	 871  		source := ""
	 872  		for name, body := range test.inputs {
	 873  			source += fmt.Sprintf("{{define %q}}%s{{end}} ", name, body)
	 874  		}
	 875  		tmpl, err := New("root").Funcs(fns).Parse(source)
	 876  		if err != nil {
	 877  			t.Errorf("error parsing %q: %v", source, err)
	 878  			continue
	 879  		}
	 880  		var b bytes.Buffer
	 881  
	 882  		if err := tmpl.ExecuteTemplate(&b, "main", data); err != nil {
	 883  			t.Errorf("%q executing %v", err.Error(), tmpl.Lookup("main"))
	 884  			continue
	 885  		}
	 886  		if got := b.String(); test.want != got {
	 887  			t.Errorf("want\n\t%q\ngot\n\t%q", test.want, got)
	 888  		}
	 889  	}
	 890  
	 891  }
	 892  
	 893  func TestErrors(t *testing.T) {
	 894  	tests := []struct {
	 895  		input string
	 896  		err	 string
	 897  	}{
	 898  		// Non-error cases.
	 899  		{
	 900  			"{{if .Cond}}<a>{{else}}<b>{{end}}",
	 901  			"",
	 902  		},
	 903  		{
	 904  			"{{if .Cond}}<a>{{end}}",
	 905  			"",
	 906  		},
	 907  		{
	 908  			"{{if .Cond}}{{else}}<b>{{end}}",
	 909  			"",
	 910  		},
	 911  		{
	 912  			"{{with .Cond}}<div>{{end}}",
	 913  			"",
	 914  		},
	 915  		{
	 916  			"{{range .Items}}<a>{{end}}",
	 917  			"",
	 918  		},
	 919  		{
	 920  			"<a href='/foo?{{range .Items}}&{{.K}}={{.V}}{{end}}'>",
	 921  			"",
	 922  		},
	 923  		// Error cases.
	 924  		{
	 925  			"{{if .Cond}}<a{{end}}",
	 926  			"z:1:5: {{if}} branches",
	 927  		},
	 928  		{
	 929  			"{{if .Cond}}\n{{else}}\n<a{{end}}",
	 930  			"z:1:5: {{if}} branches",
	 931  		},
	 932  		{
	 933  			// Missing quote in the else branch.
	 934  			`{{if .Cond}}<a href="foo">{{else}}<a href="bar>{{end}}`,
	 935  			"z:1:5: {{if}} branches",
	 936  		},
	 937  		{
	 938  			// Different kind of attribute: href implies a URL.
	 939  			"<a {{if .Cond}}href='{{else}}title='{{end}}{{.X}}'>",
	 940  			"z:1:8: {{if}} branches",
	 941  		},
	 942  		{
	 943  			"\n{{with .X}}<a{{end}}",
	 944  			"z:2:7: {{with}} branches",
	 945  		},
	 946  		{
	 947  			"\n{{with .X}}<a>{{else}}<a{{end}}",
	 948  			"z:2:7: {{with}} branches",
	 949  		},
	 950  		{
	 951  			"{{range .Items}}<a{{end}}",
	 952  			`z:1: on range loop re-entry: "<" in attribute name: "<a"`,
	 953  		},
	 954  		{
	 955  			"\n{{range .Items}} x='<a{{end}}",
	 956  			"z:2:8: on range loop re-entry: {{range}} branches",
	 957  		},
	 958  		{
	 959  			"<a b=1 c={{.H}}",
	 960  			"z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd",
	 961  		},
	 962  		{
	 963  			"<script>foo();",
	 964  			"z: ends in a non-text context: {stateJS",
	 965  		},
	 966  		{
	 967  			`<a href="{{if .F}}/foo?a={{else}}/bar/{{end}}{{.H}}">`,
	 968  			"z:1:47: {{.H}} appears in an ambiguous context within a URL",
	 969  		},
	 970  		{
	 971  			`<a onclick="alert('Hello \`,
	 972  			`unfinished escape sequence in JS string: "Hello \\"`,
	 973  		},
	 974  		{
	 975  			`<a onclick='alert("Hello\, World\`,
	 976  			`unfinished escape sequence in JS string: "Hello\\, World\\"`,
	 977  		},
	 978  		{
	 979  			`<a onclick='alert(/x+\`,
	 980  			`unfinished escape sequence in JS string: "x+\\"`,
	 981  		},
	 982  		{
	 983  			`<a onclick="/foo[\]/`,
	 984  			`unfinished JS regexp charset: "foo[\\]/"`,
	 985  		},
	 986  		{
	 987  			// It is ambiguous whether 1.5 should be 1\.5 or 1.5.
	 988  			// Either `var x = 1/- 1.5 /i.test(x)`
	 989  			// where `i.test(x)` is a method call of reference i,
	 990  			// or `/-1\.5/i.test(x)` which is a method call on a
	 991  			// case insensitive regular expression.
	 992  			`<script>{{if false}}var x = 1{{end}}/-{{"1.5"}}/i.test(x)</script>`,
	 993  			`'/' could start a division or regexp: "/-"`,
	 994  		},
	 995  		{
	 996  			`{{template "foo"}}`,
	 997  			"z:1:11: no such template \"foo\"",
	 998  		},
	 999  		{
	1000  			`<div{{template "y"}}>` +
	1001  				// Illegal starting in stateTag but not in stateText.
	1002  				`{{define "y"}} foo<b{{end}}`,
	1003  			`"<" in attribute name: " foo<b"`,
	1004  		},
	1005  		{
	1006  			`<script>reverseList = [{{template "t"}}]</script>` +
	1007  				// Missing " after recursive call.
	1008  				`{{define "t"}}{{if .Tail}}{{template "t" .Tail}}{{end}}{{.Head}}",{{end}}`,
	1009  			`: cannot compute output context for template t$htmltemplate_stateJS_elementScript`,
	1010  		},
	1011  		{
	1012  			`<input type=button value=onclick=>`,
	1013  			`html/template:z: "=" in unquoted attr: "onclick="`,
	1014  		},
	1015  		{
	1016  			`<input type=button value= onclick=>`,
	1017  			`html/template:z: "=" in unquoted attr: "onclick="`,
	1018  		},
	1019  		{
	1020  			`<input type=button value= 1+1=2>`,
	1021  			`html/template:z: "=" in unquoted attr: "1+1=2"`,
	1022  		},
	1023  		{
	1024  			"<a class=`foo>",
	1025  			"html/template:z: \"`\" in unquoted attr: \"`foo\"",
	1026  		},
	1027  		{
	1028  			`<a style=font:'Arial'>`,
	1029  			`html/template:z: "'" in unquoted attr: "font:'Arial'"`,
	1030  		},
	1031  		{
	1032  			`<a=foo>`,
	1033  			`: expected space, attr name, or end of tag, but got "=foo>"`,
	1034  		},
	1035  		{
	1036  			`Hello, {{. | urlquery | print}}!`,
	1037  			// urlquery is disallowed if it is not the last command in the pipeline.
	1038  			`predefined escaper "urlquery" disallowed in template`,
	1039  		},
	1040  		{
	1041  			`Hello, {{. | html | print}}!`,
	1042  			// html is disallowed if it is not the last command in the pipeline.
	1043  			`predefined escaper "html" disallowed in template`,
	1044  		},
	1045  		{
	1046  			`Hello, {{html . | print}}!`,
	1047  			// A direct call to html is disallowed if it is not the last command in the pipeline.
	1048  			`predefined escaper "html" disallowed in template`,
	1049  		},
	1050  		{
	1051  			`<div class={{. | html}}>Hello<div>`,
	1052  			// html is disallowed in a pipeline that is in an unquoted attribute context,
	1053  			// even if it is the last command in the pipeline.
	1054  			`predefined escaper "html" disallowed in template`,
	1055  		},
	1056  		{
	1057  			`Hello, {{. | urlquery | html}}!`,
	1058  			// html is allowed since it is the last command in the pipeline, but urlquery is not.
	1059  			`predefined escaper "urlquery" disallowed in template`,
	1060  		},
	1061  	}
	1062  	for _, test := range tests {
	1063  		buf := new(bytes.Buffer)
	1064  		tmpl, err := New("z").Parse(test.input)
	1065  		if err != nil {
	1066  			t.Errorf("input=%q: unexpected parse error %s\n", test.input, err)
	1067  			continue
	1068  		}
	1069  		err = tmpl.Execute(buf, nil)
	1070  		var got string
	1071  		if err != nil {
	1072  			got = err.Error()
	1073  		}
	1074  		if test.err == "" {
	1075  			if got != "" {
	1076  				t.Errorf("input=%q: unexpected error %q", test.input, got)
	1077  			}
	1078  			continue
	1079  		}
	1080  		if !strings.Contains(got, test.err) {
	1081  			t.Errorf("input=%q: error\n\t%q\ndoes not contain expected string\n\t%q", test.input, got, test.err)
	1082  			continue
	1083  		}
	1084  		// Check that we get the same error if we call Execute again.
	1085  		if err := tmpl.Execute(buf, nil); err == nil || err.Error() != got {
	1086  			t.Errorf("input=%q: unexpected error on second call %q", test.input, err)
	1087  
	1088  		}
	1089  	}
	1090  }
	1091  
	1092  func TestEscapeText(t *testing.T) {
	1093  	tests := []struct {
	1094  		input	string
	1095  		output context
	1096  	}{
	1097  		{
	1098  			``,
	1099  			context{},
	1100  		},
	1101  		{
	1102  			`Hello, World!`,
	1103  			context{},
	1104  		},
	1105  		{
	1106  			// An orphaned "<" is OK.
	1107  			`I <3 Ponies!`,
	1108  			context{},
	1109  		},
	1110  		{
	1111  			`<a`,
	1112  			context{state: stateTag},
	1113  		},
	1114  		{
	1115  			`<a `,
	1116  			context{state: stateTag},
	1117  		},
	1118  		{
	1119  			`<a>`,
	1120  			context{state: stateText},
	1121  		},
	1122  		{
	1123  			`<a href`,
	1124  			context{state: stateAttrName, attr: attrURL},
	1125  		},
	1126  		{
	1127  			`<a on`,
	1128  			context{state: stateAttrName, attr: attrScript},
	1129  		},
	1130  		{
	1131  			`<a href `,
	1132  			context{state: stateAfterName, attr: attrURL},
	1133  		},
	1134  		{
	1135  			`<a style	=	`,
	1136  			context{state: stateBeforeValue, attr: attrStyle},
	1137  		},
	1138  		{
	1139  			`<a href=`,
	1140  			context{state: stateBeforeValue, attr: attrURL},
	1141  		},
	1142  		{
	1143  			`<a href=x`,
	1144  			context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery, attr: attrURL},
	1145  		},
	1146  		{
	1147  			`<a href=x `,
	1148  			context{state: stateTag},
	1149  		},
	1150  		{
	1151  			`<a href=>`,
	1152  			context{state: stateText},
	1153  		},
	1154  		{
	1155  			`<a href=x>`,
	1156  			context{state: stateText},
	1157  		},
	1158  		{
	1159  			`<a href ='`,
	1160  			context{state: stateURL, delim: delimSingleQuote, attr: attrURL},
	1161  		},
	1162  		{
	1163  			`<a href=''`,
	1164  			context{state: stateTag},
	1165  		},
	1166  		{
	1167  			`<a href= "`,
	1168  			context{state: stateURL, delim: delimDoubleQuote, attr: attrURL},
	1169  		},
	1170  		{
	1171  			`<a href=""`,
	1172  			context{state: stateTag},
	1173  		},
	1174  		{
	1175  			`<a title="`,
	1176  			context{state: stateAttr, delim: delimDoubleQuote},
	1177  		},
	1178  		{
	1179  			`<a HREF='http:`,
	1180  			context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL},
	1181  		},
	1182  		{
	1183  			`<a Href='/`,
	1184  			context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL},
	1185  		},
	1186  		{
	1187  			`<a href='"`,
	1188  			context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL},
	1189  		},
	1190  		{
	1191  			`<a href="'`,
	1192  			context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL},
	1193  		},
	1194  		{
	1195  			`<a href='&apos;`,
	1196  			context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL},
	1197  		},
	1198  		{
	1199  			`<a href="&quot;`,
	1200  			context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL},
	1201  		},
	1202  		{
	1203  			`<a href="&#34;`,
	1204  			context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL},
	1205  		},
	1206  		{
	1207  			`<a href=&quot;`,
	1208  			context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery, attr: attrURL},
	1209  		},
	1210  		{
	1211  			`<img alt="1">`,
	1212  			context{state: stateText},
	1213  		},
	1214  		{
	1215  			`<img alt="1>"`,
	1216  			context{state: stateTag},
	1217  		},
	1218  		{
	1219  			`<img alt="1>">`,
	1220  			context{state: stateText},
	1221  		},
	1222  		{
	1223  			`<input checked type="checkbox"`,
	1224  			context{state: stateTag},
	1225  		},
	1226  		{
	1227  			`<a onclick="`,
	1228  			context{state: stateJS, delim: delimDoubleQuote, attr: attrScript},
	1229  		},
	1230  		{
	1231  			`<a onclick="//foo`,
	1232  			context{state: stateJSLineCmt, delim: delimDoubleQuote, attr: attrScript},
	1233  		},
	1234  		{
	1235  			"<a onclick='//\n",
	1236  			context{state: stateJS, delim: delimSingleQuote, attr: attrScript},
	1237  		},
	1238  		{
	1239  			"<a onclick='//\r\n",
	1240  			context{state: stateJS, delim: delimSingleQuote, attr: attrScript},
	1241  		},
	1242  		{
	1243  			"<a onclick='//\u2028",
	1244  			context{state: stateJS, delim: delimSingleQuote, attr: attrScript},
	1245  		},
	1246  		{
	1247  			`<a onclick="/*`,
	1248  			context{state: stateJSBlockCmt, delim: delimDoubleQuote, attr: attrScript},
	1249  		},
	1250  		{
	1251  			`<a onclick="/*/`,
	1252  			context{state: stateJSBlockCmt, delim: delimDoubleQuote, attr: attrScript},
	1253  		},
	1254  		{
	1255  			`<a onclick="/**/`,
	1256  			context{state: stateJS, delim: delimDoubleQuote, attr: attrScript},
	1257  		},
	1258  		{
	1259  			`<a onkeypress="&quot;`,
	1260  			context{state: stateJSDqStr, delim: delimDoubleQuote, attr: attrScript},
	1261  		},
	1262  		{
	1263  			`<a onclick='&quot;foo&quot;`,
	1264  			context{state: stateJS, delim: delimSingleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
	1265  		},
	1266  		{
	1267  			`<a onclick=&#39;foo&#39;`,
	1268  			context{state: stateJS, delim: delimSpaceOrTagEnd, jsCtx: jsCtxDivOp, attr: attrScript},
	1269  		},
	1270  		{
	1271  			`<a onclick=&#39;foo`,
	1272  			context{state: stateJSSqStr, delim: delimSpaceOrTagEnd, attr: attrScript},
	1273  		},
	1274  		{
	1275  			`<a onclick="&quot;foo'`,
	1276  			context{state: stateJSDqStr, delim: delimDoubleQuote, attr: attrScript},
	1277  		},
	1278  		{
	1279  			`<a onclick="'foo&quot;`,
	1280  			context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript},
	1281  		},
	1282  		{
	1283  			`<A ONCLICK="'`,
	1284  			context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript},
	1285  		},
	1286  		{
	1287  			`<a onclick="/`,
	1288  			context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript},
	1289  		},
	1290  		{
	1291  			`<a onclick="'foo'`,
	1292  			context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
	1293  		},
	1294  		{
	1295  			`<a onclick="'foo\'`,
	1296  			context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript},
	1297  		},
	1298  		{
	1299  			`<a onclick="'foo\'`,
	1300  			context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript},
	1301  		},
	1302  		{
	1303  			`<a onclick="/foo/`,
	1304  			context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
	1305  		},
	1306  		{
	1307  			`<script>/foo/ /=`,
	1308  			context{state: stateJS, element: elementScript},
	1309  		},
	1310  		{
	1311  			`<a onclick="1 /foo`,
	1312  			context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
	1313  		},
	1314  		{
	1315  			`<a onclick="1 /*c*/ /foo`,
	1316  			context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
	1317  		},
	1318  		{
	1319  			`<a onclick="/foo[/]`,
	1320  			context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript},
	1321  		},
	1322  		{
	1323  			`<a onclick="/foo\/`,
	1324  			context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript},
	1325  		},
	1326  		{
	1327  			`<a onclick="/foo/`,
	1328  			context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript},
	1329  		},
	1330  		{
	1331  			`<input checked style="`,
	1332  			context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle},
	1333  		},
	1334  		{
	1335  			`<a style="//`,
	1336  			context{state: stateCSSLineCmt, delim: delimDoubleQuote, attr: attrStyle},
	1337  		},
	1338  		{
	1339  			`<a style="//</script>`,
	1340  			context{state: stateCSSLineCmt, delim: delimDoubleQuote, attr: attrStyle},
	1341  		},
	1342  		{
	1343  			"<a style='//\n",
	1344  			context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle},
	1345  		},
	1346  		{
	1347  			"<a style='//\r",
	1348  			context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle},
	1349  		},
	1350  		{
	1351  			`<a style="/*`,
	1352  			context{state: stateCSSBlockCmt, delim: delimDoubleQuote, attr: attrStyle},
	1353  		},
	1354  		{
	1355  			`<a style="/*/`,
	1356  			context{state: stateCSSBlockCmt, delim: delimDoubleQuote, attr: attrStyle},
	1357  		},
	1358  		{
	1359  			`<a style="/**/`,
	1360  			context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle},
	1361  		},
	1362  		{
	1363  			`<a style="background: '`,
	1364  			context{state: stateCSSSqStr, delim: delimDoubleQuote, attr: attrStyle},
	1365  		},
	1366  		{
	1367  			`<a style="background: &quot;`,
	1368  			context{state: stateCSSDqStr, delim: delimDoubleQuote, attr: attrStyle},
	1369  		},
	1370  		{
	1371  			`<a style="background: '/foo?img=`,
	1372  			context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag, attr: attrStyle},
	1373  		},
	1374  		{
	1375  			`<a style="background: '/`,
	1376  			context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
	1377  		},
	1378  		{
	1379  			`<a style="background: url(&#x22;/`,
	1380  			context{state: stateCSSDqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
	1381  		},
	1382  		{
	1383  			`<a style="background: url('/`,
	1384  			context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
	1385  		},
	1386  		{
	1387  			`<a style="background: url('/)`,
	1388  			context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
	1389  		},
	1390  		{
	1391  			`<a style="background: url('/ `,
	1392  			context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
	1393  		},
	1394  		{
	1395  			`<a style="background: url(/`,
	1396  			context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle},
	1397  		},
	1398  		{
	1399  			`<a style="background: url( `,
	1400  			context{state: stateCSSURL, delim: delimDoubleQuote, attr: attrStyle},
	1401  		},
	1402  		{
	1403  			`<a style="background: url( /image?name=`,
	1404  			context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag, attr: attrStyle},
	1405  		},
	1406  		{
	1407  			`<a style="background: url(x)`,
	1408  			context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle},
	1409  		},
	1410  		{
	1411  			`<a style="background: url('x'`,
	1412  			context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle},
	1413  		},
	1414  		{
	1415  			`<a style="background: url( x `,
	1416  			context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle},
	1417  		},
	1418  		{
	1419  			`<!-- foo`,
	1420  			context{state: stateHTMLCmt},
	1421  		},
	1422  		{
	1423  			`<!-->`,
	1424  			context{state: stateHTMLCmt},
	1425  		},
	1426  		{
	1427  			`<!--->`,
	1428  			context{state: stateHTMLCmt},
	1429  		},
	1430  		{
	1431  			`<!-- foo -->`,
	1432  			context{state: stateText},
	1433  		},
	1434  		{
	1435  			`<script`,
	1436  			context{state: stateTag, element: elementScript},
	1437  		},
	1438  		{
	1439  			`<script `,
	1440  			context{state: stateTag, element: elementScript},
	1441  		},
	1442  		{
	1443  			`<script src="foo.js" `,
	1444  			context{state: stateTag, element: elementScript},
	1445  		},
	1446  		{
	1447  			`<script src='foo.js' `,
	1448  			context{state: stateTag, element: elementScript},
	1449  		},
	1450  		{
	1451  			`<script type=text/javascript `,
	1452  			context{state: stateTag, element: elementScript},
	1453  		},
	1454  		{
	1455  			`<script>`,
	1456  			context{state: stateJS, jsCtx: jsCtxRegexp, element: elementScript},
	1457  		},
	1458  		{
	1459  			`<script>foo`,
	1460  			context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript},
	1461  		},
	1462  		{
	1463  			`<script>foo</script>`,
	1464  			context{state: stateText},
	1465  		},
	1466  		{
	1467  			`<script>foo</script><!--`,
	1468  			context{state: stateHTMLCmt},
	1469  		},
	1470  		{
	1471  			`<script>document.write("<p>foo</p>");`,
	1472  			context{state: stateJS, element: elementScript},
	1473  		},
	1474  		{
	1475  			`<script>document.write("<p>foo<\/script>");`,
	1476  			context{state: stateJS, element: elementScript},
	1477  		},
	1478  		{
	1479  			`<script>document.write("<script>alert(1)</script>");`,
	1480  			context{state: stateText},
	1481  		},
	1482  		{
	1483  			`<script type="text/template">`,
	1484  			context{state: stateText},
	1485  		},
	1486  		// covering issue 19968
	1487  		{
	1488  			`<script type="TEXT/JAVASCRIPT">`,
	1489  			context{state: stateJS, element: elementScript},
	1490  		},
	1491  		// covering issue 19965
	1492  		{
	1493  			`<script TYPE="text/template">`,
	1494  			context{state: stateText},
	1495  		},
	1496  		{
	1497  			`<script type="notjs">`,
	1498  			context{state: stateText},
	1499  		},
	1500  		{
	1501  			`<Script>`,
	1502  			context{state: stateJS, element: elementScript},
	1503  		},
	1504  		{
	1505  			`<SCRIPT>foo`,
	1506  			context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript},
	1507  		},
	1508  		{
	1509  			`<textarea>value`,
	1510  			context{state: stateRCDATA, element: elementTextarea},
	1511  		},
	1512  		{
	1513  			`<textarea>value</TEXTAREA>`,
	1514  			context{state: stateText},
	1515  		},
	1516  		{
	1517  			`<textarea name=html><b`,
	1518  			context{state: stateRCDATA, element: elementTextarea},
	1519  		},
	1520  		{
	1521  			`<title>value`,
	1522  			context{state: stateRCDATA, element: elementTitle},
	1523  		},
	1524  		{
	1525  			`<style>value`,
	1526  			context{state: stateCSS, element: elementStyle},
	1527  		},
	1528  		{
	1529  			`<a xlink:href`,
	1530  			context{state: stateAttrName, attr: attrURL},
	1531  		},
	1532  		{
	1533  			`<a xmlns`,
	1534  			context{state: stateAttrName, attr: attrURL},
	1535  		},
	1536  		{
	1537  			`<a xmlns:foo`,
	1538  			context{state: stateAttrName, attr: attrURL},
	1539  		},
	1540  		{
	1541  			`<a xmlnsxyz`,
	1542  			context{state: stateAttrName},
	1543  		},
	1544  		{
	1545  			`<a data-url`,
	1546  			context{state: stateAttrName, attr: attrURL},
	1547  		},
	1548  		{
	1549  			`<a data-iconUri`,
	1550  			context{state: stateAttrName, attr: attrURL},
	1551  		},
	1552  		{
	1553  			`<a data-urlItem`,
	1554  			context{state: stateAttrName, attr: attrURL},
	1555  		},
	1556  		{
	1557  			`<a g:`,
	1558  			context{state: stateAttrName},
	1559  		},
	1560  		{
	1561  			`<a g:url`,
	1562  			context{state: stateAttrName, attr: attrURL},
	1563  		},
	1564  		{
	1565  			`<a g:iconUri`,
	1566  			context{state: stateAttrName, attr: attrURL},
	1567  		},
	1568  		{
	1569  			`<a g:urlItem`,
	1570  			context{state: stateAttrName, attr: attrURL},
	1571  		},
	1572  		{
	1573  			`<a g:value`,
	1574  			context{state: stateAttrName},
	1575  		},
	1576  		{
	1577  			`<a svg:style='`,
	1578  			context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle},
	1579  		},
	1580  		{
	1581  			`<svg:font-face`,
	1582  			context{state: stateTag},
	1583  		},
	1584  		{
	1585  			`<svg:a svg:onclick="`,
	1586  			context{state: stateJS, delim: delimDoubleQuote, attr: attrScript},
	1587  		},
	1588  		{
	1589  			`<svg:a svg:onclick="x()">`,
	1590  			context{},
	1591  		},
	1592  	}
	1593  
	1594  	for _, test := range tests {
	1595  		b, e := []byte(test.input), makeEscaper(nil)
	1596  		c := e.escapeText(context{}, &parse.TextNode{NodeType: parse.NodeText, Text: b})
	1597  		if !test.output.eq(c) {
	1598  			t.Errorf("input %q: want context\n\t%v\ngot\n\t%v", test.input, test.output, c)
	1599  			continue
	1600  		}
	1601  		if test.input != string(b) {
	1602  			t.Errorf("input %q: text node was modified: want %q got %q", test.input, test.input, b)
	1603  			continue
	1604  		}
	1605  	}
	1606  }
	1607  
	1608  func TestEnsurePipelineContains(t *testing.T) {
	1609  	tests := []struct {
	1610  		input, output string
	1611  		ids					 []string
	1612  	}{
	1613  		{
	1614  			"{{.X}}",
	1615  			".X",
	1616  			[]string{},
	1617  		},
	1618  		{
	1619  			"{{.X | html}}",
	1620  			".X | html",
	1621  			[]string{},
	1622  		},
	1623  		{
	1624  			"{{.X}}",
	1625  			".X | html",
	1626  			[]string{"html"},
	1627  		},
	1628  		{
	1629  			"{{html .X}}",
	1630  			"_eval_args_ .X | html | urlquery",
	1631  			[]string{"html", "urlquery"},
	1632  		},
	1633  		{
	1634  			"{{html .X .Y .Z}}",
	1635  			"_eval_args_ .X .Y .Z | html | urlquery",
	1636  			[]string{"html", "urlquery"},
	1637  		},
	1638  		{
	1639  			"{{.X | print}}",
	1640  			".X | print | urlquery",
	1641  			[]string{"urlquery"},
	1642  		},
	1643  		{
	1644  			"{{.X | print | urlquery}}",
	1645  			".X | print | urlquery",
	1646  			[]string{"urlquery"},
	1647  		},
	1648  		{
	1649  			"{{.X | urlquery}}",
	1650  			".X | html | urlquery",
	1651  			[]string{"html", "urlquery"},
	1652  		},
	1653  		{
	1654  			"{{.X | print 2 | .f 3}}",
	1655  			".X | print 2 | .f 3 | urlquery | html",
	1656  			[]string{"urlquery", "html"},
	1657  		},
	1658  		{
	1659  			// covering issue 10801
	1660  			"{{.X | println.x }}",
	1661  			".X | println.x | urlquery | html",
	1662  			[]string{"urlquery", "html"},
	1663  		},
	1664  		{
	1665  			// covering issue 10801
	1666  			"{{.X | (print 12 | println).x }}",
	1667  			".X | (print 12 | println).x | urlquery | html",
	1668  			[]string{"urlquery", "html"},
	1669  		},
	1670  		// The following test cases ensure that the merging of internal escapers
	1671  		// with the predefined "html" and "urlquery" escapers is correct.
	1672  		{
	1673  			"{{.X | urlquery}}",
	1674  			".X | _html_template_urlfilter | urlquery",
	1675  			[]string{"_html_template_urlfilter", "_html_template_urlnormalizer"},
	1676  		},
	1677  		{
	1678  			"{{.X | urlquery}}",
	1679  			".X | urlquery | _html_template_urlfilter | _html_template_cssescaper",
	1680  			[]string{"_html_template_urlfilter", "_html_template_cssescaper"},
	1681  		},
	1682  		{
	1683  			"{{.X | urlquery}}",
	1684  			".X | urlquery",
	1685  			[]string{"_html_template_urlnormalizer"},
	1686  		},
	1687  		{
	1688  			"{{.X | urlquery}}",
	1689  			".X | urlquery",
	1690  			[]string{"_html_template_urlescaper"},
	1691  		},
	1692  		{
	1693  			"{{.X | html}}",
	1694  			".X | html",
	1695  			[]string{"_html_template_htmlescaper"},
	1696  		},
	1697  		{
	1698  			"{{.X | html}}",
	1699  			".X | html",
	1700  			[]string{"_html_template_rcdataescaper"},
	1701  		},
	1702  	}
	1703  	for i, test := range tests {
	1704  		tmpl := template.Must(template.New("test").Parse(test.input))
	1705  		action, ok := (tmpl.Tree.Root.Nodes[0].(*parse.ActionNode))
	1706  		if !ok {
	1707  			t.Errorf("First node is not an action: %s", test.input)
	1708  			continue
	1709  		}
	1710  		pipe := action.Pipe
	1711  		originalIDs := make([]string, len(test.ids))
	1712  		copy(originalIDs, test.ids)
	1713  		ensurePipelineContains(pipe, test.ids)
	1714  		got := pipe.String()
	1715  		if got != test.output {
	1716  			t.Errorf("#%d: %s, %v: want\n\t%s\ngot\n\t%s", i, test.input, originalIDs, test.output, got)
	1717  		}
	1718  	}
	1719  }
	1720  
	1721  func TestEscapeMalformedPipelines(t *testing.T) {
	1722  	tests := []string{
	1723  		"{{ 0 | $ }}",
	1724  		"{{ 0 | $ | urlquery }}",
	1725  		"{{ 0 | (nil) }}",
	1726  		"{{ 0 | (nil) | html }}",
	1727  	}
	1728  	for _, test := range tests {
	1729  		var b bytes.Buffer
	1730  		tmpl, err := New("test").Parse(test)
	1731  		if err != nil {
	1732  			t.Errorf("failed to parse set: %q", err)
	1733  		}
	1734  		err = tmpl.Execute(&b, nil)
	1735  		if err == nil {
	1736  			t.Errorf("Expected error for %q", test)
	1737  		}
	1738  	}
	1739  }
	1740  
	1741  func TestEscapeErrorsNotIgnorable(t *testing.T) {
	1742  	var b bytes.Buffer
	1743  	tmpl, _ := New("dangerous").Parse("<a")
	1744  	err := tmpl.Execute(&b, nil)
	1745  	if err == nil {
	1746  		t.Errorf("Expected error")
	1747  	} else if b.Len() != 0 {
	1748  		t.Errorf("Emitted output despite escaping failure")
	1749  	}
	1750  }
	1751  
	1752  func TestEscapeSetErrorsNotIgnorable(t *testing.T) {
	1753  	var b bytes.Buffer
	1754  	tmpl, err := New("root").Parse(`{{define "t"}}<a{{end}}`)
	1755  	if err != nil {
	1756  		t.Errorf("failed to parse set: %q", err)
	1757  	}
	1758  	err = tmpl.ExecuteTemplate(&b, "t", nil)
	1759  	if err == nil {
	1760  		t.Errorf("Expected error")
	1761  	} else if b.Len() != 0 {
	1762  		t.Errorf("Emitted output despite escaping failure")
	1763  	}
	1764  }
	1765  
	1766  func TestRedundantFuncs(t *testing.T) {
	1767  	inputs := []interface{}{
	1768  		"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
	1769  			"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
	1770  			` !"#$%&'()*+,-./` +
	1771  			`0123456789:;<=>?` +
	1772  			`@ABCDEFGHIJKLMNO` +
	1773  			`PQRSTUVWXYZ[\]^_` +
	1774  			"`abcdefghijklmno" +
	1775  			"pqrstuvwxyz{|}~\x7f" +
	1776  			"\u00A0\u0100\u2028\u2029\ufeff\ufdec\ufffd\uffff\U0001D11E" +
	1777  			"&amp;%22\\",
	1778  		CSS(`a[href =~ "//example.com"]#foo`),
	1779  		HTML(`Hello, <b>World</b> &amp;tc!`),
	1780  		HTMLAttr(` dir="ltr"`),
	1781  		JS(`c && alert("Hello, World!");`),
	1782  		JSStr(`Hello, World & O'Reilly\x21`),
	1783  		URL(`greeting=H%69&addressee=(World)`),
	1784  	}
	1785  
	1786  	for n0, m := range redundantFuncs {
	1787  		f0 := funcMap[n0].(func(...interface{}) string)
	1788  		for n1 := range m {
	1789  			f1 := funcMap[n1].(func(...interface{}) string)
	1790  			for _, input := range inputs {
	1791  				want := f0(input)
	1792  				if got := f1(want); want != got {
	1793  					t.Errorf("%s %s with %T %q: want\n\t%q,\ngot\n\t%q", n0, n1, input, input, want, got)
	1794  				}
	1795  			}
	1796  		}
	1797  	}
	1798  }
	1799  
	1800  func TestIndirectPrint(t *testing.T) {
	1801  	a := 3
	1802  	ap := &a
	1803  	b := "hello"
	1804  	bp := &b
	1805  	bpp := &bp
	1806  	tmpl := Must(New("t").Parse(`{{.}}`))
	1807  	var buf bytes.Buffer
	1808  	err := tmpl.Execute(&buf, ap)
	1809  	if err != nil {
	1810  		t.Errorf("Unexpected error: %s", err)
	1811  	} else if buf.String() != "3" {
	1812  		t.Errorf(`Expected "3"; got %q`, buf.String())
	1813  	}
	1814  	buf.Reset()
	1815  	err = tmpl.Execute(&buf, bpp)
	1816  	if err != nil {
	1817  		t.Errorf("Unexpected error: %s", err)
	1818  	} else if buf.String() != "hello" {
	1819  		t.Errorf(`Expected "hello"; got %q`, buf.String())
	1820  	}
	1821  }
	1822  
	1823  // This is a test for issue 3272.
	1824  func TestEmptyTemplateHTML(t *testing.T) {
	1825  	page := Must(New("page").ParseFiles(os.DevNull))
	1826  	if err := page.ExecuteTemplate(os.Stdout, "page", "nothing"); err == nil {
	1827  		t.Fatal("expected error")
	1828  	}
	1829  }
	1830  
	1831  type Issue7379 int
	1832  
	1833  func (Issue7379) SomeMethod(x int) string {
	1834  	return fmt.Sprintf("<%d>", x)
	1835  }
	1836  
	1837  // This is a test for issue 7379: type assertion error caused panic, and then
	1838  // the code to handle the panic breaks escaping. It's hard to see the second
	1839  // problem once the first is fixed, but its fix is trivial so we let that go. See
	1840  // the discussion for issue 7379.
	1841  func TestPipeToMethodIsEscaped(t *testing.T) {
	1842  	tmpl := Must(New("x").Parse("<html>{{0 | .SomeMethod}}</html>\n"))
	1843  	tryExec := func() string {
	1844  		defer func() {
	1845  			panicValue := recover()
	1846  			if panicValue != nil {
	1847  				t.Errorf("panicked: %v\n", panicValue)
	1848  			}
	1849  		}()
	1850  		var b bytes.Buffer
	1851  		tmpl.Execute(&b, Issue7379(0))
	1852  		return b.String()
	1853  	}
	1854  	for i := 0; i < 3; i++ {
	1855  		str := tryExec()
	1856  		const expect = "<html>&lt;0&gt;</html>\n"
	1857  		if str != expect {
	1858  			t.Errorf("expected %q got %q", expect, str)
	1859  		}
	1860  	}
	1861  }
	1862  
	1863  // Unlike text/template, html/template crashed if given an incomplete
	1864  // template, that is, a template that had been named but not given any content.
	1865  // This is issue #10204.
	1866  func TestErrorOnUndefined(t *testing.T) {
	1867  	tmpl := New("undefined")
	1868  
	1869  	err := tmpl.Execute(nil, nil)
	1870  	if err == nil {
	1871  		t.Error("expected error")
	1872  	} else if !strings.Contains(err.Error(), "incomplete") {
	1873  		t.Errorf("expected error about incomplete template; got %s", err)
	1874  	}
	1875  }
	1876  
	1877  // This covers issue #20842.
	1878  func TestIdempotentExecute(t *testing.T) {
	1879  	tmpl := Must(New("").
	1880  		Parse(`{{define "main"}}<body>{{template "hello"}}</body>{{end}}`))
	1881  	Must(tmpl.
	1882  		Parse(`{{define "hello"}}Hello, {{"Ladies & Gentlemen!"}}{{end}}`))
	1883  	got := new(bytes.Buffer)
	1884  	var err error
	1885  	// Ensure that "hello" produces the same output when executed twice.
	1886  	want := "Hello, Ladies &amp; Gentlemen!"
	1887  	for i := 0; i < 2; i++ {
	1888  		err = tmpl.ExecuteTemplate(got, "hello", nil)
	1889  		if err != nil {
	1890  			t.Errorf("unexpected error: %s", err)
	1891  		}
	1892  		if got.String() != want {
	1893  			t.Errorf("after executing template \"hello\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want)
	1894  		}
	1895  		got.Reset()
	1896  	}
	1897  	// Ensure that the implicit re-execution of "hello" during the execution of
	1898  	// "main" does not cause the output of "hello" to change.
	1899  	err = tmpl.ExecuteTemplate(got, "main", nil)
	1900  	if err != nil {
	1901  		t.Errorf("unexpected error: %s", err)
	1902  	}
	1903  	// If the HTML escaper is added again to the action {{"Ladies & Gentlemen!"}},
	1904  	// we would expected to see the ampersand overescaped to "&amp;amp;".
	1905  	want = "<body>Hello, Ladies &amp; Gentlemen!</body>"
	1906  	if got.String() != want {
	1907  		t.Errorf("after executing template \"main\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want)
	1908  	}
	1909  }
	1910  
	1911  func BenchmarkEscapedExecute(b *testing.B) {
	1912  	tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`))
	1913  	var buf bytes.Buffer
	1914  	b.ResetTimer()
	1915  	for i := 0; i < b.N; i++ {
	1916  		tmpl.Execute(&buf, "foo & 'bar' & baz")
	1917  		buf.Reset()
	1918  	}
	1919  }
	1920  
	1921  // Covers issue 22780.
	1922  func TestOrphanedTemplate(t *testing.T) {
	1923  	t1 := Must(New("foo").Parse(`<a href="{{.}}">link1</a>`))
	1924  	t2 := Must(t1.New("foo").Parse(`bar`))
	1925  
	1926  	var b bytes.Buffer
	1927  	const wantError = `template: "foo" is an incomplete or empty template`
	1928  	if err := t1.Execute(&b, "javascript:alert(1)"); err == nil {
	1929  		t.Fatal("expected error executing t1")
	1930  	} else if gotError := err.Error(); gotError != wantError {
	1931  		t.Fatalf("got t1 execution error:\n\t%s\nwant:\n\t%s", gotError, wantError)
	1932  	}
	1933  	b.Reset()
	1934  	if err := t2.Execute(&b, nil); err != nil {
	1935  		t.Fatalf("error executing t2: %s", err)
	1936  	}
	1937  	const want = "bar"
	1938  	if got := b.String(); got != want {
	1939  		t.Fatalf("t2 rendered %q, want %q", got, want)
	1940  	}
	1941  }
	1942  
	1943  // Covers issue 21844.
	1944  func TestAliasedParseTreeDoesNotOverescape(t *testing.T) {
	1945  	const (
	1946  		tmplText = `{{.}}`
	1947  		data		 = `<baz>`
	1948  		want		 = `&lt;baz&gt;`
	1949  	)
	1950  	// Templates "foo" and "bar" both alias the same underlying parse tree.
	1951  	tpl := Must(New("foo").Parse(tmplText))
	1952  	if _, err := tpl.AddParseTree("bar", tpl.Tree); err != nil {
	1953  		t.Fatalf("AddParseTree error: %v", err)
	1954  	}
	1955  	var b1, b2 bytes.Buffer
	1956  	if err := tpl.ExecuteTemplate(&b1, "foo", data); err != nil {
	1957  		t.Fatalf(`ExecuteTemplate failed for "foo": %v`, err)
	1958  	}
	1959  	if err := tpl.ExecuteTemplate(&b2, "bar", data); err != nil {
	1960  		t.Fatalf(`ExecuteTemplate failed for "foo": %v`, err)
	1961  	}
	1962  	got1, got2 := b1.String(), b2.String()
	1963  	if got1 != want {
	1964  		t.Fatalf(`Template "foo" rendered %q, want %q`, got1, want)
	1965  	}
	1966  	if got1 != got2 {
	1967  		t.Fatalf(`Template "foo" and "bar" rendered %q and %q respectively, expected equal values`, got1, got2)
	1968  	}
	1969  }
	1970  

View as plain text