diff options
Diffstat (limited to 'src/pkg/html')
-rw-r--r-- | src/pkg/html/escape_test.go | 18 | ||||
-rw-r--r-- | src/pkg/html/template/attr.go | 4 | ||||
-rw-r--r-- | src/pkg/html/template/content.go | 3 | ||||
-rw-r--r-- | src/pkg/html/template/context.go | 4 | ||||
-rw-r--r-- | src/pkg/html/template/escape.go | 52 | ||||
-rw-r--r-- | src/pkg/html/template/escape_test.go | 32 | ||||
-rw-r--r-- | src/pkg/html/template/html.go | 4 | ||||
-rw-r--r-- | src/pkg/html/template/js.go | 2 | ||||
-rw-r--r-- | src/pkg/html/template/template.go | 8 |
9 files changed, 103 insertions, 24 deletions
diff --git a/src/pkg/html/escape_test.go b/src/pkg/html/escape_test.go index b405d4b4a..2d7ad8ac2 100644 --- a/src/pkg/html/escape_test.go +++ b/src/pkg/html/escape_test.go @@ -64,6 +64,24 @@ var unescapeTests = []unescapeTest{ "Footnote‡", "Footnote‡", }, + // Handle single ampersand. + { + "copySingleAmpersand", + "&", + "&", + }, + // Handle ampersand followed by non-entity. + { + "copyAmpersandNonEntity", + "text &test", + "text &test", + }, + // Handle "&#". + { + "copyAmpersandHash", + "text &#", + "text &#", + }, } func TestUnescape(t *testing.T) { diff --git a/src/pkg/html/template/attr.go b/src/pkg/html/template/attr.go index 3ea02880d..d65d34007 100644 --- a/src/pkg/html/template/attr.go +++ b/src/pkg/html/template/attr.go @@ -90,7 +90,7 @@ var attrTypeMap = map[string]contentType{ "name": contentTypePlain, "novalidate": contentTypeUnsafe, // Skip handler names from - // http://www.w3.org/TR/html5/Overview.html#event-handlers-on-elements-document-objects-and-window-objects + // http://www.w3.org/TR/html5/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects // since we have special handling in attrType. "open": contentTypePlain, "optimum": contentTypePlain, @@ -160,7 +160,7 @@ func attrType(name string) contentType { // Heuristics to prevent "javascript:..." injection in custom // data attributes and custom attributes like g:tweetUrl. - // http://www.w3.org/TR/html5/elements.html#embedding-custom-non-visible-data-with-the-data-attributes: + // http://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes // "Custom data attributes are intended to store custom data // private to the page or application, for which there are no // more appropriate attributes or elements." diff --git a/src/pkg/html/template/content.go b/src/pkg/html/template/content.go index 41b1116a6..3715ed5c9 100644 --- a/src/pkg/html/template/content.go +++ b/src/pkg/html/template/content.go @@ -16,7 +16,8 @@ type ( // 2. The CSS3 rule production, such as `a[href=~"https:"].foo#bar`. // 3. CSS3 declaration productions, such as `color: red; margin: 2px`. // 4. The CSS3 value production, such as `rgba(0, 0, 255, 127)`. - // See http://www.w3.org/TR/css3-syntax/#style + // See http://www.w3.org/TR/css3-syntax/#parsing and + // https://web.archive.org/web/20090211114933/http://w3.org/TR/css3-syntax#style CSS string // HTML encapsulates a known safe HTML document fragment. diff --git a/src/pkg/html/template/context.go b/src/pkg/html/template/context.go index eb47e2be3..59e794d68 100644 --- a/src/pkg/html/template/context.go +++ b/src/pkg/html/template/context.go @@ -13,7 +13,7 @@ import ( // // The zero value of type context is the start context for a template that // produces an HTML fragment as defined at -// http://www.w3.org/TR/html5/the-end.html#parsing-html-fragments +// http://www.w3.org/TR/html5/syntax.html#the-end // where the context element is null. type context struct { state state @@ -96,7 +96,7 @@ const ( // stateHTMLCmt occurs inside an <!-- HTML comment -->. stateHTMLCmt // stateRCDATA occurs inside an RCDATA element (<textarea> or <title>) - // as described at http://dev.w3.org/html5/spec/syntax.html#elements-0 + // as described at http://www.w3.org/TR/html5/syntax.html#elements-0 stateRCDATA // stateAttr occurs inside an HTML attribute whose content is text. stateAttr diff --git a/src/pkg/html/template/escape.go b/src/pkg/html/template/escape.go index 9ae9749db..4e379828d 100644 --- a/src/pkg/html/template/escape.go +++ b/src/pkg/html/template/escape.go @@ -40,10 +40,14 @@ func escapeTemplates(tmpl *Template, names ...string) error { } return err } - tmpl.escaped = true - tmpl.Tree = tmpl.text.Tree } e.commit() + for _, name := range names { + if t := tmpl.set[name]; t != nil { + t.escaped = true + t.Tree = t.text.Tree + } + } return nil } @@ -207,6 +211,18 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context { return c } +// allIdents returns the names of the identifiers under the Ident field of the node, +// which might be a singleton (Identifier) or a slice (Field). +func allIdents(node parse.Node) []string { + switch node := node.(type) { + case *parse.IdentifierNode: + return []string{node.Ident} + case *parse.FieldNode: + return node.Ident + } + panic("unidentified node type in allIdents") +} + // ensurePipelineContains ensures that the pipeline has commands with // the identifiers in s in order. // If the pipeline already has some of the sanitizers, do not interfere. @@ -229,27 +245,31 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) { idents = p.Cmds[i+1:] } dups := 0 - for _, id := range idents { - if escFnsEq(s[dups], (id.Args[0].(*parse.IdentifierNode)).Ident) { - dups++ - if dups == len(s) { - return + for _, idNode := range idents { + for _, ident := range allIdents(idNode.Args[0]) { + if escFnsEq(s[dups], ident) { + dups++ + if dups == len(s) { + return + } } } } newCmds := make([]*parse.CommandNode, n-len(idents), n+len(s)-dups) copy(newCmds, p.Cmds) // Merge existing identifier commands with the sanitizers needed. - for _, id := range idents { - pos := id.Args[0].Position() - i := indexOfStr((id.Args[0].(*parse.IdentifierNode)).Ident, s, escFnsEq) - if i != -1 { - for _, name := range s[:i] { - newCmds = appendCmd(newCmds, newIdentCmd(name, pos)) + for _, idNode := range idents { + pos := idNode.Args[0].Position() + for _, ident := range allIdents(idNode.Args[0]) { + i := indexOfStr(ident, s, escFnsEq) + if i != -1 { + for _, name := range s[:i] { + newCmds = appendCmd(newCmds, newIdentCmd(name, pos)) + } + s = s[i+1:] } - s = s[i+1:] } - newCmds = appendCmd(newCmds, id) + newCmds = appendCmd(newCmds, idNode) } // Create any remaining sanitizers. for _, name := range s { @@ -664,7 +684,7 @@ func contextAfterText(c context, s []byte) (context, int) { i = len(s) } if c.delim == delimSpaceOrTagEnd { - // http://www.w3.org/TR/html5/tokenization.html#attribute-value-unquoted-state + // http://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state // lists the runes below as error characters. // Error out because HTML parsers may differ on whether // "<a id= onclick=f(" ends inside id's or onclick's value, diff --git a/src/pkg/html/template/escape_test.go b/src/pkg/html/template/escape_test.go index 58383a6cd..3ccf93ece 100644 --- a/src/pkg/html/template/escape_test.go +++ b/src/pkg/html/template/escape_test.go @@ -1649,6 +1649,38 @@ func TestEmptyTemplate(t *testing.T) { } } +type Issue7379 int + +func (Issue7379) SomeMethod(x int) string { + return fmt.Sprintf("<%d>", x) +} + +// This is a test for issue 7379: type assertion error caused panic, and then +// the code to handle the panic breaks escaping. It's hard to see the second +// problem once the first is fixed, but its fix is trivial so we let that go. See +// the discussion for issue 7379. +func TestPipeToMethodIsEscaped(t *testing.T) { + tmpl := Must(New("x").Parse("<html>{{0 | .SomeMethod}}</html>\n")) + tryExec := func() string { + defer func() { + panicValue := recover() + if panicValue != nil { + t.Errorf("panicked: %v\n", panicValue) + } + }() + var b bytes.Buffer + tmpl.Execute(&b, Issue7379(0)) + return b.String() + } + for i := 0; i < 3; i++ { + str := tryExec() + const expect = "<html><0></html>\n" + if str != expect { + t.Errorf("expected %q got %q", expect, str) + } + } +} + func BenchmarkEscapedExecute(b *testing.B) { tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`)) var buf bytes.Buffer diff --git a/src/pkg/html/template/html.go b/src/pkg/html/template/html.go index f25f1074c..9c069efd1 100644 --- a/src/pkg/html/template/html.go +++ b/src/pkg/html/template/html.go @@ -50,12 +50,12 @@ func htmlEscaper(args ...interface{}) string { // htmlReplacementTable contains the runes that need to be escaped // inside a quoted attribute value or in a text node. var htmlReplacementTable = []string{ - // http://www.w3.org/TR/html5/tokenization.html#attribute-value-unquoted-state: " + // http://www.w3.org/TR/html5/syntax.html#attribute-value-(unquoted)-state // U+0000 NULL Parse error. Append a U+FFFD REPLACEMENT // CHARACTER character to the current attribute's value. // " // and similarly - // http://www.w3.org/TR/html5/tokenization.html#before-attribute-value-state + // http://www.w3.org/TR/html5/syntax.html#before-attribute-value-state 0: "\uFFFD", '"': """, '&': "&", diff --git a/src/pkg/html/template/js.go b/src/pkg/html/template/js.go index d594e0ad7..999a61ed0 100644 --- a/src/pkg/html/template/js.go +++ b/src/pkg/html/template/js.go @@ -99,7 +99,7 @@ func nextJSCtx(s []byte, preceding jsCtx) jsCtx { return jsCtxDivOp } -// regexPrecederKeywords is a set of reserved JS keywords that can precede a +// regexpPrecederKeywords is a set of reserved JS keywords that can precede a // regular expression in JS source. var regexpPrecederKeywords = map[string]bool{ "break": true, diff --git a/src/pkg/html/template/template.go b/src/pkg/html/template/template.go index 11cc34a50..d38965897 100644 --- a/src/pkg/html/template/template.go +++ b/src/pkg/html/template/template.go @@ -62,6 +62,10 @@ func (t *Template) escape() error { // Execute applies a parsed template to the specified data object, // writing the output to wr. +// If an error occurs executing the template or writing its output, +// execution stops, but partial results may already have been written to +// the output writer. +// A template may be executed safely in parallel. func (t *Template) Execute(wr io.Writer, data interface{}) error { if err := t.escape(); err != nil { return err @@ -71,6 +75,10 @@ func (t *Template) Execute(wr io.Writer, data interface{}) error { // ExecuteTemplate applies the template associated with t that has the given // name to the specified data object and writes the output to wr. +// If an error occurs executing the template or writing its output, +// execution stops, but partial results may already have been written to +// the output writer. +// A template may be executed safely in parallel. func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error { tmpl, err := t.lookupAndEscapeTemplate(name) if err != nil { |