diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md index e1499f82b..98785a53e 100644 --- a/docs/content/templates/functions.md +++ b/docs/content/templates/functions.md @@ -263,10 +263,96 @@ Takes a string and sanitizes it for usage in URLs, converts spaces to "-". e.g. `{{ . }}` ### safeHtml -Declares the provided string as "safe" so Go templates will not filter it. +Declares the provided string as a "safe" HTML document fragment +so Go html/template will not filter it. It should not be used +for HTML from a third-party, or HTML with unclosed tags or comments. -e.g. `{{ .Params.CopyrightHTML | safeHtml }}` +Example: Given a site-wide `config.toml` that contains this line: + copyright = "© 2015 Jane Doe. Some rights reserved." + +`{{ .Site.Copyright | safeHtml }}` would then output: + +> © 2015 Jane Doe. Some rights reserved. + +However, without the `safeHtml` function, html/template assumes +`.Site.Copyright` to be unsafe, escaping all HTML tags, +rendering the whole string as plain-text like this: + +
+

© 2015 Jane Doe. <a href="http://creativecommons.org/licenses/by/4.0/">Some rights reserved</a>.

+
+ + + +### safeCss +Declares the provided string as a known "safe" CSS string +so Go html/templates will not filter it. +"Safe" means CSS content that matches any of: + +1. The CSS3 stylesheet production, such as `p { color: purple }`. +2. The CSS3 rule production, such as `a[href=~"https:"].foo#bar`. +3. CSS3 declaration productions, such as `color: red; margin: 2px`. +4. The CSS3 value production, such as `rgba(0, 0, 255, 127)`. + +Example: Given `style = "color: red;"` defined in the front matter of your `.md` file: + +* `

` ⇒ `

` (Good!) +* `

` ⇒ `

` (Bad!) + +Note: "ZgotmplZ" is a special value that indicates that unsafe content reached a +CSS or URL context. + +### safeUrl +Declares the provided string as a "safe" URL or URL substring (see [RFC 3986][]). +A URL like `javascript:checkThatFormNotEditedBeforeLeavingPage()` from a trusted +source should go in the page, but by default dynamic `javascript:` URLs are +filtered out since they are a frequently exploited injection vector. + +[RFC 3986]: http://tools.ietf.org/html/rfc3986 + +Without `safeUrl`, only the URI schemes `http:`, `https:` and `mailto:` +are considered safe. All other URI schemes, e.g. `irc:` and +`javascript:`, get filtered and replaced with the `ZgotmplZ` unsafe +content indicator. + +Example: Given a site-wide `config.toml` that contains this menu entry: + + [[menu.main]] + name = "IRC: #golang at freenode" + url = "irc://irc.freenode.net/#golang" + +The following template: + + + +would produce `
  • IRC: #golang at freenode
  • ` +for the `irc://…` URL. + +To fix this, add ` | safeUrl` after `.Url` on the 3rd line, like this: + +
  • {{ .Name }}
  • + +With this change, we finally get `
  • IRC: #golang at freenode
  • ` +as intended. ### markdownify diff --git a/tpl/template.go b/tpl/template.go index 819343a97..9574adb9c 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -910,14 +910,20 @@ func SafeHtml(text string) template.HTML { return template.HTML(text) } +// "safeHtmlAttr" is currently disabled, pending further discussion +// on its use case. 2015-01-19 func SafeHtmlAttr(text string) template.HTMLAttr { return template.HTMLAttr(text) } -func SafeCSS(text string) template.CSS { +func SafeCss(text string) template.CSS { return template.CSS(text) } +func SafeUrl(text string) template.URL { + return template.URL(text) +} + func doArithmetic(a, b interface{}, op rune) (interface{}, error) { av := reflect.ValueOf(a) bv := reflect.ValueOf(b) @@ -1251,8 +1257,8 @@ func init() { "isset": IsSet, "echoParam": ReturnWhenSet, "safeHtml": SafeHtml, - "safeHtmlAttr": SafeHtmlAttr, - "safeCSS": SafeCSS, + "safeCss": SafeCss, + "safeUrl": SafeUrl, "markdownify": Markdownify, "first": First, "where": Where, diff --git a/tpl/template_test.go b/tpl/template_test.go index f857e6341..159d6cf53 100644 --- a/tpl/template_test.go +++ b/tpl/template_test.go @@ -898,7 +898,7 @@ func TestSafeHtmlAttr(t *testing.T) { } } -func TestSafeCSS(t *testing.T) { +func TestSafeCss(t *testing.T) { for i, this := range []struct { str string tmplStr string @@ -910,6 +910,7 @@ func TestSafeCSS(t *testing.T) { 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) @@ -922,12 +923,47 @@ func TestSafeCSS(t *testing.T) { } buf.Reset() - err = tmpl.Execute(buf, SafeCSS(this.str)) + 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) + 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) + t.Errorf("[%d] execute template with an escaped string value by SafeCss, got %v but expected %v", i, buf.String(), this.expectWithEscape) + } + } +} + +func TestSafeUrl(t *testing.T) { + for i, this := range []struct { + str string + tmplStr string + expectWithoutEscape string + expectWithEscape string + }{ + {`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, SafeUrl(this.str)) + if err != nil { + t.Errorf("[%d] execute template with an escaped string value by SafeUrl returns unexpected error: %s", i, err) + } + if buf.String() != this.expectWithEscape { + t.Errorf("[%d] execute template with an escaped string value by SafeUrl, got %v but expected %v", i, buf.String(), this.expectWithEscape) } } }