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_sanitize_css.rb
CHANGED
@@ -16,12 +16,12 @@ describe 'Sanitize::CSS' do
|
|
16
16
|
it 'should sanitize CSS properties' do
|
17
17
|
css = 'background: #fff; width: expression(alert("hi"));'
|
18
18
|
|
19
|
-
@default.properties(css).must_equal ' '
|
20
|
-
@relaxed.properties(css).must_equal 'background: #fff; '
|
21
|
-
@custom.properties(css).must_equal 'background: #fff; '
|
19
|
+
_(@default.properties(css)).must_equal ' '
|
20
|
+
_(@relaxed.properties(css)).must_equal 'background: #fff; '
|
21
|
+
_(@custom.properties(css)).must_equal 'background: #fff; '
|
22
22
|
end
|
23
23
|
|
24
|
-
it 'should allow
|
24
|
+
it 'should allow allowlisted URL protocols' do
|
25
25
|
[
|
26
26
|
"background: url(relative.jpg)",
|
27
27
|
"background: url('relative.jpg')",
|
@@ -30,13 +30,13 @@ describe 'Sanitize::CSS' do
|
|
30
30
|
"background: url(https://example.com/https.jpg)",
|
31
31
|
"background: url('https://example.com/https.jpg')",
|
32
32
|
].each do |css|
|
33
|
-
@default.properties(css).must_equal ''
|
34
|
-
@relaxed.properties(css).must_equal css
|
35
|
-
@custom.properties(css).must_equal ''
|
33
|
+
_(@default.properties(css)).must_equal ''
|
34
|
+
_(@relaxed.properties(css)).must_equal css
|
35
|
+
_(@custom.properties(css)).must_equal ''
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
it 'should not allow non-
|
39
|
+
it 'should not allow non-allowlisted URL protocols' do
|
40
40
|
[
|
41
41
|
"background: url(javascript:alert(0))",
|
42
42
|
"background: url(ja\\56 ascript:alert(0))",
|
@@ -46,18 +46,18 @@ describe 'Sanitize::CSS' do
|
|
46
46
|
"background: url('javas\\\ncript:alert(0)')",
|
47
47
|
"background: url('java\\0script:foo')"
|
48
48
|
].each do |css|
|
49
|
-
@default.properties(css).must_equal ''
|
50
|
-
@relaxed.properties(css).must_equal ''
|
51
|
-
@custom.properties(css).must_equal ''
|
49
|
+
_(@default.properties(css)).must_equal ''
|
50
|
+
_(@relaxed.properties(css)).must_equal ''
|
51
|
+
_(@custom.properties(css)).must_equal ''
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
55
|
it 'should not allow -moz-binding' do
|
56
56
|
css = "-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss')"
|
57
57
|
|
58
|
-
@default.properties(css).must_equal ''
|
59
|
-
@relaxed.properties(css).must_equal ''
|
60
|
-
@custom.properties(css).must_equal ''
|
58
|
+
_(@default.properties(css)).must_equal ''
|
59
|
+
_(@relaxed.properties(css)).must_equal ''
|
60
|
+
_(@custom.properties(css)).must_equal ''
|
61
61
|
end
|
62
62
|
|
63
63
|
it 'should not allow expressions' do
|
@@ -69,50 +69,50 @@ describe 'Sanitize::CSS' do
|
|
69
69
|
"xss:expression(alert(1))",
|
70
70
|
"height: foo(expression(alert(1)));"
|
71
71
|
].each do |css|
|
72
|
-
@default.properties(css).must_equal ''
|
73
|
-
@relaxed.properties(css).must_equal ''
|
74
|
-
@custom.properties(css).must_equal ''
|
72
|
+
_(@default.properties(css)).must_equal ''
|
73
|
+
_(@relaxed.properties(css)).must_equal ''
|
74
|
+
_(@custom.properties(css)).must_equal ''
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
78
|
it 'should not allow behaviors' do
|
79
79
|
css = "behavior: url(xss.htc);"
|
80
80
|
|
81
|
-
@default.properties(css).must_equal ''
|
82
|
-
@relaxed.properties(css).must_equal ''
|
83
|
-
@custom.properties(css).must_equal ''
|
81
|
+
_(@default.properties(css)).must_equal ''
|
82
|
+
_(@relaxed.properties(css)).must_equal ''
|
83
|
+
_(@custom.properties(css)).must_equal ''
|
84
84
|
end
|
85
85
|
|
86
86
|
describe 'when :allow_comments is true' do
|
87
87
|
it 'should preserve comments' do
|
88
|
-
@relaxed.properties('color: #fff; /* comment */ width: 100px;')
|
88
|
+
_(@relaxed.properties('color: #fff; /* comment */ width: 100px;'))
|
89
89
|
.must_equal 'color: #fff; /* comment */ width: 100px;'
|
90
90
|
|
91
|
-
@relaxed.properties("color: #fff; /* \n\ncomment */ width: 100px;")
|
91
|
+
_(@relaxed.properties("color: #fff; /* \n\ncomment */ width: 100px;"))
|
92
92
|
.must_equal "color: #fff; /* \n\ncomment */ width: 100px;"
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
96
|
describe 'when :allow_comments is false' do
|
97
97
|
it 'should strip comments' do
|
98
|
-
@custom.properties('color: #fff; /* comment */ width: 100px;')
|
98
|
+
_(@custom.properties('color: #fff; /* comment */ width: 100px;'))
|
99
99
|
.must_equal 'color: #fff; width: 100px;'
|
100
100
|
|
101
|
-
@custom.properties("color: #fff; /* \n\ncomment */ width: 100px;")
|
101
|
+
_(@custom.properties("color: #fff; /* \n\ncomment */ width: 100px;"))
|
102
102
|
.must_equal 'color: #fff; width: 100px;'
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
106
|
describe 'when :allow_hacks is true' do
|
107
107
|
it 'should allow common CSS hacks' do
|
108
|
-
@relaxed.properties('_border: 1px solid #fff; *width: 10px')
|
108
|
+
_(@relaxed.properties('_border: 1px solid #fff; *width: 10px'))
|
109
109
|
.must_equal '_border: 1px solid #fff; *width: 10px'
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
113
113
|
describe 'when :allow_hacks is false' do
|
114
114
|
it 'should not allow common CSS hacks' do
|
115
|
-
@custom.properties('_border: 1px solid #fff; *width: 10px')
|
115
|
+
_(@custom.properties('_border: 1px solid #fff; *width: 10px'))
|
116
116
|
.must_equal ' '
|
117
117
|
end
|
118
118
|
end
|
@@ -131,14 +131,14 @@ describe 'Sanitize::CSS' do
|
|
131
131
|
}
|
132
132
|
].strip
|
133
133
|
|
134
|
-
@default.stylesheet(css).strip.must_equal %[
|
134
|
+
_(@default.stylesheet(css).strip).must_equal %[
|
135
135
|
.foo { }
|
136
136
|
#bar { }
|
137
137
|
].strip
|
138
138
|
|
139
|
-
@relaxed.stylesheet(css).must_equal css
|
139
|
+
_(@relaxed.stylesheet(css)).must_equal css
|
140
140
|
|
141
|
-
@custom.stylesheet(css).strip.must_equal %[
|
141
|
+
_(@custom.stylesheet(css).strip).must_equal %[
|
142
142
|
.foo { color: #fff; }
|
143
143
|
#bar { }
|
144
144
|
].strip
|
@@ -146,34 +146,34 @@ describe 'Sanitize::CSS' do
|
|
146
146
|
|
147
147
|
describe 'when :allow_comments is true' do
|
148
148
|
it 'should preserve comments' do
|
149
|
-
@relaxed.stylesheet('.foo { color: #fff; /* comment */ width: 100px; }')
|
149
|
+
_(@relaxed.stylesheet('.foo { color: #fff; /* comment */ width: 100px; }'))
|
150
150
|
.must_equal '.foo { color: #fff; /* comment */ width: 100px; }'
|
151
151
|
|
152
|
-
@relaxed.stylesheet(".foo { color: #fff; /* \n\ncomment */ width: 100px; }")
|
152
|
+
_(@relaxed.stylesheet(".foo { color: #fff; /* \n\ncomment */ width: 100px; }"))
|
153
153
|
.must_equal ".foo { color: #fff; /* \n\ncomment */ width: 100px; }"
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|
157
157
|
describe 'when :allow_comments is false' do
|
158
158
|
it 'should strip comments' do
|
159
|
-
@custom.stylesheet('.foo { color: #fff; /* comment */ width: 100px; }')
|
159
|
+
_(@custom.stylesheet('.foo { color: #fff; /* comment */ width: 100px; }'))
|
160
160
|
.must_equal '.foo { color: #fff; width: 100px; }'
|
161
161
|
|
162
|
-
@custom.stylesheet(".foo { color: #fff; /* \n\ncomment */ width: 100px; }")
|
162
|
+
_(@custom.stylesheet(".foo { color: #fff; /* \n\ncomment */ width: 100px; }"))
|
163
163
|
.must_equal '.foo { color: #fff; width: 100px; }'
|
164
164
|
end
|
165
165
|
end
|
166
166
|
|
167
167
|
describe 'when :allow_hacks is true' do
|
168
168
|
it 'should allow common CSS hacks' do
|
169
|
-
@relaxed.stylesheet('.foo { _border: 1px solid #fff; *width: 10px }')
|
169
|
+
_(@relaxed.stylesheet('.foo { _border: 1px solid #fff; *width: 10px }'))
|
170
170
|
.must_equal '.foo { _border: 1px solid #fff; *width: 10px }'
|
171
171
|
end
|
172
172
|
end
|
173
173
|
|
174
174
|
describe 'when :allow_hacks is false' do
|
175
175
|
it 'should not allow common CSS hacks' do
|
176
|
-
@custom.stylesheet('.foo { _border: 1px solid #fff; *width: 10px }')
|
176
|
+
_(@custom.stylesheet('.foo { _border: 1px solid #fff; *width: 10px }'))
|
177
177
|
.must_equal '.foo { }'
|
178
178
|
end
|
179
179
|
end
|
@@ -185,9 +185,9 @@ describe 'Sanitize::CSS' do
|
|
185
185
|
".foo { background: #fff; font: 16pt 'Comic Sans MS'; }\n" <<
|
186
186
|
"#bar { top: 125px; background: green; }")
|
187
187
|
|
188
|
-
@custom.tree!(tree).must_be_same_as tree
|
188
|
+
_(@custom.tree!(tree)).must_be_same_as tree
|
189
189
|
|
190
|
-
Crass::Parser.stringify(tree).must_equal String.new("\n") <<
|
190
|
+
_(Crass::Parser.stringify(tree)).must_equal String.new("\n") <<
|
191
191
|
".foo { background: #fff; }\n" <<
|
192
192
|
"#bar { background: green; }"
|
193
193
|
end
|
@@ -196,26 +196,53 @@ describe 'Sanitize::CSS' do
|
|
196
196
|
|
197
197
|
describe 'class methods' do
|
198
198
|
describe '.properties' do
|
199
|
-
it 'should
|
200
|
-
|
201
|
-
|
202
|
-
|
199
|
+
it 'should sanitize CSS properties with the given config' do
|
200
|
+
css = 'background: #fff; width: expression(alert("hi"));'
|
201
|
+
|
202
|
+
_(Sanitize::CSS.properties(css)).must_equal ' '
|
203
|
+
_(Sanitize::CSS.properties(css, Sanitize::Config::RELAXED[:css])).must_equal 'background: #fff; '
|
204
|
+
_(Sanitize::CSS.properties(css, :properties => %w[background color width])).must_equal 'background: #fff; '
|
203
205
|
end
|
204
206
|
end
|
205
207
|
|
206
208
|
describe '.stylesheet' do
|
207
|
-
it 'should
|
208
|
-
|
209
|
-
|
210
|
-
|
209
|
+
it 'should sanitize a CSS stylesheet with the given config' do
|
210
|
+
css = %[
|
211
|
+
/* Yay CSS! */
|
212
|
+
.foo { color: #fff; }
|
213
|
+
#bar { background: url(yay.jpg); }
|
214
|
+
|
215
|
+
@media screen (max-width:480px) {
|
216
|
+
.foo { width: 400px; }
|
217
|
+
#bar:not(.baz) { height: 100px; }
|
218
|
+
}
|
219
|
+
].strip
|
220
|
+
|
221
|
+
_(Sanitize::CSS.stylesheet(css).strip).must_equal %[
|
222
|
+
.foo { }
|
223
|
+
#bar { }
|
224
|
+
].strip
|
225
|
+
|
226
|
+
_(Sanitize::CSS.stylesheet(css, Sanitize::Config::RELAXED[:css])).must_equal css
|
227
|
+
|
228
|
+
_(Sanitize::CSS.stylesheet(css, :properties => %w[background color width]).strip).must_equal %[
|
229
|
+
.foo { color: #fff; }
|
230
|
+
#bar { }
|
231
|
+
].strip
|
211
232
|
end
|
212
233
|
end
|
213
234
|
|
214
235
|
describe '.tree!' do
|
215
|
-
it 'should
|
216
|
-
|
217
|
-
|
218
|
-
|
236
|
+
it 'should sanitize a Crass CSS parse tree with the given config' do
|
237
|
+
tree = Crass.parse(String.new("@import url(foo.css);\n") <<
|
238
|
+
".foo { background: #fff; font: 16pt 'Comic Sans MS'; }\n" <<
|
239
|
+
"#bar { top: 125px; background: green; }")
|
240
|
+
|
241
|
+
_(Sanitize::CSS.tree!(tree, :properties => %w[background color width])).must_be_same_as tree
|
242
|
+
|
243
|
+
_(Crass::Parser.stringify(tree)).must_equal String.new("\n") <<
|
244
|
+
".foo { background: #fff; }\n" <<
|
245
|
+
"#bar { background: green; }"
|
219
246
|
end
|
220
247
|
end
|
221
248
|
end
|
@@ -229,7 +256,7 @@ describe 'Sanitize::CSS' do
|
|
229
256
|
# https://github.com/rgrove/sanitize/issues/121
|
230
257
|
it 'should parse the contents of @media rules properly' do
|
231
258
|
css = '@media { p[class="center"] { text-align: center; }}'
|
232
|
-
@relaxed.stylesheet(css).must_equal css
|
259
|
+
_(@relaxed.stylesheet(css)).must_equal css
|
233
260
|
|
234
261
|
css = %[
|
235
262
|
@media (max-width: 720px) {
|
@@ -242,7 +269,7 @@ describe 'Sanitize::CSS' do
|
|
242
269
|
}
|
243
270
|
].strip
|
244
271
|
|
245
|
-
@relaxed.stylesheet(css).must_equal %[
|
272
|
+
_(@relaxed.stylesheet(css)).must_equal %[
|
246
273
|
@media (max-width: 720px) {
|
247
274
|
p.foo > .bar { float: right; }
|
248
275
|
#baz { color: green; }
|
@@ -276,23 +303,23 @@ describe 'Sanitize::CSS' do
|
|
276
303
|
}
|
277
304
|
].strip
|
278
305
|
|
279
|
-
@relaxed.stylesheet(css).must_equal css
|
306
|
+
_(@relaxed.stylesheet(css)).must_equal css
|
280
307
|
end
|
281
308
|
|
282
309
|
describe ":at_rules" do
|
283
|
-
it "should remove blockless at-rules that aren't
|
310
|
+
it "should remove blockless at-rules that aren't allowlisted" do
|
284
311
|
css = %[
|
285
312
|
@charset 'utf-8';
|
286
313
|
@import url('foo.css');
|
287
314
|
.foo { color: green; }
|
288
315
|
].strip
|
289
316
|
|
290
|
-
@relaxed.stylesheet(css).strip.must_equal %[
|
317
|
+
_(@relaxed.stylesheet(css).strip).must_equal %[
|
291
318
|
.foo { color: green; }
|
292
319
|
].strip
|
293
320
|
end
|
294
321
|
|
295
|
-
describe "when blockless at-rules are
|
322
|
+
describe "when blockless at-rules are allowlisted" do
|
296
323
|
before do
|
297
324
|
@scss = Sanitize::CSS.new(Sanitize::Config.merge(Sanitize::Config::RELAXED[:css], {
|
298
325
|
:at_rules => ['charset', 'import']
|
@@ -306,7 +333,7 @@ describe 'Sanitize::CSS' do
|
|
306
333
|
.foo { color: green; }
|
307
334
|
].strip
|
308
335
|
|
309
|
-
@scss.stylesheet(css).must_equal %[
|
336
|
+
_(@scss.stylesheet(css)).must_equal %[
|
310
337
|
@charset 'utf-8';
|
311
338
|
@import url('foo.css');
|
312
339
|
.foo { color: green; }
|
@@ -320,7 +347,7 @@ describe 'Sanitize::CSS' do
|
|
320
347
|
.foo { color: green; }
|
321
348
|
].strip
|
322
349
|
|
323
|
-
@scss.stylesheet(css).strip.must_equal %[
|
350
|
+
_(@scss.stylesheet(css).strip).must_equal %[
|
324
351
|
.foo { color: green; }
|
325
352
|
].strip
|
326
353
|
end
|
@@ -340,7 +367,7 @@ describe 'Sanitize::CSS' do
|
|
340
367
|
@import url('https://somesite.com/something.css');
|
341
368
|
].strip
|
342
369
|
|
343
|
-
@scss.stylesheet(css).strip.must_equal %[
|
370
|
+
_(@scss.stylesheet(css).strip).must_equal %[
|
344
371
|
@import url('https://somesite.com/something.css');
|
345
372
|
].strip
|
346
373
|
end
|
@@ -361,7 +388,7 @@ describe 'Sanitize::CSS' do
|
|
361
388
|
@import url('https://fonts.googleapis.com/css?family=Indie+Flower');
|
362
389
|
].strip
|
363
390
|
|
364
|
-
@scss.stylesheet(css).strip.must_equal %[
|
391
|
+
_(@scss.stylesheet(css).strip).must_equal %[
|
365
392
|
@import 'https://fonts.googleapis.com/css?family=Indie+Flower';
|
366
393
|
@import url('https://fonts.googleapis.com/css?family=Indie+Flower');
|
367
394
|
].strip
|
@@ -374,7 +401,7 @@ describe 'Sanitize::CSS' do
|
|
374
401
|
@import url('https://nastysite.com/nasty_hax0r.css');
|
375
402
|
].strip
|
376
403
|
|
377
|
-
@scss.stylesheet(css).strip.must_equal %[
|
404
|
+
_(@scss.stylesheet(css).strip).must_equal %[
|
378
405
|
@import 'https://fonts.googleapis.com/css?family=Indie+Flower';
|
379
406
|
].strip
|
380
407
|
end
|
@@ -386,7 +413,7 @@ describe 'Sanitize::CSS' do
|
|
386
413
|
@import url('');
|
387
414
|
].strip
|
388
415
|
|
389
|
-
@scss.stylesheet(css).strip.must_equal %[
|
416
|
+
_(@scss.stylesheet(css).strip).must_equal %[
|
390
417
|
@import 'https://fonts.googleapis.com/css?family=Indie+Flower';
|
391
418
|
].strip
|
392
419
|
end
|
data/test/test_transformers.rb
CHANGED
@@ -11,12 +11,14 @@ describe 'Transformers' do
|
|
11
11
|
:transformers => lambda {|env|
|
12
12
|
return unless env[:node].element?
|
13
13
|
|
14
|
-
env[:config][:foo].must_equal :bar
|
15
|
-
env[:
|
16
|
-
env[:
|
17
|
-
env[:
|
18
|
-
env[:
|
19
|
-
env[:
|
14
|
+
_(env[:config][:foo]).must_equal :bar
|
15
|
+
_(env[:is_allowlisted]).must_equal false
|
16
|
+
_(env[:is_whitelisted]).must_equal env[:is_allowlisted]
|
17
|
+
_(env[:node]).must_be_kind_of Nokogiri::XML::Node
|
18
|
+
_(env[:node_name]).must_equal 'span'
|
19
|
+
_(env[:node_allowlist]).must_be_kind_of Set
|
20
|
+
_(env[:node_allowlist]).must_be_empty
|
21
|
+
_(env[:node_whitelist]).must_equal env[:node_allowlist]
|
20
22
|
}
|
21
23
|
)
|
22
24
|
end
|
@@ -28,7 +30,7 @@ describe 'Transformers' do
|
|
28
30
|
:transformers => proc {|env| nodes << env[:node_name] }
|
29
31
|
)
|
30
32
|
|
31
|
-
nodes.must_equal %w[
|
33
|
+
_(nodes).must_equal %w[
|
32
34
|
#document-fragment div text text text comment script text
|
33
35
|
]
|
34
36
|
end
|
@@ -40,53 +42,57 @@ describe 'Transformers' do
|
|
40
42
|
:transformers => proc {|env| nodes << env[:node_name] if env[:node].element? }
|
41
43
|
)
|
42
44
|
|
43
|
-
nodes.must_equal %w[div span strong b p]
|
45
|
+
_(nodes).must_equal %w[div span strong b p]
|
44
46
|
end
|
45
47
|
|
46
|
-
it 'should
|
47
|
-
Sanitize.fragment('<div class="foo">foo</div><span>bar</span>',
|
48
|
+
it 'should allowlist nodes in the node allowlist' do
|
49
|
+
_(Sanitize.fragment('<div class="foo">foo</div><span>bar</span>',
|
48
50
|
:transformers => [
|
49
51
|
proc {|env|
|
50
|
-
{:
|
52
|
+
{:node_allowlist => [env[:node]]} if env[:node_name] == 'div'
|
51
53
|
},
|
52
54
|
|
53
55
|
proc {|env|
|
54
|
-
env[:
|
55
|
-
env[:
|
56
|
-
env[:
|
56
|
+
_(env[:is_allowlisted]).must_equal false unless env[:node_name] == 'div'
|
57
|
+
_(env[:is_allowlisted]).must_equal true if env[:node_name] == 'div'
|
58
|
+
_(env[:node_allowlist]).must_include env[:node] if env[:node_name] == 'div'
|
59
|
+
_(env[:is_whitelisted]).must_equal env[:is_allowlisted]
|
60
|
+
_(env[:node_whitelist]).must_equal env[:node_allowlist]
|
57
61
|
}
|
58
62
|
]
|
59
|
-
).must_equal '<div class="foo">foo</div>bar'
|
63
|
+
)).must_equal '<div class="foo">foo</div>bar'
|
60
64
|
end
|
61
65
|
|
62
|
-
it 'should clear the node
|
66
|
+
it 'should clear the node allowlist after each fragment' do
|
63
67
|
called = false
|
64
68
|
|
65
69
|
Sanitize.fragment('<div>foo</div>',
|
66
|
-
:transformers => proc {|env| {:
|
70
|
+
:transformers => proc {|env| {:node_allowlist => [env[:node]]}}
|
67
71
|
)
|
68
72
|
|
69
73
|
Sanitize.fragment('<div>foo</div>',
|
70
74
|
:transformers => proc {|env|
|
71
75
|
called = true
|
72
|
-
env[:
|
73
|
-
env[:
|
76
|
+
_(env[:is_allowlisted]).must_equal false
|
77
|
+
_(env[:is_whitelisted]).must_equal env[:is_allowlisted]
|
78
|
+
_(env[:node_allowlist]).must_be_empty
|
79
|
+
_(env[:node_whitelist]).must_equal env[:node_allowlist]
|
74
80
|
}
|
75
81
|
)
|
76
82
|
|
77
|
-
called.must_equal true
|
83
|
+
_(called).must_equal true
|
78
84
|
end
|
79
85
|
|
80
86
|
it 'should accept a method transformer' do
|
81
87
|
def transformer(env); end
|
82
|
-
Sanitize.fragment('<div>foo</div>', :transformers => method(:transformer))
|
88
|
+
_(Sanitize.fragment('<div>foo</div>', :transformers => method(:transformer)))
|
83
89
|
.must_equal(' foo ')
|
84
90
|
end
|
85
91
|
|
86
|
-
describe 'Image
|
92
|
+
describe 'Image allowlist transformer' do
|
87
93
|
require 'uri'
|
88
94
|
|
89
|
-
|
95
|
+
image_allowlist_transformer = lambda do |env|
|
90
96
|
# Ignore everything except <img> elements.
|
91
97
|
return unless env[:node_name] == 'img'
|
92
98
|
|
@@ -103,37 +109,37 @@ describe 'Transformers' do
|
|
103
109
|
|
104
110
|
before do
|
105
111
|
@s = Sanitize.new(Sanitize::Config.merge(Sanitize::Config::RELAXED,
|
106
|
-
:transformers =>
|
112
|
+
:transformers => image_allowlist_transformer))
|
107
113
|
end
|
108
114
|
|
109
115
|
it 'should allow images with relative URLs' do
|
110
116
|
input = '<img src="/foo/bar.jpg">'
|
111
|
-
@s.fragment(input).must_equal(input)
|
117
|
+
_(@s.fragment(input)).must_equal(input)
|
112
118
|
end
|
113
119
|
|
114
120
|
it 'should allow images at the example.com domain' do
|
115
121
|
input = '<img src="http://example.com/foo/bar.jpg">'
|
116
|
-
@s.fragment(input).must_equal(input)
|
122
|
+
_(@s.fragment(input)).must_equal(input)
|
117
123
|
|
118
124
|
input = '<img src="https://example.com/foo/bar.jpg">'
|
119
|
-
@s.fragment(input).must_equal(input)
|
125
|
+
_(@s.fragment(input)).must_equal(input)
|
120
126
|
|
121
127
|
input = '<img src="//example.com/foo/bar.jpg">'
|
122
|
-
@s.fragment(input).must_equal(input)
|
128
|
+
_(@s.fragment(input)).must_equal(input)
|
123
129
|
end
|
124
130
|
|
125
131
|
it 'should not allow images at other domains' do
|
126
132
|
input = '<img src="http://evil.com/foo/bar.jpg">'
|
127
|
-
@s.fragment(input).must_equal('')
|
133
|
+
_(@s.fragment(input)).must_equal('')
|
128
134
|
|
129
135
|
input = '<img src="https://evil.com/foo/bar.jpg">'
|
130
|
-
@s.fragment(input).must_equal('')
|
136
|
+
_(@s.fragment(input)).must_equal('')
|
131
137
|
|
132
138
|
input = '<img src="//evil.com/foo/bar.jpg">'
|
133
|
-
@s.fragment(input).must_equal('')
|
139
|
+
_(@s.fragment(input)).must_equal('')
|
134
140
|
|
135
141
|
input = '<img src="http://subdomain.example.com/foo/bar.jpg">'
|
136
|
-
@s.fragment(input).must_equal('')
|
142
|
+
_(@s.fragment(input)).must_equal('')
|
137
143
|
end
|
138
144
|
end
|
139
145
|
|
@@ -142,8 +148,8 @@ describe 'Transformers' do
|
|
142
148
|
node = env[:node]
|
143
149
|
node_name = env[:node_name]
|
144
150
|
|
145
|
-
# Don't continue if this node is already
|
146
|
-
return if env[:
|
151
|
+
# Don't continue if this node is already allowlisted or is not an element.
|
152
|
+
return if env[:is_allowlisted] || !node.element?
|
147
153
|
|
148
154
|
# Don't continue unless the node is an iframe.
|
149
155
|
return unless node_name == 'iframe'
|
@@ -164,42 +170,42 @@ describe 'Transformers' do
|
|
164
170
|
|
165
171
|
# Now that we're sure that this is a valid YouTube embed and that there are
|
166
172
|
# no unwanted elements or attributes hidden inside it, we can tell Sanitize
|
167
|
-
# to
|
168
|
-
{:
|
173
|
+
# to allowlist the current node.
|
174
|
+
{:node_allowlist => [node]}
|
169
175
|
end
|
170
176
|
|
171
177
|
it 'should allow HTTP YouTube video embeds' do
|
172
178
|
input = '<iframe width="420" height="315" src="http://www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen bogus="bogus"><script>alert()</script></iframe>'
|
173
179
|
|
174
|
-
Sanitize.fragment(input, :transformers => youtube_transformer)
|
175
|
-
.must_equal '<iframe width="420" height="315" src="http://www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen=""
|
180
|
+
_(Sanitize.fragment(input, :transformers => youtube_transformer))
|
181
|
+
.must_equal '<iframe width="420" height="315" src="http://www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen=""></iframe>'
|
176
182
|
end
|
177
183
|
|
178
184
|
it 'should allow HTTPS YouTube video embeds' do
|
179
185
|
input = '<iframe width="420" height="315" src="https://www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen bogus="bogus"><script>alert()</script></iframe>'
|
180
186
|
|
181
|
-
Sanitize.fragment(input, :transformers => youtube_transformer)
|
182
|
-
.must_equal '<iframe width="420" height="315" src="https://www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen=""
|
187
|
+
_(Sanitize.fragment(input, :transformers => youtube_transformer))
|
188
|
+
.must_equal '<iframe width="420" height="315" src="https://www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen=""></iframe>'
|
183
189
|
end
|
184
190
|
|
185
191
|
it 'should allow protocol-relative YouTube video embeds' do
|
186
192
|
input = '<iframe width="420" height="315" src="//www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen bogus="bogus"><script>alert()</script></iframe>'
|
187
193
|
|
188
|
-
Sanitize.fragment(input, :transformers => youtube_transformer)
|
189
|
-
.must_equal '<iframe width="420" height="315" src="//www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen=""
|
194
|
+
_(Sanitize.fragment(input, :transformers => youtube_transformer))
|
195
|
+
.must_equal '<iframe width="420" height="315" src="//www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen=""></iframe>'
|
190
196
|
end
|
191
197
|
|
192
198
|
it 'should allow privacy-enhanced YouTube video embeds' do
|
193
199
|
input = '<iframe width="420" height="315" src="https://www.youtube-nocookie.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen bogus="bogus"><script>alert()</script></iframe>'
|
194
200
|
|
195
|
-
Sanitize.fragment(input, :transformers => youtube_transformer)
|
196
|
-
.must_equal '<iframe width="420" height="315" src="https://www.youtube-nocookie.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen=""
|
201
|
+
_(Sanitize.fragment(input, :transformers => youtube_transformer))
|
202
|
+
.must_equal '<iframe width="420" height="315" src="https://www.youtube-nocookie.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen=""></iframe>'
|
197
203
|
end
|
198
204
|
|
199
205
|
it 'should not allow non-YouTube video embeds' do
|
200
206
|
input = '<iframe width="420" height="315" src="http://www.fake-youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen></iframe>'
|
201
207
|
|
202
|
-
Sanitize.fragment(input, :transformers => youtube_transformer)
|
208
|
+
_(Sanitize.fragment(input, :transformers => youtube_transformer))
|
203
209
|
.must_equal('')
|
204
210
|
end
|
205
211
|
end
|
@@ -217,7 +223,7 @@ describe 'Transformers' do
|
|
217
223
|
it 'should allow the <b> tag to be changed to a <strong> tag' do
|
218
224
|
input = '<b>text</b>'
|
219
225
|
|
220
|
-
Sanitize.fragment(input, :elements => ['strong'], :transformers => b_to_strong_tag_transformer)
|
226
|
+
_(Sanitize.fragment(input, :elements => ['strong'], :transformers => b_to_strong_tag_transformer))
|
221
227
|
.must_equal '<strong>text</strong>'
|
222
228
|
end
|
223
229
|
end
|