lib/sanitize/css.rb in sanitize-3.0.4 vs lib/sanitize/css.rb in sanitize-3.1.0
- old
+ new
@@ -104,24 +104,38 @@
#
# scss.tree!(tree)
#
# @return [Array] Sanitized Crass CSS parse tree.
def tree!(tree)
+ preceded_by_property = false
+
tree.map! do |node|
next nil if node.nil?
case node[:node]
when :at_rule
+ preceded_by_property = false
next at_rule!(node)
when :comment
next node if @config[:allow_comments]
when :property
- next property!(node)
+ prop = property!(node)
+ preceded_by_property = !prop.nil?
+ next prop
+ when :semicolon
+ # Only preserve the semicolon if it was preceded by a whitelisted
+ # property. Otherwise, omit it in order to prevent redundant semicolons.
+ if preceded_by_property
+ preceded_by_property = false
+ next node
+ end
+
when :style_rule
+ preceded_by_property = false
tree!(node[:children])
next node
when :whitespace
next node
@@ -141,25 +155,22 @@
def at_rule!(rule)
name = rule[:name].downcase
return nil unless @config[:at_rules].include?(name)
if AT_RULES_WITH_STYLES.include?(name)
- # Remove the { and } tokens surrounding the @media block.
- tokens = rule[:block][:tokens][1...-1]
-
- styles = Crass::Parser.parse_rules(tokens,
+ styles = Crass::Parser.parse_rules(rule[:block],
:preserve_comments => @config[:allow_comments],
:preserve_hacks => @config[:allow_hacks])
- rule[:block][:value] = tree!(styles)
+ rule[:block] = tree!(styles)
elsif AT_RULES_WITH_PROPERTIES.include?(name)
- props = Crass::Parser.parse_properties(rule[:block][:value],
+ props = Crass::Parser.parse_properties(rule[:block],
:preserve_comments => @config[:allow_comments],
:preserve_hacks => @config[:allow_hacks])
- rule[:block][:value] = tree!(props)
+ rule[:block] = tree!(props)
else
rule.delete(:block)
end
@@ -184,38 +195,89 @@
nodes.each do |child|
value = child[:value]
case child[:node]
when :ident
- combined_value << value if String === value
+ combined_value << value.downcase if String === value
when :function
if child.key?(:name)
- return nil if child[:name].downcase == 'expression'
+ name = child[:name].downcase
+
+ if name == 'url'
+ return nil unless valid_url?(child)
+ end
+
+ combined_value << name
+ return nil if name == 'expression' || combined_value == 'expression'
end
if Array === value
nodes.concat(value)
elsif String === value
- combined_value << value
-
- if value.downcase == 'expression' || combined_value.downcase == 'expression'
- return nil
- end
+ lowercase_value = value.downcase
+ combined_value << lowercase_value
+ return nil if lowercase_value == 'expression' || combined_value == 'expression'
end
when :url
- if value =~ Sanitize::REGEX_PROTOCOL
- return nil unless @config[:protocols].include?($1.downcase)
- else
- return nil unless @config[:protocols].include?(:relative)
- end
+ return nil unless valid_url?(child)
when :bad_url
return nil
end
end
prop
+ end
+
+ # Returns `true` if the given node (which may be of type `:url` or
+ # `:function`, since the CSS syntax can produce both) uses a whitelisted
+ # protocol.
+ def valid_url?(node)
+ type = node[:node]
+
+ if type == :function
+ return false unless node.key?(:name) && node[:name].downcase == 'url'
+ return false unless Array === node[:value]
+
+ # A URL function's `:value` should be an array containing no more than one
+ # `:string` node and any number of `:whitespace` nodes.
+ #
+ # If it contains more than one `:string` node, or if it contains any other
+ # nodes except `:whitespace` nodes, it's not valid.
+ url_string_node = nil
+
+ node[:value].each do |token|
+ return false unless Hash === token
+
+ case token[:node]
+ when :string
+ return false unless url_string_node.nil?
+ url_string_node = token
+
+ when :whitespace
+ next
+
+ else
+ return false
+ end
+ end
+
+ return false if url_string_node.nil?
+ url = url_string_node[:value]
+ elsif type == :url
+ url = node[:value]
+ else
+ return false
+ end
+
+ if url =~ Sanitize::REGEX_PROTOCOL
+ return @config[:protocols].include?($1.downcase)
+ else
+ return @config[:protocols].include?(:relative)
+ end
+
+ false
end
end; end