hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

escape_test.go (52330B)

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