sanitize 4.6.4 → 6.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,25 +8,22 @@ describe 'Sanitize::Transformers::CleanElement' do
8
8
  strings = {
9
9
  :basic => {
10
10
  :html => '<b>Lo<!-- comment -->rem</b> <a href="pants" title="foo" style="text-decoration: underline;">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br/>amet <style>.foo { color: #fff; }</style> <script>alert("hello world");</script>',
11
-
12
- :default => 'Lorem ipsum dolor sit amet .foo { color: #fff; } alert("hello world");',
13
- :restricted => '<b>Lorem</b> ipsum <strong>dolor</strong> sit amet .foo { color: #fff; } alert("hello world");',
14
- :basic => '<b>Lorem</b> <a href="pants" rel="nofollow">ipsum</a> <a href="http://foo.com/" rel="nofollow"><strong>dolor</strong></a> sit<br>amet .foo { color: #fff; } alert("hello world");',
15
- :relaxed => '<b>Lorem</b> <a href="pants" title="foo" style="text-decoration: underline;">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br>amet <style>.foo { color: #fff; }</style> alert("hello world");'
11
+ :default => 'Lorem ipsum dolor sit amet ',
12
+ :restricted => '<b>Lorem</b> ipsum <strong>dolor</strong> sit amet ',
13
+ :basic => '<b>Lorem</b> <a href="pants" rel="nofollow">ipsum</a> <a href="http://foo.com/" rel="nofollow"><strong>dolor</strong></a> sit<br>amet ',
14
+ :relaxed => '<b>Lorem</b> <a href="pants" title="foo" style="text-decoration: underline;">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br>amet <style>.foo { color: #fff; }</style> '
16
15
  },
17
16
 
18
17
  :malformed => {
19
18
  :html => 'Lo<!-- comment -->rem</b> <a href=pants title="foo>ipsum <a href="http://foo.com/"><strong>dolor</a></strong> sit<br/>amet <script>alert("hello world");',
20
-
21
- :default => 'Lorem dolor sit amet alert("hello world");',
22
- :restricted => 'Lorem <strong>dolor</strong> sit amet alert("hello world");',
23
- :basic => 'Lorem <a href="pants" rel="nofollow"><strong>dolor</strong></a> sit<br>amet alert("hello world");',
24
- :relaxed => 'Lorem <a href="pants" title="foo&gt;ipsum &lt;a href="><strong>dolor</strong></a> sit<br>amet alert("hello world");',
19
+ :default => 'Lorem dolor sit amet ',
20
+ :restricted => 'Lorem <strong>dolor</strong> sit amet ',
21
+ :basic => 'Lorem <a href="pants" rel="nofollow"><strong>dolor</strong></a> sit<br>amet ',
22
+ :relaxed => 'Lorem <a href="pants" title="foo>ipsum <a href="><strong>dolor</strong></a> sit<br>amet ',
25
23
  },
26
24
 
27
25
  :unclosed => {
28
26
  :html => '<p>a</p><blockquote>b',
29
-
30
27
  :default => ' a b ',
31
28
  :restricted => ' a b ',
32
29
  :basic => '<p>a</p><blockquote>b</blockquote>',
@@ -35,7 +32,6 @@ describe 'Sanitize::Transformers::CleanElement' do
35
32
 
36
33
  :malicious => {
37
34
  :html => '<b>Lo<!-- comment -->rem</b> <a href="javascript:pants" title="foo">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br/>amet <<foo>script>alert("hello world");</script>',
38
-
39
35
  :default => 'Lorem ipsum dolor sit amet &lt;script&gt;alert("hello world");',
40
36
  :restricted => '<b>Lorem</b> ipsum <strong>dolor</strong> sit amet &lt;script&gt;alert("hello world");',
41
37
  :basic => '<b>Lorem</b> <a rel="nofollow">ipsum</a> <a href="http://foo.com/" rel="nofollow"><strong>dolor</strong></a> sit<br>amet &lt;script&gt;alert("hello world");',
@@ -166,45 +162,95 @@ describe 'Sanitize::Transformers::CleanElement' do
166
162
  }
167
163
 
168
164
  describe 'Default config' do
169
- it 'should remove non-whitelisted elements, leaving safe contents behind' do
170
- Sanitize.fragment('foo <b>bar</b> <strong><a href="#a">baz</a></strong> quux')
165
+ it 'should remove non-allowlisted elements, leaving safe contents behind' do
166
+ _(Sanitize.fragment('foo <b>bar</b> <strong><a href="#a">baz</a></strong> quux'))
171
167
  .must_equal 'foo bar baz quux'
172
168
 
173
- Sanitize.fragment('<script>alert("<xss>");</script>')
174
- .must_equal 'alert("&lt;xss&gt;");'
169
+ _(Sanitize.fragment('<script>alert("<xss>");</script>'))
170
+ .must_equal ''
175
171
 
176
- Sanitize.fragment('<<script>script>alert("<xss>");</<script>>')
177
- .must_equal '&lt;script&gt;alert("&lt;xss&gt;");&lt;/&lt;script&gt;&gt;'
172
+ _(Sanitize.fragment('<<script>script>alert("<xss>");</<script>>'))
173
+ .must_equal '&lt;'
178
174
 
179
- Sanitize.fragment('< script <>> alert("<xss>");</script>')
175
+ _(Sanitize.fragment('< script <>> alert("<xss>");</script>'))
180
176
  .must_equal '&lt; script &lt;&gt;&gt; alert("");'
181
177
  end
182
178
 
183
179
  it 'should surround the contents of :whitespace_elements with space characters when removing the element' do
184
- Sanitize.fragment('foo<div>bar</div>baz')
180
+ _(Sanitize.fragment('foo<div>bar</div>baz'))
185
181
  .must_equal 'foo bar baz'
186
182
 
187
- Sanitize.fragment('foo<br>bar<br>baz')
183
+ _(Sanitize.fragment('foo<br>bar<br>baz'))
188
184
  .must_equal 'foo bar baz'
189
185
 
190
- Sanitize.fragment('foo<hr>bar<hr>baz')
186
+ _(Sanitize.fragment('foo<hr>bar<hr>baz'))
191
187
  .must_equal 'foo bar baz'
192
188
  end
193
189
 
194
190
  it 'should not choke on several instances of the same element in a row' do
195
- Sanitize.fragment('<img src="http://www.google.com/intl/en_ALL/images/logo.gif"><img src="http://www.google.com/intl/en_ALL/images/logo.gif"><img src="http://www.google.com/intl/en_ALL/images/logo.gif"><img src="http://www.google.com/intl/en_ALL/images/logo.gif">')
191
+ _(Sanitize.fragment('<img src="http://www.google.com/intl/en_ALL/images/logo.gif"><img src="http://www.google.com/intl/en_ALL/images/logo.gif"><img src="http://www.google.com/intl/en_ALL/images/logo.gif"><img src="http://www.google.com/intl/en_ALL/images/logo.gif">'))
192
+ .must_equal ''
193
+ end
194
+
195
+ it 'should not preserve the content of removed `iframe` elements' do
196
+ _(Sanitize.fragment('<iframe>hello! <script>alert(0)</script></iframe>'))
197
+ .must_equal ''
198
+ end
199
+
200
+ it 'should not preserve the content of removed `math` elements' do
201
+ _(Sanitize.fragment('<math>hello! <script>alert(0)</script></math>'))
202
+ .must_equal ''
203
+ end
204
+
205
+ it 'should not preserve the content of removed `noembed` elements' do
206
+ _(Sanitize.fragment('<noembed>hello! <script>alert(0)</script></noembed>'))
207
+ .must_equal ''
208
+ end
209
+
210
+ it 'should not preserve the content of removed `noframes` elements' do
211
+ _(Sanitize.fragment('<noframes>hello! <script>alert(0)</script></noframes>'))
212
+ .must_equal ''
213
+ end
214
+
215
+ it 'should not preserve the content of removed `noscript` elements' do
216
+ _(Sanitize.fragment('<noscript>hello! <script>alert(0)</script></noscript>'))
217
+ .must_equal ''
218
+ end
219
+
220
+ it 'should not preserve the content of removed `plaintext` elements' do
221
+ _(Sanitize.fragment('<plaintext>hello! <script>alert(0)</script>'))
222
+ .must_equal ''
223
+ end
224
+
225
+ it 'should not preserve the content of removed `script` elements' do
226
+ _(Sanitize.fragment('<script>hello! <script>alert(0)</script></script>'))
227
+ .must_equal ''
228
+ end
229
+
230
+ it 'should not preserve the content of removed `style` elements' do
231
+ _(Sanitize.fragment('<style>hello! <script>alert(0)</script></style>'))
232
+ .must_equal ''
233
+ end
234
+
235
+ it 'should not preserve the content of removed `svg` elements' do
236
+ _(Sanitize.fragment('<svg>hello! <script>alert(0)</script></svg>'))
237
+ .must_equal ''
238
+ end
239
+
240
+ it 'should not preserve the content of removed `xmp` elements' do
241
+ _(Sanitize.fragment('<xmp>hello! <script>alert(0)</script></xmp>'))
196
242
  .must_equal ''
197
243
  end
198
244
 
199
245
  strings.each do |name, data|
200
246
  it "should clean #{name} HTML" do
201
- Sanitize.fragment(data[:html]).must_equal(data[:default])
247
+ _(Sanitize.fragment(data[:html])).must_equal(data[:default])
202
248
  end
203
249
  end
204
250
 
205
251
  protocols.each do |name, data|
206
252
  it "should not allow #{name}" do
207
- Sanitize.fragment(data[:html]).must_equal(data[:default])
253
+ _(Sanitize.fragment(data[:html])).must_equal(data[:default])
208
254
  end
209
255
  end
210
256
  end
@@ -216,13 +262,13 @@ describe 'Sanitize::Transformers::CleanElement' do
216
262
 
217
263
  strings.each do |name, data|
218
264
  it "should clean #{name} HTML" do
219
- @s.fragment(data[:html]).must_equal(data[:restricted])
265
+ _(@s.fragment(data[:html])).must_equal(data[:restricted])
220
266
  end
221
267
  end
222
268
 
223
269
  protocols.each do |name, data|
224
270
  it "should not allow #{name}" do
225
- @s.fragment(data[:html]).must_equal(data[:restricted])
271
+ _(@s.fragment(data[:html])).must_equal(data[:restricted])
226
272
  end
227
273
  end
228
274
  end
@@ -233,24 +279,24 @@ describe 'Sanitize::Transformers::CleanElement' do
233
279
  end
234
280
 
235
281
  it 'should not choke on valueless attributes' do
236
- @s.fragment('foo <a href>foo</a> bar')
237
- .must_equal 'foo <a href rel="nofollow">foo</a> bar'
282
+ _(@s.fragment('foo <a href>foo</a> bar'))
283
+ .must_equal 'foo <a href="" rel="nofollow">foo</a> bar'
238
284
  end
239
285
 
240
286
  it 'should downcase attribute names' do
241
- @s.fragment('<a HREF="javascript:alert(\'foo\')">bar</a>')
287
+ _(@s.fragment('<a HREF="javascript:alert(\'foo\')">bar</a>'))
242
288
  .must_equal '<a rel="nofollow">bar</a>'
243
289
  end
244
290
 
245
291
  strings.each do |name, data|
246
292
  it "should clean #{name} HTML" do
247
- @s.fragment(data[:html]).must_equal(data[:basic])
293
+ _(@s.fragment(data[:html])).must_equal(data[:basic])
248
294
  end
249
295
  end
250
296
 
251
297
  protocols.each do |name, data|
252
298
  it "should not allow #{name}" do
253
- @s.fragment(data[:html]).must_equal(data[:basic])
299
+ _(@s.fragment(data[:html])).must_equal(data[:basic])
254
300
  end
255
301
  end
256
302
  end
@@ -261,110 +307,124 @@ describe 'Sanitize::Transformers::CleanElement' do
261
307
  end
262
308
 
263
309
  it 'should encode special chars in attribute values' do
264
- @s.fragment('<a href="http://example.com" title="<b>&eacute;xamples</b> & things">foo</a>')
265
- .must_equal '<a href="http://example.com" title="&lt;b&gt;éxamples&lt;/b&gt; &amp; things">foo</a>'
310
+ _(@s.fragment('<a href="http://example.com" title="<b>&eacute;xamples</b> & things">foo</a>'))
311
+ .must_equal '<a href="http://example.com" title="<bxamples</b> &amp; things">foo</a>'
266
312
  end
267
313
 
268
314
  strings.each do |name, data|
269
315
  it "should clean #{name} HTML" do
270
- @s.fragment(data[:html]).must_equal(data[:relaxed])
316
+ _(@s.fragment(data[:html])).must_equal(data[:relaxed])
271
317
  end
272
318
  end
273
319
 
274
320
  protocols.each do |name, data|
275
321
  it "should not allow #{name}" do
276
- @s.fragment(data[:html]).must_equal(data[:relaxed])
322
+ _(@s.fragment(data[:html])).must_equal(data[:relaxed])
277
323
  end
278
324
  end
279
325
  end
280
326
 
281
327
  describe 'Custom configs' do
282
- it 'should allow attributes on all elements if whitelisted under :all' do
328
+ it 'should allow attributes on all elements if allowlisted under :all' do
283
329
  input = '<p class="foo">bar</p>'
284
330
 
285
- Sanitize.fragment(input).must_equal ' bar '
331
+ _(Sanitize.fragment(input)).must_equal ' bar '
286
332
 
287
- Sanitize.fragment(input, {
333
+ _(Sanitize.fragment(input, {
288
334
  :elements => ['p'],
289
335
  :attributes => {:all => ['class']}
290
- }).must_equal input
336
+ })).must_equal input
291
337
 
292
- Sanitize.fragment(input, {
338
+ _(Sanitize.fragment(input, {
293
339
  :elements => ['p'],
294
340
  :attributes => {'div' => ['class']}
295
- }).must_equal '<p>bar</p>'
341
+ })).must_equal '<p>bar</p>'
296
342
 
297
- Sanitize.fragment(input, {
343
+ _(Sanitize.fragment(input, {
298
344
  :elements => ['p'],
299
345
  :attributes => {'p' => ['title'], :all => ['class']}
300
- }).must_equal input
346
+ })).must_equal input
301
347
  end
302
348
 
303
- it "should not allow relative URLs when relative URLs aren't whitelisted" do
349
+ it "should not allow relative URLs when relative URLs aren't allowlisted" do
304
350
  input = '<a href="/foo/bar">Link</a>'
305
351
 
306
- Sanitize.fragment(input,
352
+ _(Sanitize.fragment(input,
307
353
  :elements => ['a'],
308
354
  :attributes => {'a' => ['href']},
309
355
  :protocols => {'a' => {'href' => ['http']}}
310
- ).must_equal '<a>Link</a>'
356
+ )).must_equal '<a>Link</a>'
311
357
  end
312
358
 
313
359
  it 'should allow relative URLs containing colons when the colon is not in the first path segment' do
314
360
  input = '<a href="/wiki/Special:Random">Random Page</a>'
315
361
 
316
- Sanitize.fragment(input, {
362
+ _(Sanitize.fragment(input, {
317
363
  :elements => ['a'],
318
364
  :attributes => {'a' => ['href']},
319
365
  :protocols => {'a' => {'href' => [:relative]}}
320
- }).must_equal input
366
+ })).must_equal input
321
367
  end
322
368
 
323
369
  it 'should allow relative URLs containing colons when the colon is part of an anchor' do
324
370
  input = '<a href="#fn:1">Footnote 1</a>'
325
371
 
326
- Sanitize.fragment(input, {
372
+ _(Sanitize.fragment(input, {
327
373
  :elements => ['a'],
328
374
  :attributes => {'a' => ['href']},
329
375
  :protocols => {'a' => {'href' => [:relative]}}
330
- }).must_equal input
376
+ })).must_equal input
331
377
 
332
378
  input = '<a href="somepage#fn:1">Footnote 1</a>'
333
379
 
334
- Sanitize.fragment(input, {
380
+ _(Sanitize.fragment(input, {
335
381
  :elements => ['a'],
336
382
  :attributes => {'a' => ['href']},
337
383
  :protocols => {'a' => {'href' => [:relative]}}
338
- }).must_equal input
384
+ })).must_equal input
339
385
  end
340
386
 
341
387
  it 'should remove the contents of filtered nodes when :remove_contents is true' do
342
- Sanitize.fragment('foo bar <div>baz<span>quux</span></div>',
388
+ _(Sanitize.fragment('foo bar <div>baz<span>quux</span></div>',
343
389
  :remove_contents => true
344
- ).must_equal 'foo bar '
390
+ )).must_equal 'foo bar '
345
391
  end
346
392
 
347
- it 'should remove the contents of specified nodes when :remove_contents is an Array of element names as strings' do
348
- Sanitize.fragment('foo bar <div>baz<span>quux</span><script>alert("hello!");</script></div>',
393
+ it 'should remove the contents of specified nodes when :remove_contents is an Array or Set of element names as strings' do
394
+ _(Sanitize.fragment('foo bar <div>baz<span>quux</span> <b>hi</b><script>alert("hello!");</script></div>',
349
395
  :remove_contents => ['script', 'span']
350
- ).must_equal 'foo bar baz '
396
+ )).must_equal 'foo bar baz hi '
397
+
398
+ _(Sanitize.fragment('foo bar <div>baz<span>quux</span> <b>hi</b><script>alert("hello!");</script></div>',
399
+ :remove_contents => Set.new(['script', 'span'])
400
+ )).must_equal 'foo bar baz hi '
351
401
  end
352
402
 
353
- it 'should remove the contents of specified nodes when :remove_contents is an Array of element names as symbols' do
354
- Sanitize.fragment('foo bar <div>baz<span>quux</span><script>alert("hello!");</script></div>',
403
+ it 'should remove the contents of specified nodes when :remove_contents is an Array or Set of element names as symbols' do
404
+ _(Sanitize.fragment('foo bar <div>baz<span>quux</span> <b>hi</b><script>alert("hello!");</script></div>',
355
405
  :remove_contents => [:script, :span]
356
- ).must_equal 'foo bar baz '
406
+ )).must_equal 'foo bar baz hi '
407
+
408
+ _(Sanitize.fragment('foo bar <div>baz<span>quux</span> <b>hi</b><script>alert("hello!");</script></div>',
409
+ :remove_contents => Set.new([:script, :span])
410
+ )).must_equal 'foo bar baz hi '
411
+ end
412
+
413
+ it 'should remove the contents of allowlisted iframes' do
414
+ _(Sanitize.fragment('<iframe>hi <script>hello</script></iframe>',
415
+ :elements => ['iframe']
416
+ )).must_equal '<iframe></iframe>'
357
417
  end
358
418
 
359
419
  it 'should not allow arbitrary HTML5 data attributes by default' do
360
- Sanitize.fragment('<b data-foo="bar"></b>',
420
+ _(Sanitize.fragment('<b data-foo="bar"></b>',
361
421
  :elements => ['b']
362
- ).must_equal '<b></b>'
422
+ )).must_equal '<b></b>'
363
423
 
364
- Sanitize.fragment('<b class="foo" data-foo="bar"></b>',
424
+ _(Sanitize.fragment('<b class="foo" data-foo="bar"></b>',
365
425
  :attributes => {'b' => ['class']},
366
426
  :elements => ['b']
367
- ).must_equal '<b class="foo"></b>'
427
+ )).must_equal '<b class="foo"></b>'
368
428
  end
369
429
 
370
430
  it 'should allow arbitrary HTML5 data attributes when the :attributes config includes :data' do
@@ -373,28 +433,28 @@ describe 'Sanitize::Transformers::CleanElement' do
373
433
  :elements => ['b']
374
434
  )
375
435
 
376
- s.fragment('<b data-foo="valid" data-bar="valid"></b>')
436
+ _(s.fragment('<b data-foo="valid" data-bar="valid"></b>'))
377
437
  .must_equal '<b data-foo="valid" data-bar="valid"></b>'
378
438
 
379
- s.fragment('<b data-="invalid"></b>')
439
+ _(s.fragment('<b data-="invalid"></b>'))
380
440
  .must_equal '<b></b>'
381
441
 
382
- s.fragment('<b data-="invalid"></b>')
442
+ _(s.fragment('<b data-="invalid"></b>'))
383
443
  .must_equal '<b></b>'
384
444
 
385
- s.fragment('<b data-xml="invalid"></b>')
445
+ _(s.fragment('<b data-xml="invalid"></b>'))
386
446
  .must_equal '<b></b>'
387
447
 
388
- s.fragment('<b data-xmlfoo="invalid"></b>')
448
+ _(s.fragment('<b data-xmlfoo="invalid"></b>'))
389
449
  .must_equal '<b></b>'
390
450
 
391
- s.fragment('<b data-f:oo="valid"></b>')
451
+ _(s.fragment('<b data-f:oo="valid"></b>'))
392
452
  .must_equal '<b></b>'
393
453
 
394
- s.fragment('<b data-f/oo="partial"></b>')
454
+ _(s.fragment('<b data-f/oo="partial"></b>'))
395
455
  .must_equal '<b data-f=""></b>' # Nokogiri quirk; not ideal, but harmless
396
456
 
397
- s.fragment('<b data-éfoo="valid"></b>')
457
+ _(s.fragment('<b data-éfoo="valid"></b>'))
398
458
  .must_equal '<b></b>' # Another annoying Nokogiri quirk.
399
459
  end
400
460
 
@@ -407,28 +467,86 @@ describe 'Sanitize::Transformers::CleanElement' do
407
467
  }
