Browse Source

Fix missing base directories in redirect routes.

Under the new PSR7 HTTP stack, redirect routes would be missing their
base directory as Router did not have the correct context set. Instead
of making the Router request stack PSR7 aware, I've made the request
context data PSR7 aware by introducing a new public method. Longer term
I'd like to remove the request stack entirely, as its requestAction()
will also be leaving.

Refs #9470
Mark Story 9 years ago
parent
commit
292a924da2

+ 1 - 0
src/Routing/Middleware/RoutingMiddleware.php

@@ -36,6 +36,7 @@ class RoutingMiddleware
     public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
     {
         try {
+            Router::setRequestContext($request);
             $params = (array)$request->getAttribute('params', []);
             if (empty($params['controller'])) {
                 $path = $request->getUri()->getPath();

+ 33 - 12
src/Routing/Router.php

@@ -17,6 +17,8 @@ namespace Cake\Routing;
 use Cake\Core\Configure;
 use Cake\Network\Request;
 use Cake\Utility\Inflector;
+use InvalidArgumentException;
+use Psr\Http\Message\ServerRequestInterface;
 
 /**
  * Parses the request URL into controller, action, and parameters. Uses the connected routes
@@ -391,25 +393,41 @@ class Router
     public static function pushRequest(Request $request)
     {
         static::$_requests[] = $request;
-        static::_setContext($request);
+        static::setRequestContext($request);
     }
 
     /**
      * Store the request context for a given request.
      *
-     * @param \Cake\Network\Request $request The request instance.
+     * @param \Cake\Network\Request|\Psr\Http\Message\ServerRequestInterface $request The request instance.
      * @return void
+     * @throws InvalidArgumentException When parameter is an incorrect type.
      */
-    protected static function _setContext($request)
+    public static function setRequestContext($request)
     {
-        static::$_requestContext = [
-            '_base' => $request->base,
-            '_port' => $request->port(),
-            '_scheme' => $request->scheme(),
-            '_host' => $request->host()
-        ];
+        if ($request instanceof Request) {
+            static::$_requestContext = [
+                '_base' => $request->base,
+                '_port' => $request->port(),
+                '_scheme' => $request->scheme(),
+                '_host' => $request->host()
+            ];
+            return;
+        }
+        if ($request instanceof ServerRequestInterface) {
+            $uri = $request->getUri();
+            static::$_requestContext = [
+                '_base' => $request->getAttribute('base'),
+                '_port' => $uri->getPort(),
+                '_scheme' => $uri->getScheme(),
+                '_host' => $uri->getHost(),
+            ];
+            return;
+        }
+        throw new InvalidArgumentException('Unknown request type received.');
     }
 
+
     /**
      * Pops a request off of the request stack.  Used when doing requestAction
      *
@@ -422,7 +440,7 @@ class Router
         $removed = array_pop(static::$_requests);
         $last = end(static::$_requests);
         if ($last) {
-            static::_setContext($last);
+            static::setRequestContext($last);
             reset(static::$_requests);
         }
 
@@ -570,14 +588,17 @@ class Router
         ];
         $here = $base = $output = $frag = null;
 
+        // In 4.x this should be replaced with state injected via setRequestContext
         $request = static::getRequest(true);
         if ($request) {
             $params = $request->params;
             $here = $request->here;
             $base = $request->base;
-        }
-        if (!isset($base)) {
+        } else {
             $base = Configure::read('App.base');
+            if (isset(static::$_requestContext['_base'])) {
+                $base = static::$_requestContext['_base'];
+            }
         }
 
         if (empty($url)) {

+ 3 - 1
tests/TestCase/Routing/Middleware/RoutingMiddlewareTest.php

@@ -47,6 +47,8 @@ class RoutingMiddlewareTest extends TestCase
     {
         Router::redirect('/testpath', '/pages');
         $request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/testpath']);
+        $request = $request->withAttribute('base', '/subdir');
+
         $response = new Response();
         $next = function ($req, $res) {
         };
@@ -54,7 +56,7 @@ class RoutingMiddlewareTest extends TestCase
         $response = $middleware($request, $response, $next);
 
         $this->assertEquals(301, $response->getStatusCode());
-        $this->assertEquals('http://localhost/pages', $response->getHeaderLine('Location'));
+        $this->assertEquals('http://localhost/subdir/pages', $response->getHeaderLine('Location'));
     }
 
     /**

+ 65 - 0
tests/TestCase/Routing/RouterTest.php

@@ -16,6 +16,7 @@ namespace Cake\Test\TestCase\Routing;
 
 use Cake\Core\Configure;
 use Cake\Core\Plugin;
+use Cake\Http\ServerRequestFactory;
 use Cake\Network\Request;
 use Cake\Routing\Router;
 use Cake\Routing\Route\Route;
@@ -3154,6 +3155,70 @@ class RouterTest extends TestCase
     }
 
     /**
+     * Test setting the request context.
+     *
+     * @return void
+     */
+    public function testSetRequestContextCakePHP()
+    {
+        Router::connect('/:controller/:action/*');
+        $request = new Request([
+            'base' => '/subdir',
+            'url' => 'articles/view/1'
+        ]);
+        Router::setRequestContext($request);
+        $result = Router::url(['controller' => 'things', 'action' => 'add']);
+        $this->assertEquals('/subdir/things/add', $result);
+
+        $result = Router::url(['controller' => 'things', 'action' => 'add'], true);
+        $this->assertEquals('http://localhost/subdir/things/add', $result);
+
+        $result = Router::url('/pages/home');
+        $this->assertEquals('/subdir/pages/home', $result);
+    }
+
+    /**
+     * Test setting the request context.
+     *
+     * @return void
+     */
+    public function testSetRequestContextPsr()
+    {
+        $server = [
+            'DOCUMENT_ROOT' => '/Users/markstory/Sites',
+            'SCRIPT_FILENAME' => '/Users/markstory/Sites/subdir/webroot/index.php',
+            'PHP_SELF' => '/subdir/webroot/index.php/articles/view/1',
+            'REQUEST_URI' => '/subdir/articles/view/1',
+            'QUERY_STRING' => '',
+            'SERVER_PORT' => 80,
+        ];
+
+        Router::connect('/:controller/:action/*');
+        $request = ServerRequestFactory::fromGlobals($server);
+        Router::setRequestContext($request);
+
+        $result = Router::url(['controller' => 'things', 'action' => 'add']);
+        $this->assertEquals('/subdir/things/add', $result);
+
+        $result = Router::url(['controller' => 'things', 'action' => 'add'], true);
+        $this->assertEquals('http://localhost/subdir/things/add', $result);
+
+        $result = Router::url('/pages/home');
+        $this->assertEquals('/subdir/pages/home', $result);
+    }
+
+    /**
+     * Test setting the request context.
+     *
+     * @expectedException InvalidArgumentException
+     * @return void
+     */
+    public function testSetRequestContextInvalid()
+    {
+        Router::setRequestContext(new \stdClass);
+    }
+
+    /**
      * Connect some fallback routes for testing router behavior.
      *
      * @return void