Commit 0fee6335 authored by Andrew Gerrand's avatar Andrew Gerrand

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
parent bdb2f976
...@@ -34,6 +34,7 @@ func escapeTemplates(tmpl *Template, names ...string) error { ...@@ -34,6 +34,7 @@ func escapeTemplates(tmpl *Template, names ...string) error {
// Prevent execution of unsafe templates. // Prevent execution of unsafe templates.
for _, name := range names { for _, name := range names {
if t := tmpl.set[name]; t != nil { if t := tmpl.set[name]; t != nil {
t.escapeErr = err
t.text.Tree = nil t.text.Tree = nil
t.Tree = nil t.Tree = nil
} }
...@@ -44,7 +45,7 @@ func escapeTemplates(tmpl *Template, names ...string) error { ...@@ -44,7 +45,7 @@ func escapeTemplates(tmpl *Template, names ...string) error {
e.commit() e.commit()
for _, name := range names { for _, name := range names {
if t := tmpl.set[name]; t != nil { if t := tmpl.set[name]; t != nil {
t.escaped = true t.escapeErr = escapeOK
t.Tree = t.text.Tree t.Tree = t.text.Tree
} }
} }
......
...@@ -994,6 +994,11 @@ func TestErrors(t *testing.T) { ...@@ -994,6 +994,11 @@ func TestErrors(t *testing.T) {
t.Errorf("input=%q: error\n\t%q\ndoes not contain expected string\n\t%q", test.input, got, test.err) t.Errorf("input=%q: error\n\t%q\ndoes not contain expected string\n\t%q", test.input, got, test.err)
continue continue
} }
// Check that we get the same error if we call Execute again.
if err := tmpl.Execute(buf, nil); err == nil || err.Error() != got {
t.Errorf("input=%q: unexpected error on second call %q", test.input, err)
}
} }
} }
......
...@@ -17,7 +17,8 @@ import ( ...@@ -17,7 +17,8 @@ import (
// Template is a specialized Template from "text/template" that produces a safe // Template is a specialized Template from "text/template" that produces a safe
// HTML document fragment. // HTML document fragment.
type Template struct { 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 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 // we need to keep our version of the name space and the underlying
// template's in sync. // template's in sync.
...@@ -27,6 +28,9 @@ type Template struct { ...@@ -27,6 +28,9 @@ type Template struct {
*nameSpace // common to all associated templates *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. // nameSpace is the data structure shared by all templates in an association.
type nameSpace struct { type nameSpace struct {
mu sync.Mutex mu sync.Mutex
...@@ -51,11 +55,12 @@ func (t *Template) Templates() []*Template { ...@@ -51,11 +55,12 @@ func (t *Template) Templates() []*Template {
func (t *Template) escape() error { func (t *Template) escape() error {
t.nameSpace.mu.Lock() t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock() defer t.nameSpace.mu.Unlock()
if !t.escaped { if t.escapeErr == nil {
if err := escapeTemplates(t, t.Name()); err != nil { if err := escapeTemplates(t, t.Name()); err != nil {
return err return err
} }
t.escaped = true } else if t.escapeErr != escapeOK {
return t.escapeErr
} }
return nil return nil
} }
...@@ -97,13 +102,16 @@ func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err err ...@@ -97,13 +102,16 @@ func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err err
if tmpl == nil { if tmpl == nil {
return nil, fmt.Errorf("html/template: %q is undefined", name) 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 { if tmpl.text.Tree == nil || tmpl.text.Root == nil {
return nil, fmt.Errorf("html/template: %q is an incomplete template", name) return nil, fmt.Errorf("html/template: %q is an incomplete template", name)
} }
if t.text.Lookup(name) == nil { if t.text.Lookup(name) == nil {
panic("html/template internal error: template escaping out of sync") panic("html/template internal error: template escaping out of sync")
} }
if tmpl != nil && !tmpl.escaped { if tmpl.escapeErr == nil {
err = escapeTemplates(tmpl, name) err = escapeTemplates(tmpl, name)
} }
return tmpl, err return tmpl, err
...@@ -119,7 +127,7 @@ func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err err ...@@ -119,7 +127,7 @@ func (t *Template) lookupAndEscapeTemplate(name string) (tmpl *Template, err err
// other than space, comments, and template definitions.) // other than space, comments, and template definitions.)
func (t *Template) Parse(src string) (*Template, error) { func (t *Template) Parse(src string) (*Template, error) {
t.nameSpace.mu.Lock() t.nameSpace.mu.Lock()
t.escaped = false t.escapeErr = nil
t.nameSpace.mu.Unlock() t.nameSpace.mu.Unlock()
ret, err := t.text.Parse(src) ret, err := t.text.Parse(src)
if err != nil { if err != nil {
...@@ -137,7 +145,7 @@ func (t *Template) Parse(src string) (*Template, error) { ...@@ -137,7 +145,7 @@ func (t *Template) Parse(src string) (*Template, error) {
tmpl = t.new(name) tmpl = t.new(name)
} }
// Restore our record of this text/template to its unescaped original state. // Restore our record of this text/template to its unescaped original state.
tmpl.escaped = false tmpl.escapeErr = nil
tmpl.text = v tmpl.text = v
tmpl.Tree = v.Tree tmpl.Tree = v.Tree
} }
...@@ -151,7 +159,7 @@ func (t *Template) Parse(src string) (*Template, error) { ...@@ -151,7 +159,7 @@ func (t *Template) Parse(src string) (*Template, error) {
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) { func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
t.nameSpace.mu.Lock() t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock() 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()) return nil, fmt.Errorf("html/template: cannot AddParseTree to %q after it has executed", t.Name())
} }
text, err := t.text.AddParseTree(name, tree) text, err := t.text.AddParseTree(name, tree)
...@@ -159,7 +167,7 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error ...@@ -159,7 +167,7 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error
return nil, err return nil, err
} }
ret := &Template{ ret := &Template{
false, nil,
text, text,
text.Tree, text.Tree,
t.nameSpace, t.nameSpace,
...@@ -179,7 +187,7 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error ...@@ -179,7 +187,7 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error
func (t *Template) Clone() (*Template, error) { func (t *Template) Clone() (*Template, error) {
t.nameSpace.mu.Lock() t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock() 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()) return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
} }
textClone, err := t.text.Clone() textClone, err := t.text.Clone()
...@@ -187,7 +195,7 @@ func (t *Template) Clone() (*Template, error) { ...@@ -187,7 +195,7 @@ func (t *Template) Clone() (*Template, error) {
return nil, err return nil, err
} }
ret := &Template{ ret := &Template{
false, nil,
textClone, textClone,
textClone.Tree, textClone.Tree,
&nameSpace{ &nameSpace{
...@@ -197,12 +205,12 @@ func (t *Template) Clone() (*Template, error) { ...@@ -197,12 +205,12 @@ func (t *Template) Clone() (*Template, error) {
for _, x := range textClone.Templates() { for _, x := range textClone.Templates() {
name := x.Name() name := x.Name()
src := t.set[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()) return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())
} }
x.Tree = x.Tree.Copy() x.Tree = x.Tree.Copy()
ret.set[name] = &Template{ ret.set[name] = &Template{
false, nil,
x, x,
x.Tree, x.Tree,
ret.nameSpace, ret.nameSpace,
...@@ -214,7 +222,7 @@ func (t *Template) Clone() (*Template, error) { ...@@ -214,7 +222,7 @@ func (t *Template) Clone() (*Template, error) {
// New allocates a new HTML template with the given name. // New allocates a new HTML template with the given name.
func New(name string) *Template { func New(name string) *Template {
tmpl := &Template{ tmpl := &Template{
false, nil,
template.New(name), template.New(name),
nil, nil,
&nameSpace{ &nameSpace{
...@@ -237,7 +245,7 @@ func (t *Template) New(name string) *Template { ...@@ -237,7 +245,7 @@ func (t *Template) New(name string) *Template {
// new is the implementation of New, without the lock. // new is the implementation of New, without the lock.
func (t *Template) new(name string) *Template { func (t *Template) new(name string) *Template {
tmpl := &Template{ tmpl := &Template{
false, nil,
t.text.New(name), t.text.New(name),
nil, nil,
t.nameSpace, t.nameSpace,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment