OSDN Git Service

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