[TASK] Rewrite the redirect handler as PSR-15 HTTP middleware 39/55539/5
authorBenjamin Franzke <bfr@qbus.de>
Wed, 24 Jan 2018 19:31:28 +0000 (20:31 +0100)
committerBenni Mack <benni@typo3.org>
Wed, 7 Feb 2018 21:20:10 +0000 (22:20 +0100)
The code is restructured to clearly distinct between aborting
the middleware chain – returning an own response – and forwarding
the request to the next middleware/request handler.

Change-Id: I51d88b9f9fae8d2aafca343da1e67bf60182d765
Releases: master
Resolves: #83727
Reviewed-on: https://review.typo3.org/55539
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
typo3/sysext/core/Classes/Utility/HttpUtility.php
typo3/sysext/redirects/Classes/Http/Middleware/RedirectHandler.php [new file with mode: 0644]
typo3/sysext/redirects/Classes/Http/RedirectHandler.php [deleted file]
typo3/sysext/redirects/Configuration/RequestMiddlewares.php [new file with mode: 0644]
typo3/sysext/redirects/ext_localconf.php

index 12b9c9b..a7ef021 100644 (file)
@@ -14,8 +14,6 @@ namespace TYPO3\CMS\Core\Utility;
  * The TYPO3 project - inspiring people to share!
  */
 
-use Psr\Http\Message\ResponseInterface;
-
 /**
  * HTTP Utility class
  */
@@ -121,22 +119,4 @@ class HttpUtility
             (isset($urlParts['query']) ? '?' . $urlParts['query'] : '') .
             (isset($urlParts['fragment']) ? '#' . $urlParts['fragment'] : '');
     }
-
-    /**
-     * Send Response to client and exit
-     *
-     * @param ResponseInterface $response
-     * @internal not part of public/stable API yet
-     */
-    public static function sendResponse(ResponseInterface $response)
-    {
-        if (!headers_sent()) {
-            header('HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
-            foreach ($response->getHeaders() as $name => $values) {
-                header($name . ': ' . implode(', ', $values));
-            }
-        }
-        echo $response->getBody()->__toString();
-        exit;
-    }
 }
