From 2ffd783888953f045e13cc54cda5c6a95c2383e4 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 30 Jul 2026 15:59:54 +0200 Subject: [PATCH 1/2] fix: make trashbin filename truncate multi-byte character aware Signed-off-by: Robin Appelman --- apps/files_trashbin/lib/Trashbin.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index bdd7323d4a2a5..652586a3039d7 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -1211,12 +1211,13 @@ public static function getTrashFilename(string $filename, int $timestamp): strin // oc_filecache `name` column has a limit of 250 chars $maxLength = 250; if ($length > $maxLength) { - $trashFilename = substr_replace( - $trashFilename, - '', - $maxLength / 2, - $length - $maxLength - ); + // truncate at the middle, since the last characters are fairly likely to have meaningful information such as version numbering + + $charsToRemove = $length - $maxLength + 1; + $charLength = mb_strlen($trashFilename); + $start = mb_substr($trashFilename, 0, intdiv($charLength, 2) - $charsToRemove); + $end = mb_substr($trashFilename, intdiv($charLength, 2)); + return $start . '_' . $end; } return $trashFilename; } From b9d7662172ca3d3a782747597fa5625c793f80c3 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 31 Jul 2026 17:39:48 +0200 Subject: [PATCH 2/2] test: add tests for trashbin filename truncate Signed-off-by: Robin Appelman --- apps/files_trashbin/tests/StorageTest.php | 4 ++-- apps/files_trashbin/tests/TrashbinTest.php | 27 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/files_trashbin/tests/StorageTest.php b/apps/files_trashbin/tests/StorageTest.php index ac5698c51afa5..644c74a76816d 100644 --- a/apps/files_trashbin/tests/StorageTest.php +++ b/apps/files_trashbin/tests/StorageTest.php @@ -226,7 +226,7 @@ public function testSingleStorageDeleteFolder(): void { * Test that deleting a file with a long filename puts it into the trashbin. */ public function testSingleStorageDeleteLongFilename(): void { - $truncatedFilename = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt'; + $truncatedFilename = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt'; $this->assertTrue($this->userView->file_exists(static::LONG_FILENAME)); $this->userView->unlink(static::LONG_FILENAME); @@ -245,7 +245,7 @@ public function testSingleStorageDeleteLongFilename(): void { * Test that deleting a file with the max filename length puts it into the trashbin. */ public function testSingleStorageDeleteMaxLengthFilename(): void { - $truncatedFilename = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt'; + $truncatedFilename = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt'; $this->assertTrue($this->userView->file_exists(static::MAX_FILENAME)); $this->userView->unlink(static::MAX_FILENAME); diff --git a/apps/files_trashbin/tests/TrashbinTest.php b/apps/files_trashbin/tests/TrashbinTest.php index b6bb552517773..d71610eb05dc5 100644 --- a/apps/files_trashbin/tests/TrashbinTest.php +++ b/apps/files_trashbin/tests/TrashbinTest.php @@ -32,6 +32,7 @@ use OCP\IUserManager; use OCP\Server; use OCP\Share\IShare; +use PHPUnit\Framework\Attributes\DataProvider; /** * Class Test_Encryption @@ -706,6 +707,32 @@ public static function loginHelper($user, $create = false) { \OC_Util::setupFS($user); Server::get(IRootFolder::class)->getUserFolder($user); } + + public static function trashFilenameProvider(): array { + return [ + ['foo.txt', 'foo.txt.d1234'], + [ + 'a_very_long_filename_with_a_lot_a_characters_such_that_it_reaches_the_file_length_limit_and_would_cause_issues_if_we_just_appended_the_' + . 'timestamp_because_then_the_combined_length_would_overflow_the_column_limit_of_the_filecache_and_truncate_in_db.txt', + 'a_very_long_filename_with_a_lot_a_characters_such_that_it_reaches_the_file_length_limit_and_would_cause_issues_if_we_just_ded_the_' + . 'timestamp_because_then_the_combined_length_would_overflow_the_column_limit_of_the_filecache_and_truncate_in_db.txt.d1234' + ], + [ + 'a_very_long_filename_with_a_lot_a_characters_such_that_it_reaches_the_file_length_limit_and_would_cause_issues_if_we_just_äøšá_the_' + . 'timestamp_because_then_the_combined_length_would_overflow_the_column_limit_of_the_filecache_and_truncate_in_db.txt', + 'a_very_long_filename_with_a_lot_a_characters_such_that_it_reaches_the_file_length_limit_and_would_cause_issues_if_we_ju_á_the_' + . 'timestamp_because_then_the_combined_length_would_overflow_the_column_limit_of_the_filecache_and_truncate_in_db.txt.d1234' + ], + ]; + } + + #[DataProvider(methodName: 'trashFilenameProvider')] + public function testGetTrashFilename(string $filename, string $expected): void { + $result = Trashbin::getTrashFilename($filename, 1234); + $this->assertTrue(mb_check_encoding($result, 'UTF-8')); + $this->assertEquals($expected, $result); + $this->assertTrue(strlen($result) <= 250); + } } // just a dummy class to make protected methods available for testing