diff --git a/README.md b/README.md index 69767b2..9774e77 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Active Record extensions for HTML sanitization are available in the [`loofah-act * _Whitewash_ the markup, removing all attributes and namespaced nodes. * Other common HTML transformations are built-in: * Add the _nofollow_ attribute to all hyperlinks. + * Add the _target=\_blank_ attribute to all hyperlinks. * Remove _unprintable_ characters from text nodes. * Format markup as plain text, with (or without) sensible whitespace handling around block elements. * Replace Rails's `strip_tags` and `sanitize` view helper methods. @@ -231,6 +232,7 @@ Loofah also comes with some common transformation tasks: ``` ruby doc.scrub!(:nofollow) # adds rel="nofollow" attribute to links doc.scrub!(:unprintable) # removes unprintable characters from text nodes +doc.scrub!(:targetblank) # adds target="_blank" attribute to links ``` See `Loofah::Scrubbers` for more details and example usage. diff --git a/lib/loofah/scrubbers.rb b/lib/loofah/scrubbers.rb index 6823018..c6cc0a7 100644 --- a/lib/loofah/scrubbers.rb +++ b/lib/loofah/scrubbers.rb @@ -61,6 +61,15 @@ module Loofah # => "ohai! I like your blog post" # # + # === Loofah::Scrubbers::TargetBlank / scrub!(:targetblank) + # + # +:targetblank+ adds a target="_blank" attribute to all links + # + # link_farmers_markup = "ohai! I like your blog post" + # Loofah.html5_fragment(link_farmers_markup).scrub!(:targetblank) + # => "ohai! I like your blog post" + # + # # === Loofah::Scrubbers::NoOpener / scrub!(:noopener) # # +:noopener+ adds a rel="noopener" attribute to all links @@ -213,6 +222,33 @@ def scrub(node) end end + # + # === scrub!(:targetblank) + # + # +:targetblank+ adds a target="_blank" attribute to all links. + # If there is a target already set, replaces it with target="_blank". + # + # link_farmers_markup = "ohai! I like your blog post" + # Loofah.html5_fragment(link_farmers_markup).scrub!(:targetblank) + # => "ohai! I like your blog post" + # + # On modern browsers, setting target="_blank" on anchor elements implicitly provides the same + # behavior as setting rel="noopener". + # + class TargetBlank < Scrubber + def initialize # rubocop:disable Lint/MissingSuper + @direction = :top_down + end + + def scrub(node) + return CONTINUE unless (node.type == Nokogiri::XML::Node::ELEMENT_NODE) && (node.name == "a") + + node.set_attribute("target", "_blank") + + STOP + end + end + # # === scrub!(:noopener) # @@ -292,6 +328,7 @@ def scrub(node) strip: Strip, nofollow: NoFollow, noopener: NoOpener, + targetblank: TargetBlank, newline_block_elements: NewlineBlockElements, unprintable: Unprintable, } diff --git a/test/integration/test_scrubbers.rb b/test/integration/test_scrubbers.rb index b1dc67d..6480ac4 100644 --- a/test/integration/test_scrubbers.rb +++ b/test/integration/test_scrubbers.rb @@ -16,6 +16,12 @@ class IntegrationTestScrubbers < Loofah::TestCase NOFOLLOW_FRAGMENT = 'Click here' NOFOLLOW_RESULT = 'Click here' + TARGET_FRAGMENT = 'Click here' + TARGET_RESULT = 'Click here' + + TARGET_WITH_TOP_FRAGMENT = 'Click here' + TARGET_WITH_TOP_RESULT = 'Click here' + NOFOLLOW_WITH_REL_FRAGMENT = 'Click here' NOFOLLOW_WITH_REL_RESULT = 'Click here' @@ -182,6 +188,28 @@ def html5? end end + context ":targetblank" do + context "when target is not set" do + it "adds a target='_blank' attribute to hyperlinks" do + doc = klass.parse("#{TARGET_FRAGMENT}") + result = doc.scrub!(:targetblank) + + assert_equal TARGET_RESULT, doc.xpath("/html/body").inner_html + assert_equal doc, result + end + end + + context "when target is set" do + it "replaces existing 'target' attribute with '_blank' to hyperlinks" do + doc = klass.parse("#{TARGET_WITH_TOP_FRAGMENT}") + result = doc.scrub!(:targetblank) + + assert_equal TARGET_WITH_TOP_RESULT, doc.xpath("/html/body").inner_html + assert_equal doc, result + end + end + end + context ":unprintable" do it "removes unprintable unicode characters" do doc = klass.parse("#{UNPRINTABLE_FRAGMENT}")