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.<string>=} opt_extraTags Optional extra allowed tags. 10 * @param {Object.<string, function(Node, string):boolean>=} opt_extraAttrs 11 * Optional extra allowed attributes (all tags are run through these). 12 * @throws {Error} In case of non supported markup. 13 * @return {DocumentFragment} A document fragment containing the DOM tree. 14 */ 15var parseHtmlSubset = (function() { 16 'use strict'; 17 18 var allowedAttributes = { 19 'href': function(node, value) { 20 // Only allow a[href] starting with chrome:// and https:// 21 return node.tagName == 'A' && (value.indexOf('chrome://') == 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 {!Array.<string>} 36 * @const 37 */ 38 var allowedTags = ['A', 'B', 'STRONG']; 39 40 function merge() { 41 var clone = {}; 42 for (var i = 0; i < arguments.length; ++i) { 43 if (typeof arguments[i] == 'object') { 44 for (var key in arguments[i]) { 45 if (arguments[i].hasOwnProperty(key)) 46 clone[key] = arguments[i][key]; 47 } 48 } 49 } 50 return clone; 51 } 52 53 function walk(n, f) { 54 f(n); 55 for (var i = 0; i < n.childNodes.length; i++) { 56 walk(n.childNodes[i], f); 57 } 58 } 59 60 function assertElement(tags, node) { 61 if (tags.indexOf(node.tagName) == -1) 62 throw Error(node.tagName + ' is not supported'); 63 } 64 65 function assertAttribute(attrs, attrNode, node) { 66 var n = attrNode.nodeName; 67 var v = attrNode.nodeValue; 68 if (!attrs.hasOwnProperty(n) || !attrs[n](node, v)) 69 throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported'); 70 } 71 72 return function(s, opt_extraTags, opt_extraAttrs) { 73 var extraTags = 74 (opt_extraTags || []).map(function(str) { return str.toUpperCase(); }); 75 var tags = allowedTags.concat(extraTags); 76 var attrs = merge(allowedAttributes, opt_extraAttrs || {}); 77 78 var doc = document.implementation.createHTMLDocument(''); 79 var r = doc.createRange(); 80 r.selectNode(doc.body); 81 // This does not execute any scripts because the document has no view. 82 var df = r.createContextualFragment(s); 83 walk(df, function(node) { 84 switch (node.nodeType) { 85 case Node.ELEMENT_NODE: 86 assertElement(tags, node); 87 var nodeAttrs = node.attributes; 88 for (var i = 0; i < nodeAttrs.length; ++i) { 89 assertAttribute(attrs, nodeAttrs[i], node); 90 } 91 break; 92 93 case Node.COMMENT_NODE: 94 case Node.DOCUMENT_FRAGMENT_NODE: 95 case Node.TEXT_NODE: 96 break; 97 98 default: 99 throw Error('Node type ' + node.nodeType + ' is not supported'); 100 } 101 }); 102 return df; 103 }; 104})(); 105