summaryrefslogtreecommitdiff
path: root/src/pkg/html
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/html')
-rw-r--r--src/pkg/html/escape.go10
-rw-r--r--src/pkg/html/escape_test.go97
-rw-r--r--src/pkg/html/template/clone_test.go40
-rw-r--r--src/pkg/html/template/content.go6
-rw-r--r--src/pkg/html/template/content_test.go31
-rw-r--r--src/pkg/html/template/context.go2
-rw-r--r--src/pkg/html/template/css.go8
-rw-r--r--src/pkg/html/template/escape.go4
-rw-r--r--src/pkg/html/template/escape_test.go11
-rw-r--r--src/pkg/html/template/js.go2
-rw-r--r--src/pkg/html/template/template.go18
-rw-r--r--src/pkg/html/template/transition.go4
12 files changed, 200 insertions, 33 deletions
diff --git a/src/pkg/html/escape.go b/src/pkg/html/escape.go
index eff0384e0..dd5dfa7cd 100644
--- a/src/pkg/html/escape.go
+++ b/src/pkg/html/escape.go
@@ -187,16 +187,6 @@ func unescape(b []byte) []byte {
return b
}
-// lower lower-cases the A-Z bytes in b in-place, so that "aBc" becomes "abc".
-func lower(b []byte) []byte {
- for i, c := range b {
- if 'A' <= c && c <= 'Z' {
- b[i] = c + 'a' - 'A'
- }
- }
- return b
-}
-
const escapedChars = `&'<>"`
func escape(w writer, s string) error {
diff --git a/src/pkg/html/escape_test.go b/src/pkg/html/escape_test.go
new file mode 100644
index 000000000..b405d4b4a
--- /dev/null
+++ b/src/pkg/html/escape_test.go
@@ -0,0 +1,97 @@
+// Copyright 2013 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 "testing"
+
+type unescapeTest struct {
+ // A short description of the test case.
+ desc string
+ // The HTML text.
+ html string
+ // The unescaped text.
+ unescaped string
+}
+
+var unescapeTests = []unescapeTest{
+ // Handle no entities.
+ {
+ "copy",
+ "A\ttext\nstring",
+ "A\ttext\nstring",
+ },
+ // Handle simple named entities.
+ {
+ "simple",
+ "&amp; &gt; &lt;",
+ "& > <",
+ },
+ // Handle hitting the end of the string.
+ {
+ "stringEnd",
+ "&amp &amp",
+ "& &",
+ },
+ // Handle entities with two codepoints.
+ {
+ "multiCodepoint",
+ "text &gesl; blah",
+ "text \u22db\ufe00 blah",
+ },
+ // Handle decimal numeric entities.
+ {
+ "decimalEntity",
+ "Delta = &#916; ",
+ "Delta = Δ ",
+ },
+ // Handle hexadecimal numeric entities.
+ {
+ "hexadecimalEntity",
+ "Lambda = &#x3bb; = &#X3Bb ",
+ "Lambda = λ = λ ",
+ },
+ // Handle numeric early termination.
+ {
+ "numericEnds",
+ "&# &#x &#128;43 &copy = &#169f = &#xa9",
+ "&# &#x €43 © = ©f = ©",
+ },
+ // Handle numeric ISO-8859-1 entity replacements.
+ {
+ "numericReplacements",
+ "Footnote&#x87;",
+ "Footnote‡",
+ },
+}
+
+func TestUnescape(t *testing.T) {
+ for _, tt := range unescapeTests {
+ unescaped := UnescapeString(tt.html)
+ if unescaped != tt.unescaped {
+ t.Errorf("TestUnescape %s: want %q, got %q", tt.desc, tt.unescaped, unescaped)
+ }
+ }
+}
+
+func TestUnescapeEscape(t *testing.T) {
+ ss := []string{
+ ``,
+ `abc def`,
+ `a & b`,
+ `a&amp;b`,
+ `a &amp b`,
+ `&quot;`,
+ `"`,
+ `"<&>"`,
+ `&quot;&lt;&amp;&gt;&quot;`,
+ `3&5==1 && 0<1, "0&lt;1", a+acute=&aacute;`,
+ `The special characters are: <, >, &, ' and "`,
+ }
+ for _, s := range ss {
+ if got := UnescapeString(EscapeString(s)); got != s {
+ t.Errorf("got %q want %q", got, s)
+ }
+ }
+}
diff --git a/src/pkg/html/template/clone_test.go b/src/pkg/html/template/clone_test.go
index 2663cddc2..e11bff2c5 100644
--- a/src/pkg/html/template/clone_test.go
+++ b/src/pkg/html/template/clone_test.go
@@ -6,6 +6,8 @@ package template
import (
"bytes"
+ "errors"
+ "io/ioutil"
"testing"
"text/template/parse"
)
@@ -146,3 +148,41 @@ func TestCloneCrash(t *testing.T) {
Must(t1.New("t1").Parse(`{{define "foo"}}foo{{end}}`))
t1.Clone()
}
+
+// Ensure that this guarantee from the docs is upheld:
+// "Further calls to Parse in the copy will add templates
+// to the copy but not to the original."
+func TestCloneThenParse(t *testing.T) {
+ t0 := Must(New("t0").Parse(`{{define "a"}}{{template "embedded"}}{{end}}`))
+ t1 := Must(t0.Clone())
+ Must(t1.Parse(`{{define "embedded"}}t1{{end}}`))
+ if len(t0.Templates())+1 != len(t1.Templates()) {
+ t.Error("adding a template to a clone added it to the original")
+ }
+ // double check that the embedded template isn't available in the original
+ err := t0.ExecuteTemplate(ioutil.Discard, "a", nil)
+ if err == nil {
+ t.Error("expected 'no such template' error")
+ }
+}
+
+// https://code.google.com/p/go/issues/detail?id=5980
+func TestFuncMapWorksAfterClone(t *testing.T) {
+ funcs := FuncMap{"customFunc": func() (string, error) {
+ return "", errors.New("issue5980")
+ }}
+
+ // get the expected error output (no clone)
+ uncloned := Must(New("").Funcs(funcs).Parse("{{customFunc}}"))
+ wantErr := uncloned.Execute(ioutil.Discard, nil)
+
+ // toClone must be the same as uncloned. It has to be recreated from scratch,
+ // since cloning cannot occur after execution.
+ toClone := Must(New("").Funcs(funcs).Parse("{{customFunc}}"))
+ cloned := Must(toClone.Clone())
+ gotErr := cloned.Execute(ioutil.Discard, nil)
+
+ if wantErr.Error() != gotErr.Error() {
+ t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr)
+ }
+}
diff --git a/src/pkg/html/template/content.go b/src/pkg/html/template/content.go
index 9d1f74f6f..41b1116a6 100644
--- a/src/pkg/html/template/content.go
+++ b/src/pkg/html/template/content.go
@@ -74,6 +74,9 @@ const (
// indirect returns the value, after dereferencing as many times
// as necessary to reach the base type (or nil).
func indirect(a interface{}) interface{} {
+ if a == nil {
+ return nil
+ }
if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
// Avoid creating a reflect.Value if it's not a pointer.
return a
@@ -94,6 +97,9 @@ var (
// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
// or error,
func indirectToStringerOrError(a interface{}) interface{} {
+ if a == nil {
+ return nil
+ }
v := reflect.ValueOf(a)
for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
diff --git a/src/pkg/html/template/content_test.go b/src/pkg/html/template/content_test.go
index 3c32e5e89..5f3ffe2d3 100644
--- a/src/pkg/html/template/content_test.go
+++ b/src/pkg/html/template/content_test.go
@@ -123,29 +123,29 @@ func TestTypedContent(t *testing.T) {
{
`<script>alert({{.}})</script>`,
[]string{
- `"\u003cb\u003e \"foo%\" O'Reilly &bar;"`,
+ `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`,
`"a[href =~ \"//example.com\"]#foo"`,
- `"Hello, \u003cb\u003eWorld\u003c/b\u003e &amp;tc!"`,
+ `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`,
`" dir=\"ltr\""`,
// Not escaped.
`c && alert("Hello, World!");`,
// Escape sequence not over-escaped.
`"Hello, World & O'Reilly\x21"`,
- `"greeting=H%69&addressee=(World)"`,
+ `"greeting=H%69\u0026addressee=(World)"`,
},
},
{
`<button onclick="alert({{.}})">`,
[]string{
- `&#34;\u003cb\u003e \&#34;foo%\&#34; O&#39;Reilly &amp;bar;&#34;`,
+ `&#34;\u003cb\u003e \&#34;foo%\&#34; O&#39;Reilly \u0026bar;&#34;`,
`&#34;a[href =~ \&#34;//example.com\&#34;]#foo&#34;`,
- `&#34;Hello, \u003cb\u003eWorld\u003c/b\u003e &amp;amp;tc!&#34;`,
+ `&#34;Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!&#34;`,
`&#34; dir=\&#34;ltr\&#34;&#34;`,
// Not JS escaped but HTML escaped.
`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&amp;addressee=(World)&#34;`,
+ `&#34;greeting=H%69\u0026addressee=(World)&#34;`,
},
},
{
@@ -259,3 +259,22 @@ func TestStringer(t *testing.T) {
t.Errorf("expected %q got %q", expect, b.String())
}
}
+
+// https://code.google.com/p/go/issues/detail?id=5982
+func TestEscapingNilNonemptyInterfaces(t *testing.T) {
+ tmpl := Must(New("x").Parse("{{.E}}"))
+
+ got := new(bytes.Buffer)
+ testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand
+ tmpl.Execute(got, testData)
+
+ // Use this data instead of just hard-coding "&lt;nil&gt;" to avoid
+ // dependencies on the html escaper and the behavior of fmt w.r.t. nil.
+ want := new(bytes.Buffer)
+ data := struct{ E string }{E: fmt.Sprint(nil)}
+ tmpl.Execute(want, data)
+
+ if !bytes.Equal(want.Bytes(), got.Bytes()) {
+ t.Errorf("expected %q got %q", string(want.Bytes()), string(got.Bytes()))
+ }
+}
diff --git a/src/pkg/html/template/context.go b/src/pkg/html/template/context.go
index 7202221b8..eb47e2be3 100644
--- a/src/pkg/html/template/context.go
+++ b/src/pkg/html/template/context.go
@@ -29,7 +29,7 @@ func (c context) String() string {
return fmt.Sprintf("{%v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.attr, c.element, c.err)
}
-// eq returns whether two contexts are equal.
+// eq reports whether two contexts are equal.
func (c context) eq(d context) bool {
return c.state == d.state &&
c.delim == d.delim &&
diff --git a/src/pkg/html/template/css.go b/src/pkg/html/template/css.go
index 3bcd98498..634f183f7 100644
--- a/src/pkg/html/template/css.go
+++ b/src/pkg/html/template/css.go
@@ -11,7 +11,7 @@ import (
"unicode/utf8"
)
-// endsWithCSSKeyword returns whether b ends with an ident that
+// endsWithCSSKeyword reports whether b ends with an ident that
// case-insensitively matches the lower-case kw.
func endsWithCSSKeyword(b []byte, kw string) bool {
i := len(b) - len(kw)
@@ -34,7 +34,7 @@ func endsWithCSSKeyword(b []byte, kw string) bool {
return string(bytes.ToLower(b[i:])) == kw
}
-// isCSSNmchar returns whether rune is allowed anywhere in a CSS identifier.
+// isCSSNmchar reports whether rune is allowed anywhere in a CSS identifier.
func isCSSNmchar(r rune) bool {
// Based on the CSS3 nmchar production but ignores multi-rune escape
// sequences.
@@ -99,7 +99,7 @@ func decodeCSS(s []byte) []byte {
return b
}
-// isHex returns whether the given character is a hex digit.
+// isHex reports whether the given character is a hex digit.
func isHex(c byte) bool {
return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
}
@@ -144,7 +144,7 @@ func skipCSSSpace(c []byte) []byte {
return c
}
-// isCSSSpace returns whether b is a CSS space char as defined in wc.
+// isCSSSpace reports whether b is a CSS space char as defined in wc.
func isCSSSpace(b byte) bool {
switch b {
case '\t', '\n', '\f', '\r', ' ':
diff --git a/src/pkg/html/template/escape.go b/src/pkg/html/template/escape.go
index 4829bfcc4..9ae9749db 100644
--- a/src/pkg/html/template/escape.go
+++ b/src/pkg/html/template/escape.go
@@ -35,11 +35,13 @@ func escapeTemplates(tmpl *Template, names ...string) error {
for _, name := range names {
if t := tmpl.set[name]; t != nil {
t.text.Tree = nil
+ t.Tree = nil
}
}
return err
}
tmpl.escaped = true
+ tmpl.Tree = tmpl.text.Tree
}
e.commit()
return nil
@@ -301,7 +303,7 @@ func indexOfStr(s string, strs []string, eq func(a, b string) bool) int {
return -1
}
-// escFnsEq returns whether the two escaping functions are equivalent.
+// escFnsEq reports whether the two escaping functions are equivalent.
func escFnsEq(a, b string) bool {
if e := equivEscapers[a]; e != "" {
a = e
diff --git a/src/pkg/html/template/escape_test.go b/src/pkg/html/template/escape_test.go
index de3659ba8..58383a6cd 100644
--- a/src/pkg/html/template/escape_test.go
+++ b/src/pkg/html/template/escape_test.go
@@ -538,7 +538,7 @@ func TestEscape(t *testing.T) {
{
"typed HTML in script",
`<button onclick="alert({{.W}})">`,
- `<button onclick="alert(&#34;&amp;iexcl;\u003cb class=\&#34;foo\&#34;\u003eHello\u003c/b\u003e, \u003ctextarea\u003eO&#39;World\u003c/textarea\u003e!&#34;)">`,
+ `<button onclick="alert(&#34;\u0026iexcl;\u003cb class=\&#34;foo\&#34;\u003eHello\u003c/b\u003e, \u003ctextarea\u003eO&#39;World\u003c/textarea\u003e!&#34;)">`,
},
{
"typed HTML in RCDATA",
@@ -655,6 +655,11 @@ func TestEscape(t *testing.T) {
for _, test := range tests {
tmpl := New(test.name)
tmpl = Must(tmpl.Parse(test.input))
+ // Check for bug 6459: Tree field was not set in Parse.
+ if tmpl.Tree != tmpl.text.Tree {
+ t.Errorf("%s: tree not set properly", test.name)
+ continue
+ }
b := new(bytes.Buffer)
if err := tmpl.Execute(b, data); err != nil {
t.Errorf("%s: template execution failed: %s", test.name, err)
@@ -673,6 +678,10 @@ func TestEscape(t *testing.T) {
t.Errorf("%s: escaped output for pointer: want\n\t%q\ngot\n\t%q", test.name, w, g)
continue
}
+ if tmpl.Tree != tmpl.text.Tree {
+ t.Errorf("%s: tree mismatch", test.name)
+ continue
+ }
}
}
diff --git a/src/pkg/html/template/js.go b/src/pkg/html/template/js.go
index a9740931f..d594e0ad7 100644
--- a/src/pkg/html/template/js.go
+++ b/src/pkg/html/template/js.go
@@ -341,7 +341,7 @@ var jsRegexpReplacementTable = []string{
'}': `\}`,
}
-// isJSIdentPart returns whether the given rune is a JS identifier part.
+// isJSIdentPart reports whether the given rune is a JS identifier part.
// It does not handle all the non-Latin letters, joiners, and combining marks,
// but it does handle every codepoint that can occur in a numeric literal or
// a keyword.
diff --git a/src/pkg/html/template/template.go b/src/pkg/html/template/template.go
index e183898d5..11cc34a50 100644
--- a/src/pkg/html/template/template.go
+++ b/src/pkg/html/template/template.go
@@ -21,7 +21,9 @@ type Template struct {
// We could embed the text/template field, but it's safer not to because
// we need to keep our version of the name space and the underlying
// template's in sync.
- text *template.Template
+ text *template.Template
+ // The underlying template's parse tree, updated to be HTML-safe.
+ Tree *parse.Tree
*nameSpace // common to all associated templates
}
@@ -126,8 +128,10 @@ func (t *Template) Parse(src string) (*Template, error) {
if tmpl == nil {
tmpl = t.new(name)
}
+ // Restore our record of this text/template to its unescaped original state.
tmpl.escaped = false
tmpl.text = v
+ tmpl.Tree = v.Tree
}
return t, nil
}
@@ -149,6 +153,7 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error
ret := &Template{
false,
text,
+ text.Tree,
t.nameSpace,
}
t.set[name] = ret
@@ -176,6 +181,7 @@ func (t *Template) Clone() (*Template, error) {
ret := &Template{
false,
textClone,
+ textClone.Tree,
&nameSpace{
set: make(map[string]*Template),
},
@@ -186,15 +192,11 @@ func (t *Template) Clone() (*Template, error) {
if src == nil || src.escaped {
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
}
- if x.Tree != nil {
- x.Tree = &parse.Tree{
- Name: x.Tree.Name,
- Root: x.Tree.Root.CopyList(),
- }
- }
+ x.Tree = x.Tree.Copy()
ret.set[name] = &Template{
false,
x,
+ x.Tree,
ret.nameSpace,
}
}
@@ -206,6 +208,7 @@ func New(name string) *Template {
tmpl := &Template{
false,
template.New(name),
+ nil,
&nameSpace{
set: make(map[string]*Template),
},
@@ -228,6 +231,7 @@ func (t *Template) new(name string) *Template {
tmpl := &Template{
false,
t.text.New(name),
+ nil,
t.nameSpace,
}
tmpl.set[name] = tmpl
diff --git a/src/pkg/html/template/transition.go b/src/pkg/html/template/transition.go
index 564eb2020..7f30a7ab8 100644
--- a/src/pkg/html/template/transition.go
+++ b/src/pkg/html/template/transition.go
@@ -504,12 +504,12 @@ var elementNameMap = map[string]element{
"title": elementTitle,
}
-// asciiAlpha returns whether c is an ASCII letter.
+// asciiAlpha reports whether c is an ASCII letter.
func asciiAlpha(c byte) bool {
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'
}
-// asciiAlphaNum returns whether c is an ASCII letter or digit.
+// asciiAlphaNum reports whether c is an ASCII letter or digit.
func asciiAlphaNum(c byte) bool {
return asciiAlpha(c) || '0' <= c && c <= '9'
}