github.com/huandu/go@v0.0.0-20151114150818-04e615e41150/src/html/template/js_test.go (about) 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 {`&`, `\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 {`&`, `\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 BenchmarkJSValEscaperWithNum(b *testing.B) { 336 for i := 0; i < b.N; i++ { 337 jsValEscaper(3.141592654) 338 } 339 } 340 341 func BenchmarkJSValEscaperWithStr(b *testing.B) { 342 for i := 0; i < b.N; i++ { 343 jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") 344 } 345 } 346 347 func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) { 348 for i := 0; i < b.N; i++ { 349 jsValEscaper("The quick, brown fox jumps over the lazy dog") 350 } 351 } 352 353 func BenchmarkJSValEscaperWithObj(b *testing.B) { 354 o := struct { 355 S string 356 N int 357 }{ 358 "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028", 359 42, 360 } 361 for i := 0; i < b.N; i++ { 362 jsValEscaper(o) 363 } 364 } 365 366 func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) { 367 o := struct { 368 S string 369 N int 370 }{ 371 "The quick, brown fox jumps over the lazy dog", 372 42, 373 } 374 for i := 0; i < b.N; i++ { 375 jsValEscaper(o) 376 } 377 } 378 379 func BenchmarkJSStrEscaperNoSpecials(b *testing.B) { 380 for i := 0; i < b.N; i++ { 381 jsStrEscaper("The quick, brown fox jumps over the lazy dog.") 382 } 383 } 384 385 func BenchmarkJSStrEscaper(b *testing.B) { 386 for i := 0; i < b.N; i++ { 387 jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") 388 } 389 } 390 391 func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) { 392 for i := 0; i < b.N; i++ { 393 jsRegexpEscaper("The quick, brown fox jumps over the lazy dog") 394 } 395 } 396 397 func BenchmarkJSRegexpEscaper(b *testing.B) { 398 for i := 0; i < b.N; i++ { 399 jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") 400 } 401 }