Chromium Code Reviews| Index: tools/dom/src/NodeValidatorBuilder.dart |
| diff --git a/tools/dom/src/NodeValidatorBuilder.dart b/tools/dom/src/NodeValidatorBuilder.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..61060743e1fff3e192234cf613b8c4bd2032f6bb |
| --- /dev/null |
| +++ b/tools/dom/src/NodeValidatorBuilder.dart |
| @@ -0,0 +1,454 @@ |
| +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +part of dart.dom.html; |
| + |
| + |
| +/** |
| + * Class which helps construct standard node validation policies. |
| + * |
| + * By default this will not accept anything, but the 'allow*' functions can be |
| + * used to expand what types of elements or attributes are allowed. |
| + * |
| + * All allow functions are additive- elements will be accepted if they are |
| + * accepted by any specific rule. |
| + * |
| + * It is important to remember that sanitization is not just intended to prevent |
| + * cross-site scripting attacks, but also to prevent information from being |
| + * displayed in unexpected ways. For example something displaying basic |
| + * formatted text may not expect `<video>` tags to appear. In this case an |
| + * empty NodeValidatorBuilder with just [allowTextElements] might be |
| + * appropriate. |
| + */ |
| +class NodeValidatorBuilder implements NodeValidator { |
| + |
| + final List<NodeValidator> _validators = <NodeValidator>[]; |
| + |
| + NodeValidatorBuilder() { |
| + } |
| + |
| + /** |
| + * Creates a new NodeValidatorBuilder which accepts common constructs. |
| + * |
| + * By default this will accept HTML5 elements and attributes with the default |
| + * [UriPolicy] and templating elements. |
| + * |
| + * Notable syntax which is filtered: |
| + * |
| + * * Only known-good HTML5 elements and attributes are allowed. |
| + * * All URLs must be same-origin, use [allowNavigation] and [allowImages] to |
| + * specify additional URI policies. |
| + * * Inline-styles are not allowed. |
| + * * Custom element tags are disallowed, use [allowCustomElement]. |
| + * * Custom tags extensions are disallowed, use [allowTagExtension]. |
| + * * SVG Elements are not allowed, use [allowSvg]. |
| + * |
| + * For scenarios where the HTML should only contain formatted text |
| + * [allowTextElements] is more appropriate. |
| + * |
| + * Use [allowSvg] to allow SVG elements. |
| + */ |
| + 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
|
| + return new NodeValidatorBuilder() |
| + ..allowHtml5() |
| + ..allowTemplating(); |
| + } |
| + |
| + /** |
| + * Allows navigation elements- Form and Anchor tags, along with common |
| + * attributes. |
| + * |
| + * The UriPolicy can be used to restrict the locations the navigation elements |
| + * are allowed to direct to. By default this will use the default [UriPolicy]. |
| + */ |
| + void allowNavigation([UriPolicy uriPolicy]) { |
| + if (uriPolicy == null) { |
| + uriPolicy = new UriPolicy(); |
| + } |
| + add(new _SimpleNodeValidator.allowNavigation(uriPolicy)); |
| + } |
| + |
| + /** |
| + * Allows image elements. |
| + * |
| + * The UriPolicy can be used to restrict the locations the images may be |
| + * loaded from. By default this will use the default [UriPolicy]. |
| + */ |
| + void allowImages([UriPolicy uriPolicy]) { |
| + if (uriPolicy == null) { |
| + uriPolicy = new UriPolicy(); |
| + } |
| + add(new _SimpleNodeValidator.allowImages(uriPolicy)); |
| + } |
| + |
| + /** |
| + * Allow basic text elements. |
| + * |
| + * This allows a subset of HTML5 elements, specifically just these tags and |
| + * no attributes. |
| + * |
| + * * B |
| + * * BLOCKQUOTE |
| + * * BR |
| + * * EM |
| + * * H1 |
| + * * H2 |
| + * * H3 |
| + * * H4 |
| + * * H5 |
| + * * H6 |
| + * * HR |
| + * * I |
| + * * LI |
| + * * OL |
| + * * P |
| + * * SPAN |
| + * * UL |
| + */ |
| + void allowTextElements() { |
| + add(new _SimpleNodeValidator.allowTextElements()); |
| + } |
| + |
| + /** |
| + * Allow common safe HTML5 elements and attributes. |
| + * |
| + * This list is based off of the Caja whitelists at: |
| + * https://code.google.com/p/google-caja/wiki/CajaWhitelists. |
| + * |
| + * Common things which are not allowed are script elements, style attributes |
| + * and any script handlers. |
| + */ |
| + void allowHtml5({UriPolicy uriPolicy}) { |
| + add(new _Html5NodeValidator(uriPolicy: uriPolicy)); |
| + } |
| + |
| + /** |
| + * Allow SVG elements and attributes except for known bad ones. |
| + */ |
| + void allowSvg() { |
| + add(new _SvgNodeValidator()); |
| + } |
| + |
| + /** |
| + * Allow custom elements with the specified tag name and specified attributes. |
| + * |
| + * This will allow the elements as custom tags (such as <x-foo></x-foo>), |
| + * but will not allow tag extensions. Use [allowTagExtension] to allow |
| + * tag extensions. |
| + */ |
| + void allowCustomElement(String tagName, |
| + {UriPolicy uriPolicy, |
| + Iterable<String> attributes, |
| + Iterable<String> uriAttributes}) { |
| + |
| + var tagNameUpper = tagName.toUpperCase(); |
| + var attrs; |
| + if (attributes != null) { |
| + attrs = |
| + attributes.map((name) => '$tagNameUpper::${name.toLowerCase()}'); |
| + } |
| + var uriAttrs; |
| + if (uriAttributes != null) { |
| + uriAttrs = |
| + uriAttributes.map((name) => '$tagNameUpper::${name.toLowerCase()}'); |
| + } |
| + if (uriPolicy == null) { |
| + uriPolicy = new UriPolicy(); |
| + } |
| + |
| + add(new _CustomElementNodeValidator( |
| + uriPolicy, |
| + [tagNameUpper], |
| + attrs, |
| + uriAttrs, |
| + false, |
| + true)); |
| + } |
| + |
| + /** |
| + * Allow custom tag extensions with the specified type name and specified |
| + * attributes. |
| + * |
| + * This will allow tag extensions (such as <div is="x-foo"></div>), |
| + * but will not allow custom tags. Use [allowCustomElement] to allow |
| + * custom tags. |
| + */ |
| + void allowTagExtension(String tagName, String baseName, |
| + {UriPolicy uriPolicy, |
| + Iterable<String> attributes, |
| + Iterable<String> uriAttributes}) { |
| + |
| + var baseNameUpper = baseName.toUpperCase(); |
| + var tagNameUpper = tagName.toUpperCase(); |
| + var attrs; |
| + if (attributes != null) { |
| + attrs = |
| + attributes.map((name) => '$baseNameUpper::${name.toLowerCase()}'); |
| + } |
| + var uriAttrs; |
| + if (uriAttributes != null) { |
| + uriAttrs = |
| + uriAttributes.map((name) => '$baseNameUpper::${name.toLowerCase()}'); |
| + } |
| + if (uriPolicy == null) { |
| + uriPolicy = new UriPolicy(); |
| + } |
| + |
| + add(new _CustomElementNodeValidator( |
| + uriPolicy, |
| + [tagNameUpper, baseNameUpper], |
| + attrs, |
| + uriAttrs, |
| + true, |
| + false)); |
| + } |
| + |
| + void allowElement(String tagName, {UriPolicy uriPolicy, |
| + Iterable<String> attributes, |
| + Iterable<String> uriAttributes}) { |
| + |
| + allowCustomElement(tagName, uriPolicy: uriPolicy, |
| + attributes: attributes, |
| + uriAttributes: uriAttributes); |
| + } |
| + |
| + /** |
| + * Allow templating elements (such as <template> and template-related |
| + * attributes. |
| + * |
| + * This still requires other validators to allow regular attributes to be |
| + * bound (such as [allowHtml5]). |
| + */ |
| + void allowTemplating() { |
| + add(new _TemplatingNodeValidator()); |
| + } |
| + |
| + /** |
| + * Add an additional validator to the current list of validators. |
| + * |
| + * Elements and attributes will be accepted if they are accepted by any |
| + * validators. |
| + */ |
| + void add(NodeValidator validator) { |
| + _validators.add(validator); |
| + } |
| + |
| + bool allowsElement(Element element) { |
| + return _validators.any((v) => v.allowsElement(element)); |
| + } |
| + |
| + bool allowsAttribute(Element element, String attributeName, String value) { |
| + return _validators.any( |
| + (v) => v.allowsAttribute(element, attributeName, value)); |
| + } |
| +} |
| + |
| +class _SimpleNodeValidator implements NodeValidator { |
| + final Set<String> allowedElements; |
| + final Set<String> allowedAttributes; |
| + final Set<String> allowedUriAttributes; |
| + final UriPolicy uriPolicy; |
| + |
| + factory _SimpleNodeValidator.allowNavigation(UriPolicy uriPolicy) { |
| + return new _SimpleNodeValidator(uriPolicy, |
| + allowedElements: [ |
| + 'A', |
| + 'FORM'], |
| + allowedAttributes: [ |
| + 'A::accesskey', |
| + 'A::coords', |
| + 'A::hreflang', |
| + 'A::name', |
| + 'A::shape', |
| + 'A::tabindex', |
| + 'A::target', |
| + 'A::type', |
| + 'FORM::accept', |
| + 'FORM::autocomplete', |
| + 'FORM::enctype', |
| + 'FORM::method', |
| + 'FORM::name', |
| + 'FORM::novalidate', |
| + 'FORM::target', |
| + ], |
| + allowedUriAttributes: [ |
| + 'A::href', |
| + 'FORM::action', |
| + ]); |
| + } |
| + |
| + factory _SimpleNodeValidator.allowImages(UriPolicy uriPolicy) { |
| + return new _SimpleNodeValidator(uriPolicy, |
| + allowedElements: [ |
| + 'IMG' |
| + ], |
| + allowedAttributes: [ |
| + 'IMG::align', |
| + 'IMG::alt', |
| + 'IMG::border', |
| + 'IMG::height', |
| + 'IMG::hspace', |
| + 'IMG::ismap', |
| + 'IMG::name', |
| + 'IMG::usemap', |
| + 'IMG::vspace', |
| + 'IMG::width', |
| + ], |
| + allowedUriAttributes: [ |
| + 'IMG::src', |
| + ]); |
| + } |
| + |
| + factory _SimpleNodeValidator.allowTextElements() { |
| + return new _SimpleNodeValidator(null, |
| + allowedElements: [ |
| + 'B', |
| + 'BLOCKQUOTE', |
| + 'BR', |
| + 'EM', |
| + 'H1', |
| + 'H2', |
| + 'H3', |
| + 'H4', |
| + 'H5', |
| + 'H6', |
| + 'HR', |
| + 'I', |
| + 'LI', |
| + 'OL', |
| + 'P', |
| + 'SPAN', |
| + 'UL', |
| + ]); |
| + } |
| + |
| + /** |
| + * Elements must be uppercased tag names. For example `'IMG'`. |
| + * Attributes must be uppercased tag name followed by :: followed by |
| + * lowercase attribute name. For example `'IMG:src'`. |
| + */ |
| + _SimpleNodeValidator(this.uriPolicy, |
| + {Iterable<String> allowedElements, Iterable<String> allowedAttributes, |
| + Iterable<String> allowedUriAttributes}): |
| + this.allowedElements = allowedElements != null ? |
| + new Set.from(allowedElements) : new Set(), |
| + this.allowedAttributes = allowedAttributes != null ? |
| + new Set.from(allowedAttributes) : new Set(), |
| + this.allowedUriAttributes = allowedUriAttributes != null ? |
| + new Set.from(allowedUriAttributes) : new Set(); |
| + |
| + bool allowsElement(Element element) { |
| + return allowedElements.contains(element.tagName); |
| + } |
| + |
| + bool allowsAttribute(Element element, String attributeName, String value) { |
| + var tagName = element.tagName; |
| + if (allowedUriAttributes.contains('$tagName::$attributeName')) { |
| + return uriPolicy.allowsUri(value); |
| + } else if (allowedUriAttributes.contains('*::$attributeName')) { |
| + return uriPolicy.allowsUri(value); |
| + } else if (allowedAttributes.contains('$tagName::$attributeName')) { |
| + return true; |
| + } else if (allowedAttributes.contains('*::$attributeName')) { |
| + return true; |
| + } else if (allowedAttributes.contains('$tagName::*')) { |
| + return true; |
| + } else if (allowedAttributes.contains('*::*')) { |
| + return true; |
| + } |
| + return false; |
| + } |
| +} |
| + |
| +class _CustomElementNodeValidator extends _SimpleNodeValidator { |
| + final bool allowTypeExtension; |
| + final bool allowCustomTag; |
| + |
| + _CustomElementNodeValidator(UriPolicy uriPolicy, |
| + Iterable<String> allowedElements, |
| + Iterable<String> allowedAttributes, |
| + Iterable<String> allowedUriAttributes, |
| + bool allowTypeExtension, |
| + bool allowCustomTag): |
| + |
| + super(uriPolicy, |
| + allowedElements: allowedElements, |
| + allowedAttributes: allowedAttributes, |
| + allowedUriAttributes: allowedUriAttributes), |
| + this.allowTypeExtension = allowTypeExtension == true, |
| + this.allowCustomTag = allowCustomTag == true; |
| + |
| + bool allowsElement(Element element) { |
| + if (allowTypeExtension) { |
| + var isAttr = element.attributes['is']; |
| + if (isAttr != null) { |
| + return allowedElements.contains(isAttr.toUpperCase()) && |
| + allowedElements.contains(element.tagName); |
| + } |
| + } |
| + return allowCustomTag && allowedElements.contains(element.tagName); |
| + } |
| + |
| + bool allowsAttribute(Element element, String attributeName, String value) { |
| + if (allowsElement(element)) { |
| + if (allowTypeExtension && attributeName == 'is' && |
| + allowedElements.contains(value.toUpperCase())) { |
| + return true; |
| + } |
| + return super.allowsAttribute(element, attributeName, value); |
| + } |
| + return false; |
| + } |
| +} |
| + |
| +class _TemplatingNodeValidator extends _SimpleNodeValidator { |
| + static const _TEMPLATE_ATTRS = |
| + const <String>['bind', 'if', 'ref', 'repeat', 'syntax']; |
| + |
| + final Set<String> _templateAttrs; |
| + |
| + _TemplatingNodeValidator(): |
| + super(null, |
| + allowedElements: [ |
| + 'TEMPLATE' |
| + ], |
| + allowedAttributes: _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')), |
| + _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS) { |
| + } |
| + |
| + bool allowsAttribute(Element element, String attributeName, String value) { |
| + if (super.allowsAttribute(element, attributeName, value)) { |
| + return true; |
| + } |
| + |
| + if (attributeName == 'template' && value == "") { |
| + return true; |
| + } |
| + |
| + if (element.attributes['template'] == "" ) { |
| + return _templateAttrs.contains(attributeName); |
| + } |
| + return false; |
| + } |
| +} |
| + |
| + |
| +class _SvgNodeValidator implements NodeValidator { |
| + bool allowsElement(Element element) { |
| + if (element is svg.ScriptElement) { |
| + return false; |
| + } |
| + if (element is svg.SvgElement) { |
| + return true; |
| + } |
| + return false; |
| + } |
| + |
| + bool allowsAttribute(Element element, String attributeName, String value) { |
| + if (attributeName == 'is' || attributeName.startsWith('on')) { |
| + return false; |
| + } |
| + return allowsElement(element); |
| + } |
| +} |