sanitize 2.1.1 → 6.0.0

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.

@@ -0,0 +1,424 @@
1
+ # encoding: utf-8
2
+ require_relative 'common'
3
+
4
+ describe 'Sanitize::CSS' do
5
+ make_my_diffs_pretty!
6
+ parallelize_me!
7
+
8
+ describe 'instance methods' do
9
+ before do
10
+ @default = Sanitize::CSS.new
11
+ @relaxed = Sanitize::CSS.new(Sanitize::Config::RELAXED[:css])
12
+ @custom = Sanitize::CSS.new(:properties => %w[background color width])
13
+ end
14
+
15
+ describe '#properties' do
16
+ it 'should sanitize CSS properties' do
17
+ css = 'background: #fff; width: expression(alert("hi"));'
18
+
19
+ @default.properties(css).must_equal ' '
20
+ @relaxed.properties(css).must_equal 'background: #fff; '
21
+ @custom.properties(css).must_equal 'background: #fff; '
22
+ end
23
+
24
+ it 'should allow allowlisted URL protocols' do
25
+ [
26
+ "background: url(relative.jpg)",
27
+ "background: url('relative.jpg')",
28
+ "background: url(http://example.com/http.jpg)",
29
+ "background: url('ht\\tp://example.com/http.jpg')",
30
+ "background: url(https://example.com/https.jpg)",
31
+ "background: url('https://example.com/https.jpg')",
32
+ ].each do |css|
33
+ @default.properties(css).must_equal ''
34
+ @relaxed.properties(css).must_equal css
35
+ @custom.properties(css).must_equal ''
36
+ end
37
+ end
38
+
39
+ it 'should not allow non-allowlisted URL protocols' do
40
+ [
41
+ "background: url(javascript:alert(0))",
42
+ "background: url(ja\\56 ascript:alert(0))",
43
+ "background: url('javascript:foo')",
44
+ "background: url('ja\\56 ascript:alert(0)')",
45
+ "background: url('ja\\va\\script\\:alert(0)')",
46
+ "background: url('javas\\\ncript:alert(0)')",
47
+ "background: url('java\\0script:foo')"
48
+ ].each do |css|
49
+ @default.properties(css).must_equal ''
50
+ @relaxed.properties(css).must_equal ''
51
+ @custom.properties(css).must_equal ''
52
+ end
53
+ end
54
+
55
+ it 'should not allow -moz-binding' do
56
+ css = "-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss')"
57
+
58
+ @default.properties(css).must_equal ''
59
+ @relaxed.properties(css).must_equal ''
60
+ @custom.properties(css).must_equal ''
61
+ end
62
+
63
+ it 'should not allow expressions' do
64
+ [
65
+ "width:expression(alert(1))",
66
+ "width: /**/expression(alert(1)",
67
+ "width:e\\78 pression(\n\nalert(\n1)",
68
+ "width:\nexpression(alert(1));",
69
+ "xss:expression(alert(1))",
70
+ "height: foo(expression(alert(1)));"
71
+ ].each do |css|
72
+ @default.properties(css).must_equal ''
73
+ @relaxed.properties(css).must_equal ''
74
+ @custom.properties(css).must_equal ''
75
+ end
76
+ end
77
+
78
+ it 'should not allow behaviors' do
79
+ css = "behavior: url(xss.htc);"
80
+
81
+ @default.properties(css).must_equal ''
82
+ @relaxed.properties(css).must_equal ''
83
+ @custom.properties(css).must_equal ''
84
+ end
85
+
86
+ describe 'when :allow_comments is true' do
87
+ it 'should preserve comments' do
88
+ @relaxed.properties('color: #fff; /* comment */ width: 100px;')
89
+ .must_equal 'color: #fff; /* comment */ width: 100px;'
90
+
91
+ @relaxed.properties("color: #fff; /* \n\ncomment */ width: 100px;")
92
+ .must_equal "color: #fff; /* \n\ncomment */ width: 100px;"
93
+ end
94
+ end
95
+
96
+ describe 'when :allow_comments is false' do
97
+ it 'should strip comments' do
98
+ @custom.properties('color: #fff; /* comment */ width: 100px;')
99
+ .must_equal 'color: #fff; width: 100px;'
100
+
101
+ @custom.properties("color: #fff; /* \n\ncomment */ width: 100px;")
102
+ .must_equal 'color: #fff; width: 100px;'
103
+ end
104
+ end
105
+
106
+ describe 'when :allow_hacks is true' do
107
+ it 'should allow common CSS hacks' do
108
+ @relaxed.properties('_border: 1px solid #fff; *width: 10px')
109
+ .must_equal '_border: 1px solid #fff; *width: 10px'
110
+ end
111
+ end
112
+
113
+ describe 'when :allow_hacks is false' do
114
+ it 'should not allow common CSS hacks' do
115
+ @custom.properties('_border: 1px solid #fff; *width: 10px')
116
+ .must_equal ' '
117
+ end
118
+ end
119
+ end
120
+
121
+ describe '#stylesheet' do
122
+ it 'should sanitize a CSS stylesheet' do
123
+ css = %[
124
+ /* Yay CSS! */
125
+ .foo { color: #fff; }
126
+ #bar { background: url(yay.jpg); }
127
+
128
+ @media screen (max-width:480px) {
129
+ .foo { width: 400px; }
130
+ #bar:not(.baz) { height: 100px; }
131
+ }
132
+ ].strip
133
+
134
+ @default.stylesheet(css).strip.must_equal %[
135
+ .foo { }
136
+ #bar { }
137
+ ].strip
138
+
139
+ @relaxed.stylesheet(css).must_equal css
140
+
141
+ @custom.stylesheet(css).strip.must_equal %[
142
+ .foo { color: #fff; }
143
+ #bar { }
144
+ ].strip
145
+ end
146
+
147
+ describe 'when :allow_comments is true' do
148
+ it 'should preserve comments' do
149
+ @relaxed.stylesheet('.foo { color: #fff; /* comment */ width: 100px; }')
150
+ .must_equal '.foo { color: #fff; /* comment */ width: 100px; }'
151
+
152
+ @relaxed.stylesheet(".foo { color: #fff; /* \n\ncomment */ width: 100px; }")
153
+ .must_equal ".foo { color: #fff; /* \n\ncomment */ width: 100px; }"
154
+ end
155
+ end
156
+
157
+ describe 'when :allow_comments is false' do
158
+ it 'should strip comments' do
159
+ @custom.stylesheet('.foo { color: #fff; /* comment */ width: 100px; }')
160
+ .must_equal '.foo { color: #fff; width: 100px; }'
161
+
162
+ @custom.stylesheet(".foo { color: #fff; /* \n\ncomment */ width: 100px; }")
163
+ .must_equal '.foo { color: #fff; width: 100px; }'
164
+ end
165
+ end
166
+
167
+ describe 'when :allow_hacks is true' do
168
+ it 'should allow common CSS hacks' do
169
+ @relaxed.stylesheet('.foo { _border: 1px solid #fff; *width: 10px }')
170
+ .must_equal '.foo { _border: 1px solid #fff; *width: 10px }'
171
+ end
172
+ end
173
+
174
+ describe 'when :allow_hacks is false' do
175
+ it 'should not allow common CSS hacks' do
176
+ @custom.stylesheet('.foo { _border: 1px solid #fff; *width: 10px }')
177
+ .must_equal '.foo { }'
178
+ end
179
+ end
180
+ end
181
+
182
+ describe '#tree!' do
183
+ it 'should sanitize a Crass CSS parse tree' do
184
+ tree = Crass.parse(String.new("@import url(foo.css);\n") <<
185
+ ".foo { background: #fff; font: 16pt 'Comic Sans MS'; }\n" <<
186
+ "#bar { top: 125px; background: green; }")
187
+
188
+ @custom.tree!(tree).must_be_same_as tree
189
+
190
+ Crass::Parser.stringify(tree).must_equal String.new("\n") <<
191
+ ".foo { background: #fff; }\n" <<
192
+ "#bar { background: green; }"
193
+ end
194
+ end
195
+ end
196
+
197
+ describe 'class methods' do
198
+ describe '.properties' do
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; '
205
+ end
206
+ end
207
+
208
+ describe '.stylesheet' do
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
232
+ end
233
+ end
234
+
235
+ describe '.tree!' do
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; }"
246
+ end
247
+ end
248
+ end
249
+
250
+ describe 'functionality' do
251
+ before do
252
+ @default = Sanitize::CSS.new
253
+ @relaxed = Sanitize::CSS.new(Sanitize::Config::RELAXED[:css])
254
+ end
255
+
256
+ # https://github.com/rgrove/sanitize/issues/121
257
+ it 'should parse the contents of @media rules properly' do
258
+ css = '@media { p[class="center"] { text-align: center; }}'
259
+ @relaxed.stylesheet(css).must_equal css
260
+
261
+ css = %[
262
+ @media (max-width: 720px) {
263
+ p.foo > .bar { float: right; width: expression(body.scrollLeft + 50 + 'px'); }
264
+ #baz { color: green; }
265
+
266
+ @media (orientation: portrait) {
267
+ #baz { color: red; }
268
+ }
269
+ }
270
+ ].strip
271
+
272
+ @relaxed.stylesheet(css).must_equal %[
273
+ @media (max-width: 720px) {
274
+ p.foo > .bar { float: right; }
275
+ #baz { color: green; }
276
+
277
+ @media (orientation: portrait) {
278
+ #baz { color: red; }
279
+ }
280
+ }
281
+ ].strip
282
+ end
283
+
284
+ it 'should parse @page rules properly' do
285
+ css = %[
286
+ @page { margin: 2cm } /* All margins set to 2cm */
287
+
288
+ @page :right {
289
+ @top-center { content: "Preliminary edition" }
290
+ @bottom-center { content: counter(page) }
291
+ }
292
+
293
+ @page {
294
+ size: 8.5in 11in;
295
+ margin: 10%;
296
+
297
+ @top-left {
298
+ content: "Hamlet";
299
+ }
300
+ @top-right {
301
+ content: "Page " counter(page);
302
+ }
303
+ }
304
+ ].strip
305
+
306
+ @relaxed.stylesheet(css).must_equal css
307
+ end
308
+
309
+ describe ":at_rules" do
310
+ it "should remove blockless at-rules that aren't allowlisted" do
311
+ css = %[
312
+ @charset 'utf-8';
313
+ @import url('foo.css');
314
+ .foo { color: green; }
315
+ ].strip
316
+
317
+ @relaxed.stylesheet(css).strip.must_equal %[
318
+ .foo { color: green; }
319
+ ].strip
320
+ end
321
+
322
+ describe "when blockless at-rules are allowlisted" do
323
+ before do
324
+ @scss = Sanitize::CSS.new(Sanitize::Config.merge(Sanitize::Config::RELAXED[:css], {
325
+ :at_rules => ['charset', 'import']
326
+ }))
327
+ end
328
+
329
+ it "should not remove them" do
330
+ css = %[
331
+ @charset 'utf-8';
332
+ @import url('foo.css');
333
+ .foo { color: green; }
334
+ ].strip
335
+
336
+ @scss.stylesheet(css).must_equal %[
337
+ @charset 'utf-8';
338
+ @import url('foo.css');
339
+ .foo { color: green; }
340
+ ].strip
341
+ end
342
+
343
+ it "should remove them if they have invalid blocks" do
344
+ css = %[
345
+ @charset { color: green }
346
+ @import { color: green }
347
+ .foo { color: green; }
348
+ ].strip
349
+
350
+ @scss.stylesheet(css).strip.must_equal %[
351
+ .foo { color: green; }
352
+ ].strip
353
+ end
354
+ end
355
+
356
+ describe "when validating @import rules" do
357
+
358
+ describe "with no validation proc specified" do
359
+ before do
360
+ @scss = Sanitize::CSS.new(Sanitize::Config.merge(Sanitize::Config::RELAXED[:css], {
361
+ :at_rules => ['import']
362
+ }))
363
+ end
364
+
365
+ it "should allow any URL value" do
366
+ css = %[
367
+ @import url('https://somesite.com/something.css');
368
+ ].strip
369
+
370
+ @scss.stylesheet(css).strip.must_equal %[
371
+ @import url('https://somesite.com/something.css');
372
+ ].strip
373
+ end
374
+ end
375
+
376
+ describe "with a validation proc specified" do
377
+ before do
378
+ google_font_validator = Proc.new { |url| url.start_with?("https://fonts.googleapis.com") }
379
+
380
+ @scss = Sanitize::CSS.new(Sanitize::Config.merge(Sanitize::Config::RELAXED[:css], {
381
+ :at_rules => ['import'], :import_url_validator => google_font_validator
382
+ }))
383
+ end
384
+
385
+ it "should allow a google fonts url" do
386
+ css = %[
387
+ @import 'https://fonts.googleapis.com/css?family=Indie+Flower';
388
+ @import url('https://fonts.googleapis.com/css?family=Indie+Flower');
389
+ ].strip
390
+
391
+ @scss.stylesheet(css).strip.must_equal %[
392
+ @import 'https://fonts.googleapis.com/css?family=Indie+Flower';
393
+ @import url('https://fonts.googleapis.com/css?family=Indie+Flower');
394
+ ].strip
395
+ end
396
+
397
+ it "should not allow a nasty url" do
398
+ css = %[
399
+ @import 'https://fonts.googleapis.com/css?family=Indie+Flower';
400
+ @import 'https://nastysite.com/nasty_hax0r.css';
401
+ @import url('https://nastysite.com/nasty_hax0r.css');
402
+ ].strip
403
+
404
+ @scss.stylesheet(css).strip.must_equal %[
405
+ @import 'https://fonts.googleapis.com/css?family=Indie+Flower';
406
+ ].strip
407
+ end
408
+
409
+ it "should not allow a blank url" do
410
+ css = %[
411
+ @import 'https://fonts.googleapis.com/css?family=Indie+Flower';
412
+ @import '';
413
+ @import url('');
414
+ ].strip
415
+
416
+ @scss.stylesheet(css).strip.must_equal %[
417
+ @import 'https://fonts.googleapis.com/css?family=Indie+Flower';
418
+ ].strip
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end
424
+ end
@@ -0,0 +1,230 @@
1
+ # encoding: utf-8
2
+ require_relative 'common'
3
+
4
+ describe 'Transformers' do
5
+ make_my_diffs_pretty!
6
+ parallelize_me!
7
+
8
+ it 'should receive a complete env Hash as input' do
9
+ Sanitize.fragment('<SPAN>foo</SPAN>',
10
+ :foo => :bar,
11
+ :transformers => lambda {|env|
12
+ return unless env[:node].element?
13
+
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]
22
+ }
23
+ )
24
+ end
25
+
26
+ it 'should traverse all node types, including the fragment itself' do
27
+ nodes = []
28
+
29
+ Sanitize.fragment('<div>foo</div><!--bar--><script>cdata!</script>',
30
+ :transformers => proc {|env| nodes << env[:node_name] }
31
+ )
32
+
33
+ nodes.must_equal %w[
34
+ #document-fragment div text text text comment script text
35
+ ]
36
+ end
37
+
38
+ it 'should perform top-down traversal' do
39
+ nodes = []
40
+
41
+ Sanitize.fragment('<div><span><strong>foo</strong></span><b></b></div><p>bar</p>',
42
+ :transformers => proc {|env| nodes << env[:node_name] if env[:node].element? }
43
+ )
44
+
45
+ nodes.must_equal %w[div span strong b p]
46
+ end
47
+
48
+ it 'should allowlist nodes in the node allowlist' do
49
+ Sanitize.fragment('<div class="foo">foo</div><span>bar</span>',
50
+ :transformers => [
51
+ proc {|env|
52
+ {:node_allowlist => [env[:node]]} if env[:node_name] == 'div'
53
+ },
54
+
55
+ proc {|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]
61
+ }
62
+ ]
63
+ ).must_equal '<div class="foo">foo</div>bar'
64
+ end
65
+
66
+ it 'should clear the node allowlist after each fragment' do
67
+ called = false
68
+
69
+ Sanitize.fragment('<div>foo</div>',
70
+ :transformers => proc {|env| {:node_allowlist => [env[:node]]}}
71
+ )
72
+
73
+ Sanitize.fragment('<div>foo</div>',
74
+ :transformers => proc {|env|
75
+ called = true
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]
80
+ }
81
+ )
82
+
83
+ called.must_equal true
84
+ end
85
+
86
+ it 'should accept a method transformer' do
87
+ def transformer(env); end
88
+ Sanitize.fragment('<div>foo</div>', :transformers => method(:transformer))
89
+ .must_equal(' foo ')
90
+ end
91
+
92
+ describe 'Image allowlist transformer' do
93
+ require 'uri'
94
+
95
+ image_allowlist_transformer = lambda do |env|
96
+ # Ignore everything except <img> elements.
97
+ return unless env[:node_name] == 'img'
98
+
99
+ node = env[:node]
100
+ image_uri = URI.parse(node['src'])
101
+
102
+ # Only allow relative URLs or URLs with the example.com domain. The
103
+ # image_uri.host.nil? check ensures that protocol-relative URLs like
104
+ # "//evil.com/foo.jpg".
105
+ unless image_uri.host == 'example.com' || (image_uri.host.nil? && image_uri.relative?)
106
+ node.unlink # `Nokogiri::XML::Node#unlink` removes a node from the document
107
+ end
108
+ end
109
+
110
+ before do
111
+ @s = Sanitize.new(Sanitize::Config.merge(Sanitize::Config::RELAXED,
112
+ :transformers => image_allowlist_transformer))
113
+ end
114
+
115
+ it 'should allow images with relative URLs' do
116
+ input = '<img src="/foo/bar.jpg">'
117
+ @s.fragment(input).must_equal(input)
118
+ end
119
+
120
+ it 'should allow images at the example.com domain' do
121
+ input = '<img src="http://example.com/foo/bar.jpg">'
122
+ @s.fragment(input).must_equal(input)
123
+
124
+ input = '<img src="https://example.com/foo/bar.jpg">'
125
+ @s.fragment(input).must_equal(input)
126
+
127
+ input = '<img src="//example.com/foo/bar.jpg">'
128
+ @s.fragment(input).must_equal(input)
129
+ end
130
+
131
+ it 'should not allow images at other domains' do
132
+ input = '<img src="http://evil.com/foo/bar.jpg">'
133
+ @s.fragment(input).must_equal('')
134
+
135
+ input = '<img src="https://evil.com/foo/bar.jpg">'
136
+ @s.fragment(input).must_equal('')
137
+
138
+ input = '<img src="//evil.com/foo/bar.jpg">'
139
+ @s.fragment(input).must_equal('')
140
+
141
+ input = '<img src="http://subdomain.example.com/foo/bar.jpg">'
142
+ @s.fragment(input).must_equal('')
143
+ end
144
+ end
145
+
146
+ describe 'YouTube transformer' do
147
+ youtube_transformer = lambda do |env|
148
+ node = env[:node]
149
+ node_name = env[:node_name]
150
+
151
+ # Don't continue if this node is already allowlisted or is not an element.
152
+ return if env[:is_allowlisted] || !node.element?
153
+
154
+ # Don't continue unless the node is an iframe.
155
+ return unless node_name == 'iframe'
156
+
157
+ # Verify that the video URL is actually a valid YouTube video URL.
158
+ return unless node['src'] =~ %r|\A(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/|
159
+
160
+ # We're now certain that this is a YouTube embed, but we still need to run
161
+ # it through a special Sanitize step to ensure that no unwanted elements or
162
+ # attributes that don't belong in a YouTube embed can sneak in.
163
+ Sanitize.node!(node, {
164
+ :elements => %w[iframe],
165
+
166
+ :attributes => {
167
+ 'iframe' => %w[allowfullscreen frameborder height src width]
168
+ }
169
+ })
170
+
171
+ # Now that we're sure that this is a valid YouTube embed and that there are
172
+ # no unwanted elements or attributes hidden inside it, we can tell Sanitize
173
+ # to allowlist the current node.
174
+ {:node_allowlist => [node]}
175
+ end
176
+
177
+ it 'should allow HTTP YouTube video embeds' do
178
+ input = '<iframe width="420" height="315" src="http://www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen bogus="bogus"><script>alert()</script></iframe>'
179
+
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>'
182
+ end
183
+
184
+ it 'should allow HTTPS YouTube video embeds' do
185
+ input = '<iframe width="420" height="315" src="https://www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen bogus="bogus"><script>alert()</script></iframe>'
186
+
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>'
189
+ end
190
+
191
+ it 'should allow protocol-relative YouTube video embeds' do
192
+ input = '<iframe width="420" height="315" src="//www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen bogus="bogus"><script>alert()</script></iframe>'
193
+
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>'
196
+ end
197
+
198
+ it 'should allow privacy-enhanced YouTube video embeds' do
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>'
200
+
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>'
203
+ end
204
+
205
+ it 'should not allow non-YouTube video embeds' do
206
+ input = '<iframe width="420" height="315" src="http://www.fake-youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen></iframe>'
207
+
208
+ Sanitize.fragment(input, :transformers => youtube_transformer)
209
+ .must_equal('')
210
+ end
211
+ end
212
+
213
+ describe 'DOM modification transformer' do
214
+ b_to_strong_tag_transformer = lambda do |env|
215
+ node = env[:node]
216
+ node_name = env[:node_name]
217
+
218
+ if node_name == 'b'
219
+ node.name = 'strong'
220
+ end
221
+ end
222
+
223
+ it 'should allow the <b> tag to be changed to a <strong> tag' do
224
+ input = '<b>text</b>'
225
+
226
+ Sanitize.fragment(input, :elements => ['strong'], :transformers => b_to_strong_tag_transformer)
227
+ .must_equal '<strong>text</strong>'
228
+ end
229
+ end
230
+ end