diff options
Diffstat (limited to 'lib')
122 files changed, 3490 insertions, 909 deletions
diff --git a/lib/autoloader.php b/lib/autoloader.php index c8eebee3e0c..a29b9aece79 100644 --- a/lib/autoloader.php +++ b/lib/autoloader.php @@ -38,6 +38,7 @@ namespace OC; use \OCP\AutoloadNotAllowedException; use OCP\ILogger; +use OCP\ICache; class Autoloader { /** @var bool */ @@ -182,9 +183,9 @@ class Autoloader { /** * Sets the optional low-latency cache for class to path mapping. * - * @param \OC\Memcache\Cache $memoryCache Instance of memory cache. + * @param ICache $memoryCache Instance of memory cache. */ - public function setMemoryCache(\OC\Memcache\Cache $memoryCache = null): void { + public function setMemoryCache(ICache $memoryCache = null): void { $this->memoryCache = $memoryCache; } } diff --git a/lib/base.php b/lib/base.php index 2dd878fdbbc..21889272dd7 100644 --- a/lib/base.php +++ b/lib/base.php @@ -161,7 +161,11 @@ class OC { 'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'], ], ]; - $fakeRequest = new \OC\AppFramework\Http\Request($params, new \OC\Security\SecureRandom(), new \OC\AllConfig(new \OC\SystemConfig(self::$config))); + $fakeRequest = new \OC\AppFramework\Http\Request( + $params, + new \OC\AppFramework\Http\RequestId($_SERVER['UNIQUE_ID'] ?? '', new \OC\Security\SecureRandom()), + new \OC\AllConfig(new \OC\SystemConfig(self::$config)) + ); $scriptName = $fakeRequest->getScriptName(); if (substr($scriptName, -1) == '/') { $scriptName .= 'index.php'; @@ -604,6 +608,7 @@ class OC { $eventLogger->end('request'); }); $eventLogger->start('boot', 'Initialize'); + $eventLogger->start('runtime', 'Runtime (total - autoloader)'); // Override php.ini and log everything if we're troubleshooting if (self::$config->getValue('loglevel') === ILogger::DEBUG) { diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index b6af8b8cef4..c821786b74b 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -2,7 +2,7 @@ // autoload_classmap.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname(dirname($vendorDir)); return array( @@ -196,6 +196,8 @@ return array( 'OCP\\Dashboard\\RegisterWidgetEvent' => $baseDir . '/lib/public/Dashboard/RegisterWidgetEvent.php', 'OCP\\Dashboard\\Service\\IEventsService' => $baseDir . '/lib/public/Dashboard/Service/IEventsService.php', 'OCP\\Dashboard\\Service\\IWidgetsService' => $baseDir . '/lib/public/Dashboard/Service/IWidgetsService.php', + 'OCP\\DataCollector\\AbstractDataCollector' => $baseDir . '/lib/public/DataCollector/AbstractDataCollector.php', + 'OCP\\DataCollector\\IDataCollector' => $baseDir . '/lib/public/DataCollector/IDataCollector.php', 'OCP\\Defaults' => $baseDir . '/lib/public/Defaults.php', 'OCP\\Diagnostics\\IEvent' => $baseDir . '/lib/public/Diagnostics/IEvent.php', 'OCP\\Diagnostics\\IEventLogger' => $baseDir . '/lib/public/Diagnostics/IEventLogger.php', @@ -260,6 +262,7 @@ return array( 'OCP\\Files\\Events\\FileCacheUpdated' => $baseDir . '/lib/public/Files/Events/FileCacheUpdated.php', 'OCP\\Files\\Events\\FileScannedEvent' => $baseDir . '/lib/public/Files/Events/FileScannedEvent.php', 'OCP\\Files\\Events\\FolderScannedEvent' => $baseDir . '/lib/public/Files/Events/FolderScannedEvent.php', + 'OCP\\Files\\Events\\InvalidateMountCacheEvent' => $baseDir . '/lib/public/Files/Events/InvalidateMountCacheEvent.php', 'OCP\\Files\\Events\\NodeAddedToCache' => $baseDir . '/lib/public/Files/Events/NodeAddedToCache.php', 'OCP\\Files\\Events\\NodeRemovedFromCache' => $baseDir . '/lib/public/Files/Events/NodeRemovedFromCache.php', 'OCP\\Files\\Events\\Node\\AbstractNodeEvent' => $baseDir . '/lib/public/Files/Events/Node/AbstractNodeEvent.php', @@ -412,6 +415,7 @@ return array( 'OCP\\INavigationManager' => $baseDir . '/lib/public/INavigationManager.php', 'OCP\\IPreview' => $baseDir . '/lib/public/IPreview.php', 'OCP\\IRequest' => $baseDir . '/lib/public/IRequest.php', + 'OCP\\IRequestId' => $baseDir . '/lib/public/IRequestId.php', 'OCP\\ISearch' => $baseDir . '/lib/public/ISearch.php', 'OCP\\IServerContainer' => $baseDir . '/lib/public/IServerContainer.php', 'OCP\\ISession' => $baseDir . '/lib/public/ISession.php', @@ -465,6 +469,8 @@ return array( 'OCP\\Preview\\IVersionedPreviewFile' => $baseDir . '/lib/public/Preview/IVersionedPreviewFile.php', 'OCP\\Profile\\ILinkAction' => $baseDir . '/lib/public/Profile/ILinkAction.php', 'OCP\\Profile\\ParameterDoesNotExistException' => $baseDir . '/lib/public/Profile/ParameterDoesNotExistException.php', + 'OCP\\Profiler\\IProfile' => $baseDir . '/lib/public/Profiler/IProfile.php', + 'OCP\\Profiler\\IProfiler' => $baseDir . '/lib/public/Profiler/IProfiler.php', 'OCP\\Remote\\Api\\IApiCollection' => $baseDir . '/lib/public/Remote/Api/IApiCollection.php', 'OCP\\Remote\\Api\\IApiFactory' => $baseDir . '/lib/public/Remote/Api/IApiFactory.php', 'OCP\\Remote\\Api\\ICapabilitiesApi' => $baseDir . '/lib/public/Remote/Api/ICapabilitiesApi.php', @@ -635,6 +641,7 @@ return array( 'OC\\AppFramework\\Http\\Dispatcher' => $baseDir . '/lib/private/AppFramework/Http/Dispatcher.php', 'OC\\AppFramework\\Http\\Output' => $baseDir . '/lib/private/AppFramework/Http/Output.php', 'OC\\AppFramework\\Http\\Request' => $baseDir . '/lib/private/AppFramework/Http/Request.php', + 'OC\\AppFramework\\Http\\RequestId' => $baseDir . '/lib/private/AppFramework/Http/RequestId.php', 'OC\\AppFramework\\Logger' => $baseDir . '/lib/private/AppFramework/Logger.php', 'OC\\AppFramework\\Middleware\\AdditionalScriptsMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php', 'OC\\AppFramework\\Middleware\\CompressionMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/CompressionMiddleware.php', @@ -806,6 +813,7 @@ return array( 'OC\\Command\\FileAccess' => $baseDir . '/lib/private/Command/FileAccess.php', 'OC\\Command\\QueueBus' => $baseDir . '/lib/private/Command/QueueBus.php', 'OC\\Comments\\Comment' => $baseDir . '/lib/private/Comments/Comment.php', + 'OC\\Comments\\EmojiHelper' => $baseDir . '/lib/private/Comments/EmojiHelper.php', 'OC\\Comments\\Manager' => $baseDir . '/lib/private/Comments/Manager.php', 'OC\\Comments\\ManagerFactory' => $baseDir . '/lib/private/Comments/ManagerFactory.php', 'OC\\Config' => $baseDir . '/lib/private/Config.php', @@ -1021,6 +1029,7 @@ return array( 'OC\\DB\\Connection' => $baseDir . '/lib/private/DB/Connection.php', 'OC\\DB\\ConnectionAdapter' => $baseDir . '/lib/private/DB/ConnectionAdapter.php', 'OC\\DB\\ConnectionFactory' => $baseDir . '/lib/private/DB/ConnectionFactory.php', + 'OC\\DB\\DbDataCollector' => $baseDir . '/lib/private/DB/DbDataCollector.php', 'OC\\DB\\Exceptions\\DbalException' => $baseDir . '/lib/private/DB/Exceptions/DbalException.php', 'OC\\DB\\MigrationException' => $baseDir . '/lib/private/DB/MigrationException.php', 'OC\\DB\\MigrationService' => $baseDir . '/lib/private/DB/MigrationService.php', @@ -1031,6 +1040,7 @@ return array( 'OC\\DB\\MySQLMigrator' => $baseDir . '/lib/private/DB/MySQLMigrator.php', 'OC\\DB\\MySqlTools' => $baseDir . '/lib/private/DB/MySqlTools.php', 'OC\\DB\\OCSqlitePlatform' => $baseDir . '/lib/private/DB/OCSqlitePlatform.php', + 'OC\\DB\\ObjectParameter' => $baseDir . '/lib/private/DB/ObjectParameter.php', 'OC\\DB\\OracleConnection' => $baseDir . '/lib/private/DB/OracleConnection.php', 'OC\\DB\\OracleMigrator' => $baseDir . '/lib/private/DB/OracleMigrator.php', 'OC\\DB\\PgSqlTools' => $baseDir . '/lib/private/DB/PgSqlTools.php', @@ -1138,6 +1148,7 @@ return array( 'OC\\Files\\Node\\HookConnector' => $baseDir . '/lib/private/Files/Node/HookConnector.php', 'OC\\Files\\Node\\LazyFolder' => $baseDir . '/lib/private/Files/Node/LazyFolder.php', 'OC\\Files\\Node\\LazyRoot' => $baseDir . '/lib/private/Files/Node/LazyRoot.php', + 'OC\\Files\\Node\\LazyUserFolder' => $baseDir . '/lib/private/Files/Node/LazyUserFolder.php', 'OC\\Files\\Node\\Node' => $baseDir . '/lib/private/Files/Node/Node.php', 'OC\\Files\\Node\\NonExistingFile' => $baseDir . '/lib/private/Files/Node/NonExistingFile.php', 'OC\\Files\\Node\\NonExistingFolder' => $baseDir . '/lib/private/Files/Node/NonExistingFolder.php', @@ -1198,6 +1209,7 @@ return array( 'OC\\Files\\Type\\Detection' => $baseDir . '/lib/private/Files/Type/Detection.php', 'OC\\Files\\Type\\Loader' => $baseDir . '/lib/private/Files/Type/Loader.php', 'OC\\Files\\Type\\TemplateManager' => $baseDir . '/lib/private/Files/Type/TemplateManager.php', + 'OC\\Files\\Utils\\PathHelper' => $baseDir . '/lib/private/Files/Utils/PathHelper.php', 'OC\\Files\\Utils\\Scanner' => $baseDir . '/lib/private/Files/Utils/Scanner.php', 'OC\\Files\\View' => $baseDir . '/lib/private/Files/View.php', 'OC\\ForbiddenException' => $baseDir . '/lib/private/ForbiddenException.php', @@ -1274,8 +1286,10 @@ return array( 'OC\\Memcache\\CASTrait' => $baseDir . '/lib/private/Memcache/CASTrait.php', 'OC\\Memcache\\Cache' => $baseDir . '/lib/private/Memcache/Cache.php', 'OC\\Memcache\\Factory' => $baseDir . '/lib/private/Memcache/Factory.php', + 'OC\\Memcache\\LoggerWrapperCache' => $baseDir . '/lib/private/Memcache/LoggerWrapperCache.php', 'OC\\Memcache\\Memcached' => $baseDir . '/lib/private/Memcache/Memcached.php', 'OC\\Memcache\\NullCache' => $baseDir . '/lib/private/Memcache/NullCache.php', + 'OC\\Memcache\\ProfilerWrapperCache' => $baseDir . '/lib/private/Memcache/ProfilerWrapperCache.php', 'OC\\Memcache\\Redis' => $baseDir . '/lib/private/Memcache/Redis.php', 'OC\\MemoryInfo' => $baseDir . '/lib/private/MemoryInfo.php', 'OC\\Migration\\BackgroundRepair' => $baseDir . '/lib/private/Migration/BackgroundRepair.php', @@ -1342,6 +1356,10 @@ return array( 'OC\\Profile\\Actions\\WebsiteAction' => $baseDir . '/lib/private/Profile/Actions/WebsiteAction.php', 'OC\\Profile\\ProfileManager' => $baseDir . '/lib/private/Profile/ProfileManager.php', 'OC\\Profile\\TProfileHelper' => $baseDir . '/lib/private/Profile/TProfileHelper.php', + 'OC\\Profiler\\FileProfilerStorage' => $baseDir . '/lib/private/Profiler/FileProfilerStorage.php', + 'OC\\Profiler\\Profile' => $baseDir . '/lib/private/Profiler/Profile.php', + 'OC\\Profiler\\Profiler' => $baseDir . '/lib/private/Profiler/Profiler.php', + 'OC\\Profiler\\RoutingDataCollector' => $baseDir . '/lib/private/Profiler/RoutingDataCollector.php', 'OC\\RedisFactory' => $baseDir . '/lib/private/RedisFactory.php', 'OC\\Remote\\Api\\ApiBase' => $baseDir . '/lib/private/Remote/Api/ApiBase.php', 'OC\\Remote\\Api\\ApiCollection' => $baseDir . '/lib/private/Remote/Api/ApiCollection.php', @@ -1379,6 +1397,7 @@ return array( 'OC\\Repair\\Owncloud\\CleanPreviewsBackgroundJob' => $baseDir . '/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php', 'OC\\Repair\\Owncloud\\DropAccountTermsTable' => $baseDir . '/lib/private/Repair/Owncloud/DropAccountTermsTable.php', 'OC\\Repair\\Owncloud\\InstallCoreBundle' => $baseDir . '/lib/private/Repair/Owncloud/InstallCoreBundle.php', + 'OC\\Repair\\Owncloud\\MigrateOauthTables' => $baseDir . '/lib/private/Repair/Owncloud/MigrateOauthTables.php', 'OC\\Repair\\Owncloud\\MoveAvatars' => $baseDir . '/lib/private/Repair/Owncloud/MoveAvatars.php', 'OC\\Repair\\Owncloud\\MoveAvatarsBackgroundJob' => $baseDir . '/lib/private/Repair/Owncloud/MoveAvatarsBackgroundJob.php', 'OC\\Repair\\Owncloud\\SaveAccountsTableData' => $baseDir . '/lib/private/Repair/Owncloud/SaveAccountsTableData.php', diff --git a/lib/composer/composer/autoload_namespaces.php b/lib/composer/composer/autoload_namespaces.php index 4a9c20beed0..f1ae7a0ffec 100644 --- a/lib/composer/composer/autoload_namespaces.php +++ b/lib/composer/composer/autoload_namespaces.php @@ -2,7 +2,7 @@ // autoload_namespaces.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname(dirname($vendorDir)); return array( diff --git a/lib/composer/composer/autoload_psr4.php b/lib/composer/composer/autoload_psr4.php index b641d9c6a03..74e48cf69ae 100644 --- a/lib/composer/composer/autoload_psr4.php +++ b/lib/composer/composer/autoload_psr4.php @@ -2,7 +2,7 @@ // autoload_psr4.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname(dirname($vendorDir)); return array( diff --git a/lib/composer/composer/autoload_real.php b/lib/composer/composer/autoload_real.php index a5748c7a891..4b1ab7678ec 100644 --- a/lib/composer/composer/autoload_real.php +++ b/lib/composer/composer/autoload_real.php @@ -23,30 +23,11 @@ class ComposerAutoloaderInit53792487c5a8370acc0b06b1a864ff4c } spl_autoload_register(array('ComposerAutoloaderInit53792487c5a8370acc0b06b1a864ff4c', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); spl_autoload_unregister(array('ComposerAutoloaderInit53792487c5a8370acc0b06b1a864ff4c', 'loadClassLoader')); - $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); - if ($useStaticLoader) { - require __DIR__ . '/autoload_static.php'; - - call_user_func(\Composer\Autoload\ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::getInitializer($loader)); - } else { - $map = require __DIR__ . '/autoload_namespaces.php'; - foreach ($map as $namespace => $path) { - $loader->set($namespace, $path); - } - - $map = require __DIR__ . '/autoload_psr4.php'; - foreach ($map as $namespace => $path) { - $loader->setPsr4($namespace, $path); - } - - $classMap = require __DIR__ . '/autoload_classmap.php'; - if ($classMap) { - $loader->addClassMap($classMap); - } - } + require __DIR__ . '/autoload_static.php'; + \Composer\Autoload\ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c::getInitializer($loader)(); $loader->register(true); diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index b1e9912f506..1d414814fc4 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -225,6 +225,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Dashboard\\RegisterWidgetEvent' => __DIR__ . '/../../..' . '/lib/public/Dashboard/RegisterWidgetEvent.php', 'OCP\\Dashboard\\Service\\IEventsService' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Service/IEventsService.php', 'OCP\\Dashboard\\Service\\IWidgetsService' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Service/IWidgetsService.php', + 'OCP\\DataCollector\\AbstractDataCollector' => __DIR__ . '/../../..' . '/lib/public/DataCollector/AbstractDataCollector.php', + 'OCP\\DataCollector\\IDataCollector' => __DIR__ . '/../../..' . '/lib/public/DataCollector/IDataCollector.php', 'OCP\\Defaults' => __DIR__ . '/../../..' . '/lib/public/Defaults.php', 'OCP\\Diagnostics\\IEvent' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IEvent.php', 'OCP\\Diagnostics\\IEventLogger' => __DIR__ . '/../../..' . '/lib/public/Diagnostics/IEventLogger.php', @@ -289,6 +291,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Files\\Events\\FileCacheUpdated' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FileCacheUpdated.php', 'OCP\\Files\\Events\\FileScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FileScannedEvent.php', 'OCP\\Files\\Events\\FolderScannedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/FolderScannedEvent.php', + 'OCP\\Files\\Events\\InvalidateMountCacheEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/InvalidateMountCacheEvent.php', 'OCP\\Files\\Events\\NodeAddedToCache' => __DIR__ . '/../../..' . '/lib/public/Files/Events/NodeAddedToCache.php', 'OCP\\Files\\Events\\NodeRemovedFromCache' => __DIR__ . '/../../..' . '/lib/public/Files/Events/NodeRemovedFromCache.php', 'OCP\\Files\\Events\\Node\\AbstractNodeEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/AbstractNodeEvent.php', @@ -441,6 +444,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\INavigationManager' => __DIR__ . '/../../..' . '/lib/public/INavigationManager.php', 'OCP\\IPreview' => __DIR__ . '/../../..' . '/lib/public/IPreview.php', 'OCP\\IRequest' => __DIR__ . '/../../..' . '/lib/public/IRequest.php', + 'OCP\\IRequestId' => __DIR__ . '/../../..' . '/lib/public/IRequestId.php', 'OCP\\ISearch' => __DIR__ . '/../../..' . '/lib/public/ISearch.php', 'OCP\\IServerContainer' => __DIR__ . '/../../..' . '/lib/public/IServerContainer.php', 'OCP\\ISession' => __DIR__ . '/../../..' . '/lib/public/ISession.php', @@ -494,6 +498,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Preview\\IVersionedPreviewFile' => __DIR__ . '/../../..' . '/lib/public/Preview/IVersionedPreviewFile.php', 'OCP\\Profile\\ILinkAction' => __DIR__ . '/../../..' . '/lib/public/Profile/ILinkAction.php', 'OCP\\Profile\\ParameterDoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/Profile/ParameterDoesNotExistException.php', + 'OCP\\Profiler\\IProfile' => __DIR__ . '/../../..' . '/lib/public/Profiler/IProfile.php', + 'OCP\\Profiler\\IProfiler' => __DIR__ . '/../../..' . '/lib/public/Profiler/IProfiler.php', 'OCP\\Remote\\Api\\IApiCollection' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IApiCollection.php', 'OCP\\Remote\\Api\\IApiFactory' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IApiFactory.php', 'OCP\\Remote\\Api\\ICapabilitiesApi' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/ICapabilitiesApi.php', @@ -664,6 +670,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\AppFramework\\Http\\Dispatcher' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Dispatcher.php', 'OC\\AppFramework\\Http\\Output' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Output.php', 'OC\\AppFramework\\Http\\Request' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Request.php', + 'OC\\AppFramework\\Http\\RequestId' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/RequestId.php', 'OC\\AppFramework\\Logger' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Logger.php', 'OC\\AppFramework\\Middleware\\AdditionalScriptsMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php', 'OC\\AppFramework\\Middleware\\CompressionMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/CompressionMiddleware.php', @@ -835,6 +842,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Command\\FileAccess' => __DIR__ . '/../../..' . '/lib/private/Command/FileAccess.php', 'OC\\Command\\QueueBus' => __DIR__ . '/../../..' . '/lib/private/Command/QueueBus.php', 'OC\\Comments\\Comment' => __DIR__ . '/../../..' . '/lib/private/Comments/Comment.php', + 'OC\\Comments\\EmojiHelper' => __DIR__ . '/../../..' . '/lib/private/Comments/EmojiHelper.php', 'OC\\Comments\\Manager' => __DIR__ . '/../../..' . '/lib/private/Comments/Manager.php', 'OC\\Comments\\ManagerFactory' => __DIR__ . '/../../..' . '/lib/private/Comments/ManagerFactory.php', 'OC\\Config' => __DIR__ . '/../../..' . '/lib/private/Config.php', @@ -1050,6 +1058,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\DB\\Connection' => __DIR__ . '/../../..' . '/lib/private/DB/Connection.php', 'OC\\DB\\ConnectionAdapter' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionAdapter.php', 'OC\\DB\\ConnectionFactory' => __DIR__ . '/../../..' . '/lib/private/DB/ConnectionFactory.php', + 'OC\\DB\\DbDataCollector' => __DIR__ . '/../../..' . '/lib/private/DB/DbDataCollector.php', 'OC\\DB\\Exceptions\\DbalException' => __DIR__ . '/../../..' . '/lib/private/DB/Exceptions/DbalException.php', 'OC\\DB\\MigrationException' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationException.php', 'OC\\DB\\MigrationService' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationService.php', @@ -1060,6 +1069,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\DB\\MySQLMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/MySQLMigrator.php', 'OC\\DB\\MySqlTools' => __DIR__ . '/../../..' . '/lib/private/DB/MySqlTools.php', 'OC\\DB\\OCSqlitePlatform' => __DIR__ . '/../../..' . '/lib/private/DB/OCSqlitePlatform.php', + 'OC\\DB\\ObjectParameter' => __DIR__ . '/../../..' . '/lib/private/DB/ObjectParameter.php', 'OC\\DB\\OracleConnection' => __DIR__ . '/../../..' . '/lib/private/DB/OracleConnection.php', 'OC\\DB\\OracleMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/OracleMigrator.php', 'OC\\DB\\PgSqlTools' => __DIR__ . '/../../..' . '/lib/private/DB/PgSqlTools.php', @@ -1167,6 +1177,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Files\\Node\\HookConnector' => __DIR__ . '/../../..' . '/lib/private/Files/Node/HookConnector.php', 'OC\\Files\\Node\\LazyFolder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/LazyFolder.php', 'OC\\Files\\Node\\LazyRoot' => __DIR__ . '/../../..' . '/lib/private/Files/Node/LazyRoot.php', + 'OC\\Files\\Node\\LazyUserFolder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/LazyUserFolder.php', 'OC\\Files\\Node\\Node' => __DIR__ . '/../../..' . '/lib/private/Files/Node/Node.php', 'OC\\Files\\Node\\NonExistingFile' => __DIR__ . '/../../..' . '/lib/private/Files/Node/NonExistingFile.php', 'OC\\Files\\Node\\NonExistingFolder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/NonExistingFolder.php', @@ -1227,6 +1238,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Files\\Type\\Detection' => __DIR__ . '/../../..' . '/lib/private/Files/Type/Detection.php', 'OC\\Files\\Type\\Loader' => __DIR__ . '/../../..' . '/lib/private/Files/Type/Loader.php', 'OC\\Files\\Type\\TemplateManager' => __DIR__ . '/../../..' . '/lib/private/Files/Type/TemplateManager.php', + 'OC\\Files\\Utils\\PathHelper' => __DIR__ . '/../../..' . '/lib/private/Files/Utils/PathHelper.php', 'OC\\Files\\Utils\\Scanner' => __DIR__ . '/../../..' . '/lib/private/Files/Utils/Scanner.php', 'OC\\Files\\View' => __DIR__ . '/../../..' . '/lib/private/Files/View.php', 'OC\\ForbiddenException' => __DIR__ . '/../../..' . '/lib/private/ForbiddenException.php', @@ -1303,8 +1315,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Memcache\\CASTrait' => __DIR__ . '/../../..' . '/lib/private/Memcache/CASTrait.php', 'OC\\Memcache\\Cache' => __DIR__ . '/../../..' . '/lib/private/Memcache/Cache.php', 'OC\\Memcache\\Factory' => __DIR__ . '/../../..' . '/lib/private/Memcache/Factory.php', + 'OC\\Memcache\\LoggerWrapperCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/LoggerWrapperCache.php', 'OC\\Memcache\\Memcached' => __DIR__ . '/../../..' . '/lib/private/Memcache/Memcached.php', 'OC\\Memcache\\NullCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/NullCache.php', + 'OC\\Memcache\\ProfilerWrapperCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/ProfilerWrapperCache.php', 'OC\\Memcache\\Redis' => __DIR__ . '/../../..' . '/lib/private/Memcache/Redis.php', 'OC\\MemoryInfo' => __DIR__ . '/../../..' . '/lib/private/MemoryInfo.php', 'OC\\Migration\\BackgroundRepair' => __DIR__ . '/../../..' . '/lib/private/Migration/BackgroundRepair.php', @@ -1371,6 +1385,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Profile\\Actions\\WebsiteAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/WebsiteAction.php', 'OC\\Profile\\ProfileManager' => __DIR__ . '/../../..' . '/lib/private/Profile/ProfileManager.php', 'OC\\Profile\\TProfileHelper' => __DIR__ . '/../../..' . '/lib/private/Profile/TProfileHelper.php', + 'OC\\Profiler\\FileProfilerStorage' => __DIR__ . '/../../..' . '/lib/private/Profiler/FileProfilerStorage.php', + 'OC\\Profiler\\Profile' => __DIR__ . '/../../..' . '/lib/private/Profiler/Profile.php', + 'OC\\Profiler\\Profiler' => __DIR__ . '/../../..' . '/lib/private/Profiler/Profiler.php', + 'OC\\Profiler\\RoutingDataCollector' => __DIR__ . '/../../..' . '/lib/private/Profiler/RoutingDataCollector.php', 'OC\\RedisFactory' => __DIR__ . '/../../..' . '/lib/private/RedisFactory.php', 'OC\\Remote\\Api\\ApiBase' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiBase.php', 'OC\\Remote\\Api\\ApiCollection' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiCollection.php', @@ -1408,6 +1426,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Repair\\Owncloud\\CleanPreviewsBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php', 'OC\\Repair\\Owncloud\\DropAccountTermsTable' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/DropAccountTermsTable.php', 'OC\\Repair\\Owncloud\\InstallCoreBundle' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/InstallCoreBundle.php', + 'OC\\Repair\\Owncloud\\MigrateOauthTables' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/MigrateOauthTables.php', 'OC\\Repair\\Owncloud\\MoveAvatars' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/MoveAvatars.php', 'OC\\Repair\\Owncloud\\MoveAvatarsBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/MoveAvatarsBackgroundJob.php', 'OC\\Repair\\Owncloud\\SaveAccountsTableData' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/SaveAccountsTableData.php', diff --git a/lib/l10n/ar.js b/lib/l10n/ar.js index 8a986850199..853182c5df0 100644 --- a/lib/l10n/ar.js +++ b/lib/l10n/ar.js @@ -118,6 +118,7 @@ OC.L10N.register( "This can usually be fixed by giving the webserver write access to the config directory" : "يمكن حل هذا عادة بإعطاء خادم الوب صلاحية الكتابة في مجلد config", "Can't create or write into the data directory %s" : "لا يمكن الإنشاء أو الكتابة في مجلد البيانات %s", "Can't read file" : "لا يمكن قراءة الملف", - "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "يمكن إصلاح هذا الخطا بإعطاء مخدّم الموقع صلاحيات التعديل على مجلد الإعدادات. أنظر %s" + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "يمكن إصلاح هذا الخطا بإعطاء مخدّم الموقع صلاحيات التعديل على مجلد الإعدادات. أنظر %s", + "Please upgrade your database version" : "فضلًا قم بتحديث إصدار قاعدة بياناتك" }, "nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;"); diff --git a/lib/l10n/ar.json b/lib/l10n/ar.json index 2b4bdc5bd6b..ce726d3b323 100644 --- a/lib/l10n/ar.json +++ b/lib/l10n/ar.json @@ -116,6 +116,7 @@ "This can usually be fixed by giving the webserver write access to the config directory" : "يمكن حل هذا عادة بإعطاء خادم الوب صلاحية الكتابة في مجلد config", "Can't create or write into the data directory %s" : "لا يمكن الإنشاء أو الكتابة في مجلد البيانات %s", "Can't read file" : "لا يمكن قراءة الملف", - "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "يمكن إصلاح هذا الخطا بإعطاء مخدّم الموقع صلاحيات التعديل على مجلد الإعدادات. أنظر %s" + "This can usually be fixed by giving the webserver write access to the config directory. See %s" : "يمكن إصلاح هذا الخطا بإعطاء مخدّم الموقع صلاحيات التعديل على مجلد الإعدادات. أنظر %s", + "Please upgrade your database version" : "فضلًا قم بتحديث إصدار قاعدة بياناتك" },"pluralForm" :"nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;" }
\ No newline at end of file diff --git a/lib/l10n/sv.js b/lib/l10n/sv.js index ef690b97a68..d698b06e082 100644 --- a/lib/l10n/sv.js +++ b/lib/l10n/sv.js @@ -6,7 +6,7 @@ OC.L10N.register( "See %s" : "Se %s", "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Filerna i appen %1$s ersattes inte korrekt. Kontrollera att det är en version som är kompatibel med servern.", "Sample configuration detected" : "Exempel-konfiguration detekterad", - "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det har upptäckts att provkonfigurationen har kopierats. Detta kan bryta din installation och stöds inte. Vänligen läs dokumentationen innan du utför ändringar på config.php", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det har upptäckts att provkonfigurationen har kopierats. Detta kan bryta din installation och stöds inte. Läs dokumentationen innan du utför ändringar på config.php", "%s email verification" : "%s e-postverifikation", "Email verification" : "E-postverifikation", "Click the following button to confirm your email." : "Klicka på följande knapp för att verifiera din e-postadress.", @@ -120,9 +120,9 @@ OC.L10N.register( "Oracle username and/or password not valid" : "Oracle-användarnamnet och/eller lösenordet är felaktigt", "PostgreSQL username and/or password not valid" : "PostgreSQL-användarnamnet och/eller lösenordet är felaktigt", "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X stöds inte och %s kommer inte att fungera korrekt på denna plattform. Använd på egen risk!", - "For the best results, please consider using a GNU/Linux server instead." : "För bästa resultat, vänligen överväg att använda en GNU/Linux-server istället.", + "For the best results, please consider using a GNU/Linux server instead." : "För bästa resultat, överväg att använda en GNU/Linux-server istället.", "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Det verkar som om denna %s instans körs på en 32-bitars PHP miljö och open_basedir har konfigurerats i php.ini. Detta kommer att leda till problem med filer över 4 GB och är verkligen inte rekommenderat!", - "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Vänligen ta bort open_basedir-inställningen i din php.ini eller växla till 64-bitars PHP.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Ta bort open_basedir-inställningen i din php.ini eller växla till 64-bitars PHP.", "Set an admin username." : "Ange ett användarnamn för administratören.", "Set an admin password." : "Ange ett administratörslösenord.", "Cannot create or write into the data directory %s" : "Kan inte skapa eller skriva till data-katalogen %s", @@ -205,15 +205,15 @@ OC.L10N.register( "Login canceled by app" : "Inloggningen avbruten av appen", "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Appen \"%1$s\" kan inte installeras eftersom följande beroenden inte är uppfyllda: %2$s", "a safe home for all your data" : "ett säkert hem för all din data", - "File is currently busy, please try again later" : "Filen är för tillfället upptagen, vänligen försök igen senare", + "File is currently busy, please try again later" : "Filen är för tillfället upptagen, försök igen senare", "Cannot read file" : "Kan inte läsa fil", "Application is not enabled" : "Applikationen är inte aktiverad", "Authentication error" : "Fel vid autentisering", - "Token expired. Please reload page." : "Token har löpt ut. Vänligen uppdatera sidan.", + "Token expired. Please reload page." : "Token har löpt ut. Uppdatera sidan.", "No database drivers (sqlite, mysql, or postgresql) installed." : "Inga databasdrivrutiner (sqlite, mysql, eller postgresql) installerade.", "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Eller, om du föredrar att behålla config.php skrivskyddad, sätt alternativet \"config_is_read_only\" till true i den. Se %s", "PHP module %s not installed." : "PHP-modulen %s är inte installerad.", - "Please ask your server administrator to install the module." : "Vänligen be serveradministratören att installera modulen.", + "Please ask your server administrator to install the module." : "Be serveradministratören att installera modulen.", "PHP setting \"%s\" is not set to \"%s\"." : "PHP-inställning \"%s\" är inte inställd på \"%s\".", "Adjusting this setting in php.ini will make Nextcloud run again" : "Att ändra denna inställning i php.ini kommer göra så att Nextcloud fungerar igen", "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 är det minsta som krävs. För närvarande är %s installerat.", @@ -221,8 +221,8 @@ OC.L10N.register( "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP är tydligen inställt för att tömma \"inline doc blocks\". Detta kommer att göra flera kärnprogram otillgängliga.", "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Detta orsakas troligtvis av en cache/accelerator som t ex Zend OPchache eller eAccelerator.", "PHP modules have been installed, but they are still listed as missing?" : "PHP-moduler har installerats, men de listas fortfarande som saknade?", - "Please ask your server administrator to restart the web server." : "Vänligen be din serveradministratör att starta om webbservern.", - "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Vänligen ändra behörigheterna till 0770 så att katalogen inte kan listas av andra användare.", + "Please ask your server administrator to restart the web server." : "Be din serveradministratör att starta om webbservern.", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Ändra behörigheterna till 0770 så att katalogen inte kan listas av andra användare.", "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Säkerställ att du har filen \".ocdata\" i huvudkatalogen för din data.", "Action \"%s\" not supported or implemented." : "Åtgärd \"%s\" stöds inte eller är inte implementerad.", "Authentication failed, wrong token or provider ID given" : "Autentisering misslyckades, felaktig token eller leverantörs-ID", @@ -252,11 +252,11 @@ OC.L10N.register( "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Detta kan vanligtvis åtgärda genom att ge webbservern skrivåtkomst till rotkatalogen. Se %s", "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Rättigheter kan vanligtvis fixas genom att ge webbservern skrivåtkomst till rotkatalogen. Se %s.", "Setting locale to %s failed" : "Sätta locale till %s misslyckades", - "Please install one of these locales on your system and restart your webserver." : "Vänligen Installera en av dessa språk på ditt system och starta om webbservern.", + "Please install one of these locales on your system and restart your webserver." : "Installera en av dessa språk på ditt system och starta om webbservern.", "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload är satt till \"%s\" istället för det förväntade värdet \"0\"", "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini" : "För att åtgärda detta problem sätt värdet <code> mbstring.func_overload till </ code> <code> 0 </ code> i din php.ini", "PostgreSQL >= 9 required" : "PostgreSQL >= 9 krävs", - "Please upgrade your database version" : "Vänligen uppgradera din databasversion", + "Please upgrade your database version" : "Uppgradera din databasversion", "Your data directory is readable by other users" : "Din datamapp är läsbar av andra användare", "Your data directory must be an absolute path" : "Du måste specificera en korrekt sökväg till datamappen", "Check the value of \"datadirectory\" in your configuration" : "Kontrollera värdet av \"datakatalog\" i din konfiguration", diff --git a/lib/l10n/sv.json b/lib/l10n/sv.json index 444d8d908bb..74526fac761 100644 --- a/lib/l10n/sv.json +++ b/lib/l10n/sv.json @@ -4,7 +4,7 @@ "See %s" : "Se %s", "The files of the app %1$s were not replaced correctly. Make sure it is a version compatible with the server." : "Filerna i appen %1$s ersattes inte korrekt. Kontrollera att det är en version som är kompatibel med servern.", "Sample configuration detected" : "Exempel-konfiguration detekterad", - "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det har upptäckts att provkonfigurationen har kopierats. Detta kan bryta din installation och stöds inte. Vänligen läs dokumentationen innan du utför ändringar på config.php", + "It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php" : "Det har upptäckts att provkonfigurationen har kopierats. Detta kan bryta din installation och stöds inte. Läs dokumentationen innan du utför ändringar på config.php", "%s email verification" : "%s e-postverifikation", "Email verification" : "E-postverifikation", "Click the following button to confirm your email." : "Klicka på följande knapp för att verifiera din e-postadress.", @@ -118,9 +118,9 @@ "Oracle username and/or password not valid" : "Oracle-användarnamnet och/eller lösenordet är felaktigt", "PostgreSQL username and/or password not valid" : "PostgreSQL-användarnamnet och/eller lösenordet är felaktigt", "Mac OS X is not supported and %s will not work properly on this platform. Use it at your own risk! " : "Mac OS X stöds inte och %s kommer inte att fungera korrekt på denna plattform. Använd på egen risk!", - "For the best results, please consider using a GNU/Linux server instead." : "För bästa resultat, vänligen överväg att använda en GNU/Linux-server istället.", + "For the best results, please consider using a GNU/Linux server instead." : "För bästa resultat, överväg att använda en GNU/Linux-server istället.", "It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. This will lead to problems with files over 4 GB and is highly discouraged." : "Det verkar som om denna %s instans körs på en 32-bitars PHP miljö och open_basedir har konfigurerats i php.ini. Detta kommer att leda till problem med filer över 4 GB och är verkligen inte rekommenderat!", - "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Vänligen ta bort open_basedir-inställningen i din php.ini eller växla till 64-bitars PHP.", + "Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP." : "Ta bort open_basedir-inställningen i din php.ini eller växla till 64-bitars PHP.", "Set an admin username." : "Ange ett användarnamn för administratören.", "Set an admin password." : "Ange ett administratörslösenord.", "Cannot create or write into the data directory %s" : "Kan inte skapa eller skriva till data-katalogen %s", @@ -203,15 +203,15 @@ "Login canceled by app" : "Inloggningen avbruten av appen", "App \"%1$s\" cannot be installed because the following dependencies are not fulfilled: %2$s" : "Appen \"%1$s\" kan inte installeras eftersom följande beroenden inte är uppfyllda: %2$s", "a safe home for all your data" : "ett säkert hem för all din data", - "File is currently busy, please try again later" : "Filen är för tillfället upptagen, vänligen försök igen senare", + "File is currently busy, please try again later" : "Filen är för tillfället upptagen, försök igen senare", "Cannot read file" : "Kan inte läsa fil", "Application is not enabled" : "Applikationen är inte aktiverad", "Authentication error" : "Fel vid autentisering", - "Token expired. Please reload page." : "Token har löpt ut. Vänligen uppdatera sidan.", + "Token expired. Please reload page." : "Token har löpt ut. Uppdatera sidan.", "No database drivers (sqlite, mysql, or postgresql) installed." : "Inga databasdrivrutiner (sqlite, mysql, eller postgresql) installerade.", "Or, if you prefer to keep config.php file read only, set the option \"config_is_read_only\" to true in it. See %s" : "Eller, om du föredrar att behålla config.php skrivskyddad, sätt alternativet \"config_is_read_only\" till true i den. Se %s", "PHP module %s not installed." : "PHP-modulen %s är inte installerad.", - "Please ask your server administrator to install the module." : "Vänligen be serveradministratören att installera modulen.", + "Please ask your server administrator to install the module." : "Be serveradministratören att installera modulen.", "PHP setting \"%s\" is not set to \"%s\"." : "PHP-inställning \"%s\" är inte inställd på \"%s\".", "Adjusting this setting in php.ini will make Nextcloud run again" : "Att ändra denna inställning i php.ini kommer göra så att Nextcloud fungerar igen", "libxml2 2.7.0 is at least required. Currently %s is installed." : "libxml2 2.7.0 är det minsta som krävs. För närvarande är %s installerat.", @@ -219,8 +219,8 @@ "PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible." : "PHP är tydligen inställt för att tömma \"inline doc blocks\". Detta kommer att göra flera kärnprogram otillgängliga.", "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "Detta orsakas troligtvis av en cache/accelerator som t ex Zend OPchache eller eAccelerator.", "PHP modules have been installed, but they are still listed as missing?" : "PHP-moduler har installerats, men de listas fortfarande som saknade?", - "Please ask your server administrator to restart the web server." : "Vänligen be din serveradministratör att starta om webbservern.", - "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Vänligen ändra behörigheterna till 0770 så att katalogen inte kan listas av andra användare.", + "Please ask your server administrator to restart the web server." : "Be din serveradministratör att starta om webbservern.", + "Please change the permissions to 0770 so that the directory cannot be listed by other users." : "Ändra behörigheterna till 0770 så att katalogen inte kan listas av andra användare.", "Ensure there is a file called \".ocdata\" in the root of the data directory." : "Säkerställ att du har filen \".ocdata\" i huvudkatalogen för din data.", "Action \"%s\" not supported or implemented." : "Åtgärd \"%s\" stöds inte eller är inte implementerad.", "Authentication failed, wrong token or provider ID given" : "Autentisering misslyckades, felaktig token eller leverantörs-ID", @@ -250,11 +250,11 @@ "This can usually be fixed by giving the webserver write access to the root directory. See %s" : "Detta kan vanligtvis åtgärda genom att ge webbservern skrivåtkomst till rotkatalogen. Se %s", "Permissions can usually be fixed by giving the webserver write access to the root directory. See %s." : "Rättigheter kan vanligtvis fixas genom att ge webbservern skrivåtkomst till rotkatalogen. Se %s.", "Setting locale to %s failed" : "Sätta locale till %s misslyckades", - "Please install one of these locales on your system and restart your webserver." : "Vänligen Installera en av dessa språk på ditt system och starta om webbservern.", + "Please install one of these locales on your system and restart your webserver." : "Installera en av dessa språk på ditt system och starta om webbservern.", "mbstring.func_overload is set to \"%s\" instead of the expected value \"0\"" : "mbstring.func_overload är satt till \"%s\" istället för det förväntade värdet \"0\"", "To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini" : "För att åtgärda detta problem sätt värdet <code> mbstring.func_overload till </ code> <code> 0 </ code> i din php.ini", "PostgreSQL >= 9 required" : "PostgreSQL >= 9 krävs", - "Please upgrade your database version" : "Vänligen uppgradera din databasversion", + "Please upgrade your database version" : "Uppgradera din databasversion", "Your data directory is readable by other users" : "Din datamapp är läsbar av andra användare", "Your data directory must be an absolute path" : "Du måste specificera en korrekt sökväg till datamappen", "Check the value of \"datadirectory\" in your configuration" : "Kontrollera värdet av \"datakatalog\" i din konfiguration", diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index 127adc9ef38..5792ba1dc5d 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -41,6 +41,7 @@ use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberUtil; use OC\Profile\TProfileHelper; +use OC\Cache\CappedMemoryCache; use OCA\Settings\BackgroundJobs\VerifyUserData; use OCP\Accounts\IAccount; use OCP\Accounts\IAccountManager; @@ -116,6 +117,7 @@ class AccountManager implements IAccountManager { private $crypto; /** @var IFactory */ private $l10nfactory; + private CappedMemoryCache $internalCache; public function __construct( IDBConnection $connection, @@ -142,6 +144,7 @@ class AccountManager implements IAccountManager { $this->crypto = $crypto; // DIing IL10N results in a dependency loop $this->l10nfactory = $factory; + $this->internalCache = new CappedMemoryCache(); } /** @@ -763,7 +766,12 @@ class AccountManager implements IAccountManager { } public function getAccount(IUser $user): IAccount { - return $this->parseAccountData($user, $this->getUser($user)); + if ($this->internalCache->hasKey($user->getUID())) { + return $this->internalCache->get($user->getUID()); + } + $account = $this->parseAccountData($user, $this->getUser($user)); + $this->internalCache->set($user->getUID(), $account); + return $account; } public function updateAccount(IAccount $account): void { @@ -813,5 +821,6 @@ class AccountManager implements IAccountManager { } $this->updateUser($account->getUser(), $data, true); + $this->internalCache->set($account->getUser()->getUID(), $account); } } diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php index 6c2f905afa5..feebb32d5bc 100644 --- a/lib/private/AppFramework/App.php +++ b/lib/private/AppFramework/App.php @@ -34,11 +34,16 @@ 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; use OCP\AppFramework\Http; use OCP\AppFramework\Http\ICallbackResponse; use OCP\AppFramework\Http\IOutput; -use OCP\AppFramework\QueryException; +use OCP\Diagnostics\IEventLogger; use OCP\HintException; +use OCP\IConfig; use OCP\IRequest; /** @@ -114,20 +119,30 @@ class App { * @throws HintException */ 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); + // 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()) { + \OC::$server->get(IEventLogger::class)->activate(); + $profiler->add(new RoutingDataCollector($container['AppName'], $controllerName, $methodName)); + } + if (!is_null($urlParams)) { /** @var Request $request */ - $request = $container->query(IRequest::class); + $request = $container->get(IRequest::class); $request->setUrlParameters($urlParams); } elseif (isset($container['urlParams']) && !is_null($container['urlParams'])) { /** @var Request $request */ - $request = $container->query(IRequest::class); + $request = $container->get(IRequest::class); $request->setUrlParameters($container['urlParams']); } $appName = $container['AppName']; // first try $controllerName then go for \OCA\AppName\Controller\$controllerName try { - $controller = $container->query($controllerName); + $controller = $container->get($controllerName); } catch (QueryException $e) { if (strpos($controllerName, '\\Controller\\') !== false) { // This is from a global registered app route that is not enabled. @@ -158,6 +173,16 @@ class App { $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); + $io->setHeader('X-Debug-Token:' . $profile->getToken()); + $io->setHeader('Server-Timing: token;desc="' . $profile->getToken() . '"'); + } + if (!is_null($httpHeaders)) { $io->setHeader($httpHeaders); } diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 21af2bc46f4..f896b825f2d 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -48,8 +48,8 @@ use OC\Security\CSRF\CsrfTokenManager; use OC\Security\TrustedDomainHelper; use OCP\IConfig; use OCP\IRequest; +use OCP\IRequestId; use OCP\Security\ICrypto; -use OCP\Security\ISecureRandom; /** * Class for accessing variables in the request. @@ -92,12 +92,10 @@ class Request implements \ArrayAccess, \Countable, IRequest { 'method', 'requesttoken', ]; - /** @var ISecureRandom */ - protected $secureRandom; + /** @var RequestId */ + protected $requestId; /** @var IConfig */ protected $config; - /** @var string */ - protected $requestId = ''; /** @var ICrypto */ protected $crypto; /** @var CsrfTokenManager|null */ @@ -117,20 +115,20 @@ class Request implements \ArrayAccess, \Countable, IRequest { * - array 'cookies' the $_COOKIE array * - string 'method' the request method (GET, POST etc) * - string|false 'requesttoken' the requesttoken or false when not available - * @param ISecureRandom $secureRandom + * @param IRequestId $requestId * @param IConfig $config * @param CsrfTokenManager|null $csrfTokenManager * @param string $stream * @see https://www.php.net/manual/en/reserved.variables.php */ public function __construct(array $vars, - ISecureRandom $secureRandom, + IRequestId $requestId, IConfig $config, CsrfTokenManager $csrfTokenManager = null, string $stream = 'php://input') { $this->inputStream = $stream; $this->items['params'] = []; - $this->secureRandom = $secureRandom; + $this->requestId = $requestId; $this->config = $config; $this->csrfTokenManager = $csrfTokenManager; @@ -571,16 +569,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { * @return string */ public function getId(): string { - if (isset($this->server['UNIQUE_ID'])) { - return $this->server['UNIQUE_ID']; - } - - if (empty($this->requestId)) { - $validChars = ISecureRandom::CHAR_ALPHANUMERIC; - $this->requestId = $this->secureRandom->generate(20, $validChars); - } - - return $this->requestId; + return $this->requestId->getId(); } /** diff --git a/lib/private/AppFramework/Http/RequestId.php b/lib/private/AppFramework/Http/RequestId.php new file mode 100644 index 00000000000..70032873a75 --- /dev/null +++ b/lib/private/AppFramework/Http/RequestId.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022, Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OC\AppFramework\Http; + +use OCP\IRequestId; +use OCP\Security\ISecureRandom; + +class RequestId implements IRequestId { + protected ISecureRandom $secureRandom; + protected string $requestId; + + public function __construct(string $uniqueId, + ISecureRandom $secureRandom) { + $this->requestId = $uniqueId; + $this->secureRandom = $secureRandom; + } + + /** + * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging + * If `mod_unique_id` is installed this value will be taken. + * @return string + */ + public function getId(): string { + if (empty($this->requestId)) { + $validChars = ISecureRandom::CHAR_ALPHANUMERIC; + $this->requestId = $this->secureRandom->generate(20, $validChars); + } + + return $this->requestId; + } +} diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index fffeffd4feb..e0f36231b68 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -264,6 +264,9 @@ class SecurityMiddleware extends Middleware { if ($usernamePrefill !== '') { $params['user'] = $usernamePrefill; } + if ($this->request->getParam('direct')) { + $params['direct'] = 1; + } $url = $this->urlGenerator->linkToRoute('core.login.showLoginForm', $params); $response = new RedirectResponse($url); } else { diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 598c66b6aba..429382aa223 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -38,6 +38,7 @@ use Psr\Container\ContainerInterface; use ReflectionClass; use ReflectionException; use ReflectionParameter; +use ReflectionNamedType; use function class_exists; /** @@ -78,12 +79,13 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { $resolveName = $parameter->getName(); // try to find out if it is a class or a simple parameter - if ($parameterType !== null && !$parameterType->isBuiltin()) { + if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { $resolveName = $parameterType->getName(); } try { - $builtIn = $parameter->hasType() && $parameter->getType()->isBuiltin(); + $builtIn = $parameter->hasType() && ($parameter->getType() instanceof ReflectionNamedType) + && $parameter->getType()->isBuiltin(); return $this->query($resolveName, !$builtIn); } catch (QueryException $e) { // Service not found, use the default value when available @@ -91,7 +93,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer { return $parameter->getDefaultValue(); } - if ($parameterType !== null && !$parameterType->isBuiltin()) { + if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) { $resolveName = $parameter->getName(); try { return $this->query($resolveName); diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index e604ac715c2..0a145bfd7e6 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -44,7 +44,7 @@ interface IProvider { * @param string $uid * @param string $loginName * @param string|null $password - * @param string $name + * @param string $name Name will be trimmed to 120 chars when longer * @param int $type token type * @param int $remember whether the session token should be used for remember-me * @return IToken diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php index 0a7a821e23e..f8a0fb11c52 100644 --- a/lib/private/Authentication/Token/Manager.php +++ b/lib/private/Authentication/Token/Manager.php @@ -49,7 +49,7 @@ class Manager implements IProvider { * @param string $uid * @param string $loginName * @param string|null $password - * @param string $name + * @param string $name Name will be trimmed to 120 chars when longer * @param int $type token type * @param int $remember whether the session token should be used for remember-me * @return IToken @@ -61,6 +61,10 @@ class Manager implements IProvider { string $name, int $type = IToken::TEMPORARY_TOKEN, int $remember = IToken::DO_NOT_REMEMBER): IToken { + if (mb_strlen($name) > 128) { + $name = mb_substr($name, 0, 120) . '…'; + } + try { return $this->publicKeyTokenProvider->generateToken( $token, diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index d2ee47cf380..26337029d77 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -84,6 +84,10 @@ class PublicKeyTokenProvider implements IProvider { string $name, int $type = IToken::TEMPORARY_TOKEN, int $remember = IToken::DO_NOT_REMEMBER): IToken { + if (mb_strlen($name) > 128) { + throw new InvalidTokenException('The given name is too long'); + } + $dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember); $this->mapper->insert($dbToken); diff --git a/lib/private/Cache/CappedMemoryCache.php b/lib/private/Cache/CappedMemoryCache.php index 9260bf1f6b3..0a3300435eb 100644 --- a/lib/private/Cache/CappedMemoryCache.php +++ b/lib/private/Cache/CappedMemoryCache.php @@ -115,4 +115,8 @@ class CappedMemoryCache implements ICache, \ArrayAccess { $this->remove($key); } } + + public static function isAvailable(): bool { + return true; + } } diff --git a/lib/private/Cache/File.php b/lib/private/Cache/File.php index 0ecd894d2d2..a96a7cd9c0b 100644 --- a/lib/private/Cache/File.php +++ b/lib/private/Cache/File.php @@ -203,4 +203,8 @@ class File implements ICache { } } } + + public static function isAvailable(): bool { + return true; + } } diff --git a/lib/private/Comments/EmojiHelper.php b/lib/private/Comments/EmojiHelper.php new file mode 100644 index 00000000000..a75919edbc9 --- /dev/null +++ b/lib/private/Comments/EmojiHelper.php @@ -0,0 +1,101 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020, Georg Ehrke + * + * @author Georg Ehrke <oc.list@georgehrke.com> + * @author Joas Schilling <coding@schilljs.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OC\Comments; + +use OCP\IDBConnection; + +/** + * Copied OCA\UserStatus\Service\EmojiService + * Needs to be unified later + */ +class EmojiHelper { + + /** @var IDBConnection */ + private $db; + + /** + * EmojiService constructor. + * + * @param IDBConnection $db + */ + public function __construct(IDBConnection $db) { + $this->db = $db; + } + + /** + * @return bool + */ + public function doesPlatformSupportEmoji(): bool { + return $this->db->supports4ByteText() && + \class_exists(\IntlBreakIterator::class); + } + + /** + * @param string $emoji + * @return bool + */ + public function isValidEmoji(string $emoji): bool { + $intlBreakIterator = \IntlBreakIterator::createCharacterInstance(); + $intlBreakIterator->setText($emoji); + + $characterCount = 0; + while ($intlBreakIterator->next() !== \IntlBreakIterator::DONE) { + $characterCount++; + } + + if ($characterCount !== 1) { + return false; + } + + $codePointIterator = \IntlBreakIterator::createCodePointInstance(); + $codePointIterator->setText($emoji); + + foreach ($codePointIterator->getPartsIterator() as $codePoint) { + $codePointType = \IntlChar::charType($codePoint); + + // If the current code-point is an emoji or a modifier (like a skin-tone) + // just continue and check the next character + if ($codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_SYMBOL || + $codePointType === \IntlChar::CHAR_CATEGORY_MODIFIER_LETTER || + $codePointType === \IntlChar::CHAR_CATEGORY_OTHER_SYMBOL || + $codePointType === \IntlChar::CHAR_CATEGORY_GENERAL_OTHER_TYPES) { + continue; + } + + // If it's neither a modifier nor an emoji, we only allow + // a zero-width-joiner or a variation selector 16 + $codePointValue = \IntlChar::ord($codePoint); + if ($codePointValue === 8205 || $codePointValue === 65039) { + continue; + } + + return false; + } + + return true; + } +} diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 384187accf3..123e4f6988d 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -59,6 +59,9 @@ class Manager implements ICommentsManager { /** @var ITimeFactory */ protected $timeFactory; + /** @var EmojiHelper */ + protected $emojiHelper; + /** @var IInitialStateService */ protected $initialStateService; @@ -78,11 +81,13 @@ class Manager implements ICommentsManager { LoggerInterface $logger, IConfig $config, ITimeFactory $timeFactory, + EmojiHelper $emojiHelper, IInitialStateService $initialStateService) { $this->dbConn = $dbConn; $this->logger = $logger; $this->config = $config; $this->timeFactory = $timeFactory; + $this->emojiHelper = $emojiHelper; $this->initialStateService = $initialStateService; } @@ -148,8 +153,9 @@ class Manager implements ICommentsManager { throw new \UnexpectedValueException('Actor, Object and Verb information must be provided for saving'); } - if ($comment->getVerb() === 'reaction' && mb_strlen($comment->getMessage()) > 2) { - throw new \UnexpectedValueException('Reactions cannot be longer than 2 chars (emoji with skin tone have two chars)'); + if ($comment->getVerb() === 'reaction' && !$this->emojiHelper->isValidEmoji($comment->getMessage())) { + // 4 characters: laptop + person + gender + skin color => "🧑🏽💻" is a single emoji from the picker + throw new \UnexpectedValueException('Reactions can only be a single emoji'); } if ($comment->getId() === '') { diff --git a/lib/private/Contacts/ContactsMenu/ContactsStore.php b/lib/private/Contacts/ContactsMenu/ContactsStore.php index 5b7a942a244..0ac388ce00a 100644 --- a/lib/private/Contacts/ContactsMenu/ContactsStore.php +++ b/lib/private/Contacts/ContactsMenu/ContactsStore.php @@ -107,7 +107,7 @@ class ContactsStore implements IContactsStore { } $allContacts = $this->contactsManager->search( - $filter ?: '', + $filter ?? '', [ 'FN', 'EMAIL' @@ -146,7 +146,7 @@ class ContactsStore implements IContactsStore { * * @param IUser $self * @param Entry[] $entries - * @param string $filter + * @param string|null $filter * @return Entry[] the filtered contacts */ private function filterContacts( diff --git a/lib/private/Contacts/ContactsMenu/Manager.php b/lib/private/Contacts/ContactsMenu/Manager.php index cea67735da5..73a5a475d85 100644 --- a/lib/private/Contacts/ContactsMenu/Manager.php +++ b/lib/private/Contacts/ContactsMenu/Manager.php @@ -59,14 +59,14 @@ class Manager { /** * @param IUser $user - * @param string $filter + * @param string|null $filter * @return array */ public function getEntries(IUser $user, $filter) { $maxAutocompleteResults = max(0, $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT)); $minSearchStringLength = $this->config->getSystemValueInt('sharing.minSearchStringLength', 0); $topEntries = []; - if (strlen($filter) >= $minSearchStringLength) { + if (strlen($filter ?? '') >= $minSearchStringLength) { $entries = $this->store->getContacts($user, $filter, $maxAutocompleteResults); $sortedEntries = $this->sortEntries($entries); diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index e32c530c19e..2e38b1ddf5e 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -42,6 +42,7 @@ use Doctrine\DBAL\Driver; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\ConstraintViolationException; use Doctrine\DBAL\Exception\NotNullConstraintViolationException; +use Doctrine\DBAL\Logging\DebugStack; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSQL94Platform; @@ -49,11 +50,13 @@ use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Statement; -use OC\DB\QueryBuilder\QueryBuilder; -use OC\SystemConfig; use OCP\DB\QueryBuilder\IQueryBuilder; -use OCP\ILogger; +use OCP\IRequestId; use OCP\PreConditionNotMetException; +use OC\DB\QueryBuilder\QueryBuilder; +use OC\SystemConfig; +use Psr\Log\LoggerInterface; +use OCP\Profiler\IProfiler; class Connection extends \Doctrine\DBAL\Connection { /** @var string */ @@ -65,8 +68,7 @@ class Connection extends \Doctrine\DBAL\Connection { /** @var SystemConfig */ private $systemConfig; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; protected $lockedTable = null; @@ -76,6 +78,47 @@ class Connection extends \Doctrine\DBAL\Connection { /** @var int */ protected $queriesExecuted = 0; + /** @var DbDataCollector|null */ + protected $dbDataCollector = null; + + /** + * Initializes a new instance of the Connection class. + * + * @throws \Exception + */ + public function __construct( + array $params, + Driver $driver, + ?Configuration $config = null, + ?EventManager $eventManager = null + ) { + if (!isset($params['adapter'])) { + throw new \Exception('adapter not set'); + } + if (!isset($params['tablePrefix'])) { + throw new \Exception('tablePrefix not set'); + } + /** + * @psalm-suppress InternalMethod + */ + parent::__construct($params, $driver, $config, $eventManager); + $this->adapter = new $params['adapter']($this); + $this->tablePrefix = $params['tablePrefix']; + + $this->systemConfig = \OC::$server->getSystemConfig(); + $this->logger = \OC::$server->get(LoggerInterface::class); + + /** @var \OCP\Profiler\IProfiler */ + $profiler = \OC::$server->get(IProfiler::class); + if ($profiler->isEnabled()) { + $this->dbDataCollector = new DbDataCollector($this); + $profiler->add($this->dbDataCollector); + $debugStack = new DebugStack(); + $this->dbDataCollector->setDebugStack($debugStack); + $this->_config->setSQLLogger($debugStack); + } + } + /** * @throws Exception */ @@ -125,7 +168,7 @@ class Connection extends \Doctrine\DBAL\Connection { */ public function createQueryBuilder() { $backtrace = $this->getCallerBacktrace(); - \OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]); + $this->logger->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]); $this->queriesBuilt++; return parent::createQueryBuilder(); } @@ -138,7 +181,7 @@ class Connection extends \Doctrine\DBAL\Connection { */ public function getExpressionBuilder() { $backtrace = $this->getCallerBacktrace(); - \OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]); + $this->logger->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]); $this->queriesBuilt++; return parent::getExpressionBuilder(); } @@ -168,34 +211,6 @@ class Connection extends \Doctrine\DBAL\Connection { } /** - * Initializes a new instance of the Connection class. - * - * @param array $params The connection parameters. - * @param \Doctrine\DBAL\Driver $driver - * @param \Doctrine\DBAL\Configuration $config - * @param \Doctrine\Common\EventManager $eventManager - * @throws \Exception - */ - public function __construct(array $params, Driver $driver, Configuration $config = null, - EventManager $eventManager = null) { - if (!isset($params['adapter'])) { - throw new \Exception('adapter not set'); - } - if (!isset($params['tablePrefix'])) { - throw new \Exception('tablePrefix not set'); - } - /** - * @psalm-suppress InternalMethod - */ - parent::__construct($params, $driver, $config, $eventManager); - $this->adapter = new $params['adapter']($this); - $this->tablePrefix = $params['tablePrefix']; - - $this->systemConfig = \OC::$server->getSystemConfig(); - $this->logger = \OC::$server->getLogger(); - } - - /** * Prepares an SQL statement. * * @param string $statement The SQL statement to prepare. @@ -281,11 +296,16 @@ class Connection extends \Doctrine\DBAL\Connection { } protected function logQueryToFile(string $sql): void { - $logFile = $this->systemConfig->getValue('query_log_file', ''); + $logFile = $this->systemConfig->getValue('query_log_file'); if ($logFile !== '' && is_writable(dirname($logFile)) && (!file_exists($logFile) || is_writable($logFile))) { + $prefix = ''; + if ($this->systemConfig->getValue('query_log_file_requestid') === 'yes') { + $prefix .= \OC::$server->get(IRequestId::class)->getId() . "\t"; + } + file_put_contents( $this->systemConfig->getValue('query_log_file', ''), - $sql . "\n", + $prefix . $sql . "\n", FILE_APPEND ); } diff --git a/lib/private/DB/DbDataCollector.php b/lib/private/DB/DbDataCollector.php new file mode 100644 index 00000000000..d708955b10e --- /dev/null +++ b/lib/private/DB/DbDataCollector.php @@ -0,0 +1,154 @@ +<?php + +declare(strict_types = 1); +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\DB; + +use Doctrine\DBAL\Logging\DebugStack; +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Type; +use OC\AppFramework\Http\Request; +use OCP\AppFramework\Http\Response; + +class DbDataCollector extends \OCP\DataCollector\AbstractDataCollector { + protected ?DebugStack $debugStack = null; + private Connection $connection; + + /** + * DbDataCollector constructor. + */ + public function __construct(Connection $connection) { + $this->connection = $connection; + } + + public function setDebugStack(DebugStack $debugStack, $name = 'default'): void { + $this->debugStack = $debugStack; + } + + /** + * @inheritDoc + */ + public function collect(Request $request, Response $response, \Throwable $exception = null): void { + $queries = $this->sanitizeQueries($this->debugStack->queries); + + $this->data = [ + 'queries' => $queries, + ]; + } + + public function getName(): string { + return 'db'; + } + + public function getQueries(): array { + return $this->data['queries']; + } + + private function sanitizeQueries(array $queries): array { + foreach ($queries as $i => $query) { + $queries[$i] = $this->sanitizeQuery($query); + } + + return $queries; + } + + private function sanitizeQuery(array $query): array { + $query['explainable'] = true; + $query['runnable'] = true; + if (null === $query['params']) { + $query['params'] = []; + } + if (!\is_array($query['params'])) { + $query['params'] = [$query['params']]; + } + if (!\is_array($query['types'])) { + $query['types'] = []; + } + foreach ($query['params'] as $j => $param) { + $e = null; + if (isset($query['types'][$j])) { + // Transform the param according to the type + $type = $query['types'][$j]; + if (\is_string($type)) { + $type = Type::getType($type); + } + if ($type instanceof Type) { + $query['types'][$j] = $type->getBindingType(); + try { + $param = $type->convertToDatabaseValue($param, $this->connection->getDatabasePlatform()); + } catch (\TypeError $e) { + } catch (ConversionException $e) { + } + } + } + + [$query['params'][$j], $explainable, $runnable] = $this->sanitizeParam($param, $e); + if (!$explainable) { + $query['explainable'] = false; + } + + if (!$runnable) { + $query['runnable'] = false; + } + } + + return $query; + } + + /** + * Sanitizes a param. + * + * The return value is an array with the sanitized value and a boolean + * indicating if the original value was kept (allowing to use the sanitized + * value to explain the query). + */ + private function sanitizeParam($var, ?\Throwable $error): array { + if (\is_object($var)) { + return [$o = new ObjectParameter($var, $error), false, $o->isStringable() && !$error]; + } + + if ($error) { + return ['⚠ '.$error->getMessage(), false, false]; + } + + if (\is_array($var)) { + $a = []; + $explainable = $runnable = true; + foreach ($var as $k => $v) { + [$value, $e, $r] = $this->sanitizeParam($v, null); + $explainable = $explainable && $e; + $runnable = $runnable && $r; + $a[$k] = $value; + } + + return [$a, $explainable, $runnable]; + } + + if (\is_resource($var)) { + return [sprintf('/* Resource(%s) */', get_resource_type($var)), false, false]; + } + + return [$var, true, true]; + } +} diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php index e7361cb4cab..046e3a4924b 100644 --- a/lib/private/DB/MigrationService.php +++ b/lib/private/DB/MigrationService.php @@ -43,6 +43,7 @@ use OCP\AppFramework\App; use OCP\AppFramework\QueryException; use OCP\Migration\IMigrationStep; use OCP\Migration\IOutput; +use Psr\Log\LoggerInterface; class MigrationService { @@ -73,7 +74,7 @@ class MigrationService { $this->connection = $connection; $this->output = $output; if (null === $this->output) { - $this->output = new SimpleOutput(\OC::$server->getLogger(), $appName); + $this->output = new SimpleOutput(\OC::$server->get(LoggerInterface::class), $appName); } if ($appName === 'core') { @@ -594,6 +595,10 @@ class MigrationService { if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && $thing->getNotnull() && $thing->getType()->getName() === Types::BOOLEAN) { throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is type Bool and also NotNull, so it can not store "false".'); } + + if ($thing->getLength() > 4000 && $thing->getType()->getName() === Types::STRING) { + throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is type String, but exceeding the 4.000 length limit.'); + } } foreach ($table->getIndexes() as $thing) { @@ -634,8 +639,8 @@ class MigrationService { if ($isUsingDefaultName && \strlen($table->getName()) - $prefixLength >= 23) { throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.'); } - } elseif (!$primaryKey instanceof Index) { - throw new \InvalidArgumentException('Table "' . $table->getName() . '" has no primary key and therefor will not behave sane in clustered setups.'); + // } elseif (!$primaryKey instanceof Index && !$sourceTable instanceof Table) { + // throw new \InvalidArgumentException('Table "' . $table->getName() . '" has no primary key and therefor will not behave sane in clustered setups.'); } } diff --git a/lib/private/DB/ObjectParameter.php b/lib/private/DB/ObjectParameter.php new file mode 100644 index 00000000000..61ac16018d8 --- /dev/null +++ b/lib/private/DB/ObjectParameter.php @@ -0,0 +1,71 @@ +<?php + +declare(strict_types = 1); + +/* + * This file is part of the Symfony package. + * + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * @author Fabien Potencier <fabien@symfony.com> + * + * @license AGPL-3.0-or-later AND MIT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\DB; + +final class ObjectParameter { + private $object; + private $error; + private $stringable; + private $class; + + /** + * @param object $object + */ + public function __construct($object, ?\Throwable $error) { + $this->object = $object; + $this->error = $error; + $this->stringable = \is_callable([$object, '__toString']); + $this->class = \get_class($object); + } + + /** + * @return object + */ + public function getObject() { + return $this->object; + } + + public function getError(): ?\Throwable { + return $this->error; + } + + public function isStringable(): bool { + return $this->stringable; + } + + public function getClass(): string { + return $this->class; + } +} diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php index 03630ea14a3..e0a7549a0ad 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/FunctionBuilder.php @@ -94,6 +94,18 @@ class FunctionBuilder implements IFunctionBuilder { return new QueryFunction('COUNT(' . $quotedName . ')' . $alias); } + public function octetLength($field, $alias = ''): IQueryFunction { + $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; + $quotedName = $this->helper->quoteColumnName($field); + return new QueryFunction('OCTET_LENGTH(' . $quotedName . ')' . $alias); + } + + public function charLength($field, $alias = ''): IQueryFunction { + $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; + $quotedName = $this->helper->quoteColumnName($field); + return new QueryFunction('CHAR_LENGTH(' . $quotedName . ')' . $alias); + } + public function max($field): IQueryFunction { return new QueryFunction('MAX(' . $this->helper->quoteColumnName($field) . ')'); } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php index 43ecf599eba..a9844ec3373 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/OCIFunctionBuilder.php @@ -91,4 +91,16 @@ class OCIFunctionBuilder extends FunctionBuilder { $separator = $this->connection->quote($separator); return new QueryFunction('LISTAGG(' . $this->helper->quoteColumnName($expr) . ', ' . $separator . ')' . $orderByClause); } + + public function octetLength($field, $alias = ''): IQueryFunction { + $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; + $quotedName = $this->helper->quoteColumnName($field); + return new QueryFunction('LENGTHB(' . $quotedName . ')' . $alias); + } + + public function charLength($field, $alias = ''): IQueryFunction { + $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; + $quotedName = $this->helper->quoteColumnName($field); + return new QueryFunction('LENGTH(' . $quotedName . ')' . $alias); + } } diff --git a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php index fe700075a82..bb97d2e29f9 100644 --- a/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php +++ b/lib/private/DB/QueryBuilder/FunctionBuilder/SqliteFunctionBuilder.php @@ -48,4 +48,16 @@ class SqliteFunctionBuilder extends FunctionBuilder { public function least($x, $y): IQueryFunction { return new QueryFunction('MIN(' . $this->helper->quoteColumnName($x) . ', ' . $this->helper->quoteColumnName($y) . ')'); } + + public function octetLength($field, $alias = ''): IQueryFunction { + $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; + $quotedName = $this->helper->quoteColumnName($field); + return new QueryFunction('LENGTH(CAST(' . $quotedName . ' as BLOB))' . $alias); + } + + public function charLength($field, $alias = ''): IQueryFunction { + $alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : ''; + $quotedName = $this->helper->quoteColumnName($field); + return new QueryFunction('LENGTH(' . $quotedName . ')' . $alias); + } } diff --git a/lib/private/DB/QueryBuilder/QueryBuilder.php b/lib/private/DB/QueryBuilder/QueryBuilder.php index de326a2a317..4ecfd773492 100644 --- a/lib/private/DB/QueryBuilder/QueryBuilder.php +++ b/lib/private/DB/QueryBuilder/QueryBuilder.php @@ -53,7 +53,7 @@ use OCP\DB\QueryBuilder\ILiteral; use OCP\DB\QueryBuilder\IParameter; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryFunction; -use OCP\ILogger; +use Psr\Log\LoggerInterface; class QueryBuilder implements IQueryBuilder { @@ -63,8 +63,7 @@ class QueryBuilder implements IQueryBuilder { /** @var SystemConfig */ private $systemConfig; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var \Doctrine\DBAL\Query\QueryBuilder */ private $queryBuilder; @@ -83,9 +82,8 @@ class QueryBuilder implements IQueryBuilder { * * @param ConnectionAdapter $connection * @param SystemConfig $systemConfig - * @param ILogger $logger */ - public function __construct(ConnectionAdapter $connection, SystemConfig $systemConfig, ILogger $logger) { + public function __construct(ConnectionAdapter $connection, SystemConfig $systemConfig, LoggerInterface $logger) { $this->connection = $connection; $this->systemConfig = $systemConfig; $this->logger = $logger; @@ -229,8 +227,7 @@ class QueryBuilder implements IQueryBuilder { } } catch (\Error $e) { // likely an error during conversion of $value to string - $this->logger->debug('DB QueryBuilder: error trying to log SQL query'); - $this->logger->logException($e); + $this->logger->error('DB QueryBuilder: error trying to log SQL query', ['exception' => $e]); } } @@ -245,11 +242,10 @@ class QueryBuilder implements IQueryBuilder { if (empty($hasSelectAll) === empty($hasSelectSpecific)) { $exception = new QueryException('Query is selecting * and specific values in the same query. This is not supported in Oracle.'); - $this->logger->logException($exception, [ - 'message' => 'Query is selecting * and specific values in the same query. This is not supported in Oracle.', + $this->logger->error($exception->getMessage(), [ 'query' => $this->getSQL(), - 'level' => ILogger::ERROR, 'app' => 'core', + 'exception' => $exception, ]); } } @@ -266,21 +262,19 @@ class QueryBuilder implements IQueryBuilder { if ($hasTooLargeArrayParameter) { $exception = new QueryException('More than 1000 expressions in a list are not allowed on Oracle.'); - $this->logger->logException($exception, [ - 'message' => 'More than 1000 expressions in a list are not allowed on Oracle.', + $this->logger->error($exception->getMessage(), [ 'query' => $this->getSQL(), - 'level' => ILogger::ERROR, 'app' => 'core', + 'exception' => $exception, ]); } if ($numberOfParameters > 65535) { $exception = new QueryException('The number of parameters must not exceed 65535. Restriction by PostgreSQL.'); - $this->logger->logException($exception, [ - 'message' => 'The number of parameters must not exceed 65535. Restriction by PostgreSQL.', + $this->logger->error($exception->getMessage(), [ 'query' => $this->getSQL(), - 'level' => ILogger::ERROR, 'app' => 'core', + 'exception' => $exception, ]); } diff --git a/lib/private/Diagnostics/EventLogger.php b/lib/private/Diagnostics/EventLogger.php index 35cef0be3f5..c7b89002ea9 100644 --- a/lib/private/Diagnostics/EventLogger.php +++ b/lib/private/Diagnostics/EventLogger.php @@ -60,7 +60,8 @@ class EventLogger implements IEventLogger { } public function isLoggingActivated(): bool { - $systemValue = (bool)$this->config->getValue('diagnostics.logging', false); + $systemValue = (bool)$this->config->getValue('diagnostics.logging', false) + || (bool)$this->config->getValue('profiler', false); if ($systemValue && $this->config->getValue('debug', false)) { return true; diff --git a/lib/private/Encryption/EncryptionWrapper.php b/lib/private/Encryption/EncryptionWrapper.php index e3dc539dd00..f2803fa5ff8 100644 --- a/lib/private/Encryption/EncryptionWrapper.php +++ b/lib/private/Encryption/EncryptionWrapper.php @@ -30,7 +30,6 @@ use OC\Files\View; use OC\Memcache\ArrayCache; use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage; -use OCP\ILogger; use Psr\Log\LoggerInterface; /** @@ -48,19 +47,14 @@ class EncryptionWrapper { /** @var Manager */ private $manager; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** * EncryptionWrapper constructor. - * - * @param ArrayCache $arrayCache - * @param Manager $manager - * @param ILogger $logger */ public function __construct(ArrayCache $arrayCache, Manager $manager, - ILogger $logger + LoggerInterface $logger ) { $this->arrayCache = $arrayCache; $this->manager = $manager; @@ -101,7 +95,7 @@ class EncryptionWrapper { Filesystem::getMountManager(), $this->manager, $fileHelper, - \OC::$server->get(LoggerInterface::class), + $this->logger, $uid ); return new Encryption( diff --git a/lib/private/Encryption/Manager.php b/lib/private/Encryption/Manager.php index 55578ee25f4..5788da990d5 100644 --- a/lib/private/Encryption/Manager.php +++ b/lib/private/Encryption/Manager.php @@ -34,7 +34,7 @@ use OCP\Encryption\IEncryptionModule; use OCP\Encryption\IManager; use OCP\IConfig; use OCP\IL10N; -use OCP\ILogger; +use Psr\Log\LoggerInterface; class Manager implements IManager { @@ -44,8 +44,7 @@ class Manager implements IManager { /** @var IConfig */ protected $config; - /** @var ILogger */ - protected $logger; + protected LoggerInterface $logger; /** @var Il10n */ protected $l; @@ -59,15 +58,7 @@ class Manager implements IManager { /** @var ArrayCache */ protected $arrayCache; - /** - * @param IConfig $config - * @param ILogger $logger - * @param IL10N $l10n - * @param View $rootView - * @param Util $util - * @param ArrayCache $arrayCache - */ - public function __construct(IConfig $config, ILogger $logger, IL10N $l10n, View $rootView, Util $util, ArrayCache $arrayCache) { + public function __construct(IConfig $config, LoggerInterface $logger, IL10N $l10n, View $rootView, Util $util, ArrayCache $arrayCache) { $this->encryptionModules = []; $this->config = $config; $this->logger = $logger; diff --git a/lib/private/EventDispatcher/GenericEventWrapper.php b/lib/private/EventDispatcher/GenericEventWrapper.php index 3574bc8bb83..b4d851d700f 100644 --- a/lib/private/EventDispatcher/GenericEventWrapper.php +++ b/lib/private/EventDispatcher/GenericEventWrapper.php @@ -26,13 +26,11 @@ declare(strict_types=1); */ namespace OC\EventDispatcher; -use OCP\ILogger; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\GenericEvent; class GenericEventWrapper extends GenericEvent { - - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var GenericEvent */ private $event; @@ -43,7 +41,7 @@ class GenericEventWrapper extends GenericEvent { /** @var bool */ private $deprecationNoticeLogged = false; - public function __construct(ILogger $logger, string $eventName, ?GenericEvent $event) { + public function __construct(LoggerInterface $logger, string $eventName, ?GenericEvent $event) { parent::__construct($eventName); $this->logger = $logger; $this->event = $event; diff --git a/lib/private/EventDispatcher/SymfonyAdapter.php b/lib/private/EventDispatcher/SymfonyAdapter.php index 6cd636afc55..a12e932e380 100644 --- a/lib/private/EventDispatcher/SymfonyAdapter.php +++ b/lib/private/EventDispatcher/SymfonyAdapter.php @@ -27,12 +27,12 @@ declare(strict_types=1); */ namespace OC\EventDispatcher; -use Symfony\Component\EventDispatcher\GenericEvent; -use function is_callable; use OCP\EventDispatcher\Event; -use OCP\ILogger; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\GenericEvent; +use function is_callable; use function is_object; use function is_string; @@ -43,13 +43,12 @@ class SymfonyAdapter implements EventDispatcherInterface { /** @var EventDispatcher */ private $eventDispatcher; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** * @deprecated 20.0.0 */ - public function __construct(EventDispatcher $eventDispatcher, ILogger $logger) { + public function __construct(EventDispatcher $eventDispatcher, LoggerInterface $logger) { $this->eventDispatcher = $eventDispatcher; $this->logger = $logger; } diff --git a/lib/private/Federation/CloudFederationProviderManager.php b/lib/private/Federation/CloudFederationProviderManager.php index c19ca8429ca..c25d4a40363 100644 --- a/lib/private/Federation/CloudFederationProviderManager.php +++ b/lib/private/Federation/CloudFederationProviderManager.php @@ -32,7 +32,7 @@ use OCP\Federation\ICloudFederationProviderManager; use OCP\Federation\ICloudFederationShare; use OCP\Federation\ICloudIdManager; use OCP\Http\Client\IClientService; -use OCP\ILogger; +use Psr\Log\LoggerInterface; /** * Class Manager @@ -55,8 +55,7 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager /** @var ICloudIdManager */ private $cloudIdManager; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var array cache OCM end-points */ private $ocmEndPoints = []; @@ -69,12 +68,11 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager * @param IAppManager $appManager * @param IClientService $httpClientService * @param ICloudIdManager $cloudIdManager - * @param ILogger $logger */ public function __construct(IAppManager $appManager, IClientService $httpClientService, ICloudIdManager $cloudIdManager, - ILogger $logger) { + LoggerInterface $logger) { $this->cloudFederationProvider = []; $this->appManager = $appManager; $this->httpClientService = $httpClientService; @@ -156,7 +154,7 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager // we re-throw the exception and fall back to the old behaviour. // (flat re-shares has been introduced in Nextcloud 9.1) if ($e->getCode() === Http::STATUS_INTERNAL_SERVER_ERROR) { - $this->logger->debug($e->getMessage()); + $this->logger->debug($e->getMessage(), ['exception' => $e]); throw $e; } } @@ -190,7 +188,7 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager } } catch (\Exception $e) { // log the error and return false - $this->logger->error('error while sending notification for federated share: ' . $e->getMessage()); + $this->logger->error('error while sending notification for federated share: ' . $e->getMessage(), ['exception' => $e]); } return false; diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 7c2e635549a..949079dfa22 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -58,6 +58,7 @@ use OCP\Files\Search\ISearchOperator; use OCP\Files\Search\ISearchQuery; use OCP\Files\Storage\IStorage; use OCP\IDBConnection; +use Psr\Log\LoggerInterface; /** * Metadata cache for a storage @@ -128,7 +129,7 @@ class Cache implements ICache { return new CacheQueryBuilder( $this->connection, \OC::$server->getSystemConfig(), - \OC::$server->getLogger() + \OC::$server->get(LoggerInterface::class) ); } @@ -590,7 +591,7 @@ class Cache implements ICache { $query = $this->getQueryBuilder(); $query->delete('filecache_extended') ->where($query->expr()->in('fileid', $query->createParameter('childIds'))); - + foreach (array_chunk($childIds, 1000) as $childIdChunk) { $query->setParameter('childIds', $childIdChunk, IQueryBuilder::PARAM_INT_ARRAY); $query->execute(); diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index 87a1f940f20..b5a9101877c 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -29,7 +29,7 @@ use OC\DB\QueryBuilder\QueryBuilder; use OC\SystemConfig; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; -use OCP\ILogger; +use Psr\Log\LoggerInterface; /** * Query builder with commonly used helpers for filecache queries @@ -37,7 +37,7 @@ use OCP\ILogger; class CacheQueryBuilder extends QueryBuilder { private $alias = null; - public function __construct(IDBConnection $connection, SystemConfig $systemConfig, ILogger $logger) { + public function __construct(IDBConnection $connection, SystemConfig $systemConfig, LoggerInterface $logger) { parent::__construct($connection, $systemConfig, $logger); } diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index e065e95e4fd..3bf9abf3524 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -34,7 +34,7 @@ use OCP\Files\IMimeTypeLoader; use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchQuery; use OCP\IDBConnection; -use OCP\ILogger; +use Psr\Log\LoggerInterface; class QuerySearchHelper { @@ -44,8 +44,7 @@ class QuerySearchHelper { private $connection; /** @var SystemConfig */ private $systemConfig; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var SearchBuilder */ private $searchBuilder; /** @var QueryOptimizer */ @@ -55,7 +54,7 @@ class QuerySearchHelper { IMimeTypeLoader $mimetypeLoader, IDBConnection $connection, SystemConfig $systemConfig, - ILogger $logger, + LoggerInterface $logger, SearchBuilder $searchBuilder, QueryOptimizer $queryOptimizer ) { diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index 2de2c2f84d7..fb9e5500658 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -126,19 +126,9 @@ class Storage { * @param int $numericId * @return string|null either the storage id string or null if the numeric id is not known */ - public static function getStorageId($numericId) { - $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); - $query->select('id') - ->from('storages') - ->where($query->expr()->eq('numeric_id', $query->createNamedParameter($numericId))); - $result = $query->execute(); - $row = $result->fetch(); - $result->closeCursor(); - if ($row) { - return $row['id']; - } else { - return null; - } + public static function getStorageId(int $numericId): ?string { + $storage = self::getGlobalCache()->getStorageInfoByNumericId($numericId); + return $storage['id'] ?? null; } /** @@ -240,6 +230,7 @@ class Storage { ->from('mounts') ->where($query->expr()->eq('mount_id', $query->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))); $storageIds = $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN); + $storageIds = array_unique($storageIds); $query = $db->getQueryBuilder(); $query->delete('filecache') diff --git a/lib/private/Files/Cache/StorageGlobal.php b/lib/private/Files/Cache/StorageGlobal.php index 7162f8e4908..a898c435415 100644 --- a/lib/private/Files/Cache/StorageGlobal.php +++ b/lib/private/Files/Cache/StorageGlobal.php @@ -41,8 +41,10 @@ class StorageGlobal { /** @var IDBConnection */ private $connection; - /** @var array[] */ + /** @var array<string, array> */ private $cache = []; + /** @var array<int, array> */ + private $numericIdCache = []; public function __construct(IDBConnection $connection) { $this->connection = $connection; @@ -68,7 +70,7 @@ class StorageGlobal { * @param string $storageId * @return array|null */ - public function getStorageInfo($storageId) { + public function getStorageInfo(string $storageId): ?array { if (!isset($this->cache[$storageId])) { $builder = $this->connection->getQueryBuilder(); $query = $builder->select(['id', 'numeric_id', 'available', 'last_checked']) @@ -81,9 +83,33 @@ class StorageGlobal { if ($row) { $this->cache[$storageId] = $row; + $this->numericIdCache[(int)$row['numeric_id']] = $row; } } - return isset($this->cache[$storageId]) ? $this->cache[$storageId] : null; + return $this->cache[$storageId] ?? null; + } + + /** + * @param int $numericId + * @return array|null + */ + public function getStorageInfoByNumericId(int $numericId): ?array { + if (!isset($this->numericIdCache[$numericId])) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select(['id', 'numeric_id', 'available', 'last_checked']) + ->from('storages') + ->where($builder->expr()->eq('numeric_id', $builder->createNamedParameter($numericId))); + + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { + $this->numericIdCache[$numericId] = $row; + $this->cache[$row['id']] = $row; + } + } + return $this->numericIdCache[$numericId] ?? null; } public function clearCache() { diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php index cd8a2a2e29f..334fce15d9e 100644 --- a/lib/private/Files/Config/MountProviderCollection.php +++ b/lib/private/Files/Config/MountProviderCollection.php @@ -75,16 +75,15 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { } /** - * Get all configured mount points for the user - * - * @param \OCP\IUser $user - * @return \OCP\Files\Mount\IMountPoint[] + * @param IUser $user + * @param IMountProvider[] $providers + * @return IMountPoint[] */ - public function getMountsForUser(IUser $user) { + 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); - }, $this->providers); + }, $providers); $mounts = array_filter($mounts, function ($result) { return is_array($result); }); @@ -94,14 +93,31 @@ class MountProviderCollection implements IMountProviderCollection, Emitter { return $this->filterMounts($user, $mounts); } - public function addMountForUser(IUser $user, IMountManager $mountManager) { + public function getMountsForUser(IUser $user): array { + return $this->getUserMountsForProviders($user, $this->providers); + } + + public function getUserMountsForProviderClasses(IUser $user, array $mountProviderClasses): array { + $providers = array_filter( + $this->providers, + fn (IMountProvider $mountProvider) => (in_array(get_class($mountProvider), $mountProviderClasses)) + ); + return $this->getUserMountsForProviders($user, $providers); + } + + public function addMountForUser(IUser $user, IMountManager $mountManager, callable $providerFilter = null) { // shared mount provider gets to go last since it needs to know existing files // to check for name collisions $firstMounts = []; - $firstProviders = array_filter($this->providers, function (IMountProvider $provider) { + if ($providerFilter) { + $providers = array_filter($this->providers, $providerFilter); + } else { + $providers = $this->providers; + } + $firstProviders = array_filter($providers, function (IMountProvider $provider) { return (get_class($provider) !== 'OCA\Files_Sharing\MountProvider'); }); - $lastProviders = array_filter($this->providers, function (IMountProvider $provider) { + $lastProviders = array_filter($providers, function (IMountProvider $provider) { return (get_class($provider) === 'OCA\Files_Sharing\MountProvider'); }); foreach ($firstProviders as $provider) { diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index dc2640361e7..666ba9b065b 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -38,9 +38,9 @@ use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\ICache; use OCP\IDBConnection; -use OCP\ILogger; use OCP\IUser; use OCP\IUserManager; +use Psr\Log\LoggerInterface; /** * Cache mounts points per user in the cache so we can easilly look them up @@ -64,10 +64,7 @@ class UserMountCache implements IUserMountCache { **/ private $mountsForUsers; - /** - * @var ILogger - */ - private $logger; + private LoggerInterface $logger; /** * @var ICache @@ -76,12 +73,8 @@ class UserMountCache implements IUserMountCache { /** * UserMountCache constructor. - * - * @param IDBConnection $connection - * @param IUserManager $userManager - * @param ILogger $logger */ - public function __construct(IDBConnection $connection, IUserManager $userManager, ILogger $logger) { + public function __construct(IDBConnection $connection, IUserManager $userManager, LoggerInterface $logger) { $this->connection = $connection; $this->userManager = $userManager; $this->logger = $logger; @@ -89,7 +82,7 @@ class UserMountCache implements IUserMountCache { $this->mountsForUsers = new CappedMemoryCache(); } - public function registerMounts(IUser $user, array $mounts) { + public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null) { // 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(); @@ -110,6 +103,11 @@ class UserMountCache implements IUserMountCache { $newMounts = array_combine($newMountRootIds, $newMounts); $cachedMounts = $this->getMountsForUser($user); + if (is_array($mountProviderClasses)) { + $cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses) { + return in_array($mountInfo->getMountProvider(), $mountProviderClasses); + }); + } $cachedMountRootIds = array_map(function (ICachedMountInfo $mount) { return $mount->getRootId(); }, $cachedMounts); @@ -446,4 +444,39 @@ class UserMountCache implements IUserMountCache { $this->cacheInfoCache = new CappedMemoryCache(); $this->mountsForUsers = new CappedMemoryCache(); } + + public function getMountForPath(IUser $user, string $path): ICachedMountInfo { + $mounts = $this->getMountsForUser($user); + $mountPoints = array_map(function (ICachedMountInfo $mount) { + return $mount->getMountPoint(); + }, $mounts); + $mounts = array_combine($mountPoints, $mounts); + + $current = $path; + // walk up the directory tree until we find a path that has a mountpoint set + // the loop will return if a mountpoint is found or break if none are found + while (true) { + $mountPoint = $current . '/'; + if (isset($mounts[$mountPoint])) { + return $mounts[$mountPoint]; + } elseif ($current === '') { + break; + } + + $current = dirname($current); + if ($current === '.' || $current === '/') { + $current = ''; + } + } + + throw new NotFoundException("No cached mount for path " . $path); + } + + public function getMountsInPath(IUser $user, string $path): array { + $path = rtrim($path, '/') . '/'; + $mounts = $this->getMountsForUser($user); + return array_filter($mounts, function (ICachedMountInfo $mount) use ($path) { + return $mount->getMountPoint() !== $path && strpos($mount->getMountPoint(), $path) === 0; + }); + } } diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index 9db9252037f..20b44e2736a 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -46,6 +46,7 @@ use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorageFactory; use OCP\IUser; use OCP\IUserManager; +use OCP\IUserSession; class Filesystem { @@ -324,6 +325,18 @@ class Filesystem { if (self::$defaultInstance) { return false; } + self::initInternal($root); + + //load custom mount config + self::initMountPoints($user); + + return true; + } + + public static function initInternal($root) { + if (self::$defaultInstance) { + return false; + } self::getLoader(); self::$defaultInstance = new View($root); /** @var IEventDispatcher $eventDispatcher */ @@ -338,9 +351,6 @@ class Filesystem { self::$mounts = \OC::$server->getMountManager(); } - //load custom mount config - self::initMountPoints($user); - self::$loaded = true; return true; @@ -378,6 +388,15 @@ class Filesystem { * @return View */ public static function getView() { + if (!self::$defaultInstance) { + /** @var IUserSession $session */ + $session = \OC::$server->get(IUserSession::class); + $user = $session->getUser(); + if ($user) { + $userDir = '/' . $user->getUID() . '/files'; + self::initInternal($userDir); + } + } return self::$defaultInstance; } @@ -736,7 +755,7 @@ class Filesystem { * @return \OC\Files\FileInfo|false False if file does not exist */ public static function getFileInfo($path, $includeMountPoints = true) { - return self::$defaultInstance->getFileInfo($path, $includeMountPoints); + return self::getView()->getFileInfo($path, $includeMountPoints); } /** diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php index 66832690363..69285018d17 100644 --- a/lib/private/Files/Mount/Manager.php +++ b/lib/private/Files/Mount/Manager.php @@ -125,7 +125,7 @@ class Manager implements IMountManager { * @return IMountPoint[] */ public function findIn(string $path): array { - $this->setupManager->setupForPath($path); + $this->setupManager->setupForPath($path, true); $path = $this->formatPath($path); if (isset($this->inPathCache[$path])) { @@ -158,7 +158,6 @@ class Manager implements IMountManager { * @return IMountPoint[] */ public function findByStorageId(string $id): array { - \OC_Util::setupFS(); if (\strlen($id) > 64) { $id = md5($id); } @@ -204,4 +203,22 @@ class Manager implements IMountManager { public function getSetupManager(): SetupManager { return $this->setupManager; } + + /** + * Return all mounts in a path from a specific mount provider + * + * @param string $path + * @param string[] $mountProviders + * @return MountPoint[] + */ + public function getMountsByMountProvider(string $path, array $mountProviders) { + $this->getSetupManager()->setupForProvider($path, $mountProviders); + if (in_array('', $mountProviders)) { + return $this->mounts; + } else { + return array_filter($this->mounts, function ($mount) use ($mountProviders) { + return in_array($mount->getMountProvider(), $mountProviders); + }); + } + } } diff --git a/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php b/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php index 0043503f2cd..3a20afba5a5 100644 --- a/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php +++ b/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php @@ -31,18 +31,17 @@ use OC\Files\Storage\Wrapper\Jail; use OCP\Files\Config\IRootMountProvider; use OCP\Files\Storage\IStorageFactory; use OCP\IConfig; -use OCP\ILogger; +use Psr\Log\LoggerInterface; /** * Mount provider for object store app data folder for previews */ class ObjectStorePreviewCacheMountProvider implements IRootMountProvider { - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var IConfig */ private $config; - public function __construct(ILogger $logger, IConfig $config) { + public function __construct(LoggerInterface $logger, IConfig $config) { $this->logger = $logger; $this->config = $config; } diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 400fd6bedcc..d058805b20e 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -36,6 +36,7 @@ use OC\Files\Cache\Wrapper\CacheJail; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchOrder; use OC\Files\Search\SearchQuery; +use OC\Files\Utils\PathHelper; use OCP\Files\Cache\ICacheEntry; use OCP\Files\FileInfo; use OCP\Files\Mount\IMountPoint; @@ -76,17 +77,7 @@ class Folder extends Node implements \OCP\Files\Folder { * @return string|null */ public function getRelativePath($path) { - if ($this->path === '' or $this->path === '/') { - return $this->normalizePath($path); - } - if ($path === $this->path) { - return '/'; - } elseif (strpos($path, $this->path . '/') !== 0) { - return null; - } else { - $path = substr($path, strlen($this->path)); - return $this->normalizePath($path); - } + return PathHelper::getRelativePath($this->getPath(), $path); } /** @@ -342,70 +333,7 @@ class Folder extends Node implements \OCP\Files\Folder { * @return \OC\Files\Node\Node[] */ public function getById($id) { - $mountCache = $this->root->getUserMountCache(); - if (strpos($this->getPath(), '/', 1) > 0) { - [, $user] = explode('/', $this->getPath()); - } else { - $user = null; - } - $mountsContainingFile = $mountCache->getMountsForFileId((int)$id, $user); - - // when a user has access trough the same storage trough multiple paths - // (such as an external storage that is both mounted for a user and shared to the user) - // the mount cache will only hold a single entry for the storage - // this can lead to issues as the different ways the user has access to a storage can have different permissions - // - // so instead of using the cached entries directly, we instead filter the current mounts by the rootid of the cache entry - - $mountRootIds = array_map(function ($mount) { - return $mount->getRootId(); - }, $mountsContainingFile); - $mountRootPaths = array_map(function ($mount) { - return $mount->getRootInternalPath(); - }, $mountsContainingFile); - $mountRoots = array_combine($mountRootIds, $mountRootPaths); - - $mounts = $this->root->getMountsIn($this->path); - $mounts[] = $this->root->getMount($this->path); - - $mountsContainingFile = array_filter($mounts, function ($mount) use ($mountRoots) { - return isset($mountRoots[$mount->getStorageRootId()]); - }); - - if (count($mountsContainingFile) === 0) { - if ($user === $this->getAppDataDirectoryName()) { - return $this->getByIdInRootMount((int)$id); - } - return []; - } - - $nodes = array_map(function (IMountPoint $mount) use ($id, $mountRoots) { - $rootInternalPath = $mountRoots[$mount->getStorageRootId()]; - $cacheEntry = $mount->getStorage()->getCache()->get((int)$id); - if (!$cacheEntry) { - return null; - } - - // cache jails will hide the "true" internal path - $internalPath = ltrim($rootInternalPath . '/' . $cacheEntry->getPath(), '/'); - $pathRelativeToMount = substr($internalPath, strlen($rootInternalPath)); - $pathRelativeToMount = ltrim($pathRelativeToMount, '/'); - $absolutePath = rtrim($mount->getMountPoint() . $pathRelativeToMount, '/'); - return $this->root->createNode($absolutePath, new \OC\Files\FileInfo( - $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount, - \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount)) - )); - }, $mountsContainingFile); - - $nodes = array_filter($nodes); - - $folders = array_filter($nodes, function (Node $node) { - return $this->getRelativePath($node->getPath()); - }); - usort($folders, function ($a, $b) { - return $b->getPath() <=> $a->getPath(); - }); - return $folders; + return $this->root->getByIdInPath((int)$id, $this->getPath()); } protected function getAppDataDirectoryName(): string { diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index bfdaeeccff7..7d5038e85a2 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -25,6 +25,9 @@ declare(strict_types=1); */ namespace OC\Files\Node; +use OC\Files\Utils\PathHelper; +use OCP\Constants; + /** * Class LazyFolder * @@ -40,13 +43,16 @@ class LazyFolder implements \OCP\Files\Folder { /** @var LazyFolder | null */ protected $folder = null; + protected array $data; + /** * LazyFolder constructor. * * @param \Closure $folderClosure */ - public function __construct(\Closure $folderClosure) { + public function __construct(\Closure $folderClosure, array $data = []) { $this->folderClosure = $folderClosure; + $this->data = $data; } /** @@ -181,6 +187,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function getPath() { + if (isset($this->data['path'])) { + return $this->data['path']; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -230,6 +239,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function getPermissions() { + if (isset($this->data['permissions'])) { + return $this->data['permissions']; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -237,6 +249,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function isReadable() { + if (isset($this->data['permissions'])) { + return ($this->data['permissions'] & Constants::PERMISSION_READ) == Constants::PERMISSION_READ; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -244,6 +259,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function isUpdateable() { + if (isset($this->data['permissions'])) { + return ($this->data['permissions'] & Constants::PERMISSION_UPDATE) == Constants::PERMISSION_UPDATE; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -251,6 +269,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function isDeletable() { + if (isset($this->data['permissions'])) { + return ($this->data['permissions'] & Constants::PERMISSION_DELETE) == Constants::PERMISSION_DELETE; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -258,6 +279,9 @@ class LazyFolder implements \OCP\Files\Folder { * @inheritDoc */ public function isShareable() { + if (isset($this->data['permissions'])) { + return ($this->data['permissions'] & Constants::PERMISSION_SHARE) == Constants::PERMISSION_SHARE; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -359,13 +383,6 @@ class LazyFolder implements \OCP\Files\Folder { /** * @inheritDoc */ - public function getRelativePath($path) { - return $this->__call(__FUNCTION__, func_get_args()); - } - - /** - * @inheritDoc - */ public function isSubNode($node) { return $this->__call(__FUNCTION__, func_get_args()); } @@ -495,4 +512,8 @@ class LazyFolder implements \OCP\Files\Folder { public function getUploadTime(): int { return $this->__call(__FUNCTION__, func_get_args()); } + + public function getRelativePath($path) { + return PathHelper::getRelativePath($this->getPath(), $path); + } } diff --git a/lib/private/Files/Node/LazyRoot.php b/lib/private/Files/Node/LazyRoot.php index c4d61f653e4..c01b9fdbb83 100644 --- a/lib/private/Files/Node/LazyRoot.php +++ b/lib/private/Files/Node/LazyRoot.php @@ -39,4 +39,8 @@ class LazyRoot extends LazyFolder implements IRootFolder { public function getUserFolder($userId) { return $this->__call(__FUNCTION__, func_get_args()); } + + public function getByIdInPath(int $id, string $path) { + return $this->__call(__FUNCTION__, func_get_args()); + } } diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php new file mode 100644 index 00000000000..d91759117c1 --- /dev/null +++ b/lib/private/Files/Node/LazyUserFolder.php @@ -0,0 +1,66 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Node; + +use OCP\Constants; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\IUser; + +class LazyUserFolder extends LazyFolder { + private IRootFolder $root; + private IUser $user; + private string $path; + + public function __construct(IRootFolder $rootFolder, IUser $user) { + $this->root = $rootFolder; + $this->user = $user; + $this->path = '/' . $user->getUID() . '/files'; + parent::__construct(function () use ($user) { + try { + return $this->root->get('/' . $user->getUID() . '/files'); + } catch (NotFoundException $e) { + if (!$this->root->nodeExists('/' . $user->getUID())) { + $this->root->newFolder('/' . $user->getUID()); + } + return $this->root->newFolder('/' . $user->getUID() . '/files'); + } + }, [ + 'path' => $this->path, + 'permissions' => Constants::PERMISSION_ALL, + ]); + } + + public function get($path) { + return $this->root->get('/' . $this->user->getUID() . '/files/' . ltrim($path, '/')); + } + + /** + * @param int $id + * @return \OC\Files\Node\Node[] + */ + public function getById($id) { + return $this->root->getByIdInPath((int)$id, $this->getPath()); + } +} diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index b939bfce945..c8975154059 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -31,6 +31,7 @@ namespace OC\Files\Node; use OC\Files\Filesystem; use OC\Files\Mount\MoveableMount; +use OC\Files\Utils\PathHelper; use OCP\Files\FileInfo; use OCP\Files\InvalidPathException; use OCP\Files\NotFoundException; @@ -153,12 +154,11 @@ class Node implements \OCP\Files\Node { } } - /** - * @return \OC\Files\Storage\Storage - * @throws \OCP\Files\NotFoundException - */ public function getStorage() { - [$storage,] = $this->view->resolvePath($this->path); + $storage = $this->getMountPoint()->getStorage(); + if (!$storage) { + throw new \Exception("No storage for node"); + } return $storage; } @@ -173,8 +173,7 @@ class Node implements \OCP\Files\Node { * @return string */ public function getInternalPath() { - [, $internalPath] = $this->view->resolvePath($this->path); - return $internalPath; + return $this->getFileInfo()->getInternalPath(); } /** @@ -298,23 +297,7 @@ class Node implements \OCP\Files\Node { * @return string */ protected function normalizePath($path) { - if ($path === '' or $path === '/') { - return '/'; - } - //no windows style slashes - $path = str_replace('\\', '/', $path); - //add leading slash - if ($path[0] !== '/') { - $path = '/' . $path; - } - //remove duplicate slashes - while (strpos($path, '//') !== false) { - $path = str_replace('//', '/', $path); - } - //remove trailing slash - $path = rtrim($path, '/'); - - return $path; + return PathHelper::normalizePath($path); } /** @@ -447,6 +430,15 @@ class Node implements \OCP\Files\Node { if (!$this->view->rename($this->path, $targetPath)) { throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath); } + + $mountPoint = $this->getMountPoint(); + if ($mountPoint) { + // update the cached fileinfo with the new (internal) path + /** @var \OC\Files\FileInfo $oldFileInfo */ + $oldFileInfo = $this->getFileInfo(); + $this->fileInfo = new \OC\Files\FileInfo($targetPath, $oldFileInfo->getStorage(), $mountPoint->getInternalPath($targetPath), $oldFileInfo->getData(), $mountPoint, $oldFileInfo->getOwner()); + } + $targetNode = $this->root->get($targetPath); $this->sendHooks(['postRename'], [$this, $targetNode]); $this->sendHooks(['postWrite'], [$targetNode]); diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index 88ac4a31d34..7592d4caf37 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -33,8 +33,10 @@ namespace OC\Files\Node; use OC\Cache\CappedMemoryCache; +use OC\Files\FileInfo; use OC\Files\Mount\Manager; use OC\Files\Mount\MountPoint; +use OC\Files\Utils\PathHelper; use OC\Files\View; use OC\Hooks\PublicEmitter; use OC\User\NoUserException; @@ -42,11 +44,12 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IUserMountCache; use OCP\Files\Events\Node\FilesystemTornDownEvent; use OCP\Files\IRootFolder; +use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; -use OCP\ILogger; use OCP\IUser; use OCP\IUserManager; +use Psr\Log\LoggerInterface; /** * Class Root @@ -73,7 +76,7 @@ class Root extends Folder implements IRootFolder { private ?IUser $user; private CappedMemoryCache $userFolderCache; private IUserMountCache $userMountCache; - private ILogger $logger; + private LoggerInterface $logger; private IUserManager $userManager; private IEventDispatcher $eventDispatcher; @@ -81,16 +84,13 @@ class Root extends Folder implements IRootFolder { * @param Manager $manager * @param View $view * @param IUser|null $user - * @param IUserMountCache $userMountCache - * @param ILogger $logger - * @param IUserManager $userManager */ public function __construct( $manager, $view, $user, IUserMountCache $userMountCache, - ILogger $logger, + LoggerInterface $logger, IUserManager $userManager, IEventDispatcher $eventDispatcher ) { @@ -217,7 +217,7 @@ class Root extends Folder implements IRootFolder { /** * @param string $targetPath - * @return \OC\Files\Node\Node + * @return Node * @throws \OCP\Files\NotPermittedException */ public function rename($targetPath) { @@ -230,7 +230,7 @@ class Root extends Folder implements IRootFolder { /** * @param string $targetPath - * @return \OC\Files\Node\Node + * @return Node * @throws \OCP\Files\NotPermittedException */ public function copy($targetPath) { @@ -380,15 +380,20 @@ class Root extends Folder implements IRootFolder { $userId = $userObject->getUID(); if (!$this->userFolderCache->hasKey($userId)) { - \OC\Files\Filesystem::initMountPoints($userId); - - try { - $folder = $this->get('/' . $userId . '/files'); - } catch (NotFoundException $e) { - if (!$this->nodeExists('/' . $userId)) { - $this->newFolder('/' . $userId); + if ($this->mountManager->getSetupManager()->isSetupComplete($userObject)) { + try { + $folder = $this->get('/' . $userId . '/files'); + if (!$folder instanceof \OCP\Files\Folder) { + throw new \Exception("User folder for $userId exists as a file"); + } + } catch (NotFoundException $e) { + if (!$this->nodeExists('/' . $userId)) { + $this->newFolder('/' . $userId); + } + $folder = $this->newFolder('/' . $userId . '/files'); } - $folder = $this->newFolder('/' . $userId . '/files'); + } else { + $folder = new LazyUserFolder($this, $userObject); } $this->userFolderCache->set($userId, $folder); @@ -400,4 +405,82 @@ class Root extends Folder implements IRootFolder { public function getUserMountCache() { return $this->userMountCache; } + + /** + * @param int $id + * @return Node[] + */ + public function getByIdInPath(int $id, string $path): array { + $mountCache = $this->getUserMountCache(); + if (strpos($path, '/', 1) > 0) { + [, $user] = explode('/', $path); + } else { + $user = null; + } + $mountsContainingFile = $mountCache->getMountsForFileId($id, $user); + + // when a user has access trough the same storage trough multiple paths + // (such as an external storage that is both mounted for a user and shared to the user) + // the mount cache will only hold a single entry for the storage + // this can lead to issues as the different ways the user has access to a storage can have different permissions + // + // so instead of using the cached entries directly, we instead filter the current mounts by the rootid of the cache entry + + $mountRootIds = array_map(function ($mount) { + return $mount->getRootId(); + }, $mountsContainingFile); + $mountRootPaths = array_map(function ($mount) { + return $mount->getRootInternalPath(); + }, $mountsContainingFile); + $mountProviders = array_unique(array_map(function ($mount) { + return $mount->getMountProvider(); + }, $mountsContainingFile)); + $mountRoots = array_combine($mountRootIds, $mountRootPaths); + + $mounts = $this->mountManager->getMountsByMountProvider($path, $mountProviders); + + $mountsContainingFile = array_filter($mounts, function ($mount) use ($mountRoots) { + return isset($mountRoots[$mount->getStorageRootId()]); + }); + + if (count($mountsContainingFile) === 0) { + if ($user === $this->getAppDataDirectoryName()) { + $folder = $this->get($path); + if ($folder instanceof Folder) { + return $folder->getByIdInRootMount($id); + } else { + throw new \Exception("getByIdInPath with non folder"); + } + } + return []; + } + + $nodes = array_map(function (IMountPoint $mount) use ($id, $mountRoots) { + $rootInternalPath = $mountRoots[$mount->getStorageRootId()]; + $cacheEntry = $mount->getStorage()->getCache()->get($id); + if (!$cacheEntry) { + return null; + } + + // cache jails will hide the "true" internal path + $internalPath = ltrim($rootInternalPath . '/' . $cacheEntry->getPath(), '/'); + $pathRelativeToMount = substr($internalPath, strlen($rootInternalPath)); + $pathRelativeToMount = ltrim($pathRelativeToMount, '/'); + $absolutePath = rtrim($mount->getMountPoint() . $pathRelativeToMount, '/'); + return $this->createNode($absolutePath, new FileInfo( + $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount, + \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount)) + )); + }, $mountsContainingFile); + + $nodes = array_filter($nodes); + + $folders = array_filter($nodes, function (Node $node) use ($path) { + return PathHelper::getRelativePath($path, $node->getPath()) !== null; + }); + usort($folders, function ($a, $b) { + return $b->getPath() <=> $a->getPath(); + }); + return $folders; + } } diff --git a/lib/private/Files/ObjectStore/Swift.php b/lib/private/Files/ObjectStore/Swift.php index a3c8d92f3d2..b463cb9d44d 100644 --- a/lib/private/Files/ObjectStore/Swift.php +++ b/lib/private/Files/ObjectStore/Swift.php @@ -32,6 +32,7 @@ use Icewind\Streams\RetryWrapper; use OCP\Files\NotFoundException; use OCP\Files\ObjectStore\IObjectStore; use OCP\Files\StorageAuthException; +use Psr\Log\LoggerInterface; const SWIFT_SEGMENT_SIZE = 1073741824; // 1GB @@ -48,7 +49,7 @@ class Swift implements IObjectStore { $this->swiftFactory = $connectionFactory ?: new SwiftFactory( \OC::$server->getMemCacheFactory()->createDistributed('swift::'), $params, - \OC::$server->getLogger() + \OC::$server->get(LoggerInterface::class) ); $this->params = $params; } diff --git a/lib/private/Files/ObjectStore/SwiftFactory.php b/lib/private/Files/ObjectStore/SwiftFactory.php index df4b5a93678..bd75ccada2e 100644 --- a/lib/private/Files/ObjectStore/SwiftFactory.php +++ b/lib/private/Files/ObjectStore/SwiftFactory.php @@ -40,7 +40,6 @@ use GuzzleHttp\HandlerStack; use OCP\Files\StorageAuthException; use OCP\Files\StorageNotAvailableException; use OCP\ICache; -use OCP\ILogger; use OpenStack\Common\Auth\Token; use OpenStack\Common\Error\BadResponseError; use OpenStack\Common\Transport\Utils as TransportUtils; @@ -50,13 +49,14 @@ use OpenStack\Identity\v3\Service as IdentityV3Service; use OpenStack\ObjectStore\v1\Models\Container; use OpenStack\OpenStack; use Psr\Http\Message\RequestInterface; +use Psr\Log\LoggerInterface; class SwiftFactory { private $cache; private $params; /** @var Container|null */ private $container = null; - private $logger; + private LoggerInterface $logger; public const DEFAULT_OPTIONS = [ 'autocreate' => false, @@ -65,7 +65,7 @@ class SwiftFactory { 'catalogType' => 'object-store' ]; - public function __construct(ICache $cache, array $params, ILogger $logger) { + public function __construct(ICache $cache, array $params, LoggerInterface $logger) { $this->cache = $cache; $this->params = $params; $this->logger = $logger; @@ -203,7 +203,7 @@ class SwiftFactory { $this->logger->debug('Cached token for swift expired'); } } catch (\Exception $e) { - $this->logger->logException($e); + $this->logger->error($e->getMessage(), ['exception' => $e]); } } } @@ -279,7 +279,7 @@ class SwiftFactory { /** @var RequestInterface $request */ $request = $e->getRequest(); $host = $request->getUri()->getHost() . ':' . $request->getUri()->getPort(); - \OC::$server->getLogger()->error("Can't connect to object storage server at $host"); + $this->logger->error("Can't connect to object storage server at $host", ['exception' => $e]); throw new StorageNotAvailableException("Can't connect to object storage server at $host", StorageNotAvailableException::STATUS_ERROR, $e); } } diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index 9726fbef428..ddb0bbceb81 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -40,16 +40,26 @@ use OC_Util; use OCP\Constants; use OCP\Diagnostics\IEventLogger; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Config\IHomeMountProvider; use OCP\Files\Config\IMountProvider; use OCP\Files\Config\IUserMountCache; +use OCP\Files\Events\InvalidateMountCacheEvent; use OCP\Files\Events\Node\FilesystemTornDownEvent; use OCP\Files\Mount\IMountManager; use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; +use OCP\Group\Events\UserAddedEvent; +use OCP\Group\Events\UserRemovedEvent; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; use OCP\Lockdown\ILockdownManager; +use OCP\Share\Events\ShareCreatedEvent; +use Psr\Log\LoggerInterface; class SetupManager { private bool $rootSetup = false; @@ -57,11 +67,19 @@ class SetupManager { private MountProviderCollection $mountProviderCollection; private IMountManager $mountManager; private IUserManager $userManager; + // List of users for which at least one mount is setup private array $setupUsers = []; + // List of users for which all mounts are setup + private array $setupUsersComplete = []; + /** @var array<string, string[]> */ + private array $setupUserMountProviders = []; private IEventDispatcher $eventDispatcher; private IUserMountCache $userMountCache; private ILockdownManager $lockdownManager; private IUserSession $userSession; + private ICache $cache; + private LoggerInterface $logger; + private IConfig $config; private bool $listeningForProviders; public function __construct( @@ -72,7 +90,10 @@ class SetupManager { IEventDispatcher $eventDispatcher, IUserMountCache $userMountCache, ILockdownManager $lockdownManager, - IUserSession $userSession + IUserSession $userSession, + ICacheFactory $cacheFactory, + LoggerInterface $logger, + IConfig $config ) { $this->eventLogger = $eventLogger; $this->mountProviderCollection = $mountProviderCollection; @@ -81,8 +102,21 @@ class SetupManager { $this->eventDispatcher = $eventDispatcher; $this->userMountCache = $userMountCache; $this->lockdownManager = $lockdownManager; + $this->logger = $logger; $this->userSession = $userSession; + $this->cache = $cacheFactory->createDistributed('setupmanager::'); $this->listeningForProviders = false; + $this->config = $config; + + $this->setupListeners(); + } + + private function isSetupStarted(IUser $user): bool { + return in_array($user->getUID(), $this->setupUsers, true); + } + + public function isSetupComplete(IUser $user): bool { + return in_array($user->getUID(), $this->setupUsersComplete, true); } private function setupBuiltinWrappers() { @@ -159,15 +193,33 @@ class SetupManager { * Setup the full filesystem for the specified user */ public function setupForUser(IUser $user): void { - $this->setupRoot(); + if ($this->isSetupComplete($user)) { + return; + } + $this->setupUsersComplete[] = $user->getUID(); + if (!isset($this->setupUserMountProviders[$user->getUID()])) { + $this->setupUserMountProviders[$user->getUID()] = []; + } + + $this->setupForUserWith($user, function () use ($user) { + $this->mountProviderCollection->addMountForUser($user, $this->mountManager, function ( + IMountProvider $provider + ) use ($user) { + return !in_array(get_class($provider), $this->setupUserMountProviders[$user->getUID()]); + }); + }); + $this->afterUserFullySetup($user); + } + + /** + * part of the user setup that is run only once per user + */ + private function oneTimeUserSetup(IUser $user) { if (in_array($user->getUID(), $this->setupUsers, true)) { return; } $this->setupUsers[] = $user->getUID(); - - $this->eventLogger->start('setup_fs', 'Setup filesystem'); - $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]); @@ -176,7 +228,7 @@ class SetupManager { $userDir = '/' . $user->getUID() . '/files'; - Filesystem::init($user, $userDir); + Filesystem::initInternal($userDir); if ($this->lockdownManager->canAccessFilesystem()) { // home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers @@ -187,13 +239,6 @@ class SetupManager { $homeMount->getStorage()->mkdir(''); $homeMount->getStorage()->getScanner()->scan(''); } - - // Chance to mount for other storages - $mounts = $this->mountProviderCollection->addMountForUser($user, $this->mountManager); - $mounts[] = $homeMount; - $this->userMountCache->registerMounts($user, $mounts); - - $this->listenForNewMountProviders(); } else { $this->mountManager->addMount(new MountPoint( new NullStorage([]), @@ -203,9 +248,51 @@ class SetupManager { new NullStorage([]), '/' . $user->getUID() . '/files' )); + $this->setupUsersComplete[] = $user->getUID(); + } + + $this->listenForNewMountProviders(); + } + + /** + * Final housekeeping after a user has been fully setup + */ + private function afterUserFullySetup(IUser $user): void { + $userRoot = '/' . $user->getUID() . '/'; + $mounts = $this->mountManager->getAll(); + $mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) { + return strpos($mount->getMountPoint(), $userRoot) === 0; + }); + $this->userMountCache->registerMounts($user, $mounts); + + $cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60); + if ($cacheDuration > 0) { + $this->cache->set($user->getUID(), true, $cacheDuration); + } + } + + /** + * @param IUser $user + * @param IMountPoint $mounts + * @return void + * @throws \OCP\HintException + * @throws \OC\ServerNotAvailableException + */ + private function setupForUserWith(IUser $user, callable $mountCallback): void { + $this->setupRoot(); + + if (!$this->isSetupStarted($user)) { + $this->oneTimeUserSetup($user); + } + + $this->eventLogger->start('setup_fs', 'Setup filesystem'); + + if ($this->lockdownManager->canAccessFilesystem()) { + $mountCallback(); } \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]); + $userDir = '/' . $user->getUID() . '/files'; OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]); $this->eventLogger->end('setup_fs'); @@ -240,35 +327,157 @@ class SetupManager { } /** - * Set up the filesystem for the specified path + * Get the user to setup for a path or `null` if the root needs to be setup + * + * @param string $path + * @return IUser|null */ - public function setupForPath(string $path): void { + private function getUserForPath(string $path) { if (substr_count($path, '/') < 2) { if ($user = $this->userSession->getUser()) { - $this->setupForUser($user); + return $user; } else { - $this->setupRoot(); + return null; } - return; } elseif (strpos($path, '/appdata_' . \OC_Util::getInstanceId()) === 0 || strpos($path, '/files_external/') === 0) { - $this->setupRoot(); - return; + return null; } else { [, $userId] = explode('/', $path); } - $user = $this->userManager->get($userId); + return $this->userManager->get($userId); + } + + /** + * Set up the filesystem for the specified path + */ + public function setupForPath(string $path, bool $includeChildren = false): void { + $user = $this->getUserForPath($path); + if (!$user) { + $this->setupRoot(); + return; + } + + if ($this->isSetupComplete($user)) { + return; + } + + if ($this->fullSetupRequired($user)) { + $this->setupForUser($user); + return; + } + + if (!isset($this->setupUserMountProviders[$user->getUID()])) { + $this->setupUserMountProviders[$user->getUID()] = []; + } + $setupProviders = &$this->setupUserMountProviders[$user->getUID()]; + $currentProviders = []; + + try { + $cachedMount = $this->userMountCache->getMountForPath($user, $path); + } catch (NotFoundException $e) { + $this->setupForUser($user); + return; + } + + $mounts = []; + if (!in_array($cachedMount->getMountProvider(), $setupProviders)) { + $setupProviders[] = $cachedMount->getMountProvider(); + $currentProviders[] = $cachedMount->getMountProvider(); + if ($cachedMount->getMountProvider()) { + $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]); + } else { + $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup"); + $this->setupForUser($user); + return; + } + } + if ($includeChildren) { + $subCachedMounts = $this->userMountCache->getMountsInPath($user, $path); + foreach ($subCachedMounts as $cachedMount) { + if (!in_array($cachedMount->getMountProvider(), $setupProviders)) { + $setupProviders[] = $cachedMount->getMountProvider(); + $currentProviders[] = $cachedMount->getMountProvider(); + if ($cachedMount->getMountProvider()) { + $mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()])); + } else { + $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup"); + $this->setupForUser($user); + return; + } + } + } + } + + if (count($mounts)) { + $this->userMountCache->registerMounts($user, $mounts, $currentProviders); + $this->setupForUserWith($user, function () use ($mounts) { + array_walk($mounts, [$this->mountManager, 'addMount']); + }); + } elseif (!$this->isSetupStarted($user)) { + $this->oneTimeUserSetup($user); + } + } + + private function fullSetupRequired(IUser $user): bool { + // we perform a "cached" setup only after having done the full setup recently + // this is also used to trigger a full setup after handling events that are likely + // to change the available mounts + return !$this->cache->get($user->getUID()); + } + + /** + * @param string $path + * @param string[] $providers + */ + public function setupForProvider(string $path, array $providers): void { + $user = $this->getUserForPath($path); if (!$user) { $this->setupRoot(); return; } - $this->setupForUser($user); + if ($this->isSetupComplete($user)) { + return; + } + + if ($this->fullSetupRequired($user)) { + $this->setupForUser($user); + return; + } + + // home providers are always used + $providers = array_filter($providers, function (string $provider) { + return !is_subclass_of($provider, IHomeMountProvider::class); + }); + + if (in_array('', $providers)) { + $this->setupForUser($user); + } + $setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? []; + + $providers = array_diff($providers, $setupProviders); + if (count($providers) === 0) { + if (!$this->isSetupStarted($user)) { + $this->oneTimeUserSetup($user); + } + return; + } else { + $this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers); + $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers); + } + + $this->userMountCache->registerMounts($user, $mounts, $providers); + $this->setupForUserWith($user, function () use ($mounts) { + array_walk($mounts, [$this->mountManager, 'addMount']); + }); } public function tearDown() { $this->setupUsers = []; + $this->setupUsersComplete = []; + $this->setupUserMountProviders = []; $this->rootSetup = false; $this->mountManager->clear(); $this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent()); @@ -280,7 +489,9 @@ class SetupManager { private function listenForNewMountProviders() { if (!$this->listeningForProviders) { $this->listeningForProviders = true; - $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (IMountProvider $provider) { + $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function ( + IMountProvider $provider + ) { foreach ($this->setupUsers as $userId) { $user = $this->userManager->get($userId); if ($user) { @@ -291,4 +502,40 @@ class SetupManager { }); } } + + private function setupListeners() { + // note that this event handling is intentionally pessimistic + // clearing the cache to often is better than not enough + + $this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) { + $this->cache->remove($event->getUser()->getUID()); + }); + $this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) { + $this->cache->remove($event->getUser()->getUID()); + }); + $this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) { + $this->cache->remove($event->getShare()->getSharedWith()); + }); + $this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event + ) { + if ($user = $event->getUser()) { + $this->cache->remove($user->getUID()); + } else { + $this->cache->clear(); + } + }); + + $genericEvents = [ + '\OCA\Circles::onCircleCreation', + '\OCA\Circles::onCircleDestruction', + '\OCA\Circles::onMemberNew', + '\OCA\Circles::onMemberLeaving', + ]; + + foreach ($genericEvents as $genericEvent) { + $this->eventDispatcher->addListener($genericEvent, function ($event) { + $this->cache->clear(); + }); + } + } } diff --git a/lib/private/Files/SetupManagerFactory.php b/lib/private/Files/SetupManagerFactory.php index 56e70d09961..1d9efbd411f 100644 --- a/lib/private/Files/SetupManagerFactory.php +++ b/lib/private/Files/SetupManagerFactory.php @@ -28,9 +28,12 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Config\IMountProviderCollection; use OCP\Files\Config\IUserMountCache; use OCP\Files\Mount\IMountManager; +use OCP\ICacheFactory; +use OCP\IConfig; use OCP\IUserManager; use OCP\IUserSession; use OCP\Lockdown\ILockdownManager; +use Psr\Log\LoggerInterface; class SetupManagerFactory { private IEventLogger $eventLogger; @@ -41,6 +44,9 @@ class SetupManagerFactory { private ILockdownManager $lockdownManager; private IUserSession $userSession; private ?SetupManager $setupManager; + private ICacheFactory $cacheFactory; + private LoggerInterface $logger; + private IConfig $config; public function __construct( IEventLogger $eventLogger, @@ -49,7 +55,10 @@ class SetupManagerFactory { IEventDispatcher $eventDispatcher, IUserMountCache $userMountCache, ILockdownManager $lockdownManager, - IUserSession $userSession + IUserSession $userSession, + ICacheFactory $cacheFactory, + LoggerInterface $logger, + IConfig $config ) { $this->eventLogger = $eventLogger; $this->mountProviderCollection = $mountProviderCollection; @@ -58,6 +67,9 @@ class SetupManagerFactory { $this->userMountCache = $userMountCache; $this->lockdownManager = $lockdownManager; $this->userSession = $userSession; + $this->cacheFactory = $cacheFactory; + $this->logger = $logger; + $this->config = $config; $this->setupManager = null; } @@ -72,6 +84,9 @@ class SetupManagerFactory { $this->userMountCache, $this->lockdownManager, $this->userSession, + $this->cacheFactory, + $this->logger, + $this->config ); } return $this->setupManager; diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php index b4a85755b20..ee874d97b55 100644 --- a/lib/private/Files/Storage/DAV.php +++ b/lib/private/Files/Storage/DAV.php @@ -587,8 +587,8 @@ class DAV extends Common { return false; } return [ - 'mtime' => strtotime($response['{DAV:}getlastmodified']), - 'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0, + 'mtime' => isset($response['{DAV:}getlastmodified']) ? strtotime($response['{DAV:}getlastmodified']) : null, + 'size' => (int)($response['{DAV:}getcontentlength'] ?? 0), ]; } catch (\Exception $e) { $this->convertException($e, $path); @@ -804,9 +804,12 @@ class DAV extends Common { } else { return false; } - } else { + } elseif (isset($response['{DAV:}getlastmodified'])) { $remoteMtime = strtotime($response['{DAV:}getlastmodified']); return $remoteMtime > $time; + } else { + // neither `getetag` nor `getlastmodified` is set + return false; } } catch (ClientHttpException $e) { if ($e->getHttpStatus() === 405) { diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 75611d1ab0f..4cfe932cc9f 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -50,7 +50,7 @@ use OCP\Encryption\Keys\IStorage; use OCP\Files\Cache\ICacheEntry; use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage; -use OCP\ILogger; +use Psr\Log\LoggerInterface; class Encryption extends Wrapper { use LocalTempFileTrait; @@ -64,8 +64,7 @@ class Encryption extends Wrapper { /** @var \OCP\Encryption\IManager */ private $encryptionManager; - /** @var \OCP\ILogger */ - private $logger; + private LoggerInterface $logger; /** @var string */ private $uid; @@ -96,21 +95,12 @@ class Encryption extends Wrapper { /** * @param array $parameters - * @param IManager $encryptionManager - * @param Util $util - * @param ILogger $logger - * @param IFile $fileHelper - * @param string $uid - * @param IStorage $keyStorage - * @param Update $update - * @param Manager $mountManager - * @param ArrayCache $arrayCache */ public function __construct( $parameters, IManager $encryptionManager = null, Util $util = null, - ILogger $logger = null, + LoggerInterface $logger = null, IFile $fileHelper = null, $uid = null, IStorage $keyStorage = null, @@ -448,9 +438,8 @@ class Encryption extends Wrapper { } } } catch (ModuleDoesNotExistsException $e) { - $this->logger->logException($e, [ - 'message' => 'Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted', - 'level' => ILogger::WARN, + $this->logger->warning('Encryption module "' . $encryptionModuleId . '" not found, file will be stored unencrypted', [ + 'exception' => $e, 'app' => 'core', ]); } @@ -503,8 +492,7 @@ class Encryption extends Wrapper { try { $result = $this->fixUnencryptedSize($path, $size, $unencryptedSize); } catch (\Exception $e) { - $this->logger->error('Couldn\'t re-calculate unencrypted size for ' . $path); - $this->logger->logException($e); + $this->logger->error('Couldn\'t re-calculate unencrypted size for ' . $path, ['exception' => $e]); } unset($this->fixUnencryptedSizeOf[$this->getFullPath($path)]); } diff --git a/lib/private/Files/Type/Detection.php b/lib/private/Files/Type/Detection.php index bd2bfb5f939..4cade024460 100644 --- a/lib/private/Files/Type/Detection.php +++ b/lib/private/Files/Type/Detection.php @@ -42,8 +42,8 @@ declare(strict_types=1); namespace OC\Files\Type; use OCP\Files\IMimeTypeDetector; -use OCP\ILogger; use OCP\IURLGenerator; +use Psr\Log\LoggerInterface; /** * Class Detection @@ -66,8 +66,7 @@ class Detection implements IMimeTypeDetector { /** @var IURLGenerator */ private $urlGenerator; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var string */ private $customConfigDir; @@ -75,14 +74,8 @@ class Detection implements IMimeTypeDetector { /** @var string */ private $defaultConfigDir; - /** - * @param IURLGenerator $urlGenerator - * @param ILogger $logger - * @param string $customConfigDir - * @param string $defaultConfigDir - */ public function __construct(IURLGenerator $urlGenerator, - ILogger $logger, + LoggerInterface $logger, string $customConfigDir, string $defaultConfigDir) { $this->urlGenerator = $urlGenerator; diff --git a/lib/private/Files/Utils/PathHelper.php b/lib/private/Files/Utils/PathHelper.php new file mode 100644 index 00000000000..07985e884ce --- /dev/null +++ b/lib/private/Files/Utils/PathHelper.php @@ -0,0 +1,71 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Files\Utils; + +class PathHelper { + /** + * Make a path relative to a root path, or return null if the path is outside the root + * + * @param string $root + * @param string $path + * @return ?string + */ + public static function getRelativePath(string $root, string $path) { + if ($root === '' or $root === '/') { + return self::normalizePath($path); + } + if ($path === $root) { + return '/'; + } elseif (strpos($path, $root . '/') !== 0) { + return null; + } else { + $path = substr($path, strlen($root)); + return self::normalizePath($path); + } + } + + /** + * @param string $path + * @return string + */ + public static function normalizePath(string $path): string { + if ($path === '' or $path === '/') { + return '/'; + } + //no windows style slashes + $path = str_replace('\\', '/', $path); + //add leading slash + if ($path[0] !== '/') { + $path = '/' . $path; + } + //remove duplicate slashes + while (strpos($path, '//') !== false) { + $path = str_replace('//', '/', $path); + } + //remove trailing slash + $path = rtrim($path, '/'); + + return $path; + } +} diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php index 5b633617608..dc220bc710d 100644 --- a/lib/private/Files/Utils/Scanner.php +++ b/lib/private/Files/Utils/Scanner.php @@ -48,7 +48,7 @@ use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; use OCP\Files\StorageNotAvailableException; use OCP\IDBConnection; -use OCP\ILogger; +use Psr\Log\LoggerInterface; /** * Class Scanner @@ -71,8 +71,7 @@ class Scanner extends PublicEmitter { /** @var IEventDispatcher */ private $dispatcher; - /** @var ILogger */ - protected $logger; + protected LoggerInterface $logger; /** * Whether to use a DB transaction @@ -92,9 +91,8 @@ class Scanner extends PublicEmitter { * @param string $user * @param IDBConnection|null $db * @param IEventDispatcher $dispatcher - * @param ILogger $logger */ - public function __construct($user, $db, IEventDispatcher $dispatcher, ILogger $logger) { + public function __construct($user, $db, IEventDispatcher $dispatcher, LoggerInterface $logger) { $this->user = $user; $this->db = $db; $this->dispatcher = $dispatcher; @@ -263,8 +261,7 @@ class Scanner extends PublicEmitter { } $propagator->commitBatch(); } catch (StorageNotAvailableException $e) { - $this->logger->error('Storage ' . $storage->getId() . ' not available'); - $this->logger->logException($e); + $this->logger->error('Storage ' . $storage->getId() . ' not available', ['exception' => $e]); $this->emit('\OC\Files\Utils\Scanner', 'StorageNotAvailable', [$e]); } if ($this->useTransaction) { diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 779e0611591..e23dd4312aa 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1099,6 +1099,7 @@ class View { [Filesystem::signal_param_path => $this->getHookPath($path)] ); } + /** @var Storage|null $storage */ [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix); if ($storage) { return $storage->hash($type, $internalPath, $raw); @@ -1179,7 +1180,7 @@ class View { throw $e; } - if ($result && in_array('delete', $hooks) and $result) { + if ($result && in_array('delete', $hooks)) { $this->removeUpdate($storage, $internalPath); } if ($result && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') { @@ -1435,124 +1436,122 @@ class View { if (!Filesystem::isValidPath($directory)) { return []; } + $path = $this->getAbsolutePath($directory); $path = Filesystem::normalizePath($path); $mount = $this->getMount($directory); - if (!$mount) { - return []; - } $storage = $mount->getStorage(); $internalPath = $mount->getInternalPath($path); - if ($storage) { - $cache = $storage->getCache($internalPath); - $user = \OC_User::getUser(); + if (!$storage) { + return []; + } - $data = $this->getCacheEntry($storage, $internalPath, $directory); + $cache = $storage->getCache($internalPath); + $user = \OC_User::getUser(); - if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) { - return []; - } + $data = $this->getCacheEntry($storage, $internalPath, $directory); - $folderId = $data['fileid']; - $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter + if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() & Constants::PERMISSION_READ)) { + return []; + } - $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); + $folderId = $data['fileid']; + $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter - $fileNames = array_map(function (ICacheEntry $content) { - return $content->getName(); - }, $contents); - /** - * @var \OC\Files\FileInfo[] $fileInfos - */ - $fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) { - if ($sharingDisabled) { - $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; - } - $owner = $this->getUserObjectForOwner($storage->getOwner($content['path'])); - return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner); - }, $contents); - $files = array_combine($fileNames, $fileInfos); - - //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders - $mounts = Filesystem::getMountManager()->findIn($path); - $dirLength = strlen($path); - foreach ($mounts as $mount) { - $mountPoint = $mount->getMountPoint(); - $subStorage = $mount->getStorage(); - if ($subStorage) { - $subCache = $subStorage->getCache(''); + $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); - $rootEntry = $subCache->get(''); - if (!$rootEntry) { - $subScanner = $subStorage->getScanner(); - try { - $subScanner->scanFile(''); - } catch (\OCP\Files\StorageNotAvailableException $e) { - continue; - } catch (\OCP\Files\StorageInvalidException $e) { - continue; - } catch (\Exception $e) { - // sometimes when the storage is not available it can be any exception - \OC::$server->getLogger()->logException($e, [ - 'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"', - 'level' => ILogger::ERROR, - 'app' => 'lib', - ]); - continue; - } - $rootEntry = $subCache->get(''); + $fileNames = array_map(function (ICacheEntry $content) { + return $content->getName(); + }, $contents); + /** + * @var \OC\Files\FileInfo[] $fileInfos + */ + $fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) { + if ($sharingDisabled) { + $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; + } + $owner = $this->getUserObjectForOwner($storage->getOwner($content['path'])); + return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner); + }, $contents); + $files = array_combine($fileNames, $fileInfos); + + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mounts = Filesystem::getMountManager()->findIn($path); + $dirLength = strlen($path); + foreach ($mounts as $mount) { + $mountPoint = $mount->getMountPoint(); + $subStorage = $mount->getStorage(); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + + $rootEntry = $subCache->get(''); + if (!$rootEntry) { + $subScanner = $subStorage->getScanner(); + try { + $subScanner->scanFile(''); + } catch (\OCP\Files\StorageNotAvailableException $e) { + continue; + } catch (\OCP\Files\StorageInvalidException $e) { + continue; + } catch (\Exception $e) { + // sometimes when the storage is not available it can be any exception + \OC::$server->getLogger()->logException($e, [ + 'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"', + 'level' => ILogger::ERROR, + 'app' => 'lib', + ]); + continue; } + $rootEntry = $subCache->get(''); + } - if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) { - $relativePath = trim(substr($mountPoint, $dirLength), '/'); - if ($pos = strpos($relativePath, '/')) { - //mountpoint inside subfolder add size to the correct folder - $entryName = substr($relativePath, 0, $pos); - foreach ($files as &$entry) { - if ($entry->getName() === $entryName) { - $entry->addSubEntry($rootEntry, $mountPoint); - } + if ($rootEntry && ($rootEntry->getPermissions() & Constants::PERMISSION_READ)) { + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { + //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach ($files as &$entry) { + if ($entry->getName() === $entryName) { + $entry->addSubEntry($rootEntry, $mountPoint); } - } else { //mountpoint in this folder, add an entry for it - $rootEntry['name'] = $relativePath; - $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; - $permissions = $rootEntry['permissions']; - // do not allow renaming/deleting the mount point if they are not shared files/folders - // for shared files/folders we use the permissions given by the owner - if ($mount instanceof MoveableMount) { - $rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; - } else { - $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)); - } - - $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ + } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $permissions = $rootEntry['permissions']; + // do not allow renaming/deleting the mount point if they are not shared files/folders + // for shared files/folders we use the permissions given by the owner + if ($mount instanceof MoveableMount) { + $rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE; + } else { + $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)); + } - // if sharing was disabled for the user we remove the share permissions - if (\OCP\Util::isSharingDisabledForUser()) { - $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; - } + $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ - $owner = $this->getUserObjectForOwner($subStorage->getOwner('')); - $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); + // if sharing was disabled for the user we remove the share permissions + if (\OCP\Util::isSharingDisabledForUser()) { + $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; } - } - } - } - if ($mimetype_filter) { - $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) { - if (strpos($mimetype_filter, '/')) { - return $file->getMimetype() === $mimetype_filter; - } else { - return $file->getMimePart() === $mimetype_filter; + $owner = $this->getUserObjectForOwner($subStorage->getOwner('')); + $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); } - }); + } } + } - return array_values($files); - } else { - return []; + if ($mimetype_filter) { + $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) { + if (strpos($mimetype_filter, '/')) { + return $file->getMimetype() === $mimetype_filter; + } else { + return $file->getMimePart() === $mimetype_filter; + } + }); } + + return array_values($files); } /** diff --git a/lib/private/Group/Group.php b/lib/private/Group/Group.php index 9a9996f7f60..2ef4d2ee23f 100644 --- a/lib/private/Group/Group.php +++ b/lib/private/Group/Group.php @@ -354,8 +354,7 @@ class Group implements IGroup { } foreach ($this->backends as $backend) { if ($backend->implementsActions(\OC\Group\Backend::DELETE_GROUP)) { - $result = true; - $backend->deleteGroup($this->gid); + $result = $result || $backend->deleteGroup($this->gid); } } if ($result) { diff --git a/lib/private/Group/Manager.php b/lib/private/Group/Manager.php index e1bd009e79c..28f7a400b41 100644 --- a/lib/private/Group/Manager.php +++ b/lib/private/Group/Manager.php @@ -44,8 +44,8 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\GroupInterface; use OCP\IGroup; use OCP\IGroupManager; -use OCP\ILogger; use OCP\IUser; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -71,8 +71,7 @@ class Manager extends PublicEmitter implements IGroupManager { private $userManager; /** @var EventDispatcherInterface */ private $dispatcher; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var \OC\Group\Group[] */ private $cachedGroups = []; @@ -83,14 +82,9 @@ class Manager extends PublicEmitter implements IGroupManager { /** @var \OC\SubAdmin */ private $subAdmin = null; - /** - * @param \OC\User\Manager $userManager - * @param EventDispatcherInterface $dispatcher - * @param ILogger $logger - */ public function __construct(\OC\User\Manager $userManager, EventDispatcherInterface $dispatcher, - ILogger $logger) { + LoggerInterface $logger) { $this->userManager = $userManager; $this->dispatcher = $dispatcher; $this->logger = $logger; diff --git a/lib/private/Http/Client/Client.php b/lib/private/Http/Client/Client.php index 673f566e354..3ba85a2dd9f 100644 --- a/lib/private/Http/Client/Client.php +++ b/lib/private/Http/Client/Client.php @@ -39,7 +39,6 @@ use OCP\Http\Client\IClient; use OCP\Http\Client\IResponse; use OCP\ICertificateManager; use OCP\IConfig; -use OCP\ILogger; /** * Class Client @@ -51,8 +50,6 @@ class Client implements IClient { private $client; /** @var IConfig */ private $config; - /** @var ILogger */ - private $logger; /** @var ICertificateManager */ private $certificateManager; /** @var LocalAddressChecker */ @@ -60,13 +57,11 @@ class Client implements IClient { public function __construct( IConfig $config, - ILogger $logger, ICertificateManager $certificateManager, GuzzleClient $client, LocalAddressChecker $localAddressChecker ) { $this->config = $config; - $this->logger = $logger; $this->client = $client; $this->certificateManager = $certificateManager; $this->localAddressChecker = $localAddressChecker; diff --git a/lib/private/Http/Client/ClientService.php b/lib/private/Http/Client/ClientService.php index d1aed2d7883..e868d4af7a5 100644 --- a/lib/private/Http/Client/ClientService.php +++ b/lib/private/Http/Client/ClientService.php @@ -33,7 +33,6 @@ use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; use OCP\ICertificateManager; use OCP\IConfig; -use OCP\ILogger; /** * Class ClientService @@ -43,8 +42,6 @@ use OCP\ILogger; class ClientService implements IClientService { /** @var IConfig */ private $config; - /** @var ILogger */ - private $logger; /** @var ICertificateManager */ private $certificateManager; /** @var DnsPinMiddleware */ @@ -53,12 +50,10 @@ class ClientService implements IClientService { private $localAddressChecker; public function __construct(IConfig $config, - ILogger $logger, ICertificateManager $certificateManager, DnsPinMiddleware $dnsPinMiddleware, LocalAddressChecker $localAddressChecker) { $this->config = $config; - $this->logger = $logger; $this->certificateManager = $certificateManager; $this->dnsPinMiddleware = $dnsPinMiddleware; $this->localAddressChecker = $localAddressChecker; @@ -76,7 +71,6 @@ class ClientService implements IClientService { return new Client( $this->config, - $this->logger, $this->certificateManager, $client, $this->localAddressChecker diff --git a/lib/private/Http/Client/LocalAddressChecker.php b/lib/private/Http/Client/LocalAddressChecker.php index c534c5971d1..2789b1b5935 100644 --- a/lib/private/Http/Client/LocalAddressChecker.php +++ b/lib/private/Http/Client/LocalAddressChecker.php @@ -25,14 +25,13 @@ declare(strict_types=1); */ namespace OC\Http\Client; -use OCP\ILogger; use OCP\Http\Client\LocalServerException; +use Psr\Log\LoggerInterface; class LocalAddressChecker { - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; - public function __construct(ILogger $logger) { + public function __construct(LoggerInterface $logger) { $this->logger = $logger; } diff --git a/lib/private/Lock/DBLockingProvider.php b/lib/private/Lock/DBLockingProvider.php index 1ea78d047d2..5eb03ad856b 100644 --- a/lib/private/Lock/DBLockingProvider.php +++ b/lib/private/Lock/DBLockingProvider.php @@ -31,9 +31,9 @@ use OC\DB\QueryBuilder\Literal; use OCP\AppFramework\Utility\ITimeFactory; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; -use OCP\ILogger; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; +use Psr\Log\LoggerInterface; /** * Locking provider that stores the locks in the database @@ -44,10 +44,7 @@ class DBLockingProvider extends AbstractLockingProvider { */ private $connection; - /** - * @var \OCP\ILogger - */ - private $logger; + private LoggerInterface $logger; /** * @var \OCP\AppFramework\Utility\ITimeFactory @@ -104,15 +101,11 @@ class DBLockingProvider extends AbstractLockingProvider { } /** - * @param \OCP\IDBConnection $connection - * @param \OCP\ILogger $logger - * @param \OCP\AppFramework\Utility\ITimeFactory $timeFactory - * @param int $ttl * @param bool $cacheSharedLocks */ public function __construct( IDBConnection $connection, - ILogger $logger, + LoggerInterface $logger, ITimeFactory $timeFactory, int $ttl = 3600, $cacheSharedLocks = true diff --git a/lib/private/Mail/Mailer.php b/lib/private/Mail/Mailer.php index 61f506d2676..2f3480498be 100644 --- a/lib/private/Mail/Mailer.php +++ b/lib/private/Mail/Mailer.php @@ -41,7 +41,6 @@ use OCP\Defaults; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IL10N; -use OCP\ILogger; use OCP\IURLGenerator; use OCP\L10N\IFactory; use OCP\Mail\Events\BeforeMessageSent; @@ -49,6 +48,7 @@ use OCP\Mail\IAttachment; use OCP\Mail\IEMailTemplate; use OCP\Mail\IMailer; use OCP\Mail\IMessage; +use Psr\Log\LoggerInterface; /** * Class Mailer provides some basic functions to create a mail message that can be used in combination with @@ -73,8 +73,7 @@ class Mailer implements IMailer { private $instance = null; /** @var IConfig */ private $config; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var Defaults */ private $defaults; /** @var IURLGenerator */ @@ -86,16 +85,8 @@ class Mailer implements IMailer { /** @var IFactory */ private $l10nFactory; - /** - * @param IConfig $config - * @param ILogger $logger - * @param Defaults $defaults - * @param IURLGenerator $urlGenerator - * @param IL10N $l10n - * @param IEventDispatcher $dispatcher - */ public function __construct(IConfig $config, - ILogger $logger, + LoggerInterface $logger, Defaults $defaults, IURLGenerator $urlGenerator, IL10N $l10n, diff --git a/lib/private/Memcache/APCu.php b/lib/private/Memcache/APCu.php index 56345890bf2..f0eb98b9db2 100644 --- a/lib/private/Memcache/APCu.php +++ b/lib/private/Memcache/APCu.php @@ -148,10 +148,7 @@ class APCu extends Cache implements IMemcache { } } - /** - * @return bool - */ - public static function isAvailable() { + public static function isAvailable(): bool { if (!extension_loaded('apcu')) { return false; } elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enabled')) { diff --git a/lib/private/Memcache/ArrayCache.php b/lib/private/Memcache/ArrayCache.php index b89aff0b7ed..13597a068b3 100644 --- a/lib/private/Memcache/ArrayCache.php +++ b/lib/private/Memcache/ArrayCache.php @@ -153,7 +153,7 @@ class ArrayCache extends Cache implements IMemcache { /** * {@inheritDoc} */ - public static function isAvailable() { + public static function isAvailable(): bool { return true; } } diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php index 7791c4beae9..604f764c03c 100644 --- a/lib/private/Memcache/Factory.php +++ b/lib/private/Memcache/Factory.php @@ -31,52 +31,48 @@ */ namespace OC\Memcache; +use OCP\Profiler\IProfiler; use OCP\ICache; use OCP\ICacheFactory; -use OCP\ILogger; use OCP\IMemcache; +use Psr\Log\LoggerInterface; class Factory implements ICacheFactory { public const NULL_CACHE = NullCache::class; - /** - * @var string $globalPrefix - */ - private $globalPrefix; + private string $globalPrefix; - /** - * @var ILogger $logger - */ - private $logger; + private LoggerInterface $logger; /** - * @var string $localCacheClass + * @var ?class-string<ICache> $localCacheClass */ - private $localCacheClass; + private ?string $localCacheClass; /** - * @var string $distributedCacheClass + * @var ?class-string<ICache> $distributedCacheClass */ - private $distributedCacheClass; + private ?string $distributedCacheClass; /** - * @var string $lockingCacheClass + * @var ?class-string<IMemcache> $lockingCacheClass */ - private $lockingCacheClass; + private ?string $lockingCacheClass; - /** @var string */ - private $logFile; + private string $logFile; + + private IProfiler $profiler; /** * @param string $globalPrefix - * @param ILogger $logger - * @param string|null $localCacheClass - * @param string|null $distributedCacheClass - * @param string|null $lockingCacheClass + * @param LoggerInterface $logger + * @param ?class-string<ICache> $localCacheClass + * @param ?class-string<ICache> $distributedCacheClass + * @param ?class-string<IMemcache> $lockingCacheClass * @param string $logFile */ - public function __construct(string $globalPrefix, ILogger $logger, - $localCacheClass = null, $distributedCacheClass = null, $lockingCacheClass = null, string $logFile = '') { + public function __construct(string $globalPrefix, LoggerInterface $logger, IProfiler $profiler, + ?string $localCacheClass = null, ?string $distributedCacheClass = null, ?string $lockingCacheClass = null, string $logFile = '') { $this->logger = $logger; $this->logFile = $logFile; $this->globalPrefix = $globalPrefix; @@ -108,6 +104,7 @@ class Factory implements ICacheFactory { $this->localCacheClass = $localCacheClass; $this->distributedCacheClass = $distributedCacheClass; $this->lockingCacheClass = $lockingCacheClass; + $this->profiler = $profiler; } /** @@ -117,7 +114,19 @@ class Factory implements ICacheFactory { * @return IMemcache */ public function createLocking(string $prefix = ''): IMemcache { - return new $this->lockingCacheClass($this->globalPrefix . '/' . $prefix, $this->logFile); + assert($this->lockingCacheClass !== null); + $cache = new $this->lockingCacheClass($this->globalPrefix . '/' . $prefix); + if ($this->profiler->isEnabled() && $this->lockingCacheClass === '\OC\Memcache\Redis') { + // We only support the profiler with Redis + $cache = new ProfilerWrapperCache($cache, 'Locking'); + $this->profiler->add($cache); + } + + if ($this->lockingCacheClass === Redis::class && + $this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { + $cache = new LoggerWrapperCache($cache, $this->logFile); + } + return $cache; } /** @@ -127,7 +136,19 @@ class Factory implements ICacheFactory { * @return ICache */ public function createDistributed(string $prefix = ''): ICache { - return new $this->distributedCacheClass($this->globalPrefix . '/' . $prefix, $this->logFile); + assert($this->distributedCacheClass !== null); + $cache = new $this->distributedCacheClass($this->globalPrefix . '/' . $prefix); + if ($this->profiler->isEnabled() && $this->distributedCacheClass === '\OC\Memcache\Redis') { + // We only support the profiler with Redis + $cache = new ProfilerWrapperCache($cache, 'Distributed'); + $this->profiler->add($cache); + } + + if ($this->distributedCacheClass === Redis::class && $this->logFile !== '' + && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { + $cache = new LoggerWrapperCache($cache, $this->logFile); + } + return $cache; } /** @@ -137,7 +158,19 @@ class Factory implements ICacheFactory { * @return ICache */ public function createLocal(string $prefix = ''): ICache { - return new $this->localCacheClass($this->globalPrefix . '/' . $prefix, $this->logFile); + assert($this->localCacheClass !== null); + $cache = new $this->localCacheClass($this->globalPrefix . '/' . $prefix); + if ($this->profiler->isEnabled() && $this->localCacheClass === '\OC\Memcache\Redis') { + // We only support the profiler with Redis + $cache = new ProfilerWrapperCache($cache, 'Local'); + $this->profiler->add($cache); + } + + if ($this->localCacheClass === Redis::class && $this->logFile !== '' + && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { + $cache = new LoggerWrapperCache($cache, $this->logFile); + } + return $cache; } /** diff --git a/lib/private/Memcache/LoggerWrapperCache.php b/lib/private/Memcache/LoggerWrapperCache.php new file mode 100644 index 00000000000..55c0e76db79 --- /dev/null +++ b/lib/private/Memcache/LoggerWrapperCache.php @@ -0,0 +1,177 @@ +<?php + +declare(strict_types = 1); +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Memcache; + +use OCP\IMemcacheTTL; + +/** + * Cache wrapper that logs the cache operation in a log file + */ +class LoggerWrapperCache extends Cache implements IMemcacheTTL { + /** @var Redis */ + protected $wrappedCache; + + /** @var string $logFile */ + private $logFile; + + /** @var string $prefix */ + protected $prefix; + + public function __construct(Redis $wrappedCache, string $logFile) { + parent::__construct($wrappedCache->getPrefix()); + $this->wrappedCache = $wrappedCache; + $this->logFile = $logFile; + } + + /** + * @return string Prefix used for caching purposes + */ + public function getPrefix() { + return $this->prefix; + } + + protected function getNameSpace() { + return $this->prefix; + } + + /** @inheritDoc */ + public function get($key) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::get::' . $key . "\n", + FILE_APPEND + ); + return $this->wrappedCache->get($key); + } + + /** @inheritDoc */ + public function set($key, $value, $ttl = 0) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::set::' . $key . '::' . $ttl . '::' . json_encode($value) . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->set($key, $value, $$ttl); + } + + /** @inheritDoc */ + public function hasKey($key) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::hasKey::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->hasKey($key); + } + + /** @inheritDoc */ + public function remove($key) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::remove::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->remove($key); + } + + /** @inheritDoc */ + public function clear($prefix = '') { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::clear::' . $prefix . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->clear($prefix); + } + + /** @inheritDoc */ + public function add($key, $value, $ttl = 0) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::add::' . $key . '::' . $value . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->add($key, $value, $ttl); + } + + /** @inheritDoc */ + public function inc($key, $step = 1) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::inc::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->inc($key, $step); + } + + /** @inheritDoc */ + public function dec($key, $step = 1) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::dec::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->dec($key, $step); + } + + /** @inheritDoc */ + public function cas($key, $old, $new) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::cas::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->cas($key, $old, $new); + } + + /** @inheritDoc */ + public function cad($key, $old) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::cad::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->cad($key, $old); + } + + /** @inheritDoc */ + public function setTTL($key, $ttl) { + $this->wrappedCache->setTTL($key, $ttl); + } + + public static function isAvailable(): bool { + return true; + } +} diff --git a/lib/private/Memcache/Memcached.php b/lib/private/Memcache/Memcached.php index f78be581d63..db4aa7ba9cc 100644 --- a/lib/private/Memcache/Memcached.php +++ b/lib/private/Memcache/Memcached.php @@ -196,7 +196,7 @@ class Memcached extends Cache implements IMemcache { return $result; } - public static function isAvailable() { + public static function isAvailable(): bool { return extension_loaded('memcached'); } diff --git a/lib/private/Memcache/NullCache.php b/lib/private/Memcache/NullCache.php index 7b56ec932f4..fc41595dfe1 100644 --- a/lib/private/Memcache/NullCache.php +++ b/lib/private/Memcache/NullCache.php @@ -67,7 +67,7 @@ class NullCache extends Cache implements \OCP\IMemcache { return true; } - public static function isAvailable() { + public static function isAvailable(): bool { return true; } } diff --git a/lib/private/Memcache/ProfilerWrapperCache.php b/lib/private/Memcache/ProfilerWrapperCache.php new file mode 100644 index 00000000000..8e9b160ba0e --- /dev/null +++ b/lib/private/Memcache/ProfilerWrapperCache.php @@ -0,0 +1,220 @@ +<?php + +declare(strict_types = 1); +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Memcache; + +use OC\AppFramework\Http\Request; +use OCP\AppFramework\Http\Response; +use OCP\DataCollector\AbstractDataCollector; +use OCP\IMemcacheTTL; + +/** + * Cache wrapper that logs profiling information + */ +class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL, \ArrayAccess { + /** @var Redis $wrappedCache*/ + protected $wrappedCache; + + /** @var string $prefix */ + protected $prefix; + + /** @var string $type */ + private $type; + + public function __construct(Redis $wrappedCache, string $type) { + $this->prefix = $wrappedCache->getPrefix(); + $this->wrappedCache = $wrappedCache; + $this->type = $type; + $this->data['queries'] = []; + $this->data['cacheHit'] = 0; + $this->data['cacheMiss'] = 0; + } + + public function getPrefix(): string { + return $this->prefix; + } + + /** @inheritDoc */ + public function get($key) { + $start = microtime(true); + $ret = $this->wrappedCache->get($key); + if ($ret === null) { + $this->data['cacheMiss']++; + } else { + $this->data['cacheHit']++; + } + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::get::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function set($key, $value, $ttl = 0) { + $start = microtime(true); + $ret = $this->wrappedCache->set($key, $value, $ttl); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::set::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function hasKey($key) { + $start = microtime(true); + $ret = $this->wrappedCache->hasKey($key); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::hasKey::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function remove($key) { + $start = microtime(true); + $ret = $this->wrappedCache->remove($key); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::remove::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function clear($prefix = '') { + $start = microtime(true); + $ret = $this->wrappedCache->clear($prefix); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::clear::' . $prefix, + ]; + return $ret; + } + + /** @inheritDoc */ + public function add($key, $value, $ttl = 0) { + $start = microtime(true); + $ret = $this->wrappedCache->add($key, $value, $ttl); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::add::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function inc($key, $step = 1) { + $start = microtime(true); + $ret = $this->wrappedCache->inc($key, $step); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::inc::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function dec($key, $step = 1) { + $start = microtime(true); + $ret = $this->wrappedCache->dec($key, $step); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::dev::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function cas($key, $old, $new) { + $start = microtime(true); + $ret = $this->wrappedCache->cas($key, $old, $new); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::cas::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function cad($key, $old) { + $start = microtime(true); + $ret = $this->wrappedCache->cad($key, $old); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::cad::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function setTTL($key, $ttl) { + $this->wrappedCache->setTTL($key, $ttl); + } + + public function offsetExists($offset): bool { + return $this->hasKey($offset); + } + + public function offsetSet($offset, $value): void { + $this->set($offset, $value); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) { + return $this->get($offset); + } + + public function offsetUnset($offset): void { + $this->remove($offset); + } + + public function collect(Request $request, Response $response, \Throwable $exception = null): void { + // Nothing to do here $data is already ready + } + + public function getName(): string { + return 'cache/' . $this->type . '/' . $this->prefix; + } + + public static function isAvailable(): bool { + return true; + } +} diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php index 63180dd8066..9b07da2d99c 100644 --- a/lib/private/Memcache/Redis.php +++ b/lib/private/Memcache/Redis.php @@ -37,38 +37,16 @@ class Redis extends Cache implements IMemcacheTTL { */ private static $cache = null; - private $logFile; - public function __construct($prefix = '', string $logFile = '') { parent::__construct($prefix); - $this->logFile = $logFile; if (is_null(self::$cache)) { self::$cache = \OC::$server->getGetRedisFactory()->getInstance(); } } - /** - * entries in redis get namespaced to prevent collisions between ownCloud instances and users - */ - protected function getNameSpace() { - return $this->prefix; - } - - private function logEnabled(): bool { - return $this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile)); - } - public function get($key) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::get::' . $key . "\n", - FILE_APPEND - ); - } - - $result = self::$cache->get($this->getNameSpace() . $key); - if ($result === false && !self::$cache->exists($this->getNameSpace() . $key)) { + $result = self::$cache->get($this->getPrefix() . $key); + if ($result === false && !self::$cache->exists($this->getPrefix() . $key)) { return null; } else { return json_decode($result, true); @@ -76,43 +54,19 @@ class Redis extends Cache implements IMemcacheTTL { } public function set($key, $value, $ttl = 0) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::set::' . $key . '::' . $ttl . '::' . json_encode($value) . "\n", - FILE_APPEND - ); - } - if ($ttl > 0) { - return self::$cache->setex($this->getNameSpace() . $key, $ttl, json_encode($value)); + return self::$cache->setex($this->getPrefix() . $key, $ttl, json_encode($value)); } else { - return self::$cache->set($this->getNameSpace() . $key, json_encode($value)); + return self::$cache->set($this->getPrefix() . $key, json_encode($value)); } } public function hasKey($key) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::hasKey::' . $key . "\n", - FILE_APPEND - ); - } - - return (bool)self::$cache->exists($this->getNameSpace() . $key); + return (bool)self::$cache->exists($this->getPrefix() . $key); } public function remove($key) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::remove::' . $key . "\n", - FILE_APPEND - ); - } - - if (self::$cache->del($this->getNameSpace() . $key)) { + if (self::$cache->del($this->getPrefix() . $key)) { return true; } else { return false; @@ -120,15 +74,7 @@ class Redis extends Cache implements IMemcacheTTL { } public function clear($prefix = '') { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::clear::' . $prefix . "\n", - FILE_APPEND - ); - } - - $prefix = $this->getNameSpace() . $prefix . '*'; + $prefix = $this->getPrefix() . $prefix . '*'; $keys = self::$cache->keys($prefix); $deleted = self::$cache->del($keys); @@ -153,14 +99,6 @@ class Redis extends Cache implements IMemcacheTTL { if ($ttl !== 0 && is_int($ttl)) { $args['ex'] = $ttl; } - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::add::' . $key . '::' . $value . "\n", - FILE_APPEND - ); - } - return self::$cache->set($this->getPrefix() . $key, $value, $args); } @@ -173,15 +111,7 @@ class Redis extends Cache implements IMemcacheTTL { * @return int | bool */ public function inc($key, $step = 1) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::inc::' . $key . "\n", - FILE_APPEND - ); - } - - return self::$cache->incrBy($this->getNameSpace() . $key, $step); + return self::$cache->incrBy($this->getPrefix() . $key, $step); } /** @@ -192,18 +122,10 @@ class Redis extends Cache implements IMemcacheTTL { * @return int | bool */ public function dec($key, $step = 1) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::dec::' . $key . "\n", - FILE_APPEND - ); - } - if (!$this->hasKey($key)) { return false; } - return self::$cache->decrBy($this->getNameSpace() . $key, $step); + return self::$cache->decrBy($this->getPrefix() . $key, $step); } /** @@ -215,21 +137,13 @@ class Redis extends Cache implements IMemcacheTTL { * @return bool */ public function cas($key, $old, $new) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::cas::' . $key . "\n", - FILE_APPEND - ); - } - if (!is_int($new)) { $new = json_encode($new); } - self::$cache->watch($this->getNameSpace() . $key); + self::$cache->watch($this->getPrefix() . $key); if ($this->get($key) === $old) { $result = self::$cache->multi() - ->set($this->getNameSpace() . $key, $new) + ->set($this->getPrefix() . $key, $new) ->exec(); return $result !== false; } @@ -245,18 +159,10 @@ class Redis extends Cache implements IMemcacheTTL { * @return bool */ public function cad($key, $old) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::cad::' . $key . "\n", - FILE_APPEND - ); - } - - self::$cache->watch($this->getNameSpace() . $key); + self::$cache->watch($this->getPrefix() . $key); if ($this->get($key) === $old) { $result = self::$cache->multi() - ->del($this->getNameSpace() . $key) + ->del($this->getPrefix() . $key) ->exec(); return $result !== false; } @@ -265,10 +171,10 @@ class Redis extends Cache implements IMemcacheTTL { } public function setTTL($key, $ttl) { - self::$cache->expire($this->getNameSpace() . $key, $ttl); + self::$cache->expire($this->getPrefix() . $key, $ttl); } - public static function isAvailable() { + public static function isAvailable(): bool { return \OC::$server->getGetRedisFactory()->isAvailable(); } } diff --git a/lib/private/Migration/SimpleOutput.php b/lib/private/Migration/SimpleOutput.php index 5024fdac585..f97bcb767f8 100644 --- a/lib/private/Migration/SimpleOutput.php +++ b/lib/private/Migration/SimpleOutput.php @@ -21,8 +21,8 @@ */ namespace OC\Migration; -use OCP\ILogger; use OCP\Migration\IOutput; +use Psr\Log\LoggerInterface; /** * Class SimpleOutput @@ -33,12 +33,10 @@ use OCP\Migration\IOutput; * @package OC\Migration */ class SimpleOutput implements IOutput { - - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; private $appName; - public function __construct(ILogger $logger, $appName) { + public function __construct(LoggerInterface $logger, $appName) { $this->logger = $logger; $this->appName = $appName; } diff --git a/lib/private/Preview/Imaginary.php b/lib/private/Preview/Imaginary.php index 7e6ce86d4eb..4da88f1ab26 100644 --- a/lib/private/Preview/Imaginary.php +++ b/lib/private/Preview/Imaginary.php @@ -89,18 +89,26 @@ class Imaginary extends ProviderV2 { $mimeType = 'jpeg'; } - $parameters = [ - 'width' => $maxX, - 'height' => $maxY, - 'stripmeta' => 'true', - 'type' => $mimeType, + $operations = [ + [ + 'operation' => 'autorotate', + ], + [ + 'operation' => ($crop ? 'smartcrop' : 'fit'), + 'params' => [ + 'width' => $maxX, + 'height' => $maxY, + 'stripmeta' => 'true', + 'type' => $mimeType, + 'norotation' => 'true', + ] + ] ]; - try { $response = $httpClient->post( - $imaginaryUrl . ($crop ? '/smartcrop' : '/fit'), [ - 'query' => $parameters, + $imaginaryUrl . '/pipeline', [ + 'query' => ['operations' => json_encode($operations)], 'stream' => true, 'content-type' => $file->getMimeType(), 'body' => $stream, diff --git a/lib/private/Profiler/FileProfilerStorage.php b/lib/private/Profiler/FileProfilerStorage.php new file mode 100644 index 00000000000..ce09ed51ed9 --- /dev/null +++ b/lib/private/Profiler/FileProfilerStorage.php @@ -0,0 +1,286 @@ +<?php + +declare(strict_types = 1); +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * @author Alexandre Salomé <alexandre.salome@gmail.com> + * + * @license AGPL-3.0-or-later AND MIT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Profiler; + +use OCP\Profiler\IProfile; + +/** + * Storage for profiler using files. + */ +class FileProfilerStorage { + // Folder where profiler data are stored. + private string $folder; + + /** + * Constructs the file storage using a "dsn-like" path. + * + * Example : "file:/path/to/the/storage/folder" + * + * @throws \RuntimeException + */ + public function __construct(string $folder) { + $this->folder = $folder; + + if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder)); + } + } + + public function find(?string $url, ?int $limit, ?string $method, int $start = null, int $end = null, string $statusCode = null): array { + $file = $this->getIndexFilename(); + + if (!file_exists($file)) { + return []; + } + + $file = fopen($file, 'r'); + fseek($file, 0, \SEEK_END); + + $result = []; + while (\count($result) < $limit && $line = $this->readLineFromFile($file)) { + $values = str_getcsv($line); + [$csvToken, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values; + $csvTime = (int) $csvTime; + + if ($url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method) || $statusCode && false === strpos($csvStatusCode, $statusCode)) { + continue; + } + + if (!empty($start) && $csvTime < $start) { + continue; + } + + if (!empty($end) && $csvTime > $end) { + continue; + } + + $result[$csvToken] = [ + 'token' => $csvToken, + 'method' => $csvMethod, + 'url' => $csvUrl, + 'time' => $csvTime, + 'parent' => $csvParent, + 'status_code' => $csvStatusCode, + ]; + } + + fclose($file); + + return array_values($result); + } + + public function purge(): void { + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator($this->folder, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($iterator as $file) { + if (is_file($file)) { + unlink($file); + } else { + rmdir($file); + } + } + } + + public function read(string $token): ?IProfile { + if (!$token || !file_exists($file = $this->getFilename($token))) { + return null; + } + + if (\function_exists('gzcompress')) { + $file = 'compress.zlib://'.$file; + } + + return $this->createProfileFromData($token, unserialize(file_get_contents($file))); + } + + /** + * @throws \RuntimeException + */ + public function write(IProfile $profile): bool { + $file = $this->getFilename($profile->getToken()); + + $profileIndexed = is_file($file); + if (!$profileIndexed) { + // Create directory + $dir = \dirname($file); + if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir)); + } + } + + $profileToken = $profile->getToken(); + // when there are errors in sub-requests, the parent and/or children tokens + // may equal the profile token, resulting in infinite loops + $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; + $childrenToken = array_filter(array_map(function (IProfile $p) use ($profileToken) { + return $profileToken !== $p->getToken() ? $p->getToken() : null; + }, $profile->getChildren())); + + // Store profile + $data = [ + 'token' => $profileToken, + 'parent' => $parentToken, + 'children' => $childrenToken, + 'data' => $profile->getCollectors(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + 'status_code' => $profile->getStatusCode(), + ]; + + $context = stream_context_create(); + + if (\function_exists('gzcompress')) { + $file = 'compress.zlib://'.$file; + stream_context_set_option($context, 'zlib', 'level', 3); + } + + if (false === file_put_contents($file, serialize($data), 0, $context)) { + return false; + } + + if (!$profileIndexed) { + // Add to index + if (false === $file = fopen($this->getIndexFilename(), 'a')) { + return false; + } + + fputcsv($file, [ + $profile->getToken(), + $profile->getMethod(), + $profile->getUrl(), + $profile->getTime(), + $profile->getParentToken(), + $profile->getStatusCode(), + ]); + fclose($file); + } + + return true; + } + + /** + * Gets filename to store data, associated to the token. + * + * @return string The profile filename + */ + protected function getFilename(string $token): string { + // Uses 4 last characters, because first are mostly the same. + $folderA = substr($token, -2, 2); + $folderB = substr($token, -4, 2); + + return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token; + } + + /** + * Gets the index filename. + * + * @return string The index filename + */ + protected function getIndexFilename(): string { + return $this->folder.'/index.csv'; + } + + /** + * Reads a line in the file, backward. + * + * This function automatically skips the empty lines and do not include the line return in result value. + * + * @param resource $file The file resource, with the pointer placed at the end of the line to read + * + * @return ?string A string representing the line or null if beginning of file is reached + */ + protected function readLineFromFile($file): ?string { + $line = ''; + $position = ftell($file); + + if (0 === $position) { + return null; + } + + while (true) { + $chunkSize = min($position, 1024); + $position -= $chunkSize; + fseek($file, $position); + + if (0 === $chunkSize) { + // bof reached + break; + } + + $buffer = fread($file, $chunkSize); + + if (false === ($upTo = strrpos($buffer, "\n"))) { + $line = $buffer.$line; + continue; + } + + $position += $upTo; + $line = substr($buffer, $upTo + 1).$line; + fseek($file, max(0, $position), \SEEK_SET); + + if ('' !== $line) { + break; + } + } + + return '' === $line ? null : $line; + } + + protected function createProfileFromData(string $token, array $data, IProfile $parent = null): IProfile { + $profile = new Profile($token); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setStatusCode($data['status_code']); + $profile->setCollectors($data['data']); + + if (!$parent && $data['parent']) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + foreach ($data['children'] as $token) { + if (!$token || !file_exists($file = $this->getFilename($token))) { + continue; + } + + if (\function_exists('gzcompress')) { + $file = 'compress.zlib://'.$file; + } + + $profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile)); + } + + return $profile; + } +} diff --git a/lib/private/Profiler/Profile.php b/lib/private/Profiler/Profile.php new file mode 100644 index 00000000000..648c49c0330 --- /dev/null +++ b/lib/private/Profiler/Profile.php @@ -0,0 +1,168 @@ +<?php + +declare(strict_types = 1); +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Profiler; + +use OCP\DataCollector\IDataCollector; +use OCP\Profiler\IProfile; + +class Profile implements \JsonSerializable, IProfile { + private string $token; + + private ?int $time = null; + + private ?string $url = null; + + private ?string $method = null; + + private ?int $statusCode = null; + + /** @var array<string, IDataCollector> */ + private array $collectors = []; + + private ?IProfile $parent = null; + + /** @var IProfile[] */ + private array $children = []; + + public function __construct(string $token) { + $this->token = $token; + } + + public function getToken(): string { + return $this->token; + } + + public function setToken(string $token): void { + $this->token = $token; + } + + public function getTime(): ?int { + return $this->time; + } + + public function setTime(int $time): void { + $this->time = $time; + } + + public function getUrl(): ?string { + return $this->url; + } + + public function setUrl(string $url): void { + $this->url = $url; + } + + public function getMethod(): ?string { + return $this->method; + } + + public function setMethod(string $method): void { + $this->method = $method; + } + + public function getStatusCode(): ?int { + return $this->statusCode; + } + + public function setStatusCode(int $statusCode): void { + $this->statusCode = $statusCode; + } + + public function addCollector(IDataCollector $collector) { + $this->collectors[$collector->getName()] = $collector; + } + + public function getParent(): ?IProfile { + return $this->parent; + } + + public function setParent(?IProfile $parent): void { + $this->parent = $parent; + } + + public function getParentToken(): ?string { + return $this->parent ? $this->parent->getToken() : null; + } + + /** @return IProfile[] */ + public function getChildren(): array { + return $this->children; + } + + /** + * @param IProfile[] $children + */ + public function setChildren(array $children): void { + $this->children = []; + foreach ($children as $child) { + $this->addChild($child); + } + } + + public function addChild(IProfile $profile): void { + $this->children[] = $profile; + $profile->setParent($this); + } + + /** + * @return IDataCollector[] + */ + public function getCollectors(): array { + return $this->collectors; + } + + /** + * @param IDataCollector[] $collectors + */ + public function setCollectors(array $collectors): void { + $this->collectors = $collectors; + } + + public function __sleep(): array { + return ['token', 'parent', 'children', 'collectors', 'method', 'url', 'time', 'statusCode']; + } + + #[\ReturnTypeWillChange] + public function jsonSerialize() { + // Everything but parent + return [ + 'token' => $this->token, + 'method' => $this->method, + 'children' => $this->children, + 'url' => $this->url, + 'statusCode' => $this->statusCode, + 'time' => $this->time, + 'collectors' => $this->collectors, + ]; + } + + public function getCollector(string $collectorName): ?IDataCollector { + if (!array_key_exists($collectorName, $this->collectors)) { + return null; + } + return $this->collectors[$collectorName]; + } +} diff --git a/lib/private/Profiler/Profiler.php b/lib/private/Profiler/Profiler.php new file mode 100644 index 00000000000..8aa800fbc6d --- /dev/null +++ b/lib/private/Profiler/Profiler.php @@ -0,0 +1,105 @@ +<?php + +declare(strict_types = 1); + +/** + * @copyright 2021 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Profiler; + +use OC\AppFramework\Http\Request; +use OCP\AppFramework\Http\Response; +use OCP\DataCollector\IDataCollector; +use OCP\Profiler\IProfiler; +use OCP\Profiler\IProfile; +use OC\SystemConfig; + +class Profiler implements IProfiler { + /** @var array<string, IDataCollector> */ + private array $dataCollectors = []; + + private ?FileProfilerStorage $storage = null; + + private bool $enabled = false; + + public function __construct(SystemConfig $config) { + $this->enabled = $config->getValue('profiler', false); + if ($this->enabled) { + $this->storage = new FileProfilerStorage($config->getValue('datadirectory', \OC::$SERVERROOT . '/data') . '/profiler'); + } + } + + public function add(IDataCollector $dataCollector): void { + $this->dataCollectors[$dataCollector->getName()] = $dataCollector; + } + + public function loadProfileFromResponse(Response $response): ?IProfile { + if (!$token = $response->getHeaders()['X-Debug-Token']) { + return null; + } + + return $this->loadProfile($token); + } + + public function loadProfile(string $token): ?IProfile { + return $this->storage->read($token); + } + + public function saveProfile(IProfile $profile): bool { + return $this->storage->write($profile); + } + + public function collect(Request $request, Response $response): IProfile { + $profile = new Profile($request->getId()); + $profile->setTime(time()); + $profile->setUrl($request->getRequestUri()); + $profile->setMethod($request->getMethod()); + $profile->setStatusCode($response->getStatus()); + foreach ($this->dataCollectors as $dataCollector) { + $dataCollector->collect($request, $response, null); + + // We clone for subrequests + $profile->addCollector(clone $dataCollector); + } + return $profile; + } + + /** + * @return array[] + */ + public function find(?string $url, ?int $limit, ?string $method, ?int $start, ?int $end, + string $statusCode = null): array { + return $this->storage->find($url, $limit, $method, $start, $end, $statusCode); + } + + public function dataProviders(): array { + return array_keys($this->dataCollectors); + } + + public function isEnabled(): bool { + return $this->enabled; + } + + public function setEnabled(bool $enabled): void { + $this->enabled = $enabled; + } +} diff --git a/lib/private/Profiler/RoutingDataCollector.php b/lib/private/Profiler/RoutingDataCollector.php new file mode 100644 index 00000000000..e6659230879 --- /dev/null +++ b/lib/private/Profiler/RoutingDataCollector.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Profiler; + +use OC\AppFramework\Http\Request; +use OCP\AppFramework\Http\Response; +use OCP\DataCollector\AbstractDataCollector; + +class RoutingDataCollector extends AbstractDataCollector { + private string $appName; + private string $controllerName; + private string $actionName; + + public function __construct(string $appName, string $controllerName, string $actionName) { + $this->appName = $appName; + $this->controllerName = $controllerName; + $this->actionName = $actionName; + } + + public function collect(Request $request, Response $response, \Throwable $exception = null): void { + $this->data = [ + 'appName' => $this->appName, + 'controllerName' => $this->controllerName, + 'actionName' => $this->actionName, + ]; + } + + public function getName(): string { + return 'router'; + } +} diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 408d0f1b7aa..91bfd47f5de 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -47,6 +47,7 @@ use OC\Repair\Collation; use OC\Repair\MoveUpdaterStepFile; use OC\Repair\NC22\LookupServerSendCheck; use OC\Repair\Owncloud\CleanPreviews; +use OC\Repair\Owncloud\MigrateOauthTables; use OC\Repair\NC11\FixMountStorages; use OC\Repair\Owncloud\MoveAvatars; use OC\Repair\Owncloud\InstallCoreBundle; @@ -93,13 +94,12 @@ class Repair implements IOutput { /** @var string */ private $currentStep; - private $logger; + private LoggerInterface $logger; /** * Creates a new repair step runner * * @param IRepairStep[] $repairSteps array of RepairStep instances - * @param EventDispatcherInterface $dispatcher */ public function __construct(array $repairSteps, EventDispatcherInterface $dispatcher, LoggerInterface $logger) { $this->repairSteps = $repairSteps; @@ -171,7 +171,7 @@ class Repair implements IOutput { */ public static function getRepairSteps() { return [ - new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), false), + new Collation(\OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class), \OC::$server->getDatabaseConnection(), false), new RepairMimeTypes(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), new CleanTags(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager()), new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), @@ -185,6 +185,7 @@ class Repair implements IOutput { \OC::$server->getUserManager(), \OC::$server->getConfig() ), + new MigrateOauthTables(\OC::$server->get(Connection::class)), new FixMountStorages(\OC::$server->getDatabaseConnection()), new UpdateLanguageCodes(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()), new InstallCoreBundle( @@ -197,7 +198,7 @@ class Repair implements IOutput { new ClearGeneratedAvatarCache(\OC::$server->getConfig(), \OC::$server->query(AvatarManager::class)), new AddPreviewBackgroundCleanupJob(\OC::$server->getJobList()), new AddCleanupUpdaterBackupsJob(\OC::$server->getJobList()), - new CleanupCardDAVPhotoCache(\OC::$server->getConfig(), \OC::$server->getAppDataDir('dav-photocache'), \OC::$server->getLogger()), + new CleanupCardDAVPhotoCache(\OC::$server->getConfig(), \OC::$server->getAppDataDir('dav-photocache'), \OC::$server->get(LoggerInterface::class)), new AddClenupLoginFlowV2BackgroundJob(\OC::$server->getJobList()), new RemoveLinkShares(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig(), \OC::$server->getGroupManager(), \OC::$server->getNotificationManager(), \OC::$server->query(ITimeFactory::class)), new ClearCollectionsAccessCache(\OC::$server->getConfig(), \OC::$server->query(IManager::class)), @@ -238,7 +239,7 @@ class Repair implements IOutput { $connectionAdapter = \OC::$server->get(ConnectionAdapter::class); $config = \OC::$server->getConfig(); $steps = [ - new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), $connectionAdapter, true), + new Collation(\OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class), $connectionAdapter, true), new SqliteAutoincrement($connection), new SaveAccountsTableData($connectionAdapter, $config), new DropAccountTermsTable($connectionAdapter), diff --git a/lib/private/Repair/Collation.php b/lib/private/Repair/Collation.php index e949c261b80..25e85f00af8 100644 --- a/lib/private/Repair/Collation.php +++ b/lib/private/Repair/Collation.php @@ -30,16 +30,15 @@ use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\MySQLPlatform; use OCP\IConfig; use OCP\IDBConnection; -use OCP\ILogger; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; +use Psr\Log\LoggerInterface; class Collation implements IRepairStep { /** @var IConfig */ protected $config; - /** @var ILogger */ - protected $logger; + protected LoggerInterface $logger; /** @var IDBConnection */ protected $connection; @@ -48,12 +47,14 @@ class Collation implements IRepairStep { protected $ignoreFailures; /** - * @param IConfig $config - * @param ILogger $logger - * @param IDBConnection $connection * @param bool $ignoreFailures */ - public function __construct(IConfig $config, ILogger $logger, IDBConnection $connection, $ignoreFailures) { + public function __construct( + IConfig $config, + LoggerInterface $logger, + IDBConnection $connection, + $ignoreFailures + ) { $this->connection = $connection; $this->config = $config; $this->logger = $logger; @@ -83,7 +84,7 @@ class Collation implements IRepairStep { $query->execute(); } catch (DriverException $e) { // Just log this - $this->logger->logException($e); + $this->logger->error($e->getMessage(), ['exception' => $e]); if (!$this->ignoreFailures) { throw $e; } @@ -95,7 +96,7 @@ class Collation implements IRepairStep { $query->execute(); } catch (DriverException $e) { // Just log this - $this->logger->logException($e); + $this->logger->error($e->getMessage(), ['exception' => $e]); if (!$this->ignoreFailures) { throw $e; } diff --git a/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php b/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php index 39b48562791..25dc24fb66c 100644 --- a/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php +++ b/lib/private/Repair/NC16/CleanupCardDAVPhotoCache.php @@ -30,9 +30,9 @@ use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IConfig; -use OCP\ILogger; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; +use Psr\Log\LoggerInterface; use RuntimeException; /** @@ -52,10 +52,9 @@ class CleanupCardDAVPhotoCache implements IRepairStep { /** @var IAppData */ private $appData; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; - public function __construct(IConfig $config, IAppData $appData, ILogger $logger) { + public function __construct(IConfig $config, IAppData $appData, LoggerInterface $logger) { $this->config = $config; $this->appData = $appData; $this->logger = $logger; @@ -71,7 +70,7 @@ class CleanupCardDAVPhotoCache implements IRepairStep { } catch (NotFoundException $e) { return; } catch (RuntimeException $e) { - $this->logger->logException($e, ['message' => 'Failed to fetch directory listing in CleanupCardDAVPhotoCache']); + $this->logger->error('Failed to fetch directory listing in CleanupCardDAVPhotoCache', ['exception' => $e]); return; } @@ -90,7 +89,7 @@ class CleanupCardDAVPhotoCache implements IRepairStep { /** @var ISimpleFolder $folder */ $folder->getFile('photo.')->delete(); } catch (\Exception $e) { - $this->logger->logException($e); + $this->logger->error($e->getMessage(), ['exception' => $e]); $output->warning('Could not delete file "dav-photocache/' . $folder->getName() . '/photo."'); } } diff --git a/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php index 67f9d6912d9..7f4bbc35c17 100644 --- a/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php +++ b/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php @@ -29,15 +29,14 @@ use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; -use OCP\ILogger; use OCP\IUserManager; +use Psr\Log\LoggerInterface; class CleanPreviewsBackgroundJob extends QueuedJob { /** @var IRootFolder */ private $rootFolder; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var IJobList */ private $jobList; @@ -50,15 +49,9 @@ class CleanPreviewsBackgroundJob extends QueuedJob { /** * CleanPreviewsBackgroundJob constructor. - * - * @param IRootFolder $rootFolder - * @param ILogger $logger - * @param IJobList $jobList - * @param ITimeFactory $timeFactory - * @param IUserManager $userManager */ public function __construct(IRootFolder $rootFolder, - ILogger $logger, + LoggerInterface $logger, IJobList $jobList, ITimeFactory $timeFactory, IUserManager $userManager) { diff --git a/lib/private/Repair/Owncloud/MigrateOauthTables.php b/lib/private/Repair/Owncloud/MigrateOauthTables.php new file mode 100644 index 00000000000..4011a043f20 --- /dev/null +++ b/lib/private/Repair/Owncloud/MigrateOauthTables.php @@ -0,0 +1,124 @@ +<?php +/** + * @copyright 2021 Louis Chemineau <louis@chmn.me> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OC\Repair\Owncloud; + +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use OC\DB\Connection; +use OC\DB\SchemaWrapper; +use OCP\DB\QueryBuilder\IQueryBuilder; + +class MigrateOauthTables implements IRepairStep { + + /** @var Connection */ + protected $db; + + /** + * @param Connection $db + */ + public function __construct(Connection $db) { + $this->db = $db; + } + + /** + * @return string + */ + public function getName() { + return 'Migrate oauth2_clients table to nextcloud schema'; + } + + public function run(IOutput $output) { + $schema = new SchemaWrapper($this->db); + if (!$schema->hasTable('oauth2_clients')) { + $output->info("oauth2_clients table does not exist."); + return; + } + + $output->info("Update the oauth2_access_tokens table schema."); + $schema = new SchemaWrapper($this->db); + $table = $schema->getTable('oauth2_access_tokens'); + if (!$table->hasColumn('hashed_code')) { + $table->addColumn('hashed_code', 'string', [ + 'notnull' => true, + 'length' => 128, + ]); + } + if (!$table->hasColumn('encrypted_token')) { + $table->addColumn('encrypted_token', 'string', [ + 'notnull' => true, + 'length' => 786, + ]); + } + if (!$table->hasIndex('oauth2_access_hash_idx')) { + $table->addUniqueIndex(['hashed_code'], 'oauth2_access_hash_idx'); + } + if (!$table->hasIndex('oauth2_access_client_id_idx')) { + $table->addIndex(['client_id'], 'oauth2_access_client_id_idx'); + } + + $output->info("Update the oauth2_clients table schema."); + $schema = new SchemaWrapper($this->db); + $table = $schema->getTable('oauth2_clients'); + if ($table->getColumn('name')->getLength() !== 64) { + $table->getColumn('name')->setLength(64); + } + if ($table->hasColumn('allow_subdomains')) { + $table->dropColumn('allow_subdomains'); + } + + if (!$schema->getTable('oauth2_clients')->hasColumn('client_identifier')) { + $table->addColumn('client_identifier', 'string', [ + 'notnull' => true, + 'length' => 64, + 'default' => '' + ]); + $table->addIndex(['client_identifier'], 'oauth2_client_id_idx'); + } + + $this->db->migrateToSchema($schema->getWrappedSchema()); + + + if ($schema->getTable('oauth2_clients')->hasColumn('identifier')) { + $output->info("Move identifier column's data to the new client_identifier column."); + // 1. Fetch all [id, identifier] couple. + $selectQuery = $this->db->getQueryBuilder(); + $selectQuery->select('id', 'identifier')->from('oauth2_clients'); + $result = $selectQuery->executeQuery(); + $identifiers = $result->fetchAll(); + $result->closeCursor(); + + // 2. Insert them into the client_identifier column. + foreach ($identifiers as ["id" => $id, "identifier" => $clientIdentifier]) { + $insertQuery = $this->db->getQueryBuilder(); + $insertQuery->update('oauth2_clients') + ->set('client_identifier', $insertQuery->createNamedParameter($clientIdentifier, IQueryBuilder::PARAM_STR)) + ->where($insertQuery->expr()->eq('id', $insertQuery->createNamedParameter($id, IQueryBuilder::PARAM_INT))) + ->executeStatement(); + } + + $output->info("Drop the identifier column."); + $schema = new SchemaWrapper($this->db); + $table = $schema->getTable('oauth2_clients'); + $table->dropColumn('identifier'); + $this->db->migrateToSchema($schema->getWrappedSchema()); + } + } +} diff --git a/lib/private/Route/CachingRouter.php b/lib/private/Route/CachingRouter.php index 553ea285fd4..f65060e710b 100644 --- a/lib/private/Route/CachingRouter.php +++ b/lib/private/Route/CachingRouter.php @@ -24,7 +24,7 @@ */ namespace OC\Route; -use OCP\ILogger; +use Psr\Log\LoggerInterface; class CachingRouter extends Router { /** @@ -34,9 +34,8 @@ class CachingRouter extends Router { /** * @param \OCP\ICache $cache - * @param ILogger $logger */ - public function __construct($cache, ILogger $logger) { + public function __construct($cache, LoggerInterface $logger) { $this->cache = $cache; parent::__construct($logger); } diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php index fcde8f08897..b957173cacc 100644 --- a/lib/private/Route/Router.php +++ b/lib/private/Route/Router.php @@ -34,9 +34,9 @@ namespace OC\Route; use OC\AppFramework\Routing\RouteParser; use OCP\AppFramework\App; -use OCP\ILogger; use OCP\Route\IRouter; use OCP\Util; +use Psr\Log\LoggerInterface; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Generator\UrlGenerator; @@ -61,15 +61,11 @@ class Router implements IRouter { protected $loaded = false; /** @var array */ protected $loadedApps = []; - /** @var ILogger */ - protected $logger; + protected LoggerInterface $logger; /** @var RequestContext */ protected $context; - /** - * @param ILogger $logger - */ - public function __construct(ILogger $logger) { + public function __construct(LoggerInterface $logger) { $this->logger = $logger; $baseUrl = \OC::$WEBROOT; if (!(\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) { @@ -364,7 +360,7 @@ class Router implements IRouter { try { return $this->getGenerator()->generate($name, $parameters, $referenceType); } catch (RouteNotFoundException $e) { - $this->logger->logException($e, ['level' => ILogger::INFO]); + $this->logger->info($e->getMessage(), ['exception' => $e]); return ''; } } diff --git a/lib/private/Search/SearchComposer.php b/lib/private/Search/SearchComposer.php index d5f35f504e0..3c228261ec2 100644 --- a/lib/private/Search/SearchComposer.php +++ b/lib/private/Search/SearchComposer.php @@ -28,14 +28,14 @@ declare(strict_types=1); namespace OC\Search; use InvalidArgumentException; -use OC\AppFramework\Bootstrap\Coordinator; use OCP\AppFramework\QueryException; -use OCP\ILogger; use OCP\IServerContainer; use OCP\IUser; use OCP\Search\IProvider; use OCP\Search\ISearchQuery; use OCP\Search\SearchResult; +use OC\AppFramework\Bootstrap\Coordinator; +use Psr\Log\LoggerInterface; use function array_map; /** @@ -68,12 +68,11 @@ class SearchComposer { /** @var IServerContainer */ private $container; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; public function __construct(Coordinator $bootstrapCoordinator, IServerContainer $container, - ILogger $logger) { + LoggerInterface $logger) { $this->container = $container; $this->logger = $logger; $this->bootstrapCoordinator = $bootstrapCoordinator; @@ -99,9 +98,8 @@ class SearchComposer { $this->providers[$provider->getId()] = $provider; } catch (QueryException $e) { // Log an continue. We can be fault tolerant here. - $this->logger->logException($e, [ - 'message' => 'Could not load search provider dynamically: ' . $e->getMessage(), - 'level' => ILogger::ERROR, + $this->logger->error('Could not load search provider dynamically: ' . $e->getMessage(), [ + 'exception' => $e, 'app' => $registration->getAppId(), ]); } diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php index abbe77c6637..c47d102b881 100644 --- a/lib/private/Security/Bruteforce/Throttler.php +++ b/lib/private/Security/Bruteforce/Throttler.php @@ -36,8 +36,8 @@ use OC\Security\Normalizer\IpAddress; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; use OCP\IDBConnection; -use OCP\ILogger; use OCP\Security\Bruteforce\MaxDelayReached; +use Psr\Log\LoggerInterface; /** * Class Throttler implements the bruteforce protection for security actions in @@ -62,22 +62,15 @@ class Throttler { private $db; /** @var ITimeFactory */ private $timeFactory; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var IConfig */ private $config; /** @var bool */ private $hasAttemptsDeleted = false; - /** - * @param IDBConnection $db - * @param ITimeFactory $timeFactory - * @param ILogger $logger - * @param IConfig $config - */ public function __construct(IDBConnection $db, ITimeFactory $timeFactory, - ILogger $logger, + LoggerInterface $logger, IConfig $config) { $this->db = $db; $this->timeFactory = $timeFactory; diff --git a/lib/private/Security/CertificateManager.php b/lib/private/Security/CertificateManager.php index 6f3b01e23b9..fa26c19ceae 100644 --- a/lib/private/Security/CertificateManager.php +++ b/lib/private/Security/CertificateManager.php @@ -36,8 +36,8 @@ use OC\Files\Filesystem; use OCP\ICertificate; use OCP\ICertificateManager; use OCP\IConfig; -use OCP\ILogger; use OCP\Security\ISecureRandom; +use Psr\Log\LoggerInterface; /** * Manage trusted certificates for users @@ -53,23 +53,16 @@ class CertificateManager implements ICertificateManager { */ protected $config; - /** - * @var ILogger - */ - protected $logger; + protected LoggerInterface $logger; /** @var ISecureRandom */ protected $random; - /** - * @param \OC\Files\View $view relative to data/ - * @param IConfig $config - * @param ILogger $logger - * @param ISecureRandom $random - */ + private ?string $bundlePath = null; + public function __construct(\OC\Files\View $view, IConfig $config, - ILogger $logger, + LoggerInterface $logger, ISecureRandom $random) { $this->view = $view; $this->config = $config; @@ -145,7 +138,8 @@ class CertificateManager implements ICertificateManager { $defaultCertificates = file_get_contents(\OC::$SERVERROOT . '/resources/config/ca-bundle.crt'); if (strlen($defaultCertificates) < 1024) { // sanity check to verify that we have some content for our bundle // log as exception so we have a stacktrace - $this->logger->logException(new \Exception('Shipped ca-bundle is empty, refusing to create certificate bundle')); + $e = new \Exception('Shipped ca-bundle is empty, refusing to create certificate bundle'); + $this->logger->error($e->getMessage(), ['exception' => $e]); return; } @@ -190,6 +184,7 @@ class CertificateManager implements ICertificateManager { if (!Filesystem::isValidPath($name) or Filesystem::isFileBlacklisted($name)) { throw new \Exception('Filename is not valid'); } + $this->bundlePath = null; $dir = $this->getPathToCertificates() . 'uploads/'; if (!$this->view->file_exists($dir)) { @@ -217,6 +212,8 @@ class CertificateManager implements ICertificateManager { if (!Filesystem::isValidPath($name)) { return false; } + $this->bundlePath = null; + $path = $this->getPathToCertificates() . 'uploads/'; if ($this->view->file_exists($path . $name)) { $this->view->unlink($path . $name); @@ -241,15 +238,18 @@ class CertificateManager implements ICertificateManager { */ public function getAbsoluteBundlePath(): string { try { - if (!$this->hasCertificates()) { - return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; - } + if (!$this->bundlePath) { + if (!$this->hasCertificates()) { + $this->bundlePath = \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; + } - if ($this->needsRebundling()) { - $this->createCertificateBundle(); - } + if ($this->needsRebundling()) { + $this->createCertificateBundle(); + } - return $this->view->getLocalFile($this->getCertificateBundle()); + $this->bundlePath = $this->view->getLocalFile($this->getCertificateBundle()); + } + return $this->bundlePath; } catch (\Exception $e) { return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; } diff --git a/lib/private/Security/IdentityProof/Manager.php b/lib/private/Security/IdentityProof/Manager.php index 0878efeebc7..c92d7390969 100644 --- a/lib/private/Security/IdentityProof/Manager.php +++ b/lib/private/Security/IdentityProof/Manager.php @@ -32,9 +32,9 @@ namespace OC\Security\IdentityProof; use OC\Files\AppData\Factory; use OCP\Files\IAppData; use OCP\IConfig; -use OCP\ILogger; use OCP\IUser; use OCP\Security\ICrypto; +use Psr\Log\LoggerInterface; class Manager { /** @var IAppData */ @@ -43,13 +43,12 @@ class Manager { private $crypto; /** @var IConfig */ private $config; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; public function __construct(Factory $appDataFactory, ICrypto $crypto, IConfig $config, - ILogger $logger + LoggerInterface $logger ) { $this->appData = $appDataFactory->get('identityproof'); $this->crypto = $crypto; diff --git a/lib/private/Server.php b/lib/private/Server.php index 00afaf1d6a9..b214ba3ce54 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -60,6 +60,7 @@ use OC\App\AppStore\Fetcher\AppFetcher; use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\AppFramework\Bootstrap\Coordinator; use OC\AppFramework\Http\Request; +use OC\AppFramework\Http\RequestId; use OC\AppFramework\Utility\TimeFactory; use OC\Authentication\Events\LoginFailed; use OC\Authentication\Listeners\LoginFailedListener; @@ -205,6 +206,7 @@ use OCP\ILogger; use OCP\INavigationManager; use OCP\IPreview; use OCP\IRequest; +use OCP\IRequestId; use OCP\ISearch; use OCP\IServerContainer; use OCP\ISession; @@ -258,6 +260,8 @@ use OCA\Files_External\Service\UserStoragesService; use OCA\Files_External\Service\UserGlobalStoragesService; use OCA\Files_External\Service\GlobalStoragesService; use OCA\Files_External\Service\BackendService; +use OCP\Profiler\IProfiler; +use OC\Profiler\Profiler; /** * Class Server @@ -342,6 +346,10 @@ class Server extends ServerContainer implements IServerContainer { ); }); + $this->registerService(IProfiler::class, function (Server $c) { + return new Profiler($c->get(SystemConfig::class)); + }); + $this->registerService(\OCP\Encryption\IManager::class, function (Server $c) { $view = new View(); $util = new Encryption\Util( @@ -352,7 +360,7 @@ class Server extends ServerContainer implements IServerContainer { ); return new Encryption\Manager( $c->get(\OCP\IConfig::class), - $c->get(ILogger::class), + $c->get(LoggerInterface::class), $c->getL10N('core'), new View(), $util, @@ -426,7 +434,7 @@ class Server extends ServerContainer implements IServerContainer { $view, null, $c->get(IUserMountCache::class), - $this->get(ILogger::class), + $this->get(LoggerInterface::class), $this->get(IUserManager::class), $this->get(IEventDispatcher::class), ); @@ -464,7 +472,11 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(\OCP\IUserManager::class, \OC\User\Manager::class); $this->registerService(\OCP\IGroupManager::class, function (ContainerInterface $c) { - $groupManager = new \OC\Group\Manager($this->get(IUserManager::class), $c->get(SymfonyAdapter::class), $this->get(ILogger::class)); + $groupManager = new \OC\Group\Manager( + $this->get(IUserManager::class), + $c->get(SymfonyAdapter::class), + $this->get(LoggerInterface::class) + ); $groupManager->listen('\OC\Group', 'preCreate', function ($gid) { /** @var IEventDispatcher $dispatcher */ $dispatcher = $this->get(IEventDispatcher::class); @@ -545,7 +557,7 @@ class Server extends ServerContainer implements IServerContainer { $c->get(\OCP\IConfig::class), $c->get(ISecureRandom::class), $c->getLockdownManager(), - $c->get(ILogger::class), + $c->get(LoggerInterface::class), $c->get(IEventDispatcher::class) ); /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */ @@ -685,7 +697,9 @@ class Server extends ServerContainer implements IServerContainer { $this->registerDeprecatedAlias('UserCache', ICache::class); $this->registerService(Factory::class, function (Server $c) { - $arrayCacheFactory = new \OC\Memcache\Factory('', $c->get(ILogger::class), + $profiler = $c->get(IProfiler::class); + $arrayCacheFactory = new \OC\Memcache\Factory('', $c->get(LoggerInterface::class), + $profiler, ArrayCache::class, ArrayCache::class, ArrayCache::class @@ -709,7 +723,9 @@ class Server extends ServerContainer implements IServerContainer { $instanceId = \OC_Util::getInstanceId(); $path = \OC::$SERVERROOT; $prefix = md5($instanceId . '-' . $version . '-' . $path); - return new \OC\Memcache\Factory($prefix, $c->get(ILogger::class), + return new \OC\Memcache\Factory($prefix, + $c->get(LoggerInterface::class), + $profiler, $config->getSystemValue('memcache.local', null), $config->getSystemValue('memcache.distributed', null), $config->getSystemValue('memcache.locking', null), @@ -759,6 +775,7 @@ class Server extends ServerContainer implements IServerContainer { $c->get(KnownUserService::class) ); }); + $this->registerAlias(IAvatarManager::class, AvatarManager::class); /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('AvatarManager', AvatarManager::class); @@ -790,7 +807,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService(Router::class, function (Server $c) { $cacheFactory = $c->get(ICacheFactory::class); - $logger = $c->get(ILogger::class); + $logger = $c->get(LoggerInterface::class); if ($cacheFactory->isLocalCacheAvailable()) { $router = new \OC\Route\CachingRouter($cacheFactory->createLocal('route'), $logger); } else { @@ -851,7 +868,6 @@ class Server extends ServerContainer implements IServerContainer { } $connectionParams = $factory->createConnectionParams(); $connection = $factory->getConnection($type, $connectionParams); - $connection->getConfiguration()->setSQLLogger($c->getQueryLogger()); return $connection; }); /** @deprecated 19.0.0 */ @@ -861,7 +877,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(IClientService::class, ClientService::class); $this->registerService(LocalAddressChecker::class, function (ContainerInterface $c) { return new LocalAddressChecker( - $c->get(ILogger::class), + $c->get(LoggerInterface::class), ); }); $this->registerService(NegativeDnsCache::class, function (ContainerInterface $c) { @@ -932,7 +948,7 @@ class Server extends ServerContainer implements IServerContainer { $mountCache = new UserMountCache( $c->get(IDBConnection::class), $c->get(IUserManager::class), - $c->get(ILogger::class) + $c->get(LoggerInterface::class) ); $listener = new UserMountCacheListener($mountCache); $listener->listen($c->get(IUserManager::class)); @@ -949,7 +965,7 @@ class Server extends ServerContainer implements IServerContainer { // builtin providers $config = $c->get(\OCP\IConfig::class); - $logger = $c->get(ILogger::class); + $logger = $c->get(LoggerInterface::class); $manager->registerProvider(new CacheMountProvider($config)); $manager->registerHomeProvider(new LocalHomeMountProvider()); $manager->registerHomeProvider(new ObjectHomeMountProvider($config)); @@ -1033,7 +1049,7 @@ class Server extends ServerContainer implements IServerContainer { : '', 'urlParams' => $urlParams, ], - $this->get(ISecureRandom::class), + $this->get(IRequestId::class), $this->get(\OCP\IConfig::class), $this->get(CsrfTokenManager::class), $stream @@ -1042,10 +1058,17 @@ class Server extends ServerContainer implements IServerContainer { /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('Request', \OCP\IRequest::class); + $this->registerService(IRequestId::class, function (ContainerInterface $c): IRequestId { + return new RequestId( + $_SERVER['UNIQUE_ID'] ?? '', + $this->get(ISecureRandom::class) + ); + }); + $this->registerService(IMailer::class, function (Server $c) { return new Mailer( $c->get(\OCP\IConfig::class), - $c->get(ILogger::class), + $c->get(LoggerInterface::class), $c->get(Defaults::class), $c->get(IURLGenerator::class), $c->getL10N('lib'), @@ -1085,7 +1108,7 @@ class Server extends ServerContainer implements IServerContainer { } return new DBLockingProvider( $c->get(IDBConnection::class), - $c->get(ILogger::class), + $c->get(LoggerInterface::class), new TimeFactory(), $ttl, !\OC::$CLI @@ -1108,7 +1131,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService(IMimeTypeDetector::class, function (ContainerInterface $c) { return new \OC\Files\Type\Detection( $c->get(IURLGenerator::class), - $c->get(ILogger::class), + $c->get(LoggerInterface::class), \OC::$configDir, \OC::$SERVERROOT . '/resources/config/' ); @@ -1205,7 +1228,7 @@ class Server extends ServerContainer implements IServerContainer { $c->get(IURLGenerator::class), $this->get(ICacheFactory::class), $c->get(SystemConfig::class), - $c->get(ILogger::class) + $c->get(LoggerInterface::class) ); }); $this->registerAlias(\OCP\EventDispatcher\IEventDispatcher::class, \OC\EventDispatcher\EventDispatcher::class); @@ -1214,7 +1237,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(EventDispatcherInterface::class, \OC\EventDispatcher\SymfonyAdapter::class); $this->registerService('CryptoWrapper', function (ContainerInterface $c) { - // FIXME: Instantiiated here due to cyclic dependency + // FIXME: Instantiated here due to cyclic dependency $request = new Request( [ 'get' => $_GET, @@ -1227,7 +1250,7 @@ class Server extends ServerContainer implements IServerContainer { ? $_SERVER['REQUEST_METHOD'] : null, ], - $c->get(ISecureRandom::class), + $c->get(IRequestId::class), $c->get(\OCP\IConfig::class) ); @@ -1254,7 +1277,7 @@ class Server extends ServerContainer implements IServerContainer { $factory = new $factoryClass($this); $manager = new \OC\Share20\Manager( - $c->get(ILogger::class), + $c->get(LoggerInterface::class), $c->get(\OCP\IConfig::class), $c->get(ISecureRandom::class), $c->get(IHasher::class), @@ -1333,7 +1356,7 @@ class Server extends ServerContainer implements IServerContainer { $c->get(IAppManager::class), $c->get(IClientService::class), $c->get(ICloudIdManager::class), - $c->get(ILogger::class) + $c->get(LoggerInterface::class) ); }); @@ -1442,7 +1465,7 @@ class Server extends ServerContainer implements IServerContainer { // Delete avatar on user deletion $dispatcher->addListener('OCP\IUser::preDelete', function (GenericEvent $e) { - $logger = $this->get(ILogger::class); + $logger = $this->get(LoggerInterface::class); $manager = $this->getAvatarManager(); /** @var IUser $user */ $user = $e->getSubject(); @@ -2351,8 +2374,8 @@ class Server extends ServerContainer implements IServerContainer { private function registerDeprecatedAlias(string $alias, string $target) { $this->registerService($alias, function (ContainerInterface $container) use ($target, $alias) { try { - /** @var ILogger $logger */ - $logger = $container->get(ILogger::class); + /** @var LoggerInterface $logger */ + $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 diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index b634d41a640..3fca9e3fe14 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -57,7 +57,6 @@ use OCP\HintException; use OCP\IConfig; use OCP\IGroupManager; use OCP\IL10N; -use OCP\ILogger; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; @@ -75,6 +74,7 @@ use OCP\Share\IManager; use OCP\Share\IProviderFactory; use OCP\Share\IShare; use OCP\Share\IShareProvider; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -85,8 +85,7 @@ class Manager implements IManager { /** @var IProviderFactory */ private $factory; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var IConfig */ private $config; /** @var ISecureRandom */ @@ -125,7 +124,7 @@ class Manager implements IManager { private $knownUserService; public function __construct( - ILogger $logger, + LoggerInterface $logger, IConfig $config, ISecureRandom $secureRandom, IHasher $hasher, @@ -951,7 +950,7 @@ class Manager implements IManager { return; } } catch (\Exception $e) { - $this->logger->logException($e, ['message' => 'Share notification mail could not be sent']); + $this->logger->error('Share notification mail could not be sent', ['exception' => $e]); } } diff --git a/lib/private/Template/CSSResourceLocator.php b/lib/private/Template/CSSResourceLocator.php index 9ee11e723e5..a038ba7ce9b 100644 --- a/lib/private/Template/CSSResourceLocator.php +++ b/lib/private/Template/CSSResourceLocator.php @@ -31,7 +31,7 @@ */ namespace OC\Template; -use OCP\ILogger; +use Psr\Log\LoggerInterface; class CSSResourceLocator extends ResourceLocator { @@ -39,13 +39,12 @@ class CSSResourceLocator extends ResourceLocator { protected $scssCacher; /** - * @param ILogger $logger * @param string $theme * @param array $core_map * @param array $party_map * @param SCSSCacher $scssCacher */ - public function __construct(ILogger $logger, $theme, $core_map, $party_map, $scssCacher) { + public function __construct(LoggerInterface $logger, $theme, $core_map, $party_map, $scssCacher) { $this->scssCacher = $scssCacher; parent::__construct($logger, $theme, $core_map, $party_map); diff --git a/lib/private/Template/IconsCacher.php b/lib/private/Template/IconsCacher.php index 01500aa2e9c..c2956d5712a 100644 --- a/lib/private/Template/IconsCacher.php +++ b/lib/private/Template/IconsCacher.php @@ -36,13 +36,11 @@ use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; use OCP\Files\SimpleFS\ISimpleFolder; -use OCP\ILogger; use OCP\IURLGenerator; +use Psr\Log\LoggerInterface; class IconsCacher { - - /** @var ILogger */ - protected $logger; + protected LoggerInterface $logger; /** @var IAppData */ protected $appData; @@ -68,13 +66,9 @@ class IconsCacher { private $cachedList; /** - * @param ILogger $logger - * @param Factory $appDataFactory - * @param IURLGenerator $urlGenerator - * @param ITimeFactory $timeFactory * @throws \OCP\Files\NotPermittedException */ - public function __construct(ILogger $logger, + public function __construct(LoggerInterface $logger, Factory $appDataFactory, IURLGenerator $urlGenerator, ITimeFactory $timeFactory) { diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php index d2073fbbb83..a6d9f0ee558 100644 --- a/lib/private/Template/JSCombiner.php +++ b/lib/private/Template/JSCombiner.php @@ -33,8 +33,8 @@ use OCP\Files\NotPermittedException; use OCP\Files\SimpleFS\ISimpleFolder; use OCP\ICache; use OCP\ICacheFactory; -use OCP\ILogger; use OCP\IURLGenerator; +use Psr\Log\LoggerInterface; class JSCombiner { @@ -50,24 +50,16 @@ class JSCombiner { /** @var SystemConfig */ protected $config; - /** @var ILogger */ - protected $logger; + protected LoggerInterface $logger; /** @var ICacheFactory */ private $cacheFactory; - /** - * @param IAppData $appData - * @param IURLGenerator $urlGenerator - * @param ICacheFactory $cacheFactory - * @param SystemConfig $config - * @param ILogger $logger - */ public function __construct(IAppData $appData, IURLGenerator $urlGenerator, ICacheFactory $cacheFactory, SystemConfig $config, - ILogger $logger) { + LoggerInterface $logger) { $this->appData = $appData; $this->urlGenerator = $urlGenerator; $this->cacheFactory = $cacheFactory; diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php index bf84e1ae958..95ae0d3d832 100644 --- a/lib/private/Template/JSResourceLocator.php +++ b/lib/private/Template/JSResourceLocator.php @@ -27,12 +27,14 @@ */ namespace OC\Template; +use Psr\Log\LoggerInterface; + class JSResourceLocator extends ResourceLocator { /** @var JSCombiner */ protected $jsCombiner; - public function __construct(\OCP\ILogger $logger, $theme, array $core_map, array $party_map, JSCombiner $JSCombiner) { + public function __construct(LoggerInterface $logger, $theme, array $core_map, array $party_map, JSCombiner $JSCombiner) { parent::__construct($logger, $theme, $core_map, $party_map); $this->jsCombiner = $JSCombiner; diff --git a/lib/private/Template/ResourceLocator.php b/lib/private/Template/ResourceLocator.php index 3ca34259907..5a50cc6fd1b 100755 --- a/lib/private/Template/ResourceLocator.php +++ b/lib/private/Template/ResourceLocator.php @@ -29,6 +29,8 @@ */ namespace OC\Template; +use Psr\Log\LoggerInterface; + abstract class ResourceLocator { protected $theme; @@ -39,16 +41,14 @@ abstract class ResourceLocator { protected $resources = []; - /** @var \OCP\ILogger */ - protected $logger; + protected LoggerInterface $logger; /** - * @param \OCP\ILogger $logger * @param string $theme * @param array $core_map * @param array $party_map */ - public function __construct(\OCP\ILogger $logger, $theme, $core_map, $party_map) { + public function __construct(LoggerInterface $logger, $theme, $core_map, $party_map) { $this->logger = $logger; $this->theme = $theme; $this->mapping = $core_map + $party_map; diff --git a/lib/private/Template/SCSSCacher.php b/lib/private/Template/SCSSCacher.php index 536557e7a3d..a552122b358 100644 --- a/lib/private/Template/SCSSCacher.php +++ b/lib/private/Template/SCSSCacher.php @@ -41,16 +41,14 @@ use OCP\Files\SimpleFS\ISimpleFolder; use OCP\ICache; use OCP\ICacheFactory; use OCP\IConfig; -use OCP\ILogger; use OCP\IMemcache; use OCP\IURLGenerator; +use Psr\Log\LoggerInterface; use ScssPhp\ScssPhp\Compiler; use ScssPhp\ScssPhp\OutputStyle; class SCSSCacher { - - /** @var ILogger */ - protected $logger; + protected LoggerInterface $logger; /** @var IAppData */ protected $appData; @@ -91,17 +89,9 @@ class SCSSCacher { private $appConfig; /** - * @param ILogger $logger - * @param Factory $appDataFactory - * @param IURLGenerator $urlGenerator - * @param IConfig $config - * @param \OC_Defaults $defaults * @param string $serverRoot - * @param ICacheFactory $cacheFactory - * @param IconsCacher $iconsCacher - * @param ITimeFactory $timeFactory */ - public function __construct(ILogger $logger, + public function __construct(LoggerInterface $logger, Factory $appDataFactory, IURLGenerator $urlGenerator, IConfig $config, @@ -340,7 +330,7 @@ class SCSSCacher { '@import "functions.scss";' . '@import "' . $fileNameSCSS . '";'); } catch (\Exception $e) { - $this->logger->logException($e, ['app' => 'scss_cacher']); + $this->logger->error($e->getMessage(), ['app' => 'scss_cacher', 'exception' => $e]); return false; } @@ -395,7 +385,7 @@ class SCSSCacher { try { $file->delete(); } catch (NotPermittedException $e) { - $this->logger->logException($e, ['message' => 'SCSSCacher::resetCache unable to delete file: ' . $file->getName(), 'app' => 'scss_cacher']); + $this->logger->error('SCSSCacher::resetCache unable to delete file: ' . $file->getName(), ['exception' => $e, 'app' => 'scss_cacher']); } } } @@ -431,7 +421,7 @@ class SCSSCacher { $scss->compile($variables); $this->injectedVariables = $variables; } catch (\Exception $e) { - $this->logger->logException($e, ['app' => 'scss_cacher']); + $this->logger->error($e->getMessage(), ['exception' => $e, 'app' => 'scss_cacher']); } return $variables; diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index d7a86ff92bf..cf06f021590 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -57,6 +57,7 @@ use OCP\IUserSession; use OCP\Support\Subscription\IRegistry; use OCP\UserStatus\IManager as IUserStatusManager; use OCP\Util; +use Psr\Log\LoggerInterface; class TemplateLayout extends \OC_Template { private static $versionHash = ''; @@ -345,7 +346,7 @@ class TemplateLayout extends \OC_Template { } $locator = new \OC\Template\CSSResourceLocator( - \OC::$server->getLogger(), + \OC::$server->get(LoggerInterface::class), $theme, [ \OC::$SERVERROOT => \OC::$WEBROOT ], [ \OC::$SERVERROOT => \OC::$WEBROOT ], @@ -380,7 +381,7 @@ class TemplateLayout extends \OC_Template { $theme = \OC_Util::getTheme(); $locator = new \OC\Template\JSResourceLocator( - \OC::$server->getLogger(), + \OC::$server->get(LoggerInterface::class), $theme, [ \OC::$SERVERROOT => \OC::$WEBROOT ], [ \OC::$SERVERROOT => \OC::$WEBROOT ], diff --git a/lib/private/Updater/ChangesCheck.php b/lib/private/Updater/ChangesCheck.php index e3ced6e5b12..e2b66853788 100644 --- a/lib/private/Updater/ChangesCheck.php +++ b/lib/private/Updater/ChangesCheck.php @@ -30,21 +30,20 @@ namespace OC\Updater; use OCP\AppFramework\Db\DoesNotExistException; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; -use OCP\ILogger; +use Psr\Log\LoggerInterface; class ChangesCheck { /** @var IClientService */ protected $clientService; /** @var ChangesMapper */ private $mapper; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; public const RESPONSE_NO_CONTENT = 0; public const RESPONSE_USE_CACHE = 1; public const RESPONSE_HAS_CONTENT = 2; - public function __construct(IClientService $clientService, ChangesMapper $mapper, ILogger $logger) { + public function __construct(IClientService $clientService, ChangesMapper $mapper, LoggerInterface $logger) { $this->clientService = $clientService; $this->mapper = $mapper; $this->logger = $logger; diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index cd26337cd20..365a01c4595 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -54,7 +54,6 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\NotPermittedException; use OCP\IConfig; -use OCP\ILogger; use OCP\IRequest; use OCP\ISession; use OCP\IUser; @@ -64,6 +63,7 @@ use OCP\Security\ISecureRandom; use OCP\Session\Exceptions\SessionNotAvailableException; use OCP\User\Events\PostLoginEvent; use OCP\Util; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\GenericEvent; /** @@ -114,21 +114,10 @@ class Session implements IUserSession, Emitter { /** @var ILockdownManager */ private $lockdownManager; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; /** @var IEventDispatcher */ private $dispatcher; - /** - * @param Manager $manager - * @param ISession $session - * @param ITimeFactory $timeFactory - * @param IProvider|null $tokenProvider - * @param IConfig $config - * @param ISecureRandom $random - * @param ILockdownManager $lockdownManager - * @param ILogger $logger - */ public function __construct(Manager $manager, ISession $session, ITimeFactory $timeFactory, @@ -136,7 +125,7 @@ class Session implements IUserSession, Emitter { IConfig $config, ISecureRandom $random, ILockdownManager $lockdownManager, - ILogger $logger, + LoggerInterface $logger, IEventDispatcher $dispatcher ) { $this->manager = $manager; @@ -533,9 +522,8 @@ class Session implements IUserSession, Emitter { } catch (ExpiredTokenException $e) { throw $e; } catch (InvalidTokenException $ex) { - $this->logger->logException($ex, [ - 'level' => ILogger::DEBUG, - 'message' => 'Token is not valid: ' . $ex->getMessage(), + $this->logger->debug('Token is not valid: ' . $ex->getMessage(), [ + 'exception' => $ex, ]); return false; } @@ -890,7 +878,7 @@ class Session implements IUserSession, Emitter { } catch (SessionNotAvailableException $ex) { return false; } catch (InvalidTokenException $ex) { - \OC::$server->getLogger()->warning('Renewing session token failed', ['app' => 'core']); + $this->logger->warning('Renewing session token failed', ['app' => 'core']); return false; } diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php index efb9252e346..547ffef8607 100644 --- a/lib/private/legacy/OC_Helper.php +++ b/lib/private/legacy/OC_Helper.php @@ -485,7 +485,7 @@ class OC_Helper { * @return array * @throws \OCP\Files\NotFoundException */ - public static function getStorageInfo($path, $rootInfo = null) { + public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true) { // return storage info without adding mount points $includeExtStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false); @@ -495,7 +495,7 @@ class OC_Helper { if (!$rootInfo instanceof \OCP\Files\FileInfo) { throw new \OCP\Files\NotFoundException(); } - $used = $rootInfo->getSize(); + $used = $rootInfo->getSize($includeMountPoints); if ($used < 0) { $used = 0; } diff --git a/lib/private/legacy/OC_Response.php b/lib/private/legacy/OC_Response.php index 6cfd53d2651..e4525fe9e10 100644 --- a/lib/private/legacy/OC_Response.php +++ b/lib/private/legacy/OC_Response.php @@ -97,7 +97,6 @@ class OC_Response { if (getenv('modHeadersAvailable') !== 'true') { header('Referrer-Policy: no-referrer'); // https://www.w3.org/TR/referrer-policy/ header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE - header('X-Download-Options: noopen'); // https://msdn.microsoft.com/en-us/library/jj542450(v=vs.85).aspx header('X-Frame-Options: SAMEORIGIN'); // Disallow iFraming from other domains header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html header('X-Robots-Tag: none'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag diff --git a/lib/public/DB/QueryBuilder/IFunctionBuilder.php b/lib/public/DB/QueryBuilder/IFunctionBuilder.php index 04c5cbd07bd..d4edc8ea9f8 100644 --- a/lib/public/DB/QueryBuilder/IFunctionBuilder.php +++ b/lib/public/DB/QueryBuilder/IFunctionBuilder.php @@ -124,6 +124,24 @@ interface IFunctionBuilder { public function count($count = '', $alias = ''): IQueryFunction; /** + * @param string|ILiteral|IParameter|IQueryFunction $field The input to be measured + * @param string $alias Alias for the length + * + * @return IQueryFunction + * @since 24.0.0 + */ + public function octetLength($field, $alias = ''): IQueryFunction; + + /** + * @param string|ILiteral|IParameter|IQueryFunction $field The input to be measured + * @param string $alias Alias for the length + * + * @return IQueryFunction + * @since 24.0.0 + */ + public function charLength($field, $alias = ''): IQueryFunction; + + /** * Takes the maximum of all rows in a column * * If you want to get the maximum value of multiple columns in the same row, use `greatest` instead diff --git a/lib/public/DataCollector/AbstractDataCollector.php b/lib/public/DataCollector/AbstractDataCollector.php new file mode 100644 index 00000000000..68298671b7b --- /dev/null +++ b/lib/public/DataCollector/AbstractDataCollector.php @@ -0,0 +1,87 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * @author Fabien Potencier <fabien@symfony.com> + * + * @license AGPL-3.0-or-later AND MIT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP\DataCollector; + +/** + * Children of this class must store the collected data in + * the data property. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Bernhard Schussek <bschussek@symfony.com> + * @author Carl Schwan <carl@carlschwan.eu> + * @since 24.0.0 + */ +abstract class AbstractDataCollector implements IDataCollector, \JsonSerializable { + /** @var array */ + protected $data = []; + + /** + * @since 24.0.0 + */ + public function getName(): string { + return static::class; + } + + /** + * Reset the state of the profiler. By default it only empties the + * $this->data contents, but you can override this method to do + * additional cleaning. + * @since 24.0.0 + */ + public function reset(): void { + $this->data = []; + } + + /** + * @since 24.0.0 + */ + public function __sleep(): array { + return ['data']; + } + + /** + * @internal to prevent implementing \Serializable + * @since 24.0.0 + */ + final protected function serialize() { + } + + /** + * @internal to prevent implementing \Serializable + * @since 24.0.0 + */ + final protected function unserialize(string $data) { + } + + /** + * @since 24.0.0 + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() { + return $this->data; + } +} diff --git a/lib/public/DataCollector/IDataCollector.php b/lib/public/DataCollector/IDataCollector.php new file mode 100644 index 00000000000..0fb914727df --- /dev/null +++ b/lib/public/DataCollector/IDataCollector.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * @author Fabien Potencier <fabien@symfony.com> + * + * @license AGPL-3.0-or-later AND MIT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP\DataCollector; + +use OC\AppFramework\Http\Request; +use OCP\AppFramework\Http\Response; + +/** + * DataCollectorInterface. + * + * @since 24.0.0 + */ +interface IDataCollector { + /** + * Collects data for the given Request and Response. + * @since 24.0.0 + */ + public function collect(Request $request, Response $response, \Throwable $exception = null): void; + + /** + * Reset the state of the profiler. + * @since 24.0.0 + */ + public function reset(): void; + + /** + * Returns the name of the collector. + * @since 24.0.0 + */ + public function getName(): string; +} diff --git a/lib/public/Files/Config/IMountProviderCollection.php b/lib/public/Files/Config/IMountProviderCollection.php index f845d72cee6..2d42246b863 100644 --- a/lib/public/Files/Config/IMountProviderCollection.php +++ b/lib/public/Files/Config/IMountProviderCollection.php @@ -39,6 +39,16 @@ interface IMountProviderCollection { public function getMountsForUser(IUser $user); /** + * Get the configured mount points for the user from a specific mount provider + * + * @param \OCP\IUser $user + * @param class-string<IMountProvider>[] $mountProviderClasses + * @return \OCP\Files\Mount\IMountPoint[] + * @since 24.0.0 + */ + public function getUserMountsForProviderClasses(IUser $user, array $mountProviderClasses): array; + + /** * Get the configured home mount for this user * * @param \OCP\IUser $user diff --git a/lib/public/Files/Config/IUserMountCache.php b/lib/public/Files/Config/IUserMountCache.php index 08f95990d3c..4411200c7ae 100644 --- a/lib/public/Files/Config/IUserMountCache.php +++ b/lib/public/Files/Config/IUserMountCache.php @@ -25,6 +25,7 @@ namespace OCP\Files\Config; use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotFoundException; use OCP\IUser; /** @@ -38,9 +39,10 @@ interface IUserMountCache { * * @param IUser $user * @param IMountPoint[] $mounts + * @param array|null $mountProviderClasses * @since 9.0.0 */ - public function registerMounts(IUser $user, array $mounts); + public function registerMounts(IUser $user, array $mounts, array $mountProviderClasses = null); /** * Get all cached mounts for a user @@ -125,4 +127,26 @@ interface IUserMountCache { * @since 20.0.0 */ public function clear(): void; + + /** + * Get all cached mounts for a user + * + * @param IUser $user + * @param string $path + * @return ICachedMountInfo + * @throws NotFoundException + * @since 24.0.0 + */ + public function getMountForPath(IUser $user, string $path): ICachedMountInfo; + + /** + * Get all cached mounts for a user inside a path + * + * @param IUser $user + * @param string $path + * @return ICachedMountInfo[] + * @throws NotFoundException + * @since 24.0.0 + */ + public function getMountsInPath(IUser $user, string $path): array; } diff --git a/lib/public/Files/Events/InvalidateMountCacheEvent.php b/lib/public/Files/Events/InvalidateMountCacheEvent.php new file mode 100644 index 00000000000..6508e168d4c --- /dev/null +++ b/lib/public/Files/Events/InvalidateMountCacheEvent.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP\Files\Events; + +use OCP\EventDispatcher\Event; +use OCP\IUser; + +/** + * Used to notify the filesystem setup manager that the available mounts for a user have changed + * + * @since 24.0.0 + */ +class InvalidateMountCacheEvent extends Event { + private ?IUser $user; + + /** + * @param IUser|null $user user + * + * @since 24.0.0 + */ + public function __construct(?IUser $user) { + parent::__construct(); + $this->user = $user; + } + + /** + * @return IUser|null user + * + * @since 24.0.0 + */ + public function getUser(): ?IUser { + return $this->user; + } +} diff --git a/lib/public/Files/IRootFolder.php b/lib/public/Files/IRootFolder.php index f89a0041146..7d007cb690c 100644 --- a/lib/public/Files/IRootFolder.php +++ b/lib/public/Files/IRootFolder.php @@ -38,11 +38,22 @@ interface IRootFolder extends Folder, Emitter { * Returns a view to user's files folder * * @param string $userId user ID - * @return \OCP\Files\Folder + * @return Folder * @throws NoUserException * @throws NotPermittedException * * @since 8.2.0 */ public function getUserFolder($userId); + + /** + * Get a file or folder by fileid, inside a parent path + * + * @param int $id + * @param string $path + * @return Node[] + * + * @since 24.0.0 + */ + public function getByIdInPath(int $id, string $path); } diff --git a/lib/public/ICache.php b/lib/public/ICache.php index 47b0e2f4c3d..0e818277f60 100644 --- a/lib/public/ICache.php +++ b/lib/public/ICache.php @@ -76,4 +76,10 @@ interface ICache { * @since 6.0.0 */ public function clear($prefix = ''); + + /** + * Check if the cache implementation is available + * @since 24.0.0 + */ + public static function isAvailable(): bool; } diff --git a/lib/public/IRequestId.php b/lib/public/IRequestId.php new file mode 100644 index 00000000000..dba06088cc9 --- /dev/null +++ b/lib/public/IRequestId.php @@ -0,0 +1,39 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2022, Joas Schilling <coding@schilljs.com> + * + * @author Joas Schilling <coding@schilljs.com> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCP; + +/** + * @since 24.0.0 + */ +interface IRequestId { + /** + * Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging + * If `mod_unique_id` is installed this value will be taken. + * + * @return string + * @since 24.0.0 + */ + public function getId(): string; +} diff --git a/lib/public/Profiler/IProfile.php b/lib/public/Profiler/IProfile.php new file mode 100644 index 00000000000..1831496a5a7 --- /dev/null +++ b/lib/public/Profiler/IProfile.php @@ -0,0 +1,168 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP\Profiler; + +use OCP\DataCollector\IDataCollector; + +/** + * This interface store the results of the profiling of one + * request. You can get the saved profiles from the @see IProfiler. + * + * ```php + * <?php + * $profiler = \OC::$server->get(IProfiler::class); + * $profiles = $profiler->find('/settings/users', 10); + * ``` + * + * This interface is meant to be used directly and not extended. + * @since 24.0.0 + */ +interface IProfile { + /** + * Get the token of the profile + * @since 24.0.0 + */ + public function getToken(): string; + + /** + * Set the token of the profile + * @since 24.0.0 + */ + public function setToken(string $token): void; + + /** + * Get the time of the profile + * @since 24.0.0 + */ + public function getTime(): ?int; + + /** + * Set the time of the profile + * @since 24.0.0 + */ + public function setTime(int $time): void; + + /** + * Get the url of the profile + * @since 24.0.0 + */ + public function getUrl(): ?string; + + /** + * Set the url of the profile + * @since 24.0.0 + */ + public function setUrl(string $url): void; + + /** + * Get the method of the profile + * @since 24.0.0 + */ + public function getMethod(): ?string; + + /** + * Set the method of the profile + * @since 24.0.0 + */ + public function setMethod(string $method): void; + + /** + * Get the status code of the profile + * @since 24.0.0 + */ + public function getStatusCode(): ?int; + + /** + * Set the status code of the profile + * @since 24.0.0 + */ + public function setStatusCode(int $statusCode): void; + + /** + * Add a data collector to the profile + * @since 24.0.0 + */ + public function addCollector(IDataCollector $collector); + + /** + * Get the parent profile to this profile + * @since 24.0.0 + */ + public function getParent(): ?IProfile; + + /** + * Set the parent profile to this profile + * @since 24.0.0 + */ + public function setParent(?IProfile $parent): void; + + /** + * Get the parent token to this profile + * @since 24.0.0 + */ + public function getParentToken(): ?string; + + /** + * Get the profile's children + * @return IProfile[] + * @since 24.0.0 + **/ + public function getChildren(): array; + + /** + * Set the profile's children + * @param IProfile[] $children + * @since 24.0.0 + */ + public function setChildren(array $children): void; + + /** + * Add the child profile + * @since 24.0.0 + */ + public function addChild(IProfile $profile): void; + + /** + * Get all the data collectors + * @return IDataCollector[] + * @since 24.0.0 + */ + public function getCollectors(): array; + + /** + * Set all the data collectors + * @param IDataCollector[] $collectors + * @since 24.0.0 + */ + public function setCollectors(array $collectors): void; + + /** + * Get a data collector by name + * @since 24.0.0 + */ + public function getCollector(string $collectorName): ?IDataCollector; +} diff --git a/lib/public/Profiler/IProfiler.php b/lib/public/Profiler/IProfiler.php new file mode 100644 index 00000000000..78325089523 --- /dev/null +++ b/lib/public/Profiler/IProfiler.php @@ -0,0 +1,101 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP\Profiler; + +use OC\AppFramework\Http\Request; +use OCP\AppFramework\Http\Response; +use OCP\DataCollector\IDataCollector; + +/** + * This interface allows to interact with the built-in Nextcloud profiler. + * @since 24.0.0 + */ +interface IProfiler { + /** + * Add a new data collector to the profiler. This allows to later on + * collect all the data from every registered collector. + * + * @see IDataCollector + * @since 24.0.0 + */ + public function add(IDataCollector $dataCollector): void; + + /** + * Load a profile from a response object + * @since 24.0.0 + */ + public function loadProfileFromResponse(Response $response): ?IProfile; + + /** + * Load a profile from the response token + * @since 24.0.0 + */ + public function loadProfile(string $token): ?IProfile; + + /** + * Save a profile on the disk. This allows to later load it again in the + * profiler user interface. + * @since 24.0.0 + */ + public function saveProfile(IProfile $profile): bool; + + /** + * Find a profile from various search parameters + * @since 24.0.0 + */ + public function find(?string $url, ?int $limit, ?string $method, ?int $start, ?int $end, string $statusCode = null): array; + + /** + * Get the list of data providers by identifier + * @return string[] + * @since 24.0.0 + */ + public function dataProviders(): array; + + /** + * Check if the profiler is enabled. + * + * If it is not enabled, data provider shouldn't be created and + * shouldn't collect any data. + * @since 24.0.0 + */ + public function isEnabled(): bool; + + /** + * Set if the profiler is enabled. + * @see isEnabled + * @since 24.0.0 + */ + public function setEnabled(bool $enabled): void; + + /** + * Collect all the information from the current request and construct + * a IProfile from it. + * @since 24.0.0 + */ + public function collect(Request $request, Response $response): IProfile; +} |