From 5bff334aa53f701521aa5c58b861f177966ef9c2 Mon Sep 17 00:00:00 2001 From: Helmut Hummel <typo3@helhum.io> Date: Tue, 31 Aug 2021 15:33:55 +0200 Subject: [PATCH] [TASK] Add a PathUtility method to check for host-absolute URLs This replaces a lot of string-based comparisons spread around the code, which have been even wrong. Having a single place for this kind of checks is beneficial. The method is added to PathUtility as similar methods are already present there. Releases: master Resolves: #95121 Change-Id: Ic73aac5d8c758dee5e0424bf2d54cd62b4b09e0c Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70913 Tested-by: core-ci <typo3@b13.com> Tested-by: Markus Klein <markus.klein@typo3.org> Tested-by: Benni Mack <benni@typo3.org> Reviewed-by: Markus Klein <markus.klein@typo3.org> Reviewed-by: Benni Mack <benni@typo3.org> --- .../Classes/Resource/PublicUrlPrefixer.php | 3 +- .../Classes/Template/ModuleTemplate.php | 2 +- .../Cache/Backend/SimpleFileBackend.php | 2 +- .../core/Classes/LinkHandling/LinkService.php | 4 +-- .../core/Classes/Log/Writer/FileWriter.php | 2 +- .../core/Classes/Page/AssetRenderer.php | 2 +- .../core/Classes/Utility/PathUtility.php | 20 ++++++++++++- .../Tests/Unit/Utility/PathUtilityTest.php | 28 +++++++++++++++++++ .../Classes/Evaluation/SourceHost.php | 5 +++- 9 files changed, 59 insertions(+), 9 deletions(-) diff --git a/typo3/sysext/backend/Classes/Resource/PublicUrlPrefixer.php b/typo3/sysext/backend/Classes/Resource/PublicUrlPrefixer.php index 4f21c9b42024..2dfeeef90022 100644 --- a/typo3/sysext/backend/Classes/Resource/PublicUrlPrefixer.php +++ b/typo3/sysext/backend/Classes/Resource/PublicUrlPrefixer.php @@ -20,6 +20,7 @@ namespace TYPO3\CMS\Backend\Resource; use TYPO3\CMS\Core\Resource\Event\GeneratePublicUrlForResourceEvent; use TYPO3\CMS\Core\Resource\ResourceInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\PathUtility; class PublicUrlPrefixer { @@ -39,7 +40,7 @@ class PublicUrlPrefixer try { $resource = $event->getResource(); $originalUrl = $event->getStorage()->getPublicUrl($resource); - if (!$originalUrl || strpos($originalUrl, '://') !== false) { + if (!$originalUrl || PathUtility::hasProtocolAndScheme($originalUrl)) { return; } $event->setPublicUrl(GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $originalUrl); diff --git a/typo3/sysext/backend/Classes/Template/ModuleTemplate.php b/typo3/sysext/backend/Classes/Template/ModuleTemplate.php index 7efe52a0cdde..d94fa4facda9 100644 --- a/typo3/sysext/backend/Classes/Template/ModuleTemplate.php +++ b/typo3/sysext/backend/Classes/Template/ModuleTemplate.php @@ -676,7 +676,7 @@ class ModuleTemplate */ protected function getUriForFileName($filename) { - if (strpos($filename, '://')) { + if (PathUtility::hasProtocolAndScheme($filename)) { return $filename; } $urlPrefix = ''; diff --git a/typo3/sysext/core/Classes/Cache/Backend/SimpleFileBackend.php b/typo3/sysext/core/Classes/Cache/Backend/SimpleFileBackend.php index 8bd48d302fd7..87a26ebc7336 100644 --- a/typo3/sysext/core/Classes/Cache/Backend/SimpleFileBackend.php +++ b/typo3/sysext/core/Classes/Cache/Backend/SimpleFileBackend.php @@ -116,7 +116,7 @@ class SimpleFileBackend extends AbstractBackend implements PhpCapableBackendInte { // Skip handling if directory is a stream resource // This is used by unit tests with vfs:// directories - if (strpos($cacheDirectory, '://')) { + if (PathUtility::hasProtocolAndScheme($cacheDirectory)) { $this->temporaryCacheDirectory = $cacheDirectory; return; } diff --git a/typo3/sysext/core/Classes/LinkHandling/LinkService.php b/typo3/sysext/core/Classes/LinkHandling/LinkService.php index 79e68a9f0bf6..0f55a6054f27 100644 --- a/typo3/sysext/core/Classes/LinkHandling/LinkService.php +++ b/typo3/sysext/core/Classes/LinkHandling/LinkService.php @@ -21,7 +21,7 @@ use TYPO3\CMS\Core\LinkHandling\Exception\UnknownLinkHandlerException; use TYPO3\CMS\Core\LinkHandling\Exception\UnknownUrnException; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Core\Utility\StringUtility; +use TYPO3\CMS\Core\Utility\PathUtility; /** * Class LinkService, responsible to find what kind of resource (type) is used @@ -126,7 +126,7 @@ class LinkService implements SingletonInterface if ($fragment) { $result['fragment'] = $fragment; } - } elseif ((strpos($urn, '://') || StringUtility::beginsWith($urn, '//')) && $this->handlers[self::TYPE_URL]) { + } elseif ($this->handlers[self::TYPE_URL] && PathUtility::hasProtocolAndScheme($urn)) { $result = $this->handlers[self::TYPE_URL]->resolveHandlerData(['url' => $urn]); $result['type'] = self::TYPE_URL; } elseif (stripos($urn, 'mailto:') === 0 && $this->handlers[self::TYPE_EMAIL]) { diff --git a/typo3/sysext/core/Classes/Log/Writer/FileWriter.php b/typo3/sysext/core/Classes/Log/Writer/FileWriter.php index 54adfd0217d6..4447f0eb018d 100644 --- a/typo3/sysext/core/Classes/Log/Writer/FileWriter.php +++ b/typo3/sysext/core/Classes/Log/Writer/FileWriter.php @@ -100,7 +100,7 @@ class FileWriter extends AbstractWriter { $logFile = $relativeLogFile; // Skip handling if logFile is a stream resource. This is used by unit tests with vfs:// directories - if (false === strpos($logFile, '://') && !PathUtility::isAbsolutePath($logFile)) { + if (!PathUtility::hasProtocolAndScheme($logFile) && !PathUtility::isAbsolutePath($logFile)) { $logFile = GeneralUtility::getFileAbsFileName($logFile); if (empty($logFile)) { throw new InvalidLogWriterConfigurationException( diff --git a/typo3/sysext/core/Classes/Page/AssetRenderer.php b/typo3/sysext/core/Classes/Page/AssetRenderer.php index 79cbcc3ecd31..ddf39a27129c 100644 --- a/typo3/sysext/core/Classes/Page/AssetRenderer.php +++ b/typo3/sysext/core/Classes/Page/AssetRenderer.php @@ -113,7 +113,7 @@ class AssetRenderer private function getAbsoluteWebPath(string $file): string { - if (strpos($file, '://') !== false || strpos($file, '//') === 0) { + if (PathUtility::hasProtocolAndScheme($file)) { return $file; } $file = PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName($file)); diff --git a/typo3/sysext/core/Classes/Utility/PathUtility.php b/typo3/sysext/core/Classes/Utility/PathUtility.php index 0af7baba9225..b7ef423b489b 100644 --- a/typo3/sysext/core/Classes/Utility/PathUtility.php +++ b/typo3/sysext/core/Classes/Utility/PathUtility.php @@ -49,7 +49,7 @@ class PathUtility $targetPath = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH') . $targetPath; } } - } elseif (strpos($targetPath, '://') !== false) { + } elseif (static::hasProtocolAndScheme($targetPath)) { return $targetPath; } elseif (file_exists(Environment::getPublicPath() . '/' . $targetPath)) { if (!Environment::isCli()) { @@ -416,4 +416,22 @@ class PathUtility { return substr($path, strlen(Environment::getPublicPath() . '/')); } + + /** + * Tries to guess whether a given URL hast protocol and (optional) scheme. + * Scheme relative URLs match as well. + * Current implementation is two simple string operations. + * + * This is just a guess. For a more detailed validation and parsing, + * use \TYPO3\CMS\Core\Utility\GeneralUtility::isValidUrl() + * + * @param string $path + * @return bool + * + * @internal + */ + public static function hasProtocolAndScheme(string $path): bool + { + return strpos($path, '//') === 0 || strpos($path, '://') > 0; + } } diff --git a/typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php index 5fbd871c7b6e..86a4e48bfd2e 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php +++ b/typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php @@ -555,4 +555,32 @@ class PathUtilityTest extends UnitTestCase self::assertSame($expectedResult, PathUtility::isAbsolutePath($inputPath)); } + + public function hasProtocolAndSchemeDataProvider(): array + { + return [ + ['//example.com/demo.html', true], + ['http://example.com/demo.html', true], + ['http://example.com/demo.html', true], + ['https://example.com/demo.html', true], + ['f://example.com/demo.html', true], + ['f:/example.com/demo.html', false], + ['://example.com/demo.html', false], + [':/example.com/demo.html', false], + ['/example.com/demo.html', false], + ['example.com/demo.html', false], + ['demo.html', false], + ['\\\\server\\unc-path\\demo.html', false], + ['\\\\example.com\\demo.html', false], + ]; + } + + /** + * @test + * @dataProvider hasProtocolAndSchemeDataProvider + */ + public function hasProtocolAndScheme(string $url, bool $result) + { + self::assertSame($result, PathUtility::hasProtocolAndScheme($url)); + } } diff --git a/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php b/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php index f1845b1b9e75..ffe706c243ea 100644 --- a/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php +++ b/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php @@ -17,9 +17,12 @@ declare(strict_types=1); namespace TYPO3\CMS\Redirects\Evaluation; +use TYPO3\CMS\Core\Utility\PathUtility; + /** * Class SourceHost * Triggered from DataHandler as TCA formevals hook for validation / sanitation of domain values. + * * @internal */ class SourceHost @@ -54,7 +57,7 @@ class SourceHost } // 2) Check if value contains a protocol like http:// https:// etc... - if (strpos($value, '://') !== false) { + if (PathUtility::hasProtocolAndScheme($value)) { $tmp = $this->parseUrl($value); if (!empty($tmp)) { return $tmp; -- GitLab