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.
14 func TestNextJsCtx(t
*testing
.T
) {
19 // Statement terminators precede regexps.
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
26 // while (x) {...} /foo/.test(x) || panic()
28 // But member, call, grouping, and array expression terminators
32 // At the start of a primary expression, array, or expression
33 // statement, expect a regexp.
37 // Assignment operators precede regexps as do all exclusively
38 // prefix and binary operators.
44 // Whether the + or - is infix or prefix, it cannot precede a
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
57 // When we have many dashes or pluses, then they are grouped
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
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
78 {jsCtxDivOp
, "x\u2028"},
79 {jsCtxDivOp
, "preturn"},
80 // Numbers precede div ops.
82 // Dots that are part of a number are div preceders.
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
)
90 if nextJSCtx([]byte(test
.s
), jsCtxDivOp
) != test
.jsCtx
{
91 t
.Errorf("want %s got %q", test
.jsCtx
, test
.s
)
95 if nextJSCtx([]byte(" "), jsCtxRegexp
) != jsCtxRegexp
{
96 t
.Error("Blank tokens")
99 if nextJSCtx([]byte(" "), jsCtxDivOp
) != jsCtxDivOp
{
100 t
.Error("Blank tokens")
104 func TestJSValEscaper(t
*testing
.T
) {
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 "},
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 "},
135 {math
.Copysign(0, -1), " -0 "},
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"
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
)
158 // Make sure that escaping corner cases are not broken
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
)
168 func TestJSStrEscaper(t
*testing
.T
) {
179 {"\u2028", `\u2028`},
180 {"\u2029", `\u2029`},
183 {"foo\r\nbar", `foo\r\nbar`},
184 // Preserve attribute boundaries.
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[`},
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.
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-`,
209 // Invalid UTF-8 sequence
210 {"foo\xA0bar", "foo\xA0bar"},
211 // Invalid unicode scalar value.
212 {"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
215 for _
, test
:= range tests
{
216 esc
:= jsStrEscaper(test
.x
)
218 t
.Errorf("%q: want %q got %q", test
.x
, test
.esc
, esc
)
223 func TestJSRegexpEscaper(t
*testing
.T
) {
234 {"\u2028", `\u2028`},
235 {"\u2029", `\u2029`},
238 {"foo\r\nbar", `foo\r\nbar`},
239 // Preserve attribute boundaries.
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\[`},
248 // Escaping text spans.
249 {"<!--", `\x3c!\-\-`},
254 {"[](){}", `\[\]\(\)\{\}`},
255 {"$foo|x.y", `\$foo\|x\.y`},
259 for _
, test
:= range tests
{
260 esc
:= jsRegexpEscaper(test
.x
)
262 t
.Errorf("%q: want %q got %q", test
.x
, test
.esc
, esc
)
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" +
275 "pqrstuvwxyz{|}~\x7f" +
276 "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
280 escaper
func(...interface{}) string
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?` +
293 `PQRSTUVWXYZ[\\]^_` +
295 "pqrstuvwxyz{|}~\x7f" +
296 "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
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\?` +
308 `PQRSTUVWXYZ\[\\\]\^_` +
310 `pqrstuvwxyz\{\|\}~` + "\u007f" +
311 "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
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
)
321 // Escape it rune by rune to make sure that any
322 // fast-path checking does not break escaping.
324 for _
, c
:= range input
{
325 buf
.WriteString(test
.escaper(string(c
)))
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
)
335 func TestIsJsMimeType(t
*testing
.T
) {
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},
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
)
354 func BenchmarkJSValEscaperWithNum(b
*testing
.B
) {
355 for i
:= 0; i
< b
.N
; i
++ {
356 jsValEscaper(3.141592654)
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>")
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")
372 func BenchmarkJSValEscaperWithObj(b
*testing
.B
) {
377 "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
380 for i
:= 0; i
< b
.N
; i
++ {
385 func BenchmarkJSValEscaperWithObjNoSpecials(b
*testing
.B
) {
390 "The quick, brown fox jumps over the lazy dog",
393 for i
:= 0; i
< b
.N
; i
++ {
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.")
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>")
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")
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>")