Path: blob/trunk/third_party/closure/goog/html/safescript.js
4532 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview The SafeScript type and its builders.8*9* TODO(xtof): Link to document stating type contract.10*/1112goog.module('goog.html.SafeScript');13goog.module.declareLegacyNamespace();1415const Const = goog.require('goog.string.Const');16const TypedString = goog.require('goog.string.TypedString');17const trustedtypes = goog.require('goog.html.trustedtypes');18const {fail} = goog.require('goog.asserts');19const utils = goog.require('goog.utils');2021/**22* Token used to ensure that object is created only from this file. No code23* outside of this file can access this token.24* @const {!Object}25*/26const CONSTRUCTOR_TOKEN_PRIVATE = {};2728/**29* A string-like object which represents JavaScript code and that carries the30* security type contract that its value, as a string, will not cause execution31* of unconstrained attacker controlled code (XSS) when evaluated as JavaScript32* in a browser.33*34* Instances of this type must be created via the factory method35* `SafeScript.fromConstant` and not by invoking its constructor. The36* constructor intentionally takes an extra parameter that cannot be constructed37* outside of this file and the type is immutable; hence only a default instance38* corresponding to the empty string can be obtained via constructor invocation.39*40* A SafeScript's string representation can safely be interpolated as the41* content of a script element within HTML. The SafeScript string should not be42* escaped before interpolation.43*44* Note that the SafeScript might contain text that is attacker-controlled but45* that text should have been interpolated with appropriate escaping,46* sanitization and/or validation into the right location in the script, such47* that it is highly constrained in its effect (for example, it had to match a48* set of whitelisted words).49*50* A SafeScript can be constructed via security-reviewed unchecked51* conversions. In this case producers of SafeScript must ensure themselves that52* the SafeScript does not contain unsafe script. Note in particular that53* `<` is dangerous, even when inside JavaScript strings, and so should54* always be forbidden or JavaScript escaped in user controlled input. For55* example, if `</script><script>evil</script>"` were56* interpolated inside a JavaScript string, it would break out of the context57* of the original script element and `evil` would execute. Also note58* that within an HTML script (raw text) element, HTML character references,59* such as "<" are not allowed. See60* http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements.61* Creating SafeScript objects HAS SIDE-EFFECTS due to calling Trusted Types Web62* API.63*64* @see SafeScript#fromConstant65* @final66* @implements {TypedString}67*/68class SafeScript {69/**70* @param {!TrustedScript|string} value71* @param {!Object} token package-internal implementation detail.72*/73constructor(value, token) {74if (goog.DEBUG && token !== CONSTRUCTOR_TOKEN_PRIVATE) {75throw Error('SafeScript is not meant to be built directly');76}7778/**79* The contained value of this SafeScript. The field has a purposely ugly80* name to make (non-compiled) code that attempts to directly access this81* field stand out.82* @const83* @private {!TrustedScript|string}84*/85this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = value;8687/**88* @override89* @const90*/91this.implementsGoogStringTypedString = true;92}9394/**95* Returns a string-representation of this value.96*97* To obtain the actual string value wrapped in a SafeScript, use98* `SafeScript.unwrap`.99*100* @return {string}101* @see SafeScript#unwrap102* @override103*/104toString() {105return this.privateDoNotAccessOrElseSafeScriptWrappedValue_.toString();106}107108/**109* Creates a SafeScript object from a compile-time constant string.110*111* @param {!Const} script A compile-time-constant string from which to create112* a SafeScript.113* @return {!SafeScript} A SafeScript object initialized to `script`.114*/115static fromConstant(script) {116const scriptString = Const.unwrap(script);117if (scriptString.length === 0) {118return SafeScript.EMPTY;119}120return SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(121scriptString);122}123124/**125* Creates a SafeScript JSON representation from anything that could be passed126* to JSON.stringify.127* @param {*} val128* @return {!SafeScript}129*/130static fromJson(val) {131return SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(132SafeScript.stringify_(val));133}134135/**136* Returns this SafeScript's value as a string.137*138* IMPORTANT: In code where it is security relevant that an object's type is139* indeed `SafeScript`, use `SafeScript.unwrap` instead of140* this method. If in doubt, assume that it's security relevant. In141* particular, note that goog.html functions which return a goog.html type do142* not guarantee the returned instance is of the right type. For example:143*144* <pre>145* var fakeSafeHtml = new String('fake');146* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;147* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);148* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by149* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml150* // instanceof goog.html.SafeHtml.151* </pre>152*153* @see SafeScript#unwrap154* @override155*/156getTypedStringValue() {157return this.privateDoNotAccessOrElseSafeScriptWrappedValue_.toString();158}159160/**161* Performs a runtime check that the provided object is indeed a162* SafeScript object, and returns its value.163*164* @param {!SafeScript} safeScript The object to extract from.165* @return {string} The safeScript object's contained string, unless166* the run-time type check fails. In that case, `unwrap` returns an167* innocuous string, or, if assertions are enabled, throws168* `asserts.AssertionError`.169*/170static unwrap(safeScript) {171return SafeScript.unwrapTrustedScript(safeScript).toString();172}173174/**175* Unwraps value as TrustedScript if supported or as a string if not.176* @param {!SafeScript} safeScript177* @return {!TrustedScript|string}178* @see SafeScript.unwrap179*/180static unwrapTrustedScript(safeScript) {181// Perform additional Run-time type-checking to ensure that182// safeScript is indeed an instance of the expected type. This183// provides some additional protection against security bugs due to184// application code that disables type checks.185// Specifically, the following checks are performed:186// 1. The object is an instance of the expected type.187// 2. The object is not an instance of a subclass.188if (safeScript instanceof SafeScript &&189safeScript.constructor === SafeScript) {190return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_;191} else {192fail(193'expected object of type SafeScript, got \'' + safeScript +194'\' of type ' + utils.typeOf(safeScript));195return 'type_error:SafeScript';196}197}198199/**200* Converts the given value to an embeddable JSON string and returns it. The201* resulting string can be embedded in HTML because the '<' character is202* encoded.203*204* @param {*} val205* @return {string}206* @private207*/208static stringify_(val) {209const json = JSON.stringify(val);210return json.replace(/</g, '\\x3c');211}212213/**214* Package-internal utility method to create SafeScript instances.215*216* @param {string} script The string to initialize the SafeScript object with.217* @return {!SafeScript} The initialized SafeScript object.218* @package219*/220static createSafeScriptSecurityPrivateDoNotAccessOrElse(script) {221/** @noinline */222const noinlineScript = script;223const policy = trustedtypes.getPolicyPrivateDoNotAccessOrElse();224const trustedScript =225policy ? policy.createScript(noinlineScript) : noinlineScript;226return new SafeScript(trustedScript, CONSTRUCTOR_TOKEN_PRIVATE);227}228}229230/**231* A SafeScript instance corresponding to the empty string.232* @const {!SafeScript}233*/234SafeScript.EMPTY = /** @type {!SafeScript} */ ({235// NOTE: this compiles to nothing, but hides the possible side effect of236// SafeScript creation (due to calling trustedTypes.createPolicy) from the237// compiler so that the entire call can be removed if the result is not used.238valueOf: function() {239return SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse('');240},241}.valueOf());242243244exports = SafeScript;245246247