Rails: Routes, url_for, and slashes

Two surprises with Rails today involving URLs and escape sequences.

1. Regular expressions in routes operate on the raw URL.

Suppose you have these routes:


map.connect 'test/:id', :controller => 'test', :id => /[^\/]*/
map.connect 'test/:hi/:lo', :controller => 'test'

The first route has one parameter, and its regular expression matches all strings without a slash. The second route has two parameters separated by slash. You might wonder what the following routes to (note that %2F is an escaped slash):


"/test/foo%2Fbar"

Answer:


>> ActionController::Routing::Routes.recognize_path("/test/foo%2Fbar")
=> {:action=>"index", :id=>"foo/bar", :controller=>"test"}

It is the first route.

Conclusion:

The regexp operates on the raw URL string, but the param passed to the controller is the unescaped URL string.

2. url_for has inconsistent escaping behavior.

url_for escapes slash (/), ampersand (&), percent (%), and possibly others, but it does not escape comma (,), semi-colon (;), colon (:), plus (+), among others.

Consider our route above:


map.connect 'test/:id', :controller => 'test', :id => /[^\/]*/

Then following path routes to:


>> ActionController::Routing::Routes.recognize_path("/test/foo%2Fbar%3Abaz%3Bquux%2Bhoge%40dur%25blah")
=> {:action=>"index", :id=>"foo/bar:baz;quux+hoge@dur%blah", :controller=>"test"}

Yet this is not reversible using url_for:


>> id = "foo/bar:baz;quux+hoge@dur%blah"
=> "foo/bar:baz;quux+hoge@dur%blah"
>> app.url_for({:controller => "test", :id => id}) ActionController::RoutingError: No route matches {:action=>"index", :id=>"foo/bar:baz;quux+hoge@dur%blah", :controller=>"test"}

It seems that the regexp is matched against the raw input. Suppose we remove the regexp:


map.connect 'test/:id', :controller => 'test'

Then the output of url_for is:


>> app.url_for({:controller => "test", :id => id})
=> "http://www.example.com/test/foo%2Fbar:baz;quux+hoge@dur%25blah"

This is unexpected! Some characters come out escaped and others do not.

I am not sure why this is, or whether it was intended.