From 7159ff8aaeb5a0b771ce013862241cb33727b7df Mon Sep 17 00:00:00 2001
From: Simon Schaufelberger <simonschaufi+typo3@gmail.com>
Date: Tue, 14 Sep 2021 19:09:30 +0200
Subject: [PATCH] [BUGFIX] Fix TypoScriptFrontendController initialization in
 subrequests

With the introduction of #94402 error pages are fetched via a
sub-request. Manual "page not found" ErrorController invocations
within an extbase action then resulted in rendering the originally
requested page instead of the defined 404 page.
This happened because the PrepareTypoScriptFrontendRendering
middleware hold a reference to the outer TSFE instance which
contains the original page id.

Instead of injecting the stateful TSFE (which is generally discouraged),
TSFE in now passed as request attribute. The container will
log an according warning message from now on.

Resolves: #95174
Releases: master
Change-Id: Ieda58e2bef8f08762fcba06b76df03aff7b10d5c
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/71084
Tested-by: core-ci <typo3@b13.com>
Tested-by: Simon Gilli <typo3@gilbertsoft.org>
Tested-by: Benjamin Franzke <bfr@qbus.de>
Reviewed-by: Simon Gilli <typo3@gilbertsoft.org>
Reviewed-by: Benjamin Franzke <bfr@qbus.de>
---
 .../PrepareTypoScriptFrontendRendering.php    | 21 +++++++--------
 .../ShortcutAndMountPointRedirect.php         | 26 +++++++------------
 .../TypoScriptFrontendInitialization.php      |  3 +++
 .../frontend/Configuration/Services.yaml      |  7 +++++
 4 files changed, 29 insertions(+), 28 deletions(-)

