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 part of dart.dom.html; | 5 part of dart.dom.html; |
6 | 6 |
7 | |
8 /** | 7 /** |
9 * Class which helps construct standard node validation policies. | 8 * Class which helps construct standard node validation policies. |
10 * | 9 * |
11 * By default this will not accept anything, but the 'allow*' functions can be | 10 * 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. | 11 * used to expand what types of elements or attributes are allowed. |
13 * | 12 * |
14 * All allow functions are additive- elements will be accepted if they are | 13 * All allow functions are additive- elements will be accepted if they are |
15 * accepted by any specific rule. | 14 * accepted by any specific rule. |
16 * | 15 * |
17 * It is important to remember that sanitization is not just intended to prevent | 16 * 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 | 17 * cross-site scripting attacks, but also to prevent information from being |
19 * displayed in unexpected ways. For example something displaying basic | 18 * displayed in unexpected ways. For example something displaying basic |
20 * formatted text may not expect `<video>` tags to appear. In this case an | 19 * formatted text may not expect `<video>` tags to appear. In this case an |
21 * empty NodeValidatorBuilder with just [allowTextElements] might be | 20 * empty NodeValidatorBuilder with just [allowTextElements] might be |
22 * appropriate. | 21 * appropriate. |
23 */ | 22 */ |
24 class NodeValidatorBuilder implements NodeValidator { | 23 class NodeValidatorBuilder implements NodeValidator { |
25 | |
26 final List<NodeValidator> _validators = <NodeValidator>[]; | 24 final List<NodeValidator> _validators = <NodeValidator>[]; |
27 | 25 |
28 NodeValidatorBuilder() { | 26 NodeValidatorBuilder() {} |
29 } | |
30 | 27 |
31 /** | 28 /** |
32 * Creates a new NodeValidatorBuilder which accepts common constructs. | 29 * Creates a new NodeValidatorBuilder which accepts common constructs. |
33 * | 30 * |
34 * By default this will accept HTML5 elements and attributes with the default | 31 * By default this will accept HTML5 elements and attributes with the default |
35 * [UriPolicy] and templating elements. | 32 * [UriPolicy] and templating elements. |
36 * | 33 * |
37 * Notable syntax which is filtered: | 34 * Notable syntax which is filtered: |
38 * | 35 * |
39 * * Only known-good HTML5 elements and attributes are allowed. | 36 * * Only known-good HTML5 elements and attributes are allowed. |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
148 * Allow custom elements with the specified tag name and specified attributes. | 145 * Allow custom elements with the specified tag name and specified attributes. |
149 * | 146 * |
150 * This will allow the elements as custom tags (such as <x-foo></x-foo>), | 147 * This will allow the elements as custom tags (such as <x-foo></x-foo>), |
151 * but will not allow tag extensions. Use [allowTagExtension] to allow | 148 * but will not allow tag extensions. Use [allowTagExtension] to allow |
152 * tag extensions. | 149 * tag extensions. |
153 */ | 150 */ |
154 void allowCustomElement(String tagName, | 151 void allowCustomElement(String tagName, |
155 {UriPolicy uriPolicy, | 152 {UriPolicy uriPolicy, |
156 Iterable<String> attributes, | 153 Iterable<String> attributes, |
157 Iterable<String> uriAttributes}) { | 154 Iterable<String> uriAttributes}) { |
158 | |
159 var tagNameUpper = tagName.toUpperCase(); | 155 var tagNameUpper = tagName.toUpperCase(); |
160 var attrs; | 156 var attrs = attributes |
161 if (attributes != null) { | 157 ?.map /*<String>*/ ((name) => '$tagNameUpper::${name.toLowerCase()}'); |
162 attrs = | 158 var uriAttrs = uriAttributes |
163 attributes.map((name) => '$tagNameUpper::${name.toLowerCase()}'); | 159 ?.map /*<String>*/ ((name) => '$tagNameUpper::${name.toLowerCase()}'); |
164 } | |
165 var uriAttrs; | |
166 if (uriAttributes != null) { | |
167 uriAttrs = | |
168 uriAttributes.map((name) => '$tagNameUpper::${name.toLowerCase()}'); | |
169 } | |
170 if (uriPolicy == null) { | 160 if (uriPolicy == null) { |
171 uriPolicy = new UriPolicy(); | 161 uriPolicy = new UriPolicy(); |
172 } | 162 } |
173 | 163 |
174 add(new _CustomElementNodeValidator( | 164 add(new _CustomElementNodeValidator( |
175 uriPolicy, | 165 uriPolicy, [tagNameUpper], attrs, uriAttrs, false, true)); |
176 [tagNameUpper], | |
177 attrs, | |
178 uriAttrs, | |
179 false, | |
180 true)); | |
181 } | 166 } |
182 | 167 |
183 /** | 168 /** |
184 * Allow custom tag extensions with the specified type name and specified | 169 * Allow custom tag extensions with the specified type name and specified |
185 * attributes. | 170 * attributes. |
186 * | 171 * |
187 * This will allow tag extensions (such as <div is="x-foo"></div>), | 172 * This will allow tag extensions (such as <div is="x-foo"></div>), |
188 * but will not allow custom tags. Use [allowCustomElement] to allow | 173 * but will not allow custom tags. Use [allowCustomElement] to allow |
189 * custom tags. | 174 * custom tags. |
190 */ | 175 */ |
191 void allowTagExtension(String tagName, String baseName, | 176 void allowTagExtension(String tagName, String baseName, |
192 {UriPolicy uriPolicy, | 177 {UriPolicy uriPolicy, |
193 Iterable<String> attributes, | 178 Iterable<String> attributes, |
194 Iterable<String> uriAttributes}) { | 179 Iterable<String> uriAttributes}) { |
195 | |
196 var baseNameUpper = baseName.toUpperCase(); | 180 var baseNameUpper = baseName.toUpperCase(); |
197 var tagNameUpper = tagName.toUpperCase(); | 181 var tagNameUpper = tagName.toUpperCase(); |
198 var attrs; | 182 var attrs = attributes |
199 if (attributes != null) { | 183 ?.map /*<String>*/ ((name) => '$baseNameUpper::${name.toLowerCase()}'); |
200 attrs = | 184 var uriAttrs = uriAttributes |
201 attributes.map((name) => '$baseNameUpper::${name.toLowerCase()}'); | 185 ?.map /*<String>*/ ((name) => '$baseNameUpper::${name.toLowerCase()}'); |
202 } | |
203 var uriAttrs; | |
204 if (uriAttributes != null) { | |
205 uriAttrs = | |
206 uriAttributes.map((name) => '$baseNameUpper::${name.toLowerCase()}'); | |
207 } | |
208 if (uriPolicy == null) { | 186 if (uriPolicy == null) { |
209 uriPolicy = new UriPolicy(); | 187 uriPolicy = new UriPolicy(); |
210 } | 188 } |
211 | 189 |
212 add(new _CustomElementNodeValidator( | 190 add(new _CustomElementNodeValidator(uriPolicy, |
213 uriPolicy, | 191 [tagNameUpper, baseNameUpper], attrs, uriAttrs, true, false)); |
214 [tagNameUpper, baseNameUpper], | |
215 attrs, | |
216 uriAttrs, | |
217 true, | |
218 false)); | |
219 } | 192 } |
220 | 193 |
221 void allowElement(String tagName, {UriPolicy uriPolicy, | 194 void allowElement(String tagName, |
222 Iterable<String> attributes, | 195 {UriPolicy uriPolicy, |
223 Iterable<String> uriAttributes}) { | 196 Iterable<String> attributes, |
224 | 197 Iterable<String> uriAttributes}) { |
225 allowCustomElement(tagName, uriPolicy: uriPolicy, | 198 allowCustomElement(tagName, |
| 199 uriPolicy: uriPolicy, |
226 attributes: attributes, | 200 attributes: attributes, |
227 uriAttributes: uriAttributes); | 201 uriAttributes: uriAttributes); |
228 } | 202 } |
229 | 203 |
230 /** | 204 /** |
231 * Allow templating elements (such as <template> and template-related | 205 * Allow templating elements (such as <template> and template-related |
232 * attributes. | 206 * attributes. |
233 * | 207 * |
234 * This still requires other validators to allow regular attributes to be | 208 * This still requires other validators to allow regular attributes to be |
235 * bound (such as [allowHtml5]). | 209 * bound (such as [allowHtml5]). |
(...skipping 10 matching lines...) Expand all Loading... |
246 */ | 220 */ |
247 void add(NodeValidator validator) { | 221 void add(NodeValidator validator) { |
248 _validators.add(validator); | 222 _validators.add(validator); |
249 } | 223 } |
250 | 224 |
251 bool allowsElement(Element element) { | 225 bool allowsElement(Element element) { |
252 return _validators.any((v) => v.allowsElement(element)); | 226 return _validators.any((v) => v.allowsElement(element)); |
253 } | 227 } |
254 | 228 |
255 bool allowsAttribute(Element element, String attributeName, String value) { | 229 bool allowsAttribute(Element element, String attributeName, String value) { |
256 return _validators.any( | 230 return _validators |
257 (v) => v.allowsAttribute(element, attributeName, value)); | 231 .any((v) => v.allowsAttribute(element, attributeName, value)); |
258 } | 232 } |
259 } | 233 } |
260 | 234 |
261 class _SimpleNodeValidator implements NodeValidator { | 235 class _SimpleNodeValidator implements NodeValidator { |
262 final Set<String> allowedElements = new Set<String>(); | 236 final Set<String> allowedElements = new Set<String>(); |
263 final Set<String> allowedAttributes = new Set<String>(); | 237 final Set<String> allowedAttributes = new Set<String>(); |
264 final Set<String> allowedUriAttributes = new Set<String>(); | 238 final Set<String> allowedUriAttributes = new Set<String>(); |
265 final UriPolicy uriPolicy; | 239 final UriPolicy uriPolicy; |
266 | 240 |
267 factory _SimpleNodeValidator.allowNavigation(UriPolicy uriPolicy) { | 241 factory _SimpleNodeValidator.allowNavigation(UriPolicy uriPolicy) { |
268 return new _SimpleNodeValidator(uriPolicy, | 242 return new _SimpleNodeValidator(uriPolicy, allowedElements: const [ |
269 allowedElements: const [ | 243 'A', |
270 'A', | 244 'FORM' |
271 'FORM'], | 245 ], allowedAttributes: const [ |
272 allowedAttributes: const [ | 246 'A::accesskey', |
273 'A::accesskey', | 247 'A::coords', |
274 'A::coords', | 248 'A::hreflang', |
275 'A::hreflang', | 249 'A::name', |
276 'A::name', | 250 'A::shape', |
277 'A::shape', | 251 'A::tabindex', |
278 'A::tabindex', | 252 'A::target', |
279 'A::target', | 253 'A::type', |
280 'A::type', | 254 'FORM::accept', |
281 'FORM::accept', | 255 'FORM::autocomplete', |
282 'FORM::autocomplete', | 256 'FORM::enctype', |
283 'FORM::enctype', | 257 'FORM::method', |
284 'FORM::method', | 258 'FORM::name', |
285 'FORM::name', | 259 'FORM::novalidate', |
286 'FORM::novalidate', | 260 'FORM::target', |
287 'FORM::target', | 261 ], allowedUriAttributes: const [ |
288 ], | 262 'A::href', |
289 allowedUriAttributes: const [ | 263 'FORM::action', |
290 'A::href', | 264 ]); |
291 'FORM::action', | |
292 ]); | |
293 } | 265 } |
294 | 266 |
295 factory _SimpleNodeValidator.allowImages(UriPolicy uriPolicy) { | 267 factory _SimpleNodeValidator.allowImages(UriPolicy uriPolicy) { |
296 return new _SimpleNodeValidator(uriPolicy, | 268 return new _SimpleNodeValidator(uriPolicy, allowedElements: const [ |
297 allowedElements: const [ | 269 'IMG' |
298 'IMG' | 270 ], allowedAttributes: const [ |
299 ], | 271 'IMG::align', |
300 allowedAttributes: const [ | 272 'IMG::alt', |
301 'IMG::align', | 273 'IMG::border', |
302 'IMG::alt', | 274 'IMG::height', |
303 'IMG::border', | 275 'IMG::hspace', |
304 'IMG::height', | 276 'IMG::ismap', |
305 'IMG::hspace', | 277 'IMG::name', |
306 'IMG::ismap', | 278 'IMG::usemap', |
307 'IMG::name', | 279 'IMG::vspace', |
308 'IMG::usemap', | 280 'IMG::width', |
309 'IMG::vspace', | 281 ], allowedUriAttributes: const [ |
310 'IMG::width', | 282 'IMG::src', |
311 ], | 283 ]); |
312 allowedUriAttributes: const [ | |
313 'IMG::src', | |
314 ]); | |
315 } | 284 } |
316 | 285 |
317 factory _SimpleNodeValidator.allowTextElements() { | 286 factory _SimpleNodeValidator.allowTextElements() { |
318 return new _SimpleNodeValidator(null, | 287 return new _SimpleNodeValidator(null, allowedElements: const [ |
319 allowedElements: const [ | 288 'B', |
320 'B', | 289 'BLOCKQUOTE', |
321 'BLOCKQUOTE', | 290 'BR', |
322 'BR', | 291 'EM', |
323 'EM', | 292 'H1', |
324 'H1', | 293 'H2', |
325 'H2', | 294 'H3', |
326 'H3', | 295 'H4', |
327 'H4', | 296 'H5', |
328 'H5', | 297 'H6', |
329 'H6', | 298 'HR', |
330 'HR', | 299 'I', |
331 'I', | 300 'LI', |
332 'LI', | 301 'OL', |
333 'OL', | 302 'P', |
334 'P', | 303 'SPAN', |
335 'SPAN', | 304 'UL', |
336 'UL', | 305 ]); |
337 ]); | |
338 } | 306 } |
339 | 307 |
340 /** | 308 /** |
341 * Elements must be uppercased tag names. For example `'IMG'`. | 309 * Elements must be uppercased tag names. For example `'IMG'`. |
342 * Attributes must be uppercased tag name followed by :: followed by | 310 * Attributes must be uppercased tag name followed by :: followed by |
343 * lowercase attribute name. For example `'IMG:src'`. | 311 * lowercase attribute name. For example `'IMG:src'`. |
344 */ | 312 */ |
345 _SimpleNodeValidator(this.uriPolicy, | 313 _SimpleNodeValidator(this.uriPolicy, |
346 {Iterable<String> allowedElements, Iterable<String> allowedAttributes, | 314 {Iterable<String> allowedElements, |
347 Iterable<String> allowedUriAttributes}) { | 315 Iterable<String> allowedAttributes, |
| 316 Iterable<String> allowedUriAttributes}) { |
348 this.allowedElements.addAll(allowedElements ?? const []); | 317 this.allowedElements.addAll(allowedElements ?? const []); |
349 allowedAttributes = allowedAttributes ?? const []; | 318 allowedAttributes = allowedAttributes ?? const []; |
350 allowedUriAttributes = allowedUriAttributes ?? const []; | 319 allowedUriAttributes = allowedUriAttributes ?? const []; |
351 var legalAttributes = allowedAttributes.where( | 320 var legalAttributes = allowedAttributes |
352 (x) => !_Html5NodeValidator._uriAttributes.contains(x)); | 321 .where((x) => !_Html5NodeValidator._uriAttributes.contains(x)); |
353 var extraUriAttributes = allowedAttributes.where( | 322 var extraUriAttributes = allowedAttributes |
354 (x) => _Html5NodeValidator._uriAttributes.contains(x)); | 323 .where((x) => _Html5NodeValidator._uriAttributes.contains(x)); |
355 this.allowedAttributes.addAll(legalAttributes); | 324 this.allowedAttributes.addAll(legalAttributes); |
356 this.allowedUriAttributes.addAll(allowedUriAttributes); | 325 this.allowedUriAttributes.addAll(allowedUriAttributes); |
357 this.allowedUriAttributes.addAll(extraUriAttributes); | 326 this.allowedUriAttributes.addAll(extraUriAttributes); |
358 } | 327 } |
359 | 328 |
360 bool allowsElement(Element element) { | 329 bool allowsElement(Element element) { |
361 return allowedElements.contains(Element._safeTagName(element)); | 330 return allowedElements.contains(Element._safeTagName(element)); |
362 } | 331 } |
363 | 332 |
364 bool allowsAttribute(Element element, String attributeName, String value) { | 333 bool allowsAttribute(Element element, String attributeName, String value) { |
(...skipping 12 matching lines...) Expand all Loading... |
377 return true; | 346 return true; |
378 } | 347 } |
379 return false; | 348 return false; |
380 } | 349 } |
381 } | 350 } |
382 | 351 |
383 class _CustomElementNodeValidator extends _SimpleNodeValidator { | 352 class _CustomElementNodeValidator extends _SimpleNodeValidator { |
384 final bool allowTypeExtension; | 353 final bool allowTypeExtension; |
385 final bool allowCustomTag; | 354 final bool allowCustomTag; |
386 | 355 |
387 _CustomElementNodeValidator(UriPolicy uriPolicy, | 356 _CustomElementNodeValidator( |
| 357 UriPolicy uriPolicy, |
388 Iterable<String> allowedElements, | 358 Iterable<String> allowedElements, |
389 Iterable<String> allowedAttributes, | 359 Iterable<String> allowedAttributes, |
390 Iterable<String> allowedUriAttributes, | 360 Iterable<String> allowedUriAttributes, |
391 bool allowTypeExtension, | 361 bool allowTypeExtension, |
392 bool allowCustomTag): | 362 bool allowCustomTag) |
393 | 363 : this.allowTypeExtension = allowTypeExtension == true, |
394 super(uriPolicy, | 364 this.allowCustomTag = allowCustomTag == true, |
395 allowedElements: allowedElements, | 365 super(uriPolicy, |
396 allowedAttributes: allowedAttributes, | 366 allowedElements: allowedElements, |
397 allowedUriAttributes: allowedUriAttributes), | 367 allowedAttributes: allowedAttributes, |
398 this.allowTypeExtension = allowTypeExtension == true, | 368 allowedUriAttributes: allowedUriAttributes); |
399 this.allowCustomTag = allowCustomTag == true; | |
400 | 369 |
401 bool allowsElement(Element element) { | 370 bool allowsElement(Element element) { |
402 if (allowTypeExtension) { | 371 if (allowTypeExtension) { |
403 var isAttr = element.attributes['is']; | 372 var isAttr = element.attributes['is']; |
404 if (isAttr != null) { | 373 if (isAttr != null) { |
405 return allowedElements.contains(isAttr.toUpperCase()) && | 374 return allowedElements.contains(isAttr.toUpperCase()) && |
406 allowedElements.contains(Element._safeTagName(element)); | 375 allowedElements.contains(Element._safeTagName(element)); |
407 } | 376 } |
408 } | 377 } |
409 return allowCustomTag && allowedElements.contains(Element._safeTagName(eleme
nt)); | 378 return allowCustomTag && |
| 379 allowedElements.contains(Element._safeTagName(element)); |
410 } | 380 } |
411 | 381 |
412 bool allowsAttribute(Element element, String attributeName, String value) { | 382 bool allowsAttribute(Element element, String attributeName, String value) { |
413 if (allowsElement(element)) { | 383 if (allowsElement(element)) { |
414 if (allowTypeExtension && attributeName == 'is' && | 384 if (allowTypeExtension && |
| 385 attributeName == 'is' && |
415 allowedElements.contains(value.toUpperCase())) { | 386 allowedElements.contains(value.toUpperCase())) { |
416 return true; | 387 return true; |
417 } | 388 } |
418 return super.allowsAttribute(element, attributeName, value); | 389 return super.allowsAttribute(element, attributeName, value); |
419 } | 390 } |
420 return false; | 391 return false; |
421 } | 392 } |
422 } | 393 } |
423 | 394 |
424 class _TemplatingNodeValidator extends _SimpleNodeValidator { | 395 class _TemplatingNodeValidator extends _SimpleNodeValidator { |
425 static const _TEMPLATE_ATTRS = | 396 static const _TEMPLATE_ATTRS = const <String>[ |
426 const <String>['bind', 'if', 'ref', 'repeat', 'syntax']; | 397 'bind', |
| 398 'if', |
| 399 'ref', |
| 400 'repeat', |
| 401 'syntax' |
| 402 ]; |
427 | 403 |
428 final Set<String> _templateAttrs; | 404 final Set<String> _templateAttrs; |
429 | 405 |
430 _TemplatingNodeValidator(): | 406 _TemplatingNodeValidator() |
431 super(null, | 407 : _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS), |
432 allowedElements: [ | 408 super(null, |
433 'TEMPLATE' | 409 allowedElements: ['TEMPLATE'], |
434 ], | 410 allowedAttributes: |
435 allowedAttributes: _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')), | 411 _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')) {} |
436 _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS) { | |
437 } | |
438 | 412 |
439 bool allowsAttribute(Element element, String attributeName, String value) { | 413 bool allowsAttribute(Element element, String attributeName, String value) { |
440 if (super.allowsAttribute(element, attributeName, value)) { | 414 if (super.allowsAttribute(element, attributeName, value)) { |
441 return true; | 415 return true; |
442 } | 416 } |
443 | 417 |
444 if (attributeName == 'template' && value == "") { | 418 if (attributeName == 'template' && value == "") { |
445 return true; | 419 return true; |
446 } | 420 } |
447 | 421 |
448 if (element.attributes['template'] == "" ) { | 422 if (element.attributes['template'] == "") { |
449 return _templateAttrs.contains(attributeName); | 423 return _templateAttrs.contains(attributeName); |
450 } | 424 } |
451 return false; | 425 return false; |
452 } | 426 } |
453 } | 427 } |
454 | 428 |
455 | |
456 class _SvgNodeValidator implements NodeValidator { | 429 class _SvgNodeValidator implements NodeValidator { |
457 bool allowsElement(Element element) { | 430 bool allowsElement(Element element) { |
458 if (element is svg.ScriptElement) { | 431 if (element is svg.ScriptElement) { |
459 return false; | 432 return false; |
460 } | 433 } |
461 // Firefox 37 has issues with creating foreign elements inside a | 434 // Firefox 37 has issues with creating foreign elements inside a |
462 // foreignobject tag as SvgElement. We don't want foreignobject contents | 435 // foreignobject tag as SvgElement. We don't want foreignobject contents |
463 // anyway, so just remove the whole tree outright. And we can't rely | 436 // anyway, so just remove the whole tree outright. And we can't rely |
464 // on IE recognizing the SvgForeignObject type, so go by tagName. Bug 23144 | 437 // on IE recognizing the SvgForeignObject type, so go by tagName. Bug 23144 |
465 if (element is svg.SvgElement && Element._safeTagName(element) == 'foreignOb
ject') { | 438 if (element is svg.SvgElement && |
| 439 Element._safeTagName(element) == 'foreignObject') { |
466 return false; | 440 return false; |
467 } | 441 } |
468 if (element is svg.SvgElement) { | 442 if (element is svg.SvgElement) { |
469 return true; | 443 return true; |
470 } | 444 } |
471 return false; | 445 return false; |
472 } | 446 } |
473 | 447 |
474 bool allowsAttribute(Element element, String attributeName, String value) { | 448 bool allowsAttribute(Element element, String attributeName, String value) { |
475 if (attributeName == 'is' || attributeName.startsWith('on')) { | 449 if (attributeName == 'is' || attributeName.startsWith('on')) { |
476 return false; | 450 return false; |
477 } | 451 } |
478 return allowsElement(element); | 452 return allowsElement(element); |
479 } | 453 } |
480 } | 454 } |
OLD | NEW |