‪TYPO3CMS  ‪main
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;
34 
38 class ‪PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
39 {
40  use LoggerAwareTrait;
41 
42  public function ‪__construct(
43  private readonly ‪CacheHashCalculator $cacheHashCalculator,
44  ) {}
45 
49  public function ‪process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
50  {
51  $cacheInstruction = $request->getAttribute('frontend.cache.instruction', new ‪CacheInstruction());
52  $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
53  $pageNotFoundOnValidationError = (bool)(‪$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError'] ?? true);
54  $pageArguments = $request->getAttribute('routing');
55  if (!($pageArguments instanceof ‪PageArguments)) {
56  // Page Arguments must be set in order to validate. This middleware only works if PageArguments
57  // is available, and is usually combined with the Page Resolver middleware
58  return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
59  $request,
60  'Page Arguments could not be resolved',
62  );
63  }
64  if (!(‪$GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ?? true)
65  && ($pageArguments->getArguments()['no_cache'] ?? $request->getParsedBody()['no_cache'] ?? false)
66  ) {
67  $cacheInstruction->disableCache('EXT:frontend: Caching disabled by no_cache query argument.');
68  }
69  if (!$cacheInstruction->isCachingAllowed() && !$pageNotFoundOnValidationError) {
70  // No need to test anything if caching was already disabled.
71  return $handler->handle($request);
72  }
73  // Evaluate the cache hash parameter or dynamic arguments when coming from a Site-based routing
74  $cHash = (string)($pageArguments->getArguments()['cHash'] ?? '');
75  ‪$queryParams = $pageArguments->getDynamicArguments();
76  if ($cHash !== '' || !empty(‪$queryParams)) {
77  $relevantParametersForCacheHashArgument = $this->getRelevantParametersForCacheHashCalculation($pageArguments);
78  if ($cHash !== '') {
79  if (empty($relevantParametersForCacheHashArgument)) {
80  // cHash was given, but nothing to be calculated, so let's do a redirect to the current page but without the cHash
81  $this->logger->notice('The incoming cHash "{hash}" is given but not needed. cHash is unset', ['hash' => $cHash]);
82  $uri = $request->getUri();
83  unset(‪$queryParams['cHash']);
84  $uri = $uri->withQuery(‪HttpUtility::buildQueryString(‪$queryParams));
85  return new ‪RedirectResponse($uri, 308);
86  }
87  if (!$this->‪evaluateCacheHashParameter($cacheInstruction, $cHash, $relevantParametersForCacheHashArgument, $pageNotFoundOnValidationError)) {
88  return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
89  $request,
90  'Request parameters could not be validated (&cHash comparison failed)',
92  );
93  }
94  // No cHash given but was required
95  } elseif (!$this->‪evaluatePageArgumentsWithoutCacheHash($cacheInstruction, $pageArguments, $pageNotFoundOnValidationError)) {
96  return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
97  $request,
98  'Request parameters could not be validated (&cHash empty)',
100  );
101  }
102  }
103 
104  return $handler->handle($request);
105  }
106 
112  protected function getRelevantParametersForCacheHashCalculation(‪PageArguments $pageArguments): array
113  {
114  ‪$queryParams = $pageArguments->getDynamicArguments();
115  ‪$queryParams['id'] = $pageArguments->‪getPageId();
116  return $this->cacheHashCalculator->getRelevantParameters(‪HttpUtility::buildQueryString(‪$queryParams));
117  }
118 
128  protected function ‪evaluateCacheHashParameter(‪CacheInstruction $cacheInstruction, string $cHash, array $relevantParameters, bool $pageNotFoundOnCacheHashError): bool
129  {
130  $calculatedCacheHash = $this->cacheHashCalculator->calculateCacheHash($relevantParameters);
131  if (hash_equals($calculatedCacheHash, $cHash)) {
132  return true;
133  }
134  // Early return to trigger the error controller
135  if ($pageNotFoundOnCacheHashError) {
136  return false;
137  }
138  // Caching is disabled now (but no 404)
139  $cacheInstruction->‪disableCache('EXT:frontend: Incoming cHash "' . $cHash . '" and calculated cHash "' . $calculatedCacheHash . '" did not match.' .
140  ' The field list used was "' . implode(',', array_keys($relevantParameters)) . '". Caching is disabled.');
141  return true;
142  }
143 
151  protected function ‪evaluateQueryParametersWithoutCacheHash(‪CacheInstruction $cacheInstruction, array $dynamicArguments, bool $pageNotFoundOnCacheHashError): bool
152  {
153  if (!$this->cacheHashCalculator->doParametersRequireCacheHash(‪HttpUtility::buildQueryString($dynamicArguments))) {
154  return true;
155  }
156  // cHash is required, but not given, so trigger a 404
157  if ($pageNotFoundOnCacheHashError) {
158  return false;
159  }
160  // Caching is disabled now (but no 404)
161  $cacheInstruction->‪disableCache('EXT:frontend: No cHash query argument was sent for GET vars though required. Caching is disabled.');
162  return true;
163  }
164 
171  protected function ‪evaluatePageArgumentsWithoutCacheHash(‪CacheInstruction $cacheInstruction, ‪PageArguments $pageArguments, bool $pageNotFoundOnCacheHashError): bool
172  {
173  // legacy behaviour
174  if (!(‪$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['enforceValidation'] ?? false)) {
175  return $this->‪evaluateQueryParametersWithoutCacheHash($cacheInstruction, $pageArguments->getDynamicArguments(), $pageNotFoundOnCacheHashError);
176  }
177  $relevantParameters = $this->getRelevantParametersForCacheHashCalculation($pageArguments);
178  // There are parameters that would be needed for the current page, but no cHash is given.
179  // Thus, a "page not found" error is thrown - as configured via "pageNotFoundOnCHashError".
180  if (!empty($relevantParameters) && $pageNotFoundOnCacheHashError) {
181  return false;
182  }
183  // There are no parameters that require a cHash.
184  // We end up here when the site was called with an `id` param, e.g. https://example.org/index?id=123.
185  // Avoid disabling caches in this case.
186  if (empty($relevantParameters)) {
187  return true;
188  }
189  // Caching is disabled now (but no 404)
190  $cacheInstruction->‪disableCache('EXT:frontend: No cHash query argument was sent for given query parameters. Caching is disabled');
191  return true;
192  }
193 }
‪TYPO3\CMS\Core\Routing\PageArguments
Definition: PageArguments.php:26
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\__construct
‪__construct(private readonly CacheHashCalculator $cacheHashCalculator,)
Definition: PageArgumentValidator.php:42
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\evaluatePageArgumentsWithoutCacheHash
‪evaluatePageArgumentsWithoutCacheHash(CacheInstruction $cacheInstruction, PageArguments $pageArguments, bool $pageNotFoundOnCacheHashError)
Definition: PageArgumentValidator.php:171
‪TYPO3\CMS\Frontend\Page\PageAccessFailureReasons\INVALID_PAGE_ARGUMENTS
‪const INVALID_PAGE_ARGUMENTS
Definition: PageAccessFailureReasons.php:37
‪TYPO3\CMS\Core\Routing\PageArguments\getPageId
‪getPageId()
Definition: PageArguments.php:95
‪TYPO3\CMS\Frontend\Page\PageAccessFailureReasons\CACHEHASH_EMPTY
‪const CACHEHASH_EMPTY
Definition: PageAccessFailureReasons.php:39
‪TYPO3\CMS\Frontend\Cache\CacheInstruction\disableCache
‪disableCache(string $reason)
Definition: CacheInstruction.php:47
‪TYPO3\CMS\Frontend\Controller\ErrorController
Definition: ErrorController.php:38
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\evaluateQueryParametersWithoutCacheHash
‪evaluateQueryParametersWithoutCacheHash(CacheInstruction $cacheInstruction, array $dynamicArguments, bool $pageNotFoundOnCacheHashError)
Definition: PageArgumentValidator.php:151
‪TYPO3\CMS\Frontend\Middleware
Definition: BackendUserAuthenticator.php:18
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator
Definition: PageArgumentValidator.php:39
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\evaluateCacheHashParameter
‪bool evaluateCacheHashParameter(CacheInstruction $cacheInstruction, string $cHash, array $relevantParameters, bool $pageNotFoundOnCacheHashError)
Definition: PageArgumentValidator.php:128
‪TYPO3\CMS\Core\Utility\HttpUtility\buildQueryString
‪static string buildQueryString(array $parameters, string $prependCharacter='', bool $skipEmptyParameters=false)
Definition: HttpUtility.php:124
‪TYPO3\CMS\Core\Http\RedirectResponse
Definition: RedirectResponse.php:30
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\process
‪process(ServerRequestInterface $request, RequestHandlerInterface $handler)
Definition: PageArgumentValidator.php:49
‪TYPO3\CMS\Frontend\Page\PageAccessFailureReasons\CACHEHASH_COMPARISON_FAILED
‪const CACHEHASH_COMPARISON_FAILED
Definition: PageAccessFailureReasons.php:38
‪TYPO3\CMS\Frontend\Page\CacheHashCalculator
Definition: CacheHashCalculator.php:25
‪TYPO3\CMS\Frontend\Cache\CacheInstruction
Definition: CacheInstruction.php:29
‪TYPO3\CMS\Core\Utility\HttpUtility
Definition: HttpUtility.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Frontend\Page\PageAccessFailureReasons
Definition: PageAccessFailureReasons.php:25
‪TYPO3\CMS\Frontend\Middleware\PageArgumentValidator\$queryParams
‪$queryParams['id']
Definition: PageArgumentValidator.php:115