rails-html-sanitizer-1.6.2/0000755000004100000410000000000014740465306015643 5ustar www-datawww-datarails-html-sanitizer-1.6.2/rails-html-sanitizer.gemspec0000644000004100000410000000500714740465306023274 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: rails-html-sanitizer 1.6.2 ruby lib Gem::Specification.new do |s| s.name = "rails-html-sanitizer".freeze s.version = "1.6.2" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/rails/rails-html-sanitizer/issues", "changelog_uri" => "https://github.com/rails/rails-html-sanitizer/blob/v1.6.2/CHANGELOG.md", "documentation_uri" => "https://www.rubydoc.info/gems/rails-html-sanitizer/1.6.2", "source_code_uri" => "https://github.com/rails/rails-html-sanitizer/tree/v1.6.2" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Rafael Mendon\u00E7a Fran\u00E7a".freeze, "Kasper Timm Hansen".freeze, "Mike Dalessio".freeze] s.date = "2024-12-12" s.description = "HTML sanitization for Rails applications".freeze s.email = ["rafaelmfranca@gmail.com".freeze, "kaspth@gmail.com".freeze, "mike.dalessio@gmail.com".freeze] s.files = ["CHANGELOG.md".freeze, "MIT-LICENSE".freeze, "README.md".freeze, "lib/rails-html-sanitizer.rb".freeze, "lib/rails/html/sanitizer.rb".freeze, "lib/rails/html/sanitizer/version.rb".freeze, "lib/rails/html/scrubbers.rb".freeze, "test/rails_api_test.rb".freeze, "test/sanitizer_test.rb".freeze, "test/scrubbers_test.rb".freeze] s.homepage = "https://github.com/rails/rails-html-sanitizer".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.7.0".freeze) s.rubygems_version = "3.3.15".freeze s.summary = "This gem is responsible to sanitize HTML fragments in Rails applications.".freeze s.test_files = ["test/rails_api_test.rb".freeze, "test/sanitizer_test.rb".freeze, "test/scrubbers_test.rb".freeze] if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_runtime_dependency(%q.freeze, ["~> 2.21"]) s.add_runtime_dependency(%q.freeze, [">= 1.15.7", "!= 1.16.0", "!= 1.16.0.rc1", "!= 1.16.1", "!= 1.16.2", "!= 1.16.3", "!= 1.16.4", "!= 1.16.5", "!= 1.16.6", "!= 1.16.7"]) else s.add_dependency(%q.freeze, ["~> 2.21"]) s.add_dependency(%q.freeze, [">= 1.15.7", "!= 1.16.0", "!= 1.16.0.rc1", "!= 1.16.1", "!= 1.16.2", "!= 1.16.3", "!= 1.16.4", "!= 1.16.5", "!= 1.16.6", "!= 1.16.7"]) end end rails-html-sanitizer-1.6.2/lib/0000755000004100000410000000000014740465306016411 5ustar www-datawww-datarails-html-sanitizer-1.6.2/lib/rails-html-sanitizer.rb0000644000004100000410000000356514740465306023031 0ustar www-datawww-data# frozen_string_literal: true require_relative "rails/html/sanitizer/version" require "loofah" require_relative "rails/html/scrubbers" require_relative "rails/html/sanitizer" module Rails Html = HTML # :nodoc: end module ActionView module Helpers module SanitizeHelper module ClassMethods # Replaces the allowed tags for the +sanitize+ helper. # # class Application < Rails::Application # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' # end # def sanitized_allowed_tags=(tags) sanitizer_vendor.safe_list_sanitizer.allowed_tags = tags end # Replaces the allowed HTML attributes for the +sanitize+ helper. # # class Application < Rails::Application # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc'] # end # def sanitized_allowed_attributes=(attributes) sanitizer_vendor.safe_list_sanitizer.allowed_attributes = attributes end [:protocol_separator, :uri_attributes, :bad_tags, :allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties, :allowed_protocols].each do |meth| meth_name = "sanitized_#{meth}" define_method(meth_name) { deprecate_option(meth_name) } define_method("#{meth_name}=") { |_| deprecate_option("#{meth_name}=") } end private def deprecate_option(name) ActiveSupport::Deprecation.warn "The #{name} option is deprecated " \ "and has no effect. Until Rails 5 the old behavior can still be " \ "installed. To do this add the `rails-deprecated-sanitizer` to " \ "your Gemfile. Consult the Rails 4.2 upgrade guide for more information." end end end end end rails-html-sanitizer-1.6.2/lib/rails/0000755000004100000410000000000014740465306017523 5ustar www-datawww-datarails-html-sanitizer-1.6.2/lib/rails/html/0000755000004100000410000000000014740465306020467 5ustar www-datawww-datarails-html-sanitizer-1.6.2/lib/rails/html/sanitizer.rb0000644000004100000410000003500014740465306023022 0ustar www-datawww-data# frozen_string_literal: true module Rails module HTML class Sanitizer class << self def html5_support? return @html5_support if defined?(@html5_support) @html5_support = Loofah.respond_to?(:html5_support?) && Loofah.html5_support? end def best_supported_vendor html5_support? ? Rails::HTML5::Sanitizer : Rails::HTML4::Sanitizer end end def sanitize(html, options = {}) raise NotImplementedError, "subclasses must implement sanitize method." end private def remove_xpaths(node, xpaths) node.xpath(*xpaths).remove node end def properly_encode(fragment, options) fragment.xml? ? fragment.to_xml(options) : fragment.to_html(options) end end module Concern module ComposedSanitize def sanitize(html, options = {}) return unless html return html if html.empty? serialize(scrub(parse_fragment(html), options)) end end module Parser module HTML4 def parse_fragment(html) Loofah.html4_fragment(html) end end module HTML5 def parse_fragment(html) Loofah.html5_fragment(html) end end if Rails::HTML::Sanitizer.html5_support? end module Scrubber module Full def scrub(fragment, options = {}) fragment.scrub!(TextOnlyScrubber.new) end end module Link def initialize super @link_scrubber = TargetScrubber.new @link_scrubber.tags = %w(a) @link_scrubber.attributes = %w(href) end def scrub(fragment, options = {}) fragment.scrub!(@link_scrubber) end end module SafeList # The default safe list for tags DEFAULT_ALLOWED_TAGS = Set.new([ "a", "abbr", "acronym", "address", "b", "big", "blockquote", "br", "cite", "code", "dd", "del", "dfn", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "ins", "kbd", "li", "mark", "ol", "p", "pre", "samp", "small", "span", "strong", "sub", "sup", "time", "tt", "ul", "var", ]).freeze # The default safe list for attributes DEFAULT_ALLOWED_ATTRIBUTES = Set.new([ "abbr", "alt", "cite", "class", "datetime", "height", "href", "lang", "name", "src", "title", "width", "xml:lang", ]).freeze def self.included(klass) class << klass attr_accessor :allowed_tags attr_accessor :allowed_attributes end klass.allowed_tags = DEFAULT_ALLOWED_TAGS.dup klass.allowed_attributes = DEFAULT_ALLOWED_ATTRIBUTES.dup end def initialize(prune: false) @permit_scrubber = PermitScrubber.new(prune: prune) end def scrub(fragment, options = {}) if scrubber = options[:scrubber] # No duck typing, Loofah ensures subclass of Loofah::Scrubber fragment.scrub!(scrubber) elsif allowed_tags(options) || allowed_attributes(options) @permit_scrubber.tags = allowed_tags(options) @permit_scrubber.attributes = allowed_attributes(options) fragment.scrub!(@permit_scrubber) else fragment.scrub!(:strip) end end def sanitize_css(style_string) Loofah::HTML5::Scrub.scrub_css(style_string) end private def allowed_tags(options) options[:tags] || self.class.allowed_tags end def allowed_attributes(options) options[:attributes] || self.class.allowed_attributes end end end module Serializer module UTF8Encode def serialize(fragment) properly_encode(fragment, encoding: "UTF-8") end end end end end module HTML4 module Sanitizer module VendorMethods def full_sanitizer Rails::HTML4::FullSanitizer end def link_sanitizer Rails::HTML4::LinkSanitizer end def safe_list_sanitizer Rails::HTML4::SafeListSanitizer end def white_list_sanitizer # :nodoc: safe_list_sanitizer end end extend VendorMethods end # == Rails::HTML4::FullSanitizer # # Removes all tags from HTML4 but strips out scripts, forms and comments. # # full_sanitizer = Rails::HTML4::FullSanitizer.new # full_sanitizer.sanitize("Bold no more! See more here...") # # => "Bold no more! See more here..." # class FullSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML4 include HTML::Concern::Scrubber::Full include HTML::Concern::Serializer::UTF8Encode end # == Rails::HTML4::LinkSanitizer # # Removes +a+ tags and +href+ attributes from HTML4 leaving only the link text. # # link_sanitizer = Rails::HTML4::LinkSanitizer.new # link_sanitizer.sanitize('Only the link text will be kept.') # # => "Only the link text will be kept." # class LinkSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML4 include HTML::Concern::Scrubber::Link include HTML::Concern::Serializer::UTF8Encode end # == Rails::HTML4::SafeListSanitizer # # Sanitizes HTML4 and CSS from an extensive safe list. # # === Whitespace # # We can't make any guarantees about whitespace being kept or stripped. Loofah uses Nokogiri, # which wraps either a C or Java parser for the respective Ruby implementation. Those two # parsers determine how whitespace is ultimately handled. # # When the stripped markup will be rendered the users browser won't take whitespace into account # anyway. It might be better to suggest your users wrap their whitespace sensitive content in # pre tags or that you do so automatically. # # === Options # # Sanitizes both html and css via the safe lists found in # Rails::HTML::Concern::Scrubber::SafeList # # SafeListSanitizer also accepts options to configure the safe list used when sanitizing html. # There's a class level option: # # Rails::HTML4::SafeListSanitizer.allowed_tags = %w(table tr td) # Rails::HTML4::SafeListSanitizer.allowed_attributes = %w(id class style) # # Tags and attributes can also be passed to +sanitize+. Passed options take precedence over the # class level options. # # === Examples # # safe_list_sanitizer = Rails::HTML4::SafeListSanitizer.new # # # default: sanitize via a extensive safe list of allowed elements # safe_list_sanitizer.sanitize(@article.body) # # # sanitize via the supplied tags and attributes # safe_list_sanitizer.sanitize( # @article.body, # tags: %w(table tr td), # attributes: %w(id class style), # ) # # # sanitize via a custom Loofah scrubber # safe_list_sanitizer.sanitize(@article.body, scrubber: ArticleScrubber.new) # # # prune nodes from the tree instead of stripping tags and leaving inner content # safe_list_sanitizer = Rails::HTML4::SafeListSanitizer.new(prune: true) # # # the sanitizer can also sanitize CSS # safe_list_sanitizer.sanitize_css('background-color: #000;') # class SafeListSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML4 include HTML::Concern::Scrubber::SafeList include HTML::Concern::Serializer::UTF8Encode end end module HTML5 class Sanitizer class << self def full_sanitizer Rails::HTML5::FullSanitizer end def link_sanitizer Rails::HTML5::LinkSanitizer end def safe_list_sanitizer Rails::HTML5::SafeListSanitizer end def white_list_sanitizer # :nodoc: safe_list_sanitizer end end end # == Rails::HTML5::FullSanitizer # # Removes all tags from HTML5 but strips out scripts, forms and comments. # # full_sanitizer = Rails::HTML5::FullSanitizer.new # full_sanitizer.sanitize("Bold no more! See more here...") # # => "Bold no more! See more here..." # class FullSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML5 include HTML::Concern::Scrubber::Full include HTML::Concern::Serializer::UTF8Encode end # == Rails::HTML5::LinkSanitizer # # Removes +a+ tags and +href+ attributes from HTML5 leaving only the link text. # # link_sanitizer = Rails::HTML5::LinkSanitizer.new # link_sanitizer.sanitize('Only the link text will be kept.') # # => "Only the link text will be kept." # class LinkSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML5 include HTML::Concern::Scrubber::Link include HTML::Concern::Serializer::UTF8Encode end # == Rails::HTML5::SafeListSanitizer # # Sanitizes HTML5 and CSS from an extensive safe list. # # === Whitespace # # We can't make any guarantees about whitespace being kept or stripped. Loofah uses Nokogiri, # which wraps either a C or Java parser for the respective Ruby implementation. Those two # parsers determine how whitespace is ultimately handled. # # When the stripped markup will be rendered the users browser won't take whitespace into account # anyway. It might be better to suggest your users wrap their whitespace sensitive content in # pre tags or that you do so automatically. # # === Options # # Sanitizes both html and css via the safe lists found in # Rails::HTML::Concern::Scrubber::SafeList # # SafeListSanitizer also accepts options to configure the safe list used when sanitizing html. # There's a class level option: # # Rails::HTML5::SafeListSanitizer.allowed_tags = %w(table tr td) # Rails::HTML5::SafeListSanitizer.allowed_attributes = %w(id class style) # # Tags and attributes can also be passed to +sanitize+. Passed options take precedence over the # class level options. # # === Examples # # safe_list_sanitizer = Rails::HTML5::SafeListSanitizer.new # # # default: sanitize via a extensive safe list of allowed elements # safe_list_sanitizer.sanitize(@article.body) # # # sanitize via the supplied tags and attributes # safe_list_sanitizer.sanitize( # @article.body, # tags: %w(table tr td), # attributes: %w(id class style), # ) # # # sanitize via a custom Loofah scrubber # safe_list_sanitizer.sanitize(@article.body, scrubber: ArticleScrubber.new) # # # prune nodes from the tree instead of stripping tags and leaving inner content # safe_list_sanitizer = Rails::HTML5::SafeListSanitizer.new(prune: true) # # # the sanitizer can also sanitize CSS # safe_list_sanitizer.sanitize_css('background-color: #000;') # class SafeListSanitizer < Rails::HTML::Sanitizer include HTML::Concern::ComposedSanitize include HTML::Concern::Parser::HTML5 include HTML::Concern::Scrubber::SafeList include HTML::Concern::Serializer::UTF8Encode end end if Rails::HTML::Sanitizer.html5_support? module HTML Sanitizer.extend(HTML4::Sanitizer::VendorMethods) # :nodoc: FullSanitizer = HTML4::FullSanitizer # :nodoc: LinkSanitizer = HTML4::LinkSanitizer # :nodoc: SafeListSanitizer = HTML4::SafeListSanitizer # :nodoc: WhiteListSanitizer = SafeListSanitizer # :nodoc: end end rails-html-sanitizer-1.6.2/lib/rails/html/scrubbers.rb0000644000004100000410000001556514740465306023022 0ustar www-datawww-data# frozen_string_literal: true module Rails module HTML # === Rails::HTML::PermitScrubber # # +Rails::HTML::PermitScrubber+ allows you to permit only your own tags and/or attributes. # # +Rails::HTML::PermitScrubber+ can be subclassed to determine: # - When a node should be skipped via +skip_node?+. # - When a node is allowed via +allowed_node?+. # - When an attribute should be scrubbed via +scrub_attribute?+. # # Subclasses don't need to worry if tags or attributes are set or not. # If tags or attributes are not set, Loofah's behavior will be used. # If you override +allowed_node?+ and no tags are set, it will not be called. # Instead Loofahs behavior will be used. # Likewise for +scrub_attribute?+ and attributes respectively. # # Text and CDATA nodes are skipped by default. # Unallowed elements will be stripped, i.e. element is removed but its subtree kept. # Supplied tags and attributes should be Enumerables. # # +tags=+ # If set, elements excluded will be stripped. # If not, elements are stripped based on Loofahs +HTML5::Scrub.allowed_element?+. # # +attributes=+ # If set, attributes excluded will be removed. # If not, attributes are removed based on Loofahs +HTML5::Scrub.scrub_attributes+. # # class CommentScrubber < Rails::HTML::PermitScrubber # def initialize # super # self.tags = %w(form script comment blockquote) # end # # def skip_node?(node) # node.text? # end # # def scrub_attribute?(name) # name == "style" # end # end # # See the documentation for +Nokogiri::XML::Node+ to understand what's possible # with nodes: https://nokogiri.org/rdoc/Nokogiri/XML/Node.html class PermitScrubber < Loofah::Scrubber attr_reader :tags, :attributes, :prune def initialize(prune: false) @prune = prune @direction = @prune ? :top_down : :bottom_up @tags, @attributes = nil, nil end def tags=(tags) @tags = validate!(tags.dup, :tags) end def attributes=(attributes) @attributes = validate!(attributes.dup, :attributes) end def scrub(node) if Loofah::HTML5::Scrub.cdata_needs_escaping?(node) replacement = Loofah::HTML5::Scrub.cdata_escape(node) node.replace(replacement) return CONTINUE end return CONTINUE if skip_node?(node) unless (node.element? || node.comment?) && keep_node?(node) return STOP unless scrub_node(node) == CONTINUE end scrub_attributes(node) CONTINUE end protected def allowed_node?(node) @tags.include?(node.name) end def skip_node?(node) node.text? end def scrub_attribute?(name) !@attributes.include?(name) end def keep_node?(node) if @tags allowed_node?(node) else Loofah::HTML5::Scrub.allowed_element?(node.name) end end def scrub_node(node) # If a node has a namespace, then it's a tag in either a `math` or `svg` foreign context, # and we should always prune it to avoid namespace confusion and mutation XSS vectors. unless prune || node.namespace node.before(node.children) end node.remove end def scrub_attributes(node) if @attributes node.attribute_nodes.each do |attr| if scrub_attribute?(attr.name) attr.remove else scrub_attribute(node, attr) end end scrub_css_attribute(node) else Loofah::HTML5::Scrub.scrub_attributes(node) end end def scrub_css_attribute(node) if Loofah::HTML5::Scrub.respond_to?(:scrub_css_attribute) Loofah::HTML5::Scrub.scrub_css_attribute(node) else style = node.attributes["style"] style.value = Loofah::HTML5::Scrub.scrub_css(style.value) if style end end def validate!(var, name) if var && !var.is_a?(Enumerable) raise ArgumentError, "You should pass :#{name} as an Enumerable" end if var && name == :tags if var.include?("mglyph") warn("WARNING: 'mglyph' tags cannot be allowed by the PermitScrubber and will be scrubbed") var.delete("mglyph") end if var.include?("malignmark") warn("WARNING: 'malignmark' tags cannot be allowed by the PermitScrubber and will be scrubbed") var.delete("malignmark") end if var.include?("noscript") warn("WARNING: 'noscript' tags cannot be allowed by the PermitScrubber and will be scrubbed") var.delete("noscript") end end var end def scrub_attribute(node, attr_node) attr_name = if attr_node.namespace "#{attr_node.namespace.prefix}:#{attr_node.node_name}" else attr_node.node_name end return if Loofah::HTML5::SafeList::ATTR_VAL_IS_URI.include?(attr_name) && Loofah::HTML5::Scrub.scrub_uri_attribute(attr_node) if Loofah::HTML5::SafeList::SVG_ATTR_VAL_ALLOWS_REF.include?(attr_name) Loofah::HTML5::Scrub.scrub_attribute_that_allows_local_ref(attr_node) end if Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) && attr_name == "xlink:href" && attr_node.value =~ /^\s*[^#\s].*/m attr_node.remove end node.remove_attribute(attr_node.name) if attr_name == "src" && attr_node.value !~ /[^[:space:]]/ Loofah::HTML5::Scrub.force_correct_attribute_escaping! node end end # === Rails::HTML::TargetScrubber # # Where +Rails::HTML::PermitScrubber+ picks out tags and attributes to permit in # sanitization, +Rails::HTML::TargetScrubber+ targets them for removal. # # +tags=+ # If set, elements included will be stripped. # # +attributes=+ # If set, attributes included will be removed. class TargetScrubber < PermitScrubber def allowed_node?(node) !super end def scrub_attribute?(name) !super end end # === Rails::HTML::TextOnlyScrubber # # +Rails::HTML::TextOnlyScrubber+ allows you to permit text nodes. # # Unallowed elements will be stripped, i.e. element is removed but its subtree kept. class TextOnlyScrubber < Loofah::Scrubber def initialize @direction = :bottom_up end def scrub(node) if node.text? CONTINUE else node.before node.children node.remove end end end end end rails-html-sanitizer-1.6.2/lib/rails/html/sanitizer/0000755000004100000410000000000014740465306022477 5ustar www-datawww-datarails-html-sanitizer-1.6.2/lib/rails/html/sanitizer/version.rb0000644000004100000410000000017014740465306024507 0ustar www-datawww-data# frozen_string_literal: true module Rails module HTML class Sanitizer VERSION = "1.6.2" end end end rails-html-sanitizer-1.6.2/test/0000755000004100000410000000000014740465306016622 5ustar www-datawww-datarails-html-sanitizer-1.6.2/test/rails_api_test.rb0000644000004100000410000000663314740465306022161 0ustar www-datawww-data# frozen_string_literal: true require "minitest/autorun" require "rails-html-sanitizer" class RailsApiTest < Minitest::Test def test_html_module_name_alias assert_equal(Rails::Html, Rails::HTML) assert_equal("Rails::HTML", Rails::Html.name) assert_equal("Rails::HTML", Rails::HTML.name) end def test_html_scrubber_class_names assert(Rails::Html::PermitScrubber) assert(Rails::Html::TargetScrubber) assert(Rails::Html::TextOnlyScrubber) assert(Rails::Html::Sanitizer) end def test_best_supported_vendor_when_html5_is_not_supported_returns_html4 Rails::HTML::Sanitizer.stub(:html5_support?, false) do assert_equal(Rails::HTML4::Sanitizer, Rails::HTML::Sanitizer.best_supported_vendor) end end def test_best_supported_vendor_when_html5_is_supported_returns_html5 skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support? Rails::HTML::Sanitizer.stub(:html5_support?, true) do assert_equal(Rails::HTML5::Sanitizer, Rails::HTML::Sanitizer.best_supported_vendor) end end def test_html4_sanitizer_alias_full assert_equal(Rails::HTML4::FullSanitizer, Rails::HTML::FullSanitizer) assert_equal("Rails::HTML4::FullSanitizer", Rails::HTML::FullSanitizer.name) end def test_html4_sanitizer_alias_link assert_equal(Rails::HTML4::LinkSanitizer, Rails::HTML::LinkSanitizer) assert_equal("Rails::HTML4::LinkSanitizer", Rails::HTML::LinkSanitizer.name) end def test_html4_sanitizer_alias_safe_list assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML::SafeListSanitizer) assert_equal("Rails::HTML4::SafeListSanitizer", Rails::HTML::SafeListSanitizer.name) end def test_html4_full_sanitizer assert_equal(Rails::HTML4::FullSanitizer, Rails::HTML::Sanitizer.full_sanitizer) assert_equal(Rails::HTML4::FullSanitizer, Rails::HTML4::Sanitizer.full_sanitizer) end def test_html4_link_sanitizer assert_equal(Rails::HTML4::LinkSanitizer, Rails::HTML::Sanitizer.link_sanitizer) assert_equal(Rails::HTML4::LinkSanitizer, Rails::HTML4::Sanitizer.link_sanitizer) end def test_html4_safe_list_sanitizer assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML::Sanitizer.safe_list_sanitizer) assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML4::Sanitizer.safe_list_sanitizer) end def test_html4_white_list_sanitizer assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML::Sanitizer.white_list_sanitizer) assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML4::Sanitizer.white_list_sanitizer) end def test_html5_full_sanitizer skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support? assert_equal(Rails::HTML5::FullSanitizer, Rails::HTML5::Sanitizer.full_sanitizer) end def test_html5_link_sanitizer skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support? assert_equal(Rails::HTML5::LinkSanitizer, Rails::HTML5::Sanitizer.link_sanitizer) end def test_html5_safe_list_sanitizer skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support? assert_equal(Rails::HTML5::SafeListSanitizer, Rails::HTML5::Sanitizer.safe_list_sanitizer) end def test_html5_white_list_sanitizer skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support? assert_equal(Rails::HTML5::SafeListSanitizer, Rails::HTML5::Sanitizer.white_list_sanitizer) end end rails-html-sanitizer-1.6.2/test/sanitizer_test.rb0000644000004100000410000013301514740465306022221 0ustar www-datawww-data# frozen_string_literal: true require "minitest/autorun" require "rails-html-sanitizer" puts "nokogiri version info: #{Nokogiri::VERSION_INFO}" puts "html5 support: #{Rails::HTML::Sanitizer.html5_support?}" # # NOTE that many of these tests contain multiple acceptable results. # # In some cases, this is because of how the HTML4 parser's recovery behavior changed in libxml2 # 2.9.14 and 2.10.0. For more details, see: # # - https://github.com/sparklemotion/nokogiri/releases/tag/v1.13.5 # - https://gitlab.gnome.org/GNOME/libxml2/-/issues/380 # # In other cases, multiple acceptable results are provided because Nokogiri's vendored libxml2 is # patched to entity-escape server-side includes (aks "SSI", aka ``). # # In many other cases, it's because the parser used by Nokogiri on JRuby (xerces+nekohtml) parses # slightly differently than libxml2 in edge cases. # module SanitizerTests def self.loofah_html5_support? Loofah.respond_to?(:html5_support?) && Loofah.html5_support? end class BaseSanitizerTest < Minitest::Test class XpathRemovalTestSanitizer < Rails::HTML::Sanitizer def sanitize(html, options = {}) fragment = Loofah.fragment(html) remove_xpaths(fragment, options[:xpaths]).to_s end end def test_sanitizer_sanitize_raises_not_implemented_error assert_raises NotImplementedError do Rails::HTML::Sanitizer.new.sanitize("asdf") end end def test_remove_xpaths_removes_an_xpath html = %(

