OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /// This tests HTML validation and sanitization, which is very important | 5 /// This tests HTML validation and sanitization, which is very important |
6 /// for prevent XSS or other attacks. If you suppress this, or parts of it | 6 /// for prevent XSS or other attacks. If you suppress this, or parts of it |
7 /// please make it a critical bug and bring it to the attention of the | 7 /// please make it a critical bug and bring it to the attention of the |
8 /// dart:html maintainers. | 8 /// dart:html maintainers. |
9 library node_validator_test; | 9 library node_validator_test; |
10 | 10 |
11 import 'dart:html'; | 11 import 'dart:html'; |
12 import 'dart:svg' as svg; | 12 import 'dart:svg' as svg; |
13 | 13 |
14 import 'package:expect/minitest.dart'; | 14 import 'package:expect/minitest.dart'; |
15 | 15 |
16 import 'utils.dart'; | 16 import 'utils.dart'; |
17 | 17 |
18 void validateHtml(String html, String reference, NodeValidator validator) { | 18 void validateHtml(String html, String reference, NodeValidator validator) { |
19 var a = document.body.createFragment(html, validator: validator); | 19 var a = document.body.createFragment(html, validator: validator); |
20 var b = document.body.createFragment(reference, | 20 var b = document.body |
21 treeSanitizer: NodeTreeSanitizer.trusted); | 21 .createFragment(reference, treeSanitizer: NodeTreeSanitizer.trusted); |
22 | 22 |
23 // Prevent a false pass when both the html and the reference both get entirely | 23 // Prevent a false pass when both the html and the reference both get entirely |
24 // deleted, which is technically a match, but unlikely to be what we meant. | 24 // deleted, which is technically a match, but unlikely to be what we meant. |
25 if (reference != '') { | 25 if (reference != '') { |
26 expect(b.childNodes.length > 0, isTrue); | 26 expect(b.childNodes.length > 0, isTrue); |
27 } | 27 } |
28 validateNodeTree(a, b); | 28 validateNodeTree(a, b); |
29 } | 29 } |
30 | 30 |
31 class RecordingUriValidator implements UriPolicy { | 31 class RecordingUriValidator implements UriPolicy { |
32 final List<String> calls = <String>[]; | 32 final List<String> calls = <String>[]; |
33 | 33 |
34 bool allowsUri(String uri) { | 34 bool allowsUri(String uri) { |
35 calls.add('$uri'); | 35 calls.add('$uri'); |
36 return false; | 36 return false; |
37 } | 37 } |
38 | 38 |
39 void reset() { | 39 void reset() { |
40 calls.clear(); | 40 calls.clear(); |
41 } | 41 } |
42 } | 42 } |
43 | 43 |
44 void testHtml(String name, NodeValidator validator, String html, | 44 void testHtml(String name, NodeValidator validator, String html, |
45 [String reference]) { | 45 [String reference]) { |
46 test(name, () { | 46 test(name, () { |
47 if (reference == null) { | 47 if (reference == null) { |
48 reference = html; | 48 reference = html; |
49 } | 49 } |
50 | 50 |
51 validateHtml(html, reference, validator); | 51 validateHtml(html, reference, validator); |
52 }); | 52 }); |
53 } | 53 } |
54 | 54 |
55 main() { | 55 main() { |
56 group('DOM_sanitization', () { | 56 group('DOM_sanitization', () { |
57 var validator = new NodeValidatorBuilder.common(); | 57 var validator = new NodeValidatorBuilder.common(); |
58 | 58 |
59 testHtml('allows simple constructs', | 59 testHtml('allows simple constructs', validator, |
60 validator, | |
61 '<div class="baz">something</div>'); | 60 '<div class="baz">something</div>'); |
62 | 61 |
63 testHtml('blocks unknown attributes', | 62 testHtml('blocks unknown attributes', validator, |
64 validator, | 63 '<div foo="baz">something</div>', '<div>something</div>'); |
65 '<div foo="baz">something</div>', | |
66 '<div>something</div>'); | |
67 | 64 |
68 testHtml('blocks custom element', | 65 testHtml('blocks custom element', validator, |
69 validator, | 66 '<x-my-element>something</x-my-element>', ''); |
70 '<x-my-element>something</x-my-element>', | |
71 ''); | |
72 | 67 |
73 testHtml('blocks custom is element', | 68 testHtml('blocks custom is element', validator, |
74 validator, | 69 '<div is="x-my-element">something</div>', ''); |
75 '<div is="x-my-element">something</div>', | |
76 ''); | |
77 | 70 |
78 testHtml('blocks body elements', | 71 testHtml( |
79 validator, | 72 'blocks body elements', validator, '<body background="s"></body>', ''); |
80 '<body background="s"></body>', | |
81 ''); | |
82 | 73 |
83 testHtml('allows select elements', | 74 testHtml( |
| 75 'allows select elements', |
84 validator, | 76 validator, |
85 '<select>' | 77 '<select>' |
86 '<option>a</option>' | 78 '<option>a</option>' |
87 '</select>'); | 79 '</select>'); |
88 | 80 |
89 testHtml('blocks sequential script elements', | 81 testHtml('blocks sequential script elements', validator, |
90 validator, | 82 '<div><script></script><script></script></div>', '<div></div>'); |
91 '<div><script></script><script></script></div>', | |
92 '<div></div>'); | |
93 | 83 |
94 testHtml('blocks inline styles', | 84 testHtml('blocks inline styles', validator, |
95 validator, | 85 '<div style="background: red"></div>', '<div></div>'); |
96 '<div style="background: red"></div>', | |
97 '<div></div>'); | |
98 | 86 |
99 testHtml('blocks namespaced attributes', | 87 testHtml('blocks namespaced attributes', validator, |
100 validator, | 88 '<div ns:foo="foo"></div>', '<div></div>'); |
101 '<div ns:foo="foo"></div>', | |
102 '<div></div>'); | |
103 | 89 |
104 testHtml('blocks namespaced common attributes', | 90 testHtml('blocks namespaced common attributes', validator, |
105 validator, | 91 '<div ns:class="foo"></div>', '<div></div>'); |
106 '<div ns:class="foo"></div>', | |
107 '<div></div>'); | |
108 | 92 |
109 testHtml('blocks namespaced common elements', | 93 testHtml('blocks namespaced common elements', validator, |
110 validator, | 94 '<ns:div></ns:div>', ''); |
111 '<ns:div></ns:div>', | |
112 ''); | |
113 | 95 |
114 testHtml('allows CDATA sections', | 96 testHtml('allows CDATA sections', validator, |
115 validator, | |
116 '<span>![CDATA[ some text ]]></span>'); | 97 '<span>![CDATA[ some text ]]></span>'); |
117 | 98 |
118 test('sanitizes template contents', () { | 99 test('sanitizes template contents', () { |
119 if (!TemplateElement.supported) return; | 100 if (!TemplateElement.supported) return; |
120 | 101 |
121 var html = '<template>' | 102 var html = '<template>' |
122 '<div></div>' | 103 '<div></div>' |
123 '<script></script>' | 104 '<script></script>' |
124 '<img src="http://example.com/foo"/>' | 105 '<img src="http://example.com/foo"/>' |
125 '</template>'; | 106 '</template>'; |
126 | 107 |
127 var fragment = document.body.createFragment(html, validator: validator); | 108 var fragment = document.body.createFragment(html, validator: validator); |
128 var template = fragment.nodes.single as TemplateElement; | 109 var template = fragment.nodes.single as TemplateElement; |
129 | 110 |
130 var expectedContent = document.body.createFragment( | 111 var expectedContent = document.body.createFragment('<div></div>' |
131 '<div></div>' | |
132 '<img/>'); | 112 '<img/>'); |
133 | 113 |
134 validateNodeTree(template.content, expectedContent); | 114 validateNodeTree(template.content, expectedContent); |
135 }); | 115 }); |
136 | 116 |
137 test("appendHtml is sanitized", () { | 117 test("appendHtml is sanitized", () { |
138 var html = '<body background="s"></body><div></div>'; | 118 var html = '<body background="s"></body><div></div>'; |
139 document.body.appendHtml('<div id="stuff"></div>'); | 119 document.body.appendHtml('<div id="stuff"></div>'); |
140 var stuff = document.querySelector("#stuff"); | 120 var stuff = document.querySelector("#stuff"); |
141 stuff.appendHtml(html); | 121 stuff.appendHtml(html); |
142 expect(stuff.childNodes.length, 1); | 122 expect(stuff.childNodes.length, 1); |
143 stuff.remove(); | 123 stuff.remove(); |
144 }); | 124 }); |
145 | 125 |
146 test("documentFragment.appendHtml is sanitized", () { | 126 test("documentFragment.appendHtml is sanitized", () { |
147 var html = '<div id="things></div>'; | 127 var html = '<div id="things></div>'; |
148 var fragment = new DocumentFragment.html(html); | 128 var fragment = new DocumentFragment.html(html); |
149 fragment.appendHtml('<div id="bad"><script></script></div>'); | 129 fragment.appendHtml('<div id="bad"><script></script></div>'); |
150 expect(fragment.childNodes.length, 1); | 130 expect(fragment.childNodes.length, 1); |
151 var child = fragment.childNodes[0] as Element; | 131 var child = fragment.childNodes[0] as Element; |
152 expect(child.id, "bad"); | 132 expect(child.id, "bad"); |
153 expect(child.childNodes.length, 0); | 133 expect(child.childNodes.length, 0); |
154 }); | 134 }); |
155 | 135 |
156 testHtml("sanitizes embed", | 136 testHtml( |
157 validator, | 137 "sanitizes embed", |
158 "<div><embed src='' type='application/x-shockwave-flash'></embed></div>", | 138 validator, |
159 "<div></div>"); | 139 "<div><embed src='' type='application/x-shockwave-flash'></embed></div>"
, |
| 140 "<div></div>"); |
160 }); | 141 }); |
161 | 142 |
162 group('URI_sanitization', () { | 143 group('URI_sanitization', () { |
163 var recorder = new RecordingUriValidator(); | 144 var recorder = new RecordingUriValidator(); |
164 var validator = new NodeValidatorBuilder()..allowHtml5(uriPolicy: recorder); | 145 var validator = new NodeValidatorBuilder()..allowHtml5(uriPolicy: recorder); |
165 | 146 |
166 checkUriPolicyCalls(String name, String html, String reference, | 147 checkUriPolicyCalls(String name, String html, String reference, |
167 List<String> expectedCalls) { | 148 List<String> expectedCalls) { |
168 | |
169 test(name, () { | 149 test(name, () { |
170 recorder.reset(); | 150 recorder.reset(); |
171 | 151 |
172 validateHtml(html, reference, validator); | 152 validateHtml(html, reference, validator); |
173 expect(recorder.calls, expectedCalls); | 153 expect(recorder.calls, expectedCalls); |
174 }); | 154 }); |
175 } | 155 } |
176 | 156 |
177 checkUriPolicyCalls('a::href', | 157 checkUriPolicyCalls('a::href', '<a href="s"></a>', '<a></a>', ['s']); |
178 '<a href="s"></a>', | |
179 '<a></a>', | |
180 ['s']); | |
181 | 158 |
182 checkUriPolicyCalls('area::href', | 159 checkUriPolicyCalls( |
183 '<area href="s"></area>', | 160 'area::href', '<area href="s"></area>', '<area></area>', ['s']); |
184 '<area></area>', | |
185 ['s']); | |
186 | 161 |
187 checkUriPolicyCalls('blockquote::cite', | 162 checkUriPolicyCalls( |
| 163 'blockquote::cite', |
188 '<blockquote cite="s"></blockquote>', | 164 '<blockquote cite="s"></blockquote>', |
189 '<blockquote></blockquote>', | 165 '<blockquote></blockquote>', |
190 ['s']); | 166 ['s']); |
191 checkUriPolicyCalls('command::icon', | 167 checkUriPolicyCalls( |
192 '<command icon="s"/>', | 168 'command::icon', '<command icon="s"/>', '<command/>', ['s']); |
193 '<command/>', | 169 checkUriPolicyCalls('img::src', '<img src="s"/>', '<img/>', ['s']); |
194 ['s']); | 170 checkUriPolicyCalls('input::src', '<input src="s"/>', '<input/>', ['s']); |
195 checkUriPolicyCalls('img::src', | 171 checkUriPolicyCalls( |
196 '<img src="s"/>', | 172 'ins::cite', '<ins cite="s"></ins>', '<ins></ins>', ['s']); |
197 '<img/>', | 173 checkUriPolicyCalls('q::cite', '<q cite="s"></q>', '<q></q>', ['s']); |
198 ['s']); | 174 checkUriPolicyCalls( |
199 checkUriPolicyCalls('input::src', | 175 'video::poster', '<video poster="s"/>', '<video/>', ['s']); |
200 '<input src="s"/>', | |
201 '<input/>', | |
202 ['s']); | |
203 checkUriPolicyCalls('ins::cite', | |
204 '<ins cite="s"></ins>', | |
205 '<ins></ins>', | |
206 ['s']); | |
207 checkUriPolicyCalls('q::cite', | |
208 '<q cite="s"></q>', | |
209 '<q></q>', | |
210 ['s']); | |
211 checkUriPolicyCalls('video::poster', | |
212 '<video poster="s"/>', | |
213 '<video/>', | |
214 ['s']); | |
215 }); | 176 }); |
216 | 177 |
217 group('allowNavigation', () { | 178 group('allowNavigation', () { |
218 var validator = new NodeValidatorBuilder()..allowNavigation(); | 179 var validator = new NodeValidatorBuilder()..allowNavigation(); |
219 | 180 |
220 testHtml('allows anchor tags', | 181 testHtml('allows anchor tags', validator, '<a href="#foo">foo</a>'); |
221 validator, | |
222 '<a href="#foo">foo</a>'); | |
223 | 182 |
224 testHtml('allows form elements', | 183 testHtml('allows form elements', validator, |
225 validator, | |
226 '<form method="post" action="/foo"></form>'); | 184 '<form method="post" action="/foo"></form>'); |
227 | 185 |
228 testHtml('disallows script navigation', | 186 testHtml('disallows script navigation', validator, |
229 validator, | 187 '<a href="javascript:foo = 1">foo</a>', '<a>foo</a>'); |
230 '<a href="javascript:foo = 1">foo</a>', | |
231 '<a>foo</a>'); | |
232 | 188 |
233 testHtml('disallows cross-site navigation', | 189 testHtml('disallows cross-site navigation', validator, |
234 validator, | 190 '<a href="http://example.com">example.com</a>', '<a>example.com</a>'); |
235 '<a href="http://example.com">example.com</a>', | |
236 '<a>example.com</a>'); | |
237 | 191 |
238 testHtml('blocks other elements', | 192 testHtml('blocks other elements', validator, |
239 validator, | 193 '<a href="#foo"><b>foo</b></a>', '<a href="#foo"></a>'); |
240 '<a href="#foo"><b>foo</b></a>', | |
241 '<a href="#foo"></a>'); | |
242 | 194 |
243 testHtml('blocks tag extension', | 195 testHtml('blocks tag extension', validator, '<a is="x-foo"></a>', ''); |
244 validator, | |
245 '<a is="x-foo"></a>', | |
246 ''); | |
247 }); | 196 }); |
248 | 197 |
249 group('allowImages', () { | 198 group('allowImages', () { |
250 var validator = new NodeValidatorBuilder()..allowImages(); | 199 var validator = new NodeValidatorBuilder()..allowImages(); |
251 | 200 |
252 testHtml('allows images', | 201 testHtml('allows images', validator, |
253 validator, | |
254 '<img src="/foo.jpg" alt="something" width="100" height="100"/>'); | 202 '<img src="/foo.jpg" alt="something" width="100" height="100"/>'); |
255 | 203 |
256 testHtml('blocks onerror', | 204 testHtml('blocks onerror', validator, |
257 validator, | 205 '<img src="/foo.jpg" onerror="something"/>', '<img src="/foo.jpg"/>'); |
258 '<img src="/foo.jpg" onerror="something"/>', | |
259 '<img src="/foo.jpg"/>'); | |
260 | 206 |
261 testHtml('enforces same-origin', | 207 testHtml('enforces same-origin', validator, |
262 validator, | 208 '<img src="http://example.com/foo.jpg"/>', '<img/>'); |
263 '<img src="http://example.com/foo.jpg"/>', | |
264 '<img/>'); | |
265 }); | 209 }); |
266 | 210 |
267 group('allowCustomElement', () { | 211 group('allowCustomElement', () { |
268 var validator = new NodeValidatorBuilder() | 212 var validator = new NodeValidatorBuilder() |
269 ..allowCustomElement( | 213 ..allowCustomElement('x-foo', attributes: ['bar'], uriAttributes: ['baz']) |
270 'x-foo', | |
271 attributes: ['bar'], | |
272 uriAttributes: ['baz']) | |
273 ..allowHtml5(); | 214 ..allowHtml5(); |
274 | 215 |
275 testHtml('allows custom elements', | 216 testHtml('allows custom elements', validator, |
276 validator, | |
277 '<x-foo bar="something" baz="/foo.jpg"></x-foo>'); | 217 '<x-foo bar="something" baz="/foo.jpg"></x-foo>'); |
278 | 218 |
| 219 testHtml('validates custom tag URIs', validator, |
| 220 '<x-foo baz="http://example.com/foo.jpg"></x-foo>', '<x-foo></x-foo>'); |
279 | 221 |
280 testHtml('validates custom tag URIs', | 222 testHtml('blocks type extensions', validator, '<div is="x-foo"></div>', ''); |
281 validator, | |
282 '<x-foo baz="http://example.com/foo.jpg"></x-foo>', | |
283 '<x-foo></x-foo>'); | |
284 | 223 |
285 testHtml('blocks type extensions', | 224 testHtml('blocks tags on non-matching elements', validator, |
286 validator, | 225 '<div bar="foo"></div>', '<div></div>'); |
287 '<div is="x-foo"></div>', | |
288 ''); | |
289 | |
290 testHtml('blocks tags on non-matching elements', | |
291 validator, | |
292 '<div bar="foo"></div>', | |
293 '<div></div>'); | |
294 }); | 226 }); |
295 | 227 |
296 group('identify Uri attributes listed as attributes', () { | 228 group('identify Uri attributes listed as attributes', () { |
297 var validator = new NodeValidatorBuilder() | 229 var validator = new NodeValidatorBuilder() |
298 ..allowElement( | 230 ..allowElement('a', attributes: ['href']); |
299 'a', | |
300 attributes: ['href']); | |
301 | 231 |
302 testHtml('reject different-origin link', | 232 testHtml( |
303 validator, | 233 'reject different-origin link', |
| 234 validator, |
304 '<a href="http://www.google.com/foo">Google-Foo</a>', | 235 '<a href="http://www.google.com/foo">Google-Foo</a>', |
305 '<a>Google-Foo</a>'); | 236 '<a>Google-Foo</a>'); |
306 }); | 237 }); |
307 | 238 |
308 group('allowTagExtension', () { | 239 group('allowTagExtension', () { |
309 var validator = new NodeValidatorBuilder() | 240 var validator = new NodeValidatorBuilder() |
310 ..allowTagExtension( | 241 ..allowTagExtension('x-foo', 'div', |
311 'x-foo', | 242 attributes: ['bar'], uriAttributes: ['baz']) |
312 'div', | |
313 attributes: ['bar'], | |
314 uriAttributes: ['baz']) | |
315 ..allowHtml5(); | 243 ..allowHtml5(); |
316 | 244 |
317 testHtml('allows tag extensions', | 245 testHtml('allows tag extensions', validator, |
318 validator, | |
319 '<div is="x-foo" bar="something" baz="/foo.jpg"></div>'); | 246 '<div is="x-foo" bar="something" baz="/foo.jpg"></div>'); |
320 | 247 |
321 testHtml('blocks custom elements', | 248 testHtml('blocks custom elements', validator, '<x-foo></x-foo>', ''); |
322 validator, | |
323 '<x-foo></x-foo>', | |
324 ''); | |
325 | 249 |
326 testHtml('validates tag extension URIs', | 250 testHtml( |
| 251 'validates tag extension URIs', |
327 validator, | 252 validator, |
328 '<div is="x-foo" baz="http://example.com/foo.jpg"></div>', | 253 '<div is="x-foo" baz="http://example.com/foo.jpg"></div>', |
329 '<div is="x-foo"></div>'); | 254 '<div is="x-foo"></div>'); |
330 | 255 |
331 testHtml('blocks tags on non-matching elements', | 256 testHtml('blocks tags on non-matching elements', validator, |
332 validator, | 257 '<div bar="foo"></div>', '<div></div>'); |
333 '<div bar="foo"></div>', | |
334 '<div></div>'); | |
335 | 258 |
336 testHtml('blocks non-matching tags', | 259 testHtml('blocks non-matching tags', validator, |
337 validator, | 260 '<span is="x-foo">something</span>', ''); |
338 '<span is="x-foo">something</span>', | |
339 ''); | |
340 | 261 |
341 validator = new NodeValidatorBuilder() | 262 validator = new NodeValidatorBuilder() |
342 ..allowTagExtension( | 263 ..allowTagExtension('x-foo', 'div', |
343 'x-foo', | 264 attributes: ['bar'], uriAttributes: ['baz']) |
344 'div', | 265 ..allowTagExtension('x-else', 'div'); |
345 attributes: ['bar'], | |
346 uriAttributes: ['baz']) | |
347 ..allowTagExtension( | |
348 'x-else', | |
349 'div'); | |
350 | 266 |
351 testHtml('blocks tags on non-matching custom elements', | 267 testHtml('blocks tags on non-matching custom elements', validator, |
352 validator, | 268 '<div bar="foo" is="x-else"></div>', '<div is="x-else"></div>'); |
353 '<div bar="foo" is="x-else"></div>', | |
354 '<div is="x-else"></div>'); | |
355 }); | 269 }); |
356 | 270 |
357 group('allowTemplating', () { | 271 group('allowTemplating', () { |
358 var validator = new NodeValidatorBuilder() | 272 var validator = new NodeValidatorBuilder() |
359 ..allowTemplating() | 273 ..allowTemplating() |
360 ..allowHtml5(); | 274 ..allowHtml5(); |
361 | 275 |
362 testHtml('allows templates', | 276 testHtml( |
363 validator, | 277 'allows templates', validator, '<template bind="{{a}}"></template>'); |
364 '<template bind="{{a}}"></template>'); | |
365 | 278 |
366 testHtml('allows template attributes', | 279 testHtml('allows template attributes', validator, |
367 validator, | |
368 '<template bind="{{a}}" ref="foo" repeat="{{}}" if="{{}}" syntax="foo"><
/template>'); | 280 '<template bind="{{a}}" ref="foo" repeat="{{}}" if="{{}}" syntax="foo"><
/template>'); |
369 | 281 |
370 testHtml('allows template attribute', | 282 testHtml('allows template attribute', validator, |
371 validator, | |
372 '<div template repeat="{{}}"></div>'); | 283 '<div template repeat="{{}}"></div>'); |
373 | 284 |
374 testHtml('blocks illegal template attribute', | 285 testHtml('blocks illegal template attribute', validator, |
375 validator, | 286 '<div template="foo" repeat="{{}}"></div>', '<div></div>'); |
376 '<div template="foo" repeat="{{}}"></div>', | |
377 '<div></div>'); | |
378 }); | 287 }); |
379 | 288 |
380 group('allowSvg', () { | 289 group('allowSvg', () { |
381 var validator = new NodeValidatorBuilder() | 290 var validator = new NodeValidatorBuilder() |
382 ..allowSvg() | 291 ..allowSvg() |
383 ..allowTextElements(); | 292 ..allowTextElements(); |
384 | 293 |
385 testHtml('allows basic SVG', | 294 testHtml( |
386 validator, | 295 'allows basic SVG', |
387 '<svg xmlns="http://www.w3.org/2000/svg' | 296 validator, |
388 'xmlns:xlink="http://www.w3.org/1999/xlink">' | 297 '<svg xmlns="http://www.w3.org/2000/svg' |
| 298 'xmlns:xlink="http://www.w3.org/1999/xlink">' |
389 '<image xlink:href="foo" data-foo="bar"/>' | 299 '<image xlink:href="foo" data-foo="bar"/>' |
390 '</svg>'); | 300 '</svg>'); |
391 | 301 |
392 testHtml('blocks script elements', | 302 testHtml( |
393 validator, | 303 'blocks script elements', |
394 '<svg xmlns="http://www.w3.org/2000/svg>' | 304 validator, |
| 305 '<svg xmlns="http://www.w3.org/2000/svg>' |
395 '<script></script>' | 306 '<script></script>' |
396 '</svg>', | 307 '</svg>', |
397 ''); | 308 ''); |
398 | 309 |
399 testHtml('blocks script elements but allows other', | 310 testHtml( |
400 validator, | 311 'blocks script elements but allows other', |
401 '<svg xmlns="http://www.w3.org/2000/svg>' | 312 validator, |
| 313 '<svg xmlns="http://www.w3.org/2000/svg>' |
402 '<script></script><ellipse cx="200" cy="80" rx="100" ry="50"></ellipse>' | 314 '<script></script><ellipse cx="200" cy="80" rx="100" ry="50"></ellipse>' |
403 '</svg>', | 315 '</svg>', |
404 '<svg xmlns="http://www.w3.org/2000/svg>' | 316 '<svg xmlns="http://www.w3.org/2000/svg>' |
405 '<ellipse cx="200" cy="80" rx="100" ry="50"></ellipse>' | 317 '<ellipse cx="200" cy="80" rx="100" ry="50"></ellipse>' |
406 '</svg>'); | 318 '</svg>'); |
407 | 319 |
408 testHtml('blocks script handlers', | 320 testHtml( |
409 validator, | 321 'blocks script handlers', |
410 '<svg xmlns="http://www.w3.org/2000/svg' | 322 validator, |
411 'xmlns:xlink="http://www.w3.org/1999/xlink">' | 323 '<svg xmlns="http://www.w3.org/2000/svg' |
| 324 'xmlns:xlink="http://www.w3.org/1999/xlink">' |
412 '<image xlink:href="foo" onerror="something"/>' | 325 '<image xlink:href="foo" onerror="something"/>' |
413 '</svg>', | 326 '</svg>', |
414 '<svg xmlns="http://www.w3.org/2000/svg' | 327 '<svg xmlns="http://www.w3.org/2000/svg' |
415 'xmlns:xlink="http://www.w3.org/1999/xlink">' | 328 'xmlns:xlink="http://www.w3.org/1999/xlink">' |
416 '<image xlink:href="foo"/>' | 329 '<image xlink:href="foo"/>' |
417 '</svg>'); | 330 '</svg>'); |
418 | 331 |
419 testHtml('blocks foreignObject content', | 332 testHtml( |
420 validator, | 333 'blocks foreignObject content', |
421 '<svg xmlns="http://www.w3.org/2000/svg">' | 334 validator, |
| 335 '<svg xmlns="http://www.w3.org/2000/svg">' |
422 '<foreignobject width="100" height="150">' | 336 '<foreignobject width="100" height="150">' |
423 '<body xmlns="http://www.w3.org/1999/xhtml">' | 337 '<body xmlns="http://www.w3.org/1999/xhtml">' |
424 '<div>Some content</div>' | 338 '<div>Some content</div>' |
425 '</body>' | 339 '</body>' |
426 '</foreignobject>' | 340 '</foreignobject>' |
427 '<b>42</b>' | 341 '<b>42</b>' |
428 '</svg>', | 342 '</svg>', |
429 '<svg xmlns="http://www.w3.org/2000/svg">' | 343 '<svg xmlns="http://www.w3.org/2000/svg">' |
430 '<b>42</b>' | 344 '<b>42</b>' |
431 '</svg>'); | 345 '</svg>'); |
432 }); | 346 }); |
433 | 347 |
434 group('allowInlineStyles', () { | 348 group('allowInlineStyles', () { |
435 var validator = new NodeValidatorBuilder() | 349 var validator = new NodeValidatorBuilder() |
436 ..allowTextElements() | 350 ..allowTextElements() |
437 ..allowInlineStyles(); | 351 ..allowInlineStyles(); |
438 | 352 |
439 testHtml('allows inline styles', | 353 testHtml('allows inline styles', validator, |
440 validator, | |
441 '<span style="background-color:red">text</span>'); | 354 '<span style="background-color:red">text</span>'); |
442 | 355 |
443 testHtml('blocks other attributes', | 356 testHtml('blocks other attributes', validator, |
444 validator, | 357 '<span class="red-span"></span>', '<span></span>'); |
445 '<span class="red-span"></span>', | |
446 '<span></span>'); | |
447 | 358 |
448 validator = new NodeValidatorBuilder() | 359 validator = new NodeValidatorBuilder() |
449 ..allowTextElements() | 360 ..allowTextElements() |
450 ..allowInlineStyles(tagName: 'span'); | 361 ..allowInlineStyles(tagName: 'span'); |
451 | 362 |
452 testHtml('scoped allows inline styles on spans', | 363 testHtml('scoped allows inline styles on spans', validator, |
453 validator, | |
454 '<span style="background-color:red">text</span>'); | 364 '<span style="background-color:red">text</span>'); |
455 | 365 |
456 testHtml('scoped blocks inline styles on LIs', | 366 testHtml('scoped blocks inline styles on LIs', validator, |
457 validator, | 367 '<li style="background-color:red">text</li>', '<li>text</li>'); |
458 '<li style="background-color:red">text</li>', | |
459 '<li>text</li>'); | |
460 }); | 368 }); |
461 | 369 |
462 group('throws', () { | 370 group('throws', () { |
463 var validator = new NodeValidator.throws(new NodeValidatorBuilder.common()); | 371 var validator = new NodeValidator.throws(new NodeValidatorBuilder.common()); |
464 | 372 |
465 var validationError = throwsArgumentError; | 373 var validationError = throwsArgumentError; |
466 | 374 |
467 test('does not throw on valid syntax', () { | 375 test('does not throw on valid syntax', () { |
468 expect(() { | 376 expect(() { |
469 document.body.createFragment('<div></div>', validator: validator); | 377 document.body.createFragment('<div></div>', validator: validator); |
470 }, returnsNormally); | 378 }, returnsNormally); |
471 }); | 379 }); |
472 | 380 |
473 test('throws on invalid elements', () { | 381 test('throws on invalid elements', () { |
474 expect(() { | 382 expect(() { |
475 document.body.createFragment('<foo></foo>', validator: validator); | 383 document.body.createFragment('<foo></foo>', validator: validator); |
476 }, validationError); | 384 }, validationError); |
477 }); | 385 }); |
478 | 386 |
479 test('throws on invalid attributes', () { | 387 test('throws on invalid attributes', () { |
480 expect(() { | 388 expect(() { |
481 document.body.createFragment('<div foo="bar"></div>', | 389 document.body |
482 validator: validator); | 390 .createFragment('<div foo="bar"></div>', validator: validator); |
483 }, validationError); | 391 }, validationError); |
484 }); | 392 }); |
485 | 393 |
486 test('throws on invalid attribute values', () { | 394 test('throws on invalid attribute values', () { |
487 expect(() { | 395 expect(() { |
488 document.body.createFragment('<img src="http://example.com/foo.jpg"/>', | 396 document.body.createFragment('<img src="http://example.com/foo.jpg"/>', |
489 validator: validator); | 397 validator: validator); |
490 }, validationError); | 398 }, validationError); |
491 }); | 399 }); |
492 }); | 400 }); |
493 | 401 |
494 group('svg', () { | 402 group('svg', () { |
495 test('parsing', () { | 403 test('parsing', () { |
496 var svgText = | 404 var svgText = '<svg xmlns="http://www.w3.org/2000/svg' |
497 '<svg xmlns="http://www.w3.org/2000/svg' | 405 'xmlns:xlink="http://www.w3.org/1999/xlink">' |
498 'xmlns:xlink="http://www.w3.org/1999/xlink">' | |
499 '<image xlink:href="foo" data-foo="bar"/>' | 406 '<image xlink:href="foo" data-foo="bar"/>' |
500 '</svg>'; | 407 '</svg>'; |
501 | 408 |
502 var fragment = new DocumentFragment.svg(svgText); | 409 var fragment = new DocumentFragment.svg(svgText); |
503 var element = fragment.nodes.first as Element; | 410 var element = fragment.nodes.first as Element; |
504 expect(element is svg.SvgSvgElement, isTrue); | 411 expect(element is svg.SvgSvgElement, isTrue); |
505 expect(element.children[0] is svg.ImageElement, isTrue); | 412 expect(element.children[0] is svg.ImageElement, isTrue); |
506 }); | 413 }); |
507 }); | 414 }); |
508 | 415 |
509 group('dom_clobbering', () { | 416 group('dom_clobbering', () { |
510 var validator = new NodeValidatorBuilder.common(); | 417 var validator = new NodeValidatorBuilder.common(); |
511 | 418 |
512 testHtml('DOM clobbering of attributes with single node', | 419 testHtml( |
513 validator, | 420 'DOM clobbering of attributes with single node', |
514 "<form id='single_node_clobbering' onmouseover='alert(1)'><input name='attri
butes'>", | 421 validator, |
515 ""); | 422 "<form id='single_node_clobbering' onmouseover='alert(1)'><input name='a
ttributes'>", |
| 423 ""); |
516 | 424 |
517 testHtml('DOM clobbering of attributes with multiple nodes', | 425 testHtml( |
518 validator, | 426 'DOM clobbering of attributes with multiple nodes', |
519 "<form onmouseover='alert(1)'><input name='attributes'>" | 427 validator, |
520 "<input name='attributes'>", | 428 "<form onmouseover='alert(1)'><input name='attributes'>" |
521 ""); | 429 "<input name='attributes'>", |
| 430 ""); |
522 | 431 |
523 testHtml('DOM clobbering of lastChild', | 432 testHtml('DOM clobbering of lastChild', validator, |
524 validator, | 433 "<form><input name='lastChild'><input onmouseover='alert(1)'>", ""); |
525 "<form><input name='lastChild'><input onmouseover='alert(1)'>", | |
526 ""); | |
527 | 434 |
528 testHtml('DOM clobbering of both children and lastChild', | 435 testHtml( |
529 validator, | 436 'DOM clobbering of both children and lastChild', |
530 "<form><input name='lastChild'><input name='children'>" | 437 validator, |
531 "<input id='children'><input onmouseover='alert(1)'>", | 438 "<form><input name='lastChild'><input name='children'>" |
532 ""); | 439 "<input id='children'><input onmouseover='alert(1)'>", |
| 440 ""); |
533 | 441 |
534 testHtml('DOM clobbering of both children and lastChild, different order', | 442 testHtml( |
535 validator, | 443 'DOM clobbering of both children and lastChild, different order', |
536 "<form><input name='children'><input name='children'>" | 444 validator, |
537 "<input id='children' name='lastChild'>" | 445 "<form><input name='children'><input name='children'>" |
538 "<input id='bad' onmouseover='alert(1)'>", | 446 "<input id='children' name='lastChild'>" |
539 ""); | 447 "<input id='bad' onmouseover='alert(1)'>", |
| 448 ""); |
540 | 449 |
541 test('tagName makes containing form invalid', () { | 450 test('tagName makes containing form invalid', () { |
542 var fragment = document.body.createFragment( | 451 var fragment = document.body.createFragment( |
543 "<form onmouseover='alert(2)'><input name='tagName'>", | 452 "<form onmouseover='alert(2)'><input name='tagName'>", |
544 validator: validator); | 453 validator: validator); |
545 var form = fragment.lastChild as FormElement; | 454 var form = fragment.lastChild as FormElement; |
546 // If the tagName was clobbered, the sanitizer should have removed | 455 // If the tagName was clobbered, the sanitizer should have removed |
547 // the whole thing and form is null. | 456 // the whole thing and form is null. |
548 // If the tagName was not clobbered, then there will be content, | 457 // If the tagName was not clobbered, then there will be content, |
549 // but the tagName should be the normal value. IE11 has started | 458 // but the tagName should be the normal value. IE11 has started |
550 // doing this. | 459 // doing this. |
551 if (form != null) { | 460 if (form != null) { |
552 expect(form.tagName, 'FORM'); | 461 expect(form.tagName, 'FORM'); |
553 } | 462 } |
554 }); | 463 }); |
555 | 464 |
556 test('tagName without mouseover', () { | 465 test('tagName without mouseover', () { |
557 var fragment = document.body.createFragment( | 466 var fragment = document.body |
558 "<form><input name='tagName'>", | 467 .createFragment("<form><input name='tagName'>", validator: validator); |
559 validator: validator); | |
560 var form = fragment.lastChild as FormElement; | 468 var form = fragment.lastChild as FormElement; |
561 // If the tagName was clobbered, the sanitizer should have removed | 469 // If the tagName was clobbered, the sanitizer should have removed |
562 // the whole thing and form is null. | 470 // the whole thing and form is null. |
563 // If the tagName was not clobbered, then there will be content, | 471 // If the tagName was not clobbered, then there will be content, |
564 // but the tagName should be the normal value. | 472 // but the tagName should be the normal value. |
565 if (form != null) { | 473 if (form != null) { |
566 expect(form.tagName, 'FORM'); | 474 expect(form.tagName, 'FORM'); |
567 } | 475 } |
568 }); | 476 }); |
569 }); | 477 }); |
570 } | 478 } |
OLD | NEW |