blob: aa05ec111319feea9b982ce380c7647c282edbf3 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Parse a very small subset of HTML. This ensures that insecure HTML /
* javascript cannot be injected into the new tab page.
* @param {string} s The string to parse.
* @param {array=} extraTags Extra allowed tags.
* @param {object=} extraAttrs Extra allowed attributes (all tags are run
* through these).
* @throws {Error} In case of non supported markup.
* @return {DocumentFragment} A document fragment containing the DOM tree.
*/
var parseHtmlSubset = (function() {
'use strict';
var allowedAttributes = {
'href': function(node, value) {
// Only allow a[href] starting with http:// and https://
return node.tagName == 'A' && (value.indexOf('http://') == 0 ||
value.indexOf('https://') == 0);
},
'target': function(node, value) {
// Allow a[target] but reset the value to "".
if (node.tagName != 'A')
return false;
node.setAttribute('target', '');
return true;
}
};
/**
* Whitelist of tag names allowed in parseHtmlSubset.
* @type {[string]}
*/
var allowedTags = ['A', 'B', 'STRONG'];
function merge() {
var clone = {};
for (var i = 0; i < arguments.length; ++i) {
if (typeof arguments[i] == 'object') {
for (var key in arguments[i]) {
if (arguments[i].hasOwnProperty(key))
clone[key] = arguments[i][key];
}
}
}
return clone;
}
function walk(n, f) {
f(n);
for (var i = 0; i < n.childNodes.length; i++) {
walk(n.childNodes[i], f);
}
}
function assertElement(tags, node) {
if (tags.indexOf(node.tagName) == -1)
throw Error(node.tagName + ' is not supported');
}
function assertAttribute(attrs, attrNode, node) {
var n = attrNode.nodeName;
var v = attrNode.nodeValue;
if (!attrs.hasOwnProperty(n) || !attrs[n](node, v))
throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported');
}
return function(s, extraTags, extraAttrs) {
var tags = allowedTags.concat(extraTags);
var attrs = merge(allowedAttributes, extraAttrs);
var r = document.createRange();
r.selectNode(document.body);
// This does not execute any scripts.
var df = r.createContextualFragment(s);
walk(df, function(node) {
switch (node.nodeType) {
case Node.ELEMENT_NODE:
assertElement(tags, node);
var nodeAttrs = node.attributes;
for (var i = 0; i < nodeAttrs.length; ++i) {
assertAttribute(attrs, nodeAttrs[i], node);
}
break;
case Node.COMMENT_NODE:
case Node.DOCUMENT_FRAGMENT_NODE:
case Node.TEXT_NODE:
break;
default:
throw Error('Node type ' + node.nodeType + ' is not supported');
}
});
return df;
};
})();