github.com/c9s/go@v0.0.0-20180120015821-984e81f64e0c/src/html/template/escape_test.go (about) 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(`¡<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, <Cincinatti>!", 65 }, 66 { 67 "else", 68 "{{if .F}}{{.H}}{{else}}{{.G}}{{end}}!", 69 "<Goodbye>!", 70 }, 71 { 72 "overescaping1", 73 "Hello, {{.C | html}}!", 74 "Hello, <Cincinatti>!", 75 }, 76 { 77 "overescaping2", 78 "Hello, {{html .C}}!", 79 "Hello, <Cincinatti>!", 80 }, 81 { 82 "overescaping3", 83 "{{with .C}}{{$msg := .}}Hello, {{$msg}}!{{end}}", 84 "Hello, <Cincinatti>!", 85 }, 86 { 87 "assignment", 88 "{{if $x := .H}}{{$x}}{{end}}", 89 "<Hello>", 90 }, 91 { 92 "withBody", 93 "{{with .H}}{{.}}{{end}}", 94 "<Hello>", 95 }, 96 { 97 "withElse", 98 "{{with .E}}{{.}}{{else}}{{.H}}{{end}}", 99 "<Hello>", 100 }, 101 { 102 "rangeBody", 103 "{{range .A}}{{.}}{{end}}", 104 "<a><b>", 105 }, 106 { 107 "rangeElse", 108 "{{range .E}}{{.}}{{else}}{{.H}}{{end}}", 109 "<Hello>", 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=<Hello>>", 125 }, 126 { 127 "urlStartRel", 128 `<a href='{{"/foo/bar?a=b&c=d"}}'>`, 129 `<a href='/foo/bar?a=b&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&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&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("\u003cHello\u003e")'>`, 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(["\u003ca\u003e","\u003cb\u003e"])'>`, 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(["\u003ca\u003e","\u003cb\u003e"])'>`, 220 }, 221 { 222 "jsStr", 223 "<button onclick='alert("{{.H}}")'>", 224 `<button onclick='alert("\x3cHello\x3e")'>`, 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 'f' looking for beginning of object key string */null in numbers)'>`, 230 }, 231 { 232 "jsMarshaler", 233 `<button onclick='alert({{.M}})'>`, 234 `<button onclick='alert({"\u003cfoo\u003e":"O'Reilly"})'>`, 235 }, 236 { 237 "jsStrNotUnderEscaped", 238 "<button onclick='alert({{.C | urlquery}})'>", 239 // URL escaped, then quoted for JS. 240 `<button onclick='alert("%3CCincinatti%3E")'>`, 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 contain 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&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: '{{"/**/'\";:// \\"}}', "{{"/**/'\";:// \\"}}"">`, 398 `<a style="font-family: '\2f**\2f\27\22\3b\3a\2f\2f \\', "\2f**\2f\27\22\3b\3a\2f\2f \\"">`, 399 }, 400 { 401 "styleURLSpecialsEncoded", 402 `<a style="border-image: url({{"/**/'\";:// \\"}}), url("{{"/**/'\";:// \\"}}"), url('{{"/**/'\";:// \\"}}'), 'http://www.example.com/?q={{"/**/'\";:// \\"}}''">`, 403 `<a style="border-image: url(/**/%27%22;://%20%5c), url("/**/%27%22;://%20%5c"), 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, <Cincinatti></b>", 409 }, 410 { 411 "HTML comment not first < in text node.", 412 "<<!-- -->!--", 413 "<!--", 414 }, 415 { 416 "HTML normalization 1", 417 "a < b", 418 "a < b", 419 }, 420 { 421 "HTML normalization 2", 422 "a << b", 423 "a << b", 424 }, 425 { 426 "HTML normalization 3", 427 "a<<!-- --><!-- -->b", 428 "a<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 "<!DOCTYPE", 444 }, 445 { 446 "Split HTML comment", 447 "<b>Hello, <!-- name of {{if .T}}city -->{{.C}}{{else}}world -->{{.W}}{{end}}</b>", 448 "<b>Hello, <Cincinatti></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(""); /* alert({{.H}}) */">`, 499 // Attribute comment tests should pass if the comments 500 // are successfully elided. 501 `<a onclick="f(""); /* 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 `¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!`, 532 }, 533 { 534 "typed HTML in attribute", 535 `<div title="{{.W}}">`, 536 `<div title="¡Hello, O'World!">`, 537 }, 538 { 539 "typed HTML in script", 540 `<button onclick="alert({{.W}})">`, 541 `<button onclick="alert("\u0026iexcl;\u003cb class=\"foo\"\u003eHello\u003c/b\u003e, \u003ctextarea\u003eO'World\u003c/textarea\u003e!")">`, 542 }, 543 { 544 "typed HTML in RCDATA", 545 `<textarea>{{.W}}</textarea>`, 546 `<textarea>¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!</textarea>`, 547 }, 548 { 549 "range in textarea", 550 "<textarea>{{range .A}}{{.}}{{end}}</textarea>", 551 "<textarea><a><b></textarea>", 552 }, 553 { 554 "No tag injection", 555 `{{"10$"}}<{{"script src,evil.org/pwnd.js"}}...`, 556 `10$<script src,evil.org/pwnd.js...`, 557 }, 558 { 559 "No comment injection", 560 `<{{"!--"}}`, 561 `<!--`, 562 }, 563 { 564 "No RCDATA end tag injection", 565 `<textarea><{{"/textarea "}}...</textarea>`, 566 `<textarea></textarea ...</textarea>`, 567 }, 568 { 569 "optional attrs", 570 `<img class="{{"iconClass"}}"` + 571 `{{if .T}} id="{{"<iconId>"}}"{{end}}` + 572 // Double quotes inside if/else. 573 ` src=` + 574 `{{if .T}}"?{{"<iconPath>"}}"` + 575 `{{else}}"images/cleardot.gif"{{end}}` + 576 // Missing space before title, but it is not a 577 // part of the src attribute. 578 `{{if .T}}title="{{"<title>"}}"{{end}}` + 579 // Quotes outside if/else. 580 ` alt="` + 581 `{{if .T}}{{"<alt>"}}` + 582 `{{else}}{{if .F}}{{"<title>"}}{{end}}` + 583 `{{end}}"` + 584 `>`, 585 `<img class="iconClass" id="<iconId>" src="?%3ciconPath%3e"title="<title>" alt="<alt>">`, 586 }, 587 { 588 "conditional valueless attr name", 589 `<input{{if .T}} checked{{end}} name=n>`, 590 `<input checked name=n>`, 591 }, 592 { 593 "conditional dynamic valueless attr name 1", 594 `<input{{if .T}} {{"checked"}}{{end}} name=n>`, 595 `<input checked name=n>`, 596 }, 597 { 598 "conditional dynamic valueless attr name 2", 599 `<input {{if .T}}{{"checked"}} {{end}}name=n>`, 600 `<input checked name=n>`, 601 }, 602 { 603 "dynamic attribute name", 604 `<img on{{"load"}}="alert({{"loaded"}})">`, 605 // Treated as JS since quotes are inserted. 606 `<img onload="alert("loaded")">`, 607 }, 608 { 609 "bad dynamic attribute name 1", 610 // Allow checked, selected, disabled, but not JS or 611 // CSS attributes. 612 `<input {{"onchange"}}="{{"doEvil()"}}">`, 613 `<input ZgotmplZ="doEvil()">`, 614 }, 615 { 616 "bad dynamic attribute name 2", 617 `<div {{"sTyle"}}="{{"color: expression(alert(1337))"}}">`, 618 `<div ZgotmplZ="color: expression(alert(1337))">`, 619 }, 620 { 621 "bad dynamic attribute name 3", 622 // Allow title or alt, but not a URL. 623 `<img {{"src"}}="{{"javascript:doEvil()"}}">`, 624 `<img ZgotmplZ="javascript:doEvil()">`, 625 }, 626 { 627 "bad dynamic attribute name 4", 628 // Structure preservation requires values to associate 629 // with a consistent attribute. 630 `<input checked {{""}}="Whose value am I?">`, 631 `<input checked ZgotmplZ="Whose value am I?">`, 632 }, 633 { 634 "dynamic element name", 635 `<h{{3}}><table><t{{"head"}}>...</h{{3}}>`, 636 `<h3><table><thead>...</h3>`, 637 }, 638 { 639 "bad dynamic element name", 640 // Dynamic element names are typically used to switch 641 // between (thead, tfoot, tbody), (ul, ol), (th, td), 642 // and other replaceable sets. 643 // We do not currently easily support (ul, ol). 644 // If we do change to support that, this test should 645 // catch failures to filter out special tag names which 646 // would violate the structure preservation property -- 647 // if any special tag name could be substituted, then 648 // the content could be raw text/RCDATA for some inputs 649 // and regular HTML content for others. 650 `<{{"script"}}>{{"doEvil()"}}</{{"script"}}>`, 651 `<script>doEvil()</script>`, 652 }, 653 { 654 "srcset bad URL in second position", 655 `<img srcset="{{"/not-an-image#,javascript:alert(1)"}}">`, 656 // The second URL is also filtered. 657 `<img srcset="/not-an-image#,#ZgotmplZ">`, 658 }, 659 } 660 661 for _, test := range tests { 662 tmpl := New(test.name) 663 tmpl = Must(tmpl.Parse(test.input)) 664 // Check for bug 6459: Tree field was not set in Parse. 665 if tmpl.Tree != tmpl.text.Tree { 666 t.Errorf("%s: tree not set properly", test.name) 667 continue 668 } 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 if tmpl.Tree != tmpl.text.Tree { 688 t.Errorf("%s: tree mismatch", test.name) 689 continue 690 } 691 } 692 } 693 694 func TestEscapeMap(t *testing.T) { 695 data := map[string]string{ 696 "html": `<h1>Hi!</h1>`, 697 "urlquery": `http://www.foo.com/index.html?title=main`, 698 } 699 for _, test := range [...]struct { 700 desc, input, output string 701 }{ 702 // covering issue 20323 703 { 704 "field with predefined escaper name 1", 705 `{{.html | print}}`, 706 `<h1>Hi!</h1>`, 707 }, 708 // covering issue 20323 709 { 710 "field with predefined escaper name 2", 711 `{{.urlquery | print}}`, 712 `http://www.foo.com/index.html?title=main`, 713 }, 714 } { 715 tmpl := Must(New("").Parse(test.input)) 716 b := new(bytes.Buffer) 717 if err := tmpl.Execute(b, data); err != nil { 718 t.Errorf("%s: template execution failed: %s", test.desc, err) 719 continue 720 } 721 if w, g := test.output, b.String(); w != g { 722 t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.desc, w, g) 723 continue 724 } 725 } 726 } 727 728 func TestEscapeSet(t *testing.T) { 729 type dataItem struct { 730 Children []*dataItem 731 X string 732 } 733 734 data := dataItem{ 735 Children: []*dataItem{ 736 {X: "foo"}, 737 {X: "<bar>"}, 738 { 739 Children: []*dataItem{ 740 {X: "baz"}, 741 }, 742 }, 743 }, 744 } 745 746 tests := []struct { 747 inputs map[string]string 748 want string 749 }{ 750 // The trivial set. 751 { 752 map[string]string{ 753 "main": ``, 754 }, 755 ``, 756 }, 757 // A template called in the start context. 758 { 759 map[string]string{ 760 "main": `Hello, {{template "helper"}}!`, 761 // Not a valid top level HTML template. 762 // "<b" is not a full tag. 763 "helper": `{{"<World>"}}`, 764 }, 765 `Hello, <World>!`, 766 }, 767 // A template called in a context other than the start. 768 { 769 map[string]string{ 770 "main": `<a onclick='a = {{template "helper"}};'>`, 771 // Not a valid top level HTML template. 772 // "<b" is not a full tag. 773 "helper": `{{"<a>"}}<b`, 774 }, 775 `<a onclick='a = "\u003ca\u003e"<b;'>`, 776 }, 777 // A recursive template that ends in its start context. 778 { 779 map[string]string{ 780 "main": `{{range .Children}}{{template "main" .}}{{else}}{{.X}} {{end}}`, 781 }, 782 `foo <bar> baz `, 783 }, 784 // A recursive helper template that ends in its start context. 785 { 786 map[string]string{ 787 "main": `{{template "helper" .}}`, 788 "helper": `{{if .Children}}<ul>{{range .Children}}<li>{{template "main" .}}</li>{{end}}</ul>{{else}}{{.X}}{{end}}`, 789 }, 790 `<ul><li>foo</li><li><bar></li><li><ul><li>baz</li></ul></li></ul>`, 791 }, 792 // Co-recursive templates that end in its start context. 793 { 794 map[string]string{ 795 "main": `<blockquote>{{range .Children}}{{template "helper" .}}{{end}}</blockquote>`, 796 "helper": `{{if .Children}}{{template "main" .}}{{else}}{{.X}}<br>{{end}}`, 797 }, 798 `<blockquote>foo<br><bar><br><blockquote>baz<br></blockquote></blockquote>`, 799 }, 800 // A template that is called in two different contexts. 801 { 802 map[string]string{ 803 "main": `<button onclick="title='{{template "helper"}}'; ...">{{template "helper"}}</button>`, 804 "helper": `{{11}} of {{"<100>"}}`, 805 }, 806 `<button onclick="title='11 of \x3c100\x3e'; ...">11 of <100></button>`, 807 }, 808 // A non-recursive template that ends in a different context. 809 // helper starts in jsCtxRegexp and ends in jsCtxDivOp. 810 { 811 map[string]string{ 812 "main": `<script>var x={{template "helper"}}/{{"42"}};</script>`, 813 "helper": "{{126}}", 814 }, 815 `<script>var x= 126 /"42";</script>`, 816 }, 817 // A recursive template that ends in a similar context. 818 { 819 map[string]string{ 820 "main": `<script>var x=[{{template "countdown" 4}}];</script>`, 821 "countdown": `{{.}}{{if .}},{{template "countdown" . | pred}}{{end}}`, 822 }, 823 `<script>var x=[ 4 , 3 , 2 , 1 , 0 ];</script>`, 824 }, 825 // A recursive template that ends in a different context. 826 /* 827 { 828 map[string]string{ 829 "main": `<a href="/foo{{template "helper" .}}">`, 830 "helper": `{{if .Children}}{{range .Children}}{{template "helper" .}}{{end}}{{else}}?x={{.X}}{{end}}`, 831 }, 832 `<a href="/foo?x=foo?x=%3cbar%3e?x=baz">`, 833 }, 834 */ 835 } 836 837 // pred is a template function that returns the predecessor of a 838 // natural number for testing recursive templates. 839 fns := FuncMap{"pred": func(a ...interface{}) (interface{}, error) { 840 if len(a) == 1 { 841 if i, _ := a[0].(int); i > 0 { 842 return i - 1, nil 843 } 844 } 845 return nil, fmt.Errorf("undefined pred(%v)", a) 846 }} 847 848 for _, test := range tests { 849 source := "" 850 for name, body := range test.inputs { 851 source += fmt.Sprintf("{{define %q}}%s{{end}} ", name, body) 852 } 853 tmpl, err := New("root").Funcs(fns).Parse(source) 854 if err != nil { 855 t.Errorf("error parsing %q: %v", source, err) 856 continue 857 } 858 var b bytes.Buffer 859 860 if err := tmpl.ExecuteTemplate(&b, "main", data); err != nil { 861 t.Errorf("%q executing %v", err.Error(), tmpl.Lookup("main")) 862 continue 863 } 864 if got := b.String(); test.want != got { 865 t.Errorf("want\n\t%q\ngot\n\t%q", test.want, got) 866 } 867 } 868 869 } 870 871 func TestErrors(t *testing.T) { 872 tests := []struct { 873 input string 874 err string 875 }{ 876 // Non-error cases. 877 { 878 "{{if .Cond}}<a>{{else}}<b>{{end}}", 879 "", 880 }, 881 { 882 "{{if .Cond}}<a>{{end}}", 883 "", 884 }, 885 { 886 "{{if .Cond}}{{else}}<b>{{end}}", 887 "", 888 }, 889 { 890 "{{with .Cond}}<div>{{end}}", 891 "", 892 }, 893 { 894 "{{range .Items}}<a>{{end}}", 895 "", 896 }, 897 { 898 "<a href='/foo?{{range .Items}}&{{.K}}={{.V}}{{end}}'>", 899 "", 900 }, 901 // Error cases. 902 { 903 "{{if .Cond}}<a{{end}}", 904 "z:1:5: {{if}} branches", 905 }, 906 { 907 "{{if .Cond}}\n{{else}}\n<a{{end}}", 908 "z:1:5: {{if}} branches", 909 }, 910 { 911 // Missing quote in the else branch. 912 `{{if .Cond}}<a href="foo">{{else}}<a href="bar>{{end}}`, 913 "z:1:5: {{if}} branches", 914 }, 915 { 916 // Different kind of attribute: href implies a URL. 917 "<a {{if .Cond}}href='{{else}}title='{{end}}{{.X}}'>", 918 "z:1:8: {{if}} branches", 919 }, 920 { 921 "\n{{with .X}}<a{{end}}", 922 "z:2:7: {{with}} branches", 923 }, 924 { 925 "\n{{with .X}}<a>{{else}}<a{{end}}", 926 "z:2:7: {{with}} branches", 927 }, 928 { 929 "{{range .Items}}<a{{end}}", 930 `z:1: on range loop re-entry: "<" in attribute name: "<a"`, 931 }, 932 { 933 "\n{{range .Items}} x='<a{{end}}", 934 "z:2:8: on range loop re-entry: {{range}} branches", 935 }, 936 { 937 "<a b=1 c={{.H}}", 938 "z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd", 939 }, 940 { 941 "<script>foo();", 942 "z: ends in a non-text context: {stateJS", 943 }, 944 { 945 `<a href="{{if .F}}/foo?a={{else}}/bar/{{end}}{{.H}}">`, 946 "z:1:47: {{.H}} appears in an ambiguous context within a URL", 947 }, 948 { 949 `<a onclick="alert('Hello \`, 950 `unfinished escape sequence in JS string: "Hello \\"`, 951 }, 952 { 953 `<a onclick='alert("Hello\, World\`, 954 `unfinished escape sequence in JS string: "Hello\\, World\\"`, 955 }, 956 { 957 `<a onclick='alert(/x+\`, 958 `unfinished escape sequence in JS string: "x+\\"`, 959 }, 960 { 961 `<a onclick="/foo[\]/`, 962 `unfinished JS regexp charset: "foo[\\]/"`, 963 }, 964 { 965 // It is ambiguous whether 1.5 should be 1\.5 or 1.5. 966 // Either `var x = 1/- 1.5 /i.test(x)` 967 // where `i.test(x)` is a method call of reference i, 968 // or `/-1\.5/i.test(x)` which is a method call on a 969 // case insensitive regular expression. 970 `<script>{{if false}}var x = 1{{end}}/-{{"1.5"}}/i.test(x)</script>`, 971 `'/' could start a division or regexp: "/-"`, 972 }, 973 { 974 `{{template "foo"}}`, 975 "z:1:11: no such template \"foo\"", 976 }, 977 { 978 `<div{{template "y"}}>` + 979 // Illegal starting in stateTag but not in stateText. 980 `{{define "y"}} foo<b{{end}}`, 981 `"<" in attribute name: " foo<b"`, 982 }, 983 { 984 `<script>reverseList = [{{template "t"}}]</script>` + 985 // Missing " after recursive call. 986 `{{define "t"}}{{if .Tail}}{{template "t" .Tail}}{{end}}{{.Head}}",{{end}}`, 987 `: cannot compute output context for template t$htmltemplate_stateJS_elementScript`, 988 }, 989 { 990 `<input type=button value=onclick=>`, 991 `html/template:z: "=" in unquoted attr: "onclick="`, 992 }, 993 { 994 `<input type=button value= onclick=>`, 995 `html/template:z: "=" in unquoted attr: "onclick="`, 996 }, 997 { 998 `<input type=button value= 1+1=2>`, 999 `html/template:z: "=" in unquoted attr: "1+1=2"`, 1000 }, 1001 { 1002 "<a class=`foo>", 1003 "html/template:z: \"`\" in unquoted attr: \"`foo\"", 1004 }, 1005 { 1006 `<a style=font:'Arial'>`, 1007 `html/template:z: "'" in unquoted attr: "font:'Arial'"`, 1008 }, 1009 { 1010 `<a=foo>`, 1011 `: expected space, attr name, or end of tag, but got "=foo>"`, 1012 }, 1013 { 1014 `Hello, {{. | urlquery | print}}!`, 1015 // urlquery is disallowed if it is not the last command in the pipeline. 1016 `predefined escaper "urlquery" disallowed in template`, 1017 }, 1018 { 1019 `Hello, {{. | html | print}}!`, 1020 // html is disallowed if it is not the last command in the pipeline. 1021 `predefined escaper "html" disallowed in template`, 1022 }, 1023 { 1024 `Hello, {{html . | print}}!`, 1025 // A direct call to html is disallowed if it is not the last command in the pipeline. 1026 `predefined escaper "html" disallowed in template`, 1027 }, 1028 { 1029 `<div class={{. | html}}>Hello<div>`, 1030 // html is disallowed in a pipeline that is in an unquoted attribute context, 1031 // even if it is the last command in the pipeline. 1032 `predefined escaper "html" disallowed in template`, 1033 }, 1034 { 1035 `Hello, {{. | urlquery | html}}!`, 1036 // html is allowed since it is the last command in the pipeline, but urlquery is not. 1037 `predefined escaper "urlquery" disallowed in template`, 1038 }, 1039 } 1040 for _, test := range tests { 1041 buf := new(bytes.Buffer) 1042 tmpl, err := New("z").Parse(test.input) 1043 if err != nil { 1044 t.Errorf("input=%q: unexpected parse error %s\n", test.input, err) 1045 continue 1046 } 1047 err = tmpl.Execute(buf, nil) 1048 var got string 1049 if err != nil { 1050 got = err.Error() 1051 } 1052 if test.err == "" { 1053 if got != "" { 1054 t.Errorf("input=%q: unexpected error %q", test.input, got) 1055 } 1056 continue 1057 } 1058 if !strings.Contains(got, test.err) { 1059 t.Errorf("input=%q: error\n\t%q\ndoes not contain expected string\n\t%q", test.input, got, test.err) 1060 continue 1061 } 1062 // Check that we get the same error if we call Execute again. 1063 if err := tmpl.Execute(buf, nil); err == nil || err.Error() != got { 1064 t.Errorf("input=%q: unexpected error on second call %q", test.input, err) 1065 1066 } 1067 } 1068 } 1069 1070 func TestEscapeText(t *testing.T) { 1071 tests := []struct { 1072 input string 1073 output context 1074 }{ 1075 { 1076 ``, 1077 context{}, 1078 }, 1079 { 1080 `Hello, World!`, 1081 context{}, 1082 }, 1083 { 1084 // An orphaned "<" is OK. 1085 `I <3 Ponies!`, 1086 context{}, 1087 }, 1088 { 1089 `<a`, 1090 context{state: stateTag}, 1091 }, 1092 { 1093 `<a `, 1094 context{state: stateTag}, 1095 }, 1096 { 1097 `<a>`, 1098 context{state: stateText}, 1099 }, 1100 { 1101 `<a href`, 1102 context{state: stateAttrName, attr: attrURL}, 1103 }, 1104 { 1105 `<a on`, 1106 context{state: stateAttrName, attr: attrScript}, 1107 }, 1108 { 1109 `<a href `, 1110 context{state: stateAfterName, attr: attrURL}, 1111 }, 1112 { 1113 `<a style = `, 1114 context{state: stateBeforeValue, attr: attrStyle}, 1115 }, 1116 { 1117 `<a href=`, 1118 context{state: stateBeforeValue, attr: attrURL}, 1119 }, 1120 { 1121 `<a href=x`, 1122 context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery, attr: attrURL}, 1123 }, 1124 { 1125 `<a href=x `, 1126 context{state: stateTag}, 1127 }, 1128 { 1129 `<a href=>`, 1130 context{state: stateText}, 1131 }, 1132 { 1133 `<a href=x>`, 1134 context{state: stateText}, 1135 }, 1136 { 1137 `<a href ='`, 1138 context{state: stateURL, delim: delimSingleQuote, attr: attrURL}, 1139 }, 1140 { 1141 `<a href=''`, 1142 context{state: stateTag}, 1143 }, 1144 { 1145 `<a href= "`, 1146 context{state: stateURL, delim: delimDoubleQuote, attr: attrURL}, 1147 }, 1148 { 1149 `<a href=""`, 1150 context{state: stateTag}, 1151 }, 1152 { 1153 `<a title="`, 1154 context{state: stateAttr, delim: delimDoubleQuote}, 1155 }, 1156 { 1157 `<a HREF='http:`, 1158 context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL}, 1159 }, 1160 { 1161 `<a Href='/`, 1162 context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL}, 1163 }, 1164 { 1165 `<a href='"`, 1166 context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL}, 1167 }, 1168 { 1169 `<a href="'`, 1170 context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL}, 1171 }, 1172 { 1173 `<a href=''`, 1174 context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL}, 1175 }, 1176 { 1177 `<a href=""`, 1178 context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL}, 1179 }, 1180 { 1181 `<a href=""`, 1182 context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL}, 1183 }, 1184 { 1185 `<a href="`, 1186 context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery, attr: attrURL}, 1187 }, 1188 { 1189 `<img alt="1">`, 1190 context{state: stateText}, 1191 }, 1192 { 1193 `<img alt="1>"`, 1194 context{state: stateTag}, 1195 }, 1196 { 1197 `<img alt="1>">`, 1198 context{state: stateText}, 1199 }, 1200 { 1201 `<input checked type="checkbox"`, 1202 context{state: stateTag}, 1203 }, 1204 { 1205 `<a onclick="`, 1206 context{state: stateJS, delim: delimDoubleQuote, attr: attrScript}, 1207 }, 1208 { 1209 `<a onclick="//foo`, 1210 context{state: stateJSLineCmt, delim: delimDoubleQuote, attr: attrScript}, 1211 }, 1212 { 1213 "<a onclick='//\n", 1214 context{state: stateJS, delim: delimSingleQuote, attr: attrScript}, 1215 }, 1216 { 1217 "<a onclick='//\r\n", 1218 context{state: stateJS, delim: delimSingleQuote, attr: attrScript}, 1219 }, 1220 { 1221 "<a onclick='//\u2028", 1222 context{state: stateJS, delim: delimSingleQuote, attr: attrScript}, 1223 }, 1224 { 1225 `<a onclick="/*`, 1226 context{state: stateJSBlockCmt, delim: delimDoubleQuote, attr: attrScript}, 1227 }, 1228 { 1229 `<a onclick="/*/`, 1230 context{state: stateJSBlockCmt, delim: delimDoubleQuote, attr: attrScript}, 1231 }, 1232 { 1233 `<a onclick="/**/`, 1234 context{state: stateJS, delim: delimDoubleQuote, attr: attrScript}, 1235 }, 1236 { 1237 `<a onkeypress=""`, 1238 context{state: stateJSDqStr, delim: delimDoubleQuote, attr: attrScript}, 1239 }, 1240 { 1241 `<a onclick='"foo"`, 1242 context{state: stateJS, delim: delimSingleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, 1243 }, 1244 { 1245 `<a onclick='foo'`, 1246 context{state: stateJS, delim: delimSpaceOrTagEnd, jsCtx: jsCtxDivOp, attr: attrScript}, 1247 }, 1248 { 1249 `<a onclick='foo`, 1250 context{state: stateJSSqStr, delim: delimSpaceOrTagEnd, attr: attrScript}, 1251 }, 1252 { 1253 `<a onclick=""foo'`, 1254 context{state: stateJSDqStr, delim: delimDoubleQuote, attr: attrScript}, 1255 }, 1256 { 1257 `<a onclick="'foo"`, 1258 context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript}, 1259 }, 1260 { 1261 `<A ONCLICK="'`, 1262 context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript}, 1263 }, 1264 { 1265 `<a onclick="/`, 1266 context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript}, 1267 }, 1268 { 1269 `<a onclick="'foo'`, 1270 context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, 1271 }, 1272 { 1273 `<a onclick="'foo\'`, 1274 context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript}, 1275 }, 1276 { 1277 `<a onclick="'foo\'`, 1278 context{state: stateJSSqStr, delim: delimDoubleQuote, attr: attrScript}, 1279 }, 1280 { 1281 `<a onclick="/foo/`, 1282 context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, 1283 }, 1284 { 1285 `<script>/foo/ /=`, 1286 context{state: stateJS, element: elementScript}, 1287 }, 1288 { 1289 `<a onclick="1 /foo`, 1290 context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, 1291 }, 1292 { 1293 `<a onclick="1 /*c*/ /foo`, 1294 context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, 1295 }, 1296 { 1297 `<a onclick="/foo[/]`, 1298 context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript}, 1299 }, 1300 { 1301 `<a onclick="/foo\/`, 1302 context{state: stateJSRegexp, delim: delimDoubleQuote, attr: attrScript}, 1303 }, 1304 { 1305 `<a onclick="/foo/`, 1306 context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp, attr: attrScript}, 1307 }, 1308 { 1309 `<input checked style="`, 1310 context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle}, 1311 }, 1312 { 1313 `<a style="//`, 1314 context{state: stateCSSLineCmt, delim: delimDoubleQuote, attr: attrStyle}, 1315 }, 1316 { 1317 `<a style="//</script>`, 1318 context{state: stateCSSLineCmt, delim: delimDoubleQuote, attr: attrStyle}, 1319 }, 1320 { 1321 "<a style='//\n", 1322 context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle}, 1323 }, 1324 { 1325 "<a style='//\r", 1326 context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle}, 1327 }, 1328 { 1329 `<a style="/*`, 1330 context{state: stateCSSBlockCmt, delim: delimDoubleQuote, attr: attrStyle}, 1331 }, 1332 { 1333 `<a style="/*/`, 1334 context{state: stateCSSBlockCmt, delim: delimDoubleQuote, attr: attrStyle}, 1335 }, 1336 { 1337 `<a style="/**/`, 1338 context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle}, 1339 }, 1340 { 1341 `<a style="background: '`, 1342 context{state: stateCSSSqStr, delim: delimDoubleQuote, attr: attrStyle}, 1343 }, 1344 { 1345 `<a style="background: "`, 1346 context{state: stateCSSDqStr, delim: delimDoubleQuote, attr: attrStyle}, 1347 }, 1348 { 1349 `<a style="background: '/foo?img=`, 1350 context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag, attr: attrStyle}, 1351 }, 1352 { 1353 `<a style="background: '/`, 1354 context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, 1355 }, 1356 { 1357 `<a style="background: url("/`, 1358 context{state: stateCSSDqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, 1359 }, 1360 { 1361 `<a style="background: url('/`, 1362 context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, 1363 }, 1364 { 1365 `<a style="background: url('/)`, 1366 context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, 1367 }, 1368 { 1369 `<a style="background: url('/ `, 1370 context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, 1371 }, 1372 { 1373 `<a style="background: url(/`, 1374 context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrStyle}, 1375 }, 1376 { 1377 `<a style="background: url( `, 1378 context{state: stateCSSURL, delim: delimDoubleQuote, attr: attrStyle}, 1379 }, 1380 { 1381 `<a style="background: url( /image?name=`, 1382 context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag, attr: attrStyle}, 1383 }, 1384 { 1385 `<a style="background: url(x)`, 1386 context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle}, 1387 }, 1388 { 1389 `<a style="background: url('x'`, 1390 context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle}, 1391 }, 1392 { 1393 `<a style="background: url( x `, 1394 context{state: stateCSS, delim: delimDoubleQuote, attr: attrStyle}, 1395 }, 1396 { 1397 `<!-- foo`, 1398 context{state: stateHTMLCmt}, 1399 }, 1400 { 1401 `<!-->`, 1402 context{state: stateHTMLCmt}, 1403 }, 1404 { 1405 `<!--->`, 1406 context{state: stateHTMLCmt}, 1407 }, 1408 { 1409 `<!-- foo -->`, 1410 context{state: stateText}, 1411 }, 1412 { 1413 `<script`, 1414 context{state: stateTag, element: elementScript}, 1415 }, 1416 { 1417 `<script `, 1418 context{state: stateTag, element: elementScript}, 1419 }, 1420 { 1421 `<script src="foo.js" `, 1422 context{state: stateTag, element: elementScript}, 1423 }, 1424 { 1425 `<script src='foo.js' `, 1426 context{state: stateTag, element: elementScript}, 1427 }, 1428 { 1429 `<script type=text/javascript `, 1430 context{state: stateTag, element: elementScript}, 1431 }, 1432 { 1433 `<script>`, 1434 context{state: stateJS, jsCtx: jsCtxRegexp, element: elementScript}, 1435 }, 1436 { 1437 `<script>foo`, 1438 context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript}, 1439 }, 1440 { 1441 `<script>foo</script>`, 1442 context{state: stateText}, 1443 }, 1444 { 1445 `<script>foo</script><!--`, 1446 context{state: stateHTMLCmt}, 1447 }, 1448 { 1449 `<script>document.write("<p>foo</p>");`, 1450 context{state: stateJS, element: elementScript}, 1451 }, 1452 { 1453 `<script>document.write("<p>foo<\/script>");`, 1454 context{state: stateJS, element: elementScript}, 1455 }, 1456 { 1457 `<script>document.write("<script>alert(1)</script>");`, 1458 context{state: stateText}, 1459 }, 1460 { 1461 `<script type="text/template">`, 1462 context{state: stateText}, 1463 }, 1464 // covering issue 19968 1465 { 1466 `<script type="TEXT/JAVASCRIPT">`, 1467 context{state: stateJS, element: elementScript}, 1468 }, 1469 // covering issue 19965 1470 { 1471 `<script TYPE="text/template">`, 1472 context{state: stateText}, 1473 }, 1474 { 1475 `<script type="notjs">`, 1476 context{state: stateText}, 1477 }, 1478 { 1479 `<Script>`, 1480 context{state: stateJS, element: elementScript}, 1481 }, 1482 { 1483 `<SCRIPT>foo`, 1484 context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript}, 1485 }, 1486 { 1487 `<textarea>value`, 1488 context{state: stateRCDATA, element: elementTextarea}, 1489 }, 1490 { 1491 `<textarea>value</TEXTAREA>`, 1492 context{state: stateText}, 1493 }, 1494 { 1495 `<textarea name=html><b`, 1496 context{state: stateRCDATA, element: elementTextarea}, 1497 }, 1498 { 1499 `<title>value`, 1500 context{state: stateRCDATA, element: elementTitle}, 1501 }, 1502 { 1503 `<style>value`, 1504 context{state: stateCSS, element: elementStyle}, 1505 }, 1506 { 1507 `<a xlink:href`, 1508 context{state: stateAttrName, attr: attrURL}, 1509 }, 1510 { 1511 `<a xmlns`, 1512 context{state: stateAttrName, attr: attrURL}, 1513 }, 1514 { 1515 `<a xmlns:foo`, 1516 context{state: stateAttrName, attr: attrURL}, 1517 }, 1518 { 1519 `<a xmlnsxyz`, 1520 context{state: stateAttrName}, 1521 }, 1522 { 1523 `<a data-url`, 1524 context{state: stateAttrName, attr: attrURL}, 1525 }, 1526 { 1527 `<a data-iconUri`, 1528 context{state: stateAttrName, attr: attrURL}, 1529 }, 1530 { 1531 `<a data-urlItem`, 1532 context{state: stateAttrName, attr: attrURL}, 1533 }, 1534 { 1535 `<a g:`, 1536 context{state: stateAttrName}, 1537 }, 1538 { 1539 `<a g:url`, 1540 context{state: stateAttrName, attr: attrURL}, 1541 }, 1542 { 1543 `<a g:iconUri`, 1544 context{state: stateAttrName, attr: attrURL}, 1545 }, 1546 { 1547 `<a g:urlItem`, 1548 context{state: stateAttrName, attr: attrURL}, 1549 }, 1550 { 1551 `<a g:value`, 1552 context{state: stateAttrName}, 1553 }, 1554 { 1555 `<a svg:style='`, 1556 context{state: stateCSS, delim: delimSingleQuote, attr: attrStyle}, 1557 }, 1558 { 1559 `<svg:font-face`, 1560 context{state: stateTag}, 1561 }, 1562 { 1563 `<svg:a svg:onclick="`, 1564 context{state: stateJS, delim: delimDoubleQuote, attr: attrScript}, 1565 }, 1566 { 1567 `<svg:a svg:onclick="x()">`, 1568 context{}, 1569 }, 1570 } 1571 1572 for _, test := range tests { 1573 b, e := []byte(test.input), makeEscaper(nil) 1574 c := e.escapeText(context{}, &parse.TextNode{NodeType: parse.NodeText, Text: b}) 1575 if !test.output.eq(c) { 1576 t.Errorf("input %q: want context\n\t%v\ngot\n\t%v", test.input, test.output, c) 1577 continue 1578 } 1579 if test.input != string(b) { 1580 t.Errorf("input %q: text node was modified: want %q got %q", test.input, test.input, b) 1581 continue 1582 } 1583 } 1584 } 1585 1586 func TestEnsurePipelineContains(t *testing.T) { 1587 tests := []struct { 1588 input, output string 1589 ids []string 1590 }{ 1591 { 1592 "{{.X}}", 1593 ".X", 1594 []string{}, 1595 }, 1596 { 1597 "{{.X | html}}", 1598 ".X | html", 1599 []string{}, 1600 }, 1601 { 1602 "{{.X}}", 1603 ".X | html", 1604 []string{"html"}, 1605 }, 1606 { 1607 "{{html .X}}", 1608 "_eval_args_ .X | html | urlquery", 1609 []string{"html", "urlquery"}, 1610 }, 1611 { 1612 "{{html .X .Y .Z}}", 1613 "_eval_args_ .X .Y .Z | html | urlquery", 1614 []string{"html", "urlquery"}, 1615 }, 1616 { 1617 "{{.X | print}}", 1618 ".X | print | urlquery", 1619 []string{"urlquery"}, 1620 }, 1621 { 1622 "{{.X | print | urlquery}}", 1623 ".X | print | urlquery", 1624 []string{"urlquery"}, 1625 }, 1626 { 1627 "{{.X | urlquery}}", 1628 ".X | html | urlquery", 1629 []string{"html", "urlquery"}, 1630 }, 1631 { 1632 "{{.X | print 2 | .f 3}}", 1633 ".X | print 2 | .f 3 | urlquery | html", 1634 []string{"urlquery", "html"}, 1635 }, 1636 { 1637 // covering issue 10801 1638 "{{.X | println.x }}", 1639 ".X | println.x | urlquery | html", 1640 []string{"urlquery", "html"}, 1641 }, 1642 { 1643 // covering issue 10801 1644 "{{.X | (print 12 | println).x }}", 1645 ".X | (print 12 | println).x | urlquery | html", 1646 []string{"urlquery", "html"}, 1647 }, 1648 // The following test cases ensure that the merging of internal escapers 1649 // with the predefined "html" and "urlquery" escapers is correct. 1650 { 1651 "{{.X | urlquery}}", 1652 ".X | _html_template_urlfilter | urlquery", 1653 []string{"_html_template_urlfilter", "_html_template_urlnormalizer"}, 1654 }, 1655 { 1656 "{{.X | urlquery}}", 1657 ".X | urlquery | _html_template_urlfilter | _html_template_cssescaper", 1658 []string{"_html_template_urlfilter", "_html_template_cssescaper"}, 1659 }, 1660 { 1661 "{{.X | urlquery}}", 1662 ".X | urlquery", 1663 []string{"_html_template_urlnormalizer"}, 1664 }, 1665 { 1666 "{{.X | urlquery}}", 1667 ".X | urlquery", 1668 []string{"_html_template_urlescaper"}, 1669 }, 1670 { 1671 "{{.X | html}}", 1672 ".X | html", 1673 []string{"_html_template_htmlescaper"}, 1674 }, 1675 { 1676 "{{.X | html}}", 1677 ".X | html", 1678 []string{"_html_template_rcdataescaper"}, 1679 }, 1680 } 1681 for i, test := range tests { 1682 tmpl := template.Must(template.New("test").Parse(test.input)) 1683 action, ok := (tmpl.Tree.Root.Nodes[0].(*parse.ActionNode)) 1684 if !ok { 1685 t.Errorf("First node is not an action: %s", test.input) 1686 continue 1687 } 1688 pipe := action.Pipe 1689 originalIDs := make([]string, len(test.ids)) 1690 copy(originalIDs, test.ids) 1691 ensurePipelineContains(pipe, test.ids) 1692 got := pipe.String() 1693 if got != test.output { 1694 t.Errorf("#%d: %s, %v: want\n\t%s\ngot\n\t%s", i, test.input, originalIDs, test.output, got) 1695 } 1696 } 1697 } 1698 1699 func TestEscapeMalformedPipelines(t *testing.T) { 1700 tests := []string{ 1701 "{{ 0 | $ }}", 1702 "{{ 0 | $ | urlquery }}", 1703 "{{ 0 | (nil) }}", 1704 "{{ 0 | (nil) | html }}", 1705 } 1706 for _, test := range tests { 1707 var b bytes.Buffer 1708 tmpl, err := New("test").Parse(test) 1709 if err != nil { 1710 t.Errorf("failed to parse set: %q", err) 1711 } 1712 err = tmpl.Execute(&b, nil) 1713 if err == nil { 1714 t.Errorf("Expected error for %q", test) 1715 } 1716 } 1717 } 1718 1719 func TestEscapeErrorsNotIgnorable(t *testing.T) { 1720 var b bytes.Buffer 1721 tmpl, _ := New("dangerous").Parse("<a") 1722 err := tmpl.Execute(&b, nil) 1723 if err == nil { 1724 t.Errorf("Expected error") 1725 } else if b.Len() != 0 { 1726 t.Errorf("Emitted output despite escaping failure") 1727 } 1728 } 1729 1730 func TestEscapeSetErrorsNotIgnorable(t *testing.T) { 1731 var b bytes.Buffer 1732 tmpl, err := New("root").Parse(`{{define "t"}}<a{{end}}`) 1733 if err != nil { 1734 t.Errorf("failed to parse set: %q", err) 1735 } 1736 err = tmpl.ExecuteTemplate(&b, "t", nil) 1737 if err == nil { 1738 t.Errorf("Expected error") 1739 } else if b.Len() != 0 { 1740 t.Errorf("Emitted output despite escaping failure") 1741 } 1742 } 1743 1744 func TestRedundantFuncs(t *testing.T) { 1745 inputs := []interface{}{ 1746 "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + 1747 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 1748 ` !"#$%&'()*+,-./` + 1749 `0123456789:;<=>?` + 1750 `@ABCDEFGHIJKLMNO` + 1751 `PQRSTUVWXYZ[\]^_` + 1752 "`abcdefghijklmno" + 1753 "pqrstuvwxyz{|}~\x7f" + 1754 "\u00A0\u0100\u2028\u2029\ufeff\ufdec\ufffd\uffff\U0001D11E" + 1755 "&%22\\", 1756 CSS(`a[href =~ "//example.com"]#foo`), 1757 HTML(`Hello, <b>World</b> &tc!`), 1758 HTMLAttr(` dir="ltr"`), 1759 JS(`c && alert("Hello, World!");`), 1760 JSStr(`Hello, World & O'Reilly\x21`), 1761 URL(`greeting=H%69&addressee=(World)`), 1762 } 1763 1764 for n0, m := range redundantFuncs { 1765 f0 := funcMap[n0].(func(...interface{}) string) 1766 for n1 := range m { 1767 f1 := funcMap[n1].(func(...interface{}) string) 1768 for _, input := range inputs { 1769 want := f0(input) 1770 if got := f1(want); want != got { 1771 t.Errorf("%s %s with %T %q: want\n\t%q,\ngot\n\t%q", n0, n1, input, input, want, got) 1772 } 1773 } 1774 } 1775 } 1776 } 1777 1778 func TestIndirectPrint(t *testing.T) { 1779 a := 3 1780 ap := &a 1781 b := "hello" 1782 bp := &b 1783 bpp := &bp 1784 tmpl := Must(New("t").Parse(`{{.}}`)) 1785 var buf bytes.Buffer 1786 err := tmpl.Execute(&buf, ap) 1787 if err != nil { 1788 t.Errorf("Unexpected error: %s", err) 1789 } else if buf.String() != "3" { 1790 t.Errorf(`Expected "3"; got %q`, buf.String()) 1791 } 1792 buf.Reset() 1793 err = tmpl.Execute(&buf, bpp) 1794 if err != nil { 1795 t.Errorf("Unexpected error: %s", err) 1796 } else if buf.String() != "hello" { 1797 t.Errorf(`Expected "hello"; got %q`, buf.String()) 1798 } 1799 } 1800 1801 // This is a test for issue 3272. 1802 func TestEmptyTemplate(t *testing.T) { 1803 page := Must(New("page").ParseFiles(os.DevNull)) 1804 if err := page.ExecuteTemplate(os.Stdout, "page", "nothing"); err == nil { 1805 t.Fatal("expected error") 1806 } 1807 } 1808 1809 type Issue7379 int 1810 1811 func (Issue7379) SomeMethod(x int) string { 1812 return fmt.Sprintf("<%d>", x) 1813 } 1814 1815 // This is a test for issue 7379: type assertion error caused panic, and then 1816 // the code to handle the panic breaks escaping. It's hard to see the second 1817 // problem once the first is fixed, but its fix is trivial so we let that go. See 1818 // the discussion for issue 7379. 1819 func TestPipeToMethodIsEscaped(t *testing.T) { 1820 tmpl := Must(New("x").Parse("<html>{{0 | .SomeMethod}}</html>\n")) 1821 tryExec := func() string { 1822 defer func() { 1823 panicValue := recover() 1824 if panicValue != nil { 1825 t.Errorf("panicked: %v\n", panicValue) 1826 } 1827 }() 1828 var b bytes.Buffer 1829 tmpl.Execute(&b, Issue7379(0)) 1830 return b.String() 1831 } 1832 for i := 0; i < 3; i++ { 1833 str := tryExec() 1834 const expect = "<html><0></html>\n" 1835 if str != expect { 1836 t.Errorf("expected %q got %q", expect, str) 1837 } 1838 } 1839 } 1840 1841 // Unlike text/template, html/template crashed if given an incomplete 1842 // template, that is, a template that had been named but not given any content. 1843 // This is issue #10204. 1844 func TestErrorOnUndefined(t *testing.T) { 1845 tmpl := New("undefined") 1846 1847 err := tmpl.Execute(nil, nil) 1848 if err == nil { 1849 t.Error("expected error") 1850 } 1851 if !strings.Contains(err.Error(), "incomplete") { 1852 t.Errorf("expected error about incomplete template; got %s", err) 1853 } 1854 } 1855 1856 // This covers issue #20842. 1857 func TestIdempotentExecute(t *testing.T) { 1858 tmpl := Must(New(""). 1859 Parse(`{{define "main"}}<body>{{template "hello"}}</body>{{end}}`)) 1860 Must(tmpl. 1861 Parse(`{{define "hello"}}Hello, {{"Ladies & Gentlemen!"}}{{end}}`)) 1862 got := new(bytes.Buffer) 1863 var err error 1864 // Ensure that "hello" produces the same output when executed twice. 1865 want := "Hello, Ladies & Gentlemen!" 1866 for i := 0; i < 2; i++ { 1867 err = tmpl.ExecuteTemplate(got, "hello", nil) 1868 if err != nil { 1869 t.Errorf("unexpected error: %s", err) 1870 } 1871 if got.String() != want { 1872 t.Errorf("after executing template \"hello\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want) 1873 } 1874 got.Reset() 1875 } 1876 // Ensure that the implicit re-execution of "hello" during the execution of 1877 // "main" does not cause the output of "hello" to change. 1878 err = tmpl.ExecuteTemplate(got, "main", nil) 1879 if err != nil { 1880 t.Errorf("unexpected error: %s", err) 1881 } 1882 // If the HTML escaper is added again to the action {{"Ladies & Gentlemen!"}}, 1883 // we would expected to see the ampersand overescaped to "&amp;". 1884 want = "<body>Hello, Ladies & Gentlemen!</body>" 1885 if got.String() != want { 1886 t.Errorf("after executing template \"main\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want) 1887 } 1888 } 1889 1890 func BenchmarkEscapedExecute(b *testing.B) { 1891 tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`)) 1892 var buf bytes.Buffer 1893 b.ResetTimer() 1894 for i := 0; i < b.N; i++ { 1895 tmpl.Execute(&buf, "foo & 'bar' & baz") 1896 buf.Reset() 1897 } 1898 } 1899 1900 // Covers issue 22780. 1901 func TestOrphanedTemplate(t *testing.T) { 1902 t1 := Must(New("foo").Parse(`<a href="{{.}}">link1</a>`)) 1903 t2 := Must(t1.New("foo").Parse(`bar`)) 1904 1905 var b bytes.Buffer 1906 const wantError = `template: "foo" is an incomplete or empty template` 1907 if err := t1.Execute(&b, "javascript:alert(1)"); err == nil { 1908 t.Fatal("expected error executing t1") 1909 } else if gotError := err.Error(); gotError != wantError { 1910 t.Fatalf("got t1 execution error:\n\t%s\nwant:\n\t%s", gotError, wantError) 1911 } 1912 b.Reset() 1913 if err := t2.Execute(&b, nil); err != nil { 1914 t.Fatalf("error executing t2: %s", err) 1915 } 1916 const want = "bar" 1917 if got := b.String(); got != want { 1918 t.Fatalf("t2 rendered %q, want %q", got, want) 1919 } 1920 } 1921 1922 // Covers issue 21844. 1923 func TestAliasedParseTreeDoesNotOverescape(t *testing.T) { 1924 const ( 1925 tmplText = `{{.}}` 1926 data = `<baz>` 1927 want = `<baz>` 1928 ) 1929 // Templates "foo" and "bar" both alias the same underlying parse tree. 1930 tpl := Must(New("foo").Parse(tmplText)) 1931 if _, err := tpl.AddParseTree("bar", tpl.Tree); err != nil { 1932 t.Fatalf("AddParseTree error: %v", err) 1933 } 1934 var b1, b2 bytes.Buffer 1935 if err := tpl.ExecuteTemplate(&b1, "foo", data); err != nil { 1936 t.Fatalf(`ExecuteTemplate failed for "foo": %v`, err) 1937 } 1938 if err := tpl.ExecuteTemplate(&b2, "bar", data); err != nil { 1939 t.Fatalf(`ExecuteTemplate failed for "foo": %v`, err) 1940 } 1941 got1, got2 := b1.String(), b2.String() 1942 if got1 != want { 1943 t.Fatalf(`Template "foo" rendered %q, want %q`, got1, want) 1944 } 1945 if got1 != got2 { 1946 t.Fatalf(`Template "foo" and "bar" rendered %q and %q respectively, expected equal values`, got1, got2) 1947 } 1948 }