Home | History | Annotate | Download | only in template
      1 // Copyright 2011 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package template
      6 
      7 import (
      8 	"bytes"
      9 	"math"
     10 	"strings"
     11 	"testing"
     12 )
     13 
     14 func TestNextJsCtx(t *testing.T) {
     15 	tests := []struct {
     16 		jsCtx jsCtx
     17 		s     string
     18 	}{
     19 		// Statement terminators precede regexps.
     20 		{jsCtxRegexp, ";"},
     21 		// This is not airtight.
     22 		//     ({ valueOf: function () { return 1 } } / 2)
     23 		// is valid JavaScript but in practice, devs do not do this.
     24 		// A block followed by a statement starting with a RegExp is
     25 		// much more common:
     26 		//     while (x) {...} /foo/.test(x) || panic()
     27 		{jsCtxRegexp, "}"},
     28 		// But member, call, grouping, and array expression terminators
     29 		// precede div ops.
     30 		{jsCtxDivOp, ")"},
     31 		{jsCtxDivOp, "]"},
     32 		// At the start of a primary expression, array, or expression
     33 		// statement, expect a regexp.
     34 		{jsCtxRegexp, "("},
     35 		{jsCtxRegexp, "["},
     36 		{jsCtxRegexp, "{"},
     37 		// Assignment operators precede regexps as do all exclusively
     38 		// prefix and binary operators.
     39 		{jsCtxRegexp, "="},
     40 		{jsCtxRegexp, "+="},
     41 		{jsCtxRegexp, "*="},
     42 		{jsCtxRegexp, "*"},
     43 		{jsCtxRegexp, "!"},
     44 		// Whether the + or - is infix or prefix, it cannot precede a
     45 		// div op.
     46 		{jsCtxRegexp, "+"},
     47 		{jsCtxRegexp, "-"},
     48 		// An incr/decr op precedes a div operator.
     49 		// This is not airtight. In (g = ++/h/i) a regexp follows a
     50 		// pre-increment operator, but in practice devs do not try to
     51 		// increment or decrement regular expressions.
     52 		// (g++/h/i) where ++ is a postfix operator on g is much more
     53 		// common.
     54 		{jsCtxDivOp, "--"},
     55 		{jsCtxDivOp, "++"},
     56 		{jsCtxDivOp, "x--"},
     57 		// When we have many dashes or pluses, then they are grouped
     58 		// left to right.
     59 		{jsCtxRegexp, "x---"}, // A postfix -- then a -.
     60 		// return followed by a slash returns the regexp literal or the
     61 		// slash starts a regexp literal in an expression statement that
     62 		// is dead code.
     63 		{jsCtxRegexp, "return"},
     64 		{jsCtxRegexp, "return "},
     65 		{jsCtxRegexp, "return\t"},
     66 		{jsCtxRegexp, "return\n"},
     67 		{jsCtxRegexp, "return\u2028"},
     68 		// Identifiers can be divided and cannot validly be preceded by
     69 		// a regular expressions. Semicolon insertion cannot happen
     70 		// between an identifier and a regular expression on a new line
     71 		// because the one token lookahead for semicolon insertion has
     72 		// to conclude that it could be a div binary op and treat it as
     73 		// such.
     74 		{jsCtxDivOp, "x"},
     75 		{jsCtxDivOp, "x "},
     76 		{jsCtxDivOp, "x\t"},
     77 		{jsCtxDivOp, "x\n"},
     78 		{jsCtxDivOp, "x\u2028"},
     79 		{jsCtxDivOp, "preturn"},
     80 		// Numbers precede div ops.
     81 		{jsCtxDivOp, "0"},
     82 		// Dots that are part of a number are div preceders.
     83 		{jsCtxDivOp, "0."},
     84 	}
     85 
     86 	for _, test := range tests {
     87 		if nextJSCtx([]byte(test.s), jsCtxRegexp) != test.jsCtx {
     88 			t.Errorf("want %s got %q", test.jsCtx, test.s)
     89 		}
     90 		if nextJSCtx([]byte(test.s), jsCtxDivOp) != test.jsCtx {
     91 			t.Errorf("want %s got %q", test.jsCtx, test.s)
     92 		}
     93 	}
     94 
     95 	if nextJSCtx([]byte("   "), jsCtxRegexp) != jsCtxRegexp {
     96 		t.Error("Blank tokens")
     97 	}
     98 
     99 	if nextJSCtx([]byte("   "), jsCtxDivOp) != jsCtxDivOp {
    100 		t.Error("Blank tokens")
    101 	}
    102 }
    103 
    104 func TestJSValEscaper(t *testing.T) {
    105 	tests := []struct {
    106 		x  interface{}
    107 		js string
    108 	}{
    109 		{int(42), " 42 "},
    110 		{uint(42), " 42 "},
    111 		{int16(42), " 42 "},
    112 		{uint16(42), " 42 "},
    113 		{int32(-42), " -42 "},
    114 		{uint32(42), " 42 "},
    115 		{int16(-42), " -42 "},
    116 		{uint16(42), " 42 "},
    117 		{int64(-42), " -42 "},
    118 		{uint64(42), " 42 "},
    119 		{uint64(1) << 53, " 9007199254740992 "},
    120 		// ulp(1 << 53) > 1 so this loses precision in JS
    121 		// but it is still a representable integer literal.
    122 		{uint64(1)<<53 + 1, " 9007199254740993 "},
    123 		{float32(1.0), " 1 "},
    124 		{float32(-1.0), " -1 "},
    125 		{float32(0.5), " 0.5 "},
    126 		{float32(-0.5), " -0.5 "},
    127 		{float32(1.0) / float32(256), " 0.00390625 "},
    128 		{float32(0), " 0 "},
    129 		{math.Copysign(0, -1), " -0 "},
    130 		{float64(1.0), " 1 "},
    131 		{float64(-1.0), " -1 "},
    132 		{float64(0.5), " 0.5 "},
    133 		{float64(-0.5), " -0.5 "},
    134 		{float64(0), " 0 "},
    135 		{math.Copysign(0, -1), " -0 "},
    136 		{"", `""`},
    137 		{"foo", `"foo"`},
    138 		// Newlines.
    139 		{"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`},
    140 		// "\v" == "v" on IE 6 so use "\x0b" instead.
    141 		{"\t\x0b", `"\t\u000b"`},
    142 		{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
    143 		{[]interface{}{}, "[]"},
    144 		{[]interface{}{42, "foo", nil}, `[42,"foo",null]`},
    145 		{[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
    146 		{"<!--", `"\u003c!--"`},
    147 		{"-->", `"--\u003e"`},
    148 		{"<![CDATA[", `"\u003c![CDATA["`},
    149 		{"]]>", `"]]\u003e"`},
    150 		{"</script", `"\u003c/script"`},
    151 		{"\U0001D11E", "\"\U0001D11E\""}, // or "\uD834\uDD1E"
    152 	}
    153 
    154 	for _, test := range tests {
    155 		if js := jsValEscaper(test.x); js != test.js {
    156 			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
    157 		}
    158 		// Make sure that escaping corner cases are not broken
    159 		// by nesting.
    160 		a := []interface{}{test.x}
    161 		want := "[" + strings.TrimSpace(test.js) + "]"
    162 		if js := jsValEscaper(a); js != want {
    163 			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
    164 		}
    165 	}
    166 }
    167 
    168 func TestJSStrEscaper(t *testing.T) {
    169 	tests := []struct {
    170 		x   interface{}
    171 		esc string
    172 	}{
    173 		{"", ``},
    174 		{"foo", `foo`},
    175 		{"\u0000", `\0`},
    176 		{"\t", `\t`},
    177 		{"\n", `\n`},
    178 		{"\r", `\r`},
    179 		{"\u2028", `\u2028`},
    180 		{"\u2029", `\u2029`},
    181 		{"\\", `\\`},
    182 		{"\\n", `\\n`},
    183 		{"foo\r\nbar", `foo\r\nbar`},
    184 		// Preserve attribute boundaries.
    185 		{`"`, `\x22`},
    186 		{`'`, `\x27`},
    187 		// Allow embedding in HTML without further escaping.
    188 		{`&amp;`, `\x26amp;`},
    189 		// Prevent breaking out of text node and element boundaries.
    190 		{"</script>", `\x3c\/script\x3e`},
    191 		{"<![CDATA[", `\x3c![CDATA[`},
    192 		{"]]>", `]]\x3e`},
    193 		// http://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span
    194 		//   "The text in style, script, title, and textarea elements
    195 		//   must not have an escaping text span start that is not
    196 		//   followed by an escaping text span end."
    197 		// Furthermore, spoofing an escaping text span end could lead
    198 		// to different interpretation of a </script> sequence otherwise
    199 		// masked by the escaping text span, and spoofing a start could
    200 		// allow regular text content to be interpreted as script
    201 		// allowing script execution via a combination of a JS string
    202 		// injection followed by an HTML text injection.
    203 		{"<!--", `\x3c!--`},
    204 		{"-->", `--\x3e`},
    205 		// From http://code.google.com/p/doctype/wiki/ArticleUtf7
    206 		{"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
    207 			`\x2bADw-script\x2bAD4-alert(1)\x2bADw-\/script\x2bAD4-`,
    208 		},
    209 		// Invalid UTF-8 sequence
    210 		{"foo\xA0bar", "foo\xA0bar"},
    211 		// Invalid unicode scalar value.
    212 		{"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
    213 	}
    214 
    215 	for _, test := range tests {
    216 		esc := jsStrEscaper(test.x)
    217 		if esc != test.esc {
    218 			t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
    219 		}
    220 	}
    221 }
    222 
    223 func TestJSRegexpEscaper(t *testing.T) {
    224 	tests := []struct {
    225 		x   interface{}
    226 		esc string
    227 	}{
    228 		{"", `(?:)`},
    229 		{"foo", `foo`},
    230 		{"\u0000", `\0`},
    231 		{"\t", `\t`},
    232 		{"\n", `\n`},
    233 		{"\r", `\r`},
    234 		{"\u2028", `\u2028`},
    235 		{"\u2029", `\u2029`},
    236 		{"\\", `\\`},
    237 		{"\\n", `\\n`},
    238 		{"foo\r\nbar", `foo\r\nbar`},
    239 		// Preserve attribute boundaries.
    240 		{`"`, `\x22`},
    241 		{`'`, `\x27`},
    242 		// Allow embedding in HTML without further escaping.
    243 		{`&amp;`, `\x26amp;`},
    244 		// Prevent breaking out of text node and element boundaries.
    245 		{"</script>", `\x3c\/script\x3e`},
    246 		{"<![CDATA[", `\x3c!\[CDATA\[`},
    247 		{"]]>", `\]\]\x3e`},
    248 		// Escaping text spans.
    249 		{"<!--", `\x3c!\-\-`},
    250 		{"-->", `\-\-\x3e`},
    251 		{"*", `\*`},
    252 		{"+", `\x2b`},
    253 		{"?", `\?`},
    254 		{"[](){}", `\[\]\(\)\{\}`},
    255 		{"$foo|x.y", `\$foo\|x\.y`},
    256 		{"x^y", `x\^y`},
    257 	}
    258 
    259 	for _, test := range tests {
    260 		esc := jsRegexpEscaper(test.x)
    261 		if esc != test.esc {
    262 			t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
    263 		}
    264 	}
    265 }
    266 
    267 func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
    268 	input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
    269 		"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
    270 		` !"#$%&'()*+,-./` +
    271 		`0123456789:;<=>?` +
    272 		`@ABCDEFGHIJKLMNO` +
    273 		`PQRSTUVWXYZ[\]^_` +
    274 		"`abcdefghijklmno" +
    275 		"pqrstuvwxyz{|}~\x7f" +
    276 		"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
    277 
    278 	tests := []struct {
    279 		name    string
    280 		escaper func(...interface{}) string
    281 		escaped string
    282 	}{
    283 		{
    284 			"jsStrEscaper",
    285 			jsStrEscaper,
    286 			"\\0\x01\x02\x03\x04\x05\x06\x07" +
    287 				"\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
    288 				"\x10\x11\x12\x13\x14\x15\x16\x17" +
    289 				"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
    290 				` !\x22#$%\x26\x27()*\x2b,-.\/` +
    291 				`0123456789:;\x3c=\x3e?` +
    292 				`@ABCDEFGHIJKLMNO` +
    293 				`PQRSTUVWXYZ[\\]^_` +
    294 				"`abcdefghijklmno" +
    295 				"pqrstuvwxyz{|}~\x7f" +
    296 				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
    297 		},
    298 		{
    299 			"jsRegexpEscaper",
    300 			jsRegexpEscaper,
    301 			"\\0\x01\x02\x03\x04\x05\x06\x07" +
    302 				"\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
    303 				"\x10\x11\x12\x13\x14\x15\x16\x17" +
    304 				"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
    305 				` !\x22#\$%\x26\x27\(\)\*\x2b,\-\.\/` +
    306 				`0123456789:;\x3c=\x3e\?` +
    307 				`@ABCDEFGHIJKLMNO` +
    308 				`PQRSTUVWXYZ\[\\\]\^_` +
    309 				"`abcdefghijklmno" +
    310 				`pqrstuvwxyz\{\|\}~` + "\u007f" +
    311 				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
    312 		},
    313 	}
    314 
    315 	for _, test := range tests {
    316 		if s := test.escaper(input); s != test.escaped {
    317 			t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
    318 			continue
    319 		}
    320 
    321 		// Escape it rune by rune to make sure that any
    322 		// fast-path checking does not break escaping.
    323 		var buf bytes.Buffer
    324 		for _, c := range input {
    325 			buf.WriteString(test.escaper(string(c)))
    326 		}
    327 
    328 		if s := buf.String(); s != test.escaped {
    329 			t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
    330 			continue
    331 		}
    332 	}
    333 }
    334 
    335 func TestIsJsMimeType(t *testing.T) {
    336 	tests := []struct {
    337 		in  string
    338 		out bool
    339 	}{
    340 		{"application/javascript;version=1.8", true},
    341 		{"application/javascript;version=1.8;foo=bar", true},
    342 		{"application/javascript/version=1.8", false},
    343 		{"text/javascript", true},
    344 		{"application/json", true},
    345 	}
    346 
    347 	for _, test := range tests {
    348 		if isJSType(test.in) != test.out {
    349 			t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out)
    350 		}
    351 	}
    352 }
    353 
    354 func BenchmarkJSValEscaperWithNum(b *testing.B) {
    355 	for i := 0; i < b.N; i++ {
    356 		jsValEscaper(3.141592654)
    357 	}
    358 }
    359 
    360 func BenchmarkJSValEscaperWithStr(b *testing.B) {
    361 	for i := 0; i < b.N; i++ {
    362 		jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
    363 	}
    364 }
    365 
    366 func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) {
    367 	for i := 0; i < b.N; i++ {
    368 		jsValEscaper("The quick, brown fox jumps over the lazy dog")
    369 	}
    370 }
    371 
    372 func BenchmarkJSValEscaperWithObj(b *testing.B) {
    373 	o := struct {
    374 		S string
    375 		N int
    376 	}{
    377 		"The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
    378 		42,
    379 	}
    380 	for i := 0; i < b.N; i++ {
    381 		jsValEscaper(o)
    382 	}
    383 }
    384 
    385 func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) {
    386 	o := struct {
    387 		S string
    388 		N int
    389 	}{
    390 		"The quick, brown fox jumps over the lazy dog",
    391 		42,
    392 	}
    393 	for i := 0; i < b.N; i++ {
    394 		jsValEscaper(o)
    395 	}
    396 }
    397 
    398 func BenchmarkJSStrEscaperNoSpecials(b *testing.B) {
    399 	for i := 0; i < b.N; i++ {
    400 		jsStrEscaper("The quick, brown fox jumps over the lazy dog.")
    401 	}
    402 }
    403 
    404 func BenchmarkJSStrEscaper(b *testing.B) {
    405 	for i := 0; i < b.N; i++ {
    406 		jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
    407 	}
    408 }
    409 
    410 func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) {
    411 	for i := 0; i < b.N; i++ {
    412 		jsRegexpEscaper("The quick, brown fox jumps over the lazy dog")
    413 	}
    414 }
    415 
    416 func BenchmarkJSRegexpEscaper(b *testing.B) {
    417 	for i := 0; i < b.N; i++ {
    418 		jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
    419 	}
    420 }
    421