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 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 |