Posted over 9 years ago. Visible to the public. Repeats.

Don't mix Array#join and String#html_safe

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;'


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


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>'
Growing Rails Applications in Practice
Check out our new e-book:
Learn to structure large Ruby on Rails codebases with the tools you already know and love.

Owner of this card:

Henning Koch
Last edit:
27 days ago
by Henning Koch
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Henning Koch to makandra dev
This website uses short-lived cookies to improve usability.
Accept or learn more