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		{`&amp;`, `\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		{`&amp;`, `\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