From 791cce6c3d573f37b66c0d724f06a60319833b5b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Sat, 7 Jan 2017 21:19:52 -0500 Subject: [PATCH] Push host checking into Route. Add a `parseRequest` method to `Route` as well. This gives the 'new' API a consistent implementation across all the routing layers. It also provides a more flexible way for people to customize routing as they have access to the entire request via the new API. --- src/Routing/Route/Route.php | 24 +++++-- src/Routing/RouteCollection.php | 8 +-- tests/TestCase/Routing/Route/RouteTest.php | 74 ++++++++++++++++++++++ 3 files changed, 95 insertions(+), 11 deletions(-) diff --git a/src/Routing/Route/Route.php b/src/Routing/Route/Route.php index 056aae203a3..d730de168c7 100644 --- a/src/Routing/Route/Route.php +++ b/src/Routing/Route/Route.php @@ -15,6 +15,7 @@ namespace Cake\Routing\Route; use Cake\Http\ServerRequest; +use Psr\Http\Message\ServerRequestInterface; use Cake\Routing\Router; /** @@ -281,6 +282,24 @@ public function getName() return $this->_name = strtolower($name); } + /** + * Checks to see if the given URL can be parsed by this route. + * + * If the route can be parsed an array of parameters will be returned; if not + * false will be returned. + * + * @param \Psr\Http\Message\ServerRequestInterface $request The URL to attempt to parse. + * @return array|false An array of request parameters, or false on failure. + */ + public function parseRequest(ServerRequestInterface $request) + { + $uri = $request->getUri(); + if (isset($this->options['_host']) && !$this->hostMatches($uri->getHost())) { + return false; + } + return $this->parse($uri->getPath(), $request->getMethod()); + } + /** * Checks to see if the given URL can be parsed by this route. * @@ -290,6 +309,7 @@ public function getName() * @param string $url The URL to attempt to parse. * @param string $method The HTTP method of the request being parsed. * @return array|false An array of request parameters, or false on failure. + * @deprecated 3.4.0 Use/implement parseRequest() instead as it provides more flexibility/control. */ public function parse($url, $method = '') { @@ -356,7 +376,6 @@ public function parse($url, $method = '') } } } - $route['_matchedRoute'] = $this->template; return $route; @@ -370,9 +389,6 @@ public function parse($url, $method = '') */ public function hostMatches($host) { - if (!isset($this->options['_host'])) { - return true; - } $pattern = '@^' . str_replace('\*', '.*', preg_quote($this->options['_host'], '@')) . '$@'; return preg_match($pattern, $host) !== 0; diff --git a/src/Routing/RouteCollection.php b/src/Routing/RouteCollection.php index d5858ecc8ba..38d97dc9266 100644 --- a/src/Routing/RouteCollection.php +++ b/src/Routing/RouteCollection.php @@ -158,9 +158,7 @@ public function parse($url, $method = '') public function parseRequest(ServerRequestInterface $request) { $uri = $request->getUri(); - $method = $request->getMethod(); $urlPath = $uri->getPath(); - $host = $uri->getHost(); foreach (array_keys($this->_paths) as $path) { if (strpos($urlPath, $path) !== 0) { continue; @@ -168,11 +166,7 @@ public function parseRequest(ServerRequestInterface $request) /* @var \Cake\Routing\Route\Route $route */ foreach ($this->_paths[$path] as $route) { - if (!$route->hostMatches($host)) { - continue; - } - - $r = $route->parse($urlPath, $method); + $r = $route->parseRequest($request); if ($r === false) { continue; } diff --git a/tests/TestCase/Routing/Route/RouteTest.php b/tests/TestCase/Routing/Route/RouteTest.php index ea965fb5040..ac6fbfa57ff 100644 --- a/tests/TestCase/Routing/Route/RouteTest.php +++ b/tests/TestCase/Routing/Route/RouteTest.php @@ -15,6 +15,7 @@ namespace Cake\Test\TestCase\Routing\Route; use Cake\Core\Configure; +use Cake\Http\ServerRequest; use Cake\Routing\Router; use Cake\Routing\Route\Route; use Cake\TestSuite\TestCase; @@ -856,6 +857,79 @@ public function testQueryStringGeneration() ini_set('arg_separator.output', $restore); } + /** + * Ensure that parseRequest() calls parse() as that is required + * for backwards compat + * + * @return void + */ + public function testParseRequestDelegates() + { + $route = $this->getMockBuilder('Cake\Routing\Route\Route') + ->setMethods(['parse']) + ->setConstructorArgs(['/forward', ['controller' => 'Articles', 'action' => 'index']]) + ->getMock(); + + $route->expects($this->once()) + ->method('parse') + ->with('/forward', 'GET') + ->will($this->returnValue('works!')); + + $request = new ServerRequest([ + 'environment' => [ + 'REQUEST_METHOD' => 'GET', + 'PATH_INFO' => '/forward' + ] + ]); + $result = $route->parseRequest($request); + } + + /** + * Test that parseRequest() applies host conditions + * + * @return void + */ + public function testParseRequestHostConditions() + { + $route = new Route( + '/fallback', + ['controller' => 'Articles', 'action' => 'index'], + ['_host' => '*.example.com'] + ); + + $request = new ServerRequest([ + 'environment' => [ + 'HTTP_HOST' => 'a.example.com', + 'PATH_INFO' => '/fallback' + ] + ]); + $result = $route->parseRequest($request); + $expected = [ + 'controller' => 'Articles', + 'action' => 'index', + 'pass' => [], + '_matchedRoute' => '/fallback' + ]; + $this->assertEquals($expected, $result, 'Should match, domain is correct'); + + $request = new ServerRequest([ + 'environment' => [ + 'HTTP_HOST' => 'foo.bar.example.com', + 'PATH_INFO' => '/fallback' + ] + ]); + $result = $route->parseRequest($request); + $this->assertEquals($expected, $result, 'Should match, domain is a matching subdomain'); + + $request = new ServerRequest([ + 'environment' => [ + 'HTTP_HOST' => 'example.test.com', + 'PATH_INFO' => '/fallback' + ] + ]); + $this->assertFalse($route->parseRequest($request)); + } + /** * test the parse method of Route. *