hello

) assert_equal %(

hello

), xpath_sanitize(html, xpaths: %w(.//script)) end def test_remove_xpaths_removes_all_occurrences_of_xpath html = %(

hello

) assert_equal %(

hello

), xpath_sanitize(html, xpaths: %w(.//script)) end def test_remove_xpaths_called_with_faulty_xpath assert_raises Nokogiri::XML::XPath::SyntaxError do xpath_sanitize("

hello

", xpaths: %w(..faulty_xpath)) end end def test_remove_xpaths_called_with_xpath_string assert_equal "", xpath_sanitize("", xpaths: ".//a") end def test_remove_xpaths_called_with_enumerable_xpaths assert_equal "", xpath_sanitize("", xpaths: %w(.//a .//span)) end protected def xpath_sanitize(input, options = {}) XpathRemovalTestSanitizer.new.sanitize(input, options) end end module ModuleUnderTest def module_under_test self.class.instance_variable_get(:@module_under_test) end end module FullSanitizerTest include ModuleUnderTest def test_strip_tags_with_quote input = '<" hi' result = full_sanitize(input) acceptable_results = [ # libxml2 >= 2.9.14 and xerces+neko %{<" hi}, # other libxml2 %{ hi}, ] assert_includes(acceptable_results, result) end def test_strip_invalid_html assert_equal "<<", full_sanitize("<<This is a test.

\n\n\n\n

It no longer contains any HTML.

\n} assert_equal expected, full_sanitize(input) end def test_remove_unclosed_tags input = "This is <-- not\n a comment here." result = full_sanitize(input) acceptable_results = [ # libxml2 >= 2.9.14 and xerces+neko %{This is <-- not\n a comment here.}, # other libxml2 %{This is }, ] assert_includes(acceptable_results, result) end def test_strip_cdata input = "This has a ]]> here." result = full_sanitize(input) acceptable_results = [ # libxml2 = 2.9.14 %{This has a <![CDATA[]]> here.}, # other libxml2 %{This has a ]]> here.}, # xerces+neko %{This has a here.}, ] assert_includes(acceptable_results, result) end def test_strip_blank_string assert_nil full_sanitize(nil) assert_equal "", full_sanitize("") assert_equal " ", full_sanitize(" ") end def test_strip_tags_with_plaintext assert_equal "Don't touch me", full_sanitize("Don't touch me") end def test_strip_tags_with_tags assert_equal "This is a test.", full_sanitize("

This is a test.

") end def test_escape_tags_with_many_open_quotes assert_equal "<<", full_sanitize("<<") end def test_strip_tags_with_sentence assert_equal "This is a test.", full_sanitize("This is a test.") end def test_strip_tags_with_comment assert_equal "This has a here.", full_sanitize("This has a here.") end def test_strip_tags_with_frozen_string assert_equal "Frozen string with no tags", full_sanitize("Frozen string with no tags") end def test_full_sanitize_respect_html_escaping_of_the_given_string assert_equal 'test\r\nstring', full_sanitize('test\r\nstring') assert_equal "&", full_sanitize("&") assert_equal "&", full_sanitize("&") assert_equal "&amp;", full_sanitize("&amp;") assert_equal "omg <script>BOM</script>", full_sanitize("omg <script>BOM</script>") end def test_sanitize_ascii_8bit_string full_sanitize("".encode("ASCII-8BIT")).tap do |sanitized| assert_equal "hello", sanitized assert_equal Encoding::UTF_8, sanitized.encoding end end protected def full_sanitize(input, options = {}) module_under_test::FullSanitizer.new.sanitize(input, options) end end class HTML4FullSanitizerTest < Minitest::Test @module_under_test = Rails::HTML4 include FullSanitizerTest end class HTML5FullSanitizerTest < Minitest::Test @module_under_test = Rails::HTML5 include FullSanitizerTest end if loofah_html5_support? module LinkSanitizerTest include ModuleUnderTest def test_strip_links_with_tags_in_tags expected = "<a href='hello'>all day long</a>" input = "<a href='hello'>all day long</a>" assert_equal expected, link_sanitize(input) end def test_strip_links_with_unclosed_tags assert_equal "", link_sanitize("on my mind\nall day long") end def test_strip_links_leaves_nonlink_tags assert_equal "My mind\nall day long", link_sanitize("My mind\nall day long") end def test_strip_links_with_links assert_equal "0wn3d", link_sanitize("0wn3d") end def test_strip_links_with_linkception assert_equal "Magic", link_sanitize("Magic") end def test_sanitize_ascii_8bit_string link_sanitize("".encode("ASCII-8BIT")).tap do |sanitized| assert_equal "
hello
", sanitized assert_equal Encoding::UTF_8, sanitized.encoding end end protected def link_sanitize(input, options = {}) module_under_test::LinkSanitizer.new.sanitize(input, options) end end class HTML4LinkSanitizerTest < Minitest::Test @module_under_test = Rails::HTML4 include LinkSanitizerTest end class HTML5LinkSanitizerTest < Minitest::Test @module_under_test = Rails::HTML5 include LinkSanitizerTest end if loofah_html5_support? module SafeListSanitizerTest include ModuleUnderTest def test_sanitize_nested_script assert_equal '<script>alert("XSS");</script>', safe_list_sanitize('alert("XSS");/', tags: %w(em)) end def test_sanitize_nested_script_in_style input = 'alert("XSS");/' result = safe_list_sanitize(input, tags: %w(em)) acceptable_results = [ # libxml2 %{<script>alert("XSS");</script>}, # xerces+neko. unavoidable double-escaping, see loofah/docs/2022-10-decision-on-cdata-nodes.md %{&lt;script&gt;alert(\"XSS\");&lt;&lt;/style&gt;/script&gt;}, ] assert_includes(acceptable_results, result) end def test_strip_unclosed_cdata input = "This has an unclosed ]] here..." result = safe_list_sanitize(input) acceptable_results = [ # libxml2 = 2.9.14 %{This has an unclosed <![CDATA[]] here...}, # other libxml2 %{This has an unclosed ]] here...}, # xerces+neko %{This has an unclosed } ] assert_includes(acceptable_results, result) end def test_sanitize_form assert_sanitized "
", "" end def test_sanitize_plaintext # note that the `plaintext` tag has been deprecated since HTML 2 # https://developer.mozilla.org/en-US/docs/Web/HTML/Element/plaintext input = "<span>foo</span></plaintext>" result = safe_list_sanitize(input) acceptable_results = [ # libxml2 "<span>foo</span>", # xerces+nekohtml-unit "&lt;span&gt;foo&lt;/span&gt;&lt;/plaintext&gt;", # xerces+cyberneko "&lt;span&gt;foo&lt;/span&gt;" ] assert_includes(acceptable_results, result) end def test_sanitize_script assert_sanitized "a b c<script language=\"Javascript\">blah blah blah</script>d e f", "a b cblah blah blahd e f" end def test_sanitize_js_handlers raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>} assert_sanitized raw, %{onthis="do that" <a href="#" name="foo">hello</a>} end def test_sanitize_javascript_href raw = %{href="javascript:bang" <a href="javascript:bang" name="hello">foo</a>, <span href="javascript:bang">bar</span>} assert_sanitized raw, %{href="javascript:bang" <a name="hello">foo</a>, <span>bar</span>} end def test_sanitize_image_src raw = %{src="javascript:bang" <img src="javascript:bang" width="5">foo</img>, <span src="javascript:bang">bar</span>} assert_sanitized raw, %{src="javascript:bang" <img width="5">foo, <span>bar</span>} end def test_should_allow_anchors assert_sanitized %(<a href="foo" onclick="bar"><script>baz</script></a>), %(<a href=\"foo\">baz</a>) end def test_video_poster_sanitization scope_allowed_tags(%w(video)) do scope_allowed_attributes %w(src poster) do expected = if RUBY_PLATFORM == "java" # xerces+nekohtml alphabetizes the attributes! FML. %(<video poster="posterimage.jpg" src="videofile.ogg"></video>) else %(<video src="videofile.ogg" poster="posterimage.jpg"></video>) end assert_sanitized( %(<video src="videofile.ogg" autoplay poster="posterimage.jpg"></video>), expected, ) assert_sanitized( %(<video src="videofile.ogg" poster=javascript:alert(1)></video>), %(<video src="videofile.ogg"></video>), ) end end end # RFC 3986, sec 4.2 def test_allow_colons_in_path_component assert_sanitized "<a href=\"./this:that\">foo</a>" end %w(src width height alt).each do |img_attr| define_method "test_should_allow_image_#{img_attr}_attribute" do assert_sanitized %(<img #{img_attr}="foo" onclick="bar" />), %(<img #{img_attr}="foo">) end end def test_lang_and_xml_lang # https://html.spec.whatwg.org/multipage/dom.html#the-lang-and-xml:lang-attributes # # 3.2.6.2 The lang and xml:lang attributes # # ... Authors must not use the lang attribute in the XML namespace on HTML elements in HTML # documents. To ease migration to and from XML, authors may specify an attribute in no namespace # with no prefix and with the literal localname "xml:lang" on HTML elements in HTML documents, # but such attributes must only be specified if a lang attribute in no namespace is also # specified, and both attributes must have the same value when compared in an ASCII # case-insensitive manner. input = expected = "<div lang=\"en\" xml:lang=\"en\">foo</div>" assert_sanitized(input, expected) end def test_should_handle_non_html assert_sanitized "abc" end def test_should_handle_blank_text assert_nil(safe_list_sanitize(nil)) assert_equal("", safe_list_sanitize("")) assert_equal(" ", safe_list_sanitize(" ")) end def test_setting_allowed_tags_affects_sanitization scope_allowed_tags %w(u) do |sanitizer| assert_equal "<u></u>", sanitizer.sanitize("<a><u></u></a>") end end def test_setting_allowed_attributes_affects_sanitization scope_allowed_attributes %w(foo) do |sanitizer| input = '<a foo="hello" bar="world"></a>' assert_equal '<a foo="hello"></a>', sanitizer.sanitize(input) end end def test_custom_tags_overrides_allowed_tags scope_allowed_tags %(u) do |sanitizer| input = "<a><u></u></a>" assert_equal "<a></a>", sanitizer.sanitize(input, tags: %w(a)) end end def test_custom_attributes_overrides_allowed_attributes scope_allowed_attributes %(foo) do |sanitizer| input = '<a foo="hello" bar="world"></a>' assert_equal '<a bar="world"></a>', sanitizer.sanitize(input, attributes: %w(bar)) end end def test_should_allow_prune sanitizer = module_under_test::SafeListSanitizer.new(prune: true) text = "<u>leave me <b>now</b></u>" assert_equal "<u>leave me </u>", sanitizer.sanitize(text, tags: %w(u)) end def test_should_allow_custom_tags text = "<u>foo</u>" assert_equal text, safe_list_sanitize(text, tags: %w(u)) end def test_should_allow_only_custom_tags text = "<u>foo</u> with <i>bar</i>" assert_equal "<u>foo</u> with bar", safe_list_sanitize(text, tags: %w(u)) end def test_should_allow_custom_tags_with_attributes text = %(<blockquote cite="http://example.com/">foo</blockquote>) assert_equal text, safe_list_sanitize(text) end def test_should_allow_custom_tags_with_custom_attributes text = %(<blockquote foo="bar">Lorem ipsum</blockquote>) assert_equal text, safe_list_sanitize(text, attributes: ["foo"]) end def test_scrub_style_if_style_attribute_option_is_passed input = '<p style="color: #000; background-image: url(http://www.ragingplatypus.com/i/cam-full.jpg);"></p>' actual = safe_list_sanitize(input, attributes: %w(style)) assert_includes(['<p style="color: #000;"></p>', '<p style="color:#000;"></p>'], actual) end def test_should_raise_argument_error_if_tags_is_not_enumerable assert_raises ArgumentError do safe_list_sanitize("<a>some html</a>", tags: "foo") end end def test_should_raise_argument_error_if_attributes_is_not_enumerable assert_raises ArgumentError do safe_list_sanitize("<a>some html</a>", attributes: "foo") end end def test_should_not_accept_non_loofah_inheriting_scrubber scrubber = Object.new def scrubber.scrub(node); node.name = "h1"; end assert_raises Loofah::ScrubberNotFound do safe_list_sanitize("<a>some html</a>", scrubber: scrubber) end end def test_should_accept_loofah_inheriting_scrubber scrubber = Loofah::Scrubber.new def scrubber.scrub(node); node.replace("<h1>#{node.inner_html}</h1>"); end html = "<script>hello!</script>" assert_equal "<h1>hello!</h1>", safe_list_sanitize(html, scrubber: scrubber) end def test_should_accept_loofah_scrubber_that_wraps_a_block scrubber = Loofah::Scrubber.new { |node| node.replace("<h1>#{node.inner_html}</h1>") } html = "<script>hello!</script>" assert_equal "<h1>hello!</h1>", safe_list_sanitize(html, scrubber: scrubber) end def test_custom_scrubber_takes_precedence_over_other_options scrubber = Loofah::Scrubber.new { |node| node.replace("<h1>#{node.inner_html}</h1>") } html = "<script>hello!</script>" assert_equal "<h1>hello!</h1>", safe_list_sanitize(html, scrubber: scrubber, tags: ["foo"]) end def test_should_strip_src_attribute_in_img_with_bad_protocols assert_sanitized %(<img src="javascript:bang" title="1">), %(<img title="1">) end def test_should_strip_href_attribute_in_a_with_bad_protocols assert_sanitized %(<a href="javascript:bang" title="1">boo</a>), %(<a title="1">boo</a>) end def test_should_block_script_tag assert_sanitized %(<SCRIPT\nSRC=http://ha.ckers.org/xss.js></SCRIPT>), "" end def test_should_not_fall_for_xss_image_hack_with_uppercase_tags assert_sanitized %(<IMG """><SCRIPT>alert("XSS")</SCRIPT>">), %(<img>alert("XSS")"&gt;) end [%(<IMG SRC="javascript:alert('XSS');">), %(<IMG SRC=javascript:alert('XSS')>), %(<IMG SRC=JaVaScRiPt:alert('XSS')>), %(<IMG SRC=javascript:alert(&quot;XSS&quot;)>), %(<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>), %(<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>), %(<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>), %(<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>), %(<IMG SRC="jav\tascript:alert('XSS');">), %(<IMG SRC="jav&#x09;ascript:alert('XSS');">), %(<IMG SRC="jav&#x0A;ascript:alert('XSS');">), %(<IMG SRC="jav&#x0D;ascript:alert('XSS');">), %(<IMG SRC=" &#14; javascript:alert('XSS');">), %(<IMG SRC="javascript&#x3a;alert('XSS');">), %(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each do |img_hack| define_method "test_should_not_fall_for_xss_image_hack_#{img_hack}" do assert_sanitized img_hack, "<img>" end end def test_should_sanitize_tag_broken_up_by_null input = %(<SCR\0IPT>alert(\"XSS\")</SCR\0IPT>) result = safe_list_sanitize(input) acceptable_results = [ # libxml2 "", # xerces+neko 'alert("XSS")', ] assert_includes(acceptable_results, result) end def test_should_sanitize_invalid_script_tag assert_sanitized %(<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>), "" end def test_should_sanitize_script_tag_with_multiple_open_brackets assert_sanitized %(<<SCRIPT>alert("XSS");//<</SCRIPT>), "&lt;alert(\"XSS\");//&lt;" end def test_should_sanitize_script_tag_with_multiple_open_brackets_2 input = %(<iframe src=http://ha.ckers.org/scriptlet.html\n<a) result = safe_list_sanitize(input) acceptable_results = [ # libxml2 "", # xerces+neko "&lt;a", ] assert_includes(acceptable_results, result) end def test_should_sanitize_unclosed_script assert_sanitized %(<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>), "" end def test_should_sanitize_half_open_scripts input = %(<IMG SRC="javascript:alert('XSS')") result = safe_list_sanitize(input) acceptable_results = [ # libxml2 "<img>", # libgumbo "", ] assert_includes(acceptable_results, result) end def test_should_not_fall_for_ridiculous_hack img_hack = %(<IMG\nSRC\n=\n"\nj\na\nv\na\ns\nc\nr\ni\np\nt\n:\na\nl\ne\nr\nt\n(\n'\nX\nS\nS\n'\n)\n"\n>) assert_sanitized img_hack, "<img>" end def test_should_sanitize_attributes input = %(<SPAN title="'><script>alert()</script>">blah</SPAN>) result = safe_list_sanitize(input) acceptable_results = [ # libxml2 %(<span title="'&gt;&lt;script&gt;alert()&lt;/script&gt;">blah</span>), # libgumbo # this looks scary, but it's fine. for a more detailed analysis check out: # https://github.com/discourse/discourse/pull/21522#issuecomment-1545697968 %(<span title="'><script>alert()</script>">blah</span>) ] assert_includes(acceptable_results, result) end def test_should_sanitize_invalid_tag_names assert_sanitized(%(a b c<script/XSS src="http://ha.ckers.org/xss.js"></script>d e f), "a b cd e f") end def test_should_sanitize_non_alpha_and_non_digit_characters_in_tags assert_sanitized('<a onclick!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>foo</a>', "<a>foo</a>") end def test_should_sanitize_invalid_tag_names_in_single_tags input = %(<img/src="http://ha.ckers.org/xss.js"/>) result = safe_list_sanitize(input) acceptable_results = [ # libxml2 "<img>", # libgumbo %(<img src="http://ha.ckers.org/xss.js">), ] assert_includes(acceptable_results, result) end def test_should_sanitize_img_dynsrc_lowsrc assert_sanitized(%(<img lowsrc="javascript:alert('XSS')" />), "<img>") end def test_should_sanitize_img_vbscript assert_sanitized %(<img src='vbscript:msgbox("XSS")' />), "<img>" end def test_should_sanitize_cdata_section input = "<![CDATA[<span>section</span>]]>" result = safe_list_sanitize(input) acceptable_results = [ # libxml2 = 2.9.14 %{&lt;![CDATA[<span>section</span>]]&gt;}, # other libxml2 %{section]]&gt;}, # xerces+neko "", ] assert_includes(acceptable_results, result) end def test_should_sanitize_unterminated_cdata_section input = "<![CDATA[<span>neverending..." result = safe_list_sanitize(input) acceptable_results = [ # libxml2 = 2.9.14 %{&lt;![CDATA[<span>neverending...</span>}, # other libxml2 %{neverending...}, # xerces+neko "" ] assert_includes(acceptable_results, result) end def test_should_not_mangle_urls_with_ampersand assert_sanitized %{<a href=\"http://www.domain.com?var1=1&amp;var2=2\">my link</a>} end def test_should_sanitize_neverending_attribute # note that assert_dom_equal chokes in this case! so avoid using assert_sanitized assert_equal("<span class=\"\\\"></span>", safe_list_sanitize("<span class=\"\\\">")) end [ %(<a href="javascript&#x3a;alert('XSS');">), %(<a href="javascript&#x003a;alert('XSS');">), %(<a href="javascript&#x3A;alert('XSS');">), %(<a href="javascript&#x003A;alert('XSS');">) ].each_with_index do |enc_hack, i| define_method "test_x03a_handling_#{i + 1}" do assert_sanitized enc_hack, "<a></a>" end end def test_x03a_legitimate assert_sanitized %(<a href="http&#x3a;//legit">asdf</a>), %(<a href="http://legit">asdf</a>) assert_sanitized %(<a href="http&#x3A;//legit">asdf</a>), %(<a href="http://legit">asdf</a>) end def test_sanitize_ascii_8bit_string safe_list_sanitize("<div><a>hello</a></div>".encode("ASCII-8BIT")).tap do |sanitized| assert_equal "<div><a>hello</a></div>", sanitized assert_equal Encoding::UTF_8, sanitized.encoding end end def test_sanitize_data_attributes assert_sanitized %(<a href="/blah" data-method="post">foo</a>), %(<a href="/blah">foo</a>) assert_sanitized %(<a data-remote="true" data-type="script" data-method="get" data-cross-domain="true" href="attack.js">Launch the missiles</a>), %(<a href="attack.js">Launch the missiles</a>) end def test_allow_data_attribute_if_requested text = %(<a data-foo="foo">foo</a>) assert_equal %(<a data-foo="foo">foo</a>), safe_list_sanitize(text, attributes: ["data-foo"]) end # https://developer.mozilla.org/en-US/docs/Glossary/Void_element VOID_ELEMENTS = %w[area base br col embed hr img input keygen link meta param source track wbr] %w(strong em b i p code pre tt samp kbd var sub sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr acronym a img blockquote del ins time).each do |tag_name| define_method "test_default_safelist_should_allow_#{tag_name}" do if VOID_ELEMENTS.include?(tag_name) assert_sanitized("<#{tag_name}>") else assert_sanitized("<#{tag_name}>foo</#{tag_name}>") end end end def test_datetime_attribute assert_sanitized("<time datetime=\"2023-01-01\">Today</time>") end def test_abbr_attribute scope_allowed_tags(%w(table tr th td)) do assert_sanitized(%(<table><tr><td abbr="UK">United Kingdom</td></tr></table>)) end end def test_uri_escaping_of_href_attr_in_a_tag_in_safe_list_sanitizer html = %{<a href='examp<!--" unsafeattr=foo()>-->le.com'>test</a>} text = safe_list_sanitize(html) acceptable_results = [ # nokogiri's vendored+patched libxml2 (0002-Update-entities-to-remove-handling-of-ssi.patch) %{<a href="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>}, # system libxml2 %{<a href="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, # xerces+neko %{<a href="examp&lt;!--%22 unsafeattr=foo()&gt;--&gt;le.com">test</a>} ] assert_includes(acceptable_results, text) end def test_uri_escaping_of_src_attr_in_a_tag_in_safe_list_sanitizer html = %{<a src='examp<!--" unsafeattr=foo()>-->le.com'>test</a>} text = safe_list_sanitize(html) acceptable_results = [ # nokogiri's vendored+patched libxml2 (0002-Update-entities-to-remove-handling-of-ssi.patch) %{<a src="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>}, # system libxml2 %{<a src="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, # xerces+neko %{<a src="examp&lt;!--%22 unsafeattr=foo()&gt;--&gt;le.com">test</a>} ] assert_includes(acceptable_results, text) end def test_uri_escaping_of_name_attr_in_a_tag_in_safe_list_sanitizer html = %{<a name='examp<!--" unsafeattr=foo()>-->le.com'>test</a>} text = safe_list_sanitize(html) acceptable_results = [ # nokogiri's vendored+patched libxml2 (0002-Update-entities-to-remove-handling-of-ssi.patch) %{<a name="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>}, # system libxml2 %{<a name="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, # xerces+neko %{<a name="examp&lt;!--%22 unsafeattr=foo()&gt;--&gt;le.com">test</a>} ] assert_includes(acceptable_results, text) end def test_uri_escaping_of_name_action_in_a_tag_in_safe_list_sanitizer html = %{<a action='examp<!--" unsafeattr=foo()>-->le.com'>test</a>} text = safe_list_sanitize(html, attributes: ["action"]) acceptable_results = [ # nokogiri's vendored+patched libxml2 (0002-Update-entities-to-remove-handling-of-ssi.patch) %{<a action="examp&lt;!--%22%20unsafeattr=foo()&gt;--&gt;le.com">test</a>}, # system libxml2 %{<a action="examp<!--%22%20unsafeattr=foo()>-->le.com">test</a>}, # xerces+neko %{<a action="examp&lt;!--%22 unsafeattr=foo()&gt;--&gt;le.com">test</a>}, ] assert_includes(acceptable_results, text) end def test_exclude_node_type_processing_instructions input = "<div>text</div><?div content><b>text</b>" result = safe_list_sanitize(input) acceptable_results = [ # jruby cyberneko (nokogiri < 1.14.0) "<div>text</div>", # everything else "<div>text</div><b>text</b>", ] assert_includes(acceptable_results, result) end def test_exclude_node_type_comment assert_equal("<div>text</div><b>text</b>", safe_list_sanitize("<div>text</div><!-- comment --><b>text</b>")) end %w[text/plain text/css image/png image/gif image/jpeg].each do |mediatype| define_method "test_mediatype_#{mediatype}_allowed" do input = %Q(<img src="data:#{mediatype};base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">) expected = input actual = safe_list_sanitize(input) assert_equal(expected, actual) input = %Q(<img src="DATA:#{mediatype};base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">) expected = input actual = safe_list_sanitize(input) assert_equal(expected, actual) end end def test_mediatype_text_html_disallowed input = '<img src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">' expected = "<img>" actual = safe_list_sanitize(input) assert_equal(expected, actual) input = '<img src="DATA:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">' expected = "<img>" actual = safe_list_sanitize(input) assert_equal(expected, actual) end def test_mediatype_image_svg_xml_disallowed input = '<img src="data:image/svg+xml;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">' expected = "<img>" actual = safe_list_sanitize(input) assert_equal(expected, actual) input = '<img src="DATA:image/svg+xml;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">' expected = "<img>" actual = safe_list_sanitize(input) assert_equal(expected, actual) end def test_mediatype_other_disallowed input = '<a href="data:foo;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">foo</a>' expected = "<a>foo</a>" actual = safe_list_sanitize(input) assert_equal(expected, actual) input = '<a href="DATA:foo;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">foo</a>' expected = "<a>foo</a>" actual = safe_list_sanitize(input) assert_equal(expected, actual) end def test_scrubbing_svg_attr_values_that_allow_ref input = '<div fill="yellow url(http://bad.com/) #fff">hey</div>' expected = '<div fill="yellow #fff">hey</div>' actual = scope_allowed_attributes %w(fill) do safe_list_sanitize(input) end assert_equal(expected, actual) end def test_style_with_css_payload input, tags = "<style>div > span { background: \"red\"; }</style>", ["style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<style>div &gt; span { background: \"red\"; }</style>", # libgumbo "<style>div > span { background: \"red\"; }</style>", ] assert_includes(acceptable_results, actual) end def test_combination_of_select_and_style_with_css_payload input, tags = "<select><style>div > span { background: \"red\"; }</style></select>", ["select", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<select><style>div &gt; span { background: \"red\"; }</style></select>", # libgumbo "<select>div &gt; span { background: \"red\"; }</select>", ] assert_includes(acceptable_results, actual) end def test_combination_of_select_and_style_with_script_payload input, tags = "<select><style><script>alert(1)</script></style></select>", ["select", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<select><style>&lt;script&gt;alert(1)&lt;/script&gt;</style></select>", # libgumbo "<select>alert(1)</select>", ] assert_includes(acceptable_results, actual) end def test_combination_of_svg_and_style_with_script_payload input, tags = "<svg><style><script>alert(1)</script></style></svg>", ["svg", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<svg><style>&lt;script&gt;alert(1)&lt;/script&gt;</style></svg>", # libgumbo "<svg><style></style></svg>", ] assert_includes(acceptable_results, actual) end def test_combination_of_math_and_style_with_img_payload input, tags = "<math><style><img src=x onerror=alert(1)></style></math>", ["math", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<math><style>&lt;img src=x onerror=alert(1)&gt;</style></math>", # libgumbo "<math><style></style></math>", ] assert_includes(acceptable_results, actual) end def test_combination_of_math_and_style_with_img_payload_2 input, tags = "<math><style><img src=x onerror=alert(1)></style></math>", ["math", "style", "img"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<math><style>&lt;img src=x onerror=alert(1)&gt;</style></math>", # libgumbo "<math><style></style></math><img src=\"x\">", ] assert_includes(acceptable_results, actual) end def test_combination_of_svg_and_style_with_img_payload input, tags = "<svg><style><img src=x onerror=alert(1)></style></svg>", ["svg", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<svg><style>&lt;img src=x onerror=alert(1)&gt;</style></svg>", # libgumbo "<svg><style></style></svg>", ] assert_includes(acceptable_results, actual) end def test_combination_of_svg_and_style_with_img_payload_2 input, tags = "<svg><style><img src=x onerror=alert(1)></style></svg>", ["svg", "style", "img"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<svg><style>&lt;img src=x onerror=alert(1)&gt;</style></svg>", # libgumbo "<svg><style></style></svg><img src=\"x\">", ] assert_includes(acceptable_results, actual) end def test_combination_of_svg_and_style_with_escaped_img_payload # https://hackerone.com/reports/2503220 input, tags = "<svg><style>&lt;img src onerror=alert(1)>", ["svg", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<svg><style>&amp;lt;img src onerror=alert(1)&gt;</style></svg>", # libgumbo "<svg><style>&lt;img src onerror=alert(1)&gt;</style></svg>", ] assert_includes(acceptable_results, actual) end def test_combination_of_math_and_style_with_escaped_img_payload # https://hackerone.com/reports/2503220 input, tags = "<math><style>&lt;img src onerror=alert(1)>", ["math", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<math><style>&amp;lt;img src onerror=alert(1)&gt;</style></math>", # libgumbo "<math><style>&lt;img src onerror=alert(1)&gt;</style></math>", ] assert_includes(acceptable_results, actual) end def test_combination_of_style_and_disallowed_svg_with_script_payload # https://hackerone.com/reports/2519936 input, tags = "<svg><style><style class='</style><script>alert(1)</script>'>", ["style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<style>&lt;style class='</style>alert(1)'&gt;", # libgumbo "", ] assert_includes(acceptable_results, actual) end def test_combination_of_style_and_disallowed_math_with_script_payload # https://hackerone.com/reports/2519936 input, tags = "<math><style><style class='</style><script>alert(1)</script>'>", ["style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<style>&lt;style class='</style>alert(1)'&gt;", # libgumbo "", ] assert_includes(acceptable_results, actual) end def test_math_with_disallowed_mtext_and_img_payload # https://hackerone.com/reports/2519941 input, tags = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>", ["math", "style"] actual = safe_list_sanitize(input, tags: tags) acceptable_results = [ # libxml2 "<math><style>&lt;img src=: onerror=alert(1)&gt;</style></math>", # libgumbo "<math></math>", ] assert_includes(acceptable_results, actual) end def test_should_sanitize_illegal_style_properties raw = %(display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;) expected = %(display:block;width:100%;height:100%;background-color:black;background-x:center;background-y:center;) assert_equal expected, sanitize_css(raw) end def test_should_sanitize_with_trailing_space raw = "display:block; " expected = "display:block;" assert_equal expected, sanitize_css(raw) end def test_should_sanitize_xul_style_attributes raw = %(-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss')) assert_equal "", sanitize_css(raw) end def test_should_sanitize_div_background_image_unicode_encoded [ convert_to_css_hex("url(javascript:alert(1))", false), convert_to_css_hex("url(javascript:alert(1))", true), convert_to_css_hex("url(https://example.com)", false), convert_to_css_hex("url(https://example.com)", true), ].each do |propval| raw = "background-image:" + propval assert_empty(sanitize_css(raw)) end end def test_should_allow_div_background_image_unicode_encoded_safe_functions [ convert_to_css_hex("rgb(255,0,0)", false), convert_to_css_hex("rgb(255,0,0)", true), ].each do |propval| raw = "background-image:" + propval assert_includes(sanitize_css(raw), "background-image") end end def test_should_sanitize_div_style_expression raw = %(width: expression(alert('XSS'));) assert_equal "", sanitize_css(raw) end def test_should_sanitize_across_newlines raw = %(\nwidth:\nexpression(alert('XSS'));\n) assert_equal "", sanitize_css(raw) end def test_should_prune_mglyph # https://hackerone.com/reports/2519936 input = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>" tags = %w(math mtext table mglyph style).freeze actual = nil assert_output(nil, /WARNING: 'mglyph' tags cannot be allowed by the PermitScrubber/) do actual = safe_list_sanitize(input, tags: tags) end acceptable_results = [ # libxml2 "<math><mtext><table><style>&lt;img src=: onerror=alert(1)&gt;</style></table></mtext></math>", # libgumbo "<math><mtext><style><img src=: onerror=alert(1)></style><table></table></mtext></math>", ] assert_includes(acceptable_results, actual) end def test_should_prune_malignmark # https://hackerone.com/reports/2519936 input = "<math><mtext><table><malignmark><style><img src=: onerror=alert(1)>" tags = %w(math mtext table malignmark style).freeze actual = nil assert_output(nil, /WARNING: 'malignmark' tags cannot be allowed by the PermitScrubber/) do actual = safe_list_sanitize(input, tags: tags) end acceptable_results = [ # libxml2 "<math><mtext><table><style>&lt;img src=: onerror=alert(1)&gt;</style></table></mtext></math>", # libgumbo "<math><mtext><style><img src=: onerror=alert(1)></style><table></table></mtext></math>", ] assert_includes(acceptable_results, actual) end def test_should_prune_noscript # https://hackerone.com/reports/2509647 input = "<div><noscript><p id='</noscript><script>alert(1)</script>'></noscript>" tags = ["p", "div", "noscript"].freeze actual = nil assert_output(nil, /WARNING: 'noscript' tags cannot be allowed by the PermitScrubber/) do actual = safe_list_sanitize(input, tags: tags, attributes: %w(id)) end acceptable_results = [ # libxml2 "<div><p id=\"&lt;/noscript&gt;&lt;script&gt;alert(1)&lt;/script&gt;\"></p></div>", # libgumbo "<div><p id=\"</noscript><script>alert(1)</script>\"></p></div>", ] assert_includes(acceptable_results, actual) end protected def safe_list_sanitize(input, options = {}) module_under_test::SafeListSanitizer.new.sanitize(input, options) end def assert_sanitized(input, expected = nil) assert_equal((expected || input), safe_list_sanitize(input)) end def scope_allowed_tags(tags) old_tags = module_under_test::SafeListSanitizer.allowed_tags module_under_test::SafeListSanitizer.allowed_tags = tags yield module_under_test::SafeListSanitizer.new ensure module_under_test::SafeListSanitizer.allowed_tags = old_tags end def scope_allowed_attributes(attributes) old_attributes = module_under_test::SafeListSanitizer.allowed_attributes module_under_test::SafeListSanitizer.allowed_attributes = attributes yield module_under_test::SafeListSanitizer.new ensure module_under_test::SafeListSanitizer.allowed_attributes = old_attributes end def sanitize_css(input) module_under_test::SafeListSanitizer.new.sanitize_css(input) end # note that this is used for testing CSS hex encoding: \\[0-9a-f]{1,6} def convert_to_css_hex(string, escape_parens = false) string.chars.map do |c| if !escape_parens && (c == "(" || c == ")") c else format('\00%02X', c.ord) end end.join end end class HTML4SafeListSanitizerTest < Minitest::Test @module_under_test = Rails::HTML4 include SafeListSanitizerTest end class HTML5SafeListSanitizerTest < Minitest::Test @module_under_test = Rails::HTML5 include SafeListSanitizerTest def test_should_not_be_vulnerable_to_nokogiri_foreign_style_serialization_bug # https://hackerone.com/reports/2503220 input = "<svg><style>&lt;img src onerror=alert(1)>" result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: ["svg", "style"]) browser = Nokogiri::HTML5::Document.parse(result) xss = browser.at_xpath("//img/@onerror") assert_nil(xss) end def test_should_not_be_vulnerable_to_ns_confusion_2519936 # https://hackerone.com/reports/2519936 input = "<math><style><style class='</style><script>alert(1)</script>'>" result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: ["style"]) browser = Nokogiri::HTML5::Document.parse(result) xss = browser.at_xpath("//script") assert_nil(xss) end def test_should_not_be_vulnerable_to_ns_confusion_2519941 # https://hackerone.com/reports/2519941 input = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>" result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: %w(math style)) browser = Nokogiri::HTML5::Document.parse(result) xss = browser.at_xpath("//img/@onerror") assert_nil(xss) end def test_should_not_be_vulnerable_to_mglyph_namespace_confusion # https://hackerone.com/reports/2519936 input = "<math><mtext><table><mglyph><style><img src=: onerror=alert(1)>" tags = %w(math mtext table mglyph style) result = nil assert_output(nil, /WARNING/) do result = safe_list_sanitize(input, tags: tags) end browser = Nokogiri::HTML5::Document.parse(result) xss = browser.at_xpath("//img/@onerror") assert_nil(xss) end def test_should_not_be_vulnerable_to_malignmark_namespace_confusion # https://hackerone.com/reports/2519936 input = "<math><mtext><table><malignmark><style><img src=: onerror=alert(1)>" tags = %w(math mtext table malignmark style) result = nil assert_output(nil, /WARNING/) do result = safe_list_sanitize(input, tags: tags) end browser = Nokogiri::HTML5::Document.parse(result) xss = browser.at_xpath("//img/@onerror") assert_nil(xss) end def test_should_not_be_vulnerable_to_noscript_attacks # https://hackerone.com/reports/2509647 skip("browser assertion requires parse_noscript_content_as_text") unless Nokogiri::VERSION >= "1.17" input = '<noscript><p id="</noscript><script>alert(1)</script>"></noscript>' result = nil assert_output(nil, /WARNING/) do result = Rails::HTML5::SafeListSanitizer.new.sanitize(input, tags: %w(p div noscript), attributes: %w(id class style)) end browser = Nokogiri::HTML5::Document.parse(result, parse_noscript_content_as_text: true) xss = browser.at_xpath("//script") assert_nil(xss) end end if loofah_html5_support? end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rails-html-sanitizer-1.6.2/test/scrubbers_test.rb���������������������������������������������������0000644�0000041�0000041�00000017715�14740465306�022213� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# frozen_string_literal: true require "minitest/autorun" require "rails-html-sanitizer" class ScrubberTest < Minitest::Test protected def scrub_fragment(html) Loofah.scrub_fragment(html, @scrubber).to_s end def assert_scrubbed(html, expected = html) output = scrub_fragment(html) assert_equal expected, output end def to_node(text) Loofah.fragment(text).children.first end def assert_node_skipped(text) assert_scrub_returns(Loofah::Scrubber::CONTINUE, text) end def assert_scrub_stopped(text) assert_scrub_returns(Loofah::Scrubber::STOP, text) end def assert_scrub_returns(return_value, text) node = to_node(text) assert_equal return_value, @scrubber.scrub(node) end end class PermitScrubberTest < ScrubberTest def setup @scrubber = Rails::HTML::PermitScrubber.new end def test_responds_to_scrub assert @scrubber.respond_to?(:scrub) end def test_default_scrub_behavior assert_scrubbed "<tag>hello</tag>", "hello" end def test_default_scrub_removes_comments assert_scrubbed("<div>one</div><!-- two --><span>three</span>", "<div>one</div><span>three</span>") end def test_default_scrub_removes_processing_instructions input = "<div>one</div><?div two><span>three</span>" result = scrub_fragment(input) acceptable_results = [ # jruby cyberneko (nokogiri < 1.14.0) "<div>one</div>", # everything else "<div>one</div><span>three</span>", ] assert_includes(acceptable_results, result) end def test_default_attributes_removal_behavior assert_scrubbed '<p cooler="hello">hello</p>', "<p>hello</p>" end def test_leaves_supplied_tags @scrubber.tags = %w(a) assert_scrubbed "<a>hello</a>" end def test_leaves_only_supplied_tags html = "<tag>leave me <span>now</span></tag>" @scrubber.tags = %w(tag) assert_scrubbed html, "<tag>leave me now</tag>" end def test_prunes_tags @scrubber = Rails::HTML::PermitScrubber.new(prune: true) @scrubber.tags = %w(tag) html = "<tag>leave me <span>now</span></tag>" assert_scrubbed html, "<tag>leave me </tag>" end def test_leaves_comments_when_supplied_as_tag @scrubber.tags = %w(div comment) assert_scrubbed("<div>one</div><!-- two --><span>three</span>", "<div>one</div><!-- two -->three") end def test_leaves_only_supplied_tags_nested html = "<tag>leave <em>me <span>now</span></em></tag>" @scrubber.tags = %w(tag) assert_scrubbed html, "<tag>leave me now</tag>" end def test_leaves_supplied_attributes @scrubber.attributes = %w(cooler) assert_scrubbed '<a cooler="hello"></a>' end def test_leaves_only_supplied_attributes @scrubber.attributes = %w(cooler) assert_scrubbed '<a cooler="hello" b="c" d="e"></a>', '<a cooler="hello"></a>' end def test_leaves_supplied_tags_and_attributes @scrubber.tags = %w(tag) @scrubber.attributes = %w(cooler) assert_scrubbed '<tag cooler="hello"></tag>' end def test_leaves_only_supplied_tags_and_attributes @scrubber.tags = %w(tag) @scrubber.attributes = %w(cooler) html = '<a></a><tag href=""></tag><tag cooler=""></tag>' assert_scrubbed html, '<tag></tag><tag cooler=""></tag>' end def test_does_not_allow_safelisted_mglyph # https://hackerone.com/reports/2519936 assert_output(nil, /WARNING: 'mglyph' tags cannot be allowed by the PermitScrubber/) do @scrubber.tags = ["div", "mglyph", "span"] end assert_equal(["div", "span"], @scrubber.tags) end def test_does_not_allow_safelisted_malignmark # https://hackerone.com/reports/2519936 assert_output(nil, /WARNING: 'malignmark' tags cannot be allowed by the PermitScrubber/) do @scrubber.tags = ["div", "malignmark", "span"] end assert_equal(["div", "span"], @scrubber.tags) end def test_does_not_allow_safelisted_noscript # https://hackerone.com/reports/2509647 assert_output(nil, /WARNING: 'noscript' tags cannot be allowed by the PermitScrubber/) do @scrubber.tags = ["div", "noscript", "span"] end assert_equal(["div", "span"], @scrubber.tags) end def test_leaves_text assert_scrubbed("some text") end def test_skips_text_nodes assert_node_skipped("some text") end def test_tags_accessor_validation e = assert_raises(ArgumentError) do @scrubber.tags = "tag" end assert_equal "You should pass :tags as an Enumerable", e.message assert_nil @scrubber.tags, "Tags should be nil when validation fails" end def test_attributes_accessor_validation e = assert_raises(ArgumentError) do @scrubber.attributes = "cooler" end assert_equal "You should pass :attributes as an Enumerable", e.message assert_nil @scrubber.attributes, "Attributes should be nil when validation fails" end end class TargetScrubberTest < ScrubberTest def setup @scrubber = Rails::HTML::TargetScrubber.new end def test_targeting_tags_removes_only_them @scrubber.tags = %w(a h1) html = "<script></script><a></a><h1></h1>" assert_scrubbed html, "<script></script>" end def test_targeting_tags_removes_only_them_nested @scrubber.tags = %w(a) html = "<tag><a><tag><a></a></tag></a></tag>" assert_scrubbed html, "<tag><tag></tag></tag>" end def test_targeting_attributes_removes_only_them @scrubber.attributes = %w(class id) html = '<a class="a" id="b" onclick="c"></a>' assert_scrubbed html, '<a onclick="c"></a>' end def test_targeting_tags_and_attributes_removes_only_them @scrubber.tags = %w(tag) @scrubber.attributes = %w(remove) html = '<tag remove="" other=""></tag><a remove="" other=""></a>' assert_scrubbed html, '<a other=""></a>' end def test_prunes_tags @scrubber = Rails::HTML::TargetScrubber.new(prune: true) @scrubber.tags = %w(span) html = "<tag>leave me <span>now</span></tag>" assert_scrubbed html, "<tag>leave me </tag>" end end class TextOnlyScrubberTest < ScrubberTest def setup @scrubber = Rails::HTML::TextOnlyScrubber.new end def test_removes_all_tags_and_keep_the_content assert_scrubbed "<tag>hello</tag>", "hello" end def test_skips_text_nodes assert_node_skipped("some text") end end class ReturningStopFromScrubNodeTest < ScrubberTest class ScrubStopper < Rails::HTML::PermitScrubber def scrub_node(node) Loofah::Scrubber::STOP end end class ScrubContinuer < Rails::HTML::PermitScrubber def scrub_node(node) Loofah::Scrubber::CONTINUE end end def test_returns_stop_from_scrub_if_scrub_node_does @scrubber = ScrubStopper.new assert_scrub_stopped "<script>remove me</script>" end def test_returns_continue_from_scrub_if_scrub_node_does @scrubber = ScrubContinuer.new assert_node_skipped "<script>keep me</script>" end end class PermitScrubberMinimalOperationsTest < ScrubberTest class TestPermitScrubber < Rails::HTML::PermitScrubber def initialize @scrub_attribute_args = [] @scrub_attributes_args = [] super self.tags = ["div"] self.attributes = ["class"] end def scrub_attributes(node) @scrub_attributes_args << node.name super end def scrub_attribute(node, attr) @scrub_attribute_args << [node.name, attr.name] super end end def test_does_not_scrub_removed_attributes @scrubber = TestPermitScrubber.new input = "<div class='foo' href='bar'></div>" frag = scrub_fragment(input) assert_equal("<div class=\"foo\"></div>", frag) assert_equal([["div", "class"]], @scrubber.instance_variable_get(:@scrub_attribute_args)) end def test_does_not_scrub_attributes_of_a_removed_node @scrubber = TestPermitScrubber.new input = "<div class='foo' href='bar'><svg xlink:href='asdf'><set></set></svg></div>" frag = scrub_fragment(input) assert_equal("<div class=\"foo\"></div>", frag) assert_equal(["div"], @scrubber.instance_variable_get(:@scrub_attributes_args)) end end ���������������������������������������������������rails-html-sanitizer-1.6.2/MIT-LICENSE��������������������������������������������������������������0000644�0000041�0000041�00000002142�14740465306�017276� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Copyright (c) 2013-2023 Rafael Mendonça França, Kasper Timm Hansen, Mike Dalessio MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������rails-html-sanitizer-1.6.2/README.md����������������������������������������������������������������0000644�0000041�0000041�00000022544�14740465306�017131� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Rails HTML Sanitizers This gem is responsible for sanitizing HTML fragments in Rails applications. Specifically, this is the set of sanitizers used to implement the Action View `SanitizerHelper` methods `sanitize`, `sanitize_css`, `strip_tags` and `strip_links`. Rails HTML Sanitizer is only intended to be used with Rails applications. If you need similar functionality but aren't using Rails, consider using the underlying sanitization library [Loofah](https://github.com/flavorjones/loofah) directly. ## Usage ### Sanitizers All sanitizers respond to `sanitize`, and are available in variants that use either HTML4 or HTML5 parsing, under the `Rails::HTML4` and `Rails::HTML5` namespaces, respectively. NOTE: The HTML5 sanitizers are not supported on JRuby. Users may programmatically check for support by calling `Rails::HTML::Sanitizer.html5_support?`. #### FullSanitizer ```ruby full_sanitizer = Rails::HTML5::FullSanitizer.new full_sanitizer.sanitize("<b>Bold</b> no more! <a href='more.html'>See more here</a>...") # => Bold no more! See more here... ``` or, if you insist on parsing the content as HTML4: ```ruby full_sanitizer = Rails::HTML4::FullSanitizer.new full_sanitizer.sanitize("<b>Bold</b> no more! <a href='more.html'>See more here</a>...") # => Bold no more! See more here... ``` #### LinkSanitizer ```ruby link_sanitizer = Rails::HTML5::LinkSanitizer.new link_sanitizer.sanitize('<a href="example.com">Only the link text will be kept.</a>') # => Only the link text will be kept. ``` or, if you insist on parsing the content as HTML4: ```ruby link_sanitizer = Rails::HTML4::LinkSanitizer.new link_sanitizer.sanitize('<a href="example.com">Only the link text will be kept.</a>') # => Only the link text will be kept. ``` #### SafeListSanitizer This sanitizer is also available as an HTML4 variant, but for simplicity we'll document only the HTML5 variant below. ```ruby safe_list_sanitizer = Rails::HTML5::SafeListSanitizer.new # sanitize via an extensive safe list of allowed elements safe_list_sanitizer.sanitize(@article.body) # sanitize only the supplied tags and attributes safe_list_sanitizer.sanitize(@article.body, tags: %w(table tr td), attributes: %w(id class style)) # sanitize via a custom scrubber safe_list_sanitizer.sanitize(@article.body, scrubber: ArticleScrubber.new) # prune nodes from the tree instead of stripping tags and leaving inner content safe_list_sanitizer = Rails::HTML5::SafeListSanitizer.new(prune: true) # the sanitizer can also sanitize css safe_list_sanitizer.sanitize_css('background-color: #000;') ``` ### Scrubbers Scrubbers are objects responsible for removing nodes or attributes you don't want in your HTML document. This gem includes two scrubbers `Rails::HTML::PermitScrubber` and `Rails::HTML::TargetScrubber`. #### `Rails::HTML::PermitScrubber` This scrubber allows you to permit only the tags and attributes you want. ```ruby scrubber = Rails::HTML::PermitScrubber.new scrubber.tags = ['a'] html_fragment = Loofah.fragment('<a><img/ ></a>') html_fragment.scrub!(scrubber) html_fragment.to_s # => "<a></a>" ``` By default, inner content is left, but it can be removed as well. ```ruby scrubber = Rails::HTML::PermitScrubber.new scrubber.tags = ['a'] html_fragment = Loofah.fragment('<a><span>text</span></a>') html_fragment.scrub!(scrubber) html_fragment.to_s # => "<a>text</a>" scrubber = Rails::HTML::PermitScrubber.new(prune: true) scrubber.tags = ['a'] html_fragment = Loofah.fragment('<a><span>text</span></a>') html_fragment.scrub!(scrubber) html_fragment.to_s # => "<a></a>" ``` #### `Rails::HTML::TargetScrubber` Where `PermitScrubber` picks out tags and attributes to permit in sanitization, `Rails::HTML::TargetScrubber` targets them for removal. See https://github.com/flavorjones/loofah/blob/main/lib/loofah/html5/safelist.rb for the tag list. **Note:** by default, it will scrub anything that is not part of the permitted tags from loofah `HTML5::Scrub.allowed_element?`. ```ruby scrubber = Rails::HTML::TargetScrubber.new scrubber.tags = ['img'] html_fragment = Loofah.fragment('<a><img/ ></a>') html_fragment.scrub!(scrubber) html_fragment.to_s # => "<a></a>" ``` Similarly to `PermitScrubber`, nodes can be fully pruned. ```ruby scrubber = Rails::HTML::TargetScrubber.new scrubber.tags = ['span'] html_fragment = Loofah.fragment('<a><span>text</span></a>') html_fragment.scrub!(scrubber) html_fragment.to_s # => "<a>text</a>" scrubber = Rails::HTML::TargetScrubber.new(prune: true) scrubber.tags = ['span'] html_fragment = Loofah.fragment('<a><span>text</span></a>') html_fragment.scrub!(scrubber) html_fragment.to_s # => "<a></a>" ``` #### Custom Scrubbers You can also create custom scrubbers in your application if you want to. ```ruby class CommentScrubber < Rails::HTML::PermitScrubber def initialize super self.tags = %w( form script comment blockquote ) self.attributes = %w( style ) end def skip_node?(node) node.text? end end ``` See `Rails::HTML::PermitScrubber` documentation to learn more about which methods can be overridden. #### Custom Scrubber in a Rails app Using the `CommentScrubber` from above, you can use this in a Rails view like so: ```ruby <%= sanitize @comment, scrubber: CommentScrubber.new %> ``` ### A note on HTML entities __Rails HTML sanitizers are intended to be used by the view layer, at page-render time. They are *not* intended to sanitize persisted strings that will be sanitized *again* at page-render time.__ Proper HTML sanitization will replace some characters with HTML entities. For example, text containing a `<` character will be updated to contain `&lt;` to ensure that the markup is well-formed. This is important to keep in mind because __HTML entities will render improperly if they are sanitized twice.__ #### A concrete example showing the problem that can arise Imagine the user is asked to enter their employer's name, which will appear on their public profile page. Then imagine they enter `JPMorgan Chase & Co.`. If you sanitize this before persisting it in the database, the stored string will be `JPMorgan Chase &amp; Co.` When the page is rendered, if this string is sanitized a second time by the view layer, the HTML will contain `JPMorgan Chase &amp;amp; Co.` which will render as "JPMorgan Chase &amp;amp; Co.". Another problem that can arise is rendering the sanitized string in a non-HTML context (for example, if it ends up being part of an SMS message). In this case, it may contain inappropriate HTML entities. #### Suggested alternatives You might simply choose to persist the untrusted string as-is (the raw input), and then ensure that the string will be properly sanitized by the view layer. That raw string, if rendered in an non-HTML context (like SMS), must also be sanitized by a method appropriate for that context. You may wish to look into using [Loofah](https://github.com/flavorjones/loofah) or [Sanitize](https://github.com/rgrove/sanitize) to customize how this sanitization works, including omitting HTML entities in the final string. If you really want to sanitize the string that's stored in your database, you may wish to look into [Loofah::ActiveRecord](https://github.com/flavorjones/loofah-activerecord) rather than use the Rails HTML sanitizers. ### A note on module names In versions < 1.6, the only module defined by this library was `Rails::Html`. Starting in 1.6, we define three additional modules: - `Rails::HTML` for general functionality (replacing `Rails::Html`) - `Rails::HTML4` containing sanitizers that parse content as HTML4 - `Rails::HTML5` containing sanitizers that parse content as HTML5 (if supported) The following aliases are maintained for backwards compatibility: - `Rails::Html` points to `Rails::HTML` - `Rails::HTML::FullSanitizer` points to `Rails::HTML4::FullSanitizer` - `Rails::HTML::LinkSanitizer` points to `Rails::HTML4::LinkSanitizer` - `Rails::HTML::SafeListSanitizer` points to `Rails::HTML4::SafeListSanitizer` ## Installation Add this line to your application's Gemfile: gem 'rails-html-sanitizer' And then execute: $ bundle Or install it yourself as: $ gem install rails-html-sanitizer ## Support matrix | branch | ruby support | actively maintained | security support | |--------|--------------|---------------------|----------------------------------------| | 1.6.x | >= 2.7 | yes | yes | | 1.5.x | >= 2.5 | no | while Rails 6.1 is in security support | | 1.4.x | >= 1.8.7 | no | no | ## Read more Loofah is what underlies the sanitizers and scrubbers of rails-html-sanitizer. - [Loofah and Loofah Scrubbers](https://github.com/flavorjones/loofah) The `node` argument passed to some methods in a custom scrubber is an instance of `Nokogiri::XML::Node`. - [`Nokogiri::XML::Node`](https://nokogiri.org/rdoc/Nokogiri/XML/Node.html) - [Nokogiri](http://nokogiri.org) ## Contributing to Rails HTML Sanitizers Rails HTML Sanitizers is work of many contributors. You're encouraged to submit pull requests, propose features and discuss issues. See [CONTRIBUTING](CONTRIBUTING.md). ### Security reports Trying to report a possible security vulnerability in this project? Please check out the [Rails project's security policy](https://rubyonrails.org/security) for instructions. ## License Rails HTML Sanitizers is released under the [MIT License](MIT-LICENSE). ������������������������������������������������������������������������������������������������������������������������������������������������������������rails-html-sanitizer-1.6.2/CHANGELOG.md�������������������������������������������������������������0000644�0000041�0000041�00000016763�14740465306�017471� 0����������������������������������������������������������������������������������������������������ustar �www-data������������������������www-data���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## v1.6.2 / 2024-12-12 * `PermitScrubber` fully supports frozen "allowed tags". v1.6.1 introduced safety checks that may remove unsafe tags from the allowed list, which introduced a regression for applications passing a frozen array of allowed tags. Tags and attributes are now properly copied when they are passed to the scrubber. Fixes #195. *Mike Dalessio* ## 1.6.1 / 2024-12-02 This is a performance and security release which addresses several possible XSS vulnerabilities. * The dependency on Nokogiri is updated to v1.15.7 or >=1.16.8. This change addresses CVE-2024-53985 (GHSA-w8gc-x259-rc7x). *Mike Dalessio* * Disallowed tags will be pruned when they appear in foreign content (i.e. SVG or MathML content), regardless of the `prune:` option value. Previously, disallowed tags were "stripped" unless the gem was configured with the `prune: true` option. The CVEs addressed by this change are: - CVE-2024-53986 (GHSA-638j-pmjw-jq48) - CVE-2024-53987 (GHSA-2x5m-9ch4-qgrr) *Mike Dalessio* * The tags "noscript", "mglyph", and "malignmark" will not be allowed, even if explicitly added to the allowlist. If applications try to allow any of these tags, a warning is emitted and the tags are removed from the allow-list. The CVEs addressed by this change are: - CVE-2024-53988 (GHSA-cfjx-w229-hgx5) - CVE-2024-53989 (GHSA-rxv5-gxqc-xx8g) Please note that we _may_ restore support for allowing "noscript" in a future release. We do not expect to ever allow "mglyph" or "malignmark", though, especially since browser support is minimal for these tags. *Mike Dalessio* * Improve performance by eliminating needless operations on attributes that are being removed. #188 *Mike Dalessio* ## 1.6.0 / 2023-05-26 * Dependencies have been updated: - Loofah `~>2.21` and Nokogiri `~>1.14` for HTML5 parser support - As a result, required Ruby version is now `>= 2.7.0` Security updates will continue to be made on the `1.5.x` release branch as long as Rails 6.1 (which supports Ruby 2.5) is still in security support. *Mike Dalessio* * HTML5 standards-compliant sanitizers are now available on platforms supported by Nokogiri::HTML5. These are available as: - `Rails::HTML5::FullSanitizer` - `Rails::HTML5::LinkSanitizer` - `Rails::HTML5::SafeListSanitizer` And a new "vendor" is provided at `Rails::HTML5::Sanitizer` that can be used in a future version of Rails. Note that for symmetry `Rails::HTML4::Sanitizer` is also added, though its behavior is identical to the vendor class methods on `Rails::HTML::Sanitizer`. Users may call `Rails::HTML::Sanitizer.best_supported_vendor` to get back the HTML5 vendor if it's supported, else the legacy HTML4 vendor. *Mike Dalessio* * Module namespaces have changed, but backwards compatibility is provided by aliases. The library defines three additional modules: - `Rails::HTML` for general functionality (replacing `Rails::Html`) - `Rails::HTML4` containing sanitizers that parse content as HTML4 - `Rails::HTML5` containing sanitizers that parse content as HTML5 The following aliases are maintained for backwards compatibility: - `Rails::Html` points to `Rails::HTML` - `Rails::HTML::FullSanitizer` points to `Rails::HTML4::FullSanitizer` - `Rails::HTML::LinkSanitizer` points to `Rails::HTML4::LinkSanitizer` - `Rails::HTML::SafeListSanitizer` points to `Rails::HTML4::SafeListSanitizer` *Mike Dalessio* * `LinkSanitizer` always returns UTF-8 encoded strings. `SafeListSanitizer` and `FullSanitizer` already ensured this encoding. *Mike Dalessio* * `SafeListSanitizer` allows `time` tag and `lang` attribute by default. *Mike Dalessio* * The constant `Rails::Html::XPATHS_TO_REMOVE` has been removed. It's not necessary with the existing sanitizers, and should have been a private constant all along anyway. *Mike Dalessio* ## 1.5.0 / 2023-01-20 * `SafeListSanitizer`, `PermitScrubber`, and `TargetScrubber` now all support pruning of unsafe tags. By default, unsafe tags are still stripped, but this behavior can be changed to prune the element and its children from the document by passing `prune: true` to any of these classes' constructors. *seyerian* ## 1.4.4 / 2022-12-13 * Address inefficient regular expression complexity with certain configurations of Rails::Html::Sanitizer. Fixes CVE-2022-23517. See [GHSA-5x79-w82f-gw8w](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-5x79-w82f-gw8w) for more information. *Mike Dalessio* * Address improper sanitization of data URIs. Fixes CVE-2022-23518 and #135. See [GHSA-mcvf-2q2m-x72m](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-mcvf-2q2m-x72m) for more information. *Mike Dalessio* * Address possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer. Fixes CVE-2022-23520. See [GHSA-rrfc-7g8p-99q8](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-rrfc-7g8p-99q8) for more information. *Mike Dalessio* * Address possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer. Fixes CVE-2022-23519. See [GHSA-9h9g-93gc-623h](https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-9h9g-93gc-623h) for more information. *Mike Dalessio* ## 1.4.3 / 2022-06-09 * Address a possible XSS vulnerability with certain configurations of Rails::Html::Sanitizer. Prevent the combination of `select` and `style` as allowed tags in SafeListSanitizer. Fixes CVE-2022-32209 *Mike Dalessio* ## 1.4.2 / 2021-08-23 * Slightly improve performance. Assuming elements are more common than comments, make one less method call per node. *Mike Dalessio* ## 1.4.1 / 2021-08-18 * Fix regression in v1.4.0 that did not pass comment nodes to the scrubber. Some scrubbers will want to override the default behavior and allow comments, but v1.4.0 only passed through elements to the scrubber's `keep_node?` method. This change once again allows the scrubber to make the decision on comment nodes, but still skips other non-elements like processing instructions (see #115). *Mike Dalessio* ## 1.4.0 / 2021-08-18 * Processing Instructions are no longer allowed by Rails::Html::PermitScrubber Previously, a PI with a name (or "target") matching an allowed tag name was not scrubbed. There are no known security issues associated with these PIs, but similar to comments it's preferred to omit these nodes when possible from sanitized output. Fixes #115. *Mike Dalessio* ## 1.3.0 * Address deprecations in Loofah 2.3.0. *Josh Goodall* ## 1.2.0 * Remove needless `white_list_sanitizer` deprecation. By deprecating this, we were forcing Rails 5.2 to be updated or spew deprecations that users could do nothing about. That's pointless and I'm sorry for adding that! Now there's no deprecation warning and Rails 5.2 works out of the box, while Rails 6 can use the updated naming. *Kasper Timm Hansen* ## 1.1.0 * Add `safe_list_sanitizer` and deprecate `white_list_sanitizer` to be removed in 1.2.0. https://github.com/rails/rails-html-sanitizer/pull/87 *Juanito Fatas* * Remove `href` from LinkScrubber's `tags` as it's not an element. https://github.com/rails/rails-html-sanitizer/pull/92 *Juanito Fatas* * Explain that we don't need to bump Loofah here if there's CVEs. https://github.com/rails/rails-html-sanitizer/commit/d4d823c617fdd0064956047f7fbf23fff305a69b *Kasper Timm Hansen* ## 1.0.1 * Added support for Rails 4.2.0.beta2 and above ## 1.0.0 * First release. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������