var x = [{{range .}}'{{.}},{{end}}] // Discussion: // If an iteration through a range would cause it to end in a // different context than an earlier pass, there is no single context. - // In the example, the
tag is missing a '>'. - // EscapeSet cannot tell whether {{.}} is meant to be an HTML class or - // the content of a broken
element and complains because the - // second iteration would produce something like + // In the example, there is missing a quote, so it is not clear + // whether {{.}} is meant to be inside a JS string or in a JS value + // context. The second iteration would produce something like // - //
var x = ['firstValue,'secondValue] ErrRangeLoopReentry // ErrSlashAmbig: '/' could start a division or regexp. diff --git a/src/pkg/html/template/escape.go b/src/pkg/html/template/escape.go index 28615a93180..e8eae8f174b 100644 --- a/src/pkg/html/template/escape.go +++ b/src/pkg/html/template/escape.go @@ -12,31 +12,23 @@ import ( "template/parse" ) -// Escape rewrites each action in the template to guarantee that the output is +// escape rewrites each action in the template to guarantee that the output is // properly escaped. -func Escape(t *template.Template) (*template.Template, error) { +func escape(t *template.Template) error { var s template.Set s.Add(t) - if _, err := EscapeSet(&s, t.Name()); err != nil { - return nil, err - } + return escapeSet(&s, t.Name()) // TODO: if s contains cloned dependencies due to self-recursion // cross-context, error out. - return t, nil } -// EscapeSet rewrites the template set to guarantee that the output of any of +// 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, 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"} - } +func escapeSet(s *template.Set, names ...string) error { e := newEscaper(s) for _, name := range names { c, _ := e.escapeTree(context{}, name, 0) @@ -53,11 +45,11 @@ func EscapeSet(s *template.Set, names ...string) (*template.Set, error) { t.Tree = nil } } - return nil, err + return err } } e.commit() - return s, nil + return nil } // funcMap maps command names to functions that render their inputs safe. @@ -509,7 +501,7 @@ func (e *escaper) escapeTree(c context, name string, line int) (context, string) }, dname } if dname != name { - // Use any template derived during an earlier call to EscapeSet + // Use any template derived during an earlier call to escapeSet // with different top level templates, or clone if necessary. dt := e.template(dname) if dt == nil { @@ -675,7 +667,7 @@ func contextAfterText(c context, s []byte) (context, int) { // http://www.w3.org/TR/html5/tokenization.html#attribute-value-unquoted-state // lists the runes below as error characters. // Error out because HTML parsers may differ on whether - // "{{.}}`)))) + tmpl := Must(New("t").Parse(`{{.}}`)) var buf bytes.Buffer b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/src/pkg/html/template/template.go b/src/pkg/html/template/template.go new file mode 100644 index 00000000000..04066ab40e5 --- /dev/null +++ b/src/pkg/html/template/template.go @@ -0,0 +1,239 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package html + +import ( + "fmt" + "io" + "path/filepath" + "template" +) + +// Set is a specialized template.Set that produces a safe HTML document +// fragment. +type Set struct { + escaped map[string]bool + template.Set +} + +// Template is a specialized template.Template that produces a safe HTML +// document fragment. +type Template struct { + escaped bool + *template.Template +} + +// Execute applies the named template to the specified data object, writing +// the output to wr. +func (s *Set) Execute(wr io.Writer, name string, data interface{}) error { + if !s.escaped[name] { + if err := escapeSet(&s.Set, name); err != nil { + return err + } + if s.escaped == nil { + s.escaped = make(map[string]bool) + } + s.escaped[name] = true + } + return s.Set.Execute(wr, name, data) +} + +// Parse parses a string into a set of named templates. Parse may be called +// multiple times for a given set, adding the templates defined in the string +// to the set. If a template is redefined, the element in the set is +// overwritten with the new definition. +func (set *Set) Parse(src string) (*Set, error) { + set.escaped = nil + s, err := set.Set.Parse(src) + if err != nil { + return nil, err + } + if s != &(set.Set) { + panic("allocated new set") + } + return set, nil +} + +// Parse parses the template definition string to construct an internal +// representation of the template for execution. +func (tmpl *Template) Parse(src string) (*Template, error) { + tmpl.escaped = false + t, err := tmpl.Template.Parse(src) + if err != nil { + return nil, err + } + tmpl.Template = t + return tmpl, nil +} + +// Execute applies a parsed template to the specified data object, +// writing the output to wr. +func (t *Template) Execute(wr io.Writer, data interface{}) error { + if !t.escaped { + if err := escape(t.Template); err != nil { + return err + } + t.escaped = true + } + return t.Template.Execute(wr, data) +} + +// New allocates a new HTML template with the given name. +func New(name string) *Template { + return &Template{false, template.New(name)} +} + +// Must panics if err is non-nil in the same way as template.Must. +func Must(t *Template, err error) *Template { + t.Template = template.Must(t.Template, err) + return t +} + +// ParseFile creates a new Template and parses the template definition from +// the named file. The template name is the base name of the file. +func ParseFile(filename string) (*Template, error) { + t, err := template.ParseFile(filename) + if err != nil { + return nil, err + } + return &Template{false, t}, nil +} + +// ParseFile reads the template definition from a file and parses it to +// construct an internal representation of the template for execution. +// The returned template will be nil if an error occurs. +func (tmpl *Template) ParseFile(filename string) (*Template, error) { + t, err := tmpl.Template.ParseFile(filename) + if err != nil { + return nil, err + } + tmpl.Template = t + return tmpl, nil +} + +// SetMust panics if the error is non-nil just like template.SetMust. +func SetMust(s *Set, err error) *Set { + if err != nil { + template.SetMust(&(s.Set), err) + } + return s +} + +// ParseFiles parses the named files into a set of named templates. +// Each file must be parseable by itself. +// If an error occurs, parsing stops and the returned set is nil. +func (set *Set) ParseFiles(filenames ...string) (*Set, error) { + s, err := set.Set.ParseFiles(filenames...) + if err != nil { + return nil, err + } + if s != &(set.Set) { + panic("allocated new set") + } + return set, nil +} + +// ParseSetFiles creates a new Set and parses the set definition from the +// named files. Each file must be individually parseable. +func ParseSetFiles(filenames ...string) (*Set, error) { + set := new(Set) + s, err := set.Set.ParseFiles(filenames...) + if err != nil { + return nil, err + } + if s != &(set.Set) { + panic("allocated new set") + } + return set, nil +} + +// ParseGlob parses the set definition from the files identified by the +// pattern. The pattern is processed by filepath.Glob and must match at +// least one file. +// If an error occurs, parsing stops and the returned set is nil. +func (s *Set) ParseGlob(pattern string) (*Set, error) { + filenames, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + if len(filenames) == 0 { + return nil, fmt.Errorf("pattern matches no files: %#q", pattern) + } + return s.ParseFiles(filenames...) +} + +// ParseSetGlob creates a new Set and parses the set definition from the +// files identified by the pattern. The pattern is processed by filepath.Glob +// and must match at least one file. +func ParseSetGlob(pattern string) (*Set, error) { + set, err := new(Set).ParseGlob(pattern) + if err != nil { + return nil, err + } + return set, nil +} + +// Functions and methods to parse stand-alone template files into a set. + +// ParseTemplateFiles parses the named template files and adds them to the set +// in the same way as template.ParseTemplateFiles but ensures that templates +// with upper-case names are contextually-autoescaped. +func (set *Set) ParseTemplateFiles(filenames ...string) (*Set, error) { + s, err := set.Set.ParseTemplateFiles(filenames...) + if err != nil { + return nil, err + } + if s != &(set.Set) { + panic("new set allocated") + } + return set, nil +} + +// ParseTemplateGlob parses the template files matched by the +// patern and adds them to the set. Each template will be named +// the base name of its file. +// Unlike with ParseGlob, each file should be a stand-alone template +// definition suitable for Template.Parse (not Set.Parse); that is, the +// file does not contain {{define}} clauses. ParseTemplateGlob is +// therefore equivalent to calling the ParseFile function to create +// individual templates, which are then added to the set. +// Each file must be parseable by itself. +// If an error occurs, parsing stops and the returned set is nil. +func (s *Set) ParseTemplateGlob(pattern string) (*Set, error) { + filenames, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + return s.ParseTemplateFiles(filenames...) +} + +// ParseTemplateFiles creates a set by parsing the named files, +// each of which defines a single template. Each template will be +// named the base name of its file. +// Unlike with ParseFiles, each file should be a stand-alone template +// definition suitable for Template.Parse (not Set.Parse); that is, the +// file does not contain {{define}} clauses. ParseTemplateFiles is +// therefore equivalent to calling the ParseFile function to create +// individual templates, which are then added to the set. +// Each file must be parseable by itself. Parsing stops if an error is +// encountered. +func ParseTemplateFiles(filenames ...string) (*Set, error) { + return new(Set).ParseTemplateFiles(filenames...) +} + +// ParseTemplateGlob creates a set by parsing the files matched +// by the pattern, each of which defines a single template. The pattern +// is processed by filepath.Glob and must match at least one file. Each +// template will be named the base name of its file. +// Unlike with ParseGlob, each file should be a stand-alone template +// definition suitable for Template.Parse (not Set.Parse); that is, the +// file does not contain {{define}} clauses. ParseTemplateGlob is +// therefore equivalent to calling the ParseFile function to create +// individual templates, which are then added to the set. +// Each file must be parseable by itself. Parsing stops if an error is +// encountered. +func ParseTemplateGlob(pattern string) (*Set, error) { + return new(Set).ParseTemplateGlob(pattern) +}