Best K6 code snippet using js.State
escape_test.go
Source:escape_test.go
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(`¡<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, <Cincinatti>!",57 },58 {59 "else",60 "{{if .F}}{{.H}}{{else}}{{.G}}{{end}}!",61 "<Goodbye>!",62 },63 {64 "overescaping1",65 "Hello, {{.C | html}}!",66 "Hello, <Cincinatti>!",67 },68 {69 "overescaping2",70 "Hello, {{html .C}}!",71 "Hello, <Cincinatti>!",72 },73 {74 "overescaping3",75 "{{with .C}}{{$msg := .}}Hello, {{$msg}}!{{end}}",76 "Hello, <Cincinatti>!",77 },78 {79 "assignment",80 "{{if $x := .H}}{{$x}}{{end}}",81 "<Hello>",82 },83 {84 "withBody",85 "{{with .H}}{{.}}{{end}}",86 "<Hello>",87 },88 {89 "withElse",90 "{{with .E}}{{.}}{{else}}{{.H}}{{end}}",91 "<Hello>",92 },93 {94 "rangeBody",95 "{{range .A}}{{.}}{{end}}",96 "<a><b>",97 },98 {99 "rangeElse",100 "{{range .E}}{{.}}{{else}}{{.H}}{{end}}",101 "<Hello>",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=<Hello>>",117 },118 {119 "urlStartRel",120 `<a href='{{"/foo/bar?a=b&c=d"}}'>`,121 `<a href='/foo/bar?a=b&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&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&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("\u003cHello\u003e")'>`,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(["\u003ca\u003e","\u003cb\u003e"])'>`,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(["\u003ca\u003e","\u003cb\u003e"])'>`,212 },213 {214 "jsStr",215 "<button onclick='alert("{{.H}}")'>",216 `<button onclick='alert("\x3cHello\x3e")'>`,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 'f' looking for beginning of object key string */null in numbers)'>`,222 },223 {224 "jsMarshaler",225 `<button onclick='alert({{.M}})'>`,226 `<button onclick='alert({"\u003cfoo\u003e":"O'Reilly"})'>`,227 },228 {229 "jsStrNotUnderEscaped",230 "<button onclick='alert({{.C | urlquery}})'>",231 // URL escaped, then quoted for JS.232 `<button onclick='alert("%3CCincinatti%3E")'>`,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&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: '{{"/**/'\";:// \\"}}', "{{"/**/'\";:// \\"}}"">`,390 `<a style="font-family: '\2f**\2f\27\22\3b\3a\2f\2f \\', "\2f**\2f\27\22\3b\3a\2f\2f \\"">`,391 },392 {393 "styleURLSpecialsEncoded",394 `<a style="border-image: url({{"/**/'\";:// \\"}}), url("{{"/**/'\";:// \\"}}"), url('{{"/**/'\";:// \\"}}'), 'http://www.example.com/?q={{"/**/'\";:// \\"}}''">`,395 `<a style="border-image: url(/**/%27%22;://%20%5c), url("/**/%27%22;://%20%5c"), url('/**/%27%22;://%20%5c'), 'http://www.example.com/?q=%2f%2a%2a%2f%27%22%3b%3a%2f%2f%20%5c''">`,396 },397 {398 "HTML comment",399 "<b>Hello, <!-- name of world -->{{.C}}</b>",400 "<b>Hello, <Cincinatti></b>",401 },402 {403 "HTML comment not first < in text node.",404 "<<!-- -->!--",405 "<!--",406 },407 {408 "HTML normalization 1",409 "a < b",410 "a < b",411 },412 {413 "HTML normalization 2",414 "a << b",415 "a << b",416 },417 {418 "HTML normalization 3",419 "a<<!-- --><!-- -->b",420 "a<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 "<!DOCTYPE",436 },437 {438 "Split HTML comment",439 "<b>Hello, <!-- name of {{if .T}}city -->{{.C}}{{else}}world -->{{.W}}{{end}}</b>",440 "<b>Hello, <Cincinatti></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(""); /* alert({{.H}}) */">`,491 // Attribute comment tests should pass if the comments492 // are successfully elided.493 `<a onclick="f(""); /* 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 `¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!`,524 },525 {526 "typed HTML in attribute",527 `<div title="{{.W}}">`,528 `<div title="¡Hello, O'World!">`,529 },530 {531 "typed HTML in script",532 `<button onclick="alert({{.W}})">`,533 `<button onclick="alert("\u0026iexcl;\u003cb class=\"foo\"\u003eHello\u003c/b\u003e, \u003ctextarea\u003eO'World\u003c/textarea\u003e!")">`,534 },535 {536 "typed HTML in RCDATA",537 `<textarea>{{.W}}</textarea>`,538 `<textarea>¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!</textarea>`,539 },540 {541 "range in textarea",542 "<textarea>{{range .A}}{{.}}{{end}}</textarea>",543 "<textarea><a><b></textarea>",544 },545 {546 "No tag injection",547 `{{"10$"}}<{{"script src,evil.org/pwnd.js"}}...`,548 `10$<script src,evil.org/pwnd.js...`,549 },550 {551 "No comment injection",552 `<{{"!--"}}`,553 `<!--`,554 },555 {556 "No RCDATA end tag injection",557 `<textarea><{{"/textarea "}}...</textarea>`,558 `<textarea></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="<iconId>" src="?%3ciconPath%3e"title="<title>" alt="<alt>">`,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("loaded")">`,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 `<script>doEvil()</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, <World>!`,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 = "\u003ca\u003e"<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 <bar> 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><bar></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><bar><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 <100></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=''`,1089 context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery, attr: attrURL},1090 },1091 {1092 `<a href=""`,1093 context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL},1094 },1095 {1096 `<a href=""`,1097 context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery, attr: attrURL},1098 },1099 {1100 `<a href="`,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=""`,1153 context{state: stateJSDqStr, delim: delimDoubleQuote, attr: attrScript},1154 },1155 {1156 `<a onclick='"foo"`,1157 context{state: stateJS, delim: delimSingleQuote, jsCtx: jsCtxDivOp, attr: attrScript},1158 },1159 {1160 `<a onclick='foo'`,1161 context{state: stateJS, delim: delimSpaceOrTagEnd, jsCtx: jsCtxDivOp, attr: attrScript},1162 },1163 {1164 `<a onclick='foo`,1165 context{state: stateJSSqStr, delim: delimSpaceOrTagEnd, attr: attrScript},1166 },1167 {1168 `<a onclick=""foo'`,1169 context{state: stateJSDqStr, delim: delimDoubleQuote, attr: attrScript},1170 },1171 {1172 `<a onclick="'foo"`,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: "`,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("/`,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 "&%22\\",1626 CSS(`a[href =~ "//example.com"]#foo`),1627 HTML(`Hello, <b>World</b> &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><0></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}...
transition.go
Source:transition.go
...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>...
context.go
Source:context.go
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 "fmt"7)8// context describes the state an HTML parser must be in when it reaches the9// portion of HTML produced by evaluating a particular template node.10//11// The zero value of type context is the start context for a template that12// produces an HTML fragment as defined at13// http://www.w3.org/TR/html5/syntax.html#the-end14// where the context element is null.15type context struct {16 state state17 delim delim18 urlPart urlPart19 jsCtx jsCtx20 attr attr21 element element22 err *Error23}24func (c context) String() string {25 return fmt.Sprintf("{%v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.attr, c.element, c.err)26}27// eq reports whether two contexts are equal.28func (c context) eq(d context) bool {29 return c.state == d.state &&30 c.delim == d.delim &&31 c.urlPart == d.urlPart &&32 c.jsCtx == d.jsCtx &&33 c.attr == d.attr &&34 c.element == d.element &&35 c.err == d.err36}37// mangle produces an identifier that includes a suffix that distinguishes it38// from template names mangled with different contexts.39func (c context) mangle(templateName string) string {40 // The mangled name for the default context is the input templateName.41 if c.state == stateText {42 return templateName43 }44 s := templateName + "$htmltemplate_" + c.state.String()45 if c.delim != 0 {46 s += "_" + c.delim.String()47 }48 if c.urlPart != 0 {49 s += "_" + c.urlPart.String()50 }51 if c.jsCtx != 0 {52 s += "_" + c.jsCtx.String()53 }54 if c.attr != 0 {55 s += "_" + c.attr.String()56 }57 if c.element != 0 {58 s += "_" + c.element.String()59 }60 return s61}62// state describes a high-level HTML parser state.63//64// It bounds the top of the element stack, and by extension the HTML insertion65// mode, but also contains state that does not correspond to anything in the66// HTML5 parsing algorithm because a single token production in the HTML67// grammar may contain embedded actions in a template. For instance, the quoted68// HTML attribute produced by69// <div title="Hello {{.World}}">70// is a single token in HTML's grammar but in a template spans several nodes.71type state uint872const (73 // stateText is parsed character data. An HTML parser is in74 // this state when its parse position is outside an HTML tag,75 // directive, comment, and special element body.76 stateText state = iota77 // stateTag occurs before an HTML attribute or the end of a tag.78 stateTag79 // stateAttrName occurs inside an attribute name.80 // It occurs between the ^'s in ` ^name^ = value`.81 stateAttrName82 // stateAfterName occurs after an attr name has ended but before any83 // equals sign. It occurs between the ^'s in ` name^ ^= value`.84 stateAfterName85 // stateBeforeValue occurs after the equals sign but before the value.86 // It occurs between the ^'s in ` name =^ ^value`.87 stateBeforeValue88 // stateHTMLCmt occurs inside an <!-- HTML comment -->.89 stateHTMLCmt90 // stateRCDATA occurs inside an RCDATA element (<textarea> or <title>)91 // as described at http://www.w3.org/TR/html5/syntax.html#elements-092 stateRCDATA93 // stateAttr occurs inside an HTML attribute whose content is text.94 stateAttr95 // stateURL occurs inside an HTML attribute whose content is a URL.96 stateURL97 // stateJS occurs inside an event handler or script element.98 stateJS99 // stateJSDqStr occurs inside a JavaScript double quoted string.100 stateJSDqStr101 // stateJSSqStr occurs inside a JavaScript single quoted string.102 stateJSSqStr103 // stateJSRegexp occurs inside a JavaScript regexp literal.104 stateJSRegexp105 // stateJSBlockCmt occurs inside a JavaScript /* block comment */.106 stateJSBlockCmt107 // stateJSLineCmt occurs inside a JavaScript // line comment.108 stateJSLineCmt109 // stateCSS occurs inside a <style> element or style attribute.110 stateCSS111 // stateCSSDqStr occurs inside a CSS double quoted string.112 stateCSSDqStr113 // stateCSSSqStr occurs inside a CSS single quoted string.114 stateCSSSqStr115 // stateCSSDqURL occurs inside a CSS double quoted url("...").116 stateCSSDqURL117 // stateCSSSqURL occurs inside a CSS single quoted url('...').118 stateCSSSqURL119 // stateCSSURL occurs inside a CSS unquoted url(...).120 stateCSSURL121 // stateCSSBlockCmt occurs inside a CSS /* block comment */.122 stateCSSBlockCmt123 // stateCSSLineCmt occurs inside a CSS // line comment.124 stateCSSLineCmt125 // stateError is an infectious error state outside any valid126 // HTML/CSS/JS construct.127 stateError128)129var stateNames = [...]string{130 stateText: "stateText",131 stateTag: "stateTag",132 stateAttrName: "stateAttrName",133 stateAfterName: "stateAfterName",134 stateBeforeValue: "stateBeforeValue",135 stateHTMLCmt: "stateHTMLCmt",136 stateRCDATA: "stateRCDATA",137 stateAttr: "stateAttr",138 stateURL: "stateURL",139 stateJS: "stateJS",140 stateJSDqStr: "stateJSDqStr",141 stateJSSqStr: "stateJSSqStr",142 stateJSRegexp: "stateJSRegexp",143 stateJSBlockCmt: "stateJSBlockCmt",144 stateJSLineCmt: "stateJSLineCmt",145 stateCSS: "stateCSS",146 stateCSSDqStr: "stateCSSDqStr",147 stateCSSSqStr: "stateCSSSqStr",148 stateCSSDqURL: "stateCSSDqURL",149 stateCSSSqURL: "stateCSSSqURL",150 stateCSSURL: "stateCSSURL",151 stateCSSBlockCmt: "stateCSSBlockCmt",152 stateCSSLineCmt: "stateCSSLineCmt",153 stateError: "stateError",154}155func (s state) String() string {156 if int(s) < len(stateNames) {157 return stateNames[s]158 }159 return fmt.Sprintf("illegal state %d", int(s))160}161// isComment is true for any state that contains content meant for template162// authors & maintainers, not for end-users or machines.163func isComment(s state) bool {164 switch s {165 case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateCSSBlockCmt, stateCSSLineCmt:166 return true167 }168 return false169}170// isInTag return whether s occurs solely inside an HTML tag.171func isInTag(s state) bool {172 switch s {173 case stateTag, stateAttrName, stateAfterName, stateBeforeValue, stateAttr:174 return true175 }176 return false177}178// delim is the delimiter that will end the current HTML attribute.179type delim uint8180const (181 // delimNone occurs outside any attribute.182 delimNone delim = iota183 // delimDoubleQuote occurs when a double quote (") closes the attribute.184 delimDoubleQuote185 // delimSingleQuote occurs when a single quote (') closes the attribute.186 delimSingleQuote187 // delimSpaceOrTagEnd occurs when a space or right angle bracket (>)188 // closes the attribute.189 delimSpaceOrTagEnd190)191var delimNames = [...]string{192 delimNone: "delimNone",193 delimDoubleQuote: "delimDoubleQuote",194 delimSingleQuote: "delimSingleQuote",195 delimSpaceOrTagEnd: "delimSpaceOrTagEnd",196}197func (d delim) String() string {198 if int(d) < len(delimNames) {199 return delimNames[d]200 }201 return fmt.Sprintf("illegal delim %d", int(d))202}203// urlPart identifies a part in an RFC 3986 hierarchical URL to allow different204// encoding strategies.205type urlPart uint8206const (207 // urlPartNone occurs when not in a URL, or possibly at the start:208 // ^ in "^http://auth/path?k=v#frag".209 urlPartNone urlPart = iota210 // urlPartPreQuery occurs in the scheme, authority, or path; between the211 // ^s in "h^ttp://auth/path^?k=v#frag".212 urlPartPreQuery213 // urlPartQueryOrFrag occurs in the query portion between the ^s in214 // "http://auth/path?^k=v#frag^".215 urlPartQueryOrFrag216 // urlPartUnknown occurs due to joining of contexts both before and217 // after the query separator.218 urlPartUnknown219)220var urlPartNames = [...]string{221 urlPartNone: "urlPartNone",222 urlPartPreQuery: "urlPartPreQuery",223 urlPartQueryOrFrag: "urlPartQueryOrFrag",224 urlPartUnknown: "urlPartUnknown",225}226func (u urlPart) String() string {227 if int(u) < len(urlPartNames) {228 return urlPartNames[u]229 }230 return fmt.Sprintf("illegal urlPart %d", int(u))231}232// jsCtx determines whether a '/' starts a regular expression literal or a233// division operator.234type jsCtx uint8235const (236 // jsCtxRegexp occurs where a '/' would start a regexp literal.237 jsCtxRegexp jsCtx = iota238 // jsCtxDivOp occurs where a '/' would start a division operator.239 jsCtxDivOp240 // jsCtxUnknown occurs where a '/' is ambiguous due to context joining.241 jsCtxUnknown242)243func (c jsCtx) String() string {244 switch c {245 case jsCtxRegexp:246 return "jsCtxRegexp"247 case jsCtxDivOp:248 return "jsCtxDivOp"249 case jsCtxUnknown:250 return "jsCtxUnknown"251 }252 return fmt.Sprintf("illegal jsCtx %d", int(c))253}254// element identifies the HTML element when inside a start tag or special body.255// Certain HTML element (for example <script> and <style>) have bodies that are256// treated differently from stateText so the element type is necessary to257// transition into the correct context at the end of a tag and to identify the258// end delimiter for the body.259type element uint8260const (261 // elementNone occurs outside a special tag or special element body.262 elementNone element = iota263 // elementScript corresponds to the raw text <script> element.264 elementScript265 // elementStyle corresponds to the raw text <style> element.266 elementStyle267 // elementTextarea corresponds to the RCDATA <textarea> element.268 elementTextarea269 // elementTitle corresponds to the RCDATA <title> element.270 elementTitle271)272var elementNames = [...]string{273 elementNone: "elementNone",274 elementScript: "elementScript",275 elementStyle: "elementStyle",276 elementTextarea: "elementTextarea",277 elementTitle: "elementTitle",278}279func (e element) String() string {280 if int(e) < len(elementNames) {281 return elementNames[e]282 }283 return fmt.Sprintf("illegal element %d", int(e))284}285// attr identifies the current HTML attribute when inside the attribute,286// that is, starting from stateAttrName until stateTag/stateText (exclusive).287type attr uint8288const (289 // attrNone corresponds to a normal attribute or no attribute.290 attrNone attr = iota291 // attrScript corresponds to an event handler attribute.292 attrScript293 // attrStyle corresponds to the style attribute whose value is CSS.294 attrStyle295 // attrURL corresponds to an attribute whose value is a URL.296 attrURL297)298var attrNames = [...]string{299 attrNone: "attrNone",300 attrScript: "attrScript",301 attrStyle: "attrStyle",302 attrURL: "attrURL",303}304func (a attr) String() string {305 if int(a) < len(attrNames) {306 return attrNames[a]307 }308 return fmt.Sprintf("illegal attr %d", int(a))309}...
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.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!