408
468
  )
409
469
 
410
- s.fragment('<p>foo</p>').must_equal "\nfoo\n"
411
- s.fragment('<p>foo</p><p>bar</p>').must_equal "\nfoo\n\nbar\n"
412
- s.fragment('foo<div>bar</div>baz').must_equal "foo\nbar\nbaz"
413
- s.fragment('foo<br>bar<br>baz').must_equal "foo\nbar\nbaz"
470
+ _(s.fragment('<p>foo</p>')).must_equal "\nfoo\n"
471
+ _(s.fragment('<p>foo</p><p>bar</p>')).must_equal "\nfoo\n\nbar\n"
472
+ _(s.fragment('foo<div>bar</div>baz')).must_equal "foo\nbar\nbaz"
473
+ _(s.fragment('foo<br>bar<br>baz')).must_equal "foo\nbar\nbaz"
414
474
  end
415
475
 
416
- it 'handles protocols correctly regardless of case' do
476
+ it 'should handle protocols correctly regardless of case' do
417
477
  input = '<a href="hTTpS://foo.com/">Text</a>'
418
478
 
419
- Sanitize.fragment(input, {
479
+ _(Sanitize.fragment(input, {
420
480
  :elements => ['a'],
421
481
  :attributes => {'a' => ['href']},
422
482
  :protocols => {'a' => {'href' => ['https']}}
423
- }).must_equal input
483
+ })).must_equal input
424
484
 
