tpl/tplimpl: Escape Markdown attributes in render hooks and shortcodes

This commit is contained in:
Joe Mooring 2024-12-04 14:01:31 -08:00 committed by Bjørn Erik Pedersen
parent b8c15f245b
commit 54398f8d57
No known key found for this signature in database
7 changed files with 74 additions and 68 deletions

View file

@ -90,7 +90,7 @@ baseURL="https://example.org"
[markup.goldmark] [markup.goldmark]
[markup.goldmark.renderer] [markup.goldmark.renderer]
unsafe = true unsafe = true
`) `)
b.WithTemplates("index.html", ` b.WithTemplates("index.html", `
@ -223,16 +223,16 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- layouts/_default/single.html -- -- layouts/_default/single.html --
{{ .Title }}|{{ .Content }}|$ {{ .Title }}|{{ .Content }}|$
` `
t.Run("Default multilingual", func(t *testing.T) { t.Run("Default multilingual", func(t *testing.T) {
b := Test(t, files) b := Test(t, files)
b.AssertFileContent("public/nn/p1/index.html", b.AssertFileContent("public/nn/p1/index.html",
"p1|<p><a href=\"/nn/p2/\">P2</a\n></p>", "<img alt=\"Pixel\" src=\"/nn/p1/pixel.nn.png\">") "p1|<p><a href=\"/nn/p2/\">P2</a\n></p>", "<img src=\"/nn/p1/pixel.nn.png\" alt=\"Pixel\">")
b.AssertFileContent("public/en/p1/index.html", b.AssertFileContent("public/en/p1/index.html",
"p1 en|<p><a href=\"/en/p2/\">P2</a\n></p>", "<img alt=\"Pixel\" src=\"/nn/p1/pixel.nn.png\">") "p1 en|<p><a href=\"/en/p2/\">P2</a\n></p>", "<img src=\"/nn/p1/pixel.nn.png\" alt=\"Pixel\">")
}) })
t.Run("Disabled", func(t *testing.T) { t.Run("Disabled", func(t *testing.T) {
@ -279,7 +279,7 @@ Image: ![alt-"<>&](/destination-"<> 'title-"<>&')
if enabled { if enabled {
b.AssertFileContent("public/index.html", b.AssertFileContent("public/index.html",
"Link: <a href=\"/destination-%22%3C%3E\" title=\"title-&#34;&lt;&gt;&amp;\">text-&quot;&lt;&gt;&amp;</a>", "Link: <a href=\"/destination-%22%3C%3E\" title=\"title-&#34;&lt;&gt;&amp;\">text-&quot;&lt;&gt;&amp;</a>",
"img alt=\"alt-&quot;&lt;&gt;&amp;\" src=\"/destination-%22%3C%3E\" title=\"title-&#34;&lt;&gt;&amp;\">", "img src=\"/destination-%22%3C%3E\" alt=\"alt-&quot;&lt;&gt;&amp;\" title=\"title-&#34;&lt;&gt;&amp;\">",
"&gt;&lt;script&gt;", "&gt;&lt;script&gt;",
) )
} else { } else {

View file

@ -89,6 +89,12 @@ title = true
| Codecademy Hoodie | False | 42.99 | | Codecademy Hoodie | False | 42.99 |
{.foo} {.foo}
## Table 2
a|b
---|---
1|2
{id="\"><script>alert()</script>"}
-- layouts/_default/single.html -- -- layouts/_default/single.html --
Summary: {{ .Summary }} Summary: {{ .Summary }}
@ -97,7 +103,8 @@ Content: {{ .Content }}
` `
b := hugolib.Test(t, files) b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", "<table class=\"foo\">") b.AssertFileContent("public/p1/index.html", `<table class="foo">`)
b.AssertFileContent("public/p1/index.html", `<table id="&#34;&gt;&lt;script&gt;alert()&lt;/script&gt;">`)
} }
// Issue 12811. // Issue 12811.
@ -166,14 +173,8 @@ title: "Home"
| Codecademy Tee | False | 19.99 | | Codecademy Tee | False | 19.99 |
| Codecademy Hoodie | False | 42.99 | | Codecademy Hoodie | False | 42.99 |
-- layouts/index.xml -- -- layouts/index.xml --
Content: {{ .Content }} Content: {{ .Content }}
` `
b := hugolib.Test(t, files) b := hugolib.Test(t, files)

View file

@ -1,7 +1,7 @@
{{- $u := urls.Parse .Destination -}} {{- $u := urls.Parse .Destination -}}
{{- $src := $u.String -}} {{- $src := $u.String -}}
{{- if not $u.IsAbs -}} {{- if not $u.IsAbs -}}
{{- $path := strings.TrimPrefix "./" $u.Path }} {{- $path := strings.TrimPrefix "./" $u.Path -}}
{{- with or (.PageInner.Resources.Get $path) (resources.Get $path) -}} {{- with or (.PageInner.Resources.Get $path) (resources.Get $path) -}}
{{- $src = .RelPermalink -}} {{- $src = .RelPermalink -}}
{{- with $u.RawQuery -}} {{- with $u.RawQuery -}}
@ -12,11 +12,12 @@
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- $attributes := merge .Attributes (dict "alt" .Text "src" $src "title" (.Title | transform.HTMLEscape)) -}} <img src="{{ $src }}" alt="{{ .Text }}"
<img {{- with .Title }} title="{{ . }}" {{- end -}}
{{- range $k, $v := $attributes -}} {{- range $k, $v := .Attributes -}}
{{- if $v -}} {{- if $v -}}
{{- printf " %s=%q" $k $v | safeHTMLAttr -}} {{- printf " %s=%q" $k ($v | transform.HTMLEscape) | safeHTMLAttr -}}
{{- end -}} {{- end -}}
{{- end -}}> {{- end -}}
>
{{- /**/ -}} {{- /**/ -}}

View file

@ -1,9 +1,9 @@
{{- $u := urls.Parse .Destination -}} {{- $u := urls.Parse .Destination -}}
{{- $href := $u.String -}} {{- $href := $u.String -}}
{{- if strings.HasPrefix $u.String "#" }} {{- if strings.HasPrefix $u.String "#" -}}
{{- $href = printf "%s#%s" .PageInner.RelPermalink $u.Fragment }} {{- $href = printf "%s#%s" .PageInner.RelPermalink $u.Fragment -}}
{{- else if not $u.IsAbs -}} {{- else if and $href (not $u.IsAbs) -}}
{{- $path := strings.TrimPrefix "./" $u.Path }} {{- $path := strings.TrimPrefix "./" $u.Path -}}
{{- with or {{- with or
($.PageInner.GetPage $path) ($.PageInner.GetPage $path)
($.PageInner.Resources.Get $path) ($.PageInner.Resources.Get $path)
@ -18,12 +18,5 @@
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- $attributes := dict "href" $href "title" (.Title | transform.HTMLEscape) -}} <a href="{{ $href }}" {{- with .Title }} title="{{ . }}" {{- end }}>{{ .Text }}</a>
<a
{{- range $k, $v := $attributes -}}
{{- if $v -}}
{{- printf " %s=%q" $k $v | safeHTMLAttr -}}
{{- end -}}
{{- end -}}
>{{ .Text }}</a>
{{- /**/ -}} {{- /**/ -}}

View file

@ -1,7 +1,7 @@
<table <table
{{- range $k, $v := .Attributes }} {{- range $k, $v := .Attributes }}
{{- if $v }} {{- if $v }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }} {{- printf " %s=%q" $k ($v | transform.HTMLEscape) | safeHTMLAttr }}
{{- end }} {{- end }}
{{- end }}> {{- end }}>
<thead> <thead>

View file

@ -26,7 +26,7 @@ Renders an embedded YouTube video.
{{- if not $pc.Disable }} {{- if not $pc.Disable }}
{{- with $id := or (.Get "id") (.Get 0) }} {{- with $id := or (.Get "id") (.Get 0) }}
{{/* Set defaults. */}} {{- /* Set defaults. */}}
{{- $allowFullScreen := "allowfullscreen" }} {{- $allowFullScreen := "allowfullscreen" }}
{{- $autoplay := 0 }} {{- $autoplay := 0 }}
{{- $class := "" }} {{- $class := "" }}
@ -70,23 +70,8 @@ Renders an embedded YouTube video.
{{- $start := or ($.Get "start") $start }} {{- $start := or ($.Get "start") $start }}
{{- $title := or ($.Get "title") $title }} {{- $title := or ($.Get "title") $title }}
{{- /* Determine host. */}}
{{- $host := cond $pc.PrivacyEnhanced "www.youtube-nocookie.com" "www.youtube.com" }}
{{- /* Set styles. */}}
{{- $divStyle := "position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;" }}
{{- $iframeStyle := "position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" }}
{{- if $class }}
{{- $iframeStyle = "" }}
{{- end }}
{{- /* Set class or style of wrapping div element. */}}
{{- $divClassOrStyle := printf "style=%q" $divStyle }}
{{- with $class }}
{{- $divClassOrStyle = printf "class=%q" $class }}
{{- end }}
{{- /* Define src attribute. */}} {{- /* Define src attribute. */}}
{{- $host := cond $pc.PrivacyEnhanced "www.youtube-nocookie.com" "www.youtube.com" }}
{{- $src := printf "https://%s/embed/%s" $host $id }} {{- $src := printf "https://%s/embed/%s" $host $id }}
{{- $params := dict {{- $params := dict
"autoplay" $autoplay "autoplay" $autoplay
@ -108,25 +93,33 @@ Renders an embedded YouTube video.
{{- $src = printf "%s?%s" $src . }} {{- $src = printf "%s?%s" $src . }}
{{- end }} {{- end }}
{{- /* Set div attributes. */}}
{{- $divStyle := "position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;" }}
{{- if $class }}
{{- $divStyle = "" }}
{{- end }}
{{- /* Set iframe attributes. */}} {{- /* Set iframe attributes. */}}
{{- $iframeAttributes := dict {{- $iframeStyle := "position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" }}
"allow" "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" {{- if $class }}
"allowfullscreen" $allowFullScreen {{- $iframeStyle = "" }}
"loading" $loading {{- end }}
"referrerpolicy" "strict-origin-when-cross-origin" {{- $allow := "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" }}
"src" $src {{- $referrerpolicy := "strict-origin-when-cross-origin" }}
"style" $iframeStyle
"title" $title
}}
{{- /* Render. */}} {{- /* Render. */}}
<div {{ $divClassOrStyle | safeHTMLAttr }}> <div
{{- with $class }} class="{{ . }}" {{- end }}
{{- with $divStyle }} style="{{ . | safeCSS }}" {{- end -}}
>
<iframe <iframe
{{- range $k, $v := $iframeAttributes }} {{- with $allow }} allow="{{ . }}" {{- end }}
{{- if $v }} {{- with $allowFullScreen }} allowfullscreen="{{ . }}" {{- end }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }} {{- with $loading }} loading="{{ . }}" {{- end }}
{{- end }} {{- with $referrerpolicy }} referrerpolicy="{{ . }}" {{- end }}
{{- end }} {{- with $src }} src="{{ . }}" {{- end }}
{{- with $iframeStyle}} style="{{ . | safeCSS }}" {{- end }}
{{- with $title }} title="{{ . }}" {{- end -}}
></iframe> ></iframe>
</div> </div>
{{- else }} {{- else }}

View file

@ -91,6 +91,9 @@ title: s1/p3
[430](p2/) [430](p2/)
[440](/s1/p2/) [440](/s1/p2/)
[450](../s1/p2/) [450](../s1/p2/)
// empty
[]()
` `
b := hugolib.Test(t, files) b := hugolib.Test(t, files)
@ -122,6 +125,8 @@ title: s1/p3
`<a href="/s1/p2/">430</a>`, `<a href="/s1/p2/">430</a>`,
`<a href="/s1/p2/">440</a>`, `<a href="/s1/p2/">440</a>`,
`<a href="/s1/p2/">450</a>`, `<a href="/s1/p2/">450</a>`,
`<a href=""></a>`,
) )
b.AssertFileContent("public/s1/p2/index.html", b.AssertFileContent("public/s1/p2/index.html",
@ -148,10 +153,17 @@ block = false
[markup.goldmark.renderHooks.image] [markup.goldmark.renderHooks.image]
enableDefault = true enableDefault = true
-- content/p1/index.md -- -- content/p1/index.md --
![]()
![alt1](./pixel.png) ![alt1](./pixel.png)
![alt2](pixel.png?a=b&c=d#fragment) ![alt2-&<>'](pixel.png "&<>'")
![alt3](pixel.png?a=b&c=d#fragment)
{.foo #bar} {.foo #bar}
![alt4](pixel.png)
{id="\"><script>alert()</script>"}
-- content/p1/pixel.png -- -- content/p1/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- layouts/_default/single.html -- -- layouts/_default/single.html --
@ -160,15 +172,21 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
b := hugolib.Test(t, files) b := hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", b.AssertFileContent("public/p1/index.html",
`<img alt="alt1" src="/dir/p1/pixel.png">`, `<img src="" alt="">`,
`<img alt="alt2" src="/dir/p1/pixel.png?a=b&c=d#fragment">`, `<img src="/dir/p1/pixel.png" alt="alt1">`,
`<img src="/dir/p1/pixel.png" alt="alt2-&amp;&lt;&gt;&rsquo;" title="&amp;&lt;&gt;&#39;">`,
`<img src="/dir/p1/pixel.png?a=b&amp;c=d#fragment" alt="alt3">`,
`<img src="/dir/p1/pixel.png" alt="alt4">`,
) )
files = strings.Replace(files, "block = false", "block = true", -1) files = strings.Replace(files, "block = false", "block = true", -1)
b = hugolib.Test(t, files) b = hugolib.Test(t, files)
b.AssertFileContent("public/p1/index.html", b.AssertFileContent("public/p1/index.html",
`<img alt="alt1" src="/dir/p1/pixel.png">`, `<img src="" alt="">`,
`<img alt="alt2" class="foo" id="bar" src="/dir/p1/pixel.png?a=b&c=d#fragment">`, `<img src="/dir/p1/pixel.png" alt="alt1">`,
`<img src="/dir/p1/pixel.png" alt="alt2-&amp;&lt;&gt;&rsquo;" title="&amp;&lt;&gt;&#39;">`,
`<img src="/dir/p1/pixel.png?a=b&amp;c=d#fragment" alt="alt3" class="foo" id="bar">`,
`<img src="/dir/p1/pixel.png" alt="alt4" id="&#34;&gt;&lt;script&gt;alert()&lt;/script&gt;">`,
) )
} }