// Copyright 2013 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview * HTML tag filtering, and balancing. * A more user-friendly API is exposed via {@code goog.labs.html.sanitizer}. * @visibility {//visibility:private} */ goog.provide('goog.labs.html.scrubber'); goog.require('goog.array'); goog.require('goog.dom.tags'); goog.require('goog.labs.html.attributeRewriterPresubmitWorkaround'); goog.require('goog.string'); /** * Replaces tags not on the white-list with empty text nodes, dropping all * attributes, and drops other non-text nodes such as comments. * * @param {!Object} tagWhitelist a set of lower-case tag names * following the convention established by {@link goog.object.createSet}. * @param {!Object>} * attrWhitelist * maps lower-case tag names and the special string {@code "*"} to functions * from decoded attribute values to sanitized values or {@code null} to * indicate that the attribute is not allowed with that value. * * For example, if {@code attrWhitelist['a']['href']} is defined then it * is used to sanitize the value of the link's URL. * * If {@code attrWhitelist['*']['id']} is defined, and * {@code attrWhitelist['div']['id']} is not, then the former is used to * sanitize any {@code id} attribute on a {@code
} element. * @param {string} html a string of HTML * @return {string} the input but with potentially dangerous tokens removed. */ goog.labs.html.scrubber.scrub = function(tagWhitelist, attrWhitelist, html) { return goog.labs.html.scrubber.render_( goog.labs.html.scrubber.balance_( goog.labs.html.scrubber.filter_( tagWhitelist, attrWhitelist, goog.labs.html.scrubber.lex_(html)))); }; /** * Balances tags in trusted HTML. * @param {string} html a string of HTML * @return {string} the input but with an end-tag for each non-void start tag * and only for non-void start tags, and with start and end tags nesting * properly. */ goog.labs.html.scrubber.balance = function(html) { return goog.labs.html.scrubber.render_( goog.labs.html.scrubber.balance_( goog.labs.html.scrubber.lex_(html))); }; /** Character code constant for {@code '<'}. @private */ goog.labs.html.scrubber.CC_LT_ = '<'.charCodeAt(0); /** Character code constant for {@code '!'}. @private */ goog.labs.html.scrubber.CC_BANG_ = '!'.charCodeAt(0); /** Character code constant for {@code '/'}. @private */ goog.labs.html.scrubber.CC_SLASH_ = '/'.charCodeAt(0); /** Character code constant for {@code '?'}. @private */ goog.labs.html.scrubber.CC_QMARK_ = '?'.charCodeAt(0); /** * Matches content following a tag name or attribute value, and before the * beginning of the next attribute value. * @private */ goog.labs.html.scrubber.ATTR_VALUE_PRECEDER_ = '[^=>]+'; /** @private */ goog.labs.html.scrubber.UNQUOTED_ATTR_VALUE_ = '(?:[^"\'\\s>][^\\s>]*)'; /** @private */ goog.labs.html.scrubber.DOUBLE_QUOTED_ATTR_VALUE_ = '(?:"[^"]*"?)'; /** @private */ goog.labs.html.scrubber.SINGLE_QUOTED_ATTR_VALUE_ = "(?:'[^']*'?)"; /** * Matches the equals-sign and any attribute value following it, but does not * capture any {@code >} that would close the tag. * @private */ goog.labs.html.scrubber.ATTR_VALUE_ = '=\\s*(?:' + goog.labs.html.scrubber.UNQUOTED_ATTR_VALUE_ + '|' + goog.labs.html.scrubber.DOUBLE_QUOTED_ATTR_VALUE_ + '|' + goog.labs.html.scrubber.SINGLE_QUOTED_ATTR_VALUE_ + ')?'; /** * The body of a tag between the end of the name and the closing {@code >} * if any. * @private */ goog.labs.html.scrubber.ATTRS_ = '(?:' + goog.labs.html.scrubber.ATTR_VALUE_PRECEDER_ + '|' + goog.labs.html.scrubber.ATTR_VALUE_ + ')*'; /** * A character that continues a tag name as defined at * http://www.w3.org/html/wg/drafts/html/master/syntax.html#tag-name-state * @private */ goog.labs.html.scrubber.TAG_NAME_CHAR_ = '[^\t\f\n />]'; /** * Matches when the next character cannot continue a tag name. * @private */ goog.labs.html.scrubber.BREAK_ = '(?!' + goog.labs.html.scrubber.TAG_NAME_CHAR_ + ')'; /** * Matches the open tag and body of a special element : * one whose body cannot contain nested elements so uses special parsing rules. * It does not include the end tag. * @private */ goog.labs.html.scrubber.SPECIAL_ELEMENT_ = '<(?:' + // Special tag name. '(iframe|script|style|textarea|title|xmp)' + // End of tag name goog.labs.html.scrubber.BREAK_ + // Attributes goog.labs.html.scrubber.ATTRS_ + '>' + // Element content includes non '<' characters, and // '<' that don't start a matching end tag. // This uses a back-reference to the tag name to determine whether // the tag names match. // Since matching is case-insensitive, this can only be used in // a case-insensitive regular expression. // JavaScript does not treat Turkish dotted I's as equivalent to their // ASCII equivalents. '(?:[^<]|<(?!/\\1' + goog.labs.html.scrubber.BREAK_ + '))*' + ')'; /** * Regexp pattern for an HTML tag. * @private */ goog.labs.html.scrubber.TAG_ = '<[/]?[a-z]' + goog.labs.html.scrubber.TAG_NAME_CHAR_ + '*' + goog.labs.html.scrubber.ATTRS_ + '>?'; /** * Regexp pattern for an HTML text node. * @private */ goog.labs.html.scrubber.TEXT_NODE_ = '(?:[^<]|<(?![a-z]|[?!/]))+'; /** * Matches HTML comments including HTML 5 "bogus comments" of the form * {@code } or {@code } or {@code }. * @private */ goog.labs.html.scrubber.COMMENT_ = '