From a8ce6210b37d4d1d41c1c6fa26bd627b1a11b597 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Sun, 29 Apr 2012 12:56:44 +0200 Subject: [PATCH] [Routing] added support for hostname in the apache matcher dumper --- .../Matcher/Dumper/ApacheMatcherDumper.php | 77 +++++++++++++-- .../Tests/Fixtures/dumper/url_matcher1.apache | 94 ++++++++++++++++++- .../Tests/Matcher/ApacheUrlMatcherTest.php | 14 +++ .../Dumper/ApacheMatcherDumperTest.php | 65 +++++++++++++ 4 files changed, 240 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php index e9fc82d458..f55e76cec1 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php @@ -46,17 +46,55 @@ class ApacheMatcherDumper extends MatcherDumper $rules = array("# skip \"real\" requests\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]"); $methodVars = array(); + $hostnameRegexUnique = 0; + $prevHosnameRegex = ''; foreach ($this->getRoutes()->all() as $name => $route) { - $rules[] = $this->dumpRoute($name, $route, $options); - $methodVars = array_merge($methodVars, $this->getRouteMethods($route)); - } + $compiledRoute = $route->compile(); + $hostnameRegex = $compiledRoute->getHostnameRegex(); + + if (null !== $hostnameRegex && $prevHosnameRegex !== $hostnameRegex) { + + $prevHosnameRegex = $hostnameRegex; + $hostnameRegexUnique++; + + $rule = array(); + + $regex = $this->regexToApacheRegex($hostnameRegex); + $regex = self::escape($regex, ' ', '\\'); + + $rule[] = sprintf('RewriteCond %%{HTTP:Host} %s', $regex); + + $variables = array(); + $variables[] = sprintf('E=__ROUTING_hostname_%s:1', $hostnameRegexUnique); + + foreach ($compiledRoute->getHostnameVariables() as $i => $variable) { + $variables[] = sprintf('E=__ROUTING_hostname_%s_%s:%%%d', $hostnameRegexUnique, $variable, $i+1); + } + + $variables = implode(',', $variables); + + $rule[] = sprintf('RewriteRule .? - [%s]', $variables); + + $rules[] = implode("\n", $rule); + } + + $rules[] = $this->dumpRoute($name, $route, $options, $hostnameRegexUnique); + + if ($req = $route->getRequirement('_method')) { + $methods = explode('|', strtoupper($req)); + $methodVars = array_merge($methodVars, $methods); + } + } if (0 < count($methodVars)) { $rule = array('# 405 Method Not Allowed'); $methodVars = array_values(array_unique($methodVars)); + if (in_array('GET', $methodVars) && !in_array('HEAD', $methodVars)) { + $methodVars[] = 'HEAD'; + } foreach ($methodVars as $i => $methodVar) { - $rule[] = sprintf('RewriteCond %%{_ROUTING_allow_%s} !-z%s', $methodVar, isset($methodVars[$i + 1]) ? ' [OR]' : ''); + $rule[] = sprintf('RewriteCond %%{ENV:_ROUTING__allow_%s} =1%s', $methodVar, isset($methodVars[$i + 1]) ? ' [OR]' : ''); } $rule[] = sprintf('RewriteRule .* %s [QSA,L]', $options['script_name']); @@ -66,7 +104,16 @@ class ApacheMatcherDumper extends MatcherDumper return implode("\n\n", $rules)."\n"; } - private function dumpRoute($name, $route, array $options) + /** + * Dumps a single route + * + * @param string $name Route name + * @param Route $route The route + * @param array $options Options + * @param bool $hostnameRegexUnique Unique identifier for the hostname regex + * @return string The compiled route + */ + private function dumpRoute($name, $route, array $options, $hostnameRegexUnique) { $compiledRoute = $route->compile(); @@ -79,7 +126,10 @@ class ApacheMatcherDumper extends MatcherDumper $hasTrailingSlash = (!$methods || in_array('HEAD', $methods)) && '/$' === substr($regex, -2) && '^/$' !== $regex; $variables = array('E=_ROUTING_route:'.$name); - foreach ($compiledRoute->getVariables() as $i => $variable) { + foreach ($compiledRoute->getHostnameVariables() as $variable) { + $variables[] = sprintf('E=_ROUTING_param_%s:%%{ENV:__ROUTING_hostname_%s_%s}', $variable, $hostnameRegexUnique, $variable); + } + foreach ($compiledRoute->getPathVariables() as $i => $variable) { $variables[] = 'E=_ROUTING_param_'.$variable.':%'.($i + 1); } foreach ($route->getDefaults() as $key => $value) { @@ -98,10 +148,13 @@ class ApacheMatcherDumper extends MatcherDumper if (0 < count($methods)) { $allow = array(); foreach ($methods as $method) { - $methodVars[] = $method; $allow[] = 'E=_ROUTING_allow_'.$method.':1'; } + if ($hostnameRegex = $compiledRoute->getHostnameRegex()) { + $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_hostname_%s} =1", $hostnameRegexUnique); + } + $rule[] = "RewriteCond %{REQUEST_URI} $regex"; $rule[] = sprintf("RewriteCond %%{REQUEST_METHOD} !^(%s)$ [NC]", implode('|', $methods)); $rule[] = sprintf('RewriteRule .* - [S=%d,%s]', $hasTrailingSlash ? 2 : 1, implode(',', $allow)); @@ -109,11 +162,21 @@ class ApacheMatcherDumper extends MatcherDumper // redirect with trailing slash appended if ($hasTrailingSlash) { + + if ($hostnameRegex = $compiledRoute->getHostnameRegex()) { + $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_hostname_%s} =1", $hostnameRegexUnique); + } + $rule[] = 'RewriteCond %{REQUEST_URI} '.substr($regex, 0, -2).'$'; $rule[] = 'RewriteRule .* $0/ [QSA,L,R=301]'; } // the main rule + + if ($hostnameRegex = $compiledRoute->getHostnameRegex()) { + $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_hostname_%s} =1", $hostnameRegexUnique); + } + $rule[] = "RewriteCond %{REQUEST_URI} $regex"; $rule[] = "RewriteRule .* {$options['script_name']} [QSA,L,$variables]"; diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache index baa07b6d60..4b9c11d4d0 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache @@ -68,8 +68,96 @@ RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz6,E=_ROUTING_default_foo:bar\ RewriteCond %{REQUEST_URI} ^/te\ st/baz$ RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz7] +# baz8 +RewriteCond %{REQUEST_URI} ^/te\\\ st/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz8] + +# baz9 +RewriteCond %{REQUEST_URI} ^/test/(te\\\ st)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz9,E=_ROUTING_param_baz:%1] + +RewriteCond %{HTTP:Host} ^a\.example\.com$ +RewriteRule .? - [E=__ROUTING_hostname_1:1] + +# route1 +RewriteCond %{ENV:__ROUTING_hostname_1} =1 +RewriteCond %{REQUEST_URI} ^/route1$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route1] + +# route2 +RewriteCond %{ENV:__ROUTING_hostname_1} =1 +RewriteCond %{REQUEST_URI} ^/c2/route2$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route2] + +RewriteCond %{HTTP:Host} ^b\.example\.com$ +RewriteRule .? - [E=__ROUTING_hostname_2:1] + +# route3 +RewriteCond %{ENV:__ROUTING_hostname_2} =1 +RewriteCond %{REQUEST_URI} ^/c2/route3$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route3] + +RewriteCond %{HTTP:Host} ^a\.example\.com$ +RewriteRule .? - [E=__ROUTING_hostname_3:1] + +# route4 +RewriteCond %{ENV:__ROUTING_hostname_3} =1 +RewriteCond %{REQUEST_URI} ^/route4$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route4] + +RewriteCond %{HTTP:Host} ^c\.example\.com$ +RewriteRule .? - [E=__ROUTING_hostname_4:1] + +# route5 +RewriteCond %{ENV:__ROUTING_hostname_4} =1 +RewriteCond %{REQUEST_URI} ^/route5$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route5] + +# route6 +RewriteCond %{REQUEST_URI} ^/route6$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route6] + +RewriteCond %{HTTP:Host} ^([^\.]++)\.example\.com$ +RewriteRule .? - [E=__ROUTING_hostname_5:1,E=__ROUTING_hostname_5_var1:%1] + +# route11 +RewriteCond %{ENV:__ROUTING_hostname_5} =1 +RewriteCond %{REQUEST_URI} ^/route11$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route11,E=_ROUTING_param_var1:%{ENV:__ROUTING_hostname_5_var1}] + +# route12 +RewriteCond %{ENV:__ROUTING_hostname_5} =1 +RewriteCond %{REQUEST_URI} ^/route12$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route12,E=_ROUTING_param_var1:%{ENV:__ROUTING_hostname_5_var1},E=_ROUTING_default_var1:val] + +# route13 +RewriteCond %{ENV:__ROUTING_hostname_5} =1 +RewriteCond %{REQUEST_URI} ^/route13/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route13,E=_ROUTING_param_var1:%{ENV:__ROUTING_hostname_5_var1},E=_ROUTING_param_name:%1] + +# route14 +RewriteCond %{ENV:__ROUTING_hostname_5} =1 +RewriteCond %{REQUEST_URI} ^/route14/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route14,E=_ROUTING_param_var1:%{ENV:__ROUTING_hostname_5_var1},E=_ROUTING_param_name:%1,E=_ROUTING_default_var1:val] + +RewriteCond %{HTTP:Host} ^c\.example\.com$ +RewriteRule .? - [E=__ROUTING_hostname_6:1] + +# route15 +RewriteCond %{ENV:__ROUTING_hostname_6} =1 +RewriteCond %{REQUEST_URI} ^/route15/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route15,E=_ROUTING_param_name:%1] + +# route16 +RewriteCond %{REQUEST_URI} ^/route16/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route16,E=_ROUTING_param_name:%1,E=_ROUTING_default_var1:val] + +# route17 +RewriteCond %{REQUEST_URI} ^/route17$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route17] + # 405 Method Not Allowed -RewriteCond %{_ROUTING_allow_GET} !-z [OR] -RewriteCond %{_ROUTING_allow_HEAD} !-z [OR] -RewriteCond %{_ROUTING_allow_POST} !-z +RewriteCond %{ENV:_ROUTING__allow_GET} =1 [OR] +RewriteCond %{ENV:_ROUTING__allow_HEAD} =1 [OR] +RewriteCond %{ENV:_ROUTING__allow_POST} =1 RewriteRule .* app.php [QSA,L] diff --git a/src/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php index 3161f3ce69..6550911eb7 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php @@ -118,6 +118,20 @@ class ApacheUrlMatcherTest extends \PHPUnit_Framework_TestCase '_route' => 'hello', ), ), + array( + 'REDIRECT_REDIRECT_ envs', + '/hello/world', + array( + 'REDIRECT_REDIRECT__ROUTING_route' => 'hello', + 'REDIRECT_REDIRECT__ROUTING_param__controller' => 'AcmeBundle:Default:index', + 'REDIRECT_REDIRECT__ROUTING_param_name' => 'world', + ), + array( + '_controller' => 'AcmeBundle:Default:index', + 'name' => 'world', + '_route' => 'hello', + ), + ), ); } } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php index 2847564b90..e72976f047 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php @@ -124,6 +124,71 @@ class ApacheMatcherDumperTest extends \PHPUnit_Framework_TestCase $collection->add('baz7', new Route( '/te st/baz' )); + // space preceded with \ in path + $collection->add('baz8', new Route( + '/te\\ st/baz' + )); + // space preceded with \ in requirement + $collection->add('baz9', new Route( + '/test/{baz}', + array(), + array( + 'baz' => 'te\\\\ st', + ) + )); + + $collection1 = new RouteCollection(); + + $route1 = new Route('/route1', array(), array(), array(), 'a.example.com'); + $collection1->add('route1', $route1); + + $collection2 = new RouteCollection(); + + $route2 = new Route('/route2', array(), array(), array(), 'a.example.com'); + $collection2->add('route2', $route2); + + $route3 = new Route('/route3', array(), array(), array(), 'b.example.com'); + $collection2->add('route3', $route3); + + $collection1->addCollection($collection2, '/c2'); + + $route4 = new Route('/route4', array(), array(), array(), 'a.example.com'); + $collection1->add('route4', $route4); + + $route5 = new Route('/route5', array(), array(), array(), 'c.example.com'); + $collection1->add('route5', $route5); + + $route6 = new Route('/route6', array(), array(), array(), null); + $collection1->add('route6', $route6); + + $collection->addCollection($collection1); + + // hostname and variables + + $collection1 = new RouteCollection(); + + $route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route11', $route11); + + $route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route12', $route12); + + $route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route13', $route13); + + $route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route14', $route14); + + $route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com'); + $collection1->add('route15', $route15); + + $route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null); + $collection1->add('route16', $route16); + + $route17 = new Route('/route17', array(), array(), array(), null); + $collection1->add('route17', $route17); + + $collection->addCollection($collection1); return $collection; }