|  | // 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 inline styles on elements. | 
|  | * | 
|  | * If [tagName] is not specified then this allows inline styles on all | 
|  | * elements. Otherwise tagName limits the styles to the specified elements. | 
|  | */ | 
|  | void allowInlineStyles({String? tagName}) { | 
|  | if (tagName == null) { | 
|  | tagName = '*'; | 
|  | } else { | 
|  | tagName = tagName.toUpperCase(); | 
|  | } | 
|  | add(new _SimpleNodeValidator(null, allowedAttributes: ['$tagName::style'])); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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 = attributes | 
|  | ?.map<String>((name) => '$tagNameUpper::${name.toLowerCase()}'); | 
|  | var uriAttrs = uriAttributes | 
|  | ?.map<String>((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 = attributes | 
|  | ?.map<String>((name) => '$baseNameUpper::${name.toLowerCase()}'); | 
|  | var uriAttrs = uriAttributes | 
|  | ?.map<String>((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 = new Set<String>(); | 
|  | final Set<String> allowedAttributes = new Set<String>(); | 
|  | final Set<String> allowedUriAttributes = new Set<String>(); | 
|  | final UriPolicy? uriPolicy; | 
|  |  | 
|  | factory _SimpleNodeValidator.allowNavigation(UriPolicy uriPolicy) { | 
|  | return new _SimpleNodeValidator(uriPolicy, allowedElements: const [ | 
|  | 'A', | 
|  | 'FORM' | 
|  | ], allowedAttributes: const [ | 
|  | '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: const [ | 
|  | 'A::href', | 
|  | 'FORM::action', | 
|  | ]); | 
|  | } | 
|  |  | 
|  | factory _SimpleNodeValidator.allowImages(UriPolicy uriPolicy) { | 
|  | return new _SimpleNodeValidator(uriPolicy, allowedElements: const [ | 
|  | 'IMG' | 
|  | ], allowedAttributes: const [ | 
|  | 'IMG::align', | 
|  | 'IMG::alt', | 
|  | 'IMG::border', | 
|  | 'IMG::height', | 
|  | 'IMG::hspace', | 
|  | 'IMG::ismap', | 
|  | 'IMG::name', | 
|  | 'IMG::usemap', | 
|  | 'IMG::vspace', | 
|  | 'IMG::width', | 
|  | ], allowedUriAttributes: const [ | 
|  | 'IMG::src', | 
|  | ]); | 
|  | } | 
|  |  | 
|  | factory _SimpleNodeValidator.allowTextElements() { | 
|  | return new _SimpleNodeValidator(null, allowedElements: const [ | 
|  | '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.addAll(allowedElements ?? const []); | 
|  | allowedAttributes = allowedAttributes ?? const []; | 
|  | allowedUriAttributes = allowedUriAttributes ?? const []; | 
|  | var legalAttributes = allowedAttributes | 
|  | .where((x) => !_Html5NodeValidator._uriAttributes.contains(x)); | 
|  | var extraUriAttributes = allowedAttributes | 
|  | .where((x) => _Html5NodeValidator._uriAttributes.contains(x)); | 
|  | this.allowedAttributes.addAll(legalAttributes); | 
|  | this.allowedUriAttributes.addAll(allowedUriAttributes); | 
|  | this.allowedUriAttributes.addAll(extraUriAttributes); | 
|  | } | 
|  |  | 
|  | bool allowsElement(Element element) { | 
|  | return allowedElements.contains(Element._safeTagName(element)); | 
|  | } | 
|  |  | 
|  | bool allowsAttribute(Element element, String attributeName, String value) { | 
|  | var tagName = Element._safeTagName(element); | 
|  | 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) | 
|  | : this.allowTypeExtension = allowTypeExtension == true, | 
|  | this.allowCustomTag = allowCustomTag == true, | 
|  | super(uriPolicy, | 
|  | allowedElements: allowedElements, | 
|  | allowedAttributes: allowedAttributes, | 
|  | allowedUriAttributes: allowedUriAttributes); | 
|  |  | 
|  | bool allowsElement(Element element) { | 
|  | if (allowTypeExtension) { | 
|  | var isAttr = element.attributes['is']; | 
|  | if (isAttr != null) { | 
|  | return allowedElements.contains(isAttr.toUpperCase()) && | 
|  | allowedElements.contains(Element._safeTagName(element)); | 
|  | } | 
|  | } | 
|  | return allowCustomTag && | 
|  | allowedElements.contains(Element._safeTagName(element)); | 
|  | } | 
|  |  | 
|  | 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() | 
|  | : _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS), | 
|  | super(null, | 
|  | allowedElements: ['TEMPLATE'], | 
|  | allowedAttributes: | 
|  | _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')) {} | 
|  |  | 
|  | 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; | 
|  | } | 
|  | // Firefox 37 has issues with creating foreign elements inside a | 
|  | // foreignobject tag as SvgElement. We don't want foreignobject contents | 
|  | // anyway, so just remove the whole tree outright. And we can't rely | 
|  | // on IE recognizing the SvgForeignObject type, so go by tagName. Bug 23144 | 
|  | if (element is svg.SvgElement && | 
|  | Element._safeTagName(element) == 'foreignObject') { | 
|  | 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); | 
|  | } | 
|  | } |