7262d0e9f9c9707654eaff189d27966967d7f515
[platform/framework/web/crosswalk.git] / src / ui / webui / resources / js / parse_html_subset.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.<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  */
15 var 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   /** @param {...Object} var_args Objects to merge. */
41   function merge(var_args) {
42     var clone = {};
43     for (var i = 0; i < arguments.length; ++i) {
44       if (typeof arguments[i] == 'object') {
45         for (var key in arguments[i]) {
46           if (arguments[i].hasOwnProperty(key))
47             clone[key] = arguments[i][key];
48         }
49       }
50     }
51     return clone;
52   }
53
54   function walk(n, f) {
55     f(n);
56     for (var i = 0; i < n.childNodes.length; i++) {
57       walk(n.childNodes[i], f);
58     }
59   }
60
61   function assertElement(tags, node) {
62     if (tags.indexOf(node.tagName) == -1)
63       throw Error(node.tagName + ' is not supported');
64   }
65
66   function assertAttribute(attrs, attrNode, node) {
67     var n = attrNode.nodeName;
68     var v = attrNode.nodeValue;
69     if (!attrs.hasOwnProperty(n) || !attrs[n](node, v))
70       throw Error(node.tagName + '[' + n + '="' + v + '"] is not supported');
71   }
72
73   return function(s, opt_extraTags, opt_extraAttrs) {
74     var extraTags =
75         (opt_extraTags || []).map(function(str) { return str.toUpperCase(); });
76     var tags = allowedTags.concat(extraTags);
77     var attrs = merge(allowedAttributes, opt_extraAttrs || {});
78
79     var doc = document.implementation.createHTMLDocument('');
80     var r = doc.createRange();
81     r.selectNode(doc.body);
82     // This does not execute any scripts because the document has no view.
83     var df = r.createContextualFragment(s);
84     walk(df, function(node) {
85       switch (node.nodeType) {
86         case Node.ELEMENT_NODE:
87           assertElement(tags, node);
88           var nodeAttrs = node.attributes;
89           for (var i = 0; i < nodeAttrs.length; ++i) {
90             assertAttribute(attrs, nodeAttrs[i], node);
91           }
92           break;
93
94         case Node.COMMENT_NODE:
95         case Node.DOCUMENT_FRAGMENT_NODE:
96         case Node.TEXT_NODE:
97           break;
98
99         default:
100           throw Error('Node type ' + node.nodeType + ' is not supported');
101       }
102     });
103     return df;
104   };
105 })();