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