Skip to content

Commit

Permalink
feat: adds :targetblank scrubber
Browse files Browse the repository at this point in the history
`: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! <a href='http://www.myswarmysite.com/'>I like your blog post</a>"
  Loofah.html5_fragment(link_farmers_markup).scrub!(:targetblank)
  => "ohai! <a href='http://www.myswarmysite.com/' target="_blank">I like your blog post</a>"
```

Co-authored-by: Thiago Araujo <thd.araujo@gmail.com>
  • Loading branch information
Stefanni Brasil and thdaraujo committed Oct 14, 2023
1 parent 992b054 commit 09e11ad
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
37 changes: 37 additions & 0 deletions lib/loofah/scrubbers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ module Loofah
# => "ohai! <a href='http://www.myswarmysite.com/' rel="nofollow">I like your blog post</a>"
#
#
# === Loofah::Scrubbers::TargetBlank / scrub!(:targetblank)
#
# +:targetblank+ adds a target="_blank" attribute to all links
#
# link_farmers_markup = "ohai! <a href='http://www.myswarmysite.com/'>I like your blog post</a>"
# Loofah.html5_fragment(link_farmers_markup).scrub!(:targetblank)
# => "ohai! <a href='http://www.myswarmysite.com/' target="_blank">I like your blog post</a>"
#
#
# === Loofah::Scrubbers::NoOpener / scrub!(:noopener)
#
# +:noopener+ adds a rel="noopener" attribute to all links
Expand Down Expand Up @@ -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! <a href='http://www.myswarmysite.com/'>I like your blog post</a>"
# Loofah.html5_fragment(link_farmers_markup).scrub!(:targetblank)
# => "ohai! <a href='http://www.myswarmysite.com/' target="_blank">I like your blog post</a>"
#
# 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)
#
Expand Down Expand Up @@ -292,6 +328,7 @@ def scrub(node)
strip: Strip,
nofollow: NoFollow,
noopener: NoOpener,
targetblank: TargetBlank,
newline_block_elements: NewlineBlockElements,
unprintable: Unprintable,
}
Expand Down
28 changes: 28 additions & 0 deletions test/integration/test_scrubbers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ class IntegrationTestScrubbers < Loofah::TestCase
NOFOLLOW_FRAGMENT = '<a href="http://www.example.com/">Click here</a>'
NOFOLLOW_RESULT = '<a href="http://www.example.com/" rel="nofollow">Click here</a>'

TARGET_FRAGMENT = '<a href="http://www.example.com/">Click here</a>'
TARGET_RESULT = '<a href="http://www.example.com/" target="_blank">Click here</a>'

TARGET_WITH_TOP_FRAGMENT = '<a href="http://www.example.com/" target="_top">Click here</a>'
TARGET_WITH_TOP_RESULT = '<a href="http://www.example.com/" target="_blank">Click here</a>'

NOFOLLOW_WITH_REL_FRAGMENT = '<a href="http://www.example.com/" rel="noopener">Click here</a>'
NOFOLLOW_WITH_REL_RESULT = '<a href="http://www.example.com/" rel="noopener nofollow">Click here</a>'

Expand Down Expand Up @@ -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("<html><body>#{TARGET_FRAGMENT}</body></html>")
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("<html><body>#{TARGET_WITH_TOP_FRAGMENT}</body></html>")
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("<html><body>#{UNPRINTABLE_FRAGMENT}</body></html>")
Expand Down

0 comments on commit 09e11ad

Please sign in to comment.