Path: blob/trunk/third_party/closure/goog/html/sanitizer/htmlsanitizer.js
2868 views
// Copyright 2016 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.131415/**16* @fileoverview An HTML sanitizer that can satisfy a variety of security17* policies.18*19* This package provides html sanitizing functions. It does not enforce string20* to string conversion, instead returning a dom-like element when possible.21*22* Examples of usage of the static {@code goog.goog.html.sanitizer.sanitize}:23* <pre>24* var safeHtml = goog.html.sanitizer.sanitize('<script src="xss.js" />');25* goog.dom.safe.setInnerHtml(el, safeHtml);26* </pre>27*28* @supported IE 10+, Chrome 26+, Firefox 22+, Safari 7.1+, Opera 15+29*/3031goog.provide('goog.html.sanitizer.HtmlSanitizer');32goog.provide('goog.html.sanitizer.HtmlSanitizer.Builder');33goog.provide('goog.html.sanitizer.HtmlSanitizerAttributePolicy');34goog.provide('goog.html.sanitizer.HtmlSanitizerPolicy');35goog.provide('goog.html.sanitizer.HtmlSanitizerPolicyContext');36goog.provide('goog.html.sanitizer.HtmlSanitizerPolicyHints');37goog.provide('goog.html.sanitizer.HtmlSanitizerUrlPolicy');3839goog.require('goog.array');40goog.require('goog.asserts');41goog.require('goog.dom');42goog.require('goog.dom.NodeType');43goog.require('goog.functions');44goog.require('goog.html.SafeHtml');45goog.require('goog.html.SafeStyle');46goog.require('goog.html.SafeUrl');47goog.require('goog.html.sanitizer.AttributeSanitizedWhitelist');48goog.require('goog.html.sanitizer.AttributeWhitelist');49goog.require('goog.html.sanitizer.CssSanitizer');50goog.require('goog.html.sanitizer.TagBlacklist');51goog.require('goog.html.sanitizer.TagWhitelist');52goog.require('goog.html.uncheckedconversions');53goog.require('goog.object');54goog.require('goog.string');55goog.require('goog.string.Const');56goog.require('goog.userAgent');575859/**60* Type for optional hints to policy handler functions.61* @typedef {{62* tagName: (string|undefined),63* attributeName: (string|undefined),64* cssProperty: (string|undefined)65* }}66*/67goog.html.sanitizer.HtmlSanitizerPolicyHints;686970/**71* Type for optional context objects to the policy handler functions.72* @typedef {{73* cssStyle: (?CSSStyleDeclaration|undefined)74* }}75*/76goog.html.sanitizer.HtmlSanitizerPolicyContext;777879/**80* Type for a policy function.81* @typedef {function(string, goog.html.sanitizer.HtmlSanitizerPolicyHints=,82* goog.html.sanitizer.HtmlSanitizerPolicyContext=,83* goog.html.sanitizer.HtmlSanitizerPolicy=):?string}84*/85goog.html.sanitizer.HtmlSanitizerPolicy;868788/**89* Type for a URL policy function.90*91* @typedef {function(string, !goog.html.sanitizer.HtmlSanitizerPolicyHints=):92* ?goog.html.SafeUrl}93*/94goog.html.sanitizer.HtmlSanitizerUrlPolicy;959697/**98* Type for attribute policy configuration.99* @typedef {{100* tagName: string,101* attributeName: string,102* policy: ?goog.html.sanitizer.HtmlSanitizerPolicy103* }}104*/105goog.html.sanitizer.HtmlSanitizerAttributePolicy;106107108/**109* Whether the HTML sanitizer is supported. For now mainly exclude110* IE9 or below where we know the sanitizer is insecure.111* @const @private {boolean}112*/113goog.html.sanitizer.HTML_SANITIZER_SUPPORTED_ =114!goog.userAgent.IE || document.documentMode >= 10;115116117/**118* Whether the template tag is supported.119* @const @package120*/121goog.html.sanitizer.HTML_SANITIZER_TEMPLATE_SUPPORTED =122!goog.userAgent.IE || document.documentMode == null;123124125/**126* Prefix used by all internal html sanitizer booking properties.127* @const @private {string}128*/129goog.html.sanitizer.HTML_SANITIZER_BOOKKEEPING_PREFIX_ = 'data-sanitizer-';130131132/**133* Temporary attribute name in which html sanitizer uses for bookkeeping.134* @const @private {string}135*/136goog.html.sanitizer.HTML_SANITIZER_BOOKKEEPING_ATTR_NAME_ =137goog.html.sanitizer.HTML_SANITIZER_BOOKKEEPING_PREFIX_ + 'elem-num';138139140/**141* Attribute name added to span tags that replace unknown tags. The value of142* this attribute is the name of the tag before the sanitization occurred.143* @const @private144*/145goog.html.sanitizer.HTML_SANITIZER_SANITIZED_ATTR_NAME_ =146goog.html.sanitizer.HTML_SANITIZER_BOOKKEEPING_PREFIX_ + 'original-tag';147148149/**150* Attribute name added to blacklisted tags to then filter them from the output.151* @const @private152*/153goog.html.sanitizer.HTML_SANITIZER_BLACKLISTED_TAG_ =154goog.html.sanitizer.HTML_SANITIZER_BOOKKEEPING_PREFIX_ + 'blacklisted-tag';155156157/**158* Map of property descriptors we use to avoid looking up the prototypes159* multiple times.160* @const @private {!Object<string, !ObjectPropertyDescriptor>}161*/162goog.html.sanitizer.HTML_SANITIZER_PROPERTY_DESCRIPTORS_ =163goog.html.sanitizer.HTML_SANITIZER_SUPPORTED_ ? {164'attributes':165Object.getOwnPropertyDescriptor(Element.prototype, 'attributes'),166'setAttribute':167Object.getOwnPropertyDescriptor(Element.prototype, 'setAttribute'),168'innerHTML':169Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML'),170'nodeName': Object.getOwnPropertyDescriptor(Node.prototype, 'nodeName'),171'parentNode':172Object.getOwnPropertyDescriptor(Node.prototype, 'parentNode'),173'childNodes':174Object.getOwnPropertyDescriptor(Node.prototype, 'childNodes'),175'style': Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'style')176} :177{};178179180/**181* Creates an HTML sanitizer.182* @param {!goog.html.sanitizer.HtmlSanitizer.Builder=} opt_builder183* @final @constructor @struct184*/185goog.html.sanitizer.HtmlSanitizer = function(opt_builder) {186var builder = opt_builder || new goog.html.sanitizer.HtmlSanitizer.Builder();187188builder.installPolicies_();189190/** @private {boolean} */191this.shouldSanitizeTemplateContents_ =192builder.shouldSanitizeTemplateContents_;193194/** @private {!Object<string, !goog.html.sanitizer.HtmlSanitizerPolicy>} */195this.attributeHandlers_ = goog.object.clone(builder.attributeWhitelist_);196197/** @private {!Object<string, boolean>} */198this.tagBlacklist_ = goog.object.clone(builder.tagBlacklist_);199200/** @private {!Object<string, boolean>} */201this.tagWhitelist_ = goog.object.clone(builder.tagWhitelist_);202203/** @private {boolean} */204this.shouldAddOriginalTagNames_ = builder.shouldAddOriginalTagNames_;205206// Add whitelist data-* attributes from the builder to the attributeHandlers207// with a default cleanUpAttribute function. data-* attributes are inert as208// per HTML5 specs, so not much sanitization needed.209goog.array.forEach(builder.dataAttributeWhitelist_, function(dataAttr) {210goog.asserts.assert(goog.string.startsWith(dataAttr, 'data-'));211goog.asserts.assert(!goog.string.startsWith(212dataAttr, goog.html.sanitizer.HTML_SANITIZER_BOOKKEEPING_PREFIX_));213214this.attributeHandlers_['* ' + dataAttr.toUpperCase()] =215/** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */ (216goog.html.sanitizer.HtmlSanitizer.cleanUpAttribute_);217}, this);218};219220221222/**223* Converts a HtmlSanitizerUrlPolicy to a HtmlSanitizerPolicy by calling the224* HtmlSanitizerUrlPolicy with the required arguments and unwrapping the225* returned SafeUrl.226* @param {!goog.html.sanitizer.HtmlSanitizerUrlPolicy} customUrlPolicy227* @return {!goog.html.sanitizer.HtmlSanitizerPolicy}228* @private229*/230goog.html.sanitizer.HtmlSanitizer.sanitizeUrl_ = function(customUrlPolicy) {231return /** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */ (232function(url, policyHints) {233var trimmed = goog.html.sanitizer.HtmlSanitizer.cleanUpAttribute_(234url, policyHints);235var safeUrl = customUrlPolicy(trimmed, policyHints);236if (safeUrl && goog.html.SafeUrl.unwrap(safeUrl) !=237goog.html.SafeUrl.INNOCUOUS_STRING) {238return goog.html.SafeUrl.unwrap(safeUrl);239} else {240return null;241}242});243};244245246247/**248* The builder for the HTML Sanitizer. All methods except build return this.249* @final @constructor @struct250*/251goog.html.sanitizer.HtmlSanitizer.Builder = function() {252/**253* A set of attribute sanitization functions. Default built-in handlers are254* all tag-agnostic by design. Note that some attributes behave differently255* when attached to different nodes (for example, the href attribute will256* generally not make a network request, but <link href=""> does), and257* so when necessary a tag-specific handler can be used to override a258* tag-agnostic one.259* @private {!Object<string, !goog.html.sanitizer.HtmlSanitizerPolicy>}260*/261this.attributeWhitelist_ = {};262goog.array.forEach(263[264goog.html.sanitizer.AttributeWhitelist,265goog.html.sanitizer.AttributeSanitizedWhitelist266],267function(wl) {268goog.array.forEach(goog.object.getKeys(wl), function(attr) {269this.attributeWhitelist_[attr] =270/** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */271(goog.html.sanitizer.HtmlSanitizer.cleanUpAttribute_);272}, this);273},274this);275276/**277* A set of attribute handlers that should not inherit their default policy278* during build().279* @private {!Object<string, boolean>}280*/281this.attributeOverrideList_ = {};282283284/**285* Keeps track of whether we allow form tags.286* @private {boolean}287*/288this.allowFormTag_ = false;289290/**291* Whether the content of TEMPLATE tags (assuming TEMPLATE is whitelisted)292* should be sanitized or passed through.293* @private {boolean}294*/295this.shouldSanitizeTemplateContents_ = true;296297/**298* List of data attributes to whitelist. Data-attributes are inert and don't299* require sanitization.300* @private {!Array<string>}301*/302this.dataAttributeWhitelist_ = [];303304/**305* A tag blacklist, to effectively remove an element and its children from the306* dom.307* @private {!Object<string, boolean>}308*/309this.tagBlacklist_ = {};310311/**312* A tag whitelist, to effectively allow an element and its children from the313* dom.314* @private {!Object<string, boolean>}315*/316this.tagWhitelist_ = goog.object.clone(goog.html.sanitizer.TagWhitelist);317318/**319* Whether non-whitelisted and non-blacklisted tags that have been converted320* to <span&rt; tags will contain the original tag in a data attribute.321* @private {boolean}322*/323this.shouldAddOriginalTagNames_ = false;324325/**326* A function to be applied to URLs found on the parsing process which do not327* trigger requests.328* @private {!goog.html.sanitizer.HtmlSanitizerPolicy}329*/330this.urlPolicy_ = goog.html.sanitizer.HtmlSanitizer.defaultUrlPolicy_;331332/**333* A function to be applied to urls found on the parsing process which may334* trigger requests.335* @private {!goog.html.sanitizer.HtmlSanitizerPolicy}336*/337this.networkRequestUrlPolicy_ =338goog.html.sanitizer.HtmlSanitizer.defaultNetworkRequestUrlPolicy_;339340/**341* A function to be applied to names found on the parsing process.342* @private {!goog.html.sanitizer.HtmlSanitizerPolicy}343*/344this.namePolicy_ = goog.html.sanitizer.HtmlSanitizer.defaultNamePolicy_;345346/**347* A function to be applied to other tokens (i.e. classes and IDs) found on348* the parsing process.349* @private {!goog.html.sanitizer.HtmlSanitizerPolicy}350*/351this.tokenPolicy_ = goog.html.sanitizer.HtmlSanitizer.defaultTokenPolicy_;352353/**354* A function to sanitize inline CSS styles.355* @private {(undefined|function(356* !goog.html.sanitizer.HtmlSanitizerPolicy,357* string,358* !goog.html.sanitizer.HtmlSanitizerPolicyHints,359* !goog.html.sanitizer.HtmlSanitizerPolicyContext):?string)}360*/361this.sanitizeCssPolicy_ = undefined;362363/**364* True iff policies have been installed for the instance.365* @private {boolean}366*/367this.policiesInstalled_ = false;368};369370371/**372* Extends the list of allowed data attributes.373* @param {!Array<string>} dataAttributeWhitelist374* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}375*/376goog.html.sanitizer.HtmlSanitizer.Builder.prototype.allowDataAttributes =377function(dataAttributeWhitelist) {378goog.array.extend(this.dataAttributeWhitelist_, dataAttributeWhitelist);379return this;380};381382383/**384* Allows form tags in the HTML. Without this all form tags and content will be385* dropped.386* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}387*/388goog.html.sanitizer.HtmlSanitizer.Builder.prototype.allowFormTag = function() {389this.allowFormTag_ = true;390return this;391};392393394/**395* Extends the tag whitelist (Package-internal utility method only).396* @param {!Array<string>} tags The list of tags to be added to the whitelist.397* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}398* @package399*/400goog.html.sanitizer.HtmlSanitizer.Builder.prototype401.alsoAllowTagsPrivateDoNotAccessOrElse = function(tags) {402goog.array.forEach(tags, function(tag) {403this.tagWhitelist_[tag.toUpperCase()] = true;404}, this);405return this;406};407408409/**410* Extends the attribute whitelist (Package-internal utility method only).411* @param {!Array<(string|!goog.html.sanitizer.HtmlSanitizerAttributePolicy)>}412* attrs The list of attributes to be added to the whitelist.413* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}414* @package415*/416goog.html.sanitizer.HtmlSanitizer.Builder.prototype417.alsoAllowAttributesPrivateDoNotAccessOrElse = function(attrs) {418goog.array.forEach(attrs, function(attr) {419if (goog.isString(attr)) {420attr = {tagName: '*', attributeName: attr, policy: null};421}422var handlerName = goog.html.sanitizer.HtmlSanitizer.attrIdentifier_(423attr.tagName, attr.attributeName);424this.attributeWhitelist_[handlerName] = attr.policy ?425attr.policy :426/** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */ (427goog.html.sanitizer.HtmlSanitizer.cleanUpAttribute_);428this.attributeOverrideList_[handlerName] = true;429}, this);430return this;431};432433434/**435* Turns off sanitization of template tag contents and pass them unmodified436* (Package-internal utility method only).437* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}438* @throws {!Error}439* @package440*/441goog.html.sanitizer.HtmlSanitizer.Builder.prototype442.keepUnsanitizedTemplateContentsPrivateDoNotAccessOrElse = function() {443if (!goog.html.sanitizer.HTML_SANITIZER_TEMPLATE_SUPPORTED) {444throw new Error(445'Cannot let unsanitized template contents through on ' +446'browsers that do not support TEMPLATE');447}448this.shouldSanitizeTemplateContents_ = false;449return this;450};451452453/**454* Allows only the provided whitelist of tags. Tags still need to be in the455* TagWhitelist to be allowed.456* <p>457* SPAN tags are ALWAYS ALLOWED as part of the mechanism required to preserve458* the HTML tree structure (when removing non-blacklisted tags and459* non-whitelisted tags).460* @param {!Array<string>} tagWhitelist461* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}462* @throws {Error} Thrown if an attempt is made to allow a non-whitelisted tag.463*/464goog.html.sanitizer.HtmlSanitizer.Builder.prototype.onlyAllowTags = function(465tagWhitelist) {466this.tagWhitelist_ = {'SPAN': true};467goog.array.forEach(tagWhitelist, function(tag) {468tag = tag.toUpperCase();469if (goog.html.sanitizer.TagWhitelist[tag]) {470this.tagWhitelist_[tag] = true;471} else {472throw new Error(473'Only whitelisted tags can be allowed. See ' +474'goog.html.sanitizer.TagWhitelist');475}476}, this);477return this;478};479480481/**482* Allows only the provided whitelist of attributes, possibly setting a custom483* policy for them. The set of tag/attribute combinations need to be a subset of484* the currently allowed combinations.485* <p>486* Note that you cannot define a generic handler for an attribute if only a487* tag-specific one is present, and vice versa. To configure the sanitizer to488* accept an attribute only for a specific tag when only a generic handler is489* whitelisted, use the goog.html.sanitizer.HtmlSanitizerPolicyHints parameter490* and simply reject the attribute in unwanted tags.491* <p>492* Also note that the sanitizer's policy is still called after the provided one,493* to ensure that supplying misconfigured policy cannot introduce494* vulnerabilities. To completely override an existing attribute policy or to495* allow new attributes, see the goog.html.sanitizer.unsafe package.496* @param {!Array<(string|!goog.html.sanitizer.HtmlSanitizerAttributePolicy)>}497* attrWhitelist The subset of attributes that the sanitizer will accept.498* Attributes can come in of two forms:499* - string: allow all values for this attribute on all tags.500* - HtmlSanitizerAttributePolicy: allows specifying a policy for a501* particular tag. The tagName can be "*", which means all tags. If no502* policy is passed, the default is to allow all values.503* The tag and attribute names are case-insensitive.504* Note that the policy for id, URLs, names etc is controlled separately505* (using withCustom* methods).506* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}507* @throws {Error} Thrown if an attempt is made to allow a non-whitelisted508* attribute.509*/510goog.html.sanitizer.HtmlSanitizer.Builder.prototype.onlyAllowAttributes =511function(attrWhitelist) {512var oldWhitelist = this.attributeWhitelist_;513this.attributeWhitelist_ = {};514goog.array.forEach(attrWhitelist, function(attr) {515if (goog.typeOf(attr) === 'string') {516attr = {tagName: '*', attributeName: attr.toUpperCase(), policy: null};517}518var handlerName = goog.html.sanitizer.HtmlSanitizer.attrIdentifier_(519attr.tagName, attr.attributeName);520if (!oldWhitelist[handlerName]) {521throw new Error('Only whitelisted attributes can be allowed.');522}523this.attributeWhitelist_[handlerName] = attr.policy ?524attr.policy :525/** @type {goog.html.sanitizer.HtmlSanitizerPolicy} */ (526goog.html.sanitizer.HtmlSanitizer.cleanUpAttribute_);527}, this);528return this;529};530531532/**533* Adds the original tag name in the data attribute 'original-tag' when unknown534* tags are sanitized to <span&rt;, so that caller can distinguish them from535* actual <span&rt; tags.536* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}537*/538goog.html.sanitizer.HtmlSanitizer.Builder.prototype.addOriginalTagNames =539function() {540this.shouldAddOriginalTagNames_ = true;541return this;542};543544545/**546* Sets a custom network URL policy.547* @param {!goog.html.sanitizer.HtmlSanitizerUrlPolicy}548* customNetworkReqUrlPolicy549* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}550*/551goog.html.sanitizer.HtmlSanitizer.Builder.prototype552.withCustomNetworkRequestUrlPolicy = function(customNetworkReqUrlPolicy) {553this.networkRequestUrlPolicy_ =554goog.html.sanitizer.HtmlSanitizer.sanitizeUrl_(customNetworkReqUrlPolicy);555return this;556};557558559/**560* Sets a custom non-network URL policy.561* @param {!goog.html.sanitizer.HtmlSanitizerUrlPolicy} customUrlPolicy562* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}563*/564goog.html.sanitizer.HtmlSanitizer.Builder.prototype.withCustomUrlPolicy =565function(customUrlPolicy) {566this.urlPolicy_ =567goog.html.sanitizer.HtmlSanitizer.sanitizeUrl_(customUrlPolicy);568return this;569};570571572/**573* Sets a custom name policy.574* @param {!goog.html.sanitizer.HtmlSanitizerPolicy} customNamePolicy575* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}576*/577goog.html.sanitizer.HtmlSanitizer.Builder.prototype.withCustomNamePolicy =578function(customNamePolicy) {579this.namePolicy_ = customNamePolicy;580return this;581};582583584/**585* Sets a custom token policy.586* @param {!goog.html.sanitizer.HtmlSanitizerPolicy} customTokenPolicy587* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}588*/589goog.html.sanitizer.HtmlSanitizer.Builder.prototype.withCustomTokenPolicy =590function(customTokenPolicy) {591this.tokenPolicy_ = customTokenPolicy;592return this;593};594595596/**597* Allows inline CSS styles.598* @return {!goog.html.sanitizer.HtmlSanitizer.Builder}599*/600goog.html.sanitizer.HtmlSanitizer.Builder.prototype.allowCssStyles =601function() {602this.sanitizeCssPolicy_ = goog.html.sanitizer.HtmlSanitizer.sanitizeCssBlock_;603return this;604};605606607/**608* Wraps a custom policy function with the sanitizer's default policy.609* @param {?goog.html.sanitizer.HtmlSanitizerPolicy} customPolicy The custom610* policy for the tag/attribute combination.611* @param {!goog.html.sanitizer.HtmlSanitizerPolicy} defaultPolicy The612* sanitizer's policy that is always called after the custom policy.613* @return {!goog.html.sanitizer.HtmlSanitizerPolicy}614* @private615*/616goog.html.sanitizer.HtmlSanitizer.wrapPolicy_ = function(617customPolicy, defaultPolicy) {618return /** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */ (function(619value, hints, ctx, policy) {620var result = customPolicy(value, hints, ctx, policy);621return result == null ? null : defaultPolicy(result, hints, ctx, policy);622});623};624625626/**627* Installs the sanitizer's default policy for a specific tag/attribute628* combination on the provided whitelist, but only if a policy already exists.629* @param {!Object<string, !goog.html.sanitizer.HtmlSanitizerPolicy>}630* whitelist The whitelist to modify.631* @param {!Object<string, boolean>} overrideList The set of attributes handlers632* that should not be wrapped with a default policy.633* @param {string} key The tag/attribute combination634* @param {!goog.html.sanitizer.HtmlSanitizerPolicy} defaultPolicy The635* sanitizer's policy.636* @private637*/638goog.html.sanitizer.HtmlSanitizer.installDefaultPolicy_ = function(639whitelist, overrideList, key, defaultPolicy) {640if (whitelist[key] && !overrideList[key]) {641whitelist[key] = goog.html.sanitizer.HtmlSanitizer.wrapPolicy_(642whitelist[key], defaultPolicy);643}644};645646647/**648* Builds and returns a goog.html.sanitizer.HtmlSanitizer object.649* @return {!goog.html.sanitizer.HtmlSanitizer}650*/651goog.html.sanitizer.HtmlSanitizer.Builder.prototype.build = function() {652return new goog.html.sanitizer.HtmlSanitizer(this);653};654655/**656* Installs the sanitization policies for the attributes.657* May only be called once.658* @private659*/660goog.html.sanitizer.HtmlSanitizer.Builder.prototype.installPolicies_ =661function() {662if (this.policiesInstalled_) {663throw new Error('HtmlSanitizer.Builder.build() can only be used once.');664}665666if (!this.allowFormTag_) {667this.tagBlacklist_['FORM'] = true;668}669670var installPolicy = goog.html.sanitizer.HtmlSanitizer.installDefaultPolicy_;671672// Binding all the non-trivial attribute sanitizers to the appropriate,673// potentially customizable, handling functions at build().674installPolicy(675this.attributeWhitelist_, this.attributeOverrideList_, '* USEMAP',676/** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */ (677goog.html.sanitizer.HtmlSanitizer.sanitizeUrlFragment_));678679var urlAttributes = ['* ACTION', '* CITE', '* HREF'];680goog.array.forEach(urlAttributes, function(attribute) {681installPolicy(682this.attributeWhitelist_, this.attributeOverrideList_, attribute,683this.urlPolicy_);684}, this);685686var networkUrlAttributes = [687// LONGDESC can result in a network request. See b/23381636.688'* LONGDESC', '* SRC', 'LINK HREF'689];690goog.array.forEach(networkUrlAttributes, function(attribute) {691installPolicy(692this.attributeWhitelist_, this.attributeOverrideList_, attribute,693this.networkRequestUrlPolicy_);694}, this);695696var nameAttributes = ['* FOR', '* HEADERS', '* NAME'];697goog.array.forEach(nameAttributes, function(attribute) {698installPolicy(699this.attributeWhitelist_, this.attributeOverrideList_, attribute,700/** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */ (goog.partial(701goog.html.sanitizer.HtmlSanitizer.sanitizeName_,702this.namePolicy_)));703}, this);704705installPolicy(706this.attributeWhitelist_, this.attributeOverrideList_, 'A TARGET',707/** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */ (goog.partial(708goog.html.sanitizer.HtmlSanitizer.allowedAttributeValues_,709['_blank', '_self'])));710711installPolicy(712this.attributeWhitelist_, this.attributeOverrideList_, '* CLASS',713/** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */ (goog.partial(714goog.html.sanitizer.HtmlSanitizer.sanitizeClasses_,715this.tokenPolicy_)));716717installPolicy(718this.attributeWhitelist_, this.attributeOverrideList_, '* ID',719/** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */ (goog.partial(720goog.html.sanitizer.HtmlSanitizer.sanitizeId_, this.tokenPolicy_)));721722if (this.sanitizeCssPolicy_) {723installPolicy(724this.attributeWhitelist_, this.attributeOverrideList_, '* STYLE',725/** @type {!goog.html.sanitizer.HtmlSanitizerPolicy} */ (goog.partial(726this.sanitizeCssPolicy_, this.networkRequestUrlPolicy_)));727} else {728installPolicy(729this.attributeWhitelist_, this.attributeOverrideList_, '* STYLE',730goog.functions.NULL);731}732this.policiesInstalled_ = true;733};734735736/**737* The default policy for URLs: allow any.738* @param {string} token The URL to undergo this policy.739* @return {?string}740* @private741*/742goog.html.sanitizer.HtmlSanitizer.defaultUrlPolicy_ =743goog.html.sanitizer.HtmlSanitizer.sanitizeUrl_(goog.html.SafeUrl.sanitize);744745746/**747* The default policy for URLs which cause network requests: drop all.748* @param {string} token The URL to undergo this policy.749* @return {null}750* @private751*/752goog.html.sanitizer.HtmlSanitizer.defaultNetworkRequestUrlPolicy_ =753goog.functions.NULL;754755756/**757* The default policy for attribute names: drop all.758* @param {string} token The name to undergo this policy.759* @return {?string}760* @private761*/762goog.html.sanitizer.HtmlSanitizer.defaultNamePolicy_ = goog.functions.NULL;763764765/**766* The default policy for other tokens (i.e. class names and IDs): drop all.767* @param {string} token The token to undergo this policy.768* @return {?string}769* @private770*/771goog.html.sanitizer.HtmlSanitizer.defaultTokenPolicy_ = goog.functions.NULL;772773774775/**776* Returns a key into the attribute handlers dictionary given a node name and777* an attribute name. If no node name is given, returns a key applying to all778* nodes.779* @param {?string} nodeName780* @param {string} attributeName781* @return {string} key into attribute handlers dict782* @private783*/784goog.html.sanitizer.HtmlSanitizer.attrIdentifier_ = function(785nodeName, attributeName) {786if (!nodeName) {787nodeName = '*';788}789return (nodeName + ' ' + attributeName).toUpperCase();790};791792793/**794* Sanitizes a block of CSS rules.795* @param {goog.html.sanitizer.HtmlSanitizerPolicy} policySanitizeUrl796* @param {string} attrValue797* @param {goog.html.sanitizer.HtmlSanitizerPolicyHints} policyHints798* @param {goog.html.sanitizer.HtmlSanitizerPolicyContext} policyContext799* @return {?string} sanitizedCss from the policyContext800* @private801*/802goog.html.sanitizer.HtmlSanitizer.sanitizeCssBlock_ = function(803policySanitizeUrl, attrValue, policyHints, policyContext) {804if (!policyContext.cssStyle) {805return null;806}807var naiveUriRewriter = function(uri, prop) {808policyHints.cssProperty = prop;809return goog.html.uncheckedconversions810.safeUrlFromStringKnownToSatisfyTypeContract(811goog.string.Const.from(812'HtmlSanitizerPolicy created with networkRequestUrlPolicy_ ' +813'when installing \'* STYLE\' handler.'),814policySanitizeUrl(uri, policyHints) || '');815};816var sanitizedStyle = goog.html.SafeStyle.unwrap(817goog.html.sanitizer.CssSanitizer.sanitizeInlineStyle(818policyContext.cssStyle, naiveUriRewriter));819return sanitizedStyle == '' ? null : sanitizedStyle;820};821822823/**824* Cleans up an attribute value that we don't particularly want to do anything825* to. At the moment we just trim the whitespace.826* @param {string} attrValue827* @param {goog.html.sanitizer.HtmlSanitizerPolicyHints} policyHints828* @return {string} sanitizedAttrValue829* @private830*/831goog.html.sanitizer.HtmlSanitizer.cleanUpAttribute_ = function(832attrValue, policyHints) {833return goog.string.trim(attrValue);834};835836837/**838* Allows a set of attribute values.839* @param {!Array<string>} allowedValues Set of allowed values lowercased.840* @param {string} attrValue841* @param {goog.html.sanitizer.HtmlSanitizerPolicyHints} policyHints842* @return {?string} sanitizedAttrValue843* @private844*/845goog.html.sanitizer.HtmlSanitizer.allowedAttributeValues_ = function(846allowedValues, attrValue, policyHints) {847var trimmed = goog.string.trim(attrValue);848return goog.array.contains(allowedValues, trimmed.toLowerCase()) ? trimmed :849null;850};851852853/**854* Sanitizes URL fragments.855* @param {string} urlFragment856* @param {goog.html.sanitizer.HtmlSanitizerPolicyHints} policyHints857* @return {?string} sanitizedAttrValue858* @private859*/860goog.html.sanitizer.HtmlSanitizer.sanitizeUrlFragment_ = function(861urlFragment, policyHints) {862var trimmed = goog.string.trim(urlFragment);863if (trimmed && trimmed.charAt(0) == '#') {864// We do not apply the name or token policy to Url Fragments by design.865return trimmed;866}867return null;868};869870871/**872* Runs an attribute name through a name policy.873* @param {goog.html.sanitizer.HtmlSanitizerPolicy} namePolicy874* @param {string} attrName875* @param {goog.html.sanitizer.HtmlSanitizerPolicyHints} policyHints876* @return {?string} sanitizedAttrValue877* @private878*/879goog.html.sanitizer.HtmlSanitizer.sanitizeName_ = function(880namePolicy, attrName, policyHints) {881var trimmed = goog.string.trim(attrName);882/* TODO(user): fail on names which contain illegal characters.883* NOTE(jasvir):884* There are two cases to be concerned about - escaped quotes in attribute885* values which is the responsibility of the serializer and illegal886* characters. The latter does violate the spec but I do not believe it has887* a security consequence.888*/889return namePolicy(trimmed, policyHints);890};891892893/**894* Ensures that the class prefix is present on all space-separated tokens895* (i.e. all class names).896* @param {goog.html.sanitizer.HtmlSanitizerPolicy} tokenPolicy897* @param {string} attrValue898* @param {goog.html.sanitizer.HtmlSanitizerPolicyHints} policyHints899* @return {?string} sanitizedAttrValue900* @private901*/902goog.html.sanitizer.HtmlSanitizer.sanitizeClasses_ = function(903tokenPolicy, attrValue, policyHints) {904// TODO(user): use a browser-supplied class list instead of a string.905var classes = attrValue.split(/(?:\s+)/);906var sanitizedClasses = [];907for (var i = 0; i < classes.length; i++) {908// TODO(user): skip classes which contain illegal characters.909var sanitizedClass = tokenPolicy(classes[i], policyHints);910if (sanitizedClass) {911sanitizedClasses.push(sanitizedClass);912}913}914return sanitizedClasses.length == 0 ? null : sanitizedClasses.join(' ');915};916917918/**919* Ensures that the id prefix is present.920* @param {goog.html.sanitizer.HtmlSanitizerPolicy} tokenPolicy921* @param {string} attrValue922* @param {goog.html.sanitizer.HtmlSanitizerPolicyHints} policyHints923* @return {?string} sanitizedAttrValue924* @private925*/926goog.html.sanitizer.HtmlSanitizer.sanitizeId_ = function(927tokenPolicy, attrValue, policyHints) {928var trimmed = goog.string.trim(attrValue);929// TODO(user): fail on IDs which contain illegal characters.930return tokenPolicy(trimmed, policyHints);931};932933934/**935* Parses a string of unsanitized HTML and provides an iterator over the936* resulting DOM tree nodes. This DOM parsing must be wholly inert (that is,937* it does not cause execution of any active content or cause the browser to938* issue any requests). The returned iterator is guaranteed to iterate over a939* parent element before iterating over any of its children.940* @param {string} unsanitizedHtml941* @return {!TreeWalker} Dom tree iterator942* @private943*/944goog.html.sanitizer.HtmlSanitizer.getDomTreeWalker_ = function(945unsanitizedHtml) {946var iteratorParent;947// Use a <template> element if possible.948var templateElement = document.createElement('template');949if ('content' in templateElement) {950templateElement.innerHTML = unsanitizedHtml;951iteratorParent = templateElement.content;952} else {953// In browsers where <template> is not implemented, use an HTMLDocument.954var doc = document.implementation.createHTMLDocument('x');955iteratorParent = doc.body;956doc.body.innerHTML = unsanitizedHtml;957}958return document.createTreeWalker(959iteratorParent, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null,960false);961};962963// TODO(pelizzi): both getAttribute* functions accept a Node but are defined on964// Element. Investigate.965966/**967* Returns an element's attributes without falling prey to things like968* <form><input name="attributes">969* <input name="attributes"></form>.970* @param {!Node} node971* @return {?NamedNodeMap}972* @private973*/974goog.html.sanitizer.HtmlSanitizer.getAttributes_ = function(node) {975var attrDescriptor =976goog.html.sanitizer.HTML_SANITIZER_PROPERTY_DESCRIPTORS_['attributes'];977if (attrDescriptor && attrDescriptor.get) {978return attrDescriptor.get.apply(node);979} else {980return node.attributes instanceof NamedNodeMap ? node.attributes : null;981}982};983984/**985* Returns a specific attribute from an element without falling prey to986* clobbering.987* @param {!Node} node988* @param {string} attrName989* @return {string}990* @private991*/992goog.html.sanitizer.HtmlSanitizer.getAttribute_ = function(node, attrName) {993var protoFn = Element.prototype.getAttribute;994if (protoFn && node instanceof Element) {995var ret = protoFn.call(/** @type {!Element} */ (node), attrName);996return ret || ''; // FireFox returns null997} else {998return '';999}1000};10011002/**1003* Sets an element's attributes without falling prey to things like1004* <form><input name="attributes">1005* <input name="attributes"></form>.1006* @param {!Node} node1007* @param {string} name1008* @param {string} value1009* @private1010*/1011goog.html.sanitizer.HtmlSanitizer.setAttribute_ = function(node, name, value) {1012var attrDescriptor =1013goog.html.sanitizer.HTML_SANITIZER_PROPERTY_DESCRIPTORS_['setAttribute'];1014if (attrDescriptor && attrDescriptor.value) {1015try {1016attrDescriptor.value.call(node, name, value);1017} catch (e) {1018// IE throws an exception if the src attribute contains HTTP credentials.1019// However the attribute gets set anyway.1020if (e.message.indexOf('A security problem occurred') == -1) {1021throw e;1022}1023}1024}1025};102610271028/**1029* Returns a node's innerHTML property value without falling prey to clobbering.1030* @param {!Node} node1031* @return {string}1032* @private1033*/1034goog.html.sanitizer.HtmlSanitizer.getInnerHTML_ = function(node) {1035var descriptor =1036goog.html.sanitizer.HTML_SANITIZER_PROPERTY_DESCRIPTORS_['innerHTML'];1037if (descriptor && descriptor.get) {1038return descriptor.get.apply(node);1039} else {1040return (typeof node.innerHTML == 'string') ? node.innerHTML : '';1041}1042};104310441045/**1046* Returns an element's style without falling prey to things like1047* <form><input name="style">1048* <input name="style"></form>.1049* @param {!Node} node1050* @return {?CSSStyleDeclaration}1051* @private1052*/1053goog.html.sanitizer.HtmlSanitizer.getStyle_ = function(node) {1054var styleDescriptor =1055goog.html.sanitizer.HTML_SANITIZER_PROPERTY_DESCRIPTORS_['style'];1056if (node instanceof HTMLElement && styleDescriptor && styleDescriptor.get) {1057return styleDescriptor.get.apply(node);1058} else {1059return node.style instanceof CSSStyleDeclaration ? node.style : null;1060}1061};106210631064/**1065* Returns a node's nodeName without falling prey to things like1066* <form><input name="nodeName"></form>.1067* @param {!Node} node1068* @return {string}1069* @private1070*/1071goog.html.sanitizer.HtmlSanitizer.getNodeName_ = function(node) {1072var nodeNameDescriptor =1073goog.html.sanitizer.HTML_SANITIZER_PROPERTY_DESCRIPTORS_['nodeName'];1074if (nodeNameDescriptor && nodeNameDescriptor.get) {1075return nodeNameDescriptor.get.apply(node);1076} else {1077return (typeof node.nodeName == 'string') ? node.nodeName : 'unknown';1078}1079};108010811082/**1083* Returns a node's parentNode without falling prey to things like1084* <form><input name="parentNode"></form>.1085* @param {?Node} node1086* @return {?Node}1087* @private1088*/1089goog.html.sanitizer.HtmlSanitizer.getParentNode_ = function(node) {1090if (node == null) {1091return null;1092}1093var parentNodeDescriptor =1094goog.html.sanitizer.HTML_SANITIZER_PROPERTY_DESCRIPTORS_['parentNode'];1095if (parentNodeDescriptor && parentNodeDescriptor.get) {1096return parentNodeDescriptor.get.apply(node);1097} else {1098// We need to ensure that parentNode is returning the actual parent node1099// and not a child node that happens to have a name of "parentNode".1100// We check that the node returned by parentNode is itself not named1101// "parentNode" - this could happen legitimately but on IE we have no better1102// means of avoiding the pitfall.1103var parentNode = node.parentNode;1104if (parentNode && parentNode.name && typeof parentNode.name == 'string' &&1105parentNode.name.toLowerCase() == 'parentnode') {1106return null;1107} else {1108return parentNode;1109}1110}1111};111211131114/**1115* Returns the value of node.childNodes without falling prey to clobbering.1116* @param {!Node} node1117* @return {?NodeList}1118* @private1119*/1120goog.html.sanitizer.HtmlSanitizer.getChildNodes_ = function(node) {1121var descriptor =1122goog.html.sanitizer.HTML_SANITIZER_PROPERTY_DESCRIPTORS_['childNodes'];1123if (goog.dom.isElement(node) && descriptor && descriptor.get) {1124return descriptor.get.apply(node);1125} else {1126return node.childNodes instanceof NodeList ? node.childNodes : null;1127}1128};112911301131/**1132* Parses the DOM tree of a given HTML string, then walks the tree. For each1133* element, it creates a new sanitized version, applies sanitized attributes,1134* and returns a SafeHtml object representing the sanitized tree.1135* @param {?string} unsanitizedHtml1136* @return {!goog.html.SafeHtml} Sanitized HTML1137* @final1138*/1139goog.html.sanitizer.HtmlSanitizer.prototype.sanitize = function(1140unsanitizedHtml) {1141var sanitizedParent = this.sanitizeToDomNode(unsanitizedHtml);1142var sanitizedString = new XMLSerializer().serializeToString(sanitizedParent);11431144// Remove the outer span added in sanitizeToDomNode. We could create an1145// element from it and then pull out the innerHTML, but this is more1146// performant.1147if (goog.string.startsWith(sanitizedString, '<span')) {1148if (goog.string.endsWith(sanitizedString, '</span>')) {1149sanitizedString = sanitizedString.slice(1150sanitizedString.indexOf('>') + 1, -1 * ('</span>'.length));1151} else if (goog.string.endsWith(sanitizedString, '/>')) {1152sanitizedString = '';1153}1154}11551156return goog.html.uncheckedconversions1157.safeHtmlFromStringKnownToSatisfyTypeContract(1158goog.string.Const.from('Output of HTML sanitizer'), sanitizedString);1159};116011611162/**1163* Parses the DOM tree of a given HTML string, then walks the tree. For each1164* element, it creates a new sanitized version, applies sanitized attributes,1165* and returns a span element containing the sanitized content.1166* @param {?string} unsanitizedHtml1167* @return {!HTMLSpanElement} Sanitized HTML1168* @final1169*/1170goog.html.sanitizer.HtmlSanitizer.prototype.sanitizeToDomNode = function(1171unsanitizedHtml) {1172var sanitizedParent =1173/** @type {!HTMLSpanElement} */ (document.createElement('span'));11741175if (!goog.html.sanitizer.HTML_SANITIZER_SUPPORTED_ || !unsanitizedHtml) {1176// TODO(danesh): IE9 or earlier versions don't provide an easy way to1177// parse HTML inertly. Handle in a way other than an empty span perhaps.1178return sanitizedParent;1179}11801181// Get the treeWalker initialized.1182try {1183var treeWalker =1184goog.html.sanitizer.HtmlSanitizer.getDomTreeWalker_(unsanitizedHtml);1185} catch (e) {1186return sanitizedParent;1187}11881189// Used in order to find the correct parent node in the sanitizedParent.1190var elementMap = {};1191// Used in order to give a unique identifier to each node for lookups.1192var elemNum = 0;1193// Used for iteration.1194var dirtyNode;1195while (dirtyNode = treeWalker.nextNode()) {1196elemNum++;11971198// Get a clean (sanitized) version of the dirty node.1199var cleanNode = this.sanitizeElement_(dirtyNode);1200if (cleanNode.nodeType != goog.dom.NodeType.TEXT) {1201this.sanitizeAttrs_(dirtyNode, cleanNode);1202elementMap[elemNum] = cleanNode;1203goog.html.sanitizer.HtmlSanitizer.setAttribute_(1204dirtyNode, goog.html.sanitizer.HTML_SANITIZER_BOOKKEEPING_ATTR_NAME_,1205String(elemNum));1206}12071208// TODO(pelizzi): [IMPROVEMENT] type-checking against clobbering (e.g.1209// ClobberedNode wrapper). Closure can unwrap these at compile time, see1210// ClosureOptimizePrimitives.java, jakubvrana has created one for1211// goog.dom.Tag. Alternatively, create two actual wrappers that expose1212// clobber-safe functions, getters and setters for Node and Element.12131214// TODO(pelizzi): [IMPROVEMENT] consider switching from elementMap[elemNum]1215// to a WeakMap for browsers that support it (e.g. use a ElementWeakMap that1216// falls back to using data attributes).1217// @type {ElementWeakMap<ClobberedNode, Node>}12181219// TODO(pelizzi): [IMPROVEMENT] add an API to sanitize *from* DOM nodes so1220// that we don't have to use innerHTML on template recursion but instead we1221// can use importNode. The API could also be public as it is still a way to1222// make a document fragment conform to a policy, somewhat useful.12231224// Template tag contents require special handling as they are not traversed1225// by the treewalker.1226var dirtyNodeName =1227goog.html.sanitizer.HtmlSanitizer.getNodeName_(dirtyNode);1228if (goog.html.sanitizer.HTML_SANITIZER_TEMPLATE_SUPPORTED &&1229dirtyNodeName.toLowerCase() === 'template' &&1230!cleanNode.hasAttribute(1231goog.html.sanitizer.HTML_SANITIZER_BLACKLISTED_TAG_)) {1232this.processTemplateContents_(dirtyNode, cleanNode);1233}12341235// Finds the parent to which cleanNode should be appended.1236var dirtyParent =1237goog.html.sanitizer.HtmlSanitizer.getParentNode_(dirtyNode);1238var isSanitizedParent = false;1239if (goog.isNull(dirtyParent)) {1240isSanitizedParent = true;1241} else if (1242goog.html.sanitizer.HtmlSanitizer.getNodeName_(dirtyParent)1243.toLowerCase() == 'body' ||1244dirtyParent.nodeType == goog.dom.NodeType.DOCUMENT_FRAGMENT) {1245var dirtyGrandParent =1246goog.html.sanitizer.HtmlSanitizer.getParentNode_(dirtyParent);1247// The following checks if target is an immediate child of the inert1248// parent template element1249if (dirtyParent.nodeType == goog.dom.NodeType.DOCUMENT_FRAGMENT &&1250goog.isNull(dirtyGrandParent)) {1251isSanitizedParent = true;1252} else if (1253goog.html.sanitizer.HtmlSanitizer.getNodeName_(dirtyParent)1254.toLowerCase() == 'body') {1255// The following checks if target is an immediate child of the inert1256// parent HtmlDocument1257var dirtyGrtGrandParent =1258goog.html.sanitizer.HtmlSanitizer.getParentNode_(dirtyGrandParent);1259if (goog.isNull(goog.html.sanitizer.HtmlSanitizer.getParentNode_(1260dirtyGrtGrandParent))) {1261isSanitizedParent = true;1262}1263}1264}1265var target;1266if (isSanitizedParent || !dirtyParent) {1267target = sanitizedParent;1268} else {1269target = elementMap[goog.html.sanitizer.HtmlSanitizer.getAttribute_(1270dirtyParent,1271goog.html.sanitizer.HTML_SANITIZER_BOOKKEEPING_ATTR_NAME_)];1272}1273if (target.content) {1274target = target.content;1275}1276// Do not attach blacklisted tags that have been sanitized into templates.1277if (!goog.dom.isElement(cleanNode) ||1278!cleanNode.hasAttribute(1279goog.html.sanitizer.HTML_SANITIZER_BLACKLISTED_TAG_)) {1280target.appendChild(cleanNode);1281}1282}12831284return sanitizedParent;1285};128612871288/**1289* Returns a sanitized version of an element, with no children or user-provided1290* attributes.1291* @param {!Node} dirtyNode1292* @return {!Node}1293* @private1294*/1295goog.html.sanitizer.HtmlSanitizer.prototype.sanitizeElement_ = function(1296dirtyNode) {1297// Text nodes don't need to be sanitized.1298if (dirtyNode.nodeType == goog.dom.NodeType.TEXT) {1299return document.createTextNode(dirtyNode.data);1300}1301// Non text nodes get an empty node based on black/white lists.1302var elemName =1303goog.html.sanitizer.HtmlSanitizer.getNodeName_(dirtyNode).toUpperCase();1304var sanitized = false;1305var blacklisted = false;1306var cleanElemName;1307if (elemName in goog.html.sanitizer.TagBlacklist ||1308elemName in this.tagBlacklist_) {1309// If it's in the inert blacklist, replace with template (and then add a1310// special data attribute to distinguish it from real template tags).1311// Note that this node will not be added to the final output, i.e. the1312// template tag is only an internal representation, and eventually will be1313// deleted.1314cleanElemName = 'template';1315blacklisted = true;1316} else if (this.tagWhitelist_[elemName]) {1317// If it's in the whitelist, keep as is.1318cleanElemName = elemName;1319} else {1320// If it's not in any list, replace with span. If the relevant builder1321// option is enabled, they will bear the original tag name in a data1322// attribute.1323cleanElemName = 'span';1324sanitized = true;1325}1326var cleanElem = document.createElement(cleanElemName);1327if (this.shouldAddOriginalTagNames_ && sanitized) {1328goog.html.sanitizer.HtmlSanitizer.setAttribute_(1329cleanElem, goog.html.sanitizer.HTML_SANITIZER_SANITIZED_ATTR_NAME_,1330elemName.toLowerCase());1331}1332if (blacklisted) {1333goog.html.sanitizer.HtmlSanitizer.setAttribute_(1334cleanElem, goog.html.sanitizer.HTML_SANITIZER_BLACKLISTED_TAG_, '');1335}1336return cleanElem;1337};133813391340/**1341* Applies sanitized versions of attributes from a dirtyNode to a corresponding1342* cleanNode.1343* @param {!Node} dirtyNode1344* @param {!Node} cleanNode1345* @return {!Node} cleanNode with sanitized attributes1346* @private1347*/1348goog.html.sanitizer.HtmlSanitizer.prototype.sanitizeAttrs_ = function(1349dirtyNode, cleanNode) {1350var attributes = goog.html.sanitizer.HtmlSanitizer.getAttributes_(dirtyNode);1351if (attributes == null) {1352return cleanNode;1353}1354for (var i = 0, attribute; attribute = attributes[i]; i++) {1355if (attribute.specified) {1356var cleanValue = this.sanitizeAttribute_(dirtyNode, attribute);1357if (!goog.isNull(cleanValue)) {1358goog.html.sanitizer.HtmlSanitizer.setAttribute_(1359cleanNode, attribute.name, cleanValue);1360}1361}1362}1363return cleanNode;1364};136513661367/**1368* Sanitizes an attribute value by looking up an attribute handler for the given1369* node and attribute names.1370* @param {!Node} dirtyNode1371* @param {!Attr} attribute1372* @return {?string} sanitizedAttrValue1373* @private1374*/1375goog.html.sanitizer.HtmlSanitizer.prototype.sanitizeAttribute_ = function(1376dirtyNode, attribute) {1377var attributeName = attribute.name;1378if (goog.string.startsWith(1379goog.html.sanitizer.HTML_SANITIZER_BOOKKEEPING_PREFIX_,1380attributeName)) {1381return null;1382}13831384var nodeName = goog.html.sanitizer.HtmlSanitizer.getNodeName_(dirtyNode);1385var unsanitizedAttrValue = attribute.value;13861387// Create policy hints object1388var policyHints = {1389tagName: goog.string.trim(nodeName).toLowerCase(),1390attributeName: goog.string.trim(attributeName).toLowerCase()1391};1392var policyContext = goog.html.sanitizer.HtmlSanitizer.getContext_(1393policyHints.attributeName, dirtyNode);13941395// Prefer attribute handler for this specific tag.1396var tagHandlerIndex = goog.html.sanitizer.HtmlSanitizer.attrIdentifier_(1397nodeName, attributeName);1398if (tagHandlerIndex in this.attributeHandlers_) {1399var handler = this.attributeHandlers_[tagHandlerIndex];1400return handler(unsanitizedAttrValue, policyHints, policyContext);1401}1402// Fall back on attribute handler for wildcard tag.1403var genericHandlerIndex =1404goog.html.sanitizer.HtmlSanitizer.attrIdentifier_(null, attributeName);1405if (genericHandlerIndex in this.attributeHandlers_) {1406var handler = this.attributeHandlers_[genericHandlerIndex];1407return handler(unsanitizedAttrValue, policyHints, policyContext);1408}1409return null;1410};141114121413/**1414* Processes the contents of a template tag. These are not traversed through the1415* treewalker because they belong to a separate document, and thus require1416* special handling.1417* <p>1418* If the relevant builder option is enabled and the template tag is allowed,1419* this method copies the contents over to the output DOM tree without1420* sanitization, otherwise the template contents are sanitized recursively.1421* @param {!Node} dirtyNode1422* @param {!Node} cleanNode1423* @private1424*/1425goog.html.sanitizer.HtmlSanitizer.prototype.processTemplateContents_ = function(1426dirtyNode, cleanNode) {1427// If the template element was sanitized into a span tag, do not insert1428// unsanitized tags!1429if (this.shouldSanitizeTemplateContents_ ||1430cleanNode.nodeName.toLowerCase() !== 'template') {1431var dirtyNodeHTML =1432goog.html.sanitizer.HtmlSanitizer.getInnerHTML_(dirtyNode);1433var templateSpan = this.sanitizeToDomNode(dirtyNodeHTML);1434// appendChild with a forEach instead of an innertHTML as the latter is1435// slower.1436goog.array.forEach(templateSpan.childNodes, function(node) {1437cleanNode.appendChild(node);1438});1439} else {1440var templateDoc =1441/** @type {!HTMLTemplateElement} */ (cleanNode).content.ownerDocument;1442var dirtyCopy =1443goog.asserts.assert(templateDoc.importNode(dirtyNode, true));1444var dirtyCopyChildren =1445goog.html.sanitizer.HtmlSanitizer.getChildNodes_(dirtyCopy);1446// appendChild with a forEach instead of an innerHTML as the latter is1447// slower and vulnerable to mXSS.1448goog.array.forEach(dirtyCopyChildren, function(node) {1449cleanNode.appendChild(node);1450});1451}1452};145314541455/**1456* Retrieves a HtmlSanitizerPolicyContext from a dirty node given an attribute1457* name.1458* @param {string} attributeName1459* @param {!Node} dirtyNode1460* @return {!goog.html.sanitizer.HtmlSanitizerPolicyContext}1461* @private1462*/1463goog.html.sanitizer.HtmlSanitizer.getContext_ = function(1464attributeName, dirtyNode) {1465var policyContext = {cssStyle: undefined};1466if (attributeName == 'style') {1467policyContext.cssStyle =1468goog.html.sanitizer.HtmlSanitizer.getStyle_(dirtyNode);1469}1470return policyContext;1471};147214731474/**1475* Sanitizes a HTML string using a sanitizer with default options.1476* @param {string} unsanitizedHtml1477* @return {!goog.html.SafeHtml} sanitizedHtml1478*/1479goog.html.sanitizer.HtmlSanitizer.sanitize = function(unsanitizedHtml) {1480var sanitizer = new goog.html.sanitizer.HtmlSanitizer.Builder().build();1481return sanitizer.sanitize(unsanitizedHtml);1482};148314841485