How to use Context method of js Package

Best K6 code snippet using js.Context

escape_test.go

Source:escape_test.go Github

copy

Full Screen

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

Full Screen

Full Screen

transition.go

Source:transition.go Github

copy

Full Screen

1// Copyright 2011 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4package template5import (6 "bytes"7 "strings"8)9// transitionFunc is the array of context transition functions for text nodes.10// A transition function takes a context and template text input, and returns11// the updated context and the number of bytes consumed from the front of the12// input.13var transitionFunc = [...]func(context, []byte) (context, int){14 stateText: tText,15 stateTag: tTag,16 stateAttrName: tAttrName,17 stateAfterName: tAfterName,18 stateBeforeValue: tBeforeValue,19 stateHTMLCmt: tHTMLCmt,20 stateRCDATA: tSpecialTagEnd,21 stateAttr: tAttr,22 stateURL: tURL,23 stateJS: tJS,24 stateJSDqStr: tJSDelimited,25 stateJSSqStr: tJSDelimited,26 stateJSRegexp: tJSDelimited,27 stateJSBlockCmt: tBlockCmt,28 stateJSLineCmt: tLineCmt,29 stateCSS: tCSS,30 stateCSSDqStr: tCSSStr,31 stateCSSSqStr: tCSSStr,32 stateCSSDqURL: tCSSStr,33 stateCSSSqURL: tCSSStr,34 stateCSSURL: tCSSStr,35 stateCSSBlockCmt: tBlockCmt,36 stateCSSLineCmt: tLineCmt,37 stateError: tError,38}39var commentStart = []byte("<!--")40var commentEnd = []byte("-->")41// tText is the context transition function for the text state.42func tText(c context, s []byte) (context, int) {43 k := 044 for {45 i := k + bytes.IndexByte(s[k:], '<')46 if i < k || i+1 == len(s) {47 return c, len(s)48 } else if i+4 <= len(s) && bytes.Equal(commentStart, s[i:i+4]) {49 return context{state: stateHTMLCmt}, i + 450 }51 i++52 end := false53 if s[i] == '/' {54 if i+1 == len(s) {55 return c, len(s)56 }57 end, i = true, i+158 }59 j, e := eatTagName(s, i)60 if j != i {61 if end {62 e = elementNone63 }64 // We've found an HTML tag.65 return context{state: stateTag, element: e}, j66 }67 k = j68 }69}70var elementContentType = [...]state{71 elementNone: stateText,72 elementScript: stateJS,73 elementStyle: stateCSS,74 elementTextarea: stateRCDATA,75 elementTitle: stateRCDATA,76}77// tTag is the context transition function for the tag state.78func tTag(c context, s []byte) (context, int) {79 // Find the attribute name.80 i := eatWhiteSpace(s, 0)81 if i == len(s) {82 return c, len(s)83 }84 if s[i] == '>' {85 return context{86 state: elementContentType[c.element],87 element: c.element,88 }, i + 189 }90 j, err := eatAttrName(s, i)91 if err != nil {92 return context{state: stateError, err: err}, len(s)93 }94 state, attr := stateTag, attrNone95 if i == j {96 return context{97 state: stateError,98 err: errorf(ErrBadHTML, nil, 0, "expected space, attr name, or end of tag, but got %q", s[i:]),99 }, len(s)100 }101 switch attrType(string(s[i:j])) {102 case contentTypeURL:103 attr = attrURL104 case contentTypeCSS:105 attr = attrStyle106 case contentTypeJS:107 attr = attrScript108 }109 if j == len(s) {110 state = stateAttrName111 } else {112 state = stateAfterName113 }114 return context{state: state, element: c.element, attr: attr}, j115}116// tAttrName is the context transition function for stateAttrName.117func tAttrName(c context, s []byte) (context, int) {118 i, err := eatAttrName(s, 0)119 if err != nil {120 return context{state: stateError, err: err}, len(s)121 } else if i != len(s) {122 c.state = stateAfterName123 }124 return c, i125}126// tAfterName is the context transition function for stateAfterName.127func tAfterName(c context, s []byte) (context, int) {128 // Look for the start of the value.129 i := eatWhiteSpace(s, 0)130 if i == len(s) {131 return c, len(s)132 } else if s[i] != '=' {133 // Occurs due to tag ending '>', and valueless attribute.134 c.state = stateTag135 return c, i136 }137 c.state = stateBeforeValue138 // Consume the "=".139 return c, i + 1140}141var attrStartStates = [...]state{142 attrNone: stateAttr,143 attrScript: stateJS,144 attrStyle: stateCSS,145 attrURL: stateURL,146}147// tBeforeValue is the context transition function for stateBeforeValue.148func tBeforeValue(c context, s []byte) (context, int) {149 i := eatWhiteSpace(s, 0)150 if i == len(s) {151 return c, len(s)152 }153 // Find the attribute delimiter.154 delim := delimSpaceOrTagEnd155 switch s[i] {156 case '\'':157 delim, i = delimSingleQuote, i+1158 case '"':159 delim, i = delimDoubleQuote, i+1160 }161 c.state, c.delim = attrStartStates[c.attr], delim162 return c, i163}164// tHTMLCmt is the context transition function for stateHTMLCmt.165func tHTMLCmt(c context, s []byte) (context, int) {166 if i := bytes.Index(s, commentEnd); i != -1 {167 return context{}, i + 3168 }169 return c, len(s)170}171// specialTagEndMarkers maps element types to the character sequence that172// case-insensitively signals the end of the special tag body.173var specialTagEndMarkers = [...][]byte{174 elementScript: []byte("script"),175 elementStyle: []byte("style"),176 elementTextarea: []byte("textarea"),177 elementTitle: []byte("title"),178}179var (180 specialTagEndPrefix = []byte("</")181 tagEndSeparators = []byte("> \t\n\f/")182)183// tSpecialTagEnd is the context transition function for raw text and RCDATA184// element states.185func tSpecialTagEnd(c context, s []byte) (context, int) {186 if c.element != elementNone {187 if i := indexTagEnd(s, specialTagEndMarkers[c.element]); i != -1 {188 return context{}, i189 }190 }191 return c, len(s)192}193// indexTagEnd finds the index of a special tag end in a case insensitive way, or returns -1194func indexTagEnd(s []byte, tag []byte) int {195 res := 0196 plen := len(specialTagEndPrefix)197 for len(s) > 0 {198 // Try to find the tag end prefix first199 i := bytes.Index(s, specialTagEndPrefix)200 if i == -1 {201 return i202 }203 s = s[i+plen:]204 // Try to match the actual tag if there is still space for it205 if len(tag) <= len(s) && bytes.EqualFold(tag, s[:len(tag)]) {206 s = s[len(tag):]207 // Check the tag is followed by a proper separator208 if len(s) > 0 && bytes.IndexByte(tagEndSeparators, s[0]) != -1 {209 return res + i210 }211 res += len(tag)212 }213 res += i + plen214 }215 return -1216}217// tAttr is the context transition function for the attribute state.218func tAttr(c context, s []byte) (context, int) {219 return c, len(s)220}221// tURL is the context transition function for the URL state.222func tURL(c context, s []byte) (context, int) {223 if bytes.IndexAny(s, "#?") >= 0 {224 c.urlPart = urlPartQueryOrFrag225 } else if len(s) != eatWhiteSpace(s, 0) && c.urlPart == urlPartNone {226 // HTML5 uses "Valid URL potentially surrounded by spaces" for227 // attrs: http://www.w3.org/TR/html5/index.html#attributes-1228 c.urlPart = urlPartPreQuery229 }230 return c, len(s)231}232// tJS is the context transition function for the JS state.233func tJS(c context, s []byte) (context, int) {234 i := bytes.IndexAny(s, `"'/`)235 if i == -1 {236 // Entire input is non string, comment, regexp tokens.237 c.jsCtx = nextJSCtx(s, c.jsCtx)238 return c, len(s)239 }240 c.jsCtx = nextJSCtx(s[:i], c.jsCtx)241 switch s[i] {242 case '"':243 c.state, c.jsCtx = stateJSDqStr, jsCtxRegexp244 case '\'':245 c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp246 case '/':247 switch {248 case i+1 < len(s) && s[i+1] == '/':249 c.state, i = stateJSLineCmt, i+1250 case i+1 < len(s) && s[i+1] == '*':251 c.state, i = stateJSBlockCmt, i+1252 case c.jsCtx == jsCtxRegexp:253 c.state = stateJSRegexp254 case c.jsCtx == jsCtxDivOp:255 c.jsCtx = jsCtxRegexp256 default:257 return context{258 state: stateError,259 err: errorf(ErrSlashAmbig, nil, 0, "'/' could start a division or regexp: %.32q", s[i:]),260 }, len(s)261 }262 default:263 panic("unreachable")264 }265 return c, i + 1266}267// tJSDelimited is the context transition function for the JS string and regexp268// states.269func tJSDelimited(c context, s []byte) (context, int) {270 specials := `\"`271 switch c.state {272 case stateJSSqStr:273 specials = `\'`274 case stateJSRegexp:275 specials = `\/[]`276 }277 k, inCharset := 0, false278 for {279 i := k + bytes.IndexAny(s[k:], specials)280 if i < k {281 break282 }283 switch s[i] {284 case '\\':285 i++286 if i == len(s) {287 return context{288 state: stateError,289 err: errorf(ErrPartialEscape, nil, 0, "unfinished escape sequence in JS string: %q", s),290 }, len(s)291 }292 case '[':293 inCharset = true294 case ']':295 inCharset = false296 default:297 // end delimiter298 if !inCharset {299 c.state, c.jsCtx = stateJS, jsCtxDivOp300 return c, i + 1301 }302 }303 k = i + 1304 }305 if inCharset {306 // This can be fixed by making context richer if interpolation307 // into charsets is desired.308 return context{309 state: stateError,310 err: errorf(ErrPartialCharset, nil, 0, "unfinished JS regexp charset: %q", s),311 }, len(s)312 }313 return c, len(s)314}315var blockCommentEnd = []byte("*/")316// tBlockCmt is the context transition function for /*comment*/ states.317func tBlockCmt(c context, s []byte) (context, int) {318 i := bytes.Index(s, blockCommentEnd)319 if i == -1 {320 return c, len(s)321 }322 switch c.state {323 case stateJSBlockCmt:324 c.state = stateJS325 case stateCSSBlockCmt:326 c.state = stateCSS327 default:328 panic(c.state.String())329 }330 return c, i + 2331}332// tLineCmt is the context transition function for //comment states.333func tLineCmt(c context, s []byte) (context, int) {334 var lineTerminators string335 var endState state336 switch c.state {337 case stateJSLineCmt:338 lineTerminators, endState = "\n\r\u2028\u2029", stateJS339 case stateCSSLineCmt:340 lineTerminators, endState = "\n\f\r", stateCSS341 // Line comments are not part of any published CSS standard but342 // are supported by the 4 major browsers.343 // This defines line comments as344 // LINECOMMENT ::= "//" [^\n\f\d]*345 // since http://www.w3.org/TR/css3-syntax/#SUBTOK-nl defines346 // newlines:347 // nl ::= #xA | #xD #xA | #xD | #xC348 default:349 panic(c.state.String())350 }351 i := bytes.IndexAny(s, lineTerminators)352 if i == -1 {353 return c, len(s)354 }355 c.state = endState356 // Per section 7.4 of EcmaScript 5 : http://es5.github.com/#x7.4357 // "However, the LineTerminator at the end of the line is not358 // considered to be part of the single-line comment; it is359 // recognized separately by the lexical grammar and becomes part360 // of the stream of input elements for the syntactic grammar."361 return c, i362}363// tCSS is the context transition function for the CSS state.364func tCSS(c context, s []byte) (context, int) {365 // CSS quoted strings are almost never used except for:366 // (1) URLs as in background: "/foo.png"367 // (2) Multiword font-names as in font-family: "Times New Roman"368 // (3) List separators in content values as in inline-lists:369 // <style>370 // ul.inlineList { list-style: none; padding:0 }371 // ul.inlineList > li { display: inline }372 // ul.inlineList > li:before { content: ", " }373 // ul.inlineList > li:first-child:before { content: "" }374 // </style>375 // <ul class=inlineList><li>One<li>Two<li>Three</ul>376 // (4) Attribute value selectors as in a[href="http://example.com/"]377 //378 // We conservatively treat all strings as URLs, but make some379 // allowances to avoid confusion.380 //381 // In (1), our conservative assumption is justified.382 // In (2), valid font names do not contain ':', '?', or '#', so our383 // conservative assumption is fine since we will never transition past384 // urlPartPreQuery.385 // In (3), our protocol heuristic should not be tripped, and there386 // should not be non-space content after a '?' or '#', so as long as387 // we only %-encode RFC 3986 reserved characters we are ok.388 // In (4), we should URL escape for URL attributes, and for others we389 // have the attribute name available if our conservative assumption390 // proves problematic for real code.391 k := 0392 for {393 i := k + bytes.IndexAny(s[k:], `("'/`)394 if i < k {395 return c, len(s)396 }397 switch s[i] {398 case '(':399 // Look for url to the left.400 p := bytes.TrimRight(s[:i], "\t\n\f\r ")401 if endsWithCSSKeyword(p, "url") {402 j := len(s) - len(bytes.TrimLeft(s[i+1:], "\t\n\f\r "))403 switch {404 case j != len(s) && s[j] == '"':405 c.state, j = stateCSSDqURL, j+1406 case j != len(s) && s[j] == '\'':407 c.state, j = stateCSSSqURL, j+1408 default:409 c.state = stateCSSURL410 }411 return c, j412 }413 case '/':414 if i+1 < len(s) {415 switch s[i+1] {416 case '/':417 c.state = stateCSSLineCmt418 return c, i + 2419 case '*':420 c.state = stateCSSBlockCmt421 return c, i + 2422 }423 }424 case '"':425 c.state = stateCSSDqStr426 return c, i + 1427 case '\'':428 c.state = stateCSSSqStr429 return c, i + 1430 }431 k = i + 1432 }433}434// tCSSStr is the context transition function for the CSS string and URL states.435func tCSSStr(c context, s []byte) (context, int) {436 var endAndEsc string437 switch c.state {438 case stateCSSDqStr, stateCSSDqURL:439 endAndEsc = `\"`440 case stateCSSSqStr, stateCSSSqURL:441 endAndEsc = `\'`442 case stateCSSURL:443 // Unquoted URLs end with a newline or close parenthesis.444 // The below includes the wc (whitespace character) and nl.445 endAndEsc = "\\\t\n\f\r )"446 default:447 panic(c.state.String())448 }449 k := 0450 for {451 i := k + bytes.IndexAny(s[k:], endAndEsc)452 if i < k {453 c, nread := tURL(c, decodeCSS(s[k:]))454 return c, k + nread455 }456 if s[i] == '\\' {457 i++458 if i == len(s) {459 return context{460 state: stateError,461 err: errorf(ErrPartialEscape, nil, 0, "unfinished escape sequence in CSS string: %q", s),462 }, len(s)463 }464 } else {465 c.state = stateCSS466 return c, i + 1467 }468 c, _ = tURL(c, decodeCSS(s[:i+1]))469 k = i + 1470 }471}472// tError is the context transition function for the error state.473func tError(c context, s []byte) (context, int) {474 return c, len(s)475}476// eatAttrName returns the largest j such that s[i:j] is an attribute name.477// It returns an error if s[i:] does not look like it begins with an478// attribute name, such as encountering a quote mark without a preceding479// equals sign.480func eatAttrName(s []byte, i int) (int, *Error) {481 for j := i; j < len(s); j++ {482 switch s[j] {483 case ' ', '\t', '\n', '\f', '\r', '=', '>':484 return j, nil485 case '\'', '"', '<':486 // These result in a parse warning in HTML5 and are487 // indicative of serious problems if seen in an attr488 // name in a template.489 return -1, errorf(ErrBadHTML, nil, 0, "%q in attribute name: %.32q", s[j:j+1], s)490 default:491 // No-op.492 }493 }494 return len(s), nil495}496var elementNameMap = map[string]element{497 "script": elementScript,498 "style": elementStyle,499 "textarea": elementTextarea,500 "title": elementTitle,501}502// asciiAlpha reports whether c is an ASCII letter.503func asciiAlpha(c byte) bool {504 return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'505}506// asciiAlphaNum reports whether c is an ASCII letter or digit.507func asciiAlphaNum(c byte) bool {508 return asciiAlpha(c) || '0' <= c && c <= '9'509}510// eatTagName returns the largest j such that s[i:j] is a tag name and the tag type.511func eatTagName(s []byte, i int) (int, element) {512 if i == len(s) || !asciiAlpha(s[i]) {513 return i, elementNone514 }515 j := i + 1516 for j < len(s) {517 x := s[j]518 if asciiAlphaNum(x) {519 j++520 continue521 }522 // Allow "x-y" or "x:y" but not "x-", "-y", or "x--y".523 if (x == ':' || x == '-') && j+1 < len(s) && asciiAlphaNum(s[j+1]) {524 j += 2525 continue526 }527 break528 }529 return j, elementNameMap[strings.ToLower(string(s[i:j]))]530}531// eatWhiteSpace returns the largest j such that s[i:j] is white space.532func eatWhiteSpace(s []byte, i int) int {533 for j := i; j < len(s); j++ {534 switch s[j] {535 case ' ', '\t', '\n', '\f', '\r':536 // No-op.537 default:538 return j539 }540 }541 return len(s)542}...

