123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- // 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.SafeUrl and its builders.
- */
- goog.provide('goog.html.safeUrlTest');
- goog.require('goog.html.SafeUrl');
- goog.require('goog.html.TrustedResourceUrl');
- goog.require('goog.i18n.bidi.Dir');
- goog.require('goog.object');
- goog.require('goog.string.Const');
- goog.require('goog.testing.jsunit');
- goog.require('goog.userAgent');
- goog.setTestOnly('goog.html.safeUrlTest');
- function testSafeUrl() {
- var safeUrl = goog.html.SafeUrl.fromConstant(
- goog.string.Const.from('javascript:trusted();'));
- var extracted = goog.html.SafeUrl.unwrap(safeUrl);
- assertEquals('javascript:trusted();', extracted);
- assertEquals('javascript:trusted();', goog.html.SafeUrl.unwrap(safeUrl));
- assertEquals('SafeUrl{javascript:trusted();}', String(safeUrl));
- // URLs are always LTR.
- assertEquals(goog.i18n.bidi.Dir.LTR, safeUrl.getDirection());
- // Interface markers are present.
- assertTrue(safeUrl.implementsGoogStringTypedString);
- assertTrue(safeUrl.implementsGoogI18nBidiDirectionalString);
- }
- function testSafeUrlFromBlob_withSafeType() {
- if (isIE9OrLower()) {
- return;
- }
- assertBlobTypeIsSafe('image/png', true);
- assertBlobTypeIsSafe('iMage/pNg', true);
- assertBlobTypeIsSafe('video/mpeg', true);
- assertBlobTypeIsSafe('video/ogg', true);
- assertBlobTypeIsSafe('video/mp4', true);
- assertBlobTypeIsSafe('video/ogg', true);
- assertBlobTypeIsSafe('video/webm', true);
- }
- function testSafeUrlFromBlob_withUnsafeType() {
- if (isIE9OrLower()) {
- return;
- }
- assertBlobTypeIsSafe('', false);
- assertBlobTypeIsSafe('ximage/png', false);
- assertBlobTypeIsSafe('image/pngx', false);
- assertBlobTypeIsSafe('video/whatever', false);
- assertBlobTypeIsSafe('video/', false);
- }
- /** @return {boolean} True if running on IE9 or lower. */
- function isIE9OrLower() {
- return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10');
- }
- /**
- * Tests creating a SafeUrl from a blob with the given MIME type, asserting
- * whether or not the SafeUrl returned is innocuous or not depending on the
- * given boolean.
- * @param {string} type MIME type to test
- * @param {boolean} isSafe Whether the given MIME type should be considered safe
- * by {@link SafeUrl.fromBlob}.
- */
- function assertBlobTypeIsSafe(type, isSafe) {
- var safeUrl = goog.html.SafeUrl.fromBlob(new Blob(['test'], {type: type}));
- var extracted = goog.html.SafeUrl.unwrap(safeUrl);
- if (isSafe) {
- assertEquals('blob:', extracted.substring(0, 5));
- } else {
- assertEquals(goog.html.SafeUrl.INNOCUOUS_STRING, extracted);
- }
- }
- function testSafeUrlFromDataUrl_withSafeType() {
- assertDataUrlIsSafe(
- 'data:image/png;base64,' +
- 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=',
- true);
- assertDataUrlIsSafe('dATa:iMage/pNg;bASe64,abc===', true);
- assertDataUrlIsSafe('data:image/webp;base64,abc===', true);
- assertDataUrlIsSafe('data:video/mpeg;base64,abc', true);
- assertDataUrlIsSafe('data:video/ogg;base64,z=', true);
- assertDataUrlIsSafe('data:video/mp4;base64,z=', true);
- assertDataUrlIsSafe('data:video/ogg;base64,z=', true);
- assertDataUrlIsSafe('data:video/webm;base64,z=', true);
- }
- function testSafeUrlFromDataUrl_withUnsafeType() {
- assertDataUrlIsSafe('', false);
- assertDataUrlIsSafe(':', false);
- assertDataUrlIsSafe('data:', false);
- assertDataUrlIsSafe('not-data:image/png;base64,z=', false);
- assertDataUrlIsSafe(' data:image/png;base64,z=', false);
- assertDataUrlIsSafe('data:image/png;base64,z= ', false);
- assertDataUrlIsSafe('data:ximage/png', false);
- assertDataUrlIsSafe('data:ximage/png;base64,z=', false);
- assertDataUrlIsSafe('data:image/pngx;base64,z=', false);
- assertDataUrlIsSafe('data:video/whatever;base64,z=', false);
- assertDataUrlIsSafe('data:video/;base64,z=', false);
- assertDataUrlIsSafe('data:image/png;base64,', false);
- assertDataUrlIsSafe('data:image/png;base64,abc=!', false);
- assertDataUrlIsSafe('data:image/png;base64,$$', false);
- assertDataUrlIsSafe('data:image/png;base64,\0', false);
- assertDataUrlIsSafe('data:video/mp4;baze64,z=', false);
- assertDataUrlIsSafe('data:video/mp4;,z=', false);
- assertDataUrlIsSafe('data:text/html,sdfsdfsdfsfsdfs;base64,anything', false);
- // Valid base64 image URL, but with disallowed mime-type.
- assertDataUrlIsSafe('data:image/svg+xml;base64,abc', false);
- }
- /**
- * Tests creating a SafeUrl from a data URL, asserting whether or not the
- * SafeUrl returned is innocuous or not depending on the given boolean.
- * @param {string} url URL to test.
- * @param {boolean} isSafe Whether the given URL type should be considered safe
- * by {@link SafeUrl.fromDataUrl}.
- */
- function assertDataUrlIsSafe(url, isSafe) {
- var safeUrl = goog.html.SafeUrl.fromDataUrl(url);
- assertEquals(
- isSafe ? url : goog.html.SafeUrl.INNOCUOUS_STRING,
- goog.html.SafeUrl.unwrap(safeUrl));
- }
- function testSafeUrlFromTelUrl_withSafeType() {
- assertTelUrlIsSafe('tEl:+1(23)129-29192A.ABC#;eXt=29', true);
- assertTelUrlIsSafe('tEL:123;randmomparam=123', true);
- }
- function testSafeUrlFromTelUrl_withUnsafeType() {
- assertTelUrlIsSafe('', false);
- assertTelUrlIsSafe(':', false);
- assertTelUrlIsSafe('tell:', false);
- assertTelUrlIsSafe('not-tel:+1', false);
- assertTelUrlIsSafe(' tel:+1', false);
- }
- /**
- * Tests creating a SafeUrl from a tel URL, asserting whether or not the
- * SafeUrl returned is innocuous or not depending on the given boolean.
- * @param {string} url URL to test.
- * @param {boolean} isSafe Whether the given URL type should be considered safe
- * by {@link SafeUrl.fromTelUrl}.
- */
- function assertTelUrlIsSafe(url, isSafe) {
- var safeUrl = goog.html.SafeUrl.fromTelUrl(url);
- assertEquals(
- isSafe ? url : goog.html.SafeUrl.INNOCUOUS_STRING,
- goog.html.SafeUrl.unwrap(safeUrl));
- }
- function testFromTrustedResourceUrl() {
- var url = goog.string.Const.from('test');
- var trustedResourceUrl = goog.html.TrustedResourceUrl.fromConstant(url);
- var safeUrl = goog.html.SafeUrl.fromTrustedResourceUrl(trustedResourceUrl);
- assertEquals(
- goog.string.Const.unwrap(url), goog.html.SafeUrl.unwrap(safeUrl));
- }
- /** @suppress {checkTypes} */
- function testUnwrap() {
- var privateFieldName = 'privateDoNotAccessOrElseSafeHtmlWrappedValue_';
- var markerFieldName = 'SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_';
- var propNames = goog.object.getKeys(goog.html.SafeUrl.sanitize(''));
- assertContains(privateFieldName, propNames);
- assertContains(markerFieldName, propNames);
- var evil = {};
- evil[privateFieldName] = 'javascript:evil()';
- evil[markerFieldName] = {};
- var exception = assertThrows(function() { goog.html.SafeUrl.unwrap(evil); });
- assertContains('expected object of type SafeUrl', exception.message);
- }
- /**
- * Assert that url passes through sanitization unchanged.
- * @param {string|!goog.string.TypedString} url The URL to sanitize.
- */
- function assertGoodUrl(url) {
- var expected = url;
- if (url.implementsGoogStringTypedString) {
- expected = url.getTypedStringValue();
- }
- var safeUrl = goog.html.SafeUrl.sanitize(url);
- var extracted = goog.html.SafeUrl.unwrap(safeUrl);
- assertEquals(expected, extracted);
- }
- /**
- * Assert that url fails sanitization.
- * @param {string|!goog.string.TypedString} url The URL to sanitize.
- */
- function assertBadUrl(url) {
- assertEquals(
- goog.html.SafeUrl.INNOCUOUS_STRING,
- goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(url)));
- }
- function testSafeUrlSanitize_validatesUrl() {
- // Whitelisted schemes.
- assertGoodUrl('http://example.com/');
- assertGoodUrl('https://example.com');
- assertGoodUrl('mailto:foo@example.com');
- assertGoodUrl('ftp://example.com');
- assertGoodUrl('ftp://username@example.com');
- assertGoodUrl('ftp://username:password@example.com');
- // Scheme is case-insensitive
- assertGoodUrl('HTtp://example.com/');
- // Different URL components go through.
- assertGoodUrl('https://example.com/path?foo=bar#baz');
- // Scheme-less URL with authority.
- assertGoodUrl('//example.com/path');
- // Absolute path with no authority.
- assertGoodUrl('/path');
- assertGoodUrl('/path?foo=bar#baz');
- // Relative path.
- assertGoodUrl('path');
- assertGoodUrl('path?foo=bar#baz');
- assertGoodUrl('p//ath');
- assertGoodUrl('p//ath?foo=bar#baz');
- assertGoodUrl('#baz');
- // Restricted character ':' after [/?#].
- assertGoodUrl('?:');
- // .sanitize() works on program constants.
- assertGoodUrl(goog.string.Const.from('http://example.com/'));
- // Non-whitelisted schemes.
- assertBadUrl('javascript:evil();');
- assertBadUrl('javascript:evil();//\nhttp://good.com/');
- assertBadUrl('data:blah');
- // Restricted character before [/?#].
- assertBadUrl(':');
- // '\' is not treated like '/': no restricted characters allowed after it.
- assertBadUrl('\\:');
- // Regex anchored to the left: doesn't match on '/:'.
- assertBadUrl(':/:');
- // Regex multiline not enabled: first line would match but second one
- // wouldn't.
- assertBadUrl('path\n:');
- // .sanitize() does not exempt values known to be program constants.
- assertBadUrl(goog.string.Const.from('data:blah'));
- }
- function testSafeUrlSanitize_idempotentForSafeUrlArgument() {
- // This matches the safe prefix.
- var safeUrl = goog.html.SafeUrl.sanitize('https://www.google.com/');
- var safeUrl2 = goog.html.SafeUrl.sanitize(safeUrl);
- assertEquals(
- goog.html.SafeUrl.unwrap(safeUrl), goog.html.SafeUrl.unwrap(safeUrl2));
- // This doesn't match the safe prefix, getting converted into an innocuous
- // string.
- safeUrl = goog.html.SafeUrl.sanitize('disallowed:foo');
- safeUrl2 = goog.html.SafeUrl.sanitize(safeUrl);
- assertEquals(
- goog.html.SafeUrl.unwrap(safeUrl), goog.html.SafeUrl.unwrap(safeUrl2));
- }
|