How to only make links html safe and ignore other html tags in Rails

1.4k views Asked by At

I have a requirement where I need to make links clickable in text while keeping any other html tags as text (not html_safe). This means I cannot make the entire text html_safe as that will render the other html tags and I cannot sanitize the text and remove the other html tags. I've seen other websites handle this by making the html_safe links and other text on their own lines. It looks like the following when inspecting the html.

<span>
  "This is an "
  <a href="https://example.com/" target="_blank" rel="nofollow">https://example.com/</a> 
  "other <b>HTML</b>"
</span>

What would be the best way to do this in Rails 4?

2

There are 2 answers

4
max On

When you call .html_safe on a string your actually getting an object that behaves like a string but is a ActiveSupport::SafeBuffer. When you append a string to a ActiveSupport::SafeBuffer its automatically escaped. Lets say you want to construct a span where the text is user input:

'<span>'.html_safe + text +'</span>'.html_safe

In this case we are safe against an XXS attack as the user originated text is automatically escaped:

irb(main):004:0> "<span>".html_safe + "<script>alert('You have been haxxored!')</script>" + "</span>".html_safe
=> "<span>&lt;script&gt;alert(&#39;You have been haxxored&#39;)&lt;/script&gt;</span>"

That's what happens automatically when you output a variable in your views as the view is constructed as a ActiveSupport::SafeBuffer. Whenever you output a regular string it will be automatically escaped thus its secure by default.

Of course there is always going to be a huge number of programmers that just give proceed to give themselves a XSS vulnerability out of ignorance:

# WAAAH! Rails is escaping my tags! Bad rails!
'<span>'+ text +'</span>'.html_safe

Another way to approach the problem is to use the tag helpers, partials or Nokogiri instead of using string concatenation to construct HTML which in itself is tedious, borderline unreadible and error prone.

0
TenJack On

I was able to get this working using the following.

  #module ApplicationHelper
  def url_regexp
    @url_regexp ||= %r{
      (?:(?:https?|ftp|file):\/\/|www\.|ftp\.)
      (?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|
           [-A-Z0-9+&@#\/%=~_|$?!:,.])*
      (?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)|
            [A-Z0-9+&@#\/%=~_|$])
    }ix
  end

  #in the view
    <%- "This is a test https://example.com".partition(url_regexp).each do |text| %>
      <%- if text =~ url_regexp %>
        <%= "<a href='#{text}' target='_blank'>#{text}</a>".html_safe %>
      <%- else %>
        <%= text %>
    <% end %>