Full Screen

Full Screen

Context

Using AI Code Generation

copy

Full Screen

1js.Global().Get("document").Call("getElementById", "btn").Call("addEventListener", "click", func() {2 js.Global().Call("alert", "Button Clicked")3})4js.Global().Get("document").Call("getElementById", "btn").Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {5 js.Global().Call("alert", "Button Clicked")6}))7js.Global().Get("document").Call("getElementById", "btn").Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {8 js.Global().Call("alert", "Button Clicked")9}))10js.Global().Get("document").Call("getElementById", "btn").Call("removeEventListener", "click")11js.Global().Get("document").Call("getElementById", "btn").Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {12 js.Global().Call("alert", "Button Clicked")13}))14js.Global().Get("document").Call("getElementById", "btn").Call("removeEventListener", "click")15js.Global().Get("document").Call("getElementById", "btn").Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {16 js.Global().Call("alert", "Button Clicked")17}))18js.Global().Get("document").Call("getElementById", "btn").Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {19 js.Global().Call("alert", "Button Clicked")20}))21js.Global().Get("document").Call("getElementById", "btn").Call("removeEventListener", "click")22js.Global().Get("document").Call("getElementById", "btn").Call("addEventListener", "click", js.FuncOf(func(this js.Value, args []js.Value) interface{} {

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run K6 automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Most used method in

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful