Tuesday, November 21, 2006

How to escape html in your rails controller

The problem:

Occassionally you want to use the h() function (like in the embedded ruby template) in your controller. In particular, I needed to do this in order to echo back (invalid) user supplied email addresses in a flash[:notice]. You don't want to escape the whole flash[:notice] because you might actually want to put some genuine html in it. Therefore, you've got to do this in your controller.

The solution:
Suppose your stringy is:
"Joe Example" <joe@example.com>
Simply invoke CGI.escapeHTML(stringy) in your controller to produce the escaped stringy. Note that if you want to escape a URL, you simply invoke CGI.escape.

The how and why:

It turns out that (as far as I can tell) h() isn't actually a function in rails. If you look through the Rails API, you won't find it anywhere. I found this curious, and even tried a recursive grep in my rails gem dir for all methods beginning with 'h' (grep -R 'def h' *). Nadda. So it would appear this is not actually a function in rails, but something that the embedded ruby parser picks up, maybe even via a regular expression. Unfortunately, that means we can't just find the associated helper and require it in our foo_controller.rb (or application.rb). As tempting as it is, we certainly shouldn't need to reinvent the wheel in these sorts of scenarios, right?

So what then? Well, it occurred to me that surely the Rails team, at least, wouldn't go and reinvent the wheel. Afterall, they were pretty busy when they were building Basecamp. If they're building a system to run under FastCGI, surely they wouldn't shy away from using Ruby's CGI library, right? On a hunch, I checked if this was already "require"d somewhere deep down in the framework. Just went ahead and invoked CGI.escapeHTML from a controller. And valois! There we have it. You could probably even mess with the sessions/cookies using the CGI module... but that would be rather evil.

7 comments:

Unknown said...

I wondered about the h() method recently too. I came across you blog post and did some digging, and I found it!

http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB/Util.html#M000624

h is an alias for html_escape and is part of ERB.

Anonymous said...
This comment has been removed by the author.
Anonymous said...

Thank you so much for this! This is a great solution for not having the Rails html_escape() or h() alias helper within the controller scope.

superluminary said...

Awesome stuff but it doesn't deal with dots well, eg. I wanted to pass a search term in the form www.mysite.com/users/search/email@example.com. The standard rails routing treats the last .com as a separate parameter and falls over. I'm using a regex instead

kikito said...

Doesn't URI.escape(url) do just this?

Unknown said...

# File actionpack/lib/action_view/erb/util.rb
def html_escape(s)
s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
end

undef :h
alias h html_escape

module_function :html_escape
module_function :h

# excuse the formatting; I don't know how to make this look reasonably pretty

Anders said...

valois? voila!