mirror of
https://github.com/gohugoio/hugo.git
synced 2025-03-19 07:01:35 +00:00
Add safeUrl
; disable safeHtmlAttr
; rename safeCSS
to safeCss
- Add `safeUrl` template function (Fixes #347) - Add TestSafeUrl() fashioned after @tatsushid great examples - Disable `safeHtmlAttr` pending further discussions on its other use cases because `safeUrl` is a cleaner solution to #347. (There are also `safeJs` and `safeJsStr` that we could implement if there are legitimate demands for them.) - Rename `safeCSS` to `safeCss` (to follow the convention of `safeHtml`) - Add/expand documentation on `safeHtml`, `safeCss` and `safeUrl`
This commit is contained in:
parent
f5946ea3dd
commit
724cc0ddff
3 changed files with 137 additions and 9 deletions
|
@ -263,10 +263,96 @@ Takes a string and sanitizes it for usage in URLs, converts spaces to "-".
|
||||||
e.g. `<a href="/tags/{{ . | urlize }}">{{ . }}</a>`
|
e.g. `<a href="/tags/{{ . | urlize }}">{{ . }}</a>`
|
||||||
|
|
||||||
### safeHtml
|
### 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. <a href=\"http://creativecommons.org/licenses/by/4.0/\">Some rights reserved</a>."
|
||||||
|
|
||||||
|
`{{ .Site.Copyright | safeHtml }}` would then output:
|
||||||
|
|
||||||
|
> © 2015 Jane Doe. <a href="http://creativecommons.org/licenses/by/4.0/">Some rights reserved</a>.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>© 2015 Jane Doe. <a href="http://creativecommons.org/licenses/by/4.0/">Some rights reserved</a>.</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
### safeHtmlAttr
|
||||||
|
Declares the provided string as a "safe" HTML attribute
|
||||||
|
from a trusted source, for example, ` dir="ltr"`,
|
||||||
|
so Go html/template will not filter it.
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
* `<a href="{{ .Url }}">` ⇒ `<a href="#ZgotmplZ">` (Bad!)
|
||||||
|
* `<a {{ printf "href=%q" .Url | safeHtmlAttr }}>` ⇒ `<a href="irc://irc.freenode.net/#golang">` (Good!)
|
||||||
|
-->
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
* `<p style="{{ .Params.style | safeCss }}">…</p>` ⇒ `<p style="color: red;">…</p>` (Good!)
|
||||||
|
* `<p style="{{ .Params.style }}">…</p>` ⇒ `<p style="ZgotmplZ">…</p>` (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:
|
||||||
|
|
||||||
|
<ul class="sidebar-menu">
|
||||||
|
{{ range .Site.Menus.main }}
|
||||||
|
<li><a href="{{ .Url }}">{{ .Name }}</a></li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
would produce `<li><a href="#ZgotmplZ">IRC: #golang at freenode</a></li>`
|
||||||
|
for the `irc://…` URL.
|
||||||
|
|
||||||
|
To fix this, add ` | safeUrl` after `.Url` on the 3rd line, like this:
|
||||||
|
|
||||||
|
<li><a href="{{ .Url | safeUrl }}">{{ .Name }}</a></li>
|
||||||
|
|
||||||
|
With this change, we finally get `<li><a href="irc://irc.freenode.net/#golang">IRC: #golang at freenode</a></li>`
|
||||||
|
as intended.
|
||||||
|
|
||||||
### markdownify
|
### markdownify
|
||||||
|
|
||||||
|
|
|
@ -910,14 +910,20 @@ func SafeHtml(text string) template.HTML {
|
||||||
return template.HTML(text)
|
return template.HTML(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "safeHtmlAttr" is currently disabled, pending further discussion
|
||||||
|
// on its use case. 2015-01-19
|
||||||
func SafeHtmlAttr(text string) template.HTMLAttr {
|
func SafeHtmlAttr(text string) template.HTMLAttr {
|
||||||
return template.HTMLAttr(text)
|
return template.HTMLAttr(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SafeCSS(text string) template.CSS {
|
func SafeCss(text string) template.CSS {
|
||||||
return template.CSS(text)
|
return template.CSS(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SafeUrl(text string) template.URL {
|
||||||
|
return template.URL(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)
|
||||||
|
@ -1251,8 +1257,8 @@ func init() {
|
||||||
"isset": IsSet,
|
"isset": IsSet,
|
||||||
"echoParam": ReturnWhenSet,
|
"echoParam": ReturnWhenSet,
|
||||||
"safeHtml": SafeHtml,
|
"safeHtml": SafeHtml,
|
||||||
"safeHtmlAttr": SafeHtmlAttr,
|
"safeCss": SafeCss,
|
||||||
"safeCSS": SafeCSS,
|
"safeUrl": SafeUrl,
|
||||||
"markdownify": Markdownify,
|
"markdownify": Markdownify,
|
||||||
"first": First,
|
"first": First,
|
||||||
"where": Where,
|
"where": Where,
|
||||||
|
|
|
@ -898,7 +898,7 @@ func TestSafeHtmlAttr(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSafeCSS(t *testing.T) {
|
func TestSafeCss(t *testing.T) {
|
||||||
for i, this := range []struct {
|
for i, this := range []struct {
|
||||||
str string
|
str string
|
||||||
tmplStr string
|
tmplStr string
|
||||||
|
@ -910,6 +910,7 @@ func TestSafeCSS(t *testing.T) {
|
||||||
tmpl, err := template.New("test").Parse(this.tmplStr)
|
tmpl, err := template.New("test").Parse(this.tmplStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("[%d] unable to create new html template %q: %s", this.tmplStr, err)
|
t.Errorf("[%d] unable to create new html template %q: %s", this.tmplStr, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
@ -922,12 +923,47 @@ func TestSafeCSS(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
err = tmpl.Execute(buf, SafeCSS(this.str))
|
err = tmpl.Execute(buf, SafeCss(this.str))
|
||||||
if err != nil {
|
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 {
|
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`, `<a href="{{ . }}">IRC</a>`, `<a href="#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, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue