unsafe_test.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. // Copyright 2016 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 Test for the unsafe API of the HTML Sanitizer.
  16. */
  17. goog.setTestOnly();
  18. goog.require('goog.html.SafeHtml');
  19. goog.require('goog.html.sanitizer.HtmlSanitizer');
  20. goog.require('goog.html.sanitizer.TagBlacklist');
  21. goog.require('goog.html.sanitizer.unsafe');
  22. goog.require('goog.string.Const');
  23. goog.require('goog.testing.dom');
  24. goog.require('goog.testing.jsunit');
  25. goog.require('goog.userAgent');
  26. /**
  27. * @return {boolean} Whether the browser is IE8 or below.
  28. */
  29. function isIE8() {
  30. return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(9);
  31. }
  32. /**
  33. * @return {boolean} Whether the browser is IE9.
  34. */
  35. function isIE9() {
  36. return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(10) && !isIE8();
  37. }
  38. var just = goog.string.Const.from('test');
  39. /**
  40. * Sanitizes the original HTML and asserts that it is the same as the expected
  41. * HTML. Supports adding tags and attributes through the unsafe API.
  42. * @param {string} originalHtml
  43. * @param {string} expectedHtml
  44. * @param {?Array<string>=} opt_tags
  45. * @param {?Array<(string|!goog.html.sanitizer.HtmlSanitizerAttributePolicy)>=}
  46. * opt_attrs
  47. * @param {?goog.html.sanitizer.HtmlSanitizer.Builder=} opt_builder
  48. */
  49. function assertSanitizedHtml(
  50. originalHtml, expectedHtml, opt_tags, opt_attrs, opt_builder) {
  51. var builder = opt_builder || new goog.html.sanitizer.HtmlSanitizer.Builder();
  52. if (opt_tags)
  53. builder = goog.html.sanitizer.unsafe.alsoAllowTags(just, builder, opt_tags);
  54. if (opt_attrs)
  55. builder = goog.html.sanitizer.unsafe.alsoAllowAttributes(
  56. just, builder, opt_attrs);
  57. var sanitizer = builder.build();
  58. try {
  59. var sanitized = sanitizer.sanitize(originalHtml);
  60. if (isIE9()) {
  61. assertEquals('', goog.html.SafeHtml.unwrap(sanitized));
  62. return;
  63. }
  64. goog.testing.dom.assertHtmlMatches(
  65. expectedHtml, goog.html.SafeHtml.unwrap(sanitized),
  66. true /* opt_strictAttributes */);
  67. } catch (err) {
  68. if (!isIE8()) {
  69. throw err;
  70. }
  71. }
  72. }
  73. function testAllowEmptyTagList() {
  74. var input = '<sdf><aaa></aaa></sdf><b></b>';
  75. var expected = '<span><span></span></span><b></b>';
  76. assertSanitizedHtml(input, expected, []);
  77. }
  78. function testAllowBlacklistedTag() {
  79. var input = '<div><script>aaa</script></div>';
  80. var expected = '<div></div>';
  81. assertSanitizedHtml(input, expected, ['SCriPT']);
  82. }
  83. function testAllowUnknownTags() {
  84. var input = '<hello><bye>aaa</bye></hello><zzz></zzz>';
  85. var expected = '<hello><span>aaa</span></hello><zzz></zzz>';
  86. assertSanitizedHtml(input, expected, ['HElLO', 'zZZ']);
  87. }
  88. function testAllowAlreadyWhiteListedTag() {
  89. var input = '<hello><p><zzz></zzz></p></hello>';
  90. var expected = '<span><p><zzz></zzz></p></span>';
  91. assertSanitizedHtml(input, expected, ['p', 'ZZZ']);
  92. }
  93. function testAllowEmptyAttrList() {
  94. var input = '<a href="#" qwe="nope">b</a>';
  95. var expected = '<a href="#">b</a>';
  96. assertSanitizedHtml(input, expected, null, []);
  97. }
  98. function testAllowUnknownAttributeSimple() {
  99. var input = '<qqq zzz="3" nnn="no"></qqq>';
  100. var expected = '<span zzz="3"></span>';
  101. assertSanitizedHtml(input, expected, null, ['Zzz']);
  102. }
  103. function testAllowUnknownAttributeWildCard() {
  104. var input = '<div ab="yes" bb="no"><img ab="yep" bb="no" /></div>';
  105. var expected = '<div ab="yes"><img ab="yep" /></div>';
  106. assertSanitizedHtml(
  107. input, expected, null, [{tagName: '*', attributeName: 'aB'}]);
  108. }
  109. function testAllowUnknownAttributeOnSpecificTag() {
  110. var input = '<a www="3" zzz="4">fff</a><img www="3" />';
  111. var expected = '<a www="3">fff</a><img />';
  112. assertSanitizedHtml(
  113. input, expected, null, [{tagName: 'a', attributeName: 'WwW'}]);
  114. }
  115. function testAllowUnknownAttributePolicy() {
  116. var input = '<img ab="yes" /><img ab="no" />';
  117. var expected = '<img ab="yes" /><img />';
  118. assertSanitizedHtml(input, expected, null, [{
  119. tagName: '*',
  120. attributeName: 'aB',
  121. policy: function(value, hints) {
  122. assertEquals(hints.attributeName, 'ab');
  123. return value === 'yes' ? value : null;
  124. }
  125. }]);
  126. }
  127. function testAllowOverwriteAttrPolicy() {
  128. var input = '<a href="yes"></a><a href="no"></a>';
  129. var expected = '<a href="yes"></a><a></a>';
  130. assertSanitizedHtml(
  131. input, expected, null, [{
  132. tagName: 'a',
  133. attributeName: 'href',
  134. policy: function(value) { return value === 'yes' ? value : null; }
  135. }]);
  136. }
  137. function testAllowDAttribute() {
  138. var input = '<path d="1.5 1.5 1.5 14.5 14.5 14.5 14.5 1.5"/>';
  139. var expected = '<path d="1.5 1.5 1.5 14.5 14.5 14.5 14.5 1.5"/>';
  140. assertSanitizedHtml(
  141. input, expected, ['path'], [{tagName: 'path', attributeName: 'd'}]);
  142. }
  143. function testWhitelistAliasing() {
  144. var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
  145. goog.html.sanitizer.unsafe.alsoAllowTags(just, builder, ['QqQ']);
  146. goog.html.sanitizer.unsafe.alsoAllowAttributes(just, builder, ['QqQ']);
  147. builder.build();
  148. assertUndefined(goog.html.sanitizer.TagWhitelist['QQQ']);
  149. assertUndefined(goog.html.sanitizer.TagWhitelist['QqQ']);
  150. assertUndefined(goog.html.sanitizer.TagWhitelist['qqq']);
  151. assertUndefined(goog.html.sanitizer.AttributeWhitelist['* QQQ']);
  152. assertUndefined(goog.html.sanitizer.AttributeWhitelist['* QqQ']);
  153. assertUndefined(goog.html.sanitizer.AttributeWhitelist['* qqq']);
  154. }
  155. function testTemplateUnsanitized() {
  156. if (!goog.html.sanitizer.HTML_SANITIZER_TEMPLATE_SUPPORTED) {
  157. return;
  158. }
  159. var input = '<template><div>a</div><script>qqq</script>' +
  160. '<template>a</template></template>';
  161. // TODO(pelizzi): use unblockTag once it's available
  162. delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];
  163. var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
  164. goog.html.sanitizer.unsafe.keepUnsanitizedTemplateContents(just, builder);
  165. assertSanitizedHtml(input, input, ['TEMPLATE'], null, builder);
  166. goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;
  167. }
  168. function testTemplateSanitizedUnsanitizedXSS() {
  169. if (!goog.html.sanitizer.HTML_SANITIZER_TEMPLATE_SUPPORTED) {
  170. return;
  171. }
  172. var input = '<template><p>a</p><script>aaaa;</script></template>';
  173. var expected = '<span><p>a</p></span>';
  174. delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];
  175. var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
  176. goog.html.sanitizer.unsafe.keepUnsanitizedTemplateContents(just, builder);
  177. assertSanitizedHtml(input, expected, null, null, builder);
  178. goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;
  179. }
  180. function testTemplateUnsanitizedThrowsIE() {
  181. if (goog.html.sanitizer.HTML_SANITIZER_TEMPLATE_SUPPORTED) {
  182. return;
  183. }
  184. var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
  185. assertThrows(function() {
  186. goog.html.sanitizer.unsafe.keepUnsanitizedTemplateContents(just, builder);
  187. });
  188. }
  189. function testAllowRelaxExistingAttributePolicyWildcard() {
  190. var input = '<a href="javascript:alert(1)"></a>';
  191. // define a tag-specific one, takes precedence
  192. assertSanitizedHtml(
  193. input, input, null,
  194. [{tagName: 'a', attributeName: 'href', policy: goog.functions.identity}]);
  195. // overwrite the global one
  196. assertSanitizedHtml(
  197. input, input, null,
  198. [{tagName: '*', attributeName: 'href', policy: goog.functions.identity}]);
  199. }
  200. function testAllowRelaxExistingAttributePolicySpecific() {
  201. var input = '<a target="foo"></a>';
  202. var expected = '<a></a>';
  203. // overwrite the global one, the specific one still has precedence
  204. assertSanitizedHtml(input, expected, null, [
  205. {tagName: '*', attributeName: 'target', policy: goog.functions.identity}
  206. ]);
  207. // overwrite the tag-specific one, this one should take precedence
  208. assertSanitizedHtml(input, input, null, [
  209. {tagName: 'a', attributeName: 'target', policy: goog.functions.identity}
  210. ]);
  211. }