Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(775)

Unified Diff: tools/dom/templates/html/impl/impl_Element.darttemplate

Issue 16374007: First rev of Safe DOM (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/dom/templates/html/impl/impl_Element.darttemplate
diff --git a/tools/dom/templates/html/impl/impl_Element.darttemplate b/tools/dom/templates/html/impl/impl_Element.darttemplate
index 1698edcd7f7dec3834be8dd2f1275440bd9d5f09..a18094f0b0f6a27f0768c763005cb2b19e74ee0a 100644
--- a/tools/dom/templates/html/impl/impl_Element.darttemplate
+++ b/tools/dom/templates/html/impl/impl_Element.darttemplate
@@ -309,20 +309,34 @@ $(ANNOTATIONS)abstract class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC {
/**
* Creates an HTML element from a valid fragment of HTML.
*
- * The [html] fragment must represent valid HTML with a single element root,
- * which will be parsed and returned.
+ * var element = new Element.html('<div class="foo">content</div>');
*
- * Important: the contents of [html] should not contain any user-supplied
- * data. Without strict data validation it is impossible to prevent script
- * injection exploits.
+ * The HTML fragment should contain only one single root element, any
+ * leading or trailing text nodes will be removed.
*
- * It is instead recommended that elements be constructed via [Element.tag]
- * and text be added via [text].
+ * The HTML fragment is parsed as if it occurred within the context of a
+ * `<body>` tag, this means that special elements such as `<caption>` which
+ * must be parsed within the scope of a `<table>` element will be dropped. Use
+ * [createFragment] to parse contextual HTML fragments.
+ *
+ * Unless a validator is provided this will perform the default validation
+ * and remove all scriptable elements and attributes.
+ *
+ * See also:
+ *
+ * * [NodeValidator]
*
- * var element = new Element.html('<div class="foo">content</div>');
*/
- factory $CLASSNAME.html(String html) =>
- _$(CLASSNAME)FactoryProvider.createElement_html(html);
+ factory Element.html(String html,
+ {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
+ var fragment = document.body.createFragment(html, validator: validator,
+ treeSanitizer: treeSanitizer);
+
+ if (fragment._firstElementChild != fragment._lastElementChild) {
+ throw new StateError("More than one element root");
+ }
+ return fragment._firstElementChild;
+ }
/**
* Creates the HTML element specified by the tag name.
@@ -1171,136 +1185,135 @@ $endif
return new Point(p.x + current.offsetLeft, p.y + current.offsetTop);
}
-$if DART2JS
- @JSName('innerHTML')
- @DomName('HTMLElement.innerHTML')
- String get innerHtml => JS('String', '#.innerHTML', this);
-
- void set innerHtml(String value) {
- JS('', '#.innerHTML = #', this, value);
- // Polyfill relies on mutation observers for upgrading, but we want it
- // immediate.
- Platform.upgradeCustomElements(this);
- }
-$endif
-
-$!MEMBERS
-}
+ static HtmlDocument _parseDocument;
+ static NodeValidatorBuilder _defaultValidator;
+ static _ValidatingTreeSanitizer _defaultSanitizer;
-final _START_TAG_REGEXP = new RegExp('<(\\w+)');
-class _ElementFactoryProvider {
- static const _CUSTOM_PARENT_TAG_MAP = const {
- 'body' : 'html',
- 'head' : 'html',
- 'caption' : 'table',
- 'td': 'tr',
- 'th': 'tr',
- 'colgroup': 'table',
- 'col' : 'colgroup',
- 'tr' : 'tbody',
- 'tbody' : 'table',
- 'tfoot' : 'table',
- 'thead' : 'table',
- 'track' : 'audio',
- };
-
- @DomName('Document.createElement')
- static Element createElement_html(String html) {
- // TODO(jacobr): this method can be made more robust and performant.
- // 1) Cache the dummy parent elements required to use innerHTML rather than
- // creating them every call.
- // 2) Verify that the html does not contain leading or trailing text nodes.
- // 3) Verify that the html does not contain both <head> and <body> tags.
- // 4) Detatch the created element from its dummy parent.
- String parentTag = 'div';
- String tag;
- final match = _START_TAG_REGEXP.firstMatch(html);
- if (match != null) {
- tag = match.group(1).toLowerCase();
- if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) {
- return _createTableForIE(html, tag);
+ /**
+ * Create a DocumentFragment from the HTML fragment and ensure that it follows
+ * the sanitization rules specified by the validator or treeSanitizer.
+ *
+ * If the default validation behavior is too restrictive then a new
+ * NodeValidator should be created, either extending or wrapping a default
+ * validator and overriding the validation APIs.
+ *
+ * The treeSanitizer is used to walk the generated node tree and sanitize it.
+ * A custom treeSanitizer can also be provided to perform special validation
+ * rules but since the API is more complex to implement this is discouraged.
+ *
+ * The returned tree is guaranteed to only contain nodes and attributes which
+ * are allowed by the provided validator.
+ *
+ * See also:
+ *
+ * * [NodeValidator]
+ * * [NodeTreeSanitizer]
+ */
+ DocumentFragment createFragment(String html,
+ {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
+ if (treeSanitizer == null) {
+ if (validator == null) {
+ if (_defaultValidator == null) {
+ _defaultValidator = new NodeValidatorBuilder.common();
+ }
+ validator = _defaultValidator;
+ }
+ if (_defaultSanitizer == null) {
+ _defaultSanitizer = new _ValidatingTreeSanitizer(validator);
+ } else {
+ _defaultSanitizer.validator = validator;
}
- parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
- if (parentTag == null) parentTag = 'div';
+ treeSanitizer = _defaultSanitizer;
+ } else if (validator != null) {
+ throw new ArgumentError(
+ 'validator can only be passed if treeSanitizer is null');
}
- final temp = new Element.tag(parentTag);
- temp.innerHtml = html;
-
- Element element;
- if (temp.children.length == 1) {
- element = temp.children[0];
- } else if (parentTag == 'html' && temp.children.length == 2) {
- // In html5 the root <html> tag will always have a <body> and a <head>,
- // even though the inner html only contains one of them.
- element = temp.children[tag == 'head' ? 0 : 1];
+ if (_parseDocument == null) {
+ _parseDocument = document.implementation.createHtmlDocument('');
+ }
+ var contextElement;
+ if (this is BodyElement) {
+ contextElement = _parseDocument.body;
} else {
- _singleNode(temp.children);
+ contextElement = _parseDocument.$dom_createElement(tagName);
+ _parseDocument.body.append(contextElement);
}
- element.remove();
- return element;
+ var fragment;
+ if (Range.supportsCreateContextualFragment) {
+ var range = _parseDocument.$dom_createRange();
+ range.selectNodeContents(contextElement);
+ fragment = range.createContextualFragment(html);
+ } else {
+ contextElement._innerHtml = html;
+
+ fragment = _parseDocument.createDocumentFragment();
+ while (contextElement.firstChild != null) {
+ fragment.append(contextElement.firstChild);
+ }
+ }
+ if (contextElement != _parseDocument.body) {
+ contextElement.remove();
+ }
+
+ treeSanitizer.sanitizeTree(fragment);
+ return fragment;
}
/**
- * IE table elements don't support innerHTML (even in standards mode).
- * Instead we use a div and inject the table element in the innerHtml string.
- * This technique works on other browsers too, but it's probably slower,
- * so we only use it when running on IE.
- *
- * See also innerHTML:
- * <http://msdn.microsoft.com/en-us/library/ie/ms533897(v=vs.85).aspx>
- * and Building Tables Dynamically:
- * <http://msdn.microsoft.com/en-us/library/ie/ms532998(v=vs.85).aspx>.
+ * Parses the HTML fragment and sets it as the contents of this element.
+ *
+ * This uses the default sanitization behavior to sanitize the HTML fragment,
+ * use [setInnerHtml] to override the default behavior.
*/
- static Element _createTableForIE(String html, String tag) {
- var div = new Element.tag('div');
- div.innerHtml = '<table>$html</table>';
- var table = _singleNode(div.children);
- Element element;
- switch (tag) {
- case 'td':
- case 'th':
- TableRowElement row = _singleNode(table.rows);
- element = _singleNode(row.cells);
- break;
- case 'tr':
- element = _singleNode(table.rows);
- break;
- case 'tbody':
- element = _singleNode(table.tBodies);
- break;
- case 'thead':
- element = table.tHead;
- break;
- case 'tfoot':
- element = table.tFoot;
- break;
- case 'caption':
- element = table.caption;
- break;
- case 'colgroup':
- element = _getColgroup(table);
- break;
- case 'col':
- element = _singleNode(_getColgroup(table).children);
- break;
- }
- element.remove();
- return element;
+ void set innerHtml(String html) {
+ this.setInnerHtml(html);
}
- static TableColElement _getColgroup(TableElement table) {
- // TODO(jmesserly): is there a better way to do this?
- return _singleNode(table.children.where((n) => n.tagName == 'COLGROUP')
- .toList());
+ /**
+ * Parses the HTML fragment and sets it as the contents of this element.
+ * This ensures that the generated content follows the sanitization rules
+ * specified by the validator or treeSanitizer.
+ *
+ * If the default validation behavior is too restrictive then a new
+ * NodeValidator should be created, either extending or wrapping a default
+ * validator and overriding the validation APIs.
+ *
+ * The treeSanitizer is used to walk the generated node tree and sanitize it.
+ * A custom treeSanitizer can also be provided to perform special validation
+ * rules but since the API is more complex to implement this is discouraged.
+ *
+ * The resulting tree is guaranteed to only contain nodes and attributes which
+ * are allowed by the provided validator.
+ *
+ * See also:
+ *
+ * * [NodeValidator]
+ * * [NodeTreeSanitizer]
+ */
+ void setInnerHtml(String html,
+ {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
+ text = null;
+ append(createFragment(
+ html, validator: validator, treeSanitizer: treeSanitizer));
}
+ String get innerHtml => _innerHtml;
- static Node _singleNode(List<Node> list) {
- if (list.length == 1) return list[0];
- throw new ArgumentError('HTML had ${list.length} '
- 'top level elements but 1 expected');
+ /**
+ * For use while transitioning to the safe [innerHtml] or [setInnerHtml].
+ * Unsafe because it opens the app to cross-site scripting vulnerabilities.
+ */
+ @deprecated
+ void set unsafeInnerHtml(String html) {
+ _innerHtml = html;
}
+$!MEMBERS
+}
+
+
+class _ElementFactoryProvider {
+
@DomName('Document.createElement')
$if DART2JS
// Optimization to improve performance until the dart2js compiler inlines this

Powered by Google App Engine
This is Rietveld 408576698