sanitize 4.6.5 → 6.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sanitize might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/HISTORY.md +235 -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 +3 -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 -1
- data/lib/sanitize.rb +53 -68
- 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 +7 -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
|