aboutsummaryrefslogtreecommitdiffstats
path: root/lib/public/AppFramework
diff options
context:
space:
mode:
Diffstat (limited to 'lib/public/AppFramework')
-rw-r--r--lib/public/AppFramework/ApiController.php74
-rw-r--r--lib/public/AppFramework/App.php140
-rw-r--r--lib/public/AppFramework/Attribute/ASince.php34
-rw-r--r--lib/public/AppFramework/Attribute/Catchable.php23
-rw-r--r--lib/public/AppFramework/Attribute/Consumable.php27
-rw-r--r--lib/public/AppFramework/Attribute/Dispatchable.php23
-rw-r--r--lib/public/AppFramework/Attribute/ExceptionalImplementable.php38
-rw-r--r--lib/public/AppFramework/Attribute/Implementable.php27
-rw-r--r--lib/public/AppFramework/Attribute/Listenable.php23
-rw-r--r--lib/public/AppFramework/Attribute/Throwable.php23
-rw-r--r--lib/public/AppFramework/AuthPublicShareController.php235
-rw-r--r--lib/public/AppFramework/Bootstrap/IBootContext.php59
-rw-r--r--lib/public/AppFramework/Bootstrap/IBootstrap.php33
-rw-r--r--lib/public/AppFramework/Bootstrap/IRegistrationContext.php450
-rw-r--r--lib/public/AppFramework/Controller.php141
-rw-r--r--lib/public/AppFramework/Db/DoesNotExistException.php25
-rw-r--r--lib/public/AppFramework/Db/Entity.php314
-rw-r--r--lib/public/AppFramework/Db/IMapperException.php14
-rw-r--r--lib/public/AppFramework/Db/MultipleObjectsReturnedException.php25
-rw-r--r--lib/public/AppFramework/Db/QBMapper.php377
-rw-r--r--lib/public/AppFramework/Db/TTransactional.php88
-rw-r--r--lib/public/AppFramework/Http.php309
-rw-r--r--lib/public/AppFramework/Http/Attribute/ARateLimit.php43
-rw-r--r--lib/public/AppFramework/Http/Attribute/AnonRateLimit.php22
-rw-r--r--lib/public/AppFramework/Http/Attribute/ApiRoute.php47
-rw-r--r--lib/public/AppFramework/Http/Attribute/AppApiAdminAccessWithoutUser.php21
-rw-r--r--lib/public/AppFramework/Http/Attribute/AuthorizedAdminSetting.php40
-rw-r--r--lib/public/AppFramework/Http/Attribute/BruteForceProtection.php36
-rw-r--r--lib/public/AppFramework/Http/Attribute/CORS.php23
-rw-r--r--lib/public/AppFramework/Http/Attribute/ExAppRequired.php21
-rw-r--r--lib/public/AppFramework/Http/Attribute/FrontpageRoute.php47
-rw-r--r--lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php22
-rw-r--r--lib/public/AppFramework/Http/Attribute/NoAdminRequired.php21
-rw-r--r--lib/public/AppFramework/Http/Attribute/NoCSRFRequired.php21
-rw-r--r--lib/public/AppFramework/Http/Attribute/OpenAPI.php91
-rw-r--r--lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php38
-rw-r--r--lib/public/AppFramework/Http/Attribute/PublicPage.php21
-rw-r--r--lib/public/AppFramework/Http/Attribute/RequestHeader.php34
-rw-r--r--lib/public/AppFramework/Http/Attribute/Route.php145
-rw-r--r--lib/public/AppFramework/Http/Attribute/StrictCookiesRequired.php21
-rw-r--r--lib/public/AppFramework/Http/Attribute/SubAdminRequired.php21
-rw-r--r--lib/public/AppFramework/Http/Attribute/UseSession.php21
-rw-r--r--lib/public/AppFramework/Http/Attribute/UserRateLimit.php22
-rw-r--r--lib/public/AppFramework/Http/ContentSecurityPolicy.php90
-rw-r--r--lib/public/AppFramework/Http/DataDisplayResponse.php72
-rw-r--r--lib/public/AppFramework/Http/DataDownloadResponse.php56
-rw-r--r--lib/public/AppFramework/Http/DataResponse.php65
-rw-r--r--lib/public/AppFramework/Http/DownloadResponse.php37
-rw-r--r--lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php549
-rw-r--r--lib/public/AppFramework/Http/EmptyFeaturePolicy.php164
-rw-r--r--lib/public/AppFramework/Http/Events/BeforeLoginTemplateRenderedEvent.php35
-rw-r--r--lib/public/AppFramework/Http/Events/BeforeTemplateRenderedEvent.php49
-rw-r--r--lib/public/AppFramework/Http/FeaturePolicy.php41
-rw-r--r--lib/public/AppFramework/Http/FileDisplayResponse.php55
-rw-r--r--lib/public/AppFramework/Http/ICallbackResponse.php23
-rw-r--r--lib/public/AppFramework/Http/IOutput.php59
-rw-r--r--lib/public/AppFramework/Http/JSONResponse.php91
-rw-r--r--lib/public/AppFramework/Http/NotFoundResponse.php30
-rw-r--r--lib/public/AppFramework/Http/ParameterOutOfRangeException.php62
-rw-r--r--lib/public/AppFramework/Http/RedirectResponse.php44
-rw-r--r--lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php36
-rw-r--r--lib/public/AppFramework/Http/Response.php408
-rw-r--r--lib/public/AppFramework/Http/StandaloneTemplateResponse.php24
-rw-r--r--lib/public/AppFramework/Http/StreamResponse.php53
-rw-r--r--lib/public/AppFramework/Http/StrictContentSecurityPolicy.php70
-rw-r--r--lib/public/AppFramework/Http/StrictEvalContentSecurityPolicy.php33
-rw-r--r--lib/public/AppFramework/Http/StrictInlineContentSecurityPolicy.php33
-rw-r--r--lib/public/AppFramework/Http/Template/ExternalShareMenuAction.php29
-rw-r--r--lib/public/AppFramework/Http/Template/IMenuAction.php51
-rw-r--r--lib/public/AppFramework/Http/Template/LinkMenuAction.php26
-rw-r--r--lib/public/AppFramework/Http/Template/PublicTemplateResponse.php176
-rw-r--r--lib/public/AppFramework/Http/Template/SimpleMenuAction.php120
-rw-r--r--lib/public/AppFramework/Http/TemplateResponse.php197
-rw-r--r--lib/public/AppFramework/Http/TextPlainResponse.php47
-rw-r--r--lib/public/AppFramework/Http/TooManyRequestsResponse.php41
-rw-r--r--lib/public/AppFramework/Http/ZipResponse.php77
-rw-r--r--lib/public/AppFramework/IAppContainer.php56
-rw-r--r--lib/public/AppFramework/Middleware.php85
-rw-r--r--lib/public/AppFramework/OCS/OCSBadRequestException.php28
-rw-r--r--lib/public/AppFramework/OCS/OCSException.php17
-rw-r--r--lib/public/AppFramework/OCS/OCSForbiddenException.php28
-rw-r--r--lib/public/AppFramework/OCS/OCSNotFoundException.php28
-rw-r--r--lib/public/AppFramework/OCS/OCSPreconditionFailedException.php28
-rw-r--r--lib/public/AppFramework/OCSController.php107
-rw-r--r--lib/public/AppFramework/PublicShareController.php119
-rw-r--r--lib/public/AppFramework/QueryException.php23
-rw-r--r--lib/public/AppFramework/Services/IAppConfig.php324
-rw-r--r--lib/public/AppFramework/Services/IInitialState.php43
-rw-r--r--lib/public/AppFramework/Services/InitialStateProvider.php33
-rw-r--r--lib/public/AppFramework/Utility/IControllerMethodReflector.php59
-rw-r--r--lib/public/AppFramework/Utility/ITimeFactory.php51
91 files changed, 7252 insertions, 0 deletions
diff --git a/lib/public/AppFramework/ApiController.php b/lib/public/AppFramework/ApiController.php
new file mode 100644
index 00000000000..729582c8505
--- /dev/null
+++ b/lib/public/AppFramework/ApiController.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework;
+
+use OCP\AppFramework\Http\Attribute\NoAdminRequired;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\PublicPage;
+use OCP\AppFramework\Http\Response;
+use OCP\IRequest;
+
+/**
+ * Base class to inherit your controllers from that are used for RESTful APIs
+ * @since 7.0.0
+ */
+abstract class ApiController extends Controller {
+ private $corsMethods;
+ private $corsAllowedHeaders;
+ private $corsMaxAge;
+
+ /**
+ * constructor of the controller
+ * @param string $appName the name of the app
+ * @param IRequest $request an instance of the request
+ * @param string $corsMethods comma separated string of HTTP verbs which
+ * should be allowed for websites or webapps when calling your API, defaults to
+ * 'PUT, POST, GET, DELETE, PATCH'
+ * @param string $corsAllowedHeaders comma separated string of HTTP headers
+ * which should be allowed for websites or webapps when calling your API,
+ * defaults to 'Authorization, Content-Type, Accept'
+ * @param int $corsMaxAge number in seconds how long a preflighted OPTIONS
+ * request should be cached, defaults to 1728000 seconds
+ * @since 7.0.0
+ */
+ public function __construct($appName,
+ IRequest $request,
+ $corsMethods = 'PUT, POST, GET, DELETE, PATCH',
+ $corsAllowedHeaders = 'Authorization, Content-Type, Accept',
+ $corsMaxAge = 1728000) {
+ parent::__construct($appName, $request);
+ $this->corsMethods = $corsMethods;
+ $this->corsAllowedHeaders = $corsAllowedHeaders;
+ $this->corsMaxAge = $corsMaxAge;
+ }
+
+
+ /**
+ * This method implements a preflighted cors response for you that you can
+ * link to for the options request
+ *
+ * @since 7.0.0
+ */
+ #[NoCSRFRequired]
+ #[PublicPage]
+ #[NoAdminRequired]
+ public function preflightedCors() {
+ $origin = $this->request->getHeader('origin');
+ if ($origin === '') {
+ $origin = '*';
+ }
+
+ $response = new Response();
+ $response->addHeader('Access-Control-Allow-Origin', $origin);
+ $response->addHeader('Access-Control-Allow-Methods', $this->corsMethods);
+ $response->addHeader('Access-Control-Max-Age', (string)$this->corsMaxAge);
+ $response->addHeader('Access-Control-Allow-Headers', $this->corsAllowedHeaders);
+ $response->addHeader('Access-Control-Allow-Credentials', 'false');
+ return $response;
+ }
+}
diff --git a/lib/public/AppFramework/App.php b/lib/public/AppFramework/App.php
new file mode 100644
index 00000000000..c00fde47418
--- /dev/null
+++ b/lib/public/AppFramework/App.php
@@ -0,0 +1,140 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework;
+
+use OC\AppFramework\Utility\SimpleContainer;
+use OC\ServerContainer;
+use OCP\IConfig;
+use OCP\Server;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class App
+ *
+ * Any application must inherit this call - all controller instances to be used are
+ * to be registered using IContainer::registerService
+ * @since 6.0.0
+ */
+class App {
+ /** @var IAppContainer */
+ private $container;
+
+ /**
+ * Turns an app id into a namespace by convention. The id is split at the
+ * underscores, all parts are CamelCased and reassembled. e.g.:
+ * some_app_id -> OCA\SomeAppId
+ * @param string $appId the app id
+ * @param string $topNamespace the namespace which should be prepended to
+ * the transformed app id, defaults to OCA\
+ * @return string the starting namespace for the app
+ * @since 8.0.0
+ */
+ public static function buildAppNamespace(string $appId, string $topNamespace = 'OCA\\'): string {
+ return \OC\AppFramework\App::buildAppNamespace($appId, $topNamespace);
+ }
+
+
+ /**
+ * @param string $appName
+ * @param array $urlParams an array with variables extracted from the routes
+ * @since 6.0.0
+ */
+ public function __construct(string $appName, array $urlParams = []) {
+ $runIsSetupDirectly = Server::get(IConfig::class)->getSystemValueBool('debug')
+ && !ini_get('zend.exception_ignore_args');
+
+ if ($runIsSetupDirectly) {
+ $applicationClassName = get_class($this);
+ $e = new \RuntimeException('App class ' . $applicationClassName . ' is not setup via query() but directly');
+ $setUpViaQuery = false;
+
+ $classNameParts = explode('\\', trim($applicationClassName, '\\'));
+
+ foreach ($e->getTrace() as $step) {
+ if (isset($step['class'], $step['function'], $step['args'][0])
+ && $step['class'] === ServerContainer::class
+ && $step['function'] === 'query'
+ && $step['args'][0] === $applicationClassName) {
+ $setUpViaQuery = true;
+ break;
+ } elseif (isset($step['class'], $step['function'], $step['args'][0])
+ && $step['class'] === ServerContainer::class
+ && $step['function'] === 'getAppContainer'
+ && $step['args'][1] === $classNameParts[1]) {
+ $setUpViaQuery = true;
+ break;
+ } elseif (isset($step['class'], $step['function'], $step['args'][0])
+ && $step['class'] === SimpleContainer::class
+ && preg_match('/{closure:OC\\\\AppFramework\\\\Utility\\\\SimpleContainer::buildClass\\(\\):\\d+}/', $step['function'])
+ && $step['args'][0] === $this) {
+ /* We are setup through a lazy ghost, fine */
+ $setUpViaQuery = true;
+ break;
+ }
+ }
+
+ if (!$setUpViaQuery && $applicationClassName !== \OCP\AppFramework\App::class) {
+ Server::get(LoggerInterface::class)->error($e->getMessage(), [
+ 'app' => $appName,
+ 'exception' => $e,
+ ]);
+ }
+ }
+
+ try {
+ $this->container = \OC::$server->getRegisteredAppContainer($appName);
+ } catch (QueryException $e) {
+ $this->container = new \OC\AppFramework\DependencyInjection\DIContainer($appName, $urlParams);
+ }
+ }
+
+ /**
+ * @return IAppContainer
+ * @since 6.0.0
+ */
+ public function getContainer(): IAppContainer {
+ return $this->container;
+ }
+
+ /**
+ * This function is called by the routing component to fire up the frameworks dispatch mechanism.
+ *
+ * Example code in routes.php of the task app:
+ * $this->create('tasks_index', '/')->get()->action(
+ * function($params){
+ * $app = new TaskApp($params);
+ * $app->dispatch('PageController', 'index');
+ * }
+ * );
+ *
+ *
+ * Example for for TaskApp implementation:
+ * class TaskApp extends \OCP\AppFramework\App {
+ *
+ * public function __construct($params){
+ * parent::__construct('tasks', $params);
+ *
+ * $this->getContainer()->registerService('PageController', function(IAppContainer $c){
+ * $a = $c->query('API');
+ * $r = $c->query('Request');
+ * return new PageController($a, $r);
+ * });
+ * }
+ * }
+ *
+ * @param string $controllerName the name of the controller under which it is
+ * stored in the DI container
+ * @param string $methodName the method that you want to call
+ * @since 6.0.0
+ */
+ public function dispatch(string $controllerName, string $methodName) {
+ \OC\AppFramework\App::main($controllerName, $methodName, $this->container);
+ }
+}
diff --git a/lib/public/AppFramework/Attribute/ASince.php b/lib/public/AppFramework/Attribute/ASince.php
new file mode 100644
index 00000000000..1e0c45348cf
--- /dev/null
+++ b/lib/public/AppFramework/Attribute/ASince.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Attribute;
+
+use Attribute;
+
+/**
+ * Abstract base attribute to declare an API's stability.
+ *
+ * @since 32.0.0
+ */
+#[Consumable(since: '32.0.0')]
+abstract class ASince {
+ /**
+ * @param string $since For shipped apps and server code such as core/ and lib/,
+ * this should be the server version. For other apps it
+ * should be the semantic app version.
+ */
+ public function __construct(
+ protected string $since,
+ ) {
+ }
+
+ public function getSince(): string {
+ return $this->since;
+ }
+}
diff --git a/lib/public/AppFramework/Attribute/Catchable.php b/lib/public/AppFramework/Attribute/Catchable.php
new file mode 100644
index 00000000000..d45401550f6
--- /dev/null
+++ b/lib/public/AppFramework/Attribute/Catchable.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute to declare that the exception is "catchable" by apps.
+ *
+ * @since 32.0.0
+ */
+#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
+#[Consumable(since: '32.0.0')]
+#[Implementable(since: '32.0.0')]
+class Catchable extends ASince {
+}
diff --git a/lib/public/AppFramework/Attribute/Consumable.php b/lib/public/AppFramework/Attribute/Consumable.php
new file mode 100644
index 00000000000..2175bb0af88
--- /dev/null
+++ b/lib/public/AppFramework/Attribute/Consumable.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute to declare that the API stability is limited to "consuming" the
+ * class, interface, enum, etc. Apps are not allowed to implement or replace them.
+ *
+ * For events use @see \OCP\AppFramework\Attribute\Listenable
+ * For exceptions use @see \OCP\AppFramework\Attribute\Catchable
+ *
+ * @since 32.0.0
+ */
+#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
+#[Consumable(since: '32.0.0')]
+#[Implementable(since: '32.0.0')]
+class Consumable extends ASince {
+}
diff --git a/lib/public/AppFramework/Attribute/Dispatchable.php b/lib/public/AppFramework/Attribute/Dispatchable.php
new file mode 100644
index 00000000000..ff703d4749e
--- /dev/null
+++ b/lib/public/AppFramework/Attribute/Dispatchable.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute to declare that the event is "dispatchable" by apps.
+ *
+ * @since 32.0.0
+ */
+#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
+#[Consumable(since: '32.0.0')]
+#[Implementable(since: '32.0.0')]
+class Dispatchable extends ASince {
+}
diff --git a/lib/public/AppFramework/Attribute/ExceptionalImplementable.php b/lib/public/AppFramework/Attribute/ExceptionalImplementable.php
new file mode 100644
index 00000000000..23e9f830d9b
--- /dev/null
+++ b/lib/public/AppFramework/Attribute/ExceptionalImplementable.php
@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute to declare that the API marked as Consumable/Listenable/Catchable
+ * has an exception and is Implementable/Dispatchable/Throwable by a dedicated
+ * app. Changes to such an API have to be communicated to the affected app maintainers.
+ *
+ * @since 32.0.0
+ */
+#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
+#[Consumable(since: '32.0.0')]
+#[Implementable(since: '32.0.0')]
+class ExceptionalImplementable {
+ public function __construct(
+ protected string $app,
+ protected ?string $class = null,
+ ) {
+ }
+
+ public function getApp(): string {
+ return $this->app;
+ }
+
+ public function getClass(): ?string {
+ return $this->class;
+ }
+}
diff --git a/lib/public/AppFramework/Attribute/Implementable.php b/lib/public/AppFramework/Attribute/Implementable.php
new file mode 100644
index 00000000000..40ce0e0cf06
--- /dev/null
+++ b/lib/public/AppFramework/Attribute/Implementable.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute to declare that the API stability is limited to "implementing" the
+ * class, interface, enum, etc.
+ *
+ * For events use @see \OCP\AppFramework\Attribute\Dispatchable
+ * For exceptions use @see \OCP\AppFramework\Attribute\Throwable
+ *
+ * @since 32.0.0
+ */
+#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
+#[Consumable(since: '32.0.0')]
+#[Implementable(since: '32.0.0')]
+class Implementable extends ASince {
+}
diff --git a/lib/public/AppFramework/Attribute/Listenable.php b/lib/public/AppFramework/Attribute/Listenable.php
new file mode 100644
index 00000000000..98c2ca78690
--- /dev/null
+++ b/lib/public/AppFramework/Attribute/Listenable.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute to declare that the event is "listenable" by apps.
+ *
+ * @since 32.0.0
+ */
+#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
+#[Consumable(since: '32.0.0')]
+#[Implementable(since: '32.0.0')]
+class Listenable extends ASince {
+}
diff --git a/lib/public/AppFramework/Attribute/Throwable.php b/lib/public/AppFramework/Attribute/Throwable.php
new file mode 100644
index 00000000000..2c763c76b4c
--- /dev/null
+++ b/lib/public/AppFramework/Attribute/Throwable.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute to declare that the exception is "throwable" by apps.
+ *
+ * @since 32.0.0
+ */
+#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
+#[Consumable(since: '32.0.0')]
+#[Implementable(since: '32.0.0')]
+class Throwable extends ASince {
+}
diff --git a/lib/public/AppFramework/AuthPublicShareController.php b/lib/public/AppFramework/AuthPublicShareController.php
new file mode 100644
index 00000000000..28a92fedcc9
--- /dev/null
+++ b/lib/public/AppFramework/AuthPublicShareController.php
@@ -0,0 +1,235 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework;
+
+use OCP\AppFramework\Http\Attribute\BruteForceProtection;
+use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
+use OCP\AppFramework\Http\Attribute\PublicPage;
+use OCP\AppFramework\Http\Attribute\UseSession;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IURLGenerator;
+
+/**
+ * Base controller for interactive public shares
+ *
+ * It will verify if the user is properly authenticated to the share. If not the
+ * user will be redirected to an authentication page.
+ *
+ * Use this for a controller that is to be called directly by a user. So the
+ * normal public share page for files/calendars etc.
+ *
+ * @since 14.0.0
+ */
+abstract class AuthPublicShareController extends PublicShareController {
+ /** @var IURLGenerator */
+ protected $urlGenerator;
+
+ /**
+ * @since 14.0.0
+ */
+ public function __construct(string $appName,
+ IRequest $request,
+ ISession $session,
+ IURLGenerator $urlGenerator) {
+ parent::__construct($appName, $request, $session);
+
+ $this->urlGenerator = $urlGenerator;
+ }
+
+ /**
+ * Show the authentication page
+ * The form has to submit to the authenticate method route
+ *
+ * @since 14.0.0
+ */
+ #[NoCSRFRequired]
+ #[PublicPage]
+ public function showAuthenticate(): TemplateResponse {
+ return new TemplateResponse('core', 'publicshareauth', [], 'guest');
+ }
+
+ /**
+ * The template to show when authentication failed
+ *
+ * @since 14.0.0
+ */
+ protected function showAuthFailed(): TemplateResponse {
+ return new TemplateResponse('core', 'publicshareauth', ['wrongpw' => true], 'guest');
+ }
+
+ /**
+ * The template to show after user identification
+ *
+ * @since 24.0.0
+ */
+ protected function showIdentificationResult(bool $success): TemplateResponse {
+ return new TemplateResponse('core', 'publicshareauth', ['identityOk' => $success], 'guest');
+ }
+
+ /**
+ * Validates that the provided identity is allowed to receive a temporary password
+ *
+ * @since 24.0.0
+ */
+ protected function validateIdentity(?string $identityToken = null): bool {
+ return false;
+ }
+
+ /**
+ * Generates a password
+ *
+ * @since 24.0.0
+ */
+ protected function generatePassword(): void {
+ }
+
+ /**
+ * Verify the password
+ *
+ * @since 24.0.0
+ */
+ protected function verifyPassword(string $password): bool {
+ return false;
+ }
+
+ /**
+ * Function called after failed authentication
+ *
+ * You can use this to do some logging for example
+ *
+ * @since 14.0.0
+ */
+ protected function authFailed() {
+ }
+
+ /**
+ * Function called after successful authentication
+ *
+ * You can use this to do some logging for example
+ *
+ * @since 14.0.0
+ */
+ protected function authSucceeded() {
+ }
+
+ /**
+ * Authenticate the share
+ *
+ * @since 14.0.0
+ */
+ #[BruteForceProtection(action: 'publicLinkAuth')]
+ #[PublicPage]
+ #[UseSession]
+ final public function authenticate(string $password = '', string $passwordRequest = 'no', string $identityToken = '') {
+ // Already authenticated
+ if ($this->isAuthenticated()) {
+ return $this->getRedirect();
+ }
+
+ // Is user requesting a temporary password?
+ if ($passwordRequest == '') {
+ if ($this->validateIdentity($identityToken)) {
+ $this->generatePassword();
+ $response = $this->showIdentificationResult(true);
+ return $response;
+ } else {
+ $response = $this->showIdentificationResult(false);
+ $response->throttle();
+ return $response;
+ }
+ }
+
+ if (!$this->verifyPassword($password)) {
+ $this->authFailed();
+ $response = $this->showAuthFailed();
+ $response->throttle();
+ return $response;
+ }
+
+ $this->session->regenerateId(true, true);
+ $response = $this->getRedirect();
+
+ $this->session->set('public_link_authenticated_token', $this->getToken());
+ $this->session->set('public_link_authenticated_password_hash', $this->getPasswordHash());
+
+ $this->authSucceeded();
+
+ return $response;
+ }
+
+ /**
+ * Default landing page
+ *
+ * @since 14.0.0
+ */
+ abstract public function showShare(): TemplateResponse;
+
+ /**
+ * @since 14.0.0
+ */
+ final public function getAuthenticationRedirect(string $redirect): RedirectResponse {
+ return new RedirectResponse(
+ $this->urlGenerator->linkToRoute($this->getRoute('showAuthenticate'), ['token' => $this->getToken(), 'redirect' => $redirect])
+ );
+ }
+
+
+ /**
+ * @since 14.0.0
+ */
+ private function getRoute(string $function): string {
+ $app = strtolower($this->appName);
+ $class = (new \ReflectionClass($this))->getShortName();
+ if (str_ends_with($class, 'Controller')) {
+ $class = substr($class, 0, -10);
+ }
+ return $app . '.' . $class . '.' . $function;
+ }
+
+ /**
+ * @since 14.0.0
+ */
+ private function getRedirect(): RedirectResponse {
+ //Get all the stored redirect parameters:
+ $params = $this->session->get('public_link_authenticate_redirect');
+
+ $route = $this->getRoute('showShare');
+
+ if ($params === null) {
+ $params = [
+ 'token' => $this->getToken(),
+ ];
+ } else {
+ $params = json_decode($params, true);
+ if (isset($params['_route'])) {
+ $route = $params['_route'];
+ unset($params['_route']);
+ }
+
+ // If the token doesn't match the rest of the arguments can't be trusted either
+ if (isset($params['token']) && $params['token'] !== $this->getToken()) {
+ $params = [
+ 'token' => $this->getToken(),
+ ];
+ }
+
+ // We need a token
+ if (!isset($params['token'])) {
+ $params = [
+ 'token' => $this->getToken(),
+ ];
+ }
+ }
+
+ return new RedirectResponse($this->urlGenerator->linkToRoute($route, $params));
+ }
+}
diff --git a/lib/public/AppFramework/Bootstrap/IBootContext.php b/lib/public/AppFramework/Bootstrap/IBootContext.php
new file mode 100644
index 00000000000..cdf3a0af732
--- /dev/null
+++ b/lib/public/AppFramework/Bootstrap/IBootContext.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Bootstrap;
+
+use OCP\AppFramework\IAppContainer;
+use OCP\IServerContainer;
+use Psr\Container\ContainerExceptionInterface;
+use Throwable;
+
+/**
+ * @since 20.0.0
+ */
+interface IBootContext {
+ /**
+ * Get hold of the app's container
+ *
+ * Useful to register and query app-specific services
+ *
+ * @return IAppContainer
+ * @since 20.0.0
+ */
+ public function getAppContainer(): IAppContainer;
+
+ /**
+ * Get hold of the server DI container
+ *
+ * Useful to register and query system-wide services
+ *
+ * @return IServerContainer
+ * @since 20.0.0
+ */
+ public function getServerContainer(): IServerContainer;
+
+ /**
+ * Invoke the given callable and inject all parameters based on their types
+ * and names
+ *
+ * Note: when used with methods, make sure they are public or use \Closure::fromCallable
+ * to wrap the private method call, e.g.
+ * * `$context->injectFn([$obj, 'publicMethod'])`
+ * * `$context->injectFn([$this, 'publicMethod'])`
+ * * `$context->injectFn(\Closure::fromCallable([$this, 'privateMethod']))`
+ *
+ * Note: the app container will be queried
+ *
+ * @param callable $fn
+ * @throws ContainerExceptionInterface if at least one of the parameter can't be resolved
+ * @throws Throwable any error the function invocation might cause
+ * @return mixed|null the return value of the invoked function, if any
+ * @since 20.0.0
+ */
+ public function injectFn(callable $fn);
+}
diff --git a/lib/public/AppFramework/Bootstrap/IBootstrap.php b/lib/public/AppFramework/Bootstrap/IBootstrap.php
new file mode 100644
index 00000000000..7260d2b77a1
--- /dev/null
+++ b/lib/public/AppFramework/Bootstrap/IBootstrap.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Bootstrap;
+
+/**
+ * @since 20.0.0
+ */
+interface IBootstrap {
+ /**
+ * @param IRegistrationContext $context
+ *
+ * @since 20.0.0
+ */
+ public function register(IRegistrationContext $context): void;
+
+ /**
+ * Boot the application
+ *
+ * At this stage you can assume that all services are registered and the DI
+ * container(s) are ready to be queried.
+ *
+ * @param IBootContext $context
+ *
+ * @since 20.0.0
+ */
+ public function boot(IBootContext $context): void;
+}
diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
new file mode 100644
index 00000000000..70b35228c87
--- /dev/null
+++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php
@@ -0,0 +1,450 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Bootstrap;
+
+use OCP\AppFramework\IAppContainer;
+use OCP\Authentication\TwoFactorAuth\IProvider;
+use OCP\Calendar\ICalendarProvider;
+use OCP\Capabilities\ICapability;
+use OCP\Collaboration\Reference\IReferenceProvider;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Template\ICustomTemplateProvider;
+use OCP\IContainer;
+use OCP\Mail\Provider\IProvider as IMailProvider;
+use OCP\Notification\INotifier;
+use OCP\Preview\IProviderV2;
+use OCP\SpeechToText\ISpeechToTextProvider;
+use OCP\TextProcessing\IProvider as ITextProcessingProvider;
+use OCP\TextToImage\IProvider as ITextToImageProvider;
+use OCP\Translation\ITranslationProvider;
+
+/**
+ * The context object passed to IBootstrap::register
+ *
+ * @since 20.0.0
+ * @see IBootstrap::register()
+ */
+interface IRegistrationContext {
+ /**
+ * @param string $capability
+ * @psalm-param class-string<ICapability> $capability
+ * @see IAppContainer::registerCapability
+ *
+ * @since 20.0.0
+ */
+ public function registerCapability(string $capability): void;
+
+ /**
+ * Register an implementation of \OCP\Support\CrashReport\IReporter that
+ * will receive unhandled exceptions and throwables
+ *
+ * @param string $reporterClass
+ * @psalm-param class-string<\OCP\Support\CrashReport\IReporter> $reporterClass
+ * @return void
+ * @since 20.0.0
+ */
+ public function registerCrashReporter(string $reporterClass): void;
+
+ /**
+ * Register an implementation of \OCP\Dashboard\IWidget that
+ * will handle the implementation of a dashboard widget
+ *
+ * @param string $widgetClass
+ * @psalm-param class-string<\OCP\Dashboard\IWidget> $widgetClass
+ * @return void
+ * @since 20.0.0
+ */
+ public function registerDashboardWidget(string $widgetClass): void;
+
+ /**
+ * Register a service
+ *
+ * @param string $name
+ * @param callable $factory
+ * @psalm-param callable(\Psr\Container\ContainerInterface): mixed $factory
+ * @param bool $shared If set to true the factory result will be cached otherwise every query will call the factory again
+ *
+ * @return void
+ * @see IContainer::registerService()
+ *
+ * @since 20.0.0
+ */
+ public function registerService(string $name, callable $factory, bool $shared = true): void;
+
+ /**
+ * @param string $alias
+ * @psalm-param string|class-string $alias
+ * @param string $target
+ * @psalm-param string|class-string $target
+ *
+ * @return void
+ * @see IContainer::registerAlias()
+ *
+ * @since 20.0.0
+ */
+ public function registerServiceAlias(string $alias, string $target): void;
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ *
+ * @return void
+ * @see IContainer::registerParameter()
+ *
+ * @since 20.0.0
+ */
+ public function registerParameter(string $name, $value): void;
+
+ /**
+ * Register a service listener
+ *
+ * This is equivalent to calling IEventDispatcher::addServiceListener
+ *
+ * @psalm-template T of \OCP\EventDispatcher\Event
+ * @param string $event preferably the fully-qualified class name of the Event sub class to listen for
+ * @psalm-param string|class-string<T> $event preferably the fully-qualified class name of the Event sub class to listen for
+ * @param string $listener fully qualified class name (or ::class notation) of a \OCP\EventDispatcher\IEventListener that can be built by the DI container
+ * @psalm-param class-string<\OCP\EventDispatcher\IEventListener<T>> $listener fully qualified class name that can be built by the DI container
+ * @param int $priority The higher this value, the earlier an event
+ * listener will be triggered in the chain (defaults to 0)
+ *
+ * @see IEventDispatcher::addServiceListener()
+ *
+ * @since 20.0.0
+ */
+ public function registerEventListener(string $event, string $listener, int $priority = 0): void;
+
+ /**
+ * @param string $class
+ * @param bool $global load this middleware also for requests of other apps? Added in Nextcloud 26
+ * @psalm-param class-string<\OCP\AppFramework\Middleware> $class
+ *
+ * @return void
+ * @see IAppContainer::registerMiddleWare()
+ *
+ * @since 20.0.0
+ * @since 26.0.0 Added optional argument $global
+ */
+ public function registerMiddleware(string $class, bool $global = false): void;
+
+ /**
+ * Register a search provider for the unified search
+ *
+ * It is allowed to register more than one provider per app as the search
+ * results can go into distinct sections, e.g. "Files" and "Files shared
+ * with you" in the Files app.
+ *
+ * @param string $class
+ * @psalm-param class-string<\OCP\Search\IProvider> $class
+ *
+ * @return void
+ *
+ * @since 20.0.0
+ */
+ public function registerSearchProvider(string $class): void;
+
+ /**
+ * Register an alternative login option
+ *
+ * It is allowed to register more than one option per app.
+ *
+ * @param string $class
+ * @psalm-param class-string<\OCP\Authentication\IAlternativeLogin> $class
+ *
+ * @return void
+ *
+ * @since 20.0.0
+ */
+ public function registerAlternativeLogin(string $class): void;
+
+ /**
+ * Register an initialstate provider
+ *
+ * It is allowed to register more than one provider per app.
+ *
+ * @param string $class
+ * @psalm-param class-string<\OCP\AppFramework\Services\InitialStateProvider> $class
+ *
+ * @return void
+ *
+ * @since 21.0.0
+ */
+ public function registerInitialStateProvider(string $class): void;
+
+ /**
+ * Register a well known protocol handler
+ *
+ * It is allowed to register more than one handler per app.
+ *
+ * @param string $class
+ * @psalm-param class-string<\OCP\Http\WellKnown\IHandler> $class
+ *
+ * @return void
+ *
+ * @since 21.0.0
+ */
+ public function registerWellKnownHandler(string $class): void;
+
+ /**
+ * Register a custom SpeechToText provider class that can provide transcription
+ * of audio through the OCP\SpeechToText APIs
+ *
+ * @param string $providerClass
+ * @psalm-param class-string<ISpeechToTextProvider> $providerClass
+ * @since 27.0.0
+ */
+ public function registerSpeechToTextProvider(string $providerClass): void;
+
+ /**
+ * Register a custom text processing provider class that provides a promptable language model
+ * through the OCP\TextProcessing APIs
+ *
+ * @param string $providerClass
+ * @psalm-param class-string<ITextProcessingProvider> $providerClass
+ * @since 27.1.0
+ */
+ public function registerTextProcessingProvider(string $providerClass): void;
+
+ /**
+ * Register a custom text2image provider class that provides the possibility to generate images
+ * through the OCP\TextToImage APIs
+ *
+ * @param string $providerClass
+ * @psalm-param class-string<ITextToImageProvider> $providerClass
+ * @since 28.0.0
+ */
+ public function registerTextToImageProvider(string $providerClass): void;
+
+ /**
+ * Register a custom template provider class that is able to inject custom templates
+ * in addition to the user defined ones
+ *
+ * @param string $providerClass
+ * @psalm-param class-string<ICustomTemplateProvider> $providerClass
+ * @since 21.0.0
+ */
+ public function registerTemplateProvider(string $providerClass): void;
+
+ /**
+ * Register a custom translation provider class that can provide translation
+ * between languages through the OCP\Translation APIs
+ *
+ * @param string $providerClass
+ * @psalm-param class-string<ITranslationProvider> $providerClass
+ * @since 21.0.0
+ */
+ public function registerTranslationProvider(string $providerClass): void;
+
+ /**
+ * Register an INotifier class
+ *
+ * @param string $notifierClass
+ * @psalm-param class-string<INotifier> $notifierClass
+ * @since 22.0.0
+ */
+ public function registerNotifierService(string $notifierClass): void;
+
+ /**
+ * Register a two-factor provider
+ *
+ * @param string $twoFactorProviderClass
+ * @psalm-param class-string<IProvider> $twoFactorProviderClass
+ * @since 22.0.0
+ */
+ public function registerTwoFactorProvider(string $twoFactorProviderClass): void;
+
+ /**
+ * Register a preview provider
+ *
+ * It is allowed to register more than one provider per app.
+ *
+ * @param string $previewProviderClass
+ * @param string $mimeTypeRegex
+ * @psalm-param class-string<IProviderV2> $previewProviderClass
+ * @since 23.0.0
+ */
+ public function registerPreviewProvider(string $previewProviderClass, string $mimeTypeRegex): void;
+
+ /**
+ * Register a calendar provider
+ *
+ * @param string $class
+ * @psalm-param class-string<ICalendarProvider> $class
+ * @since 23.0.0
+ */
+ public function registerCalendarProvider(string $class): void;
+
+ /**
+ * Register a reference provider
+ *
+ * @param string $class
+ * @psalm-param class-string<IReferenceProvider> $class
+ * @since 25.0.0
+ */
+ public function registerReferenceProvider(string $class): void;
+
+ /**
+ * Register an implementation of \OCP\Profile\ILinkAction that
+ * will handle the implementation of a profile link action
+ *
+ * @param string $actionClass
+ * @psalm-param class-string<\OCP\Profile\ILinkAction> $actionClass
+ * @return void
+ * @since 23.0.0
+ */
+ public function registerProfileLinkAction(string $actionClass): void;
+
+ /**
+ * Register the backend of the Talk app
+ *
+ * This service must only be used by the Talk app
+ *
+ * @param string $backend
+ * @return void
+ * @since 24.0.0
+ */
+ public function registerTalkBackend(string $backend): void;
+
+ /**
+ * Register a resource backend for the DAV server
+ *
+ * @param string $actionClass
+ * @psalm-param class-string<\OCP\Calendar\Resource\IBackend> $actionClass
+ * @return void
+ * @since 24.0.0
+ */
+ public function registerCalendarResourceBackend(string $class): void;
+
+ /**
+ * Register a room backend for the DAV server
+ *
+ * @param string $actionClass
+ * @psalm-param class-string<\OCP\Calendar\Room\IBackend> $actionClass
+ * @return void
+ * @since 24.0.0
+ */
+ public function registerCalendarRoomBackend(string $class): void;
+
+ /**
+ * @param string $class
+ * @psalm-param class-string<\OCP\Calendar\Room\IBackend> $actionClass
+ * @return void
+ * @since 29.0.0
+ */
+ public function registerTeamResourceProvider(string $class): void;
+
+ /**
+ * Register an implementation of \OCP\UserMigration\IMigrator that
+ * will handle the implementation of a migrator
+ *
+ * @param string $migratorClass
+ * @psalm-param class-string<\OCP\UserMigration\IMigrator> $migratorClass
+ * @return void
+ * @since 24.0.0
+ */
+ public function registerUserMigrator(string $migratorClass): void;
+
+ /**
+ * Announce methods of classes that may contain sensitive values, which
+ * should be obfuscated before being logged.
+ *
+ * @param string $class
+ * @param string[] $methods
+ * @return void
+ * @since 25.0.0
+ */
+ public function registerSensitiveMethods(string $class, array $methods): void;
+
+ /**
+ * Register an implementation of IPublicShareTemplateProvider.
+ *
+ * @param string $class
+ * @psalm-param class-string<\OCP\Share\IPublicShareTemplateProvider> $class
+ * @return void
+ * @since 26.0.0
+ */
+ public function registerPublicShareTemplateProvider(string $class): void;
+
+ /**
+ * Register an implementation of \OCP\SetupCheck\ISetupCheck that
+ * will handle the implementation of a setup check
+ *
+ * @param class-string<\OCP\SetupCheck\ISetupCheck> $setupCheckClass
+ * @since 28.0.0
+ */
+ public function registerSetupCheck(string $setupCheckClass): void;
+
+ /**
+ * Register an implementation of \OCP\Settings\IDeclarativeSettings that
+ * will handle the implementation of declarative settings
+ *
+ * @param string $declarativeSettingsClass
+ * @psalm-param class-string<\OCP\Settings\IDeclarativeSettingsForm> $declarativeSettingsClass
+ * @return void
+ * @since 29.0.0
+ */
+ public function registerDeclarativeSettings(string $declarativeSettingsClass): void;
+
+ /**
+ * Register an implementation of \OCP\TaskProcessing\IProvider that
+ * will handle the implementation of task processing
+ *
+ * @param string $taskProcessingProviderClass
+ * @psalm-param class-string<\OCP\TaskProcessing\IProvider> $taskProcessingProviderClass
+ * @return void
+ * @since 30.0.0
+ */
+ public function registerTaskProcessingProvider(string $taskProcessingProviderClass): void;
+
+ /**
+ * Register an implementation of \OCP\TaskProcessing\ITaskType that
+ * will handle the implementation of a task processing type
+ *
+ * @param string $taskProcessingTaskTypeClass
+ * @psalm-param class-string<\OCP\TaskProcessing\ITaskType> $taskProcessingTaskTypeClass
+ * @return void
+ * @since 30.0.0
+ */
+ public function registerTaskProcessingTaskType(string $taskProcessingTaskTypeClass): void;
+
+ /**
+ * Register an implementation of \OCP\Files\Conversion\IConversionProvider
+ * that will handle the conversion of files from one MIME type to another
+ *
+ * @param string $class
+ * @psalm-param class-string<\OCP\Files\Conversion\IConversionProvider> $class
+ *
+ * @return void
+ *
+ * @since 31.0.0
+ */
+ public function registerFileConversionProvider(string $class): void;
+
+ /**
+ * Register a mail provider
+ *
+ * @param string $class
+ * @psalm-param class-string<IMailProvider> $class
+ * @since 30.0.0
+ */
+ public function registerMailProvider(string $class): void;
+
+
+ /**
+ * Register an implementation of \OCP\Config\Lexicon\IConfigLexicon that
+ * will handle the config lexicon
+ *
+ * @param string $configLexiconClass
+ *
+ * @psalm-param class-string<\OCP\Config\Lexicon\ILexicon> $configLexiconClass
+ * @since 31.0.0
+ */
+ public function registerConfigLexicon(string $configLexiconClass): void;
+}
diff --git a/lib/public/AppFramework/Controller.php b/lib/public/AppFramework/Controller.php
new file mode 100644
index 00000000000..cdeaac99366
--- /dev/null
+++ b/lib/public/AppFramework/Controller.php
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework;
+
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\Response;
+use OCP\IRequest;
+
+/**
+ * Base class to inherit your controllers from
+ * @since 6.0.0
+ */
+abstract class Controller {
+ /**
+ * app name
+ * @var string
+ * @since 7.0.0
+ */
+ protected $appName;
+
+ /**
+ * current request
+ * @var \OCP\IRequest
+ * @since 6.0.0
+ */
+ protected $request;
+
+ /**
+ * @var array
+ * @since 7.0.0
+ */
+ private $responders;
+
+ /**
+ * constructor of the controller
+ * @param string $appName the name of the app
+ * @param IRequest $request an instance of the request
+ * @since 6.0.0 - parameter $appName was added in 7.0.0 - parameter $app was removed in 7.0.0
+ */
+ public function __construct($appName,
+ IRequest $request) {
+ $this->appName = $appName;
+ $this->request = $request;
+
+ // default responders
+ $this->responders = [
+ 'json' => function ($data) {
+ if ($data instanceof DataResponse) {
+ $response = new JSONResponse(
+ $data->getData(),
+ $data->getStatus()
+ );
+ $dataHeaders = $data->getHeaders();
+ $headers = $response->getHeaders();
+ // do not overwrite Content-Type if it already exists
+ if (isset($dataHeaders['Content-Type'])) {
+ unset($headers['Content-Type']);
+ }
+ $response->setHeaders(array_merge($dataHeaders, $headers));
+
+ if ($data->getETag() !== null) {
+ $response->setETag($data->getETag());
+ }
+ if ($data->getLastModified() !== null) {
+ $response->setLastModified($data->getLastModified());
+ }
+ if ($data->isThrottled()) {
+ $response->throttle($data->getThrottleMetadata());
+ }
+
+ return $response;
+ }
+ return new JSONResponse($data);
+ }
+ ];
+ }
+
+
+ /**
+ * Parses an HTTP accept header and returns the supported responder type
+ * @param string $acceptHeader
+ * @param string $default
+ * @return string the responder type
+ * @since 7.0.0
+ * @since 9.1.0 Added default parameter
+ */
+ public function getResponderByHTTPHeader($acceptHeader, $default = 'json') {
+ $headers = explode(',', $acceptHeader);
+
+ // return the first matching responder
+ foreach ($headers as $header) {
+ $header = strtolower(trim($header));
+
+ $responder = str_replace('application/', '', $header);
+
+ if (array_key_exists($responder, $this->responders)) {
+ return $responder;
+ }
+ }
+
+ // no matching header return default
+ return $default;
+ }
+
+
+ /**
+ * Registers a formatter for a type
+ * @param string $format
+ * @param \Closure $responder
+ * @since 7.0.0
+ */
+ protected function registerResponder($format, \Closure $responder) {
+ $this->responders[$format] = $responder;
+ }
+
+
+ /**
+ * Serializes and formats a response
+ * @param mixed $response the value that was returned from a controller and
+ * is not a Response instance
+ * @param string $format the format for which a formatter has been registered
+ * @throws \DomainException if format does not match a registered formatter
+ * @return Response
+ * @since 7.0.0
+ */
+ public function buildResponse($response, $format = 'json') {
+ if (array_key_exists($format, $this->responders)) {
+ $responder = $this->responders[$format];
+
+ return $responder($response);
+ }
+ throw new \DomainException('No responder registered for format '
+ . $format . '!');
+ }
+}
diff --git a/lib/public/AppFramework/Db/DoesNotExistException.php b/lib/public/AppFramework/Db/DoesNotExistException.php
new file mode 100644
index 00000000000..416268b27c1
--- /dev/null
+++ b/lib/public/AppFramework/Db/DoesNotExistException.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Db;
+
+/**
+ * This is returned or should be returned when a find request does not find an
+ * entry in the database
+ * @since 7.0.0
+ */
+class DoesNotExistException extends \Exception implements IMapperException {
+ /**
+ * Constructor
+ * @param string $msg the error message
+ * @since 7.0.0
+ */
+ public function __construct($msg) {
+ parent::__construct($msg);
+ }
+}
diff --git a/lib/public/AppFramework/Db/Entity.php b/lib/public/AppFramework/Db/Entity.php
new file mode 100644
index 00000000000..3094070af5f
--- /dev/null
+++ b/lib/public/AppFramework/Db/Entity.php
@@ -0,0 +1,314 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Db;
+
+use OCP\DB\Types;
+
+use function lcfirst;
+use function substr;
+
+/**
+ * @method int getId()
+ * @method void setId(int $id)
+ * @since 7.0.0
+ * @psalm-consistent-constructor
+ */
+abstract class Entity {
+ /**
+ * @var int
+ */
+ public $id;
+
+ private array $_updatedFields = [];
+ /** @var array<string, \OCP\DB\Types::*> */
+ private array $_fieldTypes = ['id' => 'integer'];
+
+ /**
+ * Simple alternative constructor for building entities from a request
+ * @param array $params the array which was obtained via $this->params('key')
+ * in the controller
+ * @since 7.0.0
+ */
+ public static function fromParams(array $params): static {
+ $instance = new static();
+
+ foreach ($params as $key => $value) {
+ $method = 'set' . ucfirst($key);
+ $instance->$method($value);
+ }
+
+ return $instance;
+ }
+
+
+ /**
+ * Maps the keys of the row array to the attributes
+ * @param array $row the row to map onto the entity
+ * @since 7.0.0
+ */
+ public static function fromRow(array $row): static {
+ $instance = new static();
+
+ foreach ($row as $key => $value) {
+ $prop = $instance->columnToProperty($key);
+ $instance->setter($prop, [$value]);
+ }
+
+ $instance->resetUpdatedFields();
+
+ return $instance;
+ }
+
+
+ /**
+ * @return array<string, \OCP\DB\Types::*> with attribute and type
+ * @since 7.0.0
+ */
+ public function getFieldTypes(): array {
+ return $this->_fieldTypes;
+ }
+
+
+ /**
+ * Marks the entity as clean needed for setting the id after the insertion
+ * @since 7.0.0
+ */
+ public function resetUpdatedFields(): void {
+ $this->_updatedFields = [];
+ }
+
+ /**
+ * Generic setter for properties
+ *
+ * @throws \InvalidArgumentException
+ * @since 7.0.0
+ *
+ */
+ protected function setter(string $name, array $args): void {
+ // setters should only work for existing attributes
+ if (!property_exists($this, $name)) {
+ throw new \BadFunctionCallException($name . ' is not a valid attribute');
+ }
+
+ if ($args[0] === $this->$name) {
+ return;
+ }
+ $this->markFieldUpdated($name);
+
+ // if type definition exists, cast to correct type
+ if ($args[0] !== null && array_key_exists($name, $this->_fieldTypes)) {
+ $type = $this->_fieldTypes[$name];
+ if ($type === Types::BLOB) {
+ // (B)LOB is treated as string when we read from the DB
+ if (is_resource($args[0])) {
+ $args[0] = stream_get_contents($args[0]);
+ }
+ $type = Types::STRING;
+ }
+
+ switch ($type) {
+ case Types::BIGINT:
+ case Types::SMALLINT:
+ settype($args[0], Types::INTEGER);
+ break;
+ case Types::BINARY:
+ case Types::DECIMAL:
+ case Types::TEXT:
+ settype($args[0], Types::STRING);
+ break;
+ case Types::TIME:
+ case Types::DATE:
+ case Types::DATETIME:
+ case Types::DATETIME_TZ:
+ if (!$args[0] instanceof \DateTime) {
+ $args[0] = new \DateTime($args[0]);
+ }
+ break;
+ case Types::TIME_IMMUTABLE:
+ case Types::DATE_IMMUTABLE:
+ case Types::DATETIME_IMMUTABLE:
+ case Types::DATETIME_TZ_IMMUTABLE:
+ if (!$args[0] instanceof \DateTimeImmutable) {
+ $args[0] = new \DateTimeImmutable($args[0]);
+ }
+ break;
+ case Types::JSON:
+ if (!is_array($args[0])) {
+ $args[0] = json_decode($args[0], true);
+ }
+ break;
+ default:
+ settype($args[0], $type);
+ }
+ }
+ $this->$name = $args[0];
+
+ }
+
+ /**
+ * Generic getter for properties
+ * @since 7.0.0
+ */
+ protected function getter(string $name): mixed {
+ // getters should only work for existing attributes
+ if (property_exists($this, $name)) {
+ return $this->$name;
+ } else {
+ throw new \BadFunctionCallException($name
+ . ' is not a valid attribute');
+ }
+ }
+
+
+ /**
+ * Each time a setter is called, push the part after set
+ * into an array: for instance setId will save Id in the
+ * updated fields array so it can be easily used to create the
+ * getter method
+ * @since 7.0.0
+ */
+ public function __call(string $methodName, array $args) {
+ if (str_starts_with($methodName, 'set')) {
+ $this->setter(lcfirst(substr($methodName, 3)), $args);
+ } elseif (str_starts_with($methodName, 'get')) {
+ return $this->getter(lcfirst(substr($methodName, 3)));
+ } elseif ($this->isGetterForBoolProperty($methodName)) {
+ return $this->getter(lcfirst(substr($methodName, 2)));
+ } else {
+ throw new \BadFunctionCallException($methodName
+ . ' does not exist');
+ }
+ }
+
+ /**
+ * @param string $methodName
+ * @return bool
+ * @since 18.0.0
+ */
+ protected function isGetterForBoolProperty(string $methodName): bool {
+ if (str_starts_with($methodName, 'is')) {
+ $fieldName = lcfirst(substr($methodName, 2));
+ return isset($this->_fieldTypes[$fieldName]) && str_starts_with($this->_fieldTypes[$fieldName], 'bool');
+ }
+ return false;
+ }
+
+ /**
+ * Mark am attribute as updated
+ * @param string $attribute the name of the attribute
+ * @since 7.0.0
+ */
+ protected function markFieldUpdated(string $attribute): void {
+ $this->_updatedFields[$attribute] = true;
+ }
+
+
+ /**
+ * Transform a database columnname to a property
+ *
+ * @param string $columnName the name of the column
+ * @return string the property name
+ * @since 7.0.0
+ */
+ public function columnToProperty(string $columnName) {
+ $parts = explode('_', $columnName);
+ $property = '';
+
+ foreach ($parts as $part) {
+ if ($property === '') {
+ $property = $part;
+ } else {
+ $property .= ucfirst($part);
+ }
+ }
+
+ return $property;
+ }
+
+
+ /**
+ * Transform a property to a database column name
+ *
+ * @param string $property the name of the property
+ * @return string the column name
+ * @since 7.0.0
+ */
+ public function propertyToColumn(string $property): string {
+ $parts = preg_split('/(?=[A-Z])/', $property);
+
+ $column = '';
+ foreach ($parts as $part) {
+ if ($column === '') {
+ $column = $part;
+ } else {
+ $column .= '_' . lcfirst($part);
+ }
+ }
+
+ return $column;
+ }
+
+
+ /**
+ * @return array array of updated fields for update query
+ * @since 7.0.0
+ */
+ public function getUpdatedFields(): array {
+ return $this->_updatedFields;
+ }
+
+
+ /**
+ * Adds type information for a field so that it's automatically cast to
+ * that value once its being returned from the database
+ *
+ * @param string $fieldName the name of the attribute
+ * @param \OCP\DB\Types::* $type the type which will be used to match a cast
+ * @since 31.0.0 Parameter $type is now restricted to {@see \OCP\DB\Types} constants. The formerly accidentally supported types 'int'|'bool'|'double' are mapped to Types::INTEGER|Types::BOOLEAN|Types::FLOAT accordingly.
+ * @since 7.0.0
+ */
+ protected function addType(string $fieldName, string $type): void {
+ /** @psalm-suppress TypeDoesNotContainType */
+ if (in_array($type, ['bool', 'double', 'int', 'array', 'object'], true)) {
+ // Mapping legacy strings to the actual types
+ $type = match ($type) {
+ 'int' => Types::INTEGER,
+ 'bool' => Types::BOOLEAN,
+ 'double' => Types::FLOAT,
+ 'array',
+ 'object' => Types::STRING,
+ };
+ }
+
+ $this->_fieldTypes[$fieldName] = $type;
+ }
+
+
+ /**
+ * Slugify the value of a given attribute
+ * Warning: This doesn't result in a unique value
+ *
+ * @param string $attributeName the name of the attribute, which value should be slugified
+ * @return string slugified value
+ * @since 7.0.0
+ * @deprecated 24.0.0
+ */
+ public function slugify(string $attributeName): string {
+ // toSlug should only work for existing attributes
+ if (property_exists($this, $attributeName)) {
+ $value = $this->$attributeName;
+ // replace everything except alphanumeric with a single '-'
+ $value = preg_replace('/[^A-Za-z0-9]+/', '-', $value);
+ $value = strtolower($value);
+ // trim '-'
+ return trim($value, '-');
+ }
+
+ throw new \BadFunctionCallException($attributeName . ' is not a valid attribute');
+ }
+}
diff --git a/lib/public/AppFramework/Db/IMapperException.php b/lib/public/AppFramework/Db/IMapperException.php
new file mode 100644
index 00000000000..3e91422a89f
--- /dev/null
+++ b/lib/public/AppFramework/Db/IMapperException.php
@@ -0,0 +1,14 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Db;
+
+/**
+ * @since 16.0.0
+ */
+interface IMapperException extends \Throwable {
+}
diff --git a/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php b/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php
new file mode 100644
index 00000000000..e83bc1647d7
--- /dev/null
+++ b/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Db;
+
+/**
+ * This is returned or should be returned when a find request finds more than one
+ * row
+ * @since 7.0.0
+ */
+class MultipleObjectsReturnedException extends \Exception implements IMapperException {
+ /**
+ * Constructor
+ * @param string $msg the error message
+ * @since 7.0.0
+ */
+ public function __construct($msg) {
+ parent::__construct($msg);
+ }
+}
diff --git a/lib/public/AppFramework/Db/QBMapper.php b/lib/public/AppFramework/Db/QBMapper.php
new file mode 100644
index 00000000000..7fb5b2a9afd
--- /dev/null
+++ b/lib/public/AppFramework/Db/QBMapper.php
@@ -0,0 +1,377 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Db;
+
+use Generator;
+use OCP\DB\Exception;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\DB\Types;
+use OCP\IDBConnection;
+
+/**
+ * Simple parent class for inheriting your data access layer from. This class
+ * may be subject to change in the future
+ *
+ * @since 14.0.0
+ *
+ * @template T of Entity
+ */
+abstract class QBMapper {
+ /** @var string */
+ protected $tableName;
+
+ /** @var string|class-string<T> */
+ protected $entityClass;
+
+ /** @var IDBConnection */
+ protected $db;
+
+ /**
+ * @param IDBConnection $db Instance of the Db abstraction layer
+ * @param string $tableName the name of the table. set this to allow entity
+ * @param class-string<T>|null $entityClass the name of the entity that the sql should be
+ * mapped to queries without using sql
+ * @since 14.0.0
+ */
+ public function __construct(IDBConnection $db, string $tableName, ?string $entityClass = null) {
+ $this->db = $db;
+ $this->tableName = $tableName;
+
+ // if not given set the entity name to the class without the mapper part
+ // cache it here for later use since reflection is slow
+ if ($entityClass === null) {
+ $this->entityClass = str_replace('Mapper', '', \get_class($this));
+ } else {
+ $this->entityClass = $entityClass;
+ }
+ }
+
+
+ /**
+ * @return string the table name
+ * @since 14.0.0
+ */
+ public function getTableName(): string {
+ return $this->tableName;
+ }
+
+
+ /**
+ * Deletes an entity from the table
+ *
+ * @param Entity $entity the entity that should be deleted
+ * @psalm-param T $entity the entity that should be deleted
+ * @return Entity the deleted entity
+ * @psalm-return T the deleted entity
+ * @throws Exception
+ * @since 14.0.0
+ */
+ public function delete(Entity $entity): Entity {
+ $qb = $this->db->getQueryBuilder();
+
+ $idType = $this->getParameterTypeForProperty($entity, 'id');
+
+ $qb->delete($this->tableName)
+ ->where(
+ $qb->expr()->eq('id', $qb->createNamedParameter($entity->getId(), $idType))
+ );
+ $qb->executeStatement();
+ return $entity;
+ }
+
+
+ /**
+ * Creates a new entry in the db from an entity
+ *
+ * @param Entity $entity the entity that should be created
+ * @psalm-param T $entity the entity that should be created
+ * @return Entity the saved entity with the set id
+ * @psalm-return T the saved entity with the set id
+ * @throws Exception
+ * @since 14.0.0
+ */
+ public function insert(Entity $entity): Entity {
+ // get updated fields to save, fields have to be set using a setter to
+ // be saved
+ $properties = $entity->getUpdatedFields();
+
+ $qb = $this->db->getQueryBuilder();
+ $qb->insert($this->tableName);
+
+ // build the fields
+ foreach ($properties as $property => $updated) {
+ $column = $entity->propertyToColumn($property);
+ $getter = 'get' . ucfirst($property);
+ $value = $entity->$getter();
+
+ $type = $this->getParameterTypeForProperty($entity, $property);
+ $qb->setValue($column, $qb->createNamedParameter($value, $type));
+ }
+
+ $qb->executeStatement();
+
+ if ($entity->id === null) {
+ // When autoincrement is used id is always an int
+ $entity->setId($qb->getLastInsertId());
+ }
+
+ return $entity;
+ }
+
+ /**
+ * Tries to creates a new entry in the db from an entity and
+ * updates an existing entry if duplicate keys are detected
+ * by the database
+ *
+ * @param Entity $entity the entity that should be created/updated
+ * @psalm-param T $entity the entity that should be created/updated
+ * @return Entity the saved entity with the (new) id
+ * @psalm-return T the saved entity with the (new) id
+ * @throws Exception
+ * @throws \InvalidArgumentException if entity has no id
+ * @since 15.0.0
+ */
+ public function insertOrUpdate(Entity $entity): Entity {
+ try {
+ return $this->insert($entity);
+ } catch (Exception $ex) {
+ if ($ex->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
+ return $this->update($entity);
+ }
+ throw $ex;
+ }
+ }
+
+ /**
+ * Updates an entry in the db from an entity
+ *
+ * @param Entity $entity the entity that should be created
+ * @psalm-param T $entity the entity that should be created
+ * @return Entity the saved entity with the set id
+ * @psalm-return T the saved entity with the set id
+ * @throws Exception
+ * @throws \InvalidArgumentException if entity has no id
+ * @since 14.0.0
+ */
+ public function update(Entity $entity): Entity {
+ // if entity wasn't changed it makes no sense to run a db query
+ $properties = $entity->getUpdatedFields();
+ if (\count($properties) === 0) {
+ return $entity;
+ }
+
+ // entity needs an id
+ $id = $entity->getId();
+ if ($id === null) {
+ throw new \InvalidArgumentException(
+ 'Entity which should be updated has no id');
+ }
+
+ // get updated fields to save, fields have to be set using a setter to
+ // be saved
+ // do not update the id field
+ unset($properties['id']);
+
+ $qb = $this->db->getQueryBuilder();
+ $qb->update($this->tableName);
+
+ // build the fields
+ foreach ($properties as $property => $updated) {
+ $column = $entity->propertyToColumn($property);
+ $getter = 'get' . ucfirst($property);
+ $value = $entity->$getter();
+
+ $type = $this->getParameterTypeForProperty($entity, $property);
+ $qb->set($column, $qb->createNamedParameter($value, $type));
+ }
+
+ $idType = $this->getParameterTypeForProperty($entity, 'id');
+
+ $qb->where(
+ $qb->expr()->eq('id', $qb->createNamedParameter($id, $idType))
+ );
+ $qb->executeStatement();
+
+ return $entity;
+ }
+
+ /**
+ * Returns the type parameter for the QueryBuilder for a specific property
+ * of the $entity
+ *
+ * @param Entity $entity The entity to get the types from
+ * @psalm-param T $entity
+ * @param string $property The property of $entity to get the type for
+ * @return int|string
+ * @since 16.0.0
+ */
+ protected function getParameterTypeForProperty(Entity $entity, string $property) {
+ $types = $entity->getFieldTypes();
+
+ if (!isset($types[ $property ])) {
+ return IQueryBuilder::PARAM_STR;
+ }
+
+ switch ($types[ $property ]) {
+ case 'int':
+ case Types::INTEGER:
+ case Types::SMALLINT:
+ return IQueryBuilder::PARAM_INT;
+ case Types::STRING:
+ return IQueryBuilder::PARAM_STR;
+ case 'bool':
+ case Types::BOOLEAN:
+ return IQueryBuilder::PARAM_BOOL;
+ case Types::BLOB:
+ return IQueryBuilder::PARAM_LOB;
+ case Types::DATE:
+ return IQueryBuilder::PARAM_DATETIME_MUTABLE;
+ case Types::DATETIME:
+ return IQueryBuilder::PARAM_DATETIME_MUTABLE;
+ case Types::DATETIME_TZ:
+ return IQueryBuilder::PARAM_DATETIME_TZ_MUTABLE;
+ case Types::DATE_IMMUTABLE:
+ return IQueryBuilder::PARAM_DATE_IMMUTABLE;
+ case Types::DATETIME_IMMUTABLE:
+ return IQueryBuilder::PARAM_DATETIME_IMMUTABLE;
+ case Types::DATETIME_TZ_IMMUTABLE:
+ return IQueryBuilder::PARAM_DATETIME_TZ_IMMUTABLE;
+ case Types::TIME:
+ return IQueryBuilder::PARAM_TIME_MUTABLE;
+ case Types::TIME_IMMUTABLE:
+ return IQueryBuilder::PARAM_TIME_IMMUTABLE;
+ case Types::JSON:
+ return IQueryBuilder::PARAM_JSON;
+ }
+
+ return IQueryBuilder::PARAM_STR;
+ }
+
+ /**
+ * Returns an db result and throws exceptions when there are more or less
+ * results
+ *
+ * @param IQueryBuilder $query
+ * @return array the result as row
+ * @throws Exception
+ * @throws MultipleObjectsReturnedException if more than one item exist
+ * @throws DoesNotExistException if the item does not exist
+ * @see findEntity
+ *
+ * @since 14.0.0
+ */
+ protected function findOneQuery(IQueryBuilder $query): array {
+ $result = $query->executeQuery();
+
+ $row = $result->fetch();
+ if ($row === false) {
+ $result->closeCursor();
+ $msg = $this->buildDebugMessage(
+ 'Did expect one result but found none when executing', $query
+ );
+ throw new DoesNotExistException($msg);
+ }
+
+ $row2 = $result->fetch();
+ $result->closeCursor();
+ if ($row2 !== false) {
+ $msg = $this->buildDebugMessage(
+ 'Did not expect more than one result when executing', $query
+ );
+ throw new MultipleObjectsReturnedException($msg);
+ }
+
+ return $row;
+ }
+
+ /**
+ * @param string $msg
+ * @param IQueryBuilder $sql
+ * @return string
+ * @since 14.0.0
+ */
+ private function buildDebugMessage(string $msg, IQueryBuilder $sql): string {
+ return $msg
+ . ': query "' . $sql->getSQL() . '"; ';
+ }
+
+
+ /**
+ * Creates an entity from a row. Automatically determines the entity class
+ * from the current mapper name (MyEntityMapper -> MyEntity)
+ *
+ * @param array $row the row which should be converted to an entity
+ * @return Entity the entity
+ * @psalm-return T the entity
+ * @since 14.0.0
+ */
+ protected function mapRowToEntity(array $row): Entity {
+ unset($row['DOCTRINE_ROWNUM']); // remove doctrine/dbal helper column
+ return \call_user_func($this->entityClass . '::fromRow', $row);
+ }
+
+
+ /**
+ * Runs a sql query and returns an array of entities
+ *
+ * @param IQueryBuilder $query
+ * @return list<Entity> all fetched entities
+ * @psalm-return list<T> all fetched entities
+ * @throws Exception
+ * @since 14.0.0
+ */
+ protected function findEntities(IQueryBuilder $query): array {
+ $result = $query->executeQuery();
+ try {
+ $entities = [];
+ while ($row = $result->fetch()) {
+ $entities[] = $this->mapRowToEntity($row);
+ }
+ return $entities;
+ } finally {
+ $result->closeCursor();
+ }
+ }
+
+ /**
+ * Runs a sql query and yields each resulting entity to obtain database entries in a memory-efficient way
+ *
+ * @param IQueryBuilder $query
+ * @return Generator Generator of fetched entities
+ * @psalm-return Generator<T> Generator of fetched entities
+ * @throws Exception
+ * @since 30.0.0
+ */
+ protected function yieldEntities(IQueryBuilder $query): Generator {
+ $result = $query->executeQuery();
+ try {
+ while ($row = $result->fetch()) {
+ yield $this->mapRowToEntity($row);
+ }
+ } finally {
+ $result->closeCursor();
+ }
+ }
+
+
+ /**
+ * Returns an db result and throws exceptions when there are more or less
+ * results
+ *
+ * @param IQueryBuilder $query
+ * @return Entity the entity
+ * @psalm-return T the entity
+ * @throws Exception
+ * @throws MultipleObjectsReturnedException if more than one item exist
+ * @throws DoesNotExistException if the item does not exist
+ * @since 14.0.0
+ */
+ protected function findEntity(IQueryBuilder $query): Entity {
+ return $this->mapRowToEntity($this->findOneQuery($query));
+ }
+}
diff --git a/lib/public/AppFramework/Db/TTransactional.php b/lib/public/AppFramework/Db/TTransactional.php
new file mode 100644
index 00000000000..8dd275e5420
--- /dev/null
+++ b/lib/public/AppFramework/Db/TTransactional.php
@@ -0,0 +1,88 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Db;
+
+use OC\DB\Exceptions\DbalException;
+use OCP\DB\Exception;
+use OCP\IDBConnection;
+use Throwable;
+use function OCP\Log\logger;
+
+/**
+ * Helper trait for transactional operations
+ *
+ * @since 24.0.0
+ */
+trait TTransactional {
+ /**
+ * Run an atomic database operation
+ *
+ * - Commit if no exceptions are thrown, return the callable result
+ * - Revert otherwise and rethrows the exception
+ *
+ * @template T
+ * @param callable $fn
+ * @psalm-param callable():T $fn
+ * @param IDBConnection $db
+ *
+ * @return mixed the result of the passed callable
+ * @psalm-return T
+ *
+ * @throws Exception for possible errors of commit or rollback or the custom operations within the closure
+ * @throws Throwable any other error caused by the closure
+ *
+ * @since 24.0.0
+ * @see https://docs.nextcloud.com/server/latest/developer_manual/basics/storage/database.html#transactions
+ */
+ protected function atomic(callable $fn, IDBConnection $db) {
+ $db->beginTransaction();
+ try {
+ $result = $fn();
+ $db->commit();
+ return $result;
+ } catch (Throwable $e) {
+ $db->rollBack();
+ throw $e;
+ }
+ }
+
+ /**
+ * Wrapper around atomic() to retry after a retryable exception occurred
+ *
+ * Certain transactions might need to be retried. This is especially useful
+ * in highly concurrent requests where a deadlocks is thrown by the database
+ * without waiting for the lock to be freed (e.g. due to MySQL/MariaDB deadlock
+ * detection)
+ *
+ * @template T
+ * @param callable $fn
+ * @psalm-param callable():T $fn
+ * @param IDBConnection $db
+ * @param int $maxRetries
+ *
+ * @return mixed the result of the passed callable
+ * @psalm-return T
+ *
+ * @throws Exception for possible errors of commit or rollback or the custom operations within the closure
+ * @throws Throwable any other error caused by the closure
+ *
+ * @since 27.0.0
+ */
+ protected function atomicRetry(callable $fn, IDBConnection $db, int $maxRetries = 3): mixed {
+ for ($i = 0; $i < $maxRetries; $i++) {
+ try {
+ return $this->atomic($fn, $db);
+ } catch (DbalException $e) {
+ if (!$e->isRetryable() || $i === ($maxRetries - 1)) {
+ throw $e;
+ }
+ logger('core')->warning('Retrying operation after retryable exception.', [ 'exception' => $e ]);
+ }
+ }
+ }
+}
diff --git a/lib/public/AppFramework/Http.php b/lib/public/AppFramework/Http.php
new file mode 100644
index 00000000000..72bdadc9d17
--- /dev/null
+++ b/lib/public/AppFramework/Http.php
@@ -0,0 +1,309 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework;
+
+/**
+ * Base class which contains constants for HTTP status codes
+ * @since 6.0.0
+ */
+class Http {
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_CONTINUE = 100;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_SWITCHING_PROTOCOLS = 101;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_PROCESSING = 102;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_OK = 200;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_CREATED = 201;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_ACCEPTED = 202;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_NON_AUTHORATIVE_INFORMATION = 203;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_NO_CONTENT = 204;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_RESET_CONTENT = 205;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_PARTIAL_CONTENT = 206;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_MULTI_STATUS = 207;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_ALREADY_REPORTED = 208;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_IM_USED = 226;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_MULTIPLE_CHOICES = 300;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_MOVED_PERMANENTLY = 301;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_FOUND = 302;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_SEE_OTHER = 303;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_NOT_MODIFIED = 304;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_USE_PROXY = 305;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_RESERVED = 306;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_TEMPORARY_REDIRECT = 307;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_BAD_REQUEST = 400;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_UNAUTHORIZED = 401;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_PAYMENT_REQUIRED = 402;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_FORBIDDEN = 403;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_NOT_FOUND = 404;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_METHOD_NOT_ALLOWED = 405;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_NOT_ACCEPTABLE = 406;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_PROXY_AUTHENTICATION_REQUIRED = 407;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_REQUEST_TIMEOUT = 408;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_CONFLICT = 409;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_GONE = 410;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_LENGTH_REQUIRED = 411;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_PRECONDITION_FAILED = 412;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_REQUEST_ENTITY_TOO_LARGE = 413;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_REQUEST_URI_TOO_LONG = 414;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_UNSUPPORTED_MEDIA_TYPE = 415;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_REQUEST_RANGE_NOT_SATISFIABLE = 416;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_EXPECTATION_FAILED = 417;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_IM_A_TEAPOT = 418;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_UNPROCESSABLE_ENTITY = 422;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_LOCKED = 423;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_FAILED_DEPENDENCY = 424;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_UPGRADE_REQUIRED = 426;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_PRECONDITION_REQUIRED = 428;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_TOO_MANY_REQUESTS = 429;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_INTERNAL_SERVER_ERROR = 500;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_NOT_IMPLEMENTED = 501;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_BAD_GATEWAY = 502;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_SERVICE_UNAVAILABLE = 503;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_GATEWAY_TIMEOUT = 504;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_HTTP_VERSION_NOT_SUPPORTED = 505;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_VARIANT_ALSO_NEGOTIATES = 506;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_INSUFFICIENT_STORAGE = 507;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_LOOP_DETECTED = 508;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_NOT_EXTENDED = 510;
+
+ /**
+ * @since 6.0.0
+ */
+ public const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511;
+}
diff --git a/lib/public/AppFramework/Http/Attribute/ARateLimit.php b/lib/public/AppFramework/Http/Attribute/ARateLimit.php
new file mode 100644
index 00000000000..c06b1180ae3
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/ARateLimit.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+/**
+ * Attribute for controller methods that want to limit the times a logged-in
+ * user can call the endpoint in a given time period.
+ *
+ * @since 27.0.0
+ */
+abstract class ARateLimit {
+ /**
+ * @param int $limit The maximum number of requests that can be made in the given period in seconds.
+ * @param int $period The time period in seconds.
+ * @since 27.0.0
+ */
+ public function __construct(
+ protected int $limit,
+ protected int $period,
+ ) {
+ }
+
+ /**
+ * @since 27.0.0
+ */
+ public function getLimit(): int {
+ return $this->limit;
+ }
+
+ /**
+ * @since 27.0.0
+ */
+ public function getPeriod(): int {
+ return $this->period;
+ }
+}
diff --git a/lib/public/AppFramework/Http/Attribute/AnonRateLimit.php b/lib/public/AppFramework/Http/Attribute/AnonRateLimit.php
new file mode 100644
index 00000000000..f02f2b695c5
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/AnonRateLimit.php
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that want to limit the times a not logged-in
+ * guest can call the endpoint in a given time period.
+ *
+ * @since 27.0.0
+ */
+#[Attribute(Attribute::TARGET_METHOD)]
+class AnonRateLimit extends ARateLimit {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/ApiRoute.php b/lib/public/AppFramework/Http/Attribute/ApiRoute.php
new file mode 100644
index 00000000000..1d61cfe7704
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/ApiRoute.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * This attribute can be used to define API routes on controller methods.
+ *
+ * It works in addition to the traditional routes.php method and has the same parameters
+ * (except for the `name` parameter which is not needed).
+ *
+ * @since 29.0.0
+ */
+#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+class ApiRoute extends Route {
+ /**
+ * @inheritDoc
+ *
+ * @since 29.0.0
+ */
+ public function __construct(
+ protected string $verb,
+ protected string $url,
+ protected ?array $requirements = null,
+ protected ?array $defaults = null,
+ protected ?string $root = null,
+ protected ?string $postfix = null,
+ ) {
+ parent::__construct(
+ Route::TYPE_API,
+ $verb,
+ $url,
+ $requirements,
+ $defaults,
+ $root,
+ $postfix,
+ );
+ }
+}
diff --git a/lib/public/AppFramework/Http/Attribute/AppApiAdminAccessWithoutUser.php b/lib/public/AppFramework/Http/Attribute/AppApiAdminAccessWithoutUser.php
new file mode 100644
index 00000000000..6b78fee41af
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/AppApiAdminAccessWithoutUser.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for (sub)administrator controller methods that allow access for ExApps when the User is not set.
+ *
+ * @since 30.0.0
+ */
+#[Attribute]
+class AppApiAdminAccessWithoutUser {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/AuthorizedAdminSetting.php b/lib/public/AppFramework/Http/Attribute/AuthorizedAdminSetting.php
new file mode 100644
index 00000000000..83101143fc9
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/AuthorizedAdminSetting.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+use OCP\Settings\IDelegatedSettings;
+
+/**
+ * Attribute for controller methods that should be only accessible with
+ * full admin or partial admin permissions.
+ *
+ * @since 27.0.0
+ */
+#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+class AuthorizedAdminSetting {
+ /**
+ * @param class-string<IDelegatedSettings> $settings A settings section the user needs to be able to access
+ * @since 27.0.0
+ */
+ public function __construct(
+ protected string $settings,
+ ) {
+ }
+
+ /**
+ *
+ * @return class-string<IDelegatedSettings>
+ * @since 27.0.0
+ */
+ public function getSettings(): string {
+ return $this->settings;
+ }
+}
diff --git a/lib/public/AppFramework/Http/Attribute/BruteForceProtection.php b/lib/public/AppFramework/Http/Attribute/BruteForceProtection.php
new file mode 100644
index 00000000000..0fc1a3b9b6d
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/BruteForceProtection.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that want to protect passwords, keys, tokens
+ * or other data against brute force
+ *
+ * @since 27.0.0
+ */
+#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+class BruteForceProtection {
+ /**
+ * @since 27.0.0
+ */
+ public function __construct(
+ protected string $action,
+ ) {
+ }
+
+ /**
+ * @since 27.0.0
+ */
+ public function getAction(): string {
+ return $this->action;
+ }
+}
diff --git a/lib/public/AppFramework/Http/Attribute/CORS.php b/lib/public/AppFramework/Http/Attribute/CORS.php
new file mode 100644
index 00000000000..ff639635635
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/CORS.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that can also be accessed by other websites.
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS for an explanation of the functionality and the security implications.
+ * See https://docs.nextcloud.com/server/latest/developer_manual/digging_deeper/rest_apis.html on how to implement it in your controller.
+ *
+ * @since 27.0.0
+ */
+#[Attribute]
+class CORS {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/ExAppRequired.php b/lib/public/AppFramework/Http/Attribute/ExAppRequired.php
new file mode 100644
index 00000000000..eb18da8027c
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/ExAppRequired.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that can only be accessed by ExApps
+ *
+ * @since 30.0.0
+ */
+#[Attribute]
+class ExAppRequired {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/FrontpageRoute.php b/lib/public/AppFramework/Http/Attribute/FrontpageRoute.php
new file mode 100644
index 00000000000..398116d786f
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/FrontpageRoute.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * This attribute can be used to define Frontpage routes on controller methods.
+ *
+ * It works in addition to the traditional routes.php method and has the same parameters
+ * (except for the `name` parameter which is not needed).
+ *
+ * @since 29.0.0
+ */
+#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+class FrontpageRoute extends Route {
+ /**
+ * @inheritDoc
+ *
+ * @since 29.0.0
+ */
+ public function __construct(
+ protected string $verb,
+ protected string $url,
+ protected ?array $requirements = null,
+ protected ?array $defaults = null,
+ protected ?string $root = null,
+ protected ?string $postfix = null,
+ ) {
+ parent::__construct(
+ Route::TYPE_FRONTPAGE,
+ $verb,
+ $url,
+ $requirements,
+ $defaults,
+ $root,
+ $postfix,
+ );
+ }
+}
diff --git a/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php b/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php
new file mode 100644
index 00000000000..114637935db
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/IgnoreOpenAPI.php
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that should be ignored when generating OpenAPI documentation
+ *
+ * @since 28.0.0
+ * @deprecated 28.0.0 Use {@see OpenAPI} with {@see OpenAPI::SCOPE_IGNORE} instead: `#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]`
+ */
+#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
+class IgnoreOpenAPI {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/NoAdminRequired.php b/lib/public/AppFramework/Http/Attribute/NoAdminRequired.php
new file mode 100644
index 00000000000..59c6cf86800
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/NoAdminRequired.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that can be accessed by any logged-in user
+ *
+ * @since 27.0.0
+ */
+#[Attribute]
+class NoAdminRequired {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/NoCSRFRequired.php b/lib/public/AppFramework/Http/Attribute/NoCSRFRequired.php
new file mode 100644
index 00000000000..ad7e569a3b9
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/NoCSRFRequired.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that are not CSRF protected
+ *
+ * @since 27.0.0
+ */
+#[Attribute]
+class NoCSRFRequired {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/OpenAPI.php b/lib/public/AppFramework/Http/Attribute/OpenAPI.php
new file mode 100644
index 00000000000..1b44b2a57fe
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/OpenAPI.php
@@ -0,0 +1,91 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * With this attribute a controller or a method can be moved into a different
+ * scope or tag. Scopes should be seen as API consumers, tags can be used to group
+ * different routes inside the same scope.
+ *
+ * @since 28.0.0
+ */
+#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
+class OpenAPI {
+ /**
+ * APIs used for normal user facing interaction with your app,
+ * e.g. when you would implement a mobile client or standalone frontend.
+ *
+ * @since 28.0.0
+ */
+ public const SCOPE_DEFAULT = 'default';
+
+ /**
+ * APIs used to administrate your app's configuration on an administrative level.
+ * Will be set automatically when admin permissions are required to access the route.
+ *
+ * @since 28.0.0
+ */
+ public const SCOPE_ADMINISTRATION = 'administration';
+
+ /**
+ * APIs used by servers to federate with each other.
+ *
+ * @since 28.0.0
+ */
+ public const SCOPE_FEDERATION = 'federation';
+
+ /**
+ * Ignore this controller or method in all generated OpenAPI specifications.
+ *
+ * @since 28.0.0
+ */
+ public const SCOPE_IGNORE = 'ignore';
+
+ /**
+ * APIs used by ExApps.
+ * Will be set automatically when an ExApp is required to access the route.
+ *
+ * @since 30.0.0
+ */
+ public const SCOPE_EX_APP = 'ex_app';
+
+ /**
+ * @param self::SCOPE_*|string $scope Scopes are used to define different clients.
+ * It is recommended to go with the scopes available as self::SCOPE_* constants,
+ * but in exotic cases other APIs might need documentation as well,
+ * then a free string can be provided (but it should be `a-z` only).
+ * @param ?list<string> $tags Tags can be used to group routes inside a scope
+ * for easier implementation and reviewing of the API specification.
+ * It defaults to the controller name in snake_case (should be `a-z` and underscore only).
+ * @since 28.0.0
+ */
+ public function __construct(
+ protected string $scope = self::SCOPE_DEFAULT,
+ protected ?array $tags = null,
+ ) {
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getScope(): string {
+ return $this->scope;
+ }
+
+ /**
+ * @return ?list<string>
+ * @since 28.0.0
+ */
+ public function getTags(): ?array {
+ return $this->tags;
+ }
+}
diff --git a/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php b/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php
new file mode 100644
index 00000000000..c41e5aa2445
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php
@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that require the password to be confirmed with in the last 30 minutes
+ *
+ * @since 27.0.0
+ */
+#[Attribute]
+class PasswordConfirmationRequired {
+ /**
+ * @param bool $strict - Whether password confirmation needs to happen in the request.
+ *
+ * @since 31.0.0
+ */
+ public function __construct(
+ protected bool $strict = false,
+ ) {
+ }
+
+ /**
+ * @since 31.0.0
+ */
+ public function getStrict(): bool {
+ return $this->strict;
+ }
+
+}
diff --git a/lib/public/AppFramework/Http/Attribute/PublicPage.php b/lib/public/AppFramework/Http/Attribute/PublicPage.php
new file mode 100644
index 00000000000..85c1ed06f80
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/PublicPage.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that can also be accessed by not logged-in user
+ *
+ * @since 27.0.0
+ */
+#[Attribute]
+class PublicPage {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/RequestHeader.php b/lib/public/AppFramework/Http/Attribute/RequestHeader.php
new file mode 100644
index 00000000000..1d0fbbfa0c3
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/RequestHeader.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * This attribute allows documenting request headers and is primarily intended for OpenAPI documentation.
+ * It should be added whenever you use a request header in a controller method, in order to properly describe the header and its functionality.
+ * There are no checks that ensure the header is set, so you will still need to do this yourself in the controller method.
+ *
+ * @since 32.0.0
+ */
+#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+class RequestHeader {
+ /**
+ * @param lowercase-string $name The name of the request header
+ * @param non-empty-string $description The description of the request header
+ * @param bool $indirect Allow indirect usage of the header for example in a middleware. Enabling this turns off the check which ensures that the header must be referenced in the controller method.
+ */
+ public function __construct(
+ protected string $name,
+ protected string $description,
+ protected bool $indirect = false,
+ ) {
+ }
+}
diff --git a/lib/public/AppFramework/Http/Attribute/Route.php b/lib/public/AppFramework/Http/Attribute/Route.php
new file mode 100644
index 00000000000..45e977d64f8
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/Route.php
@@ -0,0 +1,145 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * This attribute can be used to define routes on controller methods.
+ *
+ * It works in addition to the traditional routes.php method and has the same parameters
+ * (except for the `name` parameter which is not needed).
+ *
+ * @since 29.0.0
+ */
+#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
+class Route {
+
+ /**
+ * Corresponds to the `ocs` key in routes.php
+ *
+ * @see ApiRoute
+ * @since 29.0.0
+ */
+ public const TYPE_API = 'ocs';
+
+ /**
+ * Corresponds to the `routes` key in routes.php
+ *
+ * @see FrontpageRoute
+ * @since 29.0.0
+ */
+ public const TYPE_FRONTPAGE = 'routes';
+
+ /**
+ * @param string $type Either Route::TYPE_API or Route::TYPE_FRONTPAGE.
+ * @psalm-param Route::TYPE_* $type
+ * @param string $verb HTTP method of the route.
+ * @psalm-param 'GET'|'HEAD'|'POST'|'PUT'|'DELETE'|'OPTIONS'|'PATCH' $verb
+ * @param string $url The path of the route.
+ * @param ?array<string, string> $requirements Array of regexes mapped to the path parameters.
+ * @param ?array<string, mixed> $defaults Array of default values mapped to the path parameters.
+ * @param ?string $root Custom root. For OCS all apps are allowed, but for index.php only some can use it.
+ * @param ?string $postfix Postfix for the route name.
+ * @since 29.0.0
+ */
+ public function __construct(
+ protected string $type,
+ protected string $verb,
+ protected string $url,
+ protected ?array $requirements = null,
+ protected ?array $defaults = null,
+ protected ?string $root = null,
+ protected ?string $postfix = null,
+ ) {
+ }
+
+ /**
+ * @return array{
+ * verb: string,
+ * url: string,
+ * requirements?: array<string, string>,
+ * defaults?: array<string, mixed>,
+ * root?: string,
+ * postfix?: string,
+ * }
+ * @since 29.0.0
+ */
+ public function toArray() {
+ $route = [
+ 'verb' => $this->verb,
+ 'url' => $this->url,
+ ];
+
+ if ($this->requirements !== null) {
+ $route['requirements'] = $this->requirements;
+ }
+ if ($this->defaults !== null) {
+ $route['defaults'] = $this->defaults;
+ }
+ if ($this->root !== null) {
+ $route['root'] = $this->root;
+ }
+ if ($this->postfix !== null) {
+ $route['postfix'] = $this->postfix;
+ }
+
+ return $route;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getType(): string {
+ return $this->type;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getVerb(): string {
+ return $this->verb;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getUrl(): string {
+ return $this->url;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getRequirements(): ?array {
+ return $this->requirements;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getDefaults(): ?array {
+ return $this->defaults;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getRoot(): ?string {
+ return $this->root;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getPostfix(): ?string {
+ return $this->postfix;
+ }
+}
diff --git a/lib/public/AppFramework/Http/Attribute/StrictCookiesRequired.php b/lib/public/AppFramework/Http/Attribute/StrictCookiesRequired.php
new file mode 100644
index 00000000000..a2697847ca6
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/StrictCookiesRequired.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that require strict cookies
+ *
+ * @since 27.0.0
+ */
+#[Attribute]
+class StrictCookiesRequired {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/SubAdminRequired.php b/lib/public/AppFramework/Http/Attribute/SubAdminRequired.php
new file mode 100644
index 00000000000..38c4dd35f3c
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/SubAdminRequired.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that can be accessed by sub-admins
+ *
+ * @since 27.0.0
+ */
+#[Attribute]
+class SubAdminRequired {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/UseSession.php b/lib/public/AppFramework/Http/Attribute/UseSession.php
new file mode 100644
index 00000000000..f64b050144f
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/UseSession.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that need to read/write PHP session data
+ *
+ * @since 26.0.0
+ */
+#[Attribute]
+class UseSession {
+}
diff --git a/lib/public/AppFramework/Http/Attribute/UserRateLimit.php b/lib/public/AppFramework/Http/Attribute/UserRateLimit.php
new file mode 100644
index 00000000000..6fcf7127e89
--- /dev/null
+++ b/lib/public/AppFramework/Http/Attribute/UserRateLimit.php
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http\Attribute;
+
+use Attribute;
+
+/**
+ * Attribute for controller methods that want to limit the times a logged-in
+ * user can call the endpoint in a given time period.
+ *
+ * @since 27.0.0
+ */
+#[Attribute(Attribute::TARGET_METHOD)]
+class UserRateLimit extends ARateLimit {
+}
diff --git a/lib/public/AppFramework/Http/ContentSecurityPolicy.php b/lib/public/AppFramework/Http/ContentSecurityPolicy.php
new file mode 100644
index 00000000000..11ec79bbdb7
--- /dev/null
+++ b/lib/public/AppFramework/Http/ContentSecurityPolicy.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+/**
+ * Class ContentSecurityPolicy is a simple helper which allows applications to
+ * modify the Content-Security-Policy sent by Nextcloud. Per default only JavaScript,
+ * stylesheets, images, fonts, media and connections from the same domain
+ * ('self') are allowed.
+ *
+ * Even if a value gets modified above defaults will still get appended. Please
+ * notice that Nextcloud ships already with sensible defaults and those policies
+ * should require no modification at all for most use-cases.
+ *
+ * This class allows unsafe-inline of CSS.
+ *
+ * @since 8.1.0
+ */
+class ContentSecurityPolicy extends EmptyContentSecurityPolicy {
+ /** @var bool Whether inline JS snippets are allowed */
+ protected $inlineScriptAllowed = false;
+ /** @var bool Whether eval in JS scripts is allowed */
+ protected $evalScriptAllowed = false;
+ /** @var bool Whether WebAssembly compilation is allowed */
+ protected ?bool $evalWasmAllowed = false;
+ /** @var bool Whether strict-dynamic should be set */
+ protected $strictDynamicAllowed = false;
+ /** @var bool Whether strict-dynamic should be set for 'script-src-elem' */
+ protected $strictDynamicAllowedOnScripts = true;
+ /** @var array Domains from which scripts can get loaded */
+ protected $allowedScriptDomains = [
+ '\'self\'',
+ ];
+ /**
+ * @var bool Whether inline CSS is allowed
+ * TODO: Disallow per default
+ * @link https://github.com/owncloud/core/issues/13458
+ */
+ protected $inlineStyleAllowed = true;
+ /** @var array Domains from which CSS can get loaded */
+ protected $allowedStyleDomains = [
+ '\'self\'',
+ ];
+ /** @var array Domains from which images can get loaded */
+ protected $allowedImageDomains = [
+ '\'self\'',
+ 'data:',
+ 'blob:',
+ ];
+ /** @var array Domains to which connections can be done */
+ protected $allowedConnectDomains = [
+ '\'self\'',
+ ];
+ /** @var array Domains from which media elements can be loaded */
+ protected $allowedMediaDomains = [
+ '\'self\'',
+ ];
+ /** @var array Domains from which object elements can be loaded */
+ protected $allowedObjectDomains = [];
+ /** @var array Domains from which iframes can be loaded */
+ protected $allowedFrameDomains = [];
+ /** @var array Domains from which fonts can be loaded */
+ protected $allowedFontDomains = [
+ '\'self\'',
+ 'data:',
+ ];
+ /** @var array Domains from which web-workers and nested browsing content can load elements */
+ protected $allowedChildSrcDomains = [];
+
+ /** @var array Domains which can embed this Nextcloud instance */
+ protected $allowedFrameAncestors = [
+ '\'self\'',
+ ];
+
+ /** @var array Domains from which web-workers can be loaded */
+ protected $allowedWorkerSrcDomains = [];
+
+ /** @var array Domains which can be used as target for forms */
+ protected $allowedFormActionDomains = [
+ '\'self\'',
+ ];
+
+ /** @var array Locations to report violations to */
+ protected $reportTo = [];
+}
diff --git a/lib/public/AppFramework/Http/DataDisplayResponse.php b/lib/public/AppFramework/Http/DataDisplayResponse.php
new file mode 100644
index 00000000000..e1ded910328
--- /dev/null
+++ b/lib/public/AppFramework/Http/DataDisplayResponse.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class DataDisplayResponse
+ *
+ * @since 8.1.0
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class DataDisplayResponse extends Response {
+ /**
+ * response data
+ * @var string
+ */
+ protected $data;
+
+
+ /**
+ * @param string $data the data to display
+ * @param S $statusCode the Http status code, defaults to 200
+ * @param H $headers additional key value based headers
+ * @since 8.1.0
+ */
+ public function __construct(string $data = '', int $statusCode = Http::STATUS_OK, array $headers = []) {
+ parent::__construct($statusCode, $headers);
+
+ $this->data = $data;
+ $this->addHeader('Content-Disposition', 'inline; filename=""');
+ }
+
+ /**
+ * Outputs data. No processing is done.
+ * @return string
+ * @since 8.1.0
+ */
+ public function render() {
+ return $this->data;
+ }
+
+
+ /**
+ * Sets values in the data
+ * @param string $data the data to display
+ * @return DataDisplayResponse Reference to this object
+ * @since 8.1.0
+ */
+ public function setData($data) {
+ $this->data = $data;
+
+ return $this;
+ }
+
+
+ /**
+ * Used to get the set parameters
+ * @return string the data
+ * @since 8.1.0
+ */
+ public function getData() {
+ return $this->data;
+ }
+}
diff --git a/lib/public/AppFramework/Http/DataDownloadResponse.php b/lib/public/AppFramework/Http/DataDownloadResponse.php
new file mode 100644
index 00000000000..ee6bcf0d0c5
--- /dev/null
+++ b/lib/public/AppFramework/Http/DataDownloadResponse.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class DataDownloadResponse
+ *
+ * @since 8.0.0
+ * @template S of Http::STATUS_*
+ * @template C of string
+ * @template H of array<string, mixed>
+ * @template-extends DownloadResponse<Http::STATUS_*, string, array<string, mixed>>
+ */
+class DataDownloadResponse extends DownloadResponse {
+ /**
+ * @var string
+ */
+ private $data;
+
+ /**
+ * Creates a response that prompts the user to download the text
+ * @param string $data text to be downloaded
+ * @param string $filename the name that the downloaded file should have
+ * @param C $contentType the mimetype that the downloaded file should have
+ * @param S $status
+ * @param H $headers
+ * @since 8.0.0
+ */
+ public function __construct(string $data, string $filename, string $contentType, int $status = Http::STATUS_OK, array $headers = []) {
+ $this->data = $data;
+ parent::__construct($filename, $contentType, $status, $headers);
+ }
+
+ /**
+ * @param string $data
+ * @since 8.0.0
+ */
+ public function setData($data) {
+ $this->data = $data;
+ }
+
+ /**
+ * @return string
+ * @since 8.0.0
+ */
+ public function render() {
+ return $this->data;
+ }
+}
diff --git a/lib/public/AppFramework/Http/DataResponse.php b/lib/public/AppFramework/Http/DataResponse.php
new file mode 100644
index 00000000000..2b54ce848ef
--- /dev/null
+++ b/lib/public/AppFramework/Http/DataResponse.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * A generic DataResponse class that is used to return generic data responses
+ * for responders to transform
+ * @since 8.0.0
+ * @psalm-type DataResponseType = array|int|float|string|bool|object|null|\stdClass|\JsonSerializable
+ * @template S of Http::STATUS_*
+ * @template-covariant T of DataResponseType
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class DataResponse extends Response {
+ /**
+ * response data
+ * @var T
+ */
+ protected $data;
+
+
+ /**
+ * @param T $data the object or array that should be transformed
+ * @param S $statusCode the Http status code, defaults to 200
+ * @param H $headers additional key value based headers
+ * @since 8.0.0
+ */
+ public function __construct(mixed $data = [], int $statusCode = Http::STATUS_OK, array $headers = []) {
+ parent::__construct($statusCode, $headers);
+
+ $this->data = $data;
+ }
+
+
+ /**
+ * Sets values in the data json array
+ * @psalm-suppress InvalidTemplateParam
+ * @param T $data an array or object which will be transformed
+ * @return DataResponse Reference to this object
+ * @since 8.0.0
+ */
+ public function setData($data) {
+ $this->data = $data;
+
+ return $this;
+ }
+
+
+ /**
+ * Used to get the set parameters
+ * @return T the data
+ * @since 8.0.0
+ */
+ public function getData() {
+ return $this->data;
+ }
+}
diff --git a/lib/public/AppFramework/Http/DownloadResponse.php b/lib/public/AppFramework/Http/DownloadResponse.php
new file mode 100644
index 00000000000..190de022d36
--- /dev/null
+++ b/lib/public/AppFramework/Http/DownloadResponse.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Prompts the user to download the a file
+ * @since 7.0.0
+ * @template S of Http::STATUS_*
+ * @template C of string
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class DownloadResponse extends Response {
+ /**
+ * Creates a response that prompts the user to download the file
+ * @param string $filename the name that the downloaded file should have
+ * @param C $contentType the mimetype that the downloaded file should have
+ * @param S $status
+ * @param H $headers
+ * @since 7.0.0
+ */
+ public function __construct(string $filename, string $contentType, int $status = Http::STATUS_OK, array $headers = []) {
+ parent::__construct($status, $headers);
+
+ $filename = strtr($filename, ['"' => '\\"', '\\' => '\\\\']);
+
+ $this->addHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
+ $this->addHeader('Content-Type', $contentType);
+ }
+}
diff --git a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
new file mode 100644
index 00000000000..b8bbfdb7d67
--- /dev/null
+++ b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
@@ -0,0 +1,549 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+/**
+ * Class EmptyContentSecurityPolicy is a simple helper which allows applications
+ * to modify the Content-Security-Policy sent by Nexcloud. Per default the policy
+ * is forbidding everything.
+ *
+ * As alternative with sane exemptions look at ContentSecurityPolicy
+ *
+ * @see \OCP\AppFramework\Http\ContentSecurityPolicy
+ * @since 9.0.0
+ */
+class EmptyContentSecurityPolicy {
+ /** @var ?string JS nonce to be used */
+ protected ?string $jsNonce = null;
+ /** @var bool Whether strict-dynamic should be used */
+ protected $strictDynamicAllowed = null;
+ /** @var bool Whether strict-dynamic should be used on script-src-elem */
+ protected $strictDynamicAllowedOnScripts = null;
+ /**
+ * @var bool Whether eval in JS scripts is allowed
+ * TODO: Disallow per default
+ * @link https://github.com/owncloud/core/issues/11925
+ */
+ protected $evalScriptAllowed = null;
+ /** @var bool Whether WebAssembly compilation is allowed */
+ protected ?bool $evalWasmAllowed = null;
+ /** @var array Domains from which scripts can get loaded */
+ protected $allowedScriptDomains = null;
+ /**
+ * @var bool Whether inline CSS is allowed
+ * TODO: Disallow per default
+ * @link https://github.com/owncloud/core/issues/13458
+ */
+ protected $inlineStyleAllowed = null;
+ /** @var array Domains from which CSS can get loaded */
+ protected $allowedStyleDomains = null;
+ /** @var array Domains from which images can get loaded */
+ protected $allowedImageDomains = null;
+ /** @var array Domains to which connections can be done */
+ protected $allowedConnectDomains = null;
+ /** @var array Domains from which media elements can be loaded */
+ protected $allowedMediaDomains = null;
+ /** @var array Domains from which object elements can be loaded */
+ protected $allowedObjectDomains = null;
+ /** @var array Domains from which iframes can be loaded */
+ protected $allowedFrameDomains = null;
+ /** @var array Domains from which fonts can be loaded */
+ protected $allowedFontDomains = null;
+ /** @var array Domains from which web-workers and nested browsing content can load elements */
+ protected $allowedChildSrcDomains = null;
+ /** @var array Domains which can embed this Nextcloud instance */
+ protected $allowedFrameAncestors = null;
+ /** @var array Domains from which web-workers can be loaded */
+ protected $allowedWorkerSrcDomains = null;
+ /** @var array Domains which can be used as target for forms */
+ protected $allowedFormActionDomains = null;
+
+ /** @var array Locations to report violations to */
+ protected $reportTo = null;
+
+ /**
+ * @param bool $state
+ * @return EmptyContentSecurityPolicy
+ * @since 24.0.0
+ */
+ public function useStrictDynamic(bool $state = false): self {
+ $this->strictDynamicAllowed = $state;
+ return $this;
+ }
+
+ /**
+ * In contrast to `useStrictDynamic` this only sets strict-dynamic on script-src-elem
+ * Meaning only grants trust to all imports of scripts that were loaded in `<script>` tags, and thus weakens less the CSP.
+ * @param bool $state
+ * @return EmptyContentSecurityPolicy
+ * @since 28.0.0
+ */
+ public function useStrictDynamicOnScripts(bool $state = false): self {
+ $this->strictDynamicAllowedOnScripts = $state;
+ return $this;
+ }
+
+ /**
+ * The base64 encoded nonce to be used for script source.
+ * This method is only for CSPMiddleware, custom values are ignored in mergePolicies of ContentSecurityPolicyManager
+ *
+ * @param string $nonce
+ * @return $this
+ * @since 11.0.0
+ */
+ public function useJsNonce($nonce) {
+ $this->jsNonce = $nonce;
+ return $this;
+ }
+
+ /**
+ * Whether eval in JavaScript is allowed or forbidden
+ * @param bool $state
+ * @return $this
+ * @since 8.1.0
+ * @deprecated 17.0.0 Eval should not be used anymore. Please update your scripts. This function will stop functioning in a future version of Nextcloud.
+ */
+ public function allowEvalScript($state = true) {
+ $this->evalScriptAllowed = $state;
+ return $this;
+ }
+
+ /**
+ * Whether WebAssembly compilation is allowed or forbidden
+ * @param bool $state
+ * @return $this
+ * @since 28.0.0
+ */
+ public function allowEvalWasm(bool $state = true) {
+ $this->evalWasmAllowed = $state;
+ return $this;
+ }
+
+ /**
+ * Allows to execute JavaScript files from a specific domain. Use * to
+ * allow JavaScript from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedScriptDomain($domain) {
+ $this->allowedScriptDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed script domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowScriptDomain($domain) {
+ $this->allowedScriptDomains = array_diff($this->allowedScriptDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Whether inline CSS snippets are allowed or forbidden
+ * @param bool $state
+ * @return $this
+ * @since 8.1.0
+ */
+ public function allowInlineStyle($state = true) {
+ $this->inlineStyleAllowed = $state;
+ return $this;
+ }
+
+ /**
+ * Allows to execute CSS files from a specific domain. Use * to allow
+ * CSS from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedStyleDomain($domain) {
+ $this->allowedStyleDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed style domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowStyleDomain($domain) {
+ $this->allowedStyleDomains = array_diff($this->allowedStyleDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Allows using fonts from a specific domain. Use * to allow
+ * fonts from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedFontDomain($domain) {
+ $this->allowedFontDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed font domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowFontDomain($domain) {
+ $this->allowedFontDomains = array_diff($this->allowedFontDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Allows embedding images from a specific domain. Use * to allow
+ * images from all domains.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedImageDomain($domain) {
+ $this->allowedImageDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed image domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowImageDomain($domain) {
+ $this->allowedImageDomains = array_diff($this->allowedImageDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * To which remote domains the JS connect to.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedConnectDomain($domain) {
+ $this->allowedConnectDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed connect domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowConnectDomain($domain) {
+ $this->allowedConnectDomains = array_diff($this->allowedConnectDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * From which domains media elements can be embedded.
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedMediaDomain($domain) {
+ $this->allowedMediaDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed media domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowMediaDomain($domain) {
+ $this->allowedMediaDomains = array_diff($this->allowedMediaDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * From which domains objects such as <object>, <embed> or <applet> are executed
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedObjectDomain($domain) {
+ $this->allowedObjectDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed object domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowObjectDomain($domain) {
+ $this->allowedObjectDomains = array_diff($this->allowedObjectDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Which domains can be embedded in an iframe
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ */
+ public function addAllowedFrameDomain($domain) {
+ $this->allowedFrameDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed frame domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ */
+ public function disallowFrameDomain($domain) {
+ $this->allowedFrameDomains = array_diff($this->allowedFrameDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Domains from which web-workers and nested browsing content can load elements
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 8.1.0
+ * @deprecated 15.0.0 use addAllowedWorkerSrcDomains or addAllowedFrameDomain
+ */
+ public function addAllowedChildSrcDomain($domain) {
+ $this->allowedChildSrcDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove the specified allowed child src domain from the allowed domains.
+ *
+ * @param string $domain
+ * @return $this
+ * @since 8.1.0
+ * @deprecated 15.0.0 use the WorkerSrcDomains or FrameDomain
+ */
+ public function disallowChildSrcDomain($domain) {
+ $this->allowedChildSrcDomains = array_diff($this->allowedChildSrcDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Domains which can embed an iFrame of the Nextcloud instance
+ *
+ * @param string $domain
+ * @return $this
+ * @since 13.0.0
+ */
+ public function addAllowedFrameAncestorDomain($domain) {
+ $this->allowedFrameAncestors[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Domains which can embed an iFrame of the Nextcloud instance
+ *
+ * @param string $domain
+ * @return $this
+ * @since 13.0.0
+ */
+ public function disallowFrameAncestorDomain($domain) {
+ $this->allowedFrameAncestors = array_diff($this->allowedFrameAncestors, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Domain from which workers can be loaded
+ *
+ * @param string $domain
+ * @return $this
+ * @since 15.0.0
+ */
+ public function addAllowedWorkerSrcDomain(string $domain) {
+ $this->allowedWorkerSrcDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove domain from which workers can be loaded
+ *
+ * @param string $domain
+ * @return $this
+ * @since 15.0.0
+ */
+ public function disallowWorkerSrcDomain(string $domain) {
+ $this->allowedWorkerSrcDomains = array_diff($this->allowedWorkerSrcDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Domain to where forms can submit
+ *
+ * @since 17.0.0
+ *
+ * @return $this
+ */
+ public function addAllowedFormActionDomain(string $domain) {
+ $this->allowedFormActionDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Remove domain to where forms can submit
+ *
+ * @return $this
+ * @since 17.0.0
+ */
+ public function disallowFormActionDomain(string $domain) {
+ $this->allowedFormActionDomains = array_diff($this->allowedFormActionDomains, [$domain]);
+ return $this;
+ }
+
+ /**
+ * Add location to report CSP violations to
+ *
+ * @param string $location
+ * @return $this
+ * @since 15.0.0
+ */
+ public function addReportTo(string $location) {
+ $this->reportTo[] = $location;
+ return $this;
+ }
+
+ /**
+ * Get the generated Content-Security-Policy as a string
+ * @return string
+ * @since 8.1.0
+ */
+ public function buildPolicy() {
+ $policy = "default-src 'none';";
+ $policy .= "base-uri 'none';";
+ $policy .= "manifest-src 'self';";
+
+ if (!empty($this->allowedScriptDomains) || $this->evalScriptAllowed || $this->evalWasmAllowed || is_string($this->jsNonce)) {
+ $policy .= 'script-src ';
+ $scriptSrc = '';
+ if (is_string($this->jsNonce)) {
+ if ($this->strictDynamicAllowed) {
+ $scriptSrc .= '\'strict-dynamic\' ';
+ }
+ $scriptSrc .= '\'nonce-' . $this->jsNonce . '\'';
+ $allowedScriptDomains = array_flip($this->allowedScriptDomains);
+ unset($allowedScriptDomains['\'self\'']);
+ $this->allowedScriptDomains = array_flip($allowedScriptDomains);
+ if (count($allowedScriptDomains) !== 0) {
+ $scriptSrc .= ' ';
+ }
+ }
+ if (is_array($this->allowedScriptDomains)) {
+ $scriptSrc .= implode(' ', $this->allowedScriptDomains);
+ }
+ if ($this->evalScriptAllowed) {
+ $scriptSrc .= ' \'unsafe-eval\'';
+ }
+ if ($this->evalWasmAllowed) {
+ $scriptSrc .= ' \'wasm-unsafe-eval\'';
+ }
+ $policy .= $scriptSrc . ';';
+ }
+
+ // We only need to set this if 'strictDynamicAllowed' is not set because otherwise we can simply fall back to script-src
+ if ($this->strictDynamicAllowedOnScripts && is_string($this->jsNonce) && !$this->strictDynamicAllowed) {
+ $policy .= 'script-src-elem \'strict-dynamic\' ';
+ $policy .= $scriptSrc ?? '';
+ $policy .= ';';
+ }
+
+ if (!empty($this->allowedStyleDomains) || $this->inlineStyleAllowed) {
+ $policy .= 'style-src ';
+ if (is_array($this->allowedStyleDomains)) {
+ $policy .= implode(' ', $this->allowedStyleDomains);
+ }
+ if ($this->inlineStyleAllowed) {
+ $policy .= ' \'unsafe-inline\'';
+ }
+ $policy .= ';';
+ }
+
+ if (!empty($this->allowedImageDomains)) {
+ $policy .= 'img-src ' . implode(' ', $this->allowedImageDomains);
+ $policy .= ';';
+ }
+
+ if (!empty($this->allowedFontDomains)) {
+ $policy .= 'font-src ' . implode(' ', $this->allowedFontDomains);
+ $policy .= ';';
+ }
+
+ if (!empty($this->allowedConnectDomains)) {
+ $policy .= 'connect-src ' . implode(' ', $this->allowedConnectDomains);
+ $policy .= ';';
+ }
+
+ if (!empty($this->allowedMediaDomains)) {
+ $policy .= 'media-src ' . implode(' ', $this->allowedMediaDomains);
+ $policy .= ';';
+ }
+
+ if (!empty($this->allowedObjectDomains)) {
+ $policy .= 'object-src ' . implode(' ', $this->allowedObjectDomains);
+ $policy .= ';';
+ }
+
+ if (!empty($this->allowedFrameDomains)) {
+ $policy .= 'frame-src ';
+ $policy .= implode(' ', $this->allowedFrameDomains);
+ $policy .= ';';
+ }
+
+ if (!empty($this->allowedChildSrcDomains)) {
+ $policy .= 'child-src ' . implode(' ', $this->allowedChildSrcDomains);
+ $policy .= ';';
+ }
+
+ if (!empty($this->allowedFrameAncestors)) {
+ $policy .= 'frame-ancestors ' . implode(' ', $this->allowedFrameAncestors);
+ $policy .= ';';
+ } else {
+ $policy .= 'frame-ancestors \'none\';';
+ }
+
+ if (!empty($this->allowedWorkerSrcDomains)) {
+ $policy .= 'worker-src ' . implode(' ', $this->allowedWorkerSrcDomains);
+ $policy .= ';';
+ }
+
+ if (!empty($this->allowedFormActionDomains)) {
+ $policy .= 'form-action ' . implode(' ', $this->allowedFormActionDomains);
+ $policy .= ';';
+ }
+
+ if (!empty($this->reportTo)) {
+ $policy .= 'report-uri ' . implode(' ', $this->reportTo);
+ $policy .= ';';
+ }
+
+ return rtrim($policy, ';');
+ }
+}
diff --git a/lib/public/AppFramework/Http/EmptyFeaturePolicy.php b/lib/public/AppFramework/Http/EmptyFeaturePolicy.php
new file mode 100644
index 00000000000..a1d19a9f34b
--- /dev/null
+++ b/lib/public/AppFramework/Http/EmptyFeaturePolicy.php
@@ -0,0 +1,164 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+/**
+ * Class EmptyFeaturePolicy is a simple helper which allows applications
+ * to modify the FeaturePolicy sent by Nextcloud. Per default the policy
+ * is forbidding everything.
+ *
+ * As alternative with sane exemptions look at FeaturePolicy
+ *
+ * @see \OCP\AppFramework\Http\FeaturePolicy
+ * @since 17.0.0
+ */
+class EmptyFeaturePolicy {
+ /** @var string[] of allowed domains to autoplay media */
+ protected $autoplayDomains = null;
+
+ /** @var string[] of allowed domains that can access the camera */
+ protected $cameraDomains = null;
+
+ /** @var string[] of allowed domains that can use fullscreen */
+ protected $fullscreenDomains = null;
+
+ /** @var string[] of allowed domains that can use the geolocation of the device */
+ protected $geolocationDomains = null;
+
+ /** @var string[] of allowed domains that can use the microphone */
+ protected $microphoneDomains = null;
+
+ /** @var string[] of allowed domains that can use the payment API */
+ protected $paymentDomains = null;
+
+ /**
+ * Allows to use autoplay from a specific domain. Use * to allow from all domains.
+ *
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 17.0.0
+ */
+ public function addAllowedAutoplayDomain(string $domain): self {
+ $this->autoplayDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Allows to use the camera on a specific domain. Use * to allow from all domains
+ *
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 17.0.0
+ */
+ public function addAllowedCameraDomain(string $domain): self {
+ $this->cameraDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Allows the full screen functionality to be used on a specific domain. Use * to allow from all domains
+ *
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 17.0.0
+ */
+ public function addAllowedFullScreenDomain(string $domain): self {
+ $this->fullscreenDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Allows to use the geolocation on a specific domain. Use * to allow from all domains
+ *
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 17.0.0
+ */
+ public function addAllowedGeoLocationDomain(string $domain): self {
+ $this->geolocationDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Allows to use the microphone on a specific domain. Use * to allow from all domains
+ *
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 17.0.0
+ */
+ public function addAllowedMicrophoneDomain(string $domain): self {
+ $this->microphoneDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Allows to use the payment API on a specific domain. Use * to allow from all domains
+ *
+ * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
+ * @return $this
+ * @since 17.0.0
+ */
+ public function addAllowedPaymentDomain(string $domain): self {
+ $this->paymentDomains[] = $domain;
+ return $this;
+ }
+
+ /**
+ * Get the generated Feature-Policy as a string
+ *
+ * @return string
+ * @since 17.0.0
+ */
+ public function buildPolicy(): string {
+ $policy = '';
+
+ if (empty($this->autoplayDomains)) {
+ $policy .= "autoplay 'none';";
+ } else {
+ $policy .= 'autoplay ' . implode(' ', $this->autoplayDomains);
+ $policy .= ';';
+ }
+
+ if (empty($this->cameraDomains)) {
+ $policy .= "camera 'none';";
+ } else {
+ $policy .= 'camera ' . implode(' ', $this->cameraDomains);
+ $policy .= ';';
+ }
+
+ if (empty($this->fullscreenDomains)) {
+ $policy .= "fullscreen 'none';";
+ } else {
+ $policy .= 'fullscreen ' . implode(' ', $this->fullscreenDomains);
+ $policy .= ';';
+ }
+
+ if (empty($this->geolocationDomains)) {
+ $policy .= "geolocation 'none';";
+ } else {
+ $policy .= 'geolocation ' . implode(' ', $this->geolocationDomains);
+ $policy .= ';';
+ }
+
+ if (empty($this->microphoneDomains)) {
+ $policy .= "microphone 'none';";
+ } else {
+ $policy .= 'microphone ' . implode(' ', $this->microphoneDomains);
+ $policy .= ';';
+ }
+
+ if (empty($this->paymentDomains)) {
+ $policy .= "payment 'none';";
+ } else {
+ $policy .= 'payment ' . implode(' ', $this->paymentDomains);
+ $policy .= ';';
+ }
+
+ return rtrim($policy, ';');
+ }
+}
diff --git a/lib/public/AppFramework/Http/Events/BeforeLoginTemplateRenderedEvent.php b/lib/public/AppFramework/Http/Events/BeforeLoginTemplateRenderedEvent.php
new file mode 100644
index 00000000000..b724b3a72ad
--- /dev/null
+++ b/lib/public/AppFramework/Http/Events/BeforeLoginTemplateRenderedEvent.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http\Events;
+
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\EventDispatcher\Event;
+
+/**
+ * Emitted before the rendering step of the login TemplateResponse.
+ *
+ * @since 28.0.0
+ */
+class BeforeLoginTemplateRenderedEvent extends Event {
+ /**
+ * @since 28.0.0
+ */
+ public function __construct(
+ private TemplateResponse $response,
+ ) {
+ parent::__construct();
+ }
+
+ /**
+ * @since 28.0.0
+ */
+ public function getResponse(): TemplateResponse {
+ return $this->response;
+ }
+}
diff --git a/lib/public/AppFramework/Http/Events/BeforeTemplateRenderedEvent.php b/lib/public/AppFramework/Http/Events/BeforeTemplateRenderedEvent.php
new file mode 100644
index 00000000000..7219ca5bfb6
--- /dev/null
+++ b/lib/public/AppFramework/Http/Events/BeforeTemplateRenderedEvent.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http\Events;
+
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\EventDispatcher\Event;
+
+/**
+ * Emitted before the rendering step of each TemplateResponse. The event holds a
+ * flag that specifies if an user is logged in.
+ *
+ * @since 20.0.0
+ */
+class BeforeTemplateRenderedEvent extends Event {
+ /** @var bool */
+ private $loggedIn;
+ /** @var TemplateResponse */
+ private $response;
+
+ /**
+ * @since 20.0.0
+ */
+ public function __construct(bool $loggedIn, TemplateResponse $response) {
+ parent::__construct();
+
+ $this->loggedIn = $loggedIn;
+ $this->response = $response;
+ }
+
+ /**
+ * @since 20.0.0
+ */
+ public function isLoggedIn(): bool {
+ return $this->loggedIn;
+ }
+
+ /**
+ * @since 20.0.0
+ */
+ public function getResponse(): TemplateResponse {
+ return $this->response;
+ }
+}
diff --git a/lib/public/AppFramework/Http/FeaturePolicy.php b/lib/public/AppFramework/Http/FeaturePolicy.php
new file mode 100644
index 00000000000..2291a78055c
--- /dev/null
+++ b/lib/public/AppFramework/Http/FeaturePolicy.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+/**
+ * Class FeaturePolicy is a simple helper which allows applications to
+ * modify the Feature-Policy sent by Nextcloud. Per default only autoplay is allowed
+ * from the same domain and full screen as well from the same domain.
+ *
+ * Even if a value gets modified above defaults will still get appended. Please
+ * notice that Nextcloud ships already with sensible defaults and those policies
+ * should require no modification at all for most use-cases.
+ *
+ * @since 17.0.0
+ */
+class FeaturePolicy extends EmptyFeaturePolicy {
+ protected $autoplayDomains = [
+ '\'self\'',
+ ];
+
+ /** @var string[] of allowed domains that can access the camera */
+ protected $cameraDomains = [];
+
+ protected $fullscreenDomains = [
+ '\'self\'',
+ ];
+
+ /** @var string[] of allowed domains that can use the geolocation of the device */
+ protected $geolocationDomains = [];
+
+ /** @var string[] of allowed domains that can use the microphone */
+ protected $microphoneDomains = [];
+
+ /** @var string[] of allowed domains that can use the payment API */
+ protected $paymentDomains = [];
+}
diff --git a/lib/public/AppFramework/Http/FileDisplayResponse.php b/lib/public/AppFramework/Http/FileDisplayResponse.php
new file mode 100644
index 00000000000..c18404b7d91
--- /dev/null
+++ b/lib/public/AppFramework/Http/FileDisplayResponse.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+use OCP\Files\File;
+use OCP\Files\SimpleFS\ISimpleFile;
+
+/**
+ * Class FileDisplayResponse
+ *
+ * @since 11.0.0
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class FileDisplayResponse extends Response implements ICallbackResponse {
+ /** @var File|ISimpleFile */
+ private $file;
+
+ /**
+ * FileDisplayResponse constructor.
+ *
+ * @param File|ISimpleFile $file
+ * @param S $statusCode
+ * @param H $headers
+ * @since 11.0.0
+ */
+ public function __construct(File|ISimpleFile $file, int $statusCode = Http::STATUS_OK, array $headers = []) {
+ parent::__construct($statusCode, $headers);
+
+ $this->file = $file;
+ $this->addHeader('Content-Disposition', 'inline; filename="' . rawurldecode($file->getName()) . '"');
+
+ $this->setETag($file->getEtag());
+ $lastModified = new \DateTime();
+ $lastModified->setTimestamp($file->getMTime());
+ $this->setLastModified($lastModified);
+ }
+
+ /**
+ * @param IOutput $output
+ * @since 11.0.0
+ */
+ public function callback(IOutput $output) {
+ if ($output->getHttpResponseCode() !== Http::STATUS_NOT_MODIFIED) {
+ $output->setHeader('Content-Length: ' . $this->file->getSize());
+ $output->setOutput($this->file->getContent());
+ }
+ }
+}
diff --git a/lib/public/AppFramework/Http/ICallbackResponse.php b/lib/public/AppFramework/Http/ICallbackResponse.php
new file mode 100644
index 00000000000..a51f72612fb
--- /dev/null
+++ b/lib/public/AppFramework/Http/ICallbackResponse.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+/**
+ * Interface ICallbackResponse
+ *
+ * @since 8.1.0
+ */
+interface ICallbackResponse {
+ /**
+ * Outputs the content that should be printed
+ *
+ * @param IOutput $output a small wrapper that handles output
+ * @since 8.1.0
+ */
+ public function callback(IOutput $output);
+}
diff --git a/lib/public/AppFramework/Http/IOutput.php b/lib/public/AppFramework/Http/IOutput.php
new file mode 100644
index 00000000000..105eaa0edb9
--- /dev/null
+++ b/lib/public/AppFramework/Http/IOutput.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+/**
+ * Very thin wrapper class to make output testable
+ * @since 8.1.0
+ */
+interface IOutput {
+ /**
+ * @param string $out
+ * @since 8.1.0
+ */
+ public function setOutput($out);
+
+ /**
+ * @param string|resource $path or file handle
+ *
+ * @return bool false if an error occurred
+ * @since 8.1.0
+ */
+ public function setReadfile($path);
+
+ /**
+ * @param string $header
+ * @since 8.1.0
+ */
+ public function setHeader($header);
+
+ /**
+ * @return int returns the current http response code
+ * @since 8.1.0
+ */
+ public function getHttpResponseCode();
+
+ /**
+ * @param int $code sets the http status code
+ * @since 8.1.0
+ */
+ public function setHttpResponseCode($code);
+
+ /**
+ * @param string $name
+ * @param string $value
+ * @param int $expire
+ * @param string $path
+ * @param string $domain
+ * @param bool $secure
+ * @param bool $httpOnly
+ * @param string $sameSite (added in 20)
+ * @since 8.1.0
+ */
+ public function setCookie($name, $value, $expire, $path, $domain, $secure, $httpOnly, $sameSite = 'Lax');
+}
diff --git a/lib/public/AppFramework/Http/JSONResponse.php b/lib/public/AppFramework/Http/JSONResponse.php
new file mode 100644
index 00000000000..a226e29a1b5
--- /dev/null
+++ b/lib/public/AppFramework/Http/JSONResponse.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * A renderer for JSON calls
+ * @since 6.0.0
+ * @template S of Http::STATUS_*
+ * @template-covariant T of null|string|int|float|bool|array|\stdClass|\JsonSerializable
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class JSONResponse extends Response {
+ /**
+ * response data
+ * @var T
+ */
+ protected $data;
+ /**
+ * Additional `json_encode` flags
+ * @var int
+ */
+ protected $encodeFlags;
+
+
+ /**
+ * constructor of JSONResponse
+ * @param T $data the object or array that should be transformed
+ * @param S $statusCode the Http status code, defaults to 200
+ * @param H $headers
+ * @param int $encodeFlags Additional `json_encode` flags
+ * @since 6.0.0
+ * @since 30.0.0 Added `$encodeFlags` param
+ */
+ public function __construct(
+ mixed $data = [],
+ int $statusCode = Http::STATUS_OK,
+ array $headers = [],
+ int $encodeFlags = 0,
+ ) {
+ parent::__construct($statusCode, $headers);
+
+ $this->data = $data;
+ $this->encodeFlags = $encodeFlags;
+ $this->addHeader('Content-Type', 'application/json; charset=utf-8');
+ }
+
+
+ /**
+ * Returns the rendered json
+ * @return string the rendered json
+ * @since 6.0.0
+ * @throws \Exception If data could not get encoded
+ *
+ * @psalm-taint-escape has_quotes
+ * @psalm-taint-escape html
+ */
+ public function render() {
+ return json_encode($this->data, JSON_HEX_TAG | JSON_THROW_ON_ERROR | $this->encodeFlags, 2048);
+ }
+
+ /**
+ * Sets values in the data json array
+ * @psalm-suppress InvalidTemplateParam
+ * @param T $data an array or object which will be transformed
+ * to JSON
+ * @return JSONResponse Reference to this object
+ * @since 6.0.0 - return value was added in 7.0.0
+ */
+ public function setData($data) {
+ $this->data = $data;
+
+ return $this;
+ }
+
+
+ /**
+ * @return T the data
+ * @since 6.0.0
+ */
+ public function getData() {
+ return $this->data;
+ }
+}
diff --git a/lib/public/AppFramework/Http/NotFoundResponse.php b/lib/public/AppFramework/Http/NotFoundResponse.php
new file mode 100644
index 00000000000..137d1a26655
--- /dev/null
+++ b/lib/public/AppFramework/Http/NotFoundResponse.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * A generic 404 response showing an 404 error page as well to the end-user
+ * @since 8.1.0
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends TemplateResponse<Http::STATUS_*, array<string, mixed>>
+ */
+class NotFoundResponse extends TemplateResponse {
+ /**
+ * @param S $status
+ * @param H $headers
+ * @since 8.1.0
+ */
+ public function __construct(int $status = Http::STATUS_NOT_FOUND, array $headers = []) {
+ parent::__construct('core', '404', [], 'guest', $status, $headers);
+
+ $this->setContentSecurityPolicy(new ContentSecurityPolicy());
+ }
+}
diff --git a/lib/public/AppFramework/Http/ParameterOutOfRangeException.php b/lib/public/AppFramework/Http/ParameterOutOfRangeException.php
new file mode 100644
index 00000000000..3286917d4d0
--- /dev/null
+++ b/lib/public/AppFramework/Http/ParameterOutOfRangeException.php
@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\AppFramework\Http;
+
+/**
+ * @since 29.0.0
+ */
+class ParameterOutOfRangeException extends \OutOfRangeException {
+ /**
+ * @since 29.0.0
+ */
+ public function __construct(
+ protected string $parameterName,
+ protected int $actualValue,
+ protected int $minValue,
+ protected int $maxValue,
+ ) {
+ parent::__construct(
+ sprintf(
+ 'Parameter %s must be between %d and %d',
+ $this->parameterName,
+ $this->minValue,
+ $this->maxValue,
+ )
+ );
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getParameterName(): string {
+ return $this->parameterName;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getActualValue(): int {
+ return $this->actualValue;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getMinValue(): int {
+ return $this->minValue;
+ }
+
+ /**
+ * @since 29.0.0
+ */
+ public function getMaxValue(): int {
+ return $this->maxValue;
+ }
+}
diff --git a/lib/public/AppFramework/Http/RedirectResponse.php b/lib/public/AppFramework/Http/RedirectResponse.php
new file mode 100644
index 00000000000..74847205976
--- /dev/null
+++ b/lib/public/AppFramework/Http/RedirectResponse.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Redirects to a different URL
+ * @since 7.0.0
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class RedirectResponse extends Response {
+ private $redirectURL;
+
+ /**
+ * Creates a response that redirects to a url
+ * @param string $redirectURL the url to redirect to
+ * @param S $status
+ * @param H $headers
+ * @since 7.0.0
+ */
+ public function __construct(string $redirectURL, int $status = Http::STATUS_SEE_OTHER, array $headers = []) {
+ parent::__construct($status, $headers);
+
+ $this->redirectURL = $redirectURL;
+ $this->addHeader('Location', $redirectURL);
+ }
+
+
+ /**
+ * @return string the url to redirect
+ * @since 7.0.0
+ */
+ public function getRedirectURL() {
+ return $this->redirectURL;
+ }
+}
diff --git a/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php b/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php
new file mode 100644
index 00000000000..0a0c04f671d
--- /dev/null
+++ b/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+use OCP\IURLGenerator;
+
+/**
+ * Redirects to the default app
+ *
+ * @since 16.0.0
+ * @deprecated 23.0.0 Use RedirectResponse() with IURLGenerator::linkToDefaultPageUrl() instead
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends RedirectResponse<Http::STATUS_*, array<string, mixed>>
+ */
+class RedirectToDefaultAppResponse extends RedirectResponse {
+ /**
+ * Creates a response that redirects to the default app
+ *
+ * @param S $status
+ * @param H $headers
+ * @since 16.0.0
+ * @deprecated 23.0.0 Use RedirectResponse() with IURLGenerator::linkToDefaultPageUrl() instead
+ */
+ public function __construct(int $status = Http::STATUS_SEE_OTHER, array $headers = []) {
+ $urlGenerator = \OCP\Server::get(IURLGenerator::class);
+ parent::__construct($urlGenerator->linkToDefaultPageUrl(), $status, $headers);
+ }
+}
diff --git a/lib/public/AppFramework/Http/Response.php b/lib/public/AppFramework/Http/Response.php
new file mode 100644
index 00000000000..bdebb12c00d
--- /dev/null
+++ b/lib/public/AppFramework/Http/Response.php
@@ -0,0 +1,408 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IConfig;
+use OCP\IRequest;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Base class for responses. Also used to just send headers.
+ *
+ * It handles headers, HTTP status code, last modified and ETag.
+ * @since 6.0.0
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ */
+class Response {
+ /**
+ * Headers
+ * @var H
+ */
+ private $headers;
+
+
+ /**
+ * Cookies that will be need to be constructed as header
+ * @var array
+ */
+ private $cookies = [];
+
+
+ /**
+ * HTTP status code - defaults to STATUS OK
+ * @var S
+ */
+ private $status;
+
+
+ /**
+ * Last modified date
+ * @var \DateTime
+ */
+ private $lastModified;
+
+
+ /**
+ * ETag
+ * @var string
+ */
+ private $ETag;
+
+ /** @var ContentSecurityPolicy|null Used Content-Security-Policy */
+ private $contentSecurityPolicy = null;
+
+ /** @var FeaturePolicy */
+ private $featurePolicy;
+
+ /** @var bool */
+ private $throttled = false;
+ /** @var array */
+ private $throttleMetadata = [];
+
+ /**
+ * @param S $status
+ * @param H $headers
+ * @since 17.0.0
+ */
+ public function __construct(int $status = Http::STATUS_OK, array $headers = []) {
+ $this->setStatus($status);
+ $this->setHeaders($headers);
+ }
+
+ /**
+ * Caches the response
+ *
+ * @param int $cacheSeconds amount of seconds the response is fresh, 0 to disable cache.
+ * @param bool $public whether the page should be cached by public proxy. Usually should be false, unless this is a static resources.
+ * @param bool $immutable whether browser should treat the resource as immutable and not ask the server for each page load if the resource changed.
+ * @return $this
+ * @since 6.0.0 - return value was added in 7.0.0
+ */
+ public function cacheFor(int $cacheSeconds, bool $public = false, bool $immutable = false) {
+ if ($cacheSeconds > 0) {
+ $cacheStore = $public ? 'public' : 'private';
+ $this->addHeader('Cache-Control', sprintf('%s, max-age=%s, %s', $cacheStore, $cacheSeconds, ($immutable ? 'immutable' : 'must-revalidate')));
+
+ // Set expires header
+ $expires = new \DateTime();
+ $time = \OCP\Server::get(ITimeFactory::class);
+ $expires->setTimestamp($time->getTime());
+ $expires->add(new \DateInterval('PT' . $cacheSeconds . 'S'));
+ $this->addHeader('Expires', $expires->format(\DateTimeInterface::RFC7231));
+ } else {
+ $this->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
+ unset($this->headers['Expires']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a new cookie to the response
+ * @param string $name The name of the cookie
+ * @param string $value The value of the cookie
+ * @param \DateTime|null $expireDate Date on that the cookie should expire, if set
+ * to null cookie will be considered as session
+ * cookie.
+ * @param string $sameSite The samesite value of the cookie. Defaults to Lax. Other possibilities are Strict or None
+ * @return $this
+ * @since 8.0.0
+ */
+ public function addCookie($name, $value, ?\DateTime $expireDate = null, $sameSite = 'Lax') {
+ $this->cookies[$name] = ['value' => $value, 'expireDate' => $expireDate, 'sameSite' => $sameSite];
+ return $this;
+ }
+
+
+ /**
+ * Set the specified cookies
+ * @param array $cookies array('foo' => array('value' => 'bar', 'expire' => null))
+ * @return $this
+ * @since 8.0.0
+ */
+ public function setCookies(array $cookies) {
+ $this->cookies = $cookies;
+ return $this;
+ }
+
+
+ /**
+ * Invalidates the specified cookie
+ * @param string $name
+ * @return $this
+ * @since 8.0.0
+ */
+ public function invalidateCookie($name) {
+ $this->addCookie($name, 'expired', new \DateTime('1971-01-01 00:00'));
+ return $this;
+ }
+
+ /**
+ * Invalidates the specified cookies
+ * @param array $cookieNames array('foo', 'bar')
+ * @return $this
+ * @since 8.0.0
+ */
+ public function invalidateCookies(array $cookieNames) {
+ foreach ($cookieNames as $cookieName) {
+ $this->invalidateCookie($cookieName);
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the cookies
+ * @return array
+ * @since 8.0.0
+ */
+ public function getCookies() {
+ return $this->cookies;
+ }
+
+ /**
+ * Adds a new header to the response that will be called before the render
+ * function
+ * @param string $name The name of the HTTP header
+ * @param string $value The value, null will delete it
+ * @return $this
+ * @since 6.0.0 - return value was added in 7.0.0
+ */
+ public function addHeader($name, $value) {
+ $name = trim($name); // always remove leading and trailing whitespace
+ // to be able to reliably check for security
+ // headers
+
+ if ($this->status === Http::STATUS_NOT_MODIFIED
+ && stripos($name, 'x-') === 0) {
+ /** @var IConfig $config */
+ $config = \OCP\Server::get(IConfig::class);
+
+ if ($config->getSystemValueBool('debug', false)) {
+ \OCP\Server::get(LoggerInterface::class)->error('Setting custom header on a 304 is not supported (Header: {header})', [
+ 'header' => $name,
+ ]);
+ }
+ }
+
+ if (is_null($value)) {
+ unset($this->headers[$name]);
+ } else {
+ $this->headers[$name] = $value;
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Set the headers
+ * @template NewH as array<string, mixed>
+ * @param NewH $headers value header pairs
+ * @psalm-this-out static<S, NewH>
+ * @return static
+ * @since 8.0.0
+ */
+ public function setHeaders(array $headers): static {
+ /** @psalm-suppress InvalidPropertyAssignmentValue Expected due to @psalm-this-out */
+ $this->headers = $headers;
+
+ return $this;
+ }
+
+
+ /**
+ * Returns the set headers
+ * @return array{X-Request-Id: string, Cache-Control: string, Content-Security-Policy: string, Feature-Policy: string, X-Robots-Tag: string, Last-Modified?: string, ETag?: string, ...H} the headers
+ * @since 6.0.0
+ */
+ public function getHeaders() {
+ /** @var IRequest $request */
+ /**
+ * @psalm-suppress UndefinedClass
+ */
+ $request = \OCP\Server::get(IRequest::class);
+ $mergeWith = [
+ 'X-Request-Id' => $request->getId(),
+ 'Cache-Control' => 'no-cache, no-store, must-revalidate',
+ 'Content-Security-Policy' => $this->getContentSecurityPolicy()->buildPolicy(),
+ 'Feature-Policy' => $this->getFeaturePolicy()->buildPolicy(),
+ 'X-Robots-Tag' => 'noindex, nofollow',
+ ];
+
+ if ($this->lastModified) {
+ $mergeWith['Last-Modified'] = $this->lastModified->format(\DateTimeInterface::RFC7231);
+ }
+
+ if ($this->ETag) {
+ $mergeWith['ETag'] = '"' . $this->ETag . '"';
+ }
+
+ return array_merge($mergeWith, $this->headers);
+ }
+
+
+ /**
+ * By default renders no output
+ * @return string
+ * @since 6.0.0
+ */
+ public function render() {
+ return '';
+ }
+
+
+ /**
+ * Set response status
+ * @template NewS as int
+ * @param NewS $status a HTTP status code, see also the STATUS constants
+ * @psalm-this-out static<NewS, H>
+ * @return static
+ * @since 6.0.0 - return value was added in 7.0.0
+ */
+ public function setStatus($status): static {
+ /** @psalm-suppress InvalidPropertyAssignmentValue Expected due to @psalm-this-out */
+ $this->status = $status;
+
+ return $this;
+ }
+
+ /**
+ * Set a Content-Security-Policy
+ * @param EmptyContentSecurityPolicy $csp Policy to set for the response object
+ * @return $this
+ * @since 8.1.0
+ */
+ public function setContentSecurityPolicy(EmptyContentSecurityPolicy $csp) {
+ $this->contentSecurityPolicy = $csp;
+ return $this;
+ }
+
+ /**
+ * Get the currently used Content-Security-Policy
+ * @return EmptyContentSecurityPolicy|null Used Content-Security-Policy or null if
+ * none specified.
+ * @since 8.1.0
+ */
+ public function getContentSecurityPolicy() {
+ if ($this->contentSecurityPolicy === null) {
+ $this->setContentSecurityPolicy(new EmptyContentSecurityPolicy());
+ }
+ return $this->contentSecurityPolicy;
+ }
+
+
+ /**
+ * @since 17.0.0
+ */
+ public function getFeaturePolicy(): EmptyFeaturePolicy {
+ if ($this->featurePolicy === null) {
+ $this->setFeaturePolicy(new EmptyFeaturePolicy());
+ }
+ return $this->featurePolicy;
+ }
+
+ /**
+ * @since 17.0.0
+ */
+ public function setFeaturePolicy(EmptyFeaturePolicy $featurePolicy): self {
+ $this->featurePolicy = $featurePolicy;
+
+ return $this;
+ }
+
+
+
+ /**
+ * Get response status
+ * @since 6.0.0
+ * @return S
+ */
+ public function getStatus() {
+ return $this->status;
+ }
+
+
+ /**
+ * Get the ETag
+ * @return string the etag
+ * @since 6.0.0
+ */
+ public function getETag() {
+ return $this->ETag;
+ }
+
+
+ /**
+ * Get "last modified" date
+ * @return \DateTime RFC2822 formatted last modified date
+ * @since 6.0.0
+ */
+ public function getLastModified() {
+ return $this->lastModified;
+ }
+
+
+ /**
+ * Set the ETag
+ * @param string $ETag
+ * @return Response Reference to this object
+ * @since 6.0.0 - return value was added in 7.0.0
+ */
+ public function setETag($ETag) {
+ $this->ETag = $ETag;
+
+ return $this;
+ }
+
+
+ /**
+ * Set "last modified" date
+ * @param \DateTime $lastModified
+ * @return Response Reference to this object
+ * @since 6.0.0 - return value was added in 7.0.0
+ */
+ public function setLastModified($lastModified) {
+ $this->lastModified = $lastModified;
+
+ return $this;
+ }
+
+ /**
+ * Marks the response as to throttle. Will be throttled when the
+ * @BruteForceProtection annotation is added.
+ *
+ * @param array $metadata
+ * @since 12.0.0
+ */
+ public function throttle(array $metadata = []) {
+ $this->throttled = true;
+ $this->throttleMetadata = $metadata;
+ }
+
+ /**
+ * Returns the throttle metadata, defaults to empty array
+ *
+ * @return array
+ * @since 13.0.0
+ */
+ public function getThrottleMetadata() {
+ return $this->throttleMetadata;
+ }
+
+ /**
+ * Whether the current response is throttled.
+ *
+ * @since 12.0.0
+ */
+ public function isThrottled() {
+ return $this->throttled;
+ }
+}
diff --git a/lib/public/AppFramework/Http/StandaloneTemplateResponse.php b/lib/public/AppFramework/Http/StandaloneTemplateResponse.php
new file mode 100644
index 00000000000..244a6b80f9f
--- /dev/null
+++ b/lib/public/AppFramework/Http/StandaloneTemplateResponse.php
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * A template response that does not emit the loadAdditionalScripts events.
+ *
+ * This is useful for pages that are authenticated but do not yet show the
+ * full nextcloud UI. Like the 2FA page, or the grant page in the login flow.
+ *
+ * @since 16.0.0
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends TemplateResponse<Http::STATUS_*, array<string, mixed>>
+ */
+class StandaloneTemplateResponse extends TemplateResponse {
+}
diff --git a/lib/public/AppFramework/Http/StreamResponse.php b/lib/public/AppFramework/Http/StreamResponse.php
new file mode 100644
index 00000000000..d0e6e3e148a
--- /dev/null
+++ b/lib/public/AppFramework/Http/StreamResponse.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * Class StreamResponse
+ *
+ * @since 8.1.0
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class StreamResponse extends Response implements ICallbackResponse {
+ /** @var string */
+ private $filePath;
+
+ /**
+ * @param string|resource $filePath the path to the file or a file handle which should be streamed
+ * @param S $status
+ * @param H $headers
+ * @since 8.1.0
+ */
+ public function __construct(mixed $filePath, int $status = Http::STATUS_OK, array $headers = []) {
+ parent::__construct($status, $headers);
+
+ $this->filePath = $filePath;
+ }
+
+
+ /**
+ * Streams the file using readfile
+ *
+ * @param IOutput $output a small wrapper that handles output
+ * @since 8.1.0
+ */
+ public function callback(IOutput $output) {
+ // handle caching
+ if ($output->getHttpResponseCode() !== Http::STATUS_NOT_MODIFIED) {
+ if (!(is_resource($this->filePath) || file_exists($this->filePath))) {
+ $output->setHttpResponseCode(Http::STATUS_NOT_FOUND);
+ } elseif ($output->setReadfile($this->filePath) === false) {
+ $output->setHttpResponseCode(Http::STATUS_BAD_REQUEST);
+ }
+ }
+ }
+}
diff --git a/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php b/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php
new file mode 100644
index 00000000000..4b074331fc8
--- /dev/null
+++ b/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php
@@ -0,0 +1,70 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+/**
+ * Class StrictContentSecurityPolicy is a simple helper which allows applications to
+ * modify the Content-Security-Policy sent by Nextcloud. Per default only JavaScript,
+ * stylesheets, images, fonts, media and connections from the same domain
+ * ('self') are allowed.
+ *
+ * Even if a value gets modified above defaults will still get appended. Please
+ * note that Nextcloud ships already with sensible defaults and those policies
+ * should require no modification at all for most use-cases.
+ *
+ * This class represents out strictest defaults. They may get change from release
+ * to release if more strict CSP directives become available.
+ *
+ * @since 14.0.0
+ * @deprecated 17.0.0
+ */
+class StrictContentSecurityPolicy extends EmptyContentSecurityPolicy {
+ /** @var bool Whether inline JS snippets are allowed */
+ protected $inlineScriptAllowed = false;
+ /** @var bool Whether eval in JS scripts is allowed */
+ protected $evalScriptAllowed = false;
+ /** @var bool Whether WebAssembly compilation is allowed */
+ protected ?bool $evalWasmAllowed = false;
+ /** @var array Domains from which scripts can get loaded */
+ protected $allowedScriptDomains = [
+ '\'self\'',
+ ];
+ /** @var bool Whether inline CSS is allowed */
+ protected $inlineStyleAllowed = false;
+ /** @var array Domains from which CSS can get loaded */
+ protected $allowedStyleDomains = [
+ '\'self\'',
+ ];
+ /** @var array Domains from which images can get loaded */
+ protected $allowedImageDomains = [
+ '\'self\'',
+ 'data:',
+ 'blob:',
+ ];
+ /** @var array Domains to which connections can be done */
+ protected $allowedConnectDomains = [
+ '\'self\'',
+ ];
+ /** @var array Domains from which media elements can be loaded */
+ protected $allowedMediaDomains = [
+ '\'self\'',
+ ];
+ /** @var array Domains from which object elements can be loaded */
+ protected $allowedObjectDomains = [];
+ /** @var array Domains from which iframes can be loaded */
+ protected $allowedFrameDomains = [];
+ /** @var array Domains from which fonts can be loaded */
+ protected $allowedFontDomains = [
+ '\'self\'',
+ ];
+ /** @var array Domains from which web-workers and nested browsing content can load elements */
+ protected $allowedChildSrcDomains = [];
+
+ /** @var array Domains which can embed this Nextcloud instance */
+ protected $allowedFrameAncestors = [];
+}
diff --git a/lib/public/AppFramework/Http/StrictEvalContentSecurityPolicy.php b/lib/public/AppFramework/Http/StrictEvalContentSecurityPolicy.php
new file mode 100644
index 00000000000..b59dd0fcce7
--- /dev/null
+++ b/lib/public/AppFramework/Http/StrictEvalContentSecurityPolicy.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+/**
+ * Class StrictEvalContentSecurityPolicy is a simple helper which allows applications to
+ * modify the Content-Security-Policy sent by Nextcloud. Per default only JavaScript,
+ * stylesheets, images, fonts, media and connections from the same domain
+ * ('self') are allowed.
+ *
+ * Even if a value gets modified above defaults will still get appended. Please
+ * note that Nextcloud ships already with sensible defaults and those policies
+ * should require no modification at all for most use-cases.
+ *
+ * This is a temp helper class from the default ContentSecurityPolicy to allow slow
+ * migration to a stricter CSP. This does not allow unsafe eval.
+ *
+ * @since 14.0.0
+ * @deprecated 17.0.0
+ */
+class StrictEvalContentSecurityPolicy extends ContentSecurityPolicy {
+ /**
+ * @since 14.0.0
+ */
+ public function __construct() {
+ $this->evalScriptAllowed = false;
+ }
+}
diff --git a/lib/public/AppFramework/Http/StrictInlineContentSecurityPolicy.php b/lib/public/AppFramework/Http/StrictInlineContentSecurityPolicy.php
new file mode 100644
index 00000000000..e80d37c74cf
--- /dev/null
+++ b/lib/public/AppFramework/Http/StrictInlineContentSecurityPolicy.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+/**
+ * Class StrictInlineContentSecurityPolicy is a simple helper which allows applications to
+ * modify the Content-Security-Policy sent by Nextcloud. Per default only JavaScript,
+ * stylesheets, images, fonts, media and connections from the same domain
+ * ('self') are allowed.
+ *
+ * Even if a value gets modified above defaults will still get appended. Please
+ * note that Nextcloud ships already with sensible defaults and those policies
+ * should require no modification at all for most use-cases.
+ *
+ * This is a temp helper class from the default ContentSecurityPolicy to allow slow
+ * migration to a stricter CSP. This does not allow inline styles.
+ *
+ * @since 14.0.0
+ * @deprecated 17.0.0
+ */
+class StrictInlineContentSecurityPolicy extends ContentSecurityPolicy {
+ /**
+ * @since 14.0.0
+ */
+ public function __construct() {
+ $this->inlineStyleAllowed = false;
+ }
+}
diff --git a/lib/public/AppFramework/Http/Template/ExternalShareMenuAction.php b/lib/public/AppFramework/Http/Template/ExternalShareMenuAction.php
new file mode 100644
index 00000000000..281bb559a10
--- /dev/null
+++ b/lib/public/AppFramework/Http/Template/ExternalShareMenuAction.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http\Template;
+
+/**
+ * Class LinkMenuAction
+ *
+ * @since 14.0.0
+ */
+class ExternalShareMenuAction extends SimpleMenuAction {
+
+ /**
+ * ExternalShareMenuAction constructor.
+ *
+ * @param string $label Translated label
+ * @param string $icon Icon CSS class
+ * @param string $owner Owner user ID (unused)
+ * @param string $displayname Display name of the owner (unused)
+ * @param string $shareName Name of the share (unused)
+ * @since 14.0.0
+ */
+ public function __construct(string $label, string $icon, string $owner, string $displayname, string $shareName) {
+ parent::__construct('save', $label, $icon);
+ }
+}
diff --git a/lib/public/AppFramework/Http/Template/IMenuAction.php b/lib/public/AppFramework/Http/Template/IMenuAction.php
new file mode 100644
index 00000000000..124e95fe019
--- /dev/null
+++ b/lib/public/AppFramework/Http/Template/IMenuAction.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http\Template;
+
+/**
+ * Interface IMenuAction
+ *
+ * @since 14.0
+ */
+interface IMenuAction {
+ /**
+ * @since 14.0.0
+ * @return string
+ */
+ public function getId(): string;
+
+ /**
+ * The translated label of the menu item.
+ *
+ * @since 14.0.0
+ * @return string
+ */
+ public function getLabel(): string;
+
+ /**
+ * The link this menu item points to.
+ *
+ * @since 14.0.0
+ * @return string
+ */
+ public function getLink(): string;
+
+ /**
+ * @since 14.0.0
+ * @return int
+ */
+ public function getPriority(): int;
+
+ /**
+ * Custom render function.
+ * The returned HTML will be wrapped within a listitem element (`<li>...</li>`).
+ *
+ * @since 14.0.0
+ * @return string
+ */
+ public function render(): string;
+}
diff --git a/lib/public/AppFramework/Http/Template/LinkMenuAction.php b/lib/public/AppFramework/Http/Template/LinkMenuAction.php
new file mode 100644
index 00000000000..391802a1dce
--- /dev/null
+++ b/lib/public/AppFramework/Http/Template/LinkMenuAction.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http\Template;
+
+/**
+ * Class LinkMenuAction
+ *
+ * @since 14.0.0
+ */
+class LinkMenuAction extends SimpleMenuAction {
+ /**
+ * LinkMenuAction constructor.
+ *
+ * @param string $label
+ * @param string $icon
+ * @param string $link
+ * @since 14.0.0
+ */
+ public function __construct(string $label, string $icon, string $link) {
+ parent::__construct('directLink', $label, $icon, $link);
+ }
+}
diff --git a/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php b/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php
new file mode 100644
index 00000000000..4c156cdecea
--- /dev/null
+++ b/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http\Template;
+
+use InvalidArgumentException;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IInitialStateService;
+
+/**
+ * Class PublicTemplateResponse
+ *
+ * @since 14.0.0
+ * @template H of array<string, mixed>
+ * @template S of Http::STATUS_*
+ * @template-extends TemplateResponse<Http::STATUS_*, array<string, mixed>>
+ */
+class PublicTemplateResponse extends TemplateResponse {
+ private $headerTitle = '';
+ private $headerDetails = '';
+ /** @var IMenuAction[] */
+ private $headerActions = [];
+ private $footerVisible = true;
+
+ /**
+ * PublicTemplateResponse constructor.
+ *
+ * @param string $appName
+ * @param string $templateName
+ * @param array $params
+ * @param S $status
+ * @param H $headers
+ * @since 14.0.0
+ */
+ public function __construct(
+ string $appName,
+ string $templateName,
+ array $params = [],
+ $status = Http::STATUS_OK,
+ array $headers = [],
+ ) {
+ parent::__construct($appName, $templateName, $params, 'public', $status, $headers);
+ \OCP\Util::addScript('core', 'public-page-menu');
+ \OCP\Util::addScript('core', 'public-page-user-menu');
+
+ $state = \OCP\Server::get(IInitialStateService::class);
+ $state->provideLazyInitialState('core', 'public-page-menu', function () {
+ $response = [];
+ foreach ($this->headerActions as $action) {
+ // First try in it is a custom action that provides rendered HTML
+ $rendered = $action->render();
+ if ($rendered === '') {
+ // If simple action, add the response data
+ if ($action instanceof SimpleMenuAction) {
+ $response[] = $action->getData();
+ }
+ } else {
+ // custom action so add the rendered output
+ $response[] = [
+ 'id' => $action->getId(),
+ 'label' => $action->getLabel(),
+ 'html' => $rendered,
+ ];
+ }
+ }
+ return $response;
+ });
+ }
+
+ /**
+ * @param string $title
+ * @since 14.0.0
+ */
+ public function setHeaderTitle(string $title) {
+ $this->headerTitle = $title;
+ }
+
+ /**
+ * @return string
+ * @since 14.0.0
+ */
+ public function getHeaderTitle(): string {
+ return $this->headerTitle;
+ }
+
+ /**
+ * @param string $details
+ * @since 14.0.0
+ */
+ public function setHeaderDetails(string $details) {
+ $this->headerDetails = $details;
+ }
+
+ /**
+ * @return string
+ * @since 14.0.0
+ */
+ public function getHeaderDetails(): string {
+ return $this->headerDetails;
+ }
+
+ /**
+ * @param array $actions
+ * @since 14.0.0
+ * @throws InvalidArgumentException
+ */
+ public function setHeaderActions(array $actions) {
+ foreach ($actions as $action) {
+ if ($actions instanceof IMenuAction) {
+ throw new InvalidArgumentException('Actions must be of type IMenuAction');
+ }
+ $this->headerActions[] = $action;
+ }
+ usort($this->headerActions, function (IMenuAction $a, IMenuAction $b) {
+ return $a->getPriority() <=> $b->getPriority();
+ });
+ }
+
+ /**
+ * @return IMenuAction
+ * @since 14.0.0
+ * @throws \Exception
+ */
+ public function getPrimaryAction(): IMenuAction {
+ if ($this->getActionCount() > 0) {
+ return $this->headerActions[0];
+ }
+ throw new \Exception('No header actions have been set');
+ }
+
+ /**
+ * @return int
+ * @since 14.0.0
+ */
+ public function getActionCount(): int {
+ return count($this->headerActions);
+ }
+
+ /**
+ * @return IMenuAction[]
+ * @since 14.0.0
+ */
+ public function getOtherActions(): array {
+ return array_slice($this->headerActions, 1);
+ }
+
+ /**
+ * @since 14.0.0
+ */
+ public function setFooterVisible(bool $visible = false) {
+ $this->footerVisible = $visible;
+ }
+
+ /**
+ * @since 14.0.0
+ */
+ public function getFooterVisible(): bool {
+ return $this->footerVisible;
+ }
+
+ /**
+ * @return string
+ * @since 14.0.0
+ */
+ public function render(): string {
+ $params = array_merge($this->getParams(), [
+ 'template' => $this,
+ ]);
+ $this->setParams($params);
+ return parent::render();
+ }
+}
diff --git a/lib/public/AppFramework/Http/Template/SimpleMenuAction.php b/lib/public/AppFramework/Http/Template/SimpleMenuAction.php
new file mode 100644
index 00000000000..03cb9b4c7ea
--- /dev/null
+++ b/lib/public/AppFramework/Http/Template/SimpleMenuAction.php
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http\Template;
+
+/**
+ * Class SimpleMenuAction
+ *
+ * @since 14.0.0
+ */
+class SimpleMenuAction implements IMenuAction {
+ /** @var string */
+ private $id;
+
+ /** @var string */
+ private $label;
+
+ /** @var string */
+ private $icon;
+
+ /** @var string */
+ private $link;
+
+ /** @var int */
+ private $priority;
+
+ /** @var string */
+ private $detail;
+
+ /**
+ * SimpleMenuAction constructor.
+ *
+ * @param string $id
+ * @param string $label
+ * @param string $icon
+ * @param string $link
+ * @param int $priority
+ * @param string $detail
+ * @since 14.0.0
+ */
+ public function __construct(string $id, string $label, string $icon, string $link = '', int $priority = 100, string $detail = '') {
+ $this->id = $id;
+ $this->label = $label;
+ $this->icon = $icon;
+ $this->link = $link;
+ $this->priority = $priority;
+ $this->detail = $detail;
+ }
+
+ /**
+ * @return string
+ * @since 14.0.0
+ */
+ public function getId(): string {
+ return $this->id;
+ }
+
+ /**
+ * @return string
+ * @since 14.0.0
+ */
+ public function getLabel(): string {
+ return $this->label;
+ }
+
+ /**
+ * The icon CSS class to use.
+ *
+ * @return string
+ * @since 14.0.0
+ */
+ public function getIcon(): string {
+ return $this->icon;
+ }
+
+ /**
+ * @return string
+ * @since 14.0.0
+ */
+ public function getLink(): string {
+ return $this->link;
+ }
+
+ /**
+ * @return int
+ * @since 14.0.0
+ */
+ public function getPriority(): int {
+ return $this->priority;
+ }
+
+ /**
+ * Custom render function.
+ * The returned HTML must be wrapped within a listitem (`<li>...</li>`).
+ * * If an empty string is returned, the default design is used (based on the label and link specified).
+ * @return string
+ * @since 14.0.0
+ */
+ public function render(): string {
+ return '';
+ }
+
+ /**
+ * Return JSON data to let the frontend render the menu entry.
+ * @return array{id: string, label: string, href: string, icon: string, details: string|null}
+ * @since 31.0.0
+ */
+ public function getData(): array {
+ return [
+ 'id' => $this->id,
+ 'label' => $this->label,
+ 'href' => $this->link,
+ 'icon' => $this->icon,
+ 'details' => $this->detail,
+ ];
+ }
+}
diff --git a/lib/public/AppFramework/Http/TemplateResponse.php b/lib/public/AppFramework/Http/TemplateResponse.php
new file mode 100644
index 00000000000..af37a1a2313
--- /dev/null
+++ b/lib/public/AppFramework/Http/TemplateResponse.php
@@ -0,0 +1,197 @@
+<?php
+
+declare(strict_types=1);
+
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+use OCP\Server;
+use OCP\Template\ITemplateManager;
+
+/**
+ * Response for a normal template
+ * @since 6.0.0
+ *
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class TemplateResponse extends Response {
+ /**
+ * @since 20.0.0
+ */
+ public const RENDER_AS_GUEST = 'guest';
+ /**
+ * @since 20.0.0
+ */
+ public const RENDER_AS_BLANK = '';
+ /**
+ * @since 20.0.0
+ */
+ public const RENDER_AS_BASE = 'base';
+ /**
+ * @since 20.0.0
+ */
+ public const RENDER_AS_USER = 'user';
+ /**
+ * @since 20.0.0
+ */
+ public const RENDER_AS_ERROR = 'error';
+ /**
+ * @since 20.0.0
+ */
+ public const RENDER_AS_PUBLIC = 'public';
+
+ /**
+ * name of the template
+ * @var string
+ */
+ protected $templateName;
+
+ /**
+ * parameters
+ * @var array
+ */
+ protected $params;
+
+ /**
+ * rendering type (admin, user, blank)
+ * @var string
+ */
+ protected $renderAs;
+
+ /**
+ * app name
+ * @var string
+ */
+ protected $appName;
+
+ /**
+ * constructor of TemplateResponse
+ * @param string $appName the name of the app to load the template from
+ * @param string $templateName the name of the template
+ * @param array $params an array of parameters which should be passed to the
+ * template
+ * @param string $renderAs how the page should be rendered, defaults to user
+ * @param S $status
+ * @param H $headers
+ * @since 6.0.0 - parameters $params and $renderAs were added in 7.0.0
+ */
+ public function __construct(string $appName, string $templateName, array $params = [], string $renderAs = self::RENDER_AS_USER, int $status = Http::STATUS_OK, array $headers = []) {
+ parent::__construct($status, $headers);
+
+ $this->templateName = $templateName;
+ $this->appName = $appName;
+ $this->params = $params;
+ $this->renderAs = $renderAs;
+
+ $this->setContentSecurityPolicy(new ContentSecurityPolicy());
+ $this->setFeaturePolicy(new FeaturePolicy());
+ }
+
+
+ /**
+ * Sets template parameters
+ * @param array $params an array with key => value structure which sets template
+ * variables
+ * @return TemplateResponse Reference to this object
+ * @since 6.0.0 - return value was added in 7.0.0
+ */
+ public function setParams(array $params) {
+ $this->params = $params;
+
+ return $this;
+ }
+
+
+ /**
+ * Used for accessing the set parameters
+ * @return array the params
+ * @since 6.0.0
+ */
+ public function getParams() {
+ return $this->params;
+ }
+
+
+ /**
+ * @return string the app id of the used template
+ * @since 25.0.0
+ */
+ public function getApp(): string {
+ return $this->appName;
+ }
+
+
+ /**
+ * Used for accessing the name of the set template
+ * @return string the name of the used template
+ * @since 6.0.0
+ */
+ public function getTemplateName() {
+ return $this->templateName;
+ }
+
+
+ /**
+ * Sets the template page
+ * @param string $renderAs admin, user or blank. Admin also prints the admin
+ * settings header and footer, user renders the normal
+ * normal page including footer and header and blank
+ * just renders the plain template
+ * @return TemplateResponse Reference to this object
+ * @since 6.0.0 - return value was added in 7.0.0
+ */
+ public function renderAs($renderAs) {
+ $this->renderAs = $renderAs;
+
+ return $this;
+ }
+
+
+ /**
+ * Returns the set renderAs
+ * @return string the renderAs value
+ * @since 6.0.0
+ */
+ public function getRenderAs() {
+ return $this->renderAs;
+ }
+
+
+ /**
+ * Returns the rendered html
+ * @return string the rendered html
+ * @since 6.0.0
+ */
+ public function render() {
+ $renderAs = self::RENDER_AS_USER;
+ if ($this->renderAs === 'blank') {
+ // Legacy fallback as \OCP\Template needs an empty string instead of 'blank' for an unwrapped response
+ $renderAs = self::RENDER_AS_BLANK;
+ } elseif (in_array($this->renderAs, [
+ self::RENDER_AS_GUEST,
+ self::RENDER_AS_BLANK,
+ self::RENDER_AS_BASE,
+ self::RENDER_AS_ERROR,
+ self::RENDER_AS_PUBLIC,
+ self::RENDER_AS_USER], true)) {
+ $renderAs = $this->renderAs;
+ }
+
+ $template = Server::get(ITemplateManager::class)->getTemplate($this->appName, $this->templateName, $renderAs);
+
+ foreach ($this->params as $key => $value) {
+ $template->assign($key, $value);
+ }
+
+ return $template->fetchPage($this->params);
+ }
+}
diff --git a/lib/public/AppFramework/Http/TextPlainResponse.php b/lib/public/AppFramework/Http/TextPlainResponse.php
new file mode 100644
index 00000000000..9dfa2c5544d
--- /dev/null
+++ b/lib/public/AppFramework/Http/TextPlainResponse.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+
+/**
+ * A renderer for text responses
+ * @since 22.0.0
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class TextPlainResponse extends Response {
+ /** @var string */
+ private $text = '';
+
+ /**
+ * constructor of TextPlainResponse
+ * @param string $text The text body
+ * @param S $statusCode the Http status code, defaults to 200
+ * @param H $headers
+ * @since 22.0.0
+ */
+ public function __construct(string $text = '', int $statusCode = Http::STATUS_OK, array $headers = []) {
+ parent::__construct($statusCode, $headers);
+
+ $this->text = $text;
+ $this->addHeader('Content-Type', 'text/plain');
+ }
+
+
+ /**
+ * Returns the text
+ * @return string
+ * @since 22.0.0
+ * @throws \Exception If data could not get encoded
+ */
+ public function render() : string {
+ return $this->text;
+ }
+}
diff --git a/lib/public/AppFramework/Http/TooManyRequestsResponse.php b/lib/public/AppFramework/Http/TooManyRequestsResponse.php
new file mode 100644
index 00000000000..f7084ec768d
--- /dev/null
+++ b/lib/public/AppFramework/Http/TooManyRequestsResponse.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+use OCP\AppFramework\Http;
+use OCP\Server;
+use OCP\Template\ITemplateManager;
+
+/**
+ * A generic 429 response showing an 404 error page as well to the end-user
+ * @since 19.0.0
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class TooManyRequestsResponse extends Response {
+ /**
+ * @param S $status
+ * @param H $headers
+ * @since 19.0.0
+ */
+ public function __construct(int $status = Http::STATUS_TOO_MANY_REQUESTS, array $headers = []) {
+ parent::__construct($status, $headers);
+
+ $this->setContentSecurityPolicy(new ContentSecurityPolicy());
+ }
+
+ /**
+ * @return string
+ * @since 19.0.0
+ */
+ public function render() {
+ $template = Server::get(ITemplateManager::class)->getTemplate('core', '429', TemplateResponse::RENDER_AS_BLANK);
+ return $template->fetchPage();
+ }
+}
diff --git a/lib/public/AppFramework/Http/ZipResponse.php b/lib/public/AppFramework/Http/ZipResponse.php
new file mode 100644
index 00000000000..a552eb1294f
--- /dev/null
+++ b/lib/public/AppFramework/Http/ZipResponse.php
@@ -0,0 +1,77 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Http;
+
+use OC\Streamer;
+use OCP\AppFramework\Http;
+use OCP\IRequest;
+
+/**
+ * Public library to send several files in one zip archive.
+ *
+ * @since 15.0.0
+ * @template S of Http::STATUS_*
+ * @template H of array<string, mixed>
+ * @template-extends Response<Http::STATUS_*, array<string, mixed>>
+ */
+class ZipResponse extends Response implements ICallbackResponse {
+ /** @var array{internalName: string, resource: resource, size: int, time: int}[] Files to be added to the zip response */
+ private array $resources = [];
+ /** @var string Filename that the zip file should have */
+ private string $name;
+ private IRequest $request;
+
+ /**
+ * @param S $status
+ * @param H $headers
+ * @since 15.0.0
+ */
+ public function __construct(IRequest $request, string $name = 'output', int $status = Http::STATUS_OK, array $headers = []) {
+ parent::__construct($status, $headers);
+
+ $this->name = $name;
+ $this->request = $request;
+ }
+
+ /**
+ * @since 15.0.0
+ */
+ public function addResource($r, string $internalName, int $size, int $time = -1) {
+ if (!\is_resource($r)) {
+ throw new \InvalidArgumentException('No resource provided');
+ }
+
+ $this->resources[] = [
+ 'resource' => $r,
+ 'internalName' => $internalName,
+ 'size' => $size,
+ 'time' => $time,
+ ];
+ }
+
+ /**
+ * @since 15.0.0
+ */
+ public function callback(IOutput $output) {
+ $size = 0;
+ $files = count($this->resources);
+
+ foreach ($this->resources as $resource) {
+ $size += $resource['size'];
+ }
+
+ $zip = new Streamer($this->request, $size, $files);
+ $zip->sendHeaders($this->name);
+
+ foreach ($this->resources as $resource) {
+ $zip->addFileFromStream($resource['resource'], $resource['internalName'], $resource['size'], $resource['time']);
+ }
+
+ $zip->finalize();
+ }
+}
diff --git a/lib/public/AppFramework/IAppContainer.php b/lib/public/AppFramework/IAppContainer.php
new file mode 100644
index 00000000000..c20b252b0ce
--- /dev/null
+++ b/lib/public/AppFramework/IAppContainer.php
@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework;
+
+use OCP\IContainer;
+use Psr\Container\ContainerInterface;
+
+/**
+ * This is a tagging interface for a container that belongs to an app
+ *
+ * The interface currently extends IContainer, but this interface is deprecated as of Nextcloud 20,
+ * thus this interface won't extend it anymore once that was removed. So migrate to the ContainerInterface
+ * only.
+ *
+ * @deprecated 20.0.0
+ * @since 6.0.0
+ */
+interface IAppContainer extends ContainerInterface, IContainer {
+ /**
+ * used to return the appname of the set application
+ * @return string the name of your application
+ * @since 6.0.0
+ * @deprecated 20.0.0
+ */
+ public function getAppName();
+
+ /**
+ * @return \OCP\IServerContainer
+ * @since 6.0.0
+ * @deprecated 20.0.0
+ */
+ public function getServer();
+
+ /**
+ * @param string $middleWare
+ * @return boolean
+ * @since 6.0.0
+ * @deprecated 20.0.0 use \OCP\AppFramework\Bootstrap\IRegistrationContext::registerMiddleware
+ */
+ public function registerMiddleWare($middleWare);
+
+ /**
+ * Register a capability
+ *
+ * @param string $serviceName e.g. 'OCA\Files\Capabilities'
+ * @since 8.2.0
+ * @deprecated 20.0.0 use \OCP\AppFramework\Bootstrap\IRegistrationContext::registerCapability
+ */
+ public function registerCapability($serviceName);
+}
diff --git a/lib/public/AppFramework/Middleware.php b/lib/public/AppFramework/Middleware.php
new file mode 100644
index 00000000000..33bc288780e
--- /dev/null
+++ b/lib/public/AppFramework/Middleware.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework;
+
+use Exception;
+use OCP\AppFramework\Http\Response;
+
+/**
+ * Middleware is used to provide hooks before or after controller methods and
+ * deal with possible exceptions raised in the controller methods.
+ * They're modeled after Django's middleware system:
+ * https://docs.djangoproject.com/en/dev/topics/http/middleware/
+ * @since 6.0.0
+ */
+abstract class Middleware {
+ /**
+ * This is being run in normal order before the controller is being
+ * called which allows several modifications and checks
+ *
+ * @param Controller $controller the controller that is being called
+ * @param string $methodName the name of the method that will be called on
+ * the controller
+ * @return void
+ * @since 6.0.0
+ */
+ public function beforeController(Controller $controller, string $methodName) {
+ }
+
+
+ /**
+ * This is being run when either the beforeController method or the
+ * controller method itself is throwing an exception. The middleware is
+ * asked in reverse order to handle the exception and to return a response.
+ * If the response is null, it is assumed that the exception could not be
+ * handled and the error will be thrown again
+ *
+ * @param Controller $controller the controller that is being called
+ * @param string $methodName the name of the method that will be called on
+ * the controller
+ * @param Exception $exception the thrown exception
+ * @throws Exception the passed in exception if it can't handle it
+ * @return Response a Response object in case that the exception was handled
+ * @since 6.0.0
+ */
+ public function afterException(Controller $controller, string $methodName, Exception $exception) {
+ throw $exception;
+ }
+
+
+ /**
+ * This is being run after a successful controllermethod call and allows
+ * the manipulation of a Response object. The middleware is run in reverse order
+ *
+ * @param Controller $controller the controller that is being called
+ * @param string $methodName the name of the method that will be called on
+ * the controller
+ * @param Response $response the generated response from the controller
+ * @return Response a Response object
+ * @since 6.0.0
+ */
+ public function afterController(Controller $controller, string $methodName, Response $response) {
+ return $response;
+ }
+
+
+ /**
+ * This is being run after the response object has been rendered and
+ * allows the manipulation of the output. The middleware is run in reverse order
+ *
+ * @param Controller $controller the controller that is being called
+ * @param string $methodName the name of the method that will be called on
+ * the controller
+ * @param string $output the generated output from a response
+ * @return string the output that should be printed
+ * @since 6.0.0
+ */
+ public function beforeOutput(Controller $controller, string $methodName, string $output) {
+ return $output;
+ }
+}
diff --git a/lib/public/AppFramework/OCS/OCSBadRequestException.php b/lib/public/AppFramework/OCS/OCSBadRequestException.php
new file mode 100644
index 00000000000..77b8ec6c86d
--- /dev/null
+++ b/lib/public/AppFramework/OCS/OCSBadRequestException.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\OCS;
+
+use Exception;
+use OCP\AppFramework\Http;
+
+/**
+ * Class OCSBadRequestException
+ *
+ * @since 9.1.0
+ */
+class OCSBadRequestException extends OCSException {
+ /**
+ * OCSBadRequestException constructor.
+ *
+ * @param string $message
+ * @param Exception|null $previous
+ * @since 9.1.0
+ */
+ public function __construct($message = '', ?Exception $previous = null) {
+ parent::__construct($message, Http::STATUS_BAD_REQUEST, $previous);
+ }
+}
diff --git a/lib/public/AppFramework/OCS/OCSException.php b/lib/public/AppFramework/OCS/OCSException.php
new file mode 100644
index 00000000000..02901992f8d
--- /dev/null
+++ b/lib/public/AppFramework/OCS/OCSException.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\OCS;
+
+use Exception;
+
+/**
+ * Class OCSException
+ *
+ * @since 9.1.0
+ */
+class OCSException extends Exception {
+}
diff --git a/lib/public/AppFramework/OCS/OCSForbiddenException.php b/lib/public/AppFramework/OCS/OCSForbiddenException.php
new file mode 100644
index 00000000000..0d001377043
--- /dev/null
+++ b/lib/public/AppFramework/OCS/OCSForbiddenException.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\OCS;
+
+use Exception;
+use OCP\AppFramework\Http;
+
+/**
+ * Class OCSForbiddenException
+ *
+ * @since 9.1.0
+ */
+class OCSForbiddenException extends OCSException {
+ /**
+ * OCSForbiddenException constructor.
+ *
+ * @param string $message
+ * @param Exception|null $previous
+ * @since 9.1.0
+ */
+ public function __construct($message = '', ?Exception $previous = null) {
+ parent::__construct($message, Http::STATUS_FORBIDDEN, $previous);
+ }
+}
diff --git a/lib/public/AppFramework/OCS/OCSNotFoundException.php b/lib/public/AppFramework/OCS/OCSNotFoundException.php
new file mode 100644
index 00000000000..67cea9ed759
--- /dev/null
+++ b/lib/public/AppFramework/OCS/OCSNotFoundException.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\OCS;
+
+use Exception;
+use OCP\AppFramework\Http;
+
+/**
+ * Class OCSNotFoundException
+ *
+ * @since 9.1.0
+ */
+class OCSNotFoundException extends OCSException {
+ /**
+ * OCSNotFoundException constructor.
+ *
+ * @param string $message
+ * @param Exception|null $previous
+ * @since 9.1.0
+ */
+ public function __construct($message = '', ?Exception $previous = null) {
+ parent::__construct($message, Http::STATUS_NOT_FOUND, $previous);
+ }
+}
diff --git a/lib/public/AppFramework/OCS/OCSPreconditionFailedException.php b/lib/public/AppFramework/OCS/OCSPreconditionFailedException.php
new file mode 100644
index 00000000000..4fc2820eaec
--- /dev/null
+++ b/lib/public/AppFramework/OCS/OCSPreconditionFailedException.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\OCS;
+
+use Exception;
+use OCP\AppFramework\Http;
+
+/**
+ * Class OCSPreconditionFailedException
+ *
+ * @since 28.0.0
+ */
+class OCSPreconditionFailedException extends OCSException {
+ /**
+ * OCSPreconditionFailedException constructor.
+ *
+ * @param string $message
+ * @param Exception|null $previous
+ * @since 9.1.0
+ */
+ public function __construct($message = '', ?Exception $previous = null) {
+ parent::__construct($message, Http::STATUS_PRECONDITION_FAILED, $previous);
+ }
+}
diff --git a/lib/public/AppFramework/OCSController.php b/lib/public/AppFramework/OCSController.php
new file mode 100644
index 00000000000..7cde2a7e427
--- /dev/null
+++ b/lib/public/AppFramework/OCSController.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework;
+
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\Response;
+use OCP\IRequest;
+
+/**
+ * Base class to inherit your controllers from that are used for RESTful APIs
+ * @since 8.1.0
+ */
+abstract class OCSController extends ApiController {
+ /**
+ * @since 22.0.0
+ */
+ public const RESPOND_UNAUTHORISED = 997;
+
+ /**
+ * @since 22.0.0
+ */
+ public const RESPOND_SERVER_ERROR = 996;
+
+ /**
+ * @since 22.0.0
+ */
+ public const RESPOND_NOT_FOUND = 998;
+
+ /**
+ * @since 22.0.0
+ */
+ public const RESPOND_UNKNOWN_ERROR = 999;
+
+ /** @var int */
+ private $ocsVersion;
+
+ /**
+ * constructor of the controller
+ * @param string $appName the name of the app
+ * @param IRequest $request an instance of the request
+ * @param string $corsMethods comma separated string of HTTP verbs which
+ * should be allowed for websites or webapps when calling your API, defaults to
+ * 'PUT, POST, GET, DELETE, PATCH'
+ * @param string $corsAllowedHeaders comma separated string of HTTP headers
+ * which should be allowed for websites or webapps when calling your API,
+ * defaults to 'Authorization, Content-Type, Accept'
+ * @param int $corsMaxAge number in seconds how long a preflighted OPTIONS
+ * request should be cached, defaults to 1728000 seconds
+ * @since 8.1.0
+ */
+ public function __construct($appName,
+ IRequest $request,
+ $corsMethods = 'PUT, POST, GET, DELETE, PATCH',
+ $corsAllowedHeaders = 'Authorization, Content-Type, Accept, OCS-APIRequest',
+ $corsMaxAge = 1728000) {
+ parent::__construct($appName, $request, $corsMethods,
+ $corsAllowedHeaders, $corsMaxAge);
+ $this->registerResponder('json', function ($data) {
+ return $this->buildOCSResponse('json', $data);
+ });
+ $this->registerResponder('xml', function ($data) {
+ return $this->buildOCSResponse('xml', $data);
+ });
+ }
+
+ /**
+ * @param int $version
+ * @since 11.0.0
+ * @internal
+ */
+ public function setOCSVersion($version) {
+ $this->ocsVersion = $version;
+ }
+
+ /**
+ * Since the OCS endpoints default to XML we need to find out the format
+ * again
+ * @param mixed $response the value that was returned from a controller and
+ * is not a Response instance
+ * @param string $format the format for which a formatter has been registered
+ * @throws \DomainException if format does not match a registered formatter
+ * @return Response
+ * @since 9.1.0
+ */
+ public function buildResponse($response, $format = 'xml') {
+ return parent::buildResponse($response, $format);
+ }
+
+ /**
+ * Unwrap data and build ocs response
+ * @param string $format json or xml
+ * @param DataResponse $data the data which should be transformed
+ * @since 8.1.0
+ * @return \OC\AppFramework\OCS\BaseResponse
+ */
+ private function buildOCSResponse($format, DataResponse $data) {
+ if ($this->ocsVersion === 1) {
+ return new \OC\AppFramework\OCS\V1Response($data, $format);
+ }
+ return new \OC\AppFramework\OCS\V2Response($data, $format);
+ }
+}
diff --git a/lib/public/AppFramework/PublicShareController.php b/lib/public/AppFramework/PublicShareController.php
new file mode 100644
index 00000000000..999b3827565
--- /dev/null
+++ b/lib/public/AppFramework/PublicShareController.php
@@ -0,0 +1,119 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework;
+
+use OCP\IRequest;
+use OCP\ISession;
+
+/**
+ * Base controller for public shares
+ *
+ * It will verify if the user is properly authenticated to the share. If not a 404
+ * is thrown by the PublicShareMiddleware.
+ *
+ * Use this for example for a controller that is not to be called via a webbrowser
+ * directly. For example a PublicPreviewController. As this is not meant to be
+ * called by a user directly.
+ *
+ * To show an auth page extend the AuthPublicShareController
+ *
+ * @since 14.0.0
+ */
+abstract class PublicShareController extends Controller {
+ /** @var ISession */
+ protected $session;
+
+ /** @var string */
+ private $token;
+
+ /**
+ * @since 14.0.0
+ */
+ public function __construct(string $appName,
+ IRequest $request,
+ ISession $session) {
+ parent::__construct($appName, $request);
+
+ $this->session = $session;
+ }
+
+ /**
+ * Middleware set the token for the request
+ *
+ * @since 14.0.0
+ */
+ final public function setToken(string $token) {
+ $this->token = $token;
+ }
+
+ /**
+ * Get the token for this request
+ *
+ * @since 14.0.0
+ */
+ final public function getToken(): string {
+ return $this->token;
+ }
+
+ /**
+ * Get a hash of the password for this share
+ *
+ * To ensure access is blocked when the password to a share is changed we store
+ * a hash of the password for this token.
+ *
+ * @since 14.0.0
+ */
+ abstract protected function getPasswordHash(): ?string;
+
+ /**
+ * Is the provided token a valid token
+ *
+ * This function is already called from the middleware directly after setting the token.
+ *
+ * @since 14.0.0
+ */
+ abstract public function isValidToken(): bool;
+
+ /**
+ * Is a share with this token password protected
+ *
+ * @since 14.0.0
+ */
+ abstract protected function isPasswordProtected(): bool;
+
+ /**
+ * Check if a share is authenticated or not
+ *
+ * @since 14.0.0
+ */
+ public function isAuthenticated(): bool {
+ // Always authenticated against non password protected shares
+ if (!$this->isPasswordProtected()) {
+ return true;
+ }
+
+ // If we are authenticated properly
+ if ($this->session->get('public_link_authenticated_token') === $this->getToken()
+ && $this->session->get('public_link_authenticated_password_hash') === $this->getPasswordHash()) {
+ return true;
+ }
+
+ // Fail by default if nothing matches
+ return false;
+ }
+
+ /**
+ * Function called if the share is not found.
+ *
+ * You can use this to do some logging for example
+ *
+ * @since 14.0.0
+ */
+ public function shareNotFound() {
+ }
+}
diff --git a/lib/public/AppFramework/QueryException.php b/lib/public/AppFramework/QueryException.php
new file mode 100644
index 00000000000..20a964e82c8
--- /dev/null
+++ b/lib/public/AppFramework/QueryException.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework;
+
+use Exception;
+use Psr\Container\ContainerExceptionInterface;
+
+/**
+ * Class QueryException
+ *
+ * The class extends `ContainerExceptionInterface` since 20.0.0
+ *
+ * @since 8.1.0
+ * @deprecated 20.0.0 catch \Psr\Container\ContainerExceptionInterface
+ */
+class QueryException extends Exception implements ContainerExceptionInterface {
+}
diff --git a/lib/public/AppFramework/Services/IAppConfig.php b/lib/public/AppFramework/Services/IAppConfig.php
new file mode 100644
index 00000000000..aa95e665604
--- /dev/null
+++ b/lib/public/AppFramework/Services/IAppConfig.php
@@ -0,0 +1,324 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Services;
+
+use OCP\Exceptions\AppConfigUnknownKeyException;
+
+/**
+ * Wrapper for AppConfig for the AppFramework
+ *
+ * @since 20.0.0
+ */
+interface IAppConfig {
+ /**
+ * Get all keys stored for this app
+ *
+ * @return string[] the keys stored for the app
+ * @since 20.0.0
+ */
+ public function getAppKeys(): array;
+
+ /**
+ * Check if a key exists in the list of stored config values.
+ *
+ * @param string $key config key
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return bool TRUE if key exists
+ * @since 29.0.0
+ */
+ public function hasAppKey(string $key, ?bool $lazy = false): bool;
+
+ /**
+ * best way to see if a value is set as sensitive (not displayed in report)
+ *
+ * @param string $key config key
+ * @param bool|null $lazy search within lazy loaded config
+ *
+ * @return bool TRUE if value is sensitive
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @since 29.0.0
+ */
+ public function isSensitive(string $key, ?bool $lazy = false): bool;
+
+ /**
+ * Returns if the config key stored in database is lazy loaded
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $key config key
+ *
+ * @return bool TRUE if config is lazy loaded
+ * @throws AppConfigUnknownKeyException if config key is not known
+ * @see IAppConfig for details about lazy loading
+ * @since 29.0.0
+ */
+ public function isLazy(string $key): bool;
+
+ /**
+ * List all config values from an app with config key starting with $key.
+ * Returns an array with config key as key, stored value as value.
+ *
+ * **WARNING:** ignore lazy filtering, all config values are loaded from database
+ *
+ * @param string $key config keys prefix to search, can be empty.
+ * @param bool $filtered filter sensitive config values
+ *
+ * @return array<string, string|int|float|bool|array> [configKey => configValue]
+ * @since 29.0.0
+ */
+ public function getAllAppValues(string $key = '', bool $filtered = false): array;
+
+ /**
+ * Writes a new app wide value
+ *
+ * @param string $key the key of the value, under which will be saved
+ * @param string $value the value that should be stored
+ * @return void
+ * @since 20.0.0
+ * @deprecated 29.0.0 use {@see setAppValueString()}
+ */
+ public function setAppValue(string $key, string $value): void;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $key config key
+ * @param string $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function setAppValueString(string $key, string $value, bool $lazy = false, bool $sensitive = false): bool;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * When handling huge value around and/or above 2,147,483,647, a debug log will be generated
+ * on 64bits system, as php int type reach its limit (and throw an exception) on 32bits when using huge numbers.
+ *
+ * When using huge numbers, it is advised to use {@see \OCP\Util::numericToNumber()} and {@see setValueString()}
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $key config key
+ * @param int $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function setAppValueInt(string $key, int $value, bool $lazy = false, bool $sensitive = false): bool;
+
+ /**
+ * Store a config key and its value in database.
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $key config key
+ * @param float $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function setAppValueFloat(string $key, float $value, bool $lazy = false, bool $sensitive = false): bool;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $key config key
+ * @param bool $value config value
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function setAppValueBool(string $key, bool $value, bool $lazy = false): bool;
+
+ /**
+ * Store a config key and its value in database
+ *
+ * If config key is already known with the exact same config value, the database is not updated.
+ * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
+ *
+ * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
+ *
+ * @param string $key config key
+ * @param array $value config value
+ * @param bool $sensitive if TRUE value will be hidden when listing config values.
+ * @param bool $lazy set config as lazy loaded
+ *
+ * @return bool TRUE if value was different, therefor updated in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function setAppValueArray(string $key, array $value, bool $lazy = false, bool $sensitive = false): bool;
+
+ /**
+ * Looks up an app wide defined value
+ *
+ * @param string $key the key of the value, under which it was saved
+ * @param string $default the default value to be returned if the value isn't set
+ *
+ * @return string the saved value
+ * @since 20.0.0
+ * @deprecated 29.0.0 use {@see getAppValueString()}
+ */
+ public function getAppValue(string $key, string $default = ''): string;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $key config key
+ * @param string $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return string stored config value or $default if not set in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function getAppValueString(string $key, string $default = '', bool $lazy = false): string;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $key config key
+ * @param int $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return int stored config value or $default if not set in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function getAppValueInt(string $key, int $default = 0, bool $lazy = false): int;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $key config key
+ * @param float $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return float stored config value or $default if not set in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function getAppValueFloat(string $key, float $default = 0, bool $lazy = false): float;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $key config key
+ * @param bool $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return bool stored config value or $default if not set in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function getAppValueBool(string $key, bool $default = false, bool $lazy = false): bool;
+
+ /**
+ * Get config value assigned to a config key.
+ * If config key is not found in database, default value is returned.
+ * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
+ *
+ * @param string $key config key
+ * @param array $default default value
+ * @param bool $lazy search within lazy loaded config
+ *
+ * @return array stored config value or $default if not set in database
+ * @since 29.0.0
+ * @see \OCP\IAppConfig for explanation about lazy loading
+ */
+ public function getAppValueArray(string $key, array $default = [], bool $lazy = false): array;
+
+ /**
+ * Delete an app wide defined value
+ *
+ * @param string $key the key of the value, under which it was saved
+ * @return void
+ * @since 20.0.0
+ */
+ public function deleteAppValue(string $key): void;
+
+ /**
+ * Removes all keys in appconfig belonging to the app
+ *
+ * @return void
+ * @since 20.0.0
+ */
+ public function deleteAppValues(): void;
+
+ /**
+ * Set a user defined value
+ *
+ * @param string $userId the userId of the user that we want to store the value under
+ * @param string $key the key under which the value is being stored
+ * @param string $value the value that you want to store
+ * @param string $preCondition only update if the config value was previously the value passed as $preCondition
+ * @throws \OCP\PreConditionNotMetException if a precondition is specified and is not met
+ * @throws \UnexpectedValueException when trying to store an unexpected value
+ * @since 20.0.0
+ */
+ public function setUserValue(string $userId, string $key, string $value, ?string $preCondition = null): void;
+
+ /**
+ * Shortcut for getting a user defined value
+ *
+ * @param string $userId the userId of the user that we want to store the value under
+ * @param string $key the key under which the value is being stored
+ * @param mixed $default the default value to be returned if the value isn't set
+ * @return string
+ * @since 20.0.0
+ */
+ public function getUserValue(string $userId, string $key, string $default = ''): string;
+
+ /**
+ * Delete a user value
+ *
+ * @param string $userId the userId of the user that we want to store the value under
+ * @param string $key the key under which the value is being stored
+ * @since 20.0.0
+ */
+ public function deleteUserValue(string $userId, string $key): void;
+}
diff --git a/lib/public/AppFramework/Services/IInitialState.php b/lib/public/AppFramework/Services/IInitialState.php
new file mode 100644
index 00000000000..ac58bcad3cc
--- /dev/null
+++ b/lib/public/AppFramework/Services/IInitialState.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Services;
+
+use Closure;
+
+/**
+ * @since 20.0.0
+ */
+interface IInitialState {
+ /**
+ * Allows an app to provide its initial state to the template system.
+ * Use this if you know your initial state sill be used for example if
+ * you are in the render function of you controller.
+ *
+ * @since 20.0.0
+ *
+ * @param string $key
+ * @param bool|int|float|string|array|\JsonSerializable $data
+ */
+ public function provideInitialState(string $key, $data): void;
+
+ /**
+ * Allows an app to provide its initial state via a lazy method.
+ * This will call the closure when the template is being generated.
+ * Use this if your app is injected into pages. Since then the render method
+ * is not called explicitly. But we do not want to load the state on webdav
+ * requests for example.
+ *
+ * @since 20.0.0
+ *
+ * @param string $key
+ * @param Closure $closure returns a primitive or an object that implements JsonSerializable
+ * @psalm-param Closure():bool|Closure():int|Closure():float|Closure():string|Closure():array|Closure():\JsonSerializable $closure
+ */
+ public function provideLazyInitialState(string $key, Closure $closure): void;
+}
diff --git a/lib/public/AppFramework/Services/InitialStateProvider.php b/lib/public/AppFramework/Services/InitialStateProvider.php
new file mode 100644
index 00000000000..d1607bc2262
--- /dev/null
+++ b/lib/public/AppFramework/Services/InitialStateProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCP\AppFramework\Services;
+
+/**
+ * @since 21.0.0
+ */
+abstract class InitialStateProvider implements \JsonSerializable {
+ /**
+ * @since 21.0.0
+ */
+ abstract public function getKey(): string;
+
+ /**
+ * @since 21.0.0
+ */
+ abstract public function getData();
+
+ /**
+ * @since 21.0.0
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ final public function jsonSerialize() {
+ return $this->getData();
+ }
+}
diff --git a/lib/public/AppFramework/Utility/IControllerMethodReflector.php b/lib/public/AppFramework/Utility/IControllerMethodReflector.php
new file mode 100644
index 00000000000..95d7fbebb56
--- /dev/null
+++ b/lib/public/AppFramework/Utility/IControllerMethodReflector.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Utility;
+
+/**
+ * Interface ControllerMethodReflector
+ *
+ * Reads and parses annotations from doc comments
+ *
+ * @since 8.0.0
+ * @deprecated 22.0.0 will be obsolete with native attributes in PHP8
+ * @see https://help.nextcloud.com/t/how-should-we-use-php8-attributes/104278
+ */
+interface IControllerMethodReflector {
+ /**
+ * @param object $object an object or classname
+ * @param string $method the method which we want to inspect
+ * @return void
+ * @since 8.0.0
+ * @deprecated 17.0.0 Reflect should not be called multiple times and only be used internally. This will be removed in Nextcloud 18
+ */
+ public function reflect($object, string $method);
+
+ /**
+ * Inspects the PHPDoc parameters for types
+ *
+ * @param string $parameter the parameter whose type comments should be
+ * parsed
+ * @return string|null type in the type parameters (@param int $something)
+ * would return int or null if not existing
+ * @since 8.0.0
+ * @deprecated 22.0.0 this method is only used internally
+ */
+ public function getType(string $parameter);
+
+ /**
+ * @return array the arguments of the method with key => default value
+ * @since 8.0.0
+ * @deprecated 22.0.0 this method is only used internally
+ */
+ public function getParameters(): array;
+
+ /**
+ * Check if a method contains an annotation
+ *
+ * @param string $name the name of the annotation
+ * @return bool true if the annotation is found
+ * @since 8.0.0
+ * @deprecated 22.0.0 will be obsolete with native attributes in PHP8
+ * @see https://help.nextcloud.com/t/how-should-we-use-php8-attributes/104278
+ */
+ public function hasAnnotation(string $name): bool;
+}
diff --git a/lib/public/AppFramework/Utility/ITimeFactory.php b/lib/public/AppFramework/Utility/ITimeFactory.php
new file mode 100644
index 00000000000..cd63b94dee3
--- /dev/null
+++ b/lib/public/AppFramework/Utility/ITimeFactory.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OCP\AppFramework\Utility;
+
+use Psr\Clock\ClockInterface;
+
+/**
+ * Use this to get a timestamp or DateTime object in code to remain testable
+ *
+ * @since 8.0.0
+ * @since 27.0.0 Extends the \Psr\Clock\ClockInterface interface
+ * @ref https://www.php-fig.org/psr/psr-20/#21-clockinterface
+ */
+
+interface ITimeFactory extends ClockInterface {
+ /**
+ * @return int the result of a call to time()
+ * @since 8.0.0
+ */
+ public function getTime(): int;
+
+ /**
+ * @param string $time
+ * @param \DateTimeZone|null $timezone
+ * @return \DateTime
+ * @since 15.0.0
+ */
+ public function getDateTime(string $time = 'now', ?\DateTimeZone $timezone = null): \DateTime;
+
+ /**
+ * @param \DateTimeZone $timezone
+ * @return static
+ * @since 26.0.0
+ */
+ public function withTimeZone(\DateTimeZone $timezone): static;
+
+ /**
+ * @param string|null $timezone
+ * @return \DateTimeZone Requested timezone if provided, UTC otherwise
+ * @throws \Exception
+ * @since 29.0.0
+ */
+ public function getTimeZone(?string $timezone = null): \DateTimeZone;
+}