diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index 28aca47b38..9c68dc1d26 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -28,9 +28,33 @@ use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; class UrlGenerator implements UrlGeneratorInterface { protected $context; + + /** + * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. + * + * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars + * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g. + * "?" and "#" (would be interpreted wrongly as query and fragment identifier), + * "'" and """ (are used as delimiters in HTML). + */ protected $decodedChars = array( - // %2F is not valid in a URL, so we don't encode it (which is fine as the requirements explicitly allowed it) + // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning + // some webservers don't allow the slash in encoded form in the path for security reasons anyway + // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss '%2F' => '/', + // the following chars are general delimiters in the URI specification but have only special meaning in the authority component + // so they can safely be used in the path in unencoded form + '%40' => '@', + '%3A' => ':', + // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally + // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability + '%3B' => ';', + '%2C' => ',', + '%3D' => '=', + '%2B' => '+', + '%21' => '!', + '%2A' => '*', + '%7C' => '|', ); protected $routes; @@ -129,7 +153,7 @@ class UrlGenerator implements UrlGeneratorInterface } if (!$isEmpty || !$optional) { - $url = $token[1].strtr(rawurlencode($tparams[$token[3]]), $this->decodedChars).$url; + $url = $token[1].$tparams[$token[3]].$url; } $optional = false; @@ -144,14 +168,15 @@ class UrlGenerator implements UrlGeneratorInterface $url = '/'; } + // do not encode the contexts base url as it is already encoded (see Symfony\Component\HttpFoundation\Request) + $url = $this->context->getBaseUrl().strtr(rawurlencode($url), $this->decodedChars); + // add a query string if needed $extra = array_diff_key($originParameters, $variables, $defaults); if ($extra && $query = http_build_query($extra, '', '&')) { $url .= '?'.$query; } - $url = $this->context->getBaseUrl().$url; - if ($this->context->getHost()) { $scheme = $this->context->getScheme(); if (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme != $req) { diff --git a/tests/Symfony/Tests/Component/Routing/Generator/UrlGeneratorTest.php b/tests/Symfony/Tests/Component/Routing/Generator/UrlGeneratorTest.php index 17b4d3c3bf..c653d29dbc 100644 --- a/tests/Symfony/Tests/Component/Routing/Generator/UrlGeneratorTest.php +++ b/tests/Symfony/Tests/Component/Routing/Generator/UrlGeneratorTest.php @@ -206,6 +206,22 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('/app.php/foo', $this->getGenerator($routes)->generate('test', array('default' => 'foo'))); } + public function testUrlEncoding() + { + // This tests the encoding of reserved characters that are used for delimiting of URI components (defined in RFC 3986) + // and other special ASCII chars. These chars are tested as static text path, variable path and query param. + $chars = '@:[]/()*\'" +,;-._~&$<>|{}%\\^`!?foo=bar#id'; + $routes = $this->getRoutes('test', new Route("/$chars/{varpath}", array(), array('varpath' => '.+'))); + $this->assertSame('/app.php/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id' + . '/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id' + . '?query=%40%3A%5B%5D%2F%28%29%2A%27%22+%2B%2C%3B-._%7E%26%24%3C%3E%7C%7B%7D%25%5C%5E%60%21%3Ffoo%3Dbar%23id', + $this->getGenerator($routes)->generate('test', array( + 'varpath' => $chars, + 'query' => $chars + )) + ); + } + protected function getGenerator(RouteCollection $routes, array $parameters = array()) { $context = new RequestContext('/app.php');