Don't mix Array#join and String#html_safe

Updated . Posted . Visible to the public. Repeats.

You cannot use Array#join on an array of strings where some strings are html_safe and others are not. The result will be an unsafe string and will thus be escaped when rendered in a view:

unsafe_string = '<span>foo</span>'
safe_string = '<span>bar</span>'.html_safe
[unsafe_string, safe_string].join(' ') # will incorrectly render as '&lt;span&gt;foo&lt;/span&gt;&lt;span&t;bar&lt;/span&gt;'

Bad

The solution is not to call html_safe on the joined array and if you thought it would be, you don't understand how XSS protection works in Rails. Calling html_safe on the joined array will incorrectly bless the complete string as safe:

[safe_string, unsafe_string].join(' ').html_safe # will incorrectly render as '<span>foo</span><span>bar</span>' with unescaped tags

Good

Rails >=3

safe_join([unsafe_string, safe_string], ' ') # will correctly render as '&lt;span&gt;foo&lt;/span&gt; <span>bar</span>'

Note: The second argument is the separator, that could also be something like content_tag(:br).

If you need to use safe_join() in a model, you need to include ActionView::Helpers::OutputSafetyHelper.

Rails 2

Roll a custom join method that is aware of Rails XSS protection. Copy the following code to config/initializers/xss_aware_join.rb:

Array.class_eval do
  def xss_aware_join(delimiter = '')
    ''.html_safe.tap do |str|
      each_with_index do |element, i|
        str << delimiter if i > 0
        str << element
      end
    end
  end
end

You can now say:

[unsafe_string, safe_string].xss_aware_join(' ') # will correctly render as '&lt;span&gt;foo&lt;/span&gt; <span>bar</span>'
Henning Koch
Last edit
Michael Leimstädtner
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra dev (2011-01-24 10:12)