| 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..c5f0c80542a90835393044215c1d36965ea7bdbf
|
| --- /dev/null
|
| +++ b/tools/dom/src/NodeValidatorBuilder.dart
|
| @@ -0,0 +1,453 @@
|
| +// 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.
|
| + */
|
| + NodeValidatorBuilder.common() {
|
| + 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);
|
| + }
|
| +}
|
|
|