mirror of https://github.com/golang/go.git
html/template: don't indirect past a Stringer
While we're here, get rid of the old names for the escaping functions. Fixes #3073. R=golang-dev, dsymonds, rsc CC=golang-dev https://golang.org/cl/5685049
This commit is contained in:
parent
a53317668a
commit
0ce6c87004
|
|
@ -85,6 +85,22 @@ func indirect(a interface{}) interface{} {
|
||||||
return v.Interface()
|
return v.Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
|
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// indirectToStringerOrError returns the value, after dereferencing as many times
|
||||||
|
// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer
|
||||||
|
// or error,
|
||||||
|
func indirectToStringerOrError(a interface{}) interface{} {
|
||||||
|
v := reflect.ValueOf(a)
|
||||||
|
for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && 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.
|
// All pointers are dereferenced, as in the text/template package.
|
||||||
func stringify(args ...interface{}) (string, contentType) {
|
func stringify(args ...interface{}) (string, contentType) {
|
||||||
|
|
@ -107,7 +123,7 @@ func stringify(args ...interface{}) (string, contentType) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
args[i] = indirect(arg)
|
args[i] = indirectToStringerOrError(arg)
|
||||||
}
|
}
|
||||||
return fmt.Sprint(args...), contentTypePlain
|
return fmt.Sprint(args...), contentTypePlain
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ package template
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
@ -219,3 +220,42 @@ func TestTypedContent(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that we print using the String method. Was issue 3073.
|
||||||
|
type stringer struct {
|
||||||
|
v int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringer) String() string {
|
||||||
|
return fmt.Sprintf("string=%d", s.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorer struct {
|
||||||
|
v int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *errorer) Error() string {
|
||||||
|
return fmt.Sprintf("error=%d", s.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringer(t *testing.T) {
|
||||||
|
s := &stringer{3}
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
tmpl := Must(New("x").Parse("{{.}}"))
|
||||||
|
if err := tmpl.Execute(b, s); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var expect = "string=3"
|
||||||
|
if b.String() != expect {
|
||||||
|
t.Errorf("expected %q got %q", expect, b.String())
|
||||||
|
}
|
||||||
|
e := &errorer{7}
|
||||||
|
b.Reset()
|
||||||
|
if err := tmpl.Execute(b, e); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expect = "error=7"
|
||||||
|
if b.String() != expect {
|
||||||
|
t.Errorf("expected %q got %q", expect, b.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,30 +46,30 @@ func escapeTemplates(tmpl *Template, names ...string) error {
|
||||||
|
|
||||||
// funcMap maps command names to functions that render their inputs safe.
|
// funcMap maps command names to functions that render their inputs safe.
|
||||||
var funcMap = template.FuncMap{
|
var funcMap = template.FuncMap{
|
||||||
"exp_template_html_attrescaper": attrEscaper,
|
"html_template_attrescaper": attrEscaper,
|
||||||
"exp_template_html_commentescaper": commentEscaper,
|
"html_template_commentescaper": commentEscaper,
|
||||||
"exp_template_html_cssescaper": cssEscaper,
|
"html_template_cssescaper": cssEscaper,
|
||||||
"exp_template_html_cssvaluefilter": cssValueFilter,
|
"html_template_cssvaluefilter": cssValueFilter,
|
||||||
"exp_template_html_htmlnamefilter": htmlNameFilter,
|
"html_template_htmlnamefilter": htmlNameFilter,
|
||||||
"exp_template_html_htmlescaper": htmlEscaper,
|
"html_template_htmlescaper": htmlEscaper,
|
||||||
"exp_template_html_jsregexpescaper": jsRegexpEscaper,
|
"html_template_jsregexpescaper": jsRegexpEscaper,
|
||||||
"exp_template_html_jsstrescaper": jsStrEscaper,
|
"html_template_jsstrescaper": jsStrEscaper,
|
||||||
"exp_template_html_jsvalescaper": jsValEscaper,
|
"html_template_jsvalescaper": jsValEscaper,
|
||||||
"exp_template_html_nospaceescaper": htmlNospaceEscaper,
|
"html_template_nospaceescaper": htmlNospaceEscaper,
|
||||||
"exp_template_html_rcdataescaper": rcdataEscaper,
|
"html_template_rcdataescaper": rcdataEscaper,
|
||||||
"exp_template_html_urlescaper": urlEscaper,
|
"html_template_urlescaper": urlEscaper,
|
||||||
"exp_template_html_urlfilter": urlFilter,
|
"html_template_urlfilter": urlFilter,
|
||||||
"exp_template_html_urlnormalizer": urlNormalizer,
|
"html_template_urlnormalizer": urlNormalizer,
|
||||||
}
|
}
|
||||||
|
|
||||||
// equivEscapers matches contextual escapers to equivalent template builtins.
|
// equivEscapers matches contextual escapers to equivalent template builtins.
|
||||||
var equivEscapers = map[string]string{
|
var equivEscapers = map[string]string{
|
||||||
"exp_template_html_attrescaper": "html",
|
"html_template_attrescaper": "html",
|
||||||
"exp_template_html_htmlescaper": "html",
|
"html_template_htmlescaper": "html",
|
||||||
"exp_template_html_nospaceescaper": "html",
|
"html_template_nospaceescaper": "html",
|
||||||
"exp_template_html_rcdataescaper": "html",
|
"html_template_rcdataescaper": "html",
|
||||||
"exp_template_html_urlescaper": "urlquery",
|
"html_template_urlescaper": "urlquery",
|
||||||
"exp_template_html_urlnormalizer": "urlquery",
|
"html_template_urlnormalizer": "urlquery",
|
||||||
}
|
}
|
||||||
|
|
||||||
// escaper collects type inferences about templates and changes needed to make
|
// escaper collects type inferences about templates and changes needed to make
|
||||||
|
|
@ -147,17 +147,17 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
|
||||||
case stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:
|
case stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:
|
||||||
switch c.urlPart {
|
switch c.urlPart {
|
||||||
case urlPartNone:
|
case urlPartNone:
|
||||||
s = append(s, "exp_template_html_urlfilter")
|
s = append(s, "html_template_urlfilter")
|
||||||
fallthrough
|
fallthrough
|
||||||
case urlPartPreQuery:
|
case urlPartPreQuery:
|
||||||
switch c.state {
|
switch c.state {
|
||||||
case stateCSSDqStr, stateCSSSqStr:
|
case stateCSSDqStr, stateCSSSqStr:
|
||||||
s = append(s, "exp_template_html_cssescaper")
|
s = append(s, "html_template_cssescaper")
|
||||||
default:
|
default:
|
||||||
s = append(s, "exp_template_html_urlnormalizer")
|
s = append(s, "html_template_urlnormalizer")
|
||||||
}
|
}
|
||||||
case urlPartQueryOrFrag:
|
case urlPartQueryOrFrag:
|
||||||
s = append(s, "exp_template_html_urlescaper")
|
s = append(s, "html_template_urlescaper")
|
||||||
case urlPartUnknown:
|
case urlPartUnknown:
|
||||||
return context{
|
return context{
|
||||||
state: stateError,
|
state: stateError,
|
||||||
|
|
@ -167,27 +167,27 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
|
||||||
panic(c.urlPart.String())
|
panic(c.urlPart.String())
|
||||||
}
|
}
|
||||||
case stateJS:
|
case stateJS:
|
||||||
s = append(s, "exp_template_html_jsvalescaper")
|
s = append(s, "html_template_jsvalescaper")
|
||||||
// A slash after a value starts a div operator.
|
// A slash after a value starts a div operator.
|
||||||
c.jsCtx = jsCtxDivOp
|
c.jsCtx = jsCtxDivOp
|
||||||
case stateJSDqStr, stateJSSqStr:
|
case stateJSDqStr, stateJSSqStr:
|
||||||
s = append(s, "exp_template_html_jsstrescaper")
|
s = append(s, "html_template_jsstrescaper")
|
||||||
case stateJSRegexp:
|
case stateJSRegexp:
|
||||||
s = append(s, "exp_template_html_jsregexpescaper")
|
s = append(s, "html_template_jsregexpescaper")
|
||||||
case stateCSS:
|
case stateCSS:
|
||||||
s = append(s, "exp_template_html_cssvaluefilter")
|
s = append(s, "html_template_cssvaluefilter")
|
||||||
case stateText:
|
case stateText:
|
||||||
s = append(s, "exp_template_html_htmlescaper")
|
s = append(s, "html_template_htmlescaper")
|
||||||
case stateRCDATA:
|
case stateRCDATA:
|
||||||
s = append(s, "exp_template_html_rcdataescaper")
|
s = append(s, "html_template_rcdataescaper")
|
||||||
case stateAttr:
|
case stateAttr:
|
||||||
// Handled below in delim check.
|
// Handled below in delim check.
|
||||||
case stateAttrName, stateTag:
|
case stateAttrName, stateTag:
|
||||||
c.state = stateAttrName
|
c.state = stateAttrName
|
||||||
s = append(s, "exp_template_html_htmlnamefilter")
|
s = append(s, "html_template_htmlnamefilter")
|
||||||
default:
|
default:
|
||||||
if isComment(c.state) {
|
if isComment(c.state) {
|
||||||
s = append(s, "exp_template_html_commentescaper")
|
s = append(s, "html_template_commentescaper")
|
||||||
} else {
|
} else {
|
||||||
panic("unexpected state " + c.state.String())
|
panic("unexpected state " + c.state.String())
|
||||||
}
|
}
|
||||||
|
|
@ -196,9 +196,9 @@ func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
|
||||||
case delimNone:
|
case delimNone:
|
||||||
// No extra-escaping needed for raw text content.
|
// No extra-escaping needed for raw text content.
|
||||||
case delimSpaceOrTagEnd:
|
case delimSpaceOrTagEnd:
|
||||||
s = append(s, "exp_template_html_nospaceescaper")
|
s = append(s, "html_template_nospaceescaper")
|
||||||
default:
|
default:
|
||||||
s = append(s, "exp_template_html_attrescaper")
|
s = append(s, "html_template_attrescaper")
|
||||||
}
|
}
|
||||||
e.editActionNode(n, s)
|
e.editActionNode(n, s)
|
||||||
return c
|
return c
|
||||||
|
|
@ -260,22 +260,22 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) {
|
||||||
// redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
|
// redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
|
||||||
// for all x.
|
// for all x.
|
||||||
var redundantFuncs = map[string]map[string]bool{
|
var redundantFuncs = map[string]map[string]bool{
|
||||||
"exp_template_html_commentescaper": {
|
"html_template_commentescaper": {
|
||||||
"exp_template_html_attrescaper": true,
|
"html_template_attrescaper": true,
|
||||||
"exp_template_html_nospaceescaper": true,
|
"html_template_nospaceescaper": true,
|
||||||
"exp_template_html_htmlescaper": true,
|
"html_template_htmlescaper": true,
|
||||||
},
|
},
|
||||||
"exp_template_html_cssescaper": {
|
"html_template_cssescaper": {
|
||||||
"exp_template_html_attrescaper": true,
|
"html_template_attrescaper": true,
|
||||||
},
|
},
|
||||||
"exp_template_html_jsregexpescaper": {
|
"html_template_jsregexpescaper": {
|
||||||
"exp_template_html_attrescaper": true,
|
"html_template_attrescaper": true,
|
||||||
},
|
},
|
||||||
"exp_template_html_jsstrescaper": {
|
"html_template_jsstrescaper": {
|
||||||
"exp_template_html_attrescaper": true,
|
"html_template_attrescaper": true,
|
||||||
},
|
},
|
||||||
"exp_template_html_urlescaper": {
|
"html_template_urlescaper": {
|
||||||
"exp_template_html_urlnormalizer": true,
|
"html_template_urlnormalizer": true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue