diff --git a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php index f08601d80d9d881e190e5fdcfe81a40478110390..01bfa597e6e72f3238ad57d1e7f6f558a5fb79c0 100644 --- a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php +++ b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php @@ -775,8 +775,8 @@ class PageLinkBuilder extends AbstractTypolinkBuilder $currentQueryArray = array_replace_recursive($pageArguments->getQueryArguments(), $currentQueryArray); } if ($configuration['exclude'] ?? false) { - $excludeString = str_replace(',', '&', $configuration['exclude']); - $excludedQueryParts = []; + $excludeItems = array_map(urlencode(...), GeneralUtility::trimExplode(',', $configuration['exclude'])); + $excludeString = implode('&', $excludeItems); parse_str($excludeString, $excludedQueryParts); $newQueryArray = ArrayUtility::arrayDiffKeyRecursive($currentQueryArray, $excludedQueryParts); } else { diff --git a/typo3/sysext/frontend/Tests/Unit/Typolink/PageLinkBuilderTest.php b/typo3/sysext/frontend/Tests/Unit/Typolink/PageLinkBuilderTest.php index fe479d3409bf6723e324dbe7c07040db8e50f8d0..0804e821e8c18654bddb3c2c03c6b5e1fdbd36f8 100644 --- a/typo3/sysext/frontend/Tests/Unit/Typolink/PageLinkBuilderTest.php +++ b/typo3/sysext/frontend/Tests/Unit/Typolink/PageLinkBuilderTest.php @@ -25,45 +25,51 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase; final class PageLinkBuilderTest extends UnitTestCase { + public static function getQueryArgumentsExcludesParametersDataProvider(): \Generator + { + $enc = self::rawUrlEncodeSquareBracketsInUrl(...); + yield 'nested exclude from untrusted args' => [ + $enc('&key1=value1&key2=value2&key3[key31]=value31&key3[key32][key321]=value321&key3[key32][key322]=value322'), + 'untrusted', + [ + 'exclude' => implode(',', ['key1', 'key3[key31]', 'key3[key32][key321]']), + ], + $enc('&key2=value2&key3[key32][key322]=value322'), + ]; + yield 'URL encoded value' => [ + '¶m=1¶m%25=2¶m%2525=3', + 'untrusted', + [ + // internally: URL-decoded representation + 'exclude' => 'param,param%,param%25', + ], + '', + ]; + } + /** * @test + * @dataProvider getQueryArgumentsExcludesParametersDataProvider */ - public function getQueryArgumentsExcludesParameters(): void + public function getQueryArgumentsExcludesParameters(string $queryString, string $queryInformation, array $configuration, string $expectedResult): void { - $queryParameters = [ - 'key1' => 'value1', - 'key2' => 'value2', - 'key3' => [ - 'key31' => 'value31', - 'key32' => [ - 'key321' => 'value321', - 'key322' => 'value322', - ], - ], - ]; + parse_str($queryString, $queryParameters); $request = new ServerRequest('https://example.com'); $request = $request->withQueryParams($queryParameters); $request = $request->withAttribute('routing', new PageArguments(1, '', $queryParameters, [], [])); - $configuration = []; - $configuration['exclude'] = []; - $configuration['exclude'][] = 'key1'; - $configuration['exclude'][] = 'key3[key31]'; - $configuration['exclude'][] = 'key3[key32][key321]'; - $configuration['exclude'] = implode(',', $configuration['exclude']); - $expectedResult = $this->rawUrlEncodeSquareBracketsInUrl('&key2=value2&key3[key32][key322]=value322'); $GLOBALS['TSFE'] = new \stdClass(); $cObj = new ContentObjectRenderer(); $cObj->setRequest($request); $subject = $this->getAccessibleMock(PageLinkBuilder::class, null, [], '', false); $subject->_set('contentObjectRenderer', $cObj); - $actualResult = $subject->_call('getQueryArguments', 'untrusted', $configuration); + $actualResult = $subject->_call('getQueryArguments', $queryInformation, $configuration); self::assertEquals($expectedResult, $actualResult); } /** - * Encodes square brackets in URL. + * Encodes square brackets in URL for a better readability in these tests. */ - private function rawUrlEncodeSquareBracketsInUrl(string $string): string + private static function rawUrlEncodeSquareBracketsInUrl(string $string): string { return str_replace(['[', ']'], ['%5B', '%5D'], $string); }