sanitize 4.6.4 → 6.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/HISTORY.md +259 -16
- data/LICENSE +1 -1
- data/README.md +89 -76
- data/lib/sanitize/config/default.rb +15 -4
- data/lib/sanitize/config/relaxed.rb +1 -1
- data/lib/sanitize/css.rb +2 -2
- data/lib/sanitize/transformers/clean_comment.rb +1 -1
- data/lib/sanitize/transformers/clean_css.rb +4 -3
- data/lib/sanitize/transformers/clean_doctype.rb +1 -1
- data/lib/sanitize/transformers/clean_element.rb +105 -22
- data/lib/sanitize/version.rb +1 -3
- data/lib/sanitize.rb +56 -72
- data/test/common.rb +0 -31
- data/test/test_clean_comment.rb +16 -20
- data/test/test_clean_css.rb +6 -6
- data/test/test_clean_doctype.rb +22 -22
- data/test/test_clean_element.rb +200 -82
- data/test/test_config.rb +9 -9
- data/test/test_malicious_css.rb +20 -7
- data/test/test_malicious_html.rb +179 -32
- data/test/test_parser.rb +9 -38
- data/test/test_sanitize.rb +114 -29
- data/test/test_sanitize_css.rb +88 -61
- data/test/test_transformers.rb +52 -46
- metadata +17 -33
- data/test/test_unicode.rb +0 -95
data/test/test_clean_element.rb
CHANGED
@@ -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
|
-
:
|
13
|
-
:
|
14
|
-
:
|
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
|
-
:
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:relaxed => 'Lorem <a href="pants" title="foo>ipsum <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 <script>alert("hello world");',
|
40
36
|
:restricted => '<b>Lorem</b> ipsum <strong>dolor</strong> sit amet <script>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 <script>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-
|
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 '
|
169
|
+
_(Sanitize.fragment('<script>alert("<xss>");</script>'))
|
170
|
+
.must_equal ''
|
175
171
|
|
176
|
-
Sanitize.fragment('<<script>script>alert("<xss>");</<script>>')
|
177
|
-
.must_equal '<
|
172
|
+
_(Sanitize.fragment('<<script>script>alert("<xss>");</<script>>'))
|
173
|
+
.must_equal '<'
|
178
174
|
|
179
|
-
Sanitize.fragment('< script <>> alert("<xss>");</script>')
|
175
|
+
_(Sanitize.fragment('< script <>> alert("<xss>");</script>'))
|
180
176
|
.must_equal '< script <>> 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>éxamples</b> & things">foo</a>')
|
265
|
-
.must_equal '<a href="http://example.com" title="
|
310
|
+
_(@s.fragment('<a href="http://example.com" title="<b>éxamples</b> & things">foo</a>'))
|
311
|
+
.must_equal '<a href="http://example.com" title="<b>éxamples</b> & 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
|
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
|
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 '
|
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
|
data/test/test_malicious_css.rb
CHANGED
@@ -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(javascript:alert('XSS'))">],
|
36
|
-
Sanitize::Config::RELAXED).must_equal '<div></div>'
|
35
|
+
_(Sanitize.fragment(%[<div style="background-image: url(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
|