Commit f5db4d05 authored by Rob Pike's avatar Rob Pike

html/template: indirect top-level values before printing

text/template does this (in an entirely different way), so
make html/template do the same. Before this fix, the template
{{.}} given a pointer to a string prints its address instead of its
value.

R=mikesamuel, r
CC=golang-dev
https://golang.org/cl/5370098
parent 00f9b768
...@@ -6,6 +6,7 @@ package template ...@@ -6,6 +6,7 @@ package template
import ( import (
"fmt" "fmt"
"reflect"
) )
// Strings of content from a trusted source. // Strings of content from a trusted source.
...@@ -70,10 +71,25 @@ const ( ...@@ -70,10 +71,25 @@ const (
contentTypeUnsafe contentTypeUnsafe
) )
// indirect returns the value, after dereferencing as many times
// as necessary to reach the base type (or nil).
func indirect(a interface{}) interface{} {
if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
// Avoid creating a reflect.Value if it's not a pointer.
return a
}
v := reflect.ValueOf(a)
for v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
return v.Interface()
}
// stringify converts its arguments to a string and the type of the content. // stringify converts its arguments to a string and the type of the content.
// All pointers are dereferenced, as in the text/template package.
func stringify(args ...interface{}) (string, contentType) { func stringify(args ...interface{}) (string, contentType) {
if len(args) == 1 { if len(args) == 1 {
switch s := args[0].(type) { switch s := indirect(args[0]).(type) {
case string: case string:
return s, contentTypePlain return s, contentTypePlain
case CSS: case CSS:
...@@ -90,5 +106,8 @@ func stringify(args ...interface{}) (string, contentType) { ...@@ -90,5 +106,8 @@ func stringify(args ...interface{}) (string, contentType) {
return string(s), contentTypeURL return string(s), contentTypeURL
} }
} }
for i, arg := range args {
args[i] = indirect(arg)
}
return fmt.Sprint(args...), contentTypePlain return fmt.Sprint(args...), contentTypePlain
} }
...@@ -28,7 +28,7 @@ func (x *goodMarshaler) MarshalJSON() ([]byte, error) { ...@@ -28,7 +28,7 @@ func (x *goodMarshaler) MarshalJSON() ([]byte, error) {
} }
func TestEscape(t *testing.T) { func TestEscape(t *testing.T) {
var data = struct { data := struct {
F, T bool F, T bool
C, G, H string C, G, H string
A, E []string A, E []string
...@@ -50,6 +50,7 @@ func TestEscape(t *testing.T) { ...@@ -50,6 +50,7 @@ func TestEscape(t *testing.T) {
Z: nil, Z: nil,
W: HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`), W: HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
} }
pdata := &data
tests := []struct { tests := []struct {
name string name string
...@@ -668,6 +669,15 @@ func TestEscape(t *testing.T) { ...@@ -668,6 +669,15 @@ func TestEscape(t *testing.T) {
t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.name, w, g) t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.name, w, g)
continue continue
} }
b.Reset()
if err := tmpl.Execute(b, pdata); err != nil {
t.Errorf("%s: template execution failed for pointer: %s", test.name, err)
continue
}
if w, g := test.output, b.String(); w != g {
t.Errorf("%s: escaped output for pointer: want\n\t%q\ngot\n\t%q", test.name, w, g)
continue
}
} }
} }
...@@ -1605,6 +1615,29 @@ func TestRedundantFuncs(t *testing.T) { ...@@ -1605,6 +1615,29 @@ func TestRedundantFuncs(t *testing.T) {
} }
} }
func TestIndirectPrint(t *testing.T) {
a := 3
ap := &a
b := "hello"
bp := &b
bpp := &bp
tmpl := Must(New("t").Parse(`{{.}}`))
var buf bytes.Buffer
err := tmpl.Execute(&buf, ap)
if err != nil {
t.Errorf("Unexpected error: %s", err)
} else if buf.String() != "3" {
t.Errorf(`Expected "3"; got %q`, buf.String())
}
buf.Reset()
err = tmpl.Execute(&buf, bpp)
if err != nil {
t.Errorf("Unexpected error: %s", err)
} else if buf.String() != "hello" {
t.Errorf(`Expected "hello"; got %q`, buf.String())
}
}
func BenchmarkEscapedExecute(b *testing.B) { func BenchmarkEscapedExecute(b *testing.B) {
tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`)) tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`))
var buf bytes.Buffer var buf bytes.Buffer
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
) )
...@@ -117,12 +118,24 @@ var regexpPrecederKeywords = map[string]bool{ ...@@ -117,12 +118,24 @@ var regexpPrecederKeywords = map[string]bool{
"void": true, "void": true,
} }
var jsonMarshalType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
// indirectToJSONMarshaler returns the value, after dereferencing as many times
// as necessary to reach the base type (or nil) or an implementation of json.Marshal.
func indirectToJSONMarshaler(a interface{}) interface{} {
v := reflect.ValueOf(a)
for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
return v.Interface()
}
// jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has // jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has
// nether side-effects nor free variables outside (NaN, Infinity). // neither side-effects nor free variables outside (NaN, Infinity).
func jsValEscaper(args ...interface{}) string { func jsValEscaper(args ...interface{}) string {
var a interface{} var a interface{}
if len(args) == 1 { if len(args) == 1 {
a = args[0] a = indirectToJSONMarshaler(args[0])
switch t := a.(type) { switch t := a.(type) {
case JS: case JS:
return string(t) return string(t)
...@@ -135,6 +148,9 @@ func jsValEscaper(args ...interface{}) string { ...@@ -135,6 +148,9 @@ func jsValEscaper(args ...interface{}) string {
a = t.String() a = t.String()
} }
} else { } else {
for i, arg := range args {
args[i] = indirectToJSONMarshaler(arg)
}
a = fmt.Sprint(args...) a = fmt.Sprint(args...)
} }
// TODO: detect cycles before calling Marshal which loops infinitely on // TODO: detect cycles before calling Marshal which loops infinitely on
......
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