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