1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * Parse a very small subset of HTML. This ensures that insecure HTML / 7 * javascript cannot be injected into the new tab page. 8 * @param {string} s The string to parse. 9 * @param {array=} extraTags Extra allowed tags. 10 * @param {object=} extraAttrs Extra allowed attributes (all tags are run 11 * through these). 12 * @throws {Error} In case of non supported markup. 13 * @return {DocumentFragment} A document fragment containing the DOM tree. 14 */ 15 var parseHtmlSubset = (function() { 16 'use strict'; 17 18 var allowedAttributes = { 19 'href': function(node, value) { 20 // Only allow a[href] starting with http:// and https:// 21 return node.tagName == 'A' && (value.indexOf('http://') == 0 || 22 value.indexOf('https://') == 0); 23 }, 24 'target': function(node, value) { 25 // Allow a[target] but reset the value to "". 26 if (node.tagName != 'A') 27 return false; 28 node.setAttribute('target', ''); 29 return true; 30 } 31 }; 32 33 /** 34 * Whitelist of tag names allowed in parseHtmlSubset. 35 * @type {[string]} 36 */ 37 var allowedTags = ['A', 'B', 'STRONG']; 38 39 function merge() { 40 var clone = {}; 41 for (var i = 0; i < arguments.length; ++i) { 42 if (typeof arguments[i] == 'object') { 43 for (var key in arguments[i]) { 44 if (arguments[i].hasOwnProperty(key)) 45 clone[key] = arguments[i][key]; 46 } 47 } 48 } 49 return clone; 50 } 51 52 function walk(n, f) { 53 f(n); 54 for (var i = 0; i < n.childNodes.length; i++) { 55 walk(n.childNodes[i], f); 56 } 57 } 58 59 function assertElement(tags, node) { 60 if (tags.indexOf(node.tagName) == -1) 61 throw Error(node.tagName + ' is not supported'); 62 } 63 64 function assertAttribute(attrs, attrNode, node) { 65 var n = attrNode.nodeName; 66 var v = attrNode.nodeValue; 67 if (!attrs.hasOwnProperty(n) || !attrs[n](node, v)) 68 throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported'); 69 } 70 71 return function(s, extraTags, extraAttrs) { 72 var tags = allowedTags.concat(extraTags); 73 var attrs = merge(allowedAttributes, extraAttrs); 74 75 var r = document.createRange(); 76 r.selectNode(document.body); 77 // This does not execute any scripts. 78 var df = r.createContextualFragment(s); 79 walk(df, function(node) { 80 switch (node.nodeType) { 81 case Node.ELEMENT_NODE: 82 assertElement(tags, node); 83 var nodeAttrs = node.attributes; 84 for (var i = 0; i < nodeAttrs.length; ++i) { 85 assertAttribute(attrs, nodeAttrs[i], node); 86 } 87 break; 88 89 case Node.COMMENT_NODE: 90 case Node.DOCUMENT_FRAGMENT_NODE: 91 case Node.TEXT_NODE: 92 break; 93 94 default: 95 throw Error('Node type ' + node.nodeType + ' is not supported'); 96 } 97 }); 98 return df; 99 }; 100 })(); 101