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 part of dart.dom.html; | |
6 | |
7 | |
8 /** | |
9 * Class which helps construct standard node validation policies. | |
10 * | |
11 * By default this will not accept anything, but the 'allow*' functions can be | |
12 * used to expand what types of elements or attributes are allowed. | |
13 * | |
14 * All allow functions are additive- elements will be accepted if they are | |
15 * accepted by any specific rule. | |
16 * | |
17 * It is important to remember that sanitization is not just intended to prevent | |
18 * cross-site scripting attacks, but also to prevent information from being | |
19 * displayed in unexpected ways. For example something displaying basic | |
20 * formatted text may not expect `<video>` tags to appear. In this case an | |
21 * empty NodeValidatorBuilder with just [allowTextElements] might be | |
22 * appropriate. | |
23 */ | |
24 class NodeValidatorBuilder implements NodeValidator { | |
25 | |
26 final List<NodeValidator> _validators = <NodeValidator>[]; | |
27 | |
28 NodeValidatorBuilder() { | |
29 } | |
30 | |
31 /** | |
32 * Creates a new NodeValidatorBuilder which accepts common constructs. | |
33 * | |
34 * By default this will accept HTML5 elements and attributes with the default | |
35 * [UriPolicy] and templating elements. | |
36 * | |
37 * Notable syntax which is filtered: | |
38 * | |
39 * * Only known-good HTML5 elements and attributes are allowed. | |
40 * * All URLs must be same-origin, use [allowNavigation] and [allowImages] to | |
41 * specify additional URI policies. | |
42 * * Inline-styles are not allowed. | |
43 * * Custom element tags are disallowed, use [allowCustomElement]. | |
44 * * Custom tags extensions are disallowed, use [allowTagExtension]. | |
45 * * SVG Elements are not allowed, use [allowSvg]. | |
46 * | |
47 * For scenarios where the HTML should only contain formatted text | |
48 * [allowTextElements] is more appropriate. | |
49 * | |
50 * Use [allowSvg] to allow SVG elements. | |
51 */ | |
52 factory NodeValidatorBuilder.common() { | |
Jennifer Messerly
2013/08/17 06:07:07
as a factory constructor, this is hard to use in a
blois
2013/08/19 22:02:09
Done. Though most extension should be via composit
| |
53 return new NodeValidatorBuilder() | |
54 ..allowHtml5() | |
55 ..allowTemplating(); | |
56 } | |
57 | |
58 /** | |
59 * Allows navigation elements- Form and Anchor tags, along with common | |
60 * attributes. | |
61 * | |
62 * The UriPolicy can be used to restrict the locations the navigation elements | |
63 * are allowed to direct to. By default this will use the default [UriPolicy]. | |
64 */ | |
65 void allowNavigation([UriPolicy uriPolicy]) { | |
66 if (uriPolicy == null) { | |
67 uriPolicy = new UriPolicy(); | |
68 } | |
69 add(new _SimpleNodeValidator.allowNavigation(uriPolicy)); | |
70 } | |
71 | |
72 /** | |
73 * Allows image elements. | |
74 * | |
75 * The UriPolicy can be used to restrict the locations the images may be | |
76 * loaded from. By default this will use the default [UriPolicy]. | |
77 */ | |
78 void allowImages([UriPolicy uriPolicy]) { | |
79 if (uriPolicy == null) { | |
80 uriPolicy = new UriPolicy(); | |
81 } | |
82 add(new _SimpleNodeValidator.allowImages(uriPolicy)); | |
83 } | |
84 | |
85 /** | |
86 * Allow basic text elements. | |
87 * | |
88 * This allows a subset of HTML5 elements, specifically just these tags and | |
89 * no attributes. | |
90 * | |
91 * * B | |
92 * * BLOCKQUOTE | |
93 * * BR | |
94 * * EM | |
95 * * H1 | |
96 * * H2 | |
97 * * H3 | |
98 * * H4 | |
99 * * H5 | |
100 * * H6 | |
101 * * HR | |
102 * * I | |
103 * * LI | |
104 * * OL | |
105 * * P | |
106 * * SPAN | |
107 * * UL | |
108 */ | |
109 void allowTextElements() { | |
110 add(new _SimpleNodeValidator.allowTextElements()); | |
111 } | |
112 | |
113 /** | |
114 * Allow common safe HTML5 elements and attributes. | |
115 * | |
116 * This list is based off of the Caja whitelists at: | |
117 * https://code.google.com/p/google-caja/wiki/CajaWhitelists. | |
118 * | |
119 * Common things which are not allowed are script elements, style attributes | |
120 * and any script handlers. | |
121 */ | |
122 void allowHtml5({UriPolicy uriPolicy}) { | |
123 add(new _Html5NodeValidator(uriPolicy: uriPolicy)); | |
124 } | |
125 | |
126 /** | |
127 * Allow SVG elements and attributes except for known bad ones. | |
128 */ | |
129 void allowSvg() { | |
130 add(new _SvgNodeValidator()); | |
131 } | |
132 | |
133 /** | |
134 * Allow custom elements with the specified tag name and specified attributes. | |
135 * | |
136 * This will allow the elements as custom tags (such as <x-foo></x-foo>), | |
137 * but will not allow tag extensions. Use [allowTagExtension] to allow | |
138 * tag extensions. | |
139 */ | |
140 void allowCustomElement(String tagName, | |
141 {UriPolicy uriPolicy, | |
142 Iterable<String> attributes, | |
143 Iterable<String> uriAttributes}) { | |
144 | |
145 var tagNameUpper = tagName.toUpperCase(); | |
146 var attrs; | |
147 if (attributes != null) { | |
148 attrs = | |
149 attributes.map((name) => '$tagNameUpper::${name.toLowerCase()}'); | |
150 } | |
151 var uriAttrs; | |
152 if (uriAttributes != null) { | |
153 uriAttrs = | |
154 uriAttributes.map((name) => '$tagNameUpper::${name.toLowerCase()}'); | |
155 } | |
156 if (uriPolicy == null) { | |
157 uriPolicy = new UriPolicy(); | |
158 } | |
159 | |
160 add(new _CustomElementNodeValidator( | |
161 uriPolicy, | |
162 [tagNameUpper], | |
163 attrs, | |
164 uriAttrs, | |
165 false, | |
166 true)); | |
167 } | |
168 | |
169 /** | |
170 * Allow custom tag extensions with the specified type name and specified | |
171 * attributes. | |
172 * | |
173 * This will allow tag extensions (such as <div is="x-foo"></div>), | |
174 * but will not allow custom tags. Use [allowCustomElement] to allow | |
175 * custom tags. | |
176 */ | |
177 void allowTagExtension(String tagName, String baseName, | |
178 {UriPolicy uriPolicy, | |
179 Iterable<String> attributes, | |
180 Iterable<String> uriAttributes}) { | |
181 | |
182 var baseNameUpper = baseName.toUpperCase(); | |
183 var tagNameUpper = tagName.toUpperCase(); | |
184 var attrs; | |
185 if (attributes != null) { | |
186 attrs = | |
187 attributes.map((name) => '$baseNameUpper::${name.toLowerCase()}'); | |
188 } | |
189 var uriAttrs; | |
190 if (uriAttributes != null) { | |
191 uriAttrs = | |
192 uriAttributes.map((name) => '$baseNameUpper::${name.toLowerCase()}'); | |
193 } | |
194 if (uriPolicy == null) { | |
195 uriPolicy = new UriPolicy(); | |
196 } | |
197 | |
198 add(new _CustomElementNodeValidator( | |
199 uriPolicy, | |
200 [tagNameUpper, baseNameUpper], | |
201 attrs, | |
202 uriAttrs, | |
203 true, | |
204 false)); | |
205 } | |
206 | |
207 void allowElement(String tagName, {UriPolicy uriPolicy, | |
208 Iterable<String> attributes, | |
209 Iterable<String> uriAttributes}) { | |
210 | |
211 allowCustomElement(tagName, uriPolicy: uriPolicy, | |
212 attributes: attributes, | |
213 uriAttributes: uriAttributes); | |
214 } | |
215 | |
216 /** | |
217 * Allow templating elements (such as <template> and template-related | |
218 * attributes. | |
219 * | |
220 * This still requires other validators to allow regular attributes to be | |
221 * bound (such as [allowHtml5]). | |
222 */ | |
223 void allowTemplating() { | |
224 add(new _TemplatingNodeValidator()); | |
225 } | |
226 | |
227 /** | |
228 * Add an additional validator to the current list of validators. | |
229 * | |
230 * Elements and attributes will be accepted if they are accepted by any | |
231 * validators. | |
232 */ | |
233 void add(NodeValidator validator) { | |
234 _validators.add(validator); | |
235 } | |
236 | |
237 bool allowsElement(Element element) { | |
238 return _validators.any((v) => v.allowsElement(element)); | |
239 } | |
240 | |
241 bool allowsAttribute(Element element, String attributeName, String value) { | |
242 return _validators.any( | |
243 (v) => v.allowsAttribute(element, attributeName, value)); | |
244 } | |
245 } | |
246 | |
247 class _SimpleNodeValidator implements NodeValidator { | |
248 final Set<String> allowedElements; | |
249 final Set<String> allowedAttributes; | |
250 final Set<String> allowedUriAttributes; | |
251 final UriPolicy uriPolicy; | |
252 | |
253 factory _SimpleNodeValidator.allowNavigation(UriPolicy uriPolicy) { | |
254 return new _SimpleNodeValidator(uriPolicy, | |
255 allowedElements: [ | |
256 'A', | |
257 'FORM'], | |
258 allowedAttributes: [ | |
259 'A::accesskey', | |
260 'A::coords', | |
261 'A::hreflang', | |
262 'A::name', | |
263 'A::shape', | |
264 'A::tabindex', | |
265 'A::target', | |
266 'A::type', | |
267 'FORM::accept', | |
268 'FORM::autocomplete', | |
269 'FORM::enctype', | |
270 'FORM::method', | |
271 'FORM::name', | |
272 'FORM::novalidate', | |
273 'FORM::target', | |
274 ], | |
275 allowedUriAttributes: [ | |
276 'A::href', | |
277 'FORM::action', | |
278 ]); | |
279 } | |
280 | |
281 factory _SimpleNodeValidator.allowImages(UriPolicy uriPolicy) { | |
282 return new _SimpleNodeValidator(uriPolicy, | |
283 allowedElements: [ | |
284 'IMG' | |
285 ], | |
286 allowedAttributes: [ | |
287 'IMG::align', | |
288 'IMG::alt', | |
289 'IMG::border', | |
290 'IMG::height', | |
291 'IMG::hspace', | |
292 'IMG::ismap', | |
293 'IMG::name', | |
294 'IMG::usemap', | |
295 'IMG::vspace', | |
296 'IMG::width', | |
297 ], | |
298 allowedUriAttributes: [ | |
299 'IMG::src', | |
300 ]); | |
301 } | |
302 | |
303 factory _SimpleNodeValidator.allowTextElements() { | |
304 return new _SimpleNodeValidator(null, | |
305 allowedElements: [ | |
306 'B', | |
307 'BLOCKQUOTE', | |
308 'BR', | |
309 'EM', | |
310 'H1', | |
311 'H2', | |
312 'H3', | |
313 'H4', | |
314 'H5', | |
315 'H6', | |
316 'HR', | |
317 'I', | |
318 'LI', | |
319 'OL', | |
320 'P', | |
321 'SPAN', | |
322 'UL', | |
323 ]); | |
324 } | |
325 | |
326 /** | |
327 * Elements must be uppercased tag names. For example `'IMG'`. | |
328 * Attributes must be uppercased tag name followed by :: followed by | |
329 * lowercase attribute name. For example `'IMG:src'`. | |
330 */ | |
331 _SimpleNodeValidator(this.uriPolicy, | |
332 {Iterable<String> allowedElements, Iterable<String> allowedAttributes, | |
333 Iterable<String> allowedUriAttributes}): | |
334 this.allowedElements = allowedElements != null ? | |
335 new Set.from(allowedElements) : new Set(), | |
336 this.allowedAttributes = allowedAttributes != null ? | |
337 new Set.from(allowedAttributes) : new Set(), | |
338 this.allowedUriAttributes = allowedUriAttributes != null ? | |
339 new Set.from(allowedUriAttributes) : new Set(); | |
340 | |
341 bool allowsElement(Element element) { | |
342 return allowedElements.contains(element.tagName); | |
343 } | |
344 | |
345 bool allowsAttribute(Element element, String attributeName, String value) { | |
346 var tagName = element.tagName; | |
347 if (allowedUriAttributes.contains('$tagName::$attributeName')) { | |
348 return uriPolicy.allowsUri(value); | |
349 } else if (allowedUriAttributes.contains('*::$attributeName')) { | |
350 return uriPolicy.allowsUri(value); | |
351 } else if (allowedAttributes.contains('$tagName::$attributeName')) { | |
352 return true; | |
353 } else if (allowedAttributes.contains('*::$attributeName')) { | |
354 return true; | |
355 } else if (allowedAttributes.contains('$tagName::*')) { | |
356 return true; | |
357 } else if (allowedAttributes.contains('*::*')) { | |
358 return true; | |
359 } | |
360 return false; | |
361 } | |
362 } | |
363 | |
364 class _CustomElementNodeValidator extends _SimpleNodeValidator { | |
365 final bool allowTypeExtension; | |
366 final bool allowCustomTag; | |
367 | |
368 _CustomElementNodeValidator(UriPolicy uriPolicy, | |
369 Iterable<String> allowedElements, | |
370 Iterable<String> allowedAttributes, | |
371 Iterable<String> allowedUriAttributes, | |
372 bool allowTypeExtension, | |
373 bool allowCustomTag): | |
374 | |
375 super(uriPolicy, | |
376 allowedElements: allowedElements, | |
377 allowedAttributes: allowedAttributes, | |
378 allowedUriAttributes: allowedUriAttributes), | |
379 this.allowTypeExtension = allowTypeExtension == true, | |
380 this.allowCustomTag = allowCustomTag == true; | |
381 | |
382 bool allowsElement(Element element) { | |
383 if (allowTypeExtension) { | |
384 var isAttr = element.attributes['is']; | |
385 if (isAttr != null) { | |
386 return allowedElements.contains(isAttr.toUpperCase()) && | |
387 allowedElements.contains(element.tagName); | |
388 } | |
389 } | |
390 return allowCustomTag && allowedElements.contains(element.tagName); | |
391 } | |
392 | |
393 bool allowsAttribute(Element element, String attributeName, String value) { | |
394 if (allowsElement(element)) { | |
395 if (allowTypeExtension && attributeName == 'is' && | |
396 allowedElements.contains(value.toUpperCase())) { | |
397 return true; | |
398 } | |
399 return super.allowsAttribute(element, attributeName, value); | |
400 } | |
401 return false; | |
402 } | |
403 } | |
404 | |
405 class _TemplatingNodeValidator extends _SimpleNodeValidator { | |
406 static const _TEMPLATE_ATTRS = | |
407 const <String>['bind', 'if', 'ref', 'repeat', 'syntax']; | |
408 | |
409 final Set<String> _templateAttrs; | |
410 | |
411 _TemplatingNodeValidator(): | |
412 super(null, | |
413 allowedElements: [ | |
414 'TEMPLATE' | |
415 ], | |
416 allowedAttributes: _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')), | |
417 _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS) { | |
418 } | |
419 | |
420 bool allowsAttribute(Element element, String attributeName, String value) { | |
421 if (super.allowsAttribute(element, attributeName, value)) { | |
422 return true; | |
423 } | |
424 | |
425 if (attributeName == 'template' && value == "") { | |
426 return true; | |
427 } | |
428 | |
429 if (element.attributes['template'] == "" ) { | |
430 return _templateAttrs.contains(attributeName); | |
431 } | |
432 return false; | |
433 } | |
434 } | |
435 | |
436 | |
437 class _SvgNodeValidator implements NodeValidator { | |
438 bool allowsElement(Element element) { | |
439 if (element is svg.ScriptElement) { | |
440 return false; | |
441 } | |
442 if (element is svg.SvgElement) { | |
443 return true; | |
444 } | |
445 return false; | |
446 } | |
447 | |
448 bool allowsAttribute(Element element, String attributeName, String value) { | |
449 if (attributeName == 'is' || attributeName.startsWith('on')) { | |
450 return false; | |
451 } | |
452 return allowsElement(element); | |
453 } | |
454 } | |
OLD | NEW |