From 72bbfb36d22ad6765dd36fbed5e2a28b039d9e8c Mon Sep 17 00:00:00 2001 From: Oliver Hader <oliver@typo3.org> Date: Thu, 6 Jul 2023 19:32:04 +0200 Subject: [PATCH] [TASK] Extract Indexed Search inline event handling Indexed Search pagination still uses `onclick="..."` inline event handlers. For being compatible with content security policy, the corresponding handlers are added as inline JavaScript, embedded with a CSP nonce value. Resolves: #101271 Releases: main, 12.4 Change-Id: Ic1202faf8be2d1d9e84b599f62e8c9a53aeef2fa Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/79857 Tested-by: core-ci <typo3@b13.com> Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de> Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de> --- .../ViewHelpers/PageBrowsingViewHelper.php | 51 +++++++++++++++---- .../Configuration/ContentSecurityPolicies.php | 25 +++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 typo3/sysext/indexed_search/Configuration/ContentSecurityPolicies.php diff --git a/typo3/sysext/indexed_search/Classes/ViewHelpers/PageBrowsingViewHelper.php b/typo3/sysext/indexed_search/Classes/ViewHelpers/PageBrowsingViewHelper.php index e5825dcbabe6..2f8d53662978 100644 --- a/typo3/sysext/indexed_search/Classes/ViewHelpers/PageBrowsingViewHelper.php +++ b/typo3/sysext/indexed_search/Classes/ViewHelpers/PageBrowsingViewHelper.php @@ -17,6 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\IndexedSearch\ViewHelpers; +use TYPO3\CMS\Core\Page\AssetCollector; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; @@ -30,16 +31,18 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; */ final class PageBrowsingViewHelper extends AbstractTagBasedViewHelper { - /** - * @var string - */ - protected static $prefixId = 'tx_indexedsearch'; + protected static string $prefixId = 'tx_indexedsearch'; /** * @var string */ protected $tagName = 'ul'; + public function __construct(private readonly AssetCollector $assetCollector) + { + parent::__construct(); + } + public function initializeArguments(): void { $this->registerArgument('maximumNumberOfResultPages', 'int', '', true); @@ -124,11 +127,41 @@ final class PageBrowsingViewHelper extends AbstractTagBasedViewHelper */ protected function makecurrentPageSelector_link($str, $p, $freeIndexUid) { - $onclick = 'document.getElementById(' . GeneralUtility::quoteJSvalue(self::$prefixId . '_pointer') . ').value=' . GeneralUtility::quoteJSvalue((string)$p) . ';'; - if ($freeIndexUid !== null) { - $onclick .= 'document.getElementById(' . GeneralUtility::quoteJSvalue(self::$prefixId . '_freeIndexUid') . ').value=' . GeneralUtility::quoteJSvalue($freeIndexUid) . ';'; + $this->providePageSelectorJavaScript(); + return sprintf( + '<a %s>%s</a>', + GeneralUtility::implodeAttributes([ + 'href' => '#', + 'class' => 'tx-indexedsearch-page-selector', + 'data-prefix' => self::$prefixId, + 'data-pointer' => $p, + 'data-freeIndexUid' => $freeIndexUid, + ], true), + htmlspecialchars($str) + ); + } + + private function providePageSelectorJavaScript(): void + { + if ($this->assetCollector->hasInlineJavaScript(self::class)) { + return; } - $onclick .= 'document.getElementById(' . GeneralUtility::quoteJSvalue(self::$prefixId) . ').submit();return false;'; - return '<a href="#" onclick="' . htmlspecialchars($onclick) . '">' . htmlspecialchars($str) . '</a>'; + $this->assetCollector->addInlineJavaScript( + self::class, + implode(' ', [ + "document.addEventListener('click', (evt) => {", + 'evt.preventDefault();', + "if (!evt.target.classList.contains('tx-indexedsearch-page-selector')) {", + 'return;', + '}', + 'var data = evt.target.dataset;', + "document.getElementById(data.prefix + '_pointer').value = data.pointer;", + "document.getElementById(data.prefix + '_freeIndexUid').value = data.freeIndexUid;", + 'document.getElementById(data.prefix).submit();', + '});', + ]), + [], + ['useNonce' => true], + ); } } diff --git a/typo3/sysext/indexed_search/Configuration/ContentSecurityPolicies.php b/typo3/sysext/indexed_search/Configuration/ContentSecurityPolicies.php new file mode 100644 index 000000000000..effa2443841b --- /dev/null +++ b/typo3/sysext/indexed_search/Configuration/ContentSecurityPolicies.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace TYPO3\CMS\Backend; + +use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive; +use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation; +use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection; +use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode; +use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope; +use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword; +use TYPO3\CMS\Core\Type\Map; + +/** + * \TYPO3\CMS\IndexedSearch\ViewHelpers\PageBrowsingViewHelper::providePageSelectorJavaScript + * adds inline JavaScript for the corresponding pagination feature - this configuration adds + * the required CSP `nonce-proxy` for the frontend scope. + */ +return Map::fromEntries([ + Scope::frontend(), + new MutationCollection( + new Mutation(MutationMode::Extend, Directive::ScriptSrc, SourceKeyword::nonceProxy), + ), +]); -- GitLab