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 '<span>foo</span><span&t;bar</span>'
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 '<span>foo</span> <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 '<span>foo</span> <span>bar</span>'