From f5946ea3ddf4ae4256b0ef6a8ccf73fb9d1253cf Mon Sep 17 00:00:00 2001 From: Tatsushi Demachi Date: Tue, 20 Jan 2015 08:55:16 +0900 Subject: [PATCH] Add SafeHtmlAttr, SafeCSS template function This allows a template user to keep a safe HTML attribute or CSS string as is in a template. This is implementation of @anthonyfok great insight Fix #784, #347 --- tpl/template.go | 80 ++++++++++++++++++--------------- tpl/template_test.go | 105 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 35 deletions(-) diff --git a/tpl/template.go b/tpl/template.go index 8d55fc897..819343a97 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -910,6 +910,14 @@ func SafeHtml(text string) template.HTML { return template.HTML(text) } +func SafeHtmlAttr(text string) template.HTMLAttr { + return template.HTMLAttr(text) +} + +func SafeCSS(text string) template.CSS { + return template.CSS(text) +} + func doArithmetic(a, b interface{}, op rune) (interface{}, error) { av := reflect.ValueOf(a) bv := reflect.ValueOf(b) @@ -1230,41 +1238,43 @@ func (t *GoHtmlTemplate) LoadTemplates(absPath string) { func init() { funcMap = template.FuncMap{ - "urlize": helpers.Urlize, - "sanitizeurl": helpers.SanitizeUrl, - "eq": Eq, - "ne": Ne, - "gt": Gt, - "ge": Ge, - "lt": Lt, - "le": Le, - "in": In, - "intersect": Intersect, - "isset": IsSet, - "echoParam": ReturnWhenSet, - "safeHtml": SafeHtml, - "markdownify": Markdownify, - "first": First, - "where": Where, - "delimit": Delimit, - "sort": Sort, - "highlight": Highlight, - "add": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '+') }, - "sub": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '-') }, - "div": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '/') }, - "mod": Mod, - "mul": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '*') }, - "modBool": ModBool, - "lower": func(a string) string { return strings.ToLower(a) }, - "upper": func(a string) string { return strings.ToUpper(a) }, - "title": func(a string) string { return strings.Title(a) }, - "partial": Partial, - "ref": Ref, - "relref": RelRef, - "apply": Apply, - "chomp": Chomp, - "replace": Replace, - "trim": Trim, + "urlize": helpers.Urlize, + "sanitizeurl": helpers.SanitizeUrl, + "eq": Eq, + "ne": Ne, + "gt": Gt, + "ge": Ge, + "lt": Lt, + "le": Le, + "in": In, + "intersect": Intersect, + "isset": IsSet, + "echoParam": ReturnWhenSet, + "safeHtml": SafeHtml, + "safeHtmlAttr": SafeHtmlAttr, + "safeCSS": SafeCSS, + "markdownify": Markdownify, + "first": First, + "where": Where, + "delimit": Delimit, + "sort": Sort, + "highlight": Highlight, + "add": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '+') }, + "sub": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '-') }, + "div": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '/') }, + "mod": Mod, + "mul": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '*') }, + "modBool": ModBool, + "lower": func(a string) string { return strings.ToLower(a) }, + "upper": func(a string) string { return strings.ToUpper(a) }, + "title": func(a string) string { return strings.Title(a) }, + "partial": Partial, + "ref": Ref, + "relref": RelRef, + "apply": Apply, + "chomp": Chomp, + "replace": Replace, + "trim": Trim, } chompRegexp = regexp.MustCompile("[\r\n]+$") diff --git a/tpl/template_test.go b/tpl/template_test.go index 98cf2d061..f857e6341 100644 --- a/tpl/template_test.go +++ b/tpl/template_test.go @@ -1,6 +1,7 @@ package tpl import ( + "bytes" "errors" "fmt" "html/template" @@ -826,3 +827,107 @@ func TestMarkdownify(t *testing.T) { t.Errorf("Markdownify: got '%s', expected '%s'", result, expect) } } + +func TestSafeHtml(t *testing.T) { + for i, this := range []struct { + str string + tmplStr string + expectWithoutEscape string + expectWithEscape string + }{ + {`
`, `{{ . }}`, `<div></div>`, `
`}, + } { + tmpl, err := template.New("test").Parse(this.tmplStr) + if err != nil { + t.Errorf("[%d] unable to create new html template %q: %s", this.tmplStr, err) + continue + } + + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, this.str) + if err != nil { + t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithoutEscape { + t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) + } + + buf.Reset() + err = tmpl.Execute(buf, SafeHtml(this.str)) + if err != nil { + t.Errorf("[%d] execute template with an escaped string value by SafeHtml returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithEscape { + t.Errorf("[%d] execute template with an escaped string value by SafeHtml, got %v but expected %v", i, buf.String(), this.expectWithEscape) + } + } +} + +func TestSafeHtmlAttr(t *testing.T) { + for i, this := range []struct { + str string + tmplStr string + expectWithoutEscape string + expectWithEscape string + }{ + {`href="irc://irc.freenode.net/#golang"`, `irc`, `irc`, `irc`}, + } { + tmpl, err := template.New("test").Parse(this.tmplStr) + if err != nil { + t.Errorf("[%d] unable to create new html template %q: %s", this.tmplStr, err) + continue + } + + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, this.str) + if err != nil { + t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithoutEscape { + t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) + } + + buf.Reset() + err = tmpl.Execute(buf, SafeHtmlAttr(this.str)) + if err != nil { + t.Errorf("[%d] execute template with an escaped string value by SafeHtmlAttr returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithEscape { + t.Errorf("[%d] execute template with an escaped string value by SafeHtmlAttr, got %v but expected %v", i, buf.String(), this.expectWithEscape) + } + } +} + +func TestSafeCSS(t *testing.T) { + for i, this := range []struct { + str string + tmplStr string + expectWithoutEscape string + expectWithEscape string + }{ + {`width: 60px;`, `
`, `
`, `
`}, + } { + tmpl, err := template.New("test").Parse(this.tmplStr) + if err != nil { + t.Errorf("[%d] unable to create new html template %q: %s", this.tmplStr, err) + } + + buf := new(bytes.Buffer) + err = tmpl.Execute(buf, this.str) + if err != nil { + t.Errorf("[%d] execute template with a raw string value returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithoutEscape { + t.Errorf("[%d] execute template with a raw string value, got %v but expected %v", i, buf.String(), this.expectWithoutEscape) + } + + buf.Reset() + err = tmpl.Execute(buf, SafeCSS(this.str)) + if err != nil { + t.Errorf("[%d] execute template with an escaped string value by SafeCSS returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithEscape { + t.Errorf("[%d] execute template with an escaped string value by SafeCSS, got %v but expected %v", i, buf.String(), this.expectWithEscape) + } + } +}