Best K6 code snippet using html.Attr
escape_test.go
Source:escape_test.go
...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 }...
sanitize.go
Source:sanitize.go
...35 "strings"36 "golang.org/x/net/html"37)38var (39 dataAttribute = regexp.MustCompile("^data-.+")40 dataAttributeXMLPrefix = regexp.MustCompile("^xml.+")41 dataAttributeInvalidChars = regexp.MustCompile("[A-Z;]+")42)43// Sanitize takes a string that contains a HTML fragment or document and applies44// the given policy whitelist.45//46// It returns a HTML string that has been sanitized by the policy or an empty47// string if an error has occurred (most likely as a consequence of extremely48// malformed input)49func (p *Policy) Sanitize(s string) string {50 if strings.TrimSpace(s) == "" {51 return s52 }53 return p.sanitize(strings.NewReader(s)).String()54}55// SanitizeBytes takes a []byte that contains a HTML fragment or document and applies56// the given policy whitelist.57//58// It returns a []byte containing the HTML that has been sanitized by the policy59// or an empty []byte if an error has occurred (most likely as a consequence of60// extremely malformed input)61func (p *Policy) SanitizeBytes(b []byte) []byte {62 if len(bytes.TrimSpace(b)) == 0 {63 return b64 }65 return p.sanitize(bytes.NewReader(b)).Bytes()66}67// SanitizeReader takes an io.Reader that contains a HTML fragment or document68// and applies the given policy whitelist.69//70// It returns a bytes.Buffer containing the HTML that has been sanitized by the71// policy. Errors during sanitization will merely return an empty result.72func (p *Policy) SanitizeReader(r io.Reader) *bytes.Buffer {73 return p.sanitize(r)74}75// Performs the actual sanitization process.76func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {77 // It is possible that the developer has created the policy via:78 // p := bluemonday.Policy{}79 // rather than:80 // p := bluemonday.NewPolicy()81 // If this is the case, and if they haven't yet triggered an action that82 // would initiliaze the maps, then we need to do that.83 p.init()84 var (85 buff bytes.Buffer86 skipElementContent bool87 skippingElementsCount int6488 skipClosingTag bool89 closingTagToSkipStack []string90 mostRecentlyStartedToken string91 )92 tokenizer := html.NewTokenizer(r)93 for {94 if tokenizer.Next() == html.ErrorToken {95 err := tokenizer.Err()96 if err == io.EOF {97 // End of input means end of processing98 return &buff99 }100 // Raw tokenizer error101 return &bytes.Buffer{}102 }103 token := tokenizer.Token()104 switch token.Type {105 case html.DoctypeToken:106 // DocType is not handled as there is no safe parsing mechanism107 // provided by golang.org/x/net/html for the content, and this can108 // be misused to insert HTML tags that are not then sanitized109 //110 // One might wish to recursively sanitize here using the same policy111 // but I will need to do some further testing before considering112 // this.113 case html.CommentToken:114 // Comments are ignored by default115 case html.StartTagToken:116 mostRecentlyStartedToken = token.Data117 aps, ok := p.elsAndAttrs[token.Data]118 if !ok {119 if _, ok := p.setOfElementsToSkipContent[token.Data]; ok {120 skipElementContent = true121 skippingElementsCount++122 }123 if p.addSpaces {124 buff.WriteString(" ")125 }126 break127 }128 if len(token.Attr) != 0 {129 token.Attr = p.sanitizeAttrs(token.Data, token.Attr, aps)130 }131 if len(token.Attr) == 0 {132 if !p.allowNoAttrs(token.Data) {133 skipClosingTag = true134 closingTagToSkipStack = append(closingTagToSkipStack, token.Data)135 if p.addSpaces {136 buff.WriteString(" ")137 }138 break139 }140 }141 if !skipElementContent {142 buff.WriteString(token.String())143 }144 case html.EndTagToken:145 if mostRecentlyStartedToken == token.Data {146 mostRecentlyStartedToken = ""147 }148 if skipClosingTag && closingTagToSkipStack[len(closingTagToSkipStack)-1] == token.Data {149 closingTagToSkipStack = closingTagToSkipStack[:len(closingTagToSkipStack)-1]150 if len(closingTagToSkipStack) == 0 {151 skipClosingTag = false152 }153 if p.addSpaces {154 buff.WriteString(" ")155 }156 break157 }158 if _, ok := p.elsAndAttrs[token.Data]; !ok {159 if _, ok := p.setOfElementsToSkipContent[token.Data]; ok {160 skippingElementsCount--161 if skippingElementsCount == 0 {162 skipElementContent = false163 }164 }165 if p.addSpaces {166 buff.WriteString(" ")167 }168 break169 }170 if !skipElementContent {171 buff.WriteString(token.String())172 }173 case html.SelfClosingTagToken:174 aps, ok := p.elsAndAttrs[token.Data]175 if !ok {176 if p.addSpaces {177 buff.WriteString(" ")178 }179 break180 }181 if len(token.Attr) != 0 {182 token.Attr = p.sanitizeAttrs(token.Data, token.Attr, aps)183 }184 if len(token.Attr) == 0 && !p.allowNoAttrs(token.Data) {185 if p.addSpaces {186 buff.WriteString(" ")187 }188 break189 }190 if !skipElementContent {191 buff.WriteString(token.String())192 }193 case html.TextToken:194 if !skipElementContent {195 switch mostRecentlyStartedToken {196 case "script":197 // not encouraged, but if a policy allows JavaScript we198 // should not HTML escape it as that would break the output199 buff.WriteString(token.Data)200 case "style":201 // not encouraged, but if a policy allows CSS styles we202 // should not HTML escape it as that would break the output203 buff.WriteString(token.Data)204 default:205 // HTML escape the text206 buff.WriteString(token.String())207 }208 }209 default:210 // A token that didn't exist in the html package when we wrote this211 return &bytes.Buffer{}212 }213 }214}215// sanitizeAttrs takes a set of element attribute policies and the global216// attribute policies and applies them to the []html.Attribute returning a set217// of html.Attributes that match the policies218func (p *Policy) sanitizeAttrs(219 elementName string,220 attrs []html.Attribute,221 aps map[string]attrPolicy,222) []html.Attribute {223 if len(attrs) == 0 {224 return attrs225 }226 // Builds a new attribute slice based on the whether the attribute has been227 // whitelisted explicitly or globally.228 cleanAttrs := []html.Attribute{}229 for _, htmlAttr := range attrs {230 if p.allowDataAttributes {231 // If we see a data attribute, let it through.232 if isDataAttribute(htmlAttr.Key) {233 cleanAttrs = append(cleanAttrs, htmlAttr)234 continue235 }236 }237 // Is there an element specific attribute policy that applies?238 if ap, ok := aps[htmlAttr.Key]; ok {239 if ap.regexp != nil {240 if ap.regexp.MatchString(htmlAttr.Val) {241 cleanAttrs = append(cleanAttrs, htmlAttr)242 continue243 }244 } else {245 cleanAttrs = append(cleanAttrs, htmlAttr)246 continue247 }248 }249 // Is there a global attribute policy that applies?250 if ap, ok := p.globalAttrs[htmlAttr.Key]; ok {251 if ap.regexp != nil {252 if ap.regexp.MatchString(htmlAttr.Val) {253 cleanAttrs = append(cleanAttrs, htmlAttr)254 }255 } else {256 cleanAttrs = append(cleanAttrs, htmlAttr)257 }258 }259 }260 if len(cleanAttrs) == 0 {261 // If nothing was allowed, let's get out of here262 return cleanAttrs263 }264 // cleanAttrs now contains the attributes that are permitted265 if linkable(elementName) {266 if p.requireParseableURLs {267 // Ensure URLs are parseable:268 // - a.href269 // - area.href270 // - link.href271 // - blockquote.cite272 // - q.cite273 // - img.src274 // - script.src275 tmpAttrs := []html.Attribute{}276 for _, htmlAttr := range cleanAttrs {277 switch elementName {278 case "a", "area", "link":279 if htmlAttr.Key == "href" {280 if u, ok := p.validURL(htmlAttr.Val); ok {281 htmlAttr.Val = u282 tmpAttrs = append(tmpAttrs, htmlAttr)283 }284 break285 }286 tmpAttrs = append(tmpAttrs, htmlAttr)287 case "blockquote", "q":288 if htmlAttr.Key == "cite" {289 if u, ok := p.validURL(htmlAttr.Val); ok {290 htmlAttr.Val = u291 tmpAttrs = append(tmpAttrs, htmlAttr)292 }293 break294 }295 tmpAttrs = append(tmpAttrs, htmlAttr)296 case "img", "script":297 if htmlAttr.Key == "src" {298 if u, ok := p.validURL(htmlAttr.Val); ok {299 htmlAttr.Val = u300 tmpAttrs = append(tmpAttrs, htmlAttr)301 }302 break303 }304 tmpAttrs = append(tmpAttrs, htmlAttr)305 default:306 tmpAttrs = append(tmpAttrs, htmlAttr)307 }308 }309 cleanAttrs = tmpAttrs310 }311 if (p.requireNoFollow ||312 p.requireNoFollowFullyQualifiedLinks ||313 p.addTargetBlankToFullyQualifiedLinks) &&314 len(cleanAttrs) > 0 {315 // Add rel="nofollow" if a "href" exists316 switch elementName {317 case "a", "area", "link":318 var hrefFound bool319 var externalLink bool320 for _, htmlAttr := range cleanAttrs {321 if htmlAttr.Key == "href" {322 hrefFound = true323 u, err := url.Parse(htmlAttr.Val)324 if err != nil {325 continue326 }327 if u.Host != "" {328 externalLink = true329 }330 continue331 }332 }333 if hrefFound {334 var (335 noFollowFound bool336 targetBlankFound bool337 )338 addNoFollow := (p.requireNoFollow ||339 externalLink && p.requireNoFollowFullyQualifiedLinks)340 addTargetBlank := (externalLink &&341 p.addTargetBlankToFullyQualifiedLinks)342 tmpAttrs := []html.Attribute{}343 for _, htmlAttr := range cleanAttrs {344 var appended bool345 if htmlAttr.Key == "rel" && addNoFollow {346 if strings.Contains(htmlAttr.Val, "nofollow") {347 noFollowFound = true348 tmpAttrs = append(tmpAttrs, htmlAttr)349 appended = true350 } else {351 htmlAttr.Val += " nofollow"352 noFollowFound = true353 tmpAttrs = append(tmpAttrs, htmlAttr)354 appended = true355 }356 }357 if elementName == "a" && htmlAttr.Key == "target" {358 if htmlAttr.Val == "_blank" {359 targetBlankFound = true360 }361 if addTargetBlank && !targetBlankFound {362 htmlAttr.Val = "_blank"363 targetBlankFound = true364 tmpAttrs = append(tmpAttrs, htmlAttr)365 appended = true366 }367 }368 if !appended {369 tmpAttrs = append(tmpAttrs, htmlAttr)370 }371 }372 if noFollowFound || targetBlankFound {373 cleanAttrs = tmpAttrs374 }375 if addNoFollow && !noFollowFound {376 rel := html.Attribute{}377 rel.Key = "rel"378 rel.Val = "nofollow"379 cleanAttrs = append(cleanAttrs, rel)380 }381 if elementName == "a" && addTargetBlank && !targetBlankFound {382 rel := html.Attribute{}383 rel.Key = "target"384 rel.Val = "_blank"385 targetBlankFound = true386 cleanAttrs = append(cleanAttrs, rel)387 }388 if targetBlankFound {389 // target="_blank" has a security risk that allows the390 // opened window/tab to issue JavaScript calls against391 // window.opener, which in effect allow the destination392 // of the link to control the source:393 // https://dev.to/ben/the-targetblank-vulnerability-by-example394 //395 // To mitigate this risk, we need to add a specific rel396 // attribute if it is not already present.397 // rel="noopener"398 //399 // Unfortunately this is processing the rel twice (we400 // already looked at it earlier ^^) as we cannot be sure401 // of the ordering of the href and rel, and whether we402 // have fully satisfied that we need to do this. This403 // double processing only happens *if* target="_blank"404 // is true.405 var noOpenerAdded bool406 tmpAttrs := []html.Attribute{}407 for _, htmlAttr := range cleanAttrs {408 var appended bool409 if htmlAttr.Key == "rel" {410 if strings.Contains(htmlAttr.Val, "noopener") {411 noOpenerAdded = true412 tmpAttrs = append(tmpAttrs, htmlAttr)413 } else {414 htmlAttr.Val += " noopener"415 noOpenerAdded = true416 tmpAttrs = append(tmpAttrs, htmlAttr)417 }418 appended = true419 }420 if !appended {421 tmpAttrs = append(tmpAttrs, htmlAttr)422 }423 }424 if noOpenerAdded {425 cleanAttrs = tmpAttrs426 } else {427 // rel attr was not found, or else noopener would428 // have been added already429 rel := html.Attribute{}430 rel.Key = "rel"431 rel.Val = "noopener"432 cleanAttrs = append(cleanAttrs, rel)433 }434 }435 }436 default:437 }438 }439 }440 return cleanAttrs441}442func (p *Policy) allowNoAttrs(elementName string) bool {443 _, ok := p.setOfElementsAllowedWithoutAttrs[elementName]444 return ok445}446func (p *Policy) validURL(rawurl string) (string, bool) {447 if p.requireParseableURLs {448 // URLs are valid if when space is trimmed the URL is valid449 rawurl = strings.TrimSpace(rawurl)450 // URLs cannot contain whitespace, unless it is a data-uri451 if (strings.Contains(rawurl, " ") ||452 strings.Contains(rawurl, "\t") ||453 strings.Contains(rawurl, "\n")) &&454 !strings.HasPrefix(rawurl, `data:`) {455 return "", false456 }457 // URLs are valid if they parse458 u, err := url.Parse(rawurl)459 if err != nil {460 return "", false461 }462 if u.Scheme != "" {463 urlPolicy, ok := p.allowURLSchemes[u.Scheme]464 if !ok {465 return "", false466 }467 if urlPolicy == nil || urlPolicy(u) == true {468 return u.String(), true469 }470 return "", false471 }472 if p.allowRelativeURLs {473 if u.String() != "" {474 return u.String(), true475 }476 }477 return "", false478 }479 return rawurl, true480}481func linkable(elementName string) bool {482 switch elementName {483 case "a", "area", "blockquote", "img", "link", "script":484 return true485 default:486 return false487 }488}489func isDataAttribute(val string) bool {490 if !dataAttribute.MatchString(val) {491 return false492 }493 rest := strings.Split(val, "data-")494 if len(rest) == 1 {495 return false496 }497 // data-xml* is invalid.498 if dataAttributeXMLPrefix.MatchString(rest[1]) {499 return false500 }501 // no uppercase or semi-colons allowed.502 if dataAttributeInvalidChars.MatchString(rest[1]) {503 return false504 }505 return true506}...
context.go
Source:context.go
...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",...
Attr
Using AI Code Generation
1import (2func main() {3 doc, err := html.Parse(os.Stdin)4 if err != nil {5 fmt.Fprintf(os.Stderr, "findlinks1: %v6 os.Exit(1)7 }8 for _, link := range visit(nil, doc) {9 fmt.Println(link)10 }11}12func visit(links []string, n *html.Node) []string {13 if n.Type == html.ElementNode && n.Data == "a" {14 for _, a := range n.Attr {15 if a.Key == "href" {16 links = append(links, a.Val)17 }18 }19 }20 for c := n.FirstChild; c != nil; c = c.NextSibling {21 links = visit(links, c)22 }23}24import (25func main() {26 doc, err := html.Parse(os.Stdin)27 if err != nil {28 fmt.Fprintf(os.Stderr, "findlinks2: %v29 os.Exit(1)30 }31 for _, link := range visit(nil, doc) {32 fmt.Println(link)33 }34}35func visit(links []string, n *html.Node) []string {36 if n.Type == html.ElementNode && n.Data == "a" {37 for _, a := range n.Attr {38 if a.Key == "href" {39 links = append(links, a.Val)40 }41 }42 }43 if n.FirstChild != nil {44 links = visit(links, n.FirstChild)45 }46 if n.NextSibling != nil {47 links = visit(links, n.NextSibling)48 }49}
Attr
Using AI Code Generation
1import (2func main() {3 doc, err := html.Parse(os.Stdin)4 if err != nil {5 log.Fatal(err)6 }7 visitNode(doc)8}9func visitNode(n *html.Node) {10 if n.Type == html.ElementNode {11 fmt.Printf("%s ", n.Data)12 for _, a := range n.Attr {13 fmt.Printf("%s=\"%s\" ", a.Key, a.Val)14 }15 fmt.Printf("16 }17 for c := n.FirstChild; c != nil; c = c.NextSibling {18 visitNode(c)19 }20}21import (22func main() {23 doc, err := html.Parse(os.Stdin)24 if err != nil {25 log.Fatal(err)26 }27 visitNode(doc)28}29func visitNode(n *html.Node) {30 if n.Type == html.ElementNode {31 fmt.Printf("%s ", n.Data)32 for _, a := range n.Attr {33 fmt.Printf("%s=\"%s\" ", a.Key, a.Val)34 }35 fmt.Printf("36 }37 for c := n.FirstChild; c != nil; c = c.NextSibling {38 visitNode(c)39 }40}41import (42func main() {43 doc, err := html.Parse(os.Stdin)44 if err != nil {45 log.Fatal(err)46 }47 visitNode(doc)48}49func visitNode(n *html.Node) {50 if n.Type == html.ElementNode {51 fmt.Printf("%s ", n.Data)52 for _, a := range n.Attr {53 fmt.Printf("%s=\"%s\" ", a.Key, a.Val)54 }55 fmt.Printf("56 }57 for c := n.FirstChild; c != nil; c = c.NextSibling {58 visitNode(c)59 }60}61import (
Attr
Using AI Code Generation
1import (2func main() {3 if err != nil {4 log.Fatalln(err)5 }6 defer resp.Body.Close()7 doc, err := html.Parse(resp.Body)8 if err != nil {9 log.Fatalln(err)10 }11 var f func(*html.Node)12 f = func(n *html.Node) {13 if n.Type == html.ElementNode && n.Data == "a" {14 for _, a := range n.Attr {15 if a.Key == "href" {16 fmt.Println(a.Val)17 }18 }19 }20 for c := n.FirstChild; c != nil; c = c.NextSibling {21 f(c)22 }23 }24 f(doc)25}
Attr
Using AI Code Generation
1import (2func main() {3 z := html.NewTokenizer(resp.Body)4 for {5 tt := z.Next()6 switch {7 t := z.Token()8 if isAnchor {9 for _, a := range t.Attr {10 if a.Key == "href" {11 fmt.Printf("Link: %s12 }13 }14 }15 }16 }17}18import (19func main() {20 z := html.NewTokenizer(resp.Body)21 for {22 tt := z.Next()23 switch {24 t := z.Token()25 if isAnchor {26 for _, a := range t.Attr {27 if a.Key == "href" {28 fmt.Printf("Link: %s29 }30 }31 }32 }33 }34}35import (36func main() {37 z := html.NewTokenizer(resp.Body)38 for {39 tt := z.Next()40 switch {41 t := z.Token()42 if isAnchor {43 for _, a := range t.Attr {44 if a.Key == "href" {45 fmt.Printf("Link: %s
Attr
Using AI Code Generation
1import (2func main() {3 if err != nil {4 fmt.Println("Error in getting the response from the server")5 }6 defer resp.Body.Close()7 doc, err := html.Parse(resp.Body)8 if err != nil {9 fmt.Println("Error in parsing the response body")10 }11 visitNode(doc)12}13func visitNode(n *html.Node) {14 if n.Type == html.ElementNode {15 fmt.Printf("%s ", n.Data)16 }17 for c := n.FirstChild; c != nil; c = c.NextSibling {18 visitNode(c)19 }20}21import (22func main() {23 if err != nil {24 fmt.Println("Error in getting the response from the server")25 }26 defer resp.Body.Close()27 doc, err := html.Parse(resp.Body)28 if err != nil {29 fmt.Println("Error in parsing the response body")30 }31 visitNode(doc)32}33func visitNode(n *html.Node) {34 if n.Type == html.ElementNode {35 fmt.Printf("%s ", n.Data)36 }37 for _, a := range n.Attr {38 fmt.Printf("%s=%q ", a.Key, a.Val)39 }40 for c := n.FirstChild; c != nil; c = c.NextSibling {41 visitNode(c)42 }43}44import (45func main() {46 if err != nil {47 fmt.Println("Error in getting the response from the server")48 }49 defer resp.Body.Close()50 doc, err := html.Parse(resp.Body)51 if err != nil {52 fmt.Println("Error in parsing the response body")53 }54 visitNode(doc)55}56func visitNode(n *html.Node) {57 if n.Type == html.ElementNode {58 fmt.Printf("%s ", n.Data
Attr
Using AI Code Generation
1import (2func main() {3 if err != nil {4 fmt.Println("Error reading response. ", err)5 }6 defer resp.Body.Close()7 doc, err := html.Parse(resp.Body)8 if err != nil {9 fmt.Println("Error parsing HTML. ", err)10 }11 var f func(*html.Node)12 f = func(n *html.Node) {13 if n.Type == html.ElementNode && n.Data == "a" {14 for _, a := range n.Attr {15 if a.Key == "href" {16 fmt.Println(a.Val)17 }18 }19 }20 for c := n.FirstChild; c != nil; c = c.NextSibling {21 f(c)22 }23 }24 f(doc)25}
Attr
Using AI Code Generation
1import (2func main() {3 if err != nil {4 fmt.Println(err)5 }6 defer resp.Body.Close()7 doc, err := html.Parse(resp.Body)8 if err != nil {9 fmt.Println(err)10 }11 for _, a := range visit(nil, doc) {12 fmt.Println(a)13 }14}15func visit(links []string, n *html.Node) []string {16 if n.Type == html.ElementNode && n.Data == "a" {17 for _, a := range n.Attr {18 if a.Key == "href" {19 links = append(links, a.Val)20 }21 }22 }23 for c := n.FirstChild; c != nil; c = c.NextSibling {24 links = visit(links, c)25 }26}
Attr
Using AI Code Generation
1import (2func main() {3 if err != nil {4 log.Fatal(err)5 }6 defer resp.Body.Close()7 doc, err := html.Parse(resp.Body)8 if err != nil {9 log.Fatal(err)10 }11 for _, link := range visit(nil, doc) {12 fmt.Println(link)13 }14}15func visit(links []string, n *html.Node) []string {16 if n.Type == html.ElementNode {17 for _, a := range n.Attr {18 if a.Key == "href" {19 links = append(links, a.Val)20 }21 }22 }23 for c := n.FirstChild; c != nil; c = c.NextSibling {24 links = visit(links, c)25 }26}
Attr
Using AI Code Generation
1import (2func main() {3 if err != nil {4 log.Fatal(err)5 }6 htmlData, err := ioutil.ReadAll(resp.Body)7 if err != nil {8 log.Fatal(err)9 }10 htmlString := string(htmlData)11 doc, err := html.Parse(strings.NewReader(htmlString))12 if err != nil {13 log.Fatal(err)14 }15 Attr(doc)16}17func Attr(n *html.Node) {18 if n.Type == html.ElementNode {19 for _, a := range n.Attr {20 fmt.Printf("Attribute Name: %s, Value: %s\n", a.Key, a.Val)21 }22 }23 for c := n.FirstChild; c != nil; c = c.NextSibling {24 Attr(c)25 }26}
Attr
Using AI Code Generation
1import (2func getAttrValue(n *html.Node, attr string) string {3 for _, a := range n.Attr {4 if a.Key == attr {5 }6 }7}8func getAttrValue2(n *html.Node, attr string) string {9 for _, a := range n.Attr {10 if a.Key == attr {11 }12 }13}14func getAttrValue3(n *html.Node, attr string) string {15 for _, a := range n.Attr {16 if a.Key == attr {17 }18 }19}20func getAttrValue4(n *html.Node, attr string) string {21 for _, a := range n.Attr {22 if a.Key == attr {23 }24 }25}26func getAttrValue5(n *html.Node, attr string) string {27 for _, a := range n.Attr {28 if a.Key == attr {29 }30 }31}32func getAttrValue6(n *html.Node, attr string) string {33 for _, a := range n.Attr {34 if a.Key == attr {35 }36 }37}38func getAttrValue7(n *html.Node, attr string) string {39 for _, a := range n.Attr {40 if a.Key == attr {41 }42 }43}44func getAttrValue8(n *html.Node, attr string) string {45 for _, a := range n.Attr {46 if a.Key == attr {
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!!