diff options
Diffstat (limited to 'lib/private/AppFramework')
27 files changed, 212 insertions, 519 deletions
diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php index c5f61d7e938..e719ea19f90 100644 --- a/lib/private/AppFramework/App.php +++ b/lib/private/AppFramework/App.php @@ -101,7 +101,7 @@ class App { $profiler->setEnabled($profiler->isEnabled() && !is_null($urlParams) && isset($urlParams['_route']) && !str_starts_with($urlParams['_route'], 'profiler.')); if ($profiler->isEnabled()) { \OC::$server->get(IEventLogger::class)->activate(); - $profiler->add(new RoutingDataCollector($container['AppName'], $controllerName, $methodName)); + $profiler->add(new RoutingDataCollector($container['appName'], $controllerName, $methodName)); } $eventLogger->start('app:controller:params', 'Gather controller parameters'); @@ -115,7 +115,7 @@ class App { $request = $container->get(IRequest::class); $request->setUrlParameters($container['urlParams']); } - $appName = $container['AppName']; + $appName = $container['appName']; $eventLogger->end('app:controller:params'); diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index 2b04d291730..64e3dbfd928 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -20,6 +20,7 @@ use OCP\Dashboard\IManager; use OCP\Diagnostics\IEventLogger; use OCP\EventDispatcher\IEventDispatcher; use OCP\IServerContainer; +use Psr\Container\ContainerExceptionInterface; use Psr\Log\LoggerInterface; use Throwable; use function class_exists; @@ -69,26 +70,32 @@ class Coordinator { */ try { $path = $this->appManager->getAppPath($appId); + OC_App::registerAutoloading($appId, $path); } catch (AppPathNotFoundException) { // Ignore continue; } - OC_App::registerAutoloading($appId, $path); $this->eventLogger->end("bootstrap:register_app:$appId:autoloader"); /* * Next we check if there is an application class, and it implements * the \OCP\AppFramework\Bootstrap\IBootstrap interface */ - $appNameSpace = App::buildAppNamespace($appId); + if ($appId === 'core') { + $appNameSpace = 'OC\\Core'; + } else { + $appNameSpace = App::buildAppNamespace($appId); + } $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; + try { - if (class_exists($applicationClassName) && in_array(IBootstrap::class, class_implements($applicationClassName), true)) { + if (class_exists($applicationClassName) && is_a($applicationClassName, IBootstrap::class, true)) { $this->eventLogger->start("bootstrap:register_app:$appId:application", "Load `Application` instance for $appId"); try { - /** @var IBootstrap|App $application */ - $apps[$appId] = $application = $this->serverContainer->query($applicationClassName); - } catch (QueryException $e) { + /** @var IBootstrap&App $application */ + $application = $this->serverContainer->query($applicationClassName); + $apps[$appId] = $application; + } catch (ContainerExceptionInterface $e) { // Weird, but ok $this->eventLogger->end("bootstrap:register_app:$appId"); continue; @@ -171,7 +178,7 @@ class Coordinator { public function isBootable(string $appId) { $appNameSpace = App::buildAppNamespace($appId); $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; - return class_exists($applicationClassName) && - in_array(IBootstrap::class, class_implements($applicationClassName), true); + return class_exists($applicationClassName) + && in_array(IBootstrap::class, class_implements($applicationClassName), true); } } diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php index c3b829825c2..95ad129c466 100644 --- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php +++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -157,7 +157,7 @@ class RegistrationContext { /** @var ServiceRegistration<\OCP\Files\Conversion\IConversionProvider>[] */ private array $fileConversionProviders = []; - + /** @var ServiceRegistration<IMailProvider>[] */ private $mailProviders = []; diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index b6e2df4ce7b..5ccc1b7d348 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -1,20 +1,33 @@ <?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 OC\AppFramework\DependencyInjection; -use OC; use OC\AppFramework\Http; use OC\AppFramework\Http\Dispatcher; use OC\AppFramework\Http\Output; +use OC\AppFramework\Middleware\AdditionalScriptsMiddleware; +use OC\AppFramework\Middleware\CompressionMiddleware; use OC\AppFramework\Middleware\FlowV2EphemeralSessionsMiddleware; use OC\AppFramework\Middleware\MiddlewareDispatcher; +use OC\AppFramework\Middleware\NotModifiedMiddleware; use OC\AppFramework\Middleware\OCSMiddleware; +use OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware; +use OC\AppFramework\Middleware\Security\BruteForceMiddleware; use OC\AppFramework\Middleware\Security\CORSMiddleware; +use OC\AppFramework\Middleware\Security\CSPMiddleware; +use OC\AppFramework\Middleware\Security\FeaturePolicyMiddleware; +use OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware; use OC\AppFramework\Middleware\Security\RateLimitingMiddleware; +use OC\AppFramework\Middleware\Security\ReloadExecutionMiddleware; +use OC\AppFramework\Middleware\Security\SameSiteCookieMiddleware; use OC\AppFramework\Middleware\Security\SecurityMiddleware; use OC\AppFramework\Middleware\SessionMiddleware; use OC\AppFramework\ScopedPsrLogger; @@ -24,7 +37,6 @@ use OC\Diagnostics\EventLogger; use OC\Log\PsrLoggerAdapter; use OC\ServerContainer; use OC\Settings\AuthorizedGroupMapper; -use OC\User\Manager as UserManager; use OCA\WorkflowEngine\Manager; use OCP\AppFramework\Http\IOutput; use OCP\AppFramework\IAppContainer; @@ -32,14 +44,13 @@ use OCP\AppFramework\QueryException; use OCP\AppFramework\Services\IAppConfig; use OCP\AppFramework\Services\IInitialState; use OCP\AppFramework\Utility\IControllerMethodReflector; -use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\AppData\IAppDataFactory; use OCP\Files\Folder; use OCP\Files\IAppData; use OCP\Group\ISubAdmin; use OCP\IConfig; use OCP\IDBConnection; use OCP\IGroupManager; -use OCP\IInitialStateService; use OCP\IL10N; use OCP\INavigationManager; use OCP\IRequest; @@ -47,40 +58,24 @@ use OCP\IServerContainer; use OCP\ISession; use OCP\IURLGenerator; use OCP\IUserSession; -use OCP\Security\Bruteforce\IThrottler; use OCP\Security\Ip\IRemoteAddress; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -/** - * @deprecated 20.0.0 - */ class DIContainer extends SimpleContainer implements IAppContainer { private string $appName; + private array $middleWares = []; + private ServerContainer $server; - /** - * @var array - */ - private $middleWares = []; - - /** @var ServerContainer */ - private $server; - - /** - * Put your class dependencies in here - * @param string $appName the name of the app - * @param array $urlParams - * @param ServerContainer|null $server - */ public function __construct(string $appName, array $urlParams = [], ?ServerContainer $server = null) { parent::__construct(); $this->appName = $appName; - $this['appName'] = $appName; - $this['urlParams'] = $urlParams; + $this->registerParameter('appName', $appName); + $this->registerParameter('urlParams', $urlParams); - $this->registerAlias('Request', IRequest::class); + /** @deprecated 32.0.0 */ + $this->registerDeprecatedAlias('Request', IRequest::class); - /** @var \OC\ServerContainer $server */ if ($server === null) { $server = \OC::$server; } @@ -89,74 +84,73 @@ class DIContainer extends SimpleContainer implements IAppContainer { // aliases /** @deprecated 26.0.0 inject $appName */ - $this->registerAlias('AppName', 'appName'); + $this->registerDeprecatedAlias('AppName', 'appName'); /** @deprecated 26.0.0 inject $webRoot*/ - $this->registerAlias('WebRoot', 'webRoot'); + $this->registerDeprecatedAlias('WebRoot', 'webRoot'); /** @deprecated 26.0.0 inject $userId */ - $this->registerAlias('UserId', 'userId'); + $this->registerDeprecatedAlias('UserId', 'userId'); /** * Core services */ - $this->registerService(IOutput::class, function () { - return new Output($this->getServer()->getWebRoot()); - }); + /* Cannot be an alias because Output is not in OCA */ + $this->registerService(IOutput::class, fn (ContainerInterface $c): IOutput => new Output($c->get('webRoot'))); $this->registerService(Folder::class, function () { return $this->getServer()->getUserFolder(); }); - $this->registerService(IAppData::class, function (ContainerInterface $c) { - return $this->getServer()->getAppDataDir($c->get('AppName')); + $this->registerService(IAppData::class, function (ContainerInterface $c): IAppData { + return $c->get(IAppDataFactory::class)->get($c->get('appName')); }); $this->registerService(IL10N::class, function (ContainerInterface $c) { - return $this->getServer()->getL10N($c->get('AppName')); + return $this->getServer()->getL10N($c->get('appName')); }); // Log wrappers $this->registerService(LoggerInterface::class, function (ContainerInterface $c) { + /* Cannot be an alias because it uses LoggerInterface so it would infinite loop */ return new ScopedPsrLogger( $c->get(PsrLoggerAdapter::class), - $c->get('AppName') + $c->get('appName') ); }); $this->registerService(IServerContainer::class, function () { return $this->getServer(); }); - $this->registerAlias('ServerContainer', IServerContainer::class); + /** @deprecated 32.0.0 */ + $this->registerDeprecatedAlias('ServerContainer', IServerContainer::class); - $this->registerService(\OCP\WorkflowEngine\IManager::class, function (ContainerInterface $c) { - return $c->get(Manager::class); - }); + $this->registerAlias(\OCP\WorkflowEngine\IManager::class, Manager::class); - $this->registerService(ContainerInterface::class, function (ContainerInterface $c) { - return $c; - }); - $this->registerAlias(IAppContainer::class, ContainerInterface::class); + $this->registerService(ContainerInterface::class, fn (ContainerInterface $c) => $c); + $this->registerDeprecatedAlias(IAppContainer::class, ContainerInterface::class); // commonly used attributes - $this->registerService('userId', function (ContainerInterface $c) { - return $c->get(IUserSession::class)->getSession()->get('user_id'); + $this->registerService('userId', function (ContainerInterface $c): ?string { + return $c->get(ISession::class)->get('user_id'); }); - $this->registerService('webRoot', function (ContainerInterface $c) { + $this->registerService('webRoot', function (ContainerInterface $c): string { return $c->get(IServerContainer::class)->getWebRoot(); }); - $this->registerService('OC_Defaults', function (ContainerInterface $c) { + $this->registerService('OC_Defaults', function (ContainerInterface $c): object { return $c->get(IServerContainer::class)->get('ThemingDefaults'); }); - $this->registerService('Protocol', function (ContainerInterface $c) { - /** @var \OC\Server $server */ - $server = $c->get(IServerContainer::class); - $protocol = $server->getRequest()->getHttpProtocol(); + /** @deprecated 32.0.0 */ + $this->registerDeprecatedAlias('Protocol', Http::class); + $this->registerService(Http::class, function (ContainerInterface $c) { + $protocol = $c->get(IRequest::class)->getHttpProtocol(); return new Http($_SERVER, $protocol); }); - $this->registerService('Dispatcher', function (ContainerInterface $c) { + /** @deprecated 32.0.0 */ + $this->registerDeprecatedAlias('Dispatcher', Dispatcher::class); + $this->registerService(Dispatcher::class, function (ContainerInterface $c) { return new Dispatcher( $c->get('Protocol'), $c->get(MiddlewareDispatcher::class), @@ -180,42 +174,19 @@ class DIContainer extends SimpleContainer implements IAppContainer { /** * Middleware */ - $this->registerAlias('MiddlewareDispatcher', MiddlewareDispatcher::class); + /** @deprecated 32.0.0 */ + $this->registerDeprecatedAlias('MiddlewareDispatcher', MiddlewareDispatcher::class); $this->registerService(MiddlewareDispatcher::class, function (ContainerInterface $c) { $server = $this->getServer(); $dispatcher = new MiddlewareDispatcher(); - $dispatcher->registerMiddleware( - $c->get(OC\AppFramework\Middleware\CompressionMiddleware::class) - ); - - $dispatcher->registerMiddleware($c->get(OC\AppFramework\Middleware\NotModifiedMiddleware::class)); - - $dispatcher->registerMiddleware( - $c->get(OC\AppFramework\Middleware\Security\ReloadExecutionMiddleware::class) - ); - - $dispatcher->registerMiddleware( - new OC\AppFramework\Middleware\Security\SameSiteCookieMiddleware( - $c->get(IRequest::class), - $c->get(IControllerMethodReflector::class) - ) - ); - $dispatcher->registerMiddleware( - new CORSMiddleware( - $c->get(IRequest::class), - $c->get(IControllerMethodReflector::class), - $c->get(IUserSession::class), - $c->get(IThrottler::class), - $c->get(LoggerInterface::class) - ) - ); - $dispatcher->registerMiddleware( - new OCSMiddleware( - $c->get(IRequest::class) - ) - ); + $dispatcher->registerMiddleware($c->get(CompressionMiddleware::class)); + $dispatcher->registerMiddleware($c->get(NotModifiedMiddleware::class)); + $dispatcher->registerMiddleware($c->get(ReloadExecutionMiddleware::class)); + $dispatcher->registerMiddleware($c->get(SameSiteCookieMiddleware::class)); + $dispatcher->registerMiddleware($c->get(CORSMiddleware::class)); + $dispatcher->registerMiddleware($c->get(OCSMiddleware::class)); $dispatcher->registerMiddleware($c->get(FlowV2EphemeralSessionsMiddleware::class)); @@ -224,75 +195,31 @@ class DIContainer extends SimpleContainer implements IAppContainer { $c->get(IControllerMethodReflector::class), $c->get(INavigationManager::class), $c->get(IURLGenerator::class), - $server->get(LoggerInterface::class), - $c->get('AppName'), + $c->get(LoggerInterface::class), + $c->get('appName'), $server->getUserSession()->isLoggedIn(), $c->get(IGroupManager::class), $c->get(ISubAdmin::class), $server->getAppManager(), $server->getL10N('lib'), $c->get(AuthorizedGroupMapper::class), - $server->get(IUserSession::class), + $c->get(IUserSession::class), $c->get(IRemoteAddress::class), ); $dispatcher->registerMiddleware($securityMiddleware); - $dispatcher->registerMiddleware( - new OC\AppFramework\Middleware\Security\CSPMiddleware( - $server->query(OC\Security\CSP\ContentSecurityPolicyManager::class), - $server->query(OC\Security\CSP\ContentSecurityPolicyNonceManager::class), - ) - ); - $dispatcher->registerMiddleware( - $server->query(OC\AppFramework\Middleware\Security\FeaturePolicyMiddleware::class) - ); - $dispatcher->registerMiddleware( - new OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware( - $c->get(IControllerMethodReflector::class), - $c->get(ISession::class), - $c->get(IUserSession::class), - $c->get(ITimeFactory::class), - $c->get(\OC\Authentication\Token\IProvider::class), - $c->get(LoggerInterface::class), - $c->get(IRequest::class), - $c->get(UserManager::class), - ) - ); - $dispatcher->registerMiddleware( - new TwoFactorMiddleware( - $c->get(OC\Authentication\TwoFactorAuth\Manager::class), - $c->get(IUserSession::class), - $c->get(ISession::class), - $c->get(IURLGenerator::class), - $c->get(IControllerMethodReflector::class), - $c->get(IRequest::class) - ) - ); - $dispatcher->registerMiddleware( - new OC\AppFramework\Middleware\Security\BruteForceMiddleware( - $c->get(IControllerMethodReflector::class), - $c->get(IThrottler::class), - $c->get(IRequest::class), - $c->get(LoggerInterface::class) - ) - ); + $dispatcher->registerMiddleware($c->get(CSPMiddleware::class)); + $dispatcher->registerMiddleware($c->get(FeaturePolicyMiddleware::class)); + $dispatcher->registerMiddleware($c->get(PasswordConfirmationMiddleware::class)); + $dispatcher->registerMiddleware($c->get(TwoFactorMiddleware::class)); + $dispatcher->registerMiddleware($c->get(BruteForceMiddleware::class)); $dispatcher->registerMiddleware($c->get(RateLimitingMiddleware::class)); - $dispatcher->registerMiddleware( - new OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware( - $c->get(IRequest::class), - $c->get(ISession::class), - $c->get(IConfig::class), - $c->get(IThrottler::class) - ) - ); - $dispatcher->registerMiddleware( - $c->get(\OC\AppFramework\Middleware\AdditionalScriptsMiddleware::class) - ); + $dispatcher->registerMiddleware($c->get(PublicShareMiddleware::class)); + $dispatcher->registerMiddleware($c->get(AdditionalScriptsMiddleware::class)); - /** @var \OC\AppFramework\Bootstrap\Coordinator $coordinator */ $coordinator = $c->get(\OC\AppFramework\Bootstrap\Coordinator::class); $registrationContext = $coordinator->getRegistrationContext(); if ($registrationContext !== null) { - $appId = $this->getAppName(); + $appId = $this->get('appName'); foreach ($registrationContext->getMiddlewareRegistrations() as $middlewareRegistration) { if ($middlewareRegistration->getAppId() === $appId || $middlewareRegistration->isGlobal()) { @@ -304,28 +231,12 @@ class DIContainer extends SimpleContainer implements IAppContainer { $dispatcher->registerMiddleware($c->get($middleWare)); } - $dispatcher->registerMiddleware( - new SessionMiddleware( - $c->get(IControllerMethodReflector::class), - $c->get(ISession::class) - ) - ); + $dispatcher->registerMiddleware($c->get(SessionMiddleware::class)); return $dispatcher; }); - $this->registerService(IAppConfig::class, function (ContainerInterface $c) { - return new OC\AppFramework\Services\AppConfig( - $c->get(IConfig::class), - $c->get(\OCP\IAppConfig::class), - $c->get('AppName') - ); - }); - $this->registerService(IInitialState::class, function (ContainerInterface $c) { - return new OC\AppFramework\Services\InitialState( - $c->get(IInitialStateService::class), - $c->get('AppName') - ); - }); + $this->registerAlias(IAppConfig::class, \OC\AppFramework\Services\AppConfig::class); + $this->registerAlias(IInitialState::class, \OC\AppFramework\Services\InitialState::class); } /** @@ -337,13 +248,13 @@ class DIContainer extends SimpleContainer implements IAppContainer { /** * @param string $middleWare - * @return boolean|null */ - public function registerMiddleWare($middleWare) { + public function registerMiddleWare($middleWare): bool { if (in_array($middleWare, $this->middleWares, true) !== false) { return false; } $this->middleWares[] = $middleWare; + return true; } /** @@ -351,7 +262,7 @@ class DIContainer extends SimpleContainer implements IAppContainer { * @return string the name of your application */ public function getAppName() { - return $this->query('AppName'); + return $this->query('appName'); } /** @@ -371,7 +282,7 @@ class DIContainer extends SimpleContainer implements IAppContainer { return \OC_User::isAdminUser($uid); } - private function getUserId() { + private function getUserId(): string { return $this->getServer()->getSession()->get('user_id'); } @@ -438,9 +349,15 @@ class DIContainer extends SimpleContainer implements IAppContainer { return parent::query($name); } elseif (str_starts_with($name, \OC\AppFramework\App::buildAppNamespace($this->appName) . '\\')) { return parent::query($name); + } elseif ( + str_starts_with($name, 'OC\\AppFramework\\Services\\') + || str_starts_with($name, 'OC\\AppFramework\\Middleware\\') + ) { + /* AppFramework services are scoped to the application */ + return parent::query($name); } - throw new QueryException('Could not resolve ' . $name . '!' . - ' Class can not be instantiated', 1); + throw new QueryException('Could not resolve ' . $name . '!' + . ' Class can not be instantiated', 1); } } diff --git a/lib/private/AppFramework/Http.php b/lib/private/AppFramework/Http.php index 3c3e19692bf..08d6259c2a2 100644 --- a/lib/private/AppFramework/Http.php +++ b/lib/private/AppFramework/Http.php @@ -102,7 +102,7 @@ class Http extends BaseHttp { $status = self::STATUS_FOUND; } - return $this->protocolVersion . ' ' . $status . ' ' . - $this->headers[$status]; + return $this->protocolVersion . ' ' . $status . ' ' + . $this->headers[$status]; } } diff --git a/lib/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php index d129a7d770b..8d91ddf7502 100644 --- a/lib/private/AppFramework/Http/Dispatcher.php +++ b/lib/private/AppFramework/Http/Dispatcher.php @@ -64,7 +64,8 @@ class Dispatcher { * @param LoggerInterface $logger * @param IEventLogger $eventLogger */ - public function __construct(Http $protocol, + public function __construct( + Http $protocol, MiddlewareDispatcher $middlewareDispatcher, ControllerMethodReflector $reflector, IRequest $request, @@ -72,7 +73,8 @@ class Dispatcher { ConnectionAdapter $connection, LoggerInterface $logger, IEventLogger $eventLogger, - ContainerInterface $appContainer) { + ContainerInterface $appContainer, + ) { $this->protocol = $protocol; $this->middlewareDispatcher = $middlewareDispatcher; $this->reflector = $reflector; diff --git a/lib/private/AppFramework/Http/Output.php b/lib/private/AppFramework/Http/Output.php index ff0ef1c15ca..b4a8672fdc7 100644 --- a/lib/private/AppFramework/Http/Output.php +++ b/lib/private/AppFramework/Http/Output.php @@ -13,14 +13,9 @@ use OCP\AppFramework\Http\IOutput; * Very thin wrapper class to make output testable */ class Output implements IOutput { - /** @var string */ - private $webRoot; - - /** - * @param $webRoot - */ - public function __construct($webRoot) { - $this->webRoot = $webRoot; + public function __construct( + private string $webRoot, + ) { } /** diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index d177221556c..e662cb8679a 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -45,7 +45,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/'; protected string $inputStream; - protected $content; + private bool $isPutStreamContentAlreadySent = false; protected array $items = []; protected array $allowedKeys = [ 'get', @@ -64,6 +64,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { protected ?CsrfTokenManager $csrfTokenManager; protected bool $contentDecoded = false; + private ?\JsonException $decodingException = null; /** * @param array $vars An associative array with the following optional values: @@ -356,13 +357,13 @@ class Request implements \ArrayAccess, \Countable, IRequest { protected function getContent() { // If the content can't be parsed into an array then return a stream resource. if ($this->isPutStreamContent()) { - if ($this->content === false) { + if ($this->isPutStreamContentAlreadySent) { throw new \LogicException( '"put" can only be accessed once if not ' . 'application/x-www-form-urlencoded or application/json.' ); } - $this->content = false; + $this->isPutStreamContentAlreadySent = true; return fopen($this->inputStream, 'rb'); } else { $this->decodeContent(); @@ -389,7 +390,14 @@ class Request implements \ArrayAccess, \Countable, IRequest { // 'application/json' and other JSON-related content types must be decoded manually. if (preg_match(self::JSON_CONTENT_TYPE_REGEX, $this->getHeader('Content-Type')) === 1) { - $params = json_decode(file_get_contents($this->inputStream), true); + $content = file_get_contents($this->inputStream); + if ($content !== '') { + try { + $params = json_decode($content, true, flags:JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->decodingException = $e; + } + } if (\is_array($params) && \count($params) > 0) { $this->items['params'] = $params; if ($this->method === 'POST') { @@ -413,6 +421,12 @@ class Request implements \ArrayAccess, \Countable, IRequest { $this->contentDecoded = true; } + public function throwDecodingExceptionIfAny(): void { + if ($this->decodingException !== null) { + throw $this->decodingException; + } + } + /** * Checks if the CSRF check was correct diff --git a/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php b/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php index c30855a0e98..e4571dfc50e 100644 --- a/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php +++ b/lib/private/AppFramework/Middleware/FlowV2EphemeralSessionsMiddleware.php @@ -33,8 +33,8 @@ class FlowV2EphemeralSessionsMiddleware extends Middleware { } if ( - $controller instanceof ClientFlowLoginV2Controller && - ($methodName === 'grantPage' || $methodName === 'generateAppPassword') + $controller instanceof ClientFlowLoginV2Controller + && ($methodName === 'grantPage' || $methodName === 'generateAppPassword') ) { return; } diff --git a/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php b/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php index 17b423164f6..08b30092155 100644 --- a/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php +++ b/lib/private/AppFramework/Middleware/NotModifiedMiddleware.php @@ -29,7 +29,7 @@ class NotModifiedMiddleware extends Middleware { } $modifiedSinceHeader = $this->request->getHeader('IF_MODIFIED_SINCE'); - if ($modifiedSinceHeader !== '' && $response->getLastModified() !== null && trim($modifiedSinceHeader) === $response->getLastModified()->format(\DateTimeInterface::RFC2822)) { + if ($modifiedSinceHeader !== '' && $response->getLastModified() !== null && trim($modifiedSinceHeader) === $response->getLastModified()->format(\DateTimeInterface::RFC7231)) { $response->setStatus(Http::STATUS_NOT_MODIFIED); return $response; } diff --git a/lib/private/AppFramework/Middleware/OCSMiddleware.php b/lib/private/AppFramework/Middleware/OCSMiddleware.php index 46612bf0d29..64f4b0054de 100644 --- a/lib/private/AppFramework/Middleware/OCSMiddleware.php +++ b/lib/private/AppFramework/Middleware/OCSMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php index c80d06c90ae..5df4009b094 100644 --- a/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php +++ b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php index b3040673d0f..83e799e3d3b 100644 --- a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php +++ b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php index 40af67739d6..4453f5a7d4b 100644 --- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php @@ -68,8 +68,8 @@ class CORSMiddleware extends Middleware { // ensure that @CORS annotated API routes are not used in conjunction // with session authentication since this enables CSRF attack vectors - if ($this->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class) && - (!$this->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class) || $this->session->isLoggedIn())) { + if ($this->hasAnnotationOrAttribute($reflectionMethod, 'CORS', CORS::class) + && (!$this->hasAnnotationOrAttribute($reflectionMethod, 'PublicPage', PublicPage::class) || $this->session->isLoggedIn())) { $user = array_key_exists('PHP_AUTH_USER', $this->request->server) ? $this->request->server['PHP_AUTH_USER'] : null; $pass = array_key_exists('PHP_AUTH_PW', $this->request->server) ? $this->request->server['PHP_AUTH_PW'] : null; @@ -134,10 +134,10 @@ class CORSMiddleware extends Middleware { // allow credentials headers must not be true or CSRF is possible // otherwise foreach ($response->getHeaders() as $header => $value) { - if (strtolower($header) === 'access-control-allow-credentials' && - strtolower(trim($value)) === 'true') { - $msg = 'Access-Control-Allow-Credentials must not be ' . - 'set to true in order to prevent CSRF'; + if (strtolower($header) === 'access-control-allow-credentials' + && strtolower(trim($value)) === 'true') { + $msg = 'Access-Control-Allow-Credentials must not be ' + . 'set to true in order to prevent CSRF'; throw new SecurityException($msg); } } diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php index 646a5240bfc..53fbaaf5ed2 100644 --- a/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php +++ b/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php index 91f1dba874d..0380c6781aa 100644 --- a/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php +++ b/lib/private/AppFramework/Middleware/Security/Exceptions/LaxSameSiteCookieFailedException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php b/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php index 7e950f2c976..ca30f736fbc 100644 --- a/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php +++ b/lib/private/AppFramework/Middleware/Security/Exceptions/NotConfirmedException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -14,7 +15,7 @@ use OCP\AppFramework\Http; * @package OC\AppFramework\Middleware\Security\Exceptions */ class NotConfirmedException extends SecurityException { - public function __construct() { - parent::__construct('Password confirmation is required', Http::STATUS_FORBIDDEN); + public function __construct(string $message = 'Password confirmation is required') { + parent::__construct($message, Http::STATUS_FORBIDDEN); } } diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php index d00840084a3..0facbffe504 100644 --- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -79,6 +80,9 @@ class PasswordConfirmationMiddleware extends Middleware { if ($this->isPasswordConfirmationStrict($reflectionMethod)) { $authHeader = $this->request->getHeader('Authorization'); + if (!str_starts_with(strtolower($authHeader), 'basic ')) { + throw new NotConfirmedException('Required authorization header missing'); + } [, $password] = explode(':', base64_decode(substr($authHeader, 6)), 2); $loginName = $this->session->get('loginname'); $loginResult = $this->userManager->checkPassword($loginName, $password); diff --git a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php index efe56e0b124..097ed1b2b8f 100644 --- a/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -13,16 +14,10 @@ use OCP\AppFramework\Http\Response; use OCP\AppFramework\Middleware; class SameSiteCookieMiddleware extends Middleware { - /** @var Request */ - private $request; - - /** @var ControllerMethodReflector */ - private $reflector; - - public function __construct(Request $request, - ControllerMethodReflector $reflector) { - $this->request = $request; - $this->reflector = $reflector; + public function __construct( + private Request $request, + private ControllerMethodReflector $reflector, + ) { } public function beforeController($controller, $methodName) { @@ -58,7 +53,7 @@ class SameSiteCookieMiddleware extends Middleware { throw $exception; } - protected function setSameSiteCookie() { + protected function setSameSiteCookie(): void { $cookieParams = $this->request->getCookieParams(); $secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : ''; $policies = [ diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 6b054b03f44..e3a293e0fd9 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -184,8 +184,8 @@ class SecurityMiddleware extends Middleware { } // Check for strict cookie requirement - if ($this->hasAnnotationOrAttribute($reflectionMethod, 'StrictCookieRequired', StrictCookiesRequired::class) || - !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) { + if ($this->hasAnnotationOrAttribute($reflectionMethod, 'StrictCookieRequired', StrictCookiesRequired::class) + || !$this->hasAnnotationOrAttribute($reflectionMethod, 'NoCSRFRequired', NoCSRFRequired::class)) { if (!$this->request->passesStrictCookieCheck()) { throw new StrictCookieMissingException(); } diff --git a/lib/private/AppFramework/OCS/BaseResponse.php b/lib/private/AppFramework/OCS/BaseResponse.php index 5929a3993ec..05ce133db24 100644 --- a/lib/private/AppFramework/OCS/BaseResponse.php +++ b/lib/private/AppFramework/OCS/BaseResponse.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -83,9 +84,9 @@ abstract class BaseResponse extends Response { */ protected function renderResult(array $meta): string { $status = $this->getStatus(); - if ($status === Http::STATUS_NO_CONTENT || - $status === Http::STATUS_NOT_MODIFIED || - ($status >= 100 && $status <= 199)) { + if ($status === Http::STATUS_NO_CONTENT + || $status === Http::STATUS_NOT_MODIFIED + || ($status >= 100 && $status <= 199)) { // Those status codes are not supposed to have a body: // https://stackoverflow.com/q/8628725 return ''; diff --git a/lib/private/AppFramework/OCS/V1Response.php b/lib/private/AppFramework/OCS/V1Response.php index 131ca22ff24..1c2c25f5cb0 100644 --- a/lib/private/AppFramework/OCS/V1Response.php +++ b/lib/private/AppFramework/OCS/V1Response.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/OCS/V2Response.php b/lib/private/AppFramework/OCS/V2Response.php index 47cf0f60200..efc9348eb37 100644 --- a/lib/private/AppFramework/OCS/V2Response.php +++ b/lib/private/AppFramework/OCS/V2Response.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/private/AppFramework/Routing/RouteConfig.php b/lib/private/AppFramework/Routing/RouteConfig.php deleted file mode 100644 index 2b7f21a8ba5..00000000000 --- a/lib/private/AppFramework/Routing/RouteConfig.php +++ /dev/null @@ -1,279 +0,0 @@ -<?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 OC\AppFramework\Routing; - -use OC\AppFramework\DependencyInjection\DIContainer; -use OC\Route\Router; - -/** - * Class RouteConfig - * @package OC\AppFramework\routing - */ -class RouteConfig { - /** @var DIContainer */ - private $container; - - /** @var Router */ - private $router; - - /** @var array */ - private $routes; - - /** @var string */ - private $appName; - - /** @var string[] */ - private $controllerNameCache = []; - - protected $rootUrlApps = [ - 'cloud_federation_api', - 'core', - 'files_sharing', - 'files', - 'profile', - 'settings', - 'spreed', - ]; - - /** - * @param \OC\AppFramework\DependencyInjection\DIContainer $container - * @param \OC\Route\Router $router - * @param array $routes - * @internal param $appName - */ - public function __construct(DIContainer $container, Router $router, $routes) { - $this->routes = $routes; - $this->container = $container; - $this->router = $router; - $this->appName = $container['AppName']; - } - - /** - * The routes and resource will be registered to the \OCP\Route\IRouter - */ - public function register() { - // parse simple - $this->processIndexRoutes($this->routes); - - // parse resources - $this->processIndexResources($this->routes); - - /* - * OCS routes go into a different collection - */ - $oldCollection = $this->router->getCurrentCollection(); - $this->router->useCollection($oldCollection . '.ocs'); - - // parse ocs simple routes - $this->processOCS($this->routes); - - // parse ocs simple routes - $this->processOCSResources($this->routes); - - $this->router->useCollection($oldCollection); - } - - private function processOCS(array $routes): void { - $ocsRoutes = $routes['ocs'] ?? []; - foreach ($ocsRoutes as $ocsRoute) { - $this->processRoute($ocsRoute, 'ocs.'); - } - } - - /** - * Creates one route base on the give configuration - * @param array $routes - * @throws \UnexpectedValueException - */ - private function processIndexRoutes(array $routes): void { - $simpleRoutes = $routes['routes'] ?? []; - foreach ($simpleRoutes as $simpleRoute) { - $this->processRoute($simpleRoute); - } - } - - protected function processRoute(array $route, string $routeNamePrefix = ''): void { - $name = $route['name']; - $postfix = $route['postfix'] ?? ''; - $root = $this->buildRootPrefix($route, $routeNamePrefix); - - $url = $root . '/' . ltrim($route['url'], '/'); - $verb = strtoupper($route['verb'] ?? 'GET'); - - $split = explode('#', $name, 2); - if (count($split) !== 2) { - throw new \UnexpectedValueException('Invalid route name: use the format foo#bar to reference FooController::bar'); - } - [$controller, $action] = $split; - - $controllerName = $this->buildControllerName($controller); - $actionName = $this->buildActionName($action); - - /* - * The route name has to be lowercase, for symfony to match it correctly. - * This is required because smyfony allows mixed casing for controller names in the routes. - * To avoid breaking all the existing route names, registering and matching will only use the lowercase names. - * This is also safe on the PHP side because class and method names collide regardless of the casing. - */ - $routeName = strtolower($routeNamePrefix . $this->appName . '.' . $controller . '.' . $action . $postfix); - - $router = $this->router->create($routeName, $url) - ->method($verb); - - // optionally register requirements for route. This is used to - // tell the route parser how url parameters should be matched - if (array_key_exists('requirements', $route)) { - $router->requirements($route['requirements']); - } - - // optionally register defaults for route. This is used to - // tell the route parser how url parameters should be default valued - $defaults = []; - if (array_key_exists('defaults', $route)) { - $defaults = $route['defaults']; - } - - $defaults['caller'] = [$this->appName, $controllerName, $actionName]; - $router->defaults($defaults); - } - - /** - * For a given name and url restful OCS routes are created: - * - index - * - show - * - create - * - update - * - destroy - * - * @param array $routes - */ - private function processOCSResources(array $routes): void { - $this->processResources($routes['ocs-resources'] ?? [], 'ocs.'); - } - - /** - * For a given name and url restful routes are created: - * - index - * - show - * - create - * - update - * - destroy - * - * @param array $routes - */ - private function processIndexResources(array $routes): void { - $this->processResources($routes['resources'] ?? []); - } - - /** - * For a given name and url restful routes are created: - * - index - * - show - * - create - * - update - * - destroy - * - * @param array $resources - * @param string $routeNamePrefix - */ - protected function processResources(array $resources, string $routeNamePrefix = ''): void { - // declaration of all restful actions - $actions = [ - ['name' => 'index', 'verb' => 'GET', 'on-collection' => true], - ['name' => 'show', 'verb' => 'GET'], - ['name' => 'create', 'verb' => 'POST', 'on-collection' => true], - ['name' => 'update', 'verb' => 'PUT'], - ['name' => 'destroy', 'verb' => 'DELETE'], - ]; - - foreach ($resources as $resource => $config) { - $root = $this->buildRootPrefix($config, $routeNamePrefix); - - // the url parameter used as id to the resource - foreach ($actions as $action) { - $url = $root . '/' . ltrim($config['url'], '/'); - $method = $action['name']; - - $verb = strtoupper($action['verb'] ?? 'GET'); - $collectionAction = $action['on-collection'] ?? false; - if (!$collectionAction) { - $url .= '/{id}'; - } - if (isset($action['url-postfix'])) { - $url .= '/' . $action['url-postfix']; - } - - $controller = $resource; - - $controllerName = $this->buildControllerName($controller); - $actionName = $this->buildActionName($method); - - $routeName = $routeNamePrefix . $this->appName . '.' . strtolower($resource) . '.' . $method; - - $route = $this->router->create($routeName, $url) - ->method($verb); - - $route->defaults(['caller' => [$this->appName, $controllerName, $actionName]]); - } - } - } - - private function buildRootPrefix(array $route, string $routeNamePrefix): string { - $defaultRoot = $this->appName === 'core' ? '' : '/apps/' . $this->appName; - $root = $route['root'] ?? $defaultRoot; - - if ($routeNamePrefix !== '') { - // In OCS all apps are whitelisted - return $root; - } - - if (!\in_array($this->appName, $this->rootUrlApps, true)) { - // Only allow root URLS for some apps - return $defaultRoot; - } - - return $root; - } - - /** - * Based on a given route name the controller name is generated - * @param string $controller - * @return string - */ - private function buildControllerName(string $controller): string { - if (!isset($this->controllerNameCache[$controller])) { - $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; - } - return $this->controllerNameCache[$controller]; - } - - /** - * Based on the action part of the route name the controller method name is generated - * @param string $action - * @return string - */ - private function buildActionName(string $action): string { - return $this->underScoreToCamelCase($action); - } - - /** - * Underscored strings are converted to camel case strings - * @param string $str - * @return string - */ - private function underScoreToCamelCase(string $str): string { - $pattern = '/_[a-z]?/'; - return preg_replace_callback( - $pattern, - function ($matches) { - return strtoupper(ltrim($matches[0], '_')); - }, - $str); - } -} diff --git a/lib/private/AppFramework/Routing/RouteParser.php b/lib/private/AppFramework/Routing/RouteParser.php index 894a74c727b..55e58234673 100644 --- a/lib/private/AppFramework/Routing/RouteParser.php +++ b/lib/private/AppFramework/Routing/RouteParser.php @@ -76,7 +76,7 @@ class RouteParser { $url = $root . '/' . ltrim($route['url'], '/'); $verb = strtoupper($route['verb'] ?? 'GET'); - $split = explode('#', $name, 2); + $split = explode('#', $name, 3); if (count($split) !== 2) { throw new \UnexpectedValueException('Invalid route name: use the format foo#bar to reference FooController::bar'); } @@ -87,7 +87,7 @@ class RouteParser { /* * The route name has to be lowercase, for symfony to match it correctly. - * This is required because smyfony allows mixed casing for controller names in the routes. + * This is required because symfony allows mixed casing for controller names in the routes. * To avoid breaking all the existing route names, registering and matching will only use the lowercase names. * This is also safe on the PHP side because class and method names collide regardless of the casing. */ diff --git a/lib/private/AppFramework/Services/AppConfig.php b/lib/private/AppFramework/Services/AppConfig.php index 77c5ea4de0c..04d97738483 100644 --- a/lib/private/AppFramework/Services/AppConfig.php +++ b/lib/private/AppFramework/Services/AppConfig.php @@ -343,7 +343,7 @@ class AppConfig implements IAppConfig { * * @return array<string, string> */ - public function getAppInstalledVersions(): array { - return $this->appConfig->getAppInstalledVersions(); + public function getAppInstalledVersions(bool $onlyEnabled = false): array { + return $this->appConfig->getAppInstalledVersions($onlyEnabled); } } diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 24918992ea3..ed26e75ec89 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -12,7 +12,9 @@ use Closure; use OCP\AppFramework\QueryException; use OCP\IContainer; use Pimple\Container; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; use ReflectionClass; use ReflectionException; use ReflectionNamedType; @@ -23,8 +25,9 @@ use function class_exists; * SimpleContainer is a simple implementation of a container on basis of Pimple */ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { - /** @var Container */ - private $container; + public static bool $useLazyObjects = false; + + private Container $container; public function __construct() { $this->container = new Container(); @@ -49,16 +52,29 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { /** * @param ReflectionClass $class the class to instantiate - * @return \stdClass the created class + * @return object the created class * @suppress PhanUndeclaredClassInstanceof */ - private function buildClass(ReflectionClass $class) { + private function buildClass(ReflectionClass $class): object { $constructor = $class->getConstructor(); if ($constructor === null) { + /* No constructor, return a instance directly */ return $class->newInstance(); } + if (PHP_VERSION_ID >= 80400 && self::$useLazyObjects) { + /* For PHP>=8.4, use a lazy ghost to delay constructor and dependency resolving */ + /** @psalm-suppress UndefinedMethod */ + return $class->newLazyGhost(function (object $object) use ($constructor): void { + /** @psalm-suppress DirectConstructorCall For lazy ghosts we have to call the constructor directly */ + $object->__construct(...$this->buildClassConstructorParameters($constructor)); + }); + } else { + return $class->newInstanceArgs($this->buildClassConstructorParameters($constructor)); + } + } - return $class->newInstanceArgs(array_map(function (ReflectionParameter $parameter) { + private function buildClassConstructorParameters(\ReflectionMethod $constructor): array { + return array_map(function (ReflectionParameter $parameter) { $parameterType = $parameter->getType(); $resolveName = $parameter->getName(); @@ -69,10 +85,10 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { } try { - $builtIn = $parameter->hasType() && ($parameter->getType() instanceof ReflectionNamedType) - && $parameter->getType()->isBuiltin(); + $builtIn = $parameterType !== null && ($parameterType instanceof ReflectionNamedType) + && $parameterType->isBuiltin(); return $this->query($resolveName, !$builtIn); - } catch (QueryException $e) { + } catch (ContainerExceptionInterface $e) { // Service not found, use the default value when available if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); @@ -82,7 +98,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { $resolveName = $parameter->getName(); try { return $this->query($resolveName); - } catch (QueryException $e2) { + } catch (ContainerExceptionInterface $e2) { // Pass null if typed and nullable if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) { return null; @@ -95,7 +111,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { throw $e; } - }, $constructor->getParameters())); + }, $constructor->getParameters()); } public function resolve($name) { @@ -105,8 +121,8 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { if ($class->isInstantiable()) { return $this->buildClass($class); } else { - throw new QueryException($baseMsg . - ' Class can not be instantiated'); + throw new QueryException($baseMsg + . ' Class can not be instantiated'); } } catch (ReflectionException $e) { // Class does not exist @@ -153,13 +169,13 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { return $closure($this); }; $name = $this->sanitizeName($name); - if (isset($this[$name])) { - unset($this[$name]); + if (isset($this->container[$name])) { + unset($this->container[$name]); } if ($shared) { - $this[$name] = $wrapped; + $this->container[$name] = $wrapped; } else { - $this[$name] = $this->container->factory($wrapped); + $this->container[$name] = $this->container->factory($wrapped); } } @@ -170,8 +186,21 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { * @param string $alias the alias that should be registered * @param string $target the target that should be resolved instead */ - public function registerAlias($alias, $target) { - $this->registerService($alias, function (ContainerInterface $container) use ($target) { + public function registerAlias($alias, $target): void { + $this->registerService($alias, function (ContainerInterface $container) use ($target): mixed { + return $container->get($target); + }, false); + } + + protected function registerDeprecatedAlias(string $alias, string $target): void { + $this->registerService($alias, function (ContainerInterface $container) use ($target, $alias): mixed { + try { + $logger = $container->get(LoggerInterface::class); + $logger->debug('The requested alias "' . $alias . '" is deprecated. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.', ['app' => 'serverDI']); + } catch (ContainerExceptionInterface $e) { + // Could not get logger. Continue + } + return $container->get($target); }, false); } |