aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/html
diff options
context:
space:
mode:
authorIan Lance Taylor <ian@gcc.gnu.org>2018-01-17 14:20:29 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2018-01-17 14:20:29 +0000
commitc6d6367f848cfd8381aba41e035c5e7e873667c5 (patch)
treea218e98243463fc27f5053b4444e2544c63cd57a /libgo/go/html
parent9bff0086915f544fa648ea81131f035cb9ce79a4 (diff)
downloadgcc-c6d6367f848cfd8381aba41e035c5e7e873667c5.zip
gcc-c6d6367f848cfd8381aba41e035c5e7e873667c5.tar.gz
gcc-c6d6367f848cfd8381aba41e035c5e7e873667c5.tar.bz2
libgo: update to Go1.10beta2 release
Reviewed-on: https://go-review.googlesource.com/87897 From-SVN: r256794
Diffstat (limited to 'libgo/go/html')
-rw-r--r--libgo/go/html/template/attr.go1
-rw-r--r--libgo/go/html/template/content.go11
-rw-r--r--libgo/go/html/template/content_test.go166
-rw-r--r--libgo/go/html/template/context.go6
-rw-r--r--libgo/go/html/template/escape.go35
-rw-r--r--libgo/go/html/template/escape_test.go77
-rw-r--r--libgo/go/html/template/template.go13
-rw-r--r--libgo/go/html/template/transition.go4
-rw-r--r--libgo/go/html/template/url.go108
-rw-r--r--libgo/go/html/template/url_test.go57
10 files changed, 427 insertions, 51 deletions
diff --git a/libgo/go/html/template/attr.go b/libgo/go/html/template/attr.go
index 7438f51..92d2789 100644
--- a/libgo/go/html/template/attr.go
+++ b/libgo/go/html/template/attr.go
@@ -120,6 +120,7 @@ var attrTypeMap = map[string]contentType{
"src": contentTypeURL,
"srcdoc": contentTypeHTML,
"srclang": contentTypePlain,
+ "srcset": contentTypeSrcset,
"start": contentTypePlain,
"step": contentTypePlain,
"style": contentTypeCSS,
diff --git a/libgo/go/html/template/content.go b/libgo/go/html/template/content.go
index 2e14bd1..e7cdedc 100644
--- a/libgo/go/html/template/content.go
+++ b/libgo/go/html/template/content.go
@@ -83,6 +83,14 @@ type (
// the encapsulated content should come from a trusted source,
// as it will be included verbatim in the template output.
URL string
+
+ // Srcset encapsulates a known safe srcset attribute
+ // (see http://w3c.github.io/html/semantics-embedded-content.html#element-attrdef-img-srcset).
+ //
+ // Use of this type presents a security risk:
+ // the encapsulated content should come from a trusted source,
+ // as it will be included verbatim in the template output.
+ Srcset string
)
type contentType uint8
@@ -95,6 +103,7 @@ const (
contentTypeJS
contentTypeJSStr
contentTypeURL
+ contentTypeSrcset
// contentTypeUnsafe is used in attr.go for values that affect how
// embedded content and network messages are formed, vetted,
// or interpreted; or which credentials network messages carry.
@@ -156,6 +165,8 @@ func stringify(args ...interface{}) (string, contentType) {
return string(s), contentTypeJSStr
case URL:
return string(s), contentTypeURL
+ case Srcset:
+ return string(s), contentTypeSrcset
}
}
for i, arg := range args {
diff --git a/libgo/go/html/template/content_test.go b/libgo/go/html/template/content_test.go
index 0b4365c..cc092f5 100644
--- a/libgo/go/html/template/content_test.go
+++ b/libgo/go/html/template/content_test.go
@@ -19,7 +19,9 @@ func TestTypedContent(t *testing.T) {
HTMLAttr(` dir="ltr"`),
JS(`c && alert("Hello, World!");`),
JSStr(`Hello, World & O'Reilly\x21`),
- URL(`greeting=H%69&addressee=(World)`),
+ URL(`greeting=H%69,&addressee=(World)`),
+ Srcset(`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`),
+ URL(`,foo/,`),
}
// For each content sensitive escaper, see how it does on
@@ -40,6 +42,8 @@ func TestTypedContent(t *testing.T) {
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
+ `ZgotmplZ`,
+ `ZgotmplZ`,
},
},
{
@@ -53,6 +57,8 @@ func TestTypedContent(t *testing.T) {
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
+ `ZgotmplZ`,
+ `ZgotmplZ`,
},
},
{
@@ -65,7 +71,9 @@ func TestTypedContent(t *testing.T) {
` dir=&#34;ltr&#34;`,
`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
`Hello, World &amp; O&#39;Reilly\x21`,
- `greeting=H%69&amp;addressee=(World)`,
+ `greeting=H%69,&amp;addressee=(World)`,
+ `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+ `,foo/,`,
},
},
{
@@ -79,6 +87,8 @@ func TestTypedContent(t *testing.T) {
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
+ `ZgotmplZ`,
+ `ZgotmplZ`,
},
},
{
@@ -91,7 +101,9 @@ func TestTypedContent(t *testing.T) {
`&#32;dir&#61;&#34;ltr&#34;`,
`c&#32;&amp;&amp;&#32;alert(&#34;Hello,&#32;World!&#34;);`,
`Hello,&#32;World&#32;&amp;&#32;O&#39;Reilly\x21`,
- `greeting&#61;H%69&amp;addressee&#61;(World)`,
+ `greeting&#61;H%69,&amp;addressee&#61;(World)`,
+ `greeting&#61;H%69,&amp;addressee&#61;(World)&#32;2x,&#32;https://golang.org/favicon.ico&#32;500.5w`,
+ `,foo/,`,
},
},
{
@@ -104,7 +116,9 @@ func TestTypedContent(t *testing.T) {
` dir=&#34;ltr&#34;`,
`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
`Hello, World &amp; O&#39;Reilly\x21`,
- `greeting=H%69&amp;addressee=(World)`,
+ `greeting=H%69,&amp;addressee=(World)`,
+ `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+ `,foo/,`,
},
},
{
@@ -117,7 +131,9 @@ func TestTypedContent(t *testing.T) {
` dir=&#34;ltr&#34;`,
`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
`Hello, World &amp; O&#39;Reilly\x21`,
- `greeting=H%69&amp;addressee=(World)`,
+ `greeting=H%69,&amp;addressee=(World)`,
+ `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+ `,foo/,`,
},
},
{
@@ -131,7 +147,9 @@ func TestTypedContent(t *testing.T) {
`c && alert("Hello, World!");`,
// Escape sequence not over-escaped.
`"Hello, World & O'Reilly\x21"`,
- `"greeting=H%69\u0026addressee=(World)"`,
+ `"greeting=H%69,\u0026addressee=(World)"`,
+ `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
+ `",foo/,"`,
},
},
{
@@ -145,7 +163,9 @@ func TestTypedContent(t *testing.T) {
`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
// Escape sequence not over-escaped.
`&#34;Hello, World &amp; O&#39;Reilly\x21&#34;`,
- `&#34;greeting=H%69\u0026addressee=(World)&#34;`,
+ `&#34;greeting=H%69,\u0026addressee=(World)&#34;`,
+ `&#34;greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w&#34;`,
+ `&#34;,foo/,&#34;`,
},
},
{
@@ -158,7 +178,9 @@ func TestTypedContent(t *testing.T) {
`c \x26\x26 alert(\x22Hello, World!\x22);`,
// Escape sequence not over-escaped.
`Hello, World \x26 O\x27Reilly\x21`,
- `greeting=H%69\x26addressee=(World)`,
+ `greeting=H%69,\x26addressee=(World)`,
+ `greeting=H%69,\x26addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
+ `,foo\/,`,
},
},
{
@@ -171,7 +193,9 @@ func TestTypedContent(t *testing.T) {
`c \x26\x26 alert(\x22Hello, World!\x22);`,
// Escape sequence not over-escaped.
`Hello, World \x26 O\x27Reilly\x21`,
- `greeting=H%69\x26addressee=(World)`,
+ `greeting=H%69,\x26addressee=(World)`,
+ `greeting=H%69,\x26addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
+ `,foo\/,`,
},
},
{
@@ -185,7 +209,9 @@ func TestTypedContent(t *testing.T) {
`c && alert("Hello, World!");`,
// Escape sequence not over-escaped.
`"Hello, World & O'Reilly\x21"`,
- `"greeting=H%69\u0026addressee=(World)"`,
+ `"greeting=H%69,\u0026addressee=(World)"`,
+ `"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
+ `",foo/,"`,
},
},
{
@@ -199,7 +225,9 @@ func TestTypedContent(t *testing.T) {
` dir=&#34;ltr&#34;`,
`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
`Hello, World &amp; O&#39;Reilly\x21`,
- `greeting=H%69&amp;addressee=(World)`,
+ `greeting=H%69,&amp;addressee=(World)`,
+ `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+ `,foo/,`,
},
},
{
@@ -212,7 +240,9 @@ func TestTypedContent(t *testing.T) {
`c \x26\x26 alert(\x22Hello, World!\x22);`,
// Escape sequence not over-escaped.
`Hello, World \x26 O\x27Reilly\x21`,
- `greeting=H%69\x26addressee=(World)`,
+ `greeting=H%69,\x26addressee=(World)`,
+ `greeting=H%69,\x26addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
+ `,foo\/,`,
},
},
{
@@ -225,7 +255,9 @@ func TestTypedContent(t *testing.T) {
`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
`Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is done.
- `greeting=H%69&amp;addressee=%28World%29`,
+ `greeting=H%69,&amp;addressee=%28World%29`,
+ `greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
+ `,foo/,`,
},
},
{
@@ -238,7 +270,113 @@ func TestTypedContent(t *testing.T) {
`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
`Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is not done.
- `greeting=H%69&addressee=%28World%29`,
+ `greeting=H%69,&addressee=%28World%29`,
+ `greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
+ `,foo/,`,
+ },
+ },
+ {
+ `<img srcset="{{.}}">`,
+ []string{
+ `#ZgotmplZ`,
+ `#ZgotmplZ`,
+ // Commas are not esacped
+ `Hello,#ZgotmplZ`,
+ // Leading spaces are not percent escapes.
+ ` dir=%22ltr%22`,
+ // Spaces after commas are not percent escaped.
+ `#ZgotmplZ, World!%22%29;`,
+ `Hello,#ZgotmplZ`,
+ `greeting=H%69%2c&amp;addressee=%28World%29`,
+ // Metadata is not escaped.
+ `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+ `%2cfoo/%2c`,
+ },
+ },
+ {
+ `<img srcset={{.}}>`,
+ []string{
+ `#ZgotmplZ`,
+ `#ZgotmplZ`,
+ `Hello,#ZgotmplZ`,
+ // Spaces are HTML escaped not %-escaped
+ `&#32;dir&#61;%22ltr%22`,
+ `#ZgotmplZ,&#32;World!%22%29;`,
+ `Hello,#ZgotmplZ`,
+ `greeting&#61;H%69%2c&amp;addressee&#61;%28World%29`,
+ `greeting&#61;H%69,&amp;addressee&#61;(World)&#32;2x,&#32;https://golang.org/favicon.ico&#32;500.5w`,
+ // Commas are escaped.
+ `%2cfoo/%2c`,
+ },
+ },
+ {
+ `<img srcset="{{.}} 2x, https://golang.org/ 500.5w">`,
+ []string{
+ `#ZgotmplZ`,
+ `#ZgotmplZ`,
+ `Hello,#ZgotmplZ`,
+ ` dir=%22ltr%22`,
+ `#ZgotmplZ, World!%22%29;`,
+ `Hello,#ZgotmplZ`,
+ `greeting=H%69%2c&amp;addressee=%28World%29`,
+ `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+ `%2cfoo/%2c`,
+ },
+ },
+ {
+ `<img srcset="http://godoc.org/ {{.}}, https://golang.org/ 500.5w">`,
+ []string{
+ `#ZgotmplZ`,
+ `#ZgotmplZ`,
+ `Hello,#ZgotmplZ`,
+ ` dir=%22ltr%22`,
+ `#ZgotmplZ, World!%22%29;`,
+ `Hello,#ZgotmplZ`,
+ `greeting=H%69%2c&amp;addressee=%28World%29`,
+ `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+ `%2cfoo/%2c`,
+ },
+ },
+ {
+ `<img srcset="http://godoc.org/?q={{.}} 2x, https://golang.org/ 500.5w">`,
+ []string{
+ `#ZgotmplZ`,
+ `#ZgotmplZ`,
+ `Hello,#ZgotmplZ`,
+ ` dir=%22ltr%22`,
+ `#ZgotmplZ, World!%22%29;`,
+ `Hello,#ZgotmplZ`,
+ `greeting=H%69%2c&amp;addressee=%28World%29`,
+ `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+ `%2cfoo/%2c`,
+ },
+ },
+ {
+ `<img srcset="http://godoc.org/ 2x, {{.}} 500.5w">`,
+ []string{
+ `#ZgotmplZ`,
+ `#ZgotmplZ`,
+ `Hello,#ZgotmplZ`,
+ ` dir=%22ltr%22`,
+ `#ZgotmplZ, World!%22%29;`,
+ `Hello,#ZgotmplZ`,
+ `greeting=H%69%2c&amp;addressee=%28World%29`,
+ `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+ `%2cfoo/%2c`,
+ },
+ },
+ {
+ `<img srcset="http://godoc.org/ 2x, https://golang.org/ {{.}}">`,
+ []string{
+ `#ZgotmplZ`,
+ `#ZgotmplZ`,
+ `Hello,#ZgotmplZ`,
+ ` dir=%22ltr%22`,
+ `#ZgotmplZ, World!%22%29;`,
+ `Hello,#ZgotmplZ`,
+ `greeting=H%69%2c&amp;addressee=%28World%29`,
+ `greeting=H%69,&amp;addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
+ `%2cfoo/%2c`,
},
},
}
diff --git a/libgo/go/html/template/context.go b/libgo/go/html/template/context.go
index 37a3faf..50730d3 100644
--- a/libgo/go/html/template/context.go
+++ b/libgo/go/html/template/context.go
@@ -102,6 +102,8 @@ const (
stateAttr
// stateURL occurs inside an HTML attribute whose content is a URL.
stateURL
+ // stateSrcset occurs inside an HTML srcset attribute.
+ stateSrcset
// stateJS occurs inside an event handler or script element.
stateJS
// stateJSDqStr occurs inside a JavaScript double quoted string.
@@ -145,6 +147,7 @@ var stateNames = [...]string{
stateRCDATA: "stateRCDATA",
stateAttr: "stateAttr",
stateURL: "stateURL",
+ stateSrcset: "stateSrcset",
stateJS: "stateJS",
stateJSDqStr: "stateJSDqStr",
stateJSSqStr: "stateJSSqStr",
@@ -326,6 +329,8 @@ const (
attrStyle
// attrURL corresponds to an attribute whose value is a URL.
attrURL
+ // attrSrcset corresponds to a srcset attribute.
+ attrSrcset
)
var attrNames = [...]string{
@@ -334,6 +339,7 @@ var attrNames = [...]string{
attrScriptType: "attrScriptType",
attrStyle: "attrStyle",
attrURL: "attrURL",
+ attrSrcset: "attrSrcset",
}
func (a attr) String() string {
diff --git a/libgo/go/html/template/escape.go b/libgo/go/html/template/escape.go
index b51a370..5963194 100644
--- a/libgo/go/html/template/escape.go
+++ b/libgo/go/html/template/escape.go
@@ -71,6 +71,7 @@ var funcMap = template.FuncMap{
"_html_template_jsvalescaper": jsValEscaper,
"_html_template_nospaceescaper": htmlNospaceEscaper,
"_html_template_rcdataescaper": rcdataEscaper,
+ "_html_template_srcsetescaper": srcsetFilterAndEscaper,
"_html_template_urlescaper": urlEscaper,
"_html_template_urlfilter": urlFilter,
"_html_template_urlnormalizer": urlNormalizer,
@@ -215,6 +216,8 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
case stateAttrName, stateTag:
c.state = stateAttrName
s = append(s, "_html_template_htmlnamefilter")
+ case stateSrcset:
+ s = append(s, "_html_template_srcsetescaper")
default:
if isComment(c.state) {
s = append(s, "_html_template_commentescaper")
@@ -280,9 +283,22 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) {
}
// Rewrite the pipeline, creating the escapers in s at the end of the pipeline.
newCmds := make([]*parse.CommandNode, pipelineLen, pipelineLen+len(s))
- copy(newCmds, p.Cmds)
+ insertedIdents := make(map[string]bool)
+ for i := 0; i < pipelineLen; i++ {
+ cmd := p.Cmds[i]
+ newCmds[i] = cmd
+ if idNode, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
+ insertedIdents[normalizeEscFn(idNode.Ident)] = true
+ }
+ }
for _, name := range s {
- newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position()))
+ if !insertedIdents[normalizeEscFn(name)] {
+ // When two templates share an underlying parse tree via the use of
+ // AddParseTree and one template is executed after the other, this check
+ // ensures that escapers that were already inserted into the pipeline on
+ // the first escaping pass do not get inserted again.
+ newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position()))
+ }
}
p.Cmds = newCmds
}
@@ -317,13 +333,16 @@ var equivEscapers = map[string]string{
// escFnsEq reports whether the two escaping functions are equivalent.
func escFnsEq(a, b string) bool {
- if e := equivEscapers[a]; e != "" {
- a = e
- }
- if e := equivEscapers[b]; e != "" {
- b = e
+ return normalizeEscFn(a) == normalizeEscFn(b)
+}
+
+// normalizeEscFn(a) is equal to normalizeEscFn(b) for any pair of names of
+// escaper functions a and b that are equivalent.
+func normalizeEscFn(e string) string {
+ if norm := equivEscapers[e]; norm != "" {
+ return norm
}
- return a == b
+ return e
}
// redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
diff --git a/libgo/go/html/template/escape_test.go b/libgo/go/html/template/escape_test.go
index 92f12ca..55f808c 100644
--- a/libgo/go/html/template/escape_test.go
+++ b/libgo/go/html/template/escape_test.go
@@ -650,6 +650,12 @@ func TestEscape(t *testing.T) {
`<{{"script"}}>{{"doEvil()"}}</{{"script"}}>`,
`&lt;script>doEvil()&lt;/script>`,
},
+ {
+ "srcset bad URL in second position",
+ `<img srcset="{{"/not-an-image#,javascript:alert(1)"}}">`,
+ // The second URL is also filtered.
+ `<img srcset="/not-an-image#,#ZgotmplZ">`,
+ },
}
for _, test := range tests {
@@ -1840,7 +1846,7 @@ func TestErrorOnUndefined(t *testing.T) {
err := tmpl.Execute(nil, nil)
if err == nil {
- t.Fatal("expected error")
+ t.Error("expected error")
}
if !strings.Contains(err.Error(), "incomplete") {
t.Errorf("expected error about incomplete template; got %s", err)
@@ -1860,10 +1866,10 @@ func TestIdempotentExecute(t *testing.T) {
for i := 0; i < 2; i++ {
err = tmpl.ExecuteTemplate(got, "hello", nil)
if err != nil {
- t.Fatalf("unexpected error: %s", err)
+ t.Errorf("unexpected error: %s", err)
}
if got.String() != want {
- t.Fatalf("after executing template \"hello\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want)
+ t.Errorf("after executing template \"hello\", got:\n\t%q\nwant:\n\t%q\n", got.String(), want)
}
got.Reset()
}
@@ -1871,7 +1877,7 @@ func TestIdempotentExecute(t *testing.T) {
// "main" does not cause the output of "hello" to change.
err = tmpl.ExecuteTemplate(got, "main", nil)
if err != nil {
- t.Fatalf("unexpected error: %s", err)
+ t.Errorf("unexpected error: %s", err)
}
// If the HTML escaper is added again to the action {{"Ladies & Gentlemen!"}},
// we would expected to see the ampersand overescaped to "&amp;amp;".
@@ -1881,19 +1887,6 @@ func TestIdempotentExecute(t *testing.T) {
}
}
-// This covers issue #21844.
-func TestAddExistingTreeError(t *testing.T) {
- tmpl := Must(New("foo").Parse(`<p>{{.}}</p>`))
- tmpl, err := tmpl.AddParseTree("bar", tmpl.Tree)
- if err == nil {
- t.Fatalf("expected error after AddParseTree")
- }
- const want = `html/template: cannot add parse tree that template "foo" already references`
- if got := err.Error(); got != want {
- t.Errorf("got error:\n\t%q\nwant:\n\t%q\n", got, want)
- }
-}
-
func BenchmarkEscapedExecute(b *testing.B) {
tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`))
var buf bytes.Buffer
@@ -1903,3 +1896,53 @@ func BenchmarkEscapedExecute(b *testing.B) {
buf.Reset()
}
}
+
+// Covers issue 22780.
+func TestOrphanedTemplate(t *testing.T) {
+ t1 := Must(New("foo").Parse(`<a href="{{.}}">link1</a>`))
+ t2 := Must(t1.New("foo").Parse(`bar`))
+
+ var b bytes.Buffer
+ const wantError = `template: "foo" is an incomplete or empty template`
+ if err := t1.Execute(&b, "javascript:alert(1)"); err == nil {
+ t.Fatal("expected error executing t1")
+ } else if gotError := err.Error(); gotError != wantError {
+ t.Fatalf("got t1 execution error:\n\t%s\nwant:\n\t%s", gotError, wantError)
+ }
+ b.Reset()
+ if err := t2.Execute(&b, nil); err != nil {
+ t.Fatalf("error executing t2: %s", err)
+ }
+ const want = "bar"
+ if got := b.String(); got != want {
+ t.Fatalf("t2 rendered %q, want %q", got, want)
+ }
+}
+
+// Covers issue 21844.
+func TestAliasedParseTreeDoesNotOverescape(t *testing.T) {
+ const (
+ tmplText = `{{.}}`
+ data = `<baz>`
+ want = `&lt;baz&gt;`
+ )
+ // Templates "foo" and "bar" both alias the same underlying parse tree.
+ tpl := Must(New("foo").Parse(tmplText))
+ if _, err := tpl.AddParseTree("bar", tpl.Tree); err != nil {
+ t.Fatalf("AddParseTree error: %v", err)
+ }
+ var b1, b2 bytes.Buffer
+ if err := tpl.ExecuteTemplate(&b1, "foo", data); err != nil {
+ t.Fatalf(`ExecuteTemplate failed for "foo": %v`, err)
+ }
+ if err := tpl.ExecuteTemplate(&b2, "bar", data); err != nil {
+ t.Fatalf(`ExecuteTemplate failed for "foo": %v`, err)
+ }
+ got1, got2 := b1.String(), b2.String()
+ if got1 != want {
+ t.Fatalf(`Template "foo" rendered %q, want %q`, got1, want)
+ }
+ if got1 != got2 {
+ t.Fatalf(`Template "foo" and "bar" rendered %q and %q respectively, expected equal values`, got1, got2)
+ }
+}
diff --git a/libgo/go/html/template/template.go b/libgo/go/html/template/template.go
index d77aa3d..4641a37 100644
--- a/libgo/go/html/template/template.go
+++ b/libgo/go/html/template/template.go
@@ -219,11 +219,6 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
- for _, tmpl := range t.set {
- if tmpl.Tree == tree {
- return nil, fmt.Errorf("html/template: cannot add parse tree that template %q already references", tmpl.Name())
- }
- }
text, err := t.text.AddParseTree(name, tree)
if err != nil {
return nil, err
@@ -300,6 +295,10 @@ func New(name string) *Template {
// New allocates a new HTML template associated with the given one
// and with the same delimiters. The association, which is transitive,
// allows one template to invoke another with a {{template}} action.
+//
+// If a template with the given name already exists, the new HTML template
+// will replace it. The existing template will be reset and disassociated with
+// t.
func (t *Template) New(name string) *Template {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
@@ -314,6 +313,10 @@ func (t *Template) new(name string) *Template {
nil,
t.nameSpace,
}
+ if existing, ok := tmpl.set[name]; ok {
+ emptyTmpl := New(existing.Name())
+ *existing = *emptyTmpl
+ }
tmpl.set[name] = tmpl
return tmpl
}
diff --git a/libgo/go/html/template/transition.go b/libgo/go/html/template/transition.go
index df7ac22..c72cf1e 100644
--- a/libgo/go/html/template/transition.go
+++ b/libgo/go/html/template/transition.go
@@ -23,6 +23,7 @@ var transitionFunc = [...]func(context, []byte) (context, int){
stateRCDATA: tSpecialTagEnd,
stateAttr: tAttr,
stateURL: tURL,
+ stateSrcset: tURL,
stateJS: tJS,
stateJSDqStr: tJSDelimited,
stateJSSqStr: tJSDelimited,
@@ -117,6 +118,8 @@ func tTag(c context, s []byte) (context, int) {
attr = attrStyle
case contentTypeJS:
attr = attrScript
+ case contentTypeSrcset:
+ attr = attrSrcset
}
}
@@ -161,6 +164,7 @@ var attrStartStates = [...]state{
attrScriptType: stateAttr,
attrStyle: stateCSS,
attrURL: stateURL,
+ attrSrcset: stateSrcset,
}
// tBeforeValue is the context transition function for stateBeforeValue.
diff --git a/libgo/go/html/template/url.go b/libgo/go/html/template/url.go
index a0bfe76..69a6ff4 100644
--- a/libgo/go/html/template/url.go
+++ b/libgo/go/html/template/url.go
@@ -37,13 +37,23 @@ func urlFilter(args ...interface{}) string {
if t == contentTypeURL {
return s
}
+ if !isSafeUrl(s) {
+ return "#" + filterFailsafe
+ }
+ return s
+}
+
+// isSafeUrl is true if s is a relative URL or if URL has a protocol in
+// (http, https, mailto).
+func isSafeUrl(s string) bool {
if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') {
- protocol := strings.ToLower(s[:i])
- if protocol != "http" && protocol != "https" && protocol != "mailto" {
- return "#" + filterFailsafe
+
+ protocol := s[:i]
+ if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") {
+ return false
}
}
- return s
+ return true
}
// urlEscaper produces an output that can be embedded in a URL query.
@@ -69,6 +79,16 @@ func urlProcessor(norm bool, args ...interface{}) string {
norm = true
}
var b bytes.Buffer
+ if processUrlOnto(s, norm, &b) {
+ return b.String()
+ }
+ return s
+}
+
+// processUrlOnto appends a normalized URL corresponding to its input to b
+// and returns true if the appended content differs from s.
+func processUrlOnto(s string, norm bool, b *bytes.Buffer) bool {
+ b.Grow(b.Cap() + len(s) + 16)
written := 0
// The byte loop below assumes that all URLs use UTF-8 as the
// content-encoding. This is similar to the URI to IRI encoding scheme
@@ -114,12 +134,86 @@ func urlProcessor(norm bool, args ...interface{}) string {
}
}
b.WriteString(s[written:i])
- fmt.Fprintf(&b, "%%%02x", c)
+ fmt.Fprintf(b, "%%%02x", c)
written = i + 1
}
- if written == 0 {
+ b.WriteString(s[written:])
+ return written != 0
+}
+
+// Filters and normalizes srcset values which are comma separated
+// URLs followed by metadata.
+func srcsetFilterAndEscaper(args ...interface{}) string {
+ s, t := stringify(args...)
+ switch t {
+ case contentTypeSrcset:
return s
+ case contentTypeURL:
+ // Normalizing gets rid of all HTML whitespace
+ // which separate the image URL from its metadata.
+ var b bytes.Buffer
+ if processUrlOnto(s, true, &b) {
+ s = b.String()
+ }
+ // Additionally, commas separate one source from another.
+ return strings.Replace(s, ",", "%2c", -1)
}
- b.WriteString(s[written:])
+
+ var b bytes.Buffer
+ written := 0
+ for i := 0; i < len(s); i++ {
+ if s[i] == ',' {
+ filterSrcsetElement(s, written, i, &b)
+ b.WriteString(",")
+ written = i + 1
+ }
+ }
+ filterSrcsetElement(s, written, len(s), &b)
return b.String()
}
+
+// Derived from https://play.golang.org/p/Dhmj7FORT5
+const htmlSpaceAndAsciiAlnumBytes = "\x00\x36\x00\x00\x01\x00\xff\x03\xfe\xff\xff\x07\xfe\xff\xff\x07"
+
+// isHtmlSpace is true iff c is a whitespace character per
+// https://infra.spec.whatwg.org/#ascii-whitespace
+func isHtmlSpace(c byte) bool {
+ return (c <= 0x20) && 0 != (htmlSpaceAndAsciiAlnumBytes[c>>3]&(1<<uint(c&0x7)))
+}
+
+func isHtmlSpaceOrAsciiAlnum(c byte) bool {
+ return (c < 0x80) && 0 != (htmlSpaceAndAsciiAlnumBytes[c>>3]&(1<<uint(c&0x7)))
+}
+
+func filterSrcsetElement(s string, left int, right int, b *bytes.Buffer) {
+ start := left
+ for start < right && isHtmlSpace(s[start]) {
+ start += 1
+ }
+ end := right
+ for i := start; i < right; i++ {
+ if isHtmlSpace(s[i]) {
+ end = i
+ break
+ }
+ }
+ if url := s[start:end]; isSafeUrl(url) {
+ // If image metadata is only spaces or alnums then
+ // we don't need to URL normalize it.
+ metadataOk := true
+ for i := end; i < right; i++ {
+ if !isHtmlSpaceOrAsciiAlnum(s[i]) {
+ metadataOk = false
+ break
+ }
+ }
+ if metadataOk {
+ b.WriteString(s[left:start])
+ processUrlOnto(url, true, b)
+ b.WriteString(s[end:right])
+ return
+ }
+ }
+ b.WriteString("#")
+ b.WriteString(filterFailsafe)
+}
diff --git a/libgo/go/html/template/url_test.go b/libgo/go/html/template/url_test.go
index 5182e9d..75c354e 100644
--- a/libgo/go/html/template/url_test.go
+++ b/libgo/go/html/template/url_test.go
@@ -87,6 +87,51 @@ func TestURLFilters(t *testing.T) {
}
}
+func TestSrcsetFilter(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ want string
+ }{
+ {
+ "one ok",
+ "http://example.com/img.png",
+ "http://example.com/img.png",
+ },
+ {
+ "one ok with metadata",
+ " /img.png 200w",
+ " /img.png 200w",
+ },
+ {
+ "one bad",
+ "javascript:alert(1) 200w",
+ "#ZgotmplZ",
+ },
+ {
+ "two ok",
+ "foo.png, bar.png",
+ "foo.png, bar.png",
+ },
+ {
+ "left bad",
+ "javascript:alert(1), /foo.png",
+ "#ZgotmplZ, /foo.png",
+ },
+ {
+ "right bad",
+ "/bogus#, javascript:alert(1)",
+ "/bogus#,#ZgotmplZ",
+ },
+ }
+
+ for _, test := range tests {
+ if got := srcsetFilterAndEscaper(test.input); got != test.want {
+ t.Errorf("%s: srcsetFilterAndEscaper(%q) want %q != %q", test.name, test.input, test.want, got)
+ }
+ }
+}
+
func BenchmarkURLEscaper(b *testing.B) {
for i := 0; i < b.N; i++ {
urlEscaper("http://example.com:80/foo?q=bar%20&baz=x+y#frag")
@@ -110,3 +155,15 @@ func BenchmarkURLNormalizerNoSpecials(b *testing.B) {
urlNormalizer("http://example.com:80/foo?q=bar%20&baz=x+y#frag")
}
}
+
+func BenchmarkSrcsetFilter(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ srcsetFilterAndEscaper(" /foo/bar.png 200w, /baz/boo(1).png")
+ }
+}
+
+func BenchmarkSrcsetFilterNoSpecials(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ srcsetFilterAndEscaper("http://example.com:80/foo?q=bar%20&baz=x+y#frag")
+ }
+}