sanitize 7.0.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7ec07b16780ba818a5fdbb0570fb6c84245c60b2656388cab0b03b0d00bbc6a
4
- data.tar.gz: 650c2a0c59fd4af1d305051e5488bcacee3f5a638a96960163f6677b838a9661
3
+ metadata.gz: 694fdddd1e40857325f7fac61ee5679e6a9fca926413c1b035062ab7449b92c7
4
+ data.tar.gz: 426f1b4ece2fe0514139b661ba77ca7b9a6196481a0759a3ac81a9f446996e3d
5
5
  SHA512:
6
- metadata.gz: ec33f841766422dda2ee1d889d2e3f42f6b3b479c2cf557cad80de0311969a4e132e02a5a33dda296af2e2a5fec8791aa6ba8eacd6ea370428cc40ff3f08a720
7
- data.tar.gz: f4cf669dba8e416c38fb4b9eae30b86e9c7816718e80cc32eac1f536f1e2023c59cb74df7e701af717a157e1fe1ccc902a89b26992488a85400e4dc0c089d07a
6
+ metadata.gz: 3e58a303bb8a1285fbc75ab93acfd896444dc125d3b9b13884868366720bc451e5c69409b9a852014394604bc932e0ffbeeb7c1b7192eb3cd0a673ec10a79df2
7
+ data.tar.gz: 8a4b8ca81da4bc4bb070941fab4b08f8e6d36a10487d0d90820e762155fe5717acfbc919c2676650d490cd7d9cac82f7a2f4ec21bc0f373f837938a23a670d6d
data/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to Sanitize are documented in this file. The format (since version 7.0.0) is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
 
