github.com/filosottile/go@v0.0.0-20170906193555-dbed9972d994/src/html/template/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  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 "\x0b" 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  	}
   153  
   154  	for _, test := range tests {
   155  		if js := jsValEscaper(test.x); js != test.js {
   156  			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
   157  		}
   158  		// Make sure that escaping corner cases are not broken
   159  		// by nesting.
   160  		a := []interface{}{test.x}
   161  		want := "[" + strings.TrimSpace(test.js) + "]"
   162  		if js := jsValEscaper(a); js != want {
   163  			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
   164  		}
   165  	}
   166  }
   167  
   168  func TestJSStrEscaper(t *testing.T) {
   169  	tests := []struct {
   170  		x   interface{}
   171  		esc string
   172  	}{
   173  		{"", ``},
   174  		{"foo", `foo`},
   175  		{"\u0000", `\0`},
   176  		{"\t", `\t`},
   177  		{"\n", `\n`},
   178  		{"\r", `\r`},
   179  		{"\u2028", `\u2028`},
   180  		{"\u2029", `\u2029`},
   181  		{"\\", `\\`},
   182  		{"\\n", `\\n`},
   183  		{"foo\r\nbar", `foo\r\nbar`},
   184  		// Preserve attribute boundaries.
   185  		{`"`, `\x22`},
   186  		{`'`, `\x27`},
   187  		// Allow embedding in HTML without further escaping.
   188  		{`&amp;`, `\x26amp;`},
   189  		// Prevent breaking out of text node and element boundaries.
   190  		{"</script>", `\x3c\/script\x3e`},
   191  		{"<![CDATA[", `\x3c![CDATA[`},
   192  		{"]]>", `]]\x3e`},
   193  		// http://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span
   194  		//   "The text in style, script, title, and textarea elements
   195  		//   must not have an escaping text span start that is not
   196  		//   followed by an escaping text span end."
   197  		// Furthermore, spoofing an escaping text span end could lead
   198  		// to different interpretation of a </script> sequence otherwise
   199  		// masked by the escaping text span, and spoofing a start could
   200  		// allow regular text content to be interpreted as script
   201  		// allowing script execution via a combination of a JS string
   202  		// injection followed by an HTML text injection.
   203  		{"<!--", `\x3c!--`},
   204  		{"-->", `--\x3e`},
   205  		// From http://code.google.com/p/doctype/wiki/ArticleUtf7
   206  		{"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
   207  			`\x2bADw-script\x2bAD4-alert(1)\x2bADw-\/script\x2bAD4-`,
   208  		},
   209  		// Invalid UTF-8 sequence
   210  		{"foo\xA0bar", "foo\xA0bar"},
   211  		// Invalid unicode scalar value.
   212  		{"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
   213  	}
   214  
   215  	for _, test := range tests {
   216  		esc := jsStrEscaper(test.x)
   217  		if esc != test.esc {
   218  			t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
   219  		}
   220  	}
   221  }
   222  
   223  func TestJSRegexpEscaper(t *testing.T) {
   224  	tests := []struct {
   225  		x   interface{}
   226  		esc string
   227  	}{
   228  		{"", `(?:)`},
   229  		{"foo", `foo`},
   230  		{"\u0000", `\0`},
   231  		{"\t", `\t`},
   232  		{"\n", `\n`},
   233  		{"\r", `\r`},
   234  		{"\u2028", `\u2028`},
   235  		{"\u2029", `\u2029`},
   236  		{"\\", `\\`},
   237  		{"\\n", `\\n`},
   238  		{"foo\r\nbar", `foo\r\nbar`},
   239  		// Preserve attribute boundaries.
   240  		{`"`, `\x22`},
   241  		{`'`, `\x27`},
   242  		// Allow embedding in HTML without further escaping.
   243  		{`&amp;`, `\x26amp;`},
   244  		// Prevent breaking out of text node and element boundaries.
   245  		{"</script>", `\x3c\/script\x3e`},
   246  		{"<![CDATA[", `\x3c!\[CDATA\[`},
   247  		{"]]>", `\]\]\x3e`},
   248  		// Escaping text spans.
   249  		{"<!--", `\x3c!\-\-`},
   250  		{"-->", `\-\-\x3e`},
   251  		{"*", `\*`},
   252  		{"+", `\x2b`},
   253  		{"?", `\?`},
   254  		{"[](){}", `\[\]\(\)\{\}`},
   255  		{"$foo|x.y", `\$foo\|x\.y`},
   256  		{"x^y", `x\^y`},
   257  	}
   258  
   259  	for _, test := range tests {
   260  		esc := jsRegexpEscaper(test.x)
   261  		if esc != test.esc {
   262  			t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
   263  		}
   264  	}
   265  }
   266  
   267  func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
   268  	input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
   269  		"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
   270  		` !"#$%&'()*+,-./` +
   271  		`0123456789:;<=>?` +
   272  		`@ABCDEFGHIJKLMNO` +
   273  		`PQRSTUVWXYZ[\]^_` +
   274  		"`abcdefghijklmno" +
   275  		"pqrstuvwxyz{|}~\x7f" +
   276  		"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
   277  
   278  	tests := []struct {
   279  		name    string
   280  		escaper func(...interface{}) string
   281  		escaped string
   282  	}{
   283  		{
   284  			"jsStrEscaper",
   285  			jsStrEscaper,
   286  			"\\0\x01\x02\x03\x04\x05\x06\x07" +
   287  				"\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
   288  				"\x10\x11\x12\x13\x14\x15\x16\x17" +
   289  				"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
   290  				` !\x22#$%\x26\x27()*\x2b,-.\/` +
   291  				`0123456789:;\x3c=\x3e?` +
   292  				`@ABCDEFGHIJKLMNO` +
   293  				`PQRSTUVWXYZ[\\]^_` +
   294  				"`abcdefghijklmno" +
   295  				"pqrstuvwxyz{|}~\x7f" +
   296  				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
   297  		},
   298  		{
   299  			"jsRegexpEscaper",
   300  			jsRegexpEscaper,
   301  			"\\0\x01\x02\x03\x04\x05\x06\x07" +
   302  				"\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
   303  				"\x10\x11\x12\x13\x14\x15\x16\x17" +
   304  				"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
   305  				` !\x22#\$%\x26\x27\(\)\*\x2b,\-\.\/` +
   306  				`0123456789:;\x3c=\x3e\?` +
   307  				`@ABCDEFGHIJKLMNO` +
   308  				`PQRSTUVWXYZ\[\\\]\^_` +
   309  				"`abcdefghijklmno" +
   310  				`pqrstuvwxyz\{\|\}~` + "\u007f" +
   311  				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
   312  		},
   313  	}
   314  
   315  	for _, test := range tests {
   316  		if s := test.escaper(input); s != test.escaped {
   317  			t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
   318  			continue
   319  		}
   320  
   321  		// Escape it rune by rune to make sure that any
   322  		// fast-path checking does not break escaping.
   323  		var buf bytes.Buffer
   324  		for _, c := range input {
   325  			buf.WriteString(test.escaper(string(c)))
   326  		}
   327  
   328  		if s := buf.String(); s != test.escaped {
   329  			t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
   330  			continue
   331  		}
   332  	}
   333  }
   334  
   335  func TestIsJsMimeType(t *testing.T) {
   336  	tests := []struct {
   337  		in  string
   338  		out bool
   339  	}{
   340  		{"application/javascript;version=1.8", true},
   341  		{"application/javascript;version=1.8;foo=bar", true},
   342  		{"application/javascript/version=1.8", false},
   343  		{"text/javascript", true},
   344  		{"application/json", true},
   345  	}
   346  
   347  	for _, test := range tests {
   348  		if isJSType(test.in) != test.out {
   349  			t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out)
   350  		}
   351  	}
   352  }
   353  
   354  func BenchmarkJSValEscaperWithNum(b *testing.B) {
   355  	for i := 0; i < b.N; i++ {
   356  		jsValEscaper(3.141592654)
   357  	}
   358  }
   359  
   360  func BenchmarkJSValEscaperWithStr(b *testing.B) {
   361  	for i := 0; i < b.N; i++ {
   362  		jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
   363  	}
   364  }
   365  
   366  func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) {
   367  	for i := 0; i < b.N; i++ {
   368  		jsValEscaper("The quick, brown fox jumps over the lazy dog")
   369  	}
   370  }
   371  
   372  func BenchmarkJSValEscaperWithObj(b *testing.B) {
   373  	o := struct {
   374  		S string
   375  		N int
   376  	}{
   377  		"The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
   378  		42,
   379  	}
   380  	for i := 0; i < b.N; i++ {
   381  		jsValEscaper(o)
   382  	}
   383  }
   384  
   385  func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) {
   386  	o := struct {
   387  		S string
   388  		N int
   389  	}{
   390  		"The quick, brown fox jumps over the lazy dog",
   391  		42,
   392  	}
   393  	for i := 0; i < b.N; i++ {
   394  		jsValEscaper(o)
   395  	}
   396  }
   397  
   398  func BenchmarkJSStrEscaperNoSpecials(b *testing.B) {
   399  	for i := 0; i < b.N; i++ {
   400  		jsStrEscaper("The quick, brown fox jumps over the lazy dog.")
   401  	}
   402  }
   403  
   404  func BenchmarkJSStrEscaper(b *testing.B) {
   405  	for i := 0; i < b.N; i++ {
   406  		jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
   407  	}
   408  }
   409  
   410  func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) {
   411  	for i := 0; i < b.N; i++ {
   412  		jsRegexpEscaper("The quick, brown fox jumps over the lazy dog")
   413  	}
   414  }
   415  
   416  func BenchmarkJSRegexpEscaper(b *testing.B) {
   417  	for i := 0; i < b.N; i++ {
   418  		jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
   419  	}
   420  }