diff --git a/typo3/sysext/redirects/Classes/Http/Middleware/RedirectHandler.php b/typo3/sysext/redirects/Classes/Http/Middleware/RedirectHandler.php
new file mode 100644 (file)
index 0000000..8d2579a
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Redirects\Http\Middleware;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\UriInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use TYPO3\CMS\Core\Configuration\Features;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Http\RedirectResponse;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Redirects\Service\RedirectService;
+
+/**
+ * Hooks into the frontend request, and checks if a redirect should apply,
+ * If so, a redirect response is triggered.
+ */
+class RedirectHandler implements MiddlewareInterface, LoggerAwareInterface
+{
+    use LoggerAwareTrait;
+
+    /**
+     * First hook within the Frontend Request handling
+     *
+     * @param ServerRequestInterface $request
+     * @param RequestHandlerInterface $handler
+     * @return ResponseInterface
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $redirectService = GeneralUtility::makeInstance(RedirectService::class);
+        $port = $request->getUri()->getPort();
+        $matchedRedirect = $redirectService->matchRedirect(
+            $request->getUri()->getHost() . ($port ? ':' . $port : ''),
+            $request->getUri()->getPath()
+        );
+
+        // If the matched redirect is found, resolve it, and check further
+        if (is_array($matchedRedirect)) {
+            $url = $redirectService->getTargetUrl($matchedRedirect, $request->getQueryParams());
+            if ($url instanceof UriInterface) {
+                $this->logger->debug('Redirecting', ['record' => $matchedRedirect, 'uri' => $url]);
+                $response = $this->buildRedirectResponse($url, $matchedRedirect);
+                $this->incrementHitCount($matchedRedirect);
+
+                return $response;
+            }
+        }
+
+        return $handler->handle($request);
+    }
+
+    /**
+     * Creates a PSR-7 compatible Response object
+     *
+     * @param UriInterface $uri
+     * @param array $redirectRecord
+     * @return ResponseInterface
+     */
+    protected function buildRedirectResponse(UriInterface $uri, array $redirectRecord): ResponseInterface
+    {
+        return new RedirectResponse($uri, (int)$redirectRecord['target_statuscode'], ['X-Redirect-By' => 'TYPO3']);
+    }
+
+    /**
+     * Updates the sys_record's hit counter by one
+     *
+     * @param array $redirectRecord
+     */
+    protected function incrementHitCount(array $redirectRecord)
+    {
+        // Track the hit if not disabled
+        if (!GeneralUtility::makeInstance(Features::class)->isFeatureEnabled('redirects.hitCount') || $redirectRecord['disable_hitcount']) {
+            return;
+        }
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('sys_redirect');
+        $queryBuilder
+            ->update('sys_redirect')
+            ->where(
+                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($redirectRecord['uid'], \PDO::PARAM_INT))
+            )
+            ->set('hitcount', $queryBuilder->quoteIdentifier('hitcount') . '+1', false)
+            ->set('lasthiton', $GLOBALS['EXEC_TIME'])
+            ->execute();
+    }
+}
diff --git a/typo3/sysext/redirects/Classes/Http/RedirectHandler.php b/typo3/sysext/redirects/Classes/Http/RedirectHandler.php
deleted file mode 100644 (file)
index 361e273..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-declare(strict_types=1);
-namespace TYPO3\CMS\Redirects\Http;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\UriInterface;
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\LoggerAwareTrait;
-use TYPO3\CMS\Core\Configuration\Features;
-use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Http\RedirectResponse;
-use TYPO3\CMS\Core\Http\ServerRequestFactory;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\HttpUtility;
-use TYPO3\CMS\Redirects\Service\RedirectService;
-
-/**
- * Hooks into the frontend request, and checks if a redirect should apply,
- * If so, a redirect response is triggered.
- */
-class RedirectHandler implements LoggerAwareInterface
-{
-    use LoggerAwareTrait;
-
-    /**
-     * First hook within the Frontend Request handling
-     */
-    public function handle()
-    {
-        $redirectService = GeneralUtility::makeInstance(RedirectService::class);
-        //@todo The request object should be handed in by the hook in the future
-        $currentRequest = ServerRequestFactory::fromGlobals();
-        $port = $currentRequest->getUri()->getPort();
-        $matchedRedirect = $redirectService->matchRedirect(
-            $currentRequest->getUri()->getHost() . ($port ? ':' . $port : ''),
-            $currentRequest->getUri()->getPath()
-        );
-
-        // If the matched redirect is found, resolve it, and check further
-        if (!is_array($matchedRedirect)) {
-            return;
-        }
-
-        $url = $redirectService->getTargetUrl($matchedRedirect, $currentRequest->getQueryParams());
-        if ($url instanceof UriInterface) {
-            $this->logger->debug('Redirecting', ['record' => $matchedRedirect, 'uri' => $url]);
-            $response = $this->buildRedirectResponse($url, $matchedRedirect);
-            $this->incrementHitCount($matchedRedirect);
-            HttpUtility::sendResponse($response);
-        }
-    }
-
-    /**
-     * Creates a PSR-7 compatible Response object
-     *
-     * @param UriInterface $uri
-     * @param array $redirectRecord
-     * @return ResponseInterface
-     */
-    protected function buildRedirectResponse(UriInterface $uri, array $redirectRecord): ResponseInterface
-    {
-        return new RedirectResponse($uri, (int)$redirectRecord['target_statuscode'], ['X-Redirect-By' => 'TYPO3']);
-    }
-
-    /**
-     * Updates the sys_record's hit counter by one
-     *
-     * @param array $redirectRecord
-     */
-    protected function incrementHitCount(array $redirectRecord)
-    {
-        // Track the hit if not disabled
-        if (!GeneralUtility::makeInstance(Features::class)->isFeatureEnabled('redirects.hitCount') || $redirectRecord['disable_hitcount']) {
-            return;
-        }
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable('sys_redirect');
-        $queryBuilder
-            ->update('sys_redirect')
-            ->where(
-                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($redirectRecord['uid'], \PDO::PARAM_INT))
-            )
-            ->set('hitcount', $queryBuilder->quoteIdentifier('hitcount') . '+1', false)
-            ->set('lasthiton', $GLOBALS['EXEC_TIME'])
-            ->execute();
-    }
-}
diff --git a/typo3/sysext/redirects/Configuration/RequestMiddlewares.php b/typo3/sysext/redirects/Configuration/RequestMiddlewares.php
new file mode 100644 (file)
index 0000000..42387d1
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * Definitions for middlewares provided by EXT:redirects
+ */
+return [
+    'frontend' => [
+        'typo3/cms-redirects/redirecthandler' => [
+            'target' => \TYPO3\CMS\Redirects\Http\Middleware\RedirectHandler::class,
+        ],
+    ],
+];
index 4c82fde..bf29bd3 100644 (file)
@@ -1,9 +1,6 @@
 <?php
 defined('TYPO3_MODE') or die();
 
-// Register hook into the frontend to check for a possible redirect
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preprocessRequest']['redirects'] = \TYPO3\CMS\Redirects\Http\RedirectHandler::class . '->handle';
-
 // Rebuild cache in DataHandler on changing / inserting / adding redirect records
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc']['redirects'] = \TYPO3\CMS\Redirects\Hooks\DataHandlerCacheFlushingHook::class . '->rebuildRedirectCacheIfNecessary';