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 5package template 6 7import ( 8 "bytes" 9 "math" 10 "strings" 11 "testing" 12) 13 14func 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 104func 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 {nil, " null "}, 153 } 154 155 for _, test := range tests { 156 if js := jsValEscaper(test.x); js != test.js { 157 t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js) 158 } 159 // Make sure that escaping corner cases are not broken 160 // by nesting. 161 a := []interface{}{test.x} 162 want := "[" + strings.TrimSpace(test.js) + "]" 163 if js := jsValEscaper(a); js != want { 164 t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js) 165 } 166 } 167} 168 169func TestJSStrEscaper(t *testing.T) { 170 tests := []struct { 171 x interface{} 172 esc string 173 }{ 174 {"", ``}, 175 {"foo", `foo`}, 176 {"\u0000", `\0`}, 177 {"\t", `\t`}, 178 {"\n", `\n`}, 179 {"\r", `\r`}, 180 {"\u2028", `\u2028`}, 181 {"\u2029", `\u2029`}, 182 {"\\", `\\`}, 183 {"\\n", `\\n`}, 184 {"foo\r\nbar", `foo\r\nbar`}, 185 // Preserve attribute boundaries. 186 {`"`, `\x22`}, 187 {`'`, `\x27`}, 188 // Allow embedding in HTML without further escaping. 189 {`&`, `\x26amp;`}, 190 // Prevent breaking out of text node and element boundaries. 191 {"</script>", `\x3c\/script\x3e`}, 192 {"<![CDATA[", `\x3c![CDATA[`}, 193 {"]]>", `]]\x3e`}, 194 // https://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span 195 // "The text in style, script, title, and textarea elements 196 // must not have an escaping text span start that is not 197 // followed by an escaping text span end." 198 // Furthermore, spoofing an escaping text span end could lead 199 // to different interpretation of a </script> sequence otherwise 200 // masked by the escaping text span, and spoofing a start could 201 // allow regular text content to be interpreted as script 202 // allowing script execution via a combination of a JS string 203 // injection followed by an HTML text injection. 204 {"<!--", `\x3c!--`}, 205 {"-->", `--\x3e`}, 206 // From https://code.google.com/p/doctype/wiki/ArticleUtf7 207 {"+ADw-script+AD4-alert(1)+ADw-/script+AD4-", 208 `\x2bADw-script\x2bAD4-alert(1)\x2bADw-\/script\x2bAD4-`, 209 }, 210 // Invalid UTF-8 sequence 211 {"foo\xA0bar", "foo\xA0bar"}, 212 // Invalid unicode scalar value. 213 {"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"}, 214 } 215 216 for _, test := range tests { 217 esc := jsStrEscaper(test.x) 218 if esc != test.esc { 219 t.Errorf("%q: want %q got %q", test.x, test.esc, esc) 220 } 221 } 222} 223 224func TestJSRegexpEscaper(t *testing.T) { 225 tests := []struct { 226 x interface{} 227 esc string 228 }{ 229 {"", `(?:)`}, 230 {"foo", `foo`}, 231 {"\u0000", `\0`}, 232 {"\t", `\t`}, 233 {"\n", `\n`}, 234 {"\r", `\r`}, 235 {"\u2028", `\u2028`}, 236 {"\u2029", `\u2029`}, 237 {"\\", `\\`}, 238 {"\\n", `\\n`}, 239 {"foo\r\nbar", `foo\r\nbar`}, 240 // Preserve attribute boundaries. 241 {`"`, `\x22`}, 242 {`'`, `\x27`}, 243 // Allow embedding in HTML without further escaping. 244 {`&`, `\x26amp;`}, 245 // Prevent breaking out of text node and element boundaries. 246 {"</script>", `\x3c\/script\x3e`}, 247 {"<![CDATA[", `\x3c!\[CDATA\[`}, 248 {"]]>", `\]\]\x3e`}, 249 // Escaping text spans. 250 {"<!--", `\x3c!\-\-`}, 251 {"-->", `\-\-\x3e`}, 252 {"*", `\*`}, 253 {"+", `\x2b`}, 254 {"?", `\?`}, 255 {"[](){}", `\[\]\(\)\{\}`}, 256 {"$foo|x.y", `\$foo\|x\.y`}, 257 {"x^y", `x\^y`}, 258 } 259 260 for _, test := range tests { 261 esc := jsRegexpEscaper(test.x) 262 if esc != test.esc { 263 t.Errorf("%q: want %q got %q", test.x, test.esc, esc) 264 } 265 } 266} 267 268func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) { 269 input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + 270 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 271 ` !"#$%&'()*+,-./` + 272 `0123456789:;<=>?` + 273 `@ABCDEFGHIJKLMNO` + 274 `PQRSTUVWXYZ[\]^_` + 275 "`abcdefghijklmno" + 276 "pqrstuvwxyz{|}~\x7f" + 277 "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E") 278 279 tests := []struct { 280 name string 281 escaper func(...interface{}) string 282 escaped string 283 }{ 284 { 285 "jsStrEscaper", 286 jsStrEscaper, 287 "\\0\x01\x02\x03\x04\x05\x06\x07" + 288 "\x08\\t\\n\\x0b\\f\\r\x0E\x0F" + 289 "\x10\x11\x12\x13\x14\x15\x16\x17" + 290 "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 291 ` !\x22#$%\x26\x27()*\x2b,-.\/` + 292 `0123456789:;\x3c=\x3e?` + 293 `@ABCDEFGHIJKLMNO` + 294 `PQRSTUVWXYZ[\\]^_` + 295 "`abcdefghijklmno" + 296 "pqrstuvwxyz{|}~\x7f" + 297 "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E", 298 }, 299 { 300 "jsRegexpEscaper", 301 jsRegexpEscaper, 302 "\\0\x01\x02\x03\x04\x05\x06\x07" + 303 "\x08\\t\\n\\x0b\\f\\r\x0E\x0F" + 304 "\x10\x11\x12\x13\x14\x15\x16\x17" + 305 "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 306 ` !\x22#\$%\x26\x27\(\)\*\x2b,\-\.\/` + 307 `0123456789:;\x3c=\x3e\?` + 308 `@ABCDEFGHIJKLMNO` + 309 `PQRSTUVWXYZ\[\\\]\^_` + 310 "`abcdefghijklmno" + 311 `pqrstuvwxyz\{\|\}~` + "\u007f" + 312 "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E", 313 }, 314 } 315 316 for _, test := range tests { 317 if s := test.escaper(input); s != test.escaped { 318 t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s) 319 continue 320 } 321 322 // Escape it rune by rune to make sure that any 323 // fast-path checking does not break escaping. 324 var buf bytes.Buffer 325 for _, c := range input { 326 buf.WriteString(test.escaper(string(c))) 327 } 328 329 if s := buf.String(); s != test.escaped { 330 t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s) 331 continue 332 } 333 } 334} 335 336func TestIsJsMimeType(t *testing.T) { 337 tests := []struct { 338 in string 339 out bool 340 }{ 341 {"application/javascript;version=1.8", true}, 342 {"application/javascript;version=1.8;foo=bar", true}, 343 {"application/javascript/version=1.8", false}, 344 {"text/javascript", true}, 345 {"application/json", true}, 346 {"application/ld+json", true}, 347 {"module", true}, 348 } 349 350 for _, test := range tests { 351 if isJSType(test.in) != test.out { 352 t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out) 353 } 354 } 355} 356 357func BenchmarkJSValEscaperWithNum(b *testing.B) { 358 for i := 0; i < b.N; i++ { 359 jsValEscaper(3.141592654) 360 } 361} 362 363func BenchmarkJSValEscaperWithStr(b *testing.B) { 364 for i := 0; i < b.N; i++ { 365 jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") 366 } 367} 368 369func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) { 370 for i := 0; i < b.N; i++ { 371 jsValEscaper("The quick, brown fox jumps over the lazy dog") 372 } 373} 374 375func BenchmarkJSValEscaperWithObj(b *testing.B) { 376 o := struct { 377 S string 378 N int 379 }{ 380 "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028", 381 42, 382 } 383 for i := 0; i < b.N; i++ { 384 jsValEscaper(o) 385 } 386} 387 388func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) { 389 o := struct { 390 S string 391 N int 392 }{ 393 "The quick, brown fox jumps over the lazy dog", 394 42, 395 } 396 for i := 0; i < b.N; i++ { 397 jsValEscaper(o) 398 } 399} 400 401func BenchmarkJSStrEscaperNoSpecials(b *testing.B) { 402 for i := 0; i < b.N; i++ { 403 jsStrEscaper("The quick, brown fox jumps over the lazy dog.") 404 } 405} 406 407func BenchmarkJSStrEscaper(b *testing.B) { 408 for i := 0; i < b.N; i++ { 409 jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") 410 } 411} 412 413func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) { 414 for i := 0; i < b.N; i++ { 415 jsRegexpEscaper("The quick, brown fox jumps over the lazy dog") 416 } 417} 418 419func BenchmarkJSRegexpEscaper(b *testing.B) { 420 for i := 0; i < b.N; i++ { 421 jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") 422 } 423} 424