From cb63c46abec603f717a5f3b0aa39962581a0f868 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 29 Oct 2026 16:24:44 +0100 Subject: [PATCH] feat(files): expose forbidden filename capabilities publicly Move the forbidden filename capabilities into a separate `IPublicCapability` so that they are also returned for public requests, e.g. on public shares or for API clients that are not authenticated yet. Fixes: https://github.com/nextcloud/server/issues/58584 Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Ferdinand Thiessen --- .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + apps/files/lib/AppInfo/Application.php | 2 + apps/files/lib/Capabilities.php | 11 +-- apps/files/lib/PublicCapabilities.php | 40 ++++++++ apps/files/openapi.json | 95 ++++++++++-------- apps/files/tests/PublicCapabilitiesTest.php | 49 ++++++++++ openapi.json | 98 +++++++++++-------- 8 files changed, 207 insertions(+), 90 deletions(-) create mode 100644 apps/files/lib/PublicCapabilities.php create mode 100644 apps/files/tests/PublicCapabilitiesTest.php diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php index a005578aa2428..4963fa78aaccd 100644 --- a/apps/files/composer/composer/autoload_classmap.php +++ b/apps/files/composer/composer/autoload_classmap.php @@ -87,6 +87,7 @@ 'OCA\\Files\\Migration\\Version12101Date20221011153334' => $baseDir . '/../lib/Migration/Version12101Date20221011153334.php', 'OCA\\Files\\Migration\\Version2003Date20241021095629' => $baseDir . '/../lib/Migration/Version2003Date20241021095629.php', 'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php', + 'OCA\\Files\\PublicCapabilities' => $baseDir . '/../lib/PublicCapabilities.php', 'OCA\\Files\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php', 'OCA\\Files\\Search\\FileAccountScopedSearchProvider' => $baseDir . '/../lib/Search/FileAccountScopedSearchProvider.php', 'OCA\\Files\\Search\\FilesSearchProvider' => $baseDir . '/../lib/Search/FilesSearchProvider.php', diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php index 6f0f0d4cc602f..41d1ecf9cc709 100644 --- a/apps/files/composer/composer/autoload_static.php +++ b/apps/files/composer/composer/autoload_static.php @@ -102,6 +102,7 @@ class ComposerStaticInitFiles 'OCA\\Files\\Migration\\Version12101Date20221011153334' => __DIR__ . '/..' . '/../lib/Migration/Version12101Date20221011153334.php', 'OCA\\Files\\Migration\\Version2003Date20241021095629' => __DIR__ . '/..' . '/../lib/Migration/Version2003Date20241021095629.php', 'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php', + 'OCA\\Files\\PublicCapabilities' => __DIR__ . '/..' . '/../lib/PublicCapabilities.php', 'OCA\\Files\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php', 'OCA\\Files\\Search\\FileAccountScopedSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FileAccountScopedSearchProvider.php', 'OCA\\Files\\Search\\FilesSearchProvider' => __DIR__ . '/..' . '/../lib/Search/FilesSearchProvider.php', diff --git a/apps/files/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php index 56fcc6ce2c4aa..9fb6fddbdbe99 100644 --- a/apps/files/lib/AppInfo/Application.php +++ b/apps/files/lib/AppInfo/Application.php @@ -35,6 +35,7 @@ use OCA\Files\Listener\SyncLivePhotosListener; use OCA\Files\Listener\UserFirstTimeLoggedInListener; use OCA\Files\Notification\Notifier; +use OCA\Files\PublicCapabilities; use OCA\Files\Search\FileAccountScopedSearchProvider; use OCA\Files\Search\FilesSearchProvider; use OCA\Files\Sharing\Permission\NodeCreateSharePermissionType; @@ -79,6 +80,7 @@ public function register(IRegistrationContext $context): void { $context->registerCapability(Capabilities::class); $context->registerCapability(AdvancedCapabilities::class); $context->registerCapability(DirectEditingCapabilities::class); + $context->registerCapability(PublicCapabilities::class); $context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class); $context->registerEventListener(RenderReferenceEvent::class, RenderReferenceEventListener::class); diff --git a/apps/files/lib/Capabilities.php b/apps/files/lib/Capabilities.php index 1b6a3695dce7b..86c12b229b269 100644 --- a/apps/files/lib/Capabilities.php +++ b/apps/files/lib/Capabilities.php @@ -10,7 +10,6 @@ namespace OCA\Files; -use OC\Files\FilenameValidator; use OCA\Files\Service\ChunkedUploadConfig; use OCP\Capabilities\ICapability; use OCP\Files\Conversion\ConversionMimeProvider; @@ -19,7 +18,6 @@ class Capabilities implements ICapability { public function __construct( - protected FilenameValidator $filenameValidator, protected IConversionManager $fileConversionManager, ) { } @@ -27,19 +25,12 @@ public function __construct( /** * Return this classes capabilities * - * @return array{files: array{'$comment': ?string, bigfilechunking: bool, blacklisted_files: list, forbidden_filenames: list, forbidden_filename_basenames: list, forbidden_filename_characters: list, forbidden_filename_extensions: list, chunked_upload: array{max_size: int, max_parallel_count: int}, file_conversions: list}} + * @return array{files: array{bigfilechunking: bool, chunked_upload: array{max_size: int, max_parallel_count: int}, file_conversions: list}} */ #[\Override] public function getCapabilities(): array { return [ 'files' => [ - '$comment' => '"blacklisted_files" is deprecated as of Nextcloud 30, use "forbidden_filenames" instead', - 'blacklisted_files' => $this->filenameValidator->getForbiddenFilenames(), - 'forbidden_filenames' => $this->filenameValidator->getForbiddenFilenames(), - 'forbidden_filename_basenames' => $this->filenameValidator->getForbiddenBasenames(), - 'forbidden_filename_characters' => $this->filenameValidator->getForbiddenCharacters(), - 'forbidden_filename_extensions' => $this->filenameValidator->getForbiddenExtensions(), - 'bigfilechunking' => true, 'chunked_upload' => [ 'max_size' => ChunkedUploadConfig::getMaxChunkSize(), diff --git a/apps/files/lib/PublicCapabilities.php b/apps/files/lib/PublicCapabilities.php new file mode 100644 index 0000000000000..b2095c29ecaf3 --- /dev/null +++ b/apps/files/lib/PublicCapabilities.php @@ -0,0 +1,40 @@ +, forbidden_filenames: list, forbidden_filename_basenames: list, forbidden_filename_characters: list, forbidden_filename_extensions: list}} + */ + #[\Override] + public function getCapabilities(): array { + return [ + 'files' => [ + '$comment' => '"blacklisted_files" is deprecated as of Nextcloud 30, use "forbidden_filenames" instead', + 'blacklisted_files' => $this->filenameValidator->getForbiddenFilenames(), + 'forbidden_filenames' => $this->filenameValidator->getForbiddenFilenames(), + 'forbidden_filename_basenames' => $this->filenameValidator->getForbiddenBasenames(), + 'forbidden_filename_characters' => $this->filenameValidator->getForbiddenCharacters(), + 'forbidden_filename_extensions' => $this->filenameValidator->getForbiddenExtensions(), + ], + ]; + } +} diff --git a/apps/files/openapi.json b/apps/files/openapi.json index 71ddab3ec8868..089fa6fc236aa 100644 --- a/apps/files/openapi.json +++ b/apps/files/openapi.json @@ -30,13 +30,7 @@ "type": "object", "required": [ "windows_compatible_filenames", - "$comment", "bigfilechunking", - "blacklisted_files", - "forbidden_filenames", - "forbidden_filename_basenames", - "forbidden_filename_characters", - "forbidden_filename_extensions", "chunked_upload", "file_conversions", "directEditing" @@ -45,43 +39,9 @@ "windows_compatible_filenames": { "type": "boolean" }, - "$comment": { - "type": "string", - "nullable": true - }, "bigfilechunking": { "type": "boolean" }, - "blacklisted_files": { - "type": "array", - "items": { - "type": "object" - } - }, - "forbidden_filenames": { - "type": "array", - "items": { - "type": "string" - } - }, - "forbidden_filename_basenames": { - "type": "array", - "items": { - "type": "string" - } - }, - "forbidden_filename_characters": { - "type": "array", - "items": { - "type": "string" - } - }, - "forbidden_filename_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, "chunked_upload": { "type": "object", "required": [ @@ -201,6 +161,61 @@ } } }, + "PublicCapabilities": { + "type": "object", + "required": [ + "files" + ], + "properties": { + "files": { + "type": "object", + "required": [ + "$comment", + "blacklisted_files", + "forbidden_filenames", + "forbidden_filename_basenames", + "forbidden_filename_characters", + "forbidden_filename_extensions" + ], + "properties": { + "$comment": { + "type": "string", + "nullable": true + }, + "blacklisted_files": { + "type": "array", + "items": { + "type": "object" + } + }, + "forbidden_filenames": { + "type": "array", + "items": { + "type": "string" + } + }, + "forbidden_filename_basenames": { + "type": "array", + "items": { + "type": "string" + } + }, + "forbidden_filename_characters": { + "type": "array", + "items": { + "type": "string" + } + }, + "forbidden_filename_extensions": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, "Template": { "type": "object", "required": [ diff --git a/apps/files/tests/PublicCapabilitiesTest.php b/apps/files/tests/PublicCapabilitiesTest.php new file mode 100644 index 0000000000000..6e26534ef57f6 --- /dev/null +++ b/apps/files/tests/PublicCapabilitiesTest.php @@ -0,0 +1,49 @@ +filenameValidator = $this->createMock(FilenameValidator::class); + $this->capabilities = new PublicCapabilities($this->filenameValidator); + } + + public function testIsPublicCapability(): void { + self::assertInstanceOf(IPublicCapability::class, $this->capabilities); + } + + public function testGetCapabilities(): void { + $this->filenameValidator->method('getForbiddenFilenames')->willReturn(['.htaccess']); + $this->filenameValidator->method('getForbiddenBasenames')->willReturn(['con', 'prn']); + $this->filenameValidator->method('getForbiddenCharacters')->willReturn(['/', ':']); + $this->filenameValidator->method('getForbiddenExtensions')->willReturn(['.part']); + + self::assertEquals([ + 'files' => [ + '$comment' => '"blacklisted_files" is deprecated as of Nextcloud 30, use "forbidden_filenames" instead', + 'blacklisted_files' => ['.htaccess'], + 'forbidden_filenames' => ['.htaccess'], + 'forbidden_filename_basenames' => ['con', 'prn'], + 'forbidden_filename_characters' => ['/', ':'], + 'forbidden_filename_extensions' => ['.part'], + ], + ], $this->capabilities->getCapabilities()); + } +} diff --git a/openapi.json b/openapi.json index 9e3d52de88c59..805bb2f022ab2 100644 --- a/openapi.json +++ b/openapi.json @@ -1764,13 +1764,7 @@ "type": "object", "required": [ "windows_compatible_filenames", - "$comment", "bigfilechunking", - "blacklisted_files", - "forbidden_filenames", - "forbidden_filename_basenames", - "forbidden_filename_characters", - "forbidden_filename_extensions", "chunked_upload", "file_conversions", "directEditing" @@ -1779,43 +1773,9 @@ "windows_compatible_filenames": { "type": "boolean" }, - "$comment": { - "type": "string", - "nullable": true - }, "bigfilechunking": { "type": "boolean" }, - "blacklisted_files": { - "type": "array", - "items": { - "type": "object" - } - }, - "forbidden_filenames": { - "type": "array", - "items": { - "type": "string" - } - }, - "forbidden_filename_basenames": { - "type": "array", - "items": { - "type": "string" - } - }, - "forbidden_filename_characters": { - "type": "array", - "items": { - "type": "string" - } - }, - "forbidden_filename_extensions": { - "type": "array", - "items": { - "type": "string" - } - }, "chunked_upload": { "type": "object", "required": [ @@ -1911,6 +1871,61 @@ } } }, + "FilesPublicCapabilities": { + "type": "object", + "required": [ + "files" + ], + "properties": { + "files": { + "type": "object", + "required": [ + "$comment", + "blacklisted_files", + "forbidden_filenames", + "forbidden_filename_basenames", + "forbidden_filename_characters", + "forbidden_filename_extensions" + ], + "properties": { + "$comment": { + "type": "string", + "nullable": true + }, + "blacklisted_files": { + "type": "array", + "items": { + "type": "object" + } + }, + "forbidden_filenames": { + "type": "array", + "items": { + "type": "string" + } + }, + "forbidden_filename_basenames": { + "type": "array", + "items": { + "type": "string" + } + }, + "forbidden_filename_characters": { + "type": "array", + "items": { + "type": "string" + } + }, + "forbidden_filename_extensions": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, "FilesTemplate": { "type": "object", "required": [ @@ -8046,6 +8061,9 @@ { "$ref": "#/components/schemas/FilesCapabilities" }, + { + "$ref": "#/components/schemas/FilesPublicCapabilities" + }, { "$ref": "#/components/schemas/FilesSharingCapabilities" },