Preventing HTML character entities in locale files from getting munged by Rails3 xss protection

Viewed 7706

We're building an app, our first using Rails 3, and we're having to build I18n in from the outset. Being perfectionists, we want real typography to be used in our views: dashes, curled quotes, ellipses et al.

This means in our locales/xx.yml files we have two choices:

  1. Use real UTF-8 characters inline. Should work, but hard to type, and scares me due to the amount of software which still does naughty things to unicode.
  2. Use HTML character entities (’ — etc). Easier to type, and probably more compatible with misbehaving software.

I'd rather take the second option, however the auto-escaping in Rails 3 makes this problematic, as the ampersands in the YAML get auto-converted into character entities themselves, resulting in 'visible' &8217;s in the browser.

Obviously this can be worked around by using raw on strings, i.e.:

raw t('views.signup.organisation_details')

But we're not happy going down the route of globally raw-ing every time we t something as it leaves us open to making an error and producing an XSS hole.

We could selectively raw strings which we know contain character entities, but this would be hard to scale, and just feels wrong - besides, a string which contains an entity in one language may not in another.

Any suggestions on a clever rails-y way to fix this? Or are we doomed to crap typography, xss holes, hours of wasted effort or all thre?

5 Answers

There is a ticket in lighthouse for this problem, and the resolution is to append _html to the i18n key in the locales/xx.yml file and use the t alias1 to denote an html_safe string. For example:

en:
  hello: "This is a string with an accent: ó"

becomes:

en:
  hello_html: "This is a string with an accent: ó"

And it would create the following output:

This is a string with an accent: ó

This would prevent you from having to write raw t('views.signup.organisation_details') and would result in a cleaner output of: t('views.signup.organisation_details_html'). And while exchanging raw for _html doesn't seem like the greatest of trades, it does make things clear that you're outputting what is assumed to be an html_safe string.


1 I've tested the code suggested in the lighthouse ticket. What I found was that you had to specifically use the t alias. If you used I18n.t or I18n.translate the translation didn't treat _html as html_safe:

I18n.t('hello_html') 
I18n.translate('hello_html') 
# Produces => "This is a string with an accent: ó"

t('hello_html')      
# Produces => "This is a string with an accent: ó"

I don't think this is the intended behavior per the RoR TranslationHelper documentation.

If you don't wanna expose to the possibility of a mistake by simple adding .html_safe (through alias_method_chain or w/e) to everything, the best solution is simply to use it whenever it is necessary.

In our site we use markup language to get HTML output from i18n locale files, since who translates those files are not developers, just translators.

If it is only on a few places that you need your HTML to really be HTML, use .html_safe

t('views.signup.organisation_details').html_safe

The simple markup language we have works pretty well for us, but that is really case-specific :)

Related