From ff6ad483a2f69d5adb68845b913053e5692ec269 Mon Sep 17 00:00:00 2001 From: Stefan Neufeind <typo3.neufeind@speedpartner.de> Date: Mon, 18 Dec 2017 19:46:47 +0100 Subject: [PATCH] [FEATURE] Improve creation of URL query strings from arrays Adds a new method HttpUtility::buildQueryString() using http_build_query() instead of reimplementing the encoding-process like the old method GeneralUtility::implodeArrayForUrl() did. As the parameter $rawurlencodeParamName of implodeArrayForUrl() was set to "false" by default and used in several places without manually setting it to "true" using that method could lead to potentially unsafe non-encoded parameter names. Some unit-tests had wrong URLs with non-encoded braces [...], which were adapted to be properly escaped as well. Resolves: #83334 Releases: master Change-Id: Ifbaad912f0d658671356dc7bdf1579dacff272df Reviewed-on: https://review.typo3.org/55079 Reviewed-by: Benni Mack <benni@typo3.org> Tested-by: Benni Mack <benni@typo3.org> Tested-by: TYPO3com <no-reply@typo3.com> Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de> --- .../NewContentElementController.php | 3 +- .../Controller/EditDocumentController.php | 11 +-- .../Controller/LinkBrowserController.php | 3 +- .../backend/Classes/Routing/UriBuilder.php | 3 +- .../Classes/Template/DocumentTemplate.php | 8 +- .../Classes/Template/ModuleTemplate.php | 5 +- .../View/ElementBrowserFolderTreeView.php | 15 ++-- .../Tree/View/ElementBrowserPageTreeView.php | 5 +- .../Classes/Utility/BackendUtility.php | 5 +- .../backend/Classes/View/PageLayoutView.php | 5 +- .../core/Classes/Database/QueryView.php | 3 +- .../Classes/TypoScript/TemplateService.php | 3 +- .../core/Classes/Utility/GeneralUtility.php | 11 +-- .../core/Classes/Utility/HttpUtility.php | 32 +++++++ ...ture-83334-AddImprovedBuildQueryString.rst | 23 +++++ .../Tests/Unit/Utility/HttpUtilityTest.php | 86 ++++++++++++++++++- .../Classes/Mvc/Web/Routing/UriBuilder.php | 3 +- .../Unit/Mvc/Web/Routing/UriBuilderTest.php | 4 +- .../Controller/FrontendLoginController.php | 19 ++-- .../FrontendLoginControllerTest.php | 57 ++++++++---- .../ContentObject/ContentObjectRenderer.php | 5 +- .../TypoScriptFrontendController.php | 6 +- .../Middleware/PageArgumentValidator.php | 5 +- .../Classes/Plugin/AbstractPlugin.php | 3 +- .../Classes/Typolink/PageLinkBuilder.php | 6 +- .../TypoScriptFrontendControllerTest.php | 2 +- .../sysext/indexed_search/Classes/Indexer.php | 3 +- .../ViewHelpers/Uri/ActionViewHelper.php | 11 ++- .../Classes/Browser/FileBrowser.php | 3 +- .../AbstractLinkBrowserController.php | 7 +- .../RecordList/AbstractDatabaseRecordList.php | 2 +- .../Classes/RecordList/DatabaseRecordList.php | 5 +- .../Tree/View/ElementBrowserPageTreeView.php | 5 +- .../Tree/View/RecordBrowserPageTreeView.php | 3 +- .../Classes/View/FolderUtilityRenderer.php | 23 ++--- .../Classes/Controller/PreviewController.php | 3 +- .../Classes/Preview/PreviewUriBuilder.php | 3 +- 37 files changed, 291 insertions(+), 108 deletions(-) create mode 100644 typo3/sysext/core/Documentation/Changelog/9.5.x/Feature-83334-AddImprovedBuildQueryString.rst diff --git a/typo3/sysext/backend/Classes/Controller/ContentElement/NewContentElementController.php b/typo3/sysext/backend/Classes/Controller/ContentElement/NewContentElementController.php index 6d7ed6b7a065..896fb9800eee 100644 --- a/typo3/sysext/backend/Classes/Controller/ContentElement/NewContentElementController.php +++ b/typo3/sysext/backend/Classes/Controller/ContentElement/NewContentElementController.php @@ -32,6 +32,7 @@ use TYPO3\CMS\Core\Service\DependencyOrderingService; use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Fluid\View\StandaloneView; /** @@ -601,7 +602,7 @@ class NewContentElementController $tempGetVars['defVals']['tt_content'] ); unset($tempGetVars['defVals']['tt_content']); - $wizardItems[$key]['params'] = GeneralUtility::implodeArrayForUrl('', $tempGetVars); + $wizardItems[$key]['params'] = HttpUtility::buildQueryString($tempGetVars, '&'); } } // If tt_content_defValues are defined...: diff --git a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php index 70cfcd1c8d7d..1efa5196f3d7 100644 --- a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php +++ b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php @@ -925,10 +925,7 @@ class EditDocumentController $this->perms_clause = $beUser->getPagePermsClause(Permission::PAGE_SHOW); // Set other internal variables: $this->R_URL_getvars['returnUrl'] = $this->retUrl; - $this->R_URI = $this->R_URL_parts['path'] . '?' . ltrim(GeneralUtility::implodeArrayForUrl( - '', - $this->R_URL_getvars - ), '&'); + $this->R_URI = $this->R_URL_parts['path'] . HttpUtility::buildQueryString($this->R_URL_getvars, '?'); // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, unused $this->MCONF['name'] = 'xMOD_alt_doc.php'; @@ -1056,14 +1053,14 @@ class EditDocumentController if (!empty($previewConfiguration['useCacheHash'])) { $cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class); - $fullLinkParameters = GeneralUtility::implodeArrayForUrl('', array_merge($linkParameters, ['id' => $previewPageId])); + $fullLinkParameters = HttpUtility::buildQueryString(array_merge($linkParameters, ['id' => $previewPageId]), '&'); $cacheHashParameters = $cacheHashCalculator->getRelevantParameters($fullLinkParameters); $linkParameters['cHash'] = $cacheHashCalculator->calculateCacheHash($cacheHashParameters); } else { $linkParameters['no_cache'] = 1; } - return GeneralUtility::implodeArrayForUrl('', $linkParameters, '', false, true); + return HttpUtility::buildQueryString($linkParameters, '&'); } /** @@ -2595,7 +2592,7 @@ class EditDocumentController 'edit,defVals,overrideVals,columnsOnly,noView,workspace', $this->R_URL_getvars ); - $this->storeUrl = GeneralUtility::implodeArrayForUrl('', $this->storeArray); + $this->storeUrl = HttpUtility::buildQueryString($this->storeArray, '&'); $this->storeUrlMd5 = md5($this->storeUrl); } diff --git a/typo3/sysext/backend/Classes/Controller/LinkBrowserController.php b/typo3/sysext/backend/Classes/Controller/LinkBrowserController.php index 3f6da455e683..fe134247c1e8 100644 --- a/typo3/sysext/backend/Classes/Controller/LinkBrowserController.php +++ b/typo3/sysext/backend/Classes/Controller/LinkBrowserController.php @@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Http\JsonResponse; use TYPO3\CMS\Core\LinkHandling\LinkService; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Frontend\Service\TypoLinkCodecService; use TYPO3\CMS\Recordlist\Controller\AbstractLinkBrowserController; @@ -136,7 +137,7 @@ class LinkBrowserController extends AbstractLinkBrowserController $formEngineParameters['fieldChangeFunc'] = $this->parameters['fieldChangeFunc']; $formEngineParameters['fieldChangeFuncHash'] = GeneralUtility::hmac(serialize($this->parameters['fieldChangeFunc'])); - $parameters['data-add-on-params'] .= GeneralUtility::implodeArrayForUrl('P', $formEngineParameters); + $parameters['data-add-on-params'] .= HttpUtility::buildQueryString(['P' => $formEngineParameters], '&'); return $parameters; } diff --git a/typo3/sysext/backend/Classes/Routing/UriBuilder.php b/typo3/sysext/backend/Classes/Routing/UriBuilder.php index 3cacfce07542..2336459118ce 100644 --- a/typo3/sysext/backend/Classes/Routing/UriBuilder.php +++ b/typo3/sysext/backend/Classes/Routing/UriBuilder.php @@ -20,6 +20,7 @@ use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; use TYPO3\CMS\Core\Http\Uri; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\PathUtility; /** @@ -159,7 +160,7 @@ class UriBuilder implements SingletonInterface */ protected function buildUri($parameters, $referenceType) { - $uri = 'index.php?' . ltrim(GeneralUtility::implodeArrayForUrl('', $parameters, '', false, true), '&'); + $uri = 'index.php' . HttpUtility::buildQueryString($parameters, '?'); if ($referenceType === self::ABSOLUTE_PATH) { $uri = PathUtility::getAbsoluteWebPath(Environment::getBackendPath() . '/' . $uri); } else { diff --git a/typo3/sysext/backend/Classes/Template/DocumentTemplate.php b/typo3/sysext/backend/Classes/Template/DocumentTemplate.php index dfae8a074081..970e8e228559 100644 --- a/typo3/sysext/backend/Classes/Template/DocumentTemplate.php +++ b/typo3/sysext/backend/Classes/Template/DocumentTemplate.php @@ -28,6 +28,7 @@ use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\PathUtility; /** @@ -399,8 +400,11 @@ function jumpToUrl(URL) { public function makeShortcutUrl($gvList, $setList) { $GET = GeneralUtility::_GET(); - $storeArray = array_merge(GeneralUtility::compileSelectedGetVarsFromArray($gvList, $GET), ['SET' => GeneralUtility::compileSelectedGetVarsFromArray($setList, (array)$GLOBALS['SOBE']->MOD_SETTINGS)]); - return GeneralUtility::implodeArrayForUrl('', $storeArray); + $storeArray = array_merge( + GeneralUtility::compileSelectedGetVarsFromArray($gvList, $GET), + ['SET' => GeneralUtility::compileSelectedGetVarsFromArray($setList, (array)$GLOBALS['SOBE']->MOD_SETTINGS)] + ); + return HttpUtility::buildQueryString($storeArray, '&'); } /** diff --git a/typo3/sysext/backend/Classes/Template/ModuleTemplate.php b/typo3/sysext/backend/Classes/Template/ModuleTemplate.php index d1d565245977..6875734317ba 100644 --- a/typo3/sysext/backend/Classes/Template/ModuleTemplate.php +++ b/typo3/sysext/backend/Classes/Template/ModuleTemplate.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Core\Messaging\FlashMessageService; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Fluid\View\Exception\InvalidTemplateResourceException; use TYPO3\CMS\Fluid\View\StandaloneView; @@ -572,7 +573,7 @@ class ModuleTemplate * - SET[] variables a stored in $GLOBALS["SOBE"]->MOD_SETTINGS for backend * modules * - * @return string + * @return string GET-parameters for the shortcut-url only(!). String starts with '&' * @internal */ public function makeShortcutUrl($gvList, $setList) @@ -582,7 +583,7 @@ class ModuleTemplate GeneralUtility::compileSelectedGetVarsFromArray($gvList, $getParams), ['SET' => GeneralUtility::compileSelectedGetVarsFromArray($setList, (array)$GLOBALS['SOBE']->MOD_SETTINGS)] ); - return GeneralUtility::implodeArrayForUrl('', $storeArray); + return HttpUtility::buildQueryString($storeArray, '&'); } /** diff --git a/typo3/sysext/backend/Classes/Tree/View/ElementBrowserFolderTreeView.php b/typo3/sysext/backend/Classes/Tree/View/ElementBrowserFolderTreeView.php index 585d1e23e8d1..792bdf4c2f4b 100644 --- a/typo3/sysext/backend/Classes/Tree/View/ElementBrowserFolderTreeView.php +++ b/typo3/sysext/backend/Classes/Tree/View/ElementBrowserFolderTreeView.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\Backend\Tree\View; use TYPO3\CMS\Core\Resource\Folder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface; /** @@ -63,8 +64,10 @@ class ElementBrowserFolderTreeView extends FolderTreeView // Wrap icon in link (in ElementBrowser only the "titlelink" is used). if ($this->ext_IconMode === 'titlelink') { - $parameters = GeneralUtility::implodeArrayForUrl('', $this->linkParameterProvider->getUrlParameters(['identifier' => $folderObject->getCombinedIdentifier()])); - $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim($parameters, '&')) . ');'; + $parameters = HttpUtility::buildQueryString( + $this->linkParameterProvider->getUrlParameters(['identifier' => $folderObject->getCombinedIdentifier()]) + ); + $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . $parameters) . ');'; $theFolderIcon = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>'; } @@ -81,8 +84,10 @@ class ElementBrowserFolderTreeView extends FolderTreeView */ public function wrapTitle($title, $folderObject, $bank = 0) { - $parameters = GeneralUtility::implodeArrayForUrl('', $this->linkParameterProvider->getUrlParameters(['identifier' => $folderObject->getCombinedIdentifier()])); - return '<a href="#" onclick="return jumpToUrl(' . htmlspecialchars(GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim($parameters, '&'))) . ');">' . $title . '</a>'; + $parameters = HttpUtility::buildQueryString( + $this->linkParameterProvider->getUrlParameters(['identifier' => $folderObject->getCombinedIdentifier()]) + ); + return '<a href="#" onclick="return jumpToUrl(' . htmlspecialchars(GeneralUtility::quoteJSvalue($this->getThisScript() . $parameters)) . ');">' . $title . '</a>'; } /** @@ -127,7 +132,7 @@ class ElementBrowserFolderTreeView extends FolderTreeView $name = $bMark ? ' name=' . $bMark : ''; $urlParameters = $this->linkParameterProvider->getUrlParameters([]); $urlParameters['PM'] = $cmd; - $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&')) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');'; + $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . HttpUtility::buildQueryString($urlParameters)) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');'; return '<a href="#"' . htmlspecialchars($name) . ' onclick="' . htmlspecialchars($aOnClick) . '">' . $icon . '</a>'; } diff --git a/typo3/sysext/backend/Classes/Tree/View/ElementBrowserPageTreeView.php b/typo3/sysext/backend/Classes/Tree/View/ElementBrowserPageTreeView.php index ab6780b1f9c3..986528def961 100644 --- a/typo3/sysext/backend/Classes/Tree/View/ElementBrowserPageTreeView.php +++ b/typo3/sysext/backend/Classes/Tree/View/ElementBrowserPageTreeView.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\Backend\Tree\View; use TYPO3\CMS\Core\LinkHandling\LinkService; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Frontend\Page\PageRepository; use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface; @@ -118,7 +119,7 @@ class ElementBrowserPageTreeView extends BrowseTreeView $classAttr .= ' active'; } $urlParameters = $this->linkParameterProvider->getUrlParameters(['pid' => (int)$treeItem['row']['uid']]); - $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&')) . ');'; + $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . HttpUtility::buildQueryString($urlParameters)) . ');'; $cEbullet = $this->ext_isLinkable($treeItem['row']['doktype'], $treeItem['row']['uid']) ? '<a href="#" class="list-tree-show" onclick="' . htmlspecialchars($aOnClick) . '"><i class="fa fa-caret-square-o-right"></i></a>' : ''; @@ -176,7 +177,7 @@ class ElementBrowserPageTreeView extends BrowseTreeView $name = $bMark ? ' name=' . $bMark : ''; $urlParameters = $this->linkParameterProvider->getUrlParameters([]); $urlParameters['PM'] = $cmd; - $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&')) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');'; + $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($this->getThisScript() . HttpUtility::buildQueryString($urlParameters)) . ',' . GeneralUtility::quoteJSvalue($anchor) . ');'; return '<a class="list-tree-control ' . ($isOpen ? 'list-tree-control-open' : 'list-tree-control-closed') . '" href="#"' . htmlspecialchars($name) . ' onclick="' . htmlspecialchars($aOnClick) . '"><i class="fa"></i></a>'; } diff --git a/typo3/sysext/backend/Classes/Utility/BackendUtility.php b/typo3/sysext/backend/Classes/Utility/BackendUtility.php index eeeee6b0775f..cf3c6b29cc2f 100644 --- a/typo3/sysext/backend/Classes/Utility/BackendUtility.php +++ b/typo3/sysext/backend/Classes/Utility/BackendUtility.php @@ -52,6 +52,7 @@ use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Core\Utility\PathUtility; use TYPO3\CMS\Core\Versioning\VersionState; @@ -3091,7 +3092,7 @@ class BackendUtility * @param mixed $mainParams $id is the "&id=" parameter value to be sent to the module, but it can be also a parameter array which will be passed instead of the &id=... * @param string $addParams Additional parameters to pass to the script. * @param string $script The script to send the &id to, if empty it's automatically found - * @return string The completes script URL + * @return string The complete script URL */ protected static function buildScriptUrl($mainParams, $addParams, $script = '') { @@ -3107,7 +3108,7 @@ class BackendUtility $scriptUrl = (string)$uriBuilder->buildUriFromRoutePath($routePath, $mainParams); $scriptUrl .= $addParams; } else { - $scriptUrl = $script . '?' . GeneralUtility::implodeArrayForUrl('', $mainParams) . $addParams; + $scriptUrl = $script . HttpUtility::buildQueryString($mainParams, '?') . $addParams; } return $scriptUrl; diff --git a/typo3/sysext/backend/Classes/View/PageLayoutView.php b/typo3/sysext/backend/Classes/View/PageLayoutView.php index 09990441e350..25d90c93f709 100644 --- a/typo3/sysext/backend/Classes/View/PageLayoutView.php +++ b/typo3/sysext/backend/Classes/View/PageLayoutView.php @@ -3798,10 +3798,7 @@ class PageLayoutView implements LoggerAwareInterface $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); $url = (string)$uriBuilder->buildUriFromRoutePath($routePath, $urlParameters); } else { - $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim( - GeneralUtility::implodeArrayForUrl('', $urlParameters), - '&' - ); + $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . HttpUtility::buildQueryString($urlParameters, '?'); } return $url; } diff --git a/typo3/sysext/core/Classes/Database/QueryView.php b/typo3/sysext/core/Classes/Database/QueryView.php index ee176a59647b..2e81f3af84ff 100644 --- a/typo3/sysext/core/Classes/Database/QueryView.php +++ b/typo3/sysext/core/Classes/Database/QueryView.php @@ -31,6 +31,7 @@ use TYPO3\CMS\Core\Utility\CsvUtility; use TYPO3\CMS\Core\Utility\DebugUtility; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; /** * Class used in module tools/dbint (advanced search) and which may hold code specific for that module @@ -707,7 +708,7 @@ class QueryView ] ], 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI') - . GeneralUtility::implodeArrayForUrl('SET', (array)GeneralUtility::_POST('SET')) + . HttpUtility::buildQueryString(['SET' => (array)GeneralUtility::_POST('SET')], '&') ]); $out .= '<a class="btn btn-default" href="' . htmlspecialchars($url) . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>'; diff --git a/typo3/sysext/core/Classes/TypoScript/TemplateService.php b/typo3/sysext/core/Classes/TypoScript/TemplateService.php index 7e99e5547051..a1427f04d76e 100644 --- a/typo3/sysext/core/Classes/TypoScript/TemplateService.php +++ b/typo3/sysext/core/Classes/TypoScript/TemplateService.php @@ -34,6 +34,7 @@ use TYPO3\CMS\Core\TimeTracker\TimeTracker; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; @@ -1596,7 +1597,7 @@ class TemplateService $LD['no_cache'] = $no_cache ? '&no_cache=1' : ''; // linkVars if ($addParams) { - $LD['linkVars'] = GeneralUtility::implodeArrayForUrl('', GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars . $addParams), '', false, true); + $LD['linkVars'] = HttpUtility::buildQueryString(GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars . $addParams), '&'); } else { $LD['linkVars'] = $this->getTypoScriptFrontendController()->linkVars; } diff --git a/typo3/sysext/core/Classes/Utility/GeneralUtility.php b/typo3/sysext/core/Classes/Utility/GeneralUtility.php index fe4dd8e98750..d1d7576438f6 100644 --- a/typo3/sysext/core/Classes/Utility/GeneralUtility.php +++ b/typo3/sysext/core/Classes/Utility/GeneralUtility.php @@ -2620,12 +2620,11 @@ class GeneralUtility unset($params[$key]); } } - $pString = self::implodeArrayForUrl('', $params); - return $pString ? $parts . '?' . ltrim($pString, '&') : $parts; + return $parts . HttpUtility::buildQueryString($params, '?'); } /** - * Takes a full URL, $url, possibly with a querystring and overlays the $getParams arrays values onto the quirystring, packs it all together and returns the URL again. + * Takes a full URL, $url, possibly with a querystring and overlays the $getParams arrays values onto the querystring, packs it all together and returns the URL again. * So basically it adds the parameters in $getParams to an existing URL, $url * * @param string $url URL string @@ -2640,10 +2639,8 @@ class GeneralUtility parse_str($parts['query'], $getP); } ArrayUtility::mergeRecursiveWithOverrule($getP, $getParams); - $uP = explode('?', $url); - $params = self::implodeArrayForUrl('', $getP); - $outurl = $uP[0] . ($params ? '?' . substr($params, 1) : ''); - return $outurl; + [$url] = explode('?', $url); + return $url . HttpUtility::buildQueryString($getP, '?'); } /** diff --git a/typo3/sysext/core/Classes/Utility/HttpUtility.php b/typo3/sysext/core/Classes/Utility/HttpUtility.php index 29901e4d4e1b..a137144c5e69 100644 --- a/typo3/sysext/core/Classes/Utility/HttpUtility.php +++ b/typo3/sysext/core/Classes/Utility/HttpUtility.php @@ -146,4 +146,36 @@ class HttpUtility (isset($urlParts['query']) ? '?' . $urlParts['query'] : '') . (isset($urlParts['fragment']) ? '#' . $urlParts['fragment'] : ''); } + + /** + * Implodes a multidimensional array of query parameters to a string of GET parameters (eg. param[key][key2]=value2¶m[key][key3]=value3) + * and properly encodes parameter names as well as values. Spaces are encoded as %20 + * + * @param array $parameters The (multidimensional) array of query parameters with values + * @param string $prependCharacter If the created query string is not empty, prepend this character "?" or "&" else no prepend + * @param bool $skipEmptyParameters If true, empty parameters (blank string, empty array, null) are removed. + * @return string Imploded result, for example param[key][key2]=value2¶m[key][key3]=value3 + * @see explodeUrl2Array() + */ + public static function buildQueryString(array $parameters, string $prependCharacter = '', bool $skipEmptyParameters = false): string + { + if (empty($parameters)) { + return ''; + } + + if ($skipEmptyParameters) { + // This callback filters empty strings, array and null but keeps zero integers + $parameters = ArrayUtility::filterRecursive( + $parameters, + function ($item) { + return $item !== '' && $item !== [] && $item !== null; + } + ); + } + + $queryString = http_build_query($parameters, '', '&', PHP_QUERY_RFC3986); + $prependCharacter = $prependCharacter === '?' || $prependCharacter === '&' ? $prependCharacter : ''; + + return $queryString && $prependCharacter ? $prependCharacter . $queryString : $queryString; + } } diff --git a/typo3/sysext/core/Documentation/Changelog/9.5.x/Feature-83334-AddImprovedBuildQueryString.rst b/typo3/sysext/core/Documentation/Changelog/9.5.x/Feature-83334-AddImprovedBuildQueryString.rst new file mode 100644 index 000000000000..d9408a7273b5 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/9.5.x/Feature-83334-AddImprovedBuildQueryString.rst @@ -0,0 +1,23 @@ +.. include:: ../../Includes.txt + +======================================================== +Feature: #83334 - Add improved building of query strings +======================================================== + +See :issue:`83334` + +Description +=========== + +The new method :php:`\TYPO3\CMS\Core\Utility\HttpUtility::buildQueryString()` has been added as an enhancement to the `PHP function`_ :php:`http_build_query()` +to implode multidimensional parameter arrays, properly encode parameter names as well as values with an optional prepend of :php:`?` or :php:`&` if the query +string is not empty and skipping empty parameters. + +.. _`PHP function`: https://secure.php.net/manual/de/function.http-build-query.php + +Impact +====== + +Parameter arrays can be safely transformed into HTTP GET query strings using the new method. + +.. index:: PHP-API, NotScanned diff --git a/typo3/sysext/core/Tests/Unit/Utility/HttpUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/HttpUtilityTest.php index 8d9b4f70760e..2e5cc8b377ce 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/HttpUtilityTest.php +++ b/typo3/sysext/core/Tests/Unit/Utility/HttpUtilityTest.php @@ -14,6 +14,7 @@ namespace TYPO3\CMS\Core\Tests\Unit\Utility; * The TYPO3 project - inspiring people to share! */ +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; /** @@ -29,7 +30,7 @@ class HttpUtilityTest extends UnitTestCase */ public function isUrlBuiltCorrectly(array $urlParts, $expected) { - $url = \TYPO3\CMS\Core\Utility\HttpUtility::buildUrl($urlParts); + $url = HttpUtility::buildUrl($urlParts); $this->assertEquals($expected, $url); } @@ -61,4 +62,87 @@ class HttpUtilityTest extends UnitTestCase ] ]; } + + /** + * Data provider for buildQueryString + * + * @return array + */ + public function queryStringDataProvider() + { + $valueArray = ['one' => '√', 'two' => 2]; + + return [ + 'Empty input' => ['foo', [], ''], + 'String parameters' => ['foo', $valueArray, 'foo%5Bone%5D=%E2%88%9A&foo%5Btwo%5D=2'], + 'Nested array parameters' => ['foo', [$valueArray], 'foo%5B0%5D%5Bone%5D=%E2%88%9A&foo%5B0%5D%5Btwo%5D=2'], + 'Keep blank parameters' => ['foo', ['one' => '√', ''], 'foo%5Bone%5D=%E2%88%9A&foo%5B0%5D='] + ]; + } + + /** + * @test + * @dataProvider queryStringDataProvider + * @param string $name + * @param array $input + * @param string $expected + */ + public function buildQueryStringBuildsValidParameterString($name, array $input, $expected) + { + if ($name === '') { + $this->assertSame($expected, HttpUtility::buildQueryString($input)); + } else { + $this->assertSame($expected, HttpUtility::buildQueryString([$name => $input])); + } + } + + /** + * @test + */ + public function buildQueryStringCanSkipEmptyParameters() + { + $input = ['one' => '√', '']; + $expected = 'foo%5Bone%5D=%E2%88%9A'; + $this->assertSame($expected, HttpUtility::buildQueryString(['foo' => $input], '', true)); + } + + /** + * @test + */ + public function buildQueryStringCanUrlEncodeKeyNames() + { + $input = ['one' => '√', '']; + $expected = 'foo%5Bone%5D=%E2%88%9A&foo%5B0%5D='; + $this->assertSame($expected, HttpUtility::buildQueryString(['foo' => $input])); + } + + /** + * @test + */ + public function buildQueryStringCanUrlEncodeKeyNamesMultidimensional() + { + $input = ['one' => ['two' => ['three' => '√']], '']; + $expected = 'foo%5Bone%5D%5Btwo%5D%5Bthree%5D=%E2%88%9A&foo%5B0%5D='; + $this->assertSame($expected, HttpUtility::buildQueryString(['foo' => $input])); + } + + /** + * @test + */ + public function buildQueryStringSkipsLeadingCharacterOnEmptyParameters() + { + $input = []; + $expected = ''; + $this->assertSame($expected, HttpUtility::buildQueryString($input, '?', true)); + } + + /** + * @test + */ + public function buildQueryStringSkipsLeadingCharacterOnCleanedEmptyParameters() + { + $input = ['one' => '']; + $expected = ''; + $this->assertSame($expected, HttpUtility::buildQueryString(['foo' => $input], '?', true)); + } } diff --git a/typo3/sysext/extbase/Classes/Mvc/Web/Routing/UriBuilder.php b/typo3/sysext/extbase/Classes/Mvc/Web/Routing/UriBuilder.php index 9eeb3135c9e7..c9c5171511f8 100644 --- a/typo3/sysext/extbase/Classes/Mvc/Web/Routing/UriBuilder.php +++ b/typo3/sysext/extbase/Classes/Mvc/Web/Routing/UriBuilder.php @@ -18,6 +18,7 @@ use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException; use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Extbase\Mvc\Request; use TYPO3\CMS\Extbase\Mvc\Web\Request as WebRequest; @@ -731,7 +732,7 @@ class UriBuilder if (!empty($this->arguments)) { $arguments = $this->convertDomainObjectsToIdentityArrays($this->arguments); $this->lastArguments = $arguments; - $typolinkConfiguration['additionalParams'] = GeneralUtility::implodeArrayForUrl(null, $arguments); + $typolinkConfiguration['additionalParams'] = HttpUtility::buildQueryString($arguments, '&'); } if ($this->addQueryString === true) { $typolinkConfiguration['addQueryString'] = 1; diff --git a/typo3/sysext/extbase/Tests/Unit/Mvc/Web/Routing/UriBuilderTest.php b/typo3/sysext/extbase/Tests/Unit/Mvc/Web/Routing/UriBuilderTest.php index 38a36fcda061..d52c2eea73b5 100644 --- a/typo3/sysext/extbase/Tests/Unit/Mvc/Web/Routing/UriBuilderTest.php +++ b/typo3/sysext/extbase/Tests/Unit/Mvc/Web/Routing/UriBuilderTest.php @@ -623,7 +623,7 @@ class UriBuilderTest extends UnitTestCase { $this->uriBuilder->setTargetPageUid(123); $this->uriBuilder->setArguments(['foo' => 'bar', 'baz' => ['extbase' => 'fluid']]); - $expectedConfiguration = ['parameter' => 123, 'useCacheHash' => 1, 'additionalParams' => '&foo=bar&baz[extbase]=fluid']; + $expectedConfiguration = ['parameter' => 123, 'useCacheHash' => 1, 'additionalParams' => '&foo=bar&baz%5Bextbase%5D=fluid']; $actualConfiguration = $this->uriBuilder->_call('buildTypolinkConfiguration'); $this->assertEquals($expectedConfiguration, $actualConfiguration); } @@ -664,7 +664,7 @@ class UriBuilderTest extends UnitTestCase $mockDomainObject2->_set('uid', '321'); $this->uriBuilder->setTargetPageUid(123); $this->uriBuilder->setArguments(['someDomainObject' => $mockDomainObject1, 'baz' => ['someOtherDomainObject' => $mockDomainObject2]]); - $expectedConfiguration = ['parameter' => 123, 'useCacheHash' => 1, 'additionalParams' => '&someDomainObject=123&baz[someOtherDomainObject]=321']; + $expectedConfiguration = ['parameter' => 123, 'useCacheHash' => 1, 'additionalParams' => '&someDomainObject=123&baz%5BsomeOtherDomainObject%5D=321']; $actualConfiguration = $this->uriBuilder->_call('buildTypolinkConfiguration'); $this->assertEquals($expectedConfiguration, $actualConfiguration); } diff --git a/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php b/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php index b7ac1225a507..5095f3a23b2b 100644 --- a/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php +++ b/typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer; use TYPO3\CMS\Core\Exception\SiteNotFoundException; use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Frontend\Plugin\AbstractPlugin; /** @@ -907,19 +908,14 @@ class FrontendLoginController extends AbstractPlugin implements LoggerAwareInter */ protected function getPageLink($label, $piVars, $returnUrl = false) { - $additionalParams = ''; - if (!empty($piVars)) { - foreach ($piVars as $key => $val) { - $additionalParams .= '&' . $key . '=' . $val; - } - } + $additionalParams = is_array($piVars) && !empty($piVars) ? $piVars : []; // Should GETvars be preserved? if ($this->conf['preserveGETvars']) { - $additionalParams .= $this->getPreserveGetVars(); + $additionalParams = array_merge_recursive($additionalParams, $this->getPreserveGetVars()); } $this->conf['linkConfig.']['parameter'] = $this->frontendController->id; - if ($additionalParams) { - $this->conf['linkConfig.']['additionalParams'] = $additionalParams; + if (!empty($additionalParams)) { + $this->conf['linkConfig.']['additionalParams'] = HttpUtility::buildQueryString($additionalParams, '&'); } if ($returnUrl) { return htmlspecialchars($this->cObj->typoLink_URL($this->conf['linkConfig.'])); @@ -933,7 +929,7 @@ class FrontendLoginController extends AbstractPlugin implements LoggerAwareInter * Supports multi-dimensional GET-vars. * Some hardcoded values are dropped. * - * @return string additionalParams-string + * @return array additionalParams-array */ protected function getPreserveGetVars() { @@ -954,8 +950,7 @@ class FrontendLoginController extends AbstractPlugin implements LoggerAwareInter parse_str(implode('=1&', $preserveQueryStringProperties) . '=1', $preserveQueryParts); $preserveQueryParts = \TYPO3\CMS\Core\Utility\ArrayUtility::intersectRecursive($getVars, $preserveQueryParts); } - $parameters = GeneralUtility::implodeArrayForUrl('', $preserveQueryParts); - return $parameters; + return $preserveQueryParts; } /** diff --git a/typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php b/typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php index e72dfb7b45cf..48e8e99bb7a9 100644 --- a/typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php +++ b/typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php @@ -322,7 +322,7 @@ class FrontendLoginControllerTest extends UnitTestCase 'id' => 42, ], '', - '', + [], ], 'simple additional parameter is not preserved if not specified in preservedGETvars' => [ [ @@ -330,7 +330,7 @@ class FrontendLoginControllerTest extends UnitTestCase 'special' => 23, ], '', - '', + [], ], 'all params except ignored ones are preserved if preservedGETvars is set to "all"' => [ [ @@ -344,14 +344,21 @@ class FrontendLoginControllerTest extends UnitTestCase ], ], 'all', - '&special1=23&special2[foo]=bar', + [ + 'special1' => 23, + 'special2' => [ + 'foo' => 'bar', + ], + ] ], 'preserve single parameter' => [ [ 'L' => 42, ], 'L', - '&L=42' + [ + 'L' => 42, + ], ], 'preserve whole parameter array' => [ [ @@ -364,7 +371,15 @@ class FrontendLoginControllerTest extends UnitTestCase ], ], 'L,tx_someext', - '&L=3&tx_someext[foo]=simple&tx_someext[bar][baz]=simple', + [ + 'L' => 3, + 'tx_someext' => [ + 'foo' => 'simple', + 'bar' => [ + 'baz' => 'simple', + ], + ], + ], ], 'preserve part of sub array' => [ [ @@ -377,7 +392,14 @@ class FrontendLoginControllerTest extends UnitTestCase ], ], 'L,tx_someext[bar]', - '&L=3&tx_someext[bar][baz]=simple', + [ + 'L' => 3, + 'tx_someext' => [ + 'bar' => [ + 'baz' => 'simple', + ], + ], + ], ], 'preserve keys on different levels' => [ [ @@ -394,18 +416,23 @@ class FrontendLoginControllerTest extends UnitTestCase ], ], 'L,tx_ext2,tx_ext3[bar]', - '&L=3&tx_ext2[foo]=simple&tx_ext3[bar][baz]=simple', + [ + 'L' => 3, + 'tx_ext2' => [ + 'foo' => 'simple', + ], + 'tx_ext3' => [ + 'bar' => [ + 'baz' => 'simple', + ], + ], + ], ], 'preserved value that does not exist in get' => [ [], - 'L,foo[bar]', - '' - ], - 'url params are encoded' => [ - ['tx_ext1' => 'param with spaces and \\ %<>& /'], - 'L,tx_ext1', - '&tx_ext1=param%20with%20spaces%20and%20%5C%20%25%3C%3E%26%20%2F' - ], + 'L,foo%5Bbar%5D', + [], + ], ]; } diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php index 789f3f78ad59..f0a64d421383 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php +++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php @@ -53,6 +53,7 @@ use TYPO3\CMS\Core\TypoScript\TypoScriptService; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\DebugUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\MailUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Core\Utility\PathUtility; @@ -5564,7 +5565,7 @@ class ContentObjectRenderer implements LoggerAwareInterface } if (is_array($urlParameters)) { if (!empty($urlParameters)) { - $conf['additionalParams'] .= GeneralUtility::implodeArrayForUrl('', $urlParameters); + $conf['additionalParams'] .= HttpUtility::buildQueryString($urlParameters, '&'); } } else { $conf['additionalParams'] .= $urlParameters; @@ -5865,7 +5866,7 @@ class ContentObjectRenderer implements LoggerAwareInterface $newQueryArray = $currentQueryArray; } ArrayUtility::mergeRecursiveWithOverrule($newQueryArray, $overruleQueryArguments, $forceOverruleArguments); - return GeneralUtility::implodeArrayForUrl('', $newQueryArray, '', false, true); + return HttpUtility::buildQueryString($newQueryArray, '&'); } /*********************************************** diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php index 0e29f5dca992..80f49943da37 100644 --- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php +++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php @@ -2255,7 +2255,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface if ($this->cHash && is_array($GET)) { // Make sure we use the page uid and not the page alias $GET['id'] = $this->id; - $this->cHash_array = $this->cacheHash->getRelevantParameters(GeneralUtility::implodeArrayForUrl('', $GET)); + $this->cHash_array = $this->cacheHash->getRelevantParameters(HttpUtility::buildQueryString($GET)); $cHash_calc = $this->cacheHash->calculateCacheHash($this->cHash_array); if (!hash_equals($cHash_calc, $this->cHash)) { if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']) { @@ -2271,7 +2271,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface } } elseif (is_array($GET)) { // No cHash is set, check if that is correct - if ($this->cacheHash->doParametersRequireCacheHash(GeneralUtility::implodeArrayForUrl('', $GET))) { + if ($this->cacheHash->doParametersRequireCacheHash(HttpUtility::buildQueryString($GET))) { $this->reqCHash(); } } @@ -3084,7 +3084,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface // Error: This key must not be an array! continue; } - $value = GeneralUtility::implodeArrayForUrl($parameterName, $value); + $value = HttpUtility::buildQueryString([$parameterName => $value], '&'); } $this->linkVars .= $value; } diff --git a/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php b/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php index 7f0a18c7a6e9..5a9ea04a6ac6 100644 --- a/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php +++ b/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php @@ -23,6 +23,7 @@ use Psr\Http\Server\RequestHandlerInterface; use TYPO3\CMS\Core\Routing\PageArguments; use TYPO3\CMS\Core\TimeTracker\TimeTracker; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Frontend\Controller\ErrorController; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; use TYPO3\CMS\Frontend\Page\CacheHashCalculator; @@ -102,7 +103,7 @@ class PageArgumentValidator implements MiddlewareInterface if ($this->controller->cHash) { // Make sure we use the page uid and not the page alias $queryParams['id'] = $this->controller->id; - $this->controller->cHash_array = $this->cacheHashCalculator->getRelevantParameters(GeneralUtility::implodeArrayForUrl('', $queryParams)); + $this->controller->cHash_array = $this->cacheHashCalculator->getRelevantParameters(HttpUtility::buildQueryString($queryParams)); $cHash_calc = $this->cacheHashCalculator->calculateCacheHash($this->controller->cHash_array); if (!hash_equals($cHash_calc, $this->controller->cHash)) { // Early return to trigger the error controller @@ -113,7 +114,7 @@ class PageArgumentValidator implements MiddlewareInterface $this->getTimeTracker()->setTSlogMessage('The incoming cHash "' . $this->controller->cHash . '" and calculated cHash "' . $cHash_calc . '" did not match, so caching was disabled. The fieldlist used was "' . implode(',', array_keys($this->controller->cHash_array)) . '"', 2); } // No cHash is set, check if that is correct - } elseif ($this->cacheHashCalculator->doParametersRequireCacheHash(GeneralUtility::implodeArrayForUrl('', $queryParams))) { + } elseif ($this->cacheHashCalculator->doParametersRequireCacheHash(HttpUtility::buildQueryString($queryParams))) { // Will disable caching $this->controller->reqCHash(); } diff --git a/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php b/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php index 4b2d1dab75ad..a39bced3ea86 100644 --- a/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php +++ b/typo3/sysext/frontend/Classes/Plugin/AbstractPlugin.php @@ -24,6 +24,7 @@ use TYPO3\CMS\Core\Localization\LocalizationFactory; use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Core\Utility\PathUtility; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; @@ -384,7 +385,7 @@ class AbstractPlugin $conf['useCacheHash'] = $this->pi_USER_INT_obj ? 0 : $cache; $conf['no_cache'] = $this->pi_USER_INT_obj ? 0 : !$cache; $conf['parameter'] = $altPageId ? $altPageId : ($this->pi_tmpPageId ? $this->pi_tmpPageId : $this->frontendController->id); - $conf['additionalParams'] = $this->conf['parent.']['addParams'] . GeneralUtility::implodeArrayForUrl('', $urlParameters, '', true) . $this->pi_moreParams; + $conf['additionalParams'] = $this->conf['parent.']['addParams'] . HttpUtility::buildQueryString($urlParameters, '&', true) . $this->pi_moreParams; return $this->cObj->typoLink($str, $conf); } diff --git a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php index a8d7cafe926d..eae80186307d 100644 --- a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php +++ b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php @@ -31,6 +31,7 @@ use TYPO3\CMS\Core\Site\Entity\Site; use TYPO3\CMS\Core\Site\Entity\SiteInterface; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Core\Utility\RootlineUtility; use TYPO3\CMS\Frontend\Compatibility\LegacyDomainResolver; @@ -474,9 +475,8 @@ class PageLinkBuilder extends AbstractTypolinkBuilder ) { $currentQueryArray = []; parse_str(GeneralUtility::getIndpEnv('QUERY_STRING'), $currentQueryArray); - $currentQueryParams = GeneralUtility::implodeArrayForUrl('', $currentQueryArray, '', false, true); - if (!trim($currentQueryParams)) { + if (empty($currentQueryArray)) { list(, $URLparams) = explode('?', $url); list($URLparams) = explode('#', (string)$URLparams); parse_str($URLparams . $LD['orig_type'], $URLparamsArray); @@ -753,7 +753,7 @@ class PageLinkBuilder extends AbstractTypolinkBuilder $LD['no_cache'] = $no_cache ? '&no_cache=1' : ''; // linkVars if ($addParams) { - $LD['linkVars'] = GeneralUtility::implodeArrayForUrl('', GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars . $addParams), '', false, true); + $LD['linkVars'] = HttpUtility::buildQueryString(GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars . $addParams), '&'); } else { $LD['linkVars'] = $this->getTypoScriptFrontendController()->linkVars; } diff --git a/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php b/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php index ef4132e8792d..f8f69c55137b 100644 --- a/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php +++ b/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php @@ -400,7 +400,7 @@ class TypoScriptFrontendControllerTest extends UnitTestCase 'foo' => [ 1, 2, 'f' => [ 4, 5 ] ], 'blub' => 123 ], - '&L=1&foo[0]=1&foo[1]=2&foo[f][0]=4&foo[f][1]=5' + '&L=1&foo%5B0%5D=1&foo%5B1%5D=2&foo%5Bf%5D%5B0%5D=4&foo%5Bf%5D%5B1%5D=5' ], 'nested variables' => [ 'bar|foo(1-2)', diff --git a/typo3/sysext/indexed_search/Classes/Indexer.php b/typo3/sysext/indexed_search/Classes/Indexer.php index 374cccb54538..d64183827845 100644 --- a/typo3/sysext/indexed_search/Classes/Indexer.php +++ b/typo3/sysext/indexed_search/Classes/Indexer.php @@ -23,6 +23,7 @@ use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\TimeTracker\TimeTracker; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Core\Utility\PathUtility; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; @@ -390,7 +391,7 @@ class Indexer if ($createCHash) { /* @var \TYPO3\CMS\Frontend\Page\CacheHashCalculator $cacheHash */ $cacheHash = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\CacheHashCalculator::class); - $this->conf['cHash'] = $cacheHash->generateForParameters(GeneralUtility::implodeArrayForUrl('', $cHash_array)); + $this->conf['cHash'] = $cacheHash->generateForParameters(HttpUtility::buildQueryString($cHash_array)); } else { $this->conf['cHash'] = ''; } diff --git a/typo3/sysext/install/Classes/ViewHelpers/Uri/ActionViewHelper.php b/typo3/sysext/install/Classes/ViewHelpers/Uri/ActionViewHelper.php index 512d476e00f3..ea1d44d16a55 100644 --- a/typo3/sysext/install/Classes/ViewHelpers/Uri/ActionViewHelper.php +++ b/typo3/sysext/install/Classes/ViewHelpers/Uri/ActionViewHelper.php @@ -15,6 +15,7 @@ namespace TYPO3\CMS\Install\ViewHelpers\Uri; */ use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; @@ -79,9 +80,13 @@ class ActionViewHelper extends AbstractViewHelper } return GeneralUtility::getIndpEnv('TYPO3_REQUEST_SCRIPT') - . '?' - . GeneralUtility::implodeArrayForUrl('install', $arguments) - . GeneralUtility::implodeArrayForUrl('', $additionalParams) + . HttpUtility::buildQueryString( + array_merge( + ['install' => $arguments], + $additionalParams + ), + '?' + ) . ($section ? '#' . $section : ''); } } diff --git a/typo3/sysext/recordlist/Classes/Browser/FileBrowser.php b/typo3/sysext/recordlist/Classes/Browser/FileBrowser.php index 829d61963780..a69c42bddd39 100644 --- a/typo3/sysext/recordlist/Classes/Browser/FileBrowser.php +++ b/typo3/sysext/recordlist/Classes/Browser/FileBrowser.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Core\Resource\Folder; use TYPO3\CMS\Core\Resource\ProcessedFile; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface; use TYPO3\CMS\Recordlist\View\FolderUtilityRenderer; @@ -417,7 +418,7 @@ class FileBrowser extends AbstractElementBrowser implements ElementBrowserInterf $_MOD_MENU = ['displayThumbs' => '']; $_MCONF['name'] = 'file_list'; $_MOD_SETTINGS = BackendUtility::getModuleData($_MOD_MENU, GeneralUtility::_GP('SET'), $_MCONF['name']); - $addParams = GeneralUtility::implodeArrayForUrl('', $this->getUrlParameters(['identifier' => $this->selectedFolder->getCombinedIdentifier()])); + $addParams = HttpUtility::buildQueryString($this->getUrlParameters(['identifier' => $this->selectedFolder->getCombinedIdentifier()]), '&'); $thumbNailCheck = '<div class="checkbox" style="padding:5px 0 15px 0"><label for="checkDisplayThumbs">' . BackendUtility::getFuncCheck( '', diff --git a/typo3/sysext/recordlist/Classes/Controller/AbstractLinkBrowserController.php b/typo3/sysext/recordlist/Classes/Controller/AbstractLinkBrowserController.php index 42541bd09fb4..bf90c57e7d34 100644 --- a/typo3/sysext/recordlist/Classes/Controller/AbstractLinkBrowserController.php +++ b/typo3/sysext/recordlist/Classes/Controller/AbstractLinkBrowserController.php @@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Service\DependencyOrderingService; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Recordlist\LinkHandler\LinkHandlerInterface; /** @@ -385,8 +386,8 @@ abstract class AbstractLinkBrowserController if ($configuration['addParams']) { $addParams = $configuration['addParams']; } else { - $parameters = GeneralUtility::implodeArrayForUrl('', $this->getUrlParameters(['act' => $identifier])); - $addParams = 'onclick="jumpToUrl(' . htmlspecialchars(GeneralUtility::quoteJSvalue('?' . ltrim($parameters, '&'))) . ');return false;"'; + $parameters = HttpUtility::buildQueryString($this->getUrlParameters(['act' => $identifier]), '?'); + $addParams = 'onclick="jumpToUrl(' . htmlspecialchars(GeneralUtility::quoteJSvalue($parameters)) . ');return false;"'; } $menuDef[$identifier] = [ 'isActive' => $isActive, @@ -587,7 +588,7 @@ abstract class AbstractLinkBrowserController $parameters['params']['allowedExtensions'] = $this->parameters['params']['allowedExtensions'] ?? ''; $parameters['params']['blindLinkOptions'] = $this->parameters['params']['blindLinkOptions'] ?? ''; $parameters['params']['blindLinkFields'] = $this->parameters['params']['blindLinkFields'] ?? ''; - $addPassOnParams = GeneralUtility::implodeArrayForUrl('P', $parameters); + $addPassOnParams = HttpUtility::buildQueryString(['P' => $parameters], '&'); $attributes = $this->displayedLinkHandler->getBodyTagAttributes(); return array_merge( diff --git a/typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php b/typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php index 8387556381f9..0ca066bd151e 100644 --- a/typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php +++ b/typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php @@ -1174,7 +1174,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); $url = (string)$uriBuilder->buildUriFromRoutePath($routePath, $urlParameters); } else { - $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&'); + $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . HttpUtility::buildQueryString($urlParameters, '?'); } return $url; } diff --git a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php index 22562a5f8066..9da79bac4d9a 100644 --- a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php +++ b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php @@ -3777,10 +3777,7 @@ class DatabaseRecordList $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); $url = (string)$uriBuilder->buildUriFromRoutePath($routePath, $urlParameters); } else { - $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?' . ltrim( - GeneralUtility::implodeArrayForUrl('', $urlParameters), - '&' - ); + $url = GeneralUtility::getIndpEnv('SCRIPT_NAME') . HttpUtility::buildQueryString($urlParameters, '?'); } return $url; } diff --git a/typo3/sysext/recordlist/Classes/Tree/View/ElementBrowserPageTreeView.php b/typo3/sysext/recordlist/Classes/Tree/View/ElementBrowserPageTreeView.php index 5f20932aa25d..589f0d690a74 100644 --- a/typo3/sysext/recordlist/Classes/Tree/View/ElementBrowserPageTreeView.php +++ b/typo3/sysext/recordlist/Classes/Tree/View/ElementBrowserPageTreeView.php @@ -17,6 +17,7 @@ namespace TYPO3\CMS\Recordlist\Tree\View; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Imaging\IconFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; /** * Extension class for the TBE record browser @@ -55,7 +56,7 @@ class ElementBrowserPageTreeView extends \TYPO3\CMS\Backend\Tree\View\ElementBro return $out; } - $parameters = GeneralUtility::implodeArrayForUrl('', $this->linkParameterProvider->getUrlParameters(['pid' => $v['uid']])); - return '<a href="#" onclick="return jumpToUrl(' . htmlspecialchars(GeneralUtility::quoteJSvalue($this->getThisScript() . ltrim($parameters, '&'))) . ');">' . $title . '</a>'; + $parameters = HttpUtility::buildQueryString($this->linkParameterProvider->getUrlParameters(['pid' => $v['uid']])); + return '<a href="#" onclick="return jumpToUrl(' . htmlspecialchars(GeneralUtility::quoteJSvalue($this->getThisScript() . $parameters)) . ');">' . $title . '</a>'; } } diff --git a/typo3/sysext/recordlist/Classes/Tree/View/RecordBrowserPageTreeView.php b/typo3/sysext/recordlist/Classes/Tree/View/RecordBrowserPageTreeView.php index 5a8dded13e5d..01aa108f9c87 100644 --- a/typo3/sysext/recordlist/Classes/Tree/View/RecordBrowserPageTreeView.php +++ b/typo3/sysext/recordlist/Classes/Tree/View/RecordBrowserPageTreeView.php @@ -17,6 +17,7 @@ namespace TYPO3\CMS\Recordlist\Tree\View; use TYPO3\CMS\Backend\Tree\View\ElementBrowserPageTreeView; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; /** * Specific page tree for the record link handler. @@ -100,7 +101,7 @@ class RecordBrowserPageTreeView extends ElementBrowserPageTreeView public function wrapTitle($title, $record, $ext_pArrPages = false) { $urlParameters = $this->linkParameterProvider->getUrlParameters(['pid' => (int)$record['uid']]); - $url = $this->getThisScript() . ltrim(GeneralUtility::implodeArrayForUrl('', $urlParameters), '&'); + $url = $this->getThisScript() . HttpUtility::buildQueryString($urlParameters); $aOnClick = 'return jumpToUrl(' . GeneralUtility::quoteJSvalue($url) . ');'; return '<span class="list-tree-title"><a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' diff --git a/typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php b/typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php index a6cf0df89f6d..4b5962bca3b5 100644 --- a/typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php +++ b/typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php @@ -20,6 +20,7 @@ use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Resource\Folder; use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface; /** @@ -85,12 +86,12 @@ class FolderUtilityRenderer . htmlspecialchars($folderObject->getCombinedIdentifier()) . '" />'; // Make footer of upload form, including the submit button: - $redirectValue = $this->parameterProvider->getScriptUrl() . GeneralUtility::implodeArrayForUrl( - '', - $this->parameterProvider->getUrlParameters( - ['identifier' => $folderObject->getCombinedIdentifier()] - ) - ); + $redirectValue = $this->parameterProvider->getScriptUrl() . HttpUtility::buildQueryString( + $this->parameterProvider->getUrlParameters( + ['identifier' => $folderObject->getCombinedIdentifier()] + ), + '&' + ); $markup[] = '<input type="hidden" name="data[newfolder][' . $a . '][redirect]" value="' . htmlspecialchars($redirectValue) . '" />'; $markup[] = '</div></form>'; @@ -149,10 +150,10 @@ class FolderUtilityRenderer . htmlspecialchars($combinedIdentifier) . '" />'; $markup[] = '<input type="hidden" name="data[upload][' . $a . '][data]" value="' . $a . '" />'; } - $redirectValue = $this->parameterProvider->getScriptUrl() . GeneralUtility::implodeArrayForUrl( - '', - $this->parameterProvider->getUrlParameters(['identifier' => $combinedIdentifier]) - ); + $redirectValue = $this->parameterProvider->getScriptUrl() . HttpUtility::buildQueryString( + $this->parameterProvider->getUrlParameters(['identifier' => $combinedIdentifier]), + '&' + ); $markup[] = '<input type="hidden" name="data[upload][1][redirect]" value="' . htmlspecialchars($redirectValue) . '" />'; if (!empty($fileExtList)) { @@ -238,7 +239,7 @@ class FolderUtilityRenderer public function getFileSearchField($searchWord) { $action = $this->parameterProvider->getScriptUrl() - . GeneralUtility::implodeArrayForUrl('', $this->parameterProvider->getUrlParameters([])); + . HttpUtility::buildQueryString($this->parameterProvider->getUrlParameters([]), '&'); $markup = []; $markup[] = '<form method="post" action="' . htmlspecialchars($action) . '" style="padding-bottom: 15px;">'; diff --git a/typo3/sysext/workspaces/Classes/Controller/PreviewController.php b/typo3/sysext/workspaces/Classes/Controller/PreviewController.php index fb75f42f8570..76023275e983 100644 --- a/typo3/sysext/workspaces/Classes/Controller/PreviewController.php +++ b/typo3/sysext/workspaces/Classes/Controller/PreviewController.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Core\Http\HtmlResponse; use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException; use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Fluid\View\StandaloneView; use TYPO3\CMS\Workspaces\Service\StagesService; use TYPO3\CMS\Workspaces\Service\WorkspaceService; @@ -121,7 +122,7 @@ class PreviewController unset($queryParameters['route'], $queryParameters['token'], $queryParameters['previewWS']); // Assemble a query string from the retrieved parameters - $queryString = GeneralUtility::implodeArrayForUrl('', $queryParameters); + $queryString = HttpUtility::buildQueryString($queryParameters, '&'); // fetch the next and previous stage $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace( diff --git a/typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php b/typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php index 38a124239741..324ee34a52e7 100644 --- a/typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php +++ b/typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Core\Exception\SiteNotFoundException; use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException; use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Core\Versioning\VersionState; use TYPO3\CMS\Workspaces\Service\WorkspaceService; @@ -82,7 +83,7 @@ class PreviewUriBuilder 'id' => $uid, 'L' => $languageId ]; - return BackendUtility::getViewDomain($uid) . '/index.php?' . GeneralUtility::implodeArrayForUrl('', $linkParams); + return BackendUtility::getViewDomain($uid) . '/index.php?' . HttpUtility::buildQueryString($linkParams); } } -- GitLab