425
485
  input = '<a href="mailto:someone@example.com?Subject=Hello">Text</a>'
426
486
 
427
- Sanitize.fragment(input, {
487
+ _(Sanitize.fragment(input, {
428
488
  :elements => ['a'],
429
489
  :attributes => {'a' => ['href']},
430
490
  :protocols => {'a' => {'href' => ['https']}}
431
- }).must_equal "<a>Text</a>"
491
+ })).must_equal "<a>Text</a>"
432
492
  end
493
+
494
+ it 'should sanitize protocols in data attributes even if data attributes are generically allowed' do
495
+ input = '<a data-url="mailto:someone@example.com">Text</a>'
496
+
497
+ _(Sanitize.fragment(input, {
498
+ :elements => ['a'],
499
+ :attributes => {'a' => [:data]},
500
+ :protocols => {'a' => {'data-url' => ['https']}}
501
+ })).must_equal "<a>Text</a>"
502
+
503
+ _(Sanitize.fragment(input, {
504
+ :elements => ['a'],
505
+ :attributes => {'a' => [:data]},
506
+ :protocols => {'a' => {'data-url' => ['mailto']}}
507
+ })).must_equal input
508
+ end
509
+
510
+ it 'should prevent `<meta>` tags from being used to set a non-UTF-8 charset' do
511
+ _(Sanitize.document('<html><head><meta charset="utf-8"></head><body>Howdy!</body></html>',
512
+ :elements => %w[html head meta body],
513
+ :attributes => {'meta' => ['charset']}
514
+ )).must_equal "<html><head><meta charset=\"utf-8\"></head><body>Howdy!</body></html>"
515
+
516
+ _(Sanitize.document('<html><meta charset="utf-8">Howdy!</html>',
517
+ :elements => %w[html meta],
518
+ :attributes => {'meta' => ['charset']}
519
+ )).must_equal "<html><meta charset=\"utf-8\">Howdy!</html>"
520
+
521
+ _(Sanitize.document('<html><meta charset="us-ascii">Howdy!</html>',
522
+ :elements => %w[html meta],
523
+ :attributes => {'meta' => ['charset']}
524
+ )).must_equal "<html><meta charset=\"utf-8\">Howdy!</html>"
525
+
526
+ _(Sanitize.document('<html><meta http-equiv="content-type" content=" text/html; charset=us-ascii">Howdy!</html>',
527
+ :elements => %w[html meta],
528
+ :attributes => {'meta' => %w[content http-equiv]}
529
+ )).must_equal "<html><meta http-equiv=\"content-type\" content=\" text/html;charset=utf-8\">Howdy!</html>"
530
+
531
+ _(Sanitize.document('<html><meta http-equiv="Content-Type" content="text/plain;charset = us-ascii">Howdy!</html>',
532
+ :elements => %w[html meta],
533
+ :attributes => {'meta' => %w[content http-equiv]}
534
+ )).must_equal "<html><meta http-equiv=\"Content-Type\" content=\"text/plain;charset=utf-8\">Howdy!</html>"
535
+ end
536
+
537
+ it 'should not modify `<meta>` tags that already set a UTF-8 charset' do
538
+ _(Sanitize.document('<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"></head><body>Howdy!</body></html>',
539
+ :elements => %w[html head meta body],
540
+ :attributes => {'meta' => %w[content http-equiv]}
541
+ )).must_equal "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"></head><body>Howdy!</body></html>"
542
+ end
543
+
544
+ it 'always removes `<noscript>` elements even if `noscript` is in the allowlist' do
545
+ assert_equal(
546
+ '',
547
+ Sanitize.fragment('<noscript>foo</noscript>', elements: ['noscript'])
548
+ )
549
+ end
550
+
433
551
  end
