github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/tpl/internal/go_templates/htmltemplate/js_test.go (about) 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 5 //go:build go1.13 && !windows 6 // +build go1.13,!windows 7 8 package template 9 10 import ( 11 "math" 12 "strings" 13 "testing" 14 ) 15 16 func TestNextJsCtx(t *testing.T) { 17 tests := []struct { 18 jsCtx jsCtx 19 s string 20 }{ 21 // Statement terminators precede regexps. 22 {jsCtxRegexp, ";"}, 23 // This is not airtight. 24 // ({ valueOf: function () { return 1 } } / 2) 25 // is valid JavaScript but in practice, devs do not do this. 26 // A block followed by a statement starting with a RegExp is 27 // much more common: 28 // while (x) {...} /foo/.test(x) || panic() 29 {jsCtxRegexp, "}"}, 30 // But member, call, grouping, and array expression terminators 31 // precede div ops. 32 {jsCtxDivOp, ")"}, 33 {jsCtxDivOp, "]"}, 34 // At the start of a primary expression, array, or expression 35 // statement, expect a regexp. 36 {jsCtxRegexp, "("}, 37 {jsCtxRegexp, "["}, 38 {jsCtxRegexp, "{"}, 39 // Assignment operators precede regexps as do all exclusively 40 // prefix and binary operators. 41 {jsCtxRegexp, "="}, 42 {jsCtxRegexp, "+="}, 43 {jsCtxRegexp, "*="}, 44 {jsCtxRegexp, "*"}, 45 {jsCtxRegexp, "!"}, 46 // Whether the + or - is infix or prefix, it cannot precede a 47 // div op. 48 {jsCtxRegexp, "+"}, 49 {jsCtxRegexp, "-"}, 50 // An incr/decr op precedes a div operator. 51 // This is not airtight. In (g = ++/h/i) a regexp follows a 52 // pre-increment operator, but in practice devs do not try to 53 // increment or decrement regular expressions. 54 // (g++/h/i) where ++ is a postfix operator on g is much more 55 // common. 56 {jsCtxDivOp, "--"}, 57 {jsCtxDivOp, "++"}, 58 {jsCtxDivOp, "x--"}, 59 // When we have many dashes or pluses, then they are grouped 60 // left to right. 61 {jsCtxRegexp, "x---"}, // A postfix -- then a -. 62 // return followed by a slash returns the regexp literal or the 63 // slash starts a regexp literal in an expression statement that 64 // is dead code. 65 {jsCtxRegexp, "return"}, 66 {jsCtxRegexp, "return "}, 67 {jsCtxRegexp, "return\t"}, 68 {jsCtxRegexp, "return\n"}, 69 {jsCtxRegexp, "return\u2028"}, 70 // Identifiers can be divided and cannot validly be preceded by 71 // a regular expressions. Semicolon insertion cannot happen 72 // between an identifier and a regular expression on a new line 73 // because the one token lookahead for semicolon insertion has 74 // to conclude that it could be a div binary op and treat it as 75 // such. 76 {jsCtxDivOp, "x"}, 77 {jsCtxDivOp, "x "}, 78 {jsCtxDivOp, "x\t"}, 79 {jsCtxDivOp, "x\n"}, 80 {jsCtxDivOp, "x\u2028"}, 81 {jsCtxDivOp, "preturn"}, 82 // Numbers precede div ops. 83 {jsCtxDivOp, "0"}, 84 // Dots that are part of a number are div preceders. 85 {jsCtxDivOp, "0."}, 86 // Some JS interpreters treat NBSP as a normal space, so 87 // we must too in order to properly escape things. 88 {jsCtxRegexp, "=\u00A0"}, 89 } 90 91 for _, test := range tests { 92 if ctx := nextJSCtx([]byte(test.s), jsCtxRegexp); ctx != test.jsCtx { 93 t.Errorf("%q: want %s got %s", test.s, test.jsCtx, ctx) 94 } 95 if ctx := nextJSCtx([]byte(test.s), jsCtxDivOp); ctx != test.jsCtx { 96 t.Errorf("%q: want %s got %s", test.s, test.jsCtx, ctx) 97 } 98 } 99 100 if nextJSCtx([]byte(" "), jsCtxRegexp) != jsCtxRegexp { 101 t.Error("Blank tokens") 102 } 103 104 if nextJSCtx([]byte(" "), jsCtxDivOp) != jsCtxDivOp { 105 t.Error("Blank tokens") 106 } 107 } 108 109 func TestJSValEscaper(t *testing.T) { 110 tests := []struct { 111 x any 112 js string 113 }{ 114 {int(42), " 42 "}, 115 {uint(42), " 42 "}, 116 {int16(42), " 42 "}, 117 {uint16(42), " 42 "}, 118 {int32(-42), " -42 "}, 119 {uint32(42), " 42 "}, 120 {int16(-42), " -42 "}, 121 {uint16(42), " 42 "}, 122 {int64(-42), " -42 "}, 123 {uint64(42), " 42 "}, 124 {uint64(1) << 53, " 9007199254740992 "}, 125 // ulp(1 << 53) > 1 so this loses precision in JS 126 // but it is still a representable integer literal. 127 {uint64(1)<<53 + 1, " 9007199254740993 "}, 128 {float32(1.0), " 1 "}, 129 {float32(-1.0), " -1 "}, 130 {float32(0.5), " 0.5 "}, 131 {float32(-0.5), " -0.5 "}, 132 {float32(1.0) / float32(256), " 0.00390625 "}, 133 {float32(0), " 0 "}, 134 {math.Copysign(0, -1), " -0 "}, 135 {float64(1.0), " 1 "}, 136 {float64(-1.0), " -1 "}, 137 {float64(0.5), " 0.5 "}, 138 {float64(-0.5), " -0.5 "}, 139 {float64(0), " 0 "}, 140 {math.Copysign(0, -1), " -0 "}, 141 {"", `""`}, 142 {"foo", `"foo"`}, 143 // Newlines. 144 {"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`}, 145 // "\v" == "v" on IE 6 so use "\u000b" instead. 146 {"\t\x0b", `"\t\u000b"`}, 147 {struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`}, 148 {[]any{}, "[]"}, 149 {[]any{42, "foo", nil}, `[42,"foo",null]`}, 150 {[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`}, 151 {"<!--", `"\u003c!--"`}, 152 {"-->", `"--\u003e"`}, 153 {"<![CDATA[", `"\u003c![CDATA["`}, 154 {"]]>", `"]]\u003e"`}, 155 {"</script", `"\u003c/script"`}, 156 {"\U0001D11E", "\"\U0001D11E\""}, // or "\uD834\uDD1E" 157 {nil, " null "}, 158 } 159 160 for _, test := range tests { 161 if js := jsValEscaper(test.x); js != test.js { 162 t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js) 163 } 164 // Make sure that escaping corner cases are not broken 165 // by nesting. 166 a := []any{test.x} 167 want := "[" + strings.TrimSpace(test.js) + "]" 168 if js := jsValEscaper(a); js != want { 169 t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js) 170 } 171 } 172 } 173 174 func TestJSStrEscaper(t *testing.T) { 175 tests := []struct { 176 x any 177 esc string 178 }{ 179 {"", ``}, 180 {"foo", `foo`}, 181 {"\u0000", `\u0000`}, 182 {"\t", `\t`}, 183 {"\n", `\n`}, 184 {"\r", `\r`}, 185 {"\u2028", `\u2028`}, 186 {"\u2029", `\u2029`}, 187 {"\\", `\\`}, 188 {"\\n", `\\n`}, 189 {"foo\r\nbar", `foo\r\nbar`}, 190 // Preserve attribute boundaries. 191 {`"`, `\u0022`}, 192 {`'`, `\u0027`}, 193 // Allow embedding in HTML without further escaping. 194 {`&`, `\u0026amp;`}, 195 // Prevent breaking out of text node and element boundaries. 196 {"</script>", `\u003c\/script\u003e`}, 197 {"<![CDATA[", `\u003c![CDATA[`}, 198 {"]]>", `]]\u003e`}, 199 // https://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span 200 // "The text in style, script, title, and textarea elements 201 // must not have an escaping text span start that is not 202 // followed by an escaping text span end." 203 // Furthermore, spoofing an escaping text span end could lead 204 // to different interpretation of a </script> sequence otherwise 205 // masked by the escaping text span, and spoofing a start could 206 // allow regular text content to be interpreted as script 207 // allowing script execution via a combination of a JS string 208 // injection followed by an HTML text injection. 209 {"<!--", `\u003c!--`}, 210 {"-->", `--\u003e`}, 211 // From https://code.google.com/p/doctype/wiki/ArticleUtf7 212 {"+ADw-script+AD4-alert(1)+ADw-/script+AD4-", 213 `\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`, 214 }, 215 // Invalid UTF-8 sequence 216 {"foo\xA0bar", "foo\xA0bar"}, 217 // Invalid unicode scalar value. 218 {"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"}, 219 } 220 221 for _, test := range tests { 222 esc := jsStrEscaper(test.x) 223 if esc != test.esc { 224 t.Errorf("%q: want %q got %q", test.x, test.esc, esc) 225 } 226 } 227 } 228 229 func TestJSRegexpEscaper(t *testing.T) { 230 tests := []struct { 231 x any 232 esc string 233 }{ 234 {"", `(?:)`}, 235 {"foo", `foo`}, 236 {"\u0000", `\u0000`}, 237 {"\t", `\t`}, 238 {"\n", `\n`}, 239 {"\r", `\r`}, 240 {"\u2028", `\u2028`}, 241 {"\u2029", `\u2029`}, 242 {"\\", `\\`}, 243 {"\\n", `\\n`}, 244 {"foo\r\nbar", `foo\r\nbar`}, 245 // Preserve attribute boundaries. 246 {`"`, `\u0022`}, 247 {`'`, `\u0027`}, 248 // Allow embedding in HTML without further escaping. 249 {`&`, `\u0026amp;`}, 250 // Prevent breaking out of text node and element boundaries. 251 {"</script>", `\u003c\/script\u003e`}, 252 {"<![CDATA[", `\u003c!\[CDATA\[`}, 253 {"]]>", `\]\]\u003e`}, 254 // Escaping text spans. 255 {"<!--", `\u003c!\-\-`}, 256 {"-->", `\-\-\u003e`}, 257 {"*", `\*`}, 258 {"+", `\u002b`}, 259 {"?", `\?`}, 260 {"[](){}", `\[\]\(\)\{\}`}, 261 {"$foo|x.y", `\$foo\|x\.y`}, 262 {"x^y", `x\^y`}, 263 } 264 265 for _, test := range tests { 266 esc := jsRegexpEscaper(test.x) 267 if esc != test.esc { 268 t.Errorf("%q: want %q got %q", test.x, test.esc, esc) 269 } 270 } 271 } 272 273 func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) { 274 input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" + 275 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 276 ` !"#$%&'()*+,-./` + 277 `0123456789:;<=>?` + 278 `@ABCDEFGHIJKLMNO` + 279 `PQRSTUVWXYZ[\]^_` + 280 "`abcdefghijklmno" + 281 "pqrstuvwxyz{|}~\x7f" + 282 "\u00A0\u0100\u2028\u2029\ufeff\U0001D11E") 283 284 tests := []struct { 285 name string 286 escaper func(...any) string 287 escaped string 288 }{ 289 { 290 "jsStrEscaper", 291 jsStrEscaper, 292 `\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` + 293 `\u0008\t\n\u000b\f\r\u000e\u000f` + 294 `\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` + 295 `\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` + 296 ` !\u0022#$%\u0026\u0027()*\u002b,-.\/` + 297 `0123456789:;\u003c=\u003e?` + 298 `@ABCDEFGHIJKLMNO` + 299 `PQRSTUVWXYZ[\\]^_` + 300 "\\u0060abcdefghijklmno" + 301 "pqrstuvwxyz{|}~\u007f" + 302 "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E", 303 }, 304 { 305 "jsRegexpEscaper", 306 jsRegexpEscaper, 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 "`abcdefghijklmno" + 316 `pqrstuvwxyz\{\|\}~` + "\u007f" + 317 "\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E", 318 }, 319 } 320 321 for _, test := range tests { 322 if s := test.escaper(input); s != test.escaped { 323 t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s) 324 continue 325 } 326 327 // Escape it rune by rune to make sure that any 328 // fast-path checking does not break escaping. 329 var buf strings.Builder 330 for _, c := range input { 331 buf.WriteString(test.escaper(string(c))) 332 } 333 334 if s := buf.String(); s != test.escaped { 335 t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s) 336 continue 337 } 338 } 339 } 340 341 func TestIsJsMimeType(t *testing.T) { 342 tests := []struct { 343 in string 344 out bool 345 }{ 346 {"application/javascript;version=1.8", true}, 347 {"application/javascript;version=1.8;foo=bar", true}, 348 {"application/javascript/version=1.8", false}, 349 {"text/javascript", true}, 350 {"application/json", true}, 351 {"application/ld+json", true}, 352 {"module", true}, 353 } 354 355 for _, test := range tests { 356 if isJSType(test.in) != test.out { 357 t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out) 358 } 359 } 360 } 361 362 func BenchmarkJSValEscaperWithNum(b *testing.B) { 363 for i := 0; i < b.N; i++ { 364 jsValEscaper(3.141592654) 365 } 366 } 367 368 func BenchmarkJSValEscaperWithStr(b *testing.B) { 369 for i := 0; i < b.N; i++ { 370 jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") 371 } 372 } 373 374 func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) { 375 for i := 0; i < b.N; i++ { 376 jsValEscaper("The quick, brown fox jumps over the lazy dog") 377 } 378 } 379 380 func BenchmarkJSValEscaperWithObj(b *testing.B) { 381 o := struct { 382 S string 383 N int 384 }{ 385 "The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028", 386 42, 387 } 388 for i := 0; i < b.N; i++ { 389 jsValEscaper(o) 390 } 391 } 392 393 func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) { 394 o := struct { 395 S string 396 N int 397 }{ 398 "The quick, brown fox jumps over the lazy dog", 399 42, 400 } 401 for i := 0; i < b.N; i++ { 402 jsValEscaper(o) 403 } 404 } 405 406 func BenchmarkJSStrEscaperNoSpecials(b *testing.B) { 407 for i := 0; i < b.N; i++ { 408 jsStrEscaper("The quick, brown fox jumps over the lazy dog.") 409 } 410 } 411 412 func BenchmarkJSStrEscaper(b *testing.B) { 413 for i := 0; i < b.N; i++ { 414 jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") 415 } 416 } 417 418 func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) { 419 for i := 0; i < b.N; i++ { 420 jsRegexpEscaper("The quick, brown fox jumps over the lazy dog") 421 } 422 } 423 424 func BenchmarkJSRegexpEscaper(b *testing.B) { 425 for i := 0; i < b.N; i++ { 426 jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>") 427 } 428 }