diff --git a/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php b/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php
index d724c74acc18..bd382fe3b259 100644
--- a/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php
+++ b/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php
@@ -33,19 +33,13 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
  */
 class PrepareTypoScriptFrontendRendering implements MiddlewareInterface
 {
-    /**
-     * @var TypoScriptFrontendController
-     */
-    protected $controller;
-
     /**
      * @var TimeTracker
      */
     protected $timeTracker;
 
-    public function __construct(TypoScriptFrontendController $controller, TimeTracker $timeTracker)
+    public function __construct(TimeTracker $timeTracker)
     {
-        $this->controller = $controller;
         $this->timeTracker = $timeTracker;
     }
 
@@ -58,23 +52,26 @@ class PrepareTypoScriptFrontendRendering implements MiddlewareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
+        /** @var TypoScriptFrontendController */
+        $controller = $request->getAttribute('frontend.controller');
+
         // as long as TSFE throws errors with the global object, this needs to be set, but
         // should be removed later-on once TypoScript Condition Matcher is built with the current request object.
         $GLOBALS['TYPO3_REQUEST'] = $request;
         // Get from cache
         $this->timeTracker->push('Get Page from cache');
         // Locks may be acquired here
-        $this->controller->getFromCache($request);
+        $controller->getFromCache($request);
         $this->timeTracker->pull();
         // Get config if not already gotten
         // After this, we should have a valid config-array ready
-        $this->controller->getConfigArray($request);
+        $controller->getConfigArray($request);
 
         // Convert POST data to utf-8 for internal processing if metaCharset is different
-        if ($this->controller->metaCharset !== 'utf-8' && $request->getMethod() === 'POST') {
+        if ($controller->metaCharset !== 'utf-8' && $request->getMethod() === 'POST') {
             $parsedBody = $request->getParsedBody();
             if (is_array($parsedBody) && !empty($parsedBody)) {
-                $this->convertCharsetRecursivelyToUtf8($parsedBody, $this->controller->metaCharset);
+                $this->convertCharsetRecursivelyToUtf8($parsedBody, $controller->metaCharset);
                 $request = $request->withParsedBody($parsedBody);
             }
         }
@@ -86,7 +83,7 @@ class PrepareTypoScriptFrontendRendering implements MiddlewareInterface
          * However, when some middlewares returns early (e.g. Shortcut and MountPointRedirect,
          * which both skip inner middlewares), or due to Exceptions, locks still need to be released explicitly.
          */
-        $this->controller->releaseLocks();
+        $controller->releaseLocks();
 
         return $response;
     }
diff --git a/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php b/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php
index 315c2955e22a..27256e0c37e7 100644
--- a/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php
+++ b/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php
@@ -35,16 +35,6 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
  */
 class ShortcutAndMountPointRedirect implements MiddlewareInterface
 {
-    /**
-     * @var TypoScriptFrontendController
-     */
-    private $controller;
-
-    public function __construct(TypoScriptFrontendController $controller)
-    {
-        $this->controller = $controller;
-    }
-
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
         $exposeInformation = $GLOBALS['TYPO3_CONF_VARS']['FE']['exposeRedirectInformation'] ?? false;
@@ -63,14 +53,16 @@ class ShortcutAndMountPointRedirect implements MiddlewareInterface
         }
 
         // See if the current page is of doktype "External URL", if so, do a redirect as well.
-        if (empty($this->controller->config['config']['disablePageExternalUrl'] ?? null)
-            && PageRepository::DOKTYPE_LINK === (int)$this->controller->page['doktype']) {
+        /** @var TypoScriptFrontendController */
+        $controller = $request->getAttribute('frontend.controller');
+        if (empty($controller->config['config']['disablePageExternalUrl'] ?? null)
+            && PageRepository::DOKTYPE_LINK === (int)$controller->page['doktype']) {
             $externalUrl = $this->prefixExternalPageUrl(
-                $this->controller->page['url'],
+                $controller->page['url'],
                 $request->getAttribute('normalizedParams')->getSiteUrl()
             );
             if (!empty($externalUrl)) {
-                $message = 'TYPO3 External URL' . ($exposeInformation ? ' at page with ID ' . $this->controller->page['uid'] : '');
+                $message = 'TYPO3 External URL' . ($exposeInformation ? ' at page with ID ' . $controller->page['uid'] : '');
                 return new RedirectResponse(
                     $externalUrl,
                     303,
@@ -84,11 +76,13 @@ class ShortcutAndMountPointRedirect implements MiddlewareInterface
 
     protected function getRedirectUri(ServerRequestInterface $request): ?string
     {
-        $redirectToUri = $this->controller->getRedirectUriForShortcut($request);
+        /** @var TypoScriptFrontendController */
+        $controller = $request->getAttribute('frontend.controller');
+        $redirectToUri = $controller->getRedirectUriForShortcut($request);
         if ($redirectToUri !== null) {
             return $redirectToUri;
         }
-        return $this->controller->getRedirectUriForMountPoint($request);
+        return $controller->getRedirectUriForMountPoint($request);
     }
 
     /**
diff --git a/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php b/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
index d8319111630b..017e44b794bf 100644
--- a/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
+++ b/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
@@ -96,7 +96,10 @@ class TypoScriptFrontendInitialization implements MiddlewareInterface
 
         $controller->determineId($request);
 
+        $request = $request->withAttribute('frontend.controller', $controller);
         // Make TSFE globally available
+        // @todo deprecate $GLOBALS['TSFE'] once TSFE is retrieved from the
+        //       PSR-7 request attribute frontend.controller throughout TYPO3 core
         $GLOBALS['TSFE'] = $controller;
         return $handler->handle($request);
     }
diff --git a/typo3/sysext/frontend/Configuration/Services.yaml b/typo3/sysext/frontend/Configuration/Services.yaml
index 8c070b32e3db..dc0765ac62ab 100644
--- a/typo3/sysext/frontend/Configuration/Services.yaml
+++ b/typo3/sysext/frontend/Configuration/Services.yaml
@@ -7,15 +7,22 @@ services:
   TYPO3\CMS\Frontend\:
     resource: '../Classes/*'
 
+  # @deprecated since v11, will be removed in v12 - TypoScriptFrontendController is stateful and must be retrieved from the request attribute frontend.controller.
   TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController:
     factory: ['TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController', getGlobalInstance]
     shared: false
     autoconfigure: false
     autowire: false
+    deprecated:
+      package: 'typo3/cms-core'
+      version: '11.5'
+      message: 'Injection of "%service_id%" breaks subrequests. Please use the PSR-7 request attribute "frontend.controller".'
 
   TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer:
     public: true
     shared: false
+    arguments:
+      $typoScriptFrontendController: null
 
   TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler:
     public: true
-- 
GitLab