summaryrefslogtreecommitdiff
path: root/src/pkg/html
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/html')
-rw-r--r--src/pkg/html/escape_test.go18
-rw-r--r--src/pkg/html/template/attr.go4
-rw-r--r--src/pkg/html/template/content.go3
-rw-r--r--src/pkg/html/template/context.go4
-rw-r--r--src/pkg/html/template/escape.go52
-rw-r--r--src/pkg/html/template/escape_test.go32
-rw-r--r--src/pkg/html/template/html.go4
-rw-r--r--src/pkg/html/template/js.go2
-rw-r--r--src/pkg/html/template/template.go8
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>&lt;0&gt;</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",
'"': "&#34;",
'&': "&amp;",
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 {