434
552
  end
data/test/test_config.rb CHANGED
@@ -6,7 +6,7 @@ describe 'Config' do
6
6
  parallelize_me!
7
7
 
8
8
  def verify_deeply_frozen(config)
9
- config.must_be :frozen?
9
+ _(config).must_be :frozen?
10
10
 
11
11
  if Hash === config
12
12
  config.each_value {|v| verify_deeply_frozen(v) }
@@ -27,7 +27,7 @@ describe 'Config' do
27
27
  a = {:one => {:one_one => [0, '1', :a], :one_two => false, :one_three => Set.new([:a, :b, :c])}}
28
28
  b = Sanitize::Config.freeze_config(a)
29
29
 
30
- b.must_be_same_as a
30
+ _(b).must_be_same_as a
31
31
  verify_deeply_frozen a
32
32
  end
33
33
  end
@@ -40,10 +40,10 @@ describe 'Config' do
40
40
 
41
41
  c = Sanitize::Config.merge(a, b)
42
42
 
43
- c.wont_be_same_as a
44
- c.wont_be_same_as b
43
+ _(c).wont_be_same_as a
44
+ _(c).wont_be_same_as b
45
45
 
46
- c.must_equal(
46
+ _(c).must_equal(
47
47
  :one => {
48
48
  :one_one => [0, '1', :a],
49
49
  :one_two => true,
@@ -53,13 +53,13 @@ describe 'Config' do
53
53
  :two => 2
54
54
  )
55
55
 
56
- c[:one].wont_be_same_as a[:one]
57
- c[:one][:one_one].wont_be_same_as a[:one][:one_one]
56
+ _(c[:one]).wont_be_same_as a[:one]
57
+ _(c[:one][:one_one]).wont_be_same_as a[:one][:one_one]
58
58
  end
59
59
 
60
60
  it 'should raise an ArgumentError if either argument is not a Hash' do
61
- proc { Sanitize::Config.merge('foo', {}) }.must_raise ArgumentError
62
- proc { Sanitize::Config.merge({}, 'foo') }.must_raise ArgumentError
61
+ _(proc { Sanitize::Config.merge('foo', {}) }).must_raise ArgumentError
62
+ _(proc { Sanitize::Config.merge({}, 'foo') }).must_raise ArgumentError
63
63
  end
64
64
  end
65
65
  end
@@ -16,27 +16,40 @@ describe 'Malicious CSS' do
16
16
  end
17
17
 
18
18
  it 'should not be possible to inject an expression by munging it with a comment' do
19
- @s.properties(%[width:expr/*XSS*/ession(alert('XSS'))]).
19
+ _(@s.properties(%[width:expr/*XSS*/ession(alert('XSS'))])).
20
20
  must_equal ''
21
21
 
22
- @s.properties(%[width:ex/*XSS*//*/*/pression(alert("XSS"))]).
22
+ _(@s.properties(%[width:ex/*XSS*//*/*/pression(alert("XSS"))])).
23
23
  must_equal ''
24
24
  end
25
25
 
26
26
  it 'should not be possible to inject an expression by munging it with a newline' do
27
- @s.properties(%[width:\nexpression(alert('XSS'));]).
27
+ _(@s.properties(%[width:\nexpression(alert('XSS'));])).
28
28
  must_equal ''
29
29
  end
30
30
 
31
31
  it 'should not allow the javascript protocol' do
32
- @s.properties(%[background-image:url("javascript:alert('XSS')");]).
32
+ _(@s.properties(%[background-image:url("javascript:alert('XSS')");])).
33
33
  must_equal ''
34
34
 
35
- Sanitize.fragment(%[<div style="background-image: url(&#1;javascript:alert('XSS'))">],
36
- Sanitize::Config::RELAXED).must_equal '<div></div>'
35
+ _(Sanitize.fragment(%[<div style="background-image: url(&#1;javascript:alert('XSS'))">],
36
+ Sanitize::Config::RELAXED)).must_equal '<div></div>'
37
37
  end
38
38
 
39
39
  it 'should not allow behaviors' do
40
- @s.properties(%[behavior: url(xss.htc);]).must_equal ''
40
+ _(@s.properties(%[behavior: url(xss.htc);])).must_equal ''
41
+ end
42
+
43
+ describe 'sanitization bypass via CSS at-rule in HTML <style> element' do
44
+ before do
45
+ @s = Sanitize.new(Sanitize::Config::RELAXED)
46
+ end
47
+
48
+ it 'is not possible to prematurely end a <style> element' do
49
+ assert_equal(
50
+ %[<style>@media<\\/style><iframe srcdoc='<script>alert(document.domain)<\\/script>'>{}</style>],
51
+ @s.fragment(%[<style>@media</sty/**/le><iframe srcdoc='<script>alert(document.domain)</script>'></style>])
52
+ )
53
+ end
41
54
  end
42
55
  end