123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- // Copyright 2011 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package template
- import (
- "bytes"
- "math"
- "strings"
- "testing"
- )
- func TestNextJsCtx(t *testing.T) {
- tests := []struct {
- jsCtx jsCtx
- s string
- }{
- // Statement terminators precede regexps.
- {jsCtxRegexp, ";"},
- // This is not airtight.
- // ({ valueOf: function () { return 1 } } / 2)
- // is valid JavaScript but in practice, devs do not do this.
- // A block followed by a statement starting with a RegExp is
- // much more common:
- // while (x) {...} /foo/.test(x) || panic()
- {jsCtxRegexp, "}"},
- // But member, call, grouping, and array expression terminators
- // precede div ops.
- {jsCtxDivOp, ")"},
- {jsCtxDivOp, "]"},
- // At the start of a primary expression, array, or expression
- // statement, expect a regexp.
- {jsCtxRegexp, "("},
- {jsCtxRegexp, "["},
- {jsCtxRegexp, "{"},
- // Assignment operators precede regexps as do all exclusively
- // prefix and binary operators.
- {jsCtxRegexp, "="},
- {jsCtxRegexp, "+="},
- {jsCtxRegexp, "*="},
- {jsCtxRegexp, "*"},
- {jsCtxRegexp, "!"},
- // Whether the + or - is infix or prefix, it cannot precede a
- // div op.
- {jsCtxRegexp, "+"},
- {jsCtxRegexp, "-"},
- // An incr/decr op precedes a div operator.
- // This is not airtight. In (g = ++/h/i) a regexp follows a
- // pre-increment operator, but in practice devs do not try to
- // increment or decrement regular expressions.
- // (g++/h/i) where ++ is a postfix operator on g is much more
- // common.
- {jsCtxDivOp, "--"},
- {jsCtxDivOp, "++"},
- {jsCtxDivOp, "x--"},
- // When we have many dashes or pluses, then they are grouped
- // left to right.
- {jsCtxRegexp, "x---"}, // A postfix -- then a -.
- // return followed by a slash returns the regexp literal or the
- // slash starts a regexp literal in an expression statement that
- // is dead code.
- {jsCtxRegexp, "return"},
- {jsCtxRegexp, "return "},
- {jsCtxRegexp, "return\t"},
- {jsCtxRegexp, "return\n"},
- {jsCtxRegexp, "return\u2028"},
- // Identifiers can be divided and cannot validly be preceded by
- // a regular expressions. Semicolon insertion cannot happen
- // between an identifier and a regular expression on a new line
- // because the one token lookahead for semicolon insertion has
- // to conclude that it could be a div binary op and treat it as
- // such.
- {jsCtxDivOp, "x"},
- {jsCtxDivOp, "x "},
- {jsCtxDivOp, "x\t"},
- {jsCtxDivOp, "x\n"},
- {jsCtxDivOp, "x\u2028"},
- {jsCtxDivOp, "preturn"},
- // Numbers precede div ops.
- {jsCtxDivOp, "0"},
- // Dots that are part of a number are div preceders.
- {jsCtxDivOp, "0."},
- }
- for _, test := range tests {
- if nextJSCtx([]byte(test.s), jsCtxRegexp) != test.jsCtx {
- t.Errorf("want %s got %q", test.jsCtx, test.s)
- }
- if nextJSCtx([]byte(test.s), jsCtxDivOp) != test.jsCtx {
- t.Errorf("want %s got %q", test.jsCtx, test.s)
- }
- }
- if nextJSCtx([]byte(" "), jsCtxRegexp) != jsCtxRegexp {
- t.Error("Blank tokens")
- }
- if nextJSCtx([]byte(" "), jsCtxDivOp) != jsCtxDivOp {
- t.Error("Blank tokens")
- }
- }
- func TestJSValEscaper(t *testing.T) {
- tests := []struct {
- x any
- js string
- }{
- {int(42), " 42 "},
- {uint(42), " 42 "},
- {int16(42), " 42 "},
- {uint16(42), " 42 "},
- {int32(-42), " -42 "},
- {uint32(42), " 42 "},
- {int16(-42), " -42 "},
- {uint16(42), " 42 "},
- {int64(-42), " -42 "},
- {uint64(42), " 42 "},
- {uint64(1) << 53, " 9007199254740992 "},
- // ulp(1 << 53) > 1 so this loses precision in JS
- // but it is still a representable integer literal.
- {uint64(1)<<53 + 1, " 9007199254740993 "},
- {float32(1.0), " 1 "},
- {float32(-1.0), " -1 "},
- {float32(0.5), " 0.5 "},
- {float32(-0.5), " -0.5 "},
- {float32(1.0) / float32(256), " 0.00390625 "},
- {float32(0), " 0 "},
- {math.Copysign(0, -1), " -0 "},
- {float64(1.0), " 1 "},
- {float64(-1.0), " -1 "},
- {float64(0.5), " 0.5 "},
- {float64(-0.5), " -0.5 "},
- {float64(0), " 0 "},
- {math.Copysign(0, -1), " -0 "},
- {"", `""`},
- {"foo", `"foo"`},
- // Newlines.
- {"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`},
- // "\v" == "v" on IE 6 so use "\u000b" instead.
- {"\t\x0b", `"\t\u000b"`},
- {struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
- {[]any{}, "[]"},
- {[]any{42, "foo", nil}, `[42,"foo",null]`},
- {[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
- {"<!--", `"\u003c!--"`},
- {"-->", `"--\u003e"`},
- {"<![CDATA[", `"\u003c![CDATA["`},
- {"]]>", `"]]\u003e"`},
- {"</script", `"\u003c/script"`},
- {"\U0001D11E", "\"\U0001D11E\""}, // or "\uD834\uDD1E"
- {nil, " null "},
- }
- for _, test := range tests {
- if js := jsValEscaper(test.x); js != test.js {
- t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
- }
- // Make sure that escaping corner cases are not broken
- // by nesting.
- a := []any{test.x}
- want := "[" + strings.TrimSpace(test.js) + "]"
- if js := jsValEscaper(a); js != want {
- t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
- }
- }
- }
- func TestJSStrEscaper(t *testing.T) {
- tests := []struct {
- x any
- esc string
- }{
- {"", ``},
- {"foo", `foo`},
- {"\u0000", `\u0000`},
- {"\t", `\t`},
- {"\n", `\n`},
- {"\r", `\r`},
- {"\u2028", `\u2028`},
- {"\u2029", `\u2029`},
- {"\\", `\\`},
- {"\\n", `\\n`},
- {"foo\r\nbar", `foo\r\nbar`},
- // Preserve attribute boundaries.
- {`"`, `\u0022`},
- {`'`, `\u0027`},
- // Allow embedding in HTML without further escaping.
- {`&`, `\u0026amp;`},
- // Prevent breaking out of text node and element boundaries.
- {"</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
- // followed by an escaping text span end."
- // Furthermore, spoofing an escaping text span end could lead
- // to different interpretation of a </script> sequence otherwise
- // masked by the escaping text span, and spoofing a start could
- // 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.
- {"<!--", `\u003c!--`},
- {"-->", `--\u003e`},
- // From https://code.google.com/p/doctype/wiki/ArticleUtf7
- {"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
- `\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`,
- },
- // Invalid UTF-8 sequence
- {"foo\xA0bar", "foo\xA0bar"},
- // Invalid unicode scalar value.
- {"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
- }
- for _, test := range tests {
- esc := jsStrEscaper(test.x)
- if esc != test.esc {
- t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
- }
- }
- }
- func TestJSRegexpEscaper(t *testing.T) {
- tests := []struct {
- x any
- esc string
- }{
- {"", `(?:)`},
- {"foo", `foo`},
- {"\u0000", `\u0000`},
- {"\t", `\t`},
- {"\n", `\n`},
- {"\r", `\r`},
- {"\u2028", `\u2028`},
- {"\u2029", `\u2029`},
- {"\\", `\\`},
- {"\\n", `\\n`},
- {"foo\r\nbar", `foo\r\nbar`},
- // Preserve attribute boundaries.
- {`"`, `\u0022`},
- {`'`, `\u0027`},
- // Allow embedding in HTML without further escaping.
- {`&`, `\u0026amp;`},
- // Prevent breaking out of text node and element boundaries.
- {"</script>", `\u003c\/script\u003e`},
- {"<![CDATA[", `\u003c!\[CDATA\[`},
- {"]]>", `\]\]\u003e`},
- // Escaping text spans.
- {"<!--", `\u003c!\-\-`},
- {"-->", `\-\-\u003e`},
- {"*", `\*`},
- {"+", `\u002b`},
- {"?", `\?`},
- {"[](){}", `\[\]\(\)\{\}`},
- {"$foo|x.y", `\$foo\|x\.y`},
- {"x^y", `x\^y`},
- }
- for _, test := range tests {
- esc := jsRegexpEscaper(test.x)
- if esc != test.esc {
- t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
- }
- }
- }
- func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
- input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
- "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
- ` !"#$%&'()*+,-./` +
- `0123456789:;<=>?` +
- `@ABCDEFGHIJKLMNO` +
- `PQRSTUVWXYZ[\]^_` +
- "`abcdefghijklmno" +
- "pqrstuvwxyz{|}~\x7f" +
- "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
- tests := []struct {
- name string
- escaper func(...any) string
- escaped string
- }{
- {
- "jsStrEscaper",
- jsStrEscaper,
- `\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{|}~\u007f" +
- "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
- },
- {
- "jsRegexpEscaper",
- jsRegexpEscaper,
- `\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\{\|\}~` + "\u007f" +
- "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
- },
- }
- for _, test := range tests {
- if s := test.escaper(input); s != test.escaped {
- t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
- continue
- }
- // Escape it rune by rune to make sure that any
- // fast-path checking does not break escaping.
- var buf bytes.Buffer
- for _, c := range input {
- buf.WriteString(test.escaper(string(c)))
- }
- if s := buf.String(); s != test.escaped {
- t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
- continue
- }
- }
- }
- func TestIsJsMimeType(t *testing.T) {
- tests := []struct {
- in string
- out bool
- }{
- {"application/javascript;version=1.8", true},
- {"application/javascript;version=1.8;foo=bar", true},
- {"application/javascript/version=1.8", false},
- {"text/javascript", true},
- {"application/json", true},
- {"application/ld+json", true},
- {"module", true},
- }
- for _, test := range tests {
- if isJSType(test.in) != test.out {
- t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out)
- }
- }
- }
- func BenchmarkJSValEscaperWithNum(b *testing.B) {
- for i := 0; i < b.N; i++ {
- jsValEscaper(3.141592654)
- }
- }
- func BenchmarkJSValEscaperWithStr(b *testing.B) {
- for i := 0; i < b.N; i++ {
- jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
- }
- }
- func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) {
- for i := 0; i < b.N; i++ {
- jsValEscaper("The quick, brown fox jumps over the lazy dog")
- }
- }
- func BenchmarkJSValEscaperWithObj(b *testing.B) {
- o := struct {
- S string
- N int
- }{
- "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
- 42,
- }
- for i := 0; i < b.N; i++ {
- jsValEscaper(o)
- }
- }
- func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) {
- o := struct {
- S string
- N int
- }{
- "The quick, brown fox jumps over the lazy dog",
- 42,
- }
- for i := 0; i < b.N; i++ {
- jsValEscaper(o)
- }
- }
- func BenchmarkJSStrEscaperNoSpecials(b *testing.B) {
- for i := 0; i < b.N; i++ {
- jsStrEscaper("The quick, brown fox jumps over the lazy dog.")
- }
- }
- func BenchmarkJSStrEscaper(b *testing.B) {
- for i := 0; i < b.N; i++ {
- jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
- }
- }
- func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) {
- for i := 0; i < b.N; i++ {
- jsRegexpEscaper("The quick, brown fox jumps over the lazy dog")
- }
- }
- func BenchmarkJSRegexpEscaper(b *testing.B) {
- for i := 0; i < b.N; i++ {
- jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
- }
- }
|