| // 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.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.string.Const'); |
| goog.require('goog.testing.jsunit'); |
| |
| goog.setTestOnly('goog.html.safeHtmlTest'); |
| |
| |
| |
| function testSafeHtml() { |
| // TODO(user): Consider using SafeHtmlBuilder instead of newSafeHtmlForTest, |
| // when available. |
| var safeHtml = goog.html.testing.newSafeHtmlForTest('Hello <em>World</em>'); |
| assertSameHtml('Hello <em>World</em>', safeHtml); |
| assertEquals('Hello <em>World</em>', goog.html.SafeHtml.unwrap(safeHtml)); |
| assertEquals('SafeHtml{Hello <em>World</em>}', String(safeHtml)); |
| assertNull(safeHtml.getDirection()); |
| |
| safeHtml = goog.html.testing.newSafeHtmlForTest( |
| 'World <em>Hello</em>', goog.i18n.bidi.Dir.RTL); |
| assertSameHtml('World <em>Hello</em>', safeHtml); |
| assertEquals('World <em>Hello</em>', goog.html.SafeHtml.unwrap(safeHtml)); |
| assertEquals('SafeHtml{World <em>Hello</em>}', 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); |
| } |
| |
| |
| /** @suppress {checkTypes} */ |
| function testUnwrap() { |
| var evil = {}; |
| evil.safeHtmlValueWithSecurityContract__googHtmlSecurityPrivate_ = |
| '<script>evil()</script'; |
| evil.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {}; |
| |
| var exception = assertThrows(function() { |
| goog.html.SafeHtml.unwrap(evil); |
| }); |
| assertTrue(exception.message.indexOf('expected object of type SafeHtml') > 0); |
| } |
| |
| |
| function testHtmlEscape() { |
| // goog.html.SafeHtml passes through unchanged. |
| var safeHtmlIn = goog.html.SafeHtml.htmlEscape('<b>in</b>'); |
| assertTrue(safeHtmlIn === goog.html.SafeHtml.htmlEscape(safeHtmlIn)); |
| |
| // Plain strings are escaped. |
| var safeHtml = goog.html.SafeHtml.htmlEscape('Hello <em>"\'&World</em>'); |
| assertSameHtml('Hello <em>"'&World</em>', safeHtml); |
| assertEquals('SafeHtml{Hello <em>"'&World</em>}', |
| String(safeHtml)); |
| |
| // Creating from a SafeUrl escapes and retains the known direction (which is |
| // fixed to RTL for URLs). |
| var safeUrl = goog.html.SafeUrl.fromConstant( |
| goog.string.Const.from('http://example.com/?foo&bar')); |
| var escapedUrl = goog.html.SafeHtml.htmlEscape(safeUrl); |
| assertSameHtml('http://example.com/?foo&bar', escapedUrl); |
| assertEquals(goog.i18n.bidi.Dir.LTR, escapedUrl.getDirection()); |
| |
| // Creating SafeHtml from a goog.string.Const escapes as well (i.e., the |
| // value is treated like any other string). To create HTML markup from |
| // program literals, SafeHtmlBuilder should be used. |
| assertSameHtml('this & that', |
| goog.html.SafeHtml.htmlEscape(goog.string.Const.from('this & that'))); |
| } |
| |
| |
| function testSafeHtmlCreate() { |
| var br = goog.html.SafeHtml.create('br'); |
| |
| assertSameHtml('<br>', br); |
| |
| assertSameHtml('<span title="""></span>', |
| goog.html.SafeHtml.create('span', {'title': '"'})); |
| |
| assertSameHtml('<span><</span>', |
| goog.html.SafeHtml.create('span', {}, '<')); |
| |
| assertSameHtml('<span><br></span>', |
| goog.html.SafeHtml.create('span', {}, br)); |
| |
| assertSameHtml('<span></span>', goog.html.SafeHtml.create('span', {}, [])); |
| |
| assertSameHtml('<span></span>', |
| goog.html.SafeHtml.create('span', {'title': null, 'class': undefined})); |
| |
| assertSameHtml('<span>x<br>y</span>', |
| goog.html.SafeHtml.create('span', {}, ['x', br, 'y'])); |
| |
| assertSameHtml('<table border="0"></table>', |
| goog.html.SafeHtml.create('table', {'border': 0})); |
| |
| var onclick = goog.string.Const.from('alert(/"/)'); |
| assertSameHtml('<span onclick="alert(/"/)"></span>', |
| goog.html.SafeHtml.create('span', {'onclick': onclick})); |
| |
| var href = goog.html.testing.newSafeUrlForTest('?a&b'); |
| assertSameHtml('<a href="?a&b"></a>', |
| goog.html.SafeHtml.create('a', {'href': href})); |
| |
| var style = goog.html.testing.newSafeStyleForTest('border: /* " */ 0;'); |
| assertSameHtml('<hr style="border: /* " */ 0;">', |
| goog.html.SafeHtml.create('hr', {'style': style})); |
| |
| assertEquals(goog.i18n.bidi.Dir.NEUTRAL, |
| goog.html.SafeHtml.create('span').getDirection()); |
| assertNull(goog.html.SafeHtml.create('span', {'dir': 'x'}).getDirection()); |
| assertEquals(goog.i18n.bidi.Dir.NEUTRAL, |
| goog.html.SafeHtml.create('span', {'dir': 'ltr'}, 'a').getDirection()); |
| |
| assertThrows(function() { |
| goog.html.SafeHtml.create('script'); |
| }); |
| |
| assertThrows(function() { |
| goog.html.SafeHtml.create('br', {}, 'x'); |
| }); |
| |
| assertThrows(function() { |
| goog.html.SafeHtml.create('img', {'onerror': ''}); |
| }); |
| |
| assertThrows(function() { |
| goog.html.SafeHtml.create('img', {'OnError': ''}); |
| }); |
| |
| assertThrows(function() { |
| goog.html.SafeHtml.create('a', {'href': 'javascript:alert(1)'}); |
| }); |
| |
| assertThrows(function() { |
| goog.html.SafeHtml.create('a href=""'); |
| }); |
| |
| assertThrows(function() { |
| goog.html.SafeHtml.create('a', {'title="" href': ''}); |
| }); |
| } |
| |
| |
| function testSafeHtmlCreate_styleAttribute() { |
| var style = 'color:red;'; |
| var expected = '<hr style="' + style + '">'; |
| assertThrows(function() { |
| goog.html.SafeHtml.create('hr', {'style': style}); |
| }); |
| assertSameHtml(expected, goog.html.SafeHtml.create('hr', { |
| 'style': goog.html.SafeStyle.fromConstant(goog.string.Const.from(style)) |
| })); |
| assertSameHtml(expected, goog.html.SafeHtml.create('hr', { |
| 'style': {'color': 'red'} |
| })); |
| } |
| |
| |
| function testSafeHtmlCreate_urlAttributes() { |
| // TrustedResourceUrl is allowed. |
| var trustedResourceUrl = goog.html.TrustedResourceUrl.fromConstant( |
| goog.string.Const.from('https://google.com/trusted')); |
| assertSameHtml( |
| '<img src="https://google.com/trusted">', |
| goog.html.SafeHtml.create('img', {'src': trustedResourceUrl})); |
| // SafeUrl is allowed. |
| var safeUrl = goog.html.SafeUrl.sanitize('https://google.com/safe'); |
| assertSameHtml( |
| '<imG src="https://google.com/safe">', |
| goog.html.SafeHtml.create('imG', {'src': safeUrl})); |
| // Const is allowed. |
| var constUrl = goog.string.Const.from('https://google.com/const'); |
| assertSameHtml( |
| '<a href="https://google.com/const"></a>', |
| goog.html.SafeHtml.create('a', {'href': constUrl})); |
| |
| // string is not allowed. |
| assertThrows(function() { |
| goog.html.SafeHtml.create('imG', {'src': 'https://google.com'}); |
| }); |
| } |
| |
| |
| function testSafeHtmlCreateIframe() { |
| // Setting src and srcdoc. |
| var url = goog.html.TrustedResourceUrl.fromConstant( |
| goog.string.Const.from('https://google.com/trusted<')); |
| assertSameHtml( |
| '<iframe src="https://google.com/trusted<"></iframe>', |
| goog.html.SafeHtml.createIframe(url, null, {'sandbox': null})); |
| var srcdoc = goog.html.SafeHtml.create('br'); |
| assertSameHtml( |
| '<iframe srcdoc="<br>"></iframe>', |
| goog.html.SafeHtml.createIframe(null, srcdoc, {'sandbox': null})); |
| |
| // sandbox default and overriding it. |
| assertSameHtml( |
| '<iframe sandbox=""></iframe>', |
| goog.html.SafeHtml.createIframe()); |
| assertSameHtml( |
| '<iframe Sandbox="allow-same-origin allow-top-navigation"></iframe>', |
| goog.html.SafeHtml.createIframe( |
| null, null, {'Sandbox': 'allow-same-origin allow-top-navigation'})); |
| |
| // Cannot override src and srddoc. |
| assertThrows(function() { |
| goog.html.SafeHtml.createIframe(null, null, {'Src': url}); |
| }); |
| assertThrows(function() { |
| goog.html.SafeHtml.createIframe(null, null, {'Srcdoc': url}); |
| }); |
| |
| // Can set content. |
| assertSameHtml( |
| '<iframe><</iframe>', |
| goog.html.SafeHtml.createIframe(null, null, {'sandbox': null}, '<')); |
| } |
| |
| |
| function testSafeHtmlCreateStyle() { |
| var styleSheet = goog.html.SafeStyleSheet.fromConstant( |
| goog.string.Const.from('P.special { color:"red" ; }')); |
| var styleHtml = goog.html.SafeHtml.createStyle(styleSheet); |
| assertSameHtml( |
| '<style type="text/css">P.special { color:"red" ; }</style>', 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( |
| '<style type="text/css">P.special { color:"red" ; }' + |
| 'P.regular { color:blue ; }</style>', |
| styleHtml); |
| |
| // Set attribute. |
| styleHtml = goog.html.SafeHtml.createStyle(styleSheet, {'id': 'test'}); |
| var styleHtmlString = goog.html.SafeHtml.unwrap(styleHtml); |
| assertTrue(styleHtmlString, styleHtmlString.indexOf('id="test"') != -1); |
| assertTrue(styleHtmlString, styleHtmlString.indexOf('type="text/css"') != -1); |
| |
| // Set attribute to null. |
| styleHtml = goog.html.SafeHtml.createStyle( |
| goog.html.SafeStyleSheet.EMPTY, {'id': null}); |
| assertSameHtml('<style type="text/css"></style>', styleHtml); |
| |
| // Set attribute to invalid value. |
| assertThrows(function() { |
| styleHtml = goog.html.SafeHtml.createStyle( |
| goog.html.SafeStyleSheet.EMPTY, {'invalid.': 'cantdothis'}); |
| }); |
| |
| // Cannot override type attribute. |
| assertThrows(function() { |
| styleHtml = goog.html.SafeHtml.createStyle( |
| goog.html.SafeStyleSheet.EMPTY, {'Type': 'cantdothis'}); |
| }); |
| |
| // 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('<br>'); |
| |
| var html = goog.html.SafeHtml.htmlEscape('Hello'); |
| assertSameHtml('Hello<br>', goog.html.SafeHtml.concat(html, br)); |
| |
| assertSameHtml('', goog.html.SafeHtml.concat()); |
| assertSameHtml('', goog.html.SafeHtml.concat([])); |
| |
| assertSameHtml('a<br>c', goog.html.SafeHtml.concat('a', br, 'c')); |
| assertSameHtml('a<br>c', goog.html.SafeHtml.concat(['a', br, 'c'])); |
| assertSameHtml('a<br>c', goog.html.SafeHtml.concat('a', [br, 'c'])); |
| assertSameHtml('a<br>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('<b>in</b>'); |
| assertTrue(safeHtmlIn === |
| goog.html.SafeHtml.htmlEscapePreservingNewlines(safeHtmlIn)); |
| |
| assertSameHtml('a<br>c', |
| goog.html.SafeHtml.htmlEscapePreservingNewlines('a\nc')); |
| assertSameHtml('<<br>', |
| goog.html.SafeHtml.htmlEscapePreservingNewlines('<\n')); |
| assertSameHtml('<br>', |
| goog.html.SafeHtml.htmlEscapePreservingNewlines('\r\n')); |
| assertSameHtml('<br>', goog.html.SafeHtml.htmlEscapePreservingNewlines('\r')); |
| assertSameHtml('', goog.html.SafeHtml.htmlEscapePreservingNewlines('')); |
| } |
| |
| |
| function testHtmlEscapePreservingNewlinesAndSpaces() { |
| // goog.html.SafeHtml passes through unchanged. |
| var safeHtmlIn = goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces( |
| '<b>in</b>'); |
| assertTrue(safeHtmlIn === |
| goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(safeHtmlIn)); |
| |
| assertSameHtml('a<br>c', |
| goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('a\nc')); |
| assertSameHtml('<<br>', |
| goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('<\n')); |
| assertSameHtml( |
| '<br>', goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces('\r\n')); |
| assertSameHtml( |
| '<br>', 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('<br>'); |
| |
| assertEquals(ltr, goog.html.SafeHtml.concatWithDir(ltr).getDirection()); |
| assertEquals(ltr, goog.html.SafeHtml.concatWithDir(ltr, |
| goog.html.testing.newSafeHtmlForTest('', rtl)).getDirection()); |
| |
| assertSameHtml('a<br>c', goog.html.SafeHtml.concatWithDir(ltr, 'a', br, 'c')); |
| } |
| |
| |
| function assertSameHtml(expected, html) { |
| assertEquals(expected, goog.html.SafeHtml.unwrap(html)); |
| } |