‪TYPO3CMS  11.5
PageArgumentValidator.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ResponseInterface;
21 use Psr\Http\Message\ServerRequestInterface;
22 use Psr\Http\Server\MiddlewareInterface;
23 use Psr\Http\Server\RequestHandlerInterface;
24 use Psr\Log\LoggerAwareInterface;
25 use Psr\Log\LoggerAwareTrait;
26 use Psr\Log\LogLevel;
35 
39 class ‪PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
40 {
41  use LoggerAwareTrait;
42 
48  protected ‪$cacheHashCalculator;
49 
53  protected ‪$timeTracker;
54 
58  protected ‪$disableCache = false;
59 
60  public function ‪__construct(
63  ) {
64  $this->cacheHashCalculator = ‪$cacheHashCalculator;
65  $this->timeTracker = ‪$timeTracker;
66  }
67 
75  public function ‪process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
76  {
77  $this->disableCache = (bool)$request->getAttribute('noCache', false);
78  $pageNotFoundOnValidationError = (bool)(‪$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError'] ?? true);
80  $pageArguments = $request->getAttribute('routing', null);
81  if (!($pageArguments instanceof ‪PageArguments)) {
82  // Page Arguments must be set in order to validate. This middleware only works if PageArguments
83  // is available, and is usually combined with the Page Resolver middleware
84  return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
85  $request,
86  'Page Arguments could not be resolved',
88  );
89  }
90  if (‪$GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ?? true) {
91  $cachingDisabledByRequest = false;
92  } else {
93  $cachingDisabledByRequest = $pageArguments->getArguments()['no_cache'] ?? $request->getParsedBody()['no_cache'] ?? false;
94  }
95  if (($cachingDisabledByRequest || $this->disableCache) && !$pageNotFoundOnValidationError) {
96  // No need to test anything if caching was already disabled.
97  return $handler->handle($request);
98  }
99  // Evaluate the cache hash parameter or dynamic arguments when coming from a Site-based routing
100  $cHash = (string)($pageArguments->getArguments()['cHash'] ?? '');
101  ‪$queryParams = $pageArguments->getDynamicArguments();
102  if ($cHash !== '' || !empty(‪$queryParams)) {
103  $relevantParametersForCacheHashArgument = $this->getRelevantParametersForCacheHashCalculation($pageArguments);
104  if ($cHash !== '') {
105  if (empty($relevantParametersForCacheHashArgument)) {
106  // cHash was given, but nothing to be calculated, so let's do a redirect to the current page
107  // but without the cHash
108  $this->logger->notice('The incoming cHash "{hash}" is given but not needed. cHash is unset', ['hash' => $cHash]);
109  $uri = $request->getUri();
110  unset(‪$queryParams['cHash']);
111  $uri = $uri->withQuery(‪HttpUtility::buildQueryString(‪$queryParams));
112  return new ‪RedirectResponse($uri, 308);
113  }
114  if (!$this->‪evaluateCacheHashParameter($cHash, $relevantParametersForCacheHashArgument, $pageNotFoundOnValidationError)) {
115  return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
116  $request,
117  'Request parameters could not be validated (&cHash comparison failed)',
119  );
120  }
121  // No cHash given but was required
122  } elseif (!$this->‪evaluatePageArgumentsWithoutCacheHash($pageArguments, $pageNotFoundOnValidationError)) {
123  return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
124  $request,
125  'Request parameters could not be validated (&cHash empty)',
127  );
128  }
129  }
130 
131  $request = $request->withAttribute('noCache', $this->disableCache);
132  return $handler->handle($request);
133  }
134 
141  protected function getRelevantParametersForCacheHashCalculation(‪PageArguments $pageArguments): array
142  {
143  ‪$queryParams = $pageArguments->getDynamicArguments();
144  ‪$queryParams['id'] = $pageArguments->‪getPageId();
145  return $this->cacheHashCalculator->getRelevantParameters(‪HttpUtility::buildQueryString(‪$queryParams));
146  }
147 
157  protected function ‪evaluateCacheHashParameter(string $cHash, array $relevantParameters, bool $pageNotFoundOnCacheHashError): bool
158  {
159  $calculatedCacheHash = $this->cacheHashCalculator->calculateCacheHash($relevantParameters);
160  if (hash_equals($calculatedCacheHash, $cHash)) {
161  return true;
162  }
163  // Early return to trigger the error controller
164  if ($pageNotFoundOnCacheHashError) {
165  return false;
166  }
167  // Caching is disabled now (but no 404)
168  $this->disableCache = true;
169  $this->timeTracker->setTSlogMessage('The incoming cHash "' . $cHash . '" and calculated cHash "' . $calculatedCacheHash . '" did not match, so caching was disabled. The fieldlist used was "' . implode(',', array_keys($relevantParameters)) . '"', LogLevel::ERROR);
170  return true;
171  }
172 
182  protected function ‪evaluateQueryParametersWithoutCacheHash(array $dynamicArguments, bool $pageNotFoundOnCacheHashError): bool
183  {
184  if (!$this->cacheHashCalculator->doParametersRequireCacheHash(‪HttpUtility::buildQueryString($dynamicArguments))) {
185  return true;
186  }
187  // cHash is required, but not given, so trigger a 404
188  if ($pageNotFoundOnCacheHashError) {
189  return false;
190  }
191  // Caching is disabled now (but no 404)
192  $this->disableCache = true;
193  $this->timeTracker->setTSlogMessage('TSFE->reqCHash(): No &cHash parameter was sent for GET vars though required so caching is disabled', LogLevel::ERROR);
194  return true;
195  }
196 
203  protected function ‪evaluatePageArgumentsWithoutCacheHash(PageArguments $pageArguments, bool $pageNotFoundOnCacheHashError): bool
204  {
205  // legacy behaviour
206  if (!(‪$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['enforceValidation'] ?? false)) {
207  return $this->‪evaluateQueryParametersWithoutCacheHash($pageArguments->getDynamicArguments(), $pageNotFoundOnCacheHashError);
208  }
209  $relevantParameters = $this->getRelevantParametersForCacheHashCalculation($pageArguments);
210  // There are parameters that would be needed for the current page, but no cHash is given.
211  // Thus, a "page not found" error is thrown - as configured via "pageNotFoundOnCHashError".
212  if (!empty($relevantParameters) && $pageNotFoundOnCacheHashError) {
213  return false;
214  }
215  // There are no parameters that require a cHash.
216  // We end up here when the site was called with an `id` param, e.g. https://example.org/index?id=123.
217  // Avoid disabling caches in this case.
218  if (empty($relevantParameters)) {
219  return true;
220  }
221  // Caching is disabled now (but no 404)
222  $this->disableCache = true;
223  $this->timeTracker->setTSlogMessage('No &cHash parameter was sent for given query parameters, so caching is disabled', LogLevel::ERROR);
224  return true;
225  }
226 }
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\evaluateQueryParametersWithoutCacheHash
‪bool evaluateQueryParametersWithoutCacheHash(array $dynamicArguments, bool $pageNotFoundOnCacheHashError)
Definition: PageArgumentValidator.php:179
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\__construct
‪__construct(CacheHashCalculator $cacheHashCalculator, TimeTracker $timeTracker)
Definition: PageArgumentValidator.php:57
‪TYPO3\CMS\Core\Routing\PageArguments
Definition: PageArguments.php:26
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\$cacheHashCalculator
‪CacheHashCalculator $cacheHashCalculator
Definition: PageArgumentValidator.php:47
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\evaluatePageArgumentsWithoutCacheHash
‪evaluatePageArgumentsWithoutCacheHash(PageArguments $pageArguments, bool $pageNotFoundOnCacheHashError)
Definition: PageArgumentValidator.php:200
‪TYPO3\CMS\Frontend\Page\PageAccessFailureReasons\INVALID_PAGE_ARGUMENTS
‪const INVALID_PAGE_ARGUMENTS
Definition: PageAccessFailureReasons.php:36
‪TYPO3\CMS\Frontend\Page\PageAccessFailureReasons\CACHEHASH_EMPTY
‪const CACHEHASH_EMPTY
Definition: PageAccessFailureReasons.php:38
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\process
‪ResponseInterface process(ServerRequestInterface $request, RequestHandlerInterface $handler)
Definition: PageArgumentValidator.php:72
‪TYPO3\CMS\Frontend\Controller\ErrorController
Definition: ErrorController.php:39
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\evaluateCacheHashParameter
‪bool evaluateCacheHashParameter(string $cHash, array $relevantParameters, bool $pageNotFoundOnCacheHashError)
Definition: PageArgumentValidator.php:154
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\$disableCache
‪bool $disableCache
Definition: PageArgumentValidator.php:55
‪TYPO3\CMS\Frontend\Middleware
Definition: BackendUserAuthenticator.php:18
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator
Definition: PageArgumentValidator.php:40
‪TYPO3\CMS\Core\Utility\HttpUtility\buildQueryString
‪static string buildQueryString(array $parameters, string $prependCharacter='', bool $skipEmptyParameters=false)
Definition: HttpUtility.php:171
‪TYPO3\CMS\Core\Http\RedirectResponse
Definition: RedirectResponse.php:28
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Frontend\Page\PageAccessFailureReasons\CACHEHASH_COMPARISON_FAILED
‪const CACHEHASH_COMPARISON_FAILED
Definition: PageAccessFailureReasons.php:37
‪TYPO3\CMS\Frontend\Page\CacheHashCalculator
Definition: CacheHashCalculator.php:25
‪TYPO3\CMS\Core\Utility\HttpUtility
Definition: HttpUtility.php:22
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\$timeTracker
‪TimeTracker $timeTracker
Definition: PageArgumentValidator.php:51
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\TimeTracker\TimeTracker
Definition: TimeTracker.php:31
‪TYPO3\CMS\Core\Routing\PageArguments\getPageId
‪int getPageId()
Definition: PageArguments.php:111
‪TYPO3\CMS\Frontend\Page\PageAccessFailureReasons
Definition: PageAccessFailureReasons.php:25
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\$queryParams
‪$queryParams['id']
Definition: PageArgumentValidator.php:141