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(); |
| 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 |