// Copyright 2013 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Unit tests for goog.html.SafeHtml and its builders. */ goog.provide('goog.html.safeHtmlTest'); goog.require('goog.html.SafeHtml'); goog.require('goog.html.SafeScript'); goog.require('goog.html.SafeStyle'); goog.require('goog.html.SafeStyleSheet'); goog.require('goog.html.SafeUrl'); goog.require('goog.html.TrustedResourceUrl'); goog.require('goog.html.testing'); goog.require('goog.i18n.bidi.Dir'); goog.require('goog.labs.userAgent.browser'); goog.require('goog.object'); goog.require('goog.string.Const'); goog.require('goog.testing.jsunit'); goog.setTestOnly('goog.html.safeHtmlTest'); function testSafeHtml() { // TODO(xtof): Consider using SafeHtmlBuilder instead of newSafeHtmlForTest, // when available. var safeHtml = goog.html.testing.newSafeHtmlForTest('Hello World'); assertSameHtml('Hello World', safeHtml); assertEquals('Hello World', goog.html.SafeHtml.unwrap(safeHtml)); assertEquals('SafeHtml{Hello World}', String(safeHtml)); assertNull(safeHtml.getDirection()); safeHtml = goog.html.testing.newSafeHtmlForTest( 'World Hello', goog.i18n.bidi.Dir.RTL); assertSameHtml('World Hello', safeHtml); assertEquals('World Hello', goog.html.SafeHtml.unwrap(safeHtml)); assertEquals('SafeHtml{World Hello}', String(safeHtml)); assertEquals(goog.i18n.bidi.Dir.RTL, safeHtml.getDirection()); // Interface markers are present. assertTrue(safeHtml.implementsGoogStringTypedString); assertTrue(safeHtml.implementsGoogI18nBidiDirectionalString); // Pre-defined constant. assertSameHtml('', goog.html.SafeHtml.EMPTY); assertSameHtml('
', goog.html.SafeHtml.BR); } /** @suppress {checkTypes} */ function testUnwrap() { var privateFieldName = 'privateDoNotAccessOrElseSafeHtmlWrappedValue_'; var markerFieldName = 'SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_'; var propNames = goog.object.getKeys(goog.html.SafeHtml.htmlEscape('')); assertContains(privateFieldName, propNames); assertContains(markerFieldName, propNames); var evil = {}; evil[privateFieldName] = ''); }); // Can set content. assertSameHtml( '', goog.html.SafeHtml.createIframe(null, null, {'sandbox': null}, '<')); } /** @suppress {checkTypes} */ function testSafeHtmlcreateSandboxIframe() { function assertSameHtmlIfSupportsSandbox(referenceHtml, testedHtmlFunction) { if (!goog.html.SafeHtml.canUseSandboxIframe()) { assertThrows(testedHtmlFunction); } else { assertSameHtml(referenceHtml, testedHtmlFunction()); } } // Setting src and srcdoc. var url = goog.html.SafeUrl.fromConstant( goog.string.Const.from('https://google.com/trusted<')); assertSameHtmlIfSupportsSandbox( '', function() { return goog.html.SafeHtml.createSandboxIframe(url, null); }); // If set with a string, src is sanitized. assertSameHtmlIfSupportsSandbox( '', function() { return goog.html.SafeHtml.createSandboxIframe( "javascript:evil();", null); }); var srcdoc = '
'; assertSameHtmlIfSupportsSandbox( '', function() { return goog.html.SafeHtml.createSandboxIframe(null, srcdoc); }); // Cannot override src, srcdoc. assertThrows(function() { goog.html.SafeHtml.createSandboxIframe(null, null, {'Src': url}); }); assertThrows(function() { goog.html.SafeHtml.createSandboxIframe(null, null, {'Srcdoc': url}); }); // Sandboxed by default, and can't be overriden. assertSameHtmlIfSupportsSandbox('', function() { return goog.html.SafeHtml.createSandboxIframe(); }); assertThrows(function() { goog.html.SafeHtml.createSandboxIframe(null, null, {'sandbox': ''}); }); assertThrows(function() { goog.html.SafeHtml.createSandboxIframe( null, null, {'SaNdBoX': 'allow-scripts'}); }); assertThrows(function() { goog.html.SafeHtml.createSandboxIframe( null, null, {'sandbox': 'allow-same-origin allow-top-navigation'}); }); // Can set content. assertSameHtmlIfSupportsSandbox( '', function() { return goog.html.SafeHtml.createSandboxIframe(null, null, null, '<'); }); } function testSafeHtmlCanUseIframeSandbox() { // We know that the IE < 10 do not support the sandbox attribute, so use them // as a reference. if (goog.labs.userAgent.browser.isIE() && goog.labs.userAgent.browser.getVersion() < 10) { assertEquals(false, goog.html.SafeHtml.canUseSandboxIframe()); } else { assertEquals(true, goog.html.SafeHtml.canUseSandboxIframe()); } } function testSafeHtmlCreateScript() { var script = goog.html.SafeScript.fromConstant(goog.string.Const.from('function1();')); var scriptHtml = goog.html.SafeHtml.createScript(script); assertSameHtml('', scriptHtml); // Two pieces of script. var otherScript = goog.html.SafeScript.fromConstant(goog.string.Const.from('function2();')); scriptHtml = goog.html.SafeHtml.createScript([script, otherScript]); assertSameHtml('', scriptHtml); // Set attribute. scriptHtml = goog.html.SafeHtml.createScript(script, {'id': 'test'}); assertContains('id="test"', goog.html.SafeHtml.unwrap(scriptHtml)); // Set attribute to null. scriptHtml = goog.html.SafeHtml.createScript(goog.html.SafeScript.EMPTY, {'id': null}); assertSameHtml('', scriptHtml); // Set attribute to invalid value. var exception = assertThrows(function() { goog.html.SafeHtml.createScript( goog.html.SafeScript.EMPTY, {'invalid.': 'cantdothis'}); }); assertContains('Invalid attribute name', exception.message); // Cannot override type attribute. exception = assertThrows(function() { goog.html.SafeHtml.createScript( goog.html.SafeScript.EMPTY, {'Type': 'cantdothis'}); }); assertContains('Cannot set "type"', exception.message); // Cannot set src attribute. exception = assertThrows(function() { goog.html.SafeHtml.createScript( goog.html.SafeScript.EMPTY, {'src': 'cantdothis'}); }); assertContains('Cannot set "src"', exception.message); // Directionality. assertEquals(goog.i18n.bidi.Dir.NEUTRAL, scriptHtml.getDirection()); } /** @suppress {checkTypes} */ function testSafeHtmlCreateScriptSrc() { var url = goog.html.TrustedResourceUrl.fromConstant( goog.string.Const.from('https://google.com/trusted<')); assertSameHtml( '', goog.html.SafeHtml.createScriptSrc(url)); assertSameHtml( '', goog.html.SafeHtml.createScriptSrc(url, {'defer': 'defer'})); // Unsafe src. assertThrows(function() { goog.html.SafeHtml.createScriptSrc('http://example.com'); }); // Unsafe attribute. assertThrows(function() { goog.html.SafeHtml.createScriptSrc(url, {'onerror': 'alert(1)'}); }); // Cannot override src. assertThrows(function() { goog.html.SafeHtml.createScriptSrc(url, {'Src': url}); }); } function testSafeHtmlCreateMeta() { var url = goog.html.SafeUrl.fromConstant( goog.string.Const.from('https://google.com/trusted<')); // SafeUrl with no timeout gets properly escaped. assertSameHtml( '', goog.html.SafeHtml.createMetaRefresh(url)); // SafeUrl with 0 timeout also gets properly escaped. assertSameHtml( '', goog.html.SafeHtml.createMetaRefresh(url, 0)); // Positive timeouts are supported. assertSameHtml( '', goog.html.SafeHtml.createMetaRefresh(url, 1337)); // Negative timeouts are also kept, though they're not correct HTML. assertSameHtml( '', goog.html.SafeHtml.createMetaRefresh(url, -1337)); // String-based URLs work out of the box. assertSameHtml( '', goog.html.SafeHtml.createMetaRefresh('https://google.com/trusted<')); // Sanitization happens. assertSameHtml( '', goog.html.SafeHtml.createMetaRefresh('javascript:alert(1)')); } function testSafeHtmlCreateStyle() { var styleSheet = goog.html.SafeStyleSheet.fromConstant( goog.string.Const.from('P.special { color:"red" ; }')); var styleHtml = goog.html.SafeHtml.createStyle(styleSheet); assertSameHtml( '', styleHtml); // Two stylesheets. var otherStyleSheet = goog.html.SafeStyleSheet.fromConstant( goog.string.Const.from('P.regular { color:blue ; }')); styleHtml = goog.html.SafeHtml.createStyle([styleSheet, otherStyleSheet]); assertSameHtml( '', styleHtml); // Set attribute. styleHtml = goog.html.SafeHtml.createStyle(styleSheet, {'id': 'test'}); var styleHtmlString = goog.html.SafeHtml.unwrap(styleHtml); assertContains('id="test"', styleHtmlString); assertContains('type="text/css"', styleHtmlString); // Set attribute to null. styleHtml = goog.html.SafeHtml.createStyle( goog.html.SafeStyleSheet.EMPTY, {'id': null}); assertSameHtml('', styleHtml); // Set attribute to invalid value. var exception = assertThrows(function() { goog.html.SafeHtml.createStyle( goog.html.SafeStyleSheet.EMPTY, {'invalid.': 'cantdothis'}); }); assertContains('Invalid attribute name', exception.message); // Cannot override type attribute. exception = assertThrows(function() { goog.html.SafeHtml.createStyle( goog.html.SafeStyleSheet.EMPTY, {'Type': 'cantdothis'}); }); assertContains('Cannot override "type"', exception.message); // Directionality. assertEquals(goog.i18n.bidi.Dir.NEUTRAL, styleHtml.getDirection()); } function testSafeHtmlCreateWithDir() { var ltr = goog.i18n.bidi.Dir.LTR; assertEquals(ltr, goog.html.SafeHtml.createWithDir(ltr, 'br').getDirection()); } function testSafeHtmlConcat() { var br = goog.html.testing.newSafeHtmlForTest('
'); var html = goog.html.SafeHtml.htmlEscape('Hello'); assertSameHtml('Hello
', goog.html.SafeHtml.concat(html, br)); assertSameHtml('', goog.html.SafeHtml.concat()); assertSameHtml('', goog.html.SafeHtml.concat([])); assertSameHtml('a
c', goog.html.SafeHtml.concat('a', br, 'c')); assertSameHtml('a
c', goog.html.SafeHtml.concat(['a', br, 'c'])); assertSameHtml('a
c', goog.html.SafeHtml.concat('a', [br, 'c'])); assertSameHtml('a
c', goog.html.SafeHtml.concat(['a'], br, ['c'])); var ltr = goog.html.testing.newSafeHtmlForTest('', goog.i18n.bidi.Dir.LTR); var rtl = goog.html.testing.newSafeHtmlForTest('', goog.i18n.bidi.Dir.RTL); var neutral = goog.html.testing.newSafeHtmlForTest('', goog.i18n.bidi.Dir.NEUTRAL); var unknown = goog.html.testing.newSafeHtmlForTest(''); assertEquals( goog.i18n.bidi.Dir.NEUTRAL, goog.html.SafeHtml.concat().getDirection()); assertEquals( goog.i18n.bidi.Dir.LTR, goog.html.SafeHtml.concat(ltr, ltr).getDirection()); assertEquals( goog.i18n.bidi.Dir.LTR, goog.html.SafeHtml.concat(ltr, neutral, ltr).getDirection()); assertNull(goog.html.SafeHtml.concat(ltr, unknown).getDirection()); assertNull(goog.html.SafeHtml.concat(ltr, rtl).getDirection()); assertNull(goog.html.SafeHtml.concat(ltr, [rtl]).getDirection()); } function testHtmlEscapePreservingNewlines() { // goog.html.SafeHtml passes through unchanged. var safeHtmlIn = goog.html.SafeHtml.htmlEscapePreservingNewlines('in'); assertTrue( safeHtmlIn === goog.html.SafeHtml.htmlEscapePreservingNewlines(safeHtmlIn)); assertSameHtml( 'a
c', goog.html.SafeHtml.htmlEscapePreservingNewlines('a\nc')); assertSameHtml( '<
', goog.html.SafeHtml.htmlEscapePreservingNewlines('<\n')); assertSameHtml( '
', goog.html.SafeHtml.htmlEscapePreservingNewlines('\r\n')); assertSameHtml('
', goog.html.SafeHtml.htmlEscapePreservingNewlines('\r')); assertSameHtml('', goog.html.SafeHtml.htmlEscapePreservingNewlines('')); } function testHtmlEscapePreservingNewlinesAndSpaces() { // goog.html.SafeHtml passes through unchanged. var safeHtmlIn = goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('in'); assertTrue( safeHtmlIn === goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(safeHtmlIn)); assertSameHtml( 'a
c', goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('a\nc')); assertSameHtml( '<
', goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('<\n')); assertSameHtml( '
', goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('\r\n')); assertSameHtml( '
', goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('\r')); assertSameHtml( '', goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('')); assertSameHtml( 'a  b', goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('a b')); } function testSafeHtmlConcatWithDir() { var ltr = goog.i18n.bidi.Dir.LTR; var rtl = goog.i18n.bidi.Dir.RTL; var br = goog.html.testing.newSafeHtmlForTest('
'); assertEquals(ltr, goog.html.SafeHtml.concatWithDir(ltr).getDirection()); assertEquals( ltr, goog.html.SafeHtml .concatWithDir(ltr, goog.html.testing.newSafeHtmlForTest('', rtl)) .getDirection()); assertSameHtml('a
c', goog.html.SafeHtml.concatWithDir(ltr, 'a', br, 'c')); } function assertSameHtml(expected, html) { assertEquals(expected, goog.html.SafeHtml.unwrap(html)); }