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)
}
}
}