...

Source file src/html/template/js_test.go

Documentation: html/template

		 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  package template
		 6  
		 7  import (
		 8  	"bytes"
		 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  		// 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  
	 104  func 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 "\u000b" 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  
	 169  func TestJSStrEscaper(t *testing.T) {
	 170  	tests := []struct {
	 171  		x	 interface{}
	 172  		esc string
	 173  	}{
	 174  		{"", ``},
	 175  		{"foo", `foo`},
	 176  		{"\u0000", `\u0000`},
	 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  		{`"`, `\u0022`},
	 187  		{`'`, `\u0027`},
	 188  		// Allow embedding in HTML without further escaping.
	 189  		{`&amp;`, `\u0026amp;`},
	 190  		// Prevent breaking out of text node and element boundaries.
	 191  		{"</script>", `\u003c\/script\u003e`},
	 192  		{"<![CDATA[", `\u003c![CDATA[`},
	 193  		{"]]>", `]]\u003e`},
	 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  		{"<!--", `\u003c!--`},
	 205  		{"-->", `--\u003e`},
	 206  		// From https://code.google.com/p/doctype/wiki/ArticleUtf7
	 207  		{"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
	 208  			`\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`,
	 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  
	 224  func TestJSRegexpEscaper(t *testing.T) {
	 225  	tests := []struct {
	 226  		x	 interface{}
	 227  		esc string
	 228  	}{
	 229  		{"", `(?:)`},
	 230  		{"foo", `foo`},
	 231  		{"\u0000", `\u0000`},
	 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  		{`"`, `\u0022`},
	 242  		{`'`, `\u0027`},
	 243  		// Allow embedding in HTML without further escaping.
	 244  		{`&amp;`, `\u0026amp;`},
	 245  		// Prevent breaking out of text node and element boundaries.
	 246  		{"</script>", `\u003c\/script\u003e`},
	 247  		{"<![CDATA[", `\u003c!\[CDATA\[`},
	 248  		{"]]>", `\]\]\u003e`},
	 249  		// Escaping text spans.
	 250  		{"<!--", `\u003c!\-\-`},
	 251  		{"-->", `\-\-\u003e`},
	 252  		{"*", `\*`},
	 253  		{"+", `\u002b`},
	 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  
	 268  func 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  			`\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
	 288  				`\u0008\t\n\u000b\f\r\u000e\u000f` +
	 289  				`\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
	 290  				`\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
	 291  				` !\u0022#$%\u0026\u0027()*\u002b,-.\/` +
	 292  				`0123456789:;\u003c=\u003e?` +
	 293  				`@ABCDEFGHIJKLMNO` +
	 294  				`PQRSTUVWXYZ[\\]^_` +
	 295  				"`abcdefghijklmno" +
	 296  				"pqrstuvwxyz{|}~\u007f" +
	 297  				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
	 298  		},
	 299  		{
	 300  			"jsRegexpEscaper",
	 301  			jsRegexpEscaper,
	 302  			`\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
	 303  				`\u0008\t\n\u000b\f\r\u000e\u000f` +
	 304  				`\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
	 305  				`\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
	 306  				` !\u0022#\$%\u0026\u0027\(\)\*\u002b,\-\.\/` +
	 307  				`0123456789:;\u003c=\u003e\?` +
	 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  
	 336  func 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  
	 357  func BenchmarkJSValEscaperWithNum(b *testing.B) {
	 358  	for i := 0; i < b.N; i++ {
	 359  		jsValEscaper(3.141592654)
	 360  	}
	 361  }
	 362  
	 363  func 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  
	 369  func 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  
	 375  func 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  
	 388  func 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  
	 401  func 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  
	 407  func 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  
	 413  func 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  
	 419  func 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  

View as plain text