1
2
3
4
5 package template
6
7 import (
8 "errors"
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
20 {jsCtxRegexp, ";"},
21
22
23
24
25
26
27 {jsCtxRegexp, "}"},
28
29
30 {jsCtxDivOp, ")"},
31 {jsCtxDivOp, "]"},
32
33
34 {jsCtxRegexp, "("},
35 {jsCtxRegexp, "["},
36 {jsCtxRegexp, "{"},
37
38
39 {jsCtxRegexp, "="},
40 {jsCtxRegexp, "+="},
41 {jsCtxRegexp, "*="},
42 {jsCtxRegexp, "*"},
43 {jsCtxRegexp, "!"},
44
45
46 {jsCtxRegexp, "+"},
47 {jsCtxRegexp, "-"},
48
49
50
51
52
53
54 {jsCtxDivOp, "--"},
55 {jsCtxDivOp, "++"},
56 {jsCtxDivOp, "x--"},
57
58
59 {jsCtxRegexp, "x---"},
60
61
62
63 {jsCtxRegexp, "return"},
64 {jsCtxRegexp, "return "},
65 {jsCtxRegexp, "return\t"},
66 {jsCtxRegexp, "return\n"},
67 {jsCtxRegexp, "return\u2028"},
68 {jsCtxRegexp, "yield"},
69
70 {jsCtxDivOp, "x.yield"},
71 {jsCtxDivOp, "x?.yield"},
72 {jsCtxDivOp, "x.\nyield"},
73 {jsCtxDivOp, "x.in"},
74
75
76
77
78
79
80 {jsCtxDivOp, "x"},
81 {jsCtxDivOp, "x "},
82 {jsCtxDivOp, "x\t"},
83 {jsCtxDivOp, "x\n"},
84 {jsCtxDivOp, "x\u2028"},
85 {jsCtxDivOp, "preturn"},
86
87 {jsCtxDivOp, "0"},
88
89 {jsCtxDivOp, "0."},
90
91
92 {jsCtxRegexp, "=\u00A0"},
93 }
94
95 for _, test := range tests {
96 if ctx := nextJSCtx([]byte(test.s), jsCtxRegexp); ctx != test.jsCtx {
97 t.Errorf("%q: want %s got %s", test.s, test.jsCtx, ctx)
98 }
99 if ctx := nextJSCtx([]byte(test.s), jsCtxDivOp); ctx != test.jsCtx {
100 t.Errorf("%q: want %s got %s", test.s, test.jsCtx, ctx)
101 }
102 }
103
104 if nextJSCtx([]byte(" "), jsCtxRegexp) != jsCtxRegexp {
105 t.Error("Blank tokens")
106 }
107
108 if nextJSCtx([]byte(" "), jsCtxDivOp) != jsCtxDivOp {
109 t.Error("Blank tokens")
110 }
111 }
112
113 type jsonErrType struct{}
114
115 func (e *jsonErrType) MarshalJSON() ([]byte, error) {
116 return nil, errors.New("a */ b <script c </script d <!-- e <sCrIpT f </sCrIpT")
117 }
118
119 func TestJSValEscaper(t *testing.T) {
120 tests := []struct {
121 x any
122 js string
123 skipNest bool
124 }{
125 {int(42), " 42 ", false},
126 {uint(42), " 42 ", false},
127 {int16(42), " 42 ", false},
128 {uint16(42), " 42 ", false},
129 {int32(-42), " -42 ", false},
130 {uint32(42), " 42 ", false},
131 {int16(-42), " -42 ", false},
132 {uint16(42), " 42 ", false},
133 {int64(-42), " -42 ", false},
134 {uint64(42), " 42 ", false},
135 {uint64(1) << 53, " 9007199254740992 ", false},
136
137
138 {uint64(1)<<53 + 1, " 9007199254740993 ", false},
139 {float32(1.0), " 1 ", false},
140 {float32(-1.0), " -1 ", false},
141 {float32(0.5), " 0.5 ", false},
142 {float32(-0.5), " -0.5 ", false},
143 {float32(1.0) / float32(256), " 0.00390625 ", false},
144 {float32(0), " 0 ", false},
145 {math.Copysign(0, -1), " -0 ", false},
146 {float64(1.0), " 1 ", false},
147 {float64(-1.0), " -1 ", false},
148 {float64(0.5), " 0.5 ", false},
149 {float64(-0.5), " -0.5 ", false},
150 {float64(0), " 0 ", false},
151 {math.Copysign(0, -1), " -0 ", false},
152 {"", `""`, false},
153 {"foo", `"foo"`, false},
154
155 {"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`, false},
156
157 {"\t\x0b", `"\t\u000b"`, false},
158 {struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`, false},
159 {[]any{}, "[]", false},
160 {[]any{42, "foo", nil}, `[42,"foo",null]`, false},
161 {[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`, false},
162 {"<!--", `"\u003c!--"`, false},
163 {"-->", `"--\u003e"`, false},
164 {"<![CDATA[", `"\u003c![CDATA["`, false},
165 {"]]>", `"]]\u003e"`, false},
166 {"</script", `"\u003c/script"`, false},
167 {"\U0001D11E", "\"\U0001D11E\"", false},
168 {nil, " null ", false},
169 {&jsonErrType{}, " /* json: error calling MarshalJSON for type *template.jsonErrType: a * / b \\x3Cscript c \\x3C/script d \\x3C!-- e \\x3Cscript f \\x3C/script */null ", true},
170 }
171
172 for _, test := range tests {
173 if js := jsValEscaper(test.x); js != test.js {
174 t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
175 }
176 if test.skipNest {
177 continue
178 }
179
180
181 a := []any{test.x}
182 want := "[" + strings.TrimSpace(test.js) + "]"
183 if js := jsValEscaper(a); js != want {
184 t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
185 }
186 }
187 }
188
189 func TestJSStrEscaper(t *testing.T) {
190 tests := []struct {
191 x any
192 esc string
193 }{
194 {"", ``},
195 {"foo", `foo`},
196 {"\u0000", `\u0000`},
197 {"\t", `\t`},
198 {"\n", `\n`},
199 {"\r", `\r`},
200 {"\u2028", `\u2028`},
201 {"\u2029", `\u2029`},
202 {"\\", `\\`},
203 {"\\n", `\\n`},
204 {"foo\r\nbar", `foo\r\nbar`},
205
206 {`"`, `\u0022`},
207 {`'`, `\u0027`},
208
209 {`&`, `\u0026amp;`},
210
211 {"</script>", `\u003c\/script\u003e`},
212 {"<![CDATA[", `\u003c![CDATA[`},
213 {"]]>", `]]\u003e`},
214
215
216
217
218
219
220
221
222
223
224 {"<!--", `\u003c!--`},
225 {"-->", `--\u003e`},
226
227 {"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
228 `\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`,
229 },
230
231 {"foo\xA0bar", "foo\xA0bar"},
232
233 {"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
234 }
235
236 for _, test := range tests {
237 esc := jsStrEscaper(test.x)
238 if esc != test.esc {
239 t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
240 }
241 }
242 }
243
244 func TestJSRegexpEscaper(t *testing.T) {
245 tests := []struct {
246 x any
247 esc string
248 }{
249 {"", `(?:)`},
250 {"foo", `foo`},
251 {"\u0000", `\u0000`},
252 {"\t", `\t`},
253 {"\n", `\n`},
254 {"\r", `\r`},
255 {"\u2028", `\u2028`},
256 {"\u2029", `\u2029`},
257 {"\\", `\\`},
258 {"\\n", `\\n`},
259 {"foo\r\nbar", `foo\r\nbar`},
260
261 {`"`, `\u0022`},
262 {`'`, `\u0027`},
263
264 {`&`, `\u0026amp;`},
265
266 {"</script>", `\u003c\/script\u003e`},
267 {"<![CDATA[", `\u003c!\[CDATA\[`},
268 {"]]>", `\]\]\u003e`},
269
270 {"<!--", `\u003c!\-\-`},
271 {"-->", `\-\-\u003e`},
272 {"*", `\*`},
273 {"+", `\u002b`},
274 {"?", `\?`},
275 {"[](){}", `\[\]\(\)\{\}`},
276 {"$foo|x.y", `\$foo\|x\.y`},
277 {"x^y", `x\^y`},
278 }
279
280 for _, test := range tests {
281 esc := jsRegexpEscaper(test.x)
282 if esc != test.esc {
283 t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
284 }
285 }
286 }
287
288 func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
289 input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
290 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
291 ` !"#$%&'()*+,-./` +
292 `0123456789:;<=>?` +
293 `@ABCDEFGHIJKLMNO` +
294 `PQRSTUVWXYZ[\]^_` +
295 "`abcdefghijklmno" +
296 "pqrstuvwxyz{|}~\x7f" +
297 "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
298
299 tests := []struct {
300 name string
301 escaper func(...any) string
302 escaped string
303 }{
304 {
305 "jsStrEscaper",
306 jsStrEscaper,
307 `\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
308 `\u0008\t\n\u000b\f\r\u000e\u000f` +
309 `\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
310 `\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
311 ` !\u0022#$%\u0026\u0027()*\u002b,-.\/` +
312 `0123456789:;\u003c=\u003e?` +
313 `@ABCDEFGHIJKLMNO` +
314 `PQRSTUVWXYZ[\\]^_` +
315 "\\u0060abcdefghijklmno" +
316 "pqrstuvwxyz{|}~\u007f" +
317 "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
318 },
319 {
320 "jsRegexpEscaper",
321 jsRegexpEscaper,
322 `\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
323 `\u0008\t\n\u000b\f\r\u000e\u000f` +
324 `\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
325 `\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
326 ` !\u0022#\$%\u0026\u0027\(\)\*\u002b,\-\.\/` +
327 `0123456789:;\u003c=\u003e\?` +
328 `@ABCDEFGHIJKLMNO` +
329 `PQRSTUVWXYZ\[\\\]\^_` +
330 "`abcdefghijklmno" +
331 `pqrstuvwxyz\{\|\}~` + "\u007f" +
332 "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
333 },
334 }
335
336 for _, test := range tests {
337 if s := test.escaper(input); s != test.escaped {
338 t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
339 continue
340 }
341
342
343
344 var buf strings.Builder
345 for _, c := range input {
346 buf.WriteString(test.escaper(string(c)))
347 }
348
349 if s := buf.String(); s != test.escaped {
350 t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
351 continue
352 }
353 }
354 }
355
356 func TestIsJsMimeType(t *testing.T) {
357 tests := []struct {
358 in string
359 out bool
360 }{
361 {"application/javascript;version=1.8", true},
362 {"application/javascript;version=1.8;foo=bar", true},
363 {"application/javascript/version=1.8", false},
364 {"text/javascript", true},
365 {"application/json", true},
366 {"application/ld+json", true},
367 {"module", true},
368 }
369
370 for _, test := range tests {
371 if isJSType(test.in) != test.out {
372 t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out)
373 }
374 }
375 }
376
377 func BenchmarkJSValEscaperWithNum(b *testing.B) {
378 for i := 0; i < b.N; i++ {
379 jsValEscaper(3.141592654)
380 }
381 }
382
383 func BenchmarkJSValEscaperWithStr(b *testing.B) {
384 for i := 0; i < b.N; i++ {
385 jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
386 }
387 }
388
389 func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) {
390 for i := 0; i < b.N; i++ {
391 jsValEscaper("The quick, brown fox jumps over the lazy dog")
392 }
393 }
394
395 func BenchmarkJSValEscaperWithObj(b *testing.B) {
396 o := struct {
397 S string
398 N int
399 }{
400 "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
401 42,
402 }
403 for i := 0; i < b.N; i++ {
404 jsValEscaper(o)
405 }
406 }
407
408 func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) {
409 o := struct {
410 S string
411 N int
412 }{
413 "The quick, brown fox jumps over the lazy dog",
414 42,
415 }
416 for i := 0; i < b.N; i++ {
417 jsValEscaper(o)
418 }
419 }
420
421 func BenchmarkJSStrEscaperNoSpecials(b *testing.B) {
422 for i := 0; i < b.N; i++ {
423 jsStrEscaper("The quick, brown fox jumps over the lazy dog.")
424 }
425 }
426
427 func BenchmarkJSStrEscaper(b *testing.B) {
428 for i := 0; i < b.N; i++ {
429 jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
430 }
431 }
432
433 func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) {
434 for i := 0; i < b.N; i++ {
435 jsRegexpEscaper("The quick, brown fox jumps over the lazy dog")
436 }
437 }
438
439 func BenchmarkJSRegexpEscaper(b *testing.B) {
440 for i := 0; i < b.N; i++ {
441 jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
442 }
443 }
444
View as plain text