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
5package template
6
7import (
8	"strconv"
9	"strings"
10	"testing"
11)
12
13func TestEndsWithCSSKeyword(t *testing.T) {
14	tests := []struct {
15		css, kw string
16		want    bool
17	}{
18		{"", "url", false},
19		{"url", "url", true},
20		{"URL", "url", true},
21		{"Url", "url", true},
22		{"url", "important", false},
23		{"important", "important", true},
24		{"image-url", "url", false},
25		{"imageurl", "url", false},
26		{"image url", "url", true},
27	}
28	for _, test := range tests {
29		got := endsWithCSSKeyword([]byte(test.css), test.kw)
30		if got != test.want {
31			t.Errorf("want %t but got %t for css=%v, kw=%v", test.want, got, test.css, test.kw)
32		}
33	}
34}
35
36func TestIsCSSNmchar(t *testing.T) {
37	tests := []struct {
38		rune rune
39		want bool
40	}{
41		{0, false},
42		{'0', true},
43		{'9', true},
44		{'A', true},
45		{'Z', true},
46		{'a', true},
47		{'z', true},
48		{'_', true},
49		{'-', true},
50		{':', false},
51		{';', false},
52		{' ', false},
53		{0x7f, false},
54		{0x80, true},
55		{0x1234, true},
56		{0xd800, false},
57		{0xdc00, false},
58		{0xfffe, false},
59		{0x10000, true},
60		{0x110000, false},
61	}
62	for _, test := range tests {
63		got := isCSSNmchar(test.rune)
64		if got != test.want {
65			t.Errorf("%q: want %t but got %t", string(test.rune), test.want, got)
66		}
67	}
68}
69
70func TestDecodeCSS(t *testing.T) {
71	tests := []struct {
72		css, want string
73	}{
74		{``, ``},
75		{`foo`, `foo`},
76		{`foo\`, `foo`},
77		{`foo\\`, `foo\`},
78		{`\`, ``},
79		{`\A`, "\n"},
80		{`\a`, "\n"},
81		{`\0a`, "\n"},
82		{`\00000a`, "\n"},
83		{`\000000a`, "\u0000a"},
84		{`\1234 5`, "\u1234" + "5"},
85		{`\1234\20 5`, "\u1234" + " 5"},
86		{`\1234\A 5`, "\u1234" + "\n5"},
87		{"\\1234\t5", "\u1234" + "5"},
88		{"\\1234\n5", "\u1234" + "5"},
89		{"\\1234\r\n5", "\u1234" + "5"},
90		{`\12345`, "\U00012345"},
91		{`\\`, `\`},
92		{`\\ `, `\ `},
93		{`\"`, `"`},
94		{`\'`, `'`},
95		{`\.`, `.`},
96		{`\. .`, `. .`},
97		{
98			`The \3c i\3equick\3c/i\3e,\d\A\3cspan style=\27 color:brown\27\3e brown\3c/span\3e  fox jumps\2028over the \3c canine class=\22lazy\22 \3e dog\3c/canine\3e`,
99			"The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>",
100		},
101	}
102	for _, test := range tests {
103		got1 := string(decodeCSS([]byte(test.css)))
104		if got1 != test.want {
105			t.Errorf("%q: want\n\t%q\nbut got\n\t%q", test.css, test.want, got1)
106		}
107		recoded := cssEscaper(got1)
108		if got2 := string(decodeCSS([]byte(recoded))); got2 != test.want {
109			t.Errorf("%q: escape & decode not dual for %q", test.css, recoded)
110		}
111	}
112}
113
114func TestHexDecode(t *testing.T) {
115	for i := 0; i < 0x200000; i += 101 /* coprime with 16 */ {
116		s := strconv.FormatInt(int64(i), 16)
117		if got := int(hexDecode([]byte(s))); got != i {
118			t.Errorf("%s: want %d but got %d", s, i, got)
119		}
120		s = strings.ToUpper(s)
121		if got := int(hexDecode([]byte(s))); got != i {
122			t.Errorf("%s: want %d but got %d", s, i, got)
123		}
124	}
125}
126
127func TestSkipCSSSpace(t *testing.T) {
128	tests := []struct {
129		css, want string
130	}{
131		{"", ""},
132		{"foo", "foo"},
133		{"\n", ""},
134		{"\r\n", ""},
135		{"\r", ""},
136		{"\t", ""},
137		{" ", ""},
138		{"\f", ""},
139		{" foo", "foo"},
140		{"  foo", " foo"},
141		{`\20`, `\20`},
142	}
143	for _, test := range tests {
144		got := string(skipCSSSpace([]byte(test.css)))
145		if got != test.want {
146			t.Errorf("%q: want %q but got %q", test.css, test.want, got)
147		}
148	}
149}
150
151func TestCSSEscaper(t *testing.T) {
152	input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
153		"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
154		` !"#$%&'()*+,-./` +
155		`0123456789:;<=>?` +
156		`@ABCDEFGHIJKLMNO` +
157		`PQRSTUVWXYZ[\]^_` +
158		"`abcdefghijklmno" +
159		"pqrstuvwxyz{|}~\x7f" +
160		"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
161
162	want := ("\\0\x01\x02\x03\x04\x05\x06\x07" +
163		"\x08\\9 \\a\x0b\\c \\d\x0E\x0F" +
164		"\x10\x11\x12\x13\x14\x15\x16\x17" +
165		"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
166		` !\22#$%\26\27\28\29*\2b,-.\2f ` +
167		`0123456789\3a\3b\3c=\3e?` +
168		`@ABCDEFGHIJKLMNO` +
169		`PQRSTUVWXYZ[\\]^_` +
170		"`abcdefghijklmno" +
171		`pqrstuvwxyz\7b|\7d~` + "\u007f" +
172		"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
173
174	got := cssEscaper(input)
175	if got != want {
176		t.Errorf("encode: want\n\t%q\nbut got\n\t%q", want, got)
177	}
178
179	got = string(decodeCSS([]byte(got)))
180	if input != got {
181		t.Errorf("decode: want\n\t%q\nbut got\n\t%q", input, got)
182	}
183}
184
185func TestCSSValueFilter(t *testing.T) {
186	tests := []struct {
187		css, want string
188	}{
189		{"", ""},
190		{"foo", "foo"},
191		{"0", "0"},
192		{"0px", "0px"},
193		{"-5px", "-5px"},
194		{"1.25in", "1.25in"},
195		{"+.33em", "+.33em"},
196		{"100%", "100%"},
197		{"12.5%", "12.5%"},
198		{".foo", ".foo"},
199		{"#bar", "#bar"},
200		{"corner-radius", "corner-radius"},
201		{"-moz-corner-radius", "-moz-corner-radius"},
202		{"#000", "#000"},
203		{"#48f", "#48f"},
204		{"#123456", "#123456"},
205		{"U+00-FF, U+980-9FF", "U+00-FF, U+980-9FF"},
206		{"color: red", "color: red"},
207		{"<!--", "ZgotmplZ"},
208		{"-->", "ZgotmplZ"},
209		{"<![CDATA[", "ZgotmplZ"},
210		{"]]>", "ZgotmplZ"},
211		{"</style", "ZgotmplZ"},
212		{`"`, "ZgotmplZ"},
213		{`'`, "ZgotmplZ"},
214		{"`", "ZgotmplZ"},
215		{"\x00", "ZgotmplZ"},
216		{"/* foo */", "ZgotmplZ"},
217		{"//", "ZgotmplZ"},
218		{"[href=~", "ZgotmplZ"},
219		{"expression(alert(1337))", "ZgotmplZ"},
220		{"-expression(alert(1337))", "ZgotmplZ"},
221		{"expression", "ZgotmplZ"},
222		{"Expression", "ZgotmplZ"},
223		{"EXPRESSION", "ZgotmplZ"},
224		{"-moz-binding", "ZgotmplZ"},
225		{"-expr\x00ession(alert(1337))", "ZgotmplZ"},
226		{`-expr\0ession(alert(1337))`, "ZgotmplZ"},
227		{`-express\69on(alert(1337))`, "ZgotmplZ"},
228		{`-express\69 on(alert(1337))`, "ZgotmplZ"},
229		{`-exp\72 ession(alert(1337))`, "ZgotmplZ"},
230		{`-exp\52 ession(alert(1337))`, "ZgotmplZ"},
231		{`-exp\000052 ession(alert(1337))`, "ZgotmplZ"},
232		{`-expre\0000073sion`, "-expre\x073sion"},
233		{`@import url evil.css`, "ZgotmplZ"},
234	}
235	for _, test := range tests {
236		got := cssValueFilter(test.css)
237		if got != test.want {
238			t.Errorf("%q: want %q but got %q", test.css, test.want, got)
239		}
240	}
241}
242
243func BenchmarkCSSEscaper(b *testing.B) {
244	for i := 0; i < b.N; i++ {
245		cssEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
246	}
247}
248
249func BenchmarkCSSEscaperNoSpecials(b *testing.B) {
250	for i := 0; i < b.N; i++ {
251		cssEscaper("The quick, brown fox jumps over the lazy dog.")
252	}
253}
254
255func BenchmarkDecodeCSS(b *testing.B) {
256	s := []byte(`The \3c i\3equick\3c/i\3e,\d\A\3cspan style=\27 color:brown\27\3e brown\3c/span\3e fox jumps\2028over the \3c canine class=\22lazy\22 \3edog\3c/canine\3e`)
257	b.ResetTimer()
258	for i := 0; i < b.N; i++ {
259		decodeCSS(s)
260	}
261}
262
263func BenchmarkDecodeCSSNoSpecials(b *testing.B) {
264	s := []byte("The quick, brown fox jumps over the lazy dog.")
265	b.ResetTimer()
266	for i := 0; i < b.N; i++ {
267		decodeCSS(s)
268	}
269}
270
271func BenchmarkCSSValueFilter(b *testing.B) {
272	for i := 0; i < b.N; i++ {
273		cssValueFilter(`  e\78preS\0Sio/**/n(alert(1337))`)
274	}
275}
276
277func BenchmarkCSSValueFilterOk(b *testing.B) {
278	for i := 0; i < b.N; i++ {
279		cssValueFilter(`Times New Roman`)
280	}
281}
282