bug #13567 [Routing] make host matching case-insensitive (Tobion)
This PR was merged into the 2.3 branch.
Discussion
----------
[Routing] make host matching case-insensitive
| Q | A
| ------------- | ---
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #9072
| License | MIT
| Doc PR |
Ignore case in host which means:
- When generating URLs we leave the case in the host as specified.
- When matching we always return lower-cased versions of parameters (because of https://github.com/symfony/symfony/blob/2.7/src/Symfony/Component/Routing/RequestContext.php#L190 ) in the host. This is also what browers do. They lowercase the host before sending the request, i.e. WWW.eXample.org is sent as www.example.org. But when using curl for example it sends the host as-is. So the HttpFoundation Request class can actually have a non-lowercased host because it doesn't have this normalization.
Commits
-------
952388c
[Routing] make host matching case-insensitive according to RFC 3986
This commit is contained in:
commit
2e7434102a
|
@ -213,7 +213,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
|
||||||
$routeHost = '';
|
$routeHost = '';
|
||||||
foreach ($hostTokens as $token) {
|
foreach ($hostTokens as $token) {
|
||||||
if ('variable' === $token[0]) {
|
if ('variable' === $token[0]) {
|
||||||
if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) {
|
if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i', $mergedParams[$token[3]])) {
|
||||||
$message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]);
|
$message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]);
|
||||||
|
|
||||||
if ($this->strictRequirements) {
|
if ($this->strictRequirements) {
|
||||||
|
|
|
@ -46,7 +46,7 @@ class RouteCompiler implements RouteCompilerInterface
|
||||||
$result = self::compilePattern($route, $host, true);
|
$result = self::compilePattern($route, $host, true);
|
||||||
|
|
||||||
$hostVariables = $result['variables'];
|
$hostVariables = $result['variables'];
|
||||||
$variables = array_merge($variables, $hostVariables);
|
$variables = $hostVariables;
|
||||||
|
|
||||||
$hostTokens = $result['tokens'];
|
$hostTokens = $result['tokens'];
|
||||||
$hostRegex = $result['regex'];
|
$hostRegex = $result['regex'];
|
||||||
|
@ -163,7 +163,7 @@ class RouteCompiler implements RouteCompilerInterface
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '',
|
'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '',
|
||||||
'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s',
|
'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s'.($isHost ? 'i' : ''),
|
||||||
'tokens' => array_reverse($tokens),
|
'tokens' => array_reverse($tokens),
|
||||||
'variables' => $variables,
|
'variables' => $variables,
|
||||||
);
|
);
|
||||||
|
|
|
@ -195,7 +195,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
|
||||||
|
|
||||||
$host = $this->context->getHost();
|
$host = $this->context->getHost();
|
||||||
|
|
||||||
if (preg_match('#^a\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
// route1
|
// route1
|
||||||
if ($pathinfo === '/route1') {
|
if ($pathinfo === '/route1') {
|
||||||
return array('_route' => 'route1');
|
return array('_route' => 'route1');
|
||||||
|
@ -208,7 +208,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('#^b\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
// route3
|
// route3
|
||||||
if ($pathinfo === '/c2/route3') {
|
if ($pathinfo === '/c2/route3') {
|
||||||
return array('_route' => 'route3');
|
return array('_route' => 'route3');
|
||||||
|
@ -216,7 +216,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('#^a\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
// route4
|
// route4
|
||||||
if ($pathinfo === '/route4') {
|
if ($pathinfo === '/route4') {
|
||||||
return array('_route' => 'route4');
|
return array('_route' => 'route4');
|
||||||
|
@ -224,7 +224,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('#^c\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
// route5
|
// route5
|
||||||
if ($pathinfo === '/route5') {
|
if ($pathinfo === '/route5') {
|
||||||
return array('_route' => 'route5');
|
return array('_route' => 'route5');
|
||||||
|
@ -237,7 +237,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
|
||||||
return array('_route' => 'route6');
|
return array('_route' => 'route6');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('#^(?P<var1>[^\\.]++)\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^(?P<var1>[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
if (0 === strpos($pathinfo, '/route1')) {
|
if (0 === strpos($pathinfo, '/route1')) {
|
||||||
// route11
|
// route11
|
||||||
if ($pathinfo === '/route11') {
|
if ($pathinfo === '/route11') {
|
||||||
|
@ -263,7 +263,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('#^c\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
// route15
|
// route15
|
||||||
if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P<name>[^/]++)$#s', $pathinfo, $matches)) {
|
if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P<name>[^/]++)$#s', $pathinfo, $matches)) {
|
||||||
return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ());
|
return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ());
|
||||||
|
|
|
@ -207,7 +207,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
|
||||||
|
|
||||||
$host = $this->context->getHost();
|
$host = $this->context->getHost();
|
||||||
|
|
||||||
if (preg_match('#^a\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
// route1
|
// route1
|
||||||
if ($pathinfo === '/route1') {
|
if ($pathinfo === '/route1') {
|
||||||
return array('_route' => 'route1');
|
return array('_route' => 'route1');
|
||||||
|
@ -220,7 +220,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('#^b\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^b\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
// route3
|
// route3
|
||||||
if ($pathinfo === '/c2/route3') {
|
if ($pathinfo === '/c2/route3') {
|
||||||
return array('_route' => 'route3');
|
return array('_route' => 'route3');
|
||||||
|
@ -228,7 +228,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('#^a\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
// route4
|
// route4
|
||||||
if ($pathinfo === '/route4') {
|
if ($pathinfo === '/route4') {
|
||||||
return array('_route' => 'route4');
|
return array('_route' => 'route4');
|
||||||
|
@ -236,7 +236,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('#^c\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
// route5
|
// route5
|
||||||
if ($pathinfo === '/route5') {
|
if ($pathinfo === '/route5') {
|
||||||
return array('_route' => 'route5');
|
return array('_route' => 'route5');
|
||||||
|
@ -249,7 +249,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
|
||||||
return array('_route' => 'route6');
|
return array('_route' => 'route6');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('#^(?P<var1>[^\\.]++)\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^(?P<var1>[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
if (0 === strpos($pathinfo, '/route1')) {
|
if (0 === strpos($pathinfo, '/route1')) {
|
||||||
// route11
|
// route11
|
||||||
if ($pathinfo === '/route11') {
|
if ($pathinfo === '/route11') {
|
||||||
|
@ -275,7 +275,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('#^c\\.example\\.com$#s', $host, $hostMatches)) {
|
if (preg_match('#^c\\.example\\.com$#si', $host, $hostMatches)) {
|
||||||
// route15
|
// route15
|
||||||
if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P<name>[^/]++)$#s', $pathinfo, $matches)) {
|
if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P<name>[^/]++)$#s', $pathinfo, $matches)) {
|
||||||
return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ());
|
return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ());
|
||||||
|
|
|
@ -443,6 +443,13 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase
|
||||||
$this->assertNull($generator->generate('test', array('foo' => 'baz'), false));
|
$this->assertNull($generator->generate('test', array('foo' => 'baz'), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testHostIsCaseInsensitive()
|
||||||
|
{
|
||||||
|
$routes = $this->getRoutes('test', new Route('/', array(), array('locale' => 'en|de|fr'), array(), '{locale}.FooBar.com'));
|
||||||
|
$generator = $this->getGenerator($routes);
|
||||||
|
$this->assertSame('//EN.FooBar.com/app.php/', $generator->generate('test', array('locale' => 'EN'), UrlGeneratorInterface::NETWORK_PATH));
|
||||||
|
}
|
||||||
|
|
||||||
public function testGenerateNetworkPath()
|
public function testGenerateNetworkPath()
|
||||||
{
|
{
|
||||||
$routes = $this->getRoutes('test', new Route('/{name}', array(), array('_scheme' => 'http'), array(), '{locale}.example.com'));
|
$routes = $this->getRoutes('test', new Route('/{name}', array(), array('_scheme' => 'http'), array(), '{locale}.example.com'));
|
||||||
|
|
|
@ -382,4 +382,25 @@ class UrlMatcherTest extends \PHPUnit_Framework_TestCase
|
||||||
$matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'example.com'));
|
$matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'example.com'));
|
||||||
$matcher->match('/foo/bar');
|
$matcher->match('/foo/bar');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException
|
||||||
|
*/
|
||||||
|
public function testPathIsCaseSensitive()
|
||||||
|
{
|
||||||
|
$coll = new RouteCollection();
|
||||||
|
$coll->add('foo', new Route('/locale', array(), array('locale' => 'EN|FR|DE')));
|
||||||
|
|
||||||
|
$matcher = new UrlMatcher($coll, new RequestContext());
|
||||||
|
$matcher->match('/en');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHostIsCaseInsensitive()
|
||||||
|
{
|
||||||
|
$coll = new RouteCollection();
|
||||||
|
$coll->add('foo', new Route('/', array(), array('locale' => 'EN|FR|DE'), array(), '{locale}.example.com'));
|
||||||
|
|
||||||
|
$matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com'));
|
||||||
|
$this->assertEquals(array('_route' => 'foo', 'locale' => 'en'), $matcher->match('/'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,7 +208,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
|
||||||
'/hello', '#^/hello$#s', array(), array(), array(
|
'/hello', '#^/hello$#s', array(), array(), array(
|
||||||
array('text', '/hello'),
|
array('text', '/hello'),
|
||||||
),
|
),
|
||||||
'#^www\.example\.com$#s', array(), array(
|
'#^www\.example\.com$#si', array(), array(
|
||||||
array('text', 'www.example.com'),
|
array('text', 'www.example.com'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -219,7 +219,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
|
||||||
array('variable', '/', '[^/]++', 'name'),
|
array('variable', '/', '[^/]++', 'name'),
|
||||||
array('text', '/hello'),
|
array('text', '/hello'),
|
||||||
),
|
),
|
||||||
'#^www\.example\.(?P<tld>[^\.]++)$#s', array('tld'), array(
|
'#^www\.example\.(?P<tld>[^\.]++)$#si', array('tld'), array(
|
||||||
array('variable', '.', '[^\.]++', 'tld'),
|
array('variable', '.', '[^\.]++', 'tld'),
|
||||||
array('text', 'www.example'),
|
array('text', 'www.example'),
|
||||||
),
|
),
|
||||||
|
@ -230,7 +230,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
|
||||||
'/hello', '#^/hello$#s', array('locale', 'tld'), array(), array(
|
'/hello', '#^/hello$#s', array('locale', 'tld'), array(), array(
|
||||||
array('text', '/hello'),
|
array('text', '/hello'),
|
||||||
),
|
),
|
||||||
'#^(?P<locale>[^\.]++)\.example\.(?P<tld>[^\.]++)$#s', array('locale', 'tld'), array(
|
'#^(?P<locale>[^\.]++)\.example\.(?P<tld>[^\.]++)$#si', array('locale', 'tld'), array(
|
||||||
array('variable', '.', '[^\.]++', 'tld'),
|
array('variable', '.', '[^\.]++', 'tld'),
|
||||||
array('text', '.example'),
|
array('text', '.example'),
|
||||||
array('variable', '', '[^\.]++', 'locale'),
|
array('variable', '', '[^\.]++', 'locale'),
|
||||||
|
@ -242,7 +242,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
|
||||||
'/hello', '#^/hello$#s', array('locale', 'tld'), array(), array(
|
'/hello', '#^/hello$#s', array('locale', 'tld'), array(), array(
|
||||||
array('text', '/hello'),
|
array('text', '/hello'),
|
||||||
),
|
),
|
||||||
'#^(?P<locale>[^\.]++)\.example\.(?P<tld>[^\.]++)$#s', array('locale', 'tld'), array(
|
'#^(?P<locale>[^\.]++)\.example\.(?P<tld>[^\.]++)$#si', array('locale', 'tld'), array(
|
||||||
array('variable', '.', '[^\.]++', 'tld'),
|
array('variable', '.', '[^\.]++', 'tld'),
|
||||||
array('text', '.example'),
|
array('text', '.example'),
|
||||||
array('variable', '', '[^\.]++', 'locale'),
|
array('variable', '', '[^\.]++', 'locale'),
|
||||||
|
|
|
@ -247,7 +247,7 @@ class RouteTest extends \PHPUnit_Framework_TestCase
|
||||||
*/
|
*/
|
||||||
public function testSerializedRepresentationKeepsWorking()
|
public function testSerializedRepresentationKeepsWorking()
|
||||||
{
|
{
|
||||||
$serialized = 'C:31:"Symfony\Component\Routing\Route":933:{a:8:{s:4:"path";s:13:"/prefix/{foo}";s:4:"host";s:20:"{locale}.example.net";s:8:"defaults";a:1:{s:3:"foo";s:7:"default";}s:12:"requirements";a:1:{s:3:"foo";s:3:"\d+";}s:7:"options";a:1:{s:14:"compiler_class";s:39:"Symfony\Component\Routing\RouteCompiler";}s:7:"schemes";a:0:{}s:7:"methods";a:0:{}s:8:"compiled";C:39:"Symfony\Component\Routing\CompiledRoute":568:{a:8:{s:4:"vars";a:2:{i:0;s:6:"locale";i:1;s:3:"foo";}s:11:"path_prefix";s:7:"/prefix";s:10:"path_regex";s:30:"#^/prefix(?:/(?P<foo>\d+))?$#s";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:3:"\d+";i:3;s:3:"foo";}i:1;a:2:{i:0;s:4:"text";i:1;s:7:"/prefix";}}s:9:"path_vars";a:1:{i:0;s:3:"foo";}s:10:"host_regex";s:38:"#^(?P<locale>[^\.]++)\.example\.net$#s";s:11:"host_tokens";a:2:{i:0;a:2:{i:0;s:4:"text";i:1;s:12:".example.net";}i:1;a:4:{i:0;s:8:"variable";i:1;s:0:"";i:2;s:7:"[^\.]++";i:3;s:6:"locale";}}s:9:"host_vars";a:1:{i:0;s:6:"locale";}}}}}';
|
$serialized = 'C:31:"Symfony\Component\Routing\Route":934:{a:8:{s:4:"path";s:13:"/prefix/{foo}";s:4:"host";s:20:"{locale}.example.net";s:8:"defaults";a:1:{s:3:"foo";s:7:"default";}s:12:"requirements";a:1:{s:3:"foo";s:3:"\d+";}s:7:"options";a:1:{s:14:"compiler_class";s:39:"Symfony\Component\Routing\RouteCompiler";}s:7:"schemes";a:0:{}s:7:"methods";a:0:{}s:8:"compiled";C:39:"Symfony\Component\Routing\CompiledRoute":569:{a:8:{s:4:"vars";a:2:{i:0;s:6:"locale";i:1;s:3:"foo";}s:11:"path_prefix";s:7:"/prefix";s:10:"path_regex";s:30:"#^/prefix(?:/(?P<foo>\d+))?$#s";s:11:"path_tokens";a:2:{i:0;a:4:{i:0;s:8:"variable";i:1;s:1:"/";i:2;s:3:"\d+";i:3;s:3:"foo";}i:1;a:2:{i:0;s:4:"text";i:1;s:7:"/prefix";}}s:9:"path_vars";a:1:{i:0;s:3:"foo";}s:10:"host_regex";s:39:"#^(?P<locale>[^\.]++)\.example\.net$#si";s:11:"host_tokens";a:2:{i:0;a:2:{i:0;s:4:"text";i:1;s:12:".example.net";}i:1;a:4:{i:0;s:8:"variable";i:1;s:0:"";i:2;s:7:"[^\.]++";i:3;s:6:"locale";}}s:9:"host_vars";a:1:{i:0;s:6:"locale";}}}}}';
|
||||||
$unserialized = unserialize($serialized);
|
$unserialized = unserialize($serialized);
|
||||||
|
|
||||||
$route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+'));
|
$route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+'));
|
||||||
|
|
Reference in New Issue