test.ical.ly | getting PHP by the balls

Jul/10

20

How you run into problems with redirects when route parameters need to have slashes in them – and how to solve this!

Today I encountered a weird problem. It wasn’t the first time that a symfony route needed to have a parameter that must contain slashes.

This is commonly needed when you build some kind of  custom CMS within symfony where you don’t want to do massive queries to the database to match a slug and a preceding path. Think about categories and subcategories.

But I never had to redirect to such routes and it was exactly this that proved difficult.

So what you want to match is a URL like this

/article/sports/soccer/spain-is-the-world-champion-2010

with a route like this:

complicated_route:
  url: /article/:slug
  param: { module: myModule, action: index }

The above route definition will only match until /article/sports and from then on fail to match. So we need to allow for slashes in :slug first.

complicated_route:
  url: /article/:slug
  param: { module: myModule, action: index }
  requirements:
    slug: '[\w/-]+'

Now this kind of URLs will match just perfectly!

But what happens when you redirect to such a route?

Consider the following:

$this->redirect('complicated_route', array('slug' => 'sports/soccer/spain-is-the-world-champion-2010'));

or

$this->redirect('@complicated_route?slug=sports/soccer/spain-is-the-world-champion-2010');

The redirect will not really work. The URL in your browser will look correct however your front controller will not be executed. The URL will completely bypass symfony.

A look at the apache error log will reveal a bit more:

[Mon Jul 19 21:22:19 2010] [info] [client xx.xx.xx.xx] found %2f (encoded '/') in URI (decoded='/frontend_dev.php/article/sports/soccer/spain-is-the-world-champion-2010'), returning 404<
[Mon Jul 19 21:22:19 2010] [error] [client xx.xx.xx.xx] Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.
[Mon Jul 19 21:22:19 2010] [debug] core.c(3063): [client xx.xx.xx.xx] r->uri = /path/to/your/symfony/project/web/frontend_dev.php
[Mon Jul 19 21:22:19 2010] [debug] core.c(3069): [client xx.xx.xx.xx] redirected from r->uri = /path/to/your/symfony/project/web/frontend_dev.php
[Mon Jul 19 21:22:19 2010] [debug] core.c(3069): [client xx.xx.xx.xx] redirected from r->uri = /path/to/your/symfony/project/web/frontend_dev.php
[Mon Jul 19 21:22:19 2010] [debug] core.c(3069): [client xx.xx.xx.xx] redirected from r->uri = /path/to/your/symfony/project/web/frontend_dev.php
[Mon Jul 19 21:22:19 2010] [debug] core.c(3069): [client xx.xx.xx.xx] redirected from r->uri = /path/to/your/symfony/project/web/frontend_dev.php
[Mon Jul 19 21:22:19 2010] [debug] core.c(3069): [client xx.xx.xx.xx] redirected from r->uri = /path/to/your/symfony/project/web/frontend_dev.php
[Mon Jul 19 21:22:19 2010] [debug] core.c(3069): [client xx.xx.xx.xx] redirected from r->uri = /path/to/your/symfony/project/web/frontend_dev.php
[Mon Jul 19 21:22:19 2010] [debug] core.c(3069): [client xx.xx.xx.xx] redirected from r->uri = /path/to/your/symfony/project/web/frontend_dev.php
[Mon Jul 19 21:22:19 2010] [debug] core.c(3069): [client xx.xx.xx.xx] redirected from r->uri = /path/to/your/symfony/project/web/frontend_dev.php
[Mon Jul 19 21:22:19 2010] [debug] core.c(3069): [client xx.xx.xx.xx] redirected from r->uri = /path/to/your/symfony/project/web/frontend_dev.php
[Mon Jul 19 21:22:19 2010] [debug] core.c(3069): [client xx.xx.xx.xx] redirected from r->uri = /favicon.ico

There you can see it. What looks like slashes in your browser are actually url escaped slashes (%2F) and for some reason they result in infinite redirection looks in apache.

