[BUGFIX] Skip empty static routes 10/63610/8
authorGeorg Ringer <georg.ringer@gmail.com>
Sat, 7 Mar 2020 19:39:29 +0000 (20:39 +0100)
committerSusanne Moog <look@susi.dev>
Mon, 23 Mar 2020 16:02:48 +0000 (17:02 +0100)
Due to incomplete editing in the site module or wrong yaml configuration
it is possible that an empty static route is defined which will break
the complete frontend.

Skip empty routes as those don't make any sense.

Resolves: #87500
Releases: master, 9.5
Change-Id: I9f5737bf077fd3b2f0c38522ee3ae641c7e5cd21
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63610
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Daniel Goerz <daniel.goerz@posteo.de>
Tested-by: Susanne Moog <look@susi.dev>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Daniel Goerz <daniel.goerz@posteo.de>
Reviewed-by: Susanne Moog <look@susi.dev>
typo3/sysext/frontend/Classes/Middleware/StaticRouteResolver.php
typo3/sysext/frontend/Tests/Unit/Middleware/StaticRouteResolverTest.php [new file with mode: 0644]

index 1abadff..0aef8a1 100644 (file)
@@ -65,12 +65,8 @@ class StaticRouteResolver implements MiddlewareInterface
             ($configuration = $site->getConfiguration()['routes'] ?? null)
         ) {
             $path = ltrim($request->getUri()->getPath(), '/');
-            $routeNames = array_map(function (string $route) use ($site) {
-                return ltrim(trim($site->getBase()->getPath(), '/') . '/' . ltrim($route, '/'), '/');
-            }, array_column($configuration, 'route'));
-            if (in_array($path, $routeNames, true)) {
-                $key = array_search($path, $routeNames, true);
-                $routeConfig = $configuration[$key];
+            $routeConfig = $this->getApplicableStaticRoute($configuration, $site, $path);
+            if (is_array($routeConfig)) {
                 try {
                     [$content, $contentType] = $this->resolveByType($request, $site, $routeConfig['type'], $routeConfig);
                 } catch (InvalidRouteArgumentsException $e) {
@@ -84,6 +80,37 @@ class StaticRouteResolver implements MiddlewareInterface
     }
 
     /**
+     * Find the proper configuration for the static route in the static route configuration. Mainly:
+     * - needs to have a valid "route" property
+     * - needs to have a "type"
+     *
+     * @param array $staticRouteConfiguration the "routes" part of the site configuration
+     * @param Site $site the current site where the configuration is based on
+     * @param string $uriPath the path of the current request - used to match the "route" value of a single static route
+     * @return array|null the configuration for the static route that matches, or null if no route is given
+     */
+    protected function getApplicableStaticRoute(array $staticRouteConfiguration, Site $site, string $uriPath): ?array
+    {
+        $routeNames = array_map(function (?string $route) use ($site) {
+            if ($route === null || $route === '') {
+                return null;
+            }
+            return ltrim(trim($site->getBase()->getPath(), '/') . '/' . ltrim($route, '/'), '/');
+        }, array_column($staticRouteConfiguration, 'route'));
+        // Remove empty routes which would throw an error (could happen within creating a false route in the GUI)
+        $routeNames = array_filter($routeNames);
+
+        if (in_array($uriPath, $routeNames, true)) {
+            $key = array_search($uriPath, $routeNames, true);
+            // Only allow routes with a type "given"
+            if (isset($staticRouteConfiguration[$key]['type'])) {
+                return $staticRouteConfiguration[$key];
+            }
+        }
+        return null;
+    }
+
+    /**
      * @param File $file
      * @return array
      */
diff --git a/typo3/sysext/frontend/Tests/Unit/Middleware/StaticRouteResolverTest.php b/typo3/sysext/frontend/Tests/Unit/Middleware/StaticRouteResolverTest.php
new file mode 100644 (file)
index 0000000..871507c
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Frontend\Tests\Unit\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\RequestHandlerInterface;
+use TYPO3\CMS\Core\Http\HtmlResponse;
+use TYPO3\CMS\Core\Http\NullResponse;
+use TYPO3\CMS\Core\Http\RequestFactory;
+use TYPO3\CMS\Core\Http\ServerRequest;
+use TYPO3\CMS\Core\LinkHandling\LinkService;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Frontend\Middleware\StaticRouteResolver;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class StaticRouteResolverTest extends UnitTestCase
+{
+    protected $requestHandler;
+
+    /**
+     * Set up
+     */
+    protected function setUp(): void
+    {
+        parent::setUp();
+        // A request handler which expects a site to be found.
+        $this->requestHandler = new class implements RequestHandlerInterface {
+            public function handle(ServerRequestInterface $request): ResponseInterface
+            {
+                return new NullResponse();
+            }
+        };
+    }
+
+    /**
+     * @test
+     */
+    public function invalidStaticRouteDoesNotWork()
+    {
+        $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
+        $linkServiceProphecy = $this->prophesize(LinkService::class);
+        $subject = new StaticRouteResolver(
+            $requestFactoryProphecy->reveal(),
+            $linkServiceProphecy->reveal()
+        );
+        $site = new Site('lotus-flower', 13, [
+            'base' => 'https://example.com/',
+            'languages' => [
+                0 => [
+                    'languageId' => 0,
+                    'locale' => 'en_US.UTF-8',
+                    'base' => '/en/'
+                ],
+            ],
+            'routes' => [
+                [
+                    'route' => '/lotus/',
+                    'type' => 'staticText',
+                    'content' => 'nice'
+                ],
+                [
+                    'route' => null,
+                    'type' => 'staticText',
+                    'content' => 'no-route'
+                ],
+                [
+                    'route' => '',
+                    'type' => 'staticText',
+                    'content' => 'empty-route'
+                ],
+                [
+                    'route' => '/empty-type',
+                    'type' => ''
+                ],
+                [
+                    'route' => '/no-type'
+                ],
+                [
+                    'route' => '',
+                    'type' => ''
+                ]
+            ]
+        ]);
+
+        $request = new ServerRequest('https://example.com/lotus/');
+        $request = $request->withAttribute('site', $site);
+        $response = $subject->process($request, $this->requestHandler);
+        self::assertInstanceOf(HtmlResponse::class, $response);
+        self::assertEquals('nice', $response->getBody()->getContents());
+
+        $request = new ServerRequest('https://example.com/nothing');
+        $request = $request->withAttribute('site', $site);
+        $response = $subject->process($request, $this->requestHandler);
+        self::assertInstanceOf(NullResponse::class, $response);
+
+        $request = new ServerRequest('https://example.com/no-type');
+        $request = $request->withAttribute('site', $site);
+        $response = $subject->process($request, $this->requestHandler);
+        self::assertInstanceOf(NullResponse::class, $response);
+    }
+}