Path: blob/trunk/third_party/closure/goog/html/safehtml.js
4507 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/567/**8* @fileoverview The SafeHtml type and its builders.9*10* TODO(xtof): Link to document stating type contract.11*/1213goog.module('goog.html.SafeHtml');14goog.module.declareLegacyNamespace();1516const Const = goog.require('goog.string.Const');17const SafeScript = goog.require('goog.html.SafeScript');18const SafeStyle = goog.require('goog.html.SafeStyle');19const SafeStyleSheet = goog.require('goog.html.SafeStyleSheet');20const SafeUrl = goog.require('goog.html.SafeUrl');21const TagName = goog.require('goog.dom.TagName');22const TrustedResourceUrl = goog.require('goog.html.TrustedResourceUrl');23const TypedString = goog.require('goog.string.TypedString');24const asserts = goog.require('goog.asserts');25const browser = goog.require('goog.labs.userAgent.browser');26const googArray = goog.require('goog.array');27const googObject = goog.require('goog.object');28const internal = goog.require('goog.string.internal');29const tags = goog.require('goog.dom.tags');30const trustedtypes = goog.require('goog.html.trustedtypes');31const utils = goog.require('goog.utils');323334/**35* Token used to ensure that object is created only from this file. No code36* outside of this file can access this token.37* @type {!Object}38* @const39*/40const CONSTRUCTOR_TOKEN_PRIVATE = {};4142/**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* (`SafeHtml.create`, `SafeHtml.htmlEscape`),56* etc and not by invoking its constructor. The constructor intentionally takes57* an extra parameter that cannot be constructed outside of this file and the58* type is immutable; hence only a default instance corresponding to the empty59* string can be obtained via constructor invocation.60*61* Creating SafeHtml objects HAS SIDE-EFFECTS due to calling Trusted Types Web62* API.63*64* Note that there is no `SafeHtml.fromConstant`. The reason is that65* the following code would create an unsafe HTML:66*67* ```68* SafeHtml.concat(69* SafeHtml.fromConstant(Const.from('<script>')),70* SafeHtml.htmlEscape(userInput),71* SafeHtml.fromConstant(Const.from('<\/script>')));72* ```73*74* There's `goog.dom.constHtmlToNode` to create a node from constant strings75* only.76*77* @see SafeHtml.create78* @see SafeHtml.htmlEscape79* @final80* @struct81* @implements {TypedString}82*/83class SafeHtml {84/**85* @private86* @param {!TrustedHTML|string} value87* @param {!Object} token package-internal implementation detail.88*/89constructor(value, token) {90if (goog.DEBUG && token !== CONSTRUCTOR_TOKEN_PRIVATE) {91throw Error('SafeHtml is not meant to be built directly');92}9394/**95* The contained value of this SafeHtml. The field has a purposely ugly96* name to make (non-compiled) code that attempts to directly access this97* field stand out.98* @const99* @private {!TrustedHTML|string}100*/101this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = value;102103/**104* @override105* @const {boolean}106*/107this.implementsGoogStringTypedString = true;108}109110111/**112* Returns this SafeHtml's value as string.113*114* IMPORTANT: In code where it is security relevant that an object's type is115* indeed `SafeHtml`, use `SafeHtml.unwrap` instead of116* this method. If in doubt, assume that it's security relevant. In117* particular, note that goog.html functions which return a goog.html type do118* not guarantee that the returned instance is of the right type. For example:119*120* <pre>121* var fakeSafeHtml = new String('fake');122* fakeSafeHtml.__proto__ = SafeHtml.prototype;123* var newSafeHtml = SafeHtml.htmlEscape(fakeSafeHtml);124* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by125* // SafeHtml.htmlEscape() as fakeSafeHtml126* // instanceof SafeHtml.127* </pre>128*129* @return {string}130* @see SafeHtml.unwrap131* @override132*/133getTypedStringValue() {134return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_.toString();135}136137138/**139* Returns a string-representation of this value.140*141* To obtain the actual string value wrapped in a SafeHtml, use142* `SafeHtml.unwrap`.143*144* @return {string}145* @see SafeHtml.unwrap146* @override147*/148toString() {149return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_.toString();150}151152/**153* Performs a runtime check that the provided object is indeed a SafeHtml154* object, and returns its value.155* @param {!SafeHtml} safeHtml The object to extract from.156* @return {string} The SafeHtml object's contained string, unless the157* run-time type check fails. In that case, `unwrap` returns an innocuous158* string, or, if assertions are enabled, throws159* `asserts.AssertionError`.160*/161static unwrap(safeHtml) {162return SafeHtml.unwrapTrustedHTML(safeHtml).toString();163}164165166/**167* Unwraps value as TrustedHTML if supported or as a string if not.168* @param {!SafeHtml} safeHtml169* @return {!TrustedHTML|string}170* @see SafeHtml.unwrap171*/172static unwrapTrustedHTML(safeHtml) {173// Perform additional run-time type-checking to ensure that safeHtml is174// indeed an instance of the expected type. This provides some additional175// protection against security bugs due to application code that disables176// type checks. Specifically, the following checks are performed:177// 1. The object is an instance of the expected type.178// 2. The object is not an instance of a subclass.179if (safeHtml instanceof SafeHtml && safeHtml.constructor === SafeHtml) {180return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_;181} else {182asserts.fail(183`expected object of type SafeHtml, got '${safeHtml}' of type ` +184utils.typeOf(safeHtml));185return 'type_error:SafeHtml';186}187}188189/**190* Returns HTML-escaped text as a SafeHtml object.191*192* @param {!SafeHtml.TextOrHtml_} textOrHtml The text to escape. If193* the parameter is of type SafeHtml it is returned directly (no escaping194* is done).195* @return {!SafeHtml} The escaped text, wrapped as a SafeHtml.196*/197static htmlEscape(textOrHtml) {198if (textOrHtml instanceof SafeHtml) {199return textOrHtml;200}201const textIsObject = typeof textOrHtml == 'object';202let textAsString;203if (textIsObject &&204/** @type {?} */ (textOrHtml).implementsGoogStringTypedString) {205textAsString =206/** @type {!TypedString} */ (textOrHtml).getTypedStringValue();207} else {208textAsString = String(textOrHtml);209}210return SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(211internal.htmlEscape(textAsString));212}213214215/**216* Returns HTML-escaped text as a SafeHtml object, with newlines changed to217* <br>.218* @param {!SafeHtml.TextOrHtml_} textOrHtml The text to escape. If219* the parameter is of type SafeHtml it is returned directly (no escaping220* is done).221* @return {!SafeHtml} The escaped text, wrapped as a SafeHtml.222*/223static htmlEscapePreservingNewlines(textOrHtml) {224if (textOrHtml instanceof SafeHtml) {225return textOrHtml;226}227const html = SafeHtml.htmlEscape(textOrHtml);228return SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(229internal.newLineToBr(SafeHtml.unwrap(html)));230}231232233/**234* Returns HTML-escaped text as a SafeHtml object, with newlines changed to235* <br> and escaping whitespace to preserve spatial formatting.236* Character entity #160 is used to make it safer for XML.237* @param {!SafeHtml.TextOrHtml_} textOrHtml The text to escape. If238* the parameter is of type SafeHtml it is returned directly (no escaping239* is done).240* @return {!SafeHtml} The escaped text, wrapped as a SafeHtml.241*/242static htmlEscapePreservingNewlinesAndSpaces(textOrHtml) {243if (textOrHtml instanceof SafeHtml) {244return textOrHtml;245}246const html = SafeHtml.htmlEscape(textOrHtml);247return SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(248internal.whitespaceEscape(SafeHtml.unwrap(html)));249}250251/**252* Converts an arbitrary string into an HTML comment by HTML-escaping the253* contents and embedding the result between HTML comment markers.254*255* Escaping is needed because Internet Explorer supports conditional comments256* and so may render HTML markup within comments.257*258* @param {string} text259* @return {!SafeHtml}260*/261static comment(text) {262return SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(263'<!--' + internal.htmlEscape(text) + '-->');264}265266/**267* Creates a SafeHtml content consisting of a tag with optional attributes and268* optional content.269*270* For convenience tag names and attribute names are accepted as regular271* strings, instead of Const. Nevertheless, you should not pass272* user-controlled values to these parameters. Note that these parameters are273* syntactically validated at runtime, and invalid values will result in274* an exception.275*276* Example usage:277*278* SafeHtml.create('br');279* SafeHtml.create('div', {'class': 'a'});280* SafeHtml.create('p', {}, 'a');281* SafeHtml.create('p', {}, SafeHtml.create('br'));282*283* SafeHtml.create('span', {284* 'style': {'margin': '0'}285* });286*287* To guarantee SafeHtml's type contract is upheld there are restrictions on288* attribute values and tag names.289*290* - For attributes which contain script code (on*), a Const is291* required.292* - For attributes which contain style (style), a SafeStyle or a293* SafeStyle.PropertyMap is required.294* - For attributes which are interpreted as URLs (e.g. src, href) a295* SafeUrl, Const or string is required. If a string296* is passed, it will be sanitized with SafeUrl.sanitize().297* - For tags which can load code or set security relevant page metadata,298* more specific SafeHtml.create*() functions must be used. Tags299* which are not supported by this function are applet, base, embed, iframe,300* link, math, meta, object, script, style, svg, and template.301*302* @param {!TagName|string} tagName The name of the tag. Only tag names303* consisting of [a-zA-Z0-9-] are allowed. Tag names documented above are304* disallowed.305* @param {?Object<string, ?SafeHtml.AttributeValue>=} attributes Mapping306* from attribute names to their values. Only attribute names consisting307* of [a-zA-Z0-9-] are allowed. Value of null or undefined causes the308* attribute to be omitted.309* @param {!SafeHtml.TextOrHtml_|310* !Array<!SafeHtml.TextOrHtml_>=} content Content to HTML-escape and put311* inside the tag. This must be empty for void tags like <br>. Array elements312* are concatenated.313* @return {!SafeHtml} The SafeHtml content with the tag.314* @throws {!Error} If invalid tag name, attribute name, or attribute value is315* provided.316* @throws {!asserts.AssertionError} If content for void tag is provided.317* @deprecated Use a recommended templating system like Lit instead.318* More information: go/goog.html-readme // LINE-INTERNAL319*/320static create(tagName, attributes = undefined, content = undefined) {321SafeHtml.verifyTagName(String(tagName));322return SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(323String(tagName), attributes, content);324}325326327/**328* Verifies if the tag name is valid and if it doesn't change the context.329* E.g. STRONG is fine but SCRIPT throws because it changes context. See330* SafeHtml.create for an explanation of allowed tags.331* @param {string} tagName332* @return {void}333* @throws {!Error} If invalid tag name is provided.334* @package335*/336static verifyTagName(tagName) {337if (!VALID_NAMES_IN_TAG.test(tagName)) {338throw new Error(339SafeHtml.ENABLE_ERROR_MESSAGES ? `Invalid tag name <${tagName}>.` :340'');341}342if (tagName.toUpperCase() in NOT_ALLOWED_TAG_NAMES) {343throw new Error(344SafeHtml.ENABLE_ERROR_MESSAGES ?345346`Tag name <${tagName}> is not allowed for SafeHtml.` :347'');348}349}350351352/**353* Creates a SafeHtml representing an iframe tag.354*355* This by default restricts the iframe as much as possible by setting the356* sandbox attribute to the empty string. If the iframe requires less357* restrictions, set the sandbox attribute as tight as possible, but do not358* rely on the sandbox as a security feature because it is not supported by359* older browsers. If a sandbox is essential to security (e.g. for third-party360* frames), use createSandboxIframe which checks for browser support.361*362* @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox363*364* @param {?TrustedResourceUrl=} src The value of the src365* attribute. If null or undefined src will not be set.366* @param {?SafeHtml=} srcdoc The value of the srcdoc attribute.367* If null or undefined srcdoc will not be set.368* @param {?Object<string, ?SafeHtml.AttributeValue>=} attributes Mapping369* from attribute names to their values. Only attribute names consisting370* of [a-zA-Z0-9-] are allowed. Value of null or undefined causes the371* attribute to be omitted.372* @param {!SafeHtml.TextOrHtml_|373* !Array<!SafeHtml.TextOrHtml_>=} content Content to HTML-escape and put374* inside the tag. Array elements are concatenated.375* @return {!SafeHtml} The SafeHtml content with the tag.376* @throws {!Error} If invalid tag name, attribute name, or attribute value is377* provided. If attributes378* contains the src or srcdoc attributes.379*/380static createIframe(381src = undefined, srcdoc = undefined, attributes = undefined,382content = undefined) {383if (src) {384// Check whether this is really TrustedResourceUrl.385TrustedResourceUrl.unwrap(src);386}387388const fixedAttributes = {};389fixedAttributes['src'] = src || null;390fixedAttributes['srcdoc'] = srcdoc && SafeHtml.unwrap(srcdoc);391const defaultAttributes = {'sandbox': ''};392const combinedAttrs = SafeHtml.combineAttributes(393fixedAttributes, defaultAttributes, attributes);394return SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(395'iframe', combinedAttrs, content);396}397398399/**400* Creates a SafeHtml representing a sandboxed iframe tag.401*402* The sandbox attribute is enforced in its most restrictive mode, an empty403* string. Consequently, the security requirements for the src and srcdoc404* attributes are relaxed compared to SafeHtml.createIframe. This function405* will throw on browsers that do not support the sandbox attribute, as406* determined by SafeHtml.canUseSandboxIframe.407*408* The SafeHtml returned by this function can trigger downloads with no409* user interaction on Chrome (though only a few, further attempts are410* blocked). Firefox and IE will block all downloads from the sandbox.411*412* @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox413* @see https://lists.w3.org/Archives/Public/public-whatwg-archive/2013Feb/0112.html414*415* @param {string|!SafeUrl=} src The value of the src416* attribute. If null or undefined src will not be set.417* @param {string=} srcdoc The value of the srcdoc attribute.418* If null or undefined srcdoc will not be set. Will not be sanitized.419* @param {!Object<string, ?SafeHtml.AttributeValue>=} attributes Mapping420* from attribute names to their values. Only attribute names consisting421* of [a-zA-Z0-9-] are allowed. Value of null or undefined causes the422* attribute to be omitted.423* @param {!SafeHtml.TextOrHtml_|424* !Array<!SafeHtml.TextOrHtml_>=} content Content to HTML-escape and put425* inside the tag. Array elements are concatenated.426* @return {!SafeHtml} The SafeHtml content with the tag.427* @throws {!Error} If invalid tag name, attribute name, or attribute value is428* provided. If attributes429* contains the src, srcdoc or sandbox attributes. If browser does not support430* the sandbox attribute on iframe.431*/432static createSandboxIframe(433src = undefined, srcdoc = undefined, attributes = undefined,434content = undefined) {435if (!SafeHtml.canUseSandboxIframe()) {436throw new Error(437SafeHtml.ENABLE_ERROR_MESSAGES ?438'The browser does not support sandboxed iframes.' :439'');440}441442const fixedAttributes = {};443if (src) {444// Note that sanitize is a no-op on SafeUrl.445fixedAttributes['src'] = SafeUrl.unwrap(SafeUrl.sanitize(src));446} else {447fixedAttributes['src'] = null;448}449fixedAttributes['srcdoc'] = srcdoc || null;450fixedAttributes['sandbox'] = '';451const combinedAttrs =452SafeHtml.combineAttributes(fixedAttributes, {}, attributes);453return SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(454'iframe', combinedAttrs, content);455}456457458/**459* Checks if the user agent supports sandboxed iframes.460* @return {boolean}461*/462static canUseSandboxIframe() {463return goog.global['HTMLIFrameElement'] &&464('sandbox' in goog.global['HTMLIFrameElement'].prototype);465}466467468/**469* Creates a SafeHtml representing a script tag with the src attribute.470* @param {!TrustedResourceUrl} src The value of the src471* attribute.472* @param {?Object<string, ?SafeHtml.AttributeValue>=}473* attributes474* Mapping from attribute names to their values. Only attribute names475* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined476* causes the attribute to be omitted.477* @return {!SafeHtml} The SafeHtml content with the tag.478* @throws {!Error} If invalid attribute name or value is provided. If479* attributes contains the480* src attribute.481*/482static createScriptSrc(src, attributes = undefined) {483// TODO(mlourenco): The charset attribute should probably be blocked. If484// its value is attacker controlled, the script contains attacker controlled485// sub-strings (even if properly escaped) and the server does not set486// charset then XSS is likely possible.487// https://html.spec.whatwg.org/multipage/scripting.html#dom-script-charset488489// Check whether this is really TrustedResourceUrl.490TrustedResourceUrl.unwrap(src);491492const fixedAttributes = {'src': src};493const defaultAttributes = {};494const combinedAttrs = SafeHtml.combineAttributes(495fixedAttributes, defaultAttributes, attributes);496return SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(497'script', combinedAttrs);498}499500/**501* Creates a SafeHtml representing a script tag. Does not allow the language,502* src, text or type attributes to be set.503* @param {!SafeScript|!Array<!SafeScript>}504* script Content to put inside the tag. Array elements are505* concatenated.506* @param {?Object<string, ?SafeHtml.AttributeValue>=} attributes Mapping507* from attribute names to their values. Only attribute names consisting508* of [a-zA-Z0-9-] are allowed. Value of null or undefined causes the509* attribute to be omitted.510* @return {!SafeHtml} The SafeHtml content with the tag.511* @throws {!Error} If invalid attribute name or attribute value is provided.512* If attributes contains the513* language, src or text attribute.514* @deprecated Use safevalues.scriptToHtml instead.515*/516static createScript(script, attributes = undefined) {517for (let attr in attributes) {518// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty#Using_hasOwnProperty_as_a_property_name519if (Object.prototype.hasOwnProperty.call(attributes, attr)) {520const attrLower = attr.toLowerCase();521if (attrLower == 'language' || attrLower == 'src' ||522attrLower == 'text') {523throw new Error(524SafeHtml.ENABLE_ERROR_MESSAGES ?525`Cannot set "${attrLower}" attribute` :526'');527}528}529}530531let content = '';532script = googArray.concat(script);533for (let i = 0; i < script.length; i++) {534content += SafeScript.unwrap(script[i]);535}536// Convert to SafeHtml so that it's not HTML-escaped. This is safe because537// as part of its contract, SafeScript should have no dangerous '<'.538const htmlContent =539SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(content);540return SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(541'script', attributes, htmlContent);542}543544545/**546* Creates a SafeHtml representing a style tag. The type attribute is set547* to "text/css".548* @param {!SafeStyleSheet|!Array<!SafeStyleSheet>}549* styleSheet Content to put inside the tag. Array elements are550* concatenated.551* @param {?Object<string, ?SafeHtml.AttributeValue>=} attributes Mapping552* from attribute names to their values. Only attribute names consisting553* of [a-zA-Z0-9-] are allowed. Value of null or undefined causes the554* attribute to be omitted.555* @return {!SafeHtml} The SafeHtml content with the tag.556* @throws {!Error} If invalid attribute name or attribute value is provided.557* If attributes contains the558* type attribute.559*/560static createStyle(styleSheet, attributes = undefined) {561const fixedAttributes = {'type': 'text/css'};562const defaultAttributes = {};563const combinedAttrs = SafeHtml.combineAttributes(564fixedAttributes, defaultAttributes, attributes);565566let content = '';567styleSheet = googArray.concat(styleSheet);568for (let i = 0; i < styleSheet.length; i++) {569content += SafeStyleSheet.unwrap(styleSheet[i]);570}571// Convert to SafeHtml so that it's not HTML-escaped. This is safe because572// as part of its contract, SafeStyleSheet should have no dangerous '<'.573const htmlContent =574SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(content);575return SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(576'style', combinedAttrs, htmlContent);577}578579580/**581* Creates a SafeHtml representing a meta refresh tag.582* @param {!SafeUrl|string} url Where to redirect. If a string is583* passed, it will be sanitized with SafeUrl.sanitize().584* @param {number=} secs Number of seconds until the page should be585* reloaded. Will be set to 0 if unspecified.586* @return {!SafeHtml} The SafeHtml content with the tag.587*/588static createMetaRefresh(url, secs = undefined) {589// Note that sanitize is a no-op on SafeUrl.590let unwrappedUrl = SafeUrl.unwrap(SafeUrl.sanitize(url));591592if (browser.isIE() || browser.isEdge()) {593// IE/EDGE can't parse the content attribute if the url contains a594// semicolon. We can fix this by adding quotes around the url, but then we595// can't parse quotes in the URL correctly. Also, it seems that IE/EDGE596// did not unescape semicolons in these URLs at some point in the past. We597// take a best-effort approach.598//599// If the URL has semicolons (which may happen in some cases, see600// http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2601// for instance), wrap it in single quotes to protect the semicolons.602// If the URL has semicolons and single quotes, url-encode the single603// quotes as well.604//605// This is imperfect. Notice that both ' and ; are reserved characters in606// URIs, so this could do the wrong thing, but at least it will do the607// wrong thing in only rare cases.608if (internal.contains(unwrappedUrl, ';')) {609unwrappedUrl = '\'' + unwrappedUrl.replace(/'/g, '%27') + '\'';610}611}612const attributes = {613'http-equiv': 'refresh',614'content': (secs || 0) + '; url=' + unwrappedUrl,615};616617// This function will handle the HTML escaping for attributes.618return SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(619'meta', attributes);620}621622/**623* Creates a new SafeHtml object by joining the parts with separator.624* @param {!SafeHtml.TextOrHtml_} separator625* @param {!Array<!SafeHtml.TextOrHtml_|626* !Array<!SafeHtml.TextOrHtml_>>} parts Parts to join. If a part627* contains an array then each member of this array is also joined with628* the separator.629* @return {!SafeHtml}630*/631static join(separator, parts) {632const separatorHtml = SafeHtml.htmlEscape(separator);633const content = [];634635/**636* @param {!SafeHtml.TextOrHtml_|637* !Array<!SafeHtml.TextOrHtml_>} argument638*/639const addArgument = (argument) => {640if (Array.isArray(argument)) {641argument.forEach(addArgument);642} else {643const html = SafeHtml.htmlEscape(argument);644content.push(SafeHtml.unwrap(html));645}646};647648parts.forEach(addArgument);649return SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(650content.join(SafeHtml.unwrap(separatorHtml)));651}652653654/**655* Creates a new SafeHtml object by concatenating values.656* @param {...(!SafeHtml.TextOrHtml_|657* !Array<!SafeHtml.TextOrHtml_>)} var_args Values to concatenate.658* @return {!SafeHtml}659*/660static concat(var_args) {661return SafeHtml.join(SafeHtml.EMPTY, Array.prototype.slice.call(arguments));662}663664/**665* Package-internal utility method to create SafeHtml instances.666*667* @param {string} html The string to initialize the SafeHtml object with.668* @return {!SafeHtml} The initialized SafeHtml object.669* @package670*/671static createSafeHtmlSecurityPrivateDoNotAccessOrElse(html) {672/** @noinline */673const noinlineHtml = html;674const policy = trustedtypes.getPolicyPrivateDoNotAccessOrElse();675const trustedHtml = policy ? policy.createHTML(noinlineHtml) : noinlineHtml;676return new SafeHtml(trustedHtml, CONSTRUCTOR_TOKEN_PRIVATE);677}678679680/**681* Like create() but does not restrict which tags can be constructed.682*683* @param {string} tagName Tag name. Set or validated by caller.684* @param {?Object<string, ?SafeHtml.AttributeValue>=} attributes685* @param {(!SafeHtml.TextOrHtml_|686* !Array<!SafeHtml.TextOrHtml_>)=} content687* @return {!SafeHtml}688* @throws {!Error} If invalid or unsafe attribute name or value is provided.689* @throws {!asserts.AssertionError} If content for void tag is provided.690* @package691*/692static createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(693tagName, attributes = undefined, content = undefined) {694let result = `<${tagName}`;695result += SafeHtml.stringifyAttributes(tagName, attributes);696697if (content == null) {698content = [];699} else if (!Array.isArray(content)) {700content = [content];701}702703if (tags.isVoidTag(tagName.toLowerCase())) {704asserts.assert(705!content.length, `Void tag <${tagName}> does not allow content.`);706result += '>';707} else {708const html = SafeHtml.concat(content);709result += '>' + SafeHtml.unwrap(html) + '</' + tagName + '>';710}711712return SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(result);713}714715716/**717* Creates a string with attributes to insert after tagName.718* @param {string} tagName719* @param {?Object<string, ?SafeHtml.AttributeValue>=} attributes720* @return {string} Returns an empty string if there are no attributes,721* returns a string starting with a space otherwise.722* @throws {!Error} If attribute value is unsafe for the given tag and723* attribute.724* @package725*/726static stringifyAttributes(tagName, attributes = undefined) {727let result = '';728if (attributes) {729for (let name in attributes) {730// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty#Using_hasOwnProperty_as_a_property_name731if (Object.prototype.hasOwnProperty.call(attributes, name)) {732if (!VALID_NAMES_IN_TAG.test(name)) {733throw new Error(734SafeHtml.ENABLE_ERROR_MESSAGES ?735`Invalid attribute name "${name}".` :736'');737}738const value = attributes[name];739if (value == null) {740continue;741}742result += ' ' + getAttrNameAndValue(tagName, name, value);743}744}745}746return result;747}748749750/**751* @param {!Object<string, ?SafeHtml.AttributeValue>} fixedAttributes752* @param {!Object<string, string>} defaultAttributes753* @param {?Object<string, ?SafeHtml.AttributeValue>=} attributes Optional754* attributes passed to create*().755* @return {!Object<string, ?SafeHtml.AttributeValue>}756* @throws {!Error} If attributes contains an attribute with the same name as757* an attribute in fixedAttributes.758* @package759*/760static combineAttributes(761fixedAttributes, defaultAttributes, attributes = undefined) {762const combinedAttributes = {};763764for (const name in fixedAttributes) {765// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty#Using_hasOwnProperty_as_a_property_name766if (Object.prototype.hasOwnProperty.call(fixedAttributes, name)) {767asserts.assert(name.toLowerCase() == name, 'Must be lower case');768combinedAttributes[name] = fixedAttributes[name];769}770}771for (const name in defaultAttributes) {772if (Object.prototype.hasOwnProperty.call(defaultAttributes, name)) {773asserts.assert(name.toLowerCase() == name, 'Must be lower case');774combinedAttributes[name] = defaultAttributes[name];775}776}777778if (attributes) {779for (const name in attributes) {780if (Object.prototype.hasOwnProperty.call(attributes, name)) {781const nameLower = name.toLowerCase();782if (nameLower in fixedAttributes) {783throw new Error(784SafeHtml.ENABLE_ERROR_MESSAGES ?785`Cannot override "${nameLower}" attribute, got "` + name +786'" with value "' + attributes[name] + '"' :787'');788}789if (nameLower in defaultAttributes) {790delete combinedAttributes[nameLower];791}792combinedAttributes[name] = attributes[name];793}794}795}796797return combinedAttributes;798}799}800801802/**803* @define {boolean} Whether to strip out error messages or to leave them in.804*/805SafeHtml.ENABLE_ERROR_MESSAGES =806goog.define('goog.html.SafeHtml.ENABLE_ERROR_MESSAGES', goog.DEBUG);807808809/**810* Whether the `style` attribute is supported. Set to false to avoid the byte811* weight of `SafeStyle` where unneeded. An error will be thrown if812* the `style` attribute is used.813* @define {boolean}814*/815SafeHtml.SUPPORT_STYLE_ATTRIBUTE =816goog.define('goog.html.SafeHtml.SUPPORT_STYLE_ATTRIBUTE', true);817818819/**820* Shorthand for union of types that can sensibly be converted to strings821* or might already be SafeHtml (as SafeHtml is a TypedString).822* @private823* @typedef {string|number|boolean|!TypedString}824*/825SafeHtml.TextOrHtml_;826827828/**829* Coerces an arbitrary object into a SafeHtml object.830*831* If `textOrHtml` is already of type `SafeHtml`, the same832* object is returned. Otherwise, `textOrHtml` is coerced to string, and833* HTML-escaped.834*835* @param {!SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to836* coerce.837* @return {!SafeHtml} The resulting SafeHtml object.838* @deprecated Use SafeHtml.htmlEscape.839*/840SafeHtml.from = SafeHtml.htmlEscape;841842843/**844* @const845*/846const VALID_NAMES_IN_TAG = /^[a-zA-Z0-9-]+$/;847848849/**850* Set of attributes containing URL as defined at851* http://www.w3.org/TR/html5/index.html#attributes-1.852* @const {!Object<string,boolean>}853*/854const URL_ATTRIBUTES = googObject.createSet(855'action', 'cite', 'data', 'formaction', 'href', 'manifest', 'poster',856'src');857858859/**860* Tags which are unsupported via create(). They might be supported via a861* tag-specific create method. These are tags which might require a862* TrustedResourceUrl in one of their attributes or a restricted type for863* their content.864* @const {!Object<string,boolean>}865*/866const NOT_ALLOWED_TAG_NAMES = googObject.createSet(867TagName.APPLET, TagName.BASE, TagName.EMBED, TagName.IFRAME, TagName.LINK,868TagName.MATH, TagName.META, TagName.OBJECT, TagName.SCRIPT, TagName.STYLE,869TagName.SVG, TagName.TEMPLATE);870871872/**873* @typedef {string|number|!TypedString|874* !SafeStyle.PropertyMap|undefined|null}875*/876SafeHtml.AttributeValue;877878879/**880* @param {string} tagName The tag name.881* @param {string} name The attribute name.882* @param {!SafeHtml.AttributeValue} value The attribute value.883* @return {string} A "name=value" string.884* @throws {!Error} If attribute value is unsafe for the given tag and885* attribute.886* @private887*/888function getAttrNameAndValue(tagName, name, value) {889// If it's goog.string.Const, allow any valid attribute name.890if (value instanceof Const) {891value = Const.unwrap(value);892} else if (name.toLowerCase() == 'style') {893if (SafeHtml.SUPPORT_STYLE_ATTRIBUTE) {894value = getStyleValue(value);895} else {896throw new Error(897SafeHtml.ENABLE_ERROR_MESSAGES ? 'Attribute "style" not supported.' :898'');899}900} else if (/^on/i.test(name)) {901// TODO(jakubvrana): Disallow more attributes with a special meaning.902throw new Error(903SafeHtml.ENABLE_ERROR_MESSAGES ? `Attribute "${name}` +904'" requires goog.string.Const value, "' + value + '" given.' :905'');906// URL attributes handled differently according to tag.907} else if (name.toLowerCase() in URL_ATTRIBUTES) {908if (value instanceof TrustedResourceUrl) {909value = TrustedResourceUrl.unwrap(value);910} else if (value instanceof SafeUrl) {911value = SafeUrl.unwrap(value);912} else if (typeof value === 'string') {913value = SafeUrl.sanitize(value).getTypedStringValue();914} else {915throw new Error(916SafeHtml.ENABLE_ERROR_MESSAGES ?917`Attribute "${name}" on tag "${tagName}` +918'" requires goog.html.SafeUrl, goog.string.Const, or' +919' string, value "' + value + '" given.' :920'');921}922}923924// Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require925// HTML-escaping.926if (/** @type {?} */ (value).implementsGoogStringTypedString) {927// Ok to call getTypedStringValue() since there's no reliance on the type928// contract for security here.929value =930/** @type {!TypedString} */ (value).getTypedStringValue();931}932933asserts.assert(934typeof value === 'string' || typeof value === 'number',935'String or number value expected, got ' + (typeof value) +936' with value: ' + value);937return `${name}="` + internal.htmlEscape(String(value)) + '"';938}939940941/**942* Gets value allowed in "style" attribute.943* @param {!SafeHtml.AttributeValue} value It could be SafeStyle or a944* map which will be passed to SafeStyle.create.945* @return {string} Unwrapped value.946* @throws {!Error} If string value is given.947* @private948*/949function getStyleValue(value) {950if (!utils.isObject(value)) {951throw new Error(952SafeHtml.ENABLE_ERROR_MESSAGES ?953'The "style" attribute requires goog.html.SafeStyle or map ' +954'of style properties, ' + (typeof value) + ' given: ' + value :955'');956}957if (!(value instanceof SafeStyle)) {958// Process the property bag into a style object.959value = SafeStyle.create(/** @type {!SafeStyle.PropertyMap} */ (value));960}961return SafeStyle.unwrap(value);962}963964965/**966* A SafeHtml instance corresponding to the HTML doctype: "<!DOCTYPE html>".967* @const {!SafeHtml}968*/969SafeHtml.DOCTYPE_HTML = /** @type {!SafeHtml} */ ({970// NOTE: this compiles to nothing, but hides the possible side effect of971// SafeHtml creation (due to calling trustedTypes.createPolicy) from the972// compiler so that the entire call can be removed if the result is not used.973valueOf: function() {974return SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(975'<!DOCTYPE html>');976},977}.valueOf());978979/**980* A SafeHtml instance corresponding to the empty string.981* @const {!SafeHtml}982*/983SafeHtml.EMPTY = new SafeHtml(984(goog.global.trustedTypes && goog.global.trustedTypes.emptyHTML) || '',985CONSTRUCTOR_TOKEN_PRIVATE);986987/**988* A SafeHtml instance corresponding to the <br> tag.989* @const {!SafeHtml}990*/991SafeHtml.BR = /** @type {!SafeHtml} */ ({992// NOTE: this compiles to nothing, but hides the possible side effect of993// SafeHtml creation (due to calling trustedTypes.createPolicy) from the994// compiler so that the entire call can be removed if the result is not used.995valueOf: function() {996return SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse('<br>');997},998}.valueOf());99910001001exports = SafeHtml;100210031004