class Sanitize::Transformers::CleanElement

def call(env)

def call(env)
  node = env[:node]
  return if node.type != Nokogiri::XML::Node::ELEMENT_NODE || env[:is_allowlisted]
  name = env[:node_name]
  # Delete any element that isn't in the config allowlist, unless the node
  # has already been deleted from the document.
  #
  # It's important that we not try to reparent the children of a node that
  # has already been deleted, since that seems to trigger a memory leak in
  # Nokogiri.
  unless @elements.include?(name) || node.parent.nil?
    # Elements like br, div, p, etc. need to be replaced with whitespace
    # in order to preserve readability.
    if @whitespace_elements.include?(name)
      node.add_previous_sibling(Nokogiri::XML::Text.new(@whitespace_elements[name][:before].to_s, node.document))
      unless node.children.empty?
        node.add_next_sibling(Nokogiri::XML::Text.new(@whitespace_elements[name][:after].to_s, node.document))
      end
    end
    unless node.children.empty?
      unless @remove_all_contents || @remove_element_contents.include?(name)
        node.add_previous_sibling(node.children)
      end
    end
    node.unlink
    return
  end
  attr_allowlist = @attributes[name] || @attributes[:all]
  if attr_allowlist.nil?
    # Delete all attributes from elements with no allowlisted attributes.
    node.attribute_nodes.each { |attr| attr.unlink }
  else
    allow_data_attributes = attr_allowlist.include?(:data)
    # Delete any attribute that isn't allowed on this element.
    node.attribute_nodes.each do |attr|
      attr_name = attr.name.downcase
      unless attr_allowlist.include?(attr_name)
        # The attribute isn't in the allowlist, but may still be allowed
        # if it's a data attribute.
        unless allow_data_attributes && attr_name.start_with?("data-") && attr_name =~ REGEX_DATA_ATTR
          # Either the attribute isn't a data attribute or arbitrary data
          # attributes aren't allowed. Remove the attribute.
          attr.unlink
          next
        end
      end
      # The attribute is allowed.
      # Remove any attributes that use unacceptable protocols.
      if @protocols.include?(name) && @protocols[name].include?(attr_name)
        attr_protocols = @protocols[name][attr_name]
        if attr.value =~ REGEX_PROTOCOL
          unless attr_protocols.include?($1.downcase)
            attr.unlink
            next
          end
        else
          unless attr_protocols.include?(:relative)
            attr.unlink
            next
          end
        end
        # Leading and trailing whitespace around URLs is ignored at parse
        # time. Stripping it here prevents it from being escaped by the
        # libxml2 workaround below.
        attr.value = attr.value.strip
      end
      # libxml2 >= 2.9.2 doesn't escape comments within some attributes,
      # in an attempt to preserve server-side includes. This can result in
      # XSS since an unescaped double quote can allow an attacker to
      # inject a non-allowlisted attribute.
      #
      # Sanitize works around this by implementing its own escaping for
      # affected attributes, some of which can exist on any element and
      # some of which can only exist on `<a>` elements.
      #
      # This fix is technically no longer necessary with Nokogumbo >= 2.0
      # since it no longer uses libxml2's serializer, but it's retained to
      # avoid breaking use cases where people might be sanitizing
      # individual Nokogiri nodes and then serializing them manually
      # without Nokogumbo.
      #
      # The relevant libxml2 code is here:
      # <https://github.com/GNOME/libxml2/commit/960f0e275616cadc29671a218d7fb9b69eb35588>
      if UNSAFE_LIBXML_ATTRS_GLOBAL.include?(attr_name) ||
          (name == "a" && UNSAFE_LIBXML_ATTRS_A.include?(attr_name))
        attr.value = attr.value.gsub(UNSAFE_LIBXML_ESCAPE_REGEX, UNSAFE_LIBXML_ESCAPE_CHARS)
      end
    end
  end
  # Add required attributes.
  if @add_attributes.include?(name)
    @add_attributes[name].each { |key, val| node[key] = val }
  end
  # Element-specific special cases.
  case name
  # If this is an allowlisted iframe that has children, remove all its
  # children. The HTML standard says iframes shouldn't have content, but
  # when they do, this content is parsed as text and is serialized
  # verbatim without being escaped, which is unsafe because legacy
  # browsers may still render it and execute `<script>` content. So the
  # safe and correct thing to do is to always remove iframe content.
  when "iframe"
    if !node.children.empty?
      node.children.each do |child|
        child.unlink
      end
    end
  # Prevent the use of `<meta>` elements that set a charset other than
  # UTF-8, since Sanitize's output is always UTF-8.
  when "meta"
    if node.has_attribute?("charset") &&
        node["charset"].downcase != "utf-8"
      node["charset"] = "utf-8"
    end
    if node.has_attribute?("http-equiv") &&
        node.has_attribute?("content") &&
        node["http-equiv"].downcase == "content-type" &&
        node["content"].downcase =~ /;\s*charset\s*=\s*(?!utf-8)/
      node["content"] = node["content"].gsub(/;\s*charset\s*=.+\z/, ";charset=utf-8")
    end
  # A `<noscript>` element's content is parsed differently in browsers
  # depending on whether or not scripting is enabled. Since Nokogiri
  # doesn't support scripting, it always parses `<noscript>` elements as
  # if scripting is disabled. This results in edge cases where it's not
  # possible to reliably sanitize the contents of a `<noscript>` element
  # because Nokogiri can't fully replicate the parsing behavior of a
  # scripting-enabled browser. The safest thing to do is to simply remove
  # all `<noscript>` elements.
  when "noscript"
    node.unlink
  end
end

def initialize(config)

def initialize(config)
  @add_attributes = config[:add_attributes]
  @attributes = config[:attributes].dup
  @elements = config[:elements]
  @protocols = config[:protocols]
  @remove_all_contents = false
  @remove_element_contents = Set.new
  @whitespace_elements = {}
  @attributes.each do |element_name, attrs|
    unless element_name == :all
      @attributes[element_name] = Set.new(attrs).merge(@attributes[:all] || [])
    end
  end
  # Backcompat: if :whitespace_elements is a Set, convert it to a hash.
  if config[:whitespace_elements].is_a?(Set)
    config[:whitespace_elements].each do |element|
      @whitespace_elements[element] = {before: " ", after: " "}
    end
  else
    @whitespace_elements = config[:whitespace_elements]
  end
  if config[:remove_contents].is_a?(Enumerable)
    @remove_element_contents.merge(config[:remove_contents].map(&:to_s))
  else
    @remove_all_contents = !!config[:remove_contents]
  end
end