Chromium Code Reviews| 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 |