...
Run Format

Source file src/html/template/js_test.go

Documentation: html/template

  // Copyright 2011 The Go Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file.
  
  package template
  
  import (
  	"bytes"
  	"math"
  	"strings"
  	"testing"
  )
  
  func TestNextJsCtx(t *testing.T) {
  	tests := []struct {
  		jsCtx jsCtx
  		s     string
  	}{
  		// Statement terminators precede regexps.
  		{jsCtxRegexp, ";"},
  		// This is not airtight.
  		//     ({ valueOf: function () { return 1 } } / 2)
  		// is valid JavaScript but in practice, devs do not do this.
  		// A block followed by a statement starting with a RegExp is
  		// much more common:
  		//     while (x) {...} /foo/.test(x) || panic()
  		{jsCtxRegexp, "}"},
  		// But member, call, grouping, and array expression terminators
  		// precede div ops.
  		{jsCtxDivOp, ")"},
  		{jsCtxDivOp, "]"},
  		// At the start of a primary expression, array, or expression
  		// statement, expect a regexp.
  		{jsCtxRegexp, "("},
  		{jsCtxRegexp, "["},
  		{jsCtxRegexp, "{"},
  		// Assignment operators precede regexps as do all exclusively
  		// prefix and binary operators.
  		{jsCtxRegexp, "="},
  		{jsCtxRegexp, "+="},
  		{jsCtxRegexp, "*="},
  		{jsCtxRegexp, "*"},
  		{jsCtxRegexp, "!"},
  		// Whether the + or - is infix or prefix, it cannot precede a
  		// div op.
  		{jsCtxRegexp, "+"},
  		{jsCtxRegexp, "-"},
  		// An incr/decr op precedes a div operator.
  		// This is not airtight. In (g = ++/h/i) a regexp follows a
  		// pre-increment operator, but in practice devs do not try to
  		// increment or decrement regular expressions.
  		// (g++/h/i) where ++ is a postfix operator on g is much more
  		// common.
  		{jsCtxDivOp, "--"},
  		{jsCtxDivOp, "++"},
  		{jsCtxDivOp, "x--"},
  		// When we have many dashes or pluses, then they are grouped
  		// left to right.
  		{jsCtxRegexp, "x---"}, // A postfix -- then a -.
  		// return followed by a slash returns the regexp literal or the
  		// slash starts a regexp literal in an expression statement that
  		// is dead code.
  		{jsCtxRegexp, "return"},
  		{jsCtxRegexp, "return "},
  		{jsCtxRegexp, "return\t"},
  		{jsCtxRegexp, "return\n"},
  		{jsCtxRegexp, "return\u2028"},
  		// Identifiers can be divided and cannot validly be preceded by
  		// a regular expressions. Semicolon insertion cannot happen
  		// between an identifier and a regular expression on a new line
  		// because the one token lookahead for semicolon insertion has
  		// to conclude that it could be a div binary op and treat it as
  		// such.
  		{jsCtxDivOp, "x"},
  		{jsCtxDivOp, "x "},
  		{jsCtxDivOp, "x\t"},
  		{jsCtxDivOp, "x\n"},
  		{jsCtxDivOp, "x\u2028"},
  		{jsCtxDivOp, "preturn"},
  		// Numbers precede div ops.
  		{jsCtxDivOp, "0"},
  		// Dots that are part of a number are div preceders.
  		{jsCtxDivOp, "0."},
  	}
  
  	for _, test := range tests {
  		if nextJSCtx([]byte(test.s), jsCtxRegexp) != test.jsCtx {
  			t.Errorf("want %s got %q", test.jsCtx, test.s)
  		}
  		if nextJSCtx([]byte(test.s), jsCtxDivOp) != test.jsCtx {
  			t.Errorf("want %s got %q", test.jsCtx, test.s)
  		}
  	}
  
  	if nextJSCtx([]byte("   "), jsCtxRegexp) != jsCtxRegexp {
  		t.Error("Blank tokens")
  	}
  
  	if nextJSCtx([]byte("   "), jsCtxDivOp) != jsCtxDivOp {
  		t.Error("Blank tokens")
  	}
  }
  
  func TestJSValEscaper(t *testing.T) {
  	tests := []struct {
  		x  interface{}
  		js string
  	}{
  		{int(42), " 42 "},
  		{uint(42), " 42 "},
  		{int16(42), " 42 "},
  		{uint16(42), " 42 "},
  		{int32(-42), " -42 "},
  		{uint32(42), " 42 "},
  		{int16(-42), " -42 "},
  		{uint16(42), " 42 "},
  		{int64(-42), " -42 "},
  		{uint64(42), " 42 "},
  		{uint64(1) << 53, " 9007199254740992 "},
  		// ulp(1 << 53) > 1 so this loses precision in JS
  		// but it is still a representable integer literal.
  		{uint64(1)<<53 + 1, " 9007199254740993 "},
  		{float32(1.0), " 1 "},
  		{float32(-1.0), " -1 "},
  		{float32(0.5), " 0.5 "},
  		{float32(-0.5), " -0.5 "},
  		{float32(1.0) / float32(256), " 0.00390625 "},
  		{float32(0), " 0 "},
  		{math.Copysign(0, -1), " -0 "},
  		{float64(1.0), " 1 "},
  		{float64(-1.0), " -1 "},
  		{float64(0.5), " 0.5 "},
  		{float64(-0.5), " -0.5 "},
  		{float64(0), " 0 "},
  		{math.Copysign(0, -1), " -0 "},
  		{"", `""`},
  		{"foo", `"foo"`},
  		// Newlines.
  		{"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`},
  		// "\v" == "v" on IE 6 so use "\x0b" instead.
  		{"\t\x0b", `"\t\u000b"`},
  		{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
  		{[]interface{}{}, "[]"},
  		{[]interface{}{42, "foo", nil}, `[42,"foo",null]`},
  		{[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
  		{"<!--", `"\u003c!--"`},
  		{"-->", `"--\u003e"`},
  		{"<![CDATA[", `"\u003c![CDATA["`},
  		{"]]>", `"]]\u003e"`},
  		{"</script", `"\u003c/script"`},
  		{"\U0001D11E", "\"\U0001D11E\""}, // or "\uD834\uDD1E"
  	}
  
  	for _, test := range tests {
  		if js := jsValEscaper(test.x); js != test.js {
  			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
  		}
  		// Make sure that escaping corner cases are not broken
  		// by nesting.
  		a := []interface{}{test.x}
  		want := "[" + strings.TrimSpace(test.js) + "]"
  		if js := jsValEscaper(a); js != want {
  			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
  		}
  	}
  }
  
  func TestJSStrEscaper(t *testing.T) {
  	tests := []struct {
  		x   interface{}
  		esc string
  	}{
  		{"", ``},
  		{"foo", `foo`},
  		{"\u0000", `\0`},
  		{"\t", `\t`},
  		{"\n", `\n`},
  		{"\r", `\r`},
  		{"\u2028", `\u2028`},
  		{"\u2029", `\u2029`},
  		{"\\", `\\`},
  		{"\\n", `\\n`},
  		{"foo\r\nbar", `foo\r\nbar`},
  		// Preserve attribute boundaries.
  		{`"`, `\x22`},
  		{`'`, `\x27`},
  		// Allow embedding in HTML without further escaping.
  		{`&amp;`, `\x26amp;`},
  		// Prevent breaking out of text node and element boundaries.
  		{"</script>", `\x3c\/script\x3e`},
  		{"<![CDATA[", `\x3c![CDATA[`},
  		{"]]>", `]]\x3e`},
  		// http://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span
  		//   "The text in style, script, title, and textarea elements
  		//   must not have an escaping text span start that is not
  		//   followed by an escaping text span end."
  		// Furthermore, spoofing an escaping text span end could lead
  		// to different interpretation of a </script> sequence otherwise
  		// masked by the escaping text span, and spoofing a start could
  		// allow regular text content to be interpreted as script
  		// allowing script execution via a combination of a JS string
  		// injection followed by an HTML text injection.
  		{"<!--", `\x3c!--`},
  		{"-->", `--\x3e`},
  		// From http://code.google.com/p/doctype/wiki/ArticleUtf7
  		{"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
  			`\x2bADw-script\x2bAD4-alert(1)\x2bADw-\/script\x2bAD4-`,
  		},
  		// Invalid UTF-8 sequence
  		{"foo\xA0bar", "foo\xA0bar"},
  		// Invalid unicode scalar value.
  		{"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
  	}
  
  	for _, test := range tests {
  		esc := jsStrEscaper(test.x)
  		if esc != test.esc {
  			t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
  		}
  	}
  }
  
  func TestJSRegexpEscaper(t *testing.T) {
  	tests := []struct {
  		x   interface{}
  		esc string
  	}{
  		{"", `(?:)`},
  		{"foo", `foo`},
  		{"\u0000", `\0`},
  		{"\t", `\t`},
  		{"\n", `\n`},
  		{"\r", `\r`},
  		{"\u2028", `\u2028`},
  		{"\u2029", `\u2029`},
  		{"\\", `\\`},
  		{"\\n", `\\n`},
  		{"foo\r\nbar", `foo\r\nbar`},
  		// Preserve attribute boundaries.
  		{`"`, `\x22`},
  		{`'`, `\x27`},
  		// Allow embedding in HTML without further escaping.
  		{`&amp;`, `\x26amp;`},
  		// Prevent breaking out of text node and element boundaries.
  		{"</script>", `\x3c\/script\x3e`},
  		{"<![CDATA[", `\x3c!\[CDATA\[`},
  		{"]]>", `\]\]\x3e`},
  		// Escaping text spans.
  		{"<!--", `\x3c!\-\-`},
  		{"-->", `\-\-\x3e`},
  		{"*", `\*`},
  		{"+", `\x2b`},
  		{"?", `\?`},
  		{"[](){}", `\[\]\(\)\{\}`},
  		{"$foo|x.y", `\$foo\|x\.y`},
  		{"x^y", `x\^y`},
  	}
  
  	for _, test := range tests {
  		esc := jsRegexpEscaper(test.x)
  		if esc != test.esc {
  			t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
  		}
  	}
  }
  
  func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
  	input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
  		"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
  		` !"#$%&'()*+,-./` +
  		`0123456789:;<=>?` +
  		`@ABCDEFGHIJKLMNO` +
  		`PQRSTUVWXYZ[\]^_` +
  		"`abcdefghijklmno" +
  		"pqrstuvwxyz{|}~\x7f" +
  		"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
  
  	tests := []struct {
  		name    string
  		escaper func(...interface{}) string
  		escaped string
  	}{
  		{
  			"jsStrEscaper",
  			jsStrEscaper,
  			"\\0\x01\x02\x03\x04\x05\x06\x07" +
  				"\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
  				"\x10\x11\x12\x13\x14\x15\x16\x17" +
  				"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
  				` !\x22#$%\x26\x27()*\x2b,-.\/` +
  				`0123456789:;\x3c=\x3e?` +
  				`@ABCDEFGHIJKLMNO` +
  				`PQRSTUVWXYZ[\\]^_` +
  				"`abcdefghijklmno" +
  				"pqrstuvwxyz{|}~\x7f" +
  				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
  		},
  		{
  			"jsRegexpEscaper",
  			jsRegexpEscaper,
  			"\\0\x01\x02\x03\x04\x05\x06\x07" +
  				"\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
  				"\x10\x11\x12\x13\x14\x15\x16\x17" +
  				"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
  				` !\x22#\$%\x26\x27\(\)\*\x2b,\-\.\/` +
  				`0123456789:;\x3c=\x3e\?` +
  				`@ABCDEFGHIJKLMNO` +
  				`PQRSTUVWXYZ\[\\\]\^_` +
  				"`abcdefghijklmno" +
  				`pqrstuvwxyz\{\|\}~` + "\u007f" +
  				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
  		},
  	}
  
  	for _, test := range tests {
  		if s := test.escaper(input); s != test.escaped {
  			t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
  			continue
  		}
  
  		// Escape it rune by rune to make sure that any
  		// fast-path checking does not break escaping.
  		var buf bytes.Buffer
  		for _, c := range input {
  			buf.WriteString(test.escaper(string(c)))
  		}
  
  		if s := buf.String(); s != test.escaped {
  			t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
  			continue
  		}
  	}
  }
  
  func TestIsJsMimeType(t *testing.T) {
  	tests := []struct {
  		in  string
  		out bool
  	}{
  		{"application/javascript;version=1.8", true},
  		{"application/javascript;version=1.8;foo=bar", true},
  		{"application/javascript/version=1.8", false},
  		{"text/javascript", true},
  		{"application/json", true},
  	}
  
  	for _, test := range tests {
  		if isJSType(test.in) != test.out {
  			t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out)
  		}
  	}
  }
  
  func BenchmarkJSValEscaperWithNum(b *testing.B) {
  	for i := 0; i < b.N; i++ {
  		jsValEscaper(3.141592654)
  	}
  }
  
  func BenchmarkJSValEscaperWithStr(b *testing.B) {
  	for i := 0; i < b.N; i++ {
  		jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
  	}
  }
  
  func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) {
  	for i := 0; i < b.N; i++ {
  		jsValEscaper("The quick, brown fox jumps over the lazy dog")
  	}
  }
  
  func BenchmarkJSValEscaperWithObj(b *testing.B) {
  	o := struct {
  		S string
  		N int
  	}{
  		"The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
  		42,
  	}
  	for i := 0; i < b.N; i++ {
  		jsValEscaper(o)
  	}
  }
  
  func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) {
  	o := struct {
  		S string
  		N int
  	}{
  		"The quick, brown fox jumps over the lazy dog",
  		42,
  	}
  	for i := 0; i < b.N; i++ {
  		jsValEscaper(o)
  	}
  }
  
  func BenchmarkJSStrEscaperNoSpecials(b *testing.B) {
  	for i := 0; i < b.N; i++ {
  		jsStrEscaper("The quick, brown fox jumps over the lazy dog.")
  	}
  }
  
  func BenchmarkJSStrEscaper(b *testing.B) {
  	for i := 0; i < b.N; i++ {
  		jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
  	}
  }
  
  func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) {
  	for i := 0; i < b.N; i++ {
  		jsRegexpEscaper("The quick, brown fox jumps over the lazy dog")
  	}
  }
  
  func BenchmarkJSRegexpEscaper(b *testing.B) {
  	for i := 0; i < b.N; i++ {
  		jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
  	}
  }
  

View as plain text