b5470ae013
Since the sdk has been unforked, scripts should be updated to generate files by always using NNBD. As such, nnbd_src has been moved to src and the old src has been deleted. This does not address the nnbd tokens e.g. $NULLABLE. That will be done in a future CL. Change-Id: I00bead16a9d19569b07ad0601f581fc14400fdce Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/151864 Reviewed-by: Sigmund Cherem <sigmund@google.com> Commit-Queue: Srujan Gaddam <srujzs@google.com>
455 lines
13 KiB
Dart
455 lines
13 KiB
Dart
// 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);
|
|
}
|
|
}
|