[TASK] Streamline Page Argument merge strategies
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Middleware / PageArgumentValidator.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Frontend\Middleware;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use Psr\Http\Message\ResponseInterface;
20 use Psr\Http\Message\ServerRequestInterface;
21 use Psr\Http\Server\MiddlewareInterface;
22 use Psr\Http\Server\RequestHandlerInterface;
23 use TYPO3\CMS\Core\Routing\PageArguments;
24 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Frontend\Controller\ErrorController;
27 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
28 use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
29 use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
30
31 /**
32 * This middleware validates given request parameters against the common "cHash" functionality.
33 */
34 class PageArgumentValidator implements MiddlewareInterface
35 {
36
37 /**
38 * The cHash Service class used for cHash related functionality
39 *
40 * @var CacheHashCalculator
41 */
42 protected $cacheHashCalculator;
43
44 /**
45 * @var TypoScriptFrontendController
46 */
47 protected $controller;
48
49 /**
50 * @param TypoScriptFrontendController|null $controller
51 */
52 public function __construct(TypoScriptFrontendController $controller = null)
53 {
54 $this->controller = $controller ?? $GLOBALS['TSFE'];
55 $this->cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
56 }
57
58 /**
59 * Validates the &cHash parameter against the other $queryParameters / GET parameters
60 *
61 * @param ServerRequestInterface $request
62 * @param RequestHandlerInterface $handler
63 * @return ResponseInterface
64 */
65 public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
66 {
67 $pageNotFoundOnValidationError = (bool)($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError'] ?? true);
68 $pageArguments = $request->getAttribute('routing', null);
69 if ($this->controller->no_cache && !$pageNotFoundOnValidationError) {
70 // No need to test anything if caching was already disabled.
71 } else {
72 // Evaluate the cache hash parameter or dynamic arguments when coming from a Site-based routing
73 if ($pageArguments instanceof PageArguments) {
74 $queryParams = $pageArguments->getDynamicArguments();
75 } else {
76 $queryParams = $request->getQueryParams();
77 }
78 if (!empty($queryParams) && !$this->evaluateCacheHashParameter($queryParams, $pageNotFoundOnValidationError)) {
79 return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
80 $request,
81 'Request parameters could not be validated (&cHash comparison failed)',
82 ['code' => PageAccessFailureReasons::CACHEHASH_COMPARISON_FAILED]
83 );
84 }
85 }
86 return $handler->handle($request);
87 }
88
89 /**
90 * Calculates a hash string based on additional parameters in the url.
91 *
92 * Calculated hash is stored in $this->controller->cHash_array.
93 * This is used to cache pages with more parameters than just id and type.
94 *
95 * @see TypoScriptFrontendController::reqCHash()
96 * @param array $queryParams GET parameters
97 * @param bool $pageNotFoundOnCacheHashError see $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']
98 * @return bool if false, then a PageNotFound response is triggered
99 */
100 protected function evaluateCacheHashParameter(array $queryParams, bool $pageNotFoundOnCacheHashError): bool
101 {
102 if ($this->controller->cHash) {
103 // Make sure we use the page uid and not the page alias
104 $queryParams['id'] = $this->controller->id;
105 $this->controller->cHash_array = $this->cacheHashCalculator->getRelevantParameters(GeneralUtility::implodeArrayForUrl('', $queryParams));
106 $cHash_calc = $this->cacheHashCalculator->calculateCacheHash($this->controller->cHash_array);
107 if (!hash_equals($cHash_calc, $this->controller->cHash)) {
108 // Early return to trigger the error controller
109 if ($pageNotFoundOnCacheHashError) {
110 return false;
111 }
112 $this->controller->no_cache = true;
113 $this->getTimeTracker()->setTSlogMessage('The incoming cHash "' . $this->controller->cHash . '" and calculated cHash "' . $cHash_calc . '" did not match, so caching was disabled. The fieldlist used was "' . implode(',', array_keys($this->controller->cHash_array)) . '"', 2);
114 }
115 // No cHash is set, check if that is correct
116 } elseif ($this->cacheHashCalculator->doParametersRequireCacheHash(GeneralUtility::implodeArrayForUrl('', $queryParams))) {
117 // Will disable caching
118 $this->controller->reqCHash();
119 }
120 return true;
121 }
122
123 /**
124 * @return TimeTracker
125 */
126 protected function getTimeTracker(): TimeTracker
127 {
128 return GeneralUtility::makeInstance(TimeTracker::class);
129 }
130 }