Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/html/safestylesheet.js
4560 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview The SafeStyleSheet type and its builders.
9
*
10
* TODO(xtof): Link to document stating type contract.
11
*/
12
13
goog.module('goog.html.SafeStyleSheet');
14
goog.module.declareLegacyNamespace();
15
16
const Const = goog.require('goog.string.Const');
17
const SafeStyle = goog.require('goog.html.SafeStyle');
18
const TypedString = goog.require('goog.string.TypedString');
19
const googObject = goog.require('goog.object');
20
const {assert, fail} = goog.require('goog.asserts');
21
const {contains} = goog.require('goog.string.internal');
22
const utils = goog.require('goog.utils');
23
24
/**
25
* Token used to ensure that object is created only from this file. No code
26
* outside of this file can access this token.
27
* @const {!Object}
28
*/
29
const CONSTRUCTOR_TOKEN_PRIVATE = {};
30
31
/**
32
* A string-like object which represents a CSS style sheet and that carries the
33
* security type contract that its value, as a string, will not cause untrusted
34
* script execution (XSS) when evaluated as CSS in a browser.
35
*
36
* Instances of this type must be created via the factory method
37
* `SafeStyleSheet.fromConstant` and not by invoking its constructor. The
38
* constructor intentionally takes an extra parameter that cannot be constructed
39
* outside of this file and the type is immutable; hence only a default instance
40
* corresponding to the empty string can be obtained via constructor invocation.
41
*
42
* A SafeStyleSheet's string representation can safely be interpolated as the
43
* content of a style element within HTML. The SafeStyleSheet string should
44
* not be escaped before interpolation.
45
*
46
* Values of this type must be composable, i.e. for any two values
47
* `styleSheet1` and `styleSheet2` of this type,
48
* `SafeStyleSheet.unwrap(styleSheet1) + SafeStyleSheet.unwrap(styleSheet2)`
49
* must itself be a value that satisfies the SafeStyleSheet type constraint.
50
* This requirement implies that for any value `styleSheet` of this type,
51
* `SafeStyleSheet.unwrap(styleSheet1)` must end in
52
* "beginning of rule" context.
53
*
54
* A SafeStyleSheet can be constructed via security-reviewed unchecked
55
* conversions. In this case producers of SafeStyleSheet must ensure themselves
56
* that the SafeStyleSheet does not contain unsafe script. Note in particular
57
* that `<` is dangerous, even when inside CSS strings, and so should
58
* always be forbidden or CSS-escaped in user controlled input. For example, if
59
* `</style><script>evil</script>"` were interpolated
60
* inside a CSS string, it would break out of the context of the original
61
* style element and `evil` would execute. Also note that within an HTML
62
* style (raw text) element, HTML character references, such as
63
* `<`, are not allowed. See
64
* http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements
65
* (similar considerations apply to the style element).
66
*
67
* @see SafeStyleSheet#fromConstant
68
* @final
69
* @implements {TypedString}
70
*/
71
class SafeStyleSheet {
72
/**
73
* @param {string} value
74
* @param {!Object} token package-internal implementation detail.
75
*/
76
constructor(value, token) {
77
if (goog.DEBUG && token !== CONSTRUCTOR_TOKEN_PRIVATE) {
78
throw Error('SafeStyleSheet is not meant to be built directly');
79
}
80
81
/**
82
* The contained value of this SafeStyleSheet. The field has a purposely
83
* ugly name to make (non-compiled) code that attempts to directly access
84
* this field stand out.
85
* @const
86
* @private {string}
87
*/
88
this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = value;
89
90
/**
91
* @override
92
* @const
93
*/
94
this.implementsGoogStringTypedString = true;
95
}
96
97
/**
98
* Returns a string-representation of this value.
99
*
100
* To obtain the actual string value wrapped in a SafeStyleSheet, use
101
* `SafeStyleSheet.unwrap`.
102
*
103
* @return {string}
104
* @see SafeStyleSheet#unwrap
105
* @override
106
*/
107
toString() {
108
return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_.toString();
109
}
110
111
/**
112
* Creates a style sheet consisting of one selector and one style definition.
113
* Use {@link SafeStyleSheet.concat} to create longer style sheets.
114
* This function doesn't support @import, @media and similar constructs.
115
* @param {string} selector CSS selector, e.g. '#id' or 'tag .class, #id'. We
116
* support CSS3 selectors: https://w3.org/TR/css3-selectors/#selectors.
117
* @param {!SafeStyle.PropertyMap|!SafeStyle} style Style
118
* definition associated with the selector.
119
* @return {!SafeStyleSheet}
120
* @throws {!Error} If invalid selector is provided.
121
*/
122
static createRule(selector, style) {
123
if (contains(selector, '<')) {
124
throw new Error(`Selector does not allow '<', got: ${selector}`);
125
}
126
127
// Remove strings.
128
const selectorToCheck =
129
selector.replace(/('|")((?!\1)[^\r\n\f\\]|\\[\s\S])*\1/g, '');
130
131
// Check characters allowed in CSS3 selectors.
132
if (!/^[-_a-zA-Z0-9#.:* ,>+~[\]()=\\^$|]+$/.test(selectorToCheck)) {
133
throw new Error(
134
'Selector allows only [-_a-zA-Z0-9#.:* ,>+~[\\]()=\\^$|] and ' +
135
'strings, got: ' + selector);
136
}
137
138
// Check balanced () and [].
139
if (!SafeStyleSheet.hasBalancedBrackets_(selectorToCheck)) {
140
throw new Error(
141
'() and [] in selector must be balanced, got: ' + selector);
142
}
143
144
if (!(style instanceof SafeStyle)) {
145
style = SafeStyle.create(style);
146
}
147
const styleSheet =
148
`${selector}{` + SafeStyle.unwrap(style).replace(/</g, '\\3C ') + '}';
149
return SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(
150
styleSheet);
151
}
152
153
/**
154
* Checks if a string has balanced () and [] brackets.
155
* @param {string} s String to check.
156
* @return {boolean}
157
* @private
158
*/
159
static hasBalancedBrackets_(s) {
160
const brackets = {'(': ')', '[': ']'};
161
const expectedBrackets = [];
162
for (let i = 0; i < s.length; i++) {
163
const ch = s[i];
164
if (brackets[ch]) {
165
expectedBrackets.push(brackets[ch]);
166
} else if (googObject.contains(brackets, ch)) {
167
if (expectedBrackets.pop() != ch) {
168
return false;
169
}
170
}
171
}
172
return expectedBrackets.length == 0;
173
}
174
175
/**
176
* Creates a new SafeStyleSheet object by concatenating values.
177
* @param {...(!SafeStyleSheet|!Array<!SafeStyleSheet>)}
178
* var_args Values to concatenate.
179
* @return {!SafeStyleSheet}
180
*/
181
static concat(var_args) {
182
let result = '';
183
184
/**
185
* @param {!SafeStyleSheet|!Array<!SafeStyleSheet>}
186
* argument
187
*/
188
const addArgument = argument => {
189
if (Array.isArray(argument)) {
190
argument.forEach(addArgument);
191
} else {
192
result += SafeStyleSheet.unwrap(argument);
193
}
194
};
195
196
Array.prototype.forEach.call(arguments, addArgument);
197
return SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(
198
result);
199
}
200
201
/**
202
* Creates a SafeStyleSheet object from a compile-time constant string.
203
*
204
* `styleSheet` must not have any &lt; characters in it, so that
205
* the syntactic structure of the surrounding HTML is not affected.
206
*
207
* @param {!Const} styleSheet A compile-time-constant string from
208
* which to create a SafeStyleSheet.
209
* @return {!SafeStyleSheet} A SafeStyleSheet object initialized to
210
* `styleSheet`.
211
*/
212
static fromConstant(styleSheet) {
213
const styleSheetString = Const.unwrap(styleSheet);
214
if (styleSheetString.length === 0) {
215
return SafeStyleSheet.EMPTY;
216
}
217
// > is a valid character in CSS selectors and there's no strict need to
218
// block it if we already block <.
219
assert(
220
!contains(styleSheetString, '<'),
221
`Forbidden '<' character in style sheet string: ${styleSheetString}`);
222
return SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(
223
styleSheetString);
224
}
225
226
/**
227
* Returns this SafeStyleSheet's value as a string.
228
*
229
* IMPORTANT: In code where it is security relevant that an object's type is
230
* indeed `SafeStyleSheet`, use `SafeStyleSheet.unwrap`
231
* instead of this method. If in doubt, assume that it's security relevant. In
232
* particular, note that goog.html functions which return a goog.html type do
233
* not guarantee the returned instance is of the right type. For example:
234
*
235
* <pre>
236
* var fakeSafeHtml = new String('fake');
237
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
238
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
239
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
240
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
241
* // instanceof goog.html.SafeHtml.
242
* </pre>
243
*
244
* @see SafeStyleSheet#unwrap
245
* @override
246
*/
247
getTypedStringValue() {
248
return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
249
}
250
251
/**
252
* Performs a runtime check that the provided object is indeed a
253
* SafeStyleSheet object, and returns its value.
254
*
255
* @param {!SafeStyleSheet} safeStyleSheet The object to extract from.
256
* @return {string} The safeStyleSheet object's contained string, unless
257
* the run-time type check fails. In that case, `unwrap` returns an
258
* innocuous string, or, if assertions are enabled, throws
259
* `asserts.AssertionError`.
260
*/
261
static unwrap(safeStyleSheet) {
262
// Perform additional Run-time type-checking to ensure that
263
// safeStyleSheet is indeed an instance of the expected type. This
264
// provides some additional protection against security bugs due to
265
// application code that disables type checks.
266
// Specifically, the following checks are performed:
267
// 1. The object is an instance of the expected type.
268
// 2. The object is not an instance of a subclass.
269
if (safeStyleSheet instanceof SafeStyleSheet &&
270
safeStyleSheet.constructor === SafeStyleSheet) {
271
return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
272
} else {
273
fail(
274
'expected object of type SafeStyleSheet, got \'' + safeStyleSheet +
275
'\' of type ' + utils.typeOf(safeStyleSheet));
276
return 'type_error:SafeStyleSheet';
277
}
278
}
279
280
/**
281
* Package-internal utility method to create SafeStyleSheet instances.
282
*
283
* @param {string} styleSheet The string to initialize the SafeStyleSheet
284
* object with.
285
* @return {!SafeStyleSheet} The initialized SafeStyleSheet object.
286
* @package
287
*/
288
static createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet) {
289
return new SafeStyleSheet(styleSheet, CONSTRUCTOR_TOKEN_PRIVATE);
290
}
291
}
292
293
/**
294
* A SafeStyleSheet instance corresponding to the empty string.
295
* @const {!SafeStyleSheet}
296
*/
297
SafeStyleSheet.EMPTY =
298
SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse('');
299
300
301
exports = SafeStyleSheet;
302
303