html/template: don't panic on second execution of unescapable template
Fixes #8431.
LGTM=r
R=golang-codereviews, r, minux
CC=golang-codereviews
https://golang.org/cl/130830043
diff --git a/src/pkg/html/template/template.go b/src/pkg/html/template/template.go
index d389658..538837c 100644
--- a/src/pkg/html/template/template.go
+++ b/src/pkg/html/template/template.go
@@ -17,7 +17,8 @@
// Template is a specialized Template from "text/template" that produces a safe
// HTML document fragment.
type Template struct {
- escaped bool
+ // Sticky error if escaping fails.
+ escapeErr error
// 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.
@@ -27,6 +28,9 @@
*nameSpace // common to all associated templates
}
+// escapeOK is a sentinel value used to indicate valid escaping.
+var escapeOK = fmt.Errorf("template escaped correctly")
+
// nameSpace is the data structure shared by all templates in an association.
type nameSpace struct {
mu sync.Mutex
@@ -51,11 +55,12 @@
func (t *Template) escape() error {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
- if !t.escaped {
+ if t.escapeErr == nil {
if err := escapeTemplates(t, t.Name()); err != nil {
return err
}
- t.escaped = true
+ } else if t.escapeErr != escapeOK {
+ return t.escapeErr
}
return nil
}
@@ -97,13 +102,16 @@
if tmpl == nil {
return nil, fmt.Errorf("html/template: %q is undefined", name)
}
+ if tmpl.escapeErr != nil && tmpl.escapeErr != escapeOK {
+ return nil, tmpl.escapeErr
+ }
if tmpl.text.Tree == nil || tmpl.text.Root == nil {
return nil, fmt.Errorf("html/template: %q is an incomplete template", name)
}
if t.text.Lookup(name) == nil {
panic("html/template internal error: template escaping out of sync")
}
- if tmpl != nil && !tmpl.escaped {
+ if tmpl.escapeErr == nil {
err = escapeTemplates(tmpl, name)
}
return tmpl, err
@@ -119,7 +127,7 @@
// other than space, comments, and template definitions.)
func (t *Template) Parse(src string) (*Template, error) {
t.nameSpace.mu.Lock()
- t.escaped = false
+ t.escapeErr = nil
t.nameSpace.mu.Unlock()
ret, err := t.text.Parse(src)
if err != nil {
@@ -137,7 +145,7 @@
tmpl = t.new(name)
}
// Restore our record of this text/template to its unescaped original state.
- tmpl.escaped = false
+ tmpl.escapeErr = nil
tmpl.text = v
tmpl.Tree = v.Tree
}
@@ -151,7 +159,7 @@
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
- if t.escaped {
+ if t.escapeErr != nil {
return nil, fmt.Errorf("html/template: cannot AddParseTree to %q after it has executed", t.Name())
}
text, err := t.text.AddParseTree(name, tree)
@@ -159,7 +167,7 @@
return nil, err
}
ret := &Template{
- false,
+ nil,
text,
text.Tree,
t.nameSpace,
@@ -179,7 +187,7 @@
func (t *Template) Clone() (*Template, error) {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
- if t.escaped {
+ if t.escapeErr != nil {
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
}
textClone, err := t.text.Clone()
@@ -187,7 +195,7 @@
return nil, err
}
ret := &Template{
- false,
+ nil,
textClone,
textClone.Tree,
&nameSpace{
@@ -197,12 +205,12 @@
for _, x := range textClone.Templates() {
name := x.Name()
src := t.set[name]
- if src == nil || src.escaped {
+ if src == nil || src.escapeErr != nil {
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
}
x.Tree = x.Tree.Copy()
ret.set[name] = &Template{
- false,
+ nil,
x,
x.Tree,
ret.nameSpace,
@@ -214,7 +222,7 @@
// New allocates a new HTML template with the given name.
func New(name string) *Template {
tmpl := &Template{
- false,
+ nil,
template.New(name),
nil,
&nameSpace{
@@ -237,7 +245,7 @@
// new is the implementation of New, without the lock.
func (t *Template) new(name string) *Template {
tmpl := &Template{
- false,
+ nil,
t.text.New(name),
nil,
t.nameSpace,