Path: blob/trunk/third_party/closure/goog/html/safehtml.js
2868 views
// Copyright 2013 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 The SafeHtml type and its builders.17*18* TODO(xtof): Link to document stating type contract.19*/2021goog.provide('goog.html.SafeHtml');2223goog.require('goog.array');24goog.require('goog.asserts');25goog.require('goog.dom.TagName');26goog.require('goog.dom.tags');27goog.require('goog.html.SafeScript');28goog.require('goog.html.SafeStyle');29goog.require('goog.html.SafeStyleSheet');30goog.require('goog.html.SafeUrl');31goog.require('goog.html.TrustedResourceUrl');32goog.require('goog.i18n.bidi.Dir');33goog.require('goog.i18n.bidi.DirectionalString');34goog.require('goog.labs.userAgent.browser');35goog.require('goog.object');36goog.require('goog.string');37goog.require('goog.string.Const');38goog.require('goog.string.TypedString');39404142/**43* A string that is safe to use in HTML context in DOM APIs and HTML documents.44*45* A SafeHtml is a string-like object that carries the security type contract46* that its value as a string will not cause untrusted script execution when47* evaluated as HTML in a browser.48*49* Values of this type are guaranteed to be safe to use in HTML contexts,50* such as, assignment to the innerHTML DOM property, or interpolation into51* a HTML template in HTML PC_DATA context, in the sense that the use will not52* result in a Cross-Site-Scripting vulnerability.53*54* Instances of this type must be created via the factory methods55* ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}),56* etc and not by invoking its constructor. The constructor intentionally57* takes no parameters and the type is immutable; hence only a default instance58* corresponding to the empty string can be obtained via constructor invocation.59*60* @see goog.html.SafeHtml#create61* @see goog.html.SafeHtml#htmlEscape62* @constructor63* @final64* @struct65* @implements {goog.i18n.bidi.DirectionalString}66* @implements {goog.string.TypedString}67*/68goog.html.SafeHtml = function() {69/**70* The contained value of this SafeHtml. The field has a purposely ugly71* name to make (non-compiled) code that attempts to directly access this72* field stand out.73* @private {string}74*/75this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';7677/**78* A type marker used to implement additional run-time type checking.79* @see goog.html.SafeHtml#unwrap80* @const {!Object}81* @private82*/83this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =84goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;8586/**87* This SafeHtml's directionality, or null if unknown.88* @private {?goog.i18n.bidi.Dir}89*/90this.dir_ = null;91};929394/**95* @override96* @const97*/98goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true;99100101/** @override */102goog.html.SafeHtml.prototype.getDirection = function() {103return this.dir_;104};105106107/**108* @override109* @const110*/111goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true;112113114/**115* Returns this SafeHtml's value as string.116*117* IMPORTANT: In code where it is security relevant that an object's type is118* indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.unwrap} instead of119* this method. If in doubt, assume that it's security relevant. In particular,120* note that goog.html functions which return a goog.html type do not guarantee121* that the returned instance is of the right type. For example:122*123* <pre>124* var fakeSafeHtml = new String('fake');125* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;126* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);127* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by128* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml129* // instanceof goog.html.SafeHtml.130* </pre>131*132* @see goog.html.SafeHtml#unwrap133* @override134*/135goog.html.SafeHtml.prototype.getTypedStringValue = function() {136return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;137};138139140if (goog.DEBUG) {141/**142* Returns a debug string-representation of this value.143*144* To obtain the actual string value wrapped in a SafeHtml, use145* {@code goog.html.SafeHtml.unwrap}.146*147* @see goog.html.SafeHtml#unwrap148* @override149*/150goog.html.SafeHtml.prototype.toString = function() {151return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +152'}';153};154}155156157/**158* Performs a runtime check that the provided object is indeed a SafeHtml159* object, and returns its value.160* @param {!goog.html.SafeHtml} safeHtml The object to extract from.161* @return {string} The SafeHtml object's contained string, unless the run-time162* type check fails. In that case, {@code unwrap} returns an innocuous163* string, or, if assertions are enabled, throws164* {@code goog.asserts.AssertionError}.165*/166goog.html.SafeHtml.unwrap = function(safeHtml) {167// Perform additional run-time type-checking to ensure that safeHtml is indeed168// an instance of the expected type. This provides some additional protection169// against security bugs due to application code that disables type checks.170// Specifically, the following checks are performed:171// 1. The object is an instance of the expected type.172// 2. The object is not an instance of a subclass.173// 3. The object carries a type marker for the expected type. "Faking" an174// object requires a reference to the type marker, which has names intended175// to stand out in code reviews.176if (safeHtml instanceof goog.html.SafeHtml &&177safeHtml.constructor === goog.html.SafeHtml &&178safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===179goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {180return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_;181} else {182goog.asserts.fail('expected object of type SafeHtml, got \'' +183safeHtml + '\' of type ' + goog.typeOf(safeHtml));184return 'type_error:SafeHtml';185}186};187188189/**190* Shorthand for union of types that can sensibly be converted to strings191* or might already be SafeHtml (as SafeHtml is a goog.string.TypedString).192* @private193* @typedef {string|number|boolean|!goog.string.TypedString|194* !goog.i18n.bidi.DirectionalString}195*/196goog.html.SafeHtml.TextOrHtml_;197198199/**200* Returns HTML-escaped text as a SafeHtml object.201*202* If text is of a type that implements203* {@code goog.i18n.bidi.DirectionalString}, the directionality of the new204* {@code SafeHtml} object is set to {@code text}'s directionality, if known.205* Otherwise, the directionality of the resulting SafeHtml is unknown (i.e.,206* {@code null}).207*208* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If209* the parameter is of type SafeHtml it is returned directly (no escaping210* is done).211* @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.212*/213goog.html.SafeHtml.htmlEscape = function(textOrHtml) {214if (textOrHtml instanceof goog.html.SafeHtml) {215return textOrHtml;216}217var dir = null;218if (textOrHtml.implementsGoogI18nBidiDirectionalString) {219dir = textOrHtml.getDirection();220}221var textAsString;222if (textOrHtml.implementsGoogStringTypedString) {223textAsString = textOrHtml.getTypedStringValue();224} else {225textAsString = String(textOrHtml);226}227return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(228goog.string.htmlEscape(textAsString), dir);229};230231232/**233* Returns HTML-escaped text as a SafeHtml object, with newlines changed to234* <br>.235* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If236* the parameter is of type SafeHtml it is returned directly (no escaping237* is done).238* @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.239*/240goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) {241if (textOrHtml instanceof goog.html.SafeHtml) {242return textOrHtml;243}244var html = goog.html.SafeHtml.htmlEscape(textOrHtml);245return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(246goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)),247html.getDirection());248};249250251/**252* Returns HTML-escaped text as a SafeHtml object, with newlines changed to253* <br> and escaping whitespace to preserve spatial formatting. Character254* entity #160 is used to make it safer for XML.255* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If256* the parameter is of type SafeHtml it is returned directly (no escaping257* is done).258* @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.259*/260goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function(261textOrHtml) {262if (textOrHtml instanceof goog.html.SafeHtml) {263return textOrHtml;264}265var html = goog.html.SafeHtml.htmlEscape(textOrHtml);266return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(267goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)),268html.getDirection());269};270271272/**273* Coerces an arbitrary object into a SafeHtml object.274*275* If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same276* object is returned. Otherwise, {@code textOrHtml} is coerced to string, and277* HTML-escaped. If {@code textOrHtml} is of a type that implements278* {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is279* preserved.280*281* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to282* coerce.283* @return {!goog.html.SafeHtml} The resulting SafeHtml object.284* @deprecated Use goog.html.SafeHtml.htmlEscape.285*/286goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape;287288289/**290* @const291* @private292*/293goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/;294295296/**297* Set of attributes containing URL as defined at298* http://www.w3.org/TR/html5/index.html#attributes-1.299* @private @const {!Object<string,boolean>}300*/301goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet(302'action', 'cite', 'data', 'formaction', 'href', 'manifest', 'poster',303'src');304305306/**307* Tags which are unsupported via create(). They might be supported via a308* tag-specific create method. These are tags which might require a309* TrustedResourceUrl in one of their attributes or a restricted type for310* their content.311* @private @const {!Object<string,boolean>}312*/313goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet(314goog.dom.TagName.APPLET, goog.dom.TagName.BASE, goog.dom.TagName.EMBED,315goog.dom.TagName.IFRAME, goog.dom.TagName.LINK, goog.dom.TagName.MATH,316goog.dom.TagName.META, goog.dom.TagName.OBJECT, goog.dom.TagName.SCRIPT,317goog.dom.TagName.STYLE, goog.dom.TagName.SVG, goog.dom.TagName.TEMPLATE);318319320/**321* @typedef {string|number|goog.string.TypedString|322* goog.html.SafeStyle.PropertyMap|undefined}323*/324goog.html.SafeHtml.AttributeValue;325326327/**328* Creates a SafeHtml content consisting of a tag with optional attributes and329* optional content.330*331* For convenience tag names and attribute names are accepted as regular332* strings, instead of goog.string.Const. Nevertheless, you should not pass333* user-controlled values to these parameters. Note that these parameters are334* syntactically validated at runtime, and invalid values will result in335* an exception.336*337* Example usage:338*339* goog.html.SafeHtml.create('br');340* goog.html.SafeHtml.create('div', {'class': 'a'});341* goog.html.SafeHtml.create('p', {}, 'a');342* goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br'));343*344* goog.html.SafeHtml.create('span', {345* 'style': {'margin': '0'}346* });347*348* To guarantee SafeHtml's type contract is upheld there are restrictions on349* attribute values and tag names.350*351* - For attributes which contain script code (on*), a goog.string.Const is352* required.353* - For attributes which contain style (style), a goog.html.SafeStyle or a354* goog.html.SafeStyle.PropertyMap is required.355* - For attributes which are interpreted as URLs (e.g. src, href) a356* goog.html.SafeUrl, goog.string.Const or string is required. If a string357* is passed, it will be sanitized with SafeUrl.sanitize().358* - For tags which can load code or set security relevant page metadata,359* more specific goog.html.SafeHtml.create*() functions must be used. Tags360* which are not supported by this function are applet, base, embed, iframe,361* link, math, object, script, style, svg, and template.362*363* @param {!goog.dom.TagName|string} tagName The name of the tag. Only tag names364* consisting of [a-zA-Z0-9-] are allowed. Tag names documented above are365* disallowed.366* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes367* Mapping from attribute names to their values. Only attribute names368* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes369* the attribute to be omitted.370* @param {!goog.html.SafeHtml.TextOrHtml_|371* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to372* HTML-escape and put inside the tag. This must be empty for void tags373* like <br>. Array elements are concatenated.374* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.375* @throws {Error} If invalid tag name, attribute name, or attribute value is376* provided.377* @throws {goog.asserts.AssertionError} If content for void tag is provided.378*/379goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) {380goog.html.SafeHtml.verifyTagName(String(tagName));381return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(382String(tagName), opt_attributes, opt_content);383};384385386/**387* Verifies if the tag name is valid and if it doesn't change the context.388* E.g. STRONG is fine but SCRIPT throws because it changes context. See389* goog.html.SafeHtml.create for an explanation of allowed tags.390* @param {string} tagName391* @throws {Error} If invalid tag name is provided.392* @package393*/394goog.html.SafeHtml.verifyTagName = function(tagName) {395if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) {396throw Error('Invalid tag name <' + tagName + '>.');397}398if (tagName.toUpperCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) {399throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.');400}401};402403404/**405* Creates a SafeHtml representing an iframe tag.406*407* This by default restricts the iframe as much as possible by setting the408* sandbox attribute to the empty string. If the iframe requires less409* restrictions, set the sandbox attribute as tight as possible, but do not rely410* on the sandbox as a security feature because it is not supported by older411* browsers. If a sandbox is essential to security (e.g. for third-party412* frames), use createSandboxIframe which checks for browser support.413*414* @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox415*416* @param {?goog.html.TrustedResourceUrl=} opt_src The value of the src417* attribute. If null or undefined src will not be set.418* @param {?goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute.419* If null or undefined srcdoc will not be set.420* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes421* Mapping from attribute names to their values. Only attribute names422* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes423* the attribute to be omitted.424* @param {!goog.html.SafeHtml.TextOrHtml_|425* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to426* HTML-escape and put inside the tag. Array elements are concatenated.427* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.428* @throws {Error} If invalid tag name, attribute name, or attribute value is429* provided. If opt_attributes contains the src or srcdoc attributes.430*/431goog.html.SafeHtml.createIframe = function(432opt_src, opt_srcdoc, opt_attributes, opt_content) {433if (opt_src) {434// Check whether this is really TrustedResourceUrl.435goog.html.TrustedResourceUrl.unwrap(opt_src);436}437438var fixedAttributes = {};439fixedAttributes['src'] = opt_src || null;440fixedAttributes['srcdoc'] =441opt_srcdoc && goog.html.SafeHtml.unwrap(opt_srcdoc);442var defaultAttributes = {'sandbox': ''};443var attributes = goog.html.SafeHtml.combineAttributes(444fixedAttributes, defaultAttributes, opt_attributes);445return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(446'iframe', attributes, opt_content);447};448449450/**451* Creates a SafeHtml representing a sandboxed iframe tag.452*453* The sandbox attribute is enforced in its most restrictive mode, an empty454* string. Consequently, the security requirements for the src and srcdoc455* attributes are relaxed compared to SafeHtml.createIframe. This function456* will throw on browsers that do not support the sandbox attribute, as457* determined by SafeHtml.canUseSandboxIframe.458*459* The SafeHtml returned by this function can trigger downloads with no460* user interaction on Chrome (though only a few, further attempts are blocked).461* Firefox and IE will block all downloads from the sandbox.462*463* @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox464* @see https://lists.w3.org/Archives/Public/public-whatwg-archive/2013Feb/0112.html465*466* @param {string|!goog.html.SafeUrl=} opt_src The value of the src467* attribute. If null or undefined src will not be set.468* @param {string=} opt_srcdoc The value of the srcdoc attribute.469* If null or undefined srcdoc will not be set. Will not be sanitized.470* @param {!Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes471* Mapping from attribute names to their values. Only attribute names472* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes473* the attribute to be omitted.474* @param {!goog.html.SafeHtml.TextOrHtml_|475* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to476* HTML-escape and put inside the tag. Array elements are concatenated.477* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.478* @throws {Error} If invalid tag name, attribute name, or attribute value is479* provided. If opt_attributes contains the src, srcdoc or sandbox480* attributes. If browser does not support the sandbox attribute on iframe.481*/482goog.html.SafeHtml.createSandboxIframe = function(483opt_src, opt_srcdoc, opt_attributes, opt_content) {484if (!goog.html.SafeHtml.canUseSandboxIframe()) {485throw new Error('The browser does not support sandboxed iframes.');486}487488var fixedAttributes = {};489if (opt_src) {490// Note that sanitize is a no-op on SafeUrl.491fixedAttributes['src'] =492goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(opt_src));493} else {494fixedAttributes['src'] = null;495}496fixedAttributes['srcdoc'] = opt_srcdoc || null;497fixedAttributes['sandbox'] = '';498var attributes =499goog.html.SafeHtml.combineAttributes(fixedAttributes, {}, opt_attributes);500return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(501'iframe', attributes, opt_content);502};503504505/**506* Checks if the user agent supports sandboxed iframes.507* @return {boolean}508*/509goog.html.SafeHtml.canUseSandboxIframe = function() {510return goog.global['HTMLIFrameElement'] &&511('sandbox' in goog.global['HTMLIFrameElement'].prototype);512};513514515/**516* Creates a SafeHtml representing a script tag with the src attribute.517* @param {!goog.html.TrustedResourceUrl} src The value of the src518* attribute.519* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=}520* opt_attributes521* Mapping from attribute names to their values. Only attribute names522* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined523* causes the attribute to be omitted.524* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.525* @throws {Error} If invalid attribute name or value is provided. If526* opt_attributes contains the src attribute.527*/528goog.html.SafeHtml.createScriptSrc = function(src, opt_attributes) {529// TODO(mlourenco): The charset attribute should probably be blocked. If530// its value is attacker controlled, the script contains attacker controlled531// sub-strings (even if properly escaped) and the server does not set charset532// then XSS is likely possible.533// https://html.spec.whatwg.org/multipage/scripting.html#dom-script-charset534535// Check whether this is really TrustedResourceUrl.536goog.html.TrustedResourceUrl.unwrap(src);537538var fixedAttributes = {'src': src};539var defaultAttributes = {};540var attributes = goog.html.SafeHtml.combineAttributes(541fixedAttributes, defaultAttributes, opt_attributes);542return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(543'script', attributes);544};545546547/**548* Creates a SafeHtml representing a script tag. Does not allow the language,549* src, text or type attributes to be set.550* @param {!goog.html.SafeScript|!Array<!goog.html.SafeScript>}551* script Content to put inside the tag. Array elements are552* concatenated.553* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes554* Mapping from attribute names to their values. Only attribute names555* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes556* the attribute to be omitted.557* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.558* @throws {Error} If invalid attribute name or attribute value is provided. If559* opt_attributes contains the language, src, text or type attribute.560*/561goog.html.SafeHtml.createScript = function(script, opt_attributes) {562for (var attr in opt_attributes) {563var attrLower = attr.toLowerCase();564if (attrLower == 'language' || attrLower == 'src' || attrLower == 'text' ||565attrLower == 'type') {566throw Error('Cannot set "' + attrLower + '" attribute');567}568}569570var content = '';571script = goog.array.concat(script);572for (var i = 0; i < script.length; i++) {573content += goog.html.SafeScript.unwrap(script[i]);574}575// Convert to SafeHtml so that it's not HTML-escaped. This is safe because576// as part of its contract, SafeScript should have no dangerous '<'.577var htmlContent =578goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(579content, goog.i18n.bidi.Dir.NEUTRAL);580return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(581'script', opt_attributes, htmlContent);582};583584585/**586* Creates a SafeHtml representing a style tag. The type attribute is set587* to "text/css".588* @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}589* styleSheet Content to put inside the tag. Array elements are590* concatenated.591* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes592* Mapping from attribute names to their values. Only attribute names593* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes594* the attribute to be omitted.595* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.596* @throws {Error} If invalid attribute name or attribute value is provided. If597* opt_attributes contains the type attribute.598*/599goog.html.SafeHtml.createStyle = function(styleSheet, opt_attributes) {600var fixedAttributes = {'type': 'text/css'};601var defaultAttributes = {};602var attributes = goog.html.SafeHtml.combineAttributes(603fixedAttributes, defaultAttributes, opt_attributes);604605var content = '';606styleSheet = goog.array.concat(styleSheet);607for (var i = 0; i < styleSheet.length; i++) {608content += goog.html.SafeStyleSheet.unwrap(styleSheet[i]);609}610// Convert to SafeHtml so that it's not HTML-escaped. This is safe because611// as part of its contract, SafeStyleSheet should have no dangerous '<'.612var htmlContent =613goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(614content, goog.i18n.bidi.Dir.NEUTRAL);615return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(616'style', attributes, htmlContent);617};618619620/**621* Creates a SafeHtml representing a meta refresh tag.622* @param {!goog.html.SafeUrl|string} url Where to redirect. If a string is623* passed, it will be sanitized with SafeUrl.sanitize().624* @param {number=} opt_secs Number of seconds until the page should be625* reloaded. Will be set to 0 if unspecified.626* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.627*/628goog.html.SafeHtml.createMetaRefresh = function(url, opt_secs) {629630// Note that sanitize is a no-op on SafeUrl.631var unwrappedUrl = goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(url));632633if (goog.labs.userAgent.browser.isIE() ||634goog.labs.userAgent.browser.isEdge()) {635// IE/EDGE can't parse the content attribute if the url contains a636// semicolon. We can fix this by adding quotes around the url, but then we637// can't parse quotes in the URL correctly. Also, it seems that IE/EDGE638// did not unescape semicolons in these URLs at some point in the past. We639// take a best-effort approach.640//641// If the URL has semicolons (which may happen in some cases, see642// http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2643// for instance), wrap it in single quotes to protect the semicolons.644// If the URL has semicolons and single quotes, url-encode the single quotes645// as well.646//647// This is imperfect. Notice that both ' and ; are reserved characters in648// URIs, so this could do the wrong thing, but at least it will do the wrong649// thing in only rare cases.650if (goog.string.contains(unwrappedUrl, ';')) {651unwrappedUrl = "'" + unwrappedUrl.replace(/'/g, '%27') + "'";652}653}654var attributes = {655'http-equiv': 'refresh',656'content': (opt_secs || 0) + '; url=' + unwrappedUrl657};658659// This function will handle the HTML escaping for attributes.660return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(661'meta', attributes);662};663664665/**666* @param {string} tagName The tag name.667* @param {string} name The attribute name.668* @param {!goog.html.SafeHtml.AttributeValue} value The attribute value.669* @return {string} A "name=value" string.670* @throws {Error} If attribute value is unsafe for the given tag and attribute.671* @private672*/673goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) {674// If it's goog.string.Const, allow any valid attribute name.675if (value instanceof goog.string.Const) {676value = goog.string.Const.unwrap(value);677} else if (name.toLowerCase() == 'style') {678value = goog.html.SafeHtml.getStyleValue_(value);679} else if (/^on/i.test(name)) {680// TODO(jakubvrana): Disallow more attributes with a special meaning.681throw Error(682'Attribute "' + name + '" requires goog.string.Const value, "' + value +683'" given.');684// URL attributes handled differently according to tag.685} else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) {686if (value instanceof goog.html.TrustedResourceUrl) {687value = goog.html.TrustedResourceUrl.unwrap(value);688} else if (value instanceof goog.html.SafeUrl) {689value = goog.html.SafeUrl.unwrap(value);690} else if (goog.isString(value)) {691value = goog.html.SafeUrl.sanitize(value).getTypedStringValue();692} else {693throw Error(694'Attribute "' + name + '" on tag "' + tagName +695'" requires goog.html.SafeUrl, goog.string.Const, or string,' +696' value "' + value + '" given.');697}698}699700// Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require701// HTML-escaping.702if (value.implementsGoogStringTypedString) {703// Ok to call getTypedStringValue() since there's no reliance on the type704// contract for security here.705value = value.getTypedStringValue();706}707708goog.asserts.assert(709goog.isString(value) || goog.isNumber(value),710'String or number value expected, got ' + (typeof value) +711' with value: ' + value);712return name + '="' + goog.string.htmlEscape(String(value)) + '"';713};714715716/**717* Gets value allowed in "style" attribute.718* @param {!goog.html.SafeHtml.AttributeValue} value It could be SafeStyle or a719* map which will be passed to goog.html.SafeStyle.create.720* @return {string} Unwrapped value.721* @throws {Error} If string value is given.722* @private723*/724goog.html.SafeHtml.getStyleValue_ = function(value) {725if (!goog.isObject(value)) {726throw Error(727'The "style" attribute requires goog.html.SafeStyle or map ' +728'of style properties, ' + (typeof value) + ' given: ' + value);729}730if (!(value instanceof goog.html.SafeStyle)) {731// Process the property bag into a style object.732value = goog.html.SafeStyle.create(value);733}734return goog.html.SafeStyle.unwrap(value);735};736737738/**739* Creates a SafeHtml content with known directionality consisting of a tag with740* optional attributes and optional content.741* @param {!goog.i18n.bidi.Dir} dir Directionality.742* @param {string} tagName743* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes744* @param {!goog.html.SafeHtml.TextOrHtml_|745* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content746* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.747*/748goog.html.SafeHtml.createWithDir = function(749dir, tagName, opt_attributes, opt_content) {750var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content);751html.dir_ = dir;752return html;753};754755756/**757* Creates a new SafeHtml object by concatenating values.758* @param {...(!goog.html.SafeHtml.TextOrHtml_|759* !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate.760* @return {!goog.html.SafeHtml}761*/762goog.html.SafeHtml.concat = function(var_args) {763var dir = goog.i18n.bidi.Dir.NEUTRAL;764var content = '';765766/**767* @param {!goog.html.SafeHtml.TextOrHtml_|768* !Array<!goog.html.SafeHtml.TextOrHtml_>} argument769*/770var addArgument = function(argument) {771if (goog.isArray(argument)) {772goog.array.forEach(argument, addArgument);773} else {774var html = goog.html.SafeHtml.htmlEscape(argument);775content += goog.html.SafeHtml.unwrap(html);776var htmlDir = html.getDirection();777if (dir == goog.i18n.bidi.Dir.NEUTRAL) {778dir = htmlDir;779} else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) {780dir = null;781}782}783};784785goog.array.forEach(arguments, addArgument);786return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(787content, dir);788};789790791/**792* Creates a new SafeHtml object with known directionality by concatenating the793* values.794* @param {!goog.i18n.bidi.Dir} dir Directionality.795* @param {...(!goog.html.SafeHtml.TextOrHtml_|796* !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Elements of array797* arguments would be processed recursively.798* @return {!goog.html.SafeHtml}799*/800goog.html.SafeHtml.concatWithDir = function(dir, var_args) {801var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1));802html.dir_ = dir;803return html;804};805806807/**808* Type marker for the SafeHtml type, used to implement additional run-time809* type checking.810* @const {!Object}811* @private812*/813goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};814815816/**817* Package-internal utility method to create SafeHtml instances.818*819* @param {string} html The string to initialize the SafeHtml object with.820* @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be821* constructed, or null if unknown.822* @return {!goog.html.SafeHtml} The initialized SafeHtml object.823* @package824*/825goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function(826html, dir) {827return new goog.html.SafeHtml().initSecurityPrivateDoNotAccessOrElse_(828html, dir);829};830831832/**833* Called from createSafeHtmlSecurityPrivateDoNotAccessOrElse(). This834* method exists only so that the compiler can dead code eliminate static835* fields (like EMPTY) when they're not accessed.836* @param {string} html837* @param {?goog.i18n.bidi.Dir} dir838* @return {!goog.html.SafeHtml}839* @private840*/841goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(842html, dir) {843this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html;844this.dir_ = dir;845return this;846};847848849/**850* Like create() but does not restrict which tags can be constructed.851*852* @param {string} tagName Tag name. Set or validated by caller.853* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes854* @param {(!goog.html.SafeHtml.TextOrHtml_|855* !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content856* @return {!goog.html.SafeHtml}857* @throws {Error} If invalid or unsafe attribute name or value is provided.858* @throws {goog.asserts.AssertionError} If content for void tag is provided.859* @package860*/861goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse = function(862tagName, opt_attributes, opt_content) {863var dir = null;864var result = '<' + tagName;865result += goog.html.SafeHtml.stringifyAttributes(tagName, opt_attributes);866867var content = opt_content;868if (!goog.isDefAndNotNull(content)) {869content = [];870} else if (!goog.isArray(content)) {871content = [content];872}873874if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) {875goog.asserts.assert(876!content.length, 'Void tag <' + tagName + '> does not allow content.');877result += '>';878} else {879var html = goog.html.SafeHtml.concat(content);880result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>';881dir = html.getDirection();882}883884var dirAttribute = opt_attributes && opt_attributes['dir'];885if (dirAttribute) {886if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) {887// If the tag has the "dir" attribute specified then its direction is888// neutral because it can be safely used in any context.889dir = goog.i18n.bidi.Dir.NEUTRAL;890} else {891dir = null;892}893}894895return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(896result, dir);897};898899900/**901* Creates a string with attributes to insert after tagName.902* @param {string} tagName903* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes904* @return {string} Returns an empty string if there are no attributes, returns905* a string starting with a space otherwise.906* @throws {Error} If attribute value is unsafe for the given tag and attribute.907* @package908*/909goog.html.SafeHtml.stringifyAttributes = function(tagName, opt_attributes) {910var result = '';911if (opt_attributes) {912for (var name in opt_attributes) {913if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) {914throw Error('Invalid attribute name "' + name + '".');915}916var value = opt_attributes[name];917if (!goog.isDefAndNotNull(value)) {918continue;919}920result +=921' ' + goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value);922}923}924return result;925};926927928/**929* @param {!Object<string, ?goog.html.SafeHtml.AttributeValue>} fixedAttributes930* @param {!Object<string, string>} defaultAttributes931* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes932* Optional attributes passed to create*().933* @return {!Object<string, ?goog.html.SafeHtml.AttributeValue>}934* @throws {Error} If opt_attributes contains an attribute with the same name935* as an attribute in fixedAttributes.936* @package937*/938goog.html.SafeHtml.combineAttributes = function(939fixedAttributes, defaultAttributes, opt_attributes) {940var combinedAttributes = {};941var name;942943for (name in fixedAttributes) {944goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');945combinedAttributes[name] = fixedAttributes[name];946}947for (name in defaultAttributes) {948goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');949combinedAttributes[name] = defaultAttributes[name];950}951952for (name in opt_attributes) {953var nameLower = name.toLowerCase();954if (nameLower in fixedAttributes) {955throw Error(956'Cannot override "' + nameLower + '" attribute, got "' + name +957'" with value "' + opt_attributes[name] + '"');958}959if (nameLower in defaultAttributes) {960delete combinedAttributes[nameLower];961}962combinedAttributes[name] = opt_attributes[name];963}964965return combinedAttributes;966};967968969/**970* A SafeHtml instance corresponding to the HTML doctype: "<!DOCTYPE html>".971* @const {!goog.html.SafeHtml}972*/973goog.html.SafeHtml.DOCTYPE_HTML =974goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(975'<!DOCTYPE html>', goog.i18n.bidi.Dir.NEUTRAL);976977978/**979* A SafeHtml instance corresponding to the empty string.980* @const {!goog.html.SafeHtml}981*/982goog.html.SafeHtml.EMPTY =983goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(984'', goog.i18n.bidi.Dir.NEUTRAL);985986987/**988* A SafeHtml instance corresponding to the <br> tag.989* @const {!goog.html.SafeHtml}990*/991goog.html.SafeHtml.BR =992goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(993'<br>', goog.i18n.bidi.Dir.NEUTRAL);994995996