Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/html/sanitizer/csssanitizer.js
2868 views
1
// Copyright 2016 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview
17
* JavaScript support for client-side CSS sanitization.
18
*
19
* @author [email protected] (Danesh Irani)
20
* @author [email protected] (Mike Samuel)
21
*/
22
23
goog.provide('goog.html.sanitizer.CssSanitizer');
24
25
goog.require('goog.array');
26
goog.require('goog.dom');
27
goog.require('goog.dom.TagName');
28
goog.require('goog.html.SafeStyle');
29
goog.require('goog.html.SafeUrl');
30
goog.require('goog.html.uncheckedconversions');
31
goog.require('goog.object');
32
goog.require('goog.string');
33
34
35
/**
36
* The set of characters that need to be normalized inside url("...").
37
* We normalize newlines because they are not allowed inside quoted strings,
38
* normalize quote characters, angle-brackets, and asterisks because they
39
* could be used to break out of the URL or introduce targets for CSS
40
* error recovery. We normalize parentheses since they delimit unquoted
41
* URLs and calls and could be a target for error recovery.
42
* @const @private {!RegExp}
43
*/
44
goog.html.sanitizer.CssSanitizer.NORM_URL_REGEXP_ = /[\n\f\r\"\'()*<>]/g;
45
46
47
/**
48
* The replacements for NORM_URL_REGEXP.
49
* @private @const {!Object<string, string>}
50
*/
51
goog.html.sanitizer.CssSanitizer.NORM_URL_REPLACEMENTS_ = {
52
'\n': '%0a',
53
'\f': '%0c',
54
'\r': '%0d',
55
'"': '%22',
56
'\'': '%27',
57
'(': '%28',
58
')': '%29',
59
'*': '%2a',
60
'<': '%3c',
61
'>': '%3e'
62
};
63
64
65
/**
66
* Normalizes a character for use in a url() directive.
67
* @param {string} ch Character to be normalized.
68
* @return {?string} Normalized character.
69
* @private
70
*/
71
goog.html.sanitizer.CssSanitizer.normalizeUrlChar_ = function(ch) {
72
return goog.html.sanitizer.CssSanitizer.NORM_URL_REPLACEMENTS_[ch] || null;
73
};
74
75
76
/**
77
* Constructs a safe URI from a given URI and prop using a given uriRewriter
78
* function.
79
* @param {string} uri URI to be sanitized.
80
* @param {string} propName Property name which contained the URI.
81
* @param {?function(string, string):?goog.html.SafeUrl} uriRewriter A URI
82
* rewriter that returns a goog.html.SafeUrl.
83
* @return {?string} Safe URI for use in CSS.
84
* @private
85
*/
86
goog.html.sanitizer.CssSanitizer.getSafeUri_ = function(
87
uri, propName, uriRewriter) {
88
if (!uriRewriter) {
89
return null;
90
}
91
var safeUri = uriRewriter(uri, propName);
92
if (safeUri &&
93
goog.html.SafeUrl.unwrap(safeUri) != goog.html.SafeUrl.INNOCUOUS_STRING) {
94
return 'url("' +
95
goog.html.SafeUrl.unwrap(safeUri).replace(
96
goog.html.sanitizer.CssSanitizer.NORM_URL_REGEXP_,
97
goog.html.sanitizer.CssSanitizer.normalizeUrlChar_) +
98
'")';
99
}
100
return null;
101
};
102
103
104
/**
105
* Used to detect the beginning of the argument list of a CSS property value
106
* containing a CSS function call.
107
* @private @const {string}
108
*/
109
goog.html.sanitizer.CssSanitizer.FUNCTION_ARGUMENTS_BEGIN_ = '(';
110
111
112
/**
113
* Used to detect the end of the argument list of a CSS property value
114
* containing a CSS function call.
115
* @private @const {string}
116
*/
117
goog.html.sanitizer.CssSanitizer.FUNCTION_ARGUMENTS_END_ = ')';
118
119
120
/**
121
* Allowed CSS functions
122
* @const @private {!Array<string>}
123
*/
124
goog.html.sanitizer.CssSanitizer.ALLOWED_FUNCTIONS_ = [
125
'rgb',
126
'rgba',
127
'alpha',
128
'rect',
129
'image',
130
'linear-gradient',
131
'radial-gradient',
132
'repeating-linear-gradient',
133
'repeating-radial-gradient',
134
'cubic-bezier',
135
'matrix',
136
'perspective',
137
'rotate',
138
'rotate3d',
139
'rotatex',
140
'rotatey',
141
'steps',
142
'rotatez',
143
'scale',
144
'scale3d',
145
'scalex',
146
'scaley',
147
'scalez',
148
'skew',
149
'skewx',
150
'skewy',
151
'translate',
152
'translate3d',
153
'translatex',
154
'translatey',
155
'translatez'
156
];
157
158
159
/**
160
* Removes a vendor prefix from a property name.
161
* @param {string} propName A property name.
162
* @return {string} A property name without vendor prefixes.
163
* @private
164
*/
165
goog.html.sanitizer.CssSanitizer.withoutVendorPrefix_ = function(propName) {
166
// http://stackoverflow.com/a/5411098/20394 has a fairly extensive list
167
// of vendor prefices. Blink has not declared a vendor prefix distinct from
168
// -webkit- and http://css-tricks.com/tldr-on-vendor-prefix-drama/ discusses
169
// how Mozilla recognizes some -webkit- prefixes.
170
// http://wiki.csswg.org/spec/vendor-prefixes talks more about
171
// cross-implementation, and lists other prefixes.
172
return propName.replace(
173
/^-(?:apple|css|epub|khtml|moz|mso?|o|rim|wap|webkit|xv)-(?=[a-z])/i, '');
174
};
175
176
177
/**
178
* Sanitizes the value for a given a browser-parsed CSS value.
179
* @param {string} propName A property name.
180
* @param {string} propValue Value of the property as parsed by the browser.
181
* @param {function(string, string):?goog.html.SafeUrl=} opt_uriRewriter A URI
182
* rewriter that returns an unwrapped goog.html.SafeUrl.
183
* @return {?string} Sanitized property value or null.
184
* @private
185
*/
186
goog.html.sanitizer.CssSanitizer.sanitizeProperty_ = function(
187
propName, propValue, opt_uriRewriter) {
188
var outputPropValue = goog.string.trim(propValue);
189
if (outputPropValue == '') {
190
return null;
191
}
192
193
if (goog.string.caseInsensitiveStartsWith(outputPropValue, 'url(')) {
194
// Urls are rewritten according to the policy implemented in
195
// opt_uriRewriter.
196
// TODO(pelizzi): use HtmlSanitizerUrlPolicy for opt_uriRewriter.
197
if (!opt_uriRewriter) {
198
return null;
199
}
200
// TODO(danesh): Check if we need to resolve this URI.
201
var uri = goog.string.stripQuotes(
202
outputPropValue.substring(4, outputPropValue.length - 1), '"\'');
203
204
return goog.html.sanitizer.CssSanitizer.getSafeUri_(
205
uri, propName, opt_uriRewriter);
206
} else if (outputPropValue.indexOf('(') > 0) {
207
// Functions are filtered through a whitelist. Nesting whitelisted functions
208
// is not supported.
209
if (goog.string.countOf(
210
outputPropValue,
211
goog.html.sanitizer.CssSanitizer.FUNCTION_ARGUMENTS_BEGIN_) > 1 ||
212
!(goog.array.contains(
213
goog.html.sanitizer.CssSanitizer.ALLOWED_FUNCTIONS_,
214
outputPropValue
215
.substring(
216
0,
217
outputPropValue.indexOf(goog.html.sanitizer.CssSanitizer
218
.FUNCTION_ARGUMENTS_BEGIN_))
219
.toLowerCase()) &&
220
goog.string.endsWith(
221
outputPropValue,
222
goog.html.sanitizer.CssSanitizer.FUNCTION_ARGUMENTS_END_))) {
223
// TODO(b/34222379): Handle functions that may need recursing or that may
224
// appear in the middle of a string. For now, just allow functions which
225
// aren't nested.
226
return null;
227
}
228
return outputPropValue;
229
} else {
230
// Everything else is allowed.
231
return outputPropValue;
232
}
233
};
234
235
236
/**
237
* Sanitizes an inline style attribute. Short-hand attributes are expanded to
238
* their individual elements. Note: The sanitizer does not output vendor
239
* prefixed styles.
240
* @param {?CSSStyleDeclaration} cssStyle A CSS style object.
241
* @param {function(string, string):?goog.html.SafeUrl=} opt_uriRewriter A URI
242
* rewriter that returns a goog.html.SafeUrl.
243
* @return {!goog.html.SafeStyle} A sanitized inline cssText.
244
*/
245
goog.html.sanitizer.CssSanitizer.sanitizeInlineStyle = function(
246
cssStyle, opt_uriRewriter) {
247
if (!cssStyle) {
248
return goog.html.SafeStyle.EMPTY;
249
}
250
251
var cleanCssStyle = document.createElement('div').style;
252
var cssPropNames =
253
goog.html.sanitizer.CssSanitizer.getCssPropNames_(cssStyle);
254
255
for (var i = 0; i < cssPropNames.length; i++) {
256
var propName =
257
goog.html.sanitizer.CssSanitizer.withoutVendorPrefix_(cssPropNames[i]);
258
if (!goog.html.sanitizer.CssSanitizer.isDisallowedPropertyName_(propName)) {
259
var propValue =
260
goog.html.sanitizer.CssSanitizer.getCssValue_(cssStyle, propName);
261
262
var sanitizedValue = goog.html.sanitizer.CssSanitizer.sanitizeProperty_(
263
propName, propValue, opt_uriRewriter);
264
goog.html.sanitizer.CssSanitizer.setCssValue_(
265
cleanCssStyle, propName, sanitizedValue);
266
}
267
}
268
return goog.html.uncheckedconversions
269
.safeStyleFromStringKnownToSatisfyTypeContract(
270
goog.string.Const.from('Output of CSS sanitizer'),
271
cleanCssStyle.cssText || '');
272
};
273
274
275
/**
276
* Sanitizes inline CSS text and returns it as a SafeStyle object. When adequate
277
* browser support is not available, such as for IE9 and below, a
278
* SafeStyle-wrapped empty string is returned.
279
* @param {string} cssText CSS text to be sanitized.
280
* @param {function(string, string):?goog.html.SafeUrl=} opt_uriRewriter A URI
281
* rewriter that returns a goog.html.SafeUrl.
282
* @return {!goog.html.SafeStyle} A sanitized inline cssText.
283
*/
284
goog.html.sanitizer.CssSanitizer.sanitizeInlineStyleString = function(
285
cssText, opt_uriRewriter) {
286
// same check as in goog.html.sanitizer.HTML_SANITIZER_SUPPORTED_
287
if (goog.userAgent.IE && document.documentMode < 10) {
288
return new goog.html.SafeStyle();
289
}
290
291
var div = goog.html.sanitizer.CssSanitizer
292
.createInertDocument_()
293
.createElement('DIV');
294
div.style.cssText = cssText;
295
return goog.html.sanitizer.CssSanitizer.sanitizeInlineStyle(
296
div.style, opt_uriRewriter);
297
};
298
299
300
/**
301
* Creates an DOM Document object that will not execute scripts or make
302
* network requests while parsing HTML.
303
* @return {!Document}
304
* @private
305
*/
306
goog.html.sanitizer.CssSanitizer.createInertDocument_ = function() {
307
// Documents created using window.document.implementation.createHTMLDocument()
308
// use the same custom component registry as their parent document. This means
309
// that parsing arbitrary HTML can result in calls to user-defined JavaScript.
310
// This is worked around by creating a template element and its content's
311
// document. See https://github.com/cure53/DOMPurify/issues/47.
312
var doc = document;
313
if (typeof HTMLTemplateElement === 'function') {
314
doc =
315
goog.dom.createElement(goog.dom.TagName.TEMPLATE).content.ownerDocument;
316
}
317
return doc.implementation.createHTMLDocument('');
318
};
319
320
321
/**
322
* Provides a cross-browser way to get a CSS property names.
323
* @param {!CSSStyleDeclaration} cssStyle A CSS style object.
324
* @return {!Array<string>} CSS property names.
325
* @private
326
*/
327
goog.html.sanitizer.CssSanitizer.getCssPropNames_ = function(cssStyle) {
328
var propNames = [];
329
if (goog.isArrayLike(cssStyle)) {
330
// Gets property names via item().
331
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-item
332
propNames = goog.array.toArray(cssStyle);
333
} else {
334
// In IE8 and other older browsers we have to iterate over all the property
335
// names. We skip cssText because it contains the unsanitized CSS, which
336
// defeats the purpose.
337
propNames = goog.object.getKeys(cssStyle);
338
goog.array.remove(propNames, 'cssText');
339
}
340
return propNames;
341
};
342
343
344
/**
345
* Provides a way to get a CSS value without falling prey to things like
346
* &lt;form&gt;&lt;input name="propertyValue"&gt;
347
* &lt;input name="propertyValue"&gt;&lt;/form&gt;. If not available,
348
* likely only older browsers, fallback to a direct call.
349
* @param {!CSSStyleDeclaration} cssStyle A CSS style object.
350
* @param {string} propName A property name.
351
* @return {string} Value of the property as parsed by the browser.
352
* @private
353
*/
354
goog.html.sanitizer.CssSanitizer.getCssValue_ = function(cssStyle, propName) {
355
var getPropDescriptor = Object.getOwnPropertyDescriptor(
356
CSSStyleDeclaration.prototype, 'getPropertyValue');
357
if (getPropDescriptor && cssStyle.getPropertyValue) {
358
// getPropertyValue on Safari can return null
359
return getPropDescriptor.value.call(cssStyle, propName) || '';
360
} else if (cssStyle.getAttribute) {
361
// In IE8 and other older browers we make a direct call to getAttribute.
362
return String(cssStyle.getAttribute(propName) || '');
363
} else {
364
// Unsupported, likely quite old, browser.
365
return '';
366
}
367
};
368
369
370
/**
371
* Provides a way to set a CSS value without falling prey to things like
372
* &lt;form&gt;&lt;input name="property"&gt;
373
* &lt;input name="property"&gt;&lt;/form&gt;. If not available,
374
* likely only older browsers, fallback to a direct call.
375
* @param {!CSSStyleDeclaration} cssStyle A CSS style object.
376
* @param {string} propName A property name.
377
* @param {?string} sanitizedValue Sanitized value of the property to be set
378
* on the CSS style object.
379
* @private
380
*/
381
goog.html.sanitizer.CssSanitizer.setCssValue_ = function(
382
cssStyle, propName, sanitizedValue) {
383
if (sanitizedValue) {
384
var setPropDescriptor = Object.getOwnPropertyDescriptor(
385
CSSStyleDeclaration.prototype, 'setProperty');
386
if (setPropDescriptor && cssStyle.setProperty) {
387
setPropDescriptor.value.call(cssStyle, propName, sanitizedValue);
388
} else if (cssStyle.setAttribute) {
389
// In IE8 and other older browers we make a direct call to setAttribute.
390
cssStyle.setAttribute(propName, sanitizedValue);
391
}
392
}
393
};
394
395
396
/**
397
* Checks whether the property name specified should be disallowed.
398
* @param {string} propName A property name.
399
* @return {boolean} Whether the property name is disallowed.
400
* @private
401
*/
402
goog.html.sanitizer.CssSanitizer.isDisallowedPropertyName_ = function(
403
propName) {
404
// getPropertyValue doesn't deal with custom variables properly and will NOT
405
// decode CSS escapes (but the browser will do so silently). Simply disallow
406
// custom variables (http://www.w3.org/TR/css-variables/#defining-variables).
407
return goog.string.startsWith(propName, '--') ||
408
goog.string.startsWith(propName, 'var');
409
};
410
411