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