Home | History | Annotate | Download | only in js
      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