sanitize 6.0.2 → 7.0.0
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 +4 -4
- data/{HISTORY.md → CHANGELOG.md} +64 -14
- data/LICENSE +3 -1
- data/README.md +120 -239
- data/lib/sanitize/config/basic.rb +15 -15
- data/lib/sanitize/config/default.rb +45 -45
- data/lib/sanitize/config/relaxed.rb +138 -32
- data/lib/sanitize/config/restricted.rb +2 -2
- data/lib/sanitize/config.rb +12 -14
- data/lib/sanitize/css.rb +317 -284
- data/lib/sanitize/transformers/clean_cdata.rb +9 -9
- data/lib/sanitize/transformers/clean_comment.rb +9 -9
- data/lib/sanitize/transformers/clean_css.rb +59 -55
- data/lib/sanitize/transformers/clean_doctype.rb +17 -13
- data/lib/sanitize/transformers/clean_element.rb +220 -237
- data/lib/sanitize/version.rb +3 -1
- data/lib/sanitize.rb +38 -38
- data/test/common.rb +4 -3
- data/test/test_clean_comment.rb +26 -25
- data/test/test_clean_css.rb +14 -13
- data/test/test_clean_doctype.rb +21 -20
- data/test/test_clean_element.rb +258 -273
- data/test/test_config.rb +22 -21
- data/test/test_malicious_css.rb +20 -19
- data/test/test_malicious_html.rb +100 -99
- data/test/test_parser.rb +26 -25
- data/test/test_sanitize.rb +70 -69
- data/test/test_sanitize_css.rb +158 -114
- data/test/test_transformers.rb +81 -83
- metadata +16 -43
data/lib/sanitize/css.rb
CHANGED
|
@@ -1,321 +1,333 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
|
|
6
|
-
class Sanitize
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
self.
|
|
23
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "crass"
|
|
4
|
+
require "set"
|
|
5
|
+
|
|
6
|
+
class Sanitize
|
|
7
|
+
class CSS
|
|
8
|
+
attr_reader :config
|
|
9
|
+
|
|
10
|
+
# -- Class Methods ---------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
# Sanitizes inline CSS style properties.
|
|
13
|
+
#
|
|
14
|
+
# This is most useful for sanitizing non-stylesheet fragments of CSS like
|
|
15
|
+
# you would find in the `style` attribute of an HTML element. To sanitize a
|
|
16
|
+
# full CSS stylesheet, use {.stylesheet}.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# Sanitize::CSS.properties("background: url(foo.png); color: #fff;")
|
|
20
|
+
#
|
|
21
|
+
# @return [String] Sanitized CSS properties.
|
|
22
|
+
def self.properties(css, config = {})
|
|
23
|
+
new(config).properties(css)
|
|
24
|
+
end
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
26
|
+
# Sanitizes a full CSS stylesheet.
|
|
27
|
+
#
|
|
28
|
+
# A stylesheet may include selectors, at-rules, and comments. To sanitize
|
|
29
|
+
# only inline style properties such as the contents of an HTML `style`
|
|
30
|
+
# attribute, use {.properties}.
|
|
31
|
+
#
|
|
32
|
+
# @example
|
|
33
|
+
# css = %[
|
|
34
|
+
# .foo {
|
|
35
|
+
# background: url(foo.png);
|
|
36
|
+
# color: #fff;
|
|
37
|
+
# }
|
|
38
|
+
#
|
|
39
|
+
# #bar {
|
|
40
|
+
# font: 42pt 'Comic Sans MS';
|
|
41
|
+
# }
|
|
42
|
+
# ]
|
|
43
|
+
#
|
|
44
|
+
# Sanitize::CSS.stylesheet(css, Sanitize::Config::RELAXED)
|
|
45
|
+
#
|
|
46
|
+
# @return [String] Sanitized CSS stylesheet.
|
|
47
|
+
def self.stylesheet(css, config = {})
|
|
48
|
+
new(config).stylesheet(css)
|
|
49
|
+
end
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
51
|
+
# Sanitizes the given Crass CSS parse tree and all its children, modifying
|
|
52
|
+
# it in place.
|
|
53
|
+
#
|
|
54
|
+
# @example
|
|
55
|
+
# css = %[
|
|
56
|
+
# .foo {
|
|
57
|
+
# background: url(foo.png);
|
|
58
|
+
# color: #fff;
|
|
59
|
+
# }
|
|
60
|
+
#
|
|
61
|
+
# #bar {
|
|
62
|
+
# font: 42pt 'Comic Sans MS';
|
|
63
|
+
# }
|
|
64
|
+
# ]
|
|
65
|
+
#
|
|
66
|
+
# tree = Crass.parse(css)
|
|
67
|
+
# Sanitize::CSS.tree!(tree, Sanitize::Config::RELAXED)
|
|
68
|
+
#
|
|
69
|
+
# @return [Array] Sanitized Crass CSS parse tree.
|
|
70
|
+
def self.tree!(tree, config = {})
|
|
71
|
+
new(config).tree!(tree)
|
|
72
|
+
end
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
# -- Instance Methods ------------------------------------------------------
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
# Returns a new Sanitize::CSS object initialized with the settings in
|
|
77
|
+
# _config_.
|
|
78
|
+
def initialize(config = {})
|
|
79
|
+
@config = Config.merge(Config::DEFAULT[:css], config[:css] || config)
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
@at_rules = Set.new(@config[:at_rules])
|
|
82
|
+
@at_rules_with_properties = Set.new(@config[:at_rules_with_properties])
|
|
83
|
+
@at_rules_with_styles = Set.new(@config[:at_rules_with_styles])
|
|
84
|
+
@import_url_validator = @config[:import_url_validator]
|
|
85
|
+
end
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
87
|
+
# Sanitizes inline CSS style properties.
|
|
88
|
+
#
|
|
89
|
+
# This is most useful for sanitizing non-stylesheet fragments of CSS like
|
|
90
|
+
# you would find in the `style` attribute of an HTML element. To sanitize a
|
|
91
|
+
# full CSS stylesheet, use {#stylesheet}.
|
|
92
|
+
#
|
|
93
|
+
# @example
|
|
94
|
+
# scss = Sanitize::CSS.new(Sanitize::Config::RELAXED)
|
|
95
|
+
# scss.properties("background: url(foo.png); color: #fff;")
|
|
96
|
+
#
|
|
97
|
+
# @return [String] Sanitized CSS properties.
|
|
98
|
+
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)
|
|
104
|
+
Crass::Parser.stringify(tree)
|
|
105
|
+
end
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
107
|
+
# Sanitizes a full CSS stylesheet.
|
|
108
|
+
#
|
|
109
|
+
# A stylesheet may include selectors, at-rules, and comments. To sanitize
|
|
110
|
+
# only inline style properties such as the contents of an HTML `style`
|
|
111
|
+
# attribute, use {#properties}.
|
|
112
|
+
#
|
|
113
|
+
# @example
|
|
114
|
+
# css = %[
|
|
115
|
+
# .foo {
|
|
116
|
+
# background: url(foo.png);
|
|
117
|
+
# color: #fff;
|
|
118
|
+
# }
|
|
119
|
+
#
|
|
120
|
+
# #bar {
|
|
121
|
+
# font: 42pt 'Comic Sans MS';
|
|
122
|
+
# }
|
|
123
|
+
# ]
|
|
124
|
+
#
|
|
125
|
+
# scss = Sanitize::CSS.new(Sanitize::Config::RELAXED)
|
|
126
|
+
# scss.stylesheet(css)
|
|
127
|
+
#
|
|
128
|
+
# @return [String] Sanitized CSS stylesheet.
|
|
129
|
+
def stylesheet(css)
|
|
130
|
+
tree = Crass.parse(css,
|
|
131
|
+
preserve_comments: @config[:allow_comments],
|
|
132
|
+
preserve_hacks: @config[:allow_hacks])
|
|
133
|
+
|
|
134
|
+
tree!(tree)
|
|
135
|
+
Crass::Parser.stringify(tree)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Sanitizes the given Crass CSS parse tree and all its children, modifying
|
|
139
|
+
# it in place.
|
|
140
|
+
#
|
|
141
|
+
# @example
|
|
142
|
+
# css = %[
|
|
143
|
+
# .foo {
|
|
144
|
+
# background: url(foo.png);
|
|
145
|
+
# color: #fff;
|
|
146
|
+
# }
|
|
147
|
+
#
|
|
148
|
+
# #bar {
|
|
149
|
+
# font: 42pt 'Comic Sans MS';
|
|
150
|
+
# }
|
|
151
|
+
# ]
|
|
152
|
+
#
|
|
153
|
+
# scss = Sanitize::CSS.new(Sanitize::Config::RELAXED)
|
|
154
|
+
# tree = Crass.parse(css)
|
|
155
|
+
#
|
|
156
|
+
# scss.tree!(tree)
|
|
157
|
+
#
|
|
158
|
+
# @return [Array] Sanitized Crass CSS parse tree.
|
|
159
|
+
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
|
|
136
186
|
|
|
137
|
-
|
|
138
|
-
# in place.
|
|
139
|
-
#
|
|
140
|
-
# @example
|
|
141
|
-
# css = %[
|
|
142
|
-
# .foo {
|
|
143
|
-
# background: url(foo.png);
|
|
144
|
-
# color: #fff;
|
|
145
|
-
# }
|
|
146
|
-
#
|
|
147
|
-
# #bar {
|
|
148
|
-
# font: 42pt 'Comic Sans MS';
|
|
149
|
-
# }
|
|
150
|
-
# ]
|
|
151
|
-
#
|
|
152
|
-
# scss = Sanitize::CSS.new(Sanitize::Config::RELAXED)
|
|
153
|
-
# tree = Crass.parse(css)
|
|
154
|
-
#
|
|
155
|
-
# scss.tree!(tree)
|
|
156
|
-
#
|
|
157
|
-
# @return [Array] Sanitized Crass CSS parse tree.
|
|
158
|
-
def tree!(tree)
|
|
159
|
-
preceded_by_property = false
|
|
160
|
-
|
|
161
|
-
tree.map! do |node|
|
|
162
|
-
next nil if node.nil?
|
|
163
|
-
|
|
164
|
-
case node[:node]
|
|
165
|
-
when :at_rule
|
|
166
|
-
preceded_by_property = false
|
|
167
|
-
next at_rule!(node)
|
|
168
|
-
|
|
169
|
-
when :comment
|
|
170
|
-
next node if @config[:allow_comments]
|
|
171
|
-
|
|
172
|
-
when :property
|
|
173
|
-
prop = property!(node)
|
|
174
|
-
preceded_by_property = !prop.nil?
|
|
175
|
-
next prop
|
|
176
|
-
|
|
177
|
-
when :semicolon
|
|
178
|
-
# Only preserve the semicolon if it was preceded by an allowlisted
|
|
179
|
-
# property. Otherwise, omit it in order to prevent redundant semicolons.
|
|
180
|
-
if preceded_by_property
|
|
187
|
+
when :style_rule
|
|
181
188
|
preceded_by_property = false
|
|
189
|
+
tree!(node[:children])
|
|
182
190
|
next node
|
|
183
|
-
end
|
|
184
191
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
next node
|
|
192
|
+
when :whitespace
|
|
193
|
+
next node
|
|
194
|
+
end
|
|
189
195
|
|
|
190
|
-
|
|
191
|
-
next node
|
|
196
|
+
nil
|
|
192
197
|
end
|
|
193
198
|
|
|
194
|
-
|
|
199
|
+
tree
|
|
195
200
|
end
|
|
196
201
|
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
# -- Protected Instance Methods --------------------------------------------
|
|
203
|
+
protected
|
|
199
204
|
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
# Sanitizes a CSS at-rule node. Returns the sanitized node, or `nil` if the
|
|
206
|
+
# current config doesn't allow this at-rule.
|
|
207
|
+
def at_rule!(rule)
|
|
208
|
+
name = rule[:name].downcase
|
|
202
209
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
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])
|
|
207
214
|
|
|
208
|
-
|
|
209
|
-
styles = Crass::Parser.parse_rules(rule[:block],
|
|
210
|
-
:preserve_comments => @config[:allow_comments],
|
|
211
|
-
:preserve_hacks => @config[:allow_hacks])
|
|
215
|
+
rule[:block] = tree!(styles)
|
|
212
216
|
|
|
213
|
-
|
|
217
|
+
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])
|
|
214
221
|
|
|
215
|
-
|
|
216
|
-
props = Crass::Parser.parse_properties(rule[:block],
|
|
217
|
-
:preserve_comments => @config[:allow_comments],
|
|
218
|
-
:preserve_hacks => @config[:allow_hacks])
|
|
222
|
+
rule[:block] = tree!(props)
|
|
219
223
|
|
|
220
|
-
|
|
224
|
+
elsif @at_rules.include?(name)
|
|
225
|
+
return nil if name == "import" && !import_url_allowed?(rule)
|
|
226
|
+
return nil if rule.has_key?(:block)
|
|
227
|
+
else
|
|
228
|
+
return nil
|
|
229
|
+
end
|
|
221
230
|
|
|
222
|
-
|
|
223
|
-
return nil if name == "import" && !import_url_allowed?(rule)
|
|
224
|
-
return nil if rule.has_key?(:block)
|
|
225
|
-
else
|
|
226
|
-
return nil
|
|
231
|
+
rule
|
|
227
232
|
end
|
|
228
233
|
|
|
229
|
-
|
|
230
|
-
|
|
234
|
+
# Returns `true` if the given CSS function name is an image-related function
|
|
235
|
+
# that may contain image URLs that need to be validated.
|
|
236
|
+
def image_function?(name)
|
|
237
|
+
["image", "image-set", "-webkit-image-set"].include?(name)
|
|
238
|
+
end
|
|
231
239
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
240
|
+
# Passes the URL value of an @import rule to a block to ensure
|
|
241
|
+
# it's an allowed URL
|
|
242
|
+
def import_url_allowed?(rule)
|
|
243
|
+
return true unless @import_url_validator
|
|
236
244
|
|
|
237
|
-
|
|
245
|
+
url_token = rule[:tokens].detect { |t| t[:node] == :url || t[:node] == :string }
|
|
238
246
|
|
|
239
|
-
|
|
240
|
-
|
|
247
|
+
# don't allow @imports with no URL value
|
|
248
|
+
return false unless url_token && (import_url = url_token[:value])
|
|
241
249
|
|
|
242
|
-
|
|
243
|
-
|
|
250
|
+
@import_url_validator.call(import_url)
|
|
251
|
+
end
|
|
244
252
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
253
|
+
# Sanitizes a CSS property node. Returns the sanitized node, or `nil` if the
|
|
254
|
+
# current config doesn't allow this property.
|
|
255
|
+
def property!(prop)
|
|
256
|
+
name = prop[:name].downcase
|
|
249
257
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
258
|
+
# Preserve IE * and _ hacks if desired.
|
|
259
|
+
if @config[:allow_hacks]
|
|
260
|
+
name.slice!(0) if /\A[*_]/.match?(name)
|
|
261
|
+
end
|
|
254
262
|
|
|
255
|
-
|
|
263
|
+
return nil unless @config[:properties].include?(name)
|
|
256
264
|
|
|
257
|
-
|
|
258
|
-
|
|
265
|
+
nodes = prop[:children].dup
|
|
266
|
+
combined_value = +""
|
|
259
267
|
|
|
260
|
-
|
|
261
|
-
|
|
268
|
+
nodes.each do |child|
|
|
269
|
+
value = child[:value]
|
|
262
270
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
271
|
+
case child[:node]
|
|
272
|
+
when :ident
|
|
273
|
+
combined_value << value.downcase if String === value
|
|
266
274
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
275
|
+
when :function
|
|
276
|
+
if child.key?(:name)
|
|
277
|
+
name = child[:name].downcase
|
|
270
278
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
279
|
+
if name == "url"
|
|
280
|
+
return nil unless valid_url?(child)
|
|
281
|
+
end
|
|
274
282
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
283
|
+
if image_function?(name)
|
|
284
|
+
return nil unless valid_image?(child)
|
|
285
|
+
end
|
|
278
286
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
lowercase_value = value.downcase
|
|
283
|
-
combined_value << lowercase_value
|
|
284
|
-
return nil if lowercase_value == 'expression' || combined_value == 'expression'
|
|
285
|
-
end
|
|
287
|
+
combined_value << name
|
|
288
|
+
return nil if name == "expression" || combined_value == "expression"
|
|
289
|
+
end
|
|
286
290
|
|
|
287
|
-
|
|
288
|
-
|
|
291
|
+
if Array === value
|
|
292
|
+
nodes.concat(value)
|
|
293
|
+
elsif String === value
|
|
294
|
+
lowercase_value = value.downcase
|
|
295
|
+
combined_value << lowercase_value
|
|
296
|
+
return nil if lowercase_value == "expression" || combined_value == "expression"
|
|
297
|
+
end
|
|
289
298
|
|
|
290
|
-
|
|
291
|
-
|
|
299
|
+
when :url
|
|
300
|
+
return nil unless valid_url?(child)
|
|
301
|
+
|
|
302
|
+
when :bad_url
|
|
303
|
+
return nil
|
|
304
|
+
end
|
|
292
305
|
end
|
|
293
|
-
end
|
|
294
306
|
|
|
295
|
-
|
|
296
|
-
|
|
307
|
+
prop
|
|
308
|
+
end
|
|
297
309
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
310
|
+
# Returns `true` if the given node (which may be of type `:url` or
|
|
311
|
+
# `:function`, since the CSS syntax can produce both) uses an allowlisted
|
|
312
|
+
# protocol.
|
|
313
|
+
def valid_url?(node)
|
|
314
|
+
type = node[:node]
|
|
303
315
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
316
|
+
if type == :function
|
|
317
|
+
return false unless node.key?(:name) && node[:name].downcase == "url"
|
|
318
|
+
return false unless Array === node[:value]
|
|
307
319
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
320
|
+
# A URL function's `:value` should be an array containing no more than
|
|
321
|
+
# one `:string` node and any number of `:whitespace` nodes.
|
|
322
|
+
#
|
|
323
|
+
# If it contains more than one `:string` node, or if it contains any
|
|
324
|
+
# other nodes except `:whitespace` nodes, it's not valid.
|
|
325
|
+
url_string_node = nil
|
|
314
326
|
|
|
315
|
-
|
|
316
|
-
|
|
327
|
+
node[:value].each do |token|
|
|
328
|
+
return false unless Hash === token
|
|
317
329
|
|
|
318
|
-
|
|
330
|
+
case token[:node]
|
|
319
331
|
when :string
|
|
320
332
|
return false unless url_string_node.nil?
|
|
321
333
|
url_string_node = token
|
|
@@ -325,24 +337,45 @@ class Sanitize; class CSS
|
|
|
325
337
|
|
|
326
338
|
else
|
|
327
339
|
return false
|
|
340
|
+
end
|
|
328
341
|
end
|
|
342
|
+
|
|
343
|
+
return false if url_string_node.nil?
|
|
344
|
+
url = url_string_node[:value]
|
|
345
|
+
elsif type == :url
|
|
346
|
+
url = node[:value]
|
|
347
|
+
else
|
|
348
|
+
return false
|
|
329
349
|
end
|
|
330
350
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
return false
|
|
351
|
+
if url =~ Sanitize::REGEX_PROTOCOL
|
|
352
|
+
@config[:protocols].include?($1.downcase)
|
|
353
|
+
else
|
|
354
|
+
@config[:protocols].include?(:relative)
|
|
355
|
+
end
|
|
337
356
|
end
|
|
338
357
|
|
|
339
|
-
if
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
return
|
|
343
|
-
|
|
358
|
+
# Returns `true` if the given node is an image-related function and contains
|
|
359
|
+
# only strings that use an allowlisted protocol.
|
|
360
|
+
def valid_image?(node)
|
|
361
|
+
return false unless node[:node] == :function
|
|
362
|
+
return false unless node.key?(:name) && image_function?(node[:name].downcase)
|
|
363
|
+
return false unless Array === node[:value]
|
|
344
364
|
|
|
345
|
-
|
|
346
|
-
|
|
365
|
+
node[:value].each do |token|
|
|
366
|
+
return false unless Hash === token
|
|
347
367
|
|
|
348
|
-
|
|
368
|
+
case token[:node]
|
|
369
|
+
when :string
|
|
370
|
+
if token[:value] =~ Sanitize::REGEX_PROTOCOL
|
|
371
|
+
return false unless @config[:protocols].include?($1.downcase)
|
|
372
|
+
else
|
|
373
|
+
return false unless @config[:protocols].include?(:relative)
|
|
374
|
+
end
|
|
375
|
+
else
|
|
376
|
+
next
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|