From 6ff7594388d1c2ded5df39a3f01bdd1d276ef185 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 19 Oct 2026 09:45:42 +0200 Subject: [PATCH 1/2] fix(sharing): load mind maps from subfolders of public shares The public load endpoint joined the folder and the file name without a separator, so a mind map in a subfolder of a public folder share was looked up as /Subfoldernotes.km and failed with "File not found". Join them with a slash, as the logged-in endpoint does. A path outside of the share made Folder::get() throw NotPermittedException, which ended in a server error; answer it with "File not found" too. Fixes #145 Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger (cherry picked from commit ef1105e8af03d1547fd02a9ce187a643b83aca9b) --- .../PublicFileHandlingController.php | 11 ++++--- .../PublicFileHandlingControllerTest.php | 33 ++++++++++++++++--- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/lib/Controller/PublicFileHandlingController.php b/lib/Controller/PublicFileHandlingController.php index 6ae8045..62941d6 100644 --- a/lib/Controller/PublicFileHandlingController.php +++ b/lib/Controller/PublicFileHandlingController.php @@ -16,6 +16,7 @@ use OCP\Files\ForbiddenException; use OCP\Files\GenericFileException; use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; use OCP\IL10N; use OCP\IRequest; use OCP\ISession; @@ -113,15 +114,15 @@ public function load($token) { $fileNode = $node; if ($node instanceof Folder) { - $dir = $this->request->getParam('dir'); - $filename = $this->request->getParam('filename'); - $fullpath = trim($dir.$filename); - if (!$fullpath) { + $dir = (string)$this->request->getParam('dir'); + $filename = (string)$this->request->getParam('filename'); + if ($filename === '') { return new DataResponse(['message' => $this->l->t('File not found')], Http::STATUS_NOT_FOUND); } + $fullpath = trim(rtrim($dir, '/') . '/' . $filename); try { $fileNode = $node->get($fullpath); - } catch (NotFoundException $e) { + } catch (NotFoundException|NotPermittedException $e) { return new DataResponse(['message' => $this->l->t('File not found')], Http::STATUS_NOT_FOUND); } } diff --git a/tests/Unit/Controller/PublicFileHandlingControllerTest.php b/tests/Unit/Controller/PublicFileHandlingControllerTest.php index 63f0137..280de3d 100644 --- a/tests/Unit/Controller/PublicFileHandlingControllerTest.php +++ b/tests/Unit/Controller/PublicFileHandlingControllerTest.php @@ -14,6 +14,7 @@ use OCP\Constants; use OCP\Files\File; use OCP\Files\Folder; +use OCP\Files\NotPermittedException; use OCP\IL10N; use OCP\IRequest; use OCP\ISession; @@ -31,13 +32,17 @@ class PublicFileHandlingControllerTest extends TestCase { protected function setUp(): void { parent::setUp(); + $this->shareManager = $this->createMock(IManager::class); + $this->session = $this->createMock(ISession::class); + $this->createController('/', 'secret.km'); + } + + private function createController(string $dir, string $filename): void { $request = $this->createMock(IRequest::class); $request->method('getParam')->willReturnMap([ - ['dir', null, '/'], - ['filename', null, 'secret.km'], + ['dir', null, $dir], + ['filename', null, $filename], ]); - $this->shareManager = $this->createMock(IManager::class); - $this->session = $this->createMock(ISession::class); $l10n = $this->createMock(IL10N::class); $l10n->method('t')->willReturnArgument(0); @@ -72,6 +77,26 @@ public function testLoadFromReadableShare(): void { $this->assertSame(base64_encode('{"root":{}}'), $response->getData()['filecontents']); } + public function testLoadFromSubfolderOfShare(): void { + $this->createController('/Sub folder', 'notes.km'); + $file = $this->createMock(File::class); + $file->method('getContent')->willReturn('{"root":{}}'); + $folder = $this->createMock(Folder::class); + $folder->method('get')->with('/Sub folder/notes.km')->willReturn($file); + $this->mockShare(Constants::PERMISSION_READ, $folder); + + $this->assertSame(Http::STATUS_OK, $this->controller->load('token')->getStatus()); + } + + public function testLoadOutsideOfShareIsNotFound(): void { + $this->createController('/../', 'notes.km'); + $folder = $this->createMock(Folder::class); + $folder->method('get')->willThrowException(new NotPermittedException()); + $this->mockShare(Constants::PERMISSION_READ, $folder); + + $this->assertSame(Http::STATUS_NOT_FOUND, $this->controller->load('token')->getStatus()); + } + public function testLoadFromPasswordProtectedShareWithoutSessionAuthIsRefused(): void { $this->mockShare(password: 'secret', id: '42'); $this->session->method('get')->with('public_link_authenticated')->willReturn(['1', '2']); From 39a1a1c047440657470b7dfc693b84018f3441a0 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Mon, 19 Oct 2026 11:43:33 +0200 Subject: [PATCH 2/2] fix(sharing): answer saves outside of a public share with not found Saving to a path outside of a public folder share made Folder::get() throw NotPermittedException, which ended in a server error. Nothing was written, but answer it with "File not found" like the load endpoint. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger (cherry picked from commit 53ca29ea80ab142fd35ceb18efa768ac650e024f) --- lib/Controller/PublicFileHandlingController.php | 2 +- .../Controller/PublicFileHandlingControllerTest.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/Controller/PublicFileHandlingController.php b/lib/Controller/PublicFileHandlingController.php index 62941d6..d575efc 100644 --- a/lib/Controller/PublicFileHandlingController.php +++ b/lib/Controller/PublicFileHandlingController.php @@ -201,7 +201,7 @@ public function save($token, $filecontents, $path, $mtime) { } try { $file = $node->get($fullpath); - } catch (NotFoundException $e) { + } catch (NotFoundException|NotPermittedException $e) { return new DataResponse(['message' => $this->l->t('File not found')], Http::STATUS_NOT_FOUND); } } diff --git a/tests/Unit/Controller/PublicFileHandlingControllerTest.php b/tests/Unit/Controller/PublicFileHandlingControllerTest.php index 280de3d..c4a23ac 100644 --- a/tests/Unit/Controller/PublicFileHandlingControllerTest.php +++ b/tests/Unit/Controller/PublicFileHandlingControllerTest.php @@ -124,6 +124,16 @@ public function testSaveFromPasswordProtectedShareWithoutSessionAuthIsRefused(): $this->assertSame(Http::STATUS_BAD_REQUEST, $response->getStatus()); } + public function testSaveOutsideOfShareIsNotFound(): void { + $folder = $this->createMock(Folder::class); + $folder->method('get')->willThrowException(new NotPermittedException()); + $this->mockShare(Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE, $folder); + + $response = $this->controller->save('token', 'data', '/../notes.km', 123); + + $this->assertSame(Http::STATUS_NOT_FOUND, $response->getStatus()); + } + private function mockShare(int $permissions = 0, ?Folder $node = null, ?string $password = null, string $id = '1'): IShare&MockObject { $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn($password);