// EscapeSet rewrites the template set to guarantee that the output of any of // the named templates is properly escaped. // Names should include the names of all templates that might be Executed but // need not include helper templates. // If no error is returned, then the named templates have been modified. // Otherwise the named templates have been rendered unusable. func EscapeSet(s *template.Set, names ...string) (*template.Set, os.Error) { if len(names) == 0 { // TODO: Maybe add a method to Set to enumerate template names // and use those instead. return nil, &Error{ErrNoNames, "", 0, "must specify names of top level templates"} } e := newEscaper(s) for _, name := range names { c, _ := e.escapeTree(context{}, name, 0) var err os.Error if c.err != nil { err, c.err.Name = c.err, name } else if c.state != stateText { err = &Error{ErrEndContext, name, 0, fmt.Sprintf("ends in a non-text context: %v", c)} } if err != nil { // Prevent execution of unsafe templates. for _, name := range names { if t := s.Template(name); t != nil { t.Tree = nil } } return nil, err } } e.commit() return s, nil }
// Escape rewrites each action in the template to guarantee that the output is // properly escaped. func Escape(t *template.Template) (*template.Template, os.Error) { var s template.Set s.Add(t) if _, err := EscapeSet(&s, t.Name()); err != nil { return nil, err } // TODO: if s contains cloned dependencies due to self-recursion // cross-context, error out. return t, nil }
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 } } }
func TestEscapeSet(t *testing.T) { type dataItem struct { Children []*dataItem X string } data := dataItem{ Children: []*dataItem{ &dataItem{X: "foo"}, &dataItem{X: "<bar>"}, &dataItem{ Children: []*dataItem{ &dataItem{X: "baz"}, }, }, }, } tests := []struct { inputs map[string]string want string }{ // The trivial set. { map[string]string{ "main": ``, }, ``, }, // A template called in the start context. { map[string]string{ "main": `Hello, {{template "helper"}}!`, // Not a valid top level HTML template. // "<b" is not a full tag. "helper": `{{"<World>"}}`, }, `Hello, <World>!`, }, // A template called in a context other than the start. { map[string]string{ "main": `<a onclick='a = {{template "helper"}};'>`, // Not a valid top level HTML template. // "<b" is not a full tag. "helper": `{{"<a>"}}<b`, }, `<a onclick='a = "\u003ca\u003e"<b;'>`, }, // A recursive template that ends in its start context. { map[string]string{ "main": `{{range .Children}}{{template "main" .}}{{else}}{{.X}} {{end}}`, }, `foo <bar> baz `, }, // A recursive helper template that ends in its start context. { map[string]string{ "main": `{{template "helper" .}}`, "helper": `{{if .Children}}<ul>{{range .Children}}<li>{{template "main" .}}</li>{{end}}</ul>{{else}}{{.X}}{{end}}`, }, `<ul><li>foo</li><li><bar></li><li><ul><li>baz</li></ul></li></ul>`, }, // Co-recursive templates that end in its start context. { map[string]string{ "main": `<blockquote>{{range .Children}}{{template "helper" .}}{{end}}</blockquote>`, "helper": `{{if .Children}}{{template "main" .}}{{else}}{{.X}}<br>{{end}}`, }, `<blockquote>foo<br><bar><br><blockquote>baz<br></blockquote></blockquote>`, }, // A template that is called in two different contexts. { map[string]string{ "main": `<button onclick="title='{{template "helper"}}'; ...">{{template "helper"}}</button>`, "helper": `{{11}} of {{"<100>"}}`, }, `<button onclick="title='11 of \x3c100\x3e'; ...">11 of <100></button>`, }, // A non-recursive template that ends in a different context. // helper starts in jsCtxRegexp and ends in jsCtxDivOp. { map[string]string{ "main": `<script>var x={{template "helper"}}/{{"42"}};</script>`, "helper": "{{126}}", }, `<script>var x= 126 /"42";</script>`, }, // A recursive template that ends in a similar context. { map[string]string{ "main": `<script>var x=[{{template "countdown" 4}}];</script>`, "countdown": `{{.}}{{if .}},{{template "countdown" . | pred}}{{end}}`, }, `<script>var x=[ 4 , 3 , 2 , 1 , 0 ];</script>`, }, // A recursive template that ends in a different context. /* { map[string]string{ "main": `<a href="/foo{{template "helper" .}}">`, "helper": `{{if .Children}}{{range .Children}}{{template "helper" .}}{{end}}{{else}}?x={{.X}}{{end}}`, }, `<a href="/foo?x=foo?x=%3cbar%3e?x=baz">`, }, */ } // pred is a template function that returns the predecessor of a // natural number for testing recursive templates. fns := template.FuncMap{"pred": func(a ...interface{}) (interface{}, error) { if len(a) == 1 { if i, _ := a[0].(int); i > 0 { return i - 1, nil } } return nil, fmt.Errorf("undefined pred(%v)", a) }} for _, test := range tests { var s template.Set for name, src := range test.inputs { t := template.New(name) t.Funcs(fns) s.Add(template.Must(t.Parse(src))) } s.Funcs(fns) if _, err := EscapeSet(&s, "main"); err != nil { t.Errorf("%s for input:\n%v", err, test.inputs) continue } var b bytes.Buffer if err := s.Execute(&b, "main", data); err != nil { t.Errorf("%q executing %v", err.Error(), s.Template("main")) continue } if got := b.String(); test.want != got { t.Errorf("want\n\t%q\ngot\n\t%q", test.want, got) } } }