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