From c94e24fbe7bcdf605cafcfabdf97454d1e1e0685 Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 12 Jun 2013 15:59:34 +0200 Subject: Added Loofah as a dependency in actionview.gemspec. Implemented ActionView: FullSanitizer, LinkSanitizer and WhiteListSanitizer in sanitizers.rb. Deprecated protocol_separator and bad_tags. Added new tests in sanitizers_test.rb and reimplemented assert_dom_equal with Loofah. --- .../lib/action_view/helpers/sanitize_helper.rb | 47 +++++---- .../helpers/sanitize_helper/sanitizers.rb | 116 +++++++++++++++++++++ 2 files changed, 140 insertions(+), 23 deletions(-) create mode 100644 actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 049af275b6..66894b5936 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/object/try' -require 'action_view/vendor/html-scanner' +require 'action_view/helpers/sanitize_helper/sanitizers' module ActionView # = Action View Sanitize Helpers @@ -65,9 +65,9 @@ module ActionView self.class.white_list_sanitizer.sanitize_css(style) end - # Strips all HTML tags from the +html+, including comments. This uses the - # html-scanner tokenizer and so its HTML parsing ability is limited by - # that of html-scanner. + # Strips all HTML tags from the +html+, including comments. This uses + # Nokogiri for tokenization (via Loofah) and so its HTML parsing ability + # is limited by that of Nokogiri. # # strip_tags("Strip these tags!") # # => Strip these tags! @@ -134,11 +134,7 @@ module ActionView white_list_sanitizer.allowed_protocols end - def sanitized_protocol_separator=(value) - white_list_sanitizer.protocol_separator = value - end - - # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with + # Gets the ActionView::FullSanitizer instance used by +strip_tags+. Replace with # any object that responds to +sanitize+. # # class Application < Rails::Application @@ -146,21 +142,21 @@ module ActionView # end # def full_sanitizer - @full_sanitizer ||= HTML::FullSanitizer.new + @full_sanitizer ||= ActionView::FullSanitizer.new end - # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with - # any object that responds to +sanitize+. + # Gets the ActionView::LinkSanitizer instance used by +strip_links+. + # Replace with any object that responds to +sanitize+. # # class Application < Rails::Application # config.action_view.link_sanitizer = MySpecialSanitizer.new # end # def link_sanitizer - @link_sanitizer ||= HTML::LinkSanitizer.new + @link_sanitizer ||= ActionView::LinkSanitizer.new end - # Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+. + # Gets the ActionView::WhiteListSanitizer instance used by sanitize and +sanitize_css+. # Replace with any object that responds to +sanitize+. # # class Application < Rails::Application @@ -168,7 +164,12 @@ module ActionView # end # def white_list_sanitizer - @white_list_sanitizer ||= HTML::WhiteListSanitizer.new + @white_list_sanitizer ||= ActionView::WhiteListSanitizer.new + end + + + def sanitized_protocol_separator=(value) + ActionView::WhiteListSanitizer.protocol_separator = value end # Adds valid HTML attributes that the +sanitize+ helper checks for URIs. @@ -178,7 +179,7 @@ module ActionView # end # def sanitized_uri_attributes=(attributes) - HTML::WhiteListSanitizer.uri_attributes.merge(attributes) + ActionView::WhiteListSanitizer.update_uri_attributes(attributes) end # Adds to the Set of 'bad' tags for the +sanitize+ helper. @@ -188,7 +189,7 @@ module ActionView # end # def sanitized_bad_tags=(attributes) - HTML::WhiteListSanitizer.bad_tags.merge(attributes) + ActionView::WhiteListSanitizer.bad_tags = attributes end # Adds to the Set of allowed tags for the +sanitize+ helper. @@ -198,7 +199,7 @@ module ActionView # end # def sanitized_allowed_tags=(attributes) - HTML::WhiteListSanitizer.allowed_tags.merge(attributes) + ActionView::WhiteListSanitizer.update_allowed_tags(attributes) end # Adds to the Set of allowed HTML attributes for the +sanitize+ helper. @@ -208,7 +209,7 @@ module ActionView # end # def sanitized_allowed_attributes=(attributes) - HTML::WhiteListSanitizer.allowed_attributes.merge(attributes) + ActionView::WhiteListSanitizer.update_allowed_attributes(attributes) end # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers. @@ -218,7 +219,7 @@ module ActionView # end # def sanitized_allowed_css_properties=(attributes) - HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes) + ActionView::WhiteListSanitizer.update_allowed_css_properties(attributes) end # Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers. @@ -228,7 +229,7 @@ module ActionView # end # def sanitized_allowed_css_keywords=(attributes) - HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes) + ActionView::WhiteListSanitizer.update_allowed_css_keywords(attributes) end # Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers. @@ -238,7 +239,7 @@ module ActionView # end # def sanitized_shorthand_css_properties=(attributes) - HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes) + ActionView::WhiteListSanitizer.update_shorthand_css_properties(attributes) end # Adds to the Set of allowed protocols for the +sanitize+ helper. @@ -248,7 +249,7 @@ module ActionView # end # def sanitized_allowed_protocols=(attributes) - HTML::WhiteListSanitizer.allowed_protocols.merge(attributes) + ActionView::WhiteListSanitizer.update_allowed_protocols(attributes) end end end diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb new file mode 100644 index 0000000000..74be525581 --- /dev/null +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -0,0 +1,116 @@ +require 'active_support/core_ext/class/attribute' +require 'active_support/deprecation' +require 'loofah' + +module ActionView + + class FullSanitizer + def sanitize(html, options = {}) + Loofah.fragment(html).text + end + end + + class LinkSanitizer + def initialize + @link_scrubber = Loofah::Scrubber.new do |node| + next unless node.name == 'a' + node.before node.children + node.remove + end + end + + def sanitize(html, options = {}) + Loofah.scrub_fragment(html, @link_scrubber).to_s + end + end + + class WhiteListSanitizer + def sanitize(html, options = {}) + return nil unless html + validate_options(options) + + loofah_fragment = Loofah.fragment(html) + loofah_fragment.scrub!(:strip) + loofah_fragment.xpath("./form").each { |form| form.remove } + loofah_fragment.to_s + end + + def sanitize_css(style_string) + Loofah::HTML5::Scrub.scrub_css style_string + end + + def protocol_separator + ActiveSupport::Deprecation.warn('protocol_separator has been deprecated and has no effect.') + end + + def protocol_separator=(value) + ActiveSupport::Deprecation.warn('protocol_separator= has been deprecated and has no effect.') + end + + def bad_tags + ActiveSupport::Deprecation.warn('bad_tags has been deprecated and has no effect.') + end + + class << self + def protocol_separator + ActiveSupport::Deprecation.warn('protocol_separator has been deprecated and has no effect.') + end + + def protocol_separator=(value) + ActiveSupport::Deprecation.warn('protocol_separator= has been deprecated and has no effect.') + end + + def bad_tags + ActiveSupport::Deprecation.warn('The bad_tags class attribute has been deprecated and has no effect. You can still affect the tags being sanitized using bad_tags= which changes the allowed_tags.') + end + + def bad_tags=(tags) + allowed_tags.replace(allowed_tags - tags) + end + end + + [:uri_attributes, :allowed_attributes, + :allowed_tags, :allowed_protocols, :allowed_css_properties, + :allowed_css_keywords, :shorthand_css_properties].each do |attr| + class_attribute attr, :instance_writer => false + + define_method "#{self}.update_#{attr}" do |arg| + attr.merge arg + end + end + + # Constants are from Loofahs source at lib/loofah/html5/whitelist.rb + self.uri_attributes = Loofah::HTML5::WhiteList::ATTR_VAL_IS_URI + + self.allowed_tags = Loofah::HTML5::WhiteList::ALLOWED_ELEMENTS + + self.bad_tags = Set.new %w(script) + + self.allowed_attributes = Loofah::HTML5::WhiteList::ALLOWED_ATTRIBUTES + + self.allowed_css_properties = Loofah::HTML5::WhiteList::ALLOWED_CSS_PROPERTIES + + self.allowed_css_keywords = Loofah::HTML5::WhiteList::ALLOWED_CSS_KEYWORDS + + self.shorthand_css_properties = Loofah::HTML5::WhiteList::SHORTHAND_CSS_PROPERTIES + + self.allowed_protocols = Loofah::HTML5::WhiteList::ALLOWED_PROTOCOLS + + protected + def validate_options(options) + if options[:tags] && !options[:tags].is_a?(Enumerable) + raise ArgumentError, "You should pass :tags as an Enumerable" + end + + if options[:attributes] && !options[:attributes].is_a?(Enumerable) + raise ArgumentError, "You should pass :attributes as an Enumerable" + end + end + + def contains_bad_protocols?(attr_name, value) + protocol_separator = ':' + self.uri_attributes.include?(attr_name) && + (value =~ /(^[^\/:]*):|(�*58)|(p)|(�*3a)|(%|%)3A/i && !self.allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip)) + end + end +end -- cgit v1.2.3 From d4d13925d3c0510ac6a08d1478e47d2135864ac6 Mon Sep 17 00:00:00 2001 From: Timm Date: Tue, 2 Jul 2013 13:47:24 +0200 Subject: Removed duplication in the deprecated methods. --- .../lib/action_view/helpers/sanitize_helper/sanitizers.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 74be525581..91de4c8ba1 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -40,15 +40,15 @@ module ActionView end def protocol_separator - ActiveSupport::Deprecation.warn('protocol_separator has been deprecated and has no effect.') + self.class.protocol_separator end def protocol_separator=(value) - ActiveSupport::Deprecation.warn('protocol_separator= has been deprecated and has no effect.') + self.class.protocol_separator end def bad_tags - ActiveSupport::Deprecation.warn('bad_tags has been deprecated and has no effect.') + self.class.bad_tags end class << self @@ -57,11 +57,11 @@ module ActionView end def protocol_separator=(value) - ActiveSupport::Deprecation.warn('protocol_separator= has been deprecated and has no effect.') + protocol_separator end def bad_tags - ActiveSupport::Deprecation.warn('The bad_tags class attribute has been deprecated and has no effect. You can still affect the tags being sanitized using bad_tags= which changes the allowed_tags.') + ActiveSupport::Deprecation.warn('bad_tags has been deprecated and has no effect. You can still affect the tags being sanitized using ActionView::WhiteListSanitizer.bad_tags= which changes the allowed_tags.') end def bad_tags=(tags) -- cgit v1.2.3 From 2622da17585a58fc75d3f9b5fc80eb03930fa156 Mon Sep 17 00:00:00 2001 From: Timm Date: Tue, 2 Jul 2013 20:16:10 +0200 Subject: Added PermitScrubber which allows you to permit elements for sanitization. --- .../helpers/sanitize_helper/permit_scrubber.rb | 71 ++++++++++++++++++++++ .../helpers/sanitize_helper/sanitizers.rb | 27 ++++---- 2 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb b/actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb new file mode 100644 index 0000000000..749bef23fc --- /dev/null +++ b/actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb @@ -0,0 +1,71 @@ +# === PermitScrubber +# +# PermitScrubber allows you to permit only your own tags and/or attributes. +# +# Supplied tags and attributes should be Enumerables +# +# +tags=+ +# If this value is set all other elements will be stripped (their inner elements will be kept). +# If not set elements for which HTML5::Scrub.allowed_element? is false will be stripped. +# +# +attributes=+ +# Contain an elements allowed attributes. +# If none is set HTML5::Scrub.scrub_attributes implementation will be used. +class PermitScrubber < Loofah::Scrubber + attr_reader :tags, :attributes + + def tags=(tags) + @tags = validate!(tags, :tags) + end + + def attributes=(attributes) + @attributes = validate!(attributes, :attributes) + end + + def scrub(node) + return CONTINUE if text_or_cdata_node?(node) + + unless allowed_node?(node) + node.before node.children # strip + node.remove + return STOP + end + + scrub_attributes(node) + end + + protected + + def allowed_node?(node) + if @tags + @tags.include?(node.name) + else + Loofah::HTML5::Scrub.allowed_element?(node.name) + end + end + + def scrub_attributes(node) + if @attributes + node.attributes.each do |name, _| + node.remove_attribute(name) unless @attributes.include?(name) + end + else + Loofah::HTML5::Scrub.scrub_attributes(node) + end + end + + def text_or_cdata_node?(node) + case node.type + when Nokogiri::XML::Node::TEXT_NODE, Nokogiri::XML::Node::CDATA_SECTION_NODE + return true + end + false + end + + def validate!(var, name) + if var && !var.is_a?(Enumerable) + raise ArgumentError, "You should pass :#{name} as an Enumerable" + end + var + end +end diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 91de4c8ba1..cbddf3481c 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/class/attribute' require 'active_support/deprecation' +require 'action_view/helpers/sanitize_helper/permit_scrubber' require 'loofah' module ActionView @@ -25,13 +26,23 @@ module ActionView end class WhiteListSanitizer + + def initialize + @permit_scrubber = PermitScrubber.new + end + def sanitize(html, options = {}) return nil unless html - validate_options(options) loofah_fragment = Loofah.fragment(html) - loofah_fragment.scrub!(:strip) - loofah_fragment.xpath("./form").each { |form| form.remove } + if options[:tags] || options[:attributes] + @permit_scrubber.tags = options[:tags] + @permit_scrubber.attributes = options[:attributes] + loofah_fragment.scrub!(@permit_scrubber) + else + loofah_fragment.scrub!(:strip) + loofah_fragment.xpath("./form").each { |form| form.remove } + end loofah_fragment.to_s end @@ -97,16 +108,6 @@ module ActionView self.allowed_protocols = Loofah::HTML5::WhiteList::ALLOWED_PROTOCOLS protected - def validate_options(options) - if options[:tags] && !options[:tags].is_a?(Enumerable) - raise ArgumentError, "You should pass :tags as an Enumerable" - end - - if options[:attributes] && !options[:attributes].is_a?(Enumerable) - raise ArgumentError, "You should pass :attributes as an Enumerable" - end - end - def contains_bad_protocols?(attr_name, value) protocol_separator = ':' self.uri_attributes.include?(attr_name) && -- cgit v1.2.3 From 3e4ae8e5a21e1460bf0674211aef8d539c065701 Mon Sep 17 00:00:00 2001 From: Timm Date: Tue, 2 Jul 2013 21:54:34 +0200 Subject: Reordered form removal with stripping. --- .../lib/action_view/helpers/sanitize_helper/sanitizers.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index cbddf3481c..f70b47f32a 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -14,9 +14,12 @@ module ActionView class LinkSanitizer def initialize @link_scrubber = Loofah::Scrubber.new do |node| - next unless node.name == 'a' - node.before node.children - node.remove + if node.name == 'a' + node.before node.children + node.remove + else + Loofah::HTML5::Scrub.scrub_attributes(node) + end end end @@ -40,8 +43,8 @@ module ActionView @permit_scrubber.attributes = options[:attributes] loofah_fragment.scrub!(@permit_scrubber) else - loofah_fragment.scrub!(:strip) loofah_fragment.xpath("./form").each { |form| form.remove } + loofah_fragment.scrub!(:strip) end loofah_fragment.to_s end -- cgit v1.2.3 From 167e998f6128f2a04170181030fceb21047f7b79 Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 3 Jul 2013 19:55:52 +0200 Subject: Removed the contains_bad_protocols? method as well as the tests for it. Loofah already deals with this. --- .../lib/action_view/helpers/sanitize_helper/sanitizers.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index f70b47f32a..0bc4be6558 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -13,8 +13,9 @@ module ActionView class LinkSanitizer def initialize + @strip_tags = %w(a href) @link_scrubber = Loofah::Scrubber.new do |node| - if node.name == 'a' + if @strip_tags.include?(node.name) node.before node.children node.remove else @@ -109,12 +110,5 @@ module ActionView self.shorthand_css_properties = Loofah::HTML5::WhiteList::SHORTHAND_CSS_PROPERTIES self.allowed_protocols = Loofah::HTML5::WhiteList::ALLOWED_PROTOCOLS - - protected - def contains_bad_protocols?(attr_name, value) - protocol_separator = ':' - self.uri_attributes.include?(attr_name) && - (value =~ /(^[^\/:]*):|(�*58)|(p)|(�*3a)|(%|%)3A/i && !self.allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip)) - end end end -- cgit v1.2.3 From 91712cc11ded5c5f042784e098a10e1dca82e0d5 Mon Sep 17 00:00:00 2001 From: Timm Date: Thu, 4 Jul 2013 16:55:42 +0200 Subject: bad_tags include form since we remove it. Also to prevent a should_allow_form_tag test creation. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 0bc4be6558..335280c718 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -99,7 +99,7 @@ module ActionView self.allowed_tags = Loofah::HTML5::WhiteList::ALLOWED_ELEMENTS - self.bad_tags = Set.new %w(script) + self.bad_tags = Set.new %w(script form) self.allowed_attributes = Loofah::HTML5::WhiteList::ALLOWED_ATTRIBUTES -- cgit v1.2.3 From 5dfd394c4da8873d14c2055d06044844eb78ba55 Mon Sep 17 00:00:00 2001 From: Timm Date: Sun, 7 Jul 2013 10:35:52 +0200 Subject: Added guard clauses to FullSanitizer. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 335280c718..9d4e5b8e38 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -7,7 +7,12 @@ module ActionView class FullSanitizer def sanitize(html, options = {}) - Loofah.fragment(html).text + if html + return html if html.empty? + Loofah.fragment(html).text + else + nil + end end end -- cgit v1.2.3 From 55b453f2959ee176611732fa22b386916e9a9604 Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 10 Jul 2013 15:57:01 +0200 Subject: Added removal of script tags to WhiteListSanitizer. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 9d4e5b8e38..8b1e76fec1 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -49,6 +49,7 @@ module ActionView @permit_scrubber.attributes = options[:attributes] loofah_fragment.scrub!(@permit_scrubber) else + loofah_fragment.xpath("./script").each { |script| script.remove } loofah_fragment.xpath("./form").each { |form| form.remove } loofah_fragment.scrub!(:strip) end -- cgit v1.2.3 From 68f75b9795f1d9c3fc30f54e035d01d6d687d4fa Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 10 Jul 2013 16:00:36 +0200 Subject: Extracted the xpath removals into some new API that allows users to remove xpath subtrees. --- .../lib/action_view/helpers/sanitize_helper/sanitizers.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 8b1e76fec1..187d0ffbfa 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -49,8 +49,7 @@ module ActionView @permit_scrubber.attributes = options[:attributes] loofah_fragment.scrub!(@permit_scrubber) else - loofah_fragment.xpath("./script").each { |script| script.remove } - loofah_fragment.xpath("./form").each { |form| form.remove } + remove_xpaths(loofah_fragment, %w(./script ./form)) loofah_fragment.scrub!(:strip) end loofah_fragment.to_s @@ -60,6 +59,13 @@ module ActionView Loofah::HTML5::Scrub.scrub_css style_string end + def remove_xpaths(html, *xpaths) + html = Loofah.fragment(html) unless html.is_a? Nokogiri::XML::DocumentFragment + xpaths.each do |xpath| + html.xpath(xpath).each { |subtree| subtree.remove } + end + end + def protocol_separator self.class.protocol_separator end -- cgit v1.2.3 From 40bbb4914f7158ec070d7249c527217d95f74f4c Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 10 Jul 2013 16:27:39 +0200 Subject: Added comment removal. Changed definitation of remove_xpaths to not use a splat operator. --- .../helpers/sanitize_helper/sanitizers.rb | 35 +++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 187d0ffbfa..eb353d79e2 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -4,19 +4,32 @@ require 'action_view/helpers/sanitize_helper/permit_scrubber' require 'loofah' module ActionView - - class FullSanitizer + class Sanitizer def sanitize(html, options = {}) - if html - return html if html.empty? - Loofah.fragment(html).text - else - nil + raise NotImplementedError, "subclasses must implement" + end + + def remove_xpaths(html, xpaths) + html = Loofah.fragment(html) unless html.is_a? Nokogiri::XML::DocumentFragment + xpaths.each do |xpath| + html.xpath(xpath).each { |subtree| subtree.remove } end + html.to_s + end + end + + class FullSanitizer < Sanitizer + def sanitize(html, options = {}) + return nil unless html + return html if html.empty? + + fragment = Loofah.fragment(html) + remove_xpaths(fragment, %w{.//script .//form comment()}) + fragment.text end end - class LinkSanitizer + class LinkSanitizer < Sanitizer def initialize @strip_tags = %w(a href) @link_scrubber = Loofah::Scrubber.new do |node| @@ -34,7 +47,7 @@ module ActionView end end - class WhiteListSanitizer + class WhiteListSanitizer < Sanitizer def initialize @permit_scrubber = PermitScrubber.new @@ -49,7 +62,7 @@ module ActionView @permit_scrubber.attributes = options[:attributes] loofah_fragment.scrub!(@permit_scrubber) else - remove_xpaths(loofah_fragment, %w(./script ./form)) + remove_xpaths(loofah_fragment, %w{.//script .//form comment()}) loofah_fragment.scrub!(:strip) end loofah_fragment.to_s @@ -59,7 +72,7 @@ module ActionView Loofah::HTML5::Scrub.scrub_css style_string end - def remove_xpaths(html, *xpaths) + def remove_xpaths(html, xpaths) html = Loofah.fragment(html) unless html.is_a? Nokogiri::XML::DocumentFragment xpaths.each do |xpath| html.xpath(xpath).each { |subtree| subtree.remove } -- cgit v1.2.3 From 4fbec83ff423c2bad37f9a4369c15efbaae55d43 Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 10 Jul 2013 16:55:38 +0200 Subject: Added ActionView::Sanitizer and moved remove_xpaths to there. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 7 ------- 1 file changed, 7 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index eb353d79e2..21d13412c1 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -72,13 +72,6 @@ module ActionView Loofah::HTML5::Scrub.scrub_css style_string end - def remove_xpaths(html, xpaths) - html = Loofah.fragment(html) unless html.is_a? Nokogiri::XML::DocumentFragment - xpaths.each do |xpath| - html.xpath(xpath).each { |subtree| subtree.remove } - end - end - def protocol_separator self.class.protocol_separator end -- cgit v1.2.3 From c88d573739186c344f39a068a6f972804b17efe8 Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 10 Jul 2013 20:15:22 +0200 Subject: Moved requiring of Loofah from sanitizers.rb to action_view.rb. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 21d13412c1..af0aa12349 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -1,7 +1,6 @@ require 'active_support/core_ext/class/attribute' require 'active_support/deprecation' require 'action_view/helpers/sanitize_helper/permit_scrubber' -require 'loofah' module ActionView class Sanitizer -- cgit v1.2.3 From 6241bb8cf45979cc9ffaa916ed83e7cc6b48a38e Mon Sep 17 00:00:00 2001 From: Timm Date: Mon, 15 Jul 2013 21:54:43 +0200 Subject: Added ability to pass a custom scrubber to sanitize. Includes test coverage. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index af0aa12349..eab6d6a515 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -56,7 +56,9 @@ module ActionView return nil unless html loofah_fragment = Loofah.fragment(html) - if options[:tags] || options[:attributes] + if scrubber = options[:scrubber] # Loofah makes sure this is a scrubber + loofah_fragment.scrub!(scrubber) + elsif options[:tags] || options[:attributes] @permit_scrubber.tags = options[:tags] @permit_scrubber.attributes = options[:attributes] loofah_fragment.scrub!(@permit_scrubber) -- cgit v1.2.3 From 8fdf86c5f79e68a14ce4b052d6e89f09121868c6 Mon Sep 17 00:00:00 2001 From: Timm Date: Mon, 15 Jul 2013 22:02:32 +0200 Subject: Marked the private API as not needing code documentation. --- actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb | 1 + actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 1 + 2 files changed, 2 insertions(+) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb b/actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb index 749bef23fc..60c74ed35e 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb @@ -12,6 +12,7 @@ # Contain an elements allowed attributes. # If none is set HTML5::Scrub.scrub_attributes implementation will be used. class PermitScrubber < Loofah::Scrubber + # :nodoc: attr_reader :tags, :attributes def tags=(tags) diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index eab6d6a515..c014219485 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -4,6 +4,7 @@ require 'action_view/helpers/sanitize_helper/permit_scrubber' module ActionView class Sanitizer + # :nodoc: def sanitize(html, options = {}) raise NotImplementedError, "subclasses must implement" end -- cgit v1.2.3 From dad96eff0df2adbcef91360387bcc323bc76faea Mon Sep 17 00:00:00 2001 From: Timm Date: Mon, 15 Jul 2013 22:10:03 +0200 Subject: Updated the documentation to reflect the scrubber option. --- .../lib/action_view/helpers/sanitize_helper.rb | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 66894b5936..a04c7a56b5 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -27,7 +27,28 @@ module ActionView # # <%= sanitize @article.body %> # - # Custom Use (only the mentioned tags and attributes are allowed, nothing else) + # Custom Use - Custom Scrubber + # (supply a Loofah::Scrubber that does the sanitization) + # + # scrubber can either wrap a block: + # scrubber = Loofah::Scrubber.new do |node| + # node.text = "dawn of cats" + # end + # + # or be a subclass of Loofah::Scrubber which responds to scrub: + # class KittyApocalypse < Loofah::Scrubber + # def scrub(node) + # node.text = "dawn of cats" + # end + # end + # scrubber = KittyApocalypse.new + # + # <%= sanitize @article.body, scrubber: scrubber %> + # + # Learn more about scrubbers here: https://github.com/flavorjones/loofah + # + # Custom Use - tags and attributes + # (only the mentioned tags and attributes are allowed, nothing else) # # <%= sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) %> # -- cgit v1.2.3 From 42f0198148850564e53a1515a19d8a7f00f5fdd2 Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 17 Jul 2013 18:44:29 +0200 Subject: Updated documentation to tell that a custom scrubber takes precedence. --- actionview/lib/action_view/helpers/sanitize_helper.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index a04c7a56b5..4d2c6e64d9 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -45,6 +45,7 @@ module ActionView # # <%= sanitize @article.body, scrubber: scrubber %> # + # A custom scrubber takes precedence over custom tags and attributes # Learn more about scrubbers here: https://github.com/flavorjones/loofah # # Custom Use - tags and attributes -- cgit v1.2.3 From 37ac1c45a3a95e6d1eb01c7cf1f8dd0850f12de8 Mon Sep 17 00:00:00 2001 From: Timm Date: Tue, 23 Jul 2013 16:34:32 +0200 Subject: Replaced html-scanner with Loofah. --- actionview/lib/action_view/test_case.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 9e8e6f43d5..112923adf9 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -155,7 +155,7 @@ module ActionView # # Need to experiment if this priority is the best one: rendered => output_buffer def response_from_page - HTML::Document.new(@rendered.blank? ? @output_buffer : @rendered).root + Loofah.document(@rendered.blank? ? @output_buffer : @rendered).root end def say_no_to_protect_against_forgery! -- cgit v1.2.3 From 95c517b6d6c13bfff2a020b2a29ec5c9bacfebf3 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 2 Aug 2013 17:01:54 +0200 Subject: Moved Dom and Selector assertions from ActionDispatch to ActionView. --- actionview/lib/action_view/test_case.rb | 1 + actionview/lib/action_view/testing/assertions.rb | 11 + .../lib/action_view/testing/assertions/dom.rb | 72 ++++ .../lib/action_view/testing/assertions/selector.rb | 465 +++++++++++++++++++++ 4 files changed, 549 insertions(+) create mode 100644 actionview/lib/action_view/testing/assertions.rb create mode 100644 actionview/lib/action_view/testing/assertions/dom.rb create mode 100644 actionview/lib/action_view/testing/assertions/selector.rb (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 112923adf9..7415bfd684 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -34,6 +34,7 @@ module ActionView extend ActiveSupport::Concern include ActionDispatch::Assertions, ActionDispatch::TestProcess + include ActionView::Assertions include ActionController::TemplateAssertions include ActionView::Context diff --git a/actionview/lib/action_view/testing/assertions.rb b/actionview/lib/action_view/testing/assertions.rb new file mode 100644 index 0000000000..9714d3510d --- /dev/null +++ b/actionview/lib/action_view/testing/assertions.rb @@ -0,0 +1,11 @@ +module ActionView + module Assertions + autoload :DomAssertions, 'action_view/testing/assertions/dom' + autoload :SelectorAssertions, 'action_view/testing/assertions/selector' + + extend ActiveSupport::Concern + + include DomAssertions + include SelectorAssertions + end +end \ No newline at end of file diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb new file mode 100644 index 0000000000..56edf48770 --- /dev/null +++ b/actionview/lib/action_view/testing/assertions/dom.rb @@ -0,0 +1,72 @@ +module ActionView + module Assertions + module DomAssertions + # \Test two HTML strings for equivalency (e.g., equal even when attributes are in another order) + # + # # assert that the referenced method generates the appropriate HTML string + # assert_dom_equal 'Apples', link_to("Apples", "http://www.example.com") + def assert_dom_equal(expected, actual, message = nil) + expected_dom, actual_dom = doms_from_strings(expected, actual) + message ||= "Expected: #{expected_dom}\nActual: #{actual_dom}" + assert compare_doms(expected_dom, actual_dom), message + end + + # The negated form of +assert_dom_equal+. + # + # # assert that the referenced method does not generate the specified HTML string + # assert_dom_not_equal 'Apples', link_to("Oranges", "http://www.example.com") + def assert_dom_not_equal(expected, actual, message = nil) + expected_dom, actual_dom = doms_from_strings(expected, actual) + message ||= "Expected: #{expected_dom}\nActual: #{actual_dom}" + assert_not compare_doms(expected_dom, actual_dom), message + end + + protected + # +doms_from_strings+ creates a Loofah::HTML::DocumentFragment for every string in strings + def doms_from_strings(*strings) + strings.map { |str| Loofah.fragment(str) } + end + + # +compare_doms+ takes two doms loops over all their children and compares each child via +equal_children?+ + def compare_doms(expected, actual) + expected.children.each_with_index do |child, i| + return false unless equal_children?(child, actual.children[i]) + end + true + end + + # +equal_children?+ compares children according to their type + # Determines further comparison via said type + # i.e. element node children with equal names has their attributes compared using +attributes_are_equal?+ + def equal_children?(child, other_child) + return false unless child.type == other_child.type + + case child.type + when Nokogiri::XML::Node::ELEMENT_NODE + child.name == other_child.name && attributes_are_equal?(child, other_child) + else + child.to_s == other_child.to_s + end + end + + # +attributes_are_equal?+ sorts elements attributes by name and compares + # each attribute by calling +equal_attribute?+ + # If those are +true+ the attributes are considered equal + def attributes_are_equal?(element, other_element) + first_nodes = element.attribute_nodes.sort_by { |a| a.name } + other_nodes = other_element.attribute_nodes.sort_by { |a| a.name } + + return false unless first_nodes.size == other_nodes.size + first_nodes.each_with_index do |attr, i| + return false unless equal_attribute?(attr, other_nodes[i]) + end + true + end + + # +equal_attribute?+ compares attributes by their name and value + def equal_attribute?(attr, other_attr) + attr.name == other_attr.name && attr.value == other_attr.value + end + end + end +end diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb new file mode 100644 index 0000000000..bc8f40a245 --- /dev/null +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -0,0 +1,465 @@ +require 'active_support/core_ext/object/inclusion' + +#-- +# Copyright (c) 2006 Assaf Arkin (http://labnotes.org) +# Under MIT and/or CC By license. +#++ + +module ActionView + module Assertions + NO_STRIP = %w{pre script style textarea} + + # Adds the +assert_select+ method for use in Rails functional + # test cases, which can be used to make assertions on the response HTML of a controller + # action. You can also call +assert_select+ within another +assert_select+ to + # make assertions on elements selected by the enclosing assertion. + # + # Use +css_select+ to select elements without making an assertions, either + # from the response HTML or elements selected by the enclosing assertion. + # + # In addition to HTML responses, you can make the following assertions: + # + # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions. + # * +assert_select_email+ - Assertions on the HTML body of an e-mail. + module SelectorAssertions + # Select and return all matching elements. + # + # If called with a single argument, uses that argument as a selector + # to match all elements of the current page. Returns an empty + # Nokogiri::XML::NodeSet if no match is found. + # + # If called with two arguments, uses the first argument as the root + # element and the second argument as the selector. Attempts to match the + # root element and any of its children. + # Returns an empty Nokogiri::XML::NodeSet if no match is found. + # + # The selector may be a CSS selector expression (String). + # + # # Selects all div tags + # divs = css_select("div") + # + # # Selects all paragraph tags and does something interesting + # pars = css_select("p") + # pars.each do |par| + # # Do something fun with paragraphs here... + # end + # + # # Selects all list items in unordered lists + # items = css_select("ul>li") + # + # # Selects all form tags and then all inputs inside the form + # forms = css_select("form") + # forms.each do |form| + # inputs = css_select(form, "input") + # ... + # end + def css_select(*args) + raise ArgumentError, "you at least need a selector" if args.empty? + + if args.first.is_a?(String) + root, selector = response_from_page, args.first + else + root, selector = args.shift, args.first + end + + root.css(selector).tap do |matches| + if matches.empty? && root.matches?(selector) + return Nokogiri::XML::NodeSet.new(root.document, [root]) + end + end + end + + # An assertion that selects elements and makes one or more equality tests. + # + # If the first argument is an element, selects all matching elements + # starting from (and including) that element and all its children in + # depth-first order. + # + # If no element is specified, calling +assert_select+ selects from the + # response HTML unless +assert_select+ is called from within an +assert_select+ block. + # + # When called with a block +assert_select+ passes an array of selected elements + # to the block. Calling +assert_select+ from the block, with no element specified, + # runs the assertion on the complete set of elements selected by the enclosing assertion. + # Alternatively the array may be iterated through so that +assert_select+ can be called + # separately for each element. + # + # + # ==== Example + # If the response contains two ordered lists, each with four list elements then: + # assert_select "ol" do |elements| + # elements.each do |element| + # assert_select element, "li", 4 + # end + # end + # + # will pass, as will: + # assert_select "ol" do + # assert_select "li", 8 + # end + # + # The selector may be a CSS selector expression (String) or an expression + # with substitution values (Array). + # Substitution uses a custom pseudo class match. Pass in whatever attribute you want to match (enclosed in quotes) and a ? for the substitution. + # + # assert_select "div:match('id', ?)", /\d+/ + # + # === Equality Tests + # + # The equality test may be one of the following: + # * true - Assertion is true if at least one element selected. + # * false - Assertion is true if no element selected. + # * String/Regexp - Assertion is true if the text value of at least + # one element matches the string or regular expression. + # * Integer - Assertion is true if exactly that number of + # elements are selected. + # * Range - Assertion is true if the number of selected + # elements fit the range. + # If no equality test specified, the assertion is true if at least one + # element selected. + # + # To perform more than one equality tests, use a hash with the following keys: + # * :text - Narrow the selection to elements that have this text + # value (string or regexp). + # * :html - Narrow the selection to elements that have this HTML + # content (string or regexp). + # * :count - Assertion is true if the number of selected elements + # is equal to this value. + # * :minimum - Assertion is true if the number of selected + # elements is at least this value. + # * :maximum - Assertion is true if the number of selected + # elements is at most this value. + # + # If the method is called with a block, once all equality tests are + # evaluated the block is called with an array of all matched elements. + # + # # At least one form element + # assert_select "form" + # + # # Form element includes four input fields + # assert_select "form input", 4 + # + # # Page title is "Welcome" + # assert_select "title", "Welcome" + # + # # Page title is "Welcome" and there is only one title element + # assert_select "title", {count: 1, text: "Welcome"}, + # "Wrong title or more than one title element" + # + # # Page contains no forms + # assert_select "form", false, "This page must contain no forms" + # + # # Test the content and style + # assert_select "body div.header ul.menu" + # + # # Use substitution values + # assert_select "ol>li:match('id', ?)", /item-\d+/ + # + # # All input fields in the form have a name + # assert_select "form input" do + # assert_select ":match('name', ?)", /.+/ # Not empty + # end + def assert_select(*args, &block) + @selected ||= nil + + selector = HTMLSelector.new(@selected, response_from_page, args) + + matches = selector.select + assert_size_match!(matches.size, selector.comparisons, selector.source, selector.message) + + # Set @selected to allow nested assert_select. + # Can be nested several levels deep. + if block_given? && !matches.empty? + begin + in_scope, @selected = @selected, matches + yield matches + ensure + @selected = in_scope + end + end + + matches + end + + def count_description(min, max, count) #:nodoc: + pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')} + + if min && max && (max != min) + "between #{min} and #{max} elements" + elsif min && max && max == min && count + "exactly #{count} #{pluralize['element', min]}" + elsif min && !(min == 1 && max == 1) + "at least #{min} #{pluralize['element', min]}" + elsif max + "at most #{max} #{pluralize['element', max]}" + end + end + + # Extracts the content of an element, treats it as encoded HTML and runs + # nested assertion on it. + # + # You typically call this method within another assertion to operate on + # all currently selected elements. You can also pass an element or array + # of elements. + # + # The content of each element is un-encoded, and wrapped in the root + # element +encoded+. It then calls the block with all un-encoded elements. + # + # # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix) + # assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do + # # Select each entry item and then the title item + # assert_select "entry>title" do + # # Run assertions on the encoded title elements + # assert_select_encoded do + # assert_select "b" + # end + # end + # end + # + # + # # Selects all paragraph tags from within the description of an RSS feed + # assert_select "rss[version=2.0]" do + # # Select description element of each feed item. + # assert_select "channel>item>description" do + # # Run assertions on the encoded elements. + # assert_select_encoded do + # assert_select "p" + # end + # end + # end + def assert_select_encoded(element = nil, &block) + case element + when Array + elements = element + when Nokogiri::XML::Node + elements = [element] + when nil + unless elements = @selected + raise ArgumentError, "First argument is optional, but must be called from a nested assert_select" + end + else + raise ArgumentError, "Argument is optional, and may be node or array of nodes" + end + + content = elements.map do |elem| + elem.children.select(&:cdata?).map(&:content) + end.join + selected = Loofah.fragment(content) + + begin + old_selected, @selected = @selected, selected + if content.empty? + yield selected + else + assert_select ":root", &block + end + ensure + @selected = old_selected + end + end + + # Extracts the body of an email and runs nested assertions on it. + # + # You must enable deliveries for this assertion to work, use: + # ActionMailer::Base.perform_deliveries = true + # + # assert_select_email do + # assert_select "h1", "Email alert" + # end + # + # assert_select_email do + # items = assert_select "ol>li" + # items.each do + # # Work with items here... + # end + # end + def assert_select_email(&block) + deliveries = ActionMailer::Base.deliveries + assert !deliveries.empty?, "No e-mail in delivery list" + + deliveries.each do |delivery| + (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part| + if part["Content-Type"].to_s =~ /^text\/html\W/ + root = Loofah.fragment(part.body.to_s) + assert_select root, ":root", &block + end + end + end + end + + protected + # +equals+ must contain :minimum, :maximum and :count keys + def assert_size_match!(size, equals, css_selector, message = nil) + min, max, count = equals[:minimum], equals[:maximum], equals[:count] + + message ||= %(Expected #{count_description(min, max, count)} matching "#{css_selector}", found #{size}.) + if count + assert_equal size, count, message + else + assert_operator size, :>=, min, message if min + assert_operator size, :<=, max, message if max + end + end + + # +html_document+ is used in testing/integration.rb + def html_document + @html_document ||= if @response.content_type =~ /xml$/ + Loofah.xml_document(@response.body) + else + Loofah.document(@response.body) + end + end + + def response_from_page + html_document.root + end + + class HTMLSelector #:nodoc: + attr_accessor :root, :css_selector, :comparisons, :message + + alias :source :css_selector + + def initialize(selected, page, args) + @selected, @page = selected, page + + # Start with optional element followed by mandatory selector. + @root = determine_root_from(args.first) + + # First or second argument is the selector + selector = @css_selector_is_second_argument ? args.shift(2).last : args.shift + @css_selector = selector_from(selector, args) + + # Next argument is used for equality tests. + @comparisons = comparisons_from(args.shift) + + # Last argument is the message we use if the assertion fails. + @message = args.shift + + if args.shift + raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type" + end + end + + def select + filter root.css(css_selector, context) + end + + def filter(matches) + match_with = comparisons[:text] || comparisons[:html] + return matches if matches.empty? || !match_with + + content_mismatch = nil + text_matches = comparisons.has_key?(:text) + + remaining = matches.reject do |match| + # Preserve markup with to_s for html elements + content = text_matches ? match.text : match.children.to_s + + content.strip! unless NO_STRIP.include?(match.name) + content.sub!(/\A\n/, '') if text_matches && match.name == "textarea" + + unless match_with.is_a?(Regexp) ? (content =~ match_with) : (content == match_with.to_s) + content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, content) + true + end + end + + # Expecting foo found bar element only if found zero, not if + # found one but expecting two. + self.message ||= content_mismatch if remaining.empty? + + Nokogiri::XML::NodeSet.new(matches.document, remaining) + end + + def determine_root_from(root_or_selector) + @css_selector_is_second_argument = false + if root_or_selector == nil + raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?" + elsif root_or_selector.is_a?(Nokogiri::XML::Node) || root_or_selector.is_a?(Nokogiri::XML::NodeSet) + # First argument is a node (tag or text, but also HTML root), + # so we know what we're selecting from. + @css_selector_is_second_argument = true + + root_or_selector + elsif @selected + if @selected.is_a?(Array) + doc = @selected.empty? ? @page.document : @selected[0].document + @selected = Nokogiri::XML::NodeSet.new(doc, @selected) + end + @selected + else + @page + end + end + + def selector_from(selector, substitution_values) + unless selector.is_a? String + raise ArgumentError, "Expecting a selector as the first argument" + end + context.substitute!(selector, substitution_values) + end + + def comparisons_from(comparator) + comparisons = {} + case comparator + when Hash + comparisons = comparator + when String, Regexp + comparisons[:text] = comparator + when Integer + comparisons[:count] = comparator + when Range + comparisons[:minimum] = comparator.begin + comparisons[:maximum] = comparator.end + when FalseClass + comparisons[:count] = 0 + when NilClass, TrueClass + comparisons[:minimum] = 1 + else raise ArgumentError, "I don't understand what you're trying to match" + end + + # By default we're looking for at least one match. + if comparisons[:count] + comparisons[:minimum] = comparisons[:maximum] = comparisons[:count] + else + comparisons[:minimum] ||= 1 + end + comparisons + end + + def context + @context ||= SubstitutionContext.new + end + + class SubstitutionContext + def initialize(substitute = '?') + @substitute = substitute + @regexes = [] + end + + def add_regex(regex) + # Nokogiri doesn't like arbitrary values without quotes, hence inspect. + return regex.inspect unless regex.is_a?(Regexp) + @regexes.push(regex) + last_id.to_s # avoid implicit conversions of Fixnum to String + end + + def last_id + @regexes.count - 1 + end + + def match(matches, attribute, id) + matches.find_all { |node| node[attribute] =~ @regexes[id] } + end + + def substitute!(selector, values) + while !values.empty? && selector.index(@substitute) + selector.sub!(@substitute, add_regex(values.shift)) + end + selector + end + end + end + end + end +end -- cgit v1.2.3 From 09454dcca9ef4b274075aae5a4f84833605e9c54 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 2 Aug 2013 17:04:34 +0200 Subject: Removed require for active_support/core_ext/object/inclusion since in? isn't used anywhere. --- actionview/lib/action_view/testing/assertions/selector.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index bc8f40a245..3048c27b2d 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/inclusion' - #-- # Copyright (c) 2006 Assaf Arkin (http://labnotes.org) # Under MIT and/or CC By license. -- cgit v1.2.3 From dea8ddbca137db21a8e5570e357773a55c47b97e Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 2 Aug 2013 17:06:48 +0200 Subject: Removed copyright notice since we aren't relying on html-scanner anymore. --- actionview/lib/action_view/testing/assertions/selector.rb | 5 ----- 1 file changed, 5 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index 3048c27b2d..dc847e5ccf 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -1,8 +1,3 @@ -#-- -# Copyright (c) 2006 Assaf Arkin (http://labnotes.org) -# Under MIT and/or CC By license. -#++ - module ActionView module Assertions NO_STRIP = %w{pre script style textarea} -- cgit v1.2.3 From f428aeaa197aa5ec686d2f7103c0aadc883774d2 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 9 Aug 2013 18:12:25 +0200 Subject: Changed explanation for no duck typing of custom scrubbers. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index c014219485..99d4e64346 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -57,7 +57,8 @@ module ActionView return nil unless html loofah_fragment = Loofah.fragment(html) - if scrubber = options[:scrubber] # Loofah makes sure this is a scrubber + if scrubber = options[:scrubber] + # No duck typing, Loofah ensures subclass of Loofah::Scrubber loofah_fragment.scrub!(scrubber) elsif options[:tags] || options[:attributes] @permit_scrubber.tags = options[:tags] -- cgit v1.2.3 From 945e7f529e1c9cb2690a2bbe3374f883771a2cd1 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 9 Aug 2013 18:20:31 +0200 Subject: Refactored remove_xpaths to use duck typing and read better. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 99d4e64346..0e2e1826ec 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -10,11 +10,12 @@ module ActionView end def remove_xpaths(html, xpaths) - html = Loofah.fragment(html) unless html.is_a? Nokogiri::XML::DocumentFragment - xpaths.each do |xpath| - html.xpath(xpath).each { |subtree| subtree.remove } + if html.respond_to?(:xpath) + xpaths.each { |xpath| html.xpath(xpath).remove } + html.to_s + else + remove_xpaths(Loofah.fragment(html), xpaths) end - html.to_s end end -- cgit v1.2.3 From d1de087467b1d3a0bc08f6d372db33bf2dcc80d6 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 9 Aug 2013 22:10:28 +0200 Subject: Extracted the common xpaths to remove into XPATHS_TO_REMOVE. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 0e2e1826ec..3f2cdd53e6 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -3,6 +3,8 @@ require 'active_support/deprecation' require 'action_view/helpers/sanitize_helper/permit_scrubber' module ActionView + XPATHS_TO_REMOVE = %w{.//script .//form comment()} + class Sanitizer # :nodoc: def sanitize(html, options = {}) @@ -25,7 +27,7 @@ module ActionView return html if html.empty? fragment = Loofah.fragment(html) - remove_xpaths(fragment, %w{.//script .//form comment()}) + remove_xpaths(fragment, XPATHS_TO_REMOVE) fragment.text end end @@ -66,7 +68,7 @@ module ActionView @permit_scrubber.attributes = options[:attributes] loofah_fragment.scrub!(@permit_scrubber) else - remove_xpaths(loofah_fragment, %w{.//script .//form comment()}) + remove_xpaths(loofah_fragment, XPATHS_TO_REMOVE) loofah_fragment.scrub!(:strip) end loofah_fragment.to_s -- cgit v1.2.3 From 739ecdf753d35ba40e238aedec666bc5eeafb2cc Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 9 Aug 2013 22:13:05 +0200 Subject: Changed FullSanitizer sanitize to use tap method instead of temporary variable. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 3f2cdd53e6..3ba46ccaa9 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -26,9 +26,9 @@ module ActionView return nil unless html return html if html.empty? - fragment = Loofah.fragment(html) - remove_xpaths(fragment, XPATHS_TO_REMOVE) - fragment.text + Loofah.fragment(html).tap do |fragment| + remove_xpaths(fragment, XPATHS_TO_REMOVE) + end.text end end -- cgit v1.2.3 From 1cdc511b90eee7351f21359c32c6d13385846dbd Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 9 Aug 2013 23:18:37 +0200 Subject: Added LinkScrubber to remove duplication in LinkSanitizer. As such made PermitScrubber easier to subclass. --- .../helpers/sanitize_helper/permit_scrubber.rb | 72 ----------------- .../helpers/sanitize_helper/sanitizers.rb | 12 +-- .../helpers/sanitize_helper/scrubbers.rb | 91 ++++++++++++++++++++++ 3 files changed, 93 insertions(+), 82 deletions(-) delete mode 100644 actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb create mode 100644 actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb b/actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb deleted file mode 100644 index 60c74ed35e..0000000000 --- a/actionview/lib/action_view/helpers/sanitize_helper/permit_scrubber.rb +++ /dev/null @@ -1,72 +0,0 @@ -# === PermitScrubber -# -# PermitScrubber allows you to permit only your own tags and/or attributes. -# -# Supplied tags and attributes should be Enumerables -# -# +tags=+ -# If this value is set all other elements will be stripped (their inner elements will be kept). -# If not set elements for which HTML5::Scrub.allowed_element? is false will be stripped. -# -# +attributes=+ -# Contain an elements allowed attributes. -# If none is set HTML5::Scrub.scrub_attributes implementation will be used. -class PermitScrubber < Loofah::Scrubber - # :nodoc: - attr_reader :tags, :attributes - - def tags=(tags) - @tags = validate!(tags, :tags) - end - - def attributes=(attributes) - @attributes = validate!(attributes, :attributes) - end - - def scrub(node) - return CONTINUE if text_or_cdata_node?(node) - - unless allowed_node?(node) - node.before node.children # strip - node.remove - return STOP - end - - scrub_attributes(node) - end - - protected - - def allowed_node?(node) - if @tags - @tags.include?(node.name) - else - Loofah::HTML5::Scrub.allowed_element?(node.name) - end - end - - def scrub_attributes(node) - if @attributes - node.attributes.each do |name, _| - node.remove_attribute(name) unless @attributes.include?(name) - end - else - Loofah::HTML5::Scrub.scrub_attributes(node) - end - end - - def text_or_cdata_node?(node) - case node.type - when Nokogiri::XML::Node::TEXT_NODE, Nokogiri::XML::Node::CDATA_SECTION_NODE - return true - end - false - end - - def validate!(var, name) - if var && !var.is_a?(Enumerable) - raise ArgumentError, "You should pass :#{name} as an Enumerable" - end - var - end -end diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 3ba46ccaa9..905fa38446 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -1,6 +1,6 @@ require 'active_support/core_ext/class/attribute' require 'active_support/deprecation' -require 'action_view/helpers/sanitize_helper/permit_scrubber' +require 'action_view/helpers/sanitize_helper/scrubbers' module ActionView XPATHS_TO_REMOVE = %w{.//script .//form comment()} @@ -34,15 +34,7 @@ module ActionView class LinkSanitizer < Sanitizer def initialize - @strip_tags = %w(a href) - @link_scrubber = Loofah::Scrubber.new do |node| - if @strip_tags.include?(node.name) - node.before node.children - node.remove - else - Loofah::HTML5::Scrub.scrub_attributes(node) - end - end + @link_scrubber = LinkScrubber.new end def sanitize(html, options = {}) diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb new file mode 100644 index 0000000000..2dfe82efab --- /dev/null +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -0,0 +1,91 @@ +# === PermitScrubber +# +# PermitScrubber allows you to permit only your own tags and/or attributes. +# +# Supplied tags and attributes should be Enumerables +# +# +tags=+ +# If this value is set all other elements will be stripped (their inner elements will be kept). +# If not set elements for which HTML5::Scrub.allowed_element? is false will be stripped. +# +# +attributes=+ +# Contain an elements allowed attributes. +# If none is set HTML5::Scrub.scrub_attributes implementation will be used. +# +# Subclass PermitScrubber to provide your own definition of +# when a node is allowed and how attributes should be scrubbed. +class PermitScrubber < Loofah::Scrubber + # :nodoc: + attr_reader :tags, :attributes + + def tags=(tags) + @tags = validate!(tags, :tags) + end + + def attributes=(attributes) + @attributes = validate!(attributes, :attributes) + end + + def scrub(node) + return CONTINUE if should_skip_node?(node) + + unless allowed_node?(node) + node.before node.children # strip + node.remove + return STOP + end + + scrub_attributes(node) + end + + protected + + def allowed_node?(node) + if @tags + @tags.include?(node.name) + else + Loofah::HTML5::Scrub.allowed_element?(node.name) + end + end + + def scrub_attributes(node) + if @attributes + node.attributes.each do |name, _| + node.remove_attribute(name) unless @attributes.include?(name) + end + else + Loofah::HTML5::Scrub.scrub_attributes(node) + end + end + + def should_skip_node?(node) + text_or_cdata_node?(node) + end + + def text_or_cdata_node?(node) + case node.type + when Nokogiri::XML::Node::TEXT_NODE, Nokogiri::XML::Node::CDATA_SECTION_NODE + return true + end + false + end + + def validate!(var, name) + if var && !var.is_a?(Enumerable) + raise ArgumentError, "You should pass :#{name} as an Enumerable" + end + var + end +end + +# LinkScrubber overrides PermitScrubbers +allowed_node?+ to any nodes +# which names aren't a or href +class LinkScrubber < PermitScrubber + def initialize + @strip_tags = %w(a href) + end + + def allowed_node?(node) + !@strip_tags.include?(node.name) + end +end -- cgit v1.2.3 From ac0d778fe973d8eb63fe8ef2af82a165d94b432a Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 9 Aug 2013 23:38:14 +0200 Subject: Already killed off LinkScrubber. Changed it instead to be TargetScrubber, which is more general, while still allowing maximum code reuse. --- .../helpers/sanitize_helper/sanitizers.rb | 3 ++- .../helpers/sanitize_helper/scrubbers.rb | 26 +++++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 905fa38446..c6bbf5e3f7 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -34,7 +34,8 @@ module ActionView class LinkSanitizer < Sanitizer def initialize - @link_scrubber = LinkScrubber.new + @link_scrubber = TargetScrubber.new + @link_scrubber.tags = %w(a href) end def sanitize(html, options = {}) diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index 2dfe82efab..4751d84688 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -51,7 +51,7 @@ class PermitScrubber < Loofah::Scrubber def scrub_attributes(node) if @attributes node.attributes.each do |name, _| - node.remove_attribute(name) unless @attributes.include?(name) + node.remove_attribute(name) if should_remove_attributes?(name) end else Loofah::HTML5::Scrub.scrub_attributes(node) @@ -62,6 +62,10 @@ class PermitScrubber < Loofah::Scrubber text_or_cdata_node?(node) end + def should_remove_attributes?(name) + @attributes.exclude?(name) + end + def text_or_cdata_node?(node) case node.type when Nokogiri::XML::Node::TEXT_NODE, Nokogiri::XML::Node::CDATA_SECTION_NODE @@ -78,14 +82,20 @@ class PermitScrubber < Loofah::Scrubber end end -# LinkScrubber overrides PermitScrubbers +allowed_node?+ to any nodes -# which names aren't a or href -class LinkScrubber < PermitScrubber - def initialize - @strip_tags = %w(a href) +# TargetScrubber - The bizarro PermitScrubber +# +# With PermitScrubber you choose elements you don't want removed, +# with TargetScrubber you choose want you want gone. +# +# +tags=+ and +attributes=+ has the same behavior as PermitScrubber +# except they select what to get rid of. +class TargetScrubber < PermitScrubber + def allowed_node?(node) + return super unless @tags + @tags.exclude?(node.name) end - def allowed_node?(node) - !@strip_tags.include?(node.name) + def should_remove_attributes?(name) + @attributes.include?(name) end end -- cgit v1.2.3 From ea57c7cc85fd4b22e194a8ee8083c9d3d3767950 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 9 Aug 2013 23:45:40 +0200 Subject: Changed PermitScrubbers documentation to list override points for subclasses. Renamed should_remove_attributes? to should_scrub_attributes?. --- .../helpers/sanitize_helper/scrubbers.rb | 27 ++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index 4751d84688..3c8ed6f420 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -12,8 +12,11 @@ # Contain an elements allowed attributes. # If none is set HTML5::Scrub.scrub_attributes implementation will be used. # -# Subclass PermitScrubber to provide your own definition of -# when a node is allowed and how attributes should be scrubbed. +# Subclass PermitScrubber to provide your own definition of: +# +# When a node is allowed via +allowed_node?+ +# When a node should be skipped via +should_skip_node?+ +# Which attributes should be scrubbed via +should_scrub_attributes?+ class PermitScrubber < Loofah::Scrubber # :nodoc: attr_reader :tags, :attributes @@ -48,24 +51,24 @@ class PermitScrubber < Loofah::Scrubber end end + def should_skip_node?(node) + text_or_cdata_node?(node) + end + + def should_scrub_attributes?(name) + @attributes.exclude?(name) + end + def scrub_attributes(node) if @attributes node.attributes.each do |name, _| - node.remove_attribute(name) if should_remove_attributes?(name) + node.remove_attribute(name) if should_scrub_attributes?(name) end else Loofah::HTML5::Scrub.scrub_attributes(node) end end - def should_skip_node?(node) - text_or_cdata_node?(node) - end - - def should_remove_attributes?(name) - @attributes.exclude?(name) - end - def text_or_cdata_node?(node) case node.type when Nokogiri::XML::Node::TEXT_NODE, Nokogiri::XML::Node::CDATA_SECTION_NODE @@ -95,7 +98,7 @@ class TargetScrubber < PermitScrubber @tags.exclude?(node.name) end - def should_remove_attributes?(name) + def should_scrub_attributes?(name) @attributes.include?(name) end end -- cgit v1.2.3 From 557806f4022a5d49a5a651b703879f8a802a2db2 Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 10 Aug 2013 11:23:57 +0200 Subject: Changed PermitScrubber to be even more extensible. Updated TargetScrubber to be compliant. Updated documentation for PermitScrubber and TargetScrubber for clarity. --- .../helpers/sanitize_helper/scrubbers.rb | 79 +++++++++++----------- 1 file changed, 40 insertions(+), 39 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index 3c8ed6f420..1f2df3b108 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -2,21 +2,22 @@ # # PermitScrubber allows you to permit only your own tags and/or attributes. # +# 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?+ +# +# Text and CDATA nodes are skipped by defualt. +# Unallowed elements will be stripped, i.e. element is removed but its substree kept. # Supplied tags and attributes should be Enumerables # # +tags=+ -# If this value is set all other elements will be stripped (their inner elements will be kept). -# If not set elements for which HTML5::Scrub.allowed_element? is false will be stripped. +# If set, elements excluded will be stripped. +# If not, elements are stripped based on Loofahs +HTML5::Scrub.allowed_element?+ # # +attributes=+ -# Contain an elements allowed attributes. -# If none is set HTML5::Scrub.scrub_attributes implementation will be used. -# -# Subclass PermitScrubber to provide your own definition of: -# -# When a node is allowed via +allowed_node?+ -# When a node should be skipped via +should_skip_node?+ -# Which attributes should be scrubbed via +should_scrub_attributes?+ +# If set, attributes excluded will be removed. +# If not, attributes are removed based on Loofahs +HTML5::Scrub.scrub_attributes+ class PermitScrubber < Loofah::Scrubber # :nodoc: attr_reader :tags, :attributes @@ -30,13 +31,9 @@ class PermitScrubber < Loofah::Scrubber end def scrub(node) - return CONTINUE if should_skip_node?(node) + return CONTINUE if skip_node?(node) - unless allowed_node?(node) - node.before node.children # strip - node.remove - return STOP - end + return STOP if scrub_node(node) scrub_attributes(node) end @@ -44,39 +41,43 @@ class PermitScrubber < Loofah::Scrubber protected def allowed_node?(node) + @tags.include?(node.name) + end + + def skip_node?(node) + node.text? || node.cdata? + end + + def scrub_attribute?(name) + @attributes.exclude?(name) + end + + def keep_node?(node) if @tags - @tags.include?(node.name) + allowed_node?(node) else Loofah::HTML5::Scrub.allowed_element?(node.name) end end - def should_skip_node?(node) - text_or_cdata_node?(node) - end - - def should_scrub_attributes?(name) - @attributes.exclude?(name) + def scrub_node(node) + unless keep_node?(node) + node.before(node.children) # strip + node.remove + true + end end def scrub_attributes(node) if @attributes node.attributes.each do |name, _| - node.remove_attribute(name) if should_scrub_attributes?(name) + node.remove_attribute(name) if scrub_attribute?(name) end else Loofah::HTML5::Scrub.scrub_attributes(node) end end - def text_or_cdata_node?(node) - case node.type - when Nokogiri::XML::Node::TEXT_NODE, Nokogiri::XML::Node::CDATA_SECTION_NODE - return true - end - false - end - def validate!(var, name) if var && !var.is_a?(Enumerable) raise ArgumentError, "You should pass :#{name} as an Enumerable" @@ -85,20 +86,20 @@ class PermitScrubber < Loofah::Scrubber end end -# TargetScrubber - The bizarro PermitScrubber +# === TargetScrubber +# The Bizarro PermitScrubber # -# With PermitScrubber you choose elements you don't want removed, -# with TargetScrubber you choose want you want gone. +# +tags=+ +# If set, elements included will be stripped. # -# +tags=+ and +attributes=+ has the same behavior as PermitScrubber -# except they select what to get rid of. +# +attributes=+ +# If set, attributes included will be removed. class TargetScrubber < PermitScrubber def allowed_node?(node) - return super unless @tags @tags.exclude?(node.name) end - def should_scrub_attributes?(name) + def scrub_attribute?(name) @attributes.include?(name) end end -- cgit v1.2.3 From 39df4028a2816b96de3127d68d9c42687da4178e Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 10 Aug 2013 12:54:44 +0200 Subject: Refactored scrub to keep_node? instead of scrub_node calling it. Also added ability to stop traversing by returning STOP from scrub_node. --- .../lib/action_view/helpers/sanitize_helper/scrubbers.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index 1f2df3b108..07611e6927 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -33,7 +33,9 @@ class PermitScrubber < Loofah::Scrubber def scrub(node) return CONTINUE if skip_node?(node) - return STOP if scrub_node(node) + unless keep_node?(node) + return STOP if scrub_node(node) == STOP + end scrub_attributes(node) end @@ -61,11 +63,8 @@ class PermitScrubber < Loofah::Scrubber end def scrub_node(node) - unless keep_node?(node) - node.before(node.children) # strip - node.remove - true - end + node.before(node.children) # strip + node.remove end def scrub_attributes(node) -- cgit v1.2.3 From b13d22bff5fedaec96a1a45400a23cd75621beb0 Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 10 Aug 2013 12:57:19 +0200 Subject: Initialized tags and attributes to nil. --- actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index 07611e6927..cb1a0da542 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -22,6 +22,10 @@ class PermitScrubber < Loofah::Scrubber # :nodoc: attr_reader :tags, :attributes + def initialize + @tags, @attributes = nil, nil + end + def tags=(tags) @tags = validate!(tags, :tags) end -- cgit v1.2.3 From 349230e82358f375c6fb77419cd7ea82bae47916 Mon Sep 17 00:00:00 2001 From: Timm Date: Mon, 12 Aug 2013 17:15:32 +0200 Subject: Fixed: spelling error. --- actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index cb1a0da542..5a9ca8f60c 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -8,7 +8,7 @@ # - When an attribute should be scrubbed via +scrub_attribute?+ # # Text and CDATA nodes are skipped by defualt. -# Unallowed elements will be stripped, i.e. element is removed but its substree kept. +# Unallowed elements will be stripped, i.e. element is removed but its subtree kept. # Supplied tags and attributes should be Enumerables # # +tags=+ -- cgit v1.2.3 From d6a6d42e7ee4e6e7c1e8c815e8793be3b3f0f7b5 Mon Sep 17 00:00:00 2001 From: Timm Date: Mon, 12 Aug 2013 18:54:54 +0200 Subject: Reworked documentation for PermitScrubber and TargetScrubber. --- .../helpers/sanitize_helper/scrubbers.rb | 35 ++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index 5a9ca8f60c..c63214ed60 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -7,7 +7,13 @@ # - When a node is allowed via +allowed_node?+ # - When an attribute should be scrubbed via +scrub_attribute?+ # -# Text and CDATA nodes are skipped by defualt. +# Subclasses don't need to worry if tags or attributes are set or not. +# If tags or attributes are not set, Loofahs 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 # @@ -18,6 +24,23 @@ # +attributes=+ # If set, attributes excluded will be removed. # If not, attributes are removed based on Loofahs +HTML5::Scrub.scrub_attributes+ +# +# class CommentScrubber < PermitScrubber +# def allowed_node?(node) +# %w(form script comment blockquote).exclude?(node.name) +# 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: http://nokogiri.org/Nokogiri/XML/Node.html class PermitScrubber < Loofah::Scrubber # :nodoc: attr_reader :tags, :attributes @@ -90,7 +113,15 @@ class PermitScrubber < Loofah::Scrubber end # === TargetScrubber -# The Bizarro PermitScrubber +# +# Where PermitScrubber picks out tags and attributes to permit in sanitization +# TargetScrubber picks tags and attributes to target for removal +# +# It uses PermitScrubber open architecture to redefine: +# - +allowed_node?+ +# # allowed if node is not in tags +# - +scrub_attribute?+ +# # should scrub if attribute name is not in attributes # # +tags=+ # If set, elements included will be stripped. -- cgit v1.2.3 From 53f25ae348f7fdcae27c3d89369738f1e8b455d9 Mon Sep 17 00:00:00 2001 From: Timm Date: Mon, 12 Aug 2013 19:00:41 +0200 Subject: Removed :nodoc: from PermitScrubber. --- actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index c63214ed60..0b1fc967fa 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -42,7 +42,6 @@ # See the documentation for Nokogiri::XML::Node to understand what's possible # with nodes: http://nokogiri.org/Nokogiri/XML/Node.html class PermitScrubber < Loofah::Scrubber - # :nodoc: attr_reader :tags, :attributes def initialize -- cgit v1.2.3 From bffa6469b7efc8a56074ed6f8d31e9e01e685648 Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 14 Aug 2013 17:18:15 +0200 Subject: Reworked root and selector conditional assignment in css_select. --- actionview/lib/action_view/testing/assertions/selector.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index dc847e5ccf..c2ea25c8a3 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -49,11 +49,8 @@ module ActionView def css_select(*args) raise ArgumentError, "you at least need a selector" if args.empty? - if args.first.is_a?(String) - root, selector = response_from_page, args.first - else - root, selector = args.shift, args.first - end + root = args.size == 1 ? response_from_page : args.shift + selector = args.first root.css(selector).tap do |matches| if matches.empty? && root.matches?(selector) -- cgit v1.2.3 From 86c6f5b1d9ef79dd69724d01afe5c6dfb274edb8 Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 14 Aug 2013 17:21:01 +0200 Subject: Changed wording of missing selector argument exception message in css_select. --- actionview/lib/action_view/testing/assertions/selector.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index c2ea25c8a3..616d400536 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -47,7 +47,7 @@ module ActionView # ... # end def css_select(*args) - raise ArgumentError, "you at least need a selector" if args.empty? + raise ArgumentError, "you at least need a selector argument" if args.empty? root = args.size == 1 ? response_from_page : args.shift selector = args.first -- cgit v1.2.3 From 9dac1e8b8f05ab047cd1a514b42a190792eb6ce4 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 16:17:44 +0200 Subject: Removed duplication in assert_dom_equal and assert_dom_not_equal. --- actionview/lib/action_view/testing/assertions/dom.rb | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb index 56edf48770..751c88c2e0 100644 --- a/actionview/lib/action_view/testing/assertions/dom.rb +++ b/actionview/lib/action_view/testing/assertions/dom.rb @@ -6,9 +6,7 @@ module ActionView # # assert that the referenced method generates the appropriate HTML string # assert_dom_equal 'Apples', link_to("Apples", "http://www.example.com") def assert_dom_equal(expected, actual, message = nil) - expected_dom, actual_dom = doms_from_strings(expected, actual) - message ||= "Expected: #{expected_dom}\nActual: #{actual_dom}" - assert compare_doms(expected_dom, actual_dom), message + assert dom_assertion(message, expected, actual) end # The negated form of +assert_dom_equal+. @@ -16,15 +14,14 @@ module ActionView # # assert that the referenced method does not generate the specified HTML string # assert_dom_not_equal 'Apples', link_to("Oranges", "http://www.example.com") def assert_dom_not_equal(expected, actual, message = nil) - expected_dom, actual_dom = doms_from_strings(expected, actual) - message ||= "Expected: #{expected_dom}\nActual: #{actual_dom}" - assert_not compare_doms(expected_dom, actual_dom), message + assert_not dom_assertion(message, expected, actual) end protected - # +doms_from_strings+ creates a Loofah::HTML::DocumentFragment for every string in strings - def doms_from_strings(*strings) - strings.map { |str| Loofah.fragment(str) } + def dom_assertion(message = nil, *html_strings) + expected, actual = html_strings.map { |str| Loofah.fragment(str) } + message ||= "Expected: #{expected}\nActual: #{actual}" + return compare_doms(expected, actual), message end # +compare_doms+ takes two doms loops over all their children and compares each child via +equal_children?+ -- cgit v1.2.3 From 97c5e6fa027d0ef9151172c193b1f61ee4c4c70a Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 16:49:05 +0200 Subject: Changed: remove_xpaths called with String returns String, while called with Loofah fragment returns Loofah fragment. Added tests for this. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index c6bbf5e3f7..01ab9830f3 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -14,9 +14,9 @@ module ActionView def remove_xpaths(html, xpaths) if html.respond_to?(:xpath) xpaths.each { |xpath| html.xpath(xpath).remove } - html.to_s + html else - remove_xpaths(Loofah.fragment(html), xpaths) + remove_xpaths(Loofah.fragment(html), xpaths).to_s end end end -- cgit v1.2.3 From 75789d5326cdd4718976480dc64d8c5e95f7d069 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 18:56:35 +0200 Subject: Changed: return early from compare_doms if the two doms don't have the same number of children. --- actionview/lib/action_view/testing/assertions/dom.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb index 751c88c2e0..85a01f9b87 100644 --- a/actionview/lib/action_view/testing/assertions/dom.rb +++ b/actionview/lib/action_view/testing/assertions/dom.rb @@ -26,6 +26,8 @@ module ActionView # +compare_doms+ takes two doms loops over all their children and compares each child via +equal_children?+ def compare_doms(expected, actual) + return false unless expected.children.size == actual.children.size + expected.children.each_with_index do |child, i| return false unless equal_children?(child, actual.children[i]) end -- cgit v1.2.3 From 71aaddbd1464c4ad02bea9bbaada622da0b1feb1 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 19:41:24 +0200 Subject: Changed: removed @selected and @page variables from HTMLSelector since one method used them. Passed the values directly to there instead. --- .../lib/action_view/testing/assertions/selector.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index 616d400536..e3aa6bf0f1 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -310,10 +310,8 @@ module ActionView alias :source :css_selector def initialize(selected, page, args) - @selected, @page = selected, page - # Start with optional element followed by mandatory selector. - @root = determine_root_from(args.first) + @root = determine_root_from(args.first, page, selected) # First or second argument is the selector selector = @css_selector_is_second_argument ? args.shift(2).last : args.shift @@ -361,7 +359,7 @@ module ActionView Nokogiri::XML::NodeSet.new(matches.document, remaining) end - def determine_root_from(root_or_selector) + def determine_root_from(root_or_selector, page, previous_selection = nil) @css_selector_is_second_argument = false if root_or_selector == nil raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?" @@ -371,14 +369,14 @@ module ActionView @css_selector_is_second_argument = true root_or_selector - elsif @selected - if @selected.is_a?(Array) - doc = @selected.empty? ? @page.document : @selected[0].document - @selected = Nokogiri::XML::NodeSet.new(doc, @selected) + elsif previous_selection + if previous_selection.is_a?(Array) + Nokogiri::XML::NodeSet.new(previous_selection[0].document, previous_selection) + else + previous_selection end - @selected else - @page + page end end -- cgit v1.2.3 From 20615ec7e6658be53e713545967f8c054303ec3c Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 19:47:53 +0200 Subject: Changed: HTMLSelector comparisons renamed to equality_tests. --- actionview/lib/action_view/testing/assertions/selector.rb | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index e3aa6bf0f1..dda45b5730 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -155,7 +155,7 @@ module ActionView selector = HTMLSelector.new(@selected, response_from_page, args) matches = selector.select - assert_size_match!(matches.size, selector.comparisons, selector.source, selector.message) + assert_size_match!(matches.size, selector.equality_tests, selector.source, selector.message) # Set @selected to allow nested assert_select. # Can be nested several levels deep. @@ -305,7 +305,7 @@ module ActionView end class HTMLSelector #:nodoc: - attr_accessor :root, :css_selector, :comparisons, :message + attr_accessor :root, :css_selector, :equality_tests, :message alias :source :css_selector @@ -317,10 +317,7 @@ module ActionView selector = @css_selector_is_second_argument ? args.shift(2).last : args.shift @css_selector = selector_from(selector, args) - # Next argument is used for equality tests. - @comparisons = comparisons_from(args.shift) - - # Last argument is the message we use if the assertion fails. + @equality_tests = equality_tests_from(args.shift) @message = args.shift if args.shift @@ -333,11 +330,11 @@ module ActionView end def filter(matches) - match_with = comparisons[:text] || comparisons[:html] + match_with = equality_tests[:text] || equality_tests[:html] return matches if matches.empty? || !match_with content_mismatch = nil - text_matches = comparisons.has_key?(:text) + text_matches = equality_tests.has_key?(:text) remaining = matches.reject do |match| # Preserve markup with to_s for html elements @@ -387,7 +384,7 @@ module ActionView context.substitute!(selector, substitution_values) end - def comparisons_from(comparator) + def equality_tests_from(comparator) comparisons = {} case comparator when Hash -- cgit v1.2.3 From ce4396b3663c2d2c108307e8c67dc35e8170ec6e Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 20:15:25 +0200 Subject: Changed: put selector extraction into selector_from, which is renamed to extract_selector. --- actionview/lib/action_view/testing/assertions/selector.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index dda45b5730..60517c143b 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -314,8 +314,7 @@ module ActionView @root = determine_root_from(args.first, page, selected) # First or second argument is the selector - selector = @css_selector_is_second_argument ? args.shift(2).last : args.shift - @css_selector = selector_from(selector, args) + @css_selector = extract_selector(args) @equality_tests = equality_tests_from(args.shift) @message = args.shift @@ -377,11 +376,12 @@ module ActionView end end - def selector_from(selector, substitution_values) + def extract_selector(values) + selector = @css_selector_is_second_argument ? values.shift(2).last : values.shift unless selector.is_a? String raise ArgumentError, "Expecting a selector as the first argument" end - context.substitute!(selector, substitution_values) + context.substitute!(selector, values) end def equality_tests_from(comparator) -- cgit v1.2.3 From 9a536bc1958ae900b4c7c1f7c6f6e1bcd8622b18 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 20:21:51 +0200 Subject: Removed unnecessary lines from HTMLSelector initialize. --- actionview/lib/action_view/testing/assertions/selector.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index 60517c143b..3d65746701 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -310,10 +310,8 @@ module ActionView alias :source :css_selector def initialize(selected, page, args) - # Start with optional element followed by mandatory selector. + # Start with possible optional element followed by mandatory selector. @root = determine_root_from(args.first, page, selected) - - # First or second argument is the selector @css_selector = extract_selector(args) @equality_tests = equality_tests_from(args.shift) -- cgit v1.2.3 From 65ed2b6371817e9110c2eecab080f952ea0e9667 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 20:23:55 +0200 Subject: Renamed: HTMLSelector css_selector to selector. --- actionview/lib/action_view/testing/assertions/selector.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index 3d65746701..d7266d4e99 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -305,14 +305,14 @@ module ActionView end class HTMLSelector #:nodoc: - attr_accessor :root, :css_selector, :equality_tests, :message + attr_accessor :root, :selector, :equality_tests, :message - alias :source :css_selector + alias :source :selector def initialize(selected, page, args) # Start with possible optional element followed by mandatory selector. @root = determine_root_from(args.first, page, selected) - @css_selector = extract_selector(args) + @selector = extract_selector(args) @equality_tests = equality_tests_from(args.shift) @message = args.shift @@ -323,7 +323,7 @@ module ActionView end def select - filter root.css(css_selector, context) + filter root.css(selector, context) end def filter(matches) @@ -354,13 +354,13 @@ module ActionView end def determine_root_from(root_or_selector, page, previous_selection = nil) - @css_selector_is_second_argument = false + @selector_is_second_argument = false if root_or_selector == nil raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?" elsif root_or_selector.is_a?(Nokogiri::XML::Node) || root_or_selector.is_a?(Nokogiri::XML::NodeSet) # First argument is a node (tag or text, but also HTML root), # so we know what we're selecting from. - @css_selector_is_second_argument = true + @selector_is_second_argument = true root_or_selector elsif previous_selection @@ -375,7 +375,7 @@ module ActionView end def extract_selector(values) - selector = @css_selector_is_second_argument ? values.shift(2).last : values.shift + selector = @selector_is_second_argument ? values.shift(2).last : values.shift unless selector.is_a? String raise ArgumentError, "Expecting a selector as the first argument" end -- cgit v1.2.3 From cabef1415ad18063090f8fb2a48291355e536b0c Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 20:52:34 +0200 Subject: Changed: using duck typing instead of requiring subclasses of Node and NodeSet. --- actionview/lib/action_view/testing/assertions/selector.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index d7266d4e99..bbdb21f4b0 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -357,11 +357,8 @@ module ActionView @selector_is_second_argument = false if root_or_selector == nil raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?" - elsif root_or_selector.is_a?(Nokogiri::XML::Node) || root_or_selector.is_a?(Nokogiri::XML::NodeSet) - # First argument is a node (tag or text, but also HTML root), - # so we know what we're selecting from. + elsif root_or_selector.respond_to?(:css) @selector_is_second_argument = true - root_or_selector elsif previous_selection if previous_selection.is_a?(Array) -- cgit v1.2.3 From 4b55c0aa2e923e6102c9f80e12bbfde4431a7412 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 20:53:55 +0200 Subject: Moved: initial assignment of @selector_is_second_argument is now in initialize. --- actionview/lib/action_view/testing/assertions/selector.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index bbdb21f4b0..73f31c2127 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -311,6 +311,7 @@ module ActionView def initialize(selected, page, args) # Start with possible optional element followed by mandatory selector. + @selector_is_second_argument = false @root = determine_root_from(args.first, page, selected) @selector = extract_selector(args) @@ -354,7 +355,6 @@ module ActionView end def determine_root_from(root_or_selector, page, previous_selection = nil) - @selector_is_second_argument = false if root_or_selector == nil raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?" elsif root_or_selector.respond_to?(:css) -- cgit v1.2.3 From e600b3a34116d90b9df5c9bf359e05564999b359 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 21:56:39 +0200 Subject: Changed conditional check in filter. Removed weird comments. --- actionview/lib/action_view/testing/assertions/selector.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index 73f31c2127..b53e0c1b55 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -341,16 +341,12 @@ module ActionView content.strip! unless NO_STRIP.include?(match.name) content.sub!(/\A\n/, '') if text_matches && match.name == "textarea" - unless match_with.is_a?(Regexp) ? (content =~ match_with) : (content == match_with.to_s) - content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, content) - true - end + next if match_with.is_a?(Regexp) ? (content =~ match_with) : (content == match_with.to_s) + content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, content) + true end - # Expecting foo found bar element only if found zero, not if - # found one but expecting two. self.message ||= content_mismatch if remaining.empty? - Nokogiri::XML::NodeSet.new(matches.document, remaining) end -- cgit v1.2.3 From 5169b00330584f88a109f12cdc334219114e7c5e Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 16 Aug 2013 22:22:22 +0200 Subject: Extracted: create Regexp from match_with and use =~ to compare instead of checking .is_a? Regexp every time through the loop. --- actionview/lib/action_view/testing/assertions/selector.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index b53e0c1b55..fea07b2897 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -333,6 +333,7 @@ module ActionView content_mismatch = nil text_matches = equality_tests.has_key?(:text) + match_with = Regexp.new(match_with.to_s) unless match_with.is_a?(Regexp) remaining = matches.reject do |match| # Preserve markup with to_s for html elements @@ -341,7 +342,7 @@ module ActionView content.strip! unless NO_STRIP.include?(match.name) content.sub!(/\A\n/, '') if text_matches && match.name == "textarea" - next if match_with.is_a?(Regexp) ? (content =~ match_with) : (content == match_with.to_s) + next if content =~ match_with content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, content) true end -- cgit v1.2.3 From c1a786493021b184ebd5729eb21979eab1f774e4 Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 17 Aug 2013 10:36:23 +0200 Subject: Fixed: added apostrophe to possessive noun. --- actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index 0b1fc967fa..398879a42c 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -8,7 +8,7 @@ # - 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, Loofahs behavior will be used. +# 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. -- cgit v1.2.3 From 62171784fe374d101aa7cfcb0d1e32c89a3629f8 Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 17 Aug 2013 11:02:09 +0200 Subject: Simplified the removal of xpaths in remove_xpaths. Added more tests for remove_xpaths. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 01ab9830f3..75ba1a7deb 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -13,7 +13,7 @@ module ActionView def remove_xpaths(html, xpaths) if html.respond_to?(:xpath) - xpaths.each { |xpath| html.xpath(xpath).remove } + html.xpath(*xpaths).remove html else remove_xpaths(Loofah.fragment(html), xpaths).to_s -- cgit v1.2.3 From bab54e4e529ae4d727fae8209ac60f9b25802680 Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 17 Aug 2013 11:24:40 +0200 Subject: Changed back to =~ or == comparison in HTMLSelector filter. --- actionview/lib/action_view/testing/assertions/selector.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index fea07b2897..d510ac2904 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -333,7 +333,7 @@ module ActionView content_mismatch = nil text_matches = equality_tests.has_key?(:text) - match_with = Regexp.new(match_with.to_s) unless match_with.is_a?(Regexp) + regex_matching = match_with.is_a?(Regexp) remaining = matches.reject do |match| # Preserve markup with to_s for html elements @@ -342,7 +342,7 @@ module ActionView content.strip! unless NO_STRIP.include?(match.name) content.sub!(/\A\n/, '') if text_matches && match.name == "textarea" - next if content =~ match_with + next if regex_matching ? (content =~ match_with) : (content == match_with) content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, content) true end -- cgit v1.2.3 From 73c690d4fd8a15b1572e5e88a094064734f541ac Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 17 Aug 2013 11:40:40 +0200 Subject: Removed html_strings variable, no splat operator needed. --- actionview/lib/action_view/testing/assertions/dom.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb index 85a01f9b87..8df03445a8 100644 --- a/actionview/lib/action_view/testing/assertions/dom.rb +++ b/actionview/lib/action_view/testing/assertions/dom.rb @@ -6,7 +6,7 @@ module ActionView # # assert that the referenced method generates the appropriate HTML string # assert_dom_equal 'Apples', link_to("Apples", "http://www.example.com") def assert_dom_equal(expected, actual, message = nil) - assert dom_assertion(message, expected, actual) + assert dom_assertion(expected, actual, message) end # The negated form of +assert_dom_equal+. @@ -14,12 +14,12 @@ module ActionView # # assert that the referenced method does not generate the specified HTML string # assert_dom_not_equal 'Apples', link_to("Oranges", "http://www.example.com") def assert_dom_not_equal(expected, actual, message = nil) - assert_not dom_assertion(message, expected, actual) + assert_not dom_assertion(expected, actual, message) end protected - def dom_assertion(message = nil, *html_strings) - expected, actual = html_strings.map { |str| Loofah.fragment(str) } + def dom_assertion(expected_string, actual_string, message = nil) + expected, actual = Loofah.fragment(expected_string), Loofah.fragment(actual_string) message ||= "Expected: #{expected}\nActual: #{actual}" return compare_doms(expected, actual), message end -- cgit v1.2.3 From d6067e81934a491dc1bd6262930155d64f9df658 Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 17 Aug 2013 12:39:19 +0200 Subject: Changed attributes_are_equal? to equal_attribute_nodes? which takes attribute_nodes instead of nodes. --- actionview/lib/action_view/testing/assertions/dom.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb index 8df03445a8..556b70a196 100644 --- a/actionview/lib/action_view/testing/assertions/dom.rb +++ b/actionview/lib/action_view/testing/assertions/dom.rb @@ -27,7 +27,7 @@ module ActionView # +compare_doms+ takes two doms loops over all their children and compares each child via +equal_children?+ def compare_doms(expected, actual) return false unless expected.children.size == actual.children.size - + expected.children.each_with_index do |child, i| return false unless equal_children?(child, actual.children[i]) end @@ -42,7 +42,8 @@ module ActionView case child.type when Nokogiri::XML::Node::ELEMENT_NODE - child.name == other_child.name && attributes_are_equal?(child, other_child) + child.name == other_child.name && + equal_attribute_nodes?(child.attribute_nodes, other_child.attribute_nodes) else child.to_s == other_child.to_s end @@ -51,12 +52,12 @@ module ActionView # +attributes_are_equal?+ sorts elements attributes by name and compares # each attribute by calling +equal_attribute?+ # If those are +true+ the attributes are considered equal - def attributes_are_equal?(element, other_element) - first_nodes = element.attribute_nodes.sort_by { |a| a.name } - other_nodes = other_element.attribute_nodes.sort_by { |a| a.name } + def equal_attribute_nodes?(nodes, other_nodes) + return false unless nodes.size == other_nodes.size + nodes = nodes.sort_by(&:name) + other_nodes = other_nodes.sort_by(&:name) - return false unless first_nodes.size == other_nodes.size - first_nodes.each_with_index do |attr, i| + nodes.each_with_index do |attr, i| return false unless equal_attribute?(attr, other_nodes[i]) end true -- cgit v1.2.3 From 905d2bc605169fdc87b69276da1e40a9bc37b82d Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 17 Aug 2013 12:43:12 +0200 Subject: Reworked some internal documentation for equal_attribute_nodes?. --- actionview/lib/action_view/testing/assertions/dom.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb index 556b70a196..d290591fcc 100644 --- a/actionview/lib/action_view/testing/assertions/dom.rb +++ b/actionview/lib/action_view/testing/assertions/dom.rb @@ -49,9 +49,8 @@ module ActionView end end - # +attributes_are_equal?+ sorts elements attributes by name and compares - # each attribute by calling +equal_attribute?+ - # If those are +true+ the attributes are considered equal + # +equal_attribute_nodes?+ sorts attribute nodes by name and compares + # each by calling +equal_attribute?+ def equal_attribute_nodes?(nodes, other_nodes) return false unless nodes.size == other_nodes.size nodes = nodes.sort_by(&:name) -- cgit v1.2.3 From 97d20b14171db8378028b1743d9841d802192eda Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 17 Aug 2013 12:46:14 +0200 Subject: Removed case statement in equal_children? used child.element? instead. --- actionview/lib/action_view/testing/assertions/dom.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb index d290591fcc..e604c68ee0 100644 --- a/actionview/lib/action_view/testing/assertions/dom.rb +++ b/actionview/lib/action_view/testing/assertions/dom.rb @@ -40,8 +40,7 @@ module ActionView def equal_children?(child, other_child) return false unless child.type == other_child.type - case child.type - when Nokogiri::XML::Node::ELEMENT_NODE + if child.element? child.name == other_child.name && equal_attribute_nodes?(child.attribute_nodes, other_child.attribute_nodes) else -- cgit v1.2.3 From 7f7a1b5d6b9c651073cf66af844d7930eac29922 Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 17 Aug 2013 12:50:12 +0200 Subject: Removed unnecessary documentation in DomAssertions. --- actionview/lib/action_view/testing/assertions/dom.rb | 7 ------- 1 file changed, 7 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb index e604c68ee0..825720a56f 100644 --- a/actionview/lib/action_view/testing/assertions/dom.rb +++ b/actionview/lib/action_view/testing/assertions/dom.rb @@ -24,7 +24,6 @@ module ActionView return compare_doms(expected, actual), message end - # +compare_doms+ takes two doms loops over all their children and compares each child via +equal_children?+ def compare_doms(expected, actual) return false unless expected.children.size == actual.children.size @@ -34,9 +33,6 @@ module ActionView true end - # +equal_children?+ compares children according to their type - # Determines further comparison via said type - # i.e. element node children with equal names has their attributes compared using +attributes_are_equal?+ def equal_children?(child, other_child) return false unless child.type == other_child.type @@ -48,8 +44,6 @@ module ActionView end end - # +equal_attribute_nodes?+ sorts attribute nodes by name and compares - # each by calling +equal_attribute?+ def equal_attribute_nodes?(nodes, other_nodes) return false unless nodes.size == other_nodes.size nodes = nodes.sort_by(&:name) @@ -61,7 +55,6 @@ module ActionView true end - # +equal_attribute?+ compares attributes by their name and value def equal_attribute?(attr, other_attr) attr.name == other_attr.name && attr.value == other_attr.value end -- cgit v1.2.3 From cb865e1a7be5240a528aa30cad9c977c387fd40c Mon Sep 17 00:00:00 2001 From: Timm Date: Wed, 28 Aug 2013 20:07:47 +0200 Subject: Removed dom_assertion method since it created bugs. --- actionview/lib/action_view/testing/assertions/dom.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb index 825720a56f..3357b14421 100644 --- a/actionview/lib/action_view/testing/assertions/dom.rb +++ b/actionview/lib/action_view/testing/assertions/dom.rb @@ -6,7 +6,9 @@ module ActionView # # assert that the referenced method generates the appropriate HTML string # assert_dom_equal 'Apples', link_to("Apples", "http://www.example.com") def assert_dom_equal(expected, actual, message = nil) - assert dom_assertion(expected, actual, message) + expected_dom, actual_dom = Loofah.fragment(expected), Loofah.fragment(actual) + message ||= "Expected: #{expected}\nActual: #{actual}" + assert compare_doms(expected_dom, actual_dom), message end # The negated form of +assert_dom_equal+. @@ -14,16 +16,12 @@ module ActionView # # assert that the referenced method does not generate the specified HTML string # assert_dom_not_equal 'Apples', link_to("Oranges", "http://www.example.com") def assert_dom_not_equal(expected, actual, message = nil) - assert_not dom_assertion(expected, actual, message) + expected_dom, actual_dom = Loofah.fragment(expected), Loofah.fragment(actual) + message ||= "Expected: #{expected}\nActual: #{actual}" + assert_not compare_doms(expected_dom, actual_dom), message end protected - def dom_assertion(expected_string, actual_string, message = nil) - expected, actual = Loofah.fragment(expected_string), Loofah.fragment(actual_string) - message ||= "Expected: #{expected}\nActual: #{actual}" - return compare_doms(expected, actual), message - end - def compare_doms(expected, actual) return false unless expected.children.size == actual.children.size -- cgit v1.2.3 From 170f414928fcd3d3e691ee67ad59798b77b6330f Mon Sep 17 00:00:00 2001 From: Timm Date: Mon, 9 Sep 2013 18:45:13 +0200 Subject: Removed require's for html-scanner. --- actionview/lib/action_view/vendor/html-scanner.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/vendor/html-scanner.rb b/actionview/lib/action_view/vendor/html-scanner.rb index 775b827529..d26ce6012c 100644 --- a/actionview/lib/action_view/vendor/html-scanner.rb +++ b/actionview/lib/action_view/vendor/html-scanner.rb @@ -1,5 +1,8 @@ +require 'active_support/deprecation' $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/html-scanner" +ActiveSupport::Deprecation.warn("html-scanner has been deprecated in favor of using Loofah in SanitizeHelper and ActionView::Assertions.") + module HTML extend ActiveSupport::Autoload -- cgit v1.2.3 From 5430487d85de3e6ac0d886e384ef039f15e64a88 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 13 Sep 2013 15:45:44 +0200 Subject: Stylistic improvements. Some light documentation for remove_xpaths. --- .../lib/action_view/helpers/sanitize_helper/sanitizers.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 75ba1a7deb..f6c13885ff 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -5,12 +5,13 @@ require 'action_view/helpers/sanitize_helper/scrubbers' module ActionView XPATHS_TO_REMOVE = %w{.//script .//form comment()} - class Sanitizer - # :nodoc: + class Sanitizer # :nodoc: def sanitize(html, options = {}) raise NotImplementedError, "subclasses must implement" end + # call +remove_xpaths+ with string and get a string back + # call it with a node or nodeset and get back a node/nodeset def remove_xpaths(html, xpaths) if html.respond_to?(:xpath) html.xpath(*xpaths).remove @@ -23,7 +24,7 @@ module ActionView class FullSanitizer < Sanitizer def sanitize(html, options = {}) - return nil unless html + return unless html return html if html.empty? Loofah.fragment(html).tap do |fragment| @@ -44,15 +45,15 @@ module ActionView end class WhiteListSanitizer < Sanitizer - def initialize @permit_scrubber = PermitScrubber.new end def sanitize(html, options = {}) - return nil unless html + return unless html loofah_fragment = Loofah.fragment(html) + if scrubber = options[:scrubber] # No duck typing, Loofah ensures subclass of Loofah::Scrubber loofah_fragment.scrub!(scrubber) @@ -64,11 +65,12 @@ module ActionView remove_xpaths(loofah_fragment, XPATHS_TO_REMOVE) loofah_fragment.scrub!(:strip) end + loofah_fragment.to_s end def sanitize_css(style_string) - Loofah::HTML5::Scrub.scrub_css style_string + Loofah::HTML5::Scrub.scrub_css(style_string) end def protocol_separator -- cgit v1.2.3 From 0a0d151bb8dd9c4a04befbaa302471860a530a94 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 13 Sep 2013 15:52:39 +0200 Subject: Now returning html if html is blank? in FullSanitizer and WhiteListSanitizer. This means it'll return false if called with false, however that is not a valid use case. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index f6c13885ff..251820b81b 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/object/blank' require 'active_support/deprecation' require 'action_view/helpers/sanitize_helper/scrubbers' @@ -25,7 +26,7 @@ module ActionView class FullSanitizer < Sanitizer def sanitize(html, options = {}) return unless html - return html if html.empty? + return html if html.blank? Loofah.fragment(html).tap do |fragment| remove_xpaths(fragment, XPATHS_TO_REMOVE) @@ -51,6 +52,7 @@ module ActionView def sanitize(html, options = {}) return unless html + return html if html.blank? loofah_fragment = Loofah.fragment(html) -- cgit v1.2.3 From dd1955755191aece3f8e22a55689250391ebc3ba Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 13 Sep 2013 15:56:07 +0200 Subject: Stylistic improvements in ActionView::Assertions::DomAssertions. --- actionview/lib/action_view/testing/assertions/dom.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb index 3357b14421..31d3c56c49 100644 --- a/actionview/lib/action_view/testing/assertions/dom.rb +++ b/actionview/lib/action_view/testing/assertions/dom.rb @@ -22,12 +22,14 @@ module ActionView end protected + def compare_doms(expected, actual) return false unless expected.children.size == actual.children.size expected.children.each_with_index do |child, i| return false unless equal_children?(child, actual.children[i]) end + true end @@ -44,12 +46,14 @@ module ActionView def equal_attribute_nodes?(nodes, other_nodes) return false unless nodes.size == other_nodes.size + nodes = nodes.sort_by(&:name) other_nodes = other_nodes.sort_by(&:name) nodes.each_with_index do |attr, i| return false unless equal_attribute?(attr, other_nodes[i]) end + true end -- cgit v1.2.3 From 19406dad7d1d96b122d691dd34ee5d701945e77b Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 13 Sep 2013 17:00:02 +0200 Subject: Minor rewording in TargetScrubber documentation. --- actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index 398879a42c..9e573b14ca 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -113,10 +113,10 @@ end # === TargetScrubber # -# Where PermitScrubber picks out tags and attributes to permit in sanitization -# TargetScrubber picks tags and attributes to target for removal +# Where PermitScrubber picks out tags and attributes to permit in +# sanitization, TargetScrubber targets them for removal # -# It uses PermitScrubber open architecture to redefine: +# The open architecture of PermitScrubber is used to redefine: # - +allowed_node?+ # # allowed if node is not in tags # - +scrub_attribute?+ -- cgit v1.2.3 From 7f9106d5d996fec22fa8306168c21cc62b75ea9c Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 13 Sep 2013 17:24:15 +0200 Subject: Now only requiring Loofah in the places where it is needed. --- actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb | 2 ++ actionview/lib/action_view/testing/assertions.rb | 2 ++ 2 files changed, 4 insertions(+) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb index 251820b81b..7ff465ca52 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb @@ -1,6 +1,8 @@ require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/object/blank' require 'active_support/deprecation' + +require 'loofah' require 'action_view/helpers/sanitize_helper/scrubbers' module ActionView diff --git a/actionview/lib/action_view/testing/assertions.rb b/actionview/lib/action_view/testing/assertions.rb index 9714d3510d..f8a751aae3 100644 --- a/actionview/lib/action_view/testing/assertions.rb +++ b/actionview/lib/action_view/testing/assertions.rb @@ -1,3 +1,5 @@ +require 'loofah' + module ActionView module Assertions autoload :DomAssertions, 'action_view/testing/assertions/dom' -- cgit v1.2.3 From ddc24fda90f8f4086e3af4ff2699409452a64640 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 13 Sep 2013 22:37:44 +0200 Subject: Changed PermitScrubber's direction to bottom up to align better with Loofah's strip scrubber. --- actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb index 9e573b14ca..43826f5e60 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb @@ -45,6 +45,7 @@ class PermitScrubber < Loofah::Scrubber attr_reader :tags, :attributes def initialize + @direction = :bottom_up @tags, @attributes = nil, nil end -- cgit v1.2.3 From 9ef95a7019fc531ff74a91f0ca0871e6639990b7 Mon Sep 17 00:00:00 2001 From: Timm Date: Sun, 22 Sep 2013 21:08:30 +0200 Subject: Added deprecation warning for invalid selectors and skipping assertions. --- .../lib/action_view/testing/assertions/selector.rb | 29 ++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index d510ac2904..aac5a82e81 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation' + module ActionView module Assertions NO_STRIP = %w{pre script style textarea} @@ -27,6 +29,7 @@ module ActionView # Returns an empty Nokogiri::XML::NodeSet if no match is found. # # The selector may be a CSS selector expression (String). + # css_select will return nil if called with an invalid css selector. # # # Selects all div tags # divs = css_select("div") @@ -52,9 +55,11 @@ module ActionView root = args.size == 1 ? response_from_page : args.shift selector = args.first - root.css(selector).tap do |matches| - if matches.empty? && root.matches?(selector) - return Nokogiri::XML::NodeSet.new(root.document, [root]) + catch_invalid_selector do + root.css(selector).tap do |matches| + if matches.empty? && root.matches?(selector) + return Nokogiri::XML::NodeSet.new(root.document, [root]) + end end end end @@ -91,6 +96,7 @@ module ActionView # The selector may be a CSS selector expression (String) or an expression # with substitution values (Array). # Substitution uses a custom pseudo class match. Pass in whatever attribute you want to match (enclosed in quotes) and a ? for the substitution. + # assert_select will return nil if called with an invalid css selector. # # assert_select "div:match('id', ?)", /\d+/ # @@ -154,8 +160,12 @@ module ActionView selector = HTMLSelector.new(@selected, response_from_page, args) - matches = selector.select - assert_size_match!(matches.size, selector.equality_tests, selector.source, selector.message) + matches = nil + catch_invalid_selector do + matches = selector.select + + assert_size_match!(matches.size, selector.equality_tests, selector.source, selector.message) + end # Set @selected to allow nested assert_select. # Can be nested several levels deep. @@ -278,6 +288,15 @@ module ActionView end protected + + def catch_invalid_selector + begin + yield + rescue Nokogiri::CSS::SyntaxError => e + ActiveSupport::Deprecation.warn("You are using an invalid CSS selector and the assertion was not run. Please review it.\n#{e}") + end + end + # +equals+ must contain :minimum, :maximum and :count keys def assert_size_match!(size, equals, css_selector, message = nil) min, max, count = equals[:minimum], equals[:maximum], equals[:count] -- cgit v1.2.3 From 68e08fe8c955d5b4a6839d4587e2f32b77f4fe44 Mon Sep 17 00:00:00 2001 From: Timm Date: Mon, 23 Sep 2013 14:49:14 +0200 Subject: Silenced deprecation warnings in the tests. Documentation uses present tense. Changed deprecation message to not use you. Also returning from rescue block in catch_invalid_selector to abort reraising the exception. --- actionview/lib/action_view/testing/assertions/selector.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb index aac5a82e81..58f3f329b1 100644 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ b/actionview/lib/action_view/testing/assertions/selector.rb @@ -29,7 +29,7 @@ module ActionView # Returns an empty Nokogiri::XML::NodeSet if no match is found. # # The selector may be a CSS selector expression (String). - # css_select will return nil if called with an invalid css selector. + # css_select returns nil if called with an invalid css selector. # # # Selects all div tags # divs = css_select("div") @@ -96,7 +96,7 @@ module ActionView # The selector may be a CSS selector expression (String) or an expression # with substitution values (Array). # Substitution uses a custom pseudo class match. Pass in whatever attribute you want to match (enclosed in quotes) and a ? for the substitution. - # assert_select will return nil if called with an invalid css selector. + # assert_select returns nil if called with an invalid css selector. # # assert_select "div:match('id', ?)", /\d+/ # @@ -293,7 +293,8 @@ module ActionView begin yield rescue Nokogiri::CSS::SyntaxError => e - ActiveSupport::Deprecation.warn("You are using an invalid CSS selector and the assertion was not run. Please review it.\n#{e}") + ActiveSupport::Deprecation.warn("The assertion was not run because of an invalid css selector.\n#{e}") + return end end -- cgit v1.2.3 From c287572d21584f1632a90ec0091e80061fc82b1e Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 11 Oct 2013 12:48:43 +0200 Subject: Removed ActionView::Assertions. Getting ready to exchange with Rails::Dom::Testing::Assertions. --- actionview/lib/action_view/testing/assertions.rb | 13 - .../lib/action_view/testing/assertions/dom.rb | 65 --- .../lib/action_view/testing/assertions/selector.rb | 462 --------------------- 3 files changed, 540 deletions(-) delete mode 100644 actionview/lib/action_view/testing/assertions.rb delete mode 100644 actionview/lib/action_view/testing/assertions/dom.rb delete mode 100644 actionview/lib/action_view/testing/assertions/selector.rb (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/testing/assertions.rb b/actionview/lib/action_view/testing/assertions.rb deleted file mode 100644 index f8a751aae3..0000000000 --- a/actionview/lib/action_view/testing/assertions.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'loofah' - -module ActionView - module Assertions - autoload :DomAssertions, 'action_view/testing/assertions/dom' - autoload :SelectorAssertions, 'action_view/testing/assertions/selector' - - extend ActiveSupport::Concern - - include DomAssertions - include SelectorAssertions - end -end \ No newline at end of file diff --git a/actionview/lib/action_view/testing/assertions/dom.rb b/actionview/lib/action_view/testing/assertions/dom.rb deleted file mode 100644 index 31d3c56c49..0000000000 --- a/actionview/lib/action_view/testing/assertions/dom.rb +++ /dev/null @@ -1,65 +0,0 @@ -module ActionView - module Assertions - module DomAssertions - # \Test two HTML strings for equivalency (e.g., equal even when attributes are in another order) - # - # # assert that the referenced method generates the appropriate HTML string - # assert_dom_equal 'Apples', link_to("Apples", "http://www.example.com") - def assert_dom_equal(expected, actual, message = nil) - expected_dom, actual_dom = Loofah.fragment(expected), Loofah.fragment(actual) - message ||= "Expected: #{expected}\nActual: #{actual}" - assert compare_doms(expected_dom, actual_dom), message - end - - # The negated form of +assert_dom_equal+. - # - # # assert that the referenced method does not generate the specified HTML string - # assert_dom_not_equal 'Apples', link_to("Oranges", "http://www.example.com") - def assert_dom_not_equal(expected, actual, message = nil) - expected_dom, actual_dom = Loofah.fragment(expected), Loofah.fragment(actual) - message ||= "Expected: #{expected}\nActual: #{actual}" - assert_not compare_doms(expected_dom, actual_dom), message - end - - protected - - def compare_doms(expected, actual) - return false unless expected.children.size == actual.children.size - - expected.children.each_with_index do |child, i| - return false unless equal_children?(child, actual.children[i]) - end - - true - end - - def equal_children?(child, other_child) - return false unless child.type == other_child.type - - if child.element? - child.name == other_child.name && - equal_attribute_nodes?(child.attribute_nodes, other_child.attribute_nodes) - else - child.to_s == other_child.to_s - end - end - - def equal_attribute_nodes?(nodes, other_nodes) - return false unless nodes.size == other_nodes.size - - nodes = nodes.sort_by(&:name) - other_nodes = other_nodes.sort_by(&:name) - - nodes.each_with_index do |attr, i| - return false unless equal_attribute?(attr, other_nodes[i]) - end - - true - end - - def equal_attribute?(attr, other_attr) - attr.name == other_attr.name && attr.value == other_attr.value - end - end - end -end diff --git a/actionview/lib/action_view/testing/assertions/selector.rb b/actionview/lib/action_view/testing/assertions/selector.rb deleted file mode 100644 index 58f3f329b1..0000000000 --- a/actionview/lib/action_view/testing/assertions/selector.rb +++ /dev/null @@ -1,462 +0,0 @@ -require 'active_support/deprecation' - -module ActionView - module Assertions - NO_STRIP = %w{pre script style textarea} - - # Adds the +assert_select+ method for use in Rails functional - # test cases, which can be used to make assertions on the response HTML of a controller - # action. You can also call +assert_select+ within another +assert_select+ to - # make assertions on elements selected by the enclosing assertion. - # - # Use +css_select+ to select elements without making an assertions, either - # from the response HTML or elements selected by the enclosing assertion. - # - # In addition to HTML responses, you can make the following assertions: - # - # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, for example for dealing with feed item descriptions. - # * +assert_select_email+ - Assertions on the HTML body of an e-mail. - module SelectorAssertions - # Select and return all matching elements. - # - # If called with a single argument, uses that argument as a selector - # to match all elements of the current page. Returns an empty - # Nokogiri::XML::NodeSet if no match is found. - # - # If called with two arguments, uses the first argument as the root - # element and the second argument as the selector. Attempts to match the - # root element and any of its children. - # Returns an empty Nokogiri::XML::NodeSet if no match is found. - # - # The selector may be a CSS selector expression (String). - # css_select returns nil if called with an invalid css selector. - # - # # Selects all div tags - # divs = css_select("div") - # - # # Selects all paragraph tags and does something interesting - # pars = css_select("p") - # pars.each do |par| - # # Do something fun with paragraphs here... - # end - # - # # Selects all list items in unordered lists - # items = css_select("ul>li") - # - # # Selects all form tags and then all inputs inside the form - # forms = css_select("form") - # forms.each do |form| - # inputs = css_select(form, "input") - # ... - # end - def css_select(*args) - raise ArgumentError, "you at least need a selector argument" if args.empty? - - root = args.size == 1 ? response_from_page : args.shift - selector = args.first - - catch_invalid_selector do - root.css(selector).tap do |matches| - if matches.empty? && root.matches?(selector) - return Nokogiri::XML::NodeSet.new(root.document, [root]) - end - end - end - end - - # An assertion that selects elements and makes one or more equality tests. - # - # If the first argument is an element, selects all matching elements - # starting from (and including) that element and all its children in - # depth-first order. - # - # If no element is specified, calling +assert_select+ selects from the - # response HTML unless +assert_select+ is called from within an +assert_select+ block. - # - # When called with a block +assert_select+ passes an array of selected elements - # to the block. Calling +assert_select+ from the block, with no element specified, - # runs the assertion on the complete set of elements selected by the enclosing assertion. - # Alternatively the array may be iterated through so that +assert_select+ can be called - # separately for each element. - # - # - # ==== Example - # If the response contains two ordered lists, each with four list elements then: - # assert_select "ol" do |elements| - # elements.each do |element| - # assert_select element, "li", 4 - # end - # end - # - # will pass, as will: - # assert_select "ol" do - # assert_select "li", 8 - # end - # - # The selector may be a CSS selector expression (String) or an expression - # with substitution values (Array). - # Substitution uses a custom pseudo class match. Pass in whatever attribute you want to match (enclosed in quotes) and a ? for the substitution. - # assert_select returns nil if called with an invalid css selector. - # - # assert_select "div:match('id', ?)", /\d+/ - # - # === Equality Tests - # - # The equality test may be one of the following: - # * true - Assertion is true if at least one element selected. - # * false - Assertion is true if no element selected. - # * String/Regexp - Assertion is true if the text value of at least - # one element matches the string or regular expression. - # * Integer - Assertion is true if exactly that number of - # elements are selected. - # * Range - Assertion is true if the number of selected - # elements fit the range. - # If no equality test specified, the assertion is true if at least one - # element selected. - # - # To perform more than one equality tests, use a hash with the following keys: - # * :text - Narrow the selection to elements that have this text - # value (string or regexp). - # * :html - Narrow the selection to elements that have this HTML - # content (string or regexp). - # * :count - Assertion is true if the number of selected elements - # is equal to this value. - # * :minimum - Assertion is true if the number of selected - # elements is at least this value. - # * :maximum - Assertion is true if the number of selected - # elements is at most this value. - # - # If the method is called with a block, once all equality tests are - # evaluated the block is called with an array of all matched elements. - # - # # At least one form element - # assert_select "form" - # - # # Form element includes four input fields - # assert_select "form input", 4 - # - # # Page title is "Welcome" - # assert_select "title", "Welcome" - # - # # Page title is "Welcome" and there is only one title element - # assert_select "title", {count: 1, text: "Welcome"}, - # "Wrong title or more than one title element" - # - # # Page contains no forms - # assert_select "form", false, "This page must contain no forms" - # - # # Test the content and style - # assert_select "body div.header ul.menu" - # - # # Use substitution values - # assert_select "ol>li:match('id', ?)", /item-\d+/ - # - # # All input fields in the form have a name - # assert_select "form input" do - # assert_select ":match('name', ?)", /.+/ # Not empty - # end - def assert_select(*args, &block) - @selected ||= nil - - selector = HTMLSelector.new(@selected, response_from_page, args) - - matches = nil - catch_invalid_selector do - matches = selector.select - - assert_size_match!(matches.size, selector.equality_tests, selector.source, selector.message) - end - - # Set @selected to allow nested assert_select. - # Can be nested several levels deep. - if block_given? && !matches.empty? - begin - in_scope, @selected = @selected, matches - yield matches - ensure - @selected = in_scope - end - end - - matches - end - - def count_description(min, max, count) #:nodoc: - pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')} - - if min && max && (max != min) - "between #{min} and #{max} elements" - elsif min && max && max == min && count - "exactly #{count} #{pluralize['element', min]}" - elsif min && !(min == 1 && max == 1) - "at least #{min} #{pluralize['element', min]}" - elsif max - "at most #{max} #{pluralize['element', max]}" - end - end - - # Extracts the content of an element, treats it as encoded HTML and runs - # nested assertion on it. - # - # You typically call this method within another assertion to operate on - # all currently selected elements. You can also pass an element or array - # of elements. - # - # The content of each element is un-encoded, and wrapped in the root - # element +encoded+. It then calls the block with all un-encoded elements. - # - # # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix) - # assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do - # # Select each entry item and then the title item - # assert_select "entry>title" do - # # Run assertions on the encoded title elements - # assert_select_encoded do - # assert_select "b" - # end - # end - # end - # - # - # # Selects all paragraph tags from within the description of an RSS feed - # assert_select "rss[version=2.0]" do - # # Select description element of each feed item. - # assert_select "channel>item>description" do - # # Run assertions on the encoded elements. - # assert_select_encoded do - # assert_select "p" - # end - # end - # end - def assert_select_encoded(element = nil, &block) - case element - when Array - elements = element - when Nokogiri::XML::Node - elements = [element] - when nil - unless elements = @selected - raise ArgumentError, "First argument is optional, but must be called from a nested assert_select" - end - else - raise ArgumentError, "Argument is optional, and may be node or array of nodes" - end - - content = elements.map do |elem| - elem.children.select(&:cdata?).map(&:content) - end.join - selected = Loofah.fragment(content) - - begin - old_selected, @selected = @selected, selected - if content.empty? - yield selected - else - assert_select ":root", &block - end - ensure - @selected = old_selected - end - end - - # Extracts the body of an email and runs nested assertions on it. - # - # You must enable deliveries for this assertion to work, use: - # ActionMailer::Base.perform_deliveries = true - # - # assert_select_email do - # assert_select "h1", "Email alert" - # end - # - # assert_select_email do - # items = assert_select "ol>li" - # items.each do - # # Work with items here... - # end - # end - def assert_select_email(&block) - deliveries = ActionMailer::Base.deliveries - assert !deliveries.empty?, "No e-mail in delivery list" - - deliveries.each do |delivery| - (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part| - if part["Content-Type"].to_s =~ /^text\/html\W/ - root = Loofah.fragment(part.body.to_s) - assert_select root, ":root", &block - end - end - end - end - - protected - - def catch_invalid_selector - begin - yield - rescue Nokogiri::CSS::SyntaxError => e - ActiveSupport::Deprecation.warn("The assertion was not run because of an invalid css selector.\n#{e}") - return - end - end - - # +equals+ must contain :minimum, :maximum and :count keys - def assert_size_match!(size, equals, css_selector, message = nil) - min, max, count = equals[:minimum], equals[:maximum], equals[:count] - - message ||= %(Expected #{count_description(min, max, count)} matching "#{css_selector}", found #{size}.) - if count - assert_equal size, count, message - else - assert_operator size, :>=, min, message if min - assert_operator size, :<=, max, message if max - end - end - - # +html_document+ is used in testing/integration.rb - def html_document - @html_document ||= if @response.content_type =~ /xml$/ - Loofah.xml_document(@response.body) - else - Loofah.document(@response.body) - end - end - - def response_from_page - html_document.root - end - - class HTMLSelector #:nodoc: - attr_accessor :root, :selector, :equality_tests, :message - - alias :source :selector - - def initialize(selected, page, args) - # Start with possible optional element followed by mandatory selector. - @selector_is_second_argument = false - @root = determine_root_from(args.first, page, selected) - @selector = extract_selector(args) - - @equality_tests = equality_tests_from(args.shift) - @message = args.shift - - if args.shift - raise ArgumentError, "Not expecting that last argument, you either have too many arguments, or they're the wrong type" - end - end - - def select - filter root.css(selector, context) - end - - def filter(matches) - match_with = equality_tests[:text] || equality_tests[:html] - return matches if matches.empty? || !match_with - - content_mismatch = nil - text_matches = equality_tests.has_key?(:text) - regex_matching = match_with.is_a?(Regexp) - - remaining = matches.reject do |match| - # Preserve markup with to_s for html elements - content = text_matches ? match.text : match.children.to_s - - content.strip! unless NO_STRIP.include?(match.name) - content.sub!(/\A\n/, '') if text_matches && match.name == "textarea" - - next if regex_matching ? (content =~ match_with) : (content == match_with) - content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, content) - true - end - - self.message ||= content_mismatch if remaining.empty? - Nokogiri::XML::NodeSet.new(matches.document, remaining) - end - - def determine_root_from(root_or_selector, page, previous_selection = nil) - if root_or_selector == nil - raise ArgumentError, "First argument is either selector or element to select, but nil found. Perhaps you called assert_select with an element that does not exist?" - elsif root_or_selector.respond_to?(:css) - @selector_is_second_argument = true - root_or_selector - elsif previous_selection - if previous_selection.is_a?(Array) - Nokogiri::XML::NodeSet.new(previous_selection[0].document, previous_selection) - else - previous_selection - end - else - page - end - end - - def extract_selector(values) - selector = @selector_is_second_argument ? values.shift(2).last : values.shift - unless selector.is_a? String - raise ArgumentError, "Expecting a selector as the first argument" - end - context.substitute!(selector, values) - end - - def equality_tests_from(comparator) - comparisons = {} - case comparator - when Hash - comparisons = comparator - when String, Regexp - comparisons[:text] = comparator - when Integer - comparisons[:count] = comparator - when Range - comparisons[:minimum] = comparator.begin - comparisons[:maximum] = comparator.end - when FalseClass - comparisons[:count] = 0 - when NilClass, TrueClass - comparisons[:minimum] = 1 - else raise ArgumentError, "I don't understand what you're trying to match" - end - - # By default we're looking for at least one match. - if comparisons[:count] - comparisons[:minimum] = comparisons[:maximum] = comparisons[:count] - else - comparisons[:minimum] ||= 1 - end - comparisons - end - - def context - @context ||= SubstitutionContext.new - end - - class SubstitutionContext - def initialize(substitute = '?') - @substitute = substitute - @regexes = [] - end - - def add_regex(regex) - # Nokogiri doesn't like arbitrary values without quotes, hence inspect. - return regex.inspect unless regex.is_a?(Regexp) - @regexes.push(regex) - last_id.to_s # avoid implicit conversions of Fixnum to String - end - - def last_id - @regexes.count - 1 - end - - def match(matches, attribute, id) - matches.find_all { |node| node[attribute] =~ @regexes[id] } - end - - def substitute!(selector, values) - while !values.empty? && selector.index(@substitute) - selector.sub!(@substitute, add_regex(values.shift)) - end - selector - end - end - end - end - end -end -- cgit v1.2.3 From 5dc57db4b5a47e2f1e18b069a8db303f738b2d7a Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 11 Oct 2013 13:02:59 +0200 Subject: Required rails-dom-testing in test_case.rb --- actionview/lib/action_view/test_case.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 7415bfd684..ce1f763015 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -3,6 +3,8 @@ require 'action_controller' require 'action_controller/test_case' require 'action_view' +require 'rails-dom-testing' + module ActionView # = Action View Test Case class TestCase < ActiveSupport::TestCase @@ -34,7 +36,7 @@ module ActionView extend ActiveSupport::Concern include ActionDispatch::Assertions, ActionDispatch::TestProcess - include ActionView::Assertions + include Rails::Dom::Testing::Assertions include ActionController::TemplateAssertions include ActionView::Context -- cgit v1.2.3 From 83f1563ee6cae34447b4ed12f8cf39cc15177a38 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 11 Oct 2013 23:29:08 +0200 Subject: Support for changes in SelectorAssertions. --- actionview/lib/action_view/test_case.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index ce1f763015..337fa0e840 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -3,6 +3,7 @@ require 'action_controller' require 'action_controller/test_case' require 'action_view' +require 'loofah' require 'rails-dom-testing' module ActionView @@ -154,11 +155,10 @@ module ActionView private - # Support the selector assertions - # # Need to experiment if this priority is the best one: rendered => output_buffer - def response_from_page - Loofah.document(@rendered.blank? ? @output_buffer : @rendered).root + def document_root_element + @html_document ||= Loofah.document(@rendered.blank? ? @output_buffer : @rendered) + @html_document.root end def say_no_to_protect_against_forgery! @@ -239,7 +239,8 @@ module ActionView :@test_passed, :@view, :@view_context_class, - :@_subscribers + :@_subscribers, + :@html_document ] def _user_defined_ivars -- cgit v1.2.3 From 50347b15fe610d5ad8a76a315b9eac7a6f538eb4 Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 12 Oct 2013 12:41:09 +0200 Subject: Updated html-scanner deprecation message. --- actionview/lib/action_view/vendor/html-scanner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/vendor/html-scanner.rb b/actionview/lib/action_view/vendor/html-scanner.rb index d26ce6012c..fa31b5107b 100644 --- a/actionview/lib/action_view/vendor/html-scanner.rb +++ b/actionview/lib/action_view/vendor/html-scanner.rb @@ -1,7 +1,7 @@ require 'active_support/deprecation' $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/html-scanner" -ActiveSupport::Deprecation.warn("html-scanner has been deprecated in favor of using Loofah in SanitizeHelper and ActionView::Assertions.") +ActiveSupport::Deprecation.warn("html-scanner has been deprecated in favor of using Loofah in SanitizeHelper and Rails::Dom::Testing::Assertions.") module HTML extend ActiveSupport::Autoload -- cgit v1.2.3 From 38620e1bedb2cd38188eb6d7090beab5107f7347 Mon Sep 17 00:00:00 2001 From: Timm Date: Sat, 12 Oct 2013 23:09:50 +0200 Subject: Completed integration of rails-html-sanitizer in SanitizeHelper. Deprecated protocol_separator accessors and bad_tags=. --- .../lib/action_view/helpers/sanitize_helper.rb | 37 +++--- .../helpers/sanitize_helper/sanitizers.rb | 137 -------------------- .../helpers/sanitize_helper/scrubbers.rb | 139 --------------------- 3 files changed, 19 insertions(+), 294 deletions(-) delete mode 100644 actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb delete mode 100644 actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 4d2c6e64d9..13f946efa0 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/object/try' -require 'action_view/helpers/sanitize_helper/sanitizers' +require 'active_support/deprecation' +require 'rails-html-sanitizer' module ActionView # = Action View Sanitize Helpers @@ -121,7 +122,7 @@ module ActionView attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer def sanitized_protocol_separator - white_list_sanitizer.protocol_separator + ActiveSupport::Deprecation.warn('protocol_separator has been deprecated and has no effect.') end def sanitized_uri_attributes @@ -129,7 +130,7 @@ module ActionView end def sanitized_bad_tags - white_list_sanitizer.bad_tags + ActiveSupport::Deprecation.warn('bad_tags has been deprecated and has no effect. You can still affect the tags being sanitized using Rails::Html::WhiteListSanitizer.bad_tags= which changes the allowed_tags.') end def sanitized_allowed_tags @@ -156,7 +157,7 @@ module ActionView white_list_sanitizer.allowed_protocols end - # Gets the ActionView::FullSanitizer instance used by +strip_tags+. Replace with + # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with # any object that responds to +sanitize+. # # class Application < Rails::Application @@ -164,10 +165,10 @@ module ActionView # end # def full_sanitizer - @full_sanitizer ||= ActionView::FullSanitizer.new + @full_sanitizer ||= Rails::Html::FullSanitizer.new end - # Gets the ActionView::LinkSanitizer instance used by +strip_links+. + # Gets the Rails::Html::LinkSanitizer instance used by +strip_links+. # Replace with any object that responds to +sanitize+. # # class Application < Rails::Application @@ -175,10 +176,10 @@ module ActionView # end # def link_sanitizer - @link_sanitizer ||= ActionView::LinkSanitizer.new + @link_sanitizer ||= Rails::Html::LinkSanitizer.new end - # Gets the ActionView::WhiteListSanitizer instance used by sanitize and +sanitize_css+. + # Gets the Rails::Html::WhiteListSanitizer instance used by sanitize and +sanitize_css+. # Replace with any object that responds to +sanitize+. # # class Application < Rails::Application @@ -186,12 +187,12 @@ module ActionView # end # def white_list_sanitizer - @white_list_sanitizer ||= ActionView::WhiteListSanitizer.new + @white_list_sanitizer ||= Rails::Html::WhiteListSanitizer.new end def sanitized_protocol_separator=(value) - ActionView::WhiteListSanitizer.protocol_separator = value + ActiveSupport::Deprecation.warn('protocol_separator= has been deprecated and has no effect.') end # Adds valid HTML attributes that the +sanitize+ helper checks for URIs. @@ -201,7 +202,7 @@ module ActionView # end # def sanitized_uri_attributes=(attributes) - ActionView::WhiteListSanitizer.update_uri_attributes(attributes) + Rails::Html::WhiteListSanitizer.update_uri_attributes(attributes) end # Adds to the Set of 'bad' tags for the +sanitize+ helper. @@ -211,7 +212,7 @@ module ActionView # end # def sanitized_bad_tags=(attributes) - ActionView::WhiteListSanitizer.bad_tags = attributes + Rails::Html::WhiteListSanitizer.bad_tags = attributes end # Adds to the Set of allowed tags for the +sanitize+ helper. @@ -221,7 +222,7 @@ module ActionView # end # def sanitized_allowed_tags=(attributes) - ActionView::WhiteListSanitizer.update_allowed_tags(attributes) + Rails::Html::WhiteListSanitizer.update_allowed_tags(attributes) end # Adds to the Set of allowed HTML attributes for the +sanitize+ helper. @@ -231,7 +232,7 @@ module ActionView # end # def sanitized_allowed_attributes=(attributes) - ActionView::WhiteListSanitizer.update_allowed_attributes(attributes) + Rails::Html::WhiteListSanitizer.update_allowed_attributes(attributes) end # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers. @@ -241,7 +242,7 @@ module ActionView # end # def sanitized_allowed_css_properties=(attributes) - ActionView::WhiteListSanitizer.update_allowed_css_properties(attributes) + Rails::Html::WhiteListSanitizer.update_allowed_css_properties(attributes) end # Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers. @@ -251,7 +252,7 @@ module ActionView # end # def sanitized_allowed_css_keywords=(attributes) - ActionView::WhiteListSanitizer.update_allowed_css_keywords(attributes) + Rails::Html::WhiteListSanitizer.update_allowed_css_keywords(attributes) end # Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers. @@ -261,7 +262,7 @@ module ActionView # end # def sanitized_shorthand_css_properties=(attributes) - ActionView::WhiteListSanitizer.update_shorthand_css_properties(attributes) + Rails::Html::WhiteListSanitizer.update_shorthand_css_properties(attributes) end # Adds to the Set of allowed protocols for the +sanitize+ helper. @@ -271,7 +272,7 @@ module ActionView # end # def sanitized_allowed_protocols=(attributes) - ActionView::WhiteListSanitizer.update_allowed_protocols(attributes) + Rails::Html::WhiteListSanitizer.update_allowed_protocols(attributes) end end end diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb deleted file mode 100644 index 7ff465ca52..0000000000 --- a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb +++ /dev/null @@ -1,137 +0,0 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/object/blank' -require 'active_support/deprecation' - -require 'loofah' -require 'action_view/helpers/sanitize_helper/scrubbers' - -module ActionView - XPATHS_TO_REMOVE = %w{.//script .//form comment()} - - class Sanitizer # :nodoc: - def sanitize(html, options = {}) - raise NotImplementedError, "subclasses must implement" - end - - # call +remove_xpaths+ with string and get a string back - # call it with a node or nodeset and get back a node/nodeset - def remove_xpaths(html, xpaths) - if html.respond_to?(:xpath) - html.xpath(*xpaths).remove - html - else - remove_xpaths(Loofah.fragment(html), xpaths).to_s - end - end - end - - class FullSanitizer < Sanitizer - def sanitize(html, options = {}) - return unless html - return html if html.blank? - - Loofah.fragment(html).tap do |fragment| - remove_xpaths(fragment, XPATHS_TO_REMOVE) - end.text - end - end - - class LinkSanitizer < Sanitizer - def initialize - @link_scrubber = TargetScrubber.new - @link_scrubber.tags = %w(a href) - end - - def sanitize(html, options = {}) - Loofah.scrub_fragment(html, @link_scrubber).to_s - end - end - - class WhiteListSanitizer < Sanitizer - def initialize - @permit_scrubber = PermitScrubber.new - end - - def sanitize(html, options = {}) - return unless html - return html if html.blank? - - loofah_fragment = Loofah.fragment(html) - - if scrubber = options[:scrubber] - # No duck typing, Loofah ensures subclass of Loofah::Scrubber - loofah_fragment.scrub!(scrubber) - elsif options[:tags] || options[:attributes] - @permit_scrubber.tags = options[:tags] - @permit_scrubber.attributes = options[:attributes] - loofah_fragment.scrub!(@permit_scrubber) - else - remove_xpaths(loofah_fragment, XPATHS_TO_REMOVE) - loofah_fragment.scrub!(:strip) - end - - loofah_fragment.to_s - end - - def sanitize_css(style_string) - Loofah::HTML5::Scrub.scrub_css(style_string) - end - - def protocol_separator - self.class.protocol_separator - end - - def protocol_separator=(value) - self.class.protocol_separator - end - - def bad_tags - self.class.bad_tags - end - - class << self - def protocol_separator - ActiveSupport::Deprecation.warn('protocol_separator has been deprecated and has no effect.') - end - - def protocol_separator=(value) - protocol_separator - end - - def bad_tags - ActiveSupport::Deprecation.warn('bad_tags has been deprecated and has no effect. You can still affect the tags being sanitized using ActionView::WhiteListSanitizer.bad_tags= which changes the allowed_tags.') - end - - def bad_tags=(tags) - allowed_tags.replace(allowed_tags - tags) - end - end - - [:uri_attributes, :allowed_attributes, - :allowed_tags, :allowed_protocols, :allowed_css_properties, - :allowed_css_keywords, :shorthand_css_properties].each do |attr| - class_attribute attr, :instance_writer => false - - define_method "#{self}.update_#{attr}" do |arg| - attr.merge arg - end - end - - # Constants are from Loofahs source at lib/loofah/html5/whitelist.rb - self.uri_attributes = Loofah::HTML5::WhiteList::ATTR_VAL_IS_URI - - self.allowed_tags = Loofah::HTML5::WhiteList::ALLOWED_ELEMENTS - - self.bad_tags = Set.new %w(script form) - - self.allowed_attributes = Loofah::HTML5::WhiteList::ALLOWED_ATTRIBUTES - - self.allowed_css_properties = Loofah::HTML5::WhiteList::ALLOWED_CSS_PROPERTIES - - self.allowed_css_keywords = Loofah::HTML5::WhiteList::ALLOWED_CSS_KEYWORDS - - self.shorthand_css_properties = Loofah::HTML5::WhiteList::SHORTHAND_CSS_PROPERTIES - - self.allowed_protocols = Loofah::HTML5::WhiteList::ALLOWED_PROTOCOLS - end -end diff --git a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb b/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb deleted file mode 100644 index 43826f5e60..0000000000 --- a/actionview/lib/action_view/helpers/sanitize_helper/scrubbers.rb +++ /dev/null @@ -1,139 +0,0 @@ -# === PermitScrubber -# -# PermitScrubber allows you to permit only your own tags and/or attributes. -# -# 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 < PermitScrubber -# def allowed_node?(node) -# %w(form script comment blockquote).exclude?(node.name) -# 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: http://nokogiri.org/Nokogiri/XML/Node.html -class PermitScrubber < Loofah::Scrubber - attr_reader :tags, :attributes - - def initialize - @direction = :bottom_up - @tags, @attributes = nil, nil - end - - def tags=(tags) - @tags = validate!(tags, :tags) - end - - def attributes=(attributes) - @attributes = validate!(attributes, :attributes) - end - - def scrub(node) - return CONTINUE if skip_node?(node) - - unless keep_node?(node) - return STOP if scrub_node(node) == STOP - end - - scrub_attributes(node) - end - - protected - - def allowed_node?(node) - @tags.include?(node.name) - end - - def skip_node?(node) - node.text? || node.cdata? - end - - def scrub_attribute?(name) - @attributes.exclude?(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) - node.before(node.children) # strip - node.remove - end - - def scrub_attributes(node) - if @attributes - node.attributes.each do |name, _| - node.remove_attribute(name) if scrub_attribute?(name) - end - else - Loofah::HTML5::Scrub.scrub_attributes(node) - end - end - - def validate!(var, name) - if var && !var.is_a?(Enumerable) - raise ArgumentError, "You should pass :#{name} as an Enumerable" - end - var - end -end - -# === TargetScrubber -# -# Where PermitScrubber picks out tags and attributes to permit in -# sanitization, TargetScrubber targets them for removal -# -# The open architecture of PermitScrubber is used to redefine: -# - +allowed_node?+ -# # allowed if node is not in tags -# - +scrub_attribute?+ -# # should scrub if attribute name is not in attributes -# -# +tags=+ -# If set, elements included will be stripped. -# -# +attributes=+ -# If set, attributes included will be removed. -class TargetScrubber < PermitScrubber - def allowed_node?(node) - @tags.exclude?(node.name) - end - - def scrub_attribute?(name) - @attributes.include?(name) - end -end -- cgit v1.2.3 From 2a7f13ebf8cc27e0d0fa19cc737c9c2ecea3040e Mon Sep 17 00:00:00 2001 From: Timm Date: Sun, 13 Oct 2013 21:39:16 +0200 Subject: Made deprecation messages in sanitize_helper more clear. --- actionview/lib/action_view/helpers/sanitize_helper.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 13f946efa0..6e66a43a37 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -122,7 +122,7 @@ module ActionView attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer def sanitized_protocol_separator - ActiveSupport::Deprecation.warn('protocol_separator has been deprecated and has no effect.') + ActiveSupport::Deprecation.warn('sanitized_protocol_separator is deprecated and has no effect.') end def sanitized_uri_attributes @@ -130,7 +130,7 @@ module ActionView end def sanitized_bad_tags - ActiveSupport::Deprecation.warn('bad_tags has been deprecated and has no effect. You can still affect the tags being sanitized using Rails::Html::WhiteListSanitizer.bad_tags= which changes the allowed_tags.') + ActiveSupport::Deprecation.warn('sanitized_bad_tags is deprecated and has no effect. Affect the sanitized_allowed_tags using sanitized_bad_tags= instead.') end def sanitized_allowed_tags @@ -192,7 +192,7 @@ module ActionView def sanitized_protocol_separator=(value) - ActiveSupport::Deprecation.warn('protocol_separator= has been deprecated and has no effect.') + ActiveSupport::Deprecation.warn('sanitized_protocol_separator= is deprecated and has no effect.') end # Adds valid HTML attributes that the +sanitize+ helper checks for URIs. -- cgit v1.2.3 From 13da2788f3461fcf9ec6c7b3d4e38c2857a9665c Mon Sep 17 00:00:00 2001 From: Timm Date: Mon, 10 Feb 2014 12:07:52 +0100 Subject: Deprecate configurations and use allowed_tags and allowed_attributes on WhiteListSanitizer. --- .../lib/action_view/helpers/sanitize_helper.rb | 121 ++++----------------- 1 file changed, 22 insertions(+), 99 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 6e66a43a37..b614126aaf 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -121,40 +121,28 @@ module ActionView module ClassMethods #:nodoc: attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer - def sanitized_protocol_separator - ActiveSupport::Deprecation.warn('sanitized_protocol_separator is deprecated and has no effect.') - end - - def sanitized_uri_attributes - white_list_sanitizer.uri_attributes - end - - def sanitized_bad_tags - ActiveSupport::Deprecation.warn('sanitized_bad_tags is deprecated and has no effect. Affect the sanitized_allowed_tags using sanitized_bad_tags= instead.') + [:protocol_separator, + :uri_attributes, + :bad_tags, + :allowed_css_properties, + :allowed_css_keywords, + :shorthand_css_properties, + :allowed_protocols].each do |meth| + meth_name = "sanitized_#{meth}" + imp = lambda do |name| + ActiveSupport::Deprecation.warn("#{name} is deprecated and has no effect.") + end + + define_method(meth_name) { imp.(meth_name) } + define_method("#{meth_name}=") { |value| imp.("#{meth_name}=") } end def sanitized_allowed_tags - white_list_sanitizer.allowed_tags + Rails::Html::WhiteListSanitizer.allowed_tags end def sanitized_allowed_attributes - white_list_sanitizer.allowed_attributes - end - - def sanitized_allowed_css_properties - white_list_sanitizer.allowed_css_properties - end - - def sanitized_allowed_css_keywords - white_list_sanitizer.allowed_css_keywords - end - - def sanitized_shorthand_css_properties - white_list_sanitizer.shorthand_css_properties - end - - def sanitized_allowed_protocols - white_list_sanitizer.allowed_protocols + Rails::Html::WhiteListSanitizer.allowed_attributes end # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with @@ -190,89 +178,24 @@ module ActionView @white_list_sanitizer ||= Rails::Html::WhiteListSanitizer.new end - - def sanitized_protocol_separator=(value) - ActiveSupport::Deprecation.warn('sanitized_protocol_separator= is deprecated and has no effect.') - end - - # Adds valid HTML attributes that the +sanitize+ helper checks for URIs. - # - # class Application < Rails::Application - # config.action_view.sanitized_uri_attributes = 'lowsrc', 'target' - # end - # - def sanitized_uri_attributes=(attributes) - Rails::Html::WhiteListSanitizer.update_uri_attributes(attributes) - end - - # Adds to the Set of 'bad' tags for the +sanitize+ helper. - # - # class Application < Rails::Application - # config.action_view.sanitized_bad_tags = 'embed', 'object' - # end - # - def sanitized_bad_tags=(attributes) - Rails::Html::WhiteListSanitizer.bad_tags = attributes - end - - # Adds to the Set of allowed tags for the +sanitize+ helper. + # Replaces the Set of allowed tags for the +sanitize+ helper. # # class Application < Rails::Application # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' # end # - def sanitized_allowed_tags=(attributes) - Rails::Html::WhiteListSanitizer.update_allowed_tags(attributes) + def sanitized_allowed_tags=(*tags) + Rails::Html::WhiteListSanitizer.allowed_tags = tags end - # Adds to the Set of allowed HTML attributes for the +sanitize+ helper. + # Replaces the Set of 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) - Rails::Html::WhiteListSanitizer.update_allowed_attributes(attributes) - end - - # Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_css_properties = 'expression' - # end - # - def sanitized_allowed_css_properties=(attributes) - Rails::Html::WhiteListSanitizer.update_allowed_css_properties(attributes) - end - - # Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_css_keywords = 'expression' - # end - # - def sanitized_allowed_css_keywords=(attributes) - Rails::Html::WhiteListSanitizer.update_allowed_css_keywords(attributes) - end - - # Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers. - # - # class Application < Rails::Application - # config.action_view.sanitized_shorthand_css_properties = 'expression' - # end - # - def sanitized_shorthand_css_properties=(attributes) - Rails::Html::WhiteListSanitizer.update_shorthand_css_properties(attributes) - end - - # Adds to the Set of allowed protocols for the +sanitize+ helper. - # - # class Application < Rails::Application - # config.action_view.sanitized_allowed_protocols = 'ssh', 'feed' - # end - # - def sanitized_allowed_protocols=(attributes) - Rails::Html::WhiteListSanitizer.update_allowed_protocols(attributes) + def sanitized_allowed_attributes=(*attributes) + Rails::Html::WhiteListSanitizer.allowed_attributes = attributes end end end -- cgit v1.2.3 From 7587632b9346645d97b552ab0219bd209faa3238 Mon Sep 17 00:00:00 2001 From: Timm Date: Tue, 11 Feb 2014 21:01:41 +0100 Subject: Changed configuration documentation to no longer state it replaces a Set. --- actionview/lib/action_view/helpers/sanitize_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index b614126aaf..38354a1d0b 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -178,7 +178,7 @@ module ActionView @white_list_sanitizer ||= Rails::Html::WhiteListSanitizer.new end - # Replaces the Set of allowed tags for the +sanitize+ helper. + # Replaces the allowed tags for the +sanitize+ helper. # # class Application < Rails::Application # config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' @@ -188,7 +188,7 @@ module ActionView Rails::Html::WhiteListSanitizer.allowed_tags = tags end - # Replaces the Set of allowed HTML attributes for the +sanitize+ helper. + # Replaces the allowed HTML attributes for the +sanitize+ helper. # # class Application < Rails::Application # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc'] -- cgit v1.2.3 From 5d3a29229ba0a52c78d13aad99ac508f96778d77 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 23 May 2014 23:20:21 +0200 Subject: Delegate allowed tags and attributes setting to HTML::WhiteListSanitizer. --- actionview/lib/action_view/helpers/sanitize_helper.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 38354a1d0b..1acb8d5648 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -138,11 +138,11 @@ module ActionView end def sanitized_allowed_tags - Rails::Html::WhiteListSanitizer.allowed_tags + HTML::WhiteListSanitizer.allowed_tags end def sanitized_allowed_attributes - Rails::Html::WhiteListSanitizer.allowed_attributes + HTML::WhiteListSanitizer.allowed_attributes end # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with @@ -185,7 +185,7 @@ module ActionView # end # def sanitized_allowed_tags=(*tags) - Rails::Html::WhiteListSanitizer.allowed_tags = tags + HTML::WhiteListSanitizer.allowed_tags = tags end # Replaces the allowed HTML attributes for the +sanitize+ helper. @@ -195,7 +195,7 @@ module ActionView # end # def sanitized_allowed_attributes=(*attributes) - Rails::Html::WhiteListSanitizer.allowed_attributes = attributes + HTML::WhiteListSanitizer.allowed_attributes = attributes end end end -- cgit v1.2.3 From 427f3f90d4b20260a6de0990b05b74784a457ff0 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 23 May 2014 23:21:01 +0200 Subject: Add a layer of indirection making sanitizers pluggable. --- actionview/lib/action_view/helpers/sanitize_helper.rb | 11 ++++++++--- actionview/lib/action_view/vendor/html-scanner.rb | 1 + .../lib/action_view/vendor/html-scanner/html/sanitizer.rb | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 1acb8d5648..f205a988b4 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -137,6 +137,11 @@ module ActionView define_method("#{meth_name}=") { |value| imp.("#{meth_name}=") } end + # A class to vendor out the full, link and white list sanitizers + # Can be set to either HTML::Scanner or HTML::Sanitizer + mattr_accessor :sanitizer_vendor + self.sanitizer_vendor = HTML::Scanner + def sanitized_allowed_tags HTML::WhiteListSanitizer.allowed_tags end @@ -153,7 +158,7 @@ module ActionView # end # def full_sanitizer - @full_sanitizer ||= Rails::Html::FullSanitizer.new + @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new end # Gets the Rails::Html::LinkSanitizer instance used by +strip_links+. @@ -164,7 +169,7 @@ module ActionView # end # def link_sanitizer - @link_sanitizer ||= Rails::Html::LinkSanitizer.new + @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new end # Gets the Rails::Html::WhiteListSanitizer instance used by sanitize and +sanitize_css+. @@ -175,7 +180,7 @@ module ActionView # end # def white_list_sanitizer - @white_list_sanitizer ||= Rails::Html::WhiteListSanitizer.new + @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new end # Replaces the allowed tags for the +sanitize+ helper. diff --git a/actionview/lib/action_view/vendor/html-scanner.rb b/actionview/lib/action_view/vendor/html-scanner.rb index fa31b5107b..e76e15a51b 100644 --- a/actionview/lib/action_view/vendor/html-scanner.rb +++ b/actionview/lib/action_view/vendor/html-scanner.rb @@ -7,6 +7,7 @@ module HTML extend ActiveSupport::Autoload eager_autoload do + autoload :Scanner, 'html/sanitizer' autoload :CDATA, 'html/node' autoload :Document, 'html/document' autoload :FullSanitizer, 'html/sanitizer' diff --git a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb index ed34eecf55..36ec3ef6b3 100644 --- a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb @@ -3,6 +3,20 @@ require 'cgi' require 'active_support/core_ext/module/attribute_accessors' module HTML + module Scanner + def full_sanitizer + HTML::FullSanitizer + end + + def link_sanitizer + HTML::LinkSanitizer + end + + def white_list_sanitizer + HTML::WhiteListSanitizer + end + end + class Sanitizer def sanitize(text, options = {}) validate_options(options) -- cgit v1.2.3 From 017ddc6e248cea9bdfda4496c1505585b7452655 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 23 May 2014 23:21:27 +0200 Subject: Remove deprecation notice. --- actionview/lib/action_view/vendor/html-scanner.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/vendor/html-scanner.rb b/actionview/lib/action_view/vendor/html-scanner.rb index e76e15a51b..ef09b446a5 100644 --- a/actionview/lib/action_view/vendor/html-scanner.rb +++ b/actionview/lib/action_view/vendor/html-scanner.rb @@ -1,8 +1,6 @@ require 'active_support/deprecation' $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/html-scanner" -ActiveSupport::Deprecation.warn("html-scanner has been deprecated in favor of using Loofah in SanitizeHelper and Rails::Dom::Testing::Assertions.") - module HTML extend ActiveSupport::Autoload -- cgit v1.2.3 From 33019a321c7b8083068850750a3f4c466ae7c059 Mon Sep 17 00:00:00 2001 From: Timm Date: Fri, 23 May 2014 23:34:46 +0200 Subject: Remove html-scanner and its tests. --- actionview/lib/action_view/vendor/html-scanner.rb | 22 - .../vendor/html-scanner/html/document.rb | 68 -- .../action_view/vendor/html-scanner/html/node.rb | 532 ------------- .../vendor/html-scanner/html/sanitizer.rb | 202 ----- .../vendor/html-scanner/html/selector.rb | 830 --------------------- .../vendor/html-scanner/html/tokenizer.rb | 107 --- .../vendor/html-scanner/html/version.rb | 11 - 7 files changed, 1772 deletions(-) delete mode 100644 actionview/lib/action_view/vendor/html-scanner.rb delete mode 100644 actionview/lib/action_view/vendor/html-scanner/html/document.rb delete mode 100644 actionview/lib/action_view/vendor/html-scanner/html/node.rb delete mode 100644 actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb delete mode 100644 actionview/lib/action_view/vendor/html-scanner/html/selector.rb delete mode 100644 actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb delete mode 100644 actionview/lib/action_view/vendor/html-scanner/html/version.rb (limited to 'actionview/lib/action_view') diff --git a/actionview/lib/action_view/vendor/html-scanner.rb b/actionview/lib/action_view/vendor/html-scanner.rb deleted file mode 100644 index ef09b446a5..0000000000 --- a/actionview/lib/action_view/vendor/html-scanner.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'active_support/deprecation' -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/html-scanner" - -module HTML - extend ActiveSupport::Autoload - - eager_autoload do - autoload :Scanner, 'html/sanitizer' - autoload :CDATA, 'html/node' - autoload :Document, 'html/document' - autoload :FullSanitizer, 'html/sanitizer' - autoload :LinkSanitizer, 'html/sanitizer' - autoload :Node, 'html/node' - autoload :Sanitizer, 'html/sanitizer' - autoload :Selector, 'html/selector' - autoload :Tag, 'html/node' - autoload :Text, 'html/node' - autoload :Tokenizer, 'html/tokenizer' - autoload :Version, 'html/version' - autoload :WhiteListSanitizer, 'html/sanitizer' - end -end diff --git a/actionview/lib/action_view/vendor/html-scanner/html/document.rb b/actionview/lib/action_view/vendor/html-scanner/html/document.rb deleted file mode 100644 index 386820300a..0000000000 --- a/actionview/lib/action_view/vendor/html-scanner/html/document.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'html/tokenizer' -require 'html/node' -require 'html/selector' -require 'html/sanitizer' - -module HTML #:nodoc: - # A top-level HTML document. You give it a body of text, and it will parse that - # text into a tree of nodes. - class Document #:nodoc: - - # The root of the parsed document. - attr_reader :root - - # Create a new Document from the given text. - def initialize(text, strict=false, xml=false) - tokenizer = Tokenizer.new(text) - @root = Node.new(nil) - node_stack = [ @root ] - while token = tokenizer.next - node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict) - - node_stack.last.children << node unless node.tag? && node.closing == :close - if node.tag? - if node_stack.length > 1 && node.closing == :close - if node_stack.last.name == node.name - if node_stack.last.children.empty? - node_stack.last.children << Text.new(node_stack.last, node.line, node.position, "") - end - node_stack.pop - else - open_start = node_stack.last.position - 20 - open_start = 0 if open_start < 0 - close_start = node.position - 20 - close_start = 0 if close_start < 0 - msg = < hash } unless Hash === hash - hash = keys_to_symbols(hash) - hash.each do |k,v| - case k - when :tag, :content then - # keys are valid, and require no further processing - when :attributes then - hash[k] = keys_to_strings(v) - when :parent, :child, :ancestor, :descendant, :sibling, :before, - :after - hash[k] = Conditions.new(v) - when :children - hash[k] = v = keys_to_symbols(v) - v.each do |key,value| - case key - when :count, :greater_than, :less_than - # keys are valid, and require no further processing - when :only - v[key] = Conditions.new(value) - else - raise "illegal key #{key.inspect} => #{value.inspect}" - end - end - else - raise "illegal key #{k.inspect} => #{v.inspect}" - end - end - update hash - end - - private - - def keys_to_strings(hash) - Hash[hash.keys.map {|k| [k.to_s, hash[k]]}] - end - - def keys_to_symbols(hash) - Hash[hash.keys.map do |k| - raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym) - [k.to_sym, hash[k]] - end] - end - end - - # The base class of all nodes, textual and otherwise, in an HTML document. - class Node #:nodoc: - # The array of children of this node. Not all nodes have children. - attr_reader :children - - # The parent node of this node. All nodes have a parent, except for the - # root node. - attr_reader :parent - - # The line number of the input where this node was begun - attr_reader :line - - # The byte position in the input where this node was begun - attr_reader :position - - # Create a new node as a child of the given parent. - def initialize(parent, line=0, pos=0) - @parent = parent - @children = [] - @line, @position = line, pos - end - - # Returns a textual representation of the node. - def to_s - @children.join() - end - - # Returns false (subclasses must override this to provide specific matching - # behavior.) +conditions+ may be of any type. - def match(conditions) - false - end - - # Search the children of this node for the first node for which #find - # returns non +nil+. Returns the result of the #find call that succeeded. - def find(conditions) - conditions = validate_conditions(conditions) - @children.each do |child| - node = child.find(conditions) - return node if node - end - nil - end - - # Search for all nodes that match the given conditions, and return them - # as an array. - def find_all(conditions) - conditions = validate_conditions(conditions) - - matches = [] - matches << self if match(conditions) - @children.each do |child| - matches.concat child.find_all(conditions) - end - matches - end - - # Returns +false+. Subclasses may override this if they define a kind of - # tag. - def tag? - false - end - - def validate_conditions(conditions) - Conditions === conditions ? conditions : Conditions.new(conditions) - end - - def ==(node) - return false unless self.class == node.class && children.size == node.children.size - - equivalent = true - - children.size.times do |i| - equivalent &&= children[i] == node.children[i] - end - - equivalent - end - - class </) - if strict - raise "expected ]]> (got #{scanner.rest.inspect} for #{content})" - else - scanner.skip_until(/\Z/) - end - end - - return CDATA.new(parent, line, pos, scanner.pre_match.gsub(/\/]+/) - name.downcase! - - unless closing - scanner.skip(/\s*/) - attributes = {} - while attr = scanner.scan(/[-\w:]+/) - value = true - if scanner.scan(/\s*=\s*/) - if delim = scanner.scan(/['"]/) - value = "" - while text = scanner.scan(/[^#{delim}\\]+|./) - case text - when "\\" then - value << text - break if scanner.eos? - value << scanner.getch - when delim - break - else value << text - end - end - else - value = scanner.scan(/[^\s>\/]+/) - end - end - attributes[attr.downcase] = value - scanner.skip(/\s*/) - end - - closing = ( scanner.scan(/\//) ? :self : nil ) - end - - unless scanner.scan(/\s*>/) - if strict - raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})" - else - # throw away all text until we find what we're looking for - scanner.skip_until(/>/) or scanner.terminate - end - end - - Tag.new(parent, line, pos, name, attributes, closing) - end - end - end - end - - # A node that represents text, rather than markup. - class Text < Node #:nodoc: - - attr_reader :content - - # Creates a new text node as a child of the given parent, with the given - # content. - def initialize(parent, line, pos, content) - super(parent, line, pos) - @content = content - end - - # Returns the content of this node. - def to_s - @content - end - - # Returns +self+ if this node meets the given conditions. Text nodes support - # conditions of the following kinds: - # - # * if +conditions+ is a string, it must be a substring of the node's - # content - # * if +conditions+ is a regular expression, it must match the node's - # content - # * if +conditions+ is a hash, it must contain a :content key that - # is either a string or a regexp, and which is interpreted as described - # above. - def find(conditions) - match(conditions) && self - end - - # Returns non-+nil+ if this node meets the given conditions, or +nil+ - # otherwise. See the discussion of #find for the valid conditions. - def match(conditions) - case conditions - when String - @content == conditions - when Regexp - @content =~ conditions - when Hash - conditions = validate_conditions(conditions) - - # Text nodes only have :content, :parent, :ancestor - unless (conditions.keys - [:content, :parent, :ancestor]).empty? - return false - end - - match(conditions[:content]) - else - nil - end - end - - def ==(node) - return false unless super - content == node.content - end - end - - # A CDATA node is simply a text node with a specialized way of displaying - # itself. - class CDATA < Text #:nodoc: - def to_s - "" - end - end - - # A Tag is any node that represents markup. It may be an opening tag, a - # closing tag, or a self-closing tag. It has a name, and may have a hash of - # attributes. - class Tag < Node #:nodoc: - - # Either +nil+, :close, or :self - attr_reader :closing - - # Either +nil+, or a hash of attributes for this node. - attr_reader :attributes - - # The name of this tag. - attr_reader :name - - # Create a new node as a child of the given parent, using the given content - # to describe the node. It will be parsed and the node name, attributes and - # closing status extracted. - def initialize(parent, line, pos, name, attributes, closing) - super(parent, line, pos) - @name = name - @attributes = attributes - @closing = closing - end - - # A convenience for obtaining an attribute of the node. Returns +nil+ if - # the node has no attributes. - def [](attr) - @attributes ? @attributes[attr] : nil - end - - # Returns non-+nil+ if this tag can contain child nodes. - def childless?(xml = false) - return false if xml && @closing.nil? - !@closing.nil? || - @name =~ /^(img|br|hr|link|meta|area|base|basefont| - col|frame|input|isindex|param)$/ox - end - - # Returns a textual representation of the node - def to_s - if @closing == :close - "" - else - s = "<#{@name}" - @attributes.each do |k,v| - s << " #{k}" - s << "=\"#{v}\"" if String === v - end - s << " /" if @closing == :self - s << ">" - @children.each { |child| s << child.to_s } - s << "" if @closing != :self && !@children.empty? - s - end - end - - # If either the node or any of its children meet the given conditions, the - # matching node is returned. Otherwise, +nil+ is returned. (See the - # description of the valid conditions in the +match+ method.) - def find(conditions) - match(conditions) && self || super - end - - # Returns +true+, indicating that this node represents an HTML tag. - def tag? - true - end - - # Returns +true+ if the node meets any of the given conditions. The - # +conditions+ parameter must be a hash of any of the following keys - # (all are optional): - # - # * :tag: the node name must match the corresponding value - # * :attributes: a hash. The node's values must match the - # corresponding values in the hash. - # * :parent: a hash. The node's parent must match the - # corresponding hash. - # * :child: a hash. At least one of the node's immediate children - # must meet the criteria described by the hash. - # * :ancestor: a hash. At least one of the node's ancestors must - # meet the criteria described by the hash. - # * :descendant: a hash. At least one of the node's descendants - # must meet the criteria described by the hash. - # * :sibling: a hash. At least one of the node's siblings must - # meet the criteria described by the hash. - # * :after: a hash. The node must be after any sibling meeting - # the criteria described by the hash, and at least one sibling must match. - # * :before: a hash. The node must be before any sibling meeting - # the criteria described by the hash, and at least one sibling must match. - # * :children: a hash, for counting children of a node. Accepts the - # keys: - # ** :count: either a number or a range which must equal (or - # include) the number of children that match. - # ** :less_than: the number of matching children must be less than - # this number. - # ** :greater_than: the number of matching children must be - # greater than this number. - # ** :only: another hash consisting of the keys to use - # to match on the children, and only matching children will be - # counted. - # - # Conditions are matched using the following algorithm: - # - # * if the condition is a string, it must be a substring of the value. - # * if the condition is a regexp, it must match the value. - # * if the condition is a number, the value must match number.to_s. - # * if the condition is +true+, the value must not be +nil+. - # * if the condition is +false+ or +nil+, the value must be +nil+. - # - # Usage: - # - # # test if the node is a "span" tag - # node.match tag: "span" - # - # # test if the node's parent is a "div" - # node.match parent: { tag: "div" } - # - # # test if any of the node's ancestors are "table" tags - # node.match ancestor: { tag: "table" } - # - # # test if any of the node's immediate children are "em" tags - # node.match child: { tag: "em" } - # - # # test if any of the node's descendants are "strong" tags - # node.match descendant: { tag: "strong" } - # - # # test if the node has between 2 and 4 span tags as immediate children - # node.match children: { count: 2..4, only: { tag: "span" } } - # - # # get funky: test to see if the node is a "div", has a "ul" ancestor - # # and an "li" parent (with "class" = "enum"), and whether or not it has - # # a "span" descendant that contains # text matching /hello world/: - # node.match tag: "div", - # ancestor: { tag: "ul" }, - # parent: { tag: "li", - # attributes: { class: "enum" } }, - # descendant: { tag: "span", - # child: /hello world/ } - def match(conditions) - conditions = validate_conditions(conditions) - # check content of child nodes - if conditions[:content] - if children.empty? - return false unless match_condition("", conditions[:content]) - else - return false unless children.find { |child| child.match(conditions[:content]) } - end - end - - # test the name - return false unless match_condition(@name, conditions[:tag]) if conditions[:tag] - - # test attributes - (conditions[:attributes] || {}).each do |key, value| - return false unless match_condition(self[key], value) - end - - # test parent - return false unless parent.match(conditions[:parent]) if conditions[:parent] - - # test children - return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child] - - # test ancestors - if conditions[:ancestor] - return false unless catch :found do - p = self - throw :found, true if p.match(conditions[:ancestor]) while p = p.parent - end - end - - # test descendants - if conditions[:descendant] - return false unless children.find do |child| - # test the child - child.match(conditions[:descendant]) || - # test the child's descendants - child.match(:descendant => conditions[:descendant]) - end - end - - # count children - if opts = conditions[:children] - matches = children.select do |c| - (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?)) - end - - matches = matches.select { |c| c.match(opts[:only]) } if opts[:only] - opts.each do |key, value| - next if key == :only - case key - when :count - if Integer === value - return false if matches.length != value - else - return false unless value.include?(matches.length) - end - when :less_than - return false unless matches.length < value - when :greater_than - return false unless matches.length > value - else raise "unknown count condition #{key}" - end - end - end - - # test siblings - if conditions[:sibling] || conditions[:before] || conditions[:after] - siblings = parent ? parent.children : [] - self_index = siblings.index(self) - - if conditions[:sibling] - return false unless siblings.detect do |s| - s != self && s.match(conditions[:sibling]) - end - end - - if conditions[:before] - return false unless siblings[self_index+1..-1].detect do |s| - s != self && s.match(conditions[:before]) - end - end - - if conditions[:after] - return false unless siblings[0,self_index].detect do |s| - s != self && s.match(conditions[:after]) - end - end - end - - true - end - - def ==(node) - return false unless super - return false unless closing == node.closing && self.name == node.name - attributes == node.attributes - end - - private - # Match the given value to the given condition. - def match_condition(value, condition) - case condition - when String - value && value == condition - when Regexp - value && value.match(condition) - when Numeric - value == condition.to_s - when true - !value.nil? - when false, nil - value.nil? - else - false - end - end - end -end diff --git a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb deleted file mode 100644 index 36ec3ef6b3..0000000000 --- a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb +++ /dev/null @@ -1,202 +0,0 @@ -require 'set' -require 'cgi' -require 'active_support/core_ext/module/attribute_accessors' - -module HTML - module Scanner - def full_sanitizer - HTML::FullSanitizer - end - - def link_sanitizer - HTML::LinkSanitizer - end - - def white_list_sanitizer - HTML::WhiteListSanitizer - end - end - - class Sanitizer - def sanitize(text, options = {}) - validate_options(options) - return text unless sanitizeable?(text) - tokenize(text, options).join - end - - def sanitizeable?(text) - !(text.nil? || text.empty? || !text.index("<")) - end - - protected - def tokenize(text, options) - tokenizer = HTML::Tokenizer.new(text) - result = [] - while token = tokenizer.next - node = Node.parse(nil, 0, 0, token, false) - process_node node, result, options - end - result - end - - def process_node(node, result, options) - result << node.to_s - end - - def validate_options(options) - if options[:tags] && !options[:tags].is_a?(Enumerable) - raise ArgumentError, "You should pass :tags as an Enumerable" - end - - if options[:attributes] && !options[:attributes].is_a?(Enumerable) - raise ArgumentError, "You should pass :attributes as an Enumerable" - end - end - end - - class FullSanitizer < Sanitizer - def sanitize(text, options = {}) - result = super - # strip any comments, and if they have a newline at the end (ie. line with - # only a comment) strip that too - result = result.gsub(/[\n]?/m, "") if (result && result =~ /[\n]?/m) - # Recurse - handle all dirty nested tags - result == text ? result : sanitize(result, options) - end - - def process_node(node, result, options) - result << node.to_s if node.class == HTML::Text - end - end - - class LinkSanitizer < FullSanitizer - cattr_accessor :included_tags, :instance_writer => false - self.included_tags = Set.new(%w(a href)) - - def sanitizeable?(text) - !(text.nil? || text.empty? || !((text.index(""))) - end - - protected - def process_node(node, result, options) - result << node.to_s unless node.is_a?(HTML::Tag) && included_tags.include?(node.name) - end - end - - class WhiteListSanitizer < Sanitizer - [:protocol_separator, :uri_attributes, :allowed_attributes, :allowed_tags, :allowed_protocols, :bad_tags, - :allowed_css_properties, :allowed_css_keywords, :shorthand_css_properties].each do |attr| - class_attribute attr, :instance_writer => false - end - - # A regular expression of the valid characters used to separate protocols like - # the ':' in 'http://foo.com' - self.protocol_separator = /:|(�*58)|(p)|(�*3a)|(%|%)3A/i - - # Specifies a Set of HTML attributes that can have URIs. - self.uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc)) - - # Specifies a Set of 'bad' tags that the #sanitize helper will remove completely, as opposed - # to just escaping harmless tags like <font> - self.bad_tags = Set.new(%w(script)) - - # Specifies the default Set of tags that the #sanitize helper will allow unscathed. - self.allowed_tags = Set.new(%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)) - - # Specifies the default Set of html attributes that the #sanitize helper will leave - # in the allowed tag. - self.allowed_attributes = Set.new(%w(href src width height alt cite datetime title class name xml:lang abbr)) - - # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept. - self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto - feed svn urn aim rsync tag ssh sftp rtsp afs)) - - # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept. - self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse - border-color border-left-color border-right-color border-top-color clear color cursor direction display - elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height - overflow pause pause-after pause-before pitch pitch-range richness speak speak-header speak-numeral speak-punctuation - speech-rate stress text-align text-decoration text-indent unicode-bidi vertical-align voice-family volume white-space - width)) - - # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept. - self.allowed_css_keywords = Set.new(%w(auto aqua black block blue bold both bottom brown center - collapse dashed dotted fuchsia gray green !important italic left lime maroon medium none navy normal - nowrap olive pointer purple red right solid silver teal top transparent underline white yellow)) - - # Specifies the default Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers. - self.shorthand_css_properties = Set.new(%w(background border margin padding)) - - # Sanitizes a block of css code. Used by #sanitize when it comes across a style attribute - def sanitize_css(style) - # disallow urls - style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ') - - # gauntlet - if style !~ /\A([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*\z/ || - style !~ /\A(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*\z/ - return '' - end - - clean = [] - style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val| - if allowed_css_properties.include?(prop.downcase) - clean << prop + ': ' + val + ';' - elsif shorthand_css_properties.include?(prop.split('-')[0].downcase) - unless val.split().any? do |keyword| - !allowed_css_keywords.include?(keyword) && - keyword !~ /\A(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)\z/ - end - clean << prop + ': ' + val + ';' - end - end - end - clean.join(' ') - end - - protected - def tokenize(text, options) - options[:parent] = [] - options[:attributes] ||= allowed_attributes - options[:tags] ||= allowed_tags - super - end - - def process_node(node, result, options) - result << case node - when HTML::Tag - if node.closing == :close - options[:parent].shift - else - options[:parent].unshift node.name - end - - process_attributes_for node, options - - options[:tags].include?(node.name) ? node : nil - else - bad_tags.include?(options[:parent].first) ? nil : node.to_s.gsub(//login. - # - # === Matching Elements - # - # Use the #match method to determine if an element matches the selector. - # - # For simple selectors, the method returns an array with that element, - # or +nil+ if the element does not match. For complex selectors (see below) - # the method returns an array with all matched elements, of +nil+ if no - # match found. - # - # For example: - # if selector.match(element) - # puts "Element is a login form" - # end - # - # === Selecting Elements - # - # Use the #select method to select all matching elements starting with - # one element and going through all children in depth-first order. - # - # This method returns an array of all matching elements, an empty array - # if no match is found - # - # For example: - # selector = HTML::Selector.new "input[type=text]" - # matches = selector.select(element) - # matches.each do |match| - # puts "Found text field with name #{match.attributes['name']}" - # end - # - # === Expressions - # - # Selectors can match elements using any of the following criteria: - # * name -- Match an element based on its name (tag name). - # For example, p to match a paragraph. You can use * - # to match any element. - # * #id -- Match an element based on its identifier (the - # id attribute). For example, #page. - # * .class -- Match an element based on its class name, all - # class names if more than one specified. - # * [attr] -- Match an element that has the specified attribute. - # * [attr=value] -- Match an element that has the specified - # attribute and value. (More operators are supported see below) - # * :pseudo-class -- Match an element based on a pseudo class, - # such as :nth-child and :empty. - # * :not(expr) -- Match an element that does not match the - # negation expression. - # - # When using a combination of the above, the element name comes first - # followed by identifier, class names, attributes, pseudo classes and - # negation in any order. Do not separate these parts with spaces! - # Space separation is used for descendant selectors. - # - # For example: - # selector = HTML::Selector.new "form.login[action=/login]" - # The matched element must be of type +form+ and have the class +login+. - # It may have other classes, but the class +login+ is required to match. - # It must also have an attribute called +action+ with the value - # /login. - # - # This selector will match the following element: - #