mirror of
https://github.com/golang/go
synced 2024-11-20 01:04:40 -07:00
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
This commit is contained in:
parent
00f9b7680a
commit
f5db4d05f2
@ -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 (
|
|||||||
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) {
|
|||||||
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) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|||||||
Z: nil,
|
Z: nil,
|
||||||
W: HTML(`¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
|
W: HTML(`¡<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) {
|
|||||||
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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
@ -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 {
|
|||||||
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
|
||||||
|
Loading…
Reference in New Issue
Block a user