From 952388c2779198e256d8e056d8fbe1a50bb68f39 Mon Sep 17 00:00:00 2001 From: Tobias Schultze Date: Sat, 31 Jan 2015 23:26:34 +0100 Subject: [PATCH] [Routing] make host matching case-insensitive according to RFC 3986 --- .../Routing/Generator/UrlGenerator.php | 2 +- .../Component/Routing/RouteCompiler.php | 4 ++-- .../Tests/Fixtures/dumper/url_matcher1.php | 12 +++++------ .../Tests/Fixtures/dumper/url_matcher2.php | 12 +++++------ .../Tests/Generator/UrlGeneratorTest.php | 7 +++++++ .../Routing/Tests/Matcher/UrlMatcherTest.php | 21 +++++++++++++++++++ .../Routing/Tests/RouteCompilerTest.php | 8 +++---- .../Component/Routing/Tests/RouteTest.php | 2 +- 8 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Component/Routing/Generator/UrlGenerator.php b/src/Symfony/Component/Routing/Generator/UrlGenerator.php index c827ca7d66..d17941989b 100644 --- a/src/Symfony/Component/Routing/Generator/UrlGenerator.php +++ b/src/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -213,7 +213,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt $routeHost = ''; foreach ($hostTokens as $token) { 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]]); if ($this->strictRequirements) { diff --git a/src/Symfony/Component/Routing/RouteCompiler.php b/src/Symfony/Component/Routing/RouteCompiler.php index 4ffce0aa9e..8a7efbdbc2 100644 --- a/src/Symfony/Component/Routing/RouteCompiler.php +++ b/src/Symfony/Component/Routing/RouteCompiler.php @@ -46,7 +46,7 @@ class RouteCompiler implements RouteCompilerInterface $result = self::compilePattern($route, $host, true); $hostVariables = $result['variables']; - $variables = array_merge($variables, $hostVariables); + $variables = $hostVariables; $hostTokens = $result['tokens']; $hostRegex = $result['regex']; @@ -163,7 +163,7 @@ class RouteCompiler implements RouteCompilerInterface return array( '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), 'variables' => $variables, ); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php index e5f7665c81..fae9f51aa5 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -195,7 +195,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher $host = $this->context->getHost(); - if (preg_match('#^a\\.example\\.com$#s', $host, $hostMatches)) { + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { // route1 if ($pathinfo === '/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 if ($pathinfo === '/c2/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 if ($pathinfo === '/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 if ($pathinfo === '/route5') { return array('_route' => 'route5'); @@ -237,7 +237,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher return array('_route' => 'route6'); } - if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#s', $host, $hostMatches)) { + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) { if (0 === strpos($pathinfo, '/route1')) { // 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 if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#s', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php index ad157909b8..f58f3f8436 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -207,7 +207,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec $host = $this->context->getHost(); - if (preg_match('#^a\\.example\\.com$#s', $host, $hostMatches)) { + if (preg_match('#^a\\.example\\.com$#si', $host, $hostMatches)) { // route1 if ($pathinfo === '/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 if ($pathinfo === '/c2/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 if ($pathinfo === '/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 if ($pathinfo === '/route5') { return array('_route' => 'route5'); @@ -249,7 +249,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Tests\Fixtures\Redirec return array('_route' => 'route6'); } - if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#s', $host, $hostMatches)) { + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#si', $host, $hostMatches)) { if (0 === strpos($pathinfo, '/route1')) { // 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 if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#s', $pathinfo, $matches)) { return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); diff --git a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php index 22dbbf598d..0589dc6c67 100644 --- a/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php +++ b/src/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -443,6 +443,13 @@ class UrlGeneratorTest extends \PHPUnit_Framework_TestCase $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() { $routes = $this->getRoutes('test', new Route('/{name}', array(), array('_scheme' => 'http'), array(), '{locale}.example.com')); diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 58a97c5796..ba8a8287b1 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -382,4 +382,25 @@ class UrlMatcherTest extends \PHPUnit_Framework_TestCase $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'example.com')); $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('/')); + } } diff --git a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php index ef26c87161..2b7c17faaa 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCompilerTest.php @@ -208,7 +208,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase '/hello', '#^/hello$#s', array(), array(), array( array('text', '/hello'), ), - '#^www\.example\.com$#s', array(), array( + '#^www\.example\.com$#si', array(), array( array('text', 'www.example.com'), ), ), @@ -219,7 +219,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase array('variable', '/', '[^/]++', 'name'), array('text', '/hello'), ), - '#^www\.example\.(?P[^\.]++)$#s', array('tld'), array( + '#^www\.example\.(?P[^\.]++)$#si', array('tld'), array( array('variable', '.', '[^\.]++', 'tld'), array('text', 'www.example'), ), @@ -230,7 +230,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase '/hello', '#^/hello$#s', array('locale', 'tld'), array(), array( array('text', '/hello'), ), - '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#s', array('locale', 'tld'), array( + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#si', array('locale', 'tld'), array( array('variable', '.', '[^\.]++', 'tld'), array('text', '.example'), array('variable', '', '[^\.]++', 'locale'), @@ -242,7 +242,7 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase '/hello', '#^/hello$#s', array('locale', 'tld'), array(), array( array('text', '/hello'), ), - '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#s', array('locale', 'tld'), array( + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#si', array('locale', 'tld'), array( array('variable', '.', '[^\.]++', 'tld'), array('text', '.example'), array('variable', '', '[^\.]++', 'locale'), diff --git a/src/Symfony/Component/Routing/Tests/RouteTest.php b/src/Symfony/Component/Routing/Tests/RouteTest.php index 510a3e3f51..8b8f02fb42 100644 --- a/src/Symfony/Component/Routing/Tests/RouteTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteTest.php @@ -247,7 +247,7 @@ class RouteTest extends \PHPUnit_Framework_TestCase */ 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\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[^\.]++)\.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\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[^\.]++)\.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); $route = new Route('/prefix/{foo}', array('foo' => 'default'), array('foo' => '\d+'));