[TASK] Use PSR-15 middlewares in install tool 41/59541/6
authorBenjamin Franzke <bfr@qbus.de>
Tue, 22 Jan 2019 08:26:33 +0000 (09:26 +0100)
committerGeorg Ringer <georg.ringer@gmail.com>
Mon, 28 Jan 2019 19:44:25 +0000 (20:44 +0100)
Remove the old concept of priority-based HTTP request handlers,
and use statically configured PSR-15 middlewares instead.
Tranform the priority based request handlers into a set of
chained middlewares.

In case none of the middlewares handles the request a
NotFoundRequestHandler returns a 404 response
(replacing the previous exception that was thrown in that case).
This scheme of using a 404 handler as "last" request handler is
not new, it is also used in zend expressive (a PSR-15 framework).

The TYPO3 RequestHandlerInterface is marked @internal and was set
to be removed in v9, therefore we drop it now and also remove its
implementation from the frontend and backend request handlers.

Releases: master
Resolves: #87516
Change-Id: Ibbd6a0e6c4ff8aa55b4367c9e4c07e279595dbfb
Reviewed-on: https://review.typo3.org/59541
Tested-by: TYPO3com <noreply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
12 files changed:
typo3/sysext/backend/Classes/Http/RequestHandler.php
typo3/sysext/core/Classes/Core/Bootstrap.php
typo3/sysext/core/Classes/Http/RequestHandlerInterface.php [deleted file]
typo3/sysext/core/Documentation/Changelog/master/Important-87516-RemoveCoreHTTPRequestHandlerInterface.rst [new file with mode: 0644]
typo3/sysext/frontend/Classes/Http/RequestHandler.php
typo3/sysext/install/Classes/Http/Application.php
typo3/sysext/install/Classes/Http/InstallerRequestHandler.php [deleted file]
typo3/sysext/install/Classes/Http/NotFoundRequestHandler.php [new file with mode: 0644]
typo3/sysext/install/Classes/Http/RequestHandler.php [deleted file]
typo3/sysext/install/Classes/Middleware/Installer.php [new file with mode: 0644]
typo3/sysext/install/Classes/Middleware/Maintenance.php [new file with mode: 0644]
typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php

index 5377191..eecfe51 100644 (file)
@@ -17,10 +17,9 @@ namespace TYPO3\CMS\Backend\Http;
 
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
-use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
+use Psr\Http\Server\RequestHandlerInterface;
 use TYPO3\CMS\Backend\Routing\Exception\InvalidRequestTokenException;
 use TYPO3\CMS\Core\Http\RedirectResponse;
-use TYPO3\CMS\Core\Http\RequestHandlerInterface;
 use TYPO3\CMS\Core\Http\Response;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -34,20 +33,9 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  *   - route
  *   - token
  */
-class RequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterface
+class RequestHandler implements RequestHandlerInterface
 {
     /**
-     * Handles any backend request
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     */
-    public function handleRequest(ServerRequestInterface $request): ResponseInterface
-    {
-        return $this->handle($request);
-    }
-
-    /**
      * Handles a backend request, after finishing running middlewares
      * Dispatch the request to the appropriate controller through the
      * Backend Dispatcher which resolves the routing
@@ -67,26 +55,4 @@ class RequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterf
             return new RedirectResponse($url);
         }
     }
-
-    /**
-     * This request handler can handle any backend request.
-     *
-     * @param ServerRequestInterface $request
-     * @return bool If the request is BE request TRUE otherwise FALSE
-     */
-    public function canHandleRequest(ServerRequestInterface $request): bool
-    {
-        return (bool)(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_BE);
-    }
-
-    /**
-     * Returns the priority - how eager the handler is to actually handle the
-     * request.
-     *
-     * @return int The priority of the request handler.
-     */
-    public function getPriority(): int
-    {
-        return 50;
-    }
 }
