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
This commit is contained in:
Tatsushi Demachi 2015-01-20 08:55:16 +09:00 committed by Anthony Fok
parent 53b4ab4cf3
commit f5946ea3dd
2 changed files with 150 additions and 35 deletions

View file

@ -910,6 +910,14 @@ func SafeHtml(text string) template.HTML {
return template.HTML(text) 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) { func doArithmetic(a, b interface{}, op rune) (interface{}, error) {
av := reflect.ValueOf(a) av := reflect.ValueOf(a)
bv := reflect.ValueOf(b) bv := reflect.ValueOf(b)
@ -1230,41 +1238,43 @@ func (t *GoHtmlTemplate) LoadTemplates(absPath string) {
func init() { func init() {
funcMap = template.FuncMap{ funcMap = template.FuncMap{
"urlize": helpers.Urlize, "urlize": helpers.Urlize,
"sanitizeurl": helpers.SanitizeUrl, "sanitizeurl": helpers.SanitizeUrl,
"eq": Eq, "eq": Eq,
"ne": Ne, "ne": Ne,
"gt": Gt, "gt": Gt,
"ge": Ge, "ge": Ge,
"lt": Lt, "lt": Lt,
"le": Le, "le": Le,
"in": In, "in": In,
"intersect": Intersect, "intersect": Intersect,
"isset": IsSet, "isset": IsSet,
"echoParam": ReturnWhenSet, "echoParam": ReturnWhenSet,
"safeHtml": SafeHtml, "safeHtml": SafeHtml,
"markdownify": Markdownify, "safeHtmlAttr": SafeHtmlAttr,
"first": First, "safeCSS": SafeCSS,
"where": Where, "markdownify": Markdownify,
"delimit": Delimit, "first": First,
"sort": Sort, "where": Where,
"highlight": Highlight, "delimit": Delimit,
"add": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '+') }, "sort": Sort,
"sub": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '-') }, "highlight": Highlight,
"div": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '/') }, "add": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '+') },
"mod": Mod, "sub": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '-') },
"mul": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '*') }, "div": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '/') },
"modBool": ModBool, "mod": Mod,
"lower": func(a string) string { return strings.ToLower(a) }, "mul": func(a, b interface{}) (interface{}, error) { return doArithmetic(a, b, '*') },
"upper": func(a string) string { return strings.ToUpper(a) }, "modBool": ModBool,
"title": func(a string) string { return strings.Title(a) }, "lower": func(a string) string { return strings.ToLower(a) },
"partial": Partial, "upper": func(a string) string { return strings.ToUpper(a) },
"ref": Ref, "title": func(a string) string { return strings.Title(a) },
"relref": RelRef, "partial": Partial,
"apply": Apply, "ref": Ref,
"chomp": Chomp, "relref": RelRef,
"replace": Replace, "apply": Apply,
"trim": Trim, "chomp": Chomp,
"replace": Replace,
"trim": Trim,
} }
chompRegexp = regexp.MustCompile("[\r\n]+$") chompRegexp = regexp.MustCompile("[\r\n]+$")

View file

@ -1,6 +1,7 @@
package tpl package tpl
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"html/template" "html/template"
@ -826,3 +827,107 @@ func TestMarkdownify(t *testing.T) {
t.Errorf("Markdownify: got '%s', expected '%s'", result, expect) 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>`, `{{ . }}`, `&lt;div&gt;&lt;/div&gt;`, `<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"`, `<a {{ . }}>irc</a>`, `<a ZgotmplZ>irc</a>`, `<a href="irc://irc.freenode.net/#golang">irc</a>`},
} {
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;`, `<div style="{{ . }}"></div>`, `<div style="ZgotmplZ"></div>`, `<div style="width: 60px;"></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)
}
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)
}
}
}