Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/html/sanitizer/csssanitizer_test.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
/** @fileoverview testcases for CSS Sanitizer.*/
16
17
goog.setTestOnly();
18
19
goog.require('goog.array');
20
goog.require('goog.html.SafeStyle');
21
goog.require('goog.html.SafeUrl');
22
goog.require('goog.html.sanitizer.CssSanitizer');
23
goog.require('goog.html.testing');
24
goog.require('goog.string');
25
goog.require('goog.testing.jsunit');
26
goog.require('goog.userAgent');
27
goog.require('goog.userAgent.product');
28
29
30
/**
31
* @return {boolean} Returns if the browser is IE8.
32
* @private
33
*/
34
function isIE8() {
35
return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(9);
36
}
37
38
39
/**
40
* @return {boolean} Returns if the browser is Safari.
41
* @private
42
*/
43
function isSafari() {
44
return goog.userAgent.product.SAFARI;
45
}
46
47
48
/**
49
* @param {string} cssText CSS text usually associated with an inline style.
50
* @return {!CSSStyleDeclaration} A styleSheet object.
51
*/
52
function getStyleFromCssText(cssText) {
53
var styleDecleration = document.createElement('div').style;
54
styleDecleration.cssText = cssText || '';
55
return styleDecleration;
56
}
57
58
59
/**
60
* Asserts that the expected CSS text is equal to the actual CSS text.
61
* @param {string} expectedCssText Expected CSS text.
62
* @param {string} actualCssText Actual CSS text.
63
*/
64
function assertCSSTextEquals(expectedCssText, actualCssText) {
65
if (isIE8()) {
66
// We get a bunch of default values set in IE8 because of the way we iterate
67
// over the CSSStyleDecleration keys.
68
// TODO(danesh): Fix IE8 or remove this hack. It will be problematic for
69
// tests which have an extra semi-colon in the value (even if quoted).
70
var actualCssArry = actualCssText.split(/\s*;\s*/);
71
var ie8StyleString = 'WIDTH: 0px; BOTTOM: 0px; HEIGHT: 0px; TOP: 0px; ' +
72
'RIGHT: 0px; TEXT-DECORATION: none underline overline line-through; ' +
73
'LEFT: 0px; TEXT-DECORATION: underline line-through;';
74
goog.array.forEach(ie8StyleString.split(/\s*;\s*/), function(ie8Css) {
75
goog.array.remove(actualCssArry, ie8Css);
76
});
77
actualCssText = actualCssArry.join('; ');
78
}
79
assertEquals(
80
getStyleFromCssText(expectedCssText).cssText,
81
getStyleFromCssText(actualCssText).cssText);
82
}
83
84
/**
85
* Gets sanitized inline style.
86
* @param {string} sourceCss CSS to be sanitized.
87
* @param {function (string, string):?goog.html.SafeUrl=} opt_urlRewrite URL
88
* rewriter that only returns a goog.html.SafeUrl.
89
* @return {string} Sanitized inline style.
90
* @private
91
*/
92
function getSanitizedInlineStyle(sourceCss, opt_urlRewrite) {
93
try {
94
return goog.html.SafeStyle.unwrap(
95
goog.html.sanitizer.CssSanitizer.sanitizeInlineStyle(
96
getStyleFromCssText(sourceCss), opt_urlRewrite)) ||
97
'';
98
} catch (err) {
99
// IE8 doesn't like setting invalid properties. It throws an "Invalid
100
// Argument" exception.
101
if (!isIE8()) {
102
throw err;
103
}
104
return '';
105
}
106
}
107
108
109
function testValidCss() {
110
var actualCSS = 'font-family: inherit';
111
var expectedCSS = 'font-family: inherit';
112
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
113
114
// .1 -> 0.1; 1.0 -> 1
115
actualCSS = 'padding: 1pt .1pt 1pt 1.0em';
116
expectedCSS = 'padding: 1pt 0.1pt 1pt 1em';
117
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
118
119
// Negative margins are allowed.
120
actualCSS = 'margin: -7px -.5px -23px -1.25px';
121
expectedCSS = 'margin: -7px -0.5px -23px -1.25px';
122
if (isIE8()) {
123
// IE8 doesn't like sub-pixels
124
// https://blogs.msdn.microsoft.com/ie/2010/11/03/sub-pixel-fonts-in-ie9/
125
expectedCSS = expectedCSS.replace('-0.5px', '0px');
126
expectedCSS = expectedCSS.replace('-1.25px', '-1px');
127
}
128
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
129
130
actualCSS = 'quotes: "{" "}" "<" ">"';
131
expectedCSS = 'quotes: "{" "}" "<" ">";';
132
if (isSafari()) {
133
// TODO(danesh): Figure out what is wrong with WebKit (Safari).
134
expectedCSS = 'quotes: \'{\';';
135
}
136
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
137
}
138
139
140
function testInvalidCssRemoved() {
141
var actualCSS;
142
143
// Tests all have null results.
144
var expectedCSS = '';
145
146
actualCSS = 'font: Arial Black,monospace,Helvetica,#88ff88';
147
// Hash values are not allowed so are dropped.
148
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
149
150
// Negative numbers for border not allowed.
151
actualCSS = 'border : -7px -0.5px -23px -1.25px';
152
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
153
154
// Negative numbers converted to empty.
155
actualCSS = 'padding: -0 -.0 -0. -0.0 ';
156
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
157
158
// Invalid values not allowed.
159
actualCSS = 'padding : #123 - 5 "5"';
160
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
161
162
// Font-family does not allow quantities at all.
163
actualCSS = 'font-family: 7 .5 23 1.25 -7 -.5 -23 -1.25 +7 +.5 +23 +1.25 ' +
164
'7cm .5em 23.mm 1.25px -7cm -.5em -23.mm -1.25px ' +
165
'+7cm +.5em +23.mm +1.25px 0 .0 -0+00.0 /';
166
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
167
168
actualCSS = 'background: bogus url("foo.png") transparent';
169
assertCSSTextEquals(
170
expectedCSS,
171
getSanitizedInlineStyle(actualCSS, goog.html.SafeUrl.sanitize));
172
173
// expression(...) is not allowed for font so is rejected wholesale -- the
174
// internal string "pwned" is not passed through.
175
actualCSS = 'font-family: Arial Black,monospace,expression(return "pwned"),' +
176
'Helvetica,#88ff88';
177
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
178
}
179
180
181
function testCssBackground() {
182
var actualCSS, expectedCSS;
183
184
function proxyUrl(url) {
185
return goog.html.testing.newSafeUrlForTest(
186
'https://goo.gl/proxy?url=' + url);
187
}
188
189
// Don't require the URL sanitizer to protect string boundaries.
190
actualCSS = 'background-image: url("javascript:evil(1337)")';
191
expectedCSS = '';
192
assertCSSTextEquals(
193
expectedCSS,
194
getSanitizedInlineStyle(actualCSS, goog.html.SafeUrl.sanitize));
195
196
actualCSS = 'background-image: url("http://goo.gl/foo.png")';
197
expectedCSS =
198
'background-image: url(https://goo.gl/proxy?url=http://goo.gl/foo.png)';
199
assertCSSTextEquals(
200
expectedCSS, getSanitizedInlineStyle(actualCSS, proxyUrl));
201
202
// Without any URL sanitizer.
203
actualCSS = 'background: transparent url("Bar.png")';
204
var sanitizedCss = getSanitizedInlineStyle(actualCSS);
205
assertFalse(goog.string.contains(sanitizedCss, 'background-image'));
206
assertFalse(goog.string.contains(sanitizedCss, 'Bar.png'));
207
}
208
209
function testVendorPrefixed() {
210
var actualCSS = '-webkit-text-stroke: 1px red';
211
var expectedCSS = '';
212
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
213
}
214
215
function testDisallowedFunction() {
216
var actualCSS = 'border-width: calc(10px + 20px)';
217
var expectedCSS = '';
218
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
219
}
220
221
function testColor() {
222
var colors = [
223
'red', 'Red', 'RED', 'Gray', 'grey', '#abc', '#123', '#ABC123',
224
'rgb( 127, 64 , 255 )'
225
];
226
var notcolors = [
227
// Finding words that are not X11 colors is harder than you think.
228
'killitwithfire', 'invisible', 'expression(red=blue)', '#aa-1bb',
229
'#expression', '#doevil'
230
// 'rgb(0, 0, 100%)' // Invalid in all browsers
231
// 'rgba(128,255,128,50%)', // Invalid in all browsers
232
];
233
234
for (var i = 0; i < colors.length; ++i) {
235
var validColorValue = 'color: ' + colors[i];
236
assertCSSTextEquals(
237
validColorValue, getSanitizedInlineStyle(validColorValue));
238
}
239
240
for (var i = 0; i < notcolors.length; ++i) {
241
var invalidColorValue = 'color: ' + notcolors[i];
242
assertCSSTextEquals('', getSanitizedInlineStyle(invalidColorValue));
243
}
244
}
245
246
247
function testCustomVariablesSanitized() {
248
var actualCSS = '\\2d-leak: leakTest; background: var(--leak);';
249
assertCSSTextEquals('', getSanitizedInlineStyle(actualCSS));
250
}
251
252
253
function testExpressionsPreserved() {
254
if (isIE8()) {
255
// Disable this test as IE8 doesn't support expressions.
256
// https://msdn.microsoft.com/en-us/library/ms537634(v=VS.85).aspx
257
return;
258
}
259
260
var actualCSS, expectedCSS;
261
actualCSS = 'background-image: linear-gradient(to bottom right, red, blue)';
262
expectedCSS = 'background-image: linear-gradient(to right bottom, red, blue)';
263
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
264
}
265
266
267
function testMultipleInlineStyles() {
268
var actualCSS = 'margin: 1px ; padding: 0';
269
var expectedCSS = 'margin: 1px; padding: 0px;';
270
assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
271
}
272
273
274
function testSanitizeInlineStyleString() {
275
var tests = [
276
{
277
// empty string
278
inputCss: '',
279
sanitizedCss: ''
280
},
281
{
282
// one rule
283
inputCss: 'color: red',
284
sanitizedCss: 'color: red;'
285
},
286
{
287
// two rules
288
inputCss: 'color: green; padding: 10px',
289
sanitizedCss: 'color: green; padding: 10px;'
290
},
291
{
292
// malicious rule
293
inputCss: 'color: expression("pwned")',
294
sanitizedCss: ''
295
},
296
{
297
// disallowed URL
298
inputCss: 'background-image: url("http://example.com")',
299
sanitizedCss: ''
300
},
301
{
302
// disallowed URL
303
inputCss: 'background-image: url("http://example.com")',
304
sanitizedCss: '',
305
uriRewriter: function(uri) {
306
return null;
307
}
308
},
309
{
310
// allowed URL
311
inputCss: 'background-image: url("http://example.com")',
312
sanitizedCss: 'background-image: url("http://example.com");',
313
uriRewriter: goog.html.SafeUrl.sanitize
314
},
315
{
316
// preserves case
317
inputCss: 'font-family: Roboto, sans-serif',
318
sanitizedCss: 'font-family: Roboto, sans-serif'
319
}
320
];
321
322
for (var i = 0; i < tests.length; i++) {
323
var test = tests[i];
324
325
var expectedOutput = test.sanitizedCss;
326
if (goog.userAgent.IE && document.documentMode < 10) {
327
expectedOutput = '';
328
}
329
330
var safeStyle = goog.html.sanitizer.CssSanitizer.sanitizeInlineStyleString(
331
test.inputCss, test.uriRewriter);
332
var output = goog.html.SafeStyle.unwrap(safeStyle);
333
assertCSSTextEquals(expectedOutput, output);
334
}
335
}
336
337
338
/**
339
* @suppress {accessControls}
340
*/
341
function testInertDocument() {
342
if (!document.implementation.createHTMLDocument) {
343
return; // skip test
344
}
345
346
window.xssFiredInertDocument = false;
347
var doc = goog.html.sanitizer.CssSanitizer.createInertDocument_();
348
try {
349
doc.write('<script> window.xssFiredInertDocument = true; </script>');
350
} catch (e) {
351
// ignore
352
}
353
assertFalse(window.xssFiredInertDocument);
354
}
355
356
357
/**
358
* @suppress {accessControls}
359
*/
360
function testInertCustomElements() {
361
if (typeof HTMLTemplateElement != 'function' || !document.registerElement) {
362
return; // skip test
363
}
364
365
var inertDoc = goog.html.sanitizer.CssSanitizer.createInertDocument_();
366
var xFooConstructor = document.registerElement('x-foo');
367
var xFooElem =
368
document.implementation.createHTMLDocument('').createElement('x-foo');
369
assertTrue(xFooElem instanceof xFooConstructor); // sanity check
370
371
var inertXFooElem = inertDoc.createElement('x-foo');
372
assertFalse(inertXFooElem instanceof xFooConstructor);
373
}
374
375