From 4c836424dcff464fc9b6a8a2d20d05dc514fdec7 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 28 Jul 2026 20:58:07 +0200 Subject: [PATCH 1/4] fix: prevent restoring a file that's larger than the free space Signed-off-by: Robin Appelman --- apps/files_trashbin/lib/Trashbin.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index c822d4869cbc1..41301c84f41d6 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -37,6 +37,7 @@ use OCP\Files\IMimeTypeLoader; use OCP\Files\IRootFolder; use OCP\Files\Node; +use OCP\Files\NotEnoughSpaceException; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\Files\Storage\ILockingStorage; @@ -566,6 +567,14 @@ public static function restore($file, $filename, $timestamp) { $sourceNode = self::getNodeForPath($user, $sourcePath); $targetNode = self::getNodeForPath($user, $targetPath, 'files'); + + $targetParent = $targetNode->getParent(); + + $free = $targetParent->getFreeSpace(); + if ($free >= 0 && $free < $sourceNode->getSize(false)) { + throw new NotEnoughSpaceException('Not enough free space in ' . $targetParent->getPath() . ' to restore ' . $sourceNode->getPath()); + } + $run = true; $event = new BeforeNodeRestoredEvent($sourceNode, $targetNode, $run); $dispatcher = Server::get(IEventDispatcher::class); From e20699269247db378b8ded5397b5dca728473f42 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 28 Jul 2026 21:31:35 +0200 Subject: [PATCH 2/4] fix: check free space in the proper folder when restoring from trash Signed-off-by: Robin Appelman --- apps/files_trashbin/lib/Sabre/TrashbinPlugin.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php b/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php index c3ca8a832389d..274d73eb7d1d3 100644 --- a/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php +++ b/apps/files_trashbin/lib/Sabre/TrashbinPlugin.php @@ -13,6 +13,8 @@ use OC\Files\View; use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\Files_Trashbin\Trash\ITrashItem; +use OCP\Files\IRootFolder; +use OCP\Files\Mount\IMountManager; use OCP\IPreview; use Psr\Log\LoggerInterface; use Sabre\DAV\INode; @@ -43,7 +45,8 @@ class TrashbinPlugin extends ServerPlugin { public function __construct( private readonly IPreview $previewManager, - private readonly View $view, + private readonly IRootFolder $rootFolder, + private readonly IMountManager $mountManager, ) { } @@ -175,7 +178,16 @@ public function beforeMove(string $sourcePath, string $destinationPath): bool { return true; } - $freeSpace = $this->view->free_space($destinationParentPath); + $userFolder = $this->rootFolder->getUserFolder($fileInfo->getUser()->getUID()); + $originalPath = $userFolder->getFullPath(dirname($fileInfo->getOriginalLocation())); + + // Since the parent folder might no longer exist, we don't try to get the parent node to check the free space + // instead we resolve the mount where the restore would go to, and check that for the free space + $originalMount = $this->mountManager->find($originalPath); + if (!$originalMount) { + throw new \Exception('no mount found when looking for restore path'); + } + $freeSpace = $originalMount->getStorage()->free_space($originalMount->getInternalPath($originalPath)); if ( $freeSpace === FileInfo::SPACE_NOT_COMPUTED From be3c3b4a652f80b29bdf6bfd4f11514736b1a074 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 3 Aug 2026 23:42:04 +0200 Subject: [PATCH 3/4] test: adjust tests to new trashbin quota check Signed-off-by: Robin Appelman --- .../tests/Sabre/TrashbinPluginTest.php | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/apps/files_trashbin/tests/Sabre/TrashbinPluginTest.php b/apps/files_trashbin/tests/Sabre/TrashbinPluginTest.php index 5feda386350d0..001e3bcb5ba1d 100644 --- a/apps/files_trashbin/tests/Sabre/TrashbinPluginTest.php +++ b/apps/files_trashbin/tests/Sabre/TrashbinPluginTest.php @@ -9,12 +9,17 @@ namespace OCA\Files_Trashbin\Tests\Sabre; use OC\Files\FileInfo; -use OC\Files\View; use OCA\Files_Trashbin\Sabre\ITrash; use OCA\Files_Trashbin\Sabre\RestoreFolder; use OCA\Files_Trashbin\Sabre\TrashbinPlugin; use OCA\Files_Trashbin\Trash\ITrashItem; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\Mount\IMountManager; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\Storage\IStorage; use OCP\IPreview; +use OCP\IUser; use Sabre\DAV\Server; use Sabre\DAV\Tree; use Test\TestCase; @@ -34,6 +39,13 @@ public function testQuota(int $quota, int $fileSize, bool $expectedResult): void $fileInfo = $this->createMock(ITrashItem::class); $fileInfo->method('getSize') ->willReturn($fileSize); + $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn('test'); + $fileInfo->method('getUser') + ->willReturn($user); + $fileInfo->method('getOriginalLocation') + ->willReturn('relative/original/location'); $trashNode = $this->createMock(ITrash::class); $trashNode->method('getFileInfo') @@ -46,11 +58,30 @@ public function testQuota(int $quota, int $fileSize, bool $expectedResult): void $previewManager = $this->createMock(IPreview::class); - $view = $this->createMock(View::class); - $view->method('free_space') + $userFolder = $this->createMock(Folder::class); + $userFolder->method('getFullPath') + ->with('relative/original') // the parent path + ->willReturn('/full/path/to/original'); + $rootFolder = $this->createMock(IRootFolder::class); + $rootFolder->method('getUserFolder') + ->willReturn($userFolder); + + $storage = $this->createMock(IStorage::class); + $storage->method('free_space') + ->with('path/to/original') ->willReturn($quota); + $mount = $this->createMock(IMountPoint::class); + $mount->method('getStorage') + ->willReturn($storage); + $mount->method('getInternalPath') + ->with('/full/path/to/original') + ->willReturn('path/to/original'); + $mountManager = $this->createMock(IMountManager::class); + $mountManager->method('find') + ->with('/full/path/to/original') + ->willReturn($mount); - $plugin = new TrashbinPlugin($previewManager, $view); + $plugin = new TrashbinPlugin($previewManager, $rootFolder, $mountManager); $plugin->initialize($this->server); $sourcePath = 'trashbin/test/trash/file1'; @@ -60,11 +91,11 @@ public function testQuota(int $quota, int $fileSize, bool $expectedResult): void public static function quotaProvider(): array { return [ - [ 1024 * 1024, 512 * 1024, true ], - [ 512 * 1024, 513 * 1024, false ], - [ FileInfo::SPACE_NOT_COMPUTED, 1024 * 1024, true ], - [ FileInfo::SPACE_UNKNOWN, 1024 * 1024, true ], - [ FileInfo::SPACE_UNLIMITED, 1024 * 1024, true ] + [1024 * 1024, 512 * 1024, true], + [512 * 1024, 513 * 1024, false], + [FileInfo::SPACE_NOT_COMPUTED, 1024 * 1024, true], + [FileInfo::SPACE_UNKNOWN, 1024 * 1024, true], + [FileInfo::SPACE_UNLIMITED, 1024 * 1024, true] ]; } } From c14a58d1d19d24ac716a66060915087e61b6f36d Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 4 Aug 2026 00:38:35 +0200 Subject: [PATCH 4/4] chore: update baseline Signed-off-by: Robin Appelman --- build/psalm-baseline.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 80c812273eb5a..7c08a3e6931f4 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -1892,11 +1892,6 @@ - - - - -