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