safeurl_test.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. // Copyright 2013 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 Unit tests for goog.html.SafeUrl and its builders.
  16. */
  17. goog.provide('goog.html.safeUrlTest');
  18. goog.require('goog.html.SafeUrl');
  19. goog.require('goog.html.TrustedResourceUrl');
  20. goog.require('goog.i18n.bidi.Dir');
  21. goog.require('goog.object');
  22. goog.require('goog.string.Const');
  23. goog.require('goog.testing.jsunit');
  24. goog.require('goog.userAgent');
  25. goog.setTestOnly('goog.html.safeUrlTest');
  26. function testSafeUrl() {
  27. var safeUrl = goog.html.SafeUrl.fromConstant(
  28. goog.string.Const.from('javascript:trusted();'));
  29. var extracted = goog.html.SafeUrl.unwrap(safeUrl);
  30. assertEquals('javascript:trusted();', extracted);
  31. assertEquals('javascript:trusted();', goog.html.SafeUrl.unwrap(safeUrl));
  32. assertEquals('SafeUrl{javascript:trusted();}', String(safeUrl));
  33. // URLs are always LTR.
  34. assertEquals(goog.i18n.bidi.Dir.LTR, safeUrl.getDirection());
  35. // Interface markers are present.
  36. assertTrue(safeUrl.implementsGoogStringTypedString);
  37. assertTrue(safeUrl.implementsGoogI18nBidiDirectionalString);
  38. }
  39. function testSafeUrlFromBlob_withSafeType() {
  40. if (isIE9OrLower()) {
  41. return;
  42. }
  43. assertBlobTypeIsSafe('image/png', true);
  44. assertBlobTypeIsSafe('iMage/pNg', true);
  45. assertBlobTypeIsSafe('video/mpeg', true);
  46. assertBlobTypeIsSafe('video/ogg', true);
  47. assertBlobTypeIsSafe('video/mp4', true);
  48. assertBlobTypeIsSafe('video/ogg', true);
  49. assertBlobTypeIsSafe('video/webm', true);
  50. }
  51. function testSafeUrlFromBlob_withUnsafeType() {
  52. if (isIE9OrLower()) {
  53. return;
  54. }
  55. assertBlobTypeIsSafe('', false);
  56. assertBlobTypeIsSafe('ximage/png', false);
  57. assertBlobTypeIsSafe('image/pngx', false);
  58. assertBlobTypeIsSafe('video/whatever', false);
  59. assertBlobTypeIsSafe('video/', false);
  60. }
  61. /** @return {boolean} True if running on IE9 or lower. */
  62. function isIE9OrLower() {
  63. return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10');
  64. }
  65. /**
  66. * Tests creating a SafeUrl from a blob with the given MIME type, asserting
  67. * whether or not the SafeUrl returned is innocuous or not depending on the
  68. * given boolean.
  69. * @param {string} type MIME type to test
  70. * @param {boolean} isSafe Whether the given MIME type should be considered safe
  71. * by {@link SafeUrl.fromBlob}.
  72. */
  73. function assertBlobTypeIsSafe(type, isSafe) {
  74. var safeUrl = goog.html.SafeUrl.fromBlob(new Blob(['test'], {type: type}));
  75. var extracted = goog.html.SafeUrl.unwrap(safeUrl);
  76. if (isSafe) {
  77. assertEquals('blob:', extracted.substring(0, 5));
  78. } else {
  79. assertEquals(goog.html.SafeUrl.INNOCUOUS_STRING, extracted);
  80. }
  81. }
  82. function testSafeUrlFromDataUrl_withSafeType() {
  83. assertDataUrlIsSafe(
  84. 'data:image/png;base64,' +
  85. 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=',
  86. true);
  87. assertDataUrlIsSafe('dATa:iMage/pNg;bASe64,abc===', true);
  88. assertDataUrlIsSafe('data:image/webp;base64,abc===', true);
  89. assertDataUrlIsSafe('data:video/mpeg;base64,abc', true);
  90. assertDataUrlIsSafe('data:video/ogg;base64,z=', true);
  91. assertDataUrlIsSafe('data:video/mp4;base64,z=', true);
  92. assertDataUrlIsSafe('data:video/ogg;base64,z=', true);
  93. assertDataUrlIsSafe('data:video/webm;base64,z=', true);
  94. }
  95. function testSafeUrlFromDataUrl_withUnsafeType() {
  96. assertDataUrlIsSafe('', false);
  97. assertDataUrlIsSafe(':', false);
  98. assertDataUrlIsSafe('data:', false);
  99. assertDataUrlIsSafe('not-data:image/png;base64,z=', false);
  100. assertDataUrlIsSafe(' data:image/png;base64,z=', false);
  101. assertDataUrlIsSafe('data:image/png;base64,z= ', false);
  102. assertDataUrlIsSafe('data:ximage/png', false);
  103. assertDataUrlIsSafe('data:ximage/png;base64,z=', false);
  104. assertDataUrlIsSafe('data:image/pngx;base64,z=', false);
  105. assertDataUrlIsSafe('data:video/whatever;base64,z=', false);
  106. assertDataUrlIsSafe('data:video/;base64,z=', false);
  107. assertDataUrlIsSafe('data:image/png;base64,', false);
  108. assertDataUrlIsSafe('data:image/png;base64,abc=!', false);
  109. assertDataUrlIsSafe('data:image/png;base64,$$', false);
  110. assertDataUrlIsSafe('data:image/png;base64,\0', false);
  111. assertDataUrlIsSafe('data:video/mp4;baze64,z=', false);
  112. assertDataUrlIsSafe('data:video/mp4;,z=', false);
  113. assertDataUrlIsSafe('data:text/html,sdfsdfsdfsfsdfs;base64,anything', false);
  114. // Valid base64 image URL, but with disallowed mime-type.
  115. assertDataUrlIsSafe('data:image/svg+xml;base64,abc', false);
  116. }
  117. /**
  118. * Tests creating a SafeUrl from a data URL, asserting whether or not the
  119. * SafeUrl returned is innocuous or not depending on the given boolean.
  120. * @param {string} url URL to test.
  121. * @param {boolean} isSafe Whether the given URL type should be considered safe
  122. * by {@link SafeUrl.fromDataUrl}.
  123. */
  124. function assertDataUrlIsSafe(url, isSafe) {
  125. var safeUrl = goog.html.SafeUrl.fromDataUrl(url);
  126. assertEquals(
  127. isSafe ? url : goog.html.SafeUrl.INNOCUOUS_STRING,
  128. goog.html.SafeUrl.unwrap(safeUrl));
  129. }
  130. function testSafeUrlFromTelUrl_withSafeType() {
  131. assertTelUrlIsSafe('tEl:+1(23)129-29192A.ABC#;eXt=29', true);
  132. assertTelUrlIsSafe('tEL:123;randmomparam=123', true);
  133. }
  134. function testSafeUrlFromTelUrl_withUnsafeType() {
  135. assertTelUrlIsSafe('', false);
  136. assertTelUrlIsSafe(':', false);
  137. assertTelUrlIsSafe('tell:', false);
  138. assertTelUrlIsSafe('not-tel:+1', false);
  139. assertTelUrlIsSafe(' tel:+1', false);
  140. }
  141. /**
  142. * Tests creating a SafeUrl from a tel URL, asserting whether or not the
  143. * SafeUrl returned is innocuous or not depending on the given boolean.
  144. * @param {string} url URL to test.
  145. * @param {boolean} isSafe Whether the given URL type should be considered safe
  146. * by {@link SafeUrl.fromTelUrl}.
  147. */
  148. function assertTelUrlIsSafe(url, isSafe) {
  149. var safeUrl = goog.html.SafeUrl.fromTelUrl(url);
  150. assertEquals(
  151. isSafe ? url : goog.html.SafeUrl.INNOCUOUS_STRING,
  152. goog.html.SafeUrl.unwrap(safeUrl));
  153. }
  154. function testFromTrustedResourceUrl() {
  155. var url = goog.string.Const.from('test');
  156. var trustedResourceUrl = goog.html.TrustedResourceUrl.fromConstant(url);
  157. var safeUrl = goog.html.SafeUrl.fromTrustedResourceUrl(trustedResourceUrl);
  158. assertEquals(
  159. goog.string.Const.unwrap(url), goog.html.SafeUrl.unwrap(safeUrl));
  160. }
  161. /** @suppress {checkTypes} */
  162. function testUnwrap() {
  163. var privateFieldName = 'privateDoNotAccessOrElseSafeHtmlWrappedValue_';
  164. var markerFieldName = 'SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_';
  165. var propNames = goog.object.getKeys(goog.html.SafeUrl.sanitize(''));
  166. assertContains(privateFieldName, propNames);
  167. assertContains(markerFieldName, propNames);
  168. var evil = {};
  169. evil[privateFieldName] = 'javascript:evil()';
  170. evil[markerFieldName] = {};
  171. var exception = assertThrows(function() { goog.html.SafeUrl.unwrap(evil); });
  172. assertContains('expected object of type SafeUrl', exception.message);
  173. }
  174. /**
  175. * Assert that url passes through sanitization unchanged.
  176. * @param {string|!goog.string.TypedString} url The URL to sanitize.
  177. */
  178. function assertGoodUrl(url) {
  179. var expected = url;
  180. if (url.implementsGoogStringTypedString) {
  181. expected = url.getTypedStringValue();
  182. }
  183. var safeUrl = goog.html.SafeUrl.sanitize(url);
  184. var extracted = goog.html.SafeUrl.unwrap(safeUrl);
  185. assertEquals(expected, extracted);
  186. }
  187. /**
  188. * Assert that url fails sanitization.
  189. * @param {string|!goog.string.TypedString} url The URL to sanitize.
  190. */
  191. function assertBadUrl(url) {
  192. assertEquals(
  193. goog.html.SafeUrl.INNOCUOUS_STRING,
  194. goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(url)));
  195. }
  196. function testSafeUrlSanitize_validatesUrl() {
  197. // Whitelisted schemes.
  198. assertGoodUrl('http://example.com/');
  199. assertGoodUrl('https://example.com');
  200. assertGoodUrl('mailto:foo@example.com');
  201. assertGoodUrl('ftp://example.com');
  202. assertGoodUrl('ftp://username@example.com');
  203. assertGoodUrl('ftp://username:password@example.com');
  204. // Scheme is case-insensitive
  205. assertGoodUrl('HTtp://example.com/');
  206. // Different URL components go through.
  207. assertGoodUrl('https://example.com/path?foo=bar#baz');
  208. // Scheme-less URL with authority.
  209. assertGoodUrl('//example.com/path');
  210. // Absolute path with no authority.
  211. assertGoodUrl('/path');
  212. assertGoodUrl('/path?foo=bar#baz');
  213. // Relative path.
  214. assertGoodUrl('path');
  215. assertGoodUrl('path?foo=bar#baz');
  216. assertGoodUrl('p//ath');
  217. assertGoodUrl('p//ath?foo=bar#baz');
  218. assertGoodUrl('#baz');
  219. // Restricted character ':' after [/?#].
  220. assertGoodUrl('?:');
  221. // .sanitize() works on program constants.
  222. assertGoodUrl(goog.string.Const.from('http://example.com/'));
  223. // Non-whitelisted schemes.
  224. assertBadUrl('javascript:evil();');
  225. assertBadUrl('javascript:evil();//\nhttp://good.com/');
  226. assertBadUrl('data:blah');
  227. // Restricted character before [/?#].
  228. assertBadUrl(':');
  229. // '\' is not treated like '/': no restricted characters allowed after it.
  230. assertBadUrl('\\:');
  231. // Regex anchored to the left: doesn't match on '/:'.
  232. assertBadUrl(':/:');
  233. // Regex multiline not enabled: first line would match but second one
  234. // wouldn't.
  235. assertBadUrl('path\n:');
  236. // .sanitize() does not exempt values known to be program constants.
  237. assertBadUrl(goog.string.Const.from('data:blah'));
  238. }
  239. function testSafeUrlSanitize_idempotentForSafeUrlArgument() {
  240. // This matches the safe prefix.
  241. var safeUrl = goog.html.SafeUrl.sanitize('https://www.google.com/');
  242. var safeUrl2 = goog.html.SafeUrl.sanitize(safeUrl);
  243. assertEquals(
  244. goog.html.SafeUrl.unwrap(safeUrl), goog.html.SafeUrl.unwrap(safeUrl2));
  245. // This doesn't match the safe prefix, getting converted into an innocuous
  246. // string.
  247. safeUrl = goog.html.SafeUrl.sanitize('disallowed:foo');
  248. safeUrl2 = goog.html.SafeUrl.sanitize(safeUrl);
  249. assertEquals(
  250. goog.html.SafeUrl.unwrap(safeUrl), goog.html.SafeUrl.unwrap(safeUrl2));
  251. }