# frozen_string_literal: true
module Loofah
#
# Loofah provides some built-in scrubbers for sanitizing with
# HTML5lib's safelist and for accomplishing some common
# transformation tasks.
#
#
# === Loofah::Scrubbers::Strip / scrub!(:strip)
#
# +:strip+ removes unknown/unsafe tags, but leaves behind the pristine contents:
#
# unsafe_html = "ohai!
div is safe
but foo is not"
# Loofah.fragment(unsafe_html).scrub!(:strip)
# => "ohai! div is safe
but foo is not"
#
#
# === Loofah::Scrubbers::Prune / scrub!(:prune)
#
# +:prune+ removes unknown/unsafe tags and their contents (including their subtrees):
#
# unsafe_html = "ohai! div is safe
but foo is not"
# Loofah.fragment(unsafe_html).scrub!(:prune)
# => "ohai! div is safe
"
#
#
# === Loofah::Scrubbers::Escape / scrub!(:escape)
#
# +:escape+ performs HTML entity escaping on the unknown/unsafe tags:
#
# unsafe_html = "ohai! div is safe
but foo is not"
# Loofah.fragment(unsafe_html).scrub!(:escape)
# => "ohai! div is safe
<foo>but foo is <b>not</b></foo>"
#
#
# === Loofah::Scrubbers::Whitewash / scrub!(:whitewash)
#
# +:whitewash+ removes all comments, styling and attributes in
# addition to doing markup-fixer-uppery and pruning unsafe tags. I
# like to call this "whitewashing", since it's like putting a new
# layer of paint on top of the HTML input to make it look nice.
#
# messy_markup = "ohai! div with attributes
"
# Loofah.fragment(messy_markup).scrub!(:whitewash)
# => "ohai! div with attributes
"
#
# One use case for this scrubber is to clean up HTML that was
# cut-and-pasted from Microsoft Word into a WYSIWYG editor or a
# rich text editor. Microsoft's software is famous for injecting
# all kinds of cruft into its HTML output. Who needs that crap?
# Certainly not me.
#
#
# === Loofah::Scrubbers::NoFollow / scrub!(:nofollow)
#
# +:nofollow+ adds a rel="nofollow" attribute to all links
#
# link_farmers_markup = "ohai! I like your blog post"
# Loofah.fragment(link_farmers_markup).scrub!(:nofollow)
# => "ohai! I like your blog post"
#
#
# === Loofah::Scrubbers::NoOpener / scrub!(:noopener)
#
# +:noopener+ adds a rel="noopener" attribute to all links
#
# link_farmers_markup = "ohai! I like your blog post"
# Loofah.fragment(link_farmers_markup).scrub!(:noopener)
# => "ohai! I like your blog post"
#
#
# === Loofah::Scrubbers::Unprintable / scrub!(:unprintable)
#
# +:unprintable+ removes unprintable Unicode characters.
#
# markup = "Some text with an unprintable character at the end\u2028
"
# Loofah.fragment(markup).scrub!(:unprintable)
# => "Some text with an unprintable character at the end
"
#
# You may not be able to see the unprintable character in the above example, but there is a
# U+2028 character right before the closing tag. These characters can cause issues if
# the content is ever parsed by JavaScript - more information here:
#
# http://timelessrepo.com/json-isnt-a-javascript-subset
#
module Scrubbers
#
# === scrub!(:strip)
#
# +:strip+ removes unknown/unsafe tags, but leaves behind the pristine contents:
#
# unsafe_html = "ohai! div is safe
but foo is not"
# Loofah.fragment(unsafe_html).scrub!(:strip)
# => "ohai! div is safe
but foo is not"
#
class Strip < Scrubber
def initialize
@direction = :bottom_up
end
def scrub(node)
return CONTINUE if html5lib_sanitize(node) == CONTINUE
if node.children.length == 1 && node.children.first.cdata?
sanitized_text = Loofah.fragment(node.children.first.to_html).scrub!(:strip).to_html
node.before Nokogiri::XML::Text.new(sanitized_text, node.document)
else
node.before node.children
end
node.remove
end
end
#
# === scrub!(:prune)
#
# +:prune+ removes unknown/unsafe tags and their contents (including their subtrees):
#
# unsafe_html = "ohai! div is safe
but foo is not"
# Loofah.fragment(unsafe_html).scrub!(:prune)
# => "ohai! div is safe
"
#
class Prune < Scrubber
def initialize
@direction = :top_down
end
def scrub(node)
return CONTINUE if html5lib_sanitize(node) == CONTINUE
node.remove
return STOP
end
end
#
# === scrub!(:escape)
#
# +:escape+ performs HTML entity escaping on the unknown/unsafe tags:
#
# unsafe_html = "ohai! div is safe
but foo is not"
# Loofah.fragment(unsafe_html).scrub!(:escape)
# => "ohai! div is safe
<foo>but foo is <b>not</b></foo>"
#
class Escape < Scrubber
def initialize
@direction = :top_down
end
def scrub(node)
return CONTINUE if html5lib_sanitize(node) == CONTINUE
node.add_next_sibling Nokogiri::XML::Text.new(node.to_s, node.document)
node.remove
return STOP
end
end
#
# === scrub!(:whitewash)
#
# +:whitewash+ removes all comments, styling and attributes in
# addition to doing markup-fixer-uppery and pruning unsafe tags. I
# like to call this "whitewashing", since it's like putting a new
# layer of paint on top of the HTML input to make it look nice.
#
# messy_markup = "ohai! div with attributes
"
# Loofah.fragment(messy_markup).scrub!(:whitewash)
# => "ohai! div with attributes
"
#
# One use case for this scrubber is to clean up HTML that was
# cut-and-pasted from Microsoft Word into a WYSIWYG editor or a
# rich text editor. Microsoft's software is famous for injecting
# all kinds of cruft into its HTML output. Who needs that crap?
# Certainly not me.
#
class Whitewash < Scrubber
def initialize
@direction = :top_down
end
def scrub(node)
case node.type
when Nokogiri::XML::Node::ELEMENT_NODE
if HTML5::Scrub.allowed_element? node.name
node.attributes.each { |attr| node.remove_attribute(attr.first) }
return CONTINUE if node.namespaces.empty?
end
when Nokogiri::XML::Node::TEXT_NODE, Nokogiri::XML::Node::CDATA_SECTION_NODE
return CONTINUE
end
node.remove
STOP
end
end
#
# === scrub!(:nofollow)
#
# +:nofollow+ adds a rel="nofollow" attribute to all links
#
# link_farmers_markup = "ohai! I like your blog post"
# Loofah.fragment(link_farmers_markup).scrub!(:nofollow)
# => "ohai! I like your blog post"
#
class NoFollow < Scrubber
def initialize
@direction = :top_down
end
def scrub(node)
return CONTINUE unless (node.type == Nokogiri::XML::Node::ELEMENT_NODE) && (node.name == 'a')
append_attribute(node, 'rel', 'nofollow')
return STOP
end
end
#
# === scrub!(:noopener)
#
# +:noopener+ adds a rel="noopener" attribute to all links
#
# link_farmers_markup = "ohai! I like your blog post"
# Loofah.fragment(link_farmers_markup).scrub!(:noopener)
# => "ohai! I like your blog post"
#
class NoOpener < Scrubber
def initialize
@direction = :top_down
end
def scrub(node)
return CONTINUE unless (node.type == Nokogiri::XML::Node::ELEMENT_NODE) && (node.name == 'a')
append_attribute(node, 'rel', 'noopener')
return STOP
end
end
# This class probably isn't useful publicly, but is used for #to_text's current implemention
class NewlineBlockElements < Scrubber # :nodoc:
def initialize
@direction = :bottom_up
end
def scrub(node)
return CONTINUE unless Loofah::Elements::BLOCK_LEVEL.include?(node.name)
node.add_next_sibling Nokogiri::XML::Text.new("\n#{node.content}\n", node.document)
node.remove
end
end
#
# === scrub!(:unprintable)
#
# +:unprintable+ removes unprintable Unicode characters.
#
# markup = "Some text with an unprintable character at the end\u2028
"
# Loofah.fragment(markup).scrub!(:unprintable)
# => "Some text with an unprintable character at the end
"
#
# You may not be able to see the unprintable character in the above example, but there is a
# U+2028 character right before the closing tag. These characters can cause issues if
# the content is ever parsed by JavaScript - more information here:
#
# http://timelessrepo.com/json-isnt-a-javascript-subset
#
class Unprintable < Scrubber
def initialize
@direction = :top_down
end
def scrub(node)
if node.type == Nokogiri::XML::Node::TEXT_NODE || node.type == Nokogiri::XML::Node::CDATA_SECTION_NODE
node.content = node.content.gsub(/\u2028|\u2029/, '')
end
CONTINUE
end
end
#
# A hash that maps a symbol (like +:prune+) to the appropriate Scrubber (Loofah::Scrubbers::Prune).
#
MAP = {
:escape => Escape,
:prune => Prune,
:whitewash => Whitewash,
:strip => Strip,
:nofollow => NoFollow,
:noopener => NoOpener,
:newline_block_elements => NewlineBlockElements,
:unprintable => Unprintable
}
#
# Returns an array of symbols representing the built-in scrubbers
#
def self.scrubber_symbols
MAP.keys
end
end
end