diff options
Diffstat (limited to 'lib')
37 files changed, 427 insertions, 139 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 69d1f92c98c..62f66dca67b 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1392,6 +1392,7 @@ return array( 'OC\\Preview\\Generator' => $baseDir . '/lib/private/Preview/Generator.php', 'OC\\Preview\\GeneratorHelper' => $baseDir . '/lib/private/Preview/GeneratorHelper.php', 'OC\\Preview\\HEIC' => $baseDir . '/lib/private/Preview/HEIC.php', + 'OC\\Preview\\IMagickSupport' => $baseDir . '/lib/private/Preview/IMagickSupport.php', 'OC\\Preview\\Illustrator' => $baseDir . '/lib/private/Preview/Illustrator.php', 'OC\\Preview\\Image' => $baseDir . '/lib/private/Preview/Image.php', 'OC\\Preview\\Imaginary' => $baseDir . '/lib/private/Preview/Imaginary.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 1191a3bfa53..33d63a26e3e 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1425,6 +1425,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Preview\\Generator' => __DIR__ . '/../../..' . '/lib/private/Preview/Generator.php', 'OC\\Preview\\GeneratorHelper' => __DIR__ . '/../../..' . '/lib/private/Preview/GeneratorHelper.php', 'OC\\Preview\\HEIC' => __DIR__ . '/../../..' . '/lib/private/Preview/HEIC.php', + 'OC\\Preview\\IMagickSupport' => __DIR__ . '/../../..' . '/lib/private/Preview/IMagickSupport.php', 'OC\\Preview\\Illustrator' => __DIR__ . '/../../..' . '/lib/private/Preview/Illustrator.php', 'OC\\Preview\\Image' => __DIR__ . '/../../..' . '/lib/private/Preview/Image.php', 'OC\\Preview\\Imaginary' => __DIR__ . '/../../..' . '/lib/private/Preview/Imaginary.php', diff --git a/lib/l10n/fi.js b/lib/l10n/fi.js index f724b77763d..23134d43402 100644 --- a/lib/l10n/fi.js +++ b/lib/l10n/fi.js @@ -135,6 +135,7 @@ OC.L10N.register( "%1$s shared »%2$s« with you." : "%1$s jakoi kohteen »%2$s« kanssasi.", "Click the button below to open it." : "Napsauta alla olevaa painiketta avataksesi sen.", "The requested share does not exist anymore" : "Pyydettyä jakoa ei ole enää olemassa", + "The user was not created because the user limit has been reached. Check your notifications to learn more." : "Käyttäjää ei luotu, koska käyttäjäraja on tullut täyteen. Tarkista ilmoitukset saadaksesi lisätietoja.", "Could not find category \"%s\"" : "Luokkaa \"%s\" ei löytynyt", "Sunday" : "sunnuntai", "Monday" : "maanantai", @@ -184,6 +185,7 @@ OC.L10N.register( "A valid password must be provided" : "Anna kelvollinen salasana", "The username is already being used" : "Käyttäjätunnus on jo käytössä", "Could not create user" : "Ei voitu luoda käyttäjää", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "Vain seuraavat merkit ovat sallittuja käyttäjätunnuksessa: \"a-z\", \"A-Z\", \"0-9\", välilyönnit ja \"_.@-'\"", "A valid username must be provided" : "Anna kelvollinen käyttäjätunnus", "Username contains whitespace at the beginning or at the end" : "Käyttäjätunnus sisältää tyhjätilaa joko alussa tai lopussa", "Username must not consist of dots only" : "Käyttäjänimi ei voi koostua vain pisteistä", diff --git a/lib/l10n/fi.json b/lib/l10n/fi.json index ee534d2856b..1f028ceb86e 100644 --- a/lib/l10n/fi.json +++ b/lib/l10n/fi.json @@ -133,6 +133,7 @@ "%1$s shared »%2$s« with you." : "%1$s jakoi kohteen »%2$s« kanssasi.", "Click the button below to open it." : "Napsauta alla olevaa painiketta avataksesi sen.", "The requested share does not exist anymore" : "Pyydettyä jakoa ei ole enää olemassa", + "The user was not created because the user limit has been reached. Check your notifications to learn more." : "Käyttäjää ei luotu, koska käyttäjäraja on tullut täyteen. Tarkista ilmoitukset saadaksesi lisätietoja.", "Could not find category \"%s\"" : "Luokkaa \"%s\" ei löytynyt", "Sunday" : "sunnuntai", "Monday" : "maanantai", @@ -182,6 +183,7 @@ "A valid password must be provided" : "Anna kelvollinen salasana", "The username is already being used" : "Käyttäjätunnus on jo käytössä", "Could not create user" : "Ei voitu luoda käyttäjää", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "Vain seuraavat merkit ovat sallittuja käyttäjätunnuksessa: \"a-z\", \"A-Z\", \"0-9\", välilyönnit ja \"_.@-'\"", "A valid username must be provided" : "Anna kelvollinen käyttäjätunnus", "Username contains whitespace at the beginning or at the end" : "Käyttäjätunnus sisältää tyhjätilaa joko alussa tai lopussa", "Username must not consist of dots only" : "Käyttäjänimi ei voi koostua vain pisteistä", diff --git a/lib/l10n/hu.js b/lib/l10n/hu.js index 06770c1f1b0..9fb6d495087 100644 --- a/lib/l10n/hu.js +++ b/lib/l10n/hu.js @@ -114,7 +114,7 @@ OC.L10N.register( "Profile picture" : "Profilkép", "About" : "Névjegy", "Full name" : "Teljes név", - "Headline" : "Főcím", + "Headline" : "Címsor", "Organisation" : "Szervezet", "Role" : "Szerepkör", "Unknown user" : "Ismeretlen felhasználó", diff --git a/lib/l10n/hu.json b/lib/l10n/hu.json index 2b5dfb58ec6..a36249b02f6 100644 --- a/lib/l10n/hu.json +++ b/lib/l10n/hu.json @@ -112,7 +112,7 @@ "Profile picture" : "Profilkép", "About" : "Névjegy", "Full name" : "Teljes név", - "Headline" : "Főcím", + "Headline" : "Címsor", "Organisation" : "Szervezet", "Role" : "Szerepkör", "Unknown user" : "Ismeretlen felhasználó", diff --git a/lib/l10n/uk.js b/lib/l10n/uk.js index 6b290c350f5..5679eb51661 100644 --- a/lib/l10n/uk.js +++ b/lib/l10n/uk.js @@ -56,6 +56,7 @@ OC.L10N.register( "Invalid image" : "Недійсне зображення", "Avatar image is not square" : "Зображення аватара не квадратне", "View profile" : "Перегляд профілю", + "Local time: %s" : "Місцевий час: %s", "today" : "сьогодні", "tomorrow" : "завтра", "yesterday" : "вчора", @@ -100,6 +101,7 @@ OC.L10N.register( "Users" : "Користувачі", "Email" : "Електронна пошта", "Mail %s" : "Пошта %s", + "View %s on the fediverse" : "Переглянути %s у Fediverse", "Phone" : "Телефон", "Call %s" : "Телефонуйте %s", "Twitter" : "Twitter", @@ -150,7 +152,8 @@ OC.L10N.register( "%1$s shared »%2$s« with you" : "%1$s надано доступ до \"%2$s\"", "%1$s shared »%2$s« with you." : "%1$s надано доступ до \"%2$s\".", "Click the button below to open it." : "Щоб відкрити файл, натисніть кнопку нижче.", - "The requested share does not exist anymore" : "Запитувана частка більше не існує", + "The requested share does not exist anymore" : "Запитуваний спільний ресурс більше недоступний", + "The user was not created because the user limit has been reached. Check your notifications to learn more." : "Користувача не створено, оскільки досягнуто обмеження на кількість користувачів. Перевірте сповіщення для докладної інформації.", "Could not find category \"%s\"" : "Не вдалося знайти категорію \"%s\"", "Sunday" : "Неділя", "Monday" : "Понеділок", @@ -200,6 +203,7 @@ OC.L10N.register( "A valid password must be provided" : "Потрібно задати вірний пароль", "The username is already being used" : "Ім'я користувача вже використовується", "Could not create user" : "Не вдалося створити користувача", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "Лише такі символи дозволено у імені користувача: \"a-z\", \"A-Z\", \"0-9\", пробіл та \"_.@-'\"", "A valid username must be provided" : "Потрібно задати вірне ім'я користувача", "Username contains whitespace at the beginning or at the end" : "Ім'я користувача містить символ пробілу в початку або в кінці", "Username must not consist of dots only" : "Ім'я користувача не повинно складатися лише з крапок", @@ -262,6 +266,7 @@ OC.L10N.register( "%s enter the database username." : "%s введіть ім'я користувача бази даних.", "%s enter the database name." : "%s введіть назву бази даних.", "%s you may not use dots in the database name" : "%s не можна використовувати крапки в назві бази даних", + "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Досягнуто обмеження на кількість користувачів, користувача не було створено. Перевірте сповіщення для докладної інформації.", "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Тільки такі символи допускаються в імені користувача: \"a-z\", \"A-Z\", \"0-9\", і \"_.@-'\"" }, "nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);"); diff --git a/lib/l10n/uk.json b/lib/l10n/uk.json index 2bb32fd83e3..7a0854d23eb 100644 --- a/lib/l10n/uk.json +++ b/lib/l10n/uk.json @@ -54,6 +54,7 @@ "Invalid image" : "Недійсне зображення", "Avatar image is not square" : "Зображення аватара не квадратне", "View profile" : "Перегляд профілю", + "Local time: %s" : "Місцевий час: %s", "today" : "сьогодні", "tomorrow" : "завтра", "yesterday" : "вчора", @@ -98,6 +99,7 @@ "Users" : "Користувачі", "Email" : "Електронна пошта", "Mail %s" : "Пошта %s", + "View %s on the fediverse" : "Переглянути %s у Fediverse", "Phone" : "Телефон", "Call %s" : "Телефонуйте %s", "Twitter" : "Twitter", @@ -148,7 +150,8 @@ "%1$s shared »%2$s« with you" : "%1$s надано доступ до \"%2$s\"", "%1$s shared »%2$s« with you." : "%1$s надано доступ до \"%2$s\".", "Click the button below to open it." : "Щоб відкрити файл, натисніть кнопку нижче.", - "The requested share does not exist anymore" : "Запитувана частка більше не існує", + "The requested share does not exist anymore" : "Запитуваний спільний ресурс більше недоступний", + "The user was not created because the user limit has been reached. Check your notifications to learn more." : "Користувача не створено, оскільки досягнуто обмеження на кількість користувачів. Перевірте сповіщення для докладної інформації.", "Could not find category \"%s\"" : "Не вдалося знайти категорію \"%s\"", "Sunday" : "Неділя", "Monday" : "Понеділок", @@ -198,6 +201,7 @@ "A valid password must be provided" : "Потрібно задати вірний пароль", "The username is already being used" : "Ім'я користувача вже використовується", "Could not create user" : "Не вдалося створити користувача", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "Лише такі символи дозволено у імені користувача: \"a-z\", \"A-Z\", \"0-9\", пробіл та \"_.@-'\"", "A valid username must be provided" : "Потрібно задати вірне ім'я користувача", "Username contains whitespace at the beginning or at the end" : "Ім'я користувача містить символ пробілу в початку або в кінці", "Username must not consist of dots only" : "Ім'я користувача не повинно складатися лише з крапок", @@ -260,6 +264,7 @@ "%s enter the database username." : "%s введіть ім'я користувача бази даних.", "%s enter the database name." : "%s введіть назву бази даних.", "%s you may not use dots in the database name" : "%s не можна використовувати крапки в назві бази даних", + "The user limit has been reached and the user was not created. Check your notifications to learn more." : "Досягнуто обмеження на кількість користувачів, користувача не було створено. Перевірте сповіщення для докладної інформації.", "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", and \"_.@-'\"" : "Тільки такі символи допускаються в імені користувача: \"a-z\", \"A-Z\", \"0-9\", і \"_.@-'\"" },"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);" }
\ No newline at end of file diff --git a/lib/l10n/zh_TW.js b/lib/l10n/zh_TW.js index 6124af85631..7e3c03ad5a8 100644 --- a/lib/l10n/zh_TW.js +++ b/lib/l10n/zh_TW.js @@ -57,6 +57,7 @@ OC.L10N.register( "Invalid image" : "無效的圖片", "Avatar image is not square" : "頭像不是正方形", "View profile" : "檢視個人檔案", + "Local time: %s" : "本機時間:%s", "today" : "今天", "tomorrow" : "明天", "yesterday" : "昨天", @@ -204,6 +205,7 @@ OC.L10N.register( "A valid password must be provided" : "須提供有效的密碼", "The username is already being used" : "這個使用者名稱已經有人使用了", "Could not create user" : "無法建立使用者", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "使用者名稱僅允許以下字元:\"a-z\"、\"A-Z\"、\"0-9\"、空格與 \"_.@-'\"", "A valid username must be provided" : "必須提供一個有效的用戶名", "Username contains whitespace at the beginning or at the end" : "用戶名的開頭或結尾有空白", "Username must not consist of dots only" : "使用者名稱不能只包含小數點", diff --git a/lib/l10n/zh_TW.json b/lib/l10n/zh_TW.json index c9a8f17835d..743e5d90ffd 100644 --- a/lib/l10n/zh_TW.json +++ b/lib/l10n/zh_TW.json @@ -55,6 +55,7 @@ "Invalid image" : "無效的圖片", "Avatar image is not square" : "頭像不是正方形", "View profile" : "檢視個人檔案", + "Local time: %s" : "本機時間:%s", "today" : "今天", "tomorrow" : "明天", "yesterday" : "昨天", @@ -202,6 +203,7 @@ "A valid password must be provided" : "須提供有效的密碼", "The username is already being used" : "這個使用者名稱已經有人使用了", "Could not create user" : "無法建立使用者", + "Only the following characters are allowed in a username: \"a-z\", \"A-Z\", \"0-9\", spaces and \"_.@-'\"" : "使用者名稱僅允許以下字元:\"a-z\"、\"A-Z\"、\"0-9\"、空格與 \"_.@-'\"", "A valid username must be provided" : "必須提供一個有效的用戶名", "Username contains whitespace at the beginning or at the end" : "用戶名的開頭或結尾有空白", "Username must not consist of dots only" : "使用者名稱不能只包含小數點", diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php index d2ef7da9e46..abf8a08a44b 100644 --- a/lib/private/AppFramework/App.php +++ b/lib/private/AppFramework/App.php @@ -34,7 +34,6 @@ namespace OC\AppFramework; use OC\AppFramework\DependencyInjection\DIContainer; use OC\AppFramework\Http\Dispatcher; use OC\AppFramework\Http\Request; -use OC\Diagnostics\EventLogger; use OCP\Profiler\IProfiler; use OC\Profiler\RoutingDataCollector; use OCP\AppFramework\QueryException; @@ -43,7 +42,6 @@ use OCP\AppFramework\Http\ICallbackResponse; use OCP\AppFramework\Http\IOutput; use OCP\Diagnostics\IEventLogger; use OCP\HintException; -use OCP\IConfig; use OCP\IRequest; /** @@ -120,7 +118,7 @@ class App { public static function main(string $controllerName, string $methodName, DIContainer $container, array $urlParams = null) { /** @var IProfiler $profiler */ $profiler = $container->get(IProfiler::class); - $config = $container->get(IConfig::class); + $eventLogger = $container->get(IEventLogger::class); // Disable profiler on the profiler UI $profiler->setEnabled($profiler->isEnabled() && !is_null($urlParams) && isset($urlParams['_route']) && !str_starts_with($urlParams['_route'], 'profiler.')); if ($profiler->isEnabled()) { @@ -128,6 +126,8 @@ class App { $profiler->add(new RoutingDataCollector($container['AppName'], $controllerName, $methodName)); } + $eventLogger->start('app:controller:params', 'Gather controller parameters'); + if (!is_null($urlParams)) { /** @var Request $request */ $request = $container->get(IRequest::class); @@ -139,6 +139,10 @@ class App { } $appName = $container['AppName']; + $eventLogger->end('app:controller:params'); + + $eventLogger->start('app:controller:load', 'Load app controller'); + // first try $controllerName then go for \OCA\AppName\Controller\$controllerName try { $controller = $container->get($controllerName); @@ -158,10 +162,18 @@ class App { $controller = $container->query($controllerName); } + $eventLogger->end('app:controller:load'); + + $eventLogger->start('app:controller:dispatcher', 'Initialize dispatcher and pre-middleware'); + // initialize the dispatcher and run all the middleware before the controller /** @var Dispatcher $dispatcher */ $dispatcher = $container['Dispatcher']; + $eventLogger->end('app:controller:dispatcher'); + + $eventLogger->start('app:controller:run', 'Run app controller'); + [ $httpHeaders, $responseHeaders, @@ -170,11 +182,11 @@ class App { $response ] = $dispatcher->dispatch($controller, $methodName); + $eventLogger->end('app:controller:run'); + $io = $container[IOutput::class]; if ($profiler->isEnabled()) { - /** @var EventLogger $eventLogger */ - $eventLogger = $container->get(IEventLogger::class); $eventLogger->end('runtime'); $profile = $profiler->collect($container->get(IRequest::class), $response); $profiler->saveProfile($profile); diff --git a/lib/private/AppFramework/Bootstrap/Coordinator.php b/lib/private/AppFramework/Bootstrap/Coordinator.php index ff04196fef6..f41b734a25b 100644 --- a/lib/private/AppFramework/Bootstrap/Coordinator.php +++ b/lib/private/AppFramework/Bootstrap/Coordinator.php @@ -98,11 +98,14 @@ class Coordinator { * @param string[] $appIds */ private function registerApps(array $appIds): void { + $this->eventLogger->start('bootstrap:register_apps', ''); if ($this->registrationContext === null) { $this->registrationContext = new RegistrationContext($this->logger); } $apps = []; foreach ($appIds as $appId) { + $this->eventLogger->start("bootstrap:register_app:$appId", "Register $appId"); + $this->eventLogger->start("bootstrap:register_app:$appId:autoloader", "Setup autoloader for $appId"); /* * First, we have to enable the app's autoloader * @@ -114,36 +117,43 @@ class Coordinator { 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 + * Next we check if there is an application class, and it implements * the \OCP\AppFramework\Bootstrap\IBootstrap interface */ $appNameSpace = App::buildAppNamespace($appId); $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; try { if (class_exists($applicationClassName) && in_array(IBootstrap::class, class_implements($applicationClassName), 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) { // Weird, but ok + $this->eventLogger->end("bootstrap:register_app:$appId"); continue; } + $this->eventLogger->end("bootstrap:register_app:$appId:application"); - $this->eventLogger->start('bootstrap:register_app_' . $appId, ''); + $this->eventLogger->start("bootstrap:register_app:$appId:register", "`Application::register` for $appId"); $application->register($this->registrationContext->for($appId)); - $this->eventLogger->end('bootstrap:register_app_' . $appId); + $this->eventLogger->end("bootstrap:register_app:$appId:register"); } } catch (Throwable $e) { $this->logger->emergency('Error during app service registration: ' . $e->getMessage(), [ 'exception' => $e, 'app' => $appId, ]); + $this->eventLogger->end("bootstrap:register_app:$appId"); continue; } + $this->eventLogger->end("bootstrap:register_app:$appId"); } + $this->eventLogger->start('bootstrap:register_apps:apply', 'Apply all the registered service by apps'); /** * Now that all register methods have been called, we can delegate the registrations * to the actual services @@ -153,6 +163,8 @@ class Coordinator { $this->registrationContext->delegateDashboardPanelRegistrations($this->dashboardManager); $this->registrationContext->delegateEventListenerRegistrations($this->eventDispatcher); $this->registrationContext->delegateContainerRegistrations($apps); + $this->eventLogger->end('bootstrap:register_apps:apply'); + $this->eventLogger->end('bootstrap:register_apps'); } public function getRegistrationContext(): ?RegistrationContext { @@ -178,7 +190,7 @@ class Coordinator { * the instance was already created for register, but any other * (legacy) code will now do their magic via the constructor. */ - $this->eventLogger->start('bootstrap:boot_app_' . $appId, ''); + $this->eventLogger->start('bootstrap:boot_app:' . $appId, "Call `Application::boot` for $appId"); try { /** @var App $application */ $application = $this->serverContainer->query($applicationClassName); @@ -196,7 +208,7 @@ class Coordinator { 'exception' => $e, ]); } - $this->eventLogger->end('bootstrap:boot_app_' . $appId); + $this->eventLogger->end('bootstrap:boot_app:' . $appId); } public function isBootable(string $appId) { diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index ac162f6565e..1f32d6c5461 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -260,6 +260,9 @@ class Request implements \ArrayAccess, \Countable, IRequest { : null; case 'parameters': case 'params': + if ($this->isPutStreamContent()) { + return $this->items['parameters']; + } return $this->getContent(); default: return isset($this[$name]) @@ -391,12 +394,7 @@ 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->method === 'PUT' - && $this->getHeader('Content-Length') !== '0' - && $this->getHeader('Content-Length') !== '' - && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false - && strpos($this->getHeader('Content-Type'), 'application/json') === false - ) { + if ($this->isPutStreamContent()) { if ($this->content === false) { throw new \LogicException( '"put" can only be accessed once if not ' @@ -411,6 +409,14 @@ class Request implements \ArrayAccess, \Countable, IRequest { } } + private function isPutStreamContent(): bool { + return $this->method === 'PUT' + && $this->getHeader('Content-Length') !== '0' + && $this->getHeader('Content-Length') !== '' + && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false + && strpos($this->getHeader('Content-Type'), 'application/json') === false; + } + /** * Attempt to decode the content and populate parameters */ diff --git a/lib/private/Authentication/Token/PublicKeyTokenMapper.php b/lib/private/Authentication/Token/PublicKeyTokenMapper.php index 7b11ef8adf3..8feb275b3b7 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenMapper.php +++ b/lib/private/Authentication/Token/PublicKeyTokenMapper.php @@ -229,4 +229,31 @@ class PublicKeyTokenMapper extends QBMapper { ); $update->executeStatement(); } + + public function updateHashesForUser(string $userId, string $passwordHash): void { + $qb = $this->db->getQueryBuilder(); + $update = $qb->update($this->getTableName()) + ->set('password_hash', $qb->createNamedParameter($passwordHash)) + ->where( + $qb->expr()->eq('uid', $qb->createNamedParameter($userId)) + ); + $update->executeStatement(); + } + + public function getFirstTokenForUser(string $userId): ?PublicKeyToken { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($userId))) + ->setMaxResults(1) + ->orderBy('id'); + $result = $qb->executeQuery(); + + $data = $result->fetch(); + $result->closeCursor(); + if ($data === false) { + return null; + } + return PublicKeyToken::fromRow($data); + } } diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index c8adec24b31..84708065070 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -46,6 +46,8 @@ use OCP\Security\IHasher; use Psr\Log\LoggerInterface; class PublicKeyTokenProvider implements IProvider { + public const TOKEN_MIN_LENGTH = 22; + use TTransactional; /** @var PublicKeyTokenMapper */ @@ -98,13 +100,33 @@ class PublicKeyTokenProvider implements IProvider { string $name, int $type = IToken::TEMPORARY_TOKEN, int $remember = IToken::DO_NOT_REMEMBER): IToken { + if (strlen($token) < self::TOKEN_MIN_LENGTH) { + $exception = new InvalidTokenException('Token is too short, minimum of ' . self::TOKEN_MIN_LENGTH . ' characters is required, ' . strlen($token) . ' characters given'); + $this->logger->error('Invalid token provided when generating new token', ['exception' => $exception]); + throw $exception; + } + if (mb_strlen($name) > 128) { $name = mb_substr($name, 0, 120) . '…'; } + // We need to check against one old token to see if there is a password + // hash that we can reuse for detecting outdated passwords + $randomOldToken = $this->mapper->getFirstTokenForUser($uid); + $oldTokenMatches = $randomOldToken && $this->hasher->verify(sha1($password) . $password, $randomOldToken->getPasswordHash()); + $dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember); + + if ($oldTokenMatches) { + $dbToken->setPasswordHash($randomOldToken->getPasswordHash()); + } + $this->mapper->insert($dbToken); + if (!$oldTokenMatches && $password !== null) { + $this->updatePasswords($uid, $password); + } + // Add the token to the cache $this->cache[$dbToken->getToken()] = $dbToken; @@ -112,6 +134,27 @@ class PublicKeyTokenProvider implements IProvider { } public function getToken(string $tokenId): IToken { + /** + * Token length: 72 + * @see \OC\Core\Controller\ClientFlowLoginController::generateAppPassword + * @see \OC\Core\Controller\AppPasswordController::getAppPassword + * @see \OC\Core\Command\User\AddAppPassword::execute + * @see \OC\Core\Service\LoginFlowV2Service::flowDone + * @see \OCA\Talk\MatterbridgeManager::generatePassword + * @see \OCA\Preferred_Providers\Controller\PasswordController::generateAppPassword + * @see \OCA\GlobalSiteSelector\TokenHandler::generateAppPassword + * + * Token length: 22-256 - https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length + * @see \OC\User\Session::createSessionToken + * + * Token length: 29 + * @see \OCA\Settings\Controller\AuthSettingsController::generateRandomDeviceToken + * @see \OCA\Registration\Service\RegistrationService::generateAppPassword + */ + if (strlen($tokenId) < self::TOKEN_MIN_LENGTH) { + throw new InvalidTokenException('Token is too short for a generated token, should be the password during basic auth'); + } + $tokenHash = $this->hashToken($tokenId); if (isset($this->cache[$tokenHash])) { @@ -122,7 +165,7 @@ class PublicKeyTokenProvider implements IProvider { $token = $this->cache[$tokenHash]; } else { try { - $token = $this->mapper->getToken($this->hashToken($tokenId)); + $token = $this->mapper->getToken($tokenHash); $this->cache[$token->getToken()] = $token; } catch (DoesNotExistException $ex) { try { @@ -289,10 +332,11 @@ class PublicKeyTokenProvider implements IProvider { // Update the password for all tokens $tokens = $this->mapper->getTokenByUser($token->getUID()); + $hashedPassword = $this->hashPassword($password); foreach ($tokens as $t) { $publicKey = $t->getPublicKey(); $t->setPassword($this->encryptPassword($password, $publicKey)); - $t->setPasswordHash($this->hashPassword($password)); + $t->setPasswordHash($hashedPassword); $this->updateToken($t); } } @@ -481,6 +525,13 @@ class PublicKeyTokenProvider implements IProvider { $this->updateToken($t); } } + + // If password hashes are different we update them all to be equal so + // that the next execution only needs to verify once + if (count($hashNeedsUpdate) > 1) { + $newPasswordHash = $this->hashPassword($password); + $this->mapper->updateHashesForUser($uid, $newPasswordHash); + } } private function logOpensslError() { diff --git a/lib/private/Collaboration/Reference/File/FileReferenceProvider.php b/lib/private/Collaboration/Reference/File/FileReferenceProvider.php index 4e6c7ea623f..95e49cdf860 100644 --- a/lib/private/Collaboration/Reference/File/FileReferenceProvider.php +++ b/lib/private/Collaboration/Reference/File/FileReferenceProvider.php @@ -62,21 +62,21 @@ class FileReferenceProvider implements IReferenceProvider { } private function getFilesAppLinkId(string $referenceText): ?int { - $start = $this->urlGenerator->getAbsoluteURL('/apps/files'); - $startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/files'); + $start = $this->urlGenerator->getAbsoluteURL('/apps/files/'); + $startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/files/'); $fileId = null; if (mb_strpos($referenceText, $start) === 0) { $parts = parse_url($referenceText); - parse_str($parts['query'], $query); + parse_str($parts['query'] ?? '', $query); $fileId = isset($query['fileid']) ? (int)$query['fileid'] : $fileId; $fileId = isset($query['openfile']) ? (int)$query['openfile'] : $fileId; } if (mb_strpos($referenceText, $startIndex) === 0) { $parts = parse_url($referenceText); - parse_str($parts['query'], $query); + parse_str($parts['query'] ?? '', $query); $fileId = isset($query['fileid']) ? (int)$query['fileid'] : $fileId; $fileId = isset($query['openfile']) ? (int)$query['openfile'] : $fileId; } diff --git a/lib/private/Config.php b/lib/private/Config.php index ba3b8c6fe4d..7308a3769df 100644 --- a/lib/private/Config.php +++ b/lib/private/Config.php @@ -285,6 +285,13 @@ class Config { 'This can usually be fixed by giving the webserver write access to the config directory.'); } + // Never write file back if disk space should be too low + $df = disk_free_space($this->configDir); + $size = strlen($content) + 10240; + if ($df !== false && (int)$df < $size) { + throw new \Exception($this->configDir . " does not have enough space for writing the config file! Not writing it back!"); + } + // Try to acquire a file lock if (!flock($filePointer, LOCK_EX)) { throw new \Exception(sprintf('Could not acquire an exclusive lock on the config file %s', $this->configFilePath)); diff --git a/lib/private/DB/BacktraceDebugStack.php b/lib/private/DB/BacktraceDebugStack.php index be37e5a35da..6a19be89225 100644 --- a/lib/private/DB/BacktraceDebugStack.php +++ b/lib/private/DB/BacktraceDebugStack.php @@ -30,5 +30,6 @@ class BacktraceDebugStack extends DebugStack { parent::startQuery($sql, $params, $types); $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $this->queries[$this->currentQuery]['backtrace'] = $backtrace; + $this->queries[$this->currentQuery]['start'] = $this->start; } } diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php index 0e08d9d0e83..ae6481e45bb 100644 --- a/lib/private/Files/Config/MountProviderCollection.php +++ b/lib/private/Files/Config/MountProviderCollection.php @@ -26,6 +26,7 @@ namespace OC\Files\Config; use OC\Hooks\Emitter; use OC\Hooks\EmitterTrait; +use OCP\Diagnostics\IEventLogger; use OCP\Files\Config\IHomeMountProvider; use OCP\Files\Config\IMountProvider; use OCP\Files\Config\IMountProviderCollection; @@ -65,13 +66,29 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { /** @var callable[] */ private $mountFilters = []; + private IEventLogger $eventLogger; + /** * @param \OCP\Files\Storage\IStorageFactory $loader * @param IUserMountCache $mountCache */ - public function __construct(IStorageFactory $loader, IUserMountCache $mountCache) { + public function __construct( + IStorageFactory $loader, + IUserMountCache $mountCache, + IEventLogger $eventLogger + ) { $this->loader = $loader; $this->mountCache = $mountCache; + $this->eventLogger = $eventLogger; + } + + private function getMountsFromProvider(IMountProvider $provider, IUser $user, IStorageFactory $loader): array { + $class = str_replace('\\', '_', get_class($provider)); + $uid = $user->getUID(); + $this->eventLogger->start('fs:setup:provider:' . $class, "Getting mounts from $class for $uid"); + $mounts = $provider->getMountsForUser($user, $loader) ?? []; + $this->eventLogger->end('fs:setup:provider:' . $class); + return $mounts; } /** @@ -82,11 +99,8 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { private function getUserMountsForProviders(IUser $user, array $providers): array { $loader = $this->loader; $mounts = array_map(function (IMountProvider $provider) use ($user, $loader) { - return $provider->getMountsForUser($user, $loader); + return $this->getMountsFromProvider($provider, $user, $loader); }, $providers); - $mounts = array_filter($mounts, function ($result) { - return is_array($result); - }); $mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) { return array_merge($mounts, $providerMounts); }, []); @@ -121,24 +135,22 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { return (get_class($provider) === 'OCA\Files_Sharing\MountProvider'); }); foreach ($firstProviders as $provider) { - $mounts = $provider->getMountsForUser($user, $this->loader); - if (is_array($mounts)) { - $firstMounts = array_merge($firstMounts, $mounts); - } + $mounts = $this->getMountsFromProvider($provider, $user, $this->loader); + $firstMounts = array_merge($firstMounts, $mounts); } $firstMounts = $this->filterMounts($user, $firstMounts); array_walk($firstMounts, [$mountManager, 'addMount']); $lateMounts = []; foreach ($lastProviders as $provider) { - $mounts = $provider->getMountsForUser($user, $this->loader); - if (is_array($mounts)) { - $lateMounts = array_merge($lateMounts, $mounts); - } + $mounts = $this->getMountsFromProvider($provider, $user, $this->loader); + $lateMounts = array_merge($lateMounts, $mounts); } $lateMounts = $this->filterMounts($user, $lateMounts); + $this->eventLogger->start("fs:setup:add-mounts", "Add mounts to the filesystem"); array_walk($lateMounts, [$mountManager, 'addMount']); + $this->eventLogger->end("fs:setup:add-mounts"); return array_merge($lateMounts, $firstMounts); } diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index 3540b563742..fe677c5ea52 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -31,6 +31,7 @@ namespace OC\Files\Config; use OCP\Cache\CappedMemoryCache; use OCA\Files_Sharing\SharedMount; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Diagnostics\IEventLogger; use OCP\Files\Config\ICachedMountFileInfo; use OCP\Files\Config\ICachedMountInfo; use OCP\Files\Config\IUserMountCache; @@ -56,19 +57,27 @@ class UserMountCache implements IUserMountCache { private LoggerInterface $logger; /** @var CappedMemoryCache<array> */ private CappedMemoryCache $cacheInfoCache; + private IEventLogger $eventLogger; /** * UserMountCache constructor. */ - public function __construct(IDBConnection $connection, IUserManager $userManager, LoggerInterface $logger) { + public function __construct( + IDBConnection $connection, + IUserManager $userManager, + LoggerInterface $logger, + IEventLogger $eventLogger + ) { $this->connection = $connection; $this->userManager = $userManager; $this->logger = $logger; + $this->eventLogger = $eventLogger; $this->cacheInfoCache = new CappedMemoryCache(); $this->mountsForUsers = new CappedMemoryCache(); } public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null) { + $this->eventLogger->start('fs:setup:user:register', 'Registering mounts for user'); // filter out non-proper storages coming from unit tests $mounts = array_filter($mounts, function (IMountPoint $mount) { return $mount instanceof SharedMount || ($mount->getStorage() && $mount->getStorage()->getCache()); @@ -134,6 +143,7 @@ class UserMountCache implements IUserMountCache { foreach ($changedMounts as $mount) { $this->updateCachedMount($mount); } + $this->eventLogger->end('fs:setup:user:register'); } /** diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php index 9ba0e504058..805cce658a6 100644 --- a/lib/private/Files/Mount/Manager.php +++ b/lib/private/Files/Mount/Manager.php @@ -184,8 +184,13 @@ class Manager implements IMountManager { * @return IMountPoint[] */ public function findByNumericId(int $id): array { - $storageId = \OC\Files\Cache\Storage::getStorageId($id); - return $this->findByStorageId($storageId); + $result = []; + foreach ($this->mounts as $mount) { + if ($mount->getNumericStorageId() === $id) { + $result[] = $mount; + } + } + return $result; } /** diff --git a/lib/private/Files/Mount/MountPoint.php b/lib/private/Files/Mount/MountPoint.php index 49f7e560ad3..20e08120080 100644 --- a/lib/private/Files/Mount/MountPoint.php +++ b/lib/private/Files/Mount/MountPoint.php @@ -44,6 +44,7 @@ class MountPoint implements IMountPoint { protected $storage = null; protected $class; protected $storageId; + protected $numericStorageId = null; protected $rootId = null; /** @@ -195,19 +196,15 @@ class MountPoint implements IMountPoint { } /** - * @return string + * @return string|null */ public function getStorageId() { if (!$this->storageId) { - if (is_null($this->storage)) { - $storage = $this->createStorage(); //FIXME: start using exceptions - if (is_null($storage)) { - return null; - } - - $this->storage = $storage; + $storage = $this->getStorage(); + if (is_null($storage)) { + return null; } - $this->storageId = $this->storage->getId(); + $this->storageId = $storage->getId(); if (strlen($this->storageId) > 64) { $this->storageId = md5($this->storageId); } @@ -219,7 +216,14 @@ class MountPoint implements IMountPoint { * @return int */ public function getNumericStorageId() { - return $this->getStorage()->getStorageCache()->getNumericId(); + if (is_null($this->numericStorageId)) { + $storage = $this->getStorage(); + if (is_null($storage)) { + return -1; + } + $this->numericStorageId = $storage->getStorageCache()->getNumericId(); + } + return $this->numericStorageId; } /** diff --git a/lib/private/Files/Node/File.php b/lib/private/Files/Node/File.php index d8a6741dc6e..475930b361a 100644 --- a/lib/private/Files/Node/File.php +++ b/lib/private/Files/Node/File.php @@ -37,7 +37,7 @@ class File extends Node implements \OCP\Files\File { * Creates a Folder that represents a non-existing path * * @param string $path path - * @return string non-existing node class + * @return NonExistingFile non-existing node */ protected function createNonExistingNode($path) { return new NonExistingFile($this->root, $this->view, $path); diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index bf9ae3c148d..90aed642a2d 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -54,7 +54,7 @@ class Folder extends Node implements \OCP\Files\Folder { * Creates a Folder that represents a non-existing path * * @param string $path path - * @return string non-existing node class + * @return NonExistingFolder non-existing node */ protected function createNonExistingNode($path) { return new NonExistingFolder($this->root, $this->view, $path); @@ -98,7 +98,7 @@ class Folder extends Node implements \OCP\Files\Folder { * @throws \OCP\Files\NotFoundException */ public function getDirectoryListing() { - $folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo()); + $folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo(false)); return array_map(function (FileInfo $info) { if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) { @@ -114,7 +114,7 @@ class Folder extends Node implements \OCP\Files\Folder { * @param FileInfo $info * @return File|Folder */ - protected function createNode($path, FileInfo $info = null) { + protected function createNode($path, FileInfo $info = null, bool $infoHasSubMountsIncluded = true) { if (is_null($info)) { $isDir = $this->view->is_dir($path); } else { @@ -122,7 +122,7 @@ class Folder extends Node implements \OCP\Files\Folder { } $parent = dirname($path) === $this->getPath() ? $this : null; if ($isDir) { - return new Folder($this->root, $this->view, $path, $info, $parent); + return new Folder($this->root, $this->view, $path, $info, $parent, $infoHasSubMountsIncluded); } else { return new File($this->root, $this->view, $path, $info, $parent); } diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index 6c4e0b0f908..2f88cc3a15a 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -56,35 +56,35 @@ class Node implements \OCP\Files\Node { */ protected $path; - /** - * @var \OCP\Files\FileInfo - */ - protected $fileInfo; + protected ?FileInfo $fileInfo; /** * @var Node|null */ protected $parent; + private bool $infoHasSubMountsIncluded; + /** * @param \OC\Files\View $view * @param \OCP\Files\IRootFolder $root * @param string $path * @param FileInfo $fileInfo */ - public function __construct($root, $view, $path, $fileInfo = null, ?Node $parent = null) { + public function __construct($root, $view, $path, $fileInfo = null, ?Node $parent = null, bool $infoHasSubMountsIncluded = true) { $this->view = $view; $this->root = $root; $this->path = $path; $this->fileInfo = $fileInfo; $this->parent = $parent; + $this->infoHasSubMountsIncluded = $infoHasSubMountsIncluded; } /** * Creates a Node of the same type that represents a non-existing path * * @param string $path path - * @return string non-existing node class + * @return Node non-existing node * @throws \Exception */ protected function createNonExistingNode($path) { @@ -98,17 +98,23 @@ class Node implements \OCP\Files\Node { * @throws InvalidPathException * @throws NotFoundException */ - public function getFileInfo() { + public function getFileInfo(bool $includeMountPoint = true) { if (!$this->fileInfo) { if (!Filesystem::isValidPath($this->path)) { throw new InvalidPathException(); } - $fileInfo = $this->view->getFileInfo($this->path); + $fileInfo = $this->view->getFileInfo($this->path, $includeMountPoint); + $this->infoHasSubMountsIncluded = $includeMountPoint; if ($fileInfo instanceof FileInfo) { $this->fileInfo = $fileInfo; } else { throw new NotFoundException(); } + } elseif ($includeMountPoint && !$this->infoHasSubMountsIncluded && $this instanceof Folder) { + if ($this->fileInfo instanceof \OC\Files\FileInfo) { + $this->view->addSubMounts($this->fileInfo); + } + $this->infoHasSubMountsIncluded = true; } return $this->fileInfo; } @@ -179,7 +185,7 @@ class Node implements \OCP\Files\Node { * @return string */ public function getInternalPath() { - return $this->getFileInfo()->getInternalPath(); + return $this->getFileInfo(false)->getInternalPath(); } /** @@ -188,7 +194,7 @@ class Node implements \OCP\Files\Node { * @throws NotFoundException */ public function getId() { - return $this->getFileInfo()->getId(); + return $this->getFileInfo(false)->getId() ?? -1; } /** @@ -232,7 +238,7 @@ class Node implements \OCP\Files\Node { * @throws NotFoundException */ public function getPermissions() { - return $this->getFileInfo()->getPermissions(); + return $this->getFileInfo(false)->getPermissions(); } /** @@ -241,7 +247,7 @@ class Node implements \OCP\Files\Node { * @throws NotFoundException */ public function isReadable() { - return $this->getFileInfo()->isReadable(); + return $this->getFileInfo(false)->isReadable(); } /** @@ -250,7 +256,7 @@ class Node implements \OCP\Files\Node { * @throws NotFoundException */ public function isUpdateable() { - return $this->getFileInfo()->isUpdateable(); + return $this->getFileInfo(false)->isUpdateable(); } /** @@ -259,7 +265,7 @@ class Node implements \OCP\Files\Node { * @throws NotFoundException */ public function isDeletable() { - return $this->getFileInfo()->isDeletable(); + return $this->getFileInfo(false)->isDeletable(); } /** @@ -268,7 +274,7 @@ class Node implements \OCP\Files\Node { * @throws NotFoundException */ public function isShareable() { - return $this->getFileInfo()->isShareable(); + return $this->getFileInfo(false)->isShareable(); } /** @@ -277,7 +283,7 @@ class Node implements \OCP\Files\Node { * @throws NotFoundException */ public function isCreatable() { - return $this->getFileInfo()->isCreatable(); + return $this->getFileInfo(false)->isCreatable(); } /** @@ -328,42 +334,42 @@ class Node implements \OCP\Files\Node { } public function isMounted() { - return $this->getFileInfo()->isMounted(); + return $this->getFileInfo(false)->isMounted(); } public function isShared() { - return $this->getFileInfo()->isShared(); + return $this->getFileInfo(false)->isShared(); } public function getMimeType() { - return $this->getFileInfo()->getMimetype(); + return $this->getFileInfo(false)->getMimetype(); } public function getMimePart() { - return $this->getFileInfo()->getMimePart(); + return $this->getFileInfo(false)->getMimePart(); } public function getType() { - return $this->getFileInfo()->getType(); + return $this->getFileInfo(false)->getType(); } public function isEncrypted() { - return $this->getFileInfo()->isEncrypted(); + return $this->getFileInfo(false)->isEncrypted(); } public function getMountPoint() { - return $this->getFileInfo()->getMountPoint(); + return $this->getFileInfo(false)->getMountPoint(); } public function getOwner() { - return $this->getFileInfo()->getOwner(); + return $this->getFileInfo(false)->getOwner(); } public function getChecksum() { } public function getExtension(): string { - return $this->getFileInfo()->getExtension(); + return $this->getFileInfo(false)->getExtension(); } /** diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index 8d0a65d2a68..29cdbb987c3 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -202,9 +202,9 @@ class Root extends Folder implements IRootFolder { $path = $this->normalizePath($path); if ($this->isValidPath($path)) { $fullPath = $this->getFullPath($path); - $fileInfo = $this->view->getFileInfo($fullPath); + $fileInfo = $this->view->getFileInfo($fullPath, false); if ($fileInfo) { - return $this->createNode($fullPath, $fileInfo); + return $this->createNode($fullPath, $fileInfo, false); } else { throw new NotFoundException($path); } diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index 979e4ce966a..4cbd0328d76 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -212,6 +212,8 @@ class SetupManager { } $this->setupUsersComplete[] = $user->getUID(); + $this->eventLogger->start('fs:setup:user:full', 'Setup full filesystem for user'); + if (!isset($this->setupUserMountProviders[$user->getUID()])) { $this->setupUserMountProviders[$user->getUID()] = []; } @@ -226,6 +228,7 @@ class SetupManager { }); }); $this->afterUserFullySetup($user, $previouslySetupProviders); + $this->eventLogger->end('fs:setup:user:full'); } /** @@ -237,6 +240,8 @@ class SetupManager { } $this->setupUsers[] = $user->getUID(); + $this->eventLogger->start('fs:setup:user:onetime', 'Onetime filesystem for user'); + $this->setupBuiltinWrappers(); $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); @@ -250,14 +255,18 @@ class SetupManager { Filesystem::initInternal($userDir); if ($this->lockdownManager->canAccessFilesystem()) { + $this->eventLogger->start('fs:setup:user:home', 'Setup home filesystem for user'); // home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers $homeMount = $this->mountProviderCollection->getHomeMountForUser($user); $this->mountManager->addMount($homeMount); if ($homeMount->getStorageRootId() === -1) { + $this->eventLogger->start('fs:setup:user:home:scan', 'Scan home filesystem for user'); $homeMount->getStorage()->mkdir(''); $homeMount->getStorage()->getScanner()->scan(''); + $this->eventLogger->end('fs:setup:user:home:scan'); } + $this->eventLogger->end('fs:setup:user:home'); } else { $this->mountManager->addMount(new MountPoint( new NullStorage([]), @@ -271,12 +280,15 @@ class SetupManager { } $this->listenForNewMountProviders(); + + $this->eventLogger->end('fs:setup:user:onetime'); } /** * Final housekeeping after a user has been fully setup */ private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void { + $this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup'); $userRoot = '/' . $user->getUID() . '/'; $mounts = $this->mountManager->getAll(); $mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) { @@ -296,6 +308,7 @@ class SetupManager { $this->cache->set($user->getUID(), true, $cacheDuration); $this->fullSetupRequired[$user->getUID()] = false; } + $this->eventLogger->end('fs:setup:user:full:post'); } /** @@ -312,17 +325,17 @@ class SetupManager { $this->oneTimeUserSetup($user); } - $this->eventLogger->start('setup_fs', 'Setup filesystem'); - if ($this->lockdownManager->canAccessFilesystem()) { $mountCallback(); } + $this->eventLogger->start('fs:setup:user:post-init-mountpoint', 'post_initMountPoints legacy hook'); \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]); + $this->eventLogger->end('fs:setup:user:post-init-mountpoint'); $userDir = '/' . $user->getUID() . '/files'; + $this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook'); OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]); - - $this->eventLogger->end('setup_fs'); + $this->eventLogger->end('fs:setup:user:setup-hook'); } /** @@ -335,7 +348,7 @@ class SetupManager { } $this->rootSetup = true; - $this->eventLogger->start('setup_root_fs', 'Setup root filesystem'); + $this->eventLogger->start('fs:setup:root', 'Setup root filesystem'); $this->setupBuiltinWrappers(); @@ -344,7 +357,7 @@ class SetupManager { $this->mountManager->addMount($rootMountProvider); } - $this->eventLogger->end('setup_root_fs'); + $this->eventLogger->end('fs:setup:root'); } /** @@ -413,6 +426,9 @@ class SetupManager { $this->oneTimeUserSetup($user); } + $this->eventLogger->start('fs:setup:user:path', "Setup $path filesystem for user"); + $this->eventLogger->start('fs:setup:user:path:find', "Find mountpoint for $path"); + $mounts = []; if (!in_array($cachedMount->getMountProvider(), $setupProviders)) { $currentProviders[] = $cachedMount->getMountProvider(); @@ -421,13 +437,16 @@ class SetupManager { $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]); } else { $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup"); + $this->eventLogger->end('fs:setup:user:path:find'); $this->setupForUser($user); + $this->eventLogger->end('fs:setup:user:path'); return; } } if ($includeChildren) { $subCachedMounts = $this->userMountCache->getMountsInPath($user, $path); + $this->eventLogger->end('fs:setup:user:path:find'); $needsFullSetup = array_reduce($subCachedMounts, function (bool $needsFullSetup, ICachedMountInfo $cachedMountInfo) { return $needsFullSetup || $cachedMountInfo->getMountProvider() === ''; @@ -436,6 +455,7 @@ class SetupManager { if ($needsFullSetup) { $this->logger->debug("mount has no provider set, performing full setup"); $this->setupForUser($user); + $this->eventLogger->end('fs:setup:user:path'); return; } else { foreach ($subCachedMounts as $cachedMount) { @@ -446,6 +466,8 @@ class SetupManager { } } } + } else { + $this->eventLogger->end('fs:setup:user:path:find'); } if (count($mounts)) { @@ -456,6 +478,7 @@ class SetupManager { } elseif (!$this->isSetupStarted($user)) { $this->oneTimeUserSetup($user); } + $this->eventLogger->end('fs:setup:user:path'); } private function fullSetupRequired(IUser $user): bool { @@ -488,6 +511,8 @@ class SetupManager { return; } + $this->eventLogger->start('fs:setup:user:providers', "Setup filesystem for " . implode(', ', $providers)); + // home providers are always used $providers = array_filter($providers, function (string $provider) { return !is_subclass_of($provider, IHomeMountProvider::class); @@ -504,6 +529,7 @@ class SetupManager { if (!$this->isSetupStarted($user)) { $this->oneTimeUserSetup($user); } + $this->eventLogger->end('fs:setup:user:providers'); return; } else { $this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers); @@ -514,6 +540,7 @@ class SetupManager { $this->setupForUserWith($user, function () use ($mounts) { array_walk($mounts, [$this->mountManager, 'addMount']); }); + $this->eventLogger->end('fs:setup:user:providers'); } public function tearDown() { diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index f79a992c773..456f804ee56 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1412,11 +1412,7 @@ class View { if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') { //add the sizes of other mount points to the folder $extOnly = ($includeMountPoints === 'ext'); - $mounts = Filesystem::getMountManager()->findIn($path); - $info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) { - $subStorage = $mount->getStorage(); - return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage); - })); + $this->addSubMounts($info, $extOnly); } } @@ -1429,6 +1425,17 @@ class View { } /** + * Extend a FileInfo that was previously requested with `$includeMountPoints = false` to include the sub mounts + */ + public function addSubMounts(FileInfo $info, $extOnly = false): void { + $mounts = Filesystem::getMountManager()->findIn($info->getPath()); + $info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) { + $subStorage = $mount->getStorage(); + return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage); + })); + } + + /** * get the content of a directory * * @param string $directory path under datadirectory diff --git a/lib/private/Preview/IMagickSupport.php b/lib/private/Preview/IMagickSupport.php new file mode 100644 index 00000000000..e22ae93ab94 --- /dev/null +++ b/lib/private/Preview/IMagickSupport.php @@ -0,0 +1,40 @@ +<?php + +namespace OC\Preview; + +use OCP\ICache; +use OCP\ICacheFactory; + +class IMagickSupport { + private ICache $cache; + private ?\Imagick $imagick; + + public function __construct(ICacheFactory $cacheFactory) { + $this->cache = $cacheFactory->createLocal('imagick'); + + if (extension_loaded('imagick')) { + $this->imagick = new \Imagick(); + } else { + $this->imagick = null; + } + } + + public function hasExtension(): bool { + return !is_null($this->imagick); + } + + public function supportsFormat(string $format): bool { + if (is_null($this->imagick)) { + return false; + } + + $cached = $this->cache->get($format); + if (!is_null($cached)) { + return $cached; + } + + $formatSupported = count($this->imagick->queryFormats($format)) === 1; + $this->cache->set($format, $cached); + return $formatSupported; + } +} diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php index 367f0c1c057..dd6b6ba8ee1 100644 --- a/lib/private/PreviewManager.php +++ b/lib/private/PreviewManager.php @@ -33,6 +33,7 @@ namespace OC; use OC\AppFramework\Bootstrap\Coordinator; use OC\Preview\Generator; use OC\Preview\GeneratorHelper; +use OC\Preview\IMagickSupport; use OCP\AppFramework\QueryException; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\File; @@ -73,6 +74,7 @@ class PreviewManager implements IPreview { private array $loadedBootstrapProviders = []; private IServerContainer $container; private IBinaryFinder $binaryFinder; + private IMagickSupport $imagickSupport; public function __construct( IConfig $config, @@ -84,7 +86,8 @@ class PreviewManager implements IPreview { ?string $userId, Coordinator $bootstrapCoordinator, IServerContainer $container, - IBinaryFinder $binaryFinder + IBinaryFinder $binaryFinder, + IMagickSupport $imagickSupport ) { $this->config = $config; $this->rootFolder = $rootFolder; @@ -96,6 +99,7 @@ class PreviewManager implements IPreview { $this->bootstrapCoordinator = $bootstrapCoordinator; $this->container = $container; $this->binaryFinder = $binaryFinder; + $this->imagickSupport = $imagickSupport; } /** @@ -368,9 +372,7 @@ class PreviewManager implements IPreview { $this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes()); // SVG, Office and Bitmap require imagick - if (extension_loaded('imagick')) { - $checkImagick = new \Imagick(); - + if ($this->imagickSupport->hasExtension()) { $imagickProviders = [ 'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class], 'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class], @@ -390,12 +392,12 @@ class PreviewManager implements IPreview { continue; } - if (count($checkImagick->queryFormats($queryFormat)) === 1) { + if ($this->imagickSupport->supportsFormat($queryFormat)) { $this->registerCoreProvider($class, $provider['mimetype']); } } - if (count($checkImagick->queryFormats('PDF')) === 1) { + if ($this->imagickSupport->supportsFormat('PDF')) { // Office requires openoffice or libreoffice $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', null); if (!is_string($officeBinary)) { diff --git a/lib/private/Route/CachingRouter.php b/lib/private/Route/CachingRouter.php index f65060e710b..69fb3c986c9 100644 --- a/lib/private/Route/CachingRouter.php +++ b/lib/private/Route/CachingRouter.php @@ -24,20 +24,27 @@ */ namespace OC\Route; +use OCP\Diagnostics\IEventLogger; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\IRequest; +use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; class CachingRouter extends Router { - /** - * @var \OCP\ICache - */ - protected $cache; + protected ICache $cache; - /** - * @param \OCP\ICache $cache - */ - public function __construct($cache, LoggerInterface $logger) { - $this->cache = $cache; - parent::__construct($logger); + public function __construct( + ICacheFactory $cacheFactory, + LoggerInterface $logger, + IRequest $request, + IConfig $config, + IEventLogger $eventLogger, + ContainerInterface $container + ) { + $this->cache = $cacheFactory->createLocal('route'); + parent::__construct($logger, $request, $config, $eventLogger, $container); } /** diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php index 7e1acd49800..e2a092d861e 100644 --- a/lib/private/Route/Router.php +++ b/lib/private/Route/Router.php @@ -34,8 +34,12 @@ namespace OC\Route; use OC\AppFramework\Routing\RouteParser; use OCP\AppFramework\App; +use OCP\Diagnostics\IEventLogger; +use OCP\IConfig; +use OCP\IRequest; use OCP\Route\IRouter; use OCP\Util; +use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Exception\RouteNotFoundException; @@ -64,11 +68,21 @@ class Router implements IRouter { protected LoggerInterface $logger; /** @var RequestContext */ protected $context; - - public function __construct(LoggerInterface $logger) { + private IEventLogger $eventLogger; + private IConfig $config; + private ContainerInterface $container; + + public function __construct( + LoggerInterface $logger, + IRequest $request, + IConfig $config, + IEventLogger $eventLogger, + ContainerInterface $container + ) { $this->logger = $logger; + $this->config = $config; $baseUrl = \OC::$WEBROOT; - if (!(\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) { + if (!($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) { $baseUrl .= '/index.php'; } if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) { @@ -76,12 +90,13 @@ class Router implements IRouter { } else { $method = 'GET'; } - $request = \OC::$server->getRequest(); $host = $request->getServerHost(); $schema = $request->getServerProtocol(); $this->context = new RequestContext($baseUrl, $method, $host, $schema); // TODO cache $this->root = $this->getCollection('root'); + $this->eventLogger = $eventLogger; + $this->container = $container; } /** @@ -134,7 +149,7 @@ class Router implements IRouter { $routingFiles = []; } } - \OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes'); + $this->eventLogger->start('route:load:' . $requestedApp, 'Loading Routes for ' . $requestedApp); foreach ($routingFiles as $app => $file) { if (!isset($this->loadedApps[$app])) { if (!\OC_App::isAppLoaded($app)) { @@ -170,7 +185,7 @@ class Router implements IRouter { $collection->addPrefix('/ocs'); $this->root->addCollection($collection); } - \OC::$server->getEventLogger()->end('loadroutes' . $requestedApp); + $this->eventLogger->end('route:load:' . $requestedApp); } /** @@ -231,6 +246,7 @@ class Router implements IRouter { * @return array */ public function findMatchingRoute(string $url): array { + $this->eventLogger->start('route:match', 'Match route'); if (substr($url, 0, 6) === '/apps/') { // empty string / 'apps' / $app / rest of the route [, , $app,] = explode('/', $url, 4); @@ -249,7 +265,7 @@ class Router implements IRouter { $this->loadRoutes('settings'); } elseif (substr($url, 0, 6) === '/core/') { \OC::$REQUESTEDAPP = $url; - if (!\OC::$server->getConfig()->getSystemValueBool('maintenance') && !Util::needUpgrade()) { + if (!$this->config->getSystemValueBool('maintenance') && !Util::needUpgrade()) { \OC_App::loadApps(); } $this->loadRoutes('core'); @@ -276,6 +292,7 @@ class Router implements IRouter { } } + $this->eventLogger->end('route:match'); return $parameters; } @@ -289,7 +306,7 @@ class Router implements IRouter { public function match($url) { $parameters = $this->findMatchingRoute($url); - \OC::$server->getEventLogger()->start('run_route', 'Run route'); + $this->eventLogger->start('route:run', 'Run route'); if (isset($parameters['caller'])) { $caller = $parameters['caller']; unset($parameters['caller']); @@ -303,13 +320,15 @@ class Router implements IRouter { } unset($parameters['action']); unset($parameters['caller']); + $this->eventLogger->start('route:run:call', 'Run callable route'); call_user_func($action, $parameters); + $this->eventLogger->end('route:run:call'); } elseif (isset($parameters['file'])) { include $parameters['file']; } else { throw new \Exception('no action available'); } - \OC::$server->getEventLogger()->end('run_route'); + $this->eventLogger->end('route:run'); } /** @@ -434,7 +453,7 @@ class Router implements IRouter { $applicationClassName = $appNameSpace . '\\AppInfo\\Application'; if (class_exists($applicationClassName)) { - $application = \OC::$server->query($applicationClassName); + $application = $this->container->get($applicationClassName); } else { $application = new App($appName); } diff --git a/lib/private/Server.php b/lib/private/Server.php index bd33cdf58bd..35f63686457 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -86,6 +86,7 @@ use OC\EventDispatcher\SymfonyAdapter; use OC\Federation\CloudFederationFactory; use OC\Federation\CloudFederationProviderManager; use OC\Federation\CloudIdManager; +use OC\Files\Config\MountProviderCollection; use OC\Files\Config\UserMountCache; use OC\Files\Config\UserMountCacheListener; use OC\Files\Lock\LockManager; @@ -126,9 +127,11 @@ use OC\Metadata\MetadataManager; use OC\Notification\Manager; use OC\OCS\DiscoveryService; use OC\Preview\GeneratorHelper; +use OC\Preview\IMagickSupport; use OC\Remote\Api\ApiFactory; use OC\Remote\InstanceFactory; use OC\RichObjectStrings\Validator; +use OC\Route\CachingRouter; use OC\Route\Router; use OC\Security\Bruteforce\Throttler; use OC\Security\CertificateManager; @@ -336,7 +339,8 @@ class Server extends ServerContainer implements IServerContainer { $c->get(ISession::class)->get('user_id'), $c->get(Coordinator::class), $c->get(IServerContainer::class), - $c->get(IBinaryFinder::class) + $c->get(IBinaryFinder::class), + $c->get(IMagickSupport::class) ); }); /** @deprecated 19.0.0 */ @@ -819,11 +823,10 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService(Router::class, function (Server $c) { $cacheFactory = $c->get(ICacheFactory::class); - $logger = $c->get(LoggerInterface::class); if ($cacheFactory->isLocalCacheAvailable()) { - $router = new \OC\Route\CachingRouter($cacheFactory->createLocal('route'), $logger); + $router = $c->resolve(CachingRouter::class); } else { - $router = new \OC\Route\Router($logger); + $router = $c->resolve(Router::class); } return $router; }); @@ -946,11 +949,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerDeprecatedAlias('DateTimeFormatter', IDateTimeFormatter::class); $this->registerService(IUserMountCache::class, function (ContainerInterface $c) { - $mountCache = new UserMountCache( - $c->get(IDBConnection::class), - $c->get(IUserManager::class), - $c->get(LoggerInterface::class) - ); + $mountCache = $c->get(UserMountCache::class); $listener = new UserMountCacheListener($mountCache); $listener->listen($c->get(IUserManager::class)); return $mountCache; @@ -959,9 +958,10 @@ class Server extends ServerContainer implements IServerContainer { $this->registerDeprecatedAlias('UserMountCache', IUserMountCache::class); $this->registerService(IMountProviderCollection::class, function (ContainerInterface $c) { - $loader = \OC\Files\Filesystem::getLoader(); + $loader = $c->get(IStorageFactory::class); $mountCache = $c->get(IUserMountCache::class); - $manager = new \OC\Files\Config\MountProviderCollection($loader, $mountCache); + $eventLogger = $c->get(IEventLogger::class); + $manager = new MountProviderCollection($loader, $mountCache, $eventLogger); // builtin providers diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index 5a4cd32e5df..d127443944f 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -130,7 +130,7 @@ class TemplateLayout extends \OC_Template { $navigation = $this->navigationManager->getAll(); $this->assign('navigation', $navigation); $settingsNavigation = $this->navigationManager->getAll('settings'); - $this->assign('settingsnavigation', $settingsNavigation); + $this->initialState->provideInitialState('core', 'settingsNavEntries', $settingsNavigation); foreach ($navigation as $entry) { if ($entry['active']) { @@ -268,7 +268,7 @@ class TemplateLayout extends \OC_Template { $this->assign('cssfiles', []); $this->assign('printcssfiles', []); - $this->assign('versionHash', self::$versionHash); + $this->initialState->provideInitialState('core', 'versionHash', self::$versionHash); foreach ($cssFiles as $info) { $web = $info[1]; $file = $info[2]; diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index c4c2f089767..7f51d81d21b 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -158,12 +158,17 @@ class OC_App { * @param string $app * @throws Exception */ - public static function loadApp(string $app) { + public static function loadApp(string $app): void { + if (isset(self::$loadedApps[$app])) { + return; + } self::$loadedApps[$app] = true; $appPath = self::getAppPath($app); if ($appPath === false) { return; } + $eventLogger = \OC::$server->get(\OCP\Diagnostics\IEventLogger::class); + $eventLogger->start("bootstrap:load_app:$app", "Load $app"); // in case someone calls loadApp() directly self::registerAutoloading($app, $appPath); @@ -174,12 +179,12 @@ class OC_App { $hasAppPhpFile = is_file($appPath . '/appinfo/app.php'); - \OC::$server->getEventLogger()->start('bootstrap:load_app_' . $app, 'Load app: ' . $app); if ($isBootable && $hasAppPhpFile) { \OC::$server->getLogger()->error('/appinfo/app.php is not loaded when \OCP\AppFramework\Bootstrap\IBootstrap on the application class is used. Migrate everything from app.php to the Application class.', [ 'app' => $app, ]); } elseif ($hasAppPhpFile) { + $eventLogger->start("bootstrap:load_app:$app:app.php", "Load legacy app.php app $app"); \OC::$server->getLogger()->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [ 'app' => $app, ]); @@ -202,11 +207,12 @@ class OC_App { ]); } } + $eventLogger->end("bootstrap:load_app:$app:app.php"); } - \OC::$server->getEventLogger()->end('bootstrap:load_app_' . $app); $coordinator->bootApp($app); + $eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it"); $info = self::getAppInfo($app); if (!empty($info['activity']['filters'])) { foreach ($info['activity']['filters'] as $filter) { @@ -261,6 +267,10 @@ class OC_App { } } } + + $eventLogger->end("bootstrap:load_app:$app:info"); + + $eventLogger->end("bootstrap:load_app:$app"); } /** diff --git a/lib/public/AppFramework/Controller.php b/lib/public/AppFramework/Controller.php index 89cfd2e55fc..e8500d5ae1a 100644 --- a/lib/public/AppFramework/Controller.php +++ b/lib/public/AppFramework/Controller.php @@ -91,6 +91,9 @@ abstract class Controller { if ($data->getLastModified() !== null) { $response->setLastModified($data->getLastModified()); } + if ($data->isThrottled()) { + $response->throttle($data->getThrottleMetadata()); + } return $response; } diff --git a/lib/public/Files/Mount/IMountPoint.php b/lib/public/Files/Mount/IMountPoint.php index b8e7ec9118f..1272550d737 100644 --- a/lib/public/Files/Mount/IMountPoint.php +++ b/lib/public/Files/Mount/IMountPoint.php @@ -55,7 +55,7 @@ interface IMountPoint { /** * Get the id of the storages * - * @return string + * @return string|null * @since 8.0.0 */ public function getStorageId(); @@ -63,7 +63,7 @@ interface IMountPoint { /** * Get the id of the storages * - * @return int + * @return int|null * @since 9.1.0 */ public function getNumericStorageId(); |