|  | // 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  interface{} | 
|  | 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 "\x0b" instead. | 
|  | {"\t\x0b", `"\t\u000b"`}, | 
|  | {struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`}, | 
|  | {[]interface{}{}, "[]"}, | 
|  | {[]interface{}{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" | 
|  | } | 
|  |  | 
|  | 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 := []interface{}{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   interface{} | 
|  | esc string | 
|  | }{ | 
|  | {"", ``}, | 
|  | {"foo", `foo`}, | 
|  | {"\u0000", `\0`}, | 
|  | {"\t", `\t`}, | 
|  | {"\n", `\n`}, | 
|  | {"\r", `\r`}, | 
|  | {"\u2028", `\u2028`}, | 
|  | {"\u2029", `\u2029`}, | 
|  | {"\\", `\\`}, | 
|  | {"\\n", `\\n`}, | 
|  | {"foo\r\nbar", `foo\r\nbar`}, | 
|  | // Preserve attribute boundaries. | 
|  | {`"`, `\x22`}, | 
|  | {`'`, `\x27`}, | 
|  | // Allow embedding in HTML without further escaping. | 
|  | {`&`, `\x26amp;`}, | 
|  | // Prevent breaking out of text node and element boundaries. | 
|  | {"</script>", `\x3c\/script\x3e`}, | 
|  | {"<![CDATA[", `\x3c![CDATA[`}, | 
|  | {"]]>", `]]\x3e`}, | 
|  | // http://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. | 
|  | {"<!--", `\x3c!--`}, | 
|  | {"-->", `--\x3e`}, | 
|  | // From http://code.google.com/p/doctype/wiki/ArticleUtf7 | 
|  | {"+ADw-script+AD4-alert(1)+ADw-/script+AD4-", | 
|  | `\x2bADw-script\x2bAD4-alert(1)\x2bADw-\/script\x2bAD4-`, | 
|  | }, | 
|  | // 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   interface{} | 
|  | esc string | 
|  | }{ | 
|  | {"", `(?:)`}, | 
|  | {"foo", `foo`}, | 
|  | {"\u0000", `\0`}, | 
|  | {"\t", `\t`}, | 
|  | {"\n", `\n`}, | 
|  | {"\r", `\r`}, | 
|  | {"\u2028", `\u2028`}, | 
|  | {"\u2029", `\u2029`}, | 
|  | {"\\", `\\`}, | 
|  | {"\\n", `\\n`}, | 
|  | {"foo\r\nbar", `foo\r\nbar`}, | 
|  | // Preserve attribute boundaries. | 
|  | {`"`, `\x22`}, | 
|  | {`'`, `\x27`}, | 
|  | // Allow embedding in HTML without further escaping. | 
|  | {`&`, `\x26amp;`}, | 
|  | // Prevent breaking out of text node and element boundaries. | 
|  | {"</script>", `\x3c\/script\x3e`}, | 
|  | {"<![CDATA[", `\x3c!\[CDATA\[`}, | 
|  | {"]]>", `\]\]\x3e`}, | 
|  | // Escaping text spans. | 
|  | {"<!--", `\x3c!\-\-`}, | 
|  | {"-->", `\-\-\x3e`}, | 
|  | {"*", `\*`}, | 
|  | {"+", `\x2b`}, | 
|  | {"?", `\?`}, | 
|  | {"[](){}", `\[\]\(\)\{\}`}, | 
|  | {"$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(...interface{}) string | 
|  | escaped string | 
|  | }{ | 
|  | { | 
|  | "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?` + | 
|  | `@ABCDEFGHIJKLMNO` + | 
|  | `PQRSTUVWXYZ[\\]^_` + | 
|  | "`abcdefghijklmno" + | 
|  | "pqrstuvwxyz{|}~\x7f" + | 
|  | "\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\?` + | 
|  | `@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}, | 
|  | } | 
|  |  | 
|  | 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>") | 
|  | } | 
|  | } |