class Sanitize::CSS

def self.properties(css, config = {})

Returns:
  • (String) - Sanitized CSS properties.
def self.properties(css, config = {})
  self.new(config).properties(css)
end

def self.stylesheet(css, config = {})

Returns:
  • (String) - Sanitized CSS stylesheet.
def self.stylesheet(css, config = {})
  self.new(config).stylesheet(css)
end

def self.tree!(tree, config = {})

Returns:
  • (Array) - Sanitized Crass CSS parse tree.
def self.tree!(tree, config = {})
  self.new(config).tree!(tree)
end

def at_rule!(rule)

current config doesn't allow this at-rule.
Sanitizes a CSS at-rule node. Returns the sanitized node, or `nil` if the
def at_rule!(rule)
  name = rule[:name].downcase
  if @at_rules_with_styles.include?(name)
    styles = Crass::Parser.parse_rules(rule[:block],
      :preserve_comments => @config[:allow_comments],
      :preserve_hacks    => @config[:allow_hacks])
    rule[:block] = tree!(styles)
  elsif @at_rules_with_properties.include?(name)
    props = Crass::Parser.parse_properties(rule[:block],
      :preserve_comments => @config[:allow_comments],
      :preserve_hacks    => @config[:allow_hacks])
    rule[:block] = tree!(props)
  elsif @at_rules.include?(name)
    return nil if name == "import" && !import_url_allowed?(rule)
    return nil if rule.has_key?(:block)
  else
    return nil
  end
  rule
end

def import_url_allowed?(rule)

it's an allowed URL
Passes the URL value of an @import rule to a block to ensure
def import_url_allowed?(rule)
  return true unless @import_url_validator
  url_token = rule[:tokens].detect { |t| t[:node] == :url || t[:node] == :string }
  # don't allow @imports with no URL value
  return false unless url_token && (import_url = url_token[:value])
  @import_url_validator.call(import_url)
end

def initialize(config = {})

_config_.
Returns a new Sanitize::CSS object initialized with the settings in
def initialize(config = {})
  @config = Config.merge(Config::DEFAULT[:css], config[:css] || config)
  @at_rules                 = Set.new(@config[:at_rules])
  @at_rules_with_properties = Set.new(@config[:at_rules_with_properties])
  @at_rules_with_styles     = Set.new(@config[:at_rules_with_styles])
  @import_url_validator     = @config[:import_url_validator]
end

def properties(css)

Returns:
  • (String) - Sanitized CSS properties.
def properties(css)
  tree = Crass.parse_properties(css,
    :preserve_comments => @config[:allow_comments],
    :preserve_hacks    => @config[:allow_hacks])
  tree!(tree)
  Crass::Parser.stringify(tree)
end

def property!(prop)

current config doesn't allow this property.
Sanitizes a CSS property node. Returns the sanitized node, or `nil` if the
def property!(prop)
  name = prop[:name].downcase
  # Preserve IE * and _ hacks if desired.
  if @config[:allow_hacks]
    name.slice!(0) if name =~ /\A[*_]/
  end
  return nil unless @config[:properties].include?(name)
  nodes          = prop[:children].dup
  combined_value = String.new
  nodes.each do |child|
    value = child[:value]
    case child[:node]
    when :ident
      combined_value << value.downcase if String === value
    when :function
      if child.key?(:name)
        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
        lowercase_value = value.downcase
        combined_value << lowercase_value
        return nil if lowercase_value == 'expression' || combined_value == 'expression'
      end
    when :url
      return nil unless valid_url?(child)
    when :bad_url
      return nil
    end
  end
  prop
end

def stylesheet(css)

Returns:
  • (String) - Sanitized CSS stylesheet.
def stylesheet(css)
  tree = Crass.parse(css,
    :preserve_comments => @config[:allow_comments],
    :preserve_hacks    => @config[:allow_hacks])
  tree!(tree)
  Crass::Parser.stringify(tree)
end

def tree!(tree)

Returns:
  • (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
      prop = property!(node)
      preceded_by_property = !prop.nil?
      next prop
    when :semicolon
      # Only preserve the semicolon if it was preceded by an allowlisted
      # 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
    end
    nil
  end
  tree
end

def valid_url?(node)

protocol.
`:function`, since the CSS syntax can produce both) uses an allowlisted
Returns `true` if the given node (which may be of type `:url` or
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