func TestErrors(t *testing.T) { tests := []struct { input string err string }{ // Non-error cases. { "{{if .Cond}}<a>{{else}}<b>{{end}}", "", }, { "{{if .Cond}}<a>{{end}}", "", }, { "{{if .Cond}}{{else}}<b>{{end}}", "", }, { "{{with .Cond}}<div>{{end}}", "", }, { "{{range .Items}}<a>{{end}}", "", }, { "<a href='/foo?{{range .Items}}&{{.K}}={{.V}}{{end}}'>", "", }, // Error cases. { "{{if .Cond}}<a{{end}}", "z:1: {{if}} branches", }, { "{{if .Cond}}\n{{else}}\n<a{{end}}", "z:1: {{if}} branches", }, { // Missing quote in the else branch. `{{if .Cond}}<a href="foo">{{else}}<a href="bar>{{end}}`, "z:1: {{if}} branches", }, { // Different kind of attribute: href implies a URL. "<a {{if .Cond}}href='{{else}}title='{{end}}{{.X}}'>", "z:1: {{if}} branches", }, { "\n{{with .X}}<a{{end}}", "z:2: {{with}} branches", }, { "\n{{with .X}}<a>{{else}}<a{{end}}", "z:2: {{with}} branches", }, { "{{range .Items}}<a{{end}}", `z:1: on range loop re-entry: "<" in attribute name: "<a"`, }, { "\n{{range .Items}} x='<a{{end}}", "z:2: on range loop re-entry: {{range}} branches", }, { "<a b=1 c={{.H}}", "z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd", }, { "<script>foo();", "z: ends in a non-text context: {stateJS", }, { `<a href="{{if .F}}/foo?a={{else}}/bar/{{end}}{{.H}}">`, "z:1: (action: [(command: [F=[H]])]) appears in an ambiguous URL context", }, { `<a onclick="alert('Hello \`, `unfinished escape sequence in JS string: "Hello \\"`, }, { `<a onclick='alert("Hello\, World\`, `unfinished escape sequence in JS string: "Hello\\, World\\"`, }, { `<a onclick='alert(/x+\`, `unfinished escape sequence in JS string: "x+\\"`, }, { `<a onclick="/foo[\]/`, `unfinished JS regexp charset: "foo[\\]/"`, }, { // It is ambiguous whether 1.5 should be 1\.5 or 1.5. // Either `var x = 1/- 1.5 /i.test(x)` // where `i.test(x)` is a method call of reference i, // or `/-1\.5/i.test(x)` which is a method call on a // case insensitive regular expression. `<script>{{if false}}var x = 1{{end}}/-{{"1.5"}}/i.test(x)</script>`, `'/' could start a division or regexp: "/-"`, }, { `{{template "foo"}}`, "z:1: no such template foo", }, { `{{define "z"}}<div{{template "y"}}>{{end}}` + // Illegal starting in stateTag but not in stateText. `{{define "y"}} foo<b{{end}}`, `"<" in attribute name: " foo<b"`, }, { `{{define "z"}}<script>reverseList = [{{template "t"}}]</script>{{end}}` + // Missing " after recursive call. `{{define "t"}}{{if .Tail}}{{template "t" .Tail}}{{end}}{{.Head}}",{{end}}`, `: cannot compute output context for template t$htmltemplate_stateJS_elementScript`, }, { `<input type=button value=onclick=>`, `exp/template/html:z: "=" in unquoted attr: "onclick="`, }, { `<input type=button value= onclick=>`, `exp/template/html:z: "=" in unquoted attr: "onclick="`, }, { `<input type=button value= 1+1=2>`, `exp/template/html:z: "=" in unquoted attr: "1+1=2"`, }, { "<a class=`foo>", "exp/template/html:z: \"`\" in unquoted attr: \"`foo\"", }, { `<a style=font:'Arial'>`, `exp/template/html:z: "'" in unquoted attr: "font:'Arial'"`, }, { `<a=foo>`, `: expected space, attr name, or end of tag, but got "=foo>"`, }, } for _, test := range tests { var err error if strings.HasPrefix(test.input, "{{define") { var s template.Set _, err = s.Parse(test.input) if err != nil { t.Errorf("Failed to parse %q: %s", test.input, err) continue } _, err = EscapeSet(&s, "z") } else { tmpl := template.Must(template.New("z").Parse(test.input)) _, err = Escape(tmpl) } var got string if err != nil { got = err.Error() } if test.err == "" { if got != "" { t.Errorf("input=%q: unexpected error %q", test.input, got) } continue } if strings.Index(got, test.err) == -1 { t.Errorf("input=%q: error\n\t%q\ndoes not contain expected string\n\t%q", test.input, got, test.err) continue } } }