[Routing] fix encoding of static static, so UrlGenerator produces valid URLs

also added test with many special characters
This commit is contained in:
Tobias Schultze 2012-05-04 15:28:36 +02:00
parent 15b5aa4f7c
commit 3466896acd
2 changed files with 45 additions and 4 deletions

View File

@ -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) {

View File

@ -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');