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