Path: blob/trunk/third_party/closure/goog/html/sanitizer/csssanitizer_test.js
2868 views
// Copyright 2016 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/** @fileoverview testcases for CSS Sanitizer.*/1516goog.setTestOnly();1718goog.require('goog.array');19goog.require('goog.html.SafeStyle');20goog.require('goog.html.SafeUrl');21goog.require('goog.html.sanitizer.CssSanitizer');22goog.require('goog.html.testing');23goog.require('goog.string');24goog.require('goog.testing.jsunit');25goog.require('goog.userAgent');26goog.require('goog.userAgent.product');272829/**30* @return {boolean} Returns if the browser is IE8.31* @private32*/33function isIE8() {34return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(9);35}363738/**39* @return {boolean} Returns if the browser is Safari.40* @private41*/42function isSafari() {43return goog.userAgent.product.SAFARI;44}454647/**48* @param {string} cssText CSS text usually associated with an inline style.49* @return {!CSSStyleDeclaration} A styleSheet object.50*/51function getStyleFromCssText(cssText) {52var styleDecleration = document.createElement('div').style;53styleDecleration.cssText = cssText || '';54return styleDecleration;55}565758/**59* Asserts that the expected CSS text is equal to the actual CSS text.60* @param {string} expectedCssText Expected CSS text.61* @param {string} actualCssText Actual CSS text.62*/63function assertCSSTextEquals(expectedCssText, actualCssText) {64if (isIE8()) {65// We get a bunch of default values set in IE8 because of the way we iterate66// over the CSSStyleDecleration keys.67// TODO(danesh): Fix IE8 or remove this hack. It will be problematic for68// tests which have an extra semi-colon in the value (even if quoted).69var actualCssArry = actualCssText.split(/\s*;\s*/);70var ie8StyleString = 'WIDTH: 0px; BOTTOM: 0px; HEIGHT: 0px; TOP: 0px; ' +71'RIGHT: 0px; TEXT-DECORATION: none underline overline line-through; ' +72'LEFT: 0px; TEXT-DECORATION: underline line-through;';73goog.array.forEach(ie8StyleString.split(/\s*;\s*/), function(ie8Css) {74goog.array.remove(actualCssArry, ie8Css);75});76actualCssText = actualCssArry.join('; ');77}78assertEquals(79getStyleFromCssText(expectedCssText).cssText,80getStyleFromCssText(actualCssText).cssText);81}8283/**84* Gets sanitized inline style.85* @param {string} sourceCss CSS to be sanitized.86* @param {function (string, string):?goog.html.SafeUrl=} opt_urlRewrite URL87* rewriter that only returns a goog.html.SafeUrl.88* @return {string} Sanitized inline style.89* @private90*/91function getSanitizedInlineStyle(sourceCss, opt_urlRewrite) {92try {93return goog.html.SafeStyle.unwrap(94goog.html.sanitizer.CssSanitizer.sanitizeInlineStyle(95getStyleFromCssText(sourceCss), opt_urlRewrite)) ||96'';97} catch (err) {98// IE8 doesn't like setting invalid properties. It throws an "Invalid99// Argument" exception.100if (!isIE8()) {101throw err;102}103return '';104}105}106107108function testValidCss() {109var actualCSS = 'font-family: inherit';110var expectedCSS = 'font-family: inherit';111assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));112113// .1 -> 0.1; 1.0 -> 1114actualCSS = 'padding: 1pt .1pt 1pt 1.0em';115expectedCSS = 'padding: 1pt 0.1pt 1pt 1em';116assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));117118// Negative margins are allowed.119actualCSS = 'margin: -7px -.5px -23px -1.25px';120expectedCSS = 'margin: -7px -0.5px -23px -1.25px';121if (isIE8()) {122// IE8 doesn't like sub-pixels123// https://blogs.msdn.microsoft.com/ie/2010/11/03/sub-pixel-fonts-in-ie9/124expectedCSS = expectedCSS.replace('-0.5px', '0px');125expectedCSS = expectedCSS.replace('-1.25px', '-1px');126}127assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));128129actualCSS = 'quotes: "{" "}" "<" ">"';130expectedCSS = 'quotes: "{" "}" "<" ">";';131if (isSafari()) {132// TODO(danesh): Figure out what is wrong with WebKit (Safari).133expectedCSS = 'quotes: \'{\';';134}135assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));136}137138139function testInvalidCssRemoved() {140var actualCSS;141142// Tests all have null results.143var expectedCSS = '';144145actualCSS = 'font: Arial Black,monospace,Helvetica,#88ff88';146// Hash values are not allowed so are dropped.147assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));148149// Negative numbers for border not allowed.150actualCSS = 'border : -7px -0.5px -23px -1.25px';151assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));152153// Negative numbers converted to empty.154actualCSS = 'padding: -0 -.0 -0. -0.0 ';155assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));156157// Invalid values not allowed.158actualCSS = 'padding : #123 - 5 "5"';159assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));160161// Font-family does not allow quantities at all.162actualCSS = 'font-family: 7 .5 23 1.25 -7 -.5 -23 -1.25 +7 +.5 +23 +1.25 ' +163'7cm .5em 23.mm 1.25px -7cm -.5em -23.mm -1.25px ' +164'+7cm +.5em +23.mm +1.25px 0 .0 -0+00.0 /';165assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));166167actualCSS = 'background: bogus url("foo.png") transparent';168assertCSSTextEquals(169expectedCSS,170getSanitizedInlineStyle(actualCSS, goog.html.SafeUrl.sanitize));171172// expression(...) is not allowed for font so is rejected wholesale -- the173// internal string "pwned" is not passed through.174actualCSS = 'font-family: Arial Black,monospace,expression(return "pwned"),' +175'Helvetica,#88ff88';176assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));177}178179180function testCssBackground() {181var actualCSS, expectedCSS;182183function proxyUrl(url) {184return goog.html.testing.newSafeUrlForTest(185'https://goo.gl/proxy?url=' + url);186}187188// Don't require the URL sanitizer to protect string boundaries.189actualCSS = 'background-image: url("javascript:evil(1337)")';190expectedCSS = '';191assertCSSTextEquals(192expectedCSS,193getSanitizedInlineStyle(actualCSS, goog.html.SafeUrl.sanitize));194195actualCSS = 'background-image: url("http://goo.gl/foo.png")';196expectedCSS =197'background-image: url(https://goo.gl/proxy?url=http://goo.gl/foo.png)';198assertCSSTextEquals(199expectedCSS, getSanitizedInlineStyle(actualCSS, proxyUrl));200201// Without any URL sanitizer.202actualCSS = 'background: transparent url("Bar.png")';203var sanitizedCss = getSanitizedInlineStyle(actualCSS);204assertFalse(goog.string.contains(sanitizedCss, 'background-image'));205assertFalse(goog.string.contains(sanitizedCss, 'Bar.png'));206}207208function testVendorPrefixed() {209var actualCSS = '-webkit-text-stroke: 1px red';210var expectedCSS = '';211assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));212}213214function testDisallowedFunction() {215var actualCSS = 'border-width: calc(10px + 20px)';216var expectedCSS = '';217assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));218}219220function testColor() {221var colors = [222'red', 'Red', 'RED', 'Gray', 'grey', '#abc', '#123', '#ABC123',223'rgb( 127, 64 , 255 )'224];225var notcolors = [226// Finding words that are not X11 colors is harder than you think.227'killitwithfire', 'invisible', 'expression(red=blue)', '#aa-1bb',228'#expression', '#doevil'229// 'rgb(0, 0, 100%)' // Invalid in all browsers230// 'rgba(128,255,128,50%)', // Invalid in all browsers231];232233for (var i = 0; i < colors.length; ++i) {234var validColorValue = 'color: ' + colors[i];235assertCSSTextEquals(236validColorValue, getSanitizedInlineStyle(validColorValue));237}238239for (var i = 0; i < notcolors.length; ++i) {240var invalidColorValue = 'color: ' + notcolors[i];241assertCSSTextEquals('', getSanitizedInlineStyle(invalidColorValue));242}243}244245246function testCustomVariablesSanitized() {247var actualCSS = '\\2d-leak: leakTest; background: var(--leak);';248assertCSSTextEquals('', getSanitizedInlineStyle(actualCSS));249}250251252function testExpressionsPreserved() {253if (isIE8()) {254// Disable this test as IE8 doesn't support expressions.255// https://msdn.microsoft.com/en-us/library/ms537634(v=VS.85).aspx256return;257}258259var actualCSS, expectedCSS;260actualCSS = 'background-image: linear-gradient(to bottom right, red, blue)';261expectedCSS = 'background-image: linear-gradient(to right bottom, red, blue)';262assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));263}264265266function testMultipleInlineStyles() {267var actualCSS = 'margin: 1px ; padding: 0';268var expectedCSS = 'margin: 1px; padding: 0px;';269assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));270}271272273function testSanitizeInlineStyleString() {274var tests = [275{276// empty string277inputCss: '',278sanitizedCss: ''279},280{281// one rule282inputCss: 'color: red',283sanitizedCss: 'color: red;'284},285{286// two rules287inputCss: 'color: green; padding: 10px',288sanitizedCss: 'color: green; padding: 10px;'289},290{291// malicious rule292inputCss: 'color: expression("pwned")',293sanitizedCss: ''294},295{296// disallowed URL297inputCss: 'background-image: url("http://example.com")',298sanitizedCss: ''299},300{301// disallowed URL302inputCss: 'background-image: url("http://example.com")',303sanitizedCss: '',304uriRewriter: function(uri) {305return null;306}307},308{309// allowed URL310inputCss: 'background-image: url("http://example.com")',311sanitizedCss: 'background-image: url("http://example.com");',312uriRewriter: goog.html.SafeUrl.sanitize313},314{315// preserves case316inputCss: 'font-family: Roboto, sans-serif',317sanitizedCss: 'font-family: Roboto, sans-serif'318}319];320321for (var i = 0; i < tests.length; i++) {322var test = tests[i];323324var expectedOutput = test.sanitizedCss;325if (goog.userAgent.IE && document.documentMode < 10) {326expectedOutput = '';327}328329var safeStyle = goog.html.sanitizer.CssSanitizer.sanitizeInlineStyleString(330test.inputCss, test.uriRewriter);331var output = goog.html.SafeStyle.unwrap(safeStyle);332assertCSSTextEquals(expectedOutput, output);333}334}335336337/**338* @suppress {accessControls}339*/340function testInertDocument() {341if (!document.implementation.createHTMLDocument) {342return; // skip test343}344345window.xssFiredInertDocument = false;346var doc = goog.html.sanitizer.CssSanitizer.createInertDocument_();347try {348doc.write('<script> window.xssFiredInertDocument = true; </script>');349} catch (e) {350// ignore351}352assertFalse(window.xssFiredInertDocument);353}354355356/**357* @suppress {accessControls}358*/359function testInertCustomElements() {360if (typeof HTMLTemplateElement != 'function' || !document.registerElement) {361return; // skip test362}363364var inertDoc = goog.html.sanitizer.CssSanitizer.createInertDocument_();365var xFooConstructor = document.registerElement('x-foo');366var xFooElem =367document.implementation.createHTMLDocument('').createElement('x-foo');368assertTrue(xFooElem instanceof xFooConstructor); // sanity check369370var inertXFooElem = inertDoc.createElement('x-foo');371assertFalse(inertXFooElem instanceof xFooConstructor);372}373374375