Commit 1f661fc2 authored by Rob Pike's avatar Rob Pike

text/template: make the escapers for HTML etc. handle pointers correctly

Apply the same rules for argument evaluation and indirection that are
used by the regular evaluator.

Fixes #5802

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/13257043
parent 519a9e8e
...@@ -755,12 +755,21 @@ func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { ...@@ -755,12 +755,21 @@ func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
// the template. // the template.
func (s *state) printValue(n parse.Node, v reflect.Value) { func (s *state) printValue(n parse.Node, v reflect.Value) {
s.at(n) s.at(n)
iface, ok := printableValue(v)
if !ok {
s.errorf("can't print %s of type %s", n, v.Type())
}
fmt.Fprint(s.wr, iface)
}
// printableValue returns the, possibly indirected, interface value inside v that
// is best for a call to formatted printer.
func printableValue(v reflect.Value) (interface{}, bool) {
if v.Kind() == reflect.Ptr { if v.Kind() == reflect.Ptr {
v, _ = indirect(v) // fmt.Fprint handles nil. v, _ = indirect(v) // fmt.Fprint handles nil.
} }
if !v.IsValid() { if !v.IsValid() {
fmt.Fprint(s.wr, "<no value>") return "<no value>", true
return
} }
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) { if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
...@@ -769,11 +778,11 @@ func (s *state) printValue(n parse.Node, v reflect.Value) { ...@@ -769,11 +778,11 @@ func (s *state) printValue(n parse.Node, v reflect.Value) {
} else { } else {
switch v.Kind() { switch v.Kind() {
case reflect.Chan, reflect.Func: case reflect.Chan, reflect.Func:
s.errorf("can't print %s of type %s", n, v.Type()) return nil, false
} }
} }
} }
fmt.Fprint(s.wr, v.Interface()) return v.Interface(), true
} }
// Types to help sort the keys in a map for reproducible output. // Types to help sort the keys in a map for reproducible output.
......
...@@ -57,6 +57,7 @@ type T struct { ...@@ -57,6 +57,7 @@ type T struct {
Err error Err error
// Pointers // Pointers
PI *int PI *int
PS *string
PSI *[]int PSI *[]int
NIL *int NIL *int
// Function (not method) // Function (not method)
...@@ -125,6 +126,7 @@ var tVal = &T{ ...@@ -125,6 +126,7 @@ var tVal = &T{
Str: bytes.NewBuffer([]byte("foozle")), Str: bytes.NewBuffer([]byte("foozle")),
Err: errors.New("erroozle"), Err: errors.New("erroozle"),
PI: newInt(23), PI: newInt(23),
PS: newString("a string"),
PSI: newIntSlice(21, 22, 23), PSI: newIntSlice(21, 22, 23),
BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) }, BinaryFunc: func(a, b string) string { return fmt.Sprintf("[%s=%s]", a, b) },
VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") }, VariadicFunc: func(s ...string) string { return fmt.Sprint("<", strings.Join(s, "+"), ">") },
...@@ -143,9 +145,11 @@ var iVal I = tVal ...@@ -143,9 +145,11 @@ var iVal I = tVal
// Helpers for creation. // Helpers for creation.
func newInt(n int) *int { func newInt(n int) *int {
p := new(int) return &n
*p = n }
return p
func newString(s string) *string {
return &s
} }
func newIntSlice(n ...int) *[]int { func newIntSlice(n ...int) *[]int {
...@@ -282,6 +286,7 @@ var execTests = []execTest{ ...@@ -282,6 +286,7 @@ var execTests = []execTest{
// Pointers. // Pointers.
{"*int", "{{.PI}}", "23", tVal, true}, {"*int", "{{.PI}}", "23", tVal, true},
{"*string", "{{.PS}}", "a string", tVal, true},
{"*[]int", "{{.PSI}}", "[21 22 23]", tVal, true}, {"*[]int", "{{.PSI}}", "[21 22 23]", tVal, true},
{"*[]int[1]", "{{index .PSI 1}}", "22", tVal, true}, {"*[]int[1]", "{{index .PSI 1}}", "22", tVal, true},
{"NIL", "{{.NIL}}", "<nil>", tVal, true}, {"NIL", "{{.NIL}}", "<nil>", tVal, true},
...@@ -391,6 +396,7 @@ var execTests = []execTest{ ...@@ -391,6 +396,7 @@ var execTests = []execTest{
"&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true}, "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`, {"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
"&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true}, "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
{"html", `{{html .PS}}`, "a string", tVal, true},
// JavaScript. // JavaScript.
{"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true}, {"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true},
......
...@@ -452,15 +452,7 @@ func HTMLEscapeString(s string) string { ...@@ -452,15 +452,7 @@ func HTMLEscapeString(s string) string {
// HTMLEscaper returns the escaped HTML equivalent of the textual // HTMLEscaper returns the escaped HTML equivalent of the textual
// representation of its arguments. // representation of its arguments.
func HTMLEscaper(args ...interface{}) string { func HTMLEscaper(args ...interface{}) string {
ok := false return HTMLEscapeString(evalArgs(args))
var s string
if len(args) == 1 {
s, ok = args[0].(string)
}
if !ok {
s = fmt.Sprint(args...)
}
return HTMLEscapeString(s)
} }
// JavaScript escaping. // JavaScript escaping.
...@@ -545,26 +537,35 @@ func jsIsSpecial(r rune) bool { ...@@ -545,26 +537,35 @@ func jsIsSpecial(r rune) bool {
// JSEscaper returns the escaped JavaScript equivalent of the textual // JSEscaper returns the escaped JavaScript equivalent of the textual
// representation of its arguments. // representation of its arguments.
func JSEscaper(args ...interface{}) string { func JSEscaper(args ...interface{}) string {
ok := false return JSEscapeString(evalArgs(args))
var s string
if len(args) == 1 {
s, ok = args[0].(string)
}
if !ok {
s = fmt.Sprint(args...)
}
return JSEscapeString(s)
} }
// URLQueryEscaper returns the escaped value of the textual representation of // URLQueryEscaper returns the escaped value of the textual representation of
// its arguments in a form suitable for embedding in a URL query. // its arguments in a form suitable for embedding in a URL query.
func URLQueryEscaper(args ...interface{}) string { func URLQueryEscaper(args ...interface{}) string {
s, ok := "", false return url.QueryEscape(evalArgs(args))
}
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
// fmt.Sprint(args...)
// except that each argument is indirected (if a pointer), as required,
// using the same rules as the default string evaluation during template
// execution.
func evalArgs(args []interface{}) string {
ok := false
var s string
// Fast path for simple common case.
if len(args) == 1 { if len(args) == 1 {
s, ok = args[0].(string) s, ok = args[0].(string)
} }
if !ok { if !ok {
for i, arg := range args {
a, ok := printableValue(reflect.ValueOf(arg))
if ok {
args[i] = a
} // else left fmt do its thing
}
s = fmt.Sprint(args...) s = fmt.Sprint(args...)
} }
return url.QueryEscape(s) return s
} }
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