[TASK] Rename PageParameterValidator middleware
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Page / CacheHashCalculator.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Page;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\SingletonInterface;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Logic for cHash calculation
22 */
23 class CacheHashCalculator implements SingletonInterface
24 {
25 /**
26 * @var array Parameters that are relevant for cacheHash calculation. Optional.
27 */
28 protected $cachedParametersWhiteList = [];
29
30 /**
31 * @var array Parameters that are not relevant for cacheHash calculation.
32 */
33 protected $excludedParameters = [];
34
35 /**
36 * @var array Parameters that forces a presence of a valid cacheHash.
37 */
38 protected $requireCacheHashPresenceParameters = [];
39
40 /**
41 * @var array Parameters that need a value to be relevant for cacheHash calculation
42 */
43 protected $excludedParametersIfEmpty = [];
44
45 /**
46 * @var bool Whether to exclude all empty parameters for cacheHash calculation
47 */
48 protected $excludeAllEmptyParameters = false;
49
50 /**
51 * Initialise class properties by using the relevant TYPO3 configuration
52 */
53 public function __construct()
54 {
55 $this->setConfiguration($GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash'] ?? []);
56 }
57
58 /**
59 * Calculates the cHash based on the provided parameters
60 *
61 * @param array $params Array of cHash key-value pairs
62 * @return string Hash of all the values
63 */
64 public function calculateCacheHash(array $params)
65 {
66 return !empty($params) ? md5(serialize($params)) : '';
67 }
68
69 /**
70 * Returns the cHash based on provided query parameters and added values from internal call
71 *
72 * @param string $queryString Query-parameters: "&xxx=yyy&zzz=uuu
73 * @return string Hash of all the values
74 * @throws \RuntimeException
75 */
76 public function generateForParameters($queryString)
77 {
78 $cacheHashParams = $this->getRelevantParameters($queryString);
79 return $this->calculateCacheHash($cacheHashParams);
80 }
81
82 /**
83 * Checks whether a parameter of the given $queryString requires cHash calculation
84 *
85 * @param string $queryString
86 * @return bool
87 */
88 public function doParametersRequireCacheHash($queryString)
89 {
90 if (empty($this->requireCacheHashPresenceParameters)) {
91 return false;
92 }
93 $hasRequiredParameter = false;
94 $parameterNames = array_keys($this->splitQueryStringToArray($queryString));
95 foreach ($parameterNames as $parameterName) {
96 if (in_array($parameterName, $this->requireCacheHashPresenceParameters, true)) {
97 $hasRequiredParameter = true;
98 break;
99 }
100 }
101 return $hasRequiredParameter;
102 }
103
104 /**
105 * Splits the input query-parameters into an array with certain parameters filtered out.
106 * Used to create the cHash value
107 *
108 * @param string $queryString Query-parameters: "&xxx=yyy&zzz=uuu
109 * @return array Array with key/value pairs of query-parameters WITHOUT a certain list of
110 * @throws \RuntimeException
111 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typoLink()
112 */
113 public function getRelevantParameters($queryString)
114 {
115 $parameters = $this->splitQueryStringToArray($queryString);
116 $relevantParameters = [];
117 foreach ($parameters as $parameterName => $parameterValue) {
118 if ($this->isAdminPanelParameter($parameterName) || $this->isExcludedParameter($parameterName) || $this->isCoreParameter($parameterName)) {
119 continue;
120 }
121 if ($this->hasCachedParametersWhiteList() && !$this->isInCachedParametersWhiteList($parameterName)) {
122 continue;
123 }
124 if (($parameterValue === null || $parameterValue === '') && !$this->isAllowedWithEmptyValue($parameterName)) {
125 continue;
126 }
127 $relevantParameters[$parameterName] = $parameterValue;
128 }
129 if (!empty($relevantParameters)) {
130 if (empty($parameters['id'])) {
131 throw new \RuntimeException('ID parameter needs to be passed for the cHash calculation!', 1467983513);
132 }
133 $relevantParameters['id'] = $parameters['id'];
134 // Finish and sort parameters array by keys:
135 $relevantParameters['encryptionKey'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'];
136 ksort($relevantParameters);
137 }
138 return $relevantParameters;
139 }
140
141 /**
142 * Parses the query string and converts it to an array.
143 * Unlike parse_str it only creates an array with one level.
144 *
145 * e.g. foo[bar]=baz will be array('foo[bar]' => 'baz')
146 *
147 * @param string $queryString
148 * @return array
149 */
150 protected function splitQueryStringToArray($queryString)
151 {
152 $parameters = array_filter(explode('&', ltrim($queryString, '?')));
153 $parameterArray = [];
154 foreach ($parameters as $parameter) {
155 // should not remove empty values with trimExplode, otherwise cases like &=value, value is used as parameterName.
156 $parts = GeneralUtility::trimExplode('=', $parameter, false);
157 $parameterName = $parts[0];
158 $parameterValue = $parts[1] ?? '';
159 if (trim($parameterName) === '') {
160 // This parameter cannot appear in $_GET in PHP even if its value is not empty, so it should be ignored!
161 continue;
162 }
163 $parameterArray[rawurldecode($parameterName)] = rawurldecode($parameterValue);
164 }
165 return $parameterArray;
166 }
167
168 /**
169 * Checks whether the given parameter is out of a known data-set starting
170 * with ADMCMD or starts with TSFE_ADMIN_PANEL.
171 *
172 * @param string $key
173 * @return bool
174 */
175 protected function isAdminPanelParameter($key)
176 {
177 return $key === 'ADMCMD_noBeUser' || $key === 'ADMCMD_view' || $key === 'ADMCMD_editIcons'
178 || $key === 'ADMCMD_simUser' || $key === 'ADMCMD_simTime'
179 || stripos($key, 'TSFE_ADMIN_PANEL') !== false && preg_match('/TSFE_ADMIN_PANEL\\[.*?\\]/', $key);
180 }
181
182 /**
183 * Checks whether the given parameter is a core parameter
184 *
185 * @param string $key
186 * @return bool
187 */
188 protected function isCoreParameter($key)
189 {
190 return $key === 'id' || $key === 'type' || $key === 'no_cache' || $key === 'cHash' || $key === 'MP' || $key === 'ftu';
191 }
192
193 /**
194 * Checks whether the given parameter should be excluded from cHash calculation
195 *
196 * @param string $key
197 * @return bool
198 */
199 protected function isExcludedParameter($key)
200 {
201 return in_array($key, $this->excludedParameters, true);
202 }
203
204 /**
205 * Checks whether the given parameter is an exclusive parameter for cHash calculation
206 *
207 * @param string $key
208 * @return bool
209 */
210 protected function isInCachedParametersWhiteList($key)
211 {
212 return in_array($key, $this->cachedParametersWhiteList, true);
213 }
214
215 /**
216 * Checks whether cachedParametersWhiteList parameters are configured
217 *
218 * @return bool
219 */
220 protected function hasCachedParametersWhiteList()
221 {
222 return !empty($this->cachedParametersWhiteList);
223 }
224
225 /**
226 * Check whether the given parameter may be used even with an empty value
227 *
228 * @param $key
229 * @return bool
230 */
231 protected function isAllowedWithEmptyValue($key)
232 {
233 return !($this->excludeAllEmptyParameters || in_array($key, $this->excludedParametersIfEmpty, true));
234 }
235
236 /**
237 * Loops through the configuration array and calls the accordant
238 * getters with the value.
239 *
240 * @param array $configuration
241 */
242 public function setConfiguration(array $configuration)
243 {
244 foreach ($configuration as $name => $value) {
245 $setterName = 'set' . ucfirst($name);
246 if (method_exists($this, $setterName)) {
247 $this->{$setterName}($value);
248 }
249 }
250 }
251
252 /**
253 * @param array $cachedParametersWhiteList
254 */
255 protected function setCachedParametersWhiteList(array $cachedParametersWhiteList)
256 {
257 $this->cachedParametersWhiteList = $cachedParametersWhiteList;
258 }
259
260 /**
261 * @param bool $excludeAllEmptyParameters
262 */
263 protected function setExcludeAllEmptyParameters($excludeAllEmptyParameters)
264 {
265 $this->excludeAllEmptyParameters = $excludeAllEmptyParameters;
266 }
267
268 /**
269 * @param array $excludedParameters
270 */
271 protected function setExcludedParameters(array $excludedParameters)
272 {
273 $this->excludedParameters = $excludedParameters;
274 }
275
276 /**
277 * @param array $excludedParametersIfEmpty
278 */
279 protected function setExcludedParametersIfEmpty(array $excludedParametersIfEmpty)
280 {
281 $this->excludedParametersIfEmpty = $excludedParametersIfEmpty;
282 }
283
284 /**
285 * @param array $requireCacheHashPresenceParameters
286 */
287 protected function setRequireCacheHashPresenceParameters(array $requireCacheHashPresenceParameters)
288 {
289 $this->requireCacheHashPresenceParameters = $requireCacheHashPresenceParameters;
290 }
291 }