UNPKG

19.1 kBJavaScriptView Raw
1const sanitizer = require('sanitizer');
2const { sanitize } = sanitizer;
3const assert = require('assert');
4const gladSanitizer = require('../namespace/sanitize');
5
6var logMessages = [];
7
8function nmTokenPolicy(nmTokens) {
9 if ("specialtoken" === nmTokens) {
10 return nmTokens;
11 }
12 if (/[^a-z\t\n\r ]/i.test(nmTokens)) {
13 return null;
14 } else {
15 return nmTokens.replace(
16 /([^\t\n\r ]+)([\t\n\r ]+|$)/g,
17 function (_, id, spaces) {
18 return 'p-' + id + (spaces ? ' ' : '');
19 });
20 }
21}
22
23function uriPolicy(value, effects, ltype, hints) {
24 if (value && "specialurl" === value.toString()) {
25 return value;
26 }
27 return 'u:' + value.toString();
28}
29
30
31function logPolicy(msg, detail) {
32 logMessages.push(msg);
33}
34
35describe('Glad.sanitizer', function () {
36
37 it('should sanitize and convert to lowercase', () => {
38 let encoded = gladSanitizer.lowerCase('Milo & Otis');
39 assert.equal('milo & otis', encoded);
40 });
41
42 it('should sanitize and convert to uppercase', () => {
43 let encoded = gladSanitizer.upperCase('Milo & Otis');
44 assert.equal('MILO & OTIS', encoded);
45 });
46
47 it('should sanitize and convert to titleCase', () => {
48 let encoded = gladSanitizer.titleCase('milo & otis');
49 assert.equal('Milo & Otis', encoded);
50 });
51
52 it('should sanitize, clean and trim the string', () => {
53 let encoded = gladSanitizer.clean(' milo & otis ');
54 assert.equal('milo & otis', encoded);
55 });
56
57 it('should sanitize and deburr the string', () => {
58 let encoded = gladSanitizer.deburr('мilo<script></script>');
59 assert.equal('milo', encoded);
60 });
61
62 it('should sanitize and sentenceCase the string', () => {
63 let encoded = gladSanitizer.sentenceCase('the quick. fox jumped. over & out');
64 assert.equal('The quick. Fox jumped. Over &amp; out', encoded);
65 });
66
67
68});
69
70describe('Sanitizer.sanitize (npm sanitizer tests should pass)', function() {
71
72 it('should sanitize empty', function() {
73 assert.equal('', sanitize(''))
74 });
75
76 it('should sanitize simple text', function() {
77 assert.equal('hello world', sanitize('hello world'))
78 });
79
80 it('should sanitize entities', function() {
81 assert.equal('&lt;hello world&gt;', sanitize('&lt;hello world&gt;'))
82 });
83
84 it('should sanitize more entities', function() {
85 assert.equal('&amp;amp&amp;&amp;&amp;amp', sanitize('&amp&amp;&&amp'))
86 });
87
88 it('should remove unknown tags', function() {
89 assert.equal('<b>hello <i>world</i></b>', sanitize('<u:y><b>hello <bogus><i>world</i></bogus></b>'))
90 });
91
92 it('should remove unsafe tags', function() {
93 assert.equal('<b>hello <i>world</i></b>', sanitize('<b>hello <i>world</i><script src=foo.js></script></b>'))
94 });
95
96 it('should remove unsafe attributes', function() {
97 assert.equal('<b>hello <i>world</i></b>', sanitize('<b>hello <i onclick="takeOverWorld(this)">world</i></b>'))
98 });
99
100 it('should escape cruft', function() {
101 assert.equal('<b>hello <i>world&lt;</i></b> &amp; tomorrow the universe', sanitize('<b>hello <i>world<</i></b> & tomorrow the universe'))
102 });
103
104 it('should remove tag cruft', function() {
105 assert.equal('<b id="p-foo">hello <i>world&lt;</i></b>', sanitize('<b id="foo" / -->hello <i>world<</i></b>', uriPolicy, nmTokenPolicy))
106 });
107
108 it('should prefix ids and classes', function() {
109 assert.equal('<b id="p-foo" class="p-boo p-bar p-baz">hello <i>world&lt;</i></b>', sanitize('<b id="foo" class="boo bar baz">hello <i>world<</i></b>', uriPolicy, nmTokenPolicy))
110 });
111
112 it('should remove invalid ids and classes', function() {
113 assert.equal('<b>hello <i>world&lt;</i></b>', sanitize('<b id="a," class="b c/d e">hello <i class="i*j">world<</i></b>', uriPolicy, nmTokenPolicy))
114 });
115
116 it('should prefix usemap', function() {
117 assert.equal('<img usemap="#p-foo" src="u:http://bar">', sanitize('<img usemap="#foo" src="http://bar">', uriPolicy, nmTokenPolicy))
118 });
119
120 it('should remove invalid usemaps', function() {
121 assert.equal('<b>hello <i>world&lt;</i></b>', sanitize('<b id="a," class="b c/d e">hello <i class="i*j">world<</i></b>', uriPolicy, nmTokenPolicy))
122 assert.equal('<img src="u:http://bar">', sanitize('<img src="http://bar">', uriPolicy, nmTokenPolicy))
123 assert.equal('<img src="u:http://bar">', sanitize('<img usemap="" src="http://bar">', uriPolicy, nmTokenPolicy))
124 assert.equal('<img src="u:http://bar">', sanitize('<img usemap="foo" src="http://bar">', uriPolicy, nmTokenPolicy))
125 });
126
127 it('should sanitize non-string input', function() {
128 var bad = '<b whacky=foo><script src=badness.js></script>bar</b id=foo>';
129 assert.equal('<b>bar</b>', sanitize({
130 toString: function () {
131 return bad;
132 }
133 }, uriPolicy, nmTokenPolicy))
134 });
135
136 it('should sanitize special chars in attributes', function() {
137 assert.equal('<b title="a&lt;b &amp;&amp; c&gt;b">bar</b>', sanitize('<b title="a<b && c>b">bar</b>', uriPolicy, nmTokenPolicy));
138 });
139
140 it('should sanitize unclosed tags', function() {
141 assert.equal('<div id="p-foo">Bar<br>Baz</div>', sanitize('<div id="foo">Bar<br>Baz', uriPolicy, nmTokenPolicy));
142 });
143
144 it('should sanitize unopened tags', function() {
145 assert.equal('Foo<b>Bar</b>Baz', sanitize('Foo<b></select>Bar</b></b>Baz</select>', uriPolicy, nmTokenPolicy));
146 });
147
148 it('should sanitize unsafe end tags', function() {
149 assert.equal('', sanitize('</meta http-equiv="refresh" content="1;URL=http://evilgadget.com">', uriPolicy, nmTokenPolicy));
150 });
151
152 it('should sanitize empty end tags', function() {
153 assert.equal('<input>', sanitize('<input></input>', uriPolicy, nmTokenPolicy));
154 });
155
156 it('should strip onload', function() {
157 assert.equal('<img src="u:http://foo.com/bar">', sanitize('<img src=http://foo.com/bar ONLOAD=alert(1)>', uriPolicy, nmTokenPolicy));
158 });
159
160 it('should sanitize closing tag parameters', function() {
161 assert.equal('<p>1<p>2</p><p>3</p>5</p>', sanitize('<p>1</b style="x"><p>2</p /bar><p>3</p title=">4">5', uriPolicy, nmTokenPolicy));
162 });
163
164 it('should sanitize auto-closing tags', function() {
165 assert.equal('<p><a name="p-foo"></a> This is the foo section.</p><p><a name="p-bar"></a> This is the bar section.</p>', sanitize('<p><a name="foo"/> This is the foo section.</p><p><a name="bar"/> This is the bar section.</p>', uriPolicy, nmTokenPolicy));
166 });
167
168 it('should sanitize optional end tags', function() {
169 assert.equal('<ol> <li>A</li> <li>B<li>C </ol>', sanitize('<ol> <li>A</li> <li>B<li>C </ol>', uriPolicy, nmTokenPolicy));
170 });
171
172 it('should sanitize folding of html and body tags', function() {
173 assert.equal('<p>P 1</p>', sanitize('<html><head><title>Foo</title></head>'
174 + '<body><p>P 1</p></body></html>', uriPolicy, nmTokenPolicy));
175 assert.equal('Hello', sanitize('<body bgcolor="blue">Hello</body>', uriPolicy, nmTokenPolicy));
176 assert.equal('<p>Foo</p><p>One</p><p>Two</p>Three<p>Four</p>', sanitize('<html>'
177 + '<head>'
178 + '<title>Blah</title>'
179 + '<p>Foo</p>'
180 + '</head>'
181 + '<body>'
182 + '<p>One</p>'
183 + '<p>Two</p>'
184 + 'Three'
185 + '<p>Four</p>'
186 + '</body>'
187 + '</html>', uriPolicy, nmTokenPolicy));
188 });
189
190 it('should sanitize empty and valueless attributes', function() {
191 assert.equal('<input checked="" type="checkbox" id="" class="">', sanitize('<input checked type=checkbox id="" class=>', uriPolicy, nmTokenPolicy));
192 assert.equal('<input checked="" type="checkbox" id="" class="">', sanitize('<input checked type=checkbox id= class="">', uriPolicy, nmTokenPolicy));
193 assert.equal('<input checked="" type="checkbox" id="" class="">', sanitize('<input checked type=checkbox id= class = "">', uriPolicy, nmTokenPolicy));
194 });
195
196 it('should sanitize SGML short tags', function() {
197 assert.equal('', sanitize('<p/b/', uriPolicy, nmTokenPolicy));
198 assert.equal('<p>first part of the text&lt;/&gt; second part</p>', sanitize('<p<a href="/">first part of the text</> second part', uriPolicy, nmTokenPolicy));
199 assert.equal('<p></p>', sanitize('<p<b>', uriPolicy, nmTokenPolicy));
200 });
201
202 it('should sanitize Nul', function() {
203 assert.equal('<a title="x SCRIPT=javascript:alert(1) ignored=ignored"></a>', sanitize('<A TITLE="x\0 SCRIPT=javascript:alert(1) ignored=ignored">', uriPolicy, nmTokenPolicy));
204 });
205
206 it('should sanitize digits in attr names', function() {
207 assert.equal('<div>Hello</div>', sanitize('<div style1="expression(\'alert(1)\')">Hello</div>', uriPolicy, nmTokenPolicy));
208 });
209
210 it('should sanitize digits in attr names', function() {
211 assert.equal('<div>Hello</div>', sanitize('<div style1="expression(\'alert(1)\')">Hello</div>', uriPolicy, nmTokenPolicy));
212 });
213
214 it('should sanitize incomplete tag open', function() {
215 assert.equal('x', sanitize('x<a', uriPolicy, nmTokenPolicy));
216 assert.equal('x', sanitize('x<a ', uriPolicy, nmTokenPolicy));
217 assert.equal('x', sanitize('x<a\n', uriPolicy, nmTokenPolicy));
218 assert.equal('x', sanitize('x<a bc', uriPolicy, nmTokenPolicy));
219 assert.equal('x', sanitize('x<a\nbc', uriPolicy, nmTokenPolicy));
220 });
221
222 it('should sanitize with uri policy', function() {
223
224 assert.equal('<a href="http://www.example.com/">hi</a>', sanitize('<a href="http://www.example.com/">hi</a>', function(uri) {
225 return uri;
226 }));
227
228 assert.equal('<a>hi</a>', sanitize('<a href="http://www.example.com/">hi</a>', function(uri) {
229 return null;
230 }));
231
232 assert.equal('<a>hi</a>', sanitize('<a href="javascript:alert(1)">hi</a>', function(uri) {
233 return uri;
234 }));
235
236 assert.equal('<a>hi</a>', sanitize('<a href="javascript:alert(1)">hi</a>', function(uri) {
237 return null;
238 }));
239
240 assert.equal('<a>hi</a>', sanitize('<a href=" javascript:alert(1)">hi</a>', function(uri) {
241 return uri;
242 }));
243
244 assert.equal('<a>hi</a>', sanitize('<a href=" javascript:alert(1)">hi</a>', function(uri) {
245 return null;
246 }));
247
248 assert.equal('<a href="//www.example.com/">hi</a>', sanitize('<a href="//www.example.com/">hi</a>', function(uri) {
249 return uri;
250 }));
251
252 assert.equal('<a href="foo.html">hi</a>', sanitize('<a href="foo.html">hi</a>', function(uri) {
253 return uri;
254 }));
255
256 assert.equal('<a href="bar/baz.html">hi</a>', sanitize('<a href="foo.html">hi</a>', function(uri) {
257 return "bar/baz.html";
258 }));
259
260 assert.equal('<a href="mailto:jas@example.com">mail me</a>', sanitize('<a href="mailto:jas@example.com">mail me</a>', function(uri) {
261 return uri;
262 }));
263
264 assert.equal('<a>mail me</a>', sanitize('<a href="mailto:jas@example.com">mail me</a>', function(uri) {
265 return null;
266 }));
267
268 assert.equal('<a href="foo.html">test</a>', sanitize('<a href="foo.html">test</a>', function(uri, effect, ltype, hints) {
269 assert.equal("MARKUP", hints.TYPE);
270 assert.equal("href", hints.XML_ATTR);
271 assert.equal("a", hints.XML_TAG);
272 return uri;
273 }));
274 });
275
276 it('should sanitize with tag policy', function() {
277
278 assert.equal('<a href="http://www.example.com/">hi</a> there', sanitizer.sanitizeWithPolicy('<a href="http://www.example.com/">hi</a> there', function(name, attribs) {
279 return {
280 attribs: attribs
281 };
282 }));
283
284 assert.equal(' there', sanitizer.sanitizeWithPolicy('<a href="http://www.example.com/">hi</a> there', function(name, attribs) {
285 return null;
286 }));
287
288 assert.equal('<a x="y">hi</a> there', sanitizer.sanitizeWithPolicy('<a href="http://www.example.com/">hi</a> there', function(name, attribs) {
289 return {
290 attribs: ["x", "y"]
291 };
292 }));
293
294 assert.equal('<xax href="http://www.example.com/">hi</xax> there', sanitizer.sanitizeWithPolicy('<a href="http://www.example.com/">hi</a> there', function(name, attribs) {
295 return {
296 attribs: attribs,
297 tagName: 'x' + name + 'x'
298 };
299 }));
300
301 assert.equal('<span>a<xspanx r="1">b</xspanx>c</span>', sanitizer.sanitizeWithPolicy('<span>a<span r=1>b</span>c</span>', function (name, attribs) {
302 return {
303 attribs: attribs,
304 tagName: attribs.length ? 'x' + name + 'x' : name
305 };
306 }));
307
308 assert.equal('<ul><li>a</li><xlix r="1">b</xlix></ul>', sanitizer.sanitizeWithPolicy('<ul><li>a<li r=1>b</li></ul>', function (name, attribs) {
309 return {
310 attribs: attribs,
311 tagName: attribs.length ? 'x' + name + 'x' : name
312 };
313 }));
314
315 assert.equal('<ul><li>a<ul><xlix r="1">b</xlix></ul></li></ul>', sanitizer.sanitizeWithPolicy('<ul><li>a<ul><li r=1>b</li></ul></li></ul>', function (name, attribs) {
316 return {
317 attribs: attribs,
318 tagName: attribs.length ? 'x' + name + 'x' : name
319 };
320 }));
321 });
322
323 it('should log', function() {
324 logMessages = [];
325 messages = ["a.href changed"];
326 assert.equal('<a href=\"u:http://www.example.com/\">hi</a>', sanitize('<a href="http://www.example.com/">hi</a>', uriPolicy, nmTokenPolicy, logPolicy));
327 assert.equal(messages.length, logMessages.length);
328
329 logMessages.forEach(function (val, i) {
330 assert.equal(messages[i], val);
331 });
332
333 logMessages = [];
334 messages = [];
335 assert.equal('<a href=\"specialurl\">hi</a>', sanitize('<a href="specialurl">hi</a>', uriPolicy, nmTokenPolicy, logPolicy));
336 assert.equal(messages.length, logMessages.length);
337
338 logMessages.forEach(function (val, i) {
339 assert.equal(messages[i], val);
340 });
341
342 logMessages = [];
343 messages = ["div.onclick removed"];
344 assert.equal('<div></div>', sanitize('<div onclick="foo()"></div>', uriPolicy, nmTokenPolicy, logPolicy));
345 assert.equal(messages.length, logMessages.length);
346
347 logMessages.forEach(function (val, i) {
348 assert.equal(messages[i], val);
349 });
350
351 logMessages = [];
352 messages = ["div.onclick removed", "div.id changed"];
353 assert.equal('<div class="specialtoken" id="p-baz"></div>', sanitize('<div onclick="foo()" class="specialtoken" id=baz></div>', uriPolicy, nmTokenPolicy, logPolicy));
354 assert.equal(messages.length, logMessages.length);
355
356 logMessages.forEach(function (val, i) {
357 assert.equal(messages[i], val);
358 });
359
360 logMessages = [];
361 messages = ["script removed"];
362 assert.equal('', sanitize('<script>alert(1);</script>', uriPolicy, nmTokenPolicy, logPolicy));
363 assert.equal(messages.length, logMessages.length);
364
365 logMessages.forEach(function (val, i) {
366 assert.equal(messages[i], val);
367 });
368 });
369
370 it('should SAX parse', function() {
371 var events = [];
372
373 var addTextEvent = function (type, text, param) {
374 var n = events.length;
375
376 if (events[n - 3] === type && events[n - 1] === param) {
377 events[n - 2] += text;
378 } else {
379 events.push(type, text, param);
380 }
381 };
382
383 var saxParser = sanitizer.makeSaxParser({
384
385 startTag: function (name, attribs, param) {
386 events.push('startTag', name + '[' + attribs.join(';') + ']', param);
387 },
388
389 endTag: function (name, param) {
390 events.push('endTag', name, param);
391 },
392
393 pcdata: function (text, param) {
394 addTextEvent('pcdata', text, param);
395 },
396
397 cdata: function (text, param) {
398 addTextEvent('cdata', text, param);
399 },
400
401 rcdata: function (text, param) {
402 addTextEvent('rcdata', text, param);
403 },
404
405 comment: function (text, param) {
406 events.push('comment', text, param);
407 },
408
409 startDoc: function (param) {
410 events.push('startDoc', '', param);
411 },
412
413 endDoc: function (param) {
414 events.push('endDoc', '', param);
415 }
416 });
417
418 saxParser("<p id=foo>Foo&amp;Bar</p><script>alert('<b>&amp;</b>')</script>", "<param>");
419 assert.equal(['startDoc', '', '<param>', 'startTag', 'p[id;foo]', '<param>', 'pcdata', 'Foo&amp;Bar', '<param>', 'endTag', 'p', '<param>', 'startTag', 'script[]', '<param>', 'cdata', "alert('<b>&amp;</b>')", '<param>', 'endTag', 'script', '<param>', 'endDoc', '', '<param>'].join("|"), events.join("|"));
420 events = [];
421 saxParser('<some_tag some_attr=x><!-- com>--ment --></some_tag>', '$P');
422 assert.equal(['startDoc', '', '$P', 'startTag', 'some_tag[some_attr;x]', '$P', 'comment', ' com>--ment ', '$P', 'endTag', 'some_tag', '$P', 'endDoc', '', '$P'].join("|"), events.join("|"));
423 events = [];
424 saxParser('<div><unknown1><unknown2 bar></unknown1>', '$P');
425 assert.equal(['startDoc', '', '$P', 'startTag', 'div[]', '$P', 'startTag', 'unknown1[]', '$P', 'startTag', 'unknown2[bar;]', '$P', 'endTag', 'unknown1', '$P', 'endDoc', '', '$P'].join("|"), events.join("|"));
426 events = [];
427 saxParser('<x:y 3:.=4></x:y>', '$P');
428 assert.equal(['startDoc', '', '$P', 'startTag', 'x:y[3:.;4]', '$P', 'endTag', 'x:y', '$P', 'endDoc', '', '$P'].join("|"), events.join("|"));
429 events = [];
430 saxParser('<div class="testcontainer" id="test"><script>document.write("<b><script>");</script><script>document.write("document.write(");</script><script>document.write("\'Hello,</b> \'");</script><script>document.write(",\'World!\');<\\/script>");</script>!</div>', 'PARAM');
431 assert.equal(['startDoc', '', 'PARAM', 'startTag', 'div[class;testcontainer;id;test]', 'PARAM', 'startTag', 'script[]', 'PARAM', 'cdata', 'document.write("<b><script>");', 'PARAM', 'endTag', 'script', 'PARAM', 'startTag', 'script[]', 'PARAM', 'cdata', 'document.write("document.write(");', 'PARAM', 'endTag', 'script', 'PARAM', 'startTag', 'script[]', 'PARAM', 'cdata', 'document.write("\'Hello,</b> \'");', 'PARAM', 'endTag', 'script', 'PARAM', 'startTag', 'script[]', 'PARAM', 'cdata', 'document.write(",\'World!\');<\\/script>");', 'PARAM', 'endTag', 'script', 'PARAM', 'pcdata', '!', 'PARAM', 'endTag', 'div', 'PARAM', 'endDoc', '', 'PARAM'].join("|"), events.join("|"));
432 });
433});