1028 lines
38 KiB
JavaScript
1028 lines
38 KiB
JavaScript
// 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<string, boolean>} tagWhitelist a set of lower-case tag names
|
|
* following the convention established by {@link goog.object.createSet}.
|
|
* @param {!Object<string, Object<string, goog.labs.html.AttributeRewriter>>}
|
|
* 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 <div>} 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_ =
|
|
'<!--(?:[^\\-]|-+(?![\\->]))*(?:-(?:->?)?)?' +
|
|
'|<[!?/][^>]*>?';
|
|
|
|
|
|
/**
|
|
* Regexp pattern for an HTML token after a doctype.
|
|
* Special elements introduces a capturing group for use with a
|
|
* back-reference.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.HTML_TOKENS_RE_ = new RegExp(
|
|
'(?:' + goog.labs.html.scrubber.TEXT_NODE_ +
|
|
'|' + goog.labs.html.scrubber.SPECIAL_ELEMENT_ +
|
|
'|' + goog.labs.html.scrubber.TAG_ +
|
|
'|' + goog.labs.html.scrubber.COMMENT_ + ')',
|
|
'ig');
|
|
|
|
|
|
/**
|
|
* An HTML tag which captures the name in group 1,
|
|
* and any attributes in group 2.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.TAG_RE_ = new RegExp(
|
|
'<[/]?([a-z]' + goog.labs.html.scrubber.TAG_NAME_CHAR_ + '*)' +
|
|
'(' + goog.labs.html.scrubber.ATTRS_ + ')>?',
|
|
'i');
|
|
|
|
|
|
/**
|
|
* A global matcher that separates attributes out of the tag body cruft.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.ATTRS_RE_ = new RegExp(
|
|
'[^=\\s]+\\s*(?:' + goog.labs.html.scrubber.ATTR_VALUE_ + ')?', 'ig');
|
|
|
|
|
|
/**
|
|
* Returns an array of HTML tokens including tags, text nodes and comments.
|
|
* "Special" elements, like {@code <script>..</script>} whose bodies cannot
|
|
* include nested elements, are returned as single tokens.
|
|
*
|
|
* @param {string} html a string of HTML
|
|
* @return {!Array<string>}
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.lex_ = function(html) {
|
|
return ('' + html).match(goog.labs.html.scrubber.HTML_TOKENS_RE_) || [];
|
|
};
|
|
|
|
|
|
/**
|
|
* 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<string, boolean>} tagWhitelist a set of lower-case tag names
|
|
* following the convention established by {@link goog.object.createSet}.
|
|
* @param {!Object<string, Object<string, goog.labs.html.AttributeRewriter>>
|
|
* } 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 <div>} element.
|
|
* @param {!Array<string>} htmlTokens an array of HTML tokens as returned by
|
|
* {@link goog.labs.html.scrubber.lex_}.
|
|
* @return {!Array<string>} the input array modified in place to have some
|
|
* tokens removed.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.filter_ = function(
|
|
tagWhitelist, attrWhitelist, htmlTokens) {
|
|
var genericAttrWhitelist = attrWhitelist['*'];
|
|
for (var i = 0, n = htmlTokens.length; i < n; ++i) {
|
|
var htmlToken = htmlTokens[i];
|
|
if (htmlToken.charCodeAt(0) !== goog.labs.html.scrubber.CC_LT_) {
|
|
// Definitely not a tag
|
|
continue;
|
|
}
|
|
|
|
var tag = htmlToken.match(goog.labs.html.scrubber.TAG_RE_);
|
|
if (tag) {
|
|
var lowerCaseTagName = tag[1].toLowerCase();
|
|
var isCloseTag =
|
|
htmlToken.charCodeAt(1) === goog.labs.html.scrubber.CC_SLASH_;
|
|
var attrs = '';
|
|
if (!isCloseTag && tag[2]) {
|
|
var tagSpecificAttrWhitelist =
|
|
/** @type {Object<string, goog.labs.html.AttributeRewriter>} */ (
|
|
goog.labs.html.scrubber.readOwnProperty_(
|
|
attrWhitelist, lowerCaseTagName));
|
|
if (genericAttrWhitelist || tagSpecificAttrWhitelist) {
|
|
attrs = goog.labs.html.scrubber.filterAttrs_(
|
|
tag[2], genericAttrWhitelist, tagSpecificAttrWhitelist);
|
|
}
|
|
}
|
|
var specialContent = htmlToken.substring(tag[0].length);
|
|
htmlTokens[i] =
|
|
(tagWhitelist[lowerCaseTagName] === true) ?
|
|
(
|
|
(isCloseTag ? '</' : '<') + lowerCaseTagName + attrs + '>' +
|
|
specialContent
|
|
) :
|
|
'';
|
|
} else if (htmlToken.length > 1) {
|
|
switch (htmlToken.charCodeAt(1)) {
|
|
case goog.labs.html.scrubber.CC_BANG_:
|
|
case goog.labs.html.scrubber.CC_SLASH_:
|
|
case goog.labs.html.scrubber.CC_QMARK_:
|
|
htmlTokens[i] = ''; // Elide comments.
|
|
break;
|
|
default:
|
|
// Otherwise, token is just a text node that starts with '<'.
|
|
// Speed up later passes by normalizing the text node.
|
|
htmlTokens[i] = htmlTokens[i].replace(/</g, '<');
|
|
}
|
|
}
|
|
}
|
|
return htmlTokens;
|
|
};
|
|
|
|
|
|
/**
|
|
* Parses attribute names and values out of a tag body and applies the attribute
|
|
* white-list to produce a tag body containing only safe attributes.
|
|
*
|
|
* @param {string} attrsText the text of a tag between the end of the tag name
|
|
* and the beginning of the tag end marker, so {@code " foo bar='baz'"} for
|
|
* the tag {@code <tag foo bar='baz'/>}.
|
|
* @param {Object<string, goog.labs.html.AttributeRewriter>}
|
|
* genericAttrWhitelist
|
|
* a whitelist of attribute transformations for attributes that are allowed
|
|
* on any element.
|
|
* @param {Object<string, goog.labs.html.AttributeRewriter>}
|
|
* tagSpecificAttrWhitelist
|
|
* a whitelist of attribute transformations for attributes that are allowed
|
|
* on the element started by the tag whose body is {@code tagBody}.
|
|
* @return {string} a tag-body that consists only of safe attributes.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.filterAttrs_ =
|
|
function(attrsText, genericAttrWhitelist, tagSpecificAttrWhitelist) {
|
|
var attrs = attrsText.match(goog.labs.html.scrubber.ATTRS_RE_);
|
|
var nAttrs = attrs ? attrs.length : 0;
|
|
var safeAttrs = '';
|
|
for (var i = 0; i < nAttrs; ++i) {
|
|
var attr = attrs[i];
|
|
var eq = attr.indexOf('=');
|
|
var name, value;
|
|
if (eq >= 0) {
|
|
name = goog.string.trim(attr.substr(0, eq));
|
|
value = goog.string.stripQuotes(
|
|
goog.string.trim(attr.substr(eq + 1)), '"\'');
|
|
} else {
|
|
name = value = attr;
|
|
}
|
|
name = name.toLowerCase();
|
|
var rewriter = /** @type {?goog.labs.html.AttributeRewriter} */ (
|
|
tagSpecificAttrWhitelist &&
|
|
goog.labs.html.scrubber.readOwnProperty_(
|
|
tagSpecificAttrWhitelist, name) ||
|
|
genericAttrWhitelist &&
|
|
goog.labs.html.scrubber.readOwnProperty_(genericAttrWhitelist, name));
|
|
if (rewriter) {
|
|
var safeValue = rewriter(goog.string.unescapeEntities(value));
|
|
if (safeValue != null) {
|
|
if (safeValue.implementsGoogStringTypedString) {
|
|
safeValue = /** @type {goog.string.TypedString} */
|
|
(safeValue).getTypedStringValue();
|
|
}
|
|
safeValue = String(safeValue);
|
|
if (safeValue.indexOf('`') >= 0) {
|
|
safeValue += ' ';
|
|
}
|
|
safeAttrs +=
|
|
' ' + name + '="' + goog.string.htmlEscape(safeValue, false) +
|
|
'"';
|
|
}
|
|
}
|
|
}
|
|
return safeAttrs;
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {!Object} o the object
|
|
* @param {!string} k a key into o
|
|
* @return {*}
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.readOwnProperty_ = function(o, k) {
|
|
return Object.prototype.hasOwnProperty.call(o, k) ? o[k] : undefined;
|
|
};
|
|
|
|
|
|
/**
|
|
* We limit the nesting limit of balanced HTML to a large but manageable number
|
|
* so that built-in browser limits aren't likely to kick in and undo all our
|
|
* matching of start and end tags.
|
|
* <br>
|
|
* This mitigates the HTML parsing equivalent of stack smashing attacks.
|
|
* <br>
|
|
* Otherwise, crafted inputs like
|
|
* {@code <p><p><p><p>...Ad nauseam..</p></p></p></p>} could exploit
|
|
* browser bugs, and/or undocumented nesting limit recovery code to misnest
|
|
* tags.
|
|
* @private
|
|
* @const
|
|
*/
|
|
goog.labs.html.scrubber.BALANCE_NESTING_LIMIT_ = 256;
|
|
|
|
|
|
/**
|
|
* Ensures that there are end-tags for all and only for non-void start tags.
|
|
* @param {Array<string>} htmlTokens an array of HTML tokens as returned by
|
|
* {@link goog.labs.html.scrubber.lex}.
|
|
* @return {!Array<string>} the input array modified in place to have some
|
|
* tokens removed.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.balance_ = function(htmlTokens) {
|
|
var openElementStack = [];
|
|
for (var i = 0, n = htmlTokens.length; i < n; ++i) {
|
|
var htmlToken = htmlTokens[i];
|
|
if (htmlToken.charCodeAt(0) !== goog.labs.html.scrubber.CC_LT_) {
|
|
// Definitely not a tag
|
|
continue;
|
|
}
|
|
var tag = htmlToken.match(goog.labs.html.scrubber.TAG_RE_);
|
|
if (tag) {
|
|
var lowerCaseTagName = tag[1].toLowerCase();
|
|
var isCloseTag =
|
|
htmlToken.charCodeAt(1) === goog.labs.html.scrubber.CC_SLASH_;
|
|
// Special case: HTML5 mandates that </br> be treated as <br>.
|
|
if (isCloseTag && lowerCaseTagName == 'br') {
|
|
isCloseTag = false;
|
|
htmlToken = '<br>';
|
|
}
|
|
var isVoidTag = goog.dom.tags.isVoidTag(lowerCaseTagName);
|
|
if (isVoidTag && isCloseTag) {
|
|
htmlTokens[i] = '';
|
|
continue;
|
|
}
|
|
|
|
var prefix = '';
|
|
|
|
// Insert implied open tags.
|
|
var nOpenElements = openElementStack.length;
|
|
if (nOpenElements && !isCloseTag) {
|
|
var top = openElementStack[nOpenElements - 1];
|
|
var groups = goog.labs.html.scrubber.ELEMENT_GROUPS_[lowerCaseTagName];
|
|
if (groups === undefined) {
|
|
groups = goog.labs.html.scrubber.Group_.INLINE_;
|
|
}
|
|
var canContain = goog.labs.html.scrubber.ELEMENT_CONTENTS_[top];
|
|
if (!(groups & canContain)) {
|
|
var blockContainer = goog.labs.html.scrubber.BLOCK_CONTAINERS_[top];
|
|
if ('string' === typeof blockContainer) {
|
|
var containerCanContain =
|
|
goog.labs.html.scrubber.ELEMENT_CONTENTS_[blockContainer];
|
|
if (containerCanContain & groups) {
|
|
if (nOpenElements <
|
|
goog.labs.html.scrubber.BALANCE_NESTING_LIMIT_) {
|
|
prefix = '<' + blockContainer + '>';
|
|
}
|
|
openElementStack[nOpenElements] = blockContainer;
|
|
++nOpenElements;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Insert any missing close tags we need.
|
|
var newStackLen = goog.labs.html.scrubber.pickElementsToClose_(
|
|
lowerCaseTagName, isCloseTag, openElementStack);
|
|
|
|
var nClosed = nOpenElements - newStackLen;
|
|
if (nClosed) { // ["p", "a", "b"] -> "</b></a></p>"
|
|
// First, dump anything past the nesting limit.
|
|
if (nOpenElements > goog.labs.html.scrubber.BALANCE_NESTING_LIMIT_) {
|
|
nClosed -= nOpenElements -
|
|
goog.labs.html.scrubber.BALANCE_NESTING_LIMIT_;
|
|
nOpenElements = goog.labs.html.scrubber.BALANCE_NESTING_LIMIT_;
|
|
}
|
|
// Truncate to the new limit, and produce end tags.
|
|
var closeTags = openElementStack.splice(newStackLen, nClosed);
|
|
if (closeTags.length) {
|
|
closeTags.reverse();
|
|
prefix += '</' + closeTags.join('></') + '>';
|
|
}
|
|
}
|
|
|
|
// We could do resumption here to handle misnested tags like
|
|
// <b><i class="c">Foo</b>Bar</i>
|
|
// which is equivalent to
|
|
// <b><i class="c">Foo</i></b><i class="c">Bar</i>
|
|
// but that requires storing attributes on the open element stack
|
|
// which complicates all the code using it for marginal added value.
|
|
|
|
if (isCloseTag) {
|
|
// If the close tag matched an open tag, then the closed section
|
|
// included that tag name.
|
|
htmlTokens[i] = prefix;
|
|
} else {
|
|
if (!isVoidTag) {
|
|
openElementStack[openElementStack.length] = lowerCaseTagName;
|
|
}
|
|
if (openElementStack.length >
|
|
goog.labs.html.scrubber.BALANCE_NESTING_LIMIT_) {
|
|
htmlToken = '';
|
|
}
|
|
htmlTokens[i] = prefix + htmlToken;
|
|
}
|
|
}
|
|
}
|
|
if (openElementStack.length) {
|
|
if (openElementStack.length >
|
|
goog.labs.html.scrubber.BALANCE_NESTING_LIMIT_) {
|
|
openElementStack.length = goog.labs.html.scrubber.BALANCE_NESTING_LIMIT_;
|
|
}
|
|
if (openElementStack.length) {
|
|
openElementStack.reverse();
|
|
htmlTokens[htmlTokens.length] = '</' + openElementStack.join('></') + '>';
|
|
}
|
|
}
|
|
return htmlTokens;
|
|
};
|
|
|
|
|
|
/**
|
|
* Normalizes HTML tokens and concatenates them into a string.
|
|
* @param {Array<string>} htmlTokens an array of HTML tokens as returned by
|
|
* {@link goog.labs.html.scrubber.lex}.
|
|
* @return {string} a string of HTML.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.render_ = function(htmlTokens) {
|
|
for (var i = 0, n = htmlTokens.length; i < n; ++i) {
|
|
var htmlToken = htmlTokens[i];
|
|
if (htmlToken.charCodeAt(0) === goog.labs.html.scrubber.CC_LT_ &&
|
|
goog.labs.html.scrubber.TAG_RE_.test(htmlToken)) {
|
|
// The well-formedness and quotedness of attributes must be ensured by
|
|
// earlier passes. filter does this.
|
|
} else {
|
|
if (htmlToken.indexOf('<') >= 0) {
|
|
htmlToken = htmlToken.replace(/</g, '<');
|
|
}
|
|
if (htmlToken.indexOf('>') >= 0) {
|
|
htmlToken = htmlToken.replace(/>/g, '>');
|
|
}
|
|
htmlTokens[i] = htmlToken;
|
|
}
|
|
}
|
|
return htmlTokens.join('');
|
|
};
|
|
|
|
|
|
/**
|
|
* Groups of elements used to specify containment relationships.
|
|
* @enum {number}
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.Group_ = {
|
|
BLOCK_: (1 << 0),
|
|
INLINE_: (1 << 1),
|
|
INLINE_MINUS_A_: (1 << 2),
|
|
MIXED_: (1 << 3),
|
|
TABLE_CONTENT_: (1 << 4),
|
|
HEAD_CONTENT_: (1 << 5),
|
|
TOP_CONTENT_: (1 << 6),
|
|
AREA_ELEMENT_: (1 << 7),
|
|
FORM_ELEMENT_: (1 << 8),
|
|
LEGEND_ELEMENT_: (1 << 9),
|
|
LI_ELEMENT_: (1 << 10),
|
|
DL_PART_: (1 << 11),
|
|
P_ELEMENT_: (1 << 12),
|
|
OPTIONS_ELEMENT_: (1 << 13),
|
|
OPTION_ELEMENT_: (1 << 14),
|
|
PARAM_ELEMENT_: (1 << 15),
|
|
TABLE_ELEMENT_: (1 << 16),
|
|
TR_ELEMENT_: (1 << 17),
|
|
TD_ELEMENT_: (1 << 18),
|
|
COL_ELEMENT_: (1 << 19),
|
|
CHARACTER_DATA_: (1 << 20)
|
|
};
|
|
|
|
|
|
/**
|
|
* Element scopes limit where close tags can have effects.
|
|
* For example, a table cannot be implicitly closed by a {@code </p>} even if
|
|
* the table appears inside a {@code <p>} because the {@code <table>} element
|
|
* introduces a scope.
|
|
*
|
|
* @enum {number}
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.Scope_ = {
|
|
COMMON_: (1 << 0),
|
|
BUTTON_: (1 << 1),
|
|
LIST_ITEM_: (1 << 2),
|
|
TABLE_: (1 << 3)
|
|
};
|
|
|
|
|
|
/** @const @private */
|
|
goog.labs.html.scrubber.ALL_SCOPES_ =
|
|
goog.labs.html.scrubber.Scope_.COMMON_ |
|
|
goog.labs.html.scrubber.Scope_.BUTTON_ |
|
|
goog.labs.html.scrubber.Scope_.LIST_ITEM_ |
|
|
goog.labs.html.scrubber.Scope_.TABLE_;
|
|
|
|
|
|
/**
|
|
* Picks which open HTML elements to close.
|
|
*
|
|
* @param {string} lowerCaseTagName The name of the tag.
|
|
* @param {boolean} isCloseTag True for a {@code </tagname>} tag.
|
|
* @param {Array<string>} openElementStack The names of elements that have been
|
|
* opened and not subsequently closed.
|
|
* @return {number} the length of openElementStack after closing any tags that
|
|
* need to be closed.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.pickElementsToClose_ =
|
|
function(lowerCaseTagName, isCloseTag, openElementStack) {
|
|
var nOpenElements = openElementStack.length;
|
|
if (isCloseTag) {
|
|
// Look for a matching close tag inside blocking scopes.
|
|
var topMost;
|
|
if (/^h[1-6]$/.test(lowerCaseTagName)) {
|
|
// </h1> will close any header.
|
|
topMost = -1;
|
|
for (var i = nOpenElements; --i >= 0;) {
|
|
if (/^h[1-6]$/.test(openElementStack[i])) {
|
|
topMost = i;
|
|
}
|
|
}
|
|
} else {
|
|
topMost = goog.array.lastIndexOf(openElementStack, lowerCaseTagName);
|
|
}
|
|
if (topMost >= 0) {
|
|
var blockers = goog.labs.html.scrubber.ALL_SCOPES_ &
|
|
~(goog.labs.html.scrubber.ELEMENT_SCOPES_[lowerCaseTagName] | 0);
|
|
for (var i = nOpenElements; --i > topMost;) {
|
|
var blocks =
|
|
goog.labs.html.scrubber.ELEMENT_SCOPES_[openElementStack[i]] | 0;
|
|
if (blockers & blocks) {
|
|
return nOpenElements;
|
|
}
|
|
}
|
|
return topMost;
|
|
}
|
|
return nOpenElements;
|
|
} else {
|
|
// Close anything that cannot contain the tag name.
|
|
var groups = goog.labs.html.scrubber.ELEMENT_GROUPS_[lowerCaseTagName];
|
|
if (groups === undefined) {
|
|
groups = goog.labs.html.scrubber.Group_.INLINE_;
|
|
}
|
|
for (var i = nOpenElements; --i >= 0;) {
|
|
var canContain =
|
|
goog.labs.html.scrubber.ELEMENT_CONTENTS_[openElementStack[i]];
|
|
if (canContain === undefined) {
|
|
canContain = goog.labs.html.scrubber.Group_.INLINE_;
|
|
}
|
|
if (groups & canContain) {
|
|
return i + 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* The groups into which the element falls.
|
|
* The default is an inline element.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.ELEMENT_GROUPS_ = {
|
|
'a': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'abbr': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'acronym': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'address': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'applet': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'area': goog.labs.html.scrubber.Group_.AREA_ELEMENT_,
|
|
'audio': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'b': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'base': goog.labs.html.scrubber.Group_.HEAD_CONTENT_,
|
|
'basefont': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'bdi': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'bdo': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'big': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'blink': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'blockquote': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'body': goog.labs.html.scrubber.Group_.TOP_CONTENT_,
|
|
'br': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'button': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'canvas': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'caption': goog.labs.html.scrubber.Group_.TABLE_CONTENT_,
|
|
'center': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'cite': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'code': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'col': goog.labs.html.scrubber.Group_.TABLE_CONTENT_ |
|
|
goog.labs.html.scrubber.Group_.COL_ELEMENT_,
|
|
'colgroup': goog.labs.html.scrubber.Group_.TABLE_CONTENT_,
|
|
'dd': goog.labs.html.scrubber.Group_.DL_PART_,
|
|
'del': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.MIXED_,
|
|
'dfn': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'dir': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'div': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'dl': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'dt': goog.labs.html.scrubber.Group_.DL_PART_,
|
|
'em': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'fieldset': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'font': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'form': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.FORM_ELEMENT_,
|
|
'h1': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'h2': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'h3': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'h4': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'h5': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'h6': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'head': goog.labs.html.scrubber.Group_.TOP_CONTENT_,
|
|
'hr': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'html': 0,
|
|
'i': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'iframe': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'img': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'input': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'ins': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'isindex': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'kbd': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'label': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'legend': goog.labs.html.scrubber.Group_.LEGEND_ELEMENT_,
|
|
'li': goog.labs.html.scrubber.Group_.LI_ELEMENT_,
|
|
'link': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.HEAD_CONTENT_,
|
|
'listing': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'map': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'meta': goog.labs.html.scrubber.Group_.HEAD_CONTENT_,
|
|
'nobr': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'noframes': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.TOP_CONTENT_,
|
|
'noscript': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'object': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_ |
|
|
goog.labs.html.scrubber.Group_.HEAD_CONTENT_,
|
|
'ol': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'optgroup': goog.labs.html.scrubber.Group_.OPTIONS_ELEMENT_,
|
|
'option': goog.labs.html.scrubber.Group_.OPTIONS_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.OPTION_ELEMENT_,
|
|
'p': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.P_ELEMENT_,
|
|
'param': goog.labs.html.scrubber.Group_.PARAM_ELEMENT_,
|
|
'pre': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'q': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
's': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'samp': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'script': (goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_ |
|
|
goog.labs.html.scrubber.Group_.MIXED_ |
|
|
goog.labs.html.scrubber.Group_.TABLE_CONTENT_ |
|
|
goog.labs.html.scrubber.Group_.HEAD_CONTENT_ |
|
|
goog.labs.html.scrubber.Group_.TOP_CONTENT_ |
|
|
goog.labs.html.scrubber.Group_.AREA_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.FORM_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.LEGEND_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.LI_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.DL_PART_ |
|
|
goog.labs.html.scrubber.Group_.P_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.OPTIONS_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.OPTION_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.PARAM_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.TABLE_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.TR_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.TD_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.COL_ELEMENT_),
|
|
'select': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'small': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'span': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'strike': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'strong': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'style': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.HEAD_CONTENT_,
|
|
'sub': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'sup': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'table': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.TABLE_ELEMENT_,
|
|
'tbody': goog.labs.html.scrubber.Group_.TABLE_CONTENT_,
|
|
'td': goog.labs.html.scrubber.Group_.TD_ELEMENT_,
|
|
'textarea': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'tfoot': goog.labs.html.scrubber.Group_.TABLE_CONTENT_,
|
|
'th': goog.labs.html.scrubber.Group_.TD_ELEMENT_,
|
|
'thead': goog.labs.html.scrubber.Group_.TABLE_CONTENT_,
|
|
'title': goog.labs.html.scrubber.Group_.HEAD_CONTENT_,
|
|
'tr': goog.labs.html.scrubber.Group_.TABLE_CONTENT_ |
|
|
goog.labs.html.scrubber.Group_.TR_ELEMENT_,
|
|
'tt': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'u': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'ul': goog.labs.html.scrubber.Group_.BLOCK_,
|
|
'var': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'video': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'wbr': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'xmp': goog.labs.html.scrubber.Group_.BLOCK_
|
|
};
|
|
|
|
|
|
/**
|
|
* The groups which the element can contain.
|
|
* Defaults to 0.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.ELEMENT_CONTENTS_ = {
|
|
'a': goog.labs.html.scrubber.Group_.INLINE_MINUS_A_,
|
|
'abbr': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'acronym': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'address': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.P_ELEMENT_,
|
|
'applet': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.PARAM_ELEMENT_,
|
|
'b': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'bdi': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'bdo': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'big': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'blink': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'blockquote': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'body': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'button': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'canvas': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'caption': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'center': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'cite': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'code': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'colgroup': goog.labs.html.scrubber.Group_.COL_ELEMENT_,
|
|
'dd': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'del': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'dfn': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'dir': goog.labs.html.scrubber.Group_.LI_ELEMENT_,
|
|
'div': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'dl': goog.labs.html.scrubber.Group_.DL_PART_,
|
|
'dt': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'em': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'fieldset': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.LEGEND_ELEMENT_,
|
|
'font': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'form': (goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_MINUS_A_ |
|
|
goog.labs.html.scrubber.Group_.TR_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.TD_ELEMENT_),
|
|
'h1': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'h2': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'h3': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'h4': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'h5': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'h6': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'head': goog.labs.html.scrubber.Group_.HEAD_CONTENT_,
|
|
'html': goog.labs.html.scrubber.Group_.TOP_CONTENT_,
|
|
'i': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'iframe': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'ins': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'kbd': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'label': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'legend': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'li': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'listing': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'map': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.AREA_ELEMENT_,
|
|
'nobr': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'noframes': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.TOP_CONTENT_,
|
|
'noscript': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'object': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.PARAM_ELEMENT_,
|
|
'ol': goog.labs.html.scrubber.Group_.LI_ELEMENT_,
|
|
'optgroup': goog.labs.html.scrubber.Group_.OPTIONS_ELEMENT_,
|
|
'option': goog.labs.html.scrubber.Group_.CHARACTER_DATA_,
|
|
'p': goog.labs.html.scrubber.Group_.INLINE_ |
|
|
goog.labs.html.scrubber.Group_.TABLE_ELEMENT_,
|
|
'pre': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'q': goog.labs.html.scrubber.Group_.INLINE_,
|
|
's': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'samp': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'script': goog.labs.html.scrubber.Group_.CHARACTER_DATA_,
|
|
'select': goog.labs.html.scrubber.Group_.OPTIONS_ELEMENT_,
|
|
'small': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'span': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'strike': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'strong': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'style': goog.labs.html.scrubber.Group_.CHARACTER_DATA_,
|
|
'sub': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'sup': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'table': goog.labs.html.scrubber.Group_.TABLE_CONTENT_ |
|
|
goog.labs.html.scrubber.Group_.FORM_ELEMENT_,
|
|
'tbody': goog.labs.html.scrubber.Group_.TR_ELEMENT_,
|
|
'td': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'textarea': goog.labs.html.scrubber.Group_.CHARACTER_DATA_,
|
|
'tfoot': goog.labs.html.scrubber.Group_.FORM_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.TR_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.TD_ELEMENT_,
|
|
'th': goog.labs.html.scrubber.Group_.BLOCK_ |
|
|
goog.labs.html.scrubber.Group_.INLINE_,
|
|
'thead': goog.labs.html.scrubber.Group_.FORM_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.TR_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.TD_ELEMENT_,
|
|
'title': goog.labs.html.scrubber.Group_.CHARACTER_DATA_,
|
|
'tr': goog.labs.html.scrubber.Group_.FORM_ELEMENT_ |
|
|
goog.labs.html.scrubber.Group_.TD_ELEMENT_,
|
|
'tt': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'u': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'ul': goog.labs.html.scrubber.Group_.LI_ELEMENT_,
|
|
'var': goog.labs.html.scrubber.Group_.INLINE_,
|
|
'xmp': goog.labs.html.scrubber.Group_.INLINE_
|
|
};
|
|
|
|
|
|
/**
|
|
* The scopes in which an element falls.
|
|
* No property defaults to 0.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.ELEMENT_SCOPES_ = {
|
|
'applet': goog.labs.html.scrubber.Scope_.COMMON_ |
|
|
goog.labs.html.scrubber.Scope_.BUTTON_ |
|
|
goog.labs.html.scrubber.Scope_.LIST_ITEM_,
|
|
'button': goog.labs.html.scrubber.Scope_.BUTTON_,
|
|
'caption': goog.labs.html.scrubber.Scope_.COMMON_ |
|
|
goog.labs.html.scrubber.Scope_.BUTTON_ |
|
|
goog.labs.html.scrubber.Scope_.LIST_ITEM_,
|
|
'html': goog.labs.html.scrubber.Scope_.COMMON_ |
|
|
goog.labs.html.scrubber.Scope_.BUTTON_ |
|
|
goog.labs.html.scrubber.Scope_.LIST_ITEM_ |
|
|
goog.labs.html.scrubber.Scope_.TABLE_,
|
|
'object': goog.labs.html.scrubber.Scope_.COMMON_ |
|
|
goog.labs.html.scrubber.Scope_.BUTTON_ |
|
|
goog.labs.html.scrubber.Scope_.LIST_ITEM_,
|
|
'ol': goog.labs.html.scrubber.Scope_.LIST_ITEM_,
|
|
'table': goog.labs.html.scrubber.Scope_.COMMON_ |
|
|
goog.labs.html.scrubber.Scope_.BUTTON_ |
|
|
goog.labs.html.scrubber.Scope_.LIST_ITEM_ |
|
|
goog.labs.html.scrubber.Scope_.TABLE_,
|
|
'td': goog.labs.html.scrubber.Scope_.COMMON_ |
|
|
goog.labs.html.scrubber.Scope_.BUTTON_ |
|
|
goog.labs.html.scrubber.Scope_.LIST_ITEM_,
|
|
'th': goog.labs.html.scrubber.Scope_.COMMON_ |
|
|
goog.labs.html.scrubber.Scope_.BUTTON_ |
|
|
goog.labs.html.scrubber.Scope_.LIST_ITEM_,
|
|
'ul': goog.labs.html.scrubber.Scope_.LIST_ITEM_
|
|
};
|
|
|
|
|
|
/**
|
|
* Per-element, a child that can contain block content.
|
|
* @private
|
|
*/
|
|
goog.labs.html.scrubber.BLOCK_CONTAINERS_ = {
|
|
'dl': 'dd',
|
|
'ol': 'li',
|
|
'table': 'tr',
|
|
'tr': 'td',
|
|
'ul': 'li'
|
|
};
|
|
|
|
|
|
goog.labs.html.attributeRewriterPresubmitWorkaround();
|