5
+ ## 7.0.1 (2026-10-21)
6
+
7
+ ### Security
8
+
9
+ - CSS properties that contain a `{}` block anywhere in their value are now removed. Browsers that support [CSS nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting) reparse an invalid declaration containing a `{}` block as a nested style rule, so a value like `color:hover{position:fixed}` could previously be used to smuggle disallowed properties and URLs past Sanitize in a stylesheet or `<style>` element. (GHSA-wgw9-qhqg-8hv5)
10
+
11
+ - The contents of at-rules such as `@media` and `@supports` that appear in a list of declarations (inside a style rule, inside an at-rule like `@font-face`, or in CSS sanitized with `Sanitize::CSS.properties`) are now sanitized as declarations rather than as rules, matching how browsers that support CSS nesting parse them. Previously, a stylesheet like `p{@media all{position:fixed;a{}}}` could be used to smuggle disallowed properties and URLs past Sanitize, since Sanitize treated `position:fixed;a` as a style rule's selector. The same was true of CSS sanitized with `Sanitize::CSS.properties` if the result was later embedded in a style rule. Style rules nested inside these at-rules are now removed, just as style rules nested directly inside other style rules already were. (GHSA-wgw9-qhqg-8hv5)
12
+
13
+ - CSS properties, style rules, and at-rules that contain blocks or functions nested more deeply than Crass's maximum nesting depth (25 levels by default) are now removed. (GHSA-wgw9-qhqg-8hv5)
14
+
15
+ - Hardened CSS sanitization against tokenizer differentials involving bad URLs. CSS properties that contain a bad URL anywhere inside a simple block (such as `[...]` or `(...)`) are now removed, as are style rules whose selectors contain a bad URL and at-rules whose preludes contain a bad URL. URLs inside simple blocks are now also validated against the configured protocol allowlist. Previously, a bug in Crass (fixed in Crass 1.0.8) could be exploited to smuggle CSS declarations, rules, or at-rules past Sanitize by hiding them in a bad URL in one of these locations.
16
+
17
+ - CSS style rules whose selectors contain a semicolon are now removed, as a defense in depth measure. A semicolon is never valid in a selector, but when a style rule appears where a browser that supports CSS nesting expects declarations, the browser treats everything before the semicolon as a declaration. This could be used to smuggle disallowed properties past `Sanitize::CSS.tree!` when it was given a list of declarations rather than a list of rules (though this isn't something Sanitize itself does).
18
+
19
+ ### Fixed
20
+
21
+ - Removed an unused `require "cgi"` that caused a warning on Ruby 4.0, since the CGI library is no longer part of Ruby's standard library.
22
+
5
23
  ## 7.0.0 (2024-12-29)
6
24
 
7
25
  Sanitize has no breaking API changes in this release, but the major version number has been incremented because we've dropped support for end-of-life versions of Ruby. As long as you're using Ruby 3.1.0 or later, this should be a painless upgrade!
data/README.md CHANGED
@@ -12,10 +12,16 @@ Sanitize is based on the [Nokogiri HTML5 parser][nokogiri], which parses HTML th
12
12
  [crass]:https://github.com/rgrove/crass
13
13
  [nokogiri]:https://github.com/sparklemotion/nokogiri
14
14
 
15
+ > [!WARNING]
16
+ >
17
+ > Sanitize cannot fully sanitize the contents of `<math>` or `<svg>` elements. MathML and SVG elements are [foreign elements](https://html.spec.whatwg.org/multipage/syntax.html#foreign-elements) that don't follow normal HTML parsing rules.
18
+ >
19
+ > By default, Sanitize will remove all MathML and SVG elements. If you add MathML or SVG elements to a custom element allowlist, you may create a security vulnerability in your application.
20
+
15
21
  ## Links
16
22
 
17
23
  * [Home](https://github.com/rgrove/sanitize/)
18
- * [API Docs](https://rubydoc.info/github/rgrove/sanitize/Sanitize)
24
+ * [API Docs](https://rubydoc.info/gems/sanitize/Sanitize)
19
25
  * [Issues](https://github.com/rgrove/sanitize/issues)
20
26
  * [Release History](https://github.com/rgrove/sanitize/releases)
21
27
  * [Online Demo](https://sanitize-web.fly.dev/)
@@ -136,6 +142,9 @@ Sanitize.fragment(html,
136
142
 
137
143
  Sanitize will happily clean up a standalone CSS stylesheet or property string without needing to invoke the HTML parser.
138
144
 
145
+ > [!WARNING]
146
+ > This should only be used to sanitize CSS for use in a standalone CSS file. To safely sanitize CSS for use in an HTML context, such as in a `<style>` element or a `style` attribute, you must use `Sanitize.document()` or `Sanitize.fragment()` with an appropriate allowlist as described in the previous sections. Failure to do so may enable HTML injection attacks.
147
+
139
148
  ```ruby
140
149
  css = %[
141
150
  @import url(evil.css);
data/lib/sanitize/css.rb CHANGED
@@ -66,6 +66,10 @@ class Sanitize
66
66
  # tree = Crass.parse(css)
67
67
  # Sanitize::CSS.tree!(tree, Sanitize::Config::RELAXED)
68
68
  #
69
+ # The tree must be a list of rules, such as the tree returned by
70
+ # `Crass.parse`. To sanitize a list of declarations, such as the contents
71
+ # of an HTML `style` attribute, use {.properties}.
72
+ #
69
73
  # @return [Array] Sanitized Crass CSS parse tree.
70
74
  def self.tree!(tree, config = {})
71
75
  new(config).tree!(tree)
@@ -82,6 +86,11 @@ class Sanitize
82
86
  @at_rules_with_properties = Set.new(@config[:at_rules_with_properties])
83
87
  @at_rules_with_styles = Set.new(@config[:at_rules_with_styles])
84
88
  @import_url_validator = @config[:import_url_validator]
89
+
90
+ @parse_options = {
91
+ preserve_comments: @config[:allow_comments],
92
+ preserve_hacks: @config[:allow_hacks]
93
+ }.freeze
85
94
  end
86
95
 
87
96
  # Sanitizes inline CSS style properties.
@@ -96,11 +105,8 @@ class Sanitize
96
105
  #
97
106
  # @return [String] Sanitized CSS properties.
98
107
  def properties(css)
99
- tree = Crass.parse_properties(css,
100
- preserve_comments: @config[:allow_comments],
101
- preserve_hacks: @config[:allow_hacks])
102
-
103
- tree!(tree)
108
+ tree = Crass.parse_properties(css, @parse_options)
109
+ sanitize_tree!(tree, declarations: true)
104
110
  Crass::Parser.stringify(tree)
105
111
  end
106
112
 
@@ -127,10 +133,7 @@ class Sanitize
127
133
  #
128
134
  # @return [String] Sanitized CSS stylesheet.
129
135
  def stylesheet(css)
130
- tree = Crass.parse(css,
131
- preserve_comments: @config[:allow_comments],
132
- preserve_hacks: @config[:allow_hacks])
133
-
136
+ tree = Crass.parse(css, @parse_options)
134
137
  tree!(tree)
135
138
  Crass::Parser.stringify(tree)
136
139
  end
@@ -155,48 +158,13 @@ class Sanitize
155
158
  #
156
159
  # scss.tree!(tree)
157
160
  #
161
+ # The tree must be a list of rules, such as the tree returned by
162
+ # `Crass.parse`. To sanitize a list of declarations, such as the contents
163
+ # of an HTML `style` attribute, use {#properties}.
164
+ #
158
165
  # @return [Array] Sanitized Crass CSS parse tree.
159
166
  def tree!(tree)
160
- preceded_by_property = false
161
-
162
- tree.map! do |node|
163
- next nil if node.nil?
164
-
165
- case node[:node]
166
- when :at_rule
167
- preceded_by_property = false
168
- next at_rule!(node)
169
-
170
- when :comment
171
- next node if @config[:allow_comments]
172
-
173
- when :property
174
- prop = property!(node)
175
- preceded_by_property = !prop.nil?
176
- next prop
177
-
178
- when :semicolon
179
- # Only preserve the semicolon if it was preceded by an allowlisted
180
- # property. Otherwise, omit it in order to prevent redundant
181
- # semicolons.
182
- if preceded_by_property
183
- preceded_by_property = false
184
- next node
185
- end
186
-
187
- when :style_rule
188
- preceded_by_property = false
189
- tree!(node[:children])
190
- next node
191
-
192
- when :whitespace
193
- next node
194
- end
195
-
196
- nil
197
- end
198
-
199
- tree
167
+ sanitize_tree!(tree, declarations: false)
200
168
  end
201
169
 
202
170
  # -- Protected Instance Methods --------------------------------------------
@@ -204,22 +172,33 @@ class Sanitize
204
172
 
205
173
  # Sanitizes a CSS at-rule node. Returns the sanitized node, or `nil` if the
206
174
  # current config doesn't allow this at-rule.
207
- def at_rule!(rule)
175
+ #
176
+ # If _declarations_ is `true`, the at-rule appears in a list of
177
+ # declarations (such as the contents of a style rule) rather than in a list
178
+ # of rules.
179
+ def at_rule!(rule, declarations:)
208
180
  name = rule[:name].downcase
209
181
 
182
+ return nil if contains_invalid_node?(rule[:prelude])
183
+
210
184
  if @at_rules_with_styles.include?(name)
211
- styles = Crass::Parser.parse_rules(rule[:block],
212
- preserve_comments: @config[:allow_comments],
213
- preserve_hacks: @config[:allow_hacks])
185
+ # Browsers that support CSS nesting parse the block of an at-rule like
186
+ # `@media` that appears in a list of declarations as more declarations
187
+ # rather than as rules, so it must be sanitized as declarations.
188
+ # Otherwise a declaration like `position:fixed;a{}` would be parsed as
189
+ # a style rule with the selector `position:fixed;a` and would bypass
190
+ # the property allowlist.
191
+ styles = if declarations
192
+ Crass::Parser.parse_properties(rule[:block], @parse_options)
193
+ else
194
+ Crass::Parser.parse_rules(rule[:block], @parse_options)
195
+ end
214
196
 
215
- rule[:block] = tree!(styles)
197
+ rule[:block] = sanitize_tree!(styles, declarations: declarations)
216
198
 
217
199
  elsif @at_rules_with_properties.include?(name)
218
- props = Crass::Parser.parse_properties(rule[:block],
219
- preserve_comments: @config[:allow_comments],
220
- preserve_hacks: @config[:allow_hacks])
221
-
222
- rule[:block] = tree!(props)
200
+ props = Crass::Parser.parse_properties(rule[:block], @parse_options)
201
+ rule[:block] = sanitize_tree!(props, declarations: true)
223
202
 
224
203
  elsif @at_rules.include?(name)
225
204
  return nil if name == "import" && !import_url_allowed?(rule)
@@ -231,6 +210,31 @@ class Sanitize
231
210
  rule
232
211
  end
233
212
 
213
+ # Returns `true` if the given array of Crass nodes contains an invalid node
214
+ # (see {#invalid_node?}) at any depth.
215
+ def contains_invalid_node?(nodes)
216
+ contains_node?(nodes) { |node| invalid_node?(node) }
217
+ end
218
+
219
+ # Returns `true` if the given array of Crass nodes contains a node at any
220
+ # depth for which the given block returns a truthy value.
221
+ def contains_node?(nodes)
222
+ return false unless Array === nodes
223
+
224
+ stack = nodes.dup
225
+
226
+ until stack.empty?
227
+ node = stack.pop
228
+ next unless Hash === node
229
+ return true if yield(node)
230
+
231
+ value = node[:value]
232
+ stack.concat(value) if Array === value
233
+ end
234
+
235
+ false
236
+ end
237
+
234
238
  # Returns `true` if the given CSS function name is an image-related function
235
239
  # that may contain image URLs that need to be validated.
236
240
  def image_function?(name)
@@ -250,6 +254,22 @@ class Sanitize
250
254
  @import_url_validator.call(import_url)
251
255
  end
252
256
 
257
+ # Returns `true` if the given Crass node is a `:bad_url` or `:error` node.
258
+ #
259
+ # A bad URL is invalid CSS that a browser will discard, but a parser that
260
+ # disagrees with the browser about where a bad URL ends could be tricked
261
+ # into hiding CSS inside it.
262
+ #
263
+ # An `:error` node is CSS that Crass couldn't or wouldn't parse, such as a
264
+ # block nested more deeply than Crass's maximum nesting depth. Its contents
265
+ # are never inspected, but they're preserved verbatim when the tree is
266
+ # stringified, so a browser would still see them.
267
+ #
268
+ # It's safest to reject any construct that contains either one.
269
+ def invalid_node?(node)
270
+ node[:node] == :bad_url || node[:node] == :error
271
+ end
272
+
253
273
  # Sanitizes a CSS property node. Returns the sanitized node, or `nil` if the
254
274
  # current config doesn't allow this property.
255
275
  def property!(prop)
@@ -266,6 +286,9 @@ class Sanitize
266
286
  combined_value = +""
267
287
 
268
288
  nodes.each do |child|
289
+ # Reject properties that contain an invalid node at any depth.
290
+ return nil if invalid_node?(child)
291
+
269
292
  value = child[:value]
270
293
 
271
294
  case child[:node]
@@ -299,14 +322,86 @@ class Sanitize
299
322
  when :url
300
323
  return nil unless valid_url?(child)
301
324
 
302
- when :bad_url
303
- return nil
325
+ when :simple_block
326
+ # Reject properties that contain a `{}` block at any depth.
327
+ #
328
+ # A `{}` block isn't valid in a standard property value, and browsers
329
+ # that support CSS nesting will reparse an invalid declaration that
330
+ # contains one as a nested style rule (e.g.
331
+ # `color:hover{position:fixed}` becomes the rule
332
+ # `color:hover { position: fixed }`), which would allow disallowed
333
+ # properties to be smuggled past the sanitizer.
334
+ return nil if child[:start] == "{"
335
+
336
+ nodes.concat(value) if Array === value
304
337
  end
305
338
  end
306
339
 
307
340
  prop
308
341
  end
309
342
 
343
+ # Sanitizes the given Crass CSS parse tree and all its children, modifying
344
+ # it in place. See {#tree!}.
345
+ #
346
+ # If _declarations_ is `true`, the tree is a list of declarations (such as
347
+ # the contents of a style rule) rather than a list of rules.
348
+ def sanitize_tree!(tree, declarations:)
349
+ preceded_by_property = false
350
+
351
+ tree.map! do |node|
352
+ next nil if node.nil?
353
+
354
+ case node[:node]
355
+ when :at_rule
356
+ preceded_by_property = false
357
+ next at_rule!(node, declarations: declarations)
358
+
359
+ when :comment
360
+ next node if @config[:allow_comments]
361
+
362
+ when :property
363
+ prop = property!(node)
364
+ preceded_by_property = !prop.nil?
365
+ next prop
366
+
367
+ when :semicolon
368
+ # Only preserve the semicolon if it was preceded by an allowlisted
369
+ # property. Otherwise, omit it in order to prevent redundant
370
+ # semicolons.
371
+ if preceded_by_property
372
+ preceded_by_property = false
373
+ next node
374
+ end
375
+
376
+ when :style_rule
377
+ preceded_by_property = false
378
+
379
+ # Reject style rules whose selectors contain an invalid node (see
380
+ # {#invalid_node?}) or a semicolon at any depth.
381
+ #
382
+ # A semicolon is never valid in a selector, but if a style rule
383
+ # appears where a browser expects declarations (such as the contents
384
+ # of an `@media` rule in a list of declarations), the browser will
385
+ # treat everything before the semicolon as a declaration. For
386
+ # example, `position:fixed;a{}` would be treated as the declaration
387
+ # `position:fixed` followed by the style rule `a{}`.
388
+ next nil if contains_node?(node.dig(:selector, :tokens)) { |token|
389
+ invalid_node?(token) || token[:node] == :semicolon
390
+ }
391
+
392
+ sanitize_tree!(node[:children], declarations: true)
393
+ next node
394
+
395
+ when :whitespace
396
+ next node
397
+ end
398
+
399
+ nil
400
+ end
401
+
402
+ tree
403
+ end
404
+
310
405
  # Returns `true` if the given node (which may be of type `:url` or
311
406
  # `:function`, since the CSS syntax can produce both) uses an allowlisted
312
407
  # protocol.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "cgi"
4
3
  require "set"
5
4
 
6
5
  class Sanitize
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Sanitize
4
- VERSION = "7.0.0"
4
+ VERSION = "7.0.1"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sanitize
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0
4
+ version: 7.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Grove
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2024-12-30 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: crass
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 1.0.2
18
+ version: 1.0.8
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 1.0.2
25
+ version: 1.0.8
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: nokogiri
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -40,7 +40,7 @@ dependencies:
40
40
  description: |
41
41
  Sanitize is an allowlist-based HTML and CSS sanitizer. It removes all HTML
42
42
  and/or CSS from a string except the elements, attributes, and properties you
43
- choose to allow.'
43
+ choose to allow.
44
44
  email: ryan@wonko.com
45
45
  executables: []
46
46
  extensions: []
@@ -62,18 +62,6 @@ files:
62
62
  - lib/sanitize/transformers/clean_doctype.rb
63
63
  - lib/sanitize/transformers/clean_element.rb
64
64
  - lib/sanitize/version.rb
65
- - test/common.rb
66
- - test/test_clean_comment.rb
67
- - test/test_clean_css.rb
68
- - test/test_clean_doctype.rb
69
- - test/test_clean_element.rb
70
- - test/test_config.rb
71
- - test/test_malicious_css.rb
72
- - test/test_malicious_html.rb
73
- - test/test_parser.rb
74
- - test/test_sanitize.rb
75
- - test/test_sanitize_css.rb
76
- - test/test_transformers.rb
77
65
  homepage: https://github.com/rgrove/sanitize/
78
66
  licenses:
79
67
  - MIT
@@ -95,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
83
  - !ruby/object:Gem::Version
96
84
  version: '0'
97
85
  requirements: []
98
- rubygems_version: 3.6.2
86
+ rubygems_version: 4.0.20
99
87
  specification_version: 4
100
88
  summary: Allowlist-based HTML and CSS sanitizer.
101
89
  test_files: []
data/test/common.rb DELETED
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "minitest/autorun"
4
- require "sanitize"
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "common"
4
-
5
- describe "Sanitize::Transformers::CleanComment" do
6
- make_my_diffs_pretty!
7
- parallelize_me!
8
-
9
- describe "when :allow_comments is false" do
10
- before do
11
- @s = Sanitize.new(allow_comments: false, elements: ["div"])
12
- end
13
-
14
- it "should remove comments" do
15
- _(@s.fragment("foo <!-- comment --> bar")).must_equal "foo bar"
16
- _(@s.fragment("foo <!-- ")).must_equal "foo "
17
- _(@s.fragment("foo <!-- - -> bar")).must_equal "foo "
18
- _(@s.fragment("foo <!--\n\n\n\n-->bar")).must_equal "foo bar"
19
- _(@s.fragment("foo <!-- <!-- <!-- --> --> -->bar")).must_equal "foo --&gt; --&gt;bar"
20
- _(@s.fragment("foo <div <!-- comment -->>bar</div>")).must_equal "foo <div>&gt;bar</div>"
21
-
22
- # Special case: the comment markup is inside a <script>, which makes it
23
- # text content and not an actual HTML comment.
24
- _(@s.fragment("<script><!-- comment --></script>")).must_equal ""
25
-
26
- _(Sanitize.fragment("<script><!-- comment --></script>", allow_comments: false, elements: ["script"]))
27
- .must_equal "<script><!-- comment --></script>"
28
- end
29
- end
30
-
31
- describe "when :allow_comments is true" do
32
- before do
33
- @s = Sanitize.new(allow_comments: true, elements: ["div"])
34
- end
35
-
36
- it "should allow comments" do
37
- _(@s.fragment("foo <!-- comment --> bar")).must_equal "foo <!-- comment --> bar"
38
- _(@s.fragment("foo <!-- ")).must_equal "foo <!-- -->"
39
- _(@s.fragment("foo <!-- - -> bar")).must_equal "foo <!-- - -> bar-->"
40
- _(@s.fragment("foo <!--\n\n\n\n-->bar")).must_equal "foo <!--\n\n\n\n-->bar"
41
- _(@s.fragment("foo <!-- <!-- <!-- --> --> -->bar")).must_equal "foo <!-- <!-- <!-- --> --&gt; --&gt;bar"
42
- _(@s.fragment("foo <div <!-- comment -->>bar</div>")).must_equal "foo <div>&gt;bar</div>"
43
-
44
- _(Sanitize.fragment("<script><!-- comment --></script>", allow_comments: true, elements: ["script"]))
45
- .must_equal "<script><!-- comment --></script>"
46
- end
47
- end
48
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "common"
4
-
5
- describe "Sanitize::Transformers::CSS::CleanAttribute" do
6
- make_my_diffs_pretty!
7
- parallelize_me!
8
-
9
- before do
10
- @s = Sanitize.new(Sanitize::Config::RELAXED)
11
- end
12
-
13
- it "should sanitize CSS properties in style attributes" do
14
- _(@s.fragment(%[
15
- <div style="color: #fff; width: expression(alert(1)); /* <-- evil! */"></div>
16
- ].strip)).must_equal %(
17
- <div style="color: #fff; /* <-- evil! */"></div>
18
- ).strip
19
- end
20
-
21
- it "should remove the style attribute if the sanitized CSS is empty" do
22
- _(@s.fragment('<div style="width: expression(alert(1))"></div>'))
23
- .must_equal "<div></div>"
24
- end
25
- end
26
-
27
- describe "Sanitize::Transformers::CSS::CleanElement" do
28
- make_my_diffs_pretty!
29
- parallelize_me!
30
-
31
- before do
32
- @s = Sanitize.new(Sanitize::Config::RELAXED)
33
- end
34
-
35
- it "should sanitize CSS stylesheets in <style> elements" do
36
- html = %[
37
- <style>@import url(evil.css);
38
- /* Yay CSS! */
39
- .foo { color: #fff; }
40
- #bar { background: url(yay.jpg); bogus: wtf; }
41
- .evil { width: expression(xss()); }
42
-
43
- @media screen (max-width:480px) {
44
- .foo { width: 400px; }
45
- #bar:not(.baz) { height: 100px; }
46
- }
47
- </style>
48
- ].strip
49
-
50
- _(@s.fragment(html)).must_equal %[
51
- <style>
52
- /* Yay CSS! */
53
- .foo { color: #fff; }
54
- #bar { background: url(yay.jpg); }
55
- .evil { }
56
-
57
- @media screen (max-width:480px) {
58
- .foo { width: 400px; }
59
- #bar:not(.baz) { height: 100px; }
60
- }
61
- </style>
62
- ].strip
63
- end
64
-
65
- it "should remove the <style> element if the sanitized CSS is empty" do
66
- _(@s.fragment("<style></style>")).must_equal ""
67
- end
68
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "common"
4
-
5
- describe "Sanitize::Transformers::CleanDoctype" do
6
- make_my_diffs_pretty!
7
- parallelize_me!
8
-
9
- describe "when :allow_doctype is false" do
10
- before do
11
- @s = Sanitize.new(allow_doctype: false, elements: ["html"])
12
- end
13
-
14
- it "should remove doctype declarations" do
15
- _(@s.document("<!DOCTYPE html><html>foo</html>")).must_equal "<html>foo</html>"
16
- _(@s.fragment("<!DOCTYPE html>foo")).must_equal "foo"
17
- end
18
-
19
- it "should not allow doctype definitions in fragments" do
20
- _(@s.fragment("<!DOCTYPE html><html>foo</html>"))
21
- .must_equal "foo"
22
-
23
- _(@s.fragment('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"><html>foo</html>'))
24
- .must_equal "foo"
25
-
26
- _(@s.fragment("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><html>foo</html>"))
27
- .must_equal "foo"
28
- end
29
- end
30
-
31
- describe "when :allow_doctype is true" do
32
- before do
33
- @s = Sanitize.new(allow_doctype: true, elements: ["html"])
34
- end
35
-
36
- it "should allow doctype declarations in documents" do
37
- _(@s.document("<!DOCTYPE html><html>foo</html>"))
38
- .must_equal "<!DOCTYPE html><html>foo</html>"
39
-
40
- _(@s.document('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"><html>foo</html>'))
41
- .must_equal "<!DOCTYPE html><html>foo</html>"
42
-
43
- _(@s.document("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><html>foo</html>"))
44
- .must_equal "<!DOCTYPE html><html>foo</html>"
45
- end
46
-
47
- it "should not allow obviously invalid doctype declarations in documents" do
48
- _(@s.document("<!DOCTYPE blah blah blah><html>foo</html>"))
49
- .must_equal "<!DOCTYPE html><html>foo</html>"
50
-
51
- _(@s.document("<!DOCTYPE blah><html>foo</html>"))
52
- .must_equal "<!DOCTYPE html><html>foo</html>"
53
-
54
- _(@s.document('<!DOCTYPE html BLAH "-//W3C//DTD HTML 4.01//EN"><html>foo</html>'))
55
- .must_equal "<!DOCTYPE html><html>foo</html>"
56
-
57
- _(@s.document("<!whatever><html>foo</html>"))
58
- .must_equal "<html>foo</html>"
59
- end
60
-
61
- it "should not allow doctype definitions in fragments" do
62
- _(@s.fragment("<!DOCTYPE html><html>foo</html>"))
63
- .must_equal "foo"
64
-
65
- _(@s.fragment('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"><html>foo</html>'))
66
- .must_equal "foo"
67
-
68
- _(@s.fragment("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><html>foo</html>"))
69
- .must_equal "foo"
70
- end
71
- end
72
- end