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	}
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
168func 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
223func 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
267func 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
335func 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
354func BenchmarkJSValEscaperWithNum(b *testing.B) {
355	for i := 0; i < b.N; i++ {
356		jsValEscaper(3.141592654)
357	}
358}
359
360func 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
366func 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
372func 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
385func 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
398func 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
404func 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
410func 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
416func 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