I don’t have a good answer yet but I can tell you where the encoding is taking place.

The redirect() method is available in your action and implemented in sfAction::redirect() which will in turn will call sfWebController::redirect() which will forward to sfWebController::genURL() which will call sfPatternRouting::generate() in which the generate() of the sfRoute class will be called that is associated with the route in question; usually sfRequestRoute::generate().

In there the urlencode() is taking place.

So as far as I can see there are two options to solve this. Both of which are not really elegant.

  1. Write your own sfRoute class and let it handle the generate() call.
  2. Separate the URL generation from the redirect and manipulate it within your action.
$url = $this->getController()->genURL('@complicated_route?slug=sports/soccer/spain-is-the-world-champion-2010');
$url = str_replace('%2F', '/', $url);
$this->redirect($url);

This will eventually work. However I won’t consider this elegant and propose to reconsider using standard slugs and a different URL organisation maybe.

Update!

I just learned about a very good solution from someone named todd who wrote a comment on the PHP documentation.

If you have access to your vhost config or httpd.conf you can simply add an Apache directive that will allow URLs with %2F in them.

<VirtualHost *:80>
  AllowEncodedSlashes On
</VirtualHost>

With this you will no longer need the str_replace() call or any other hack!

RSS Feed

8 Comments for How you run into problems with redirects when route parameters need to have slashes in them – and how to solve this!

Tweets that mention • How you run into problems with redirects when route parameters need to have slashes in them | test.ical.ly -- Topsy.com | 20. July 2010 at 06:39

[...] This post was mentioned on Twitter by symfonynews and symfonynews, Christian Schaefer. Christian Schaefer said: New article: How you run into problems with #redirects when #route parameters need to have slashes in them http://bit.ly/c4bWgD #in #symfony [...]

Frank | 20. July 2010 at 08:18

I had this problem some days ago too. It’s quite annoying because you can not getting this to work in a nice way.

My workaround was:
<a href="”

….

$this->redirect(urldecode($this->getController()->genURL(‘some_route)))

The links will break in some cases, when it contains some other url sensetive chars. I do not like this solution at all.

Author comment by Christian | 20. July 2010 at 08:49

@Frank Yes that is very similar to my str_replace() solution. I also don’t like it at all..

davide | 20. July 2010 at 10:33

Same problem here.
In a recent project I basicall had to use a route like this:

page:
url: /:url
class: myPropelRoute
param: { module: main, action: page }
options: { model: Url, type: object }

because users had to be able to insert urls like
“cool-articles/my-cool-article”
or
“cars/formula-1/ferrari”

myPropelRoute handled the retrieving of the route without problems, but in the generation I had the same problem like you.
I ended up using a custom helper like this

# apps/frontend/lib/helper/CustomHelper.php
function custom_url_for($url, $absolute = true)
{
$url = url_for($url, $absolute);
return str_replace(‘%2F’, ‘/’, $url);
}

function custom_link_to($name, $url, $options = array())
{
$absolute = isset($options['absolute']) ? $options['absolute'] : true;
unset($options['absolute']);

return link_to($name, custom_url_for($url, $absolute), $options);
}

And it worked. But still, it feels like a hack and not like a solution…

Author comment by Christian | 20. July 2010 at 12:21

@davide @Frank I think I found a better solution!
Reload this article and see the last paragraph after “Update!”

Author comment by Christian | 20. July 2010 at 12:33

hm.. ok this still doesn’t solve the ugly URLs. I just didn’t see it in Google Chrome but Firefox and IE will display %2F instead of / …

Javier Sanchez | 20. July 2010 at 21:00

And one rewrite rule with a regular expresión in apache conf? Is not elegant but have it out of our application… :p

Frank | 20. July 2010 at 22:08

@Christian
Ah, nice to see your update and the correct/nice solution. I’ll inform our IT dept about this issue.

Leave a comment!

<<

>>

Find it!

Theme Design by devolux.org