Commit 9a86e244 authored by Rob Pike's avatar Rob Pike

html/template: make execution thread-safe

The problem is that execution can modify the template, so it needs
interlocking to have the same thread-safe guarantee as text/template.
Fixes #2439.

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/5450056
parent ce27b00f
...@@ -32,7 +32,7 @@ func escapeTemplates(tmpl *Template, names ...string) error { ...@@ -32,7 +32,7 @@ func escapeTemplates(tmpl *Template, names ...string) error {
if err != nil { if err != nil {
// Prevent execution of unsafe templates. // Prevent execution of unsafe templates.
for _, name := range names { for _, name := range names {
if t := tmpl.Lookup(name); t != nil { if t := tmpl.set[name]; t != nil {
t.text.Tree = nil t.text.Tree = nil
} }
} }
...@@ -520,7 +520,7 @@ func (e *escaper) computeOutCtx(c context, t *template.Template) context { ...@@ -520,7 +520,7 @@ func (e *escaper) computeOutCtx(c context, t *template.Template) context {
if !ok && c1.state != stateError { if !ok && c1.state != stateError {
return context{ return context{
state: stateError, state: stateError,
// TODO: Find the first node with a line in t.Tree.Root // TODO: Find the first node with a line in t.text.Tree.Root
err: errorf(ErrOutputContext, 0, "cannot compute output context for template %s", t.Name()), err: errorf(ErrOutputContext, 0, "cannot compute output context for template %s", t.Name()),
} }
} }
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"sync"
"text/template" "text/template"
) )
...@@ -20,21 +21,46 @@ type Template struct { ...@@ -20,21 +21,46 @@ type Template struct {
// 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.
text *template.Template text *template.Template
// Templates are grouped by sharing the set, a pointer. *nameSpace // common to all associated templates
set *map[string]*Template }
// nameSpace is the data structure shared by all templates in an association.
type nameSpace struct {
mu sync.Mutex
set map[string]*Template
}
// Execute applies a parsed template to the specified data object,
// writing the output to wr.
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
t.nameSpace.mu.Lock()
if !t.escaped {
if err = escapeTemplates(t, t.Name()); err != nil {
t.escaped = true
}
}
t.nameSpace.mu.Unlock()
if err != nil {
return
}
return t.text.Execute(wr, data)
} }
// ExecuteTemplate applies the template associated with t that has the given name // ExecuteTemplate applies the template associated with t that has the given name
// to the specified data object and writes the output to wr. // to the specified data object and writes the output to wr.
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error { func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) (err error) {
tmpl := t.Lookup(name) t.nameSpace.mu.Lock()
tmpl := t.set[name]
if tmpl == nil { if tmpl == nil {
t.nameSpace.mu.Unlock()
return fmt.Errorf("template: no template %q associated with template %q", name, t.Name()) return fmt.Errorf("template: no template %q associated with template %q", name, t.Name())
} }
if !tmpl.escaped { if !tmpl.escaped {
if err := escapeTemplates(tmpl, name); err != nil { // TODO: make a method of set? err = escapeTemplates(tmpl, name)
return err
} }
t.nameSpace.mu.Unlock()
if err != nil {
return
} }
return tmpl.text.ExecuteTemplate(wr, name, data) return tmpl.text.ExecuteTemplate(wr, name, data)
} }
...@@ -44,7 +70,9 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) ...@@ -44,7 +70,9 @@ func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{})
// to the set. If a template is redefined, the element in the set is // to the set. If a template is redefined, the element in the set is
// overwritten with the new definition. // overwritten with the new definition.
func (t *Template) Parse(src string) (*Template, error) { func (t *Template) Parse(src string) (*Template, error) {
t.nameSpace.mu.Lock()
t.escaped = false t.escaped = false
t.nameSpace.mu.Unlock()
ret, err := t.text.Parse(src) ret, err := t.text.Parse(src)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -52,11 +80,13 @@ func (t *Template) Parse(src string) (*Template, error) { ...@@ -52,11 +80,13 @@ func (t *Template) Parse(src string) (*Template, error) {
// In general, all the named templates might have changed underfoot. // In general, all the named templates might have changed underfoot.
// Regardless, some new ones may have been defined. // Regardless, some new ones may have been defined.
// The template.Template set has been updated; update ours. // The template.Template set has been updated; update ours.
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
for _, v := range ret.Templates() { for _, v := range ret.Templates() {
name := v.Name() name := v.Name()
tmpl := t.Lookup(name) tmpl := t.set[name]
if tmpl == nil { if tmpl == nil {
tmpl = t.New(name) tmpl = t.new(name)
} }
tmpl.escaped = false tmpl.escaped = false
tmpl.text = v tmpl.text = v
...@@ -64,18 +94,6 @@ func (t *Template) Parse(src string) (*Template, error) { ...@@ -64,18 +94,6 @@ func (t *Template) Parse(src string) (*Template, error) {
return t, nil return t, nil
} }
// Execute applies a parsed template to the specified data object,
// writing the output to wr.
func (t *Template) Execute(wr io.Writer, data interface{}) error {
if !t.escaped {
if err := escapeTemplates(t, t.Name()); err != nil {
return err
}
t.escaped = true
}
return t.text.Execute(wr, data)
}
// Add is unimplemented. // Add is unimplemented.
func (t *Template) Add(*Template) error { func (t *Template) Add(*Template) error {
return fmt.Errorf("html/template: Add unimplemented") return fmt.Errorf("html/template: Add unimplemented")
...@@ -88,13 +106,14 @@ func (t *Template) Clone(name string) error { ...@@ -88,13 +106,14 @@ func (t *Template) Clone(name string) 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 {
set := make(map[string]*Template)
tmpl := &Template{ tmpl := &Template{
false, false,
template.New(name), template.New(name),
&set, &nameSpace{
set: make(map[string]*Template),
},
} }
(*tmpl.set)[name] = tmpl tmpl.set[name] = tmpl
return tmpl return tmpl
} }
...@@ -102,12 +121,19 @@ func New(name string) *Template { ...@@ -102,12 +121,19 @@ func New(name string) *Template {
// and with the same delimiters. The association, which is transitive, // and with the same delimiters. The association, which is transitive,
// allows one template to invoke another with a {{template}} action. // allows one template to invoke another with a {{template}} action.
func (t *Template) New(name string) *Template { func (t *Template) New(name string) *Template {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
return t.new(name)
}
// new is the implementation of New, without the lock.
func (t *Template) new(name string) *Template {
tmpl := &Template{ tmpl := &Template{
false, false,
t.text.New(name), t.text.New(name),
t.set, t.nameSpace,
} }
(*tmpl.set)[name] = tmpl tmpl.set[name] = tmpl
return tmpl return tmpl
} }
...@@ -138,7 +164,9 @@ func (t *Template) Delims(left, right string) *Template { ...@@ -138,7 +164,9 @@ func (t *Template) Delims(left, right string) *Template {
// Lookup returns the template with the given name that is associated with t, // Lookup returns the template with the given name that is associated with t,
// or nil if there is no such template. // or nil if there is no such template.
func (t *Template) Lookup(name string) *Template { func (t *Template) Lookup(name string) *Template {
return (*t.set)[name] t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
return t.set[name]
} }
// Must panics if err is non-nil in the same way as template.Must. // Must panics if err is non-nil in the same way as template.Must.
......
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