Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/html/safescript.js
4532 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview The SafeScript type and its builders.
9
*
10
* TODO(xtof): Link to document stating type contract.
11
*/
12
13
goog.module('goog.html.SafeScript');
14
goog.module.declareLegacyNamespace();
15
16
const Const = goog.require('goog.string.Const');
17
const TypedString = goog.require('goog.string.TypedString');
18
const trustedtypes = goog.require('goog.html.trustedtypes');
19
const {fail} = goog.require('goog.asserts');
20
const utils = goog.require('goog.utils');
21
22
/**
23
* Token used to ensure that object is created only from this file. No code
24
* outside of this file can access this token.
25
* @const {!Object}
26
*/
27
const CONSTRUCTOR_TOKEN_PRIVATE = {};
28
29
/**
30
* A string-like object which represents JavaScript code and that carries the
31
* security type contract that its value, as a string, will not cause execution
32
* of unconstrained attacker controlled code (XSS) when evaluated as JavaScript
33
* in a browser.
34
*
35
* Instances of this type must be created via the factory method
36
* `SafeScript.fromConstant` and not by invoking its constructor. The
37
* constructor intentionally takes an extra parameter that cannot be constructed
38
* outside of this file and the type is immutable; hence only a default instance
39
* corresponding to the empty string can be obtained via constructor invocation.
40
*
41
* A SafeScript's string representation can safely be interpolated as the
42
* content of a script element within HTML. The SafeScript string should not be
43
* escaped before interpolation.
44
*
45
* Note that the SafeScript might contain text that is attacker-controlled but
46
* that text should have been interpolated with appropriate escaping,
47
* sanitization and/or validation into the right location in the script, such
48
* that it is highly constrained in its effect (for example, it had to match a
49
* set of whitelisted words).
50
*
51
* A SafeScript can be constructed via security-reviewed unchecked
52
* conversions. In this case producers of SafeScript must ensure themselves that
53
* the SafeScript does not contain unsafe script. Note in particular that
54
* `<` is dangerous, even when inside JavaScript strings, and so should
55
* always be forbidden or JavaScript escaped in user controlled input. For
56
* example, if `</script><script>evil</script>"` were
57
* interpolated inside a JavaScript string, it would break out of the context
58
* of the original script element and `evil` would execute. Also note
59
* that within an HTML script (raw text) element, HTML character references,
60
* such as "<" are not allowed. See
61
* http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements.
62
* Creating SafeScript objects HAS SIDE-EFFECTS due to calling Trusted Types Web
63
* API.
64
*
65
* @see SafeScript#fromConstant
66
* @final
67
* @implements {TypedString}
68
*/
69
class SafeScript {
70
/**
71
* @param {!TrustedScript|string} value
72
* @param {!Object} token package-internal implementation detail.
73
*/
74
constructor(value, token) {
75
if (goog.DEBUG && token !== CONSTRUCTOR_TOKEN_PRIVATE) {
76
throw Error('SafeScript is not meant to be built directly');
77
}
78
79
/**
80
* The contained value of this SafeScript. The field has a purposely ugly
81
* name to make (non-compiled) code that attempts to directly access this
82
* field stand out.
83
* @const
84
* @private {!TrustedScript|string}
85
*/
86
this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = value;
87
88
/**
89
* @override
90
* @const
91
*/
92
this.implementsGoogStringTypedString = true;
93
}
94
95
/**
96
* Returns a string-representation of this value.
97
*
98
* To obtain the actual string value wrapped in a SafeScript, use
99
* `SafeScript.unwrap`.
100
*
101
* @return {string}
102
* @see SafeScript#unwrap
103
* @override
104
*/
105
toString() {
106
return this.privateDoNotAccessOrElseSafeScriptWrappedValue_.toString();
107
}
108
109
/**
110
* Creates a SafeScript object from a compile-time constant string.
111
*
112
* @param {!Const} script A compile-time-constant string from which to create
113
* a SafeScript.
114
* @return {!SafeScript} A SafeScript object initialized to `script`.
115
*/
116
static fromConstant(script) {
117
const scriptString = Const.unwrap(script);
118
if (scriptString.length === 0) {
119
return SafeScript.EMPTY;
120
}
121
return SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
122
scriptString);
123
}
124
125
/**
126
* Creates a SafeScript JSON representation from anything that could be passed
127
* to JSON.stringify.
128
* @param {*} val
129
* @return {!SafeScript}
130
*/
131
static fromJson(val) {
132
return SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
133
SafeScript.stringify_(val));
134
}
135
136
/**
137
* Returns this SafeScript's value as a string.
138
*
139
* IMPORTANT: In code where it is security relevant that an object's type is
140
* indeed `SafeScript`, use `SafeScript.unwrap` instead of
141
* this method. If in doubt, assume that it's security relevant. In
142
* particular, note that goog.html functions which return a goog.html type do
143
* not guarantee the returned instance is of the right type. For example:
144
*
145
* <pre>
146
* var fakeSafeHtml = new String('fake');
147
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
148
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
149
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
150
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
151
* // instanceof goog.html.SafeHtml.
152
* </pre>
153
*
154
* @see SafeScript#unwrap
155
* @override
156
*/
157
getTypedStringValue() {
158
return this.privateDoNotAccessOrElseSafeScriptWrappedValue_.toString();
159
}
160
161
/**
162
* Performs a runtime check that the provided object is indeed a
163
* SafeScript object, and returns its value.
164
*
165
* @param {!SafeScript} safeScript The object to extract from.
166
* @return {string} The safeScript object's contained string, unless
167
* the run-time type check fails. In that case, `unwrap` returns an
168
* innocuous string, or, if assertions are enabled, throws
169
* `asserts.AssertionError`.
170
*/
171
static unwrap(safeScript) {
172
return SafeScript.unwrapTrustedScript(safeScript).toString();
173
}
174
175
/**
176
* Unwraps value as TrustedScript if supported or as a string if not.
177
* @param {!SafeScript} safeScript
178
* @return {!TrustedScript|string}
179
* @see SafeScript.unwrap
180
*/
181
static unwrapTrustedScript(safeScript) {
182
// Perform additional Run-time type-checking to ensure that
183
// safeScript is indeed an instance of the expected type. This
184
// provides some additional protection against security bugs due to
185
// application code that disables type checks.
186
// Specifically, the following checks are performed:
187
// 1. The object is an instance of the expected type.
188
// 2. The object is not an instance of a subclass.
189
if (safeScript instanceof SafeScript &&
190
safeScript.constructor === SafeScript) {
191
return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_;
192
} else {
193
fail(
194
'expected object of type SafeScript, got \'' + safeScript +
195
'\' of type ' + utils.typeOf(safeScript));
196
return 'type_error:SafeScript';
197
}
198
}
199
200
/**
201
* Converts the given value to an embeddable JSON string and returns it. The
202
* resulting string can be embedded in HTML because the '<' character is
203
* encoded.
204
*
205
* @param {*} val
206
* @return {string}
207
* @private
208
*/
209
static stringify_(val) {
210
const json = JSON.stringify(val);
211
return json.replace(/</g, '\\x3c');
212
}
213
214
/**
215
* Package-internal utility method to create SafeScript instances.
216
*
217
* @param {string} script The string to initialize the SafeScript object with.
218
* @return {!SafeScript} The initialized SafeScript object.
219
* @package
220
*/
221
static createSafeScriptSecurityPrivateDoNotAccessOrElse(script) {
222
/** @noinline */
223
const noinlineScript = script;
224
const policy = trustedtypes.getPolicyPrivateDoNotAccessOrElse();
225
const trustedScript =
226
policy ? policy.createScript(noinlineScript) : noinlineScript;
227
return new SafeScript(trustedScript, CONSTRUCTOR_TOKEN_PRIVATE);
228
}
229
}
230
231
/**
232
* A SafeScript instance corresponding to the empty string.
233
* @const {!SafeScript}
234
*/
235
SafeScript.EMPTY = /** @type {!SafeScript} */ ({
236
// NOTE: this compiles to nothing, but hides the possible side effect of
237
// SafeScript creation (due to calling trustedTypes.createPolicy) from the
238
// compiler so that the entire call can be removed if the result is not used.
239
valueOf: function() {
240
return SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse('');
241
},
242
}.valueOf());
243
244
245
exports = SafeScript;
246
247