From 2e662fc5eba697db59e7b250e6f6d6b85c11460b Mon Sep 17 00:00:00 2001 From: Git'Fellow <12234510+solracsf@users.noreply.github.com> Date: Fri, 29 May 2026 17:59:38 +0200 Subject: [PATCH] fix(files_versions): guard null path in event listeners Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com> --- .../lib/Listener/FileEventsListener.php | 15 +++ .../tests/Listener/FileEventsListenerTest.php | 94 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 apps/files_versions/tests/Listener/FileEventsListenerTest.php diff --git a/apps/files_versions/lib/Listener/FileEventsListener.php b/apps/files_versions/lib/Listener/FileEventsListener.php index fc27e97eeaffc..85f91707c8e64 100644 --- a/apps/files_versions/lib/Listener/FileEventsListener.php +++ b/apps/files_versions/lib/Listener/FileEventsListener.php @@ -207,6 +207,9 @@ public function write_hook(Node $node): void { } $path = $this->getPathForNode($node); + if ($path === null) { + return; + } $result = Storage::store($path); // Store the result of the version creation so it can be used in post_write_hook. @@ -311,6 +314,9 @@ public function remove_hook(Node $node): void { $node = $this->versionsDeleted[$path]; $relativePath = $this->getPathForNode($node); unset($this->versionsDeleted[$path]); + if ($relativePath === null) { + return; + } Storage::delete($relativePath); // If no new version was stored in the FS, no new version should be added in the DB. // So we simply update the associated version. @@ -324,6 +330,9 @@ public function remove_hook(Node $node): void { */ public function pre_remove_hook(Node $node): void { $path = $this->getPathForNode($node); + if ($path === null) { + return; + } Storage::markDeletedFile($path); $this->versionsDeleted[$node->getPath()] = $node; } @@ -344,6 +353,9 @@ public function rename_hook(Node $source, Node $target): void { $oldPath = $this->getPathForNode($source); $newPath = $this->getPathForNode($target); + if ($oldPath === null || $newPath === null) { + return; + } Storage::renameOrCopy($oldPath, $newPath, 'rename'); } @@ -363,6 +375,9 @@ public function copy_hook(Node $source, Node $target): void { $oldPath = $this->getPathForNode($source); $newPath = $this->getPathForNode($target); + if ($oldPath === null || $newPath === null) { + return; + } Storage::renameOrCopy($oldPath, $newPath, 'copy'); } diff --git a/apps/files_versions/tests/Listener/FileEventsListenerTest.php b/apps/files_versions/tests/Listener/FileEventsListenerTest.php new file mode 100644 index 0000000000000..24f78d89ad908 --- /dev/null +++ b/apps/files_versions/tests/Listener/FileEventsListenerTest.php @@ -0,0 +1,94 @@ +rootFolder = $this->createMock(IRootFolder::class); + $this->versionManager = $this->createMock(IVersionManager::class); + $this->mimeTypeLoader = $this->createMock(IMimeTypeLoader::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->listener = new FileEventsListener( + $this->rootFolder, + $this->versionManager, + $this->mimeTypeLoader, + $this->userSession, + $this->logger, + ); + } + + private function createUnresolvableFile(): File&MockObject { + $this->userSession->method('getUser')->willReturn(null); + + $node = $this->createMock(File::class); + $node->method('getOwner')->willReturn(null); + $node->method('getPath')->willReturn('/test.txt'); + $node->method('getId')->willReturn(42); + $node->method('getSize')->willReturn(100); + $node->method('getMTime')->willReturn(1234567890); + + return $node; + } + + private function getPrivateProperty(string $property): mixed { + $ref = new \ReflectionProperty(FileEventsListener::class, $property); + $ref->setAccessible(true); + return $ref->getValue($this->listener); + } + + public function testGetPathForNodeReturnsNullWhenUnresolvable(): void { + $node = $this->createUnresolvableFile(); + + $this->logger->expects($this->once()) + ->method('debug') + ->with('Failed to compute path for node', $this->anything()); + + $method = new \ReflectionMethod(FileEventsListener::class, 'getPathForNode'); + $method->setAccessible(true); + + $this->assertNull($method->invoke($this->listener, $node)); + } + + public function testWriteHookSkipsWhenPathUnresolvable(): void { + $node = $this->createUnresolvableFile(); + + $this->listener->write_hook($node); + + $this->assertSame([], $this->getPrivateProperty('writeHookInfo')); + } + + public function testPreRemoveHookSkipsWhenPathUnresolvable(): void { + $node = $this->createUnresolvableFile(); + + $this->listener->pre_remove_hook($node); + + $this->assertSame([], $this->getPrivateProperty('versionsDeleted')); + } +}