mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
@@ -21,7 +21,7 @@ func TestTypedContent(t *testing.T) {
|
||||
htmltemplate.HTML(`Hello, <b>World</b> &tc!`),
|
||||
htmltemplate.HTMLAttr(` dir="ltr"`),
|
||||
htmltemplate.JS(`c && alert("Hello, World!");`),
|
||||
htmltemplate.JSStr(`Hello, World & O'Reilly\x21`),
|
||||
htmltemplate.JSStr(`Hello, World & O'Reilly\u0021`),
|
||||
htmltemplate.URL(`greeting=H%69,&addressee=(World)`),
|
||||
htmltemplate.Srcset(`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`),
|
||||
htmltemplate.URL(`,foo/,`),
|
||||
@@ -73,7 +73,7 @@ func TestTypedContent(t *testing.T) {
|
||||
`Hello, <b>World</b> &tc!`,
|
||||
` dir="ltr"`,
|
||||
`c && alert("Hello, World!");`,
|
||||
`Hello, World & O'Reilly\x21`,
|
||||
`Hello, World & O'Reilly\u0021`,
|
||||
`greeting=H%69,&addressee=(World)`,
|
||||
`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
|
||||
`,foo/,`,
|
||||
@@ -103,7 +103,7 @@ func TestTypedContent(t *testing.T) {
|
||||
`Hello, World &tc!`,
|
||||
` dir="ltr"`,
|
||||
`c && alert("Hello, World!");`,
|
||||
`Hello, World & O'Reilly\x21`,
|
||||
`Hello, World & O'Reilly\u0021`,
|
||||
`greeting=H%69,&addressee=(World)`,
|
||||
`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
|
||||
`,foo/,`,
|
||||
@@ -118,7 +118,7 @@ func TestTypedContent(t *testing.T) {
|
||||
`Hello, World &tc!`,
|
||||
` dir="ltr"`,
|
||||
`c && alert("Hello, World!");`,
|
||||
`Hello, World & O'Reilly\x21`,
|
||||
`Hello, World & O'Reilly\u0021`,
|
||||
`greeting=H%69,&addressee=(World)`,
|
||||
`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
|
||||
`,foo/,`,
|
||||
@@ -133,7 +133,7 @@ func TestTypedContent(t *testing.T) {
|
||||
`Hello, <b>World</b> &tc!`,
|
||||
` dir="ltr"`,
|
||||
`c && alert("Hello, World!");`,
|
||||
`Hello, World & O'Reilly\x21`,
|
||||
`Hello, World & O'Reilly\u0021`,
|
||||
`greeting=H%69,&addressee=(World)`,
|
||||
`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
|
||||
`,foo/,`,
|
||||
@@ -149,7 +149,7 @@ func TestTypedContent(t *testing.T) {
|
||||
// Not escaped.
|
||||
`c && alert("Hello, World!");`,
|
||||
// Escape sequence not over-escaped.
|
||||
`"Hello, World & O'Reilly\x21"`,
|
||||
`"Hello, World & O'Reilly\u0021"`,
|
||||
`"greeting=H%69,\u0026addressee=(World)"`,
|
||||
`"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
|
||||
`",foo/,"`,
|
||||
@@ -165,7 +165,7 @@ func TestTypedContent(t *testing.T) {
|
||||
// Not JS escaped but HTML escaped.
|
||||
`c && alert("Hello, World!");`,
|
||||
// Escape sequence not over-escaped.
|
||||
`"Hello, World & O'Reilly\x21"`,
|
||||
`"Hello, World & O'Reilly\u0021"`,
|
||||
`"greeting=H%69,\u0026addressee=(World)"`,
|
||||
`"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
|
||||
`",foo/,"`,
|
||||
@@ -174,30 +174,30 @@ func TestTypedContent(t *testing.T) {
|
||||
{
|
||||
`<script>alert("{{.}}")</script>`,
|
||||
[]string{
|
||||
`\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
|
||||
`a[href =~ \x22\/\/example.com\x22]#foo`,
|
||||
`Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
|
||||
` dir=\x22ltr\x22`,
|
||||
`c \x26\x26 alert(\x22Hello, World!\x22);`,
|
||||
`\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
|
||||
`a[href =~ \u0022\/\/example.com\u0022]#foo`,
|
||||
`Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
|
||||
` dir=\u0022ltr\u0022`,
|
||||
`c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
|
||||
// Escape sequence not over-escaped.
|
||||
`Hello, World \x26 O\x27Reilly\x21`,
|
||||
`greeting=H%69,\x26addressee=(World)`,
|
||||
`greeting=H%69,\x26addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
|
||||
`Hello, World \u0026 O\u0027Reilly\u0021`,
|
||||
`greeting=H%69,\u0026addressee=(World)`,
|
||||
`greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
|
||||
`,foo\/,`,
|
||||
},
|
||||
},
|
||||
{
|
||||
`<script type="text/javascript">alert("{{.}}")</script>`,
|
||||
[]string{
|
||||
`\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
|
||||
`a[href =~ \x22\/\/example.com\x22]#foo`,
|
||||
`Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
|
||||
` dir=\x22ltr\x22`,
|
||||
`c \x26\x26 alert(\x22Hello, World!\x22);`,
|
||||
`\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
|
||||
`a[href =~ \u0022\/\/example.com\u0022]#foo`,
|
||||
`Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
|
||||
` dir=\u0022ltr\u0022`,
|
||||
`c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
|
||||
// Escape sequence not over-escaped.
|
||||
`Hello, World \x26 O\x27Reilly\x21`,
|
||||
`greeting=H%69,\x26addressee=(World)`,
|
||||
`greeting=H%69,\x26addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
|
||||
`Hello, World \u0026 O\u0027Reilly\u0021`,
|
||||
`greeting=H%69,\u0026addressee=(World)`,
|
||||
`greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
|
||||
`,foo\/,`,
|
||||
},
|
||||
},
|
||||
@@ -211,7 +211,7 @@ func TestTypedContent(t *testing.T) {
|
||||
// Not escaped.
|
||||
`c && alert("Hello, World!");`,
|
||||
// Escape sequence not over-escaped.
|
||||
`"Hello, World & O'Reilly\x21"`,
|
||||
`"Hello, World & O'Reilly\u0021"`,
|
||||
`"greeting=H%69,\u0026addressee=(World)"`,
|
||||
`"greeting=H%69,\u0026addressee=(World) 2x, https://golang.org/favicon.ico 500.5w"`,
|
||||
`",foo/,"`,
|
||||
@@ -227,7 +227,7 @@ func TestTypedContent(t *testing.T) {
|
||||
`Hello, <b>World</b> &tc!`,
|
||||
` dir="ltr"`,
|
||||
`c && alert("Hello, World!");`,
|
||||
`Hello, World & O'Reilly\x21`,
|
||||
`Hello, World & O'Reilly\u0021`,
|
||||
`greeting=H%69,&addressee=(World)`,
|
||||
`greeting=H%69,&addressee=(World) 2x, https://golang.org/favicon.ico 500.5w`,
|
||||
`,foo/,`,
|
||||
@@ -236,15 +236,15 @@ func TestTypedContent(t *testing.T) {
|
||||
{
|
||||
`<button onclick='alert("{{.}}")'>`,
|
||||
[]string{
|
||||
`\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
|
||||
`a[href =~ \x22\/\/example.com\x22]#foo`,
|
||||
`Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
|
||||
` dir=\x22ltr\x22`,
|
||||
`c \x26\x26 alert(\x22Hello, World!\x22);`,
|
||||
`\u003cb\u003e \u0022foo%\u0022 O\u0027Reilly \u0026bar;`,
|
||||
`a[href =~ \u0022\/\/example.com\u0022]#foo`,
|
||||
`Hello, \u003cb\u003eWorld\u003c\/b\u003e \u0026amp;tc!`,
|
||||
` dir=\u0022ltr\u0022`,
|
||||
`c \u0026\u0026 alert(\u0022Hello, World!\u0022);`,
|
||||
// Escape sequence not over-escaped.
|
||||
`Hello, World \x26 O\x27Reilly\x21`,
|
||||
`greeting=H%69,\x26addressee=(World)`,
|
||||
`greeting=H%69,\x26addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
|
||||
`Hello, World \u0026 O\u0027Reilly\u0021`,
|
||||
`greeting=H%69,\u0026addressee=(World)`,
|
||||
`greeting=H%69,\u0026addressee=(World) 2x, https:\/\/golang.org\/favicon.ico 500.5w`,
|
||||
`,foo\/,`,
|
||||
},
|
||||
},
|
||||
@@ -256,7 +256,7 @@ func TestTypedContent(t *testing.T) {
|
||||
`Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
|
||||
`%20dir%3d%22ltr%22`,
|
||||
`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
|
||||
`Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
|
||||
`Hello%2c%20World%20%26%20O%27Reilly%5cu0021`,
|
||||
// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is done.
|
||||
`greeting=H%69,&addressee=%28World%29`,
|
||||
`greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
|
||||
@@ -271,7 +271,7 @@ func TestTypedContent(t *testing.T) {
|
||||
`Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
|
||||
`%20dir%3d%22ltr%22`,
|
||||
`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
|
||||
`Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
|
||||
`Hello%2c%20World%20%26%20O%27Reilly%5cu0021`,
|
||||
// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is not done.
|
||||
`greeting=H%69,&addressee=%28World%29`,
|
||||
`greeting%3dH%2569%2c%26addressee%3d%28World%29%202x%2c%20https%3a%2f%2fgolang.org%2ffavicon.ico%20500.5w`,
|
||||
|
@@ -73,6 +73,51 @@ functions.
|
||||
For these internal escaping functions, if an action pipeline evaluates to
|
||||
a nil interface value, it is treated as though it were an empty string.
|
||||
|
||||
Namespaced and data- attributes
|
||||
|
||||
Attributes with a namespace are treated as if they had no namespace.
|
||||
Given the excerpt
|
||||
|
||||
<a my:href="{{.}}"></a>
|
||||
|
||||
At parse time the attribute will be treated as if it were just "href".
|
||||
So at parse time the template becomes:
|
||||
|
||||
<a my:href="{{. | urlescaper | attrescaper}}"></a>
|
||||
|
||||
Similarly to attributes with namespaces, attributes with a "data-" prefix are
|
||||
treated as if they had no "data-" prefix. So given
|
||||
|
||||
<a data-href="{{.}}"></a>
|
||||
|
||||
At parse time this becomes
|
||||
|
||||
<a data-href="{{. | urlescaper | attrescaper}}"></a>
|
||||
|
||||
If an attribute has both a namespace and a "data-" prefix, only the namespace
|
||||
will be removed when determining the context. For example
|
||||
|
||||
<a my:data-href="{{.}}"></a>
|
||||
|
||||
This is handled as if "my:data-href" was just "data-href" and not "href" as
|
||||
it would be if the "data-" prefix were to be ignored too. Thus at parse
|
||||
time this becomes just
|
||||
|
||||
<a my:data-href="{{. | attrescaper}}"></a>
|
||||
|
||||
As a special case, attributes with the namespace "xmlns" are always treated
|
||||
as containing URLs. Given the excerpts
|
||||
|
||||
<a xmlns:title="{{.}}"></a>
|
||||
<a xmlns:href="{{.}}"></a>
|
||||
<a xmlns:onclick="{{.}}"></a>
|
||||
|
||||
At parse time they become:
|
||||
|
||||
<a xmlns:title="{{. | urlescaper | attrescaper}}"></a>
|
||||
<a xmlns:href="{{. | urlescaper | attrescaper}}"></a>
|
||||
<a xmlns:onclick="{{. | urlescaper | attrescaper}}"></a>
|
||||
|
||||
Errors
|
||||
|
||||
See the documentation of ErrorCode for details.
|
||||
|
@@ -242,7 +242,7 @@ func TestEscape(t *testing.T) {
|
||||
{
|
||||
"jsStr",
|
||||
"<button onclick='alert("{{.H}}")'>",
|
||||
`<button onclick='alert("\x3cHello\x3e")'>`,
|
||||
`<button onclick='alert("\u003cHello\u003e")'>`,
|
||||
},
|
||||
{
|
||||
"badMarshaler",
|
||||
@@ -263,7 +263,7 @@ func TestEscape(t *testing.T) {
|
||||
{
|
||||
"jsRe",
|
||||
`<button onclick='alert(/{{"foo+bar"}}/.test(""))'>`,
|
||||
`<button onclick='alert(/foo\x2bbar/.test(""))'>`,
|
||||
`<button onclick='alert(/foo\u002bbar/.test(""))'>`,
|
||||
},
|
||||
{
|
||||
"jsReBlank",
|
||||
@@ -829,7 +829,7 @@ func TestEscapeSet(t *testing.T) {
|
||||
"main": `<button onclick="title='{{template "helper"}}'; ...">{{template "helper"}}</button>`,
|
||||
"helper": `{{11}} of {{"<100>"}}`,
|
||||
},
|
||||
`<button onclick="title='11 of \x3c100\x3e'; ...">11 of <100></button>`,
|
||||
`<button onclick="title='11 of \u003c100\u003e'; ...">11 of <100></button>`,
|
||||
},
|
||||
// A non-recursive template that ends in a different context.
|
||||
// helper starts in jsCtxRegexp and ends in jsCtxDivOp.
|
||||
|
@@ -119,9 +119,9 @@ func Example_escape() {
|
||||
// "Fran & Freddie's Diner" <tasty@example.com>
|
||||
// "Fran & Freddie's Diner" <tasty@example.com>
|
||||
// "Fran & Freddie's Diner"32<tasty@example.com>
|
||||
// \"Fran & Freddie\'s Diner\" \x3Ctasty@example.com\x3E
|
||||
// \"Fran & Freddie\'s Diner\" \x3Ctasty@example.com\x3E
|
||||
// \"Fran & Freddie\'s Diner\"32\x3Ctasty@example.com\x3E
|
||||
// \"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E
|
||||
// \"Fran \u0026 Freddie\'s Diner\" \u003Ctasty@example.com\u003E
|
||||
// \"Fran \u0026 Freddie\'s Diner\"32\u003Ctasty@example.com\u003E
|
||||
// %22Fran+%26+Freddie%27s+Diner%2232%3Ctasty%40example.com%3E
|
||||
|
||||
}
|
||||
|
@@ -164,7 +164,6 @@ func jsValEscaper(args ...interface{}) string {
|
||||
}
|
||||
// TODO: detect cycles before calling Marshal which loops infinitely on
|
||||
// cyclic data. This may be an unacceptable DoS risk.
|
||||
|
||||
b, err := json.Marshal(a)
|
||||
if err != nil {
|
||||
// Put a space before comment so that if it is flush against
|
||||
@@ -179,8 +178,8 @@ func jsValEscaper(args ...interface{}) string {
|
||||
// TODO: maybe post-process output to prevent it from containing
|
||||
// "<!--", "-->", "<![CDATA[", "]]>", or "</script"
|
||||
// in case custom marshalers produce output containing those.
|
||||
|
||||
// TODO: Maybe abbreviate \u00ab to \xab to produce more compact output.
|
||||
// Note: Do not use \x escaping to save bytes because it is not JSON compatible and this escaper
|
||||
// supports ld+json content-type.
|
||||
if len(b) == 0 {
|
||||
// In, `x=y/{{.}}*z` a json.Marshaler that produces "" should
|
||||
// not cause the output `x=y/*z`.
|
||||
@@ -261,6 +260,8 @@ func replace(s string, replacementTable []string) string {
|
||||
r, w = utf8.DecodeRuneInString(s[i:])
|
||||
var repl string
|
||||
switch {
|
||||
case int(r) < len(lowUnicodeReplacementTable):
|
||||
repl = lowUnicodeReplacementTable[r]
|
||||
case int(r) < len(replacementTable) && replacementTable[r] != "":
|
||||
repl = replacementTable[r]
|
||||
case r == '\u2028':
|
||||
@@ -284,67 +285,80 @@ func replace(s string, replacementTable []string) string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
var jsStrReplacementTable = []string{
|
||||
0: `\0`,
|
||||
var lowUnicodeReplacementTable = []string{
|
||||
0: `\u0000`, 1: `\u0001`, 2: `\u0002`, 3: `\u0003`, 4: `\u0004`, 5: `\u0005`, 6: `\u0006`,
|
||||
'\a': `\u0007`,
|
||||
'\b': `\u0008`,
|
||||
'\t': `\t`,
|
||||
'\n': `\n`,
|
||||
'\v': `\x0b`, // "\v" == "v" on IE 6.
|
||||
'\v': `\u000b`, // "\v" == "v" on IE 6.
|
||||
'\f': `\f`,
|
||||
'\r': `\r`,
|
||||
0xe: `\u000e`, 0xf: `\u000f`, 0x10: `\u0010`, 0x11: `\u0011`, 0x12: `\u0012`, 0x13: `\u0013`,
|
||||
0x14: `\u0014`, 0x15: `\u0015`, 0x16: `\u0016`, 0x17: `\u0017`, 0x18: `\u0018`, 0x19: `\u0019`,
|
||||
0x1a: `\u001a`, 0x1b: `\u001b`, 0x1c: `\u001c`, 0x1d: `\u001d`, 0x1e: `\u001e`, 0x1f: `\u001f`,
|
||||
}
|
||||
|
||||
var jsStrReplacementTable = []string{
|
||||
0: `\u0000`,
|
||||
'\t': `\t`,
|
||||
'\n': `\n`,
|
||||
'\v': `\u000b`, // "\v" == "v" on IE 6.
|
||||
'\f': `\f`,
|
||||
'\r': `\r`,
|
||||
// Encode HTML specials as hex so the output can be embedded
|
||||
// in HTML attributes without further encoding.
|
||||
'"': `\x22`,
|
||||
'&': `\x26`,
|
||||
'\'': `\x27`,
|
||||
'+': `\x2b`,
|
||||
'"': `\u0022`,
|
||||
'&': `\u0026`,
|
||||
'\'': `\u0027`,
|
||||
'+': `\u002b`,
|
||||
'/': `\/`,
|
||||
'<': `\x3c`,
|
||||
'>': `\x3e`,
|
||||
'<': `\u003c`,
|
||||
'>': `\u003e`,
|
||||
'\\': `\\`,
|
||||
}
|
||||
|
||||
// jsStrNormReplacementTable is like jsStrReplacementTable but does not
|
||||
// overencode existing escapes since this table has no entry for `\`.
|
||||
var jsStrNormReplacementTable = []string{
|
||||
0: `\0`,
|
||||
0: `\u0000`,
|
||||
'\t': `\t`,
|
||||
'\n': `\n`,
|
||||
'\v': `\x0b`, // "\v" == "v" on IE 6.
|
||||
'\v': `\u000b`, // "\v" == "v" on IE 6.
|
||||
'\f': `\f`,
|
||||
'\r': `\r`,
|
||||
// Encode HTML specials as hex so the output can be embedded
|
||||
// in HTML attributes without further encoding.
|
||||
'"': `\x22`,
|
||||
'&': `\x26`,
|
||||
'\'': `\x27`,
|
||||
'+': `\x2b`,
|
||||
'"': `\u0022`,
|
||||
'&': `\u0026`,
|
||||
'\'': `\u0027`,
|
||||
'+': `\u002b`,
|
||||
'/': `\/`,
|
||||
'<': `\x3c`,
|
||||
'>': `\x3e`,
|
||||
'<': `\u003c`,
|
||||
'>': `\u003e`,
|
||||
}
|
||||
|
||||
var jsRegexpReplacementTable = []string{
|
||||
0: `\0`,
|
||||
0: `\u0000`,
|
||||
'\t': `\t`,
|
||||
'\n': `\n`,
|
||||
'\v': `\x0b`, // "\v" == "v" on IE 6.
|
||||
'\v': `\u000b`, // "\v" == "v" on IE 6.
|
||||
'\f': `\f`,
|
||||
'\r': `\r`,
|
||||
// Encode HTML specials as hex so the output can be embedded
|
||||
// in HTML attributes without further encoding.
|
||||
'"': `\x22`,
|
||||
'"': `\u0022`,
|
||||
'$': `\$`,
|
||||
'&': `\x26`,
|
||||
'\'': `\x27`,
|
||||
'&': `\u0026`,
|
||||
'\'': `\u0027`,
|
||||
'(': `\(`,
|
||||
')': `\)`,
|
||||
'*': `\*`,
|
||||
'+': `\x2b`,
|
||||
'+': `\u002b`,
|
||||
'-': `\-`,
|
||||
'.': `\.`,
|
||||
'/': `\/`,
|
||||
'<': `\x3c`,
|
||||
'>': `\x3e`,
|
||||
'<': `\u003c`,
|
||||
'>': `\u003e`,
|
||||
'?': `\?`,
|
||||
'[': `\[`,
|
||||
'\\': `\\`,
|
||||
@@ -384,11 +398,11 @@ func isJSType(mimeType string) bool {
|
||||
// https://tools.ietf.org/html/rfc7231#section-3.1.1
|
||||
// https://tools.ietf.org/html/rfc4329#section-3
|
||||
// https://www.ietf.org/rfc/rfc4627.txt
|
||||
mimeType = strings.ToLower(mimeType)
|
||||
// discard parameters
|
||||
if i := strings.Index(mimeType, ";"); i >= 0 {
|
||||
mimeType = mimeType[:i]
|
||||
}
|
||||
mimeType = strings.ToLower(mimeType)
|
||||
mimeType = strings.TrimSpace(mimeType)
|
||||
switch mimeType {
|
||||
case
|
||||
|
@@ -139,7 +139,7 @@ func TestJSValEscaper(t *testing.T) {
|
||||
{"foo", `"foo"`},
|
||||
// Newlines.
|
||||
{"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`},
|
||||
// "\v" == "v" on IE 6 so use "\x0b" instead.
|
||||
// "\v" == "v" on IE 6 so use "\u000b" instead.
|
||||
{"\t\x0b", `"\t\u000b"`},
|
||||
{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
|
||||
{[]interface{}{}, "[]"},
|
||||
@@ -175,7 +175,7 @@ func TestJSStrEscaper(t *testing.T) {
|
||||
}{
|
||||
{"", ``},
|
||||
{"foo", `foo`},
|
||||
{"\u0000", `\0`},
|
||||
{"\u0000", `\u0000`},
|
||||
{"\t", `\t`},
|
||||
{"\n", `\n`},
|
||||
{"\r", `\r`},
|
||||
@@ -185,14 +185,14 @@ func TestJSStrEscaper(t *testing.T) {
|
||||
{"\\n", `\\n`},
|
||||
{"foo\r\nbar", `foo\r\nbar`},
|
||||
// Preserve attribute boundaries.
|
||||
{`"`, `\x22`},
|
||||
{`'`, `\x27`},
|
||||
{`"`, `\u0022`},
|
||||
{`'`, `\u0027`},
|
||||
// Allow embedding in HTML without further escaping.
|
||||
{`&`, `\x26amp;`},
|
||||
{`&`, `\u0026amp;`},
|
||||
// Prevent breaking out of text node and element boundaries.
|
||||
{"</script>", `\x3c\/script\x3e`},
|
||||
{"<![CDATA[", `\x3c![CDATA[`},
|
||||
{"]]>", `]]\x3e`},
|
||||
{"</script>", `\u003c\/script\u003e`},
|
||||
{"<![CDATA[", `\u003c![CDATA[`},
|
||||
{"]]>", `]]\u003e`},
|
||||
// https://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span
|
||||
// "The text in style, script, title, and textarea elements
|
||||
// must not have an escaping text span start that is not
|
||||
@@ -203,11 +203,11 @@ func TestJSStrEscaper(t *testing.T) {
|
||||
// allow regular text content to be interpreted as script
|
||||
// allowing script execution via a combination of a JS string
|
||||
// injection followed by an HTML text injection.
|
||||
{"<!--", `\x3c!--`},
|
||||
{"-->", `--\x3e`},
|
||||
{"<!--", `\u003c!--`},
|
||||
{"-->", `--\u003e`},
|
||||
// From https://code.google.com/p/doctype/wiki/ArticleUtf7
|
||||
{"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
|
||||
`\x2bADw-script\x2bAD4-alert(1)\x2bADw-\/script\x2bAD4-`,
|
||||
`\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`,
|
||||
},
|
||||
// Invalid UTF-8 sequence
|
||||
{"foo\xA0bar", "foo\xA0bar"},
|
||||
@@ -230,7 +230,7 @@ func TestJSRegexpEscaper(t *testing.T) {
|
||||
}{
|
||||
{"", `(?:)`},
|
||||
{"foo", `foo`},
|
||||
{"\u0000", `\0`},
|
||||
{"\u0000", `\u0000`},
|
||||
{"\t", `\t`},
|
||||
{"\n", `\n`},
|
||||
{"\r", `\r`},
|
||||
@@ -240,19 +240,19 @@ func TestJSRegexpEscaper(t *testing.T) {
|
||||
{"\\n", `\\n`},
|
||||
{"foo\r\nbar", `foo\r\nbar`},
|
||||
// Preserve attribute boundaries.
|
||||
{`"`, `\x22`},
|
||||
{`'`, `\x27`},
|
||||
{`"`, `\u0022`},
|
||||
{`'`, `\u0027`},
|
||||
// Allow embedding in HTML without further escaping.
|
||||
{`&`, `\x26amp;`},
|
||||
{`&`, `\u0026amp;`},
|
||||
// Prevent breaking out of text node and element boundaries.
|
||||
{"</script>", `\x3c\/script\x3e`},
|
||||
{"<![CDATA[", `\x3c!\[CDATA\[`},
|
||||
{"]]>", `\]\]\x3e`},
|
||||
{"</script>", `\u003c\/script\u003e`},
|
||||
{"<![CDATA[", `\u003c!\[CDATA\[`},
|
||||
{"]]>", `\]\]\u003e`},
|
||||
// Escaping text spans.
|
||||
{"<!--", `\x3c!\-\-`},
|
||||
{"-->", `\-\-\x3e`},
|
||||
{"<!--", `\u003c!\-\-`},
|
||||
{"-->", `\-\-\u003e`},
|
||||
{"*", `\*`},
|
||||
{"+", `\x2b`},
|
||||
{"+", `\u002b`},
|
||||
{"?", `\?`},
|
||||
{"[](){}", `\[\]\(\)\{\}`},
|
||||
{"$foo|x.y", `\$foo\|x\.y`},
|
||||
@@ -286,27 +286,27 @@ func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
|
||||
{
|
||||
"jsStrEscaper",
|
||||
jsStrEscaper,
|
||||
"\\0\x01\x02\x03\x04\x05\x06\x07" +
|
||||
"\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
|
||||
"\x10\x11\x12\x13\x14\x15\x16\x17" +
|
||||
"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
|
||||
` !\x22#$%\x26\x27()*\x2b,-.\/` +
|
||||
`0123456789:;\x3c=\x3e?` +
|
||||
`\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
|
||||
`\u0008\t\n\u000b\f\r\u000e\u000f` +
|
||||
`\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
|
||||
`\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
|
||||
` !\u0022#$%\u0026\u0027()*\u002b,-.\/` +
|
||||
`0123456789:;\u003c=\u003e?` +
|
||||
`@ABCDEFGHIJKLMNO` +
|
||||
`PQRSTUVWXYZ[\\]^_` +
|
||||
"`abcdefghijklmno" +
|
||||
"pqrstuvwxyz{|}~\x7f" +
|
||||
"pqrstuvwxyz{|}~\u007f" +
|
||||
"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
|
||||
},
|
||||
{
|
||||
"jsRegexpEscaper",
|
||||
jsRegexpEscaper,
|
||||
"\\0\x01\x02\x03\x04\x05\x06\x07" +
|
||||
"\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
|
||||
"\x10\x11\x12\x13\x14\x15\x16\x17" +
|
||||
"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
|
||||
` !\x22#\$%\x26\x27\(\)\*\x2b,\-\.\/` +
|
||||
`0123456789:;\x3c=\x3e\?` +
|
||||
`\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
|
||||
`\u0008\t\n\u000b\f\r\u000e\u000f` +
|
||||
`\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
|
||||
`\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
|
||||
` !\u0022#\$%\u0026\u0027\(\)\*\u002b,\-\.\/` +
|
||||
`0123456789:;\u003c=\u003e\?` +
|
||||
`@ABCDEFGHIJKLMNO` +
|
||||
`PQRSTUVWXYZ\[\\\]\^_` +
|
||||
"`abcdefghijklmno" +
|
||||
|
@@ -8,6 +8,7 @@ package template_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -124,6 +125,44 @@ func TestNumbers(t *testing.T) {
|
||||
c.mustExecute(c.root, nil, "12.34 7.5")
|
||||
}
|
||||
|
||||
func TestStringsInScriptsWithJsonContentTypeAreCorrectlyEscaped(t *testing.T) {
|
||||
// See #33671 and #37634 for more context on this.
|
||||
tests := []struct{ name, in string }{
|
||||
{"empty", ""},
|
||||
{"invalid", string(rune(-1))},
|
||||
{"null", "\u0000"},
|
||||
{"unit separator", "\u001F"},
|
||||
{"tab", "\t"},
|
||||
{"gt and lt", "<>"},
|
||||
{"quotes", `'"`},
|
||||
{"ASCII letters", "ASCII letters"},
|
||||
{"Unicode", "ʕ⊙ϖ⊙ʔ"},
|
||||
{"Pizza", "🍕"},
|
||||
}
|
||||
const (
|
||||
prefix = `<script type="application/ld+json">`
|
||||
suffix = `</script>`
|
||||
templ = prefix + `"{{.}}"` + suffix
|
||||
)
|
||||
tpl := Must(New("JS string is JSON string").Parse(templ))
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
if err := tpl.Execute(&buf, tt.in); err != nil {
|
||||
t.Fatalf("Cannot render template: %v", err)
|
||||
}
|
||||
trimmed := bytes.TrimSuffix(bytes.TrimPrefix(buf.Bytes(), []byte(prefix)), []byte(suffix))
|
||||
var got string
|
||||
if err := json.Unmarshal(trimmed, &got); err != nil {
|
||||
t.Fatalf("Cannot parse JS string %q as JSON: %v", trimmed[1:len(trimmed)-1], err)
|
||||
}
|
||||
if got != tt.in {
|
||||
t.Errorf("Serialization changed the string value: got %q want %q", got, tt.in)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
t *testing.T
|
||||
root *Template
|
||||
|
Reference in New Issue
Block a user