From ec7617fbde9910e8265d27797c476305d31919ea Mon Sep 17 00:00:00 2001 From: Oliver Hader <oliver@typo3.org> Date: Wed, 5 Jul 2023 19:44:46 +0200 Subject: [PATCH] [BUGFIX] Normalize filename of uploaded files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The filename of uploaded files might not be encoded as normalized unicode. For instance, this happens when using umlauts in filenames on HFS+ filesystem (macOS). For instance the client sends an `ö`, which is sent in NFD as `0x6fcc88`, but should be normalized as `0xc3b6`. https://en.wikipedia.org/wiki/Unicode_equivalence#Normalization Executed commands: composer req symfony/polyfill-intl-normalizer:^1.27 composer req symfony/polyfill-intl-normalizer:^1.27 \ -d typo3/sysext/core --no-update Resolves: #101253 Releases: main, 12.4, 11.5 Change-Id: I8605481ffdc3b5d96f529850bf09a1fd75d09cd2 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/79837 Tested-by: core-ci <typo3@b13.com> Tested-by: Oliver Hader <oliver.hader@typo3.org> Reviewed-by: Oliver Hader <oliver.hader@typo3.org> --- composer.json | 1 + composer.lock | 2 +- typo3/sysext/core/Classes/Http/UploadedFile.php | 5 ++++- .../sysext/core/Classes/Resource/ResourceStorage.php | 2 +- .../core/Classes/Utility/File/ExtendedFileUtility.php | 1 + .../sysext/core/Tests/Unit/Http/UploadedFileTest.php | 11 +++++++++++ typo3/sysext/core/composer.json | 1 + 7 files changed, 20 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index afc5753b3f95..63245129bfbc 100644 --- a/composer.json +++ b/composer.json @@ -87,6 +87,7 @@ "symfony/messenger": "^6.2", "symfony/mime": "^6.2", "symfony/options-resolver": "^6.2", + "symfony/polyfill-intl-normalizer": "^1.27", "symfony/property-access": "^6.2", "symfony/property-info": "^6.2.11", "symfony/rate-limiter": "^6.2", diff --git a/composer.lock b/composer.lock index 5263cc7e860a..032e3a252b48 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e692c4b3082cbc8207fa2e0897d31ca9", + "content-hash": "7396b099be92db1ca53986a033f316ff", "packages": [ { "name": "bacon/bacon-qr-code", diff --git a/typo3/sysext/core/Classes/Http/UploadedFile.php b/typo3/sysext/core/Classes/Http/UploadedFile.php index 6f1da82864f4..99f3b5013c84 100644 --- a/typo3/sysext/core/Classes/Http/UploadedFile.php +++ b/typo3/sysext/core/Classes/Http/UploadedFile.php @@ -74,7 +74,10 @@ class UploadedFile implements UploadedFileInterface } $this->error = $errorStatus; - $this->clientFilename = $clientFilename; + if ($clientFilename !== null) { + $clientFilename = \Normalizer::normalize($clientFilename); + } + $this->clientFilename = is_string($clientFilename) ? $clientFilename : null; $this->clientMediaType = $clientMediaType; } diff --git a/typo3/sysext/core/Classes/Resource/ResourceStorage.php b/typo3/sysext/core/Classes/Resource/ResourceStorage.php index 8c6879412b2f..bdfcafa48295 100644 --- a/typo3/sysext/core/Classes/Resource/ResourceStorage.php +++ b/typo3/sysext/core/Classes/Resource/ResourceStorage.php @@ -2098,7 +2098,7 @@ class ResourceStorage implements ResourceStorageInterface } else { $localFilePath = $uploadedFileData['tmp_name']; if ($targetFileName === null) { - $targetFileName = $uploadedFileData['name']; + $targetFileName = \Normalizer::normalize($uploadedFileData['name']); } $size = $uploadedFileData['size']; } diff --git a/typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php b/typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php index 8291fa248930..ea30d53b89d7 100644 --- a/typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php +++ b/typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php @@ -1049,6 +1049,7 @@ class ExtendedFileUtility extends BasicFileUtility 'size' => [$uploadedFileData['size']], ]; } + $uploadedFileData['name'] = array_map(\Normalizer::normalize(...), $uploadedFileData['name']); $resultObjects = []; $numberOfUploadedFilesForPosition = count($uploadedFileData['name']); // Loop through all uploaded files diff --git a/typo3/sysext/core/Tests/Unit/Http/UploadedFileTest.php b/typo3/sysext/core/Tests/Unit/Http/UploadedFileTest.php index c24c399381bc..517eebaa70ab 100644 --- a/typo3/sysext/core/Tests/Unit/Http/UploadedFileTest.php +++ b/typo3/sysext/core/Tests/Unit/Http/UploadedFileTest.php @@ -180,4 +180,15 @@ final class UploadedFileTest extends UnitTestCase $this->expectExceptionCode(1436717306); $upload->getStream(); } + + /** + * see https://en.wikipedia.org/wiki/Unicode_equivalence#Normalization, "NFD" + * @test + */ + public function nfdFileNameIsNormalized(): void + { + $clientFileName = hex2bin('6fcc88') . '.png'; + $subject = new UploadedFile(fopen('php://temp', 'wb+'), 0, 0, $clientFileName); + self::assertSame(hex2bin('c3b6') . '.png', $subject->getClientFilename()); + } } diff --git a/typo3/sysext/core/composer.json b/typo3/sysext/core/composer.json index a60489d95098..294a0358f623 100644 --- a/typo3/sysext/core/composer.json +++ b/typo3/sysext/core/composer.json @@ -66,6 +66,7 @@ "symfony/messenger": "^6.2", "symfony/mime": "^6.2", "symfony/options-resolver": "^6.2", + "symfony/polyfill-intl-normalizer": "^1.27", "symfony/rate-limiter": "^6.2", "symfony/routing": "^6.2", "symfony/uid": "^6.2", -- GitLab