1/**
2 * Whitelist of tag names allowed in parseHtmlSubset.
3 * @type {[string]}
4 */
5var allowedTags = ['A', 'B', 'STRONG'];
6
7/**
8 * Parse a very small subset of HTML.
9 * @param {string} s The string to parse.
10 * @throws {Error} In case of non supported markup.
11 * @return {DocumentFragment} A document fragment containing the DOM tree.
12 */
13var allowedAttributes = {
14  'href': function(node, value) {
15    // Only allow a[href] starting with http:// and https://
16    return node.tagName == 'A' && (value.indexOf('http://') == 0 ||
17        value.indexOf('https://') == 0);
18  },
19  'target': function(node, value) {
20    // Allow a[target] but reset the value to "".
21    if (node.tagName != 'A')
22      return false;
23    node.setAttribute('target', '');
24    return true;
25  }
26}
27
28/**
29 * Parse a very small subset of HTML.  This ensures that insecure HTML /
30 * javascript cannot be injected into the new tab page.
31 * @param {string} s The string to parse.
32 * @throws {Error} In case of non supported markup.
33 * @return {DocumentFragment} A document fragment containing the DOM tree.
34 */
35function parseHtmlSubset(s) {
36  function walk(n, f) {
37    f(n);
38    for (var i = 0; i < n.childNodes.length; i++) {
39      walk(n.childNodes[i], f);
40    }
41  }
42
43  function assertElement(node) {
44    if (allowedTags.indexOf(node.tagName) == -1)
45      throw Error(node.tagName + ' is not supported');
46  }
47
48  function assertAttribute(attrNode, node) {
49    var n = attrNode.nodeName;
50    var v = attrNode.nodeValue;
51    if (!allowedAttributes.hasOwnProperty(n) || !allowedAttributes[n](node, v))
52      throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported');
53  }
54
55  var r = document.createRange();
56  r.selectNode(document.body);
57  // This does not execute any scripts.
58  var df = r.createContextualFragment(s);
59  walk(df, function(node) {
60    switch (node.nodeType) {
61      case Node.ELEMENT_NODE:
62        assertElement(node);
63        var attrs = node.attributes;
64        for (var i = 0; i < attrs.length; i++) {
65          assertAttribute(attrs[i], node);
66        }
67        break;
68
69      case Node.COMMENT_NODE:
70      case Node.DOCUMENT_FRAGMENT_NODE:
71      case Node.TEXT_NODE:
72        break;
73
74      default:
75        throw Error('Node type ' + node.nodeType + ' is not supported');
76    }
77  });
78  return df;
79}
80