1 | const sanitizer = require('sanitizer');
|
2 | const { sanitize } = sanitizer;
|
3 | const assert = require('assert');
|
4 | const gladSanitizer = require('../namespace/sanitize');
|
5 |
|
6 | var logMessages = [];
|
7 |
|
8 | function 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 |
|
23 | function uriPolicy(value, effects, ltype, hints) {
|
24 | if (value && "specialurl" === value.toString()) {
|
25 | return value;
|
26 | }
|
27 | return 'u:' + value.toString();
|
28 | }
|
29 |
|
30 |
|
31 | function logPolicy(msg, detail) {
|
32 | logMessages.push(msg);
|
33 | }
|
34 |
|
35 | describe('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 & out', encoded);
|
65 | });
|
66 |
|
67 |
|
68 | });
|
69 |
|
70 | describe('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('<hello world>', sanitize('<hello world>'))
|
82 | });
|
83 |
|
84 | it('should sanitize more entities', function() {
|
85 | assert.equal('&amp&&&amp', sanitize('&&&&'))
|
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<</i></b> & 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<</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<</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<</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<</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<b && c>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</> 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&Bar</p><script>alert('<b>&</b>')</script>", "<param>");
|
419 | assert.equal(['startDoc', '', '<param>', 'startTag', 'p[id;foo]', '<param>', 'pcdata', 'Foo&Bar', '<param>', 'endTag', 'p', '<param>', 'startTag', 'script[]', '<param>', 'cdata', "alert('<b>&</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 | });
|