index 6c1d379..53688c8 100644 (file)
@@ -187,13 +187,8 @@ class Bootstrap
                 switch ($id) {
                 case \TYPO3\CMS\Frontend\Http\Application::class:
                 case \TYPO3\CMS\Backend\Http\Application::class:
-                    $entry = new $id($this->get(ConfigurationManager::class));
-                    break;
                 case \TYPO3\CMS\Install\Http\Application::class:
-                    $entry = new $id(
-                        GeneralUtility::makeInstance(\TYPO3\CMS\Install\Http\RequestHandler::class, $this->get(ConfigurationManager::class)),
-                        GeneralUtility::makeInstance(\TYPO3\CMS\Install\Http\InstallerRequestHandler::class)
-                    );
+                    $entry = new $id($this->get(ConfigurationManager::class));
                     break;
                 case \TYPO3\CMS\Core\Console\CommandApplication::class:
                     $entry = new $id;
diff --git a/typo3/sysext/core/Classes/Http/RequestHandlerInterface.php b/typo3/sysext/core/Classes/Http/RequestHandlerInterface.php
deleted file mode 100644 (file)
index 62edf40..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Core\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\ServerRequestInterface;
-
-/**
- * The interface for a request handler
- * see RequestHandler in EXT:backend/Classes/Http/ and EXT:frontend/Classes/Http
- *
- * @internal although TYPO3 Core still uses this in TYPO3 v9, this will be removed with PSR-15 RequestHandlerInterface
- */
-interface RequestHandlerInterface
-{
-    /**
-     * Handles a raw request
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     */
-    public function handleRequest(ServerRequestInterface $request);
-
-    /**
-     * Checks if the request handler can handle the given request.
-     *
-     * @param ServerRequestInterface $request
-     * @return bool TRUE if it can handle the request, otherwise FALSE
-     */
-    public function canHandleRequest(ServerRequestInterface $request);
-
-    /**
-     * Returns the priority - how eager the handler is to actually handle the
-     * request. An integer > 0 means "I want to handle this request" where
-     * "100" is default. "0" means "I am a fallback solution".
-     *
-     * @return int The priority of the request handler
-     */
-    public function getPriority();
-}
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Important-87516-RemoveCoreHTTPRequestHandlerInterface.rst b/typo3/sysext/core/Documentation/Changelog/master/Important-87516-RemoveCoreHTTPRequestHandlerInterface.rst
new file mode 100644 (file)
index 0000000..54b201b
--- /dev/null
@@ -0,0 +1,17 @@
+.. include:: ../../Includes.txt
+
+
+============================================================
+Important: #87516 - Remove core HTTP RequestHandlerInterface
+============================================================
+
+See :issue:`87516`
+
+Description
+===========
+
+The internal interface :php:`\TYPO3\CMS\Core\Http\RequestHandlerInterface` has
+been removed in favor of PSR-15 request handler and middleware interfaces which
+are now used throughout the core.
+
+.. index:: PHP-API, FullyScanned
index 5d3cd27..59cbdf7 100644 (file)
@@ -18,10 +18,9 @@ namespace TYPO3\CMS\Frontend\Http;
 
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
-use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
+use Psr\Http\Server\RequestHandlerInterface;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Http\NullResponse;
-use TYPO3\CMS\Core\Http\RequestHandlerInterface;
 use TYPO3\CMS\Core\Http\Response;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
@@ -56,7 +55,7 @@ use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
  *
  * Then the right HTTP response headers are compiled together and sent as well.
  */
-class RequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterface
+class RequestHandler implements RequestHandlerInterface
 {
     /**
      * Instance of the timetracker
@@ -65,17 +64,6 @@ class RequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterf
     protected $timeTracker;
 
     /**
-     * Handles a frontend request
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     */
-    public function handleRequest(ServerRequestInterface $request): ResponseInterface
-    {
-        return $this->handle($request);
-    }
-
-    /**
      * Puts parameters that have been added or removed from the global _GET or _POST arrays
      * into the given request (however, the PSR-7 request information takes precedence).
      *
@@ -1107,26 +1095,4 @@ class RequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterf
         }
         return $htmlTag;
     }
-
-    /**
-     * This request handler can handle any frontend request.
-     *
-     * @param ServerRequestInterface $request
-     * @return bool If the request is not an eID request, TRUE otherwise FALSE
-     */
-    public function canHandleRequest(ServerRequestInterface $request): bool
-    {
-        return true;
-    }
-
-    /**
-     * Returns the priority - how eager the handler is to actually handle the
-     * request.
-     *
-     * @return int The priority of the request handler.
-     */
-    public function getPriority(): int
-    {
-        return 50;
-    }
 }
index dd3a230..607b34a 100644 (file)
@@ -16,14 +16,18 @@ namespace TYPO3\CMS\Install\Http;
 
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Configuration\ConfigurationManager;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Context\DateTimeAspect;
 use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Context\VisibilityAspect;
 use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Http\AbstractApplication;
-use TYPO3\CMS\Core\Http\RequestHandlerInterface;
+use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Install\Middleware\Installer;
+use TYPO3\CMS\Install\Middleware\Maintenance;
 
 /**
  * Entry point for the TYPO3 Install Tool
@@ -32,25 +36,36 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class Application extends AbstractApplication
 {
     /**
-     * All available request handlers that can handle an install tool request
-     * @var array
+     * @var string
      */
-    protected $availableRequestHandlers = [];
+    protected $requestHandler = NotFoundRequestHandler::class;
+
+    /**
+     * @var ConfigurationManager
+     */
+    protected $configurationManager;
+
+    /**
+     * @param ConfigurationManager $configurationManager
+     */
+    public function __construct(ConfigurationManager $configurationManager)
+    {
+        $this->configurationManager = $configurationManager;
+    }
 
     /**
-     * Construct Application
-     *
      * @param RequestHandlerInterface $requestHandler
-     * @param RequestHandlerInterface $installerRequestHandler
+     * @return MiddlewareDispatcher
      */
-    public function __construct(
-        RequestHandlerInterface $requestHandler,
-        RequestHandlerInterface $installerRequestHandler
-    ) {
-        $this->availableRequestHandlers = [
-            $requestHandler,
-            $installerRequestHandler
-        ];
+    protected function createMiddlewareDispatcher(RequestHandlerInterface $requestHandler): MiddlewareDispatcher
+    {
+        $dispatcher = new MiddlewareDispatcher($requestHandler);
+
+        // Stack of middlewares, executed LIFO
+        $dispatcher->lazy(Installer::class);
+        $dispatcher->add(GeneralUtility::makeInstance(Maintenance::class, $this->configurationManager));
+
+        return $dispatcher;
     }
 
     /**
@@ -60,12 +75,7 @@ class Application extends AbstractApplication
     protected function handle(ServerRequestInterface $request): ResponseInterface
     {
         $this->initializeContext();
-        foreach ($this->availableRequestHandlers as $handler) {
-            if ($handler->canHandleRequest($request)) {
-                return $handler->handle($request);
-            }
-        }
-        throw new \TYPO3\CMS\Core\Exception('No suitable request handler found.', 1518448686);
+        return parent::handle($request);
     }
 
     /**
diff --git a/typo3/sysext/install/Classes/Http/InstallerRequestHandler.php b/typo3/sysext/install/Classes/Http/InstallerRequestHandler.php
deleted file mode 100644 (file)
index b094eb5..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Install\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\ServerRequestInterface;
-use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
-use TYPO3\CMS\Core\Configuration\ConfigurationManager;
-use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
-use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
-use TYPO3\CMS\Core\Http\JsonResponse;
-use TYPO3\CMS\Core\Http\RequestHandlerInterface;
-use TYPO3\CMS\Install\Controller\InstallerController;
-use TYPO3\CMS\Install\Service\EnableFileService;
-use TYPO3\CMS\Install\Service\SessionService;
-
-/**
- * Request handler to walk through the web installation process of TYPO3
- * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
- */
-class InstallerRequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterface
-{
-    /**
-     * Handles an Install Tool request when nothing is there
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     * @throws \RuntimeException
-     */
-    public function handleRequest(ServerRequestInterface $request): ResponseInterface
-    {
-        return $this->handle($request);
-    }
-
-    /**
-     * Handles an Install Tool request when nothing is there
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     * @throws \RuntimeException
-     */
-    public function handle(ServerRequestInterface $request): ResponseInterface
-    {
-        $controller = new InstallerController();
-        $actionName = $request->getParsedBody()['install']['action'] ?? $request->getQueryParams()['install']['action'] ?? 'init';
-        $action = $actionName . 'Action';
-
-        if ($actionName === 'init' || $actionName === 'mainLayout') {
-            $response = $controller->$action();
-        } elseif ($actionName === 'checkInstallerAvailable') {
-            $response = new JsonResponse([
-                'success' => $this->isInstallerAvailable(),
-            ]);
-        } elseif ($actionName === 'showInstallerNotAvailable') {
-            $response = $controller->showInstallerNotAvailableAction();
-        } elseif ($actionName === 'checkEnvironmentAndFolders'
-            || $actionName === 'showEnvironmentAndFolders'
-            || $actionName === 'executeEnvironmentAndFolders'
-        ) {
-            $this->throwIfInstallerIsNotAvailable();
-            $response = $controller->$action($request);
-        } else {
-            $this->throwIfInstallerIsNotAvailable();
-            // With main folder layout available, sessions can be handled
-            $session = new SessionService();
-            if (!$session->hasSession()) {
-                $session->startSession();
-            }
-            if ($session->isExpired()) {
-                $session->refreshSession();
-            }
-            $postValues = $request->getParsedBody()['install'];
-            $sessionTokenOk = false;
-            if (empty($postValues)) {
-                // No post data is there, no token check necessary
-                $sessionTokenOk = true;
-            }
-            if (isset($postValues['token'])) {
-                // A token must be given as soon as there is POST data
-                $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
-                if ($actionName === '') {
-                    throw new \RuntimeException('No POST action given for token check', 1505647681);
-                }
-                $sessionTokenOk = $formProtection->validateToken($postValues['token'], 'installTool', $actionName);
-            }
-            if (!$sessionTokenOk) {
-                $session->resetSession();
-                $session->startSession();
-                throw new \RuntimeException('Invalid session token', 1505647737);
-            }
-
-            if (!method_exists($controller, $action)) {
-                // Sanitize action method, preventing injecting whatever method name
-                throw new \RuntimeException(
-                    'Unknown action method ' . $action . ' in controller InstallerController',
-                    1505687700
-                );
-            }
-
-            $response = $controller->$action($request);
-
-            if ($actionName === 'executeDefaultConfiguration') {
-                // Executing last step cleans session
-                $session->destroySession();
-            }
-        }
-
-        return $response;
-    }
-
-    /**
-     * First installation is in progress, if LocalConfiguration does not exist,
-     * or if FIRST_INSTALL file exists.
-     *
-     * @param ServerRequestInterface $request
-     * @return bool Returns always TRUE
-     */
-    public function canHandleRequest(ServerRequestInterface $request): bool
-    {
-        $localConfigurationFileLocation = (new ConfigurationManager())->getLocalConfigurationFileLocation();
-        return !@is_file($localConfigurationFileLocation) || EnableFileService::isFirstInstallAllowed();
-    }
-
-    /**
-     * Returns the priority - how eager the handler is to actually handle the request.
-     *
-     * @return int The priority of the request handler.
-     */
-    public function getPriority(): int
-    {
-        return 20;
-    }
-
-    /**
-     * @throws \RuntimeException If installer is not available due to missing FIRST_INSTALL
-     */
-    protected function throwIfInstallerIsNotAvailable()
-    {
-        if (!$this->isInstallerAvailable()) {
-            throw new \RuntimeException(
-                'Installer not available',
-                1505637427
-            );
-        }
-    }
-
-    /**
-     * @return bool TRUE if FIRST_INSTALL file exists
-     */
-    protected function isInstallerAvailable(): bool
-    {
-        if (EnableFileService::isFirstInstallAllowed()) {
-            return true;
-        }
-        return false;
-    }
-}
diff --git a/typo3/sysext/install/Classes/Http/NotFoundRequestHandler.php b/typo3/sysext/install/Classes/Http/NotFoundRequestHandler.php
new file mode 100644 (file)
index 0000000..463e844
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Install\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\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Http\HtmlResponse;
+
+/**
+ * Fallback request handler for all requests inside the TYPO3 Install Tool.
+ * Returns a 404 status code, in case none of the previously executed middlewares handled the request.
+ * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
+ */
+class NotFoundRequestHandler implements RequestHandlerInterface
+{
+    /**
+     * Handles an Install Tool request when previously executed middlewares didn't handle thr request.
+     *
+     * @param ServerRequestInterface $request
+     * @param RequestHandlerInterface $handler
+     * @return ResponseInterface
+     */
+    public function handle(ServerRequestInterface $request): ResponseInterface
+    {
+        return new HtmlResponse('', 404);
+    }
+}
diff --git a/typo3/sysext/install/Classes/Http/RequestHandler.php b/typo3/sysext/install/Classes/Http/RequestHandler.php
deleted file mode 100644 (file)
index 89e4e6c..0000000
+++ /dev/null
@@ -1,353 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Install\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\ServerRequestInterface;
-use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
-use TYPO3\CMS\Core\Configuration\ConfigurationManager;
-use TYPO3\CMS\Core\Core\Environment;
-use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
-use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
-use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
-use TYPO3\CMS\Core\Http\HtmlResponse;
-use TYPO3\CMS\Core\Http\JsonResponse;
-use TYPO3\CMS\Core\Http\RequestHandlerInterface;
-use TYPO3\CMS\Core\Messaging\FlashMessage;
-use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
-use TYPO3\CMS\Core\Package\PackageInterface;
-use TYPO3\CMS\Core\Package\PackageManager;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Install\Authentication\AuthenticationService;
-use TYPO3\CMS\Install\Controller\AbstractController;
-use TYPO3\CMS\Install\Controller\EnvironmentController;
-use TYPO3\CMS\Install\Controller\IconController;
-use TYPO3\CMS\Install\Controller\LayoutController;
-use TYPO3\CMS\Install\Controller\LoginController;
-use TYPO3\CMS\Install\Controller\MaintenanceController;
-use TYPO3\CMS\Install\Controller\SettingsController;
-use TYPO3\CMS\Install\Controller\UpgradeController;
-use TYPO3\CMS\Install\Service\EnableFileService;
-use TYPO3\CMS\Install\Service\SessionService;
-
-/**
- * Default request handler for all requests inside the TYPO3 Install Tool, which does a simple hardcoded
- * dispatching to a controller based on the get/post variable.
- * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
- */
-class RequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterface
-{
-    /**
-     * @var ConfigurationManager
-     */
-    protected $configurationManager;
-
-    /**
-     * @var array List of valid controllers
-     */
-    protected $controllers = [
-        'icon' => IconController::class,
-        'layout' => LayoutController::class,
-        'login' => LoginController::class,
-        'maintenance' => MaintenanceController::class,
-        'settings' => SettingsController::class,
-        'upgrade' => UpgradeController::class,
-        'environment' => EnvironmentController::class,
-    ];
-
-    /**
-     * @param ConfigurationManager $configurationManager
-     */
-    public function __construct(ConfigurationManager $configurationManager)
-    {
-        $this->configurationManager = $configurationManager;
-    }
-
-    /**
-     * Handles an Install Tool request for normal operations
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     */
-    public function handleRequest(ServerRequestInterface $request): ResponseInterface
-    {
-        return $this->handle($request);
-    }
-
-    /**
-     * Handles an Install Tool request for normal operations
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     */
-    public function handle(ServerRequestInterface $request): ResponseInterface
-    {
-        $controllerName = $request->getQueryParams()['install']['controller'] ?? 'layout';
-        $actionName = $request->getParsedBody()['install']['action'] ?? $request->getQueryParams()['install']['action'] ?? 'init';
-        $action = $actionName . 'Action';
-
-        $session = $this->initializeSession();
-        if ($actionName === 'init') {
-            $controller = new LayoutController();
-            $response = $controller->initAction($request);
-        } elseif ($actionName === 'checkEnableInstallToolFile') {
-            $response = new JsonResponse([
-                'success' => $this->checkEnableInstallToolFile(),
-            ]);
-        } elseif ($actionName === 'showEnableInstallToolFile') {
-            $controller = new LoginController();
-            $response = $controller->showEnableInstallToolFileAction($request);
-        } elseif ($actionName === 'checkLogin') {
-            if (!$this->checkEnableInstallToolFile() && !$session->isAuthorizedBackendUserSession()) {
-                throw new \RuntimeException('Not authorized', 1505563556);
-            }
-            if ($session->isExpired() || !$session->isAuthorized()) {
-                // Session expired, log out user, start new session
-                $session->resetSession();
-                $session->startSession();
-                $response = new JsonResponse([
-                    'success' => false,
-                ]);
-            } else {
-                $session->refreshSession();
-                $response = new JsonResponse([
-                    'success' => true,
-                ]);
-            }
-        } elseif ($actionName === 'showLogin') {
-            if (!$this->checkEnableInstallToolFile()) {
-                throw new \RuntimeException('Not authorized', 1505564888);
-            }
-            $controller = new LoginController();
-            $response = $controller->showLoginAction($request);
-        } elseif ($actionName === 'login') {
-            if (!$this->checkEnableInstallToolFile()) {
-                throw new \RuntimeException('Not authorized', 1505567462);
-            }
-            $this->checkSessionToken($request, $session);
-            $this->checkSessionLifetime($session);
-            $password = $request->getParsedBody()['install']['password'] ?? null;
-            $authService = new AuthenticationService($session);
-            if ($authService->loginWithPassword($password)) {
-                $response = new JsonResponse([
-                    'success' => true,
-                ]);
-            } else {
-                if ($password === null || empty($password)) {
-                    $messageQueue = (new FlashMessageQueue('install'))->enqueue(
-                        new FlashMessage('Please enter the install tool password', '', FlashMessage::ERROR)
-                    );
-                } else {
-                    $hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
-                    $hashedPassword = $hashInstance->getHashedPassword($password);
-                    $messageQueue = (new FlashMessageQueue('install'))->enqueue(
-                        new FlashMessage(
-                            'Given password does not match the install tool login password. Calculated hash: ' . $hashedPassword,
-                            '',
-                            FlashMessage::ERROR
-                        )
-                    );
-                }
-                $response = new JsonResponse([
-                    'success' => false,
-                    'status' => $messageQueue,
-                ]);
-            }
-        } elseif ($actionName === 'logout') {
-            if (EnableFileService::installToolEnableFileExists() && !EnableFileService::isInstallToolEnableFilePermanent()) {
-                EnableFileService::removeInstallToolEnableFile();
-            }
-            $formProtection = FormProtectionFactory::get(
-                InstallToolFormProtection::class
-            );
-            $formProtection->clean();
-            $session->destroySession();
-            $response = new JsonResponse([
-                'success' => true,
-            ]);
-        } else {
-            if (
-                !$this->checkSessionToken($request, $session)
-                || !$this->checkSessionLifetime($session)
-                || !$session->isAuthorized()
-            ) {
-                return new HtmlResponse('', 403);
-            }
-            $session->refreshSession();
-            if (!array_key_exists($controllerName, $this->controllers)) {
-                throw new \RuntimeException(
-                    'Unknown controller ' . $controllerName,
-                    1505215756
-                );
-            }
-            $this->recreatePackageStatesFileIfMissing();
-            /** @var AbstractController $controller */
-            $controller = new $this->controllers[$controllerName];
-            if (!method_exists($controller, $action)) {
-                throw new \RuntimeException(
-                    'Unknown action method ' . $action . ' in controller ' . $controllerName,
-                    1505216027
-                );
-            }
-            $response = $controller->$action($request);
-        }
-
-        return $response;
-    }
-
-    /**
-     * This request handler can handle any request when not in CLI mode.
-     * Warning: Order of these methods is security relevant and interferes with different access
-     * conditions (new/existing installation). See the single method comments for details.
-     *
-     * @param ServerRequestInterface $request
-     * @return bool Returns always TRUE
-     */
-    public function canHandleRequest(ServerRequestInterface $request): bool
-    {
-        $basicIntegrity = $this->checkIfEssentialConfigurationExists()
-            && !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'])
-            && !EnableFileService::isFirstInstallAllowed();
-        if (!$basicIntegrity) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Returns the priority - how eager the handler is to actually handle the request.
-     *
-     * @return int The priority of the request handler.
-     */
-    public function getPriority(): int
-    {
-        return 50;
-    }
-
-    /**
-     * Checks if ENABLE_INSTALL_TOOL exists.
-     *
-     * @return bool
-     */
-    protected function checkEnableInstallToolFile()
-    {
-        return EnableFileService::checkInstallToolEnableFile();
-    }
-
-    /**
-     * Initialize session object.
-     * Subclass will throw exception if session can not be created or if
-     * preconditions like a valid encryption key are not set.
-     *
-     * @return SessionService
-     */
-    protected function initializeSession()
-    {
-        $session = new SessionService();
-        if (!$session->hasSession()) {
-            $session->startSession();
-        }
-        return $session;
-    }
-
-    /**
-     * Use form protection API to find out if protected POST forms are ok.
-     *
-     * @param ServerRequestInterface $request
-     * @param SessionService $session
-     * @return bool
-     */
-    protected function checkSessionToken(ServerRequestInterface $request, SessionService $session): bool
-    {
-        $postValues = $request->getParsedBody()['install'];
-        // no post data is there, so no token check necessary
-        if (empty($postValues)) {
-            return true;
-        }
-        $tokenOk = false;
-        // A token must be given as soon as there is POST data
-        if (isset($postValues['token'])) {
-            $formProtection = FormProtectionFactory::get(
-                InstallToolFormProtection::class
-            );
-            $action = (string)$postValues['action'];
-            if ($action === '') {
-                throw new \RuntimeException(
-                    'No POST action given for token check',
-                    1369326593
-                );
-            }
-            $tokenOk = $formProtection->validateToken($postValues['token'], 'installTool', $action);
-        }
-        if (!$tokenOk) {
-            $session->resetSession();
-            $session->startSession();
-        }
-        return $tokenOk;
-    }
-
-    /**
-     * Check if session expired.
-     * If the session has expired, the login form is displayed.
-     *
-     * @param SessionService $session
-     * @return bool True if session lifetime is OK
-     */
-    protected function checkSessionLifetime(SessionService $session): bool
-    {
-        $isExpired = $session->isExpired();
-        if ($isExpired) {
-            // Session expired, log out user, start new session
-            $session->resetSession();
-            $session->startSession();
-        }
-        return !$isExpired;
-    }
-
-    /**
-     * Check if LocalConfiguration.php and PackageStates.php exist
-     *
-     * @return bool TRUE when the essential configuration is available, otherwise FALSE
-     */
-    protected function checkIfEssentialConfigurationExists(): bool
-    {
-        return file_exists($this->configurationManager->getLocalConfigurationFileLocation());
-    }
-
-    /**
-     * Create PackageStates.php if missing and LocalConfiguration exists.
-     *
-     * It is fired if PackageStates.php is deleted on a running instance,
-     * all packages marked as "part of minimal system" are activated in this case.
-     */
-    protected function recreatePackageStatesFileIfMissing(): void
-    {
-        if (!file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php')) {
-            // We need a FailsafePackageManager at this moment, however this is given
-            // As Bootstrap is registering the FailsafePackageManager object as a singleton instance
-            // of the main PackageManager class. See \TYPO3\CMS\Core\Core\Bootstrap::init()
-            /** @var \TYPO3\CMS\Core\Package\FailsafePackageManager $packageManager */
-            $packageManager = GeneralUtility::makeInstance(PackageManager::class);
-            $packages = $packageManager->getAvailablePackages();
-            foreach ($packages as $package) {
-                if ($package instanceof PackageInterface && $package->isPartOfMinimalUsableSystem()) {
-                    $packageManager->activatePackage($package->getPackageKey());
-                }
-            }
-            $packageManager->forceSortAndSavePackageStates();
-        }
-    }
-}
diff --git a/typo3/sysext/install/Classes/Middleware/Installer.php b/typo3/sysext/install/Classes/Middleware/Installer.php
new file mode 100644 (file)
index 0000000..9bf56ee
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Install\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\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
+use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
+use TYPO3\CMS\Core\Http\JsonResponse;
+use TYPO3\CMS\Install\Controller\InstallerController;
+use TYPO3\CMS\Install\Service\EnableFileService;
+use TYPO3\CMS\Install\Service\SessionService;
+
+/**
+ * Middleware to walk through the web installation process of TYPO3
+ * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
+ */
+class Installer implements MiddlewareInterface
+{
+    /**
+     * Handles an Install Tool request when nothing is there
+     *
+     * @param ServerRequestInterface $request
+     * @param RequestHandlerInterface $handler
+     * @return ResponseInterface
+     * @throws \RuntimeException
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        if (!$this->canHandleRequest($request)) {
+            return $handler->handle($request);
+        }
+
+        $controller = new InstallerController();
+        $actionName = $request->getParsedBody()['install']['action'] ?? $request->getQueryParams()['install']['action'] ?? 'init';
+        $action = $actionName . 'Action';
+
+        if ($actionName === 'init' || $actionName === 'mainLayout') {
+            $response = $controller->$action();
+        } elseif ($actionName === 'checkInstallerAvailable') {
+            $response = new JsonResponse([
+                'success' => $this->isInstallerAvailable(),
+            ]);
+        } elseif ($actionName === 'showInstallerNotAvailable') {
+            $response = $controller->showInstallerNotAvailableAction();
+        } elseif ($actionName === 'checkEnvironmentAndFolders'
+            || $actionName === 'showEnvironmentAndFolders'
+            || $actionName === 'executeEnvironmentAndFolders'
+        ) {
+            $this->throwIfInstallerIsNotAvailable();
+            $response = $controller->$action($request);
+        } else {
+            $this->throwIfInstallerIsNotAvailable();
+            // With main folder layout available, sessions can be handled
+            $session = new SessionService();
+            if (!$session->hasSession()) {
+                $session->startSession();
+            }
+            if ($session->isExpired()) {
+                $session->refreshSession();
+            }
+            $postValues = $request->getParsedBody()['install'];
+            $sessionTokenOk = false;
+            if (empty($postValues)) {
+                // No post data is there, no token check necessary
+                $sessionTokenOk = true;
+            }
+            if (isset($postValues['token'])) {
+                // A token must be given as soon as there is POST data
+                $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
+                if ($actionName === '') {
+                    throw new \RuntimeException('No POST action given for token check', 1505647681);
+                }
+                $sessionTokenOk = $formProtection->validateToken($postValues['token'], 'installTool', $actionName);
+            }
+            if (!$sessionTokenOk) {
+                $session->resetSession();
+                $session->startSession();
+                throw new \RuntimeException('Invalid session token', 1505647737);
+            }
+
+            if (!method_exists($controller, $action)) {
+                // Sanitize action method, preventing injecting whatever method name
+                throw new \RuntimeException(
+                    'Unknown action method ' . $action . ' in controller InstallerController',
+                    1505687700
+                );
+            }
+
+            $response = $controller->$action($request);
+
+            if ($actionName === 'executeDefaultConfiguration') {
+                // Executing last step cleans session
+                $session->destroySession();
+            }
+        }
+
+        return $response;
+    }
+
+    /**
+     * First installation is in progress, if LocalConfiguration does not exist,
+     * or if FIRST_INSTALL file exists.
+     *
+     * @param ServerRequestInterface $request
+     * @return bool Returns always TRUE
+     */
+    protected function canHandleRequest(ServerRequestInterface $request): bool
+    {
+        $localConfigurationFileLocation = (new ConfigurationManager())->getLocalConfigurationFileLocation();
+        return !@is_file($localConfigurationFileLocation) || EnableFileService::isFirstInstallAllowed();
+    }
+
+    /**
+     * @throws \RuntimeException If installer is not available due to missing FIRST_INSTALL
+     */
+    protected function throwIfInstallerIsNotAvailable()
+    {
+        if (!$this->isInstallerAvailable()) {
+            throw new \RuntimeException(
+                'Installer not available',
+                1505637427
+            );
+        }
+    }
+
+    /**
+     * @return bool TRUE if FIRST_INSTALL file exists
+     */
+    protected function isInstallerAvailable(): bool
+    {
+        if (EnableFileService::isFirstInstallAllowed()) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/typo3/sysext/install/Classes/Middleware/Maintenance.php b/typo3/sysext/install/Classes/Middleware/Maintenance.php
new file mode 100644 (file)
index 0000000..cf19621
--- /dev/null
@@ -0,0 +1,337 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Install\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\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
+use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
+use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
+use TYPO3\CMS\Core\Http\HtmlResponse;
+use TYPO3\CMS\Core\Http\JsonResponse;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
+use TYPO3\CMS\Core\Package\PackageInterface;
+use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Install\Authentication\AuthenticationService;
+use TYPO3\CMS\Install\Controller\AbstractController;
+use TYPO3\CMS\Install\Controller\EnvironmentController;
+use TYPO3\CMS\Install\Controller\IconController;
+use TYPO3\CMS\Install\Controller\LayoutController;
+use TYPO3\CMS\Install\Controller\LoginController;
+use TYPO3\CMS\Install\Controller\MaintenanceController;
+use TYPO3\CMS\Install\Controller\SettingsController;
+use TYPO3\CMS\Install\Controller\UpgradeController;
+use TYPO3\CMS\Install\Service\EnableFileService;
+use TYPO3\CMS\Install\Service\SessionService;
+
+/**
+ * Default middleware for all requests inside the TYPO3 Install Tool, which does a simple hardcoded
+ * dispatching to a controller based on the get/post variable.
+ * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
+ */
+class Maintenance implements MiddlewareInterface
+{
+    /**
+     * @var ConfigurationManager
+     */
+    protected $configurationManager;
+
+    /**
+     * @var array List of valid controllers
+     */
+    protected $controllers = [
+        'icon' => IconController::class,
+        'layout' => LayoutController::class,
+        'login' => LoginController::class,
+        'maintenance' => MaintenanceController::class,
+        'settings' => SettingsController::class,
+        'upgrade' => UpgradeController::class,
+        'environment' => EnvironmentController::class,
+    ];
+
+    /**
+     * @param ConfigurationManager $configurationManager
+     */
+    public function __construct(ConfigurationManager $configurationManager)
+    {
+        $this->configurationManager = $configurationManager;
+    }
+
+    /**
+     * Handles an Install Tool request for normal operations
+     *
+     * @param ServerRequestInterface $request
+     * @param RequestHandlerInterface $handler
+     * @return ResponseInterface
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        if (!$this->canHandleRequest($request)) {
+            return $handler->handle($request);
+        }
+
+        $controllerName = $request->getQueryParams()['install']['controller'] ?? 'layout';
+        $actionName = $request->getParsedBody()['install']['action'] ?? $request->getQueryParams()['install']['action'] ?? 'init';
+        $action = $actionName . 'Action';
+
+        $session = $this->initializeSession();
+        if ($actionName === 'init') {
+            $controller = new LayoutController();
+            $response = $controller->initAction($request);
+        } elseif ($actionName === 'checkEnableInstallToolFile') {
+            $response = new JsonResponse([
+                'success' => $this->checkEnableInstallToolFile(),
+            ]);
+        } elseif ($actionName === 'showEnableInstallToolFile') {
+            $controller = new LoginController();
+            $response = $controller->showEnableInstallToolFileAction($request);
+        } elseif ($actionName === 'checkLogin') {
+            if (!$this->checkEnableInstallToolFile() && !$session->isAuthorizedBackendUserSession()) {
+                throw new \RuntimeException('Not authorized', 1505563556);
+            }
+            if ($session->isExpired() || !$session->isAuthorized()) {
+                // Session expired, log out user, start new session
+                $session->resetSession();
+                $session->startSession();
+                $response = new JsonResponse([
+                    'success' => false,
+                ]);
+            } else {
+                $session->refreshSession();
+                $response = new JsonResponse([
+                    'success' => true,
+                ]);
+            }
+        } elseif ($actionName === 'showLogin') {
+            if (!$this->checkEnableInstallToolFile()) {
+                throw new \RuntimeException('Not authorized', 1505564888);
+            }
+            $controller = new LoginController();
+            $response = $controller->showLoginAction($request);
+        } elseif ($actionName === 'login') {
+            if (!$this->checkEnableInstallToolFile()) {
+                throw new \RuntimeException('Not authorized', 1505567462);
+            }
+            $this->checkSessionToken($request, $session);
+            $this->checkSessionLifetime($session);
+            $password = $request->getParsedBody()['install']['password'] ?? null;
+            $authService = new AuthenticationService($session);
+            if ($authService->loginWithPassword($password)) {
+                $response = new JsonResponse([
+                    'success' => true,
+                ]);
+            } else {
+                if ($password === null || empty($password)) {
+                    $messageQueue = (new FlashMessageQueue('install'))->enqueue(
+                        new FlashMessage('Please enter the install tool password', '', FlashMessage::ERROR)
+                    );
+                } else {
+                    $hashInstance = GeneralUtility::makeInstance(PasswordHashFactory::class)->getDefaultHashInstance('BE');
+                    $hashedPassword = $hashInstance->getHashedPassword($password);
+                    $messageQueue = (new FlashMessageQueue('install'))->enqueue(
+                        new FlashMessage(
+                            'Given password does not match the install tool login password. Calculated hash: ' . $hashedPassword,
+                            '',
+                            FlashMessage::ERROR
+                        )
+                    );
+                }
+                $response = new JsonResponse([
+                    'success' => false,
+                    'status' => $messageQueue,
+                ]);
+            }
+        } elseif ($actionName === 'logout') {
+            if (EnableFileService::installToolEnableFileExists() && !EnableFileService::isInstallToolEnableFilePermanent()) {
+                EnableFileService::removeInstallToolEnableFile();
+            }
+            $formProtection = FormProtectionFactory::get(
+                InstallToolFormProtection::class
+            );
+            $formProtection->clean();
+            $session->destroySession();
+            $response = new JsonResponse([
+                'success' => true,
+            ]);
+        } else {
+            if (
+                !$this->checkSessionToken($request, $session)
+                || !$this->checkSessionLifetime($session)
+                || !$session->isAuthorized()
+            ) {
+                return new HtmlResponse('', 403);
+            }
+            $session->refreshSession();
+            if (!array_key_exists($controllerName, $this->controllers)) {
+                throw new \RuntimeException(
+                    'Unknown controller ' . $controllerName,
+                    1505215756
+                );
+            }
+            $this->recreatePackageStatesFileIfMissing();
+            /** @var AbstractController $controller */
+            $controller = new $this->controllers[$controllerName];
+            if (!method_exists($controller, $action)) {
+                throw new \RuntimeException(
+                    'Unknown action method ' . $action . ' in controller ' . $controllerName,
+                    1505216027
+                );
+            }
+            $response = $controller->$action($request);
+        }
+
+        return $response;
+    }
+
+    /**
+     * This request handler can handle any request when not in CLI mode.
+     * Warning: Order of these methods is security relevant and interferes with different access
+     * conditions (new/existing installation). See the single method comments for details.
+     *
+     * @param ServerRequestInterface $request
+     * @return bool Returns always TRUE
+     */
+    protected function canHandleRequest(ServerRequestInterface $request): bool
+    {
+        $basicIntegrity = $this->checkIfEssentialConfigurationExists()
+            && !empty($GLOBALS['TYPO3_CONF_VARS']['BE']['installToolPassword'])
+            && !EnableFileService::isFirstInstallAllowed();
+        if (!$basicIntegrity) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Checks if ENABLE_INSTALL_TOOL exists.
+     *
+     * @return bool
+     */
+    protected function checkEnableInstallToolFile()
+    {
+        return EnableFileService::checkInstallToolEnableFile();
+    }
+
+    /**
+     * Initialize session object.
+     * Subclass will throw exception if session can not be created or if
+     * preconditions like a valid encryption key are not set.
+     *
+     * @return SessionService
+     */
+    protected function initializeSession()
+    {
+        $session = new SessionService();
+        if (!$session->hasSession()) {
+            $session->startSession();
+        }
+        return $session;
+    }
+
+    /**
+     * Use form protection API to find out if protected POST forms are ok.
+     *
+     * @param ServerRequestInterface $request
+     * @param SessionService $session
+     * @return bool
+     */
+    protected function checkSessionToken(ServerRequestInterface $request, SessionService $session): bool
+    {
+        $postValues = $request->getParsedBody()['install'];
+        // no post data is there, so no token check necessary
+        if (empty($postValues)) {
+            return true;
+        }
+        $tokenOk = false;
+        // A token must be given as soon as there is POST data
+        if (isset($postValues['token'])) {
+            $formProtection = FormProtectionFactory::get(
+                InstallToolFormProtection::class
+            );
+            $action = (string)$postValues['action'];
+            if ($action === '') {
+                throw new \RuntimeException(
+                    'No POST action given for token check',
+                    1369326593
+                );
+            }
+            $tokenOk = $formProtection->validateToken($postValues['token'], 'installTool', $action);
+        }
+        if (!$tokenOk) {
+            $session->resetSession();
+            $session->startSession();
+        }
+        return $tokenOk;
+    }
+
+    /**
+     * Check if session expired.
+     * If the session has expired, the login form is displayed.
+     *
+     * @param SessionService $session
+     * @return bool True if session lifetime is OK
+     */
+    protected function checkSessionLifetime(SessionService $session): bool
+    {
+        $isExpired = $session->isExpired();
+        if ($isExpired) {
+            // Session expired, log out user, start new session
+            $session->resetSession();
+            $session->startSession();
+        }
+        return !$isExpired;
+    }
+
+    /**
+     * Check if LocalConfiguration.php and PackageStates.php exist
+     *
+     * @return bool TRUE when the essential configuration is available, otherwise FALSE
+     */
+    protected function checkIfEssentialConfigurationExists(): bool
+    {
+        return file_exists($this->configurationManager->getLocalConfigurationFileLocation());
+    }
+
+    /**
+     * Create PackageStates.php if missing and LocalConfiguration exists.
+     *
+     * It is fired if PackageStates.php is deleted on a running instance,
+     * all packages marked as "part of minimal system" are activated in this case.
+     */
+    protected function recreatePackageStatesFileIfMissing(): void
+    {
+        if (!file_exists(Environment::getLegacyConfigPath() . '/PackageStates.php')) {
+            // We need a FailsafePackageManager at this moment, however this is given
+            // As Bootstrap is registering the FailsafePackageManager object as a singleton instance
+            // of the main PackageManager class. See \TYPO3\CMS\Core\Core\Bootstrap::init()
+            /** @var \TYPO3\CMS\Core\Package\FailsafePackageManager $packageManager */
+            $packageManager = GeneralUtility::makeInstance(PackageManager::class);
+            $packages = $packageManager->getAvailablePackages();
+            foreach ($packages as $package) {
+                if ($package instanceof PackageInterface && $package->isPartOfMinimalUsableSystem()) {
+                    $packageManager->activatePackage($package->getPackageKey());
+                }
+            }
+            $packageManager->forceSortAndSavePackageStates();
+        }
+    }
+}
index 5138a87..bfef59a 100644 (file)
@@ -1239,4 +1239,9 @@ return [
             'Breaking-87193-DeprecatedFunctionalityRemoved.rst',
         ],
     ],
+    'TYPO3\CMS\Core\Http\RequestHandlerInterface' => [
+        'restFiles' => [
+            'Important-87516-RemoveCoreHTTPRequestHandlerInterface.rst',
+        ],
+    ],
 ];