aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Files
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Files')
-rw-r--r--lib/private/Files/Cache/Cache.php241
-rw-r--r--lib/private/Files/Cache/CacheEntry.php4
-rw-r--r--lib/private/Files/Cache/CacheQueryBuilder.php17
-rw-r--r--lib/private/Files/Cache/FailedCache.php2
-rw-r--r--lib/private/Files/Cache/FileAccess.php129
-rw-r--r--lib/private/Files/Cache/Propagator.php24
-rw-r--r--lib/private/Files/Cache/QuerySearchHelper.php14
-rw-r--r--lib/private/Files/Cache/Scanner.php259
-rw-r--r--lib/private/Files/Cache/SearchBuilder.php28
-rw-r--r--lib/private/Files/Cache/Storage.php11
-rw-r--r--lib/private/Files/Cache/StorageGlobal.php14
-rw-r--r--lib/private/Files/Cache/Updater.php78
-rw-r--r--lib/private/Files/Cache/Watcher.php15
-rw-r--r--lib/private/Files/Cache/Wrapper/CacheJail.php48
-rw-r--r--lib/private/Files/Cache/Wrapper/CacheWrapper.php19
-rw-r--r--lib/private/Files/Cache/Wrapper/JailPropagator.php1
-rw-r--r--lib/private/Files/Cache/Wrapper/JailWatcher.php3
-rw-r--r--lib/private/Files/Config/CachedMountFileInfo.php3
-rw-r--r--lib/private/Files/Config/CachedMountInfo.php2
-rw-r--r--lib/private/Files/Config/MountProviderCollection.php122
-rw-r--r--lib/private/Files/Config/UserMountCache.php90
-rw-r--r--lib/private/Files/Conversion/ConversionManager.php181
-rw-r--r--lib/private/Files/FileInfo.php26
-rw-r--r--lib/private/Files/FilenameValidator.php335
-rw-r--r--lib/private/Files/Filesystem.php7
-rw-r--r--lib/private/Files/Mount/HomeMountPoint.php2
-rw-r--r--lib/private/Files/Mount/Manager.php4
-rw-r--r--lib/private/Files/Mount/MountPoint.php4
-rw-r--r--lib/private/Files/Mount/ObjectHomeMountProvider.php110
-rw-r--r--lib/private/Files/Mount/RootMountProvider.php64
-rw-r--r--lib/private/Files/Node/Folder.php72
-rw-r--r--lib/private/Files/Node/HookConnector.php8
-rw-r--r--lib/private/Files/Node/LazyFolder.php10
-rw-r--r--lib/private/Files/Node/LazyUserFolder.php2
-rw-r--r--lib/private/Files/Node/Node.php15
-rw-r--r--lib/private/Files/Node/NonExistingFile.php8
-rw-r--r--lib/private/Files/Node/NonExistingFolder.php8
-rw-r--r--lib/private/Files/Node/Root.php49
-rw-r--r--lib/private/Files/Notify/Change.php1
-rw-r--r--lib/private/Files/Notify/RenameChange.php1
-rw-r--r--lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php10
-rw-r--r--lib/private/Files/ObjectStore/Azure.php7
-rw-r--r--lib/private/Files/ObjectStore/HomeObjectStoreStorage.php18
-rw-r--r--lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php13
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreScanner.php6
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php274
-rw-r--r--lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php225
-rw-r--r--lib/private/Files/ObjectStore/S3.php55
-rw-r--r--lib/private/Files/ObjectStore/S3ConfigTrait.php6
-rw-r--r--lib/private/Files/ObjectStore/S3ConnectionTrait.php82
-rw-r--r--lib/private/Files/ObjectStore/S3ObjectTrait.php177
-rw-r--r--lib/private/Files/ObjectStore/S3Signature.php13
-rw-r--r--lib/private/Files/ObjectStore/StorageObjectStore.php5
-rw-r--r--lib/private/Files/ObjectStore/SwiftFactory.php10
-rw-r--r--lib/private/Files/Search/QueryOptimizer/FlattenNestedBool.php4
-rw-r--r--lib/private/Files/Search/QueryOptimizer/FlattenSingleArgumentBinaryOperation.php10
-rw-r--r--lib/private/Files/Search/QueryOptimizer/OrEqualsToIn.php6
-rw-r--r--lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php6
-rw-r--r--lib/private/Files/Search/QueryOptimizer/SplitLargeIn.php8
-rw-r--r--lib/private/Files/Search/SearchBinaryOperator.php1
-rw-r--r--lib/private/Files/Search/SearchComparison.php2
-rw-r--r--lib/private/Files/Search/SearchOrder.php3
-rw-r--r--lib/private/Files/Search/SearchQuery.php11
-rw-r--r--lib/private/Files/SetupManager.php63
-rw-r--r--lib/private/Files/SetupManagerFactory.php3
-rw-r--r--lib/private/Files/SimpleFS/SimpleFile.php11
-rw-r--r--lib/private/Files/SimpleFS/SimpleFolder.php1
-rw-r--r--lib/private/Files/Storage/Common.php441
-rw-r--r--lib/private/Files/Storage/CommonTest.php32
-rw-r--r--lib/private/Files/Storage/DAV.php239
-rw-r--r--lib/private/Files/Storage/FailedStorage.php91
-rw-r--r--lib/private/Files/Storage/Home.php39
-rw-r--r--lib/private/Files/Storage/Local.php155
-rw-r--r--lib/private/Files/Storage/LocalRootStorage.php9
-rw-r--r--lib/private/Files/Storage/LocalTempFileTrait.php9
-rw-r--r--lib/private/Files/Storage/PolyFill/CopyDirectory.php35
-rw-r--r--lib/private/Files/Storage/Storage.php103
-rw-r--r--lib/private/Files/Storage/StorageFactory.php39
-rw-r--r--lib/private/Files/Storage/Temporary.php14
-rw-r--r--lib/private/Files/Storage/Wrapper/Availability.php354
-rw-r--r--lib/private/Files/Storage/Wrapper/Encoding.php314
-rw-r--r--lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php7
-rw-r--r--lib/private/Files/Storage/Wrapper/Encryption.php412
-rw-r--r--lib/private/Files/Storage/Wrapper/Jail.php353
-rw-r--r--lib/private/Files/Storage/Wrapper/KnownMtime.php34
-rw-r--r--lib/private/Files/Storage/Wrapper/PermissionsMask.php52
-rw-r--r--lib/private/Files/Storage/Wrapper/Quota.php82
-rw-r--r--lib/private/Files/Storage/Wrapper/Wrapper.php438
-rw-r--r--lib/private/Files/Stream/Encryption.php124
-rw-r--r--lib/private/Files/Stream/Quota.php2
-rw-r--r--lib/private/Files/Stream/SeekableHttpStream.php3
-rw-r--r--lib/private/Files/Template/TemplateManager.php91
-rw-r--r--lib/private/Files/Type/Detection.php194
-rw-r--r--lib/private/Files/Type/Loader.php15
-rw-r--r--lib/private/Files/Utils/PathHelper.php2
-rw-r--r--lib/private/Files/Utils/Scanner.php25
-rw-r--r--lib/private/Files/View.php323
97 files changed, 3546 insertions, 3546 deletions
diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php
index 3c871fdf4dc..329466e682d 100644
--- a/lib/private/Files/Cache/Cache.php
+++ b/lib/private/Files/Cache/Cache.php
@@ -9,6 +9,7 @@ namespace OC\Files\Cache;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OC\DB\Exceptions\DbalException;
+use OC\DB\QueryBuilder\Sharded\ShardDefinition;
use OC\Files\Search\SearchComparison;
use OC\Files\Search\SearchQuery;
use OC\Files\Storage\Wrapper\Encryption;
@@ -54,7 +55,7 @@ class Cache implements ICache {
protected array $partial = [];
protected string $storageId;
protected Storage $storageCache;
- protected IMimeTypeLoader$mimetypeLoader;
+ protected IMimeTypeLoader $mimetypeLoader;
protected IDBConnection $connection;
protected SystemConfig $systemConfig;
protected LoggerInterface $logger;
@@ -73,7 +74,7 @@ class Cache implements ICache {
$this->storageId = md5($this->storageId);
}
if (!$dependencies) {
- $dependencies = \OC::$server->get(CacheDependencies::class);
+ $dependencies = \OCP\Server::get(CacheDependencies::class);
}
$this->storageCache = new Storage($this->storage, true, $dependencies->getConnection());
$this->mimetypeLoader = $dependencies->getMimeTypeLoader();
@@ -87,9 +88,7 @@ class Cache implements ICache {
protected function getQueryBuilder() {
return new CacheQueryBuilder(
- $this->connection,
- $this->systemConfig,
- $this->logger,
+ $this->connection->getQueryBuilder(),
$this->metadataManager,
);
}
@@ -110,7 +109,7 @@ class Cache implements ICache {
/**
* get the stored metadata of a file or folder
*
- * @param string | int $file either the path of a file or folder or the file id for a file or folder
+ * @param string|int $file either the path of a file or folder or the file id for a file or folder
* @return ICacheEntry|false the cache entry as array or false if the file is not found in the cache
*/
public function get($file) {
@@ -122,25 +121,27 @@ class Cache implements ICache {
// normalize file
$file = $this->normalize($file);
- $query->whereStorageId($this->getNumericStorageId())
- ->wherePath($file);
+ $query->wherePath($file);
} else { //file id
$query->whereFileId($file);
}
+ $query->whereStorageId($this->getNumericStorageId());
- $result = $query->execute();
+ $result = $query->executeQuery();
$data = $result->fetch();
$result->closeCursor();
- //merge partial data
- if (!$data && is_string($file) && isset($this->partial[$file])) {
- return $this->partial[$file];
- } elseif (!$data) {
- return $data;
- } else {
+ if ($data !== false) {
$data['metadata'] = $metadataQuery->extractMetadata($data)->asArray();
return self::cacheEntryFromData($data, $this->mimetypeLoader);
+ } else {
+ //merge partial data
+ if (is_string($file) && isset($this->partial[$file])) {
+ return $this->partial[$file];
+ }
}
+
+ return false;
}
/**
@@ -169,6 +170,9 @@ class Cache implements ICache {
if ($data['storage_mtime'] == 0) {
$data['storage_mtime'] = $data['mtime'];
}
+ if (isset($data['f_permissions'])) {
+ $data['scan_permissions'] = $data['f_permissions'];
+ }
$data['permissions'] = (int)$data['permissions'];
if (isset($data['creation_time'])) {
$data['creation_time'] = (int)$data['creation_time'];
@@ -201,11 +205,12 @@ class Cache implements ICache {
$query = $this->getQueryBuilder();
$query->selectFileCache()
->whereParent($fileId)
+ ->whereStorageId($this->getNumericStorageId())
->orderBy('name', 'ASC');
$metadataQuery = $query->selectMetadata();
- $result = $query->execute();
+ $result = $query->executeQuery();
$files = $result->fetchAll();
$result->closeCursor();
@@ -249,14 +254,14 @@ class Cache implements ICache {
$file = $this->normalize($file);
if (isset($this->partial[$file])) { //add any saved partial data
- $data = array_merge($this->partial[$file], $data);
+ $data = array_merge($this->partial[$file]->getData(), $data);
unset($this->partial[$file]);
}
$requiredFields = ['size', 'mtime', 'mimetype'];
foreach ($requiredFields as $field) {
if (!isset($data[$field])) { //data not complete save as partial and return
- $this->partial[$file] = $data;
+ $this->partial[$file] = new CacheEntry($data);
return -1;
}
}
@@ -265,6 +270,9 @@ class Cache implements ICache {
if (!isset($data['parent'])) {
$data['parent'] = $this->getParentId($file);
}
+ if ($data['parent'] === -1 && $file !== '') {
+ throw new \Exception('Parent folder not in filecache for ' . $file);
+ }
$data['name'] = basename($file);
[$values, $extensionValues] = $this->normalizeData($data);
@@ -285,12 +293,13 @@ class Cache implements ICache {
if (count($extensionValues)) {
$query = $this->getQueryBuilder();
$query->insert('filecache_extended');
+ $query->hintShardKey('storage', $storageId);
$query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
foreach ($extensionValues as $column => $value) {
$query->setValue($column, $query->createNamedParameter($value));
}
- $query->execute();
+ $query->executeStatement();
}
$event = new CacheEntryInsertedEvent($this->storage, $file, $fileId, $storageId);
@@ -339,6 +348,7 @@ class Cache implements ICache {
$query->update('filecache')
->whereFileId($id)
+ ->whereStorageId($this->getNumericStorageId())
->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
return $query->expr()->orX(
$query->expr()->neq($key, $query->createNamedParameter($value)),
@@ -350,13 +360,14 @@ class Cache implements ICache {
$query->set($key, $query->createNamedParameter($value));
}
- $query->execute();
+ $query->executeStatement();
}
if (count($extensionValues)) {
try {
$query = $this->getQueryBuilder();
$query->insert('filecache_extended');
+ $query->hintShardKey('storage', $this->getNumericStorageId());
$query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT));
foreach ($extensionValues as $column => $value) {
@@ -368,6 +379,7 @@ class Cache implements ICache {
$query = $this->getQueryBuilder();
$query->update('filecache_extended')
->whereFileId($id)
+ ->hintShardKey('storage', $this->getNumericStorageId())
->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
return $query->expr()->orX(
$query->expr()->neq($key, $query->createNamedParameter($value)),
@@ -379,7 +391,7 @@ class Cache implements ICache {
$query->set($key, $query->createNamedParameter($value));
}
- $query->execute();
+ $query->executeStatement();
}
}
@@ -461,7 +473,7 @@ class Cache implements ICache {
->whereStorageId($this->getNumericStorageId())
->wherePath($file);
- $result = $query->execute();
+ $result = $query->executeQuery();
$id = $result->fetchOne();
$result->closeCursor();
@@ -514,13 +526,15 @@ class Cache implements ICache {
if ($entry instanceof ICacheEntry) {
$query = $this->getQueryBuilder();
$query->delete('filecache')
+ ->whereStorageId($this->getNumericStorageId())
->whereFileId($entry->getId());
- $query->execute();
+ $query->executeStatement();
$query = $this->getQueryBuilder();
$query->delete('filecache_extended')
- ->whereFileId($entry->getId());
- $query->execute();
+ ->whereFileId($entry->getId())
+ ->hintShardKey('storage', $this->getNumericStorageId());
+ $query->executeStatement();
if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
$this->removeChildren($entry);
@@ -563,11 +577,12 @@ class Cache implements ICache {
$query = $this->getQueryBuilder();
$query->delete('filecache_extended')
- ->where($query->expr()->in('fileid', $query->createParameter('childIds')));
+ ->where($query->expr()->in('fileid', $query->createParameter('childIds')))
+ ->hintShardKey('storage', $this->getNumericStorageId());
foreach (array_chunk($childIds, 1000) as $childIdChunk) {
$query->setParameter('childIds', $childIdChunk, IQueryBuilder::PARAM_INT_ARRAY);
- $query->execute();
+ $query->executeStatement();
}
/** @var ICacheEntry[] $childFolders */
@@ -585,6 +600,7 @@ class Cache implements ICache {
$query = $this->getQueryBuilder();
$query->delete('filecache')
+ ->whereStorageId($this->getNumericStorageId())
->whereParentInParameter('parentIds');
// Sorting before chunking allows the db to find the entries close to each
@@ -592,7 +608,7 @@ class Cache implements ICache {
sort($parentIds, SORT_NUMERIC);
foreach (array_chunk($parentIds, 1000) as $parentIdChunk) {
$query->setParameter('parentIds', $parentIdChunk, IQueryBuilder::PARAM_INT_ARRAY);
- $query->execute();
+ $query->executeStatement();
}
foreach (array_combine($deletedIds, $deletedPaths) as $fileId => $filePath) {
@@ -647,7 +663,16 @@ class Cache implements ICache {
$sourceData = $sourceCache->get($sourcePath);
if (!$sourceData) {
- throw new \Exception('Invalid source storage path: ' . $sourcePath);
+ throw new \Exception('Source path not found in cache: ' . $sourcePath);
+ }
+
+ $shardDefinition = $this->connection->getShardDefinition('filecache');
+ if (
+ $shardDefinition
+ && $shardDefinition->getShardForKey($sourceCache->getNumericStorageId()) !== $shardDefinition->getShardForKey($this->getNumericStorageId())
+ ) {
+ $this->moveFromStorageSharded($shardDefinition, $sourceCache, $sourceData, $targetPath);
+ return;
}
$sourceId = $sourceData['fileid'];
@@ -671,7 +696,7 @@ class Cache implements ICache {
$childChunks = array_chunk($childIds, 1000);
- $query = $this->connection->getQueryBuilder();
+ $query = $this->getQueryBuilder();
$fun = $query->func();
$newPathFunction = $fun->concat(
@@ -679,12 +704,15 @@ class Cache implements ICache {
$fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
);
$query->update('filecache')
- ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
->set('path_hash', $fun->md5($newPathFunction))
->set('path', $newPathFunction)
- ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
+ ->whereStorageId($sourceStorageId)
->andWhere($query->expr()->in('fileid', $query->createParameter('files')));
+ if ($sourceStorageId !== $targetStorageId) {
+ $query->set('storage', $query->createNamedParameter($targetStorageId), IQueryBuilder::PARAM_INT);
+ }
+
// when moving from an encrypted storage to a non-encrypted storage remove the `encrypted` mark
if ($sourceCache->hasEncryptionWrapper() && !$this->hasEncryptionWrapper()) {
$query->set('encrypted', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT));
@@ -726,19 +754,23 @@ class Cache implements ICache {
$query = $this->getQueryBuilder();
$query->update('filecache')
- ->set('storage', $query->createNamedParameter($targetStorageId))
->set('path', $query->createNamedParameter($targetPath))
->set('path_hash', $query->createNamedParameter(md5($targetPath)))
->set('name', $query->createNamedParameter(basename($targetPath)))
->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT))
+ ->whereStorageId($sourceStorageId)
->whereFileId($sourceId);
+ if ($sourceStorageId !== $targetStorageId) {
+ $query->set('storage', $query->createNamedParameter($targetStorageId), IQueryBuilder::PARAM_INT);
+ }
+
// when moving from an encrypted storage to a non-encrypted storage remove the `encrypted` mark
if ($sourceCache->hasEncryptionWrapper() && !$this->hasEncryptionWrapper()) {
$query->set('encrypted', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT));
}
- $query->execute();
+ $query->executeStatement();
$this->connection->commit();
@@ -773,12 +805,12 @@ class Cache implements ICache {
$query = $this->getQueryBuilder();
$query->delete('filecache')
->whereStorageId($this->getNumericStorageId());
- $query->execute();
+ $query->executeStatement();
$query = $this->connection->getQueryBuilder();
$query->delete('storages')
->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
- $query->execute();
+ $query->executeStatement();
}
/**
@@ -803,7 +835,7 @@ class Cache implements ICache {
->whereStorageId($this->getNumericStorageId())
->wherePath($file);
- $result = $query->execute();
+ $result = $query->executeQuery();
$size = $result->fetchOne();
$result->closeCursor();
@@ -837,7 +869,7 @@ class Cache implements ICache {
* search for files by mimetype
*
* @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
- * where it will search for all mimetypes in the group ('image/*')
+ * where it will search for all mimetypes in the group ('image/*')
* @return ICacheEntry[] an array of cache entries where the mimetype matches the search
*/
public function searchByMime($mimetype) {
@@ -849,26 +881,30 @@ class Cache implements ICache {
return $this->searchQuery(new SearchQuery($operator, 0, 0, [], null));
}
- public function searchQuery(ISearchQuery $searchQuery) {
- return current($this->querySearchHelper->searchInCaches($searchQuery, [$this]));
+ public function searchQuery(ISearchQuery $query) {
+ return current($this->querySearchHelper->searchInCaches($query, [$this]));
}
/**
* Re-calculate the folder size and the size of all parent folders
*
- * @param string|boolean $path
- * @param array $data (optional) meta data of the folder
+ * @param array|ICacheEntry|null $data (optional) meta data of the folder
*/
- public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
+ public function correctFolderSize(string $path, $data = null, bool $isBackgroundScan = false): void {
$this->calculateFolderSize($path, $data);
+
if ($path !== '') {
$parent = dirname($path);
if ($parent === '.' || $parent === '/') {
$parent = '';
}
+
if ($isBackgroundScan) {
$parentData = $this->get($parent);
- if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
+ if ($parentData !== false
+ && $parentData['size'] !== -1
+ && $this->getIncompleteChildrenCount($parentData['fileid']) === 0
+ ) {
$this->correctFolderSize($parent, $parentData, $isBackgroundScan);
}
} else {
@@ -889,9 +925,10 @@ class Cache implements ICache {
$query->select($query->func()->count())
->from('filecache')
->whereParent($fileId)
- ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
+ ->whereStorageId($this->getNumericStorageId())
+ ->andWhere($query->expr()->eq('size', $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$size = (int)$result->fetchOne();
$result->closeCursor();
@@ -931,12 +968,13 @@ class Cache implements ICache {
$query = $this->getQueryBuilder();
$query->select('size', 'unencrypted_size')
->from('filecache')
+ ->whereStorageId($this->getNumericStorageId())
->whereParent($id);
if ($ignoreUnknown) {
$query->andWhere($query->expr()->gte('size', $query->createNamedParameter(0)));
}
- $result = $query->execute();
+ $result = $query->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
@@ -977,8 +1015,8 @@ class Cache implements ICache {
}
// only set unencrypted size for a folder if any child entries have it set, or the folder is empty
- $shouldWriteUnEncryptedSize = $unencryptedMax > 0 || $totalSize === 0 || $entry['unencrypted_size'] > 0;
- if ($entry['size'] !== $totalSize || ($entry['unencrypted_size'] !== $unencryptedTotal && $shouldWriteUnEncryptedSize)) {
+ $shouldWriteUnEncryptedSize = $unencryptedMax > 0 || $totalSize === 0 || ($entry['unencrypted_size'] ?? 0) > 0;
+ if ($entry['size'] !== $totalSize || (($entry['unencrypted_size'] ?? 0) !== $unencryptedTotal && $shouldWriteUnEncryptedSize)) {
if ($shouldWriteUnEncryptedSize) {
// if all children have an unencrypted size of 0, just set the folder unencrypted size to 0 instead of summing the sizes
if ($unencryptedMax === 0) {
@@ -1010,7 +1048,7 @@ class Cache implements ICache {
->from('filecache')
->whereStorageId($this->getNumericStorageId());
- $result = $query->execute();
+ $result = $query->executeQuery();
$files = $result->fetchAll(\PDO::FETCH_COLUMN);
$result->closeCursor();
@@ -1029,28 +1067,19 @@ class Cache implements ICache {
* @return string|false the path of the folder or false when no folder matched
*/
public function getIncomplete() {
- // we select the fileid here first instead of directly selecting the path since this helps mariadb/mysql
- // to use the correct index.
- // The overhead of this should be minimal since the cost of selecting the path by id should be much lower
- // than the cost of finding an item with size < 0
$query = $this->getQueryBuilder();
- $query->select('fileid')
+ $query->select('path')
->from('filecache')
->whereStorageId($this->getNumericStorageId())
- ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->eq('size', $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT)))
->orderBy('fileid', 'DESC')
->setMaxResults(1);
- $result = $query->execute();
- $id = $result->fetchOne();
+ $result = $query->executeQuery();
+ $path = $result->fetchOne();
$result->closeCursor();
- if ($id === false) {
- return false;
- }
-
- $path = $this->getPathById($id);
- return $path ?? false;
+ return $path === false ? false : (string)$path;
}
/**
@@ -1066,7 +1095,7 @@ class Cache implements ICache {
->whereStorageId($this->getNumericStorageId())
->whereFileId($id);
- $result = $query->execute();
+ $result = $query->executeQuery();
$path = $result->fetchOne();
$result->closeCursor();
@@ -1084,7 +1113,7 @@ class Cache implements ICache {
*
* @param int $id
* @return array first element holding the storage id, second the path
- * @deprecated use getPathById() instead
+ * @deprecated 17.0.0 use getPathById() instead
*/
public static function getById($id) {
$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
@@ -1092,7 +1121,7 @@ class Cache implements ICache {
->from('filecache')
->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$row = $result->fetch();
$result->closeCursor();
@@ -1130,7 +1159,7 @@ class Cache implements ICache {
*/
public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
if ($sourceEntry->getId() < 0) {
- throw new \RuntimeException("Invalid source cache entry on copyFromCache");
+ throw new \RuntimeException('Invalid source cache entry on copyFromCache');
}
$data = $this->cacheEntryToArray($sourceEntry);
@@ -1141,7 +1170,7 @@ class Cache implements ICache {
$fileId = $this->put($targetPath, $data);
if ($fileId <= 0) {
- throw new \RuntimeException("Failed to copy to " . $targetPath . " from cache with source data " . json_encode($data) . " ");
+ throw new \RuntimeException('Failed to copy to ' . $targetPath . ' from cache with source data ' . json_encode($data) . ' ');
}
if ($sourceEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
$folderContent = $sourceCache->getFolderContentsById($sourceEntry->getId());
@@ -1154,7 +1183,7 @@ class Cache implements ICache {
}
private function cacheEntryToArray(ICacheEntry $entry): array {
- return [
+ $data = [
'size' => $entry->getSize(),
'mtime' => $entry->getMTime(),
'storage_mtime' => $entry->getStorageMTime(),
@@ -1167,6 +1196,10 @@ class Cache implements ICache {
'upload_time' => $entry->getUploadTime(),
'metadata_etag' => $entry->getMetadataEtag(),
];
+ if ($entry instanceof CacheEntry && isset($entry['scan_permissions'])) {
+ $data['permissions'] = $entry['scan_permissions'];
+ }
+ return $data;
}
public function getQueryFilterForStorage(): ISearchOperator {
@@ -1180,4 +1213,72 @@ class Cache implements ICache {
return null;
}
}
+
+ private function moveFromStorageSharded(ShardDefinition $shardDefinition, ICache $sourceCache, ICacheEntry $sourceEntry, $targetPath): void {
+ if ($sourceEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
+ $fileIds = $this->getChildIds($sourceCache->getNumericStorageId(), $sourceEntry->getPath());
+ } else {
+ $fileIds = [];
+ }
+ $fileIds[] = $sourceEntry->getId();
+
+ $helper = $this->connection->getCrossShardMoveHelper();
+
+ $sourceConnection = $helper->getConnection($shardDefinition, $sourceCache->getNumericStorageId());
+ $targetConnection = $helper->getConnection($shardDefinition, $this->getNumericStorageId());
+
+ $cacheItems = $helper->loadItems($sourceConnection, 'filecache', 'fileid', $fileIds);
+ $extendedItems = $helper->loadItems($sourceConnection, 'filecache_extended', 'fileid', $fileIds);
+ $metadataItems = $helper->loadItems($sourceConnection, 'files_metadata', 'file_id', $fileIds);
+
+ // when moving from an encrypted storage to a non-encrypted storage remove the `encrypted` mark
+ $removeEncryptedFlag = ($sourceCache instanceof Cache && $sourceCache->hasEncryptionWrapper()) && !$this->hasEncryptionWrapper();
+
+ $sourcePathLength = strlen($sourceEntry->getPath());
+ foreach ($cacheItems as &$cacheItem) {
+ if ($cacheItem['path'] === $sourceEntry->getPath()) {
+ $cacheItem['path'] = $targetPath;
+ $cacheItem['parent'] = $this->getParentId($targetPath);
+ $cacheItem['name'] = basename($cacheItem['path']);
+ } else {
+ $cacheItem['path'] = $targetPath . '/' . substr($cacheItem['path'], $sourcePathLength + 1); // +1 for the leading slash
+ }
+ $cacheItem['path_hash'] = md5($cacheItem['path']);
+ $cacheItem['storage'] = $this->getNumericStorageId();
+ if ($removeEncryptedFlag) {
+ $cacheItem['encrypted'] = 0;
+ }
+ }
+
+ $targetConnection->beginTransaction();
+
+ try {
+ $helper->saveItems($targetConnection, 'filecache', $cacheItems);
+ $helper->saveItems($targetConnection, 'filecache_extended', $extendedItems);
+ $helper->saveItems($targetConnection, 'files_metadata', $metadataItems);
+ } catch (\Exception $e) {
+ $targetConnection->rollback();
+ throw $e;
+ }
+
+ $sourceConnection->beginTransaction();
+
+ try {
+ $helper->deleteItems($sourceConnection, 'filecache', 'fileid', $fileIds);
+ $helper->deleteItems($sourceConnection, 'filecache_extended', 'fileid', $fileIds);
+ $helper->deleteItems($sourceConnection, 'files_metadata', 'file_id', $fileIds);
+ } catch (\Exception $e) {
+ $targetConnection->rollback();
+ $sourceConnection->rollBack();
+ throw $e;
+ }
+
+ try {
+ $sourceConnection->commit();
+ } catch (\Exception $e) {
+ $targetConnection->rollback();
+ throw $e;
+ }
+ $targetConnection->commit();
+ }
}
diff --git a/lib/private/Files/Cache/CacheEntry.php b/lib/private/Files/Cache/CacheEntry.php
index e9417c8012a..ab5bae316f4 100644
--- a/lib/private/Files/Cache/CacheEntry.php
+++ b/lib/private/Files/Cache/CacheEntry.php
@@ -110,6 +110,10 @@ class CacheEntry implements ICacheEntry {
return $this->data['upload_time'] ?? null;
}
+ public function getParentId(): int {
+ return $this->data['parent'];
+ }
+
public function getData() {
return $this->data;
}
diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php
index 9bf5f970458..5492452273b 100644
--- a/lib/private/Files/Cache/CacheQueryBuilder.php
+++ b/lib/private/Files/Cache/CacheQueryBuilder.php
@@ -8,32 +8,27 @@ declare(strict_types=1);
*/
namespace OC\Files\Cache;
-use OC\DB\QueryBuilder\QueryBuilder;
-use OC\SystemConfig;
+use OC\DB\QueryBuilder\ExtendedQueryBuilder;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\FilesMetadata\IMetadataQuery;
-use OCP\IDBConnection;
-use Psr\Log\LoggerInterface;
/**
* Query builder with commonly used helpers for filecache queries
*/
-class CacheQueryBuilder extends QueryBuilder {
+class CacheQueryBuilder extends ExtendedQueryBuilder {
private ?string $alias = null;
public function __construct(
- IDBConnection $connection,
- SystemConfig $systemConfig,
- LoggerInterface $logger,
+ IQueryBuilder $queryBuilder,
private IFilesMetadataManager $filesMetadataManager,
) {
- parent::__construct($connection, $systemConfig, $logger);
+ parent::__construct($queryBuilder);
}
public function selectTagUsage(): self {
$this
- ->select('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable')
+ ->select('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable', 'systemtag.etag', 'systemtag.color')
->selectAlias($this->createFunction('COUNT(filecache.fileid)'), 'number_files')
->selectAlias($this->createFunction('MAX(filecache.fileid)'), 'ref_file_id')
->from('filecache', 'filecache')
@@ -53,7 +48,7 @@ class CacheQueryBuilder extends QueryBuilder {
public function selectFileCache(?string $alias = null, bool $joinExtendedCache = true) {
$name = $alias ?: 'filecache';
$this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime',
- 'storage_mtime', 'encrypted', 'etag', "$name.permissions", 'checksum', 'unencrypted_size')
+ 'storage_mtime', 'encrypted', "$name.etag", "$name.permissions", 'checksum', 'unencrypted_size')
->from('filecache', $name);
if ($joinExtendedCache) {
diff --git a/lib/private/Files/Cache/FailedCache.php b/lib/private/Files/Cache/FailedCache.php
index 8ba2ac491bf..44c1016ca8e 100644
--- a/lib/private/Files/Cache/FailedCache.php
+++ b/lib/private/Files/Cache/FailedCache.php
@@ -125,7 +125,7 @@ class FailedCache implements ICache {
}
public function copyFromCache(ICache $sourceCache, ICacheEntry $sourceEntry, string $targetPath): int {
- throw new \Exception("Invalid cache");
+ throw new \Exception('Invalid cache');
}
public function getQueryFilterForStorage(): ISearchOperator {
diff --git a/lib/private/Files/Cache/FileAccess.php b/lib/private/Files/Cache/FileAccess.php
index 5818017bd66..c3f3614f3ca 100644
--- a/lib/private/Files/Cache/FileAccess.php
+++ b/lib/private/Files/Cache/FileAccess.php
@@ -10,6 +10,7 @@ namespace OC\Files\Cache;
use OC\FilesMetadata\FilesMetadataManager;
use OC\SystemConfig;
+use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Cache\IFileAccess;
use OCP\Files\IMimeTypeLoader;
@@ -31,9 +32,7 @@ class FileAccess implements IFileAccess {
private function getQuery(): CacheQueryBuilder {
return new CacheQueryBuilder(
- $this->connection,
- $this->systemConfig,
- $this->logger,
+ $this->connection->getQueryBuilder(),
$this->metadataManager,
);
}
@@ -96,4 +95,128 @@ class FileAccess implements IFileAccess {
$rows = $query->executeQuery()->fetchAll();
return $this->rowsToEntries($rows);
}
+
+ public function getByAncestorInStorage(int $storageId, int $folderId, int $fileIdCursor = 0, int $maxResults = 100, array $mimeTypeIds = [], bool $endToEndEncrypted = true, bool $serverSideEncrypted = true): \Generator {
+ $qb = $this->getQuery();
+ $qb->select('path')
+ ->from('filecache')
+ ->where($qb->expr()->eq('fileid', $qb->createNamedParameter($folderId, IQueryBuilder::PARAM_INT)));
+ $result = $qb->executeQuery();
+ /** @var array{path:string}|false $root */
+ $root = $result->fetch();
+ $result->closeCursor();
+
+ if ($root === false) {
+ throw new Exception('Could not fetch storage root');
+ }
+
+ $qb = $this->getQuery();
+
+ $path = $root['path'] === '' ? '' : $root['path'] . '/';
+
+ $qb->selectDistinct('*')
+ ->from('filecache', 'f')
+ ->where($qb->expr()->like('f.path', $qb->createNamedParameter($this->connection->escapeLikeParameter($path) . '%')))
+ ->andWhere($qb->expr()->eq('f.storage', $qb->createNamedParameter($storageId)))
+ ->andWhere($qb->expr()->gt('f.fileid', $qb->createNamedParameter($fileIdCursor, IQueryBuilder::PARAM_INT)));
+
+ if (!$endToEndEncrypted) {
+ // End to end encrypted files are descendants of a folder with encrypted=1
+ // Use a subquery to check the `encrypted` status of the parent folder
+ $subQuery = $this->getQuery()->select('p.encrypted')
+ ->from('filecache', 'p')
+ ->andWhere($qb->expr()->eq('p.fileid', 'f.parent'))
+ ->getSQL();
+
+ $qb->andWhere(
+ $qb->expr()->eq($qb->createFunction(sprintf('(%s)', $subQuery)), $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))
+ );
+ }
+
+ if (!$serverSideEncrypted) {
+ // Server side encrypted files have encrypted=1 directly
+ $qb->andWhere($qb->expr()->eq('f.encrypted', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
+ }
+
+ if (count($mimeTypeIds) > 0) {
+ $qb->andWhere($qb->expr()->in('f.mimetype', $qb->createNamedParameter($mimeTypeIds, IQueryBuilder::PARAM_INT_ARRAY)));
+ }
+
+ if ($maxResults !== 0) {
+ $qb->setMaxResults($maxResults);
+ }
+ $qb->orderBy('f.fileid', 'ASC');
+ $files = $qb->executeQuery();
+
+ while (
+ /** @var array */
+ $row = $files->fetch()
+ ) {
+ yield Cache::cacheEntryFromData($row, $this->mimeTypeLoader);
+ }
+
+ $files->closeCursor();
+ }
+
+ public function getDistinctMounts(array $mountProviders = [], bool $onlyUserFilesMounts = true): \Generator {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->selectDistinct(['root_id', 'storage_id', 'mount_provider_class'])
+ ->from('mounts');
+ if ($onlyUserFilesMounts) {
+ $qb->andWhere(
+ $qb->expr()->orX(
+ $qb->expr()->like('mount_point', $qb->createNamedParameter('/%/files/%')),
+ $qb->expr()->in('mount_provider_class', $qb->createNamedParameter([
+ \OC\Files\Mount\LocalHomeMountProvider::class,
+ \OC\Files\Mount\ObjectHomeMountProvider::class,
+ ], IQueryBuilder::PARAM_STR_ARRAY))
+ )
+ );
+ }
+ if (count($mountProviders) > 0) {
+ $qb->andWhere($qb->expr()->in('mount_provider_class', $qb->createNamedParameter($mountProviders, IQueryBuilder::PARAM_STR_ARRAY)));
+ }
+ $qb->orderBy('root_id', 'ASC');
+ $result = $qb->executeQuery();
+
+ while (
+ /** @var array{storage_id:int, root_id:int,mount_provider_class:string} $row */
+ $row = $result->fetch()
+ ) {
+ $storageId = (int)$row['storage_id'];
+ $rootId = (int)$row['root_id'];
+ $overrideRoot = $rootId;
+ // LocalHomeMountProvider is the default provider for user home directories
+ // ObjectHomeMountProvider is the home directory provider for when S3 primary storage is used
+ if ($onlyUserFilesMounts && in_array($row['mount_provider_class'], [
+ \OC\Files\Mount\LocalHomeMountProvider::class,
+ \OC\Files\Mount\ObjectHomeMountProvider::class,
+ ], true)) {
+ // Only crawl files, not cache or trashbin
+ $qb = $this->getQuery();
+ try {
+ $qb->select('fileid')
+ ->from('filecache')
+ ->where($qb->expr()->eq('storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($rootId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('path', $qb->createNamedParameter('files')));
+ /** @var array|false $root */
+ $root = $qb->executeQuery()->fetch();
+ if ($root !== false) {
+ $overrideRoot = (int)$root['fileid'];
+ }
+ } catch (Exception $e) {
+ $this->logger->error('Could not fetch home storage files root for storage ' . $storageId, ['exception' => $e]);
+ continue;
+ }
+ }
+ // Reference to root_id is still necessary even if we have the overridden_root_id, because storage_id and root_id uniquely identify a mount
+ yield [
+ 'storage_id' => $storageId,
+ 'root_id' => $rootId,
+ 'overridden_root' => $overrideRoot,
+ ];
+ }
+ $result->closeCursor();
+ }
}
diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php
index 5580dcf22a8..a6ba87896f4 100644
--- a/lib/private/Files/Cache/Propagator.php
+++ b/lib/private/Files/Cache/Propagator.php
@@ -13,6 +13,8 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Cache\IPropagator;
use OCP\Files\Storage\IReliableEtagStorage;
use OCP\IDBConnection;
+use OCP\Server;
+use Psr\Clock\ClockInterface;
use Psr\Log\LoggerInterface;
/**
@@ -39,13 +41,15 @@ class Propagator implements IPropagator {
*/
private $ignore = [];
+ private ClockInterface $clock;
+
public function __construct(\OC\Files\Storage\Storage $storage, IDBConnection $connection, array $ignore = []) {
$this->storage = $storage;
$this->connection = $connection;
$this->ignore = $ignore;
+ $this->clock = Server::get(ClockInterface::class);
}
-
/**
* @param string $internalPath
* @param int $time
@@ -59,7 +63,9 @@ class Propagator implements IPropagator {
}
}
- $storageId = (int)$this->storage->getStorageCache()->getNumericId();
+ $time = min((int)$time, $this->clock->now()->getTimestamp());
+
+ $storageId = $this->storage->getStorageCache()->getNumericId();
$parents = $this->getParents($internalPath);
@@ -79,7 +85,7 @@ class Propagator implements IPropagator {
}, $parentHashes);
$builder->update('filecache')
- ->set('mtime', $builder->func()->greatest('mtime', $builder->createNamedParameter((int)$time, IQueryBuilder::PARAM_INT)))
+ ->set('mtime', $builder->func()->greatest('mtime', $builder->createNamedParameter($time, IQueryBuilder::PARAM_INT)))
->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($builder->expr()->in('path_hash', $hashParams));
if (!$this->storage->instanceOfStorage(IReliableEtagStorage::class)) {
@@ -186,27 +192,27 @@ class Propagator implements IPropagator {
$query->update('filecache')
->set('mtime', $query->func()->greatest('mtime', $query->createParameter('time')))
->set('etag', $query->expr()->literal(uniqid()))
- ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT)))
+ ->where($query->expr()->eq('storage', $query->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')));
$sizeQuery = $this->connection->getQueryBuilder();
$sizeQuery->update('filecache')
->set('size', $sizeQuery->func()->add('size', $sizeQuery->createParameter('size')))
- ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT)))
- ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash')))
- ->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->expr()->literal(-1, IQueryBuilder::PARAM_INT)));
+ ->where($query->expr()->eq('storage', $sizeQuery->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->eq('path_hash', $sizeQuery->createParameter('hash')))
+ ->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->createNamedParameter(-1, IQueryBuilder::PARAM_INT)));
foreach ($this->batch as $item) {
$query->setParameter('time', $item['time'], IQueryBuilder::PARAM_INT);
$query->setParameter('hash', $item['hash']);
- $query->execute();
+ $query->executeStatement();
if ($item['size']) {
$sizeQuery->setParameter('size', $item['size'], IQueryBuilder::PARAM_INT);
$sizeQuery->setParameter('hash', $item['hash']);
- $sizeQuery->execute();
+ $sizeQuery->executeStatement();
}
}
diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php
index c31d62e1a86..3ddcf1ca4e6 100644
--- a/lib/private/Files/Cache/QuerySearchHelper.php
+++ b/lib/private/Files/Cache/QuerySearchHelper.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -39,9 +40,7 @@ class QuerySearchHelper {
protected function getQueryBuilder() {
return new CacheQueryBuilder(
- $this->connection,
- $this->systemConfig,
- $this->logger,
+ $this->connection->getQueryBuilder(),
$this->filesMetadataManager,
);
}
@@ -56,7 +55,7 @@ class QuerySearchHelper {
CacheQueryBuilder $query,
ISearchQuery $searchQuery,
array $caches,
- ?IMetadataQuery $metadataQuery = null
+ ?IMetadataQuery $metadataQuery = null,
): void {
$storageFilters = array_values(array_map(function (ICache $cache) {
return $cache->getQueryFilterForStorage();
@@ -90,7 +89,7 @@ class QuerySearchHelper {
$this->applySearchConstraints($query, $searchQuery, $caches);
- $result = $query->execute();
+ $result = $query->executeQuery();
$tags = $result->fetchAll();
$result->closeCursor();
return $tags;
@@ -112,7 +111,6 @@ class QuerySearchHelper {
$query
->leftJoin('file', 'vcategory_to_object', 'tagmap', $query->expr()->eq('file.fileid', 'tagmap.objid'))
->leftJoin('tagmap', 'vcategory', 'tag', $query->expr()->andX(
- $query->expr()->eq('tagmap.type', 'tag.type'),
$query->expr()->eq('tagmap.categoryid', 'tag.id'),
$query->expr()->eq('tag.type', $query->createNamedParameter('files')),
$query->expr()->eq('tag.uid', $query->createNamedParameter($user->getUID()))
@@ -170,7 +168,7 @@ class QuerySearchHelper {
$this->applySearchConstraints($query, $searchQuery, $caches, $metadataQuery);
- $result = $query->execute();
+ $result = $query->executeQuery();
$files = $result->fetchAll();
$rawEntries = array_map(function (array $data) use ($metadataQuery) {
@@ -197,7 +195,7 @@ class QuerySearchHelper {
protected function requireUser(ISearchQuery $searchQuery): IUser {
$user = $searchQuery->getUser();
if ($user === null) {
- throw new \InvalidArgumentException("This search operation requires the user to be set in the query");
+ throw new \InvalidArgumentException('This search operation requires the user to be set in the query');
}
return $user;
}
diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php
index c85104ac4b9..b067f70b8cb 100644
--- a/lib/private/Files/Cache/Scanner.php
+++ b/lib/private/Files/Cache/Scanner.php
@@ -15,6 +15,7 @@ use OC\SystemConfig;
use OCP\Files\Cache\IScanner;
use OCP\Files\ForbiddenException;
use OCP\Files\NotFoundException;
+use OCP\Files\Storage\ILockingStorage;
use OCP\Files\Storage\IReliableEtagStorage;
use OCP\IDBConnection;
use OCP\Lock\ILockingProvider;
@@ -82,7 +83,7 @@ class Scanner extends BasicEmitter implements IScanner {
*
* @param bool $useTransactions
*/
- public function setUseTransactions($useTransactions) {
+ public function setUseTransactions($useTransactions): void {
$this->useTransactions = $useTransactions;
}
@@ -107,9 +108,9 @@ class Scanner extends BasicEmitter implements IScanner {
* @param string $file
* @param int $reuseExisting
* @param int $parentId
- * @param array|null|false $cacheData existing data in the cache for the file to be scanned
+ * @param array|CacheEntry|null|false $cacheData existing data in the cache for the file to be scanned
* @param bool $lock set to false to disable getting an additional read lock during scanning
- * @param null $data the metadata for the file, as returned by the storage
+ * @param array|null $data the metadata for the file, as returned by the storage
* @return array|null an array of metadata of the scanned file
* @throws \OCP\Lock\LockedException
*/
@@ -121,139 +122,130 @@ class Scanner extends BasicEmitter implements IScanner {
return null;
}
}
+
// only proceed if $file is not a partial file, blacklist is handled by the storage
- if (!self::isPartialFile($file)) {
- // acquire a lock
+ if (self::isPartialFile($file)) {
+ return null;
+ }
+
+ // acquire a lock
+ if ($lock) {
+ if ($this->storage->instanceOfStorage(ILockingStorage::class)) {
+ $this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
+ }
+ }
+
+ try {
+ $data = $data ?? $this->getData($file);
+ } catch (ForbiddenException $e) {
if ($lock) {
- if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
- $this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
+ if ($this->storage->instanceOfStorage(ILockingStorage::class)) {
+ $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
}
}
- try {
- $data = $data ?? $this->getData($file);
- } catch (ForbiddenException $e) {
- if ($lock) {
- if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
- $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
- }
- }
+ return null;
+ }
- return null;
- }
+ try {
+ if ($data === null) {
+ $this->removeFromCache($file);
+ } else {
+ // pre-emit only if it was a file. By that we avoid counting/treating folders as files
+ if ($data['mimetype'] !== 'httpd/unix-directory') {
+ $this->emit('\OC\Files\Cache\Scanner', 'scanFile', [$file, $this->storageId]);
+ \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', ['path' => $file, 'storage' => $this->storageId]);
+ }
- try {
- if ($data) {
- // pre-emit only if it was a file. By that we avoid counting/treating folders as files
- if ($data['mimetype'] !== 'httpd/unix-directory') {
- $this->emit('\OC\Files\Cache\Scanner', 'scanFile', [$file, $this->storageId]);
- \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', ['path' => $file, 'storage' => $this->storageId]);
- }
+ $parent = dirname($file);
+ if ($parent === '.' || $parent === '/') {
+ $parent = '';
+ }
+ if ($parentId === -1) {
+ $parentId = $this->cache->getParentId($file);
+ }
- $parent = dirname($file);
- if ($parent === '.' || $parent === '/') {
- $parent = '';
- }
- if ($parentId === -1) {
- $parentId = $this->cache->getParentId($file);
+ // scan the parent if it's not in the cache (id -1) and the current file is not the root folder
+ if ($file && $parentId === -1) {
+ $parentData = $this->scanFile($parent);
+ if ($parentData === null) {
+ return null;
}
- // scan the parent if it's not in the cache (id -1) and the current file is not the root folder
- if ($file && $parentId === -1) {
- $parentData = $this->scanFile($parent);
- if (!$parentData) {
- return null;
- }
- $parentId = $parentData['fileid'];
- }
- if ($parent) {
- $data['parent'] = $parentId;
- }
- if (is_null($cacheData)) {
- /** @var CacheEntry $cacheData */
- $cacheData = $this->cache->get($file);
- }
- if ($cacheData && $reuseExisting && isset($cacheData['fileid'])) {
- // prevent empty etag
- $etag = empty($cacheData['etag']) ? $data['etag'] : $cacheData['etag'];
- $fileId = $cacheData['fileid'];
- $data['fileid'] = $fileId;
- // only reuse data if the file hasn't explicitly changed
- $mtimeUnchanged = isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime'];
- // if the folder is marked as unscanned, never reuse etags
- if ($mtimeUnchanged && $cacheData['size'] !== -1) {
- $data['mtime'] = $cacheData['mtime'];
- if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
- $data['size'] = $cacheData['size'];
- }
- if ($reuseExisting & self::REUSE_ETAG && !$this->storage->instanceOfStorage(IReliableEtagStorage::class)) {
- $data['etag'] = $etag;
- }
- }
+ $parentId = $parentData['fileid'];
+ }
+ if ($parent) {
+ $data['parent'] = $parentId;
+ }
- // we only updated unencrypted_size if it's already set
- if ($cacheData['unencrypted_size'] === 0) {
- unset($data['unencrypted_size']);
+ $cacheData = $cacheData ?? $this->cache->get($file);
+ if ($reuseExisting && $cacheData !== false && isset($cacheData['fileid'])) {
+ // prevent empty etag
+ $etag = empty($cacheData['etag']) ? $data['etag'] : $cacheData['etag'];
+ $fileId = $cacheData['fileid'];
+ $data['fileid'] = $fileId;
+ // only reuse data if the file hasn't explicitly changed
+ $mtimeUnchanged = isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime'];
+ // if the folder is marked as unscanned, never reuse etags
+ if ($mtimeUnchanged && $cacheData['size'] !== -1) {
+ $data['mtime'] = $cacheData['mtime'];
+ if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
+ $data['size'] = $cacheData['size'];
}
-
- // Only update metadata that has changed
- // i.e. get all the values in $data that are not present in the cache already
- $newData = $this->array_diff_assoc_multi($data, $cacheData->getData());
-
- // make it known to the caller that etag has been changed and needs propagation
- if (isset($newData['etag'])) {
- $data['etag_changed'] = true;
+ if ($reuseExisting & self::REUSE_ETAG && !$this->storage->instanceOfStorage(IReliableEtagStorage::class)) {
+ $data['etag'] = $etag;
}
- } else {
- // we only updated unencrypted_size if it's already set
- unset($data['unencrypted_size']);
- $newData = $data;
- $fileId = -1;
- }
- if (!empty($newData)) {
- // Reset the checksum if the data has changed
- $newData['checksum'] = '';
- $newData['parent'] = $parentId;
- $data['fileid'] = $this->addToCache($file, $newData, $fileId);
}
- $data['oldSize'] = ($cacheData && isset($cacheData['size'])) ? $cacheData['size'] : 0;
-
- if ($cacheData && isset($cacheData['encrypted'])) {
- $data['encrypted'] = $cacheData['encrypted'];
+ // we only updated unencrypted_size if it's already set
+ if (isset($cacheData['unencrypted_size']) && $cacheData['unencrypted_size'] === 0) {
+ unset($data['unencrypted_size']);
}
- // post-emit only if it was a file. By that we avoid counting/treating folders as files
- if ($data['mimetype'] !== 'httpd/unix-directory') {
- $this->emit('\OC\Files\Cache\Scanner', 'postScanFile', [$file, $this->storageId]);
- \OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', ['path' => $file, 'storage' => $this->storageId]);
+ /**
+ * Only update metadata that has changed.
+ * i.e. get all the values in $data that are not present in the cache already
+ *
+ * We need the OC implementation for usage of "getData" method below.
+ * @var \OC\Files\Cache\CacheEntry $cacheData
+ */
+ $newData = $this->array_diff_assoc_multi($data, $cacheData->getData());
+
+ // make it known to the caller that etag has been changed and needs propagation
+ if (isset($newData['etag'])) {
+ $data['etag_changed'] = true;
}
} else {
- $this->removeFromCache($file);
+ unset($data['unencrypted_size']);
+ $newData = $data;
+ $fileId = -1;
}
- } catch (\Exception $e) {
- if ($lock) {
- if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
- $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
- }
+ if (!empty($newData)) {
+ // Reset the checksum if the data has changed
+ $newData['checksum'] = '';
+ $newData['parent'] = $parentId;
+ $data['fileid'] = $this->addToCache($file, $newData, $fileId);
}
- throw $e;
- }
- // release the acquired lock
- if ($lock) {
- if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
- $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
+ if ($cacheData !== false) {
+ $data['oldSize'] = $cacheData['size'] ?? 0;
+ $data['encrypted'] = $cacheData['encrypted'] ?? false;
}
- }
- if ($data && !isset($data['encrypted'])) {
- $data['encrypted'] = false;
+ // post-emit only if it was a file. By that we avoid counting/treating folders as files
+ if ($data['mimetype'] !== 'httpd/unix-directory') {
+ $this->emit('\OC\Files\Cache\Scanner', 'postScanFile', [$file, $this->storageId]);
+ \OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', ['path' => $file, 'storage' => $this->storageId]);
+ }
+ }
+ } finally {
+ // release the acquired lock
+ if ($lock && $this->storage->instanceOfStorage(ILockingStorage::class)) {
+ $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
}
- return $data;
}
- return null;
+ return $data;
}
protected function removeFromCache($path) {
@@ -318,29 +310,26 @@ class Scanner extends BasicEmitter implements IScanner {
if ($reuse === -1) {
$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
}
- if ($lock) {
- if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
- $this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
- $this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
- }
+
+ if ($lock && $this->storage->instanceOfStorage(ILockingStorage::class)) {
+ $this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
+ $this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
}
+
try {
- try {
- $data = $this->scanFile($path, $reuse, -1, null, $lock);
- if ($data && $data['mimetype'] === 'httpd/unix-directory') {
- $size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock, $data['size']);
- $data['size'] = $size;
- }
- } catch (NotFoundException $e) {
- $this->removeFromCache($path);
- return null;
+ $data = $this->scanFile($path, $reuse, -1, lock: $lock);
+
+ if ($data !== null && $data['mimetype'] === 'httpd/unix-directory') {
+ $size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock, $data['size']);
+ $data['size'] = $size;
}
+ } catch (NotFoundException $e) {
+ $this->removeFromCache($path);
+ return null;
} finally {
- if ($lock) {
- if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
- $this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
- $this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
- }
+ if ($lock && $this->storage->instanceOfStorage(ILockingStorage::class)) {
+ $this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
+ $this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
}
}
return $data;
@@ -362,23 +351,23 @@ class Scanner extends BasicEmitter implements IScanner {
*
*/
protected function array_diff_assoc_multi(array $array1, array $array2) {
-
+
$result = [];
foreach ($array1 as $key => $value) {
-
+
// if $array2 doesn't have the same key, that's a result
if (!array_key_exists($key, $array2)) {
$result[$key] = $value;
continue;
}
-
+
// if $array2's value for the same key is different, that's a result
if ($array2[$key] !== $value && !is_array($value)) {
$result[$key] = $value;
continue;
}
-
+
if (is_array($value)) {
$nestedDiff = $this->array_diff_assoc_multi($value, $array2[$key]);
if (!empty($nestedDiff)) {
@@ -394,9 +383,9 @@ class Scanner extends BasicEmitter implements IScanner {
* Get the children currently in the cache
*
* @param int $folderId
- * @return array[]
+ * @return array<string, \OCP\Files\Cache\ICacheEntry>
*/
- protected function getExistingChildren($folderId) {
+ protected function getExistingChildren($folderId): array {
$existingChildren = [];
$children = $this->cache->getFolderContentsById($folderId);
foreach ($children as $child) {
@@ -488,7 +477,7 @@ class Scanner extends BasicEmitter implements IScanner {
$file = trim(\OC\Files\Filesystem::normalizePath($originalFile), '/');
if (trim($originalFile, '/') !== $file) {
// encoding mismatch, might require compatibility wrapper
- \OC::$server->get(LoggerInterface::class)->debug('Scanner: Skipping non-normalized file name "'. $originalFile . '" in path "' . $path . '".', ['app' => 'core']);
+ \OC::$server->get(LoggerInterface::class)->debug('Scanner: Skipping non-normalized file name "' . $originalFile . '" in path "' . $path . '".', ['app' => 'core']);
$this->emit('\OC\Files\Cache\Scanner', 'normalizedNameMismatch', [$path ? $path . '/' . $originalFile : $originalFile]);
// skip this entry
continue;
diff --git a/lib/private/Files/Cache/SearchBuilder.php b/lib/private/Files/Cache/SearchBuilder.php
index 32502cb8258..e1d3c42a8a2 100644
--- a/lib/private/Files/Cache/SearchBuilder.php
+++ b/lib/private/Files/Cache/SearchBuilder.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -12,6 +13,7 @@ use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchOperator;
use OCP\Files\Search\ISearchOrder;
+use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\FilesMetadata\IMetadataQuery;
/**
@@ -64,7 +66,7 @@ class SearchBuilder {
'owner' => 'string',
];
- /** @var array<string, int> */
+ /** @var array<string, int|string> */
protected static $paramTypeMap = [
'string' => IQueryBuilder::PARAM_STR,
'integer' => IQueryBuilder::PARAM_INT,
@@ -80,13 +82,10 @@ class SearchBuilder {
public const TAG_FAVORITE = '_$!<Favorite>!$_';
- /** @var IMimeTypeLoader */
- private $mimetypeLoader;
-
public function __construct(
- IMimeTypeLoader $mimetypeLoader
+ private IMimeTypeLoader $mimetypeLoader,
+ private IFilesMetadataManager $filesMetadataManager,
) {
- $this->mimetypeLoader = $mimetypeLoader;
}
/**
@@ -110,7 +109,7 @@ class SearchBuilder {
public function searchOperatorArrayToDBExprArray(
IQueryBuilder $builder,
array $operators,
- ?IMetadataQuery $metadataQuery = null
+ ?IMetadataQuery $metadataQuery = null,
) {
return array_filter(array_map(function ($operator) use ($builder, $metadataQuery) {
return $this->searchOperatorToDBExpr($builder, $operator, $metadataQuery);
@@ -120,7 +119,7 @@ class SearchBuilder {
public function searchOperatorToDBExpr(
IQueryBuilder $builder,
ISearchOperator $operator,
- ?IMetadataQuery $metadataQuery = null
+ ?IMetadataQuery $metadataQuery = null,
) {
$expr = $builder->expr();
@@ -156,7 +155,7 @@ class SearchBuilder {
IQueryBuilder $builder,
ISearchComparison $comparison,
array $operatorMap,
- ?IMetadataQuery $metadataQuery = null
+ ?IMetadataQuery $metadataQuery = null,
) {
if ($comparison->getExtra()) {
[$field, $value, $type, $paramType] = $this->getExtraOperatorField($comparison, $metadataQuery);
@@ -285,12 +284,19 @@ class SearchBuilder {
private function getExtraOperatorField(ISearchComparison $operator, IMetadataQuery $metadataQuery): array {
- $paramType = self::$fieldTypes[$operator->getField()];
$field = $operator->getField();
$value = $operator->getValue();
$type = $operator->getType();
- switch($operator->getExtra()) {
+ $knownMetadata = $this->filesMetadataManager->getKnownMetadata();
+ $isIndex = $knownMetadata->isIndex($field);
+ $paramType = $knownMetadata->getType($field) === 'int' ? 'integer' : 'string';
+
+ if (!$isIndex) {
+ throw new \InvalidArgumentException('Cannot search non indexed metadata key');
+ }
+
+ switch ($operator->getExtra()) {
case IMetadataQuery::EXTRA:
$metadataQuery->joinIndex($field); // join index table if not joined yet
$field = $metadataQuery->getMetadataValueField($field);
diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php
index 0929907fcff..1a3bda58e6a 100644
--- a/lib/private/Files/Cache/Storage.php
+++ b/lib/private/Files/Cache/Storage.php
@@ -80,7 +80,7 @@ class Storage {
* Adjusts the storage id to use md5 if too long
* @param string $storageId storage id
* @return string unchanged $storageId if its length is less than 64 characters,
- * else returns the md5 of $storageId
+ * else returns the md5 of $storageId
*/
public static function adjustStorageId($storageId) {
if (strlen($storageId) > 64) {
@@ -149,7 +149,7 @@ class Storage {
public function setAvailability($isAvailable, int $delay = 0) {
$available = $isAvailable ? 1 : 0;
if (!$isAvailable) {
- \OC::$server->get(LoggerInterface::class)->info('Storage with ' . $this->storageId . ' marked as unavailable', ['app' => 'lib']);
+ \OCP\Server::get(LoggerInterface::class)->info('Storage with ' . $this->storageId . ' marked as unavailable', ['app' => 'lib']);
}
$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
@@ -157,7 +157,7 @@ class Storage {
->set('available', $query->createNamedParameter($available))
->set('last_checked', $query->createNamedParameter(time() + $delay))
->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
- $query->execute();
+ $query->executeStatement();
}
/**
@@ -182,13 +182,13 @@ class Storage {
$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$query->delete('storages')
->where($query->expr()->eq('id', $query->createNamedParameter($storageId)));
- $query->execute();
+ $query->executeStatement();
if (!is_null($numericId)) {
$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$query->delete('filecache')
->where($query->expr()->eq('storage', $query->createNamedParameter($numericId)));
- $query->execute();
+ $query->executeStatement();
}
}
@@ -213,6 +213,7 @@ class Storage {
$query = $db->getQueryBuilder();
$query->delete('filecache')
->where($query->expr()->in('storage', $query->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)));
+ $query->runAcrossAllShards();
$query->executeStatement();
$query = $db->getQueryBuilder();
diff --git a/lib/private/Files/Cache/StorageGlobal.php b/lib/private/Files/Cache/StorageGlobal.php
index b55c0fcdb91..bab31b1db91 100644
--- a/lib/private/Files/Cache/StorageGlobal.php
+++ b/lib/private/Files/Cache/StorageGlobal.php
@@ -21,16 +21,14 @@ use OCP\IDBConnection;
* @package OC\Files\Cache
*/
class StorageGlobal {
- /** @var IDBConnection */
- private $connection;
-
/** @var array<string, array> */
private $cache = [];
/** @var array<int, array> */
private $numericIdCache = [];
- public function __construct(IDBConnection $connection) {
- $this->connection = $connection;
+ public function __construct(
+ private IDBConnection $connection,
+ ) {
}
/**
@@ -42,7 +40,7 @@ class StorageGlobal {
->from('storages')
->where($builder->expr()->in('id', $builder->createNamedParameter(array_values($storageIds), IQueryBuilder::PARAM_STR_ARRAY)));
- $result = $query->execute();
+ $result = $query->executeQuery();
while ($row = $result->fetch()) {
$this->cache[$row['id']] = $row;
}
@@ -60,7 +58,7 @@ class StorageGlobal {
->from('storages')
->where($builder->expr()->eq('id', $builder->createNamedParameter($storageId)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$row = $result->fetch();
$result->closeCursor();
@@ -83,7 +81,7 @@ class StorageGlobal {
->from('storages')
->where($builder->expr()->eq('numeric_id', $builder->createNamedParameter($numericId)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$row = $result->fetch();
$result->closeCursor();
diff --git a/lib/private/Files/Cache/Updater.php b/lib/private/Files/Cache/Updater.php
index e8c6d32599e..03681036aa2 100644
--- a/lib/private/Files/Cache/Updater.php
+++ b/lib/private/Files/Cache/Updater.php
@@ -9,6 +9,8 @@ namespace OC\Files\Cache;
use Doctrine\DBAL\Exception\DeadlockException;
use OC\Files\FileInfo;
+use OC\Files\ObjectStore\ObjectStoreStorage;
+use OCP\Files\Cache\ICache;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\Cache\IUpdater;
use OCP\Files\Storage\IStorage;
@@ -114,7 +116,7 @@ class Updater implements IUpdater {
}
// encryption is a pita and touches the cache itself
- if (isset($data['encrypted']) && !!$data['encrypted']) {
+ if (isset($data['encrypted']) && (bool)$data['encrypted']) {
$sizeDifference = null;
}
@@ -157,13 +159,51 @@ class Updater implements IUpdater {
}
/**
- * Rename a file or folder in the cache and update the size, etag and mtime of the parent folders
+ * Rename a file or folder in the cache.
*
* @param IStorage $sourceStorage
* @param string $source
* @param string $target
*/
public function renameFromStorage(IStorage $sourceStorage, $source, $target) {
+ $this->copyOrRenameFromStorage($sourceStorage, $source, $target, function (ICache $sourceCache) use ($sourceStorage, $source, $target) {
+ // Remove existing cache entry to no reuse the fileId.
+ if ($this->cache->inCache($target)) {
+ $this->cache->remove($target);
+ }
+
+ if ($sourceStorage === $this->storage) {
+ $this->cache->move($source, $target);
+ } else {
+ $this->cache->moveFromCache($sourceCache, $source, $target);
+ }
+ });
+ }
+
+ /**
+ * Copy a file or folder in the cache.
+ */
+ public function copyFromStorage(IStorage $sourceStorage, string $source, string $target): void {
+ $this->copyOrRenameFromStorage($sourceStorage, $source, $target, function (ICache $sourceCache, ICacheEntry $sourceInfo) use ($target) {
+ $parent = dirname($target);
+ if ($parent === '.') {
+ $parent = '';
+ }
+ $parentInCache = $this->cache->inCache($parent);
+ if (!$parentInCache) {
+ $parentData = $this->scanner->scan($parent, Scanner::SCAN_SHALLOW, -1, false);
+ $parentInCache = $parentData !== null;
+ }
+ if ($parentInCache) {
+ $this->cache->copyFromCache($sourceCache, $sourceInfo, $target);
+ }
+ });
+ }
+
+ /**
+ * Utility to copy or rename a file or folder in the cache and update the size, etag and mtime of the parent folders
+ */
+ private function copyOrRenameFromStorage(IStorage $sourceStorage, string $source, string $target, callable $operation): void {
if (!$this->enabled or Scanner::isPartialFile($source) or Scanner::isPartialFile($target)) {
return;
}
@@ -176,27 +216,25 @@ class Updater implements IUpdater {
$sourceInfo = $sourceCache->get($source);
- if ($sourceInfo !== false) {
- if ($this->cache->inCache($target)) {
- $this->cache->remove($target);
- }
+ $sourceExtension = pathinfo($source, PATHINFO_EXTENSION);
+ $targetExtension = pathinfo($target, PATHINFO_EXTENSION);
+ $targetIsTrash = preg_match("/^d\d+$/", $targetExtension);
- if ($sourceStorage === $this->storage) {
- $this->cache->move($source, $target);
- } else {
- $this->cache->moveFromCache($sourceCache, $source, $target);
+ if ($sourceInfo !== false) {
+ if (!$this->storage->instanceOfStorage(ObjectStoreStorage::class)) {
+ $operation($sourceCache, $sourceInfo);
}
- $sourceExtension = pathinfo($source, PATHINFO_EXTENSION);
- $targetExtension = pathinfo($target, PATHINFO_EXTENSION);
- $targetIsTrash = preg_match("/d\d+/", $targetExtension);
+ $isDir = $sourceInfo->getMimeType() === FileInfo::MIMETYPE_FOLDER;
+ } else {
+ $isDir = $this->storage->is_dir($target);
+ }
- if ($sourceExtension !== $targetExtension && $sourceInfo->getMimeType() !== FileInfo::MIMETYPE_FOLDER && !$targetIsTrash) {
- // handle mime type change
- $mimeType = $this->storage->getMimeType($target);
- $fileId = $this->cache->getId($target);
- $this->cache->update($fileId, ['mimetype' => $mimeType]);
- }
+ if ($sourceExtension !== $targetExtension && !$isDir && !$targetIsTrash) {
+ // handle mime type change
+ $mimeType = $this->storage->getMimeType($target);
+ $fileId = $this->cache->getId($target);
+ $this->cache->update($fileId, ['mimetype' => $mimeType]);
}
if ($sourceCache instanceof Cache) {
@@ -246,7 +284,7 @@ class Updater implements IUpdater {
// ignore the failure.
// with failures concurrent updates, someone else would have already done it.
// in the worst case the `storage_mtime` isn't updated, which should at most only trigger an extra rescan
- $this->logger->warning("Error while updating parent storage_mtime, should be safe to ignore", ['exception' => $e]);
+ $this->logger->warning('Error while updating parent storage_mtime, should be safe to ignore', ['exception' => $e]);
}
}
}
diff --git a/lib/private/Files/Cache/Watcher.php b/lib/private/Files/Cache/Watcher.php
index 2e42b716695..f1de5d3cfb8 100644
--- a/lib/private/Files/Cache/Watcher.php
+++ b/lib/private/Files/Cache/Watcher.php
@@ -33,6 +33,9 @@ class Watcher implements IWatcher {
*/
protected $scanner;
+ /** @var callable[] */
+ protected $onUpdate = [];
+
/**
* @param \OC\Files\Storage\Storage $storage
*/
@@ -100,6 +103,9 @@ class Watcher implements IWatcher {
if ($this->cache instanceof Cache) {
$this->cache->correctFolderSize($path);
}
+ foreach ($this->onUpdate as $callback) {
+ $callback($path);
+ }
}
/**
@@ -112,7 +118,7 @@ class Watcher implements IWatcher {
public function needsUpdate($path, $cachedData) {
if ($this->watchPolicy === self::CHECK_ALWAYS or ($this->watchPolicy === self::CHECK_ONCE and !in_array($path, $this->checkedPaths))) {
$this->checkedPaths[] = $path;
- return $this->storage->hasUpdated($path, $cachedData['storage_mtime']);
+ return $cachedData['storage_mtime'] === null || $this->storage->hasUpdated($path, $cachedData['storage_mtime']);
}
return false;
}
@@ -130,4 +136,11 @@ class Watcher implements IWatcher {
}
}
}
+
+ /**
+ * register a callback to be called whenever the watcher triggers and update
+ */
+ public function onUpdate(callable $callback): void {
+ $this->onUpdate[] = $callback;
+ }
}
diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php
index ea0f992114a..5bc4ee8529d 100644
--- a/lib/private/Files/Cache/Wrapper/CacheJail.php
+++ b/lib/private/Files/Cache/Wrapper/CacheJail.php
@@ -21,27 +21,29 @@ use OCP\Files\Search\ISearchOperator;
* Jail to a subdirectory of the wrapped cache
*/
class CacheJail extends CacheWrapper {
- /**
- * @var string
- */
- protected $root;
- protected $unjailedRoot;
+
+ protected string $unjailedRoot;
public function __construct(
?ICache $cache,
- string $root,
+ protected string $root,
?CacheDependencies $dependencies = null,
) {
parent::__construct($cache, $dependencies);
- $this->root = $root;
- if ($cache instanceof CacheJail) {
- $this->unjailedRoot = $cache->getSourcePath($root);
- } else {
- $this->unjailedRoot = $root;
+ $this->unjailedRoot = $root;
+ $parent = $cache;
+ while ($parent instanceof CacheWrapper) {
+ if ($parent instanceof CacheJail) {
+ $this->unjailedRoot = $parent->getSourcePath($this->unjailedRoot);
+ }
+ $parent = $parent->getCache();
}
}
+ /**
+ * @return string
+ */
protected function getRoot() {
return $this->root;
}
@@ -51,11 +53,14 @@ class CacheJail extends CacheWrapper {
*
* @return string
*/
- protected function getGetUnjailedRoot() {
+ public function getGetUnjailedRoot() {
return $this->unjailedRoot;
}
- protected function getSourcePath($path) {
+ /**
+ * @return string
+ */
+ protected function getSourcePath(string $path) {
if ($path === '') {
return $this->getRoot();
} else {
@@ -95,7 +100,7 @@ class CacheJail extends CacheWrapper {
/**
* get the stored metadata of a file or folder
*
- * @param string /int $file
+ * @param string|int $file
* @return ICacheEntry|false
*/
public function get($file) {
@@ -206,12 +211,12 @@ class CacheJail extends CacheWrapper {
/**
* update the folder size and the size of all parent folders
*
- * @param string|boolean $path
- * @param array $data (optional) meta data of the folder
+ * @param array|ICacheEntry|null $data (optional) meta data of the folder
*/
- public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
- if ($this->getCache() instanceof Cache) {
- $this->getCache()->correctFolderSize($this->getSourcePath($path), $data, $isBackgroundScan);
+ public function correctFolderSize(string $path, $data = null, bool $isBackgroundScan = false): void {
+ $cache = $this->getCache();
+ if ($cache instanceof Cache) {
+ $cache->correctFolderSize($this->getSourcePath($path), $data, $isBackgroundScan);
}
}
@@ -223,8 +228,9 @@ class CacheJail extends CacheWrapper {
* @return int|float
*/
public function calculateFolderSize($path, $entry = null) {
- if ($this->getCache() instanceof Cache) {
- return $this->getCache()->calculateFolderSize($this->getSourcePath($path), $entry);
+ $cache = $this->getCache();
+ if ($cache instanceof Cache) {
+ return $cache->calculateFolderSize($this->getSourcePath($path), $entry);
} else {
return 0;
}
diff --git a/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/lib/private/Files/Cache/Wrapper/CacheWrapper.php
index 17f1031d1cc..f2f1036d6a3 100644
--- a/lib/private/Files/Cache/Wrapper/CacheWrapper.php
+++ b/lib/private/Files/Cache/Wrapper/CacheWrapper.php
@@ -214,19 +214,19 @@ class CacheWrapper extends Cache {
return $this->getCache()->getStatus($file);
}
- public function searchQuery(ISearchQuery $searchQuery) {
- return current($this->querySearchHelper->searchInCaches($searchQuery, [$this]));
+ public function searchQuery(ISearchQuery $query) {
+ return current($this->querySearchHelper->searchInCaches($query, [$this]));
}
/**
* update the folder size and the size of all parent folders
*
- * @param string|boolean $path
- * @param array $data (optional) meta data of the folder
+ * @param array|ICacheEntry|null $data (optional) meta data of the folder
*/
- public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
- if ($this->getCache() instanceof Cache) {
- $this->getCache()->correctFolderSize($path, $data, $isBackgroundScan);
+ public function correctFolderSize(string $path, $data = null, bool $isBackgroundScan = false): void {
+ $cache = $this->getCache();
+ if ($cache instanceof Cache) {
+ $cache->correctFolderSize($path, $data, $isBackgroundScan);
}
}
@@ -238,8 +238,9 @@ class CacheWrapper extends Cache {
* @return int|float
*/
public function calculateFolderSize($path, $entry = null) {
- if ($this->getCache() instanceof Cache) {
- return $this->getCache()->calculateFolderSize($path, $entry);
+ $cache = $this->getCache();
+ if ($cache instanceof Cache) {
+ return $cache->calculateFolderSize($path, $entry);
} else {
return 0;
}
diff --git a/lib/private/Files/Cache/Wrapper/JailPropagator.php b/lib/private/Files/Cache/Wrapper/JailPropagator.php
index 19ca4a13ece..d6409b7875e 100644
--- a/lib/private/Files/Cache/Wrapper/JailPropagator.php
+++ b/lib/private/Files/Cache/Wrapper/JailPropagator.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib/private/Files/Cache/Wrapper/JailWatcher.php b/lib/private/Files/Cache/Wrapper/JailWatcher.php
index 9bd7da57233..b1ae516654a 100644
--- a/lib/private/Files/Cache/Wrapper/JailWatcher.php
+++ b/lib/private/Files/Cache/Wrapper/JailWatcher.php
@@ -55,4 +55,7 @@ class JailWatcher extends Watcher {
$this->watcher->cleanFolder($this->getSourcePath($path));
}
+ public function onUpdate(callable $callback): void {
+ $this->watcher->onUpdate($callback);
+ }
}
diff --git a/lib/private/Files/Config/CachedMountFileInfo.php b/lib/private/Files/Config/CachedMountFileInfo.php
index 41dbec87ef5..69bd4e9301e 100644
--- a/lib/private/Files/Config/CachedMountFileInfo.php
+++ b/lib/private/Files/Config/CachedMountFileInfo.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -19,7 +20,7 @@ class CachedMountFileInfo extends CachedMountInfo implements ICachedMountFileInf
?int $mountId,
string $mountProvider,
string $rootInternalPath,
- string $internalPath
+ string $internalPath,
) {
parent::__construct($user, $storageId, $rootId, $mountPoint, $mountProvider, $mountId, $rootInternalPath);
$this->internalPath = $internalPath;
diff --git a/lib/private/Files/Config/CachedMountInfo.php b/lib/private/Files/Config/CachedMountInfo.php
index 80423dcae40..79dd6c6ea1d 100644
--- a/lib/private/Files/Config/CachedMountInfo.php
+++ b/lib/private/Files/Config/CachedMountInfo.php
@@ -39,7 +39,7 @@ class CachedMountInfo implements ICachedMountInfo {
string $mountPoint,
string $mountProvider,
?int $mountId = null,
- string $rootInternalPath = ''
+ string $rootInternalPath = '',
) {
$this->user = $user;
$this->storageId = $storageId;
diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php
index 0e103690b6b..9d63184e05f 100644
--- a/lib/private/Files/Config/MountProviderCollection.php
+++ b/lib/private/Files/Config/MountProviderCollection.php
@@ -24,60 +24,43 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
use EmitterTrait;
/**
- * @var \OCP\Files\Config\IHomeMountProvider[]
+ * @var list<IHomeMountProvider>
*/
- private $homeProviders = [];
+ private array $homeProviders = [];
/**
- * @var \OCP\Files\Config\IMountProvider[]
+ * @var list<IMountProvider>
*/
- private $providers = [];
+ private array $providers = [];
- /** @var \OCP\Files\Config\IRootMountProvider[] */
- private $rootProviders = [];
+ /** @var list<IRootMountProvider> */
+ private array $rootProviders = [];
- /**
- * @var \OCP\Files\Storage\IStorageFactory
- */
- private $loader;
-
- /**
- * @var \OCP\Files\Config\IUserMountCache
- */
- private $mountCache;
-
- /** @var callable[] */
- private $mountFilters = [];
+ /** @var list<callable> */
+ private array $mountFilters = [];
- private IEventLogger $eventLogger;
-
- /**
- * @param \OCP\Files\Storage\IStorageFactory $loader
- * @param IUserMountCache $mountCache
- */
public function __construct(
- IStorageFactory $loader,
- IUserMountCache $mountCache,
- IEventLogger $eventLogger
+ private IStorageFactory $loader,
+ private IUserMountCache $mountCache,
+ private IEventLogger $eventLogger,
) {
- $this->loader = $loader;
- $this->mountCache = $mountCache;
- $this->eventLogger = $eventLogger;
}
+ /**
+ * @return list<IMountPoint>
+ */
private function getMountsFromProvider(IMountProvider $provider, IUser $user, IStorageFactory $loader): array {
$class = str_replace('\\', '_', get_class($provider));
$uid = $user->getUID();
$this->eventLogger->start('fs:setup:provider:' . $class, "Getting mounts from $class for $uid");
$mounts = $provider->getMountsForUser($user, $loader) ?? [];
$this->eventLogger->end('fs:setup:provider:' . $class);
- return $mounts;
+ return array_values($mounts);
}
/**
- * @param IUser $user
- * @param IMountProvider[] $providers
- * @return IMountPoint[]
+ * @param list<IMountProvider> $providers
+ * @return list<IMountPoint>
*/
private function getUserMountsForProviders(IUser $user, array $providers): array {
$loader = $this->loader;
@@ -90,10 +73,16 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
return $this->filterMounts($user, $mounts);
}
+ /**
+ * @return list<IMountPoint>
+ */
public function getMountsForUser(IUser $user): array {
return $this->getUserMountsForProviders($user, $this->providers);
}
+ /**
+ * @return list<IMountPoint>
+ */
public function getUserMountsForProviderClasses(IUser $user, array $mountProviderClasses): array {
$providers = array_filter(
$this->providers,
@@ -102,7 +91,10 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
return $this->getUserMountsForProviders($user, $providers);
}
- public function addMountForUser(IUser $user, IMountManager $mountManager, ?callable $providerFilter = null) {
+ /**
+ * @return list<IMountPoint>
+ */
+ public function addMountForUser(IUser $user, IMountManager $mountManager, ?callable $providerFilter = null): array {
// shared mount provider gets to go last since it needs to know existing files
// to check for name collisions
$firstMounts = [];
@@ -131,22 +123,19 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
}
$lateMounts = $this->filterMounts($user, $lateMounts);
- $this->eventLogger->start("fs:setup:add-mounts", "Add mounts to the filesystem");
+ $this->eventLogger->start('fs:setup:add-mounts', 'Add mounts to the filesystem');
array_walk($lateMounts, [$mountManager, 'addMount']);
- $this->eventLogger->end("fs:setup:add-mounts");
+ $this->eventLogger->end('fs:setup:add-mounts');
- return array_merge($lateMounts, $firstMounts);
+ return array_values(array_merge($lateMounts, $firstMounts));
}
/**
* Get the configured home mount for this user
*
- * @param \OCP\IUser $user
- * @return \OCP\Files\Mount\IMountPoint
* @since 9.1.0
*/
- public function getHomeMountForUser(IUser $user) {
- /** @var \OCP\Files\Config\IHomeMountProvider[] $providers */
+ public function getHomeMountForUser(IUser $user): IMountPoint {
$providers = array_reverse($this->homeProviders); // call the latest registered provider first to give apps an opportunity to overwrite builtin
foreach ($providers as $homeProvider) {
if ($mount = $homeProvider->getHomeMountForUser($user, $this->loader)) {
@@ -159,34 +148,36 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
/**
* Add a provider for mount points
- *
- * @param \OCP\Files\Config\IMountProvider $provider
*/
- public function registerProvider(IMountProvider $provider) {
+ public function registerProvider(IMountProvider $provider): void {
$this->providers[] = $provider;
$this->emit('\OC\Files\Config', 'registerMountProvider', [$provider]);
}
- public function registerMountFilter(callable $filter) {
+ public function registerMountFilter(callable $filter): void {
$this->mountFilters[] = $filter;
}
- private function filterMounts(IUser $user, array $mountPoints) {
- return array_filter($mountPoints, function (IMountPoint $mountPoint) use ($user) {
+ /**
+ * @param list<IMountPoint> $mountPoints
+ * @return list<IMountPoint>
+ */
+ private function filterMounts(IUser $user, array $mountPoints): array {
+ return array_values(array_filter($mountPoints, function (IMountPoint $mountPoint) use ($user) {
foreach ($this->mountFilters as $filter) {
if ($filter($mountPoint, $user) === false) {
return false;
}
}
return true;
- });
+ }));
}
/**
* Add a provider for home mount points
*
- * @param \OCP\Files\Config\IHomeMountProvider $provider
+ * @param IHomeMountProvider $provider
* @since 9.1.0
*/
public function registerHomeProvider(IHomeMountProvider $provider) {
@@ -196,21 +187,19 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
/**
* Get the mount cache which can be used to search for mounts without setting up the filesystem
- *
- * @return IUserMountCache
*/
- public function getMountCache() {
+ public function getMountCache(): IUserMountCache {
return $this->mountCache;
}
- public function registerRootProvider(IRootMountProvider $provider) {
+ public function registerRootProvider(IRootMountProvider $provider): void {
$this->rootProviders[] = $provider;
}
/**
* Get all root mountpoints
*
- * @return \OCP\Files\Mount\IMountPoint[]
+ * @return list<IMountPoint>
* @since 20.0.0
*/
public function getRootMounts(): array {
@@ -223,19 +212,36 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
}, []);
if (count($mounts) === 0) {
- throw new \Exception("No root mounts provided by any provider");
+ throw new \Exception('No root mounts provided by any provider');
}
- return $mounts;
+ return array_values($mounts);
}
- public function clearProviders() {
+ public function clearProviders(): void {
$this->providers = [];
$this->homeProviders = [];
$this->rootProviders = [];
}
+ /**
+ * @return list<IMountProvider>
+ */
public function getProviders(): array {
return $this->providers;
}
+
+ /**
+ * @return list<IHomeMountProvider>
+ */
+ public function getHomeProviders(): array {
+ return $this->homeProviders;
+ }
+
+ /**
+ * @return list<IRootMountProvider>
+ */
+ public function getRootProviders(): array {
+ return $this->rootProviders;
+ }
}
diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php
index 0afdf9cdcc2..3e53a67a044 100644
--- a/lib/private/Files/Config/UserMountCache.php
+++ b/lib/private/Files/Config/UserMountCache.php
@@ -11,6 +11,10 @@ use OC\User\LazyUser;
use OCP\Cache\CappedMemoryCache;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Diagnostics\IEventLogger;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Config\Event\UserMountAddedEvent;
+use OCP\Files\Config\Event\UserMountRemovedEvent;
+use OCP\Files\Config\Event\UserMountUpdatedEvent;
use OCP\Files\Config\ICachedMountFileInfo;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
@@ -24,8 +28,6 @@ use Psr\Log\LoggerInterface;
* Cache mounts points per user in the cache so we can easily look them up
*/
class UserMountCache implements IUserMountCache {
- private IDBConnection $connection;
- private IUserManager $userManager;
/**
* Cached mount info.
@@ -37,24 +39,19 @@ class UserMountCache implements IUserMountCache {
* @var CappedMemoryCache<string>
**/
private CappedMemoryCache $internalPathCache;
- private LoggerInterface $logger;
/** @var CappedMemoryCache<array> */
private CappedMemoryCache $cacheInfoCache;
- private IEventLogger $eventLogger;
/**
* UserMountCache constructor.
*/
public function __construct(
- IDBConnection $connection,
- IUserManager $userManager,
- LoggerInterface $logger,
- IEventLogger $eventLogger
+ private IDBConnection $connection,
+ private IUserManager $userManager,
+ private LoggerInterface $logger,
+ private IEventLogger $eventLogger,
+ private IEventDispatcher $eventDispatcher,
) {
- $this->connection = $connection;
- $this->userManager = $userManager;
- $this->logger = $logger;
- $this->eventLogger = $eventLogger;
$this->cacheInfoCache = new CappedMemoryCache();
$this->internalPathCache = new CappedMemoryCache();
$this->mountsForUsers = new CappedMemoryCache();
@@ -106,24 +103,39 @@ class UserMountCache implements IUserMountCache {
$userUID = $user->getUID();
try {
foreach ($addedMounts as $mount) {
+ $this->logger->debug("Adding mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
$this->addToCache($mount);
/** @psalm-suppress InvalidArgument */
$this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
}
foreach ($removedMounts as $mount) {
+ $this->logger->debug("Removing mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
$this->removeFromCache($mount);
unset($this->mountsForUsers[$userUID][$mount->getKey()]);
}
- foreach ($changedMounts as $mount) {
- $this->updateCachedMount($mount);
+ foreach ($changedMounts as $mountPair) {
+ $newMount = $mountPair[1];
+ $this->logger->debug("Updating mount '{$newMount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $newMount->getMountProvider()]);
+ $this->updateCachedMount($newMount);
/** @psalm-suppress InvalidArgument */
- $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ $this->mountsForUsers[$userUID][$newMount->getKey()] = $newMount;
}
$this->connection->commit();
} catch (\Throwable $e) {
$this->connection->rollBack();
throw $e;
}
+
+ // Only fire events after all mounts have already been adjusted in the database.
+ foreach ($addedMounts as $mount) {
+ $this->eventDispatcher->dispatchTyped(new UserMountAddedEvent($mount));
+ }
+ foreach ($removedMounts as $mount) {
+ $this->eventDispatcher->dispatchTyped(new UserMountRemovedEvent($mount));
+ }
+ foreach ($changedMounts as $mountPair) {
+ $this->eventDispatcher->dispatchTyped(new UserMountUpdatedEvent($mountPair[0], $mountPair[1]));
+ }
}
$this->eventLogger->end('fs:setup:user:register');
}
@@ -131,19 +143,19 @@ class UserMountCache implements IUserMountCache {
/**
* @param array<string, ICachedMountInfo> $newMounts
* @param array<string, ICachedMountInfo> $cachedMounts
- * @return ICachedMountInfo[]
+ * @return list<list{0: ICachedMountInfo, 1: ICachedMountInfo}> Pairs of old and new mounts
*/
- private function findChangedMounts(array $newMounts, array $cachedMounts) {
+ private function findChangedMounts(array $newMounts, array $cachedMounts): array {
$changed = [];
foreach ($cachedMounts as $key => $cachedMount) {
if (isset($newMounts[$key])) {
$newMount = $newMounts[$key];
if (
- $newMount->getStorageId() !== $cachedMount->getStorageId() ||
- $newMount->getMountId() !== $cachedMount->getMountId() ||
- $newMount->getMountProvider() !== $cachedMount->getMountProvider()
+ $newMount->getStorageId() !== $cachedMount->getStorageId()
+ || $newMount->getMountId() !== $cachedMount->getMountId()
+ || $newMount->getMountProvider() !== $cachedMount->getMountProvider()
) {
- $changed[] = $newMount;
+ $changed[] = [$cachedMount, $newMount];
}
}
}
@@ -177,7 +189,7 @@ class UserMountCache implements IUserMountCache {
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)));
- $query->execute();
+ $query->executeStatement();
}
private function removeFromCache(ICachedMountInfo $mount) {
@@ -187,7 +199,7 @@ class UserMountCache implements IUserMountCache {
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($mount->getUser()->getUID())))
->andWhere($builder->expr()->eq('root_id', $builder->createNamedParameter($mount->getRootId(), IQueryBuilder::PARAM_INT)))
->andWhere($builder->expr()->eq('mount_point', $builder->createNamedParameter($mount->getMountPoint())));
- $query->execute();
+ $query->executeStatement();
}
/**
@@ -237,9 +249,9 @@ class UserMountCache implements IUserMountCache {
$builder = $this->connection->getQueryBuilder();
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'mount_provider_class')
->from('mounts', 'm')
- ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($userUID)));
+ ->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userUID)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
@@ -264,7 +276,7 @@ class UserMountCache implements IUserMountCache {
$builder = $this->connection->getQueryBuilder();
$query = $builder->select('path')
->from('filecache')
- ->where($builder->expr()->eq('fileid', $builder->createPositionalParameter($info->getRootId())));
+ ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($info->getRootId())));
return $query->executeQuery()->fetchOne() ?: '';
}
@@ -278,13 +290,13 @@ class UserMountCache implements IUserMountCache {
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
->from('mounts', 'm')
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
- ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
+ ->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
if ($user) {
- $query->andWhere($builder->expr()->eq('user_id', $builder->createPositionalParameter($user)));
+ $query->andWhere($builder->expr()->eq('user_id', $builder->createNamedParameter($user)));
}
- $result = $query->execute();
+ $result = $query->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
@@ -300,9 +312,9 @@ class UserMountCache implements IUserMountCache {
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
->from('mounts', 'm')
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
- ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT)));
+ ->where($builder->expr()->eq('root_id', $builder->createNamedParameter($rootFileId, IQueryBuilder::PARAM_INT)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
@@ -321,7 +333,7 @@ class UserMountCache implements IUserMountCache {
->from('filecache')
->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$row = $result->fetch();
$result->closeCursor();
@@ -362,9 +374,9 @@ class UserMountCache implements IUserMountCache {
return $internalMountPath === '' || str_starts_with($internalPath, $internalMountPath . '/');
});
- $filteredMounts = array_filter($filteredMounts, function (ICachedMountInfo $mount) {
+ $filteredMounts = array_values(array_filter($filteredMounts, function (ICachedMountInfo $mount) {
return $this->userManager->userExists($mount->getUser()->getUID());
- });
+ }));
return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
return new CachedMountFileInfo(
@@ -390,7 +402,7 @@ class UserMountCache implements IUserMountCache {
$query = $builder->delete('mounts')
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())));
- $query->execute();
+ $query->executeStatement();
}
public function removeUserStorageMount($storageId, $userId) {
@@ -399,7 +411,7 @@ class UserMountCache implements IUserMountCache {
$query = $builder->delete('mounts')
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userId)))
->andWhere($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
- $query->execute();
+ $query->executeStatement();
}
public function remoteStorageMounts($storageId) {
@@ -407,7 +419,7 @@ class UserMountCache implements IUserMountCache {
$query = $builder->delete('mounts')
->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
- $query->execute();
+ $query->executeStatement();
}
/**
@@ -438,7 +450,7 @@ class UserMountCache implements IUserMountCache {
->where($builder->expr()->eq('m.mount_point', $mountPoint))
->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$results = [];
while ($row = $result->fetch()) {
@@ -477,7 +489,7 @@ class UserMountCache implements IUserMountCache {
}
}
- throw new NotFoundException("No cached mount for path " . $path);
+ throw new NotFoundException('No cached mount for path ' . $path);
}
public function getMountsInPath(IUser $user, string $path): array {
diff --git a/lib/private/Files/Conversion/ConversionManager.php b/lib/private/Files/Conversion/ConversionManager.php
new file mode 100644
index 00000000000..2c98a4c6404
--- /dev/null
+++ b/lib/private/Files/Conversion/ConversionManager.php
@@ -0,0 +1,181 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Files\Conversion;
+
+use OC\AppFramework\Bootstrap\Coordinator;
+use OC\ForbiddenException;
+use OC\SystemConfig;
+use OCP\Files\Conversion\IConversionManager;
+use OCP\Files\Conversion\IConversionProvider;
+use OCP\Files\File;
+use OCP\Files\GenericFileException;
+use OCP\Files\IRootFolder;
+use OCP\IL10N;
+use OCP\ITempManager;
+use OCP\L10N\IFactory;
+use OCP\PreConditionNotMetException;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
+use Psr\Log\LoggerInterface;
+use RuntimeException;
+use Throwable;
+
+class ConversionManager implements IConversionManager {
+ /** @var string[] */
+ private array $preferredApps = [
+ 'richdocuments',
+ ];
+
+ /** @var list<IConversionProvider> */
+ private array $preferredProviders = [];
+
+ /** @var list<IConversionProvider> */
+ private array $providers = [];
+
+ private IL10N $l10n;
+ public function __construct(
+ private Coordinator $coordinator,
+ private ContainerInterface $serverContainer,
+ private IRootFolder $rootFolder,
+ private ITempManager $tempManager,
+ private LoggerInterface $logger,
+ private SystemConfig $config,
+ IFactory $l10nFactory,
+ ) {
+ $this->l10n = $l10nFactory->get('files');
+ }
+
+ public function hasProviders(): bool {
+ $context = $this->coordinator->getRegistrationContext();
+ return !empty($context->getFileConversionProviders());
+ }
+
+ public function getProviders(): array {
+ $providers = [];
+ foreach ($this->getRegisteredProviders() as $provider) {
+ $providers = array_merge($providers, $provider->getSupportedMimeTypes());
+ }
+ return $providers;
+ }
+
+ public function convert(File $file, string $targetMimeType, ?string $destination = null): string {
+ if (!$this->hasProviders()) {
+ throw new PreConditionNotMetException($this->l10n->t('No file conversion providers available'));
+ }
+
+ // Operate in mebibytes
+ $fileSize = $file->getSize() / (1024 * 1024);
+ $threshold = $this->config->getValue('max_file_conversion_filesize', 100);
+ if ($fileSize > $threshold) {
+ throw new GenericFileException($this->l10n->t('File is too large to convert'));
+ }
+
+ $fileMimeType = $file->getMimetype();
+ $validProvider = $this->getValidProvider($fileMimeType, $targetMimeType);
+
+ if ($validProvider !== null) {
+ // Get the target extension given by the provider
+ $targetExtension = '';
+ foreach ($validProvider->getSupportedMimeTypes() as $mimeProvider) {
+ if ($mimeProvider->getTo() === $targetMimeType) {
+ $targetExtension = $mimeProvider->getExtension();
+ break;
+ }
+ }
+ // If destination not provided, we use the same path
+ // as the original file, but with the new extension
+ if ($destination === null) {
+ $basename = pathinfo($file->getPath(), PATHINFO_FILENAME);
+ $parent = $file->getParent();
+ $destination = $parent->getFullPath($basename . '.' . $targetExtension);
+ }
+
+ // If destination doesn't match the target extension, we throw an error
+ if (pathinfo($destination, PATHINFO_EXTENSION) !== $targetExtension) {
+ throw new GenericFileException($this->l10n->t('Destination does not match conversion extension'));
+ }
+
+ // Check destination before converting
+ $this->checkDestination($destination);
+
+ // Convert the file and write it to the destination
+ $convertedFile = $validProvider->convertFile($file, $targetMimeType);
+ $convertedFile = $this->writeToDestination($destination, $convertedFile);
+ return $convertedFile->getPath();
+ }
+
+ throw new RuntimeException($this->l10n->t('Could not convert file'));
+ }
+
+ /**
+ * @return list<IConversionProvider>
+ */
+ private function getRegisteredProviders(): array {
+ $context = $this->coordinator->getRegistrationContext();
+ foreach ($context->getFileConversionProviders() as $providerRegistration) {
+ $class = $providerRegistration->getService();
+ $appId = $providerRegistration->getAppId();
+
+ try {
+ if (in_array($appId, $this->preferredApps)) {
+ $this->preferredProviders[$class] = $this->serverContainer->get($class);
+ continue;
+ }
+
+ $this->providers[$class] = $this->serverContainer->get($class);
+ } catch (NotFoundExceptionInterface|ContainerExceptionInterface|Throwable $e) {
+ $this->logger->error('Failed to load file conversion provider ' . $class, [
+ 'exception' => $e,
+ ]);
+ }
+ }
+
+ return array_values(array_merge([], $this->preferredProviders, $this->providers));
+ }
+
+ private function checkDestination(string $destination): void {
+ if (!$this->rootFolder->nodeExists(dirname($destination))) {
+ throw new ForbiddenException($this->l10n->t('Destination does not exist'));
+ }
+
+ $folder = $this->rootFolder->get(dirname($destination));
+ if (!$folder->isCreatable()) {
+ throw new ForbiddenException($this->l10n->t('Destination is not creatable'));
+ }
+ }
+
+ private function writeToDestination(string $destination, mixed $content): File {
+ $this->checkDestination($destination);
+
+ if ($this->rootFolder->nodeExists($destination)) {
+ $file = $this->rootFolder->get($destination);
+ $parent = $file->getParent();
+
+ // Folder permissions is already checked in checkDestination method
+ $newName = $parent->getNonExistingName(basename($destination));
+ $destination = $parent->getFullPath($newName);
+ }
+
+ return $this->rootFolder->newFile($destination, $content);
+ }
+
+ private function getValidProvider(string $fileMimeType, string $targetMimeType): ?IConversionProvider {
+ foreach ($this->getRegisteredProviders() as $provider) {
+ foreach ($provider->getSupportedMimeTypes() as $mimeProvider) {
+ if ($mimeProvider->getFrom() === $fileMimeType && $mimeProvider->getTo() === $targetMimeType) {
+ return $provider;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php
index c3dcb531342..0679dc1ae72 100644
--- a/lib/private/Files/FileInfo.php
+++ b/lib/private/Files/FileInfo.php
@@ -95,11 +95,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
unset($this->data[$offset]);
}
- /**
- * @return mixed
- */
- #[\ReturnTypeWillChange]
- public function offsetGet($offset) {
+ public function offsetGet(mixed $offset): mixed {
return match ($offset) {
'type' => $this->getType(),
'etag' => $this->getEtag(),
@@ -134,7 +130,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* @return int|null
*/
public function getId() {
- return isset($this->data['fileid']) ? (int) $this->data['fileid'] : null;
+ return isset($this->data['fileid']) ? (int)$this->data['fileid'] : null;
}
/**
@@ -164,7 +160,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* @return string
*/
public function getEtag() {
- $this->updateEntryfromSubMounts();
+ $this->updateEntryFromSubMounts();
if (count($this->childEtags) > 0) {
$combinedEtag = $this->data['etag'] . '::' . implode('::', $this->childEtags);
return md5($combinedEtag);
@@ -179,7 +175,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
*/
public function getSize($includeMounts = true) {
if ($includeMounts) {
- $this->updateEntryfromSubMounts();
+ $this->updateEntryFromSubMounts();
if ($this->isEncrypted() && isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) {
return $this->data['unencrypted_size'];
@@ -195,8 +191,8 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* @return int
*/
public function getMTime() {
- $this->updateEntryfromSubMounts();
- return (int) $this->data['mtime'];
+ $this->updateEntryFromSubMounts();
+ return (int)$this->data['mtime'];
}
/**
@@ -210,14 +206,14 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
* Return the current version used for the HMAC in the encryption app
*/
public function getEncryptedVersion(): int {
- return isset($this->data['encryptedVersion']) ? (int) $this->data['encryptedVersion'] : 1;
+ return isset($this->data['encryptedVersion']) ? (int)$this->data['encryptedVersion'] : 1;
}
/**
* @return int
*/
public function getPermissions() {
- return (int) $this->data['permissions'];
+ return (int)$this->data['permissions'];
}
/**
@@ -318,7 +314,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
$this->subMounts = $mounts;
}
- private function updateEntryfromSubMounts(): void {
+ private function updateEntryFromSubMounts(): void {
if ($this->subMountsUsed) {
return;
}
@@ -379,11 +375,11 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
}
public function getCreationTime(): int {
- return (int) $this->data['creation_time'];
+ return (int)$this->data['creation_time'];
}
public function getUploadTime(): int {
- return (int) $this->data['upload_time'];
+ return (int)$this->data['upload_time'];
}
public function getParentId(): int {
diff --git a/lib/private/Files/FilenameValidator.php b/lib/private/Files/FilenameValidator.php
new file mode 100644
index 00000000000..a78c6d3cc3c
--- /dev/null
+++ b/lib/private/Files/FilenameValidator.php
@@ -0,0 +1,335 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Files;
+
+use OCP\Files\EmptyFileNameException;
+use OCP\Files\FileNameTooLongException;
+use OCP\Files\IFilenameValidator;
+use OCP\Files\InvalidCharacterInPathException;
+use OCP\Files\InvalidDirectoryException;
+use OCP\Files\InvalidPathException;
+use OCP\Files\ReservedWordException;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\IL10N;
+use OCP\L10N\IFactory;
+use Psr\Log\LoggerInterface;
+
+/**
+ * @since 30.0.0
+ */
+class FilenameValidator implements IFilenameValidator {
+
+ public const INVALID_FILE_TYPE = 100;
+
+ private IL10N $l10n;
+
+ /**
+ * @var list<string>
+ */
+ private array $forbiddenNames = [];
+
+ /**
+ * @var list<string>
+ */
+ private array $forbiddenBasenames = [];
+ /**
+ * @var list<string>
+ */
+ private array $forbiddenCharacters = [];
+
+ /**
+ * @var list<string>
+ */
+ private array $forbiddenExtensions = [];
+
+ public function __construct(
+ IFactory $l10nFactory,
+ private IDBConnection $database,
+ private IConfig $config,
+ private LoggerInterface $logger,
+ ) {
+ $this->l10n = $l10nFactory->get('core');
+ }
+
+ /**
+ * Get a list of reserved filenames that must not be used
+ * This list should be checked case-insensitive, all names are returned lowercase.
+ * @return list<string>
+ * @since 30.0.0
+ */
+ public function getForbiddenExtensions(): array {
+ if (empty($this->forbiddenExtensions)) {
+ $forbiddenExtensions = $this->getConfigValue('forbidden_filename_extensions', ['.filepart']);
+
+ // Always forbid .part files as they are used internally
+ $forbiddenExtensions[] = '.part';
+
+ $this->forbiddenExtensions = array_values($forbiddenExtensions);
+ }
+ return $this->forbiddenExtensions;
+ }
+
+ /**
+ * Get a list of forbidden filename extensions that must not be used
+ * This list should be checked case-insensitive, all names are returned lowercase.
+ * @return list<string>
+ * @since 30.0.0
+ */
+ public function getForbiddenFilenames(): array {
+ if (empty($this->forbiddenNames)) {
+ $forbiddenNames = $this->getConfigValue('forbidden_filenames', ['.htaccess']);
+
+ // Handle legacy config option
+ // TODO: Drop with Nextcloud 34
+ $legacyForbiddenNames = $this->getConfigValue('blacklisted_files', []);
+ if (!empty($legacyForbiddenNames)) {
+ $this->logger->warning('System config option "blacklisted_files" is deprecated and will be removed in Nextcloud 34, use "forbidden_filenames" instead.');
+ }
+ $forbiddenNames = array_merge($legacyForbiddenNames, $forbiddenNames);
+
+ // Ensure we are having a proper string list
+ $this->forbiddenNames = array_values($forbiddenNames);
+ }
+ return $this->forbiddenNames;
+ }
+
+ /**
+ * Get a list of forbidden file basenames that must not be used
+ * This list should be checked case-insensitive, all names are returned lowercase.
+ * @return list<string>
+ * @since 30.0.0
+ */
+ public function getForbiddenBasenames(): array {
+ if (empty($this->forbiddenBasenames)) {
+ $forbiddenBasenames = $this->getConfigValue('forbidden_filename_basenames', []);
+ // Ensure we are having a proper string list
+ $this->forbiddenBasenames = array_values($forbiddenBasenames);
+ }
+ return $this->forbiddenBasenames;
+ }
+
+ /**
+ * Get a list of characters forbidden in filenames
+ *
+ * Note: Characters in the range [0-31] are always forbidden,
+ * even if not inside this list (see OCP\Files\Storage\IStorage::verifyPath).
+ *
+ * @return list<string>
+ * @since 30.0.0
+ */
+ public function getForbiddenCharacters(): array {
+ if (empty($this->forbiddenCharacters)) {
+ // Get always forbidden characters
+ $forbiddenCharacters = str_split(\OCP\Constants::FILENAME_INVALID_CHARS);
+
+ // Get admin defined invalid characters
+ $additionalChars = $this->config->getSystemValue('forbidden_filename_characters', []);
+ if (!is_array($additionalChars)) {
+ $this->logger->error('Invalid system config value for "forbidden_filename_characters" is ignored.');
+ $additionalChars = [];
+ }
+ $forbiddenCharacters = array_merge($forbiddenCharacters, $additionalChars);
+
+ // Handle legacy config option
+ // TODO: Drop with Nextcloud 34
+ $legacyForbiddenCharacters = $this->config->getSystemValue('forbidden_chars', []);
+ if (!is_array($legacyForbiddenCharacters)) {
+ $this->logger->error('Invalid system config value for "forbidden_chars" is ignored.');
+ $legacyForbiddenCharacters = [];
+ }
+ if (!empty($legacyForbiddenCharacters)) {
+ $this->logger->warning('System config option "forbidden_chars" is deprecated and will be removed in Nextcloud 34, use "forbidden_filename_characters" instead.');
+ }
+ $forbiddenCharacters = array_merge($legacyForbiddenCharacters, $forbiddenCharacters);
+
+ $this->forbiddenCharacters = array_values($forbiddenCharacters);
+ }
+ return $this->forbiddenCharacters;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function isFilenameValid(string $filename): bool {
+ try {
+ $this->validateFilename($filename);
+ } catch (\OCP\Files\InvalidPathException) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validateFilename(string $filename): void {
+ $trimmed = trim($filename);
+ if ($trimmed === '') {
+ throw new EmptyFileNameException();
+ }
+
+ // the special directories . and .. would cause never ending recursion
+ // we check the trimmed name here to ensure unexpected trimming will not cause severe issues
+ if ($trimmed === '.' || $trimmed === '..') {
+ throw new InvalidDirectoryException($this->l10n->t('Dot files are not allowed'));
+ }
+
+ // 255 characters is the limit on common file systems (ext/xfs)
+ // oc_filecache has a 250 char length limit for the filename
+ if (isset($filename[250])) {
+ throw new FileNameTooLongException();
+ }
+
+ if (!$this->database->supports4ByteText()) {
+ // verify database - e.g. mysql only 3-byte chars
+ if (preg_match('%(?:
+ \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
+ | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
+ | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
+)%xs', $filename)) {
+ throw new InvalidCharacterInPathException();
+ }
+ }
+
+ $this->checkForbiddenName($filename);
+
+ $this->checkForbiddenExtension($filename);
+
+ $this->checkForbiddenCharacters($filename);
+ }
+
+ /**
+ * Check if the filename is forbidden
+ * @param string $path Path to check the filename
+ * @return bool True if invalid name, False otherwise
+ */
+ public function isForbidden(string $path): bool {
+ // We support paths here as this function is also used in some storage internals
+ $filename = basename($path);
+ $filename = mb_strtolower($filename);
+
+ if ($filename === '') {
+ return false;
+ }
+
+ // Check for forbidden filenames
+ $forbiddenNames = $this->getForbiddenFilenames();
+ if (in_array($filename, $forbiddenNames)) {
+ return true;
+ }
+
+ // Filename is not forbidden
+ return false;
+ }
+
+ public function sanitizeFilename(string $name, ?string $charReplacement = null): string {
+ $forbiddenCharacters = $this->getForbiddenCharacters();
+
+ if ($charReplacement === null) {
+ $charReplacement = array_diff(['_', '-', ' '], $forbiddenCharacters);
+ $charReplacement = reset($charReplacement) ?: '';
+ }
+ if (mb_strlen($charReplacement) !== 1) {
+ throw new \InvalidArgumentException('No or invalid character replacement given');
+ }
+
+ $nameLowercase = mb_strtolower($name);
+ foreach ($this->getForbiddenExtensions() as $extension) {
+ if (str_ends_with($nameLowercase, $extension)) {
+ $name = substr($name, 0, strlen($name) - strlen($extension));
+ }
+ }
+
+ $basename = strlen($name) > 1
+ ? substr($name, 0, strpos($name, '.', 1) ?: null)
+ : $name;
+ if (in_array(mb_strtolower($basename), $this->getForbiddenBasenames())) {
+ $name = str_replace($basename, $this->l10n->t('%1$s (renamed)', [$basename]), $name);
+ }
+
+ if ($name === '') {
+ $name = $this->l10n->t('renamed file');
+ }
+
+ if (in_array(mb_strtolower($name), $this->getForbiddenFilenames())) {
+ $name = $this->l10n->t('%1$s (renamed)', [$name]);
+ }
+
+ $name = str_replace($forbiddenCharacters, $charReplacement, $name);
+ return $name;
+ }
+
+ protected function checkForbiddenName(string $filename): void {
+ $filename = mb_strtolower($filename);
+ if ($this->isForbidden($filename)) {
+ throw new ReservedWordException($this->l10n->t('"%1$s" is a forbidden file or folder name.', [$filename]));
+ }
+
+ // Check for forbidden basenames - basenames are the part of the file until the first dot
+ // (except if the dot is the first character as this is then part of the basename "hidden files")
+ $basename = substr($filename, 0, strpos($filename, '.', 1) ?: null);
+ $forbiddenNames = $this->getForbiddenBasenames();
+ if (in_array($basename, $forbiddenNames)) {
+ throw new ReservedWordException($this->l10n->t('"%1$s" is a forbidden prefix for file or folder names.', [$filename]));
+ }
+ }
+
+
+ /**
+ * Check if a filename contains any of the forbidden characters
+ * @param string $filename
+ * @throws InvalidCharacterInPathException
+ */
+ protected function checkForbiddenCharacters(string $filename): void {
+ $sanitizedFileName = filter_var($filename, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
+ if ($sanitizedFileName !== $filename) {
+ throw new InvalidCharacterInPathException();
+ }
+
+ foreach ($this->getForbiddenCharacters() as $char) {
+ if (str_contains($filename, $char)) {
+ throw new InvalidCharacterInPathException($this->l10n->t('"%1$s" is not allowed inside a file or folder name.', [$char]));
+ }
+ }
+ }
+
+ /**
+ * Check if a filename has a forbidden filename extension
+ * @param string $filename The filename to validate
+ * @throws InvalidPathException
+ */
+ protected function checkForbiddenExtension(string $filename): void {
+ $filename = mb_strtolower($filename);
+ // Check for forbidden filename extensions
+ $forbiddenExtensions = $this->getForbiddenExtensions();
+ foreach ($forbiddenExtensions as $extension) {
+ if (str_ends_with($filename, $extension)) {
+ if (str_starts_with($extension, '.')) {
+ throw new InvalidPathException($this->l10n->t('"%1$s" is a forbidden file type.', [$extension]), self::INVALID_FILE_TYPE);
+ } else {
+ throw new InvalidPathException($this->l10n->t('Filenames must not end with "%1$s".', [$extension]));
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper to get lower case list from config with validation
+ * @return string[]
+ */
+ private function getConfigValue(string $key, array $fallback): array {
+ $values = $this->config->getSystemValue($key, $fallback);
+ if (!is_array($values)) {
+ $this->logger->error('Invalid system config value for "' . $key . '" is ignored.');
+ $values = $fallback;
+ }
+
+ return array_map(mb_strtolower(...), $values);
+ }
+};
diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php
index db7420c3c4c..8fe56cf060c 100644
--- a/lib/private/Files/Filesystem.php
+++ b/lib/private/Files/Filesystem.php
@@ -8,6 +8,7 @@
namespace OC\Files;
use OC\Files\Mount\MountPoint;
+use OC\Files\Storage\StorageFactory;
use OC\User\NoUserException;
use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
@@ -178,7 +179,9 @@ class Filesystem {
}
$mounts = self::getMountManager()->getAll();
- if (!self::getLoader()->addStorageWrapper($wrapperName, $wrapper, $priority, $mounts)) {
+ /** @var StorageFactory $loader */
+ $loader = self::getLoader();
+ if (!$loader->addStorageWrapper($wrapperName, $wrapper, $priority, $mounts)) {
// do not re-wrap if storage with this name already existed
return;
}
@@ -674,7 +677,7 @@ class Filesystem {
*
* @param string $path
* @param bool|string $includeMountPoints whether to add mountpoint sizes,
- * defaults to true
+ * defaults to true
* @return \OC\Files\FileInfo|false False if file does not exist
*/
public static function getFileInfo($path, $includeMountPoints = true) {
diff --git a/lib/private/Files/Mount/HomeMountPoint.php b/lib/private/Files/Mount/HomeMountPoint.php
index 0aa4b54ddf0..5a648f08c89 100644
--- a/lib/private/Files/Mount/HomeMountPoint.php
+++ b/lib/private/Files/Mount/HomeMountPoint.php
@@ -22,7 +22,7 @@ class HomeMountPoint extends MountPoint {
?IStorageFactory $loader = null,
?array $mountOptions = null,
?int $mountId = null,
- ?string $mountProvider = null
+ ?string $mountProvider = null,
) {
parent::__construct($storage, $mountpoint, $arguments, $loader, $mountOptions, $mountId, $mountProvider);
$this->user = $user;
diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php
index c2267af3c96..55de488c726 100644
--- a/lib/private/Files/Mount/Manager.php
+++ b/lib/private/Files/Mount/Manager.php
@@ -84,7 +84,7 @@ class Manager implements IMountManager {
if (count($this->mounts) === 0) {
$this->setupManager->setupRoot();
if (count($this->mounts) === 0) {
- throw new \Exception("No mounts even after explicitly setting up the root mounts");
+ throw new \Exception('No mounts even after explicitly setting up the root mounts');
}
}
@@ -104,7 +104,7 @@ class Manager implements IMountManager {
}
}
- throw new NotFoundException("No mount for path " . $path . " existing mounts (" . count($this->mounts) ."): " . implode(",", array_keys($this->mounts)));
+ throw new NotFoundException('No mount for path ' . $path . ' existing mounts (' . count($this->mounts) . '): ' . implode(',', array_keys($this->mounts)));
}
/**
diff --git a/lib/private/Files/Mount/MountPoint.php b/lib/private/Files/Mount/MountPoint.php
index cb3a3e8a22d..bab2dc8e4bd 100644
--- a/lib/private/Files/Mount/MountPoint.php
+++ b/lib/private/Files/Mount/MountPoint.php
@@ -75,7 +75,7 @@ class MountPoint implements IMountPoint {
?IStorageFactory $loader = null,
?array $mountOptions = null,
?int $mountId = null,
- ?string $mountProvider = null
+ ?string $mountProvider = null,
) {
if (is_null($arguments)) {
$arguments = [];
@@ -198,7 +198,7 @@ class MountPoint implements IMountPoint {
if (is_null($storage)) {
return -1;
}
- $this->numericStorageId = $storage->getStorageCache()->getNumericId();
+ $this->numericStorageId = $storage->getCache()->getNumericStorageId();
}
return $this->numericStorageId;
}
diff --git a/lib/private/Files/Mount/ObjectHomeMountProvider.php b/lib/private/Files/Mount/ObjectHomeMountProvider.php
index 99c52108fa8..4b088f2c808 100644
--- a/lib/private/Files/Mount/ObjectHomeMountProvider.php
+++ b/lib/private/Files/Mount/ObjectHomeMountProvider.php
@@ -7,117 +7,39 @@
*/
namespace OC\Files\Mount;
+use OC\Files\ObjectStore\HomeObjectStoreStorage;
+use OC\Files\ObjectStore\PrimaryObjectStoreConfig;
use OCP\Files\Config\IHomeMountProvider;
+use OCP\Files\Mount\IMountPoint;
use OCP\Files\Storage\IStorageFactory;
-use OCP\IConfig;
use OCP\IUser;
-use Psr\Log\LoggerInterface;
/**
* Mount provider for object store home storages
*/
class ObjectHomeMountProvider implements IHomeMountProvider {
- /**
- * @var IConfig
- */
- private $config;
-
- /**
- * ObjectStoreHomeMountProvider constructor.
- *
- * @param IConfig $config
- */
- public function __construct(IConfig $config) {
- $this->config = $config;
+ public function __construct(
+ private PrimaryObjectStoreConfig $objectStoreConfig,
+ ) {
}
/**
- * Get the cache mount for a user
+ * Get the home mount for a user
*
* @param IUser $user
* @param IStorageFactory $loader
- * @return \OCP\Files\Mount\IMountPoint
+ * @return ?IMountPoint
*/
- public function getHomeMountForUser(IUser $user, IStorageFactory $loader) {
- $config = $this->getMultiBucketObjectStoreConfig($user);
- if ($config === null) {
- $config = $this->getSingleBucketObjectStoreConfig($user);
- }
-
- if ($config === null) {
+ public function getHomeMountForUser(IUser $user, IStorageFactory $loader): ?IMountPoint {
+ $objectStoreConfig = $this->objectStoreConfig->getObjectStoreConfigForUser($user);
+ if ($objectStoreConfig === null) {
return null;
}
+ $arguments = array_merge($objectStoreConfig['arguments'], [
+ 'objectstore' => $this->objectStoreConfig->buildObjectStore($objectStoreConfig),
+ 'user' => $user,
+ ]);
- return new HomeMountPoint($user, '\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class);
- }
-
- /**
- * @param IUser $user
- * @return array|null
- */
- private function getSingleBucketObjectStoreConfig(IUser $user) {
- $config = $this->config->getSystemValue('objectstore');
- if (!is_array($config)) {
- return null;
- }
-
- // sanity checks
- if (empty($config['class'])) {
- \OC::$server->get(LoggerInterface::class)->error('No class given for objectstore', ['app' => 'files']);
- }
- if (!isset($config['arguments'])) {
- $config['arguments'] = [];
- }
- // instantiate object store implementation
- $config['arguments']['objectstore'] = new $config['class']($config['arguments']);
-
- $config['arguments']['user'] = $user;
-
- return $config;
- }
-
- /**
- * @param IUser $user
- * @return array|null
- */
- private function getMultiBucketObjectStoreConfig(IUser $user) {
- $config = $this->config->getSystemValue('objectstore_multibucket');
- if (!is_array($config)) {
- return null;
- }
-
- // sanity checks
- if (empty($config['class'])) {
- \OC::$server->get(LoggerInterface::class)->error('No class given for objectstore', ['app' => 'files']);
- }
- if (!isset($config['arguments'])) {
- $config['arguments'] = [];
- }
-
- $bucket = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null);
-
- if ($bucket === null) {
- /*
- * Use any provided bucket argument as prefix
- * and add the mapping from username => bucket
- */
- if (!isset($config['arguments']['bucket'])) {
- $config['arguments']['bucket'] = '';
- }
- $mapper = new \OC\Files\ObjectStore\Mapper($user, $this->config);
- $numBuckets = $config['arguments']['num_buckets'] ?? 64;
- $config['arguments']['bucket'] .= $mapper->getBucket($numBuckets);
-
- $this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $config['arguments']['bucket']);
- } else {
- $config['arguments']['bucket'] = $bucket;
- }
-
- // instantiate object store implementation
- $config['arguments']['objectstore'] = new $config['class']($config['arguments']);
-
- $config['arguments']['user'] = $user;
-
- return $config;
+ return new HomeMountPoint($user, HomeObjectStoreStorage::class, '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
}
}
diff --git a/lib/private/Files/Mount/RootMountProvider.php b/lib/private/Files/Mount/RootMountProvider.php
index 7ad7a7f059c..5e0c924ad38 100644
--- a/lib/private/Files/Mount/RootMountProvider.php
+++ b/lib/private/Files/Mount/RootMountProvider.php
@@ -10,79 +10,41 @@ namespace OC\Files\Mount;
use OC;
use OC\Files\ObjectStore\ObjectStoreStorage;
+use OC\Files\ObjectStore\PrimaryObjectStoreConfig;
use OC\Files\Storage\LocalRootStorage;
-use OC_App;
use OCP\Files\Config\IRootMountProvider;
use OCP\Files\Storage\IStorageFactory;
use OCP\IConfig;
-use Psr\Log\LoggerInterface;
class RootMountProvider implements IRootMountProvider {
+ private PrimaryObjectStoreConfig $objectStoreConfig;
private IConfig $config;
- private LoggerInterface $logger;
- public function __construct(IConfig $config, LoggerInterface $logger) {
+ public function __construct(PrimaryObjectStoreConfig $objectStoreConfig, IConfig $config) {
+ $this->objectStoreConfig = $objectStoreConfig;
$this->config = $config;
- $this->logger = $logger;
}
public function getRootMounts(IStorageFactory $loader): array {
- $objectStore = $this->config->getSystemValue('objectstore', null);
- $objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
+ $objectStoreConfig = $this->objectStoreConfig->getObjectStoreConfigForRoot();
- if ($objectStoreMultiBucket) {
- return [$this->getMultiBucketStoreRootMount($loader, $objectStoreMultiBucket)];
- } elseif ($objectStore) {
- return [$this->getObjectStoreRootMount($loader, $objectStore)];
+ if ($objectStoreConfig) {
+ return [$this->getObjectStoreRootMount($loader, $objectStoreConfig)];
} else {
return [$this->getLocalRootMount($loader)];
}
}
- private function validateObjectStoreConfig(array &$config) {
- if (empty($config['class'])) {
- $this->logger->error('No class given for objectstore', ['app' => 'files']);
- }
- if (!isset($config['arguments'])) {
- $config['arguments'] = [];
- }
-
- // instantiate object store implementation
- $name = $config['class'];
- if (str_starts_with($name, 'OCA\\') && substr_count($name, '\\') >= 2) {
- $segments = explode('\\', $name);
- OC_App::loadApp(strtolower($segments[1]));
- }
- }
-
private function getLocalRootMount(IStorageFactory $loader): MountPoint {
- $configDataDirectory = $this->config->getSystemValue("datadirectory", OC::$SERVERROOT . "/data");
+ $configDataDirectory = $this->config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data');
return new MountPoint(LocalRootStorage::class, '/', ['datadir' => $configDataDirectory], $loader, null, null, self::class);
}
- private function getObjectStoreRootMount(IStorageFactory $loader, array $config): MountPoint {
- $this->validateObjectStoreConfig($config);
-
- $config['arguments']['objectstore'] = new $config['class']($config['arguments']);
- // mount with plain / root object store implementation
- $config['class'] = ObjectStoreStorage::class;
-
- return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class);
- }
-
- private function getMultiBucketStoreRootMount(IStorageFactory $loader, array $config): MountPoint {
- $this->validateObjectStoreConfig($config);
-
- if (!isset($config['arguments']['bucket'])) {
- $config['arguments']['bucket'] = '';
- }
- // put the root FS always in first bucket for multibucket configuration
- $config['arguments']['bucket'] .= '0';
-
- $config['arguments']['objectstore'] = new $config['class']($config['arguments']);
- // mount with plain / root object store implementation
- $config['class'] = ObjectStoreStorage::class;
+ private function getObjectStoreRootMount(IStorageFactory $loader, array $objectStoreConfig): MountPoint {
+ $arguments = array_merge($objectStoreConfig['arguments'], [
+ 'objectstore' => $this->objectStoreConfig->buildObjectStore($objectStoreConfig),
+ ]);
- return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class);
+ return new MountPoint(ObjectStoreStorage::class, '/', $arguments, $loader, null, null, self::class);
}
}
diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php
index 51b205d545b..7453b553119 100644
--- a/lib/private/Files/Node/Folder.php
+++ b/lib/private/Files/Node/Folder.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -12,6 +13,7 @@ use OC\Files\Search\SearchComparison;
use OC\Files\Search\SearchOrder;
use OC\Files\Search\SearchQuery;
use OC\Files\Utils\PathHelper;
+use OC\User\LazyUser;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\FileInfo;
use OCP\Files\Mount\IMountPoint;
@@ -26,6 +28,9 @@ use OCP\Files\Search\ISearchQuery;
use OCP\IUserManager;
class Folder extends Node implements \OCP\Files\Folder {
+
+ private ?IUserManager $userManager = null;
+
/**
* Creates a Folder that represents a non-existing path
*
@@ -99,26 +104,15 @@ class Folder extends Node implements \OCP\Files\Folder {
}
}
- /**
- * Get the node at $path
- *
- * @param string $path
- * @return \OC\Files\Node\Node
- * @throws \OCP\Files\NotFoundException
- */
public function get($path) {
return $this->root->get($this->getFullPath($path));
}
- /**
- * @param string $path
- * @return bool
- */
public function nodeExists($path) {
try {
$this->get($path);
return true;
- } catch (NotFoundException $e) {
+ } catch (NotFoundException|NotPermittedException) {
return false;
}
}
@@ -133,8 +127,21 @@ class Folder extends Node implements \OCP\Files\Folder {
$fullPath = $this->getFullPath($path);
$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
- if (!$this->view->mkdir($fullPath) && !$this->view->is_dir($fullPath)) {
- throw new NotPermittedException('Could not create folder "' . $fullPath . '"');
+ if (!$this->view->mkdir($fullPath)) {
+ // maybe another concurrent process created the folder already
+ if (!$this->view->is_dir($fullPath)) {
+ throw new NotPermittedException('Could not create folder "' . $fullPath . '"');
+ } else {
+ // we need to ensure we don't return before the concurrent request has finished updating the cache
+ $tries = 5;
+ while (!$this->view->getFileInfo($fullPath)) {
+ if ($tries < 1) {
+ throw new NotPermittedException('Could not create folder "' . $fullPath . '", folder exists but unable to get cache entry');
+ }
+ usleep(5 * 1000);
+ $tries--;
+ }
+ }
}
$parent = dirname($fullPath) === $this->getPath() ? $this : null;
$node = new Folder($this->root, $this->view, $fullPath, null, $parent);
@@ -218,9 +225,9 @@ class Folder extends Node implements \OCP\Files\Folder {
}, array_values($resultsPerCache), array_keys($resultsPerCache)));
// don't include this folder in the results
- $files = array_filter($files, function (FileInfo $file) {
+ $files = array_values(array_filter($files, function (FileInfo $file) {
return $file->getPath() !== $this->getPath();
- });
+ }));
// since results were returned per-cache, they are no longer fully sorted
$order = $query->getOrder();
@@ -245,7 +252,26 @@ class Folder extends Node implements \OCP\Files\Folder {
$cacheEntry['internalPath'] = $cacheEntry['path'];
$cacheEntry['path'] = rtrim($appendRoot . $cacheEntry->getPath(), '/');
$subPath = $cacheEntry['path'] !== '' ? '/' . $cacheEntry['path'] : '';
- return new \OC\Files\FileInfo($this->path . $subPath, $mount->getStorage(), $cacheEntry['internalPath'], $cacheEntry, $mount);
+ $storage = $mount->getStorage();
+
+ $owner = null;
+ $ownerId = $storage->getOwner($cacheEntry['internalPath']);
+ if ($ownerId !== false) {
+ // Cache the user manager (for performance)
+ if ($this->userManager === null) {
+ $this->userManager = \OCP\Server::get(IUserManager::class);
+ }
+ $owner = new LazyUser($ownerId, $this->userManager);
+ }
+
+ return new \OC\Files\FileInfo(
+ $this->path . $subPath,
+ $storage,
+ $cacheEntry['internalPath'],
+ $cacheEntry,
+ $mount,
+ $owner,
+ );
}
/**
@@ -289,7 +315,7 @@ class Folder extends Node implements \OCP\Files\Folder {
}
public function getFirstNodeById(int $id): ?\OCP\Files\Node {
- return current($this->getById($id)) ?: null;
+ return $this->root->getFirstNodeByIdInPath($id, $this->getPath());
}
public function getAppDataDirectoryName(): string {
@@ -399,7 +425,7 @@ class Folder extends Node implements \OCP\Files\Folder {
$filterNonRecentFiles = new SearchComparison(
ISearchComparison::COMPARE_GREATER_THAN,
'mtime',
- strtotime("-2 week")
+ strtotime('-2 week')
);
if ($offset === 0 && $limit <= 100) {
$query = new SearchQuery(
@@ -435,4 +461,12 @@ class Folder extends Node implements \OCP\Files\Folder {
return $this->search($query);
}
+
+ public function verifyPath($fileName, $readonly = false): void {
+ $this->view->verifyPath(
+ $this->getPath(),
+ $fileName,
+ $readonly,
+ );
+ }
}
diff --git a/lib/private/Files/Node/HookConnector.php b/lib/private/Files/Node/HookConnector.php
index 8fd2ffa3369..1149951174c 100644
--- a/lib/private/Files/Node/HookConnector.php
+++ b/lib/private/Files/Node/HookConnector.php
@@ -39,7 +39,7 @@ class HookConnector {
private IRootFolder $root,
private View $view,
private IEventDispatcher $dispatcher,
- private LoggerInterface $logger
+ private LoggerInterface $logger,
) {
}
@@ -171,7 +171,7 @@ class HookConnector {
public function copy($arguments) {
$source = $this->getNodeForPath($arguments['oldpath']);
- $target = $this->getNodeForPath($arguments['newpath']);
+ $target = $this->getNodeForPath($arguments['newpath'], $source instanceof Folder);
$this->root->emit('\OC\Files', 'preCopy', [$source, $target]);
$this->dispatcher->dispatch('\OCP\Files::preCopy', new GenericEvent([$source, $target]));
@@ -203,7 +203,7 @@ class HookConnector {
$this->dispatcher->dispatchTyped($event);
}
- private function getNodeForPath(string $path): Node {
+ private function getNodeForPath(string $path, bool $isDir = false): Node {
$info = Filesystem::getView()->getFileInfo($path);
if (!$info) {
$fullPath = Filesystem::getView()->getAbsolutePath($path);
@@ -212,7 +212,7 @@ class HookConnector {
} else {
$info = null;
}
- if (Filesystem::is_dir($path)) {
+ if ($isDir || Filesystem::is_dir($path)) {
return new NonExistingFolder($this->root, $this->view, $fullPath, $info);
} else {
return new NonExistingFile($this->root, $this->view, $fullPath, $info);
diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php
index 83ed7e534a7..37b1efa0fad 100644
--- a/lib/private/Files/Node/LazyFolder.php
+++ b/lib/private/Files/Node/LazyFolder.php
@@ -134,9 +134,6 @@ class LazyFolder implements Folder {
$this->__call(__FUNCTION__, func_get_args());
}
- /**
- * @inheritDoc
- */
public function get($path) {
return $this->getRootFolder()->get($this->getFullPath($path));
}
@@ -422,9 +419,6 @@ class LazyFolder implements Folder {
return $this->__call(__FUNCTION__, func_get_args());
}
- /**
- * @inheritDoc
- */
public function nodeExists($path) {
return $this->__call(__FUNCTION__, func_get_args());
}
@@ -567,4 +561,8 @@ class LazyFolder implements Folder {
public function getMetadata(): array {
return $this->data['metadata'] ?? $this->__call(__FUNCTION__, func_get_args());
}
+
+ public function verifyPath($fileName, $readonly = false): void {
+ $this->__call(__FUNCTION__, func_get_args());
+ }
}
diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php
index f9908b9b7b6..77479c2fa5e 100644
--- a/lib/private/Files/Node/LazyUserFolder.php
+++ b/lib/private/Files/Node/LazyUserFolder.php
@@ -59,7 +59,7 @@ class LazyUserFolder extends LazyFolder {
}
$mountPoint = $this->mountManager->find('/' . $this->user->getUID());
if (is_null($mountPoint)) {
- throw new \Exception("No mountpoint for user folder");
+ throw new \Exception('No mountpoint for user folder');
}
return $mountPoint;
}
diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php
index e7f533e73a9..5dbdc4054bf 100644
--- a/lib/private/Files/Node/Node.php
+++ b/lib/private/Files/Node/Node.php
@@ -158,7 +158,7 @@ class Node implements INode {
public function getStorage() {
$storage = $this->getMountPoint()->getStorage();
if (!$storage) {
- throw new \Exception("No storage for node");
+ throw new \Exception('No storage for node');
}
return $storage;
}
@@ -432,11 +432,14 @@ class Node implements INode {
$targetPath = $this->normalizePath($targetPath);
$parent = $this->root->get(dirname($targetPath));
if (
- $parent instanceof Folder and
- $this->isValidPath($targetPath) and
- (
- $parent->isCreatable() ||
- ($parent->getInternalPath() === '' && $parent->getMountPoint() instanceof MoveableMount)
+ ($parent instanceof Folder)
+ && $this->isValidPath($targetPath)
+ && (
+ $parent->isCreatable()
+ || (
+ $parent->getInternalPath() === ''
+ && ($parent->getMountPoint() instanceof MoveableMount)
+ )
)
) {
$nonExisting = $this->createNonExistingNode($targetPath);
diff --git a/lib/private/Files/Node/NonExistingFile.php b/lib/private/Files/Node/NonExistingFile.php
index d154876432e..66ec2e6c040 100644
--- a/lib/private/Files/Node/NonExistingFile.php
+++ b/lib/private/Files/Node/NonExistingFile.php
@@ -38,6 +38,14 @@ class NonExistingFile extends File {
}
}
+ public function getInternalPath() {
+ if ($this->fileInfo) {
+ return parent::getInternalPath();
+ } else {
+ return $this->getParent()->getMountPoint()->getInternalPath($this->getPath());
+ }
+ }
+
public function stat() {
throw new NotFoundException();
}
diff --git a/lib/private/Files/Node/NonExistingFolder.php b/lib/private/Files/Node/NonExistingFolder.php
index 5650c99fe73..4489fdaf010 100644
--- a/lib/private/Files/Node/NonExistingFolder.php
+++ b/lib/private/Files/Node/NonExistingFolder.php
@@ -38,6 +38,14 @@ class NonExistingFolder extends Folder {
}
}
+ public function getInternalPath() {
+ if ($this->fileInfo) {
+ return parent::getInternalPath();
+ } else {
+ return $this->getParent()->getMountPoint()->getInternalPath($this->getPath());
+ }
+ }
+
public function stat() {
throw new NotFoundException();
}
diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php
index 5fb4013cfc9..76afca9dee8 100644
--- a/lib/private/Files/Node/Root.php
+++ b/lib/private/Files/Node/Root.php
@@ -28,6 +28,7 @@ use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IUser;
use OCP\IUserManager;
+use OCP\Server;
use Psr\Log\LoggerInterface;
/**
@@ -170,12 +171,6 @@ class Root extends Folder implements IRootFolder {
$this->mountManager->remove($mount);
}
- /**
- * @param string $path
- * @return Node
- * @throws \OCP\Files\NotPermittedException
- * @throws \OCP\Files\NotFoundException
- */
public function get($path) {
$path = $this->normalizePath($path);
if ($this->isValidPath($path)) {
@@ -389,13 +384,17 @@ class Root extends Folder implements IRootFolder {
// scope the cache by user, so we don't return nodes for different users
if ($this->user) {
$cachedPath = $this->pathByIdCache->get($this->user->getUID() . '::' . $id);
- if ($cachedPath && str_starts_with($path, $cachedPath)) {
+ if ($cachedPath && str_starts_with($cachedPath, $path)) {
// getting the node by path is significantly cheaper than finding it by id
- $node = $this->get($cachedPath);
- // by validating that the cached path still has the requested fileid we can work around the need to invalidate the cached path
- // if the cached path is invalid or a different file now we fall back to the uncached logic
- if ($node && $node->getId() === $id) {
- return $node;
+ try {
+ $node = $this->get($cachedPath);
+ // by validating that the cached path still has the requested fileid we can work around the need to invalidate the cached path
+ // if the cached path is invalid or a different file now we fall back to the uncached logic
+ if ($node && $node->getId() === $id) {
+ return $node;
+ }
+ } catch (NotFoundException|NotPermittedException) {
+ // The file may be moved but the old path still in cache
}
}
}
@@ -416,7 +415,7 @@ class Root extends Folder implements IRootFolder {
*/
public function getByIdInPath(int $id, string $path): array {
$mountCache = $this->getUserMountCache();
- if (strpos($path, '/', 1) > 0) {
+ if ($path !== '' && strpos($path, '/', 1) > 0) {
[, $user] = explode('/', $path);
} else {
$user = null;
@@ -459,7 +458,7 @@ class Root extends Folder implements IRootFolder {
if ($folder instanceof Folder) {
return $folder->getByIdInRootMount($id);
} else {
- throw new \Exception("getByIdInPath with non folder");
+ throw new \Exception('getByIdInPath with non folder');
}
}
return [];
@@ -477,9 +476,23 @@ class Root extends Folder implements IRootFolder {
$pathRelativeToMount = substr($internalPath, strlen($rootInternalPath));
$pathRelativeToMount = ltrim($pathRelativeToMount, '/');
$absolutePath = rtrim($mount->getMountPoint() . $pathRelativeToMount, '/');
+ $storage = $mount->getStorage();
+ if ($storage === null) {
+ return null;
+ }
+ $ownerId = $storage->getOwner($pathRelativeToMount);
+ if ($ownerId !== false) {
+ $owner = Server::get(IUserManager::class)->get($ownerId);
+ } else {
+ $owner = null;
+ }
return $this->createNode($absolutePath, new FileInfo(
- $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount,
- \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount))
+ $absolutePath,
+ $storage,
+ $cacheEntry->getPath(),
+ $cacheEntry,
+ $mount,
+ $owner,
));
}, $mountsContainingFile);
@@ -513,9 +526,9 @@ class Root extends Folder implements IRootFolder {
$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
$view = new View('');
if ($isDir) {
- return new Folder($this, $view, $path, $info, $parent);
+ return new Folder($this, $view, $fullPath, $info, $parent);
} else {
- return new File($this, $view, $path, $info, $parent);
+ return new File($this, $view, $fullPath, $info, $parent);
}
}
}
diff --git a/lib/private/Files/Notify/Change.php b/lib/private/Files/Notify/Change.php
index d2448e5deec..c8eccd11ae2 100644
--- a/lib/private/Files/Notify/Change.php
+++ b/lib/private/Files/Notify/Change.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib/private/Files/Notify/RenameChange.php b/lib/private/Files/Notify/RenameChange.php
index 98fd7099a58..28554ceaa26 100644
--- a/lib/private/Files/Notify/RenameChange.php
+++ b/lib/private/Files/Notify/RenameChange.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php b/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php
index 66fa74172d3..aaaee044bac 100644
--- a/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php
@@ -12,15 +12,15 @@ class AppdataPreviewObjectStoreStorage extends ObjectStoreStorage {
private string $internalId;
/**
- * @param array $params
+ * @param array $parameters
* @throws \Exception
*/
- public function __construct($params) {
- if (!isset($params['internal-id'])) {
+ public function __construct(array $parameters) {
+ if (!isset($parameters['internal-id'])) {
throw new \Exception('missing id in parameters');
}
- $this->internalId = (string)$params['internal-id'];
- parent::__construct($params);
+ $this->internalId = (string)$parameters['internal-id'];
+ parent::__construct($parameters);
}
public function getId(): string {
diff --git a/lib/private/Files/ObjectStore/Azure.php b/lib/private/Files/ObjectStore/Azure.php
index 55400d4131c..2729bb3c037 100644
--- a/lib/private/Files/ObjectStore/Azure.php
+++ b/lib/private/Files/ObjectStore/Azure.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -21,13 +22,13 @@ class Azure implements IObjectStore {
private $blobClient = null;
/** @var string|null */
private $endpoint = null;
- /** @var bool */
+ /** @var bool */
private $autoCreate = false;
/**
* @param array $parameters
*/
- public function __construct($parameters) {
+ public function __construct(array $parameters) {
$this->containerName = $parameters['container'];
$this->accountName = $parameters['account_name'];
$this->accountKey = $parameters['account_key'];
@@ -45,7 +46,7 @@ class Azure implements IObjectStore {
private function getBlobClient() {
if (!$this->blobClient) {
$protocol = $this->endpoint ? substr($this->endpoint, 0, strpos($this->endpoint, ':')) : 'https';
- $connectionString = "DefaultEndpointsProtocol=" . $protocol . ";AccountName=" . $this->accountName . ";AccountKey=" . $this->accountKey;
+ $connectionString = 'DefaultEndpointsProtocol=' . $protocol . ';AccountName=' . $this->accountName . ';AccountKey=' . $this->accountKey;
if ($this->endpoint) {
$connectionString .= ';BlobEndpoint=' . $this->endpoint;
}
diff --git a/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php b/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php
index b543d223f4c..4e2d10705fe 100644
--- a/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php
@@ -17,28 +17,22 @@ class HomeObjectStoreStorage extends ObjectStoreStorage implements IHomeStorage
/**
* The home user storage requires a user object to create a unique storage id
*
- * @param array $params
+ * @param array $parameters
* @throws Exception
*/
- public function __construct($params) {
- if (! isset($params['user']) || ! $params['user'] instanceof IUser) {
+ public function __construct(array $parameters) {
+ if (! isset($parameters['user']) || ! $parameters['user'] instanceof IUser) {
throw new Exception('missing user object in parameters');
}
- $this->user = $params['user'];
- parent::__construct($params);
+ $this->user = $parameters['user'];
+ parent::__construct($parameters);
}
public function getId(): string {
return 'object::user:' . $this->user->getUID();
}
- /**
- * get the owner of a path
- *
- * @param string $path The path to get the owner
- * @return string uid
- */
- public function getOwner($path): string {
+ public function getOwner(string $path): string|false {
return $this->user->getUID();
}
diff --git a/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php b/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php
new file mode 100644
index 00000000000..369182b069d
--- /dev/null
+++ b/lib/private/Files/ObjectStore/InvalidObjectStoreConfigurationException.php
@@ -0,0 +1,13 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Files\ObjectStore;
+
+class InvalidObjectStoreConfigurationException extends \Exception {
+
+}
diff --git a/lib/private/Files/ObjectStore/ObjectStoreScanner.php b/lib/private/Files/ObjectStore/ObjectStoreScanner.php
index d8a77d36dee..5c3992b8458 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreScanner.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreScanner.php
@@ -13,11 +13,11 @@ use OCP\Files\FileInfo;
class ObjectStoreScanner extends Scanner {
public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
- return [];
+ return null;
}
public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
- return [];
+ return null;
}
protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize, &$etagChanged = false) {
@@ -61,7 +61,7 @@ class ObjectStoreScanner extends Scanner {
$query->select('path')
->from('filecache')
->where($query->expr()->eq('storage', $query->createNamedParameter($this->cache->getNumericStorageId(), IQueryBuilder::PARAM_INT)))
- ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->eq('size', $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT)))
->orderBy('path', 'DESC')
->setMaxResults(1);
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index 389f744eab4..9ab11f8a3df 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -17,10 +17,12 @@ use OC\Files\Cache\CacheEntry;
use OC\Files\Storage\PolyFill\CopyDirectory;
use OCP\Files\Cache\ICache;
use OCP\Files\Cache\ICacheEntry;
+use OCP\Files\Cache\IScanner;
use OCP\Files\FileInfo;
use OCP\Files\GenericFileException;
use OCP\Files\NotFoundException;
use OCP\Files\ObjectStore\IObjectStore;
+use OCP\Files\ObjectStore\IObjectStoreMetaData;
use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload;
use OCP\Files\Storage\IChunkedFileWrite;
use OCP\Files\Storage\IStorage;
@@ -37,34 +39,35 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
private bool $handleCopiesAsOwned;
protected bool $validateWrites = true;
+ private bool $preserveCacheItemsOnDelete = false;
/**
- * @param array $params
+ * @param array $parameters
* @throws \Exception
*/
- public function __construct($params) {
- if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) {
- $this->objectStore = $params['objectstore'];
+ public function __construct(array $parameters) {
+ if (isset($parameters['objectstore']) && $parameters['objectstore'] instanceof IObjectStore) {
+ $this->objectStore = $parameters['objectstore'];
} else {
throw new \Exception('missing IObjectStore instance');
}
- if (isset($params['storageid'])) {
- $this->id = 'object::store:' . $params['storageid'];
+ if (isset($parameters['storageid'])) {
+ $this->id = 'object::store:' . $parameters['storageid'];
} else {
$this->id = 'object::store:' . $this->objectStore->getStorageId();
}
- if (isset($params['objectPrefix'])) {
- $this->objectPrefix = $params['objectPrefix'];
+ if (isset($parameters['objectPrefix'])) {
+ $this->objectPrefix = $parameters['objectPrefix'];
}
- if (isset($params['validateWrites'])) {
- $this->validateWrites = (bool)$params['validateWrites'];
+ if (isset($parameters['validateWrites'])) {
+ $this->validateWrites = (bool)$parameters['validateWrites'];
}
- $this->handleCopiesAsOwned = (bool)($params['handleCopiesAsOwned'] ?? false);
+ $this->handleCopiesAsOwned = (bool)($parameters['handleCopiesAsOwned'] ?? false);
$this->logger = \OCP\Server::get(LoggerInterface::class);
}
- public function mkdir($path, bool $force = false) {
+ public function mkdir(string $path, bool $force = false, array $metadata = []): bool {
$path = $this->normalizePath($path);
if (!$force && $this->file_exists($path)) {
$this->logger->warning("Tried to create an object store folder that already exists: $path");
@@ -74,7 +77,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
$mTime = time();
$data = [
'mimetype' => 'httpd/unix-directory',
- 'size' => 0,
+ 'size' => $metadata['size'] ?? 0,
'mtime' => $mTime,
'storage_mtime' => $mTime,
'permissions' => \OCP\Constants::PERMISSION_ALL,
@@ -109,11 +112,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
}
- /**
- * @param string $path
- * @return string
- */
- private function normalizePath($path) {
+ private function normalizePath(string $path): string {
$path = trim($path, '/');
//FIXME why do we sometimes get a path like 'files//username'?
$path = str_replace('//', '/', $path);
@@ -129,26 +128,23 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
/**
* Object Stores use a NoopScanner because metadata is directly stored in
* the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
- * @return \OC\Files\ObjectStore\ObjectStoreScanner
*/
- public function getScanner($path = '', $storage = null) {
+ public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
if (!$storage) {
$storage = $this;
}
if (!isset($this->scanner)) {
$this->scanner = new ObjectStoreScanner($storage);
}
+ /** @var \OC\Files\ObjectStore\ObjectStoreScanner */
return $this->scanner;
}
- public function getId() {
+ public function getId(): string {
return $this->id;
}
- public function rmdir($path) {
+ public function rmdir(string $path): bool {
$path = $this->normalizePath($path);
$entry = $this->getCache()->get($path);
@@ -173,12 +169,14 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
}
- $this->getCache()->remove($entry->getPath());
+ if (!$this->preserveCacheItemsOnDelete) {
+ $this->getCache()->remove($entry->getPath());
+ }
return true;
}
- public function unlink($path) {
+ public function unlink(string $path): bool {
$path = $this->normalizePath($path);
$entry = $this->getCache()->get($path);
@@ -208,11 +206,13 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
//removing from cache is ok as it does not exist in the objectstore anyway
}
- $this->getCache()->remove($entry->getPath());
+ if (!$this->preserveCacheItemsOnDelete) {
+ $this->getCache()->remove($entry->getPath());
+ }
return true;
}
- public function stat($path) {
+ public function stat(string $path): array|false {
$path = $this->normalizePath($path);
$cacheEntry = $this->getCache()->get($path);
if ($cacheEntry instanceof CacheEntry) {
@@ -229,7 +229,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
}
- public function getPermissions($path) {
+ public function getPermissions(string $path): int {
$stat = $this->stat($path);
if (is_array($stat) && isset($stat['permissions'])) {
@@ -244,17 +244,13 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
* The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
* You may need a mapping table to store your URN if it cannot be generated from the fileid.
*
- * @param int $fileId the fileid
- * @return null|string the unified resource name used to identify the object
+ * @return string the unified resource name used to identify the object
*/
- public function getURN($fileId) {
- if (is_numeric($fileId)) {
- return $this->objectPrefix . $fileId;
- }
- return null;
+ public function getURN(int $fileId): string {
+ return $this->objectPrefix . $fileId;
}
- public function opendir($path) {
+ public function opendir(string $path) {
$path = $this->normalizePath($path);
try {
@@ -271,7 +267,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
}
- public function filetype($path) {
+ public function filetype(string $path): string|false {
$path = $this->normalizePath($path);
$stat = $this->stat($path);
if ($stat) {
@@ -284,7 +280,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
}
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
$path = $this->normalizePath($path);
if (strrpos($path, '.') !== false) {
@@ -376,12 +372,12 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
return false;
}
- public function file_exists($path) {
+ public function file_exists(string $path): bool {
$path = $this->normalizePath($path);
return (bool)$this->stat($path);
}
- public function rename($source, $target) {
+ public function rename(string $source, string $target): bool {
$source = $this->normalizePath($source);
$target = $this->normalizePath($target);
$this->remove($target);
@@ -390,12 +386,12 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
return true;
}
- public function getMimeType($path) {
+ public function getMimeType(string $path): string|false {
$path = $this->normalizePath($path);
return parent::getMimeType($path);
}
- public function touch($path, $mtime = null) {
+ public function touch(string $path, ?int $mtime = null): bool {
if (is_null($mtime)) {
$mtime = time();
}
@@ -417,16 +413,6 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
//create a empty file, need to have at least on char to make it
// work with all object storage implementations
$this->file_put_contents($path, ' ');
- $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
- $stat = [
- 'etag' => $this->getETag($path),
- 'mimetype' => $mimeType,
- 'size' => 0,
- 'mtime' => $mtime,
- 'storage_mtime' => $mtime,
- 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
- ];
- $this->getCache()->put($path, $stat);
} catch (\Exception $ex) {
$this->logger->error(
'Could not create object for ' . $path,
@@ -441,37 +427,34 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
return true;
}
- public function writeBack($tmpFile, $path) {
+ public function writeBack(string $tmpFile, string $path) {
$size = filesize($tmpFile);
$this->writeStream($path, fopen($tmpFile, 'r'), $size);
}
- /**
- * external changes are not supported, exclusive access to the object storage is assumed
- *
- * @param string $path
- * @param int $time
- * @return false
- */
- public function hasUpdated($path, $time) {
+ public function hasUpdated(string $path, int $time): bool {
return false;
}
- public function needsPartFile() {
+ public function needsPartFile(): bool {
return false;
}
- public function file_put_contents($path, $data) {
- $handle = $this->fopen($path, 'w+');
- if (!$handle) {
- return false;
- }
- $result = fwrite($handle, $data);
- fclose($handle);
- return $result;
+ public function file_put_contents(string $path, mixed $data): int {
+ $fh = fopen('php://temp', 'w+');
+ fwrite($fh, $data);
+ rewind($fh);
+ return $this->writeStream($path, $fh, strlen($data));
}
public function writeStream(string $path, $stream, ?int $size = null): int {
+ if ($size === null) {
+ $stats = fstat($stream);
+ if (is_array($stats) && isset($stats['size'])) {
+ $size = $stats['size'];
+ }
+ }
+
$stat = $this->stat($path);
if (empty($stat)) {
// create new file
@@ -487,6 +470,14 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
$mimetypeDetector = \OC::$server->getMimeTypeDetector();
$mimetype = $mimetypeDetector->detectPath($path);
+ $metadata = [
+ 'mimetype' => $mimetype,
+ 'original-storage' => $this->getId(),
+ 'original-path' => $path,
+ ];
+ if ($size) {
+ $metadata['size'] = $size;
+ }
$stat['mimetype'] = $mimetype;
$stat['etag'] = $this->getETag($path);
@@ -498,30 +489,37 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
if ($exists) {
$fileId = $stat['fileid'];
} else {
+ $parent = $this->normalizePath(dirname($path));
+ if (!$this->is_dir($parent)) {
+ throw new \InvalidArgumentException("trying to upload a file ($path) inside a non-directory ($parent)");
+ }
$fileId = $this->getCache()->put($uploadPath, $stat);
}
$urn = $this->getURN($fileId);
try {
//upload to object storage
- if ($size === null) {
- $countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) {
+
+ $totalWritten = 0;
+ $countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, $size, $exists, &$totalWritten) {
+ if (is_null($size) && !$exists) {
$this->getCache()->update($fileId, [
'size' => $writtenSize,
]);
- $size = $writtenSize;
- });
- $this->objectStore->writeObject($urn, $countStream, $mimetype);
- if (is_resource($countStream)) {
- fclose($countStream);
}
- $stat['size'] = $size;
+ $totalWritten = $writtenSize;
+ });
+
+ if ($this->objectStore instanceof IObjectStoreMetaData) {
+ $this->objectStore->writeObjectWithMetaData($urn, $countStream, $metadata);
} else {
- $this->objectStore->writeObject($urn, $stream, $mimetype);
- if (is_resource($stream)) {
- fclose($stream);
- }
+ $this->objectStore->writeObject($urn, $countStream, $metadata['mimetype']);
+ }
+ if (is_resource($countStream)) {
+ fclose($countStream);
}
+
+ $stat['size'] = $totalWritten;
} catch (\Exception $ex) {
if (!$exists) {
/*
@@ -545,7 +543,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
]
);
}
- throw $ex; // make this bubble up
+ throw new GenericFileException('Error while writing stream to object store', 0, $ex);
}
if ($exists) {
@@ -561,7 +559,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
}
- return $size;
+ return $totalWritten;
}
public function getObjectStore(): IObjectStore {
@@ -570,10 +568,10 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
public function copyFromStorage(
IStorage $sourceStorage,
- $sourceInternalPath,
- $targetInternalPath,
- $preserveMtime = false
- ) {
+ string $sourceInternalPath,
+ string $targetInternalPath,
+ bool $preserveMtime = false,
+ ): bool {
if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
/** @var ObjectStoreStorage $sourceStorage */
if ($sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()) {
@@ -594,7 +592,90 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
}
- public function copy($source, $target) {
+ public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, ?ICacheEntry $sourceCacheEntry = null): bool {
+ $sourceCache = $sourceStorage->getCache();
+ if (
+ $sourceStorage->instanceOfStorage(ObjectStoreStorage::class)
+ && $sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()
+ ) {
+ if ($this->getCache()->get($targetInternalPath)) {
+ $this->unlink($targetInternalPath);
+ $this->getCache()->remove($targetInternalPath);
+ }
+ $this->getCache()->moveFromCache($sourceCache, $sourceInternalPath, $targetInternalPath);
+ // Do not import any data when source and target bucket are identical.
+ return true;
+ }
+ if (!$sourceCacheEntry) {
+ $sourceCacheEntry = $sourceCache->get($sourceInternalPath);
+ }
+
+ $this->copyObjects($sourceStorage, $sourceCache, $sourceCacheEntry);
+ if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
+ /** @var ObjectStoreStorage $sourceStorage */
+ $sourceStorage->setPreserveCacheOnDelete(true);
+ }
+ if ($sourceCacheEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) {
+ $sourceStorage->rmdir($sourceInternalPath);
+ } else {
+ $sourceStorage->unlink($sourceInternalPath);
+ }
+ if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
+ /** @var ObjectStoreStorage $sourceStorage */
+ $sourceStorage->setPreserveCacheOnDelete(false);
+ }
+ if ($this->getCache()->get($targetInternalPath)) {
+ $this->unlink($targetInternalPath);
+ $this->getCache()->remove($targetInternalPath);
+ }
+ $this->getCache()->moveFromCache($sourceCache, $sourceInternalPath, $targetInternalPath);
+
+ return true;
+ }
+
+ /**
+ * Copy the object(s) of a file or folder into this storage, without touching the cache
+ */
+ private function copyObjects(IStorage $sourceStorage, ICache $sourceCache, ICacheEntry $sourceCacheEntry) {
+ $copiedFiles = [];
+ try {
+ foreach ($this->getAllChildObjects($sourceCache, $sourceCacheEntry) as $file) {
+ $sourceStream = $sourceStorage->fopen($file->getPath(), 'r');
+ if (!$sourceStream) {
+ throw new \Exception("Failed to open source file {$file->getPath()} ({$file->getId()})");
+ }
+ $this->objectStore->writeObject($this->getURN($file->getId()), $sourceStream, $file->getMimeType());
+ if (is_resource($sourceStream)) {
+ fclose($sourceStream);
+ }
+ $copiedFiles[] = $file->getId();
+ }
+ } catch (\Exception $e) {
+ foreach ($copiedFiles as $fileId) {
+ try {
+ $this->objectStore->deleteObject($this->getURN($fileId));
+ } catch (\Exception $e) {
+ // ignore
+ }
+ }
+ throw $e;
+ }
+ }
+
+ /**
+ * @return \Iterator<ICacheEntry>
+ */
+ private function getAllChildObjects(ICache $cache, ICacheEntry $entry): \Iterator {
+ if ($entry->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
+ foreach ($cache->getFolderContentsById($entry->getId()) as $child) {
+ yield from $this->getAllChildObjects($cache, $child);
+ }
+ } else {
+ yield $entry;
+ }
+ }
+
+ public function copy(string $source, string $target): bool {
$source = $this->normalizePath($source);
$target = $this->normalizePath($target);
@@ -616,7 +697,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
if ($cache->inCache($to)) {
$cache->remove($to);
}
- $this->mkdir($to);
+ $this->mkdir($to, false, ['size' => $sourceEntry->getSize()]);
foreach ($sourceCache->getFolderContentsById($sourceEntry->getId()) as $child) {
$this->copyInner($sourceCache, $child, $to . '/' . $child->getName());
@@ -632,7 +713,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
$sourceUrn = $this->getURN($sourceEntry->getId());
if (!$cache instanceof Cache) {
- throw new \Exception("Invalid source cache for object store copy");
+ throw new \Exception('Invalid source cache for object store copy');
}
$targetId = $cache->copyFromCache($cache, $sourceEntry, $to);
@@ -662,7 +743,6 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
/**
- *
* @throws GenericFileException
*/
public function putChunkedWritePart(
@@ -670,7 +750,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
string $writeToken,
string $chunkId,
$data,
- $size = null
+ $size = null,
): ?array {
if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) {
throw new GenericFileException('Object store does not support multipart upload');
@@ -728,4 +808,8 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
$urn = $this->getURN($cacheEntry->getId());
$this->objectStore->abortMultipartUpload($urn, $writeToken);
}
+
+ public function setPreserveCacheOnDelete(bool $preserve) {
+ $this->preserveCacheItemsOnDelete = $preserve;
+ }
}
diff --git a/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php b/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php
new file mode 100644
index 00000000000..ffc33687340
--- /dev/null
+++ b/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php
@@ -0,0 +1,225 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace OC\Files\ObjectStore;
+
+use OCP\App\IAppManager;
+use OCP\Files\ObjectStore\IObjectStore;
+use OCP\IConfig;
+use OCP\IUser;
+
+/**
+ * @psalm-type ObjectStoreConfig array{class: class-string<IObjectStore>, arguments: array{multibucket: bool, ...}}
+ */
+class PrimaryObjectStoreConfig {
+ public function __construct(
+ private readonly IConfig $config,
+ private readonly IAppManager $appManager,
+ ) {
+ }
+
+ /**
+ * @param ObjectStoreConfig $config
+ */
+ public function buildObjectStore(array $config): IObjectStore {
+ return new $config['class']($config['arguments']);
+ }
+
+ /**
+ * @return ?ObjectStoreConfig
+ */
+ public function getObjectStoreConfigForRoot(): ?array {
+ if (!$this->hasObjectStore()) {
+ return null;
+ }
+
+ $config = $this->getObjectStoreConfiguration('root');
+
+ if ($config['arguments']['multibucket']) {
+ if (!isset($config['arguments']['bucket'])) {
+ $config['arguments']['bucket'] = '';
+ }
+
+ // put the root FS always in first bucket for multibucket configuration
+ $config['arguments']['bucket'] .= '0';
+ }
+ return $config;
+ }
+
+ /**
+ * @return ?ObjectStoreConfig
+ */
+ public function getObjectStoreConfigForUser(IUser $user): ?array {
+ if (!$this->hasObjectStore()) {
+ return null;
+ }
+
+ $store = $this->getObjectStoreForUser($user);
+ $config = $this->getObjectStoreConfiguration($store);
+
+ if ($config['arguments']['multibucket']) {
+ $config['arguments']['bucket'] = $this->getBucketForUser($user, $config);
+ }
+ return $config;
+ }
+
+ /**
+ * @param string $name
+ * @return ObjectStoreConfig
+ */
+ public function getObjectStoreConfiguration(string $name): array {
+ $configs = $this->getObjectStoreConfigs();
+ $name = $this->resolveAlias($name);
+ if (!isset($configs[$name])) {
+ throw new \Exception("Object store configuration for '$name' not found");
+ }
+ if (is_string($configs[$name])) {
+ throw new \Exception("Object store configuration for '{$configs[$name]}' not found");
+ }
+ return $configs[$name];
+ }
+
+ public function resolveAlias(string $name): string {
+ $configs = $this->getObjectStoreConfigs();
+
+ while (isset($configs[$name]) && is_string($configs[$name])) {
+ $name = $configs[$name];
+ }
+ return $name;
+ }
+
+ public function hasObjectStore(): bool {
+ $objectStore = $this->config->getSystemValue('objectstore', null);
+ $objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
+ return $objectStore || $objectStoreMultiBucket;
+ }
+
+ public function hasMultipleObjectStorages(): bool {
+ $objectStore = $this->config->getSystemValue('objectstore', []);
+ return isset($objectStore['default']);
+ }
+
+ /**
+ * @return ?array<string, ObjectStoreConfig|string>
+ * @throws InvalidObjectStoreConfigurationException
+ */
+ public function getObjectStoreConfigs(): ?array {
+ $objectStore = $this->config->getSystemValue('objectstore', null);
+ $objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
+
+ // new-style multibucket config uses the same 'objectstore' key but sets `'multibucket' => true`, transparently upgrade older style config
+ if ($objectStoreMultiBucket) {
+ $objectStoreMultiBucket['arguments']['multibucket'] = true;
+ return [
+ 'default' => 'server1',
+ 'server1' => $this->validateObjectStoreConfig($objectStoreMultiBucket),
+ 'root' => 'server1',
+ ];
+ } elseif ($objectStore) {
+ if (!isset($objectStore['default'])) {
+ $objectStore = [
+ 'default' => 'server1',
+ 'root' => 'server1',
+ 'server1' => $objectStore,
+ ];
+ }
+ if (!isset($objectStore['root'])) {
+ $objectStore['root'] = 'default';
+ }
+
+ if (!is_string($objectStore['default'])) {
+ throw new InvalidObjectStoreConfigurationException('The \'default\' object storage configuration is required to be a reference to another configuration.');
+ }
+ return array_map($this->validateObjectStoreConfig(...), $objectStore);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @param array|string $config
+ * @return string|ObjectStoreConfig
+ */
+ private function validateObjectStoreConfig(array|string $config): array|string {
+ if (is_string($config)) {
+ return $config;
+ }
+ if (!isset($config['class'])) {
+ throw new InvalidObjectStoreConfigurationException('No class configured for object store');
+ }
+ if (!isset($config['arguments'])) {
+ $config['arguments'] = [];
+ }
+ $class = $config['class'];
+ $arguments = $config['arguments'];
+ if (!is_array($arguments)) {
+ throw new InvalidObjectStoreConfigurationException('Configured object store arguments are not an array');
+ }
+ if (!isset($arguments['multibucket'])) {
+ $arguments['multibucket'] = false;
+ }
+ if (!is_bool($arguments['multibucket'])) {
+ throw new InvalidObjectStoreConfigurationException('arguments.multibucket must be a boolean in object store configuration');
+ }
+
+ if (!is_string($class)) {
+ throw new InvalidObjectStoreConfigurationException('Configured class for object store is not a string');
+ }
+
+ if (str_starts_with($class, 'OCA\\') && substr_count($class, '\\') >= 2) {
+ [$appId] = explode('\\', $class);
+ $this->appManager->loadApp(strtolower($appId));
+ }
+
+ if (!is_a($class, IObjectStore::class, true)) {
+ throw new InvalidObjectStoreConfigurationException('Configured class for object store is not an object store');
+ }
+ return [
+ 'class' => $class,
+ 'arguments' => $arguments,
+ ];
+ }
+
+ public function getBucketForUser(IUser $user, array $config): string {
+ $bucket = $this->getSetBucketForUser($user);
+
+ if ($bucket === null) {
+ /*
+ * Use any provided bucket argument as prefix
+ * and add the mapping from username => bucket
+ */
+ if (!isset($config['arguments']['bucket'])) {
+ $config['arguments']['bucket'] = '';
+ }
+ $mapper = new Mapper($user, $this->config);
+ $numBuckets = $config['arguments']['num_buckets'] ?? 64;
+ $bucket = $config['arguments']['bucket'] . $mapper->getBucket($numBuckets);
+
+ $this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $bucket);
+ }
+
+ return $bucket;
+ }
+
+ public function getSetBucketForUser(IUser $user): ?string {
+ return $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null);
+ }
+
+ public function getObjectStoreForUser(IUser $user): string {
+ if ($this->hasMultipleObjectStorages()) {
+ $value = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'objectstore', null);
+ if ($value === null) {
+ $value = $this->resolveAlias('default');
+ $this->config->setUserValue($user->getUID(), 'homeobjectstore', 'objectstore', $value);
+ }
+ return $value;
+ } else {
+ return 'default';
+ }
+ }
+}
diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php
index 72c19d951e4..72e1751e23d 100644
--- a/lib/private/Files/ObjectStore/S3.php
+++ b/lib/private/Files/ObjectStore/S3.php
@@ -1,20 +1,23 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+
namespace OC\Files\ObjectStore;
use Aws\Result;
use Exception;
use OCP\Files\ObjectStore\IObjectStore;
+use OCP\Files\ObjectStore\IObjectStoreMetaData;
use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload;
-class S3 implements IObjectStore, IObjectStoreMultiPartUpload {
+class S3 implements IObjectStore, IObjectStoreMultiPartUpload, IObjectStoreMetaData {
use S3ConnectionTrait;
use S3ObjectTrait;
- public function __construct($parameters) {
+ public function __construct(array $parameters) {
$parameters['primary_storage'] = true;
$this->parseParams($parameters);
}
@@ -61,7 +64,7 @@ class S3 implements IObjectStore, IObjectStoreMultiPartUpload {
'Key' => $urn,
'UploadId' => $uploadId,
'MaxParts' => 1000,
- 'PartNumberMarker' => $partNumberMarker
+ 'PartNumberMarker' => $partNumberMarker,
] + $this->getSSECParameters());
$parts = array_merge($parts, $result->get('Parts') ?? []);
$isTruncated = $result->get('IsTruncated');
@@ -89,7 +92,51 @@ class S3 implements IObjectStore, IObjectStoreMultiPartUpload {
$this->getConnection()->abortMultipartUpload([
'Bucket' => $this->bucket,
'Key' => $urn,
- 'UploadId' => $uploadId
+ 'UploadId' => $uploadId,
]);
}
+
+ private function parseS3Metadata(array $metadata): array {
+ $result = [];
+ foreach ($metadata as $key => $value) {
+ if (str_starts_with($key, 'x-amz-meta-')) {
+ $result[substr($key, strlen('x-amz-meta-'))] = $value;
+ }
+ }
+ return $result;
+ }
+
+ public function getObjectMetaData(string $urn): array {
+ $object = $this->getConnection()->headObject([
+ 'Bucket' => $this->bucket,
+ 'Key' => $urn
+ ] + $this->getSSECParameters())->toArray();
+ return [
+ 'mtime' => $object['LastModified'],
+ 'etag' => trim($object['ETag'], '"'),
+ 'size' => (int)($object['Size'] ?? $object['ContentLength']),
+ ] + $this->parseS3Metadata($object['Metadata'] ?? []);
+ }
+
+ public function listObjects(string $prefix = ''): \Iterator {
+ $results = $this->getConnection()->getPaginator('ListObjectsV2', [
+ 'Bucket' => $this->bucket,
+ 'Prefix' => $prefix,
+ ] + $this->getSSECParameters());
+
+ foreach ($results as $result) {
+ if (is_array($result['Contents'])) {
+ foreach ($result['Contents'] as $object) {
+ yield [
+ 'urn' => basename($object['Key']),
+ 'metadata' => [
+ 'mtime' => $object['LastModified'],
+ 'etag' => trim($object['ETag'], '"'),
+ 'size' => (int)($object['Size'] ?? $object['ContentLength']),
+ ],
+ ];
+ }
+ }
+ }
+ }
}
diff --git a/lib/private/Files/ObjectStore/S3ConfigTrait.php b/lib/private/Files/ObjectStore/S3ConfigTrait.php
index 3a399e6413d..5b086db8f77 100644
--- a/lib/private/Files/ObjectStore/S3ConfigTrait.php
+++ b/lib/private/Files/ObjectStore/S3ConfigTrait.php
@@ -18,9 +18,13 @@ trait S3ConfigTrait {
/** Maximum number of concurrent multipart uploads */
protected int $concurrency;
+ /** Timeout, in seconds, for the connection to S3 server, not for the
+ * request. */
+ protected float $connectTimeout;
+
protected int $timeout;
- protected string $proxy;
+ protected string|false $proxy;
protected string $storageClass;
diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php
index c7a5a8a1add..67b82a44ab7 100644
--- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php
+++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -11,9 +12,11 @@ use Aws\Credentials\Credentials;
use Aws\Exception\CredentialsException;
use Aws\S3\Exception\S3Exception;
use Aws\S3\S3Client;
-use GuzzleHttp\Promise;
+use GuzzleHttp\Promise\Create;
use GuzzleHttp\Promise\RejectedPromise;
+use OCP\Files\StorageNotAvailableException;
use OCP\ICertificateManager;
+use OCP\Server;
use Psr\Log\LoggerInterface;
trait S3ConnectionTrait {
@@ -27,7 +30,7 @@ trait S3ConnectionTrait {
protected function parseParams($params) {
if (empty($params['bucket'])) {
- throw new \Exception("Bucket has to be configured.");
+ throw new \Exception('Bucket has to be configured.');
}
$this->id = 'amazon::' . $params['bucket'];
@@ -37,6 +40,7 @@ trait S3ConnectionTrait {
// Default to 5 like the S3 SDK does
$this->concurrency = $params['concurrency'] ?? 5;
$this->proxy = $params['proxy'] ?? false;
+ $this->connectTimeout = $params['connect_timeout'] ?? 5;
$this->timeout = $params['timeout'] ?? 15;
$this->storageClass = !empty($params['storageClass']) ? $params['storageClass'] : 'STANDARD';
$this->uploadPartSize = $params['uploadPartSize'] ?? 524288000;
@@ -98,8 +102,15 @@ trait S3ConnectionTrait {
'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()),
'csm' => false,
'use_arn_region' => false,
- 'http' => ['verify' => $this->getCertificateBundlePath()],
+ 'http' => [
+ 'verify' => $this->getCertificateBundlePath(),
+ 'connect_timeout' => $this->connectTimeout,
+ ],
'use_aws_shared_config_files' => false,
+ 'retries' => [
+ 'mode' => 'standard',
+ 'max_attempts' => 5,
+ ],
];
if ($this->params['s3-accelerate']) {
@@ -116,35 +127,38 @@ trait S3ConnectionTrait {
}
$this->connection = new S3Client($options);
- if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
- $logger = \OC::$server->get(LoggerInterface::class);
- $logger->debug('Bucket "' . $this->bucket . '" This bucket name is not dns compatible, it may contain invalid characters.',
- ['app' => 'objectstore']);
- }
+ try {
+ $logger = Server::get(LoggerInterface::class);
+ if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
+ $logger->debug('Bucket "' . $this->bucket . '" This bucket name is not dns compatible, it may contain invalid characters.',
+ ['app' => 'objectstore']);
+ }
- if ($this->params['verify_bucket_exists'] && !$this->connection->doesBucketExist($this->bucket)) {
- $logger = \OC::$server->get(LoggerInterface::class);
- try {
- $logger->info('Bucket "' . $this->bucket . '" does not exist - creating it.', ['app' => 'objectstore']);
- if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
- throw new \Exception("The bucket will not be created because the name is not dns compatible, please correct it: " . $this->bucket);
- }
- $this->connection->createBucket(['Bucket' => $this->bucket]);
- $this->testTimeout();
- } catch (S3Exception $e) {
- $logger->debug('Invalid remote storage.', [
- 'exception' => $e,
- 'app' => 'objectstore',
- ]);
- if ($e->getAwsErrorCode() !== "BucketAlreadyOwnedByYou") {
- throw new \Exception('Creation of bucket "' . $this->bucket . '" failed. ' . $e->getMessage());
+ if ($this->params['verify_bucket_exists'] && !$this->connection->doesBucketExist($this->bucket)) {
+ try {
+ $logger->info('Bucket "' . $this->bucket . '" does not exist - creating it.', ['app' => 'objectstore']);
+ if (!$this->connection::isBucketDnsCompatible($this->bucket)) {
+ throw new StorageNotAvailableException('The bucket will not be created because the name is not dns compatible, please correct it: ' . $this->bucket);
+ }
+ $this->connection->createBucket(['Bucket' => $this->bucket]);
+ $this->testTimeout();
+ } catch (S3Exception $e) {
+ $logger->debug('Invalid remote storage.', [
+ 'exception' => $e,
+ 'app' => 'objectstore',
+ ]);
+ if ($e->getAwsErrorCode() !== 'BucketAlreadyOwnedByYou') {
+ throw new StorageNotAvailableException('Creation of bucket "' . $this->bucket . '" failed. ' . $e->getMessage());
+ }
}
}
- }
- // google cloud's s3 compatibility doesn't like the EncodingType parameter
- if (strpos($base_url, 'storage.googleapis.com')) {
- $this->connection->getHandlerList()->remove('s3.auto_encode');
+ // google cloud's s3 compatibility doesn't like the EncodingType parameter
+ if (strpos($base_url, 'storage.googleapis.com')) {
+ $this->connection->getHandlerList()->remove('s3.auto_encode');
+ }
+ } catch (S3Exception $e) {
+ throw new StorageNotAvailableException('S3 service is unable to handle request: ' . $e->getMessage());
}
return $this->connection;
@@ -176,10 +190,12 @@ trait S3ConnectionTrait {
return function () {
$key = empty($this->params['key']) ? null : $this->params['key'];
$secret = empty($this->params['secret']) ? null : $this->params['secret'];
+ $sessionToken = empty($this->params['session_token']) ? null : $this->params['session_token'];
if ($key && $secret) {
- return Promise\promise_for(
- new Credentials($key, $secret)
+ return Create::promiseFor(
+ // a null sessionToken match the default signature of the constructor
+ new Credentials($key, $secret, $sessionToken)
);
}
@@ -189,11 +205,11 @@ trait S3ConnectionTrait {
}
protected function getCertificateBundlePath(): ?string {
- if ((int)($this->params['use_nextcloud_bundle'] ?? "0")) {
+ if ((int)($this->params['use_nextcloud_bundle'] ?? '0')) {
// since we store the certificate bundles on the primary storage, we can't get the bundle while setting up the primary storage
if (!isset($this->params['primary_storage'])) {
/** @var ICertificateManager $certManager */
- $certManager = \OC::$server->get(ICertificateManager::class);
+ $certManager = Server::get(ICertificateManager::class);
return $certManager->getAbsoluteBundlePath();
} else {
return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt';
@@ -204,7 +220,7 @@ trait S3ConnectionTrait {
}
protected function getSSECKey(): ?string {
- if (isset($this->params['sse_c_key'])) {
+ if (isset($this->params['sse_c_key']) && !empty($this->params['sse_c_key'])) {
return $this->params['sse_c_key'];
}
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php
index 5d00c184ca7..89405de2e8e 100644
--- a/lib/private/Files/ObjectStore/S3ObjectTrait.php
+++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php
@@ -6,6 +6,8 @@
*/
namespace OC\Files\ObjectStore;
+use Aws\Command;
+use Aws\Exception\MultipartUploadException;
use Aws\S3\Exception\S3MultipartUploadException;
use Aws\S3\MultipartCopy;
use Aws\S3\MultipartUploader;
@@ -77,24 +79,42 @@ trait S3ObjectTrait {
return $fh;
}
+ private function buildS3Metadata(array $metadata): array {
+ $result = [];
+ foreach ($metadata as $key => $value) {
+ $result['x-amz-meta-' . $key] = $value;
+ }
+ return $result;
+ }
/**
* Single object put helper
*
* @param string $urn the unified resource name used to identify the object
* @param StreamInterface $stream stream with the data to write
- * @param string|null $mimetype the mimetype to set for the remove object @since 22.0.0
+ * @param array $metaData the metadata to set for the object
* @throws \Exception when something goes wrong, message will be logged
*/
- protected function writeSingle(string $urn, StreamInterface $stream, ?string $mimetype = null): void {
- $this->getConnection()->putObject([
+ protected function writeSingle(string $urn, StreamInterface $stream, array $metaData): void {
+ $mimetype = $metaData['mimetype'] ?? null;
+ unset($metaData['mimetype']);
+ unset($metaData['size']);
+
+ $args = [
'Bucket' => $this->bucket,
'Key' => $urn,
'Body' => $stream,
'ACL' => 'private',
'ContentType' => $mimetype,
+ 'Metadata' => $this->buildS3Metadata($metaData),
'StorageClass' => $this->storageClass,
- ] + $this->getSSECParameters());
+ ] + $this->getSSECParameters();
+
+ if ($size = $stream->getSize()) {
+ $args['ContentLength'] = $size;
+ }
+
+ $this->getConnection()->putObject($args);
}
@@ -103,57 +123,116 @@ trait S3ObjectTrait {
*
* @param string $urn the unified resource name used to identify the object
* @param StreamInterface $stream stream with the data to write
- * @param string|null $mimetype the mimetype to set for the remove object
+ * @param array $metaData the metadata to set for the object
* @throws \Exception when something goes wrong, message will be logged
*/
- protected function writeMultiPart(string $urn, StreamInterface $stream, ?string $mimetype = null): void {
- $uploader = new MultipartUploader($this->getConnection(), $stream, [
- 'bucket' => $this->bucket,
- 'concurrency' => $this->concurrency,
- 'key' => $urn,
- 'part_size' => $this->uploadPartSize,
- 'params' => [
- 'ContentType' => $mimetype,
- 'StorageClass' => $this->storageClass,
- ] + $this->getSSECParameters(),
- ]);
+ protected function writeMultiPart(string $urn, StreamInterface $stream, array $metaData): void {
+ $mimetype = $metaData['mimetype'] ?? null;
+ unset($metaData['mimetype']);
+ unset($metaData['size']);
+
+ $attempts = 0;
+ $uploaded = false;
+ $concurrency = $this->concurrency;
+ $exception = null;
+ $state = null;
+ $size = $stream->getSize();
+ $totalWritten = 0;
+
+ // retry multipart upload once with concurrency at half on failure
+ while (!$uploaded && $attempts <= 1) {
+ $uploader = new MultipartUploader($this->getConnection(), $stream, [
+ 'bucket' => $this->bucket,
+ 'concurrency' => $concurrency,
+ 'key' => $urn,
+ 'part_size' => $this->uploadPartSize,
+ 'state' => $state,
+ 'params' => [
+ 'ContentType' => $mimetype,
+ 'Metadata' => $this->buildS3Metadata($metaData),
+ 'StorageClass' => $this->storageClass,
+ ] + $this->getSSECParameters(),
+ 'before_upload' => function (Command $command) use (&$totalWritten) {
+ $totalWritten += $command['ContentLength'];
+ },
+ 'before_complete' => function ($_command) use (&$totalWritten, $size, &$uploader, &$attempts) {
+ if ($size !== null && $totalWritten != $size) {
+ $e = new \Exception('Incomplete multi part upload, expected ' . $size . ' bytes, wrote ' . $totalWritten);
+ throw new MultipartUploadException($uploader->getState(), $e);
+ }
+ },
+ ]);
+
+ try {
+ $uploader->upload();
+ $uploaded = true;
+ } catch (S3MultipartUploadException $e) {
+ $exception = $e;
+ $attempts++;
+
+ if ($concurrency > 1) {
+ $concurrency = round($concurrency / 2);
+ }
+
+ if ($stream->isSeekable()) {
+ $stream->rewind();
+ }
+ } catch (MultipartUploadException $e) {
+ $exception = $e;
+ break;
+ }
+ }
- try {
- $uploader->upload();
- } catch (S3MultipartUploadException $e) {
+ if (!$uploaded) {
// if anything goes wrong with multipart, make sure that you don´t poison and
// slow down s3 bucket with orphaned fragments
- $uploadInfo = $e->getState()->getId();
- if ($e->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) {
+ $uploadInfo = $exception->getState()->getId();
+ if ($exception->getState()->isInitiated() && (array_key_exists('UploadId', $uploadInfo))) {
$this->getConnection()->abortMultipartUpload($uploadInfo);
}
- throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway("Error while uploading to S3 bucket", 0, $e);
+
+ throw new \OCA\DAV\Connector\Sabre\Exception\BadGateway('Error while uploading to S3 bucket', 0, $exception);
}
}
-
- /**
- * @param string $urn the unified resource name used to identify the object
- * @param resource $stream stream with the data to write
- * @param string|null $mimetype the mimetype to set for the remove object @since 22.0.0
- * @throws \Exception when something goes wrong, message will be logged
- * @since 7.0.0
- */
public function writeObject($urn, $stream, ?string $mimetype = null) {
- $psrStream = Utils::streamFor($stream);
-
- // ($psrStream->isSeekable() && $psrStream->getSize() !== null) evaluates to true for a On-Seekable stream
- // so the optimisation does not apply
- $buffer = new Psr7\Stream(fopen("php://memory", 'rwb+'));
- Utils::copyToStream($psrStream, $buffer, $this->putSizeLimit);
- $buffer->seek(0);
- if ($buffer->getSize() < $this->putSizeLimit) {
- // buffer is fully seekable, so use it directly for the small upload
- $this->writeSingle($urn, $buffer, $mimetype);
+ $metaData = [];
+ if ($mimetype) {
+ $metaData['mimetype'] = $mimetype;
+ }
+ $this->writeObjectWithMetaData($urn, $stream, $metaData);
+ }
+
+ public function writeObjectWithMetaData(string $urn, $stream, array $metaData): void {
+ $canSeek = fseek($stream, 0, SEEK_CUR) === 0;
+ $psrStream = Utils::streamFor($stream, [
+ 'size' => $metaData['size'] ?? null,
+ ]);
+
+
+ $size = $psrStream->getSize();
+ if ($size === null || !$canSeek) {
+ // The s3 single-part upload requires the size to be known for the stream.
+ // So for input streams that don't have a known size, we need to copy (part of)
+ // the input into a temporary stream so the size can be determined
+ $buffer = new Psr7\Stream(fopen('php://temp', 'rw+'));
+ Utils::copyToStream($psrStream, $buffer, $this->putSizeLimit);
+ $buffer->seek(0);
+ if ($buffer->getSize() < $this->putSizeLimit) {
+ // buffer is fully seekable, so use it directly for the small upload
+ $this->writeSingle($urn, $buffer, $metaData);
+ } else {
+ $loadStream = new Psr7\AppendStream([$buffer, $psrStream]);
+ $this->writeMultiPart($urn, $loadStream, $metaData);
+ }
} else {
- $loadStream = new Psr7\AppendStream([$buffer, $psrStream]);
- $this->writeMultiPart($urn, $loadStream, $mimetype);
+ if ($size < $this->putSizeLimit) {
+ $this->writeSingle($urn, $psrStream, $metaData);
+ } else {
+ $this->writeMultiPart($urn, $psrStream, $metaData);
+ }
}
+ $psrStream->close();
}
/**
@@ -183,14 +262,14 @@ trait S3ObjectTrait {
if ($this->useMultipartCopy && $size > $this->copySizeLimit) {
$copy = new MultipartCopy($this->getConnection(), [
- "source_bucket" => $this->getBucket(),
- "source_key" => $from
+ 'source_bucket' => $this->getBucket(),
+ 'source_key' => $from
], array_merge([
- "bucket" => $this->getBucket(),
- "key" => $to,
- "acl" => "private",
- "params" => $this->getSSECParameters() + $this->getSSECParameters(true),
- "source_metadata" => $sourceMetadata
+ 'bucket' => $this->getBucket(),
+ 'key' => $to,
+ 'acl' => 'private',
+ 'params' => $this->getSSECParameters() + $this->getSSECParameters(true),
+ 'source_metadata' => $sourceMetadata
], $options));
$copy->copy();
} else {
diff --git a/lib/private/Files/ObjectStore/S3Signature.php b/lib/private/Files/ObjectStore/S3Signature.php
index fd24a34b090..b80382ff67d 100644
--- a/lib/private/Files/ObjectStore/S3Signature.php
+++ b/lib/private/Files/ObjectStore/S3Signature.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -41,7 +42,7 @@ class S3Signature implements SignatureInterface {
public function signRequest(
RequestInterface $request,
- CredentialsInterface $credentials
+ CredentialsInterface $credentials,
) {
$request = $this->prepareRequest($request, $credentials);
$stringToSign = $this->createCanonicalizedString($request);
@@ -56,7 +57,7 @@ class S3Signature implements SignatureInterface {
RequestInterface $request,
CredentialsInterface $credentials,
$expires,
- array $options = []
+ array $options = [],
) {
$query = [];
// URL encoding already occurs in the URI template expansion. Undo that
@@ -93,20 +94,20 @@ class S3Signature implements SignatureInterface {
}
}
- $queryString = http_build_query($query, null, '&', PHP_QUERY_RFC3986);
+ $queryString = http_build_query($query, '', '&', PHP_QUERY_RFC3986);
return $request->withUri($request->getUri()->withQuery($queryString));
}
/**
- * @param RequestInterface $request
+ * @param RequestInterface $request
* @param CredentialsInterface $creds
*
* @return RequestInterface
*/
private function prepareRequest(
RequestInterface $request,
- CredentialsInterface $creds
+ CredentialsInterface $creds,
) {
$modify = [
'remove_headers' => ['X-Amz-Date'],
@@ -129,7 +130,7 @@ class S3Signature implements SignatureInterface {
private function createCanonicalizedString(
RequestInterface $request,
- $expires = null
+ $expires = null,
) {
$buffer = $request->getMethod() . "\n";
diff --git a/lib/private/Files/ObjectStore/StorageObjectStore.php b/lib/private/Files/ObjectStore/StorageObjectStore.php
index 5e7125e18a6..888602a62e4 100644
--- a/lib/private/Files/ObjectStore/StorageObjectStore.php
+++ b/lib/private/Files/ObjectStore/StorageObjectStore.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -27,8 +28,8 @@ class StorageObjectStore implements IObjectStore {
* @return string the container or bucket name where objects are stored
* @since 7.0.0
*/
- public function getStorageId() {
- $this->storage->getId();
+ public function getStorageId(): string {
+ return $this->storage->getId();
}
/**
diff --git a/lib/private/Files/ObjectStore/SwiftFactory.php b/lib/private/Files/ObjectStore/SwiftFactory.php
index 0db5c9762d2..118724159e5 100644
--- a/lib/private/Files/ObjectStore/SwiftFactory.php
+++ b/lib/private/Files/ObjectStore/SwiftFactory.php
@@ -170,7 +170,7 @@ class SwiftFactory {
try {
/** @var \OpenStack\Identity\v2\Models\Token $token */
$token = $authService->model(\OpenStack\Identity\v2\Models\Token::class, $cachedToken['token']);
- $now = new \DateTimeImmutable("now");
+ $now = new \DateTimeImmutable('now');
if ($token->expires > $now) {
$hasValidCachedToken = true;
$this->params['v2cachedToken'] = $token;
@@ -194,13 +194,13 @@ class SwiftFactory {
} catch (ClientException $e) {
$statusCode = $e->getResponse()->getStatusCode();
if ($statusCode === 404) {
- throw new StorageAuthException('Keystone not found, verify the keystone url', $e);
+ throw new StorageAuthException('Keystone not found while connecting to object storage, verify the keystone url', $e);
} elseif ($statusCode === 412) {
- throw new StorageAuthException('Precondition failed, verify the keystone url', $e);
+ throw new StorageAuthException('Precondition failed while connecting to object storage, verify the keystone url', $e);
} elseif ($statusCode === 401) {
- throw new StorageAuthException('Authentication failed, verify the username, password and possibly tenant', $e);
+ throw new StorageAuthException('Authentication failed while connecting to object storage, verify the username, password and possibly tenant', $e);
} else {
- throw new StorageAuthException('Unknown error', $e);
+ throw new StorageAuthException('Unknown error while connecting to object storage', $e);
}
} catch (RequestException $e) {
throw new StorageAuthException('Connection reset while connecting to keystone, verify the keystone url', $e);
diff --git a/lib/private/Files/Search/QueryOptimizer/FlattenNestedBool.php b/lib/private/Files/Search/QueryOptimizer/FlattenNestedBool.php
index 2bde6f0bbdb..bb7bef2ed63 100644
--- a/lib/private/Files/Search/QueryOptimizer/FlattenNestedBool.php
+++ b/lib/private/Files/Search/QueryOptimizer/FlattenNestedBool.php
@@ -14,8 +14,8 @@ class FlattenNestedBool extends QueryOptimizerStep {
public function processOperator(ISearchOperator &$operator) {
if (
$operator instanceof SearchBinaryOperator && (
- $operator->getType() === ISearchBinaryOperator::OPERATOR_OR ||
- $operator->getType() === ISearchBinaryOperator::OPERATOR_AND
+ $operator->getType() === ISearchBinaryOperator::OPERATOR_OR
+ || $operator->getType() === ISearchBinaryOperator::OPERATOR_AND
)
) {
$newArguments = [];
diff --git a/lib/private/Files/Search/QueryOptimizer/FlattenSingleArgumentBinaryOperation.php b/lib/private/Files/Search/QueryOptimizer/FlattenSingleArgumentBinaryOperation.php
index cb04351534a..7e99c04f197 100644
--- a/lib/private/Files/Search/QueryOptimizer/FlattenSingleArgumentBinaryOperation.php
+++ b/lib/private/Files/Search/QueryOptimizer/FlattenSingleArgumentBinaryOperation.php
@@ -16,11 +16,11 @@ class FlattenSingleArgumentBinaryOperation extends ReplacingOptimizerStep {
public function processOperator(ISearchOperator &$operator): bool {
parent::processOperator($operator);
if (
- $operator instanceof ISearchBinaryOperator &&
- count($operator->getArguments()) === 1 &&
- (
- $operator->getType() === ISearchBinaryOperator::OPERATOR_OR ||
- $operator->getType() === ISearchBinaryOperator::OPERATOR_AND
+ $operator instanceof ISearchBinaryOperator
+ && count($operator->getArguments()) === 1
+ && (
+ $operator->getType() === ISearchBinaryOperator::OPERATOR_OR
+ || $operator->getType() === ISearchBinaryOperator::OPERATOR_AND
)
) {
$operator = $operator->getArguments()[0];
diff --git a/lib/private/Files/Search/QueryOptimizer/OrEqualsToIn.php b/lib/private/Files/Search/QueryOptimizer/OrEqualsToIn.php
index 3f2b36823ab..6df35c9c9a2 100644
--- a/lib/private/Files/Search/QueryOptimizer/OrEqualsToIn.php
+++ b/lib/private/Files/Search/QueryOptimizer/OrEqualsToIn.php
@@ -18,8 +18,8 @@ use OCP\Files\Search\ISearchOperator;
class OrEqualsToIn extends ReplacingOptimizerStep {
public function processOperator(ISearchOperator &$operator): bool {
if (
- $operator instanceof ISearchBinaryOperator &&
- $operator->getType() === ISearchBinaryOperator::OPERATOR_OR
+ $operator instanceof ISearchBinaryOperator
+ && $operator->getType() === ISearchBinaryOperator::OPERATOR_OR
) {
$groups = $this->groupEqualsComparisonsByField($operator->getArguments());
$newParts = array_map(function (array $group) {
@@ -32,7 +32,7 @@ class OrEqualsToIn extends ReplacingOptimizerStep {
$value = $comparison->getValue();
return $value;
}, $group);
- $in = new SearchComparison(ISearchComparison::COMPARE_IN, $field, $values);
+ $in = new SearchComparison(ISearchComparison::COMPARE_IN, $field, $values, $group[0]->getExtra());
$pathEqHash = array_reduce($group, function ($pathEqHash, ISearchComparison $comparison) {
return $comparison->getQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, true) && $pathEqHash;
}, true);
diff --git a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
index 9d50746d5d1..2994a9365a7 100644
--- a/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
+++ b/lib/private/Files/Search/QueryOptimizer/PathPrefixOptimizer.php
@@ -52,9 +52,9 @@ class PathPrefixOptimizer extends QueryOptimizerStep {
private function operatorPairIsPathPrefix(ISearchOperator $like, ISearchOperator $equal): bool {
return (
- $like instanceof ISearchComparison && $equal instanceof ISearchComparison &&
- !$like->getExtra() && !$equal->getExtra() && $like->getField() === 'path' && $equal->getField() === 'path' &&
- $like->getType() === ISearchComparison::COMPARE_LIKE_CASE_SENSITIVE && $equal->getType() === ISearchComparison::COMPARE_EQUAL
+ $like instanceof ISearchComparison && $equal instanceof ISearchComparison
+ && !$like->getExtra() && !$equal->getExtra() && $like->getField() === 'path' && $equal->getField() === 'path'
+ && $like->getType() === ISearchComparison::COMPARE_LIKE_CASE_SENSITIVE && $equal->getType() === ISearchComparison::COMPARE_EQUAL
&& $like->getValue() === SearchComparison::escapeLikeParameter($equal->getValue()) . '/%'
);
}
diff --git a/lib/private/Files/Search/QueryOptimizer/SplitLargeIn.php b/lib/private/Files/Search/QueryOptimizer/SplitLargeIn.php
index 5c7655b49c1..8aee1975708 100644
--- a/lib/private/Files/Search/QueryOptimizer/SplitLargeIn.php
+++ b/lib/private/Files/Search/QueryOptimizer/SplitLargeIn.php
@@ -18,13 +18,13 @@ use OCP\Files\Search\ISearchOperator;
class SplitLargeIn extends ReplacingOptimizerStep {
public function processOperator(ISearchOperator &$operator): bool {
if (
- $operator instanceof ISearchComparison &&
- $operator->getType() === ISearchComparison::COMPARE_IN &&
- count($operator->getValue()) > 1000
+ $operator instanceof ISearchComparison
+ && $operator->getType() === ISearchComparison::COMPARE_IN
+ && count($operator->getValue()) > 1000
) {
$chunks = array_chunk($operator->getValue(), 1000);
$chunkComparisons = array_map(function (array $values) use ($operator) {
- return new SearchComparison(ISearchComparison::COMPARE_IN, $operator->getField(), $values);
+ return new SearchComparison(ISearchComparison::COMPARE_IN, $operator->getField(), $values, $operator->getExtra());
}, $chunks);
$operator = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $chunkComparisons);
diff --git a/lib/private/Files/Search/SearchBinaryOperator.php b/lib/private/Files/Search/SearchBinaryOperator.php
index 59a1ba2dfaf..49f599933f4 100644
--- a/lib/private/Files/Search/SearchBinaryOperator.php
+++ b/lib/private/Files/Search/SearchBinaryOperator.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib/private/Files/Search/SearchComparison.php b/lib/private/Files/Search/SearchComparison.php
index e3a8f800506..c1f0176afd9 100644
--- a/lib/private/Files/Search/SearchComparison.php
+++ b/lib/private/Files/Search/SearchComparison.php
@@ -20,7 +20,7 @@ class SearchComparison implements ISearchComparison {
private string $field,
/** @var ParamValue $value */
private \DateTime|int|string|bool|array $value,
- private string $extra = ''
+ private string $extra = '',
) {
}
diff --git a/lib/private/Files/Search/SearchOrder.php b/lib/private/Files/Search/SearchOrder.php
index f3f62b933a1..5a036653f4e 100644
--- a/lib/private/Files/Search/SearchOrder.php
+++ b/lib/private/Files/Search/SearchOrder.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -12,7 +13,7 @@ class SearchOrder implements ISearchOrder {
public function __construct(
private string $direction,
private string $field,
- private string $extra = ''
+ private string $extra = '',
) {
}
diff --git a/lib/private/Files/Search/SearchQuery.php b/lib/private/Files/Search/SearchQuery.php
index e7cb031da3e..592749cf4a0 100644
--- a/lib/private/Files/Search/SearchQuery.php
+++ b/lib/private/Files/Search/SearchQuery.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -11,13 +12,13 @@ use OCP\Files\Search\ISearchQuery;
use OCP\IUser;
class SearchQuery implements ISearchQuery {
- /** @var ISearchOperator */
+ /** @var ISearchOperator */
private $searchOperation;
- /** @var integer */
+ /** @var integer */
private $limit;
- /** @var integer */
+ /** @var integer */
private $offset;
- /** @var ISearchOrder[] */
+ /** @var ISearchOrder[] */
private $order;
/** @var ?IUser */
private $user;
@@ -39,7 +40,7 @@ class SearchQuery implements ISearchQuery {
int $offset,
array $order,
?IUser $user = null,
- bool $limitToHome = false
+ bool $limitToHome = false,
) {
$this->searchOperation = $searchOperation;
$this->limit = $limit;
diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php
index 53befe57e36..b92c608a81d 100644
--- a/lib/private/Files/SetupManager.php
+++ b/lib/private/Files/SetupManager.php
@@ -21,20 +21,21 @@ use OC\Files\Storage\Wrapper\Quota;
use OC\Lockdown\Filesystem\NullStorage;
use OC\Share\Share;
use OC\Share20\ShareDisableChecker;
-use OC_App;
use OC_Hook;
-use OC_Util;
-use OCA\Files_External\Config\ConfigAdapter;
+use OCA\Files_External\Config\ExternalMountPoint;
use OCA\Files_Sharing\External\Mount;
use OCA\Files_Sharing\ISharedMountPoint;
use OCA\Files_Sharing\SharedMount;
+use OCP\App\IAppManager;
use OCP\Constants;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IHomeMountProvider;
use OCP\Files\Config\IMountProvider;
+use OCP\Files\Config\IRootMountProvider;
use OCP\Files\Config\IUserMountCache;
+use OCP\Files\Events\BeforeFileSystemSetupEvent;
use OCP\Files\Events\InvalidateMountCacheEvent;
use OCP\Files\Events\Node\FilesystemTornDownEvent;
use OCP\Files\Mount\IMountManager;
@@ -80,6 +81,7 @@ class SetupManager {
private LoggerInterface $logger,
private IConfig $config,
private ShareDisableChecker $shareDisableChecker,
+ private IAppManager $appManager,
) {
$this->cache = $cacheFactory->createDistributed('setupmanager::');
$this->listeningForProviders = false;
@@ -103,12 +105,13 @@ class SetupManager {
$this->setupBuiltinWrappersDone = true;
// load all filesystem apps before, so no setup-hook gets lost
- OC_App::loadApps(['filesystem']);
+ $this->appManager->loadApps(['filesystem']);
$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
- if ($mount->getOptions() && $storage->instanceOfStorage(Common::class)) {
- $storage->setMountOptions($mount->getOptions());
+ if ($storage->instanceOfStorage(Common::class)) {
+ $options = array_merge($mount->getOptions(), ['mount_point' => $mountPoint]);
+ $storage->setMountOptions($options);
}
return $storage;
});
@@ -133,7 +136,7 @@ class SetupManager {
// install storage availability wrapper, before most other wrappers
Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
- $externalMount = $mount instanceof ConfigAdapter || $mount instanceof Mount;
+ $externalMount = $mount instanceof ExternalMountPoint || $mount instanceof Mount;
if ($externalMount && !$storage->isLocal()) {
return new Availability(['storage' => $storage]);
}
@@ -154,7 +157,7 @@ class SetupManager {
if ($mount instanceof HomeMountPoint) {
$user = $mount->getUser();
return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
- return OC_Util::getUserQuota($user);
+ return $user->getQuotaBytes();
}, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
}
@@ -169,9 +172,9 @@ class SetupManager {
return new PermissionsMask([
'storage' => $storage,
'mask' => Constants::PERMISSION_ALL & ~(
- Constants::PERMISSION_UPDATE |
- Constants::PERMISSION_CREATE |
- Constants::PERMISSION_DELETE
+ Constants::PERMISSION_UPDATE
+ | Constants::PERMISSION_CREATE
+ | Constants::PERMISSION_DELETE
),
]);
}
@@ -200,7 +203,7 @@ class SetupManager {
$this->setupForUserWith($user, function () use ($user) {
$this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
- IMountProvider $provider
+ IMountProvider $provider,
) use ($user) {
return !in_array(get_class($provider), $this->setupUserMountProviders[$user->getUID()]);
});
@@ -226,8 +229,12 @@ class SetupManager {
$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
+ // TODO remove hook
OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]);
+ $event = new BeforeFileSystemSetupEvent($user);
+ $this->eventDispatcher->dispatchTyped($event);
+
Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
$userDir = '/' . $user->getUID() . '/files';
@@ -274,14 +281,18 @@ class SetupManager {
$mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
return str_starts_with($mount->getMountPoint(), $userRoot);
});
- $allProviders = array_map(function (IMountProvider $provider) {
+ $allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) {
return get_class($provider);
- }, $this->mountProviderCollection->getProviders());
+ }, array_merge(
+ $this->mountProviderCollection->getProviders(),
+ $this->mountProviderCollection->getHomeProviders(),
+ $this->mountProviderCollection->getRootProviders(),
+ ));
$newProviders = array_diff($allProviders, $previouslySetupProviders);
$mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
return !in_array($mount->getMountProvider(), $previouslySetupProviders);
});
- $this->userMountCache->registerMounts($user, $mounts, $newProviders);
+ $this->registerMounts($user, $mounts, $newProviders);
$cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60);
if ($cacheDuration > 0) {
@@ -381,7 +392,7 @@ class SetupManager {
}
// for the user's home folder, and includes children we need everything always
- if (rtrim($path) === "/" . $user->getUID() . "/files" && $includeChildren) {
+ if (rtrim($path) === '/' . $user->getUID() . '/files' && $includeChildren) {
$this->setupForUser($user);
return;
}
@@ -411,7 +422,7 @@ class SetupManager {
$setupProviders[] = $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->logger->debug('mount at ' . $cachedMount->getMountPoint() . ' has no provider set, performing full setup');
$this->eventLogger->end('fs:setup:user:path:find');
$this->setupForUser($user);
$this->eventLogger->end('fs:setup:user:path');
@@ -428,7 +439,7 @@ class SetupManager {
}, false);
if ($needsFullSetup) {
- $this->logger->debug("mount has no provider set, performing full setup");
+ $this->logger->debug('mount has no provider set, performing full setup');
$this->setupForUser($user);
$this->eventLogger->end('fs:setup:user:path');
return;
@@ -446,7 +457,7 @@ class SetupManager {
}
if (count($mounts)) {
- $this->userMountCache->registerMounts($user, $mounts, $currentProviders);
+ $this->registerMounts($user, $mounts, $currentProviders);
$this->setupForUserWith($user, function () use ($mounts) {
array_walk($mounts, [$this->mountManager, 'addMount']);
});
@@ -490,7 +501,7 @@ class SetupManager {
return;
}
- $this->eventLogger->start('fs:setup:user:providers', "Setup filesystem for " . implode(', ', $providers));
+ $this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
$this->oneTimeUserSetup($user);
@@ -517,7 +528,7 @@ class SetupManager {
$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
}
- $this->userMountCache->registerMounts($user, $mounts, $providers);
+ $this->registerMounts($user, $mounts, $providers);
$this->setupForUserWith($user, function () use ($mounts) {
array_walk($mounts, [$this->mountManager, 'addMount']);
});
@@ -541,7 +552,7 @@ class SetupManager {
if (!$this->listeningForProviders) {
$this->listeningForProviders = true;
$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
- IMountProvider $provider
+ IMountProvider $provider,
) {
foreach ($this->setupUsers as $userId) {
$user = $this->userManager->get($userId);
@@ -567,7 +578,7 @@ class SetupManager {
$this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
$this->cache->remove($event->getShare()->getSharedWith());
});
- $this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event
+ $this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
) {
if ($user = $event->getUser()) {
$this->cache->remove($user->getUID());
@@ -589,4 +600,10 @@ class SetupManager {
});
}
}
+
+ private function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null): void {
+ if ($this->lockdownManager->canAccessFilesystem()) {
+ $this->userMountCache->registerMounts($user, $mounts, $mountProviderClasses);
+ }
+ }
}
diff --git a/lib/private/Files/SetupManagerFactory.php b/lib/private/Files/SetupManagerFactory.php
index 6ae38c6fdbc..d2fe978fa9e 100644
--- a/lib/private/Files/SetupManagerFactory.php
+++ b/lib/private/Files/SetupManagerFactory.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace OC\Files;
use OC\Share20\ShareDisableChecker;
+use OCP\App\IAppManager;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\IMountProviderCollection;
@@ -36,6 +37,7 @@ class SetupManagerFactory {
private LoggerInterface $logger,
private IConfig $config,
private ShareDisableChecker $shareDisableChecker,
+ private IAppManager $appManager,
) {
$this->setupManager = null;
}
@@ -55,6 +57,7 @@ class SetupManagerFactory {
$this->logger,
$this->config,
$this->shareDisableChecker,
+ $this->appManager,
);
}
return $this->setupManager;
diff --git a/lib/private/Files/SimpleFS/SimpleFile.php b/lib/private/Files/SimpleFS/SimpleFile.php
index cbb3af4db29..d9c1b47d2f1 100644
--- a/lib/private/Files/SimpleFS/SimpleFile.php
+++ b/lib/private/Files/SimpleFS/SimpleFile.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -6,9 +7,11 @@
namespace OC\Files\SimpleFS;
use OCP\Files\File;
+use OCP\Files\GenericFileException;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\Lock\LockedException;
class SimpleFile implements ISimpleFile {
private File $file;
@@ -48,8 +51,10 @@ class SimpleFile implements ISimpleFile {
/**
* Get the content
*
- * @throws NotPermittedException
+ * @throws GenericFileException
+ * @throws LockedException
* @throws NotFoundException
+ * @throws NotPermittedException
*/
public function getContent(): string {
$result = $this->file->getContent();
@@ -65,8 +70,10 @@ class SimpleFile implements ISimpleFile {
* Overwrite the file
*
* @param string|resource $data
- * @throws NotPermittedException
+ * @throws GenericFileException
+ * @throws LockedException
* @throws NotFoundException
+ * @throws NotPermittedException
*/
public function putContent($data): void {
try {
diff --git a/lib/private/Files/SimpleFS/SimpleFolder.php b/lib/private/Files/SimpleFS/SimpleFolder.php
index 0da552e402b..62f3db25e9b 100644
--- a/lib/private/Files/SimpleFS/SimpleFolder.php
+++ b/lib/private/Files/SimpleFS/SimpleFolder.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php
index e613e435f03..2dc359169d7 100644
--- a/lib/private/Files/Storage/Common.php
+++ b/lib/private/Files/Storage/Common.php
@@ -13,21 +13,28 @@ use OC\Files\Cache\Propagator;
use OC\Files\Cache\Scanner;
use OC\Files\Cache\Updater;
use OC\Files\Cache\Watcher;
+use OC\Files\FilenameValidator;
use OC\Files\Filesystem;
+use OC\Files\ObjectStore\ObjectStoreStorage;
+use OC\Files\Storage\Wrapper\Encryption;
use OC\Files\Storage\Wrapper\Jail;
use OC\Files\Storage\Wrapper\Wrapper;
-use OCP\Files\EmptyFileNameException;
-use OCP\Files\FileNameTooLongException;
+use OCP\Files;
+use OCP\Files\Cache\ICache;
+use OCP\Files\Cache\IPropagator;
+use OCP\Files\Cache\IScanner;
+use OCP\Files\Cache\IUpdater;
+use OCP\Files\Cache\IWatcher;
use OCP\Files\ForbiddenException;
use OCP\Files\GenericFileException;
-use OCP\Files\InvalidCharacterInPathException;
-use OCP\Files\InvalidDirectoryException;
+use OCP\Files\IFilenameValidator;
use OCP\Files\InvalidPathException;
-use OCP\Files\ReservedWordException;
+use OCP\Files\Storage\IConstructableStorage;
use OCP\Files\Storage\ILockingStorage;
use OCP\Files\Storage\IStorage;
use OCP\Files\Storage\IWriteStreamStorage;
use OCP\Files\StorageNotAvailableException;
+use OCP\IConfig;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use OCP\Server;
@@ -44,85 +51,75 @@ use Psr\Log\LoggerInterface;
* Some \OC\Files\Storage\Common methods call functions which are first defined
* in classes which extend it, e.g. $this->stat() .
*/
-abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
+abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage, IConstructableStorage {
use LocalTempFileTrait;
- protected $cache;
- protected $scanner;
- protected $watcher;
- protected $propagator;
+ protected ?Cache $cache = null;
+ protected ?Scanner $scanner = null;
+ protected ?Watcher $watcher = null;
+ protected ?Propagator $propagator = null;
protected $storageCache;
- protected $updater;
+ protected ?Updater $updater = null;
- protected $mountOptions = [];
+ protected array $mountOptions = [];
protected $owner = null;
- /** @var ?bool */
- private $shouldLogLocks = null;
- /** @var ?LoggerInterface */
- private $logger;
+ private ?bool $shouldLogLocks = null;
+ private ?LoggerInterface $logger = null;
+ private ?IFilenameValidator $filenameValidator = null;
- public function __construct($parameters) {
+ public function __construct(array $parameters) {
}
- /**
- * Remove a file or folder
- *
- * @param string $path
- * @return bool
- */
- protected function remove($path) {
- if ($this->is_dir($path)) {
- return $this->rmdir($path);
- } elseif ($this->is_file($path)) {
- return $this->unlink($path);
- } else {
- return false;
+ protected function remove(string $path): bool {
+ if ($this->file_exists($path)) {
+ if ($this->is_dir($path)) {
+ return $this->rmdir($path);
+ } elseif ($this->is_file($path)) {
+ return $this->unlink($path);
+ }
}
+ return false;
}
- public function is_dir($path) {
+ public function is_dir(string $path): bool {
return $this->filetype($path) === 'dir';
}
- public function is_file($path) {
+ public function is_file(string $path): bool {
return $this->filetype($path) === 'file';
}
- public function filesize($path): false|int|float {
+ public function filesize(string $path): int|float|false {
if ($this->is_dir($path)) {
return 0; //by definition
} else {
$stat = $this->stat($path);
- if (isset($stat['size'])) {
- return $stat['size'];
- } else {
- return 0;
- }
+ return isset($stat['size']) ? $stat['size'] : 0;
}
}
- public function isReadable($path) {
+ public function isReadable(string $path): bool {
// at least check whether it exists
// subclasses might want to implement this more thoroughly
return $this->file_exists($path);
}
- public function isUpdatable($path) {
+ public function isUpdatable(string $path): bool {
// at least check whether it exists
// subclasses might want to implement this more thoroughly
// a non-existing file/folder isn't updatable
return $this->file_exists($path);
}
- public function isCreatable($path) {
+ public function isCreatable(string $path): bool {
if ($this->is_dir($path) && $this->isUpdatable($path)) {
return true;
}
return false;
}
- public function isDeletable($path) {
+ public function isDeletable(string $path): bool {
if ($path === '' || $path === '/') {
return $this->isUpdatable($path);
}
@@ -130,11 +127,11 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
return $this->isUpdatable($parent) && $this->isUpdatable($path);
}
- public function isSharable($path) {
+ public function isSharable(string $path): bool {
return $this->isReadable($path);
}
- public function getPermissions($path) {
+ public function getPermissions(string $path): int {
$permissions = 0;
if ($this->isCreatable($path)) {
$permissions |= \OCP\Constants::PERMISSION_CREATE;
@@ -154,7 +151,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
return $permissions;
}
- public function filemtime($path) {
+ public function filemtime(string $path): int|false {
$stat = $this->stat($path);
if (isset($stat['mtime']) && $stat['mtime'] > 0) {
return $stat['mtime'];
@@ -163,8 +160,8 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
}
}
- public function file_get_contents($path) {
- $handle = $this->fopen($path, "r");
+ public function file_get_contents(string $path): string|false {
+ $handle = $this->fopen($path, 'r');
if (!$handle) {
return false;
}
@@ -173,8 +170,8 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
return $data;
}
- public function file_put_contents($path, $data) {
- $handle = $this->fopen($path, "w");
+ public function file_put_contents(string $path, mixed $data): int|float|false {
+ $handle = $this->fopen($path, 'w');
if (!$handle) {
return false;
}
@@ -184,19 +181,19 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
return $count;
}
- public function rename($source, $target) {
+ public function rename(string $source, string $target): bool {
$this->remove($target);
$this->removeCachedFile($source);
return $this->copy($source, $target) and $this->remove($source);
}
- public function copy($source, $target) {
+ public function copy(string $source, string $target): bool {
if ($this->is_dir($source)) {
$this->remove($target);
$dir = $this->opendir($source);
$this->mkdir($target);
- while ($file = readdir($dir)) {
+ while (($file = readdir($dir)) !== false) {
if (!Filesystem::isIgnoredDir($file)) {
if (!$this->copy($source . '/' . $file, $target . '/' . $file)) {
closedir($dir);
@@ -209,16 +206,16 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
} else {
$sourceStream = $this->fopen($source, 'r');
$targetStream = $this->fopen($target, 'w');
- [, $result] = \OC_Helper::streamCopy($sourceStream, $targetStream);
+ [, $result] = Files::streamCopy($sourceStream, $targetStream, true);
if (!$result) {
- \OCP\Server::get(LoggerInterface::class)->warning("Failed to write data while copying $source to $target");
+ Server::get(LoggerInterface::class)->warning("Failed to write data while copying $source to $target");
}
$this->removeCachedFile($target);
return $result;
}
}
- public function getMimeType($path) {
+ public function getMimeType(string $path): string|false {
if ($this->is_dir($path)) {
return 'httpd/unix-directory';
} elseif ($this->file_exists($path)) {
@@ -228,31 +225,26 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
}
}
- public function hash($type, $path, $raw = false) {
+ public function hash(string $type, string $path, bool $raw = false): string|false {
$fh = $this->fopen($path, 'rb');
+ if (!$fh) {
+ return false;
+ }
$ctx = hash_init($type);
hash_update_stream($ctx, $fh);
fclose($fh);
return hash_final($ctx, $raw);
}
- public function search($query) {
- return $this->searchInDir($query);
- }
-
- public function getLocalFile($path) {
+ public function getLocalFile(string $path): string|false {
return $this->getCachedFile($path);
}
- /**
- * @param string $path
- * @param string $target
- */
- private function addLocalFolder($path, $target) {
+ private function addLocalFolder(string $path, string $target): void {
$dh = $this->opendir($path);
if (is_resource($dh)) {
while (($file = readdir($dh)) !== false) {
- if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
+ if (!Filesystem::isIgnoredDir($file)) {
if ($this->is_dir($path . '/' . $file)) {
mkdir($target . '/' . $file);
$this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
@@ -265,17 +257,12 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
}
}
- /**
- * @param string $query
- * @param string $dir
- * @return array
- */
- protected function searchInDir($query, $dir = '') {
+ protected function searchInDir(string $query, string $dir = ''): array {
$files = [];
$dh = $this->opendir($dir);
if (is_resource($dh)) {
while (($item = readdir($dh)) !== false) {
- if (\OC\Files\Filesystem::isIgnoredDir($item)) {
+ if (Filesystem::isIgnoredDir($item)) {
continue;
}
if (strstr(strtolower($item), strtolower($query)) !== false) {
@@ -291,18 +278,15 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
}
/**
+ * @inheritDoc
* Check if a file or folder has been updated since $time
*
* The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
* the mtime should always return false here. As a result storage implementations that always return false expect
* exclusive access to the backend and will not pick up files that have been added in a way that circumvents
* Nextcloud filesystem.
- *
- * @param string $path
- * @param int $time
- * @return bool
*/
- public function hasUpdated($path, $time) {
+ public function hasUpdated(string $path, int $time): bool {
return $this->filemtime($path) > $time;
}
@@ -314,76 +298,78 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
return $dependencies;
}
- public function getCache($path = '', $storage = null) {
+ public function getCache(string $path = '', ?IStorage $storage = null): ICache {
if (!$storage) {
$storage = $this;
}
+ /** @var self $storage */
if (!isset($storage->cache)) {
$storage->cache = new Cache($storage, $this->getCacheDependencies());
}
return $storage->cache;
}
- public function getScanner($path = '', $storage = null) {
+ public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
if (!$storage) {
$storage = $this;
}
+ if (!$storage->instanceOfStorage(self::class)) {
+ throw new \InvalidArgumentException('Storage is not of the correct class');
+ }
if (!isset($storage->scanner)) {
$storage->scanner = new Scanner($storage);
}
return $storage->scanner;
}
- public function getWatcher($path = '', $storage = null) {
+ public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher {
if (!$storage) {
$storage = $this;
}
if (!isset($this->watcher)) {
$this->watcher = new Watcher($storage);
- $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
+ $globalPolicy = Server::get(IConfig::class)->getSystemValueInt('filesystem_check_changes', Watcher::CHECK_NEVER);
$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
}
return $this->watcher;
}
- /**
- * get a propagator instance for the cache
- *
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
- * @return \OC\Files\Cache\Propagator
- */
- public function getPropagator($storage = null) {
+ public function getPropagator(?IStorage $storage = null): IPropagator {
if (!$storage) {
$storage = $this;
}
+ if (!$storage->instanceOfStorage(self::class)) {
+ throw new \InvalidArgumentException('Storage is not of the correct class');
+ }
+ /** @var self $storage */
if (!isset($storage->propagator)) {
- $config = \OC::$server->getSystemConfig();
- $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getValue('instanceid')]);
+ $config = Server::get(IConfig::class);
+ $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection(), ['appdata_' . $config->getSystemValueString('instanceid')]);
}
return $storage->propagator;
}
- public function getUpdater($storage = null) {
+ public function getUpdater(?IStorage $storage = null): IUpdater {
if (!$storage) {
$storage = $this;
}
+ if (!$storage->instanceOfStorage(self::class)) {
+ throw new \InvalidArgumentException('Storage is not of the correct class');
+ }
+ /** @var self $storage */
if (!isset($storage->updater)) {
$storage->updater = new Updater($storage);
}
return $storage->updater;
}
- public function getStorageCache($storage = null) {
- return $this->getCache($storage)->getStorageCache();
+ public function getStorageCache(?IStorage $storage = null): \OC\Files\Cache\Storage {
+ /** @var Cache $cache */
+ $cache = $this->getCache(storage: $storage);
+ return $cache->getStorageCache();
}
- /**
- * get the owner of a path
- *
- * @param string $path The path to get the owner
- * @return string|false uid or false
- */
- public function getOwner($path) {
+ public function getOwner(string $path): string|false {
if ($this->owner === null) {
$this->owner = \OC_User::getUser();
}
@@ -391,13 +377,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
return $this->owner;
}
- /**
- * get the ETag for a file or folder
- *
- * @param string $path
- * @return string
- */
- public function getETag($path) {
+ public function getETag(string $path): string|false {
return uniqid();
}
@@ -408,8 +388,8 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
* @param string $path The path to clean
* @return string cleaned path
*/
- public function cleanPath($path) {
- if (strlen($path) == 0 or $path[0] != '/') {
+ public function cleanPath(string $path): string {
+ if (strlen($path) == 0 || $path[0] != '/') {
$path = '/' . $path;
}
@@ -427,39 +407,28 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
/**
* Test a storage for availability
- *
- * @return bool
*/
- public function test() {
+ public function test(): bool {
try {
if ($this->stat('')) {
return true;
}
- \OC::$server->get(LoggerInterface::class)->info("External storage not available: stat() failed");
+ Server::get(LoggerInterface::class)->info('External storage not available: stat() failed');
return false;
} catch (\Exception $e) {
- \OC::$server->get(LoggerInterface::class)->warning(
- "External storage not available: " . $e->getMessage(),
+ Server::get(LoggerInterface::class)->warning(
+ 'External storage not available: ' . $e->getMessage(),
['exception' => $e]
);
return false;
}
}
- /**
- * get the free space in the storage
- *
- * @param string $path
- * @return int|float|false
- */
- public function free_space($path) {
+ public function free_space(string $path): int|float|false {
return \OCP\Files\FileInfo::SPACE_UNKNOWN;
}
- /**
- * {@inheritdoc}
- */
- public function isLocal() {
+ public function isLocal(): bool {
// the common implementation returns a temporary file by
// default, which is not local
return false;
@@ -467,11 +436,8 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
/**
* Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
- *
- * @param string $class
- * @return bool
*/
- public function instanceOfStorage($class) {
+ public function instanceOfStorage(string $class): bool {
if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
// FIXME Temporary fix to keep existing checks working
$class = '\OCA\Files_Sharing\SharedStorage';
@@ -483,107 +449,49 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
* A custom storage implementation can return an url for direct download of a give file.
*
* For now the returned array can hold the parameter url - in future more attributes might follow.
- *
- * @param string $path
- * @return array|false
*/
- public function getDirectDownload($path) {
+ public function getDirectDownload(string $path): array|false {
return [];
}
- /**
- * @inheritdoc
- * @throws InvalidPathException
- */
- public function verifyPath($path, $fileName) {
- // verify empty and dot files
- $trimmed = trim($fileName);
- if ($trimmed === '') {
- throw new EmptyFileNameException();
- }
-
- if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
- throw new InvalidDirectoryException();
- }
+ public function verifyPath(string $path, string $fileName): void {
+ $this->getFilenameValidator()
+ ->validateFilename($fileName);
- if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
- // verify database - e.g. mysql only 3-byte chars
- if (preg_match('%(?:
- \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
- | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
- | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
-)%xs', $fileName)) {
- throw new InvalidCharacterInPathException();
+ // verify also the path is valid
+ if ($path && $path !== '/' && $path !== '.') {
+ try {
+ $this->verifyPath(dirname($path), basename($path));
+ } catch (InvalidPathException $e) {
+ // Ignore invalid file type exceptions on directories
+ if ($e->getCode() !== FilenameValidator::INVALID_FILE_TYPE) {
+ $l = \OCP\Util::getL10N('lib');
+ throw new InvalidPathException($l->t('Invalid parent path'), previous: $e);
+ }
}
}
-
- // 255 characters is the limit on common file systems (ext/xfs)
- // oc_filecache has a 250 char length limit for the filename
- if (isset($fileName[250])) {
- throw new FileNameTooLongException();
- }
-
- // NOTE: $path will remain unverified for now
- $this->verifyPosixPath($fileName);
}
/**
- * @param string $fileName
- * @throws InvalidPathException
+ * Get the filename validator
+ * (cached for performance)
*/
- protected function verifyPosixPath($fileName) {
- $invalidChars = \OCP\Util::getForbiddenFileNameChars();
- $this->scanForInvalidCharacters($fileName, $invalidChars);
-
- $fileName = trim($fileName);
- $reservedNames = ['*'];
- if (in_array($fileName, $reservedNames)) {
- throw new ReservedWordException();
+ protected function getFilenameValidator(): IFilenameValidator {
+ if ($this->filenameValidator === null) {
+ $this->filenameValidator = Server::get(IFilenameValidator::class);
}
+ return $this->filenameValidator;
}
- /**
- * @param string $fileName
- * @param string[] $invalidChars
- * @throws InvalidPathException
- */
- private function scanForInvalidCharacters(string $fileName, array $invalidChars) {
- foreach ($invalidChars as $char) {
- if (str_contains($fileName, $char)) {
- throw new InvalidCharacterInPathException();
- }
- }
-
- $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
- if ($sanitizedFileName !== $fileName) {
- throw new InvalidCharacterInPathException();
- }
- }
-
- /**
- * @param array $options
- */
- public function setMountOptions(array $options) {
+ public function setMountOptions(array $options): void {
$this->mountOptions = $options;
}
- /**
- * @param string $name
- * @param mixed $default
- * @return mixed
- */
- public function getMountOption($name, $default = null) {
+ public function getMountOption(string $name, mixed $default = null): mixed {
return $this->mountOptions[$name] ?? $default;
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @param bool $preserveMtime
- * @return bool
- */
- public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
+ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, bool $preserveMtime = false): bool {
if ($sourceStorage === $this) {
return $this->copy($sourceInternalPath, $targetInternalPath);
}
@@ -593,7 +501,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
$result = $this->mkdir($targetInternalPath);
if (is_resource($dh)) {
$result = true;
- while ($result and ($file = readdir($dh)) !== false) {
+ while ($result && ($file = readdir($dh)) !== false) {
if (!Filesystem::isIgnoredDir($file)) {
$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
}
@@ -607,7 +515,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
$this->writeStream($targetInternalPath, $source);
$result = true;
} catch (\Exception $e) {
- \OC::$server->get(LoggerInterface::class)->warning('Failed to copy stream to storage', ['exception' => $e]);
+ Server::get(LoggerInterface::class)->warning('Failed to copy stream to storage', ['exception' => $e]);
}
}
@@ -628,9 +536,6 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
/**
* Check if a storage is the same as the current one, including wrapped storages
- *
- * @param IStorage $storage
- * @return bool
*/
private function isSameStorage(IStorage $storage): bool {
while ($storage->instanceOfStorage(Wrapper::class)) {
@@ -643,14 +548,11 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
return $storage === $this;
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @return bool
- */
- public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
- if ($this->isSameStorage($sourceStorage)) {
+ public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
+ if (
+ !$sourceStorage->instanceOfStorage(Encryption::class)
+ && $this->isSameStorage($sourceStorage)
+ ) {
// resolve any jailed paths
while ($sourceStorage->instanceOfStorage(Jail::class)) {
/**
@@ -669,19 +571,27 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
if ($result) {
- if ($sourceStorage->is_dir($sourceInternalPath)) {
- $result = $sourceStorage->rmdir($sourceInternalPath);
- } else {
- $result = $sourceStorage->unlink($sourceInternalPath);
+ if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
+ /** @var ObjectStoreStorage $sourceStorage */
+ $sourceStorage->setPreserveCacheOnDelete(true);
+ }
+ try {
+ if ($sourceStorage->is_dir($sourceInternalPath)) {
+ $result = $sourceStorage->rmdir($sourceInternalPath);
+ } else {
+ $result = $sourceStorage->unlink($sourceInternalPath);
+ }
+ } finally {
+ if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) {
+ /** @var ObjectStoreStorage $sourceStorage */
+ $sourceStorage->setPreserveCacheOnDelete(false);
+ }
}
}
return $result;
}
- /**
- * @inheritdoc
- */
- public function getMetaData($path) {
+ public function getMetaData(string $path): ?array {
if (Filesystem::isFileBlacklisted($path)) {
throw new ForbiddenException('Invalid path: ' . $path, false);
}
@@ -711,13 +621,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
return $data;
}
- /**
- * @param string $path
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- * @throws \OCP\Lock\LockedException
- */
- public function acquireLock($path, $type, ILockingProvider $provider) {
+ public function acquireLock(string $path, int $type, ILockingProvider $provider): void {
$logger = $this->getLockLogger();
if ($logger) {
$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
@@ -744,13 +648,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
}
}
- /**
- * @param string $path
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- * @throws \OCP\Lock\LockedException
- */
- public function releaseLock($path, $type, ILockingProvider $provider) {
+ public function releaseLock(string $path, int $type, ILockingProvider $provider): void {
$logger = $this->getLockLogger();
if ($logger) {
$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
@@ -777,13 +675,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
}
}
- /**
- * @param string $path
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- * @throws \OCP\Lock\LockedException
- */
- public function changeLock($path, $type, ILockingProvider $provider) {
+ public function changeLock(string $path, int $type, ILockingProvider $provider): void {
$logger = $this->getLockLogger();
if ($logger) {
$typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
@@ -812,8 +704,8 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
private function getLockLogger(): ?LoggerInterface {
if (is_null($this->shouldLogLocks)) {
- $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValueBool('filelocking.debug', false);
- $this->logger = $this->shouldLogLocks ? \OC::$server->get(LoggerInterface::class) : null;
+ $this->shouldLogLocks = Server::get(IConfig::class)->getSystemValueBool('filelocking.debug', false);
+ $this->logger = $this->shouldLogLocks ? Server::get(LoggerInterface::class) : null;
}
return $this->logger;
}
@@ -821,54 +713,31 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
/**
* @return array [ available, last_checked ]
*/
- public function getAvailability() {
+ public function getAvailability(): array {
return $this->getStorageCache()->getAvailability();
}
- /**
- * @param bool $isAvailable
- */
- public function setAvailability($isAvailable) {
+ public function setAvailability(bool $isAvailable): void {
$this->getStorageCache()->setAvailability($isAvailable);
}
- /**
- * Allow setting the storage owner
- *
- * This can be used for storages that do not have a dedicated owner, where we want to
- * pass the user that we setup the mountpoint for along to the storage layer
- *
- * @param string|null $user
- * @return void
- */
public function setOwner(?string $user): void {
$this->owner = $user;
}
- /**
- * @return bool
- */
- public function needsPartFile() {
+ public function needsPartFile(): bool {
return true;
}
- /**
- * fallback implementation
- *
- * @param string $path
- * @param resource $stream
- * @param int $size
- * @return int
- */
public function writeStream(string $path, $stream, ?int $size = null): int {
$target = $this->fopen($path, 'w');
if (!$target) {
throw new GenericFileException("Failed to open $path for writing");
}
try {
- [$count, $result] = \OC_Helper::streamCopy($stream, $target);
+ [$count, $result] = Files::streamCopy($stream, $target, true);
if (!$result) {
- throw new GenericFileException("Failed to copy stream");
+ throw new GenericFileException('Failed to copy stream');
}
} finally {
fclose($target);
@@ -877,7 +746,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
return $count;
}
- public function getDirectoryContent($directory): \Traversable {
+ public function getDirectoryContent(string $directory): \Traversable {
$dh = $this->opendir($directory);
if ($dh === false) {
diff --git a/lib/private/Files/Storage/CommonTest.php b/lib/private/Files/Storage/CommonTest.php
index b92e493bc2c..da796130899 100644
--- a/lib/private/Files/Storage/CommonTest.php
+++ b/lib/private/Files/Storage/CommonTest.php
@@ -14,47 +14,47 @@ class CommonTest extends \OC\Files\Storage\Common {
*/
private $storage;
- public function __construct($params) {
- $this->storage = new \OC\Files\Storage\Local($params);
+ public function __construct(array $parameters) {
+ $this->storage = new \OC\Files\Storage\Local($parameters);
}
- public function getId() {
- return 'test::'.$this->storage->getId();
+ public function getId(): string {
+ return 'test::' . $this->storage->getId();
}
- public function mkdir($path) {
+ public function mkdir(string $path): bool {
return $this->storage->mkdir($path);
}
- public function rmdir($path) {
+ public function rmdir(string $path): bool {
return $this->storage->rmdir($path);
}
- public function opendir($path) {
+ public function opendir(string $path) {
return $this->storage->opendir($path);
}
- public function stat($path) {
+ public function stat(string $path): array|false {
return $this->storage->stat($path);
}
- public function filetype($path) {
+ public function filetype(string $path): string|false {
return @$this->storage->filetype($path);
}
- public function isReadable($path) {
+ public function isReadable(string $path): bool {
return $this->storage->isReadable($path);
}
- public function isUpdatable($path) {
+ public function isUpdatable(string $path): bool {
return $this->storage->isUpdatable($path);
}
- public function file_exists($path) {
+ public function file_exists(string $path): bool {
return $this->storage->file_exists($path);
}
- public function unlink($path) {
+ public function unlink(string $path): bool {
return $this->storage->unlink($path);
}
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
return $this->storage->fopen($path, $mode);
}
- public function free_space($path) {
+ public function free_space(string $path): int|float|false {
return $this->storage->free_space($path);
}
- public function touch($path, $mtime = null) {
+ public function touch(string $path, ?int $mtime = null): bool {
return $this->storage->touch($path, $mtime);
}
}
diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php
index 63ef1399a69..2d166b5438d 100644
--- a/lib/private/Files/Storage/DAV.php
+++ b/lib/private/Files/Storage/DAV.php
@@ -20,9 +20,11 @@ use OCP\Files\ForbiddenException;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\StorageInvalidException;
use OCP\Files\StorageNotAvailableException;
+use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\ICertificateManager;
use OCP\IConfig;
+use OCP\Server;
use OCP\Util;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
@@ -81,31 +83,31 @@ class DAV extends Common {
];
/**
- * @param array $params
+ * @param array $parameters
* @throws \Exception
*/
- public function __construct($params) {
+ public function __construct(array $parameters) {
$this->statCache = new ArrayCache();
- $this->httpClientService = \OC::$server->get(IClientService::class);
- if (isset($params['host']) && isset($params['user']) && isset($params['password'])) {
- $host = $params['host'];
+ $this->httpClientService = Server::get(IClientService::class);
+ if (isset($parameters['host']) && isset($parameters['user']) && isset($parameters['password'])) {
+ $host = $parameters['host'];
//remove leading http[s], will be generated in createBaseUri()
- if (str_starts_with($host, "https://")) {
+ if (str_starts_with($host, 'https://')) {
$host = substr($host, 8);
- } elseif (str_starts_with($host, "http://")) {
+ } elseif (str_starts_with($host, 'http://')) {
$host = substr($host, 7);
}
$this->host = $host;
- $this->user = $params['user'];
- $this->password = $params['password'];
- if (isset($params['authType'])) {
- $this->authType = $params['authType'];
+ $this->user = $parameters['user'];
+ $this->password = $parameters['password'];
+ if (isset($parameters['authType'])) {
+ $this->authType = $parameters['authType'];
}
- if (isset($params['secure'])) {
- if (is_string($params['secure'])) {
- $this->secure = ($params['secure'] === 'true');
+ if (isset($parameters['secure'])) {
+ if (is_string($parameters['secure'])) {
+ $this->secure = ($parameters['secure'] === 'true');
} else {
- $this->secure = (bool)$params['secure'];
+ $this->secure = (bool)$parameters['secure'];
}
} else {
$this->secure = false;
@@ -114,20 +116,20 @@ class DAV extends Common {
// inject mock for testing
$this->certManager = \OC::$server->getCertificateManager();
}
- $this->root = $params['root'] ?? '/';
+ $this->root = rawurldecode($parameters['root'] ?? '/');
$this->root = '/' . ltrim($this->root, '/');
$this->root = rtrim($this->root, '/') . '/';
} else {
throw new \Exception('Invalid webdav storage configuration');
}
- $this->logger = \OC::$server->get(LoggerInterface::class);
- $this->eventLogger = \OC::$server->get(IEventLogger::class);
+ $this->logger = Server::get(LoggerInterface::class);
+ $this->eventLogger = Server::get(IEventLogger::class);
// This timeout value will be used for the download and upload of files
- $this->timeout = \OC::$server->get(IConfig::class)->getSystemValueInt('davstorage.request_timeout', 30);
+ $this->timeout = Server::get(IConfig::class)->getSystemValueInt('davstorage.request_timeout', IClient::DEFAULT_REQUEST_TIMEOUT);
$this->mimeTypeDetector = \OC::$server->getMimeTypeDetector();
}
- protected function init() {
+ protected function init(): void {
if ($this->ready) {
return;
}
@@ -142,7 +144,7 @@ class DAV extends Common {
$settings['authType'] = $this->authType;
}
- $proxy = \OC::$server->getConfig()->getSystemValueString('proxy', '');
+ $proxy = Server::get(IConfig::class)->getSystemValueString('proxy', '');
if ($proxy !== '') {
$settings['proxy'] = $proxy;
}
@@ -162,13 +164,13 @@ class DAV extends Common {
$lastRequestStart = 0;
$this->client->on('beforeRequest', function (RequestInterface $request) use (&$lastRequestStart) {
- $this->logger->debug("sending dav " . $request->getMethod() . " request to external storage: " . $request->getAbsoluteUrl(), ['app' => 'dav']);
+ $this->logger->debug('sending dav ' . $request->getMethod() . ' request to external storage: ' . $request->getAbsoluteUrl(), ['app' => 'dav']);
$lastRequestStart = microtime(true);
- $this->eventLogger->start('fs:storage:dav:request', "Sending dav request to external storage");
+ $this->eventLogger->start('fs:storage:dav:request', 'Sending dav request to external storage');
});
$this->client->on('afterRequest', function (RequestInterface $request) use (&$lastRequestStart) {
$elapsed = microtime(true) - $lastRequestStart;
- $this->logger->debug("dav " . $request->getMethod() . " request to external storage: " . $request->getAbsoluteUrl() . " took " . round($elapsed * 1000, 1) . "ms", ['app' => 'dav']);
+ $this->logger->debug('dav ' . $request->getMethod() . ' request to external storage: ' . $request->getAbsoluteUrl() . ' took ' . round($elapsed * 1000, 1) . 'ms', ['app' => 'dav']);
$this->eventLogger->end('fs:storage:dav:request');
});
}
@@ -176,27 +178,24 @@ class DAV extends Common {
/**
* Clear the stat cache
*/
- public function clearStatCache() {
+ public function clearStatCache(): void {
$this->statCache->clear();
}
- /** {@inheritdoc} */
- public function getId() {
+ public function getId(): string {
return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
}
- /** {@inheritdoc} */
- public function createBaseUri() {
+ public function createBaseUri(): string {
$baseUri = 'http';
if ($this->secure) {
$baseUri .= 's';
}
- $baseUri .= '://' . $this->host . $this->root;
+ $baseUri .= '://' . $this->host . $this->encodePath($this->root);
return $baseUri;
}
- /** {@inheritdoc} */
- public function mkdir($path) {
+ public function mkdir(string $path): bool {
$this->init();
$path = $this->cleanPath($path);
$result = $this->simpleResponse('MKCOL', $path, null, 201);
@@ -206,8 +205,7 @@ class DAV extends Common {
return $result;
}
- /** {@inheritdoc} */
- public function rmdir($path) {
+ public function rmdir(string $path): bool {
$this->init();
$path = $this->cleanPath($path);
// FIXME: some WebDAV impl return 403 when trying to DELETE
@@ -218,8 +216,7 @@ class DAV extends Common {
return $result;
}
- /** {@inheritdoc} */
- public function opendir($path) {
+ public function opendir(string $path) {
$this->init();
$path = $this->cleanPath($path);
try {
@@ -243,16 +240,17 @@ class DAV extends Common {
*
* @param string $path path to propfind
*
- * @return array|boolean propfind response or false if the entry was not found
+ * @return array|false propfind response or false if the entry was not found
*
* @throws ClientHttpException
*/
- protected function propfind($path) {
+ protected function propfind(string $path): array|false {
$path = $this->cleanPath($path);
$cachedResponse = $this->statCache->get($path);
// we either don't know it, or we know it exists but need more details
if (is_null($cachedResponse) || $cachedResponse === true) {
$this->init();
+ $response = false;
try {
$response = $this->client->propFind(
$this->encodePath($path),
@@ -263,9 +261,9 @@ class DAV extends Common {
if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) {
$this->statCache->clear($path . '/');
$this->statCache->set($path, false);
- return false;
+ } else {
+ $this->convertException($e, $path);
}
- $this->convertException($e, $path);
} catch (\Exception $e) {
$this->convertException($e, $path);
}
@@ -275,27 +273,25 @@ class DAV extends Common {
return $response;
}
- /** {@inheritdoc} */
- public function filetype($path) {
+ public function filetype(string $path): string|false {
try {
$response = $this->propfind($path);
if ($response === false) {
return false;
}
$responseType = [];
- if (isset($response["{DAV:}resourcetype"])) {
+ if (isset($response['{DAV:}resourcetype'])) {
/** @var ResourceType[] $response */
- $responseType = $response["{DAV:}resourcetype"]->getValue();
+ $responseType = $response['{DAV:}resourcetype']->getValue();
}
- return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
+ return (count($responseType) > 0 && $responseType[0] == '{DAV:}collection') ? 'dir' : 'file';
} catch (\Exception $e) {
$this->convertException($e, $path);
}
return false;
}
- /** {@inheritdoc} */
- public function file_exists($path) {
+ public function file_exists(string $path): bool {
try {
$path = $this->cleanPath($path);
$cachedState = $this->statCache->get($path);
@@ -313,8 +309,7 @@ class DAV extends Common {
return false;
}
- /** {@inheritdoc} */
- public function unlink($path) {
+ public function unlink(string $path): bool {
$this->init();
$path = $this->cleanPath($path);
$result = $this->simpleResponse('DELETE', $path, null, 204);
@@ -323,8 +318,7 @@ class DAV extends Common {
return $result;
}
- /** {@inheritdoc} */
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
$this->init();
$path = $this->cleanPath($path);
switch ($mode) {
@@ -352,11 +346,17 @@ class DAV extends Common {
if ($response->getStatusCode() === Http::STATUS_LOCKED) {
throw new \OCP\Lock\LockedException($path);
} else {
- \OC::$server->get(LoggerInterface::class)->error('Guzzle get returned status code ' . $response->getStatusCode(), ['app' => 'webdav client']);
+ $this->logger->error('Guzzle get returned status code ' . $response->getStatusCode(), ['app' => 'webdav client']);
}
}
- return $response->getBody();
+ $content = $response->getBody();
+
+ if ($content === null || is_string($content)) {
+ return false;
+ }
+
+ return $content;
case 'w':
case 'wb':
case 'a':
@@ -380,7 +380,7 @@ class DAV extends Common {
if (!$this->isUpdatable($path)) {
return false;
}
- if ($mode === 'w' or $mode === 'w+') {
+ if ($mode === 'w' || $mode === 'w+') {
$tmpFile = $tempManager->getTemporaryFile($ext);
} else {
$tmpFile = $this->getCachedFile($path);
@@ -396,18 +396,16 @@ class DAV extends Common {
$this->writeBack($tmpFile, $path);
});
}
+
+ return false;
}
- /**
- * @param string $tmpFile
- */
- public function writeBack($tmpFile, $path) {
+ public function writeBack(string $tmpFile, string $path): void {
$this->uploadFile($tmpFile, $path);
unlink($tmpFile);
}
- /** {@inheritdoc} */
- public function free_space($path) {
+ public function free_space(string $path): int|float|false {
$this->init();
$path = $this->cleanPath($path);
try {
@@ -425,8 +423,7 @@ class DAV extends Common {
}
}
- /** {@inheritdoc} */
- public function touch($path, $mtime = null) {
+ public function touch(string $path, ?int $mtime = null): bool {
$this->init();
if (is_null($mtime)) {
$mtime = time();
@@ -463,23 +460,14 @@ class DAV extends Common {
return true;
}
- /**
- * @param string $path
- * @param mixed $data
- * @return int|float|false
- */
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): int|float|false {
$path = $this->cleanPath($path);
$result = parent::file_put_contents($path, $data);
$this->statCache->remove($path);
return $result;
}
- /**
- * @param string $path
- * @param string $target
- */
- protected function uploadFile($path, $target) {
+ protected function uploadFile(string $path, string $target): void {
$this->init();
// invalidate
@@ -499,8 +487,7 @@ class DAV extends Common {
$this->removeCachedFile($target);
}
- /** {@inheritdoc} */
- public function rename($source, $target) {
+ public function rename(string $source, string $target): bool {
$this->init();
$source = $this->cleanPath($source);
$target = $this->cleanPath($target);
@@ -531,8 +518,7 @@ class DAV extends Common {
return false;
}
- /** {@inheritdoc} */
- public function copy($source, $target) {
+ public function copy(string $source, string $target): bool {
$this->init();
$source = $this->cleanPath($source);
$target = $this->cleanPath($target);
@@ -560,7 +546,7 @@ class DAV extends Common {
return false;
}
- public function getMetaData($path) {
+ public function getMetaData(string $path): ?array {
if (Filesystem::isFileBlacklisted($path)) {
throw new ForbiddenException('Invalid path: ' . $path, false);
}
@@ -582,11 +568,11 @@ class DAV extends Common {
}
$responseType = [];
- if (isset($response["{DAV:}resourcetype"])) {
+ if (isset($response['{DAV:}resourcetype'])) {
/** @var ResourceType[] $response */
- $responseType = $response["{DAV:}resourcetype"]->getValue();
+ $responseType = $response['{DAV:}resourcetype']->getValue();
}
- $type = (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file';
+ $type = (count($responseType) > 0 && $responseType[0] == '{DAV:}collection') ? 'dir' : 'file';
if ($type === 'dir') {
$mimeType = 'httpd/unix-directory';
} elseif (isset($response['{DAV:}getcontenttype'])) {
@@ -622,31 +608,18 @@ class DAV extends Common {
];
}
- /** {@inheritdoc} */
- public function stat($path) {
+ public function stat(string $path): array|false {
$meta = $this->getMetaData($path);
- if (!$meta) {
- return false;
- } else {
- return $meta;
- }
+ return $meta ?: false;
+
}
- /** {@inheritdoc} */
- public function getMimeType($path) {
+ public function getMimeType(string $path): string|false {
$meta = $this->getMetaData($path);
- if ($meta) {
- return $meta['mimetype'];
- } else {
- return false;
- }
+ return $meta ? $meta['mimetype'] : false;
}
- /**
- * @param string $path
- * @return string
- */
- public function cleanPath($path) {
+ public function cleanPath(string $path): string {
if ($path === '') {
return $path;
}
@@ -661,21 +634,17 @@ class DAV extends Common {
* @param string $path to encode
* @return string encoded path
*/
- protected function encodePath($path) {
+ protected function encodePath(string $path): string {
// slashes need to stay
return str_replace('%2F', '/', rawurlencode($path));
}
/**
- * @param string $method
- * @param string $path
- * @param string|resource|null $body
- * @param int $expected
* @return bool
* @throws StorageInvalidException
* @throws StorageNotAvailableException
*/
- protected function simpleResponse($method, $path, $body, $expected) {
+ protected function simpleResponse(string $method, string $path, ?string $body, int $expected): bool {
$path = $this->cleanPath($path);
try {
$response = $this->client->request($method, $this->encodePath($path), $body);
@@ -697,55 +666,37 @@ class DAV extends Common {
/**
* check if curl is installed
*/
- public static function checkDependencies() {
+ public static function checkDependencies(): bool {
return true;
}
- /** {@inheritdoc} */
- public function isUpdatable($path) {
+ public function isUpdatable(string $path): bool {
return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
}
- /** {@inheritdoc} */
- public function isCreatable($path) {
+ public function isCreatable(string $path): bool {
return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
}
- /** {@inheritdoc} */
- public function isSharable($path) {
+ public function isSharable(string $path): bool {
return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
}
- /** {@inheritdoc} */
- public function isDeletable($path) {
+ public function isDeletable(string $path): bool {
return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
}
- /** {@inheritdoc} */
- public function getPermissions($path) {
+ public function getPermissions(string $path): int {
$stat = $this->getMetaData($path);
- if ($stat) {
- return $stat['permissions'];
- } else {
- return 0;
- }
+ return $stat ? $stat['permissions'] : 0;
}
- /** {@inheritdoc} */
- public function getETag($path) {
+ public function getETag(string $path): string|false {
$meta = $this->getMetaData($path);
- if ($meta) {
- return $meta['etag'];
- } else {
- return null;
- }
+ return $meta ? $meta['etag'] : false;
}
- /**
- * @param string $permissionsString
- * @return int
- */
- protected function parsePermissions($permissionsString) {
+ protected function parsePermissions(string $permissionsString): int {
$permissions = Constants::PERMISSION_READ;
if (str_contains($permissionsString, 'R')) {
$permissions |= Constants::PERMISSION_SHARE;
@@ -763,15 +714,7 @@ class DAV extends Common {
return $permissions;
}
- /**
- * check if a file or folder has been updated since $time
- *
- * @param string $path
- * @param int $time
- * @throws \OCP\Files\StorageNotAvailableException
- * @return bool
- */
- public function hasUpdated($path, $time) {
+ public function hasUpdated(string $path, int $time): bool {
$this->init();
$path = $this->cleanPath($path);
try {
@@ -832,13 +775,13 @@ class DAV extends Common {
* @param string $path optional path from the operation
*
* @throws StorageInvalidException if the storage is invalid, for example
- * when the authentication expired or is invalid
+ * when the authentication expired or is invalid
* @throws StorageNotAvailableException if the storage is not available,
- * which might be temporary
+ * which might be temporary
* @throws ForbiddenException if the action is not allowed
*/
- protected function convertException(Exception $e, $path = '') {
- \OC::$server->get(LoggerInterface::class)->debug($e->getMessage(), ['app' => 'files_external', 'exception' => $e]);
+ protected function convertException(Exception $e, string $path = ''): void {
+ $this->logger->debug($e->getMessage(), ['app' => 'files_external', 'exception' => $e]);
if ($e instanceof ClientHttpException) {
if ($e->getHttpStatus() === Http::STATUS_LOCKED) {
throw new \OCP\Lock\LockedException($path);
@@ -869,7 +812,7 @@ class DAV extends Common {
// TODO: only log for now, but in the future need to wrap/rethrow exception
}
- public function getDirectoryContent($directory): \Traversable {
+ public function getDirectoryContent(string $directory): \Traversable {
$this->init();
$directory = $this->cleanPath($directory);
try {
diff --git a/lib/private/Files/Storage/FailedStorage.php b/lib/private/Files/Storage/FailedStorage.php
index 87a6e7f5041..a8288de48d0 100644
--- a/lib/private/Files/Storage/FailedStorage.php
+++ b/lib/private/Files/Storage/FailedStorage.php
@@ -20,177 +20,172 @@ class FailedStorage extends Common {
protected $e;
/**
- * @param array $params ['exception' => \Exception]
+ * @param array $parameters ['exception' => \Exception]
*/
- public function __construct($params) {
- $this->e = $params['exception'];
+ public function __construct(array $parameters) {
+ $this->e = $parameters['exception'];
if (!$this->e) {
throw new \InvalidArgumentException('Missing "exception" argument in FailedStorage constructor');
}
}
- public function getId() {
+ public function getId(): string {
// we can't return anything sane here
return 'failedstorage';
}
- public function mkdir($path) {
+ public function mkdir(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function rmdir($path) {
+ public function rmdir(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function opendir($path) {
+ public function opendir(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function is_dir($path) {
+ public function is_dir(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function is_file($path) {
+ public function is_file(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function stat($path) {
+ public function stat(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function filetype($path) {
+ public function filetype(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function filesize($path): false|int|float {
+ public function filesize(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function isCreatable($path) {
+ public function isCreatable(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function isReadable($path) {
+ public function isReadable(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function isUpdatable($path) {
+ public function isUpdatable(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function isDeletable($path) {
+ public function isDeletable(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function isSharable($path) {
+ public function isSharable(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function getPermissions($path) {
+ public function getPermissions(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function file_exists($path) {
+ public function file_exists(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function filemtime($path) {
+ public function filemtime(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function file_get_contents($path) {
+ public function file_get_contents(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function unlink($path) {
+ public function unlink(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function rename($source, $target) {
+ public function rename(string $source, string $target): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function copy($source, $target) {
+ public function copy(string $source, string $target): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function getMimeType($path) {
+ public function getMimeType(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function hash($type, $path, $raw = false) {
+ public function hash(string $type, string $path, bool $raw = false): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function free_space($path) {
+ public function free_space(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function search($query) {
+ public function touch(string $path, ?int $mtime = null): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function touch($path, $mtime = null) {
+ public function getLocalFile(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function getLocalFile($path) {
+ public function hasUpdated(string $path, int $time): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function hasUpdated($path, $time) {
+ public function getETag(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function getETag($path) {
+ public function getDirectDownload(string $path): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function getDirectDownload($path) {
- throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
- }
-
- public function verifyPath($path, $fileName) {
- return true;
+ public function verifyPath(string $path, string $fileName): void {
}
- public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
+ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, bool $preserveMtime = false): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function acquireLock($path, $type, ILockingProvider $provider) {
+ public function acquireLock(string $path, int $type, ILockingProvider $provider): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function releaseLock($path, $type, ILockingProvider $provider) {
+ public function releaseLock(string $path, int $type, ILockingProvider $provider): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function changeLock($path, $type, ILockingProvider $provider) {
+ public function changeLock(string $path, int $type, ILockingProvider $provider): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function getAvailability() {
+ public function getAvailability(): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function setAvailability($isAvailable) {
+ public function setAvailability(bool $isAvailable): never {
throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e);
}
- public function getCache($path = '', $storage = null) {
+ public function getCache(string $path = '', ?IStorage $storage = null): FailedCache {
return new FailedCache();
}
}
diff --git a/lib/private/Files/Storage/Home.php b/lib/private/Files/Storage/Home.php
index 9a336d2efcc..91b8071ac30 100644
--- a/lib/private/Files/Storage/Home.php
+++ b/lib/private/Files/Storage/Home.php
@@ -8,6 +8,9 @@
namespace OC\Files\Storage;
use OC\Files\Cache\HomePropagator;
+use OCP\Files\Cache\ICache;
+use OCP\Files\Cache\IPropagator;
+use OCP\Files\Storage\IStorage;
use OCP\IUser;
/**
@@ -27,25 +30,22 @@ class Home extends Local implements \OCP\Files\IHomeStorage {
/**
* Construct a Home storage instance
*
- * @param array $arguments array with "user" containing the
- * storage owner
+ * @param array $parameters array with "user" containing the
+ * storage owner
*/
- public function __construct($arguments) {
- $this->user = $arguments['user'];
+ public function __construct(array $parameters) {
+ $this->user = $parameters['user'];
$datadir = $this->user->getHome();
$this->id = 'home::' . $this->user->getUID();
parent::__construct(['datadir' => $datadir]);
}
- public function getId() {
+ public function getId(): string {
return $this->id;
}
- /**
- * @return \OC\Files\Cache\HomeCache
- */
- public function getCache($path = '', $storage = null) {
+ public function getCache(string $path = '', ?IStorage $storage = null): ICache {
if (!$storage) {
$storage = $this;
}
@@ -55,13 +55,7 @@ class Home extends Local implements \OCP\Files\IHomeStorage {
return $this->cache;
}
- /**
- * get a propagator instance for the cache
- *
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
- * @return \OC\Files\Cache\Propagator
- */
- public function getPropagator($storage = null) {
+ public function getPropagator(?IStorage $storage = null): IPropagator {
if (!$storage) {
$storage = $this;
}
@@ -72,22 +66,11 @@ class Home extends Local implements \OCP\Files\IHomeStorage {
}
- /**
- * Returns the owner of this home storage
- *
- * @return \OC\User\User owner of this home storage
- */
public function getUser(): IUser {
return $this->user;
}
- /**
- * get the owner of a path
- *
- * @param string $path The path to get the owner
- * @return string uid or false
- */
- public function getOwner($path) {
+ public function getOwner(string $path): string|false {
return $this->user->getUID();
}
}
diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php
index ac3696118f7..260f9218a88 100644
--- a/lib/private/Files/Storage/Local.php
+++ b/lib/private/Files/Storage/Local.php
@@ -17,6 +17,7 @@ use OCP\Files\IMimeTypeDetector;
use OCP\Files\Storage\IStorage;
use OCP\Files\StorageNotAvailableException;
use OCP\IConfig;
+use OCP\Server;
use OCP\Util;
use Psr\Log\LoggerInterface;
@@ -40,11 +41,11 @@ class Local extends \OC\Files\Storage\Common {
protected bool $caseInsensitive = false;
- public function __construct($arguments) {
- if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) {
+ public function __construct(array $parameters) {
+ if (!isset($parameters['datadir']) || !is_string($parameters['datadir'])) {
throw new \InvalidArgumentException('No data directory set for local storage');
}
- $this->datadir = str_replace('//', '/', $arguments['datadir']);
+ $this->datadir = str_replace('//', '/', $parameters['datadir']);
// some crazy code uses a local storage on root...
if ($this->datadir === '/') {
$this->realDataDir = $this->datadir;
@@ -56,15 +57,15 @@ class Local extends \OC\Files\Storage\Common {
$this->datadir .= '/';
}
$this->dataDirLength = strlen($this->realDataDir);
- $this->config = \OC::$server->get(IConfig::class);
- $this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class);
+ $this->config = Server::get(IConfig::class);
+ $this->mimeTypeDetector = Server::get(IMimeTypeDetector::class);
$this->defUMask = $this->config->getSystemValue('localstorage.umask', 0022);
$this->caseInsensitive = $this->config->getSystemValueBool('localstorage.case_insensitive', false);
// support Write-Once-Read-Many file systems
$this->unlinkOnTruncate = $this->config->getSystemValueBool('localstorage.unlink_on_truncate', false);
- if (isset($arguments['isExternal']) && $arguments['isExternal'] && !$this->stat('')) {
+ if (isset($parameters['isExternal']) && $parameters['isExternal'] && !$this->stat('')) {
// data dir not accessible or available, can happen when using an external storage of type Local
// on an unmounted system mount point
throw new StorageNotAvailableException('Local storage path does not exist "' . $this->getSourcePath('') . '"');
@@ -74,11 +75,11 @@ class Local extends \OC\Files\Storage\Common {
public function __destruct() {
}
- public function getId() {
+ public function getId(): string {
return 'local::' . $this->datadir;
}
- public function mkdir($path) {
+ public function mkdir(string $path): bool {
$sourcePath = $this->getSourcePath($path);
$oldMask = umask($this->defUMask);
$result = @mkdir($sourcePath, 0777, true);
@@ -86,7 +87,7 @@ class Local extends \OC\Files\Storage\Common {
return $result;
}
- public function rmdir($path) {
+ public function rmdir(string $path): bool {
if (!$this->isDeletable($path)) {
return false;
}
@@ -106,7 +107,7 @@ class Local extends \OC\Files\Storage\Common {
* @var \SplFileInfo $file
*/
$file = $it->current();
- clearstatcache(true, $this->getSourcePath($file));
+ clearstatcache(true, $file->getRealPath());
if (in_array($file->getBasename(), ['.', '..'])) {
$it->next();
continue;
@@ -117,6 +118,7 @@ class Local extends \OC\Files\Storage\Common {
}
$it->next();
}
+ unset($it); // Release iterator and thereby its potential directory lock (e.g. in case of VirtualBox shared folders)
clearstatcache(true, $this->getSourcePath($path));
return rmdir($this->getSourcePath($path));
} catch (\UnexpectedValueException $e) {
@@ -124,11 +126,11 @@ class Local extends \OC\Files\Storage\Common {
}
}
- public function opendir($path) {
+ public function opendir(string $path) {
return opendir($this->getSourcePath($path));
}
- public function is_dir($path) {
+ public function is_dir(string $path): bool {
if ($this->caseInsensitive && !$this->file_exists($path)) {
return false;
}
@@ -138,14 +140,14 @@ class Local extends \OC\Files\Storage\Common {
return is_dir($this->getSourcePath($path));
}
- public function is_file($path) {
+ public function is_file(string $path): bool {
if ($this->caseInsensitive && !$this->file_exists($path)) {
return false;
}
return is_file($this->getSourcePath($path));
}
- public function stat($path) {
+ public function stat(string $path): array|false {
$fullPath = $this->getSourcePath($path);
clearstatcache(true, $fullPath);
if (!file_exists($fullPath)) {
@@ -163,10 +165,7 @@ class Local extends \OC\Files\Storage\Common {
return $statResult;
}
- /**
- * @inheritdoc
- */
- public function getMetaData($path) {
+ public function getMetaData(string $path): ?array {
try {
$stat = $this->stat($path);
} catch (ForbiddenException $e) {
@@ -215,7 +214,7 @@ class Local extends \OC\Files\Storage\Common {
return $data;
}
- public function filetype($path) {
+ public function filetype(string $path): string|false {
$filetype = filetype($this->getSourcePath($path));
if ($filetype == 'link') {
$filetype = filetype(realpath($this->getSourcePath($path)));
@@ -223,7 +222,7 @@ class Local extends \OC\Files\Storage\Common {
return $filetype;
}
- public function filesize($path): false|int|float {
+ public function filesize(string $path): int|float|false {
if (!$this->is_file($path)) {
return 0;
}
@@ -235,15 +234,15 @@ class Local extends \OC\Files\Storage\Common {
return filesize($fullPath);
}
- public function isReadable($path) {
+ public function isReadable(string $path): bool {
return is_readable($this->getSourcePath($path));
}
- public function isUpdatable($path) {
+ public function isUpdatable(string $path): bool {
return is_writable($this->getSourcePath($path));
}
- public function file_exists($path) {
+ public function file_exists(string $path): bool {
if ($this->caseInsensitive) {
$fullPath = $this->getSourcePath($path);
$parentPath = dirname($fullPath);
@@ -257,7 +256,7 @@ class Local extends \OC\Files\Storage\Common {
}
}
- public function filemtime($path) {
+ public function filemtime(string $path): int|false {
$fullPath = $this->getSourcePath($path);
clearstatcache(true, $fullPath);
if (!$this->file_exists($path)) {
@@ -270,11 +269,11 @@ class Local extends \OC\Files\Storage\Common {
return filemtime($fullPath);
}
- public function touch($path, $mtime = null) {
+ public function touch(string $path, ?int $mtime = null): bool {
// sets the modification time of the file to the given value.
// If mtime is nil the current time is set.
// note that the access time of the file always changes to the current time.
- if ($this->file_exists($path) and !$this->isUpdatable($path)) {
+ if ($this->file_exists($path) && !$this->isUpdatable($path)) {
return false;
}
$oldMask = umask($this->defUMask);
@@ -291,11 +290,11 @@ class Local extends \OC\Files\Storage\Common {
return $result;
}
- public function file_get_contents($path) {
+ public function file_get_contents(string $path): string|false {
return file_get_contents($this->getSourcePath($path));
}
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): int|float|false {
$oldMask = umask($this->defUMask);
if ($this->unlinkOnTruncate) {
$this->unlink($path);
@@ -305,7 +304,7 @@ class Local extends \OC\Files\Storage\Common {
return $result;
}
- public function unlink($path) {
+ public function unlink(string $path): bool {
if ($this->is_dir($path)) {
return $this->rmdir($path);
} elseif ($this->is_file($path)) {
@@ -315,7 +314,7 @@ class Local extends \OC\Files\Storage\Common {
}
}
- private function checkTreeForForbiddenItems(string $path) {
+ private function checkTreeForForbiddenItems(string $path): void {
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
foreach ($iterator as $file) {
/** @var \SplFileInfo $file */
@@ -325,29 +324,31 @@ class Local extends \OC\Files\Storage\Common {
}
}
- public function rename($source, $target): bool {
+ public function rename(string $source, string $target): bool {
$srcParent = dirname($source);
$dstParent = dirname($target);
if (!$this->isUpdatable($srcParent)) {
- \OC::$server->get(LoggerInterface::class)->error('unable to rename, source directory is not writable : ' . $srcParent, ['app' => 'core']);
+ Server::get(LoggerInterface::class)->error('unable to rename, source directory is not writable : ' . $srcParent, ['app' => 'core']);
return false;
}
if (!$this->isUpdatable($dstParent)) {
- \OC::$server->get(LoggerInterface::class)->error('unable to rename, destination directory is not writable : ' . $dstParent, ['app' => 'core']);
+ Server::get(LoggerInterface::class)->error('unable to rename, destination directory is not writable : ' . $dstParent, ['app' => 'core']);
return false;
}
if (!$this->file_exists($source)) {
- \OC::$server->get(LoggerInterface::class)->error('unable to rename, file does not exists : ' . $source, ['app' => 'core']);
+ Server::get(LoggerInterface::class)->error('unable to rename, file does not exists : ' . $source, ['app' => 'core']);
return false;
}
- if ($this->is_dir($target)) {
- $this->rmdir($target);
- } elseif ($this->is_file($target)) {
- $this->unlink($target);
+ if ($this->file_exists($target)) {
+ if ($this->is_dir($target)) {
+ $this->rmdir($target);
+ } elseif ($this->is_file($target)) {
+ $this->unlink($target);
+ }
}
if ($this->is_dir($source)) {
@@ -366,7 +367,7 @@ class Local extends \OC\Files\Storage\Common {
return $this->copy($source, $target) && $this->unlink($source);
}
- public function copy($source, $target) {
+ public function copy(string $source, string $target): bool {
if ($this->is_dir($source)) {
return parent::copy($source, $target);
} else {
@@ -385,7 +386,7 @@ class Local extends \OC\Files\Storage\Common {
}
}
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
$sourcePath = $this->getSourcePath($path);
if (!file_exists($sourcePath) && $mode === 'r') {
return false;
@@ -399,11 +400,11 @@ class Local extends \OC\Files\Storage\Common {
return $result;
}
- public function hash($type, $path, $raw = false) {
+ public function hash(string $type, string $path, bool $raw = false): string|false {
return hash_file($type, $this->getSourcePath($path), $raw);
}
- public function free_space($path) {
+ public function free_space(string $path): int|float|false {
$sourcePath = $this->getSourcePath($path);
// using !is_dir because $sourcePath might be a part file or
// non-existing file, so we'd still want to use the parent dir
@@ -419,20 +420,15 @@ class Local extends \OC\Files\Storage\Common {
return Util::numericToNumber($space);
}
- public function search($query) {
+ public function search(string $query): array {
return $this->searchInDir($query);
}
- public function getLocalFile($path) {
+ public function getLocalFile(string $path): string|false {
return $this->getSourcePath($path);
}
- /**
- * @param string $query
- * @param string $dir
- * @return array
- */
- protected function searchInDir($query, $dir = '') {
+ protected function searchInDir(string $query, string $dir = ''): array {
$files = [];
$physicalDir = $this->getSourcePath($dir);
foreach (scandir($physicalDir) as $item) {
@@ -451,14 +447,7 @@ class Local extends \OC\Files\Storage\Common {
return $files;
}
- /**
- * check if a file or folder has been updated since $time
- *
- * @param string $path
- * @param int $time
- * @return bool
- */
- public function hasUpdated($path, $time) {
+ public function hasUpdated(string $path, int $time): bool {
if ($this->file_exists($path)) {
return $this->filemtime($path) > $time;
} else {
@@ -469,11 +458,9 @@ class Local extends \OC\Files\Storage\Common {
/**
* Get the source path (on disk) of a given path
*
- * @param string $path
- * @return string
* @throws ForbiddenException
*/
- public function getSourcePath($path) {
+ public function getSourcePath(string $path): string {
if (Filesystem::isFileBlacklisted($path)) {
throw new ForbiddenException('Invalid path: ' . $path, false);
}
@@ -501,28 +488,19 @@ class Local extends \OC\Files\Storage\Common {
return $fullPath;
}
- \OC::$server->get(LoggerInterface::class)->error("Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ['app' => 'core']);
+ Server::get(LoggerInterface::class)->error("Following symlinks is not allowed ('$fullPath' -> '$realPath' not inside '{$this->realDataDir}')", ['app' => 'core']);
throw new ForbiddenException('Following symlinks is not allowed', false);
}
- /**
- * {@inheritdoc}
- */
- public function isLocal() {
+ public function isLocal(): bool {
return true;
}
- /**
- * get the ETag for a file or folder
- *
- * @param string $path
- * @return string
- */
- public function getETag($path) {
+ public function getETag(string $path): string|false {
return $this->calculateEtag($path, $this->stat($path));
}
- private function calculateEtag(string $path, array $stat): string {
+ private function calculateEtag(string $path, array $stat): string|false {
if ($stat['mode'] & 0x4000 && !($stat['mode'] & 0x8000)) { // is_dir & not socket
return parent::getETag($path);
} else {
@@ -548,8 +526,8 @@ class Local extends \OC\Files\Storage\Common {
}
}
- private function canDoCrossStorageMove(IStorage $sourceStorage) {
- /** @psalm-suppress UndefinedClass */
+ private function canDoCrossStorageMove(IStorage $sourceStorage): bool {
+ /** @psalm-suppress UndefinedClass,InvalidArgument */
return $sourceStorage->instanceOfStorage(Local::class)
// Don't treat ACLStorageWrapper like local storage where copy can be done directly.
// Instead, use the slower recursive copying in php from Common::copyFromStorage with
@@ -561,20 +539,15 @@ class Local extends \OC\Files\Storage\Common {
&& !$sourceStorage->instanceOfStorage(Encryption::class);
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @param bool $preserveMtime
- * @return bool
- */
- public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
+ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, bool $preserveMtime = false): bool {
if ($this->canDoCrossStorageMove($sourceStorage)) {
- if ($sourceStorage->instanceOfStorage(Jail::class)) {
+ // resolve any jailed paths
+ while ($sourceStorage->instanceOfStorage(Jail::class)) {
/**
* @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
*/
$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
+ $sourceStorage = $sourceStorage->getUnjailedStorage();
}
/**
* @var \OC\Files\Storage\Local $sourceStorage
@@ -586,19 +559,15 @@ class Local extends \OC\Files\Storage\Common {
}
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @return bool
- */
- public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
if ($this->canDoCrossStorageMove($sourceStorage)) {
- if ($sourceStorage->instanceOfStorage(Jail::class)) {
+ // resolve any jailed paths
+ while ($sourceStorage->instanceOfStorage(Jail::class)) {
/**
* @var \OC\Files\Storage\Wrapper\Jail $sourceStorage
*/
$sourceInternalPath = $sourceStorage->getUnjailedPath($sourceInternalPath);
+ $sourceStorage = $sourceStorage->getUnjailedStorage();
}
/**
* @var \OC\Files\Storage\Local $sourceStorage
diff --git a/lib/private/Files/Storage/LocalRootStorage.php b/lib/private/Files/Storage/LocalRootStorage.php
index ae0575fe043..2e0645e092a 100644
--- a/lib/private/Files/Storage/LocalRootStorage.php
+++ b/lib/private/Files/Storage/LocalRootStorage.php
@@ -9,15 +9,14 @@ declare(strict_types=1);
namespace OC\Files\Storage;
use OC\Files\Cache\LocalRootScanner;
+use OCP\Files\Cache\IScanner;
+use OCP\Files\Storage\IStorage;
class LocalRootStorage extends Local {
- public function getScanner($path = '', $storage = null) {
+ public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
if (!$storage) {
$storage = $this;
}
- if (!isset($storage->scanner)) {
- $storage->scanner = new LocalRootScanner($storage);
- }
- return $storage->scanner;
+ return $storage->scanner ?? ($storage->scanner = new LocalRootScanner($storage));
}
}
diff --git a/lib/private/Files/Storage/LocalTempFileTrait.php b/lib/private/Files/Storage/LocalTempFileTrait.php
index 26fbcc238fd..fffc3e789f3 100644
--- a/lib/private/Files/Storage/LocalTempFileTrait.php
+++ b/lib/private/Files/Storage/LocalTempFileTrait.php
@@ -7,6 +7,8 @@
*/
namespace OC\Files\Storage;
+use OCP\Files;
+
/**
* Storage backend class for providing common filesystem operation methods
* which are not storage-backend specific.
@@ -29,10 +31,7 @@ trait LocalTempFileTrait {
return $this->cachedFiles[$path];
}
- /**
- * @param string $path
- */
- protected function removeCachedFile($path) {
+ protected function removeCachedFile(string $path): void {
unset($this->cachedFiles[$path]);
}
@@ -48,7 +47,7 @@ trait LocalTempFileTrait {
}
$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
$target = fopen($tmpFile, 'w');
- \OC_Helper::streamCopy($source, $target);
+ Files::streamCopy($source, $target);
fclose($target);
return $tmpFile;
}
diff --git a/lib/private/Files/Storage/PolyFill/CopyDirectory.php b/lib/private/Files/Storage/PolyFill/CopyDirectory.php
index 4b3e367da78..2f6167ef85e 100644
--- a/lib/private/Files/Storage/PolyFill/CopyDirectory.php
+++ b/lib/private/Files/Storage/PolyFill/CopyDirectory.php
@@ -10,45 +10,32 @@ namespace OC\Files\Storage\PolyFill;
trait CopyDirectory {
/**
* Check if a path is a directory
- *
- * @param string $path
- * @return bool
*/
- abstract public function is_dir($path);
+ abstract public function is_dir(string $path): bool;
/**
* Check if a file or folder exists
- *
- * @param string $path
- * @return bool
*/
- abstract public function file_exists($path);
+ abstract public function file_exists(string $path): bool;
/**
* Delete a file or folder
- *
- * @param string $path
- * @return bool
*/
- abstract public function unlink($path);
+ abstract public function unlink(string $path): bool;
/**
* Open a directory handle for a folder
*
- * @param string $path
- * @return resource | bool
+ * @return resource|false
*/
- abstract public function opendir($path);
+ abstract public function opendir(string $path);
/**
* Create a new folder
- *
- * @param string $path
- * @return bool
*/
- abstract public function mkdir($path);
+ abstract public function mkdir(string $path): bool;
- public function copy($source, $target) {
+ public function copy(string $source, string $target): bool {
if ($this->is_dir($source)) {
if ($this->file_exists($target)) {
$this->unlink($target);
@@ -62,15 +49,11 @@ trait CopyDirectory {
/**
* For adapters that don't support copying folders natively
- *
- * @param $source
- * @param $target
- * @return bool
*/
- protected function copyRecursive($source, $target) {
+ protected function copyRecursive(string $source, string $target): bool {
$dh = $this->opendir($source);
$result = true;
- while ($file = readdir($dh)) {
+ while (($file = readdir($dh)) !== false) {
if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
if ($this->is_dir($source . '/' . $file)) {
$this->mkdir($target . '/' . $file);
diff --git a/lib/private/Files/Storage/Storage.php b/lib/private/Files/Storage/Storage.php
index 44b1a52cfc0..aa17c12b309 100644
--- a/lib/private/Files/Storage/Storage.php
+++ b/lib/private/Files/Storage/Storage.php
@@ -5,109 +5,40 @@
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
+
namespace OC\Files\Storage;
-use OCP\Lock\ILockingProvider;
+use OCP\Files\Cache\ICache;
+use OCP\Files\Cache\IPropagator;
+use OCP\Files\Cache\IScanner;
+use OCP\Files\Cache\IUpdater;
+use OCP\Files\Cache\IWatcher;
+use OCP\Files\Storage\ILockingStorage;
+use OCP\Files\Storage\IStorage;
/**
* Provide a common interface to all different storage options
*
* All paths passed to the storage are relative to the storage and should NOT have a leading slash.
*/
-interface Storage extends \OCP\Files\Storage {
- /**
- * get a cache instance for the storage
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage|null (optional) the storage to pass to the cache
- * @return \OC\Files\Cache\Cache
- */
- public function getCache($path = '', $storage = null);
+interface Storage extends IStorage, ILockingStorage {
+ public function getCache(string $path = '', ?IStorage $storage = null): ICache;
- /**
- * get a scanner instance for the storage
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
- * @return \OC\Files\Cache\Scanner
- */
- public function getScanner($path = '', $storage = null);
+ public function getScanner(string $path = '', ?IStorage $storage = null): IScanner;
+ public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher;
- /**
- * get the user id of the owner of a file or folder
- *
- * @param string $path
- * @return string
- */
- public function getOwner($path);
+ public function getPropagator(?IStorage $storage = null): IPropagator;
- /**
- * get a watcher instance for the cache
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
- * @return \OC\Files\Cache\Watcher
- */
- public function getWatcher($path = '', $storage = null);
+ public function getUpdater(?IStorage $storage = null): IUpdater;
- /**
- * get a propagator instance for the cache
- *
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
- * @return \OC\Files\Cache\Propagator
- */
- public function getPropagator($storage = null);
+ public function getStorageCache(): \OC\Files\Cache\Storage;
- /**
- * get a updater instance for the cache
- *
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
- * @return \OC\Files\Cache\Updater
- */
- public function getUpdater($storage = null);
-
- /**
- * @return \OC\Files\Cache\Storage
- */
- public function getStorageCache();
-
- /**
- * @param string $path
- * @return array|null
- */
- public function getMetaData($path);
-
- /**
- * @param string $path The path of the file to acquire the lock for
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- * @throws \OCP\Lock\LockedException
- */
- public function acquireLock($path, $type, ILockingProvider $provider);
-
- /**
- * @param string $path The path of the file to release the lock for
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- * @throws \OCP\Lock\LockedException
- */
- public function releaseLock($path, $type, ILockingProvider $provider);
-
- /**
- * @param string $path The path of the file to change the lock for
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- * @throws \OCP\Lock\LockedException
- */
- public function changeLock($path, $type, ILockingProvider $provider);
+ public function getMetaData(string $path): ?array;
/**
* Get the contents of a directory with metadata
*
- * @param string $directory
- * @return \Traversable an iterator, containing file metadata
- *
* The metadata array will contain the following fields
*
* - name
@@ -118,5 +49,5 @@ interface Storage extends \OCP\Files\Storage {
* - storage_mtime
* - permissions
*/
- public function getDirectoryContent($directory): \Traversable;
+ public function getDirectoryContent(string $directory): \Traversable;
}
diff --git a/lib/private/Files/Storage/StorageFactory.php b/lib/private/Files/Storage/StorageFactory.php
index 18fca1d28e3..603df7fe007 100644
--- a/lib/private/Files/Storage/StorageFactory.php
+++ b/lib/private/Files/Storage/StorageFactory.php
@@ -8,7 +8,10 @@
namespace OC\Files\Storage;
use OCP\Files\Mount\IMountPoint;
+use OCP\Files\Storage\IConstructableStorage;
+use OCP\Files\Storage\IStorage;
use OCP\Files\Storage\IStorageFactory;
+use Psr\Log\LoggerInterface;
class StorageFactory implements IStorageFactory {
/**
@@ -16,19 +19,7 @@ class StorageFactory implements IStorageFactory {
*/
private $storageWrappers = [];
- /**
- * allow modifier storage behaviour by adding wrappers around storages
- *
- * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage
- *
- * @param string $wrapperName name of the wrapper
- * @param callable $callback callback
- * @param int $priority wrappers with the lower priority are applied last (meaning they get called first)
- * @param \OCP\Files\Mount\IMountPoint[] $existingMounts existing mount points to apply the wrapper to
- * @return bool true if the wrapper was added, false if there was already a wrapper with this
- * name registered
- */
- public function addStorageWrapper($wrapperName, $callback, $priority = 50, $existingMounts = []) {
+ public function addStorageWrapper(string $wrapperName, callable $callback, int $priority = 50, array $existingMounts = []): bool {
if (isset($this->storageWrappers[$wrapperName])) {
return false;
}
@@ -46,31 +37,23 @@ class StorageFactory implements IStorageFactory {
* Remove a storage wrapper by name.
* Note: internal method only to be used for cleanup
*
- * @param string $wrapperName name of the wrapper
* @internal
*/
- public function removeStorageWrapper($wrapperName) {
+ public function removeStorageWrapper(string $wrapperName): void {
unset($this->storageWrappers[$wrapperName]);
}
/**
* Create an instance of a storage and apply the registered storage wrappers
- *
- * @param \OCP\Files\Mount\IMountPoint $mountPoint
- * @param string $class
- * @param array $arguments
- * @return \OCP\Files\Storage
*/
- public function getInstance(IMountPoint $mountPoint, $class, $arguments) {
+ public function getInstance(IMountPoint $mountPoint, string $class, array $arguments): IStorage {
+ if (!is_a($class, IConstructableStorage::class, true)) {
+ \OCP\Server::get(LoggerInterface::class)->warning('Building a storage not implementing IConstructableStorage is deprecated since 31.0.0', ['class' => $class]);
+ }
return $this->wrap($mountPoint, new $class($arguments));
}
- /**
- * @param \OCP\Files\Mount\IMountPoint $mountPoint
- * @param \OCP\Files\Storage $storage
- * @return \OCP\Files\Storage
- */
- public function wrap(IMountPoint $mountPoint, $storage) {
+ public function wrap(IMountPoint $mountPoint, IStorage $storage): IStorage {
$wrappers = array_values($this->storageWrappers);
usort($wrappers, function ($a, $b) {
return $b['priority'] - $a['priority'];
@@ -81,7 +64,7 @@ class StorageFactory implements IStorageFactory {
}, $wrappers);
foreach ($wrappers as $wrapper) {
$storage = $wrapper($mountPoint->getMountPoint(), $storage, $mountPoint);
- if (!($storage instanceof \OCP\Files\Storage)) {
+ if (!($storage instanceof IStorage)) {
throw new \Exception('Invalid result from storage wrapper');
}
}
diff --git a/lib/private/Files/Storage/Temporary.php b/lib/private/Files/Storage/Temporary.php
index 95ae84cb7d9..ecf8a1315a9 100644
--- a/lib/private/Files/Storage/Temporary.php
+++ b/lib/private/Files/Storage/Temporary.php
@@ -7,16 +7,20 @@
*/
namespace OC\Files\Storage;
+use OCP\Files;
+use OCP\ITempManager;
+use OCP\Server;
+
/**
* local storage backend in temporary folder for testing purpose
*/
class Temporary extends Local {
- public function __construct($arguments = null) {
- parent::__construct(['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]);
+ public function __construct(array $parameters = []) {
+ parent::__construct(['datadir' => Server::get(ITempManager::class)->getTemporaryFolder()]);
}
- public function cleanUp() {
- \OC_Helper::rmdirr($this->datadir);
+ public function cleanUp(): void {
+ Files::rmdirr($this->datadir);
}
public function __destruct() {
@@ -24,7 +28,7 @@ class Temporary extends Local {
$this->cleanUp();
}
- public function getDataDir() {
+ public function getDataDir(): array|string {
return $this->datadir;
}
}
diff --git a/lib/private/Files/Storage/Wrapper/Availability.php b/lib/private/Files/Storage/Wrapper/Availability.php
index 149cbbaa33b..32c51a1b25e 100644
--- a/lib/private/Files/Storage/Wrapper/Availability.php
+++ b/lib/private/Files/Storage/Wrapper/Availability.php
@@ -23,12 +23,12 @@ class Availability extends Wrapper {
/** @var IConfig */
protected $config;
- public function __construct($parameters) {
- $this->config = $parameters['config'] ?? \OC::$server->getConfig();
+ public function __construct(array $parameters) {
+ $this->config = $parameters['config'] ?? \OCP\Server::get(IConfig::class);
parent::__construct($parameters);
}
- public static function shouldRecheck($availability) {
+ public static function shouldRecheck($availability): bool {
if (!$availability['available']) {
// trigger a recheck if TTL reached
if ((time() - $availability['last_checked']) > self::RECHECK_TTL_SEC) {
@@ -40,10 +40,8 @@ class Availability extends Wrapper {
/**
* Only called if availability === false
- *
- * @return bool
*/
- private function updateAvailability() {
+ private function updateAvailability(): bool {
// reset availability to false so that multiple requests don't recheck concurrently
$this->setAvailability(false);
try {
@@ -55,10 +53,7 @@ class Availability extends Wrapper {
return $result;
}
- /**
- * @return bool
- */
- private function isAvailable() {
+ private function isAvailable(): bool {
$availability = $this->getAvailability();
if (self::shouldRecheck($availability)) {
return $this->updateAvailability();
@@ -69,297 +64,137 @@ class Availability extends Wrapper {
/**
* @throws StorageNotAvailableException
*/
- private function checkAvailability() {
+ private function checkAvailability(): void {
if (!$this->isAvailable()) {
throw new StorageNotAvailableException();
}
}
- /** {@inheritdoc} */
- public function mkdir($path) {
+ /**
+ * Handles availability checks and delegates method calls dynamically
+ */
+ private function handleAvailability(string $method, mixed ...$args): mixed {
$this->checkAvailability();
try {
- return parent::mkdir($path);
+ return call_user_func_array([parent::class, $method], $args);
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
+ return false;
}
}
- /** {@inheritdoc} */
- public function rmdir($path) {
- $this->checkAvailability();
- try {
- return parent::rmdir($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function mkdir(string $path): bool {
+ return $this->handleAvailability('mkdir', $path);
}
- /** {@inheritdoc} */
- public function opendir($path) {
- $this->checkAvailability();
- try {
- return parent::opendir($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function rmdir(string $path): bool {
+ return $this->handleAvailability('rmdir', $path);
}
- /** {@inheritdoc} */
- public function is_dir($path) {
- $this->checkAvailability();
- try {
- return parent::is_dir($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function opendir(string $path) {
+ return $this->handleAvailability('opendir', $path);
}
- /** {@inheritdoc} */
- public function is_file($path) {
- $this->checkAvailability();
- try {
- return parent::is_file($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function is_dir(string $path): bool {
+ return $this->handleAvailability('is_dir', $path);
}
- /** {@inheritdoc} */
- public function stat($path) {
- $this->checkAvailability();
- try {
- return parent::stat($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function is_file(string $path): bool {
+ return $this->handleAvailability('is_file', $path);
}
- /** {@inheritdoc} */
- public function filetype($path) {
- $this->checkAvailability();
- try {
- return parent::filetype($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function stat(string $path): array|false {
+ return $this->handleAvailability('stat', $path);
}
- /** {@inheritdoc} */
- public function filesize($path): false|int|float {
- $this->checkAvailability();
- try {
- return parent::filesize($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function filetype(string $path): string|false {
+ return $this->handleAvailability('filetype', $path);
}
- /** {@inheritdoc} */
- public function isCreatable($path) {
- $this->checkAvailability();
- try {
- return parent::isCreatable($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function filesize(string $path): int|float|false {
+ return $this->handleAvailability('filesize', $path);
}
- /** {@inheritdoc} */
- public function isReadable($path) {
- $this->checkAvailability();
- try {
- return parent::isReadable($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function isCreatable(string $path): bool {
+ return $this->handleAvailability('isCreatable', $path);
}
- /** {@inheritdoc} */
- public function isUpdatable($path) {
- $this->checkAvailability();
- try {
- return parent::isUpdatable($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function isReadable(string $path): bool {
+ return $this->handleAvailability('isReadable', $path);
}
- /** {@inheritdoc} */
- public function isDeletable($path) {
- $this->checkAvailability();
- try {
- return parent::isDeletable($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function isUpdatable(string $path): bool {
+ return $this->handleAvailability('isUpdatable', $path);
}
- /** {@inheritdoc} */
- public function isSharable($path) {
- $this->checkAvailability();
- try {
- return parent::isSharable($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function isDeletable(string $path): bool {
+ return $this->handleAvailability('isDeletable', $path);
}
- /** {@inheritdoc} */
- public function getPermissions($path) {
- $this->checkAvailability();
- try {
- return parent::getPermissions($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function isSharable(string $path): bool {
+ return $this->handleAvailability('isSharable', $path);
}
- /** {@inheritdoc} */
- public function file_exists($path) {
- if ($path === '') {
- return true;
- }
- $this->checkAvailability();
- try {
- return parent::file_exists($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function getPermissions(string $path): int {
+ return $this->handleAvailability('getPermissions', $path);
}
- /** {@inheritdoc} */
- public function filemtime($path) {
- $this->checkAvailability();
- try {
- return parent::filemtime($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
+ public function file_exists(string $path): bool {
+ if ($path === '') {
+ return true;
}
+ return $this->handleAvailability('file_exists', $path);
}
- /** {@inheritdoc} */
- public function file_get_contents($path) {
- $this->checkAvailability();
- try {
- return parent::file_get_contents($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function filemtime(string $path): int|false {
+ return $this->handleAvailability('filemtime', $path);
}
- /** {@inheritdoc} */
- public function file_put_contents($path, $data) {
- $this->checkAvailability();
- try {
- return parent::file_put_contents($path, $data);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function file_get_contents(string $path): string|false {
+ return $this->handleAvailability('file_get_contents', $path);
}
- /** {@inheritdoc} */
- public function unlink($path) {
- $this->checkAvailability();
- try {
- return parent::unlink($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function file_put_contents(string $path, mixed $data): int|float|false {
+ return $this->handleAvailability('file_put_contents', $path, $data);
}
- /** {@inheritdoc} */
- public function rename($source, $target) {
- $this->checkAvailability();
- try {
- return parent::rename($source, $target);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function unlink(string $path): bool {
+ return $this->handleAvailability('unlink', $path);
}
- /** {@inheritdoc} */
- public function copy($source, $target) {
- $this->checkAvailability();
- try {
- return parent::copy($source, $target);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function rename(string $source, string $target): bool {
+ return $this->handleAvailability('rename', $source, $target);
}
- /** {@inheritdoc} */
- public function fopen($path, $mode) {
- $this->checkAvailability();
- try {
- return parent::fopen($path, $mode);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function copy(string $source, string $target): bool {
+ return $this->handleAvailability('copy', $source, $target);
}
- /** {@inheritdoc} */
- public function getMimeType($path) {
- $this->checkAvailability();
- try {
- return parent::getMimeType($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function fopen(string $path, string $mode) {
+ return $this->handleAvailability('fopen', $path, $mode);
}
- /** {@inheritdoc} */
- public function hash($type, $path, $raw = false) {
- $this->checkAvailability();
- try {
- return parent::hash($type, $path, $raw);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function getMimeType(string $path): string|false {
+ return $this->handleAvailability('getMimeType', $path);
}
- /** {@inheritdoc} */
- public function free_space($path) {
- $this->checkAvailability();
- try {
- return parent::free_space($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function hash(string $type, string $path, bool $raw = false): string|false {
+ return $this->handleAvailability('hash', $type, $path, $raw);
}
- /** {@inheritdoc} */
- public function search($query) {
- $this->checkAvailability();
- try {
- return parent::search($query);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function free_space(string $path): int|float|false {
+ return $this->handleAvailability('free_space', $path);
}
- /** {@inheritdoc} */
- public function touch($path, $mtime = null) {
- $this->checkAvailability();
- try {
- return parent::touch($path, $mtime);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function touch(string $path, ?int $mtime = null): bool {
+ return $this->handleAvailability('touch', $path, $mtime);
}
- /** {@inheritdoc} */
- public function getLocalFile($path) {
- $this->checkAvailability();
- try {
- return parent::getLocalFile($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function getLocalFile(string $path): string|false {
+ return $this->handleAvailability('getLocalFile', $path);
}
- /** {@inheritdoc} */
- public function hasUpdated($path, $time) {
+ public function hasUpdated(string $path, int $time): bool {
if (!$this->isAvailable()) {
return false;
}
@@ -372,62 +207,38 @@ class Availability extends Wrapper {
}
}
- /** {@inheritdoc} */
- public function getOwner($path) {
+ public function getOwner(string $path): string|false {
try {
return parent::getOwner($path);
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
+ return false;
}
}
- /** {@inheritdoc} */
- public function getETag($path) {
- $this->checkAvailability();
- try {
- return parent::getETag($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function getETag(string $path): string|false {
+ return $this->handleAvailability('getETag', $path);
}
- /** {@inheritdoc} */
- public function getDirectDownload($path) {
- $this->checkAvailability();
- try {
- return parent::getDirectDownload($path);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function getDirectDownload(string $path): array|false {
+ return $this->handleAvailability('getDirectDownload', $path);
}
- /** {@inheritdoc} */
- public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
- $this->checkAvailability();
- try {
- return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
+ return $this->handleAvailability('copyFromStorage', $sourceStorage, $sourceInternalPath, $targetInternalPath);
}
- /** {@inheritdoc} */
- public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
- $this->checkAvailability();
- try {
- return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
- } catch (StorageNotAvailableException $e) {
- $this->setUnavailable($e);
- }
+ public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
+ return $this->handleAvailability('moveFromStorage', $sourceStorage, $sourceInternalPath, $targetInternalPath);
}
- /** {@inheritdoc} */
- public function getMetaData($path) {
+ public function getMetaData(string $path): ?array {
$this->checkAvailability();
try {
return parent::getMetaData($path);
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
+ return null;
}
}
@@ -454,12 +265,13 @@ class Availability extends Wrapper {
- public function getDirectoryContent($directory): \Traversable {
+ public function getDirectoryContent(string $directory): \Traversable {
$this->checkAvailability();
try {
return parent::getDirectoryContent($directory);
} catch (StorageNotAvailableException $e) {
$this->setUnavailable($e);
+ return new \EmptyIterator();
}
}
}
diff --git a/lib/private/Files/Storage/Wrapper/Encoding.php b/lib/private/Files/Storage/Wrapper/Encoding.php
index 11f21eb828e..92e20cfb3df 100644
--- a/lib/private/Files/Storage/Wrapper/Encoding.php
+++ b/lib/private/Files/Storage/Wrapper/Encoding.php
@@ -9,6 +9,7 @@ namespace OC\Files\Storage\Wrapper;
use OC\Files\Filesystem;
use OCP\Cache\CappedMemoryCache;
+use OCP\Files\Cache\IScanner;
use OCP\Files\Storage\IStorage;
use OCP\ICache;
@@ -27,19 +28,15 @@ class Encoding extends Wrapper {
/**
* @param array $parameters
*/
- public function __construct($parameters) {
+ public function __construct(array $parameters) {
$this->storage = $parameters['storage'];
$this->namesCache = new CappedMemoryCache();
}
/**
* Returns whether the given string is only made of ASCII characters
- *
- * @param string $str string
- *
- * @return bool true if the string is all ASCII, false otherwise
*/
- private function isAscii($str) {
+ private function isAscii(string $str): bool {
return !preg_match('/[\\x80-\\xff]+/', $str);
}
@@ -48,11 +45,9 @@ class Encoding extends Wrapper {
* each form for each path section and returns the correct form.
* If no existing path found, returns the path as it was given.
*
- * @param string $fullPath path to check
- *
* @return string original or converted path
*/
- private function findPathToUse($fullPath) {
+ private function findPathToUse(string $fullPath): string {
$cachedPath = $this->namesCache[$fullPath];
if ($cachedPath !== null) {
return $cachedPath;
@@ -76,12 +71,11 @@ class Encoding extends Wrapper {
* Checks whether the last path section of the given path exists in NFC or NFD form
* and returns the correct form. If no existing path found, returns null.
*
- * @param string $basePath base path to check
* @param string $lastSection last section of the path to check for NFD/NFC variations
*
* @return string|null original or converted path, or null if none of the forms was found
*/
- private function findPathToUseLastSection($basePath, $lastSection) {
+ private function findPathToUseLastSection(string $basePath, string $lastSection): ?string {
$fullPath = $basePath . $lastSection;
if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) {
$this->namesCache[$fullPath] = $fullPath;
@@ -105,13 +99,7 @@ class Encoding extends Wrapper {
return null;
}
- /**
- * see https://www.php.net/manual/en/function.mkdir.php
- *
- * @param string $path
- * @return bool
- */
- public function mkdir($path) {
+ public function mkdir(string $path): bool {
// note: no conversion here, method should not be called with non-NFC names!
$result = $this->storage->mkdir($path);
if ($result) {
@@ -120,13 +108,7 @@ class Encoding extends Wrapper {
return $result;
}
- /**
- * see https://www.php.net/manual/en/function.rmdir.php
- *
- * @param string $path
- * @return bool
- */
- public function rmdir($path) {
+ public function rmdir(string $path): bool {
$result = $this->storage->rmdir($this->findPathToUse($path));
if ($result) {
unset($this->namesCache[$path]);
@@ -134,175 +116,72 @@ class Encoding extends Wrapper {
return $result;
}
- /**
- * see https://www.php.net/manual/en/function.opendir.php
- *
- * @param string $path
- * @return resource|false
- */
- public function opendir($path) {
+ public function opendir(string $path) {
$handle = $this->storage->opendir($this->findPathToUse($path));
return EncodingDirectoryWrapper::wrap($handle);
}
- /**
- * see https://www.php.net/manual/en/function.is_dir.php
- *
- * @param string $path
- * @return bool
- */
- public function is_dir($path) {
+ public function is_dir(string $path): bool {
return $this->storage->is_dir($this->findPathToUse($path));
}
- /**
- * see https://www.php.net/manual/en/function.is_file.php
- *
- * @param string $path
- * @return bool
- */
- public function is_file($path) {
+ public function is_file(string $path): bool {
return $this->storage->is_file($this->findPathToUse($path));
}
- /**
- * see https://www.php.net/manual/en/function.stat.php
- * only the following keys are required in the result: size and mtime
- *
- * @param string $path
- * @return array|bool
- */
- public function stat($path) {
+ public function stat(string $path): array|false {
return $this->storage->stat($this->findPathToUse($path));
}
- /**
- * see https://www.php.net/manual/en/function.filetype.php
- *
- * @param string $path
- * @return string|bool
- */
- public function filetype($path) {
+ public function filetype(string $path): string|false {
return $this->storage->filetype($this->findPathToUse($path));
}
- /**
- * see https://www.php.net/manual/en/function.filesize.php
- * The result for filesize when called on a folder is required to be 0
- */
- public function filesize($path): false|int|float {
+ public function filesize(string $path): int|float|false {
return $this->storage->filesize($this->findPathToUse($path));
}
- /**
- * check if a file can be created in $path
- *
- * @param string $path
- * @return bool
- */
- public function isCreatable($path) {
+ public function isCreatable(string $path): bool {
return $this->storage->isCreatable($this->findPathToUse($path));
}
- /**
- * check if a file can be read
- *
- * @param string $path
- * @return bool
- */
- public function isReadable($path) {
+ public function isReadable(string $path): bool {
return $this->storage->isReadable($this->findPathToUse($path));
}
- /**
- * check if a file can be written to
- *
- * @param string $path
- * @return bool
- */
- public function isUpdatable($path) {
+ public function isUpdatable(string $path): bool {
return $this->storage->isUpdatable($this->findPathToUse($path));
}
- /**
- * check if a file can be deleted
- *
- * @param string $path
- * @return bool
- */
- public function isDeletable($path) {
+ public function isDeletable(string $path): bool {
return $this->storage->isDeletable($this->findPathToUse($path));
}
- /**
- * check if a file can be shared
- *
- * @param string $path
- * @return bool
- */
- public function isSharable($path) {
+ public function isSharable(string $path): bool {
return $this->storage->isSharable($this->findPathToUse($path));
}
- /**
- * get the full permissions of a path.
- * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
- *
- * @param string $path
- * @return int
- */
- public function getPermissions($path) {
+ public function getPermissions(string $path): int {
return $this->storage->getPermissions($this->findPathToUse($path));
}
- /**
- * see https://www.php.net/manual/en/function.file_exists.php
- *
- * @param string $path
- * @return bool
- */
- public function file_exists($path) {
+ public function file_exists(string $path): bool {
return $this->storage->file_exists($this->findPathToUse($path));
}
- /**
- * see https://www.php.net/manual/en/function.filemtime.php
- *
- * @param string $path
- * @return int|bool
- */
- public function filemtime($path) {
+ public function filemtime(string $path): int|false {
return $this->storage->filemtime($this->findPathToUse($path));
}
- /**
- * see https://www.php.net/manual/en/function.file_get_contents.php
- *
- * @param string $path
- * @return string|false
- */
- public function file_get_contents($path) {
+ public function file_get_contents(string $path): string|false {
return $this->storage->file_get_contents($this->findPathToUse($path));
}
- /**
- * see https://www.php.net/manual/en/function.file_put_contents.php
- *
- * @param string $path
- * @param mixed $data
- * @return int|float|false
- */
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): int|float|false {
return $this->storage->file_put_contents($this->findPathToUse($path), $data);
}
- /**
- * see https://www.php.net/manual/en/function.unlink.php
- *
- * @param string $path
- * @return bool
- */
- public function unlink($path) {
+ public function unlink(string $path): bool {
$result = $this->storage->unlink($this->findPathToUse($path));
if ($result) {
unset($this->namesCache[$path]);
@@ -310,37 +189,16 @@ class Encoding extends Wrapper {
return $result;
}
- /**
- * see https://www.php.net/manual/en/function.rename.php
- *
- * @param string $source
- * @param string $target
- * @return bool
- */
- public function rename($source, $target) {
+ public function rename(string $source, string $target): bool {
// second name always NFC
return $this->storage->rename($this->findPathToUse($source), $this->findPathToUse($target));
}
- /**
- * see https://www.php.net/manual/en/function.copy.php
- *
- * @param string $source
- * @param string $target
- * @return bool
- */
- public function copy($source, $target) {
+ public function copy(string $source, string $target): bool {
return $this->storage->copy($this->findPathToUse($source), $this->findPathToUse($target));
}
- /**
- * see https://www.php.net/manual/en/function.fopen.php
- *
- * @param string $path
- * @param string $mode
- * @return resource|bool
- */
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
$result = $this->storage->fopen($this->findPathToUse($path), $mode);
if ($result && $mode !== 'r' && $mode !== 'rb') {
unset($this->namesCache[$path]);
@@ -348,131 +206,49 @@ class Encoding extends Wrapper {
return $result;
}
- /**
- * get the mimetype for a file or folder
- * The mimetype for a folder is required to be "httpd/unix-directory"
- *
- * @param string $path
- * @return string|bool
- */
- public function getMimeType($path) {
+ public function getMimeType(string $path): string|false {
return $this->storage->getMimeType($this->findPathToUse($path));
}
- /**
- * see https://www.php.net/manual/en/function.hash.php
- *
- * @param string $type
- * @param string $path
- * @param bool $raw
- * @return string|bool
- */
- public function hash($type, $path, $raw = false) {
+ public function hash(string $type, string $path, bool $raw = false): string|false {
return $this->storage->hash($type, $this->findPathToUse($path), $raw);
}
- /**
- * see https://www.php.net/manual/en/function.free_space.php
- *
- * @param string $path
- * @return int|float|bool
- */
- public function free_space($path) {
+ public function free_space(string $path): int|float|false {
return $this->storage->free_space($this->findPathToUse($path));
}
- /**
- * search for occurrences of $query in file names
- *
- * @param string $query
- * @return array|bool
- */
- public function search($query) {
- return $this->storage->search($query);
- }
-
- /**
- * see https://www.php.net/manual/en/function.touch.php
- * If the backend does not support the operation, false should be returned
- *
- * @param string $path
- * @param int $mtime
- * @return bool
- */
- public function touch($path, $mtime = null) {
+ public function touch(string $path, ?int $mtime = null): bool {
return $this->storage->touch($this->findPathToUse($path), $mtime);
}
- /**
- * get the path to a local version of the file.
- * The local version of the file can be temporary and doesn't have to be persistent across requests
- *
- * @param string $path
- * @return string|false
- */
- public function getLocalFile($path) {
+ public function getLocalFile(string $path): string|false {
return $this->storage->getLocalFile($this->findPathToUse($path));
}
- /**
- * check if a file or folder has been updated since $time
- *
- * @param string $path
- * @param int $time
- * @return bool
- *
- * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
- * returning true for other changes in the folder is optional
- */
- public function hasUpdated($path, $time) {
+ public function hasUpdated(string $path, int $time): bool {
return $this->storage->hasUpdated($this->findPathToUse($path), $time);
}
- /**
- * get a cache instance for the storage
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
- * @return \OC\Files\Cache\Cache
- */
- public function getCache($path = '', $storage = null) {
+ public function getCache(string $path = '', ?IStorage $storage = null): \OCP\Files\Cache\ICache {
if (!$storage) {
$storage = $this;
}
return $this->storage->getCache($this->findPathToUse($path), $storage);
}
- /**
- * get a scanner instance for the storage
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
- * @return \OC\Files\Cache\Scanner
- */
- public function getScanner($path = '', $storage = null) {
+ public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
if (!$storage) {
$storage = $this;
}
return $this->storage->getScanner($this->findPathToUse($path), $storage);
}
- /**
- * get the ETag for a file or folder
- *
- * @param string $path
- * @return string|false
- */
- public function getETag($path) {
+ public function getETag(string $path): string|false {
return $this->storage->getETag($this->findPathToUse($path));
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @return bool
- */
- public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
if ($sourceStorage === $this) {
return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath));
}
@@ -484,13 +260,7 @@ class Encoding extends Wrapper {
return $result;
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @return bool
- */
- public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
if ($sourceStorage === $this) {
$result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath));
if ($result) {
@@ -508,13 +278,15 @@ class Encoding extends Wrapper {
return $result;
}
- public function getMetaData($path) {
+ public function getMetaData(string $path): ?array {
$entry = $this->storage->getMetaData($this->findPathToUse($path));
- $entry['name'] = trim(Filesystem::normalizePath($entry['name']), '/');
+ if ($entry !== null) {
+ $entry['name'] = trim(Filesystem::normalizePath($entry['name']), '/');
+ }
return $entry;
}
- public function getDirectoryContent($directory): \Traversable {
+ public function getDirectoryContent(string $directory): \Traversable {
$entries = $this->storage->getDirectoryContent($this->findPathToUse($directory));
foreach ($entries as $entry) {
$entry['name'] = trim(Filesystem::normalizePath($entry['name']), '/');
diff --git a/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php b/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php
index 26b32d9aa62..0a90b49f0f1 100644
--- a/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php
+++ b/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php
@@ -13,11 +13,7 @@ use OC\Files\Filesystem;
* Normalize file names while reading directory entries
*/
class EncodingDirectoryWrapper extends DirectoryWrapper {
- /**
- * @psalm-suppress ImplementedReturnTypeMismatch Until return type is fixed upstream
- * @return string|false
- */
- public function dir_readdir() {
+ public function dir_readdir(): string|false {
$file = readdir($this->source);
if ($file !== false && $file !== '.' && $file !== '..') {
$file = trim(Filesystem::normalizePath($file), '/');
@@ -28,7 +24,6 @@ class EncodingDirectoryWrapper extends DirectoryWrapper {
/**
* @param resource $source
- * @param callable $filter
* @return resource|false
*/
public static function wrap($source) {
diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php
index 5c7862e8226..58bd4dfddcf 100644
--- a/lib/private/Files/Storage/Wrapper/Encryption.php
+++ b/lib/private/Files/Storage/Wrapper/Encryption.php
@@ -8,20 +8,22 @@
namespace OC\Files\Storage\Wrapper;
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
-use OC\Encryption\Update;
use OC\Encryption\Util;
use OC\Files\Cache\CacheEntry;
use OC\Files\Filesystem;
use OC\Files\Mount\Manager;
use OC\Files\ObjectStore\ObjectStoreStorage;
+use OC\Files\Storage\Common;
use OC\Files\Storage\LocalTempFileTrait;
use OC\Memcache\ArrayCache;
use OCP\Cache\CappedMemoryCache;
-use OCP\Encryption\Exceptions\GenericEncryptionException;
+use OCP\Encryption\Exceptions\InvalidHeaderException;
use OCP\Encryption\IFile;
use OCP\Encryption\IManager;
use OCP\Encryption\Keys\IStorage;
+use OCP\Files;
use OCP\Files\Cache\ICacheEntry;
+use OCP\Files\GenericFileException;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\Storage;
use Psr\Log\LoggerInterface;
@@ -29,90 +31,42 @@ use Psr\Log\LoggerInterface;
class Encryption extends Wrapper {
use LocalTempFileTrait;
- /** @var string */
- private $mountPoint;
-
- /** @var \OC\Encryption\Util */
- private $util;
-
- /** @var \OCP\Encryption\IManager */
- private $encryptionManager;
-
- private LoggerInterface $logger;
-
- /** @var string */
- private $uid;
-
- /** @var array */
- protected $unencryptedSize;
-
- /** @var \OCP\Encryption\IFile */
- private $fileHelper;
-
- /** @var IMountPoint */
- private $mount;
-
- /** @var IStorage */
- private $keyStorage;
-
- /** @var Update */
- private $update;
-
- /** @var Manager */
- private $mountManager;
-
- /** @var array remember for which path we execute the repair step to avoid recursions */
- private $fixUnencryptedSizeOf = [];
-
- /** @var ArrayCache */
- private $arrayCache;
-
+ private string $mountPoint;
+ protected array $unencryptedSize = [];
+ private IMountPoint $mount;
+ /** for which path we execute the repair step to avoid recursions */
+ private array $fixUnencryptedSizeOf = [];
/** @var CappedMemoryCache<bool> */
private CappedMemoryCache $encryptedPaths;
-
- private $enabled = true;
+ private bool $enabled = true;
/**
* @param array $parameters
*/
public function __construct(
- $parameters,
- ?IManager $encryptionManager = null,
- ?Util $util = null,
- ?LoggerInterface $logger = null,
- ?IFile $fileHelper = null,
- $uid = null,
- ?IStorage $keyStorage = null,
- ?Update $update = null,
- ?Manager $mountManager = null,
- ?ArrayCache $arrayCache = null
+ array $parameters,
+ private IManager $encryptionManager,
+ private Util $util,
+ private LoggerInterface $logger,
+ private IFile $fileHelper,
+ private ?string $uid,
+ private IStorage $keyStorage,
+ private Manager $mountManager,
+ private ArrayCache $arrayCache,
) {
$this->mountPoint = $parameters['mountPoint'];
$this->mount = $parameters['mount'];
- $this->encryptionManager = $encryptionManager;
- $this->util = $util;
- $this->logger = $logger;
- $this->uid = $uid;
- $this->fileHelper = $fileHelper;
- $this->keyStorage = $keyStorage;
- $this->unencryptedSize = [];
- $this->update = $update;
- $this->mountManager = $mountManager;
- $this->arrayCache = $arrayCache;
$this->encryptedPaths = new CappedMemoryCache();
parent::__construct($parameters);
}
- /**
- * see https://www.php.net/manual/en/function.filesize.php
- * The result for filesize when called on a folder is required to be 0
- */
- public function filesize($path): false|int|float {
+ public function filesize(string $path): int|float|false {
$fullPath = $this->getFullPath($path);
$info = $this->getCache()->get($path);
if ($info === false) {
- return false;
+ /* Pass call to wrapped storage, it may be a special file like a part file */
+ return $this->storage->filesize($path);
}
if (isset($this->unencryptedSize[$fullPath])) {
$size = $this->unencryptedSize[$fullPath];
@@ -150,11 +104,6 @@ class Encryption extends Wrapper {
return $this->storage->filesize($path);
}
- /**
- * @param string $path
- * @param array $data
- * @return array
- */
private function modifyMetaData(string $path, array $data): array {
$fullPath = $this->getFullPath($path);
$info = $this->getCache()->get($path);
@@ -178,7 +127,7 @@ class Encryption extends Wrapper {
return $data;
}
- public function getMetaData($path) {
+ public function getMetaData(string $path): ?array {
$data = $this->storage->getMetaData($path);
if (is_null($data)) {
return null;
@@ -186,24 +135,18 @@ class Encryption extends Wrapper {
return $this->modifyMetaData($path, $data);
}
- public function getDirectoryContent($directory): \Traversable {
+ public function getDirectoryContent(string $directory): \Traversable {
$parent = rtrim($directory, '/');
foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) {
yield $this->modifyMetaData($parent . '/' . $data['name'], $data);
}
}
- /**
- * see https://www.php.net/manual/en/function.file_get_contents.php
- *
- * @param string $path
- * @return string|false
- */
- public function file_get_contents($path) {
+ public function file_get_contents(string $path): string|false {
$encryptionModule = $this->getEncryptionModule($path);
if ($encryptionModule) {
- $handle = $this->fopen($path, "r");
+ $handle = $this->fopen($path, 'r');
if (!$handle) {
return false;
}
@@ -214,14 +157,7 @@ class Encryption extends Wrapper {
return $this->storage->file_get_contents($path);
}
- /**
- * see https://www.php.net/manual/en/function.file_put_contents.php
- *
- * @param string $path
- * @param mixed $data
- * @return int|false
- */
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): int|float|false {
// file put content will always be translated to a stream write
$handle = $this->fopen($path, 'w');
if (is_resource($handle)) {
@@ -233,13 +169,7 @@ class Encryption extends Wrapper {
return false;
}
- /**
- * see https://www.php.net/manual/en/function.unlink.php
- *
- * @param string $path
- * @return bool
- */
- public function unlink($path) {
+ public function unlink(string $path): bool {
$fullPath = $this->getFullPath($path);
if ($this->util->isExcluded($fullPath)) {
return $this->storage->unlink($path);
@@ -253,21 +183,14 @@ class Encryption extends Wrapper {
return $this->storage->unlink($path);
}
- /**
- * see https://www.php.net/manual/en/function.rename.php
- *
- * @param string $source
- * @param string $target
- * @return bool
- */
- public function rename($source, $target) {
+ public function rename(string $source, string $target): bool {
$result = $this->storage->rename($source, $target);
- if ($result &&
+ if ($result
// versions always use the keys from the original file, so we can skip
// this step for versions
- $this->isVersion($target) === false &&
- $this->encryptionManager->isEnabled()) {
+ && $this->isVersion($target) === false
+ && $this->encryptionManager->isEnabled()) {
$sourcePath = $this->getFullPath($source);
if (!$this->util->isExcluded($sourcePath)) {
$targetPath = $this->getFullPath($target);
@@ -285,18 +208,12 @@ class Encryption extends Wrapper {
return $result;
}
- /**
- * see https://www.php.net/manual/en/function.rmdir.php
- *
- * @param string $path
- * @return bool
- */
- public function rmdir($path) {
+ public function rmdir(string $path): bool {
$result = $this->storage->rmdir($path);
$fullPath = $this->getFullPath($path);
- if ($result &&
- $this->util->isExcluded($fullPath) === false &&
- $this->encryptionManager->isEnabled()
+ if ($result
+ && $this->util->isExcluded($fullPath) === false
+ && $this->encryptionManager->isEnabled()
) {
$this->keyStorage->deleteAllFileKeys($fullPath);
}
@@ -304,20 +221,14 @@ class Encryption extends Wrapper {
return $result;
}
- /**
- * check if a file can be read
- *
- * @param string $path
- * @return bool
- */
- public function isReadable($path) {
+ public function isReadable(string $path): bool {
$isReadable = true;
$metaData = $this->getMetaData($path);
if (
- !$this->is_dir($path) &&
- isset($metaData['encrypted']) &&
- $metaData['encrypted'] === true
+ !$this->is_dir($path)
+ && isset($metaData['encrypted'])
+ && $metaData['encrypted'] === true
) {
$fullPath = $this->getFullPath($path);
$module = $this->getEncryptionModule($path);
@@ -327,13 +238,7 @@ class Encryption extends Wrapper {
return $this->storage->isReadable($path) && $isReadable;
}
- /**
- * see https://www.php.net/manual/en/function.copy.php
- *
- * @param string $source
- * @param string $target
- */
- public function copy($source, $target): bool {
+ public function copy(string $source, string $target): bool {
$sourcePath = $this->getFullPath($source);
if ($this->util->isExcluded($sourcePath)) {
@@ -346,16 +251,7 @@ class Encryption extends Wrapper {
return $this->copyFromStorage($this, $source, $target);
}
- /**
- * see https://www.php.net/manual/en/function.fopen.php
- *
- * @param string $path
- * @param string $mode
- * @return resource|bool
- * @throws GenericEncryptionException
- * @throws ModuleDoesNotExistsException
- */
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
// check if the file is stored in the array cache, this means that we
// copy a file over to the versions folder, in this case we don't want to
// decrypt it
@@ -408,10 +304,8 @@ class Encryption extends Wrapper {
// if we update a encrypted file with a un-encrypted one we change the db flag
if ($targetIsEncrypted && $encryptionEnabled === false) {
$cache = $this->storage->getCache();
- if ($cache) {
- $entry = $cache->get($path);
- $cache->update($entry->getId(), ['encrypted' => 0]);
- }
+ $entry = $cache->get($path);
+ $cache->update($entry->getId(), ['encrypted' => 0]);
}
if ($encryptionEnabled) {
// if $encryptionModuleId is empty, the default module will be used
@@ -426,7 +320,7 @@ class Encryption extends Wrapper {
if (!empty($encryptionModuleId)) {
$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
$shouldEncrypt = true;
- } elseif (empty($encryptionModuleId) && $info['encrypted'] === true) {
+ } elseif ($info !== false && $info['encrypted'] === true) {
// we come from a old installation. No header and/or no module defined
// but the file is encrypted. In this case we need to use the
// OC_DEFAULT_MODULE to read the file
@@ -452,6 +346,16 @@ class Encryption extends Wrapper {
if ($shouldEncrypt === true && $encryptionModule !== null) {
$this->encryptedPaths->set($this->util->stripPartialFileExtension($path), true);
$headerSize = $this->getHeaderSize($path);
+ if ($mode === 'r' && $headerSize === 0) {
+ $firstBlock = $this->readFirstBlock($path);
+ if (!$firstBlock) {
+ throw new InvalidHeaderException("Unable to get header block for $path");
+ } elseif (!str_starts_with($firstBlock, Util::HEADER_START)) {
+ throw new InvalidHeaderException("Unable to get header size for $path, file doesn't start with encryption header");
+ } else {
+ throw new InvalidHeaderException("Unable to get header size for $path, even though file does start with encryption header");
+ }
+ }
$source = $this->storage->fopen($path, $mode);
if (!is_resource($source)) {
return false;
@@ -469,7 +373,7 @@ class Encryption extends Wrapper {
/**
- * perform some plausibility checks if the the unencrypted size is correct.
+ * perform some plausibility checks if the unencrypted size is correct.
* If not, we calculate the correct unencrypted size and return it
*
* @param string $path internal path relative to the storage root
@@ -481,9 +385,9 @@ class Encryption extends Wrapper {
$size = $this->storage->filesize($path);
$result = $unencryptedSize;
- if ($unencryptedSize < 0 ||
- ($size > 0 && $unencryptedSize === $size) ||
- $unencryptedSize > $size
+ if ($unencryptedSize < 0
+ || ($size > 0 && $unencryptedSize === $size)
+ || $unencryptedSize > $size
) {
// check if we already calculate the unencrypted size for the
// given path to avoid recursions
@@ -507,10 +411,8 @@ class Encryption extends Wrapper {
* @param string $path internal path relative to the storage root
* @param int $size size of the physical file
* @param int $unencryptedSize size of the unencrypted file
- *
- * @return int calculated unencrypted size
*/
- protected function fixUnencryptedSize(string $path, int $size, int $unencryptedSize): int {
+ protected function fixUnencryptedSize(string $path, int $size, int $unencryptedSize): int|float {
$headerSize = $this->getHeaderSize($path);
$header = $this->getHeader($path);
$encryptionModule = $this->getEncryptionModule($path);
@@ -579,12 +481,10 @@ class Encryption extends Wrapper {
// write to cache if applicable
$cache = $this->storage->getCache();
- if ($cache) {
- $entry = $cache->get($path);
- $cache->update($entry['fileid'], [
- 'unencrypted_size' => $newUnencryptedSize
- ]);
- }
+ $entry = $cache->get($path);
+ $cache->update($entry['fileid'], [
+ 'unencrypted_size' => $newUnencryptedSize
+ ]);
return $newUnencryptedSize;
}
@@ -598,7 +498,7 @@ class Encryption extends Wrapper {
* This is required as stream_read only returns smaller chunks of data when the stream fetches from a
* remote storage over the internet and it does not care about the given $blockSize.
*
- * @param handle the stream to read from
+ * @param resource $handle the stream to read from
* @param int $blockSize Length of requested data block in bytes
* @return string Data fetched from stream.
*/
@@ -616,19 +516,12 @@ class Encryption extends Wrapper {
return $data;
}
- /**
- * @param Storage\IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @param bool $preserveMtime
- * @return bool
- */
public function moveFromStorage(
Storage\IStorage $sourceStorage,
- $sourceInternalPath,
- $targetInternalPath,
- $preserveMtime = true
- ) {
+ string $sourceInternalPath,
+ string $targetInternalPath,
+ $preserveMtime = true,
+ ): bool {
if ($sourceStorage === $this) {
return $this->rename($sourceInternalPath, $targetInternalPath);
}
@@ -645,31 +538,34 @@ class Encryption extends Wrapper {
$result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true);
if ($result) {
- if ($sourceStorage->is_dir($sourceInternalPath)) {
- $result &= $sourceStorage->rmdir($sourceInternalPath);
- } else {
- $result &= $sourceStorage->unlink($sourceInternalPath);
+ $setPreserveCacheOnDelete = $sourceStorage->instanceOfStorage(ObjectStoreStorage::class) && !$this->instanceOfStorage(ObjectStoreStorage::class);
+ if ($setPreserveCacheOnDelete) {
+ /** @var ObjectStoreStorage $sourceStorage */
+ $sourceStorage->setPreserveCacheOnDelete(true);
+ }
+ try {
+ if ($sourceStorage->is_dir($sourceInternalPath)) {
+ $result = $sourceStorage->rmdir($sourceInternalPath);
+ } else {
+ $result = $sourceStorage->unlink($sourceInternalPath);
+ }
+ } finally {
+ if ($setPreserveCacheOnDelete) {
+ /** @var ObjectStoreStorage $sourceStorage */
+ $sourceStorage->setPreserveCacheOnDelete(false);
+ }
}
}
return $result;
}
-
- /**
- * @param Storage\IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @param bool $preserveMtime
- * @param bool $isRename
- * @return bool
- */
public function copyFromStorage(
Storage\IStorage $sourceStorage,
- $sourceInternalPath,
- $targetInternalPath,
+ string $sourceInternalPath,
+ string $targetInternalPath,
$preserveMtime = false,
- $isRename = false
- ) {
+ $isRename = false,
+ ): bool {
// TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed:
// - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage
// - copy the file cache update from $this->copyBetweenStorage to this method
@@ -681,20 +577,14 @@ class Encryption extends Wrapper {
/**
* Update the encrypted cache version in the database
- *
- * @param Storage\IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @param bool $isRename
- * @param bool $keepEncryptionVersion
*/
private function updateEncryptedVersion(
Storage\IStorage $sourceStorage,
- $sourceInternalPath,
- $targetInternalPath,
- $isRename,
- $keepEncryptionVersion
- ) {
+ string $sourceInternalPath,
+ string $targetInternalPath,
+ bool $isRename,
+ bool $keepEncryptionVersion,
+ ): void {
$isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath);
$cacheInformation = [
'encrypted' => $isEncrypted,
@@ -734,26 +624,19 @@ class Encryption extends Wrapper {
/**
* copy file between two storages
- *
- * @param Storage\IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @param bool $preserveMtime
- * @param bool $isRename
- * @return bool
* @throws \Exception
*/
private function copyBetweenStorage(
Storage\IStorage $sourceStorage,
- $sourceInternalPath,
- $targetInternalPath,
- $preserveMtime,
- $isRename
- ) {
+ string $sourceInternalPath,
+ string $targetInternalPath,
+ bool $preserveMtime,
+ bool $isRename,
+ ): bool {
// for versions we have nothing to do, because versions should always use the
// key from the original file. Just create a 1:1 copy and done
- if ($this->isVersion($targetInternalPath) ||
- $this->isVersion($sourceInternalPath)) {
+ if ($this->isVersion($targetInternalPath)
+ || $this->isVersion($sourceInternalPath)) {
// remember that we try to create a version so that we can detect it during
// fopen($sourceInternalPath) and by-pass the encryption in order to
// create a 1:1 copy of the file
@@ -776,9 +659,8 @@ class Encryption extends Wrapper {
// first copy the keys that we reuse the existing file key on the target location
// and don't create a new one which would break versions for example.
- $mount = $this->mountManager->findByStorageId($sourceStorage->getId());
- if (count($mount) === 1) {
- $mountPoint = $mount[0]->getMountPoint();
+ if ($sourceStorage->instanceOfStorage(Common::class) && $sourceStorage->getMountOption('mount_point')) {
+ $mountPoint = $sourceStorage->getMountOption('mount_point');
$source = $mountPoint . '/' . $sourceInternalPath;
$target = $this->getFullPath($targetInternalPath);
$this->copyKeys($source, $target);
@@ -794,9 +676,9 @@ class Encryption extends Wrapper {
$result = true;
}
if (is_resource($dh)) {
- while ($result and ($file = readdir($dh)) !== false) {
+ while ($result && ($file = readdir($dh)) !== false) {
if (!Filesystem::isIgnoredDir($file)) {
- $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename);
+ $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, $preserveMtime, $isRename);
}
}
}
@@ -804,12 +686,16 @@ class Encryption extends Wrapper {
try {
$source = $sourceStorage->fopen($sourceInternalPath, 'r');
$target = $this->fopen($targetInternalPath, 'w');
- [, $result] = \OC_Helper::streamCopy($source, $target);
+ if ($source === false || $target === false) {
+ $result = false;
+ } else {
+ [, $result] = Files::streamCopy($source, $target, true);
+ }
} finally {
- if (is_resource($source)) {
+ if (isset($source) && $source !== false) {
fclose($source);
}
- if (is_resource($target)) {
+ if (isset($target) && $target !== false) {
fclose($target);
}
}
@@ -828,7 +714,7 @@ class Encryption extends Wrapper {
return (bool)$result;
}
- public function getLocalFile($path) {
+ public function getLocalFile(string $path): string|false {
if ($this->encryptionManager->isEnabled()) {
$cachedFile = $this->getCachedFile($path);
if (is_string($cachedFile)) {
@@ -838,14 +724,14 @@ class Encryption extends Wrapper {
return $this->storage->getLocalFile($path);
}
- public function isLocal() {
+ public function isLocal(): bool {
if ($this->encryptionManager->isEnabled()) {
return false;
}
return $this->storage->isLocal();
}
- public function stat($path) {
+ public function stat(string $path): array|false {
$stat = $this->storage->stat($path);
if (!$stat) {
return false;
@@ -857,8 +743,11 @@ class Encryption extends Wrapper {
return $stat;
}
- public function hash($type, $path, $raw = false) {
+ public function hash(string $type, string $path, bool $raw = false): string|false {
$fh = $this->fopen($path, 'rb');
+ if ($fh === false) {
+ return false;
+ }
$ctx = hash_init($type);
hash_update_stream($ctx, $fh);
fclose($fh);
@@ -871,21 +760,21 @@ class Encryption extends Wrapper {
* @param string $path relative to mount point
* @return string full path including mount point
*/
- protected function getFullPath($path) {
+ protected function getFullPath(string $path): string {
return Filesystem::normalizePath($this->mountPoint . '/' . $path);
}
/**
* read first block of encrypted file, typically this will contain the
* encryption header
- *
- * @param string $path
- * @return string
*/
- protected function readFirstBlock($path) {
+ protected function readFirstBlock(string $path): string {
$firstBlock = '';
if ($this->storage->is_file($path)) {
$handle = $this->storage->fopen($path, 'r');
+ if ($handle === false) {
+ return '';
+ }
$firstBlock = fread($handle, $this->util->getHeaderSize());
fclose($handle);
}
@@ -894,11 +783,8 @@ class Encryption extends Wrapper {
/**
* return header size of given file
- *
- * @param string $path
- * @return int
*/
- protected function getHeaderSize($path) {
+ protected function getHeaderSize(string $path): int {
$headerSize = 0;
$realFile = $this->util->stripPartialFileExtension($path);
if ($this->storage->is_file($realFile)) {
@@ -915,11 +801,8 @@ class Encryption extends Wrapper {
/**
* read header from file
- *
- * @param string $path
- * @return array
*/
- protected function getHeader($path) {
+ protected function getHeader(string $path): array {
$realFile = $this->util->stripPartialFileExtension($path);
$exists = $this->storage->is_file($realFile);
if ($exists) {
@@ -951,12 +834,10 @@ class Encryption extends Wrapper {
/**
* read encryption module needed to read/write the file located at $path
*
- * @param string $path
- * @return null|\OCP\Encryption\IEncryptionModule
* @throws ModuleDoesNotExistsException
* @throws \Exception
*/
- protected function getEncryptionModule($path) {
+ protected function getEncryptionModule(string $path): ?\OCP\Encryption\IEncryptionModule {
$encryptionModule = null;
$header = $this->getHeader($path);
$encryptionModuleId = $this->util->getEncryptionModuleId($header);
@@ -972,11 +853,7 @@ class Encryption extends Wrapper {
return $encryptionModule;
}
- /**
- * @param string $path
- * @param int $unencryptedSize
- */
- public function updateUnencryptedSize($path, $unencryptedSize) {
+ public function updateUnencryptedSize(string $path, int|float $unencryptedSize): void {
$this->unencryptedSize[$path] = $unencryptedSize;
}
@@ -985,9 +862,8 @@ class Encryption extends Wrapper {
*
* @param string $source path relative to data/
* @param string $target path relative to data/
- * @return bool
*/
- protected function copyKeys($source, $target) {
+ protected function copyKeys(string $source, string $target): bool {
if (!$this->util->isExcluded($source)) {
return $this->keyStorage->copyKeys($source, $target);
}
@@ -997,22 +873,16 @@ class Encryption extends Wrapper {
/**
* check if path points to a files version
- *
- * @param $path
- * @return bool
*/
- protected function isVersion($path) {
+ protected function isVersion(string $path): bool {
$normalized = Filesystem::normalizePath($path);
return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/';
}
/**
* check if the given storage should be encrypted or not
- *
- * @param $path
- * @return bool
*/
- protected function shouldEncrypt($path) {
+ protected function shouldEncrypt(string $path): bool {
$fullPath = $this->getFullPath($path);
$mountPointConfig = $this->mount->getOption('encrypt', true);
if ($mountPointConfig === false) {
@@ -1035,7 +905,10 @@ class Encryption extends Wrapper {
public function writeStream(string $path, $stream, ?int $size = null): int {
// always fall back to fopen
$target = $this->fopen($path, 'w');
- [$count, $result] = \OC_Helper::streamCopy($stream, $target);
+ if ($target === false) {
+ throw new GenericFileException("Failed to open $path for writing");
+ }
+ [$count, $result] = Files::streamCopy($stream, $target, true);
fclose($stream);
fclose($target);
@@ -1054,11 +927,20 @@ class Encryption extends Wrapper {
/**
* Allow temporarily disabling the wrapper
- *
- * @param bool $enabled
- * @return void
*/
public function setEnabled(bool $enabled): void {
$this->enabled = $enabled;
}
+
+ /**
+ * Check if the on-disk data for a file has a valid encrypted header
+ *
+ * @param string $path
+ * @return bool
+ */
+ public function hasValidHeader(string $path): bool {
+ $firstBlock = $this->readFirstBlock($path);
+ $header = $this->util->parseRawHeader($firstBlock);
+ return (count($header) > 0);
+ }
}
diff --git a/lib/private/Files/Storage/Wrapper/Jail.php b/lib/private/Files/Storage/Wrapper/Jail.php
index 2e8306dc705..38b113cef88 100644
--- a/lib/private/Files/Storage/Wrapper/Jail.php
+++ b/lib/private/Files/Storage/Wrapper/Jail.php
@@ -11,6 +11,10 @@ use OC\Files\Cache\Wrapper\CacheJail;
use OC\Files\Cache\Wrapper\JailPropagator;
use OC\Files\Cache\Wrapper\JailWatcher;
use OC\Files\Filesystem;
+use OCP\Files;
+use OCP\Files\Cache\ICache;
+use OCP\Files\Cache\IPropagator;
+use OCP\Files\Cache\IWatcher;
use OCP\Files\Storage\IStorage;
use OCP\Files\Storage\IWriteStreamStorage;
use OCP\Lock\ILockingProvider;
@@ -27,29 +31,29 @@ class Jail extends Wrapper {
protected $rootPath;
/**
- * @param array $arguments ['storage' => $storage, 'root' => $root]
+ * @param array $parameters ['storage' => $storage, 'root' => $root]
*
* $storage: The storage that will be wrapper
* $root: The folder in the wrapped storage that will become the root folder of the wrapped storage
*/
- public function __construct($arguments) {
- parent::__construct($arguments);
- $this->rootPath = $arguments['root'];
+ public function __construct(array $parameters) {
+ parent::__construct($parameters);
+ $this->rootPath = $parameters['root'];
}
- public function getUnjailedPath($path) {
+ public function getUnjailedPath(string $path): string {
return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
}
/**
* This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper
*/
- public function getUnjailedStorage() {
+ public function getUnjailedStorage(): IStorage {
return $this->storage;
}
- public function getJailedPath($path) {
+ public function getJailedPath(string $path): ?string {
$root = rtrim($this->rootPath, '/') . '/';
if ($path !== $this->rootPath && !str_starts_with($path, $root)) {
@@ -60,427 +64,178 @@ class Jail extends Wrapper {
}
}
- public function getId() {
+ public function getId(): string {
return parent::getId();
}
- /**
- * see https://www.php.net/manual/en/function.mkdir.php
- *
- * @param string $path
- * @return bool
- */
- public function mkdir($path) {
+ public function mkdir(string $path): bool {
return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.rmdir.php
- *
- * @param string $path
- * @return bool
- */
- public function rmdir($path) {
+ public function rmdir(string $path): bool {
return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.opendir.php
- *
- * @param string $path
- * @return resource|false
- */
- public function opendir($path) {
+ public function opendir(string $path) {
return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.is_dir.php
- *
- * @param string $path
- * @return bool
- */
- public function is_dir($path) {
+ public function is_dir(string $path): bool {
return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.is_file.php
- *
- * @param string $path
- * @return bool
- */
- public function is_file($path) {
+ public function is_file(string $path): bool {
return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.stat.php
- * only the following keys are required in the result: size and mtime
- *
- * @param string $path
- * @return array|bool
- */
- public function stat($path) {
+ public function stat(string $path): array|false {
return $this->getWrapperStorage()->stat($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.filetype.php
- *
- * @param string $path
- * @return bool
- */
- public function filetype($path) {
+ public function filetype(string $path): string|false {
return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.filesize.php
- * The result for filesize when called on a folder is required to be 0
- */
- public function filesize($path): false|int|float {
+ public function filesize(string $path): int|float|false {
return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path));
}
- /**
- * check if a file can be created in $path
- *
- * @param string $path
- * @return bool
- */
- public function isCreatable($path) {
+ public function isCreatable(string $path): bool {
return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path));
}
- /**
- * check if a file can be read
- *
- * @param string $path
- * @return bool
- */
- public function isReadable($path) {
+ public function isReadable(string $path): bool {
return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path));
}
- /**
- * check if a file can be written to
- *
- * @param string $path
- * @return bool
- */
- public function isUpdatable($path) {
+ public function isUpdatable(string $path): bool {
return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path));
}
- /**
- * check if a file can be deleted
- *
- * @param string $path
- * @return bool
- */
- public function isDeletable($path) {
+ public function isDeletable(string $path): bool {
return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path));
}
- /**
- * check if a file can be shared
- *
- * @param string $path
- * @return bool
- */
- public function isSharable($path) {
+ public function isSharable(string $path): bool {
return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path));
}
- /**
- * get the full permissions of a path.
- * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
- *
- * @param string $path
- * @return int
- */
- public function getPermissions($path) {
+ public function getPermissions(string $path): int {
return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.file_exists.php
- *
- * @param string $path
- * @return bool
- */
- public function file_exists($path) {
+ public function file_exists(string $path): bool {
return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.filemtime.php
- *
- * @param string $path
- * @return int|bool
- */
- public function filemtime($path) {
+ public function filemtime(string $path): int|false {
return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.file_get_contents.php
- *
- * @param string $path
- * @return string|false
- */
- public function file_get_contents($path) {
+ public function file_get_contents(string $path): string|false {
return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.file_put_contents.php
- *
- * @param string $path
- * @param mixed $data
- * @return int|float|false
- */
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): int|float|false {
return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data);
}
- /**
- * see https://www.php.net/manual/en/function.unlink.php
- *
- * @param string $path
- * @return bool
- */
- public function unlink($path) {
+ public function unlink(string $path): bool {
return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.rename.php
- *
- * @param string $source
- * @param string $target
- * @return bool
- */
- public function rename($source, $target) {
+ public function rename(string $source, string $target): bool {
return $this->getWrapperStorage()->rename($this->getUnjailedPath($source), $this->getUnjailedPath($target));
}
- /**
- * see https://www.php.net/manual/en/function.copy.php
- *
- * @param string $source
- * @param string $target
- * @return bool
- */
- public function copy($source, $target) {
+ public function copy(string $source, string $target): bool {
return $this->getWrapperStorage()->copy($this->getUnjailedPath($source), $this->getUnjailedPath($target));
}
- /**
- * see https://www.php.net/manual/en/function.fopen.php
- *
- * @param string $path
- * @param string $mode
- * @return resource|bool
- */
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode);
}
- /**
- * get the mimetype for a file or folder
- * The mimetype for a folder is required to be "httpd/unix-directory"
- *
- * @param string $path
- * @return string|bool
- */
- public function getMimeType($path) {
+ public function getMimeType(string $path): string|false {
return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path));
}
- /**
- * see https://www.php.net/manual/en/function.hash.php
- *
- * @param string $type
- * @param string $path
- * @param bool $raw
- * @return string|bool
- */
- public function hash($type, $path, $raw = false) {
+ public function hash(string $type, string $path, bool $raw = false): string|false {
return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw);
}
- /**
- * see https://www.php.net/manual/en/function.free_space.php
- *
- * @param string $path
- * @return int|float|bool
- */
- public function free_space($path) {
+ public function free_space(string $path): int|float|false {
return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path));
}
- /**
- * search for occurrences of $query in file names
- *
- * @param string $query
- * @return array|bool
- */
- public function search($query) {
- return $this->getWrapperStorage()->search($query);
- }
-
- /**
- * see https://www.php.net/manual/en/function.touch.php
- * If the backend does not support the operation, false should be returned
- *
- * @param string $path
- * @param int $mtime
- * @return bool
- */
- public function touch($path, $mtime = null) {
+ public function touch(string $path, ?int $mtime = null): bool {
return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime);
}
- /**
- * get the path to a local version of the file.
- * The local version of the file can be temporary and doesn't have to be persistent across requests
- *
- * @param string $path
- * @return string|false
- */
- public function getLocalFile($path) {
+ public function getLocalFile(string $path): string|false {
return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path));
}
- /**
- * check if a file or folder has been updated since $time
- *
- * @param string $path
- * @param int $time
- * @return bool
- *
- * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
- * returning true for other changes in the folder is optional
- */
- public function hasUpdated($path, $time) {
+ public function hasUpdated(string $path, int $time): bool {
return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time);
}
- /**
- * get a cache instance for the storage
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage|null (optional) the storage to pass to the cache
- * @return \OC\Files\Cache\Cache
- */
- public function getCache($path = '', $storage = null) {
+ public function getCache(string $path = '', ?IStorage $storage = null): ICache {
$sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path));
return new CacheJail($sourceCache, $this->rootPath);
}
- /**
- * get the user id of the owner of a file or folder
- *
- * @param string $path
- * @return string
- */
- public function getOwner($path) {
+ public function getOwner(string $path): string|false {
return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path));
}
- /**
- * get a watcher instance for the cache
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
- * @return \OC\Files\Cache\Watcher
- */
- public function getWatcher($path = '', $storage = null) {
+ public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher {
$sourceWatcher = $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $this->getWrapperStorage());
return new JailWatcher($sourceWatcher, $this->rootPath);
}
- /**
- * get the ETag for a file or folder
- *
- * @param string $path
- * @return string|false
- */
- public function getETag($path) {
+ public function getETag(string $path): string|false {
return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path));
}
- public function getMetaData($path) {
+ public function getMetaData(string $path): ?array {
return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path));
}
- /**
- * @param string $path
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- * @throws \OCP\Lock\LockedException
- */
- public function acquireLock($path, $type, ILockingProvider $provider) {
+ public function acquireLock(string $path, int $type, ILockingProvider $provider): void {
$this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider);
}
- /**
- * @param string $path
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- */
- public function releaseLock($path, $type, ILockingProvider $provider) {
+ public function releaseLock(string $path, int $type, ILockingProvider $provider): void {
$this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider);
}
- /**
- * @param string $path
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- */
- public function changeLock($path, $type, ILockingProvider $provider) {
+ public function changeLock(string $path, int $type, ILockingProvider $provider): void {
$this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider);
}
/**
* Resolve the path for the source of the share
- *
- * @param string $path
- * @return array
*/
- public function resolvePath($path) {
+ public function resolvePath(string $path): array {
return [$this->getWrapperStorage(), $this->getUnjailedPath($path)];
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @return bool
- */
- public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
if ($sourceStorage === $this) {
return $this->copy($sourceInternalPath, $targetInternalPath);
}
return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @return bool
- */
- public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
if ($sourceStorage === $this) {
return $this->rename($sourceInternalPath, $targetInternalPath);
}
return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath));
}
- public function getPropagator($storage = null) {
+ public function getPropagator(?IStorage $storage = null): IPropagator {
if (isset($this->propagator)) {
return $this->propagator;
}
@@ -499,14 +254,14 @@ class Jail extends Wrapper {
return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
} else {
$target = $this->fopen($path, 'w');
- [$count, $result] = \OC_Helper::streamCopy($stream, $target);
+ $count = Files::streamCopy($stream, $target);
fclose($stream);
fclose($target);
return $count;
}
}
- public function getDirectoryContent($directory): \Traversable {
+ public function getDirectoryContent(string $directory): \Traversable {
return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory));
}
}
diff --git a/lib/private/Files/Storage/Wrapper/KnownMtime.php b/lib/private/Files/Storage/Wrapper/KnownMtime.php
index a7360ae1346..657c6c9250c 100644
--- a/lib/private/Files/Storage/Wrapper/KnownMtime.php
+++ b/lib/private/Files/Storage/Wrapper/KnownMtime.php
@@ -20,13 +20,13 @@ class KnownMtime extends Wrapper {
private CappedMemoryCache $knowMtimes;
private ClockInterface $clock;
- public function __construct($arguments) {
- parent::__construct($arguments);
+ public function __construct(array $parameters) {
+ parent::__construct($parameters);
$this->knowMtimes = new CappedMemoryCache();
- $this->clock = $arguments['clock'];
+ $this->clock = $parameters['clock'];
}
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): int|float|false {
$result = parent::file_put_contents($path, $data);
if ($result) {
$now = $this->clock->now()->getTimestamp();
@@ -35,7 +35,7 @@ class KnownMtime extends Wrapper {
return $result;
}
- public function stat($path) {
+ public function stat(string $path): array|false {
$stat = parent::stat($path);
if ($stat) {
$this->applyKnownMtime($path, $stat);
@@ -43,7 +43,7 @@ class KnownMtime extends Wrapper {
return $stat;
}
- public function getMetaData($path) {
+ public function getMetaData(string $path): ?array {
$stat = parent::getMetaData($path);
if ($stat) {
$this->applyKnownMtime($path, $stat);
@@ -51,19 +51,19 @@ class KnownMtime extends Wrapper {
return $stat;
}
- private function applyKnownMtime(string $path, array &$stat) {
+ private function applyKnownMtime(string $path, array &$stat): void {
if (isset($stat['mtime'])) {
$knownMtime = $this->knowMtimes->get($path) ?? 0;
$stat['mtime'] = max($stat['mtime'], $knownMtime);
}
}
- public function filemtime($path) {
+ public function filemtime(string $path): int|false {
$knownMtime = $this->knowMtimes->get($path) ?? 0;
return max(parent::filemtime($path), $knownMtime);
}
- public function mkdir($path) {
+ public function mkdir(string $path): bool {
$result = parent::mkdir($path);
if ($result) {
$this->knowMtimes->set($path, $this->clock->now()->getTimestamp());
@@ -71,7 +71,7 @@ class KnownMtime extends Wrapper {
return $result;
}
- public function rmdir($path) {
+ public function rmdir(string $path): bool {
$result = parent::rmdir($path);
if ($result) {
$this->knowMtimes->set($path, $this->clock->now()->getTimestamp());
@@ -79,7 +79,7 @@ class KnownMtime extends Wrapper {
return $result;
}
- public function unlink($path) {
+ public function unlink(string $path): bool {
$result = parent::unlink($path);
if ($result) {
$this->knowMtimes->set($path, $this->clock->now()->getTimestamp());
@@ -87,7 +87,7 @@ class KnownMtime extends Wrapper {
return $result;
}
- public function rename($source, $target) {
+ public function rename(string $source, string $target): bool {
$result = parent::rename($source, $target);
if ($result) {
$this->knowMtimes->set($target, $this->clock->now()->getTimestamp());
@@ -96,7 +96,7 @@ class KnownMtime extends Wrapper {
return $result;
}
- public function copy($source, $target) {
+ public function copy(string $source, string $target): bool {
$result = parent::copy($source, $target);
if ($result) {
$this->knowMtimes->set($target, $this->clock->now()->getTimestamp());
@@ -104,7 +104,7 @@ class KnownMtime extends Wrapper {
return $result;
}
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
$result = parent::fopen($path, $mode);
if ($result && $mode === 'w') {
$this->knowMtimes->set($path, $this->clock->now()->getTimestamp());
@@ -112,7 +112,7 @@ class KnownMtime extends Wrapper {
return $result;
}
- public function touch($path, $mtime = null) {
+ public function touch(string $path, ?int $mtime = null): bool {
$result = parent::touch($path, $mtime);
if ($result) {
$this->knowMtimes->set($path, $mtime ?? $this->clock->now()->getTimestamp());
@@ -120,7 +120,7 @@ class KnownMtime extends Wrapper {
return $result;
}
- public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
$result = parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
if ($result) {
$this->knowMtimes->set($targetInternalPath, $this->clock->now()->getTimestamp());
@@ -128,7 +128,7 @@ class KnownMtime extends Wrapper {
return $result;
}
- public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
$result = parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
if ($result) {
$this->knowMtimes->set($targetInternalPath, $this->clock->now()->getTimestamp());
diff --git a/lib/private/Files/Storage/Wrapper/PermissionsMask.php b/lib/private/Files/Storage/Wrapper/PermissionsMask.php
index c502de41a86..684040146ba 100644
--- a/lib/private/Files/Storage/Wrapper/PermissionsMask.php
+++ b/lib/private/Files/Storage/Wrapper/PermissionsMask.php
@@ -9,6 +9,7 @@ namespace OC\Files\Storage\Wrapper;
use OC\Files\Cache\Wrapper\CachePermissionsMask;
use OCP\Constants;
+use OCP\Files\Storage\IStorage;
/**
* Mask the permissions of a storage
@@ -24,41 +25,41 @@ class PermissionsMask extends Wrapper {
private $mask;
/**
- * @param array $arguments ['storage' => $storage, 'mask' => $mask]
+ * @param array $parameters ['storage' => $storage, 'mask' => $mask]
*
* $storage: The storage the permissions mask should be applied on
* $mask: The permission bits that should be kept, a combination of the \OCP\Constant::PERMISSION_ constants
*/
- public function __construct($arguments) {
- parent::__construct($arguments);
- $this->mask = $arguments['mask'];
+ public function __construct(array $parameters) {
+ parent::__construct($parameters);
+ $this->mask = $parameters['mask'];
}
- private function checkMask($permissions) {
+ private function checkMask(int $permissions): bool {
return ($this->mask & $permissions) === $permissions;
}
- public function isUpdatable($path) {
+ public function isUpdatable(string $path): bool {
return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::isUpdatable($path);
}
- public function isCreatable($path) {
+ public function isCreatable(string $path): bool {
return $this->checkMask(Constants::PERMISSION_CREATE) and parent::isCreatable($path);
}
- public function isDeletable($path) {
+ public function isDeletable(string $path): bool {
return $this->checkMask(Constants::PERMISSION_DELETE) and parent::isDeletable($path);
}
- public function isSharable($path) {
+ public function isSharable(string $path): bool {
return $this->checkMask(Constants::PERMISSION_SHARE) and parent::isSharable($path);
}
- public function getPermissions($path) {
+ public function getPermissions(string $path): int {
return $this->storage->getPermissions($path) & $this->mask;
}
- public function rename($source, $target) {
+ public function rename(string $source, string $target): bool {
//This is a rename of the transfer file to the original file
if (dirname($source) === dirname($target) && strpos($source, '.ocTransferId') > 0) {
return $this->checkMask(Constants::PERMISSION_CREATE) and parent::rename($source, $target);
@@ -66,33 +67,33 @@ class PermissionsMask extends Wrapper {
return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::rename($source, $target);
}
- public function copy($source, $target) {
+ public function copy(string $source, string $target): bool {
return $this->checkMask(Constants::PERMISSION_CREATE) and parent::copy($source, $target);
}
- public function touch($path, $mtime = null) {
+ public function touch(string $path, ?int $mtime = null): bool {
$permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE;
return $this->checkMask($permissions) and parent::touch($path, $mtime);
}
- public function mkdir($path) {
+ public function mkdir(string $path): bool {
return $this->checkMask(Constants::PERMISSION_CREATE) and parent::mkdir($path);
}
- public function rmdir($path) {
+ public function rmdir(string $path): bool {
return $this->checkMask(Constants::PERMISSION_DELETE) and parent::rmdir($path);
}
- public function unlink($path) {
+ public function unlink(string $path): bool {
return $this->checkMask(Constants::PERMISSION_DELETE) and parent::unlink($path);
}
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): int|float|false {
$permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE;
return $this->checkMask($permissions) ? parent::file_put_contents($path, $data) : false;
}
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
if ($mode === 'r' or $mode === 'rb') {
return parent::fopen($path, $mode);
} else {
@@ -101,14 +102,7 @@ class PermissionsMask extends Wrapper {
}
}
- /**
- * get a cache instance for the storage
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache
- * @return \OC\Files\Cache\Cache
- */
- public function getCache($path = '', $storage = null) {
+ public function getCache(string $path = '', ?IStorage $storage = null): \OCP\Files\Cache\ICache {
if (!$storage) {
$storage = $this;
}
@@ -116,7 +110,7 @@ class PermissionsMask extends Wrapper {
return new CachePermissionsMask($sourceCache, $this->mask);
}
- public function getMetaData($path) {
+ public function getMetaData(string $path): ?array {
$data = parent::getMetaData($path);
if ($data && isset($data['permissions'])) {
@@ -126,14 +120,14 @@ class PermissionsMask extends Wrapper {
return $data;
}
- public function getScanner($path = '', $storage = null) {
+ public function getScanner(string $path = '', ?IStorage $storage = null): \OCP\Files\Cache\IScanner {
if (!$storage) {
$storage = $this->storage;
}
return parent::getScanner($path, $storage);
}
- public function getDirectoryContent($directory): \Traversable {
+ public function getDirectoryContent(string $directory): \Traversable {
foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) {
$data['scan_permissions'] = $data['scan_permissions'] ?? $data['permissions'];
$data['permissions'] &= $this->mask;
diff --git a/lib/private/Files/Storage/Wrapper/Quota.php b/lib/private/Files/Storage/Wrapper/Quota.php
index 8c6799fdd2d..35a265f8c8e 100644
--- a/lib/private/Files/Storage/Wrapper/Quota.php
+++ b/lib/private/Files/Storage/Wrapper/Quota.php
@@ -21,11 +21,12 @@ class Quota extends Wrapper {
protected string $sizeRoot;
private SystemConfig $config;
private bool $quotaIncludeExternalStorage;
+ private bool $enabled = true;
/**
* @param array $parameters
*/
- public function __construct($parameters) {
+ public function __construct(array $parameters) {
parent::__construct($parameters);
$this->quota = $parameters['quota'] ?? null;
$this->quotaCallback = $parameters['quotaCallback'] ?? null;
@@ -33,14 +34,11 @@ class Quota extends Wrapper {
$this->quotaIncludeExternalStorage = $parameters['include_external_storage'] ?? false;
}
- /**
- * @return int|float quota value
- */
public function getQuota(): int|float {
if ($this->quota === null) {
$quotaCallback = $this->quotaCallback;
if ($quotaCallback === null) {
- throw new \Exception("No quota or quota callback provider");
+ throw new \Exception('No quota or quota callback provider');
}
$this->quota = $quotaCallback();
}
@@ -49,15 +47,13 @@ class Quota extends Wrapper {
}
private function hasQuota(): bool {
+ if (!$this->enabled) {
+ return false;
+ }
return $this->getQuota() !== FileInfo::SPACE_UNLIMITED;
}
- /**
- * @param string $path
- * @param IStorage $storage
- * @return int|float
- */
- protected function getSize($path, $storage = null) {
+ protected function getSize(string $path, ?IStorage $storage = null): int|float {
if ($this->quotaIncludeExternalStorage) {
$rootInfo = Filesystem::getFileInfo('', 'ext');
if ($rootInfo) {
@@ -75,13 +71,7 @@ class Quota extends Wrapper {
}
}
- /**
- * Get free space as limited by the quota
- *
- * @param string $path
- * @return int|float|bool
- */
- public function free_space($path) {
+ public function free_space(string $path): int|float|false {
if (!$this->hasQuota()) {
return $this->storage->free_space($path);
}
@@ -101,14 +91,7 @@ class Quota extends Wrapper {
}
}
- /**
- * see https://www.php.net/manual/en/function.file_put_contents.php
- *
- * @param string $path
- * @param mixed $data
- * @return int|float|false
- */
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): int|float|false {
if (!$this->hasQuota()) {
return $this->storage->file_put_contents($path, $data);
}
@@ -120,14 +103,7 @@ class Quota extends Wrapper {
}
}
- /**
- * see https://www.php.net/manual/en/function.copy.php
- *
- * @param string $source
- * @param string $target
- * @return bool
- */
- public function copy($source, $target) {
+ public function copy(string $source, string $target): bool {
if (!$this->hasQuota()) {
return $this->storage->copy($source, $target);
}
@@ -139,14 +115,7 @@ class Quota extends Wrapper {
}
}
- /**
- * see https://www.php.net/manual/en/function.fopen.php
- *
- * @param string $path
- * @param string $mode
- * @return resource|bool
- */
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
if (!$this->hasQuota()) {
return $this->storage->fopen($path, $mode);
}
@@ -170,10 +139,9 @@ class Quota extends Wrapper {
* Checks whether the given path is a part file
*
* @param string $path Path that may identify a .part file
- * @return bool
* @note this is needed for reusing keys
*/
- private function isPartFile($path) {
+ private function isPartFile(string $path): bool {
$extension = pathinfo($path, PATHINFO_EXTENSION);
return ($extension === 'part');
@@ -182,17 +150,11 @@ class Quota extends Wrapper {
/**
* Only apply quota for files, not metadata, trash or others
*/
- private function shouldApplyQuota(string $path): bool {
+ protected function shouldApplyQuota(string $path): bool {
return str_starts_with(ltrim($path, '/'), 'files/');
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @return bool
- */
- public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
if (!$this->hasQuota()) {
return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
}
@@ -204,13 +166,7 @@ class Quota extends Wrapper {
}
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @return bool
- */
- public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
if (!$this->hasQuota()) {
return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
}
@@ -222,7 +178,7 @@ class Quota extends Wrapper {
}
}
- public function mkdir($path) {
+ public function mkdir(string $path): bool {
if (!$this->hasQuota()) {
return $this->storage->mkdir($path);
}
@@ -234,7 +190,7 @@ class Quota extends Wrapper {
return parent::mkdir($path);
}
- public function touch($path, $mtime = null) {
+ public function touch(string $path, ?int $mtime = null): bool {
if (!$this->hasQuota()) {
return $this->storage->touch($path, $mtime);
}
@@ -245,4 +201,8 @@ class Quota extends Wrapper {
return parent::touch($path, $mtime);
}
+
+ public function enableQuota(bool $enabled): void {
+ $this->enabled = $enabled;
+ }
}
diff --git a/lib/private/Files/Storage/Wrapper/Wrapper.php b/lib/private/Files/Storage/Wrapper/Wrapper.php
index 29acc9ad1c2..7af11dd5ef7 100644
--- a/lib/private/Files/Storage/Wrapper/Wrapper.php
+++ b/lib/private/Files/Storage/Wrapper/Wrapper.php
@@ -8,7 +8,13 @@
namespace OC\Files\Storage\Wrapper;
use OC\Files\Storage\FailedStorage;
-use OCP\Files\InvalidPathException;
+use OC\Files\Storage\Storage;
+use OCP\Files;
+use OCP\Files\Cache\ICache;
+use OCP\Files\Cache\IPropagator;
+use OCP\Files\Cache\IScanner;
+use OCP\Files\Cache\IUpdater;
+use OCP\Files\Cache\IWatcher;
use OCP\Files\Storage\ILockingStorage;
use OCP\Files\Storage\IStorage;
use OCP\Files\Storage\IWriteStreamStorage;
@@ -31,16 +37,13 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea
/**
* @param array $parameters
*/
- public function __construct($parameters) {
+ public function __construct(array $parameters) {
$this->storage = $parameters['storage'];
}
- /**
- * @return \OC\Files\Storage\Storage
- */
- public function getWrapperStorage() {
+ public function getWrapperStorage(): Storage {
if (!$this->storage) {
- $message = "storage wrapper " . get_class($this) . " doesn't have a wrapped storage set";
+ $message = 'storage wrapper ' . get_class($this) . " doesn't have a wrapped storage set";
$logger = Server::get(LoggerInterface::class);
$logger->error($message);
$this->storage = new FailedStorage(['exception' => new \Exception($message)]);
@@ -48,430 +51,178 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea
return $this->storage;
}
- /**
- * Get the identifier for the storage,
- * the returned id should be the same for every storage object that is created with the same parameters
- * and two storage objects with the same id should refer to two storages that display the same files.
- *
- * @return string
- */
- public function getId() {
+ public function getId(): string {
return $this->getWrapperStorage()->getId();
}
- /**
- * see https://www.php.net/manual/en/function.mkdir.php
- *
- * @param string $path
- * @return bool
- */
- public function mkdir($path) {
+ public function mkdir(string $path): bool {
return $this->getWrapperStorage()->mkdir($path);
}
- /**
- * see https://www.php.net/manual/en/function.rmdir.php
- *
- * @param string $path
- * @return bool
- */
- public function rmdir($path) {
+ public function rmdir(string $path): bool {
return $this->getWrapperStorage()->rmdir($path);
}
- /**
- * see https://www.php.net/manual/en/function.opendir.php
- *
- * @param string $path
- * @return resource|false
- */
- public function opendir($path) {
+ public function opendir(string $path) {
return $this->getWrapperStorage()->opendir($path);
}
- /**
- * see https://www.php.net/manual/en/function.is_dir.php
- *
- * @param string $path
- * @return bool
- */
- public function is_dir($path) {
+ public function is_dir(string $path): bool {
return $this->getWrapperStorage()->is_dir($path);
}
- /**
- * see https://www.php.net/manual/en/function.is_file.php
- *
- * @param string $path
- * @return bool
- */
- public function is_file($path) {
+ public function is_file(string $path): bool {
return $this->getWrapperStorage()->is_file($path);
}
- /**
- * see https://www.php.net/manual/en/function.stat.php
- * only the following keys are required in the result: size and mtime
- *
- * @param string $path
- * @return array|bool
- */
- public function stat($path) {
+ public function stat(string $path): array|false {
return $this->getWrapperStorage()->stat($path);
}
- /**
- * see https://www.php.net/manual/en/function.filetype.php
- *
- * @param string $path
- * @return string|bool
- */
- public function filetype($path) {
+ public function filetype(string $path): string|false {
return $this->getWrapperStorage()->filetype($path);
}
- /**
- * see https://www.php.net/manual/en/function.filesize.php
- * The result for filesize when called on a folder is required to be 0
- */
- public function filesize($path): false|int|float {
+ public function filesize(string $path): int|float|false {
return $this->getWrapperStorage()->filesize($path);
}
- /**
- * check if a file can be created in $path
- *
- * @param string $path
- * @return bool
- */
- public function isCreatable($path) {
+ public function isCreatable(string $path): bool {
return $this->getWrapperStorage()->isCreatable($path);
}
- /**
- * check if a file can be read
- *
- * @param string $path
- * @return bool
- */
- public function isReadable($path) {
+ public function isReadable(string $path): bool {
return $this->getWrapperStorage()->isReadable($path);
}
- /**
- * check if a file can be written to
- *
- * @param string $path
- * @return bool
- */
- public function isUpdatable($path) {
+ public function isUpdatable(string $path): bool {
return $this->getWrapperStorage()->isUpdatable($path);
}
- /**
- * check if a file can be deleted
- *
- * @param string $path
- * @return bool
- */
- public function isDeletable($path) {
+ public function isDeletable(string $path): bool {
return $this->getWrapperStorage()->isDeletable($path);
}
- /**
- * check if a file can be shared
- *
- * @param string $path
- * @return bool
- */
- public function isSharable($path) {
+ public function isSharable(string $path): bool {
return $this->getWrapperStorage()->isSharable($path);
}
- /**
- * get the full permissions of a path.
- * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php
- *
- * @param string $path
- * @return int
- */
- public function getPermissions($path) {
+ public function getPermissions(string $path): int {
return $this->getWrapperStorage()->getPermissions($path);
}
- /**
- * see https://www.php.net/manual/en/function.file_exists.php
- *
- * @param string $path
- * @return bool
- */
- public function file_exists($path) {
+ public function file_exists(string $path): bool {
return $this->getWrapperStorage()->file_exists($path);
}
- /**
- * see https://www.php.net/manual/en/function.filemtime.php
- *
- * @param string $path
- * @return int|bool
- */
- public function filemtime($path) {
+ public function filemtime(string $path): int|false {
return $this->getWrapperStorage()->filemtime($path);
}
- /**
- * see https://www.php.net/manual/en/function.file_get_contents.php
- *
- * @param string $path
- * @return string|false
- */
- public function file_get_contents($path) {
+ public function file_get_contents(string $path): string|false {
return $this->getWrapperStorage()->file_get_contents($path);
}
- /**
- * see https://www.php.net/manual/en/function.file_put_contents.php
- *
- * @param string $path
- * @param mixed $data
- * @return int|float|false
- */
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): int|float|false {
return $this->getWrapperStorage()->file_put_contents($path, $data);
}
- /**
- * see https://www.php.net/manual/en/function.unlink.php
- *
- * @param string $path
- * @return bool
- */
- public function unlink($path) {
+ public function unlink(string $path): bool {
return $this->getWrapperStorage()->unlink($path);
}
- /**
- * see https://www.php.net/manual/en/function.rename.php
- *
- * @param string $source
- * @param string $target
- * @return bool
- */
- public function rename($source, $target) {
+ public function rename(string $source, string $target): bool {
return $this->getWrapperStorage()->rename($source, $target);
}
- /**
- * see https://www.php.net/manual/en/function.copy.php
- *
- * @param string $source
- * @param string $target
- * @return bool
- */
- public function copy($source, $target) {
+ public function copy(string $source, string $target): bool {
return $this->getWrapperStorage()->copy($source, $target);
}
- /**
- * see https://www.php.net/manual/en/function.fopen.php
- *
- * @param string $path
- * @param string $mode
- * @return resource|bool
- */
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
return $this->getWrapperStorage()->fopen($path, $mode);
}
- /**
- * get the mimetype for a file or folder
- * The mimetype for a folder is required to be "httpd/unix-directory"
- *
- * @param string $path
- * @return string|bool
- */
- public function getMimeType($path) {
+ public function getMimeType(string $path): string|false {
return $this->getWrapperStorage()->getMimeType($path);
}
- /**
- * see https://www.php.net/manual/en/function.hash.php
- *
- * @param string $type
- * @param string $path
- * @param bool $raw
- * @return string|bool
- */
- public function hash($type, $path, $raw = false) {
+ public function hash(string $type, string $path, bool $raw = false): string|false {
return $this->getWrapperStorage()->hash($type, $path, $raw);
}
- /**
- * see https://www.php.net/manual/en/function.free_space.php
- *
- * @param string $path
- * @return int|float|bool
- */
- public function free_space($path) {
+ public function free_space(string $path): int|float|false {
return $this->getWrapperStorage()->free_space($path);
}
- /**
- * search for occurrences of $query in file names
- *
- * @param string $query
- * @return array|bool
- */
- public function search($query) {
- return $this->getWrapperStorage()->search($query);
- }
-
- /**
- * see https://www.php.net/manual/en/function.touch.php
- * If the backend does not support the operation, false should be returned
- *
- * @param string $path
- * @param int $mtime
- * @return bool
- */
- public function touch($path, $mtime = null) {
+ public function touch(string $path, ?int $mtime = null): bool {
return $this->getWrapperStorage()->touch($path, $mtime);
}
- /**
- * get the path to a local version of the file.
- * The local version of the file can be temporary and doesn't have to be persistent across requests
- *
- * @param string $path
- * @return string|false
- */
- public function getLocalFile($path) {
+ public function getLocalFile(string $path): string|false {
return $this->getWrapperStorage()->getLocalFile($path);
}
- /**
- * check if a file or folder has been updated since $time
- *
- * @param string $path
- * @param int $time
- * @return bool
- *
- * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed.
- * returning true for other changes in the folder is optional
- */
- public function hasUpdated($path, $time) {
+ public function hasUpdated(string $path, int $time): bool {
return $this->getWrapperStorage()->hasUpdated($path, $time);
}
- /**
- * get a cache instance for the storage
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage|null (optional) the storage to pass to the cache
- * @return \OC\Files\Cache\Cache
- */
- public function getCache($path = '', $storage = null) {
+ public function getCache(string $path = '', ?IStorage $storage = null): ICache {
if (!$storage) {
$storage = $this;
}
return $this->getWrapperStorage()->getCache($path, $storage);
}
- /**
- * get a scanner instance for the storage
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
- * @return \OC\Files\Cache\Scanner
- */
- public function getScanner($path = '', $storage = null) {
+ public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
if (!$storage) {
$storage = $this;
}
return $this->getWrapperStorage()->getScanner($path, $storage);
}
-
- /**
- * get the user id of the owner of a file or folder
- *
- * @param string $path
- * @return string
- */
- public function getOwner($path) {
+ public function getOwner(string $path): string|false {
return $this->getWrapperStorage()->getOwner($path);
}
- /**
- * get a watcher instance for the cache
- *
- * @param string $path
- * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
- * @return \OC\Files\Cache\Watcher
- */
- public function getWatcher($path = '', $storage = null) {
+ public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher {
if (!$storage) {
$storage = $this;
}
return $this->getWrapperStorage()->getWatcher($path, $storage);
}
- public function getPropagator($storage = null) {
+ public function getPropagator(?IStorage $storage = null): IPropagator {
if (!$storage) {
$storage = $this;
}
return $this->getWrapperStorage()->getPropagator($storage);
}
- public function getUpdater($storage = null) {
+ public function getUpdater(?IStorage $storage = null): IUpdater {
if (!$storage) {
$storage = $this;
}
return $this->getWrapperStorage()->getUpdater($storage);
}
- /**
- * @return \OC\Files\Cache\Storage
- */
- public function getStorageCache() {
+ public function getStorageCache(): \OC\Files\Cache\Storage {
return $this->getWrapperStorage()->getStorageCache();
}
- /**
- * get the ETag for a file or folder
- *
- * @param string $path
- * @return string|false
- */
- public function getETag($path) {
+ public function getETag(string $path): string|false {
return $this->getWrapperStorage()->getETag($path);
}
- /**
- * Returns true
- *
- * @return true
- */
- public function test() {
+ public function test(): bool {
return $this->getWrapperStorage()->test();
}
- /**
- * Returns the wrapped storage's value for isLocal()
- *
- * @return bool wrapped storage's isLocal() value
- */
- public function isLocal() {
+ public function isLocal(): bool {
return $this->getWrapperStorage()->isLocal();
}
- /**
- * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
- *
- * @param class-string<IStorage> $class
- * @return bool
- */
- public function instanceOfStorage($class) {
+ public function instanceOfStorage(string $class): bool {
if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
// FIXME Temporary fix to keep existing checks working
$class = '\OCA\Files_Sharing\SharedStorage';
@@ -484,7 +235,7 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea
* @psalm-param class-string<T> $class
* @psalm-return T|null
*/
- public function getInstanceOfStorage(string $class) {
+ public function getInstanceOfStorage(string $class): ?IStorage {
$storage = $this;
while ($storage instanceof Wrapper) {
if ($storage instanceof $class) {
@@ -501,61 +252,29 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea
/**
* Pass any methods custom to specific storage implementations to the wrapped storage
*
- * @param string $method
- * @param array $args
* @return mixed
*/
- public function __call($method, $args) {
+ public function __call(string $method, array $args) {
return call_user_func_array([$this->getWrapperStorage(), $method], $args);
}
- /**
- * A custom storage implementation can return an url for direct download of a give file.
- *
- * For now the returned array can hold the parameter url - in future more attributes might follow.
- *
- * @param string $path
- * @return array|bool
- */
- public function getDirectDownload($path) {
+ public function getDirectDownload(string $path): array|false {
return $this->getWrapperStorage()->getDirectDownload($path);
}
- /**
- * Get availability of the storage
- *
- * @return array [ available, last_checked ]
- */
- public function getAvailability() {
+ public function getAvailability(): array {
return $this->getWrapperStorage()->getAvailability();
}
- /**
- * Set availability of the storage
- *
- * @param bool $isAvailable
- */
- public function setAvailability($isAvailable) {
+ public function setAvailability(bool $isAvailable): void {
$this->getWrapperStorage()->setAvailability($isAvailable);
}
- /**
- * @param string $path the path of the target folder
- * @param string $fileName the name of the file itself
- * @return void
- * @throws InvalidPathException
- */
- public function verifyPath($path, $fileName) {
+ public function verifyPath(string $path, string $fileName): void {
$this->getWrapperStorage()->verifyPath($path, $fileName);
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @return bool
- */
- public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
if ($sourceStorage === $this) {
return $this->copy($sourceInternalPath, $targetInternalPath);
}
@@ -563,13 +282,7 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea
return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
}
- /**
- * @param IStorage $sourceStorage
- * @param string $sourceInternalPath
- * @param string $targetInternalPath
- * @return bool
- */
- public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
if ($sourceStorage === $this) {
return $this->rename($sourceInternalPath, $targetInternalPath);
}
@@ -577,48 +290,29 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea
return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
}
- public function getMetaData($path) {
+ public function getMetaData(string $path): ?array {
return $this->getWrapperStorage()->getMetaData($path);
}
- /**
- * @param string $path
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- * @throws \OCP\Lock\LockedException
- */
- public function acquireLock($path, $type, ILockingProvider $provider) {
+ public function acquireLock(string $path, int $type, ILockingProvider $provider): void {
if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
$this->getWrapperStorage()->acquireLock($path, $type, $provider);
}
}
- /**
- * @param string $path
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- */
- public function releaseLock($path, $type, ILockingProvider $provider) {
+ public function releaseLock(string $path, int $type, ILockingProvider $provider): void {
if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
$this->getWrapperStorage()->releaseLock($path, $type, $provider);
}
}
- /**
- * @param string $path
- * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
- * @param \OCP\Lock\ILockingProvider $provider
- */
- public function changeLock($path, $type, ILockingProvider $provider) {
+ public function changeLock(string $path, int $type, ILockingProvider $provider): void {
if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
$this->getWrapperStorage()->changeLock($path, $type, $provider);
}
}
- /**
- * @return bool
- */
- public function needsPartFile() {
+ public function needsPartFile(): bool {
return $this->getWrapperStorage()->needsPartFile();
}
@@ -629,18 +323,18 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea
return $storage->writeStream($path, $stream, $size);
} else {
$target = $this->fopen($path, 'w');
- [$count, $result] = \OC_Helper::streamCopy($stream, $target);
+ $count = Files::streamCopy($stream, $target);
fclose($stream);
fclose($target);
return $count;
}
}
- public function getDirectoryContent($directory): \Traversable {
+ public function getDirectoryContent(string $directory): \Traversable {
return $this->getWrapperStorage()->getDirectoryContent($directory);
}
- public function isWrapperOf(IStorage $storage) {
+ public function isWrapperOf(IStorage $storage): bool {
$wrapped = $this->getWrapperStorage();
if ($wrapped === $storage) {
return true;
diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php
index 32c0021cd23..ef147ec421f 100644
--- a/lib/private/Files/Stream/Encryption.php
+++ b/lib/private/Files/Stream/Encryption.php
@@ -1,5 +1,7 @@
<?php
+declare(strict_types=1);
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -9,81 +11,42 @@ namespace OC\Files\Stream;
use Icewind\Streams\Wrapper;
use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException;
+use OC\Encryption\File;
+use OC\Encryption\Util;
+use OC\Files\Storage\Storage;
+use OCP\Encryption\IEncryptionModule;
use function is_array;
use function stream_context_create;
class Encryption extends Wrapper {
- /** @var \OC\Encryption\Util */
- protected $util;
-
- /** @var \OC\Encryption\File */
- protected $file;
-
- /** @var \OCP\Encryption\IEncryptionModule */
- protected $encryptionModule;
-
- /** @var \OC\Files\Storage\Storage */
- protected $storage;
-
- /** @var \OC\Files\Storage\Wrapper\Encryption */
- protected $encryptionStorage;
-
- /** @var string */
- protected $internalPath;
-
- /** @var string */
- protected $cache;
-
- /** @var integer */
- protected $size;
-
- /** @var integer */
- protected $position;
-
- /** @var integer */
- protected $unencryptedSize;
-
- /** @var integer */
- protected $headerSize;
-
- /** @var integer */
- protected $unencryptedBlockSize;
-
- /** @var array */
- protected $header;
-
- /** @var string */
- protected $fullPath;
-
- /** @var bool */
- protected $signed;
-
+ protected Util $util;
+ protected File $file;
+ protected IEncryptionModule $encryptionModule;
+ protected Storage $storage;
+ protected \OC\Files\Storage\Wrapper\Encryption $encryptionStorage;
+ protected string $internalPath;
+ protected string $cache;
+ protected ?int $size = null;
+ protected int $position;
+ protected ?int $unencryptedSize = null;
+ protected int $headerSize;
+ protected int $unencryptedBlockSize;
+ protected array $header;
+ protected string $fullPath;
+ protected bool $signed;
/**
* header data returned by the encryption module, will be written to the file
* in case of a write operation
- *
- * @var array
*/
- protected $newHeader;
-
+ protected array $newHeader;
/**
* user who perform the read/write operation null for public access
- *
- * @var string
*/
- protected $uid;
-
- /** @var bool */
- protected $readOnly;
-
- /** @var bool */
- protected $writeFlag;
-
- /** @var array */
- protected $expectedContextProperties;
-
- /** @var bool */
- protected $fileUpdated;
+ protected ?string $uid;
+ protected bool $readOnly;
+ protected bool $writeFlag;
+ protected array $expectedContextProperties;
+ protected bool $fileUpdated;
public function __construct() {
$this->expectedContextProperties = [
@@ -113,11 +76,11 @@ class Encryption extends Wrapper {
* @param string $fullPath relative to data/
* @param array $header
* @param string $uid
- * @param \OCP\Encryption\IEncryptionModule $encryptionModule
- * @param \OC\Files\Storage\Storage $storage
+ * @param IEncryptionModule $encryptionModule
+ * @param Storage $storage
* @param \OC\Files\Storage\Wrapper\Encryption $encStorage
- * @param \OC\Encryption\Util $util
- * @param \OC\Encryption\File $file
+ * @param Util $util
+ * @param File $file
* @param string $mode
* @param int|float $size
* @param int|float $unencryptedSize
@@ -128,19 +91,24 @@ class Encryption extends Wrapper {
*
* @throws \BadMethodCallException
*/
- public static function wrap($source, $internalPath, $fullPath, array $header,
+ public static function wrap(
+ $source,
+ $internalPath,
+ $fullPath,
+ array $header,
$uid,
- \OCP\Encryption\IEncryptionModule $encryptionModule,
- \OC\Files\Storage\Storage $storage,
+ IEncryptionModule $encryptionModule,
+ Storage $storage,
\OC\Files\Storage\Wrapper\Encryption $encStorage,
- \OC\Encryption\Util $util,
- \OC\Encryption\File $file,
+ Util $util,
+ File $file,
$mode,
$size,
$unencryptedSize,
$headerSize,
$signed,
- $wrapper = Encryption::class) {
+ $wrapper = Encryption::class,
+ ) {
$context = stream_context_create([
'ocencryption' => [
'source' => $source,
@@ -344,8 +312,8 @@ class Encryption extends Wrapper {
// for seekable streams the pointer is moved back to the beginning of the encrypted block
// flush will start writing there when the position moves to another block
- $positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) *
- $this->util->getBlockSize() + $this->headerSize;
+ $positionInFile = (int)floor($this->position / $this->unencryptedBlockSize)
+ * $this->util->getBlockSize() + $this->headerSize;
$resultFseek = $this->parentStreamSeek($positionInFile);
// only allow writes on seekable streams, or at the end of the encrypted stream
@@ -368,8 +336,8 @@ class Encryption extends Wrapper {
// if $data doesn't fit the current block, the fill the current block and reiterate
// after the block is filled, it is flushed and $data is updatedxxx
} else {
- $this->cache = substr($this->cache, 0, $blockPosition) .
- substr($data, 0, $this->unencryptedBlockSize - $blockPosition);
+ $this->cache = substr($this->cache, 0, $blockPosition)
+ . substr($data, 0, $this->unencryptedBlockSize - $blockPosition);
$this->flush();
$this->position += ($this->unencryptedBlockSize - $blockPosition);
$length += ($this->unencryptedBlockSize - $blockPosition);
diff --git a/lib/private/Files/Stream/Quota.php b/lib/private/Files/Stream/Quota.php
index d8e7ae6dff0..cc737910fd8 100644
--- a/lib/private/Files/Stream/Quota.php
+++ b/lib/private/Files/Stream/Quota.php
@@ -23,7 +23,7 @@ class Quota extends Wrapper {
/**
* @param resource $stream
* @param int $limit
- * @return bool|resource
+ * @return resource|false
*/
public static function wrap($stream, $limit) {
$context = stream_context_create([
diff --git a/lib/private/Files/Stream/SeekableHttpStream.php b/lib/private/Files/Stream/SeekableHttpStream.php
index 02ed1470fbd..6ce0a880e8d 100644
--- a/lib/private/Files/Stream/SeekableHttpStream.php
+++ b/lib/private/Files/Stream/SeekableHttpStream.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -90,7 +91,7 @@ class SeekableHttpStream implements File {
continue 2;
}
}
- throw new \Exception("Failed to get source stream from stream wrapper of " . get_class($responseHead));
+ throw new \Exception('Failed to get source stream from stream wrapper of ' . get_class($responseHead));
}
$rangeHeaders = array_values(array_filter($responseHead, function ($v) {
diff --git a/lib/private/Files/Template/TemplateManager.php b/lib/private/Files/Template/TemplateManager.php
index 8362298b831..80ef5a14786 100644
--- a/lib/private/Files/Template/TemplateManager.php
+++ b/lib/private/Files/Template/TemplateManager.php
@@ -18,6 +18,8 @@ use OCP\Files\GenericFileException;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
+use OCP\Files\Template\BeforeGetTemplatesEvent;
+use OCP\Files\Template\Field;
use OCP\Files\Template\FileCreatedFromTemplateEvent;
use OCP\Files\Template\ICustomTemplateProvider;
use OCP\Files\Template\ITemplateManager;
@@ -62,7 +64,7 @@ class TemplateManager implements ITemplateManager {
IPreview $previewManager,
IConfig $config,
IFactory $l10nFactory,
- LoggerInterface $logger
+ LoggerInterface $logger,
) {
$this->serverContainer = $serverContainer;
$this->eventDispatcher = $eventDispatcher;
@@ -117,20 +119,34 @@ class TemplateManager implements ITemplateManager {
}
public function listTemplates(): array {
- return array_map(function (TemplateFileCreator $entry) {
+ return array_values(array_map(function (TemplateFileCreator $entry) {
return array_merge($entry->jsonSerialize(), [
'templates' => $this->getTemplateFiles($entry)
]);
- }, $this->listCreators());
+ }, $this->listCreators()));
+ }
+
+ public function listTemplateFields(int $fileId): array {
+ foreach ($this->listCreators() as $creator) {
+ $fields = $this->getTemplateFields($creator, $fileId);
+ if (empty($fields)) {
+ continue;
+ }
+
+ return $fields;
+ }
+
+ return [];
}
/**
* @param string $filePath
* @param string $templateId
+ * @param array $templateFields
* @return array
* @throws GenericFileException
*/
- public function createFromTemplate(string $filePath, string $templateId = '', string $templateType = 'user'): array {
+ public function createFromTemplate(string $filePath, string $templateId = '', string $templateType = 'user', array $templateFields = []): array {
$userFolder = $this->rootFolder->getUserFolder($this->userId);
try {
$userFolder->get($filePath);
@@ -142,11 +158,9 @@ class TemplateManager implements ITemplateManager {
throw new GenericFileException($this->l10n->t('Invalid path'));
}
$folder = $userFolder->get(dirname($filePath));
- $targetFile = $folder->newFile(basename($filePath));
$template = null;
if ($templateType === 'user' && $templateId !== '') {
$template = $userFolder->get($templateId);
- $template->copy($targetFile->getPath());
} else {
$matchingProvider = array_filter($this->getRegisteredProviders(), function (ICustomTemplateProvider $provider) use ($templateType) {
return $templateType === get_class($provider);
@@ -154,10 +168,12 @@ class TemplateManager implements ITemplateManager {
$provider = array_shift($matchingProvider);
if ($provider) {
$template = $provider->getCustomTemplate($templateId);
- $template->copy($targetFile->getPath());
}
}
- $this->eventDispatcher->dispatchTyped(new FileCreatedFromTemplateEvent($template, $targetFile));
+
+ $targetFile = $folder->newFile(basename($filePath), ($template instanceof File ? $template->fopen('rb') : null));
+
+ $this->eventDispatcher->dispatchTyped(new FileCreatedFromTemplateEvent($template, $targetFile, $templateFields));
return $this->formatFile($userFolder->get($filePath));
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
@@ -171,14 +187,34 @@ class TemplateManager implements ITemplateManager {
* @throws \OCP\Files\NotPermittedException
* @throws \OC\User\NoUserException
*/
- private function getTemplateFolder(): Node {
+ private function getTemplateFolder(): Folder {
if ($this->getTemplatePath() !== '') {
- return $this->rootFolder->getUserFolder($this->userId)->get($this->getTemplatePath());
+ $path = $this->rootFolder->getUserFolder($this->userId)->get($this->getTemplatePath());
+ if ($path instanceof Folder) {
+ return $path;
+ }
}
throw new NotFoundException();
}
+ /**
+ * @return list<Template>
+ */
private function getTemplateFiles(TemplateFileCreator $type): array {
+ $templates = array_merge(
+ $this->getProviderTemplates($type),
+ $this->getUserTemplates($type)
+ );
+
+ $this->eventDispatcher->dispatchTyped(new BeforeGetTemplatesEvent($templates, false));
+
+ return $templates;
+ }
+
+ /**
+ * @return list<Template>
+ */
+ private function getProviderTemplates(TemplateFileCreator $type): array {
$templates = [];
foreach ($this->getRegisteredProviders() as $provider) {
foreach ($type->getMimetypes() as $mimetype) {
@@ -187,11 +223,22 @@ class TemplateManager implements ITemplateManager {
}
}
}
+
+ return $templates;
+ }
+
+ /**
+ * @return list<Template>
+ */
+ private function getUserTemplates(TemplateFileCreator $type): array {
+ $templates = [];
+
try {
$userTemplateFolder = $this->getTemplateFolder();
} catch (\Exception $e) {
return $templates;
}
+
foreach ($type->getMimetypes() as $mimetype) {
foreach ($userTemplateFolder->searchByMime($mimetype) as $templateFile) {
$template = new Template(
@@ -207,6 +254,30 @@ class TemplateManager implements ITemplateManager {
return $templates;
}
+ /*
+ * @return list<Field>
+ */
+ private function getTemplateFields(TemplateFileCreator $type, int $fileId): array {
+ $providerTemplates = $this->getProviderTemplates($type);
+ $userTemplates = $this->getUserTemplates($type);
+
+ $matchedTemplates = array_filter(
+ array_merge($providerTemplates, $userTemplates),
+ function (Template $template) use ($fileId) {
+ return $template->jsonSerialize()['fileid'] === $fileId;
+ });
+
+ if (empty($matchedTemplates)) {
+ return [];
+ }
+
+ $this->eventDispatcher->dispatchTyped(new BeforeGetTemplatesEvent($matchedTemplates, true));
+
+ return array_values(array_map(function (Template $template) {
+ return $template->jsonSerialize()['fields'] ?? [];
+ }, $matchedTemplates));
+ }
+
/**
* @param Node|File $file
* @return array
diff --git a/lib/private/Files/Type/Detection.php b/lib/private/Files/Type/Detection.php
index b1e4c098e54..6af6ce1a0b1 100644
--- a/lib/private/Files/Type/Detection.php
+++ b/lib/private/Files/Type/Detection.php
@@ -9,6 +9,8 @@ declare(strict_types=1);
namespace OC\Files\Type;
use OCP\Files\IMimeTypeDetector;
+use OCP\IBinaryFinder;
+use OCP\ITempManager;
use OCP\IURLGenerator;
use Psr\Log\LoggerInterface;
@@ -22,13 +24,17 @@ use Psr\Log\LoggerInterface;
class Detection implements IMimeTypeDetector {
private const CUSTOM_MIMETYPEMAPPING = 'mimetypemapping.json';
private const CUSTOM_MIMETYPEALIASES = 'mimetypealiases.json';
+ private const CUSTOM_MIMETYPENAMES = 'mimetypenames.json';
- protected array $mimetypes = [];
+ /** @var array<list{string, string|null}> */
+ protected array $mimeTypes = [];
protected array $secureMimeTypes = [];
- protected array $mimetypeIcons = [];
+ protected array $mimeTypeIcons = [];
/** @var array<string,string> */
protected array $mimeTypeAlias = [];
+ /** @var array<string,string> */
+ protected array $mimeTypesNames = [];
public function __construct(
private IURLGenerator $urlGenerator,
@@ -39,40 +45,47 @@ class Detection implements IMimeTypeDetector {
}
/**
- * Add an extension -> mimetype mapping
+ * Add an extension -> MIME type mapping
*
- * $mimetype is the assumed correct mime type
+ * $mimeType is the assumed correct mime type
* The optional $secureMimeType is an alternative to send to send
* to avoid potential XSS.
*
* @param string $extension
- * @param string $mimetype
+ * @param string $mimeType
* @param string|null $secureMimeType
*/
- public function registerType(string $extension,
- string $mimetype,
+ public function registerType(
+ string $extension,
+ string $mimeType,
?string $secureMimeType = null): void {
- $this->mimetypes[$extension] = [$mimetype, $secureMimeType];
- $this->secureMimeTypes[$mimetype] = $secureMimeType ?: $mimetype;
+ // Make sure the extension is a string
+ // https://github.com/nextcloud/server/issues/42902
+ $this->mimeTypes[$extension] = [$mimeType, $secureMimeType];
+ $this->secureMimeTypes[$mimeType] = $secureMimeType ?? $mimeType;
}
/**
- * Add an array of extension -> mimetype mappings
+ * Add an array of extension -> MIME type mappings
*
- * The mimetype value is in itself an array where the first index is
- * the assumed correct mimetype and the second is either a secure alternative
+ * The mimeType value is in itself an array where the first index is
+ * the assumed correct mimeType and the second is either a secure alternative
* or null if the correct is considered secure.
*
* @param array $types
*/
public function registerTypeArray(array $types): void {
- $this->mimetypes = array_merge($this->mimetypes, $types);
+ // Register the types,
+ foreach ($types as $extension => $mimeType) {
+ $this->registerType((string)$extension, $mimeType[0], $mimeType[1] ?? null);
+ }
- // Update the alternative mimetypes to avoid having to look them up each time.
- foreach ($this->mimetypes as $extension => $mimeType) {
- if (str_starts_with($extension, '_comment')) {
+ // Update the alternative mimeTypes to avoid having to look them up each time.
+ foreach ($this->mimeTypes as $extension => $mimeType) {
+ if (str_starts_with((string)$extension, '_comment')) {
continue;
}
+
$this->secureMimeTypes[$mimeType[0]] = $mimeType[1] ?? $mimeType[0];
if (isset($mimeType[1])) {
$this->secureMimeTypes[$mimeType[1]] = $mimeType[1];
@@ -93,7 +106,7 @@ class Detection implements IMimeTypeDetector {
}
/**
- * Add the mimetype aliases if they are not yet present
+ * Add the MIME type aliases if they are not yet present
*/
private function loadAliases(): void {
if (!empty($this->mimeTypeAlias)) {
@@ -119,29 +132,48 @@ class Detection implements IMimeTypeDetector {
}
/**
- * Add mimetype mappings if they are not yet present
+ * Add MIME type mappings if they are not yet present
*/
private function loadMappings(): void {
- if (!empty($this->mimetypes)) {
+ if (!empty($this->mimeTypes)) {
return;
}
- $mimetypeMapping = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypemapping.dist.json'), true);
- $mimetypeMapping = $this->loadCustomDefinitions(self::CUSTOM_MIMETYPEMAPPING, $mimetypeMapping);
+ $mimeTypeMapping = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypemapping.dist.json'), true);
+ $mimeTypeMapping = $this->loadCustomDefinitions(self::CUSTOM_MIMETYPEMAPPING, $mimeTypeMapping);
- $this->registerTypeArray($mimetypeMapping);
+ $this->registerTypeArray($mimeTypeMapping);
}
/**
- * @return array
+ * @return array<list{string, string|null}>
*/
public function getAllMappings(): array {
$this->loadMappings();
- return $this->mimetypes;
+ return $this->mimeTypes;
+ }
+
+ private function loadNamings(): void {
+ if (!empty($this->mimeTypesNames)) {
+ return;
+ }
+
+ $mimeTypeMapping = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypenames.dist.json'), true);
+ $mimeTypeMapping = $this->loadCustomDefinitions(self::CUSTOM_MIMETYPENAMES, $mimeTypeMapping);
+
+ $this->mimeTypesNames = $mimeTypeMapping;
+ }
+
+ /**
+ * @return array<string,string>
+ */
+ public function getAllNamings(): array {
+ $this->loadNamings();
+ return $this->mimeTypesNames;
}
/**
- * detect mimetype only based on filename, content of file is not used
+ * detect MIME type only based on filename, content of file is not used
*
* @param string $path
* @return string
@@ -163,8 +195,8 @@ class Detection implements IMimeTypeDetector {
$extension = strrchr($fileName, '.');
if ($extension !== false) {
$extension = strtolower($extension);
- $extension = substr($extension, 1); //remove leading .
- return $this->mimetypes[$extension][0] ?? 'application/octet-stream';
+ $extension = substr($extension, 1); // remove leading .
+ return $this->mimeTypes[$extension][0] ?? 'application/octet-stream';
}
}
@@ -172,7 +204,8 @@ class Detection implements IMimeTypeDetector {
}
/**
- * detect mimetype only based on the content of file
+ * Detect MIME type only based on the content of file.
+ *
* @param string $path
* @return string
* @since 18.0.0
@@ -185,14 +218,10 @@ class Detection implements IMimeTypeDetector {
return 'httpd/unix-directory';
}
- if (function_exists('finfo_open')
- && function_exists('finfo_file')
- && $finfo = finfo_open(FILEINFO_MIME)) {
- $info = @finfo_file($finfo, $path);
- finfo_close($finfo);
- if ($info) {
- $info = strtolower($info);
- $mimeType = str_contains($info, ';') ? substr($info, 0, strpos($info, ';')) : $info;
+ if (class_exists(finfo::class)) {
+ $finfo = new finfo(FILEINFO_MIME_TYPE);
+ $mimeType = @$finfo->file($path);
+ if ($mimeType) {
$mimeType = $this->getSecureMimeType($mimeType);
if ($mimeType !== 'application/octet-stream') {
return $mimeType;
@@ -208,7 +237,7 @@ class Detection implements IMimeTypeDetector {
if (function_exists('mime_content_type')) {
// use mime magic extension if available
$mimeType = mime_content_type($path);
- if ($mimeType !== false) {
+ if ($mimeType) {
$mimeType = $this->getSecureMimeType($mimeType);
if ($mimeType !== 'application/octet-stream') {
return $mimeType;
@@ -216,28 +245,30 @@ class Detection implements IMimeTypeDetector {
}
}
- if (\OC_Helper::canExecute('file')) {
+ $binaryFinder = \OCP\Server::get(IBinaryFinder::class);
+ $program = $binaryFinder->findBinaryPath('file');
+ if ($program !== false) {
// it looks like we have a 'file' command,
// lets see if it does have mime support
$path = escapeshellarg($path);
- $fp = popen("test -f $path && file -b --mime-type $path", 'r');
- $mimeType = fgets($fp);
- pclose($fp);
-
- if ($mimeType !== false) {
- //trim the newline
- $mimeType = trim($mimeType);
- $mimeType = $this->getSecureMimeType($mimeType);
- if ($mimeType !== 'application/octet-stream') {
+ $fp = popen("test -f $path && $program -b --mime-type $path", 'r');
+ if ($fp !== false) {
+ $mimeType = fgets($fp);
+ pclose($fp);
+ if ($mimeType) {
+ //trim the newline
+ $mimeType = trim($mimeType);
+ $mimeType = $this->getSecureMimeType($mimeType);
return $mimeType;
}
}
}
+
return 'application/octet-stream';
}
/**
- * detect mimetype based on both filename and content
+ * Detect MIME type based on both filename and content
*
* @param string $path
* @return string
@@ -253,29 +284,31 @@ class Detection implements IMimeTypeDetector {
}
/**
- * detect mimetype based on the content of a string
+ * Detect MIME type based on the content of a string
*
* @param string $data
* @return string
*/
public function detectString($data): string {
- if (function_exists('finfo_open') && function_exists('finfo_file')) {
- $finfo = finfo_open(FILEINFO_MIME);
- $info = finfo_buffer($finfo, $data);
- return str_contains($info, ';') ? substr($info, 0, strpos($info, ';')) : $info;
+ if (class_exists(finfo::class)) {
+ $finfo = new finfo(FILEINFO_MIME_TYPE);
+ $mimeType = $finfo->buffer($data);
+ if ($mimeType) {
+ return $mimeType;
+ }
}
- $tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
+ $tmpFile = \OCP\Server::get(ITempManager::class)->getTemporaryFile();
$fh = fopen($tmpFile, 'wb');
fwrite($fh, $data, 8024);
fclose($fh);
- $mime = $this->detect($tmpFile);
+ $mimeType = $this->detect($tmpFile);
unset($tmpFile);
- return $mime;
+ return $mimeType;
}
/**
- * Get a secure mimetype that won't expose potential XSS.
+ * Get a secure MIME type that won't expose potential XSS.
*
* @param string $mimeType
* @return string
@@ -288,57 +321,56 @@ class Detection implements IMimeTypeDetector {
/**
* Get path to the icon of a file type
- * @param string $mimetype the MIME type
+ * @param string $mimeType the MIME type
* @return string the url
*/
- public function mimeTypeIcon($mimetype): string {
+ public function mimeTypeIcon($mimeType): string {
$this->loadAliases();
- while (isset($this->mimeTypeAlias[$mimetype])) {
- $mimetype = $this->mimeTypeAlias[$mimetype];
+ while (isset($this->mimeTypeAlias[$mimeType])) {
+ $mimeType = $this->mimeTypeAlias[$mimeType];
}
- if (isset($this->mimetypeIcons[$mimetype])) {
- return $this->mimetypeIcons[$mimetype];
+ if (isset($this->mimeTypeIcons[$mimeType])) {
+ return $this->mimeTypeIcons[$mimeType];
}
// Replace slash and backslash with a minus
- $icon = str_replace(['/', '\\'], '-', $mimetype);
+ $icon = str_replace(['/', '\\'], '-', $mimeType);
// Is it a dir?
- if ($mimetype === 'dir') {
- $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder.svg');
- return $this->mimetypeIcons[$mimetype];
+ if ($mimeType === 'dir') {
+ $this->mimeTypeIcons[$mimeType] = $this->urlGenerator->imagePath('core', 'filetypes/folder.svg');
+ return $this->mimeTypeIcons[$mimeType];
}
- if ($mimetype === 'dir-shared') {
- $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-shared.svg');
- return $this->mimetypeIcons[$mimetype];
+ if ($mimeType === 'dir-shared') {
+ $this->mimeTypeIcons[$mimeType] = $this->urlGenerator->imagePath('core', 'filetypes/folder-shared.svg');
+ return $this->mimeTypeIcons[$mimeType];
}
- if ($mimetype === 'dir-external') {
- $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-external.svg');
- return $this->mimetypeIcons[$mimetype];
+ if ($mimeType === 'dir-external') {
+ $this->mimeTypeIcons[$mimeType] = $this->urlGenerator->imagePath('core', 'filetypes/folder-external.svg');
+ return $this->mimeTypeIcons[$mimeType];
}
// Icon exists?
try {
- $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $icon . '.svg');
- return $this->mimetypeIcons[$mimetype];
+ $this->mimeTypeIcons[$mimeType] = $this->urlGenerator->imagePath('core', 'filetypes/' . $icon . '.svg');
+ return $this->mimeTypeIcons[$mimeType];
} catch (\RuntimeException $e) {
// Specified image not found
}
// Try only the first part of the filetype
-
if (strpos($icon, '-')) {
$mimePart = substr($icon, 0, strpos($icon, '-'));
try {
- $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $mimePart . '.svg');
- return $this->mimetypeIcons[$mimetype];
+ $this->mimeTypeIcons[$mimeType] = $this->urlGenerator->imagePath('core', 'filetypes/' . $mimePart . '.svg');
+ return $this->mimeTypeIcons[$mimeType];
} catch (\RuntimeException $e) {
- // Image for the first part of the mimetype not found
+ // Image for the first part of the MIME type not found
}
}
- $this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/file.svg');
- return $this->mimetypeIcons[$mimetype];
+ $this->mimeTypeIcons[$mimeType] = $this->urlGenerator->imagePath('core', 'filetypes/file.svg');
+ return $this->mimeTypeIcons[$mimeType];
}
}
diff --git a/lib/private/Files/Type/Loader.php b/lib/private/Files/Type/Loader.php
index 247acf0141a..5fbe4139759 100644
--- a/lib/private/Files/Type/Loader.php
+++ b/lib/private/Files/Type/Loader.php
@@ -21,8 +21,6 @@ use OCP\IDBConnection;
class Loader implements IMimeTypeLoader {
use TTransactional;
- private IDBConnection $dbConnection;
-
/** @psalm-var array<int, string> */
protected array $mimetypes;
@@ -32,8 +30,9 @@ class Loader implements IMimeTypeLoader {
/**
* @param IDBConnection $dbConnection
*/
- public function __construct(IDBConnection $dbConnection) {
- $this->dbConnection = $dbConnection;
+ public function __construct(
+ private IDBConnection $dbConnection,
+ ) {
$this->mimetypes = [];
$this->mimetypeIds = [];
}
@@ -115,7 +114,7 @@ class Loader implements IMimeTypeLoader {
throw new \Exception("Database threw an unique constraint on inserting a new mimetype, but couldn't return the ID for this very mimetype");
}
- $mimetypeId = (int) $id;
+ $mimetypeId = (int)$id;
}
$this->mimetypes[$mimetypeId] = $mimetype;
@@ -136,8 +135,8 @@ class Loader implements IMimeTypeLoader {
$result->closeCursor();
foreach ($results as $row) {
- $this->mimetypes[(int) $row['id']] = $row['mimetype'];
- $this->mimetypeIds[$row['mimetype']] = (int) $row['id'];
+ $this->mimetypes[(int)$row['id']] = $row['mimetype'];
+ $this->mimetypeIds[$row['mimetype']] = (int)$row['id'];
}
}
@@ -161,6 +160,6 @@ class Loader implements IMimeTypeLoader {
$update->func()->lower('name'),
$update->createNamedParameter('%' . $this->dbConnection->escapeLikeParameter('.' . $ext))
));
- return $update->execute();
+ return $update->executeStatement();
}
}
diff --git a/lib/private/Files/Utils/PathHelper.php b/lib/private/Files/Utils/PathHelper.php
index a6ae029b957..db1294bcc10 100644
--- a/lib/private/Files/Utils/PathHelper.php
+++ b/lib/private/Files/Utils/PathHelper.php
@@ -37,6 +37,8 @@ class PathHelper {
if ($path === '' or $path === '/') {
return '/';
}
+ // No null bytes
+ $path = str_replace(chr(0), '', $path);
//no windows style slashes
$path = str_replace('\\', '/', $path);
//add leading slash
diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php
index bcc54dea0dc..576cb66b3cf 100644
--- a/lib/private/Files/Utils/Scanner.php
+++ b/lib/private/Files/Utils/Scanner.php
@@ -23,11 +23,13 @@ use OCP\Files\Events\FileScannedEvent;
use OCP\Files\Events\FolderScannedEvent;
use OCP\Files\Events\NodeAddedToCache;
use OCP\Files\Events\NodeRemovedFromCache;
+use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorage;
use OCP\Files\StorageNotAvailableException;
use OCP\IDBConnection;
use OCP\Lock\ILockingProvider;
+use OCP\Lock\LockedException;
use Psr\Log\LoggerInterface;
/**
@@ -85,7 +87,7 @@ class Scanner extends PublicEmitter {
* get all storages for $dir
*
* @param string $dir
- * @return \OC\Files\Mount\MountPoint[]
+ * @return array<string, IMountPoint>
*/
protected function getMounts($dir) {
//TODO: move to the node based fileapi once that's done
@@ -96,8 +98,9 @@ class Scanner extends PublicEmitter {
$mounts = $mountManager->findIn($dir);
$mounts[] = $mountManager->find($dir);
$mounts = array_reverse($mounts); //start with the mount of $dir
+ $mountPoints = array_map(fn ($mount) => $mount->getMountPoint(), $mounts);
- return $mounts;
+ return array_combine($mountPoints, $mounts);
}
/**
@@ -106,6 +109,7 @@ class Scanner extends PublicEmitter {
* @param \OC\Files\Mount\MountPoint $mount
*/
protected function attachListener($mount) {
+ /** @var \OC\Files\Cache\Scanner $scanner */
$scanner = $mount->getStorage()->getScanner();
$scanner->listen('\OC\Files\Cache\Scanner', 'scanFile', function ($path) use ($mount) {
$this->emit('\OC\Files\Utils\Scanner', 'scanFile', [$mount->getMountPoint() . $path]);
@@ -145,6 +149,7 @@ class Scanner extends PublicEmitter {
continue;
}
+ /** @var \OC\Files\Cache\Scanner $scanner */
$scanner = $storage->getScanner();
$this->attachListener($mount);
@@ -200,7 +205,10 @@ class Scanner extends PublicEmitter {
foreach (['', 'files'] as $path) {
if (!$storage->isCreatable($path)) {
$fullPath = $storage->getSourcePath($path);
- if (!$storage->is_dir($path) && $storage->getCache()->inCache($path)) {
+ if (isset($mounts[$mount->getMountPoint() . $path . '/'])) {
+ // /<user>/files is overwritten by a mountpoint, so this check is irrelevant
+ break;
+ } elseif (!$storage->is_dir($path) && $storage->getCache()->inCache($path)) {
throw new NotFoundException("User folder $fullPath exists in cache but not on disk");
} elseif ($storage->is_dir($path)) {
$ownerUid = fileowner($fullPath);
@@ -221,6 +229,7 @@ class Scanner extends PublicEmitter {
continue;
}
$relativePath = $mount->getInternalPath($dir);
+ /** @var \OC\Files\Cache\Scanner $scanner */
$scanner = $storage->getScanner();
$scanner->setUseTransactions(false);
$this->attachListener($mount);
@@ -252,7 +261,15 @@ class Scanner extends PublicEmitter {
try {
$propagator = $storage->getPropagator();
$propagator->beginBatch();
- $scanner->scan($relativePath, $recursive, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE);
+ try {
+ $scanner->scan($relativePath, $recursive, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE);
+ } catch (LockedException $e) {
+ if (is_string($e->getReadablePath()) && str_starts_with($e->getReadablePath(), 'scanner::')) {
+ throw new LockedException("scanner::$dir", $e, $e->getExistingLock());
+ } else {
+ throw $e;
+ }
+ }
$cache = $storage->getCache();
if ($cache instanceof Cache) {
// only re-calculate for the root folder we scanned, anything below that is taken care of by the scanner
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index 0150a3e117a..a852f453963 100644
--- a/lib/private/Files/View.php
+++ b/lib/private/Files/View.php
@@ -10,12 +10,14 @@ namespace OC\Files;
use Icewind\Streams\CallbackWrapper;
use OC\Files\Mount\MoveableMount;
use OC\Files\Storage\Storage;
+use OC\Files\Storage\Wrapper\Quota;
use OC\Share\Share;
use OC\User\LazyUser;
use OC\User\Manager as UserManager;
use OC\User\User;
use OCA\Files_Sharing\SharedMount;
use OCP\Constants;
+use OCP\Files;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\ConnectionLostException;
use OCP\Files\EmptyFileNameException;
@@ -24,10 +26,13 @@ use OCP\Files\ForbiddenException;
use OCP\Files\InvalidCharacterInPathException;
use OCP\Files\InvalidDirectoryException;
use OCP\Files\InvalidPathException;
+use OCP\Files\Mount\IMountManager;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
use OCP\Files\ReservedWordException;
use OCP\IUser;
+use OCP\IUserManager;
+use OCP\L10N\IFactory;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use OCP\Server;
@@ -221,7 +226,7 @@ class View {
$relPath = '/' . $pathParts[3];
$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
\OC_Hook::emit(
- Filesystem::CLASSNAME, "umount",
+ Filesystem::CLASSNAME, 'umount',
[Filesystem::signal_param_path => $relPath]
);
$this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
@@ -229,7 +234,7 @@ class View {
$this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
if ($result) {
\OC_Hook::emit(
- Filesystem::CLASSNAME, "post_umount",
+ Filesystem::CLASSNAME, 'post_umount',
[Filesystem::signal_param_path => $relPath]
);
}
@@ -272,6 +277,12 @@ class View {
}
}
+ protected function copyUpdate(Storage $sourceStorage, Storage $targetStorage, string $sourceInternalPath, string $targetInternalPath): void {
+ if ($this->updaterEnabled) {
+ $targetStorage->getUpdater()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+ }
+ }
+
/**
* @param string $path
* @return bool|mixed
@@ -598,13 +609,13 @@ class View {
$this->lockFile($path, ILockingProvider::LOCK_SHARED);
$exists = $this->file_exists($path);
- $run = true;
if ($this->shouldEmitHooks($path)) {
+ $run = true;
$this->emit_file_hooks_pre($exists, $path, $run);
- }
- if (!$run) {
- $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
- return false;
+ if (!$run) {
+ $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
+ return false;
+ }
}
try {
@@ -619,7 +630,7 @@ class View {
[$storage, $internalPath] = $this->resolvePath($path);
$target = $storage->fopen($internalPath, 'w');
if ($target) {
- [, $result] = \OC_Helper::streamCopy($data, $target);
+ [, $result] = Files::streamCopy($data, $target, true);
fclose($target);
fclose($data);
@@ -688,18 +699,24 @@ class View {
*
* @param string $source source path
* @param string $target target path
+ * @param array $options
*
* @return bool|mixed
* @throws LockedException
*/
- public function rename($source, $target) {
+ public function rename($source, $target, array $options = []) {
+ $checkSubMounts = $options['checkSubMounts'] ?? true;
+
$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($source));
$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($target));
if (str_starts_with($absolutePath2, $absolutePath1 . '/')) {
- throw new ForbiddenException("Moving a folder into a child folder is forbidden", false);
+ throw new ForbiddenException('Moving a folder into a child folder is forbidden', false);
}
+ /** @var IMountManager $mountManager */
+ $mountManager = \OC::$server->get(IMountManager::class);
+
$targetParts = explode('/', $absolutePath2);
$targetUser = $targetParts[1] ?? null;
$result = false;
@@ -716,6 +733,12 @@ class View {
return false;
}
+ try {
+ $this->verifyPath(dirname($target), basename($target));
+ } catch (InvalidPathException) {
+ return false;
+ }
+
$this->lockFile($source, ILockingProvider::LOCK_SHARED, true);
try {
$this->lockFile($target, ILockingProvider::LOCK_SHARED, true);
@@ -739,8 +762,6 @@ class View {
}
}
if ($run) {
- $this->verifyPath(dirname($target), basename($target));
-
$manager = Filesystem::getMountManager();
$mount1 = $this->getMount($source);
$mount2 = $this->getMount($target);
@@ -753,24 +774,28 @@ class View {
try {
$this->changeLock($target, ILockingProvider::LOCK_EXCLUSIVE, true);
+ if ($checkSubMounts) {
+ $movedMounts = $mountManager->findIn($this->getAbsolutePath($source));
+ } else {
+ $movedMounts = [];
+ }
+
if ($internalPath1 === '') {
- if ($mount1 instanceof MoveableMount) {
- $sourceParentMount = $this->getMount(dirname($source));
- if ($sourceParentMount === $mount2 && $this->targetIsNotShared($targetUser, $absolutePath2)) {
- /**
- * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
- */
- $sourceMountPoint = $mount1->getMountPoint();
- $result = $mount1->moveMount($absolutePath2);
- $manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
- } else {
- $result = false;
- }
- } else {
- $result = false;
- }
+ $sourceParentMount = $this->getMount(dirname($source));
+ $movedMounts[] = $mount1;
+ $this->validateMountMove($movedMounts, $sourceParentMount, $mount2, !$this->targetIsNotShared($targetUser, $absolutePath2));
+ /**
+ * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
+ */
+ $sourceMountPoint = $mount1->getMountPoint();
+ $result = $mount1->moveMount($absolutePath2);
+ $manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
+
// moving a file/folder within the same mount point
} elseif ($storage1 === $storage2) {
+ if (count($movedMounts) > 0) {
+ $this->validateMountMove($movedMounts, $mount1, $mount2, !$this->targetIsNotShared($targetUser, $absolutePath2));
+ }
if ($storage1) {
$result = $storage1->rename($internalPath1, $internalPath2);
} else {
@@ -778,6 +803,9 @@ class View {
}
// moving a file/folder between storages (from $storage1 to $storage2)
} else {
+ if (count($movedMounts) > 0) {
+ $this->validateMountMove($movedMounts, $mount1, $mount2, !$this->targetIsNotShared($targetUser, $absolutePath2));
+ }
$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
}
@@ -828,6 +856,56 @@ class View {
}
/**
+ * @throws ForbiddenException
+ */
+ private function validateMountMove(array $mounts, IMountPoint $sourceMount, IMountPoint $targetMount, bool $targetIsShared): void {
+ $targetPath = $this->getRelativePath($targetMount->getMountPoint());
+ if ($targetPath) {
+ $targetPath = trim($targetPath, '/');
+ } else {
+ $targetPath = $targetMount->getMountPoint();
+ }
+
+ $l = \OC::$server->get(IFactory::class)->get('files');
+ foreach ($mounts as $mount) {
+ $sourcePath = $this->getRelativePath($mount->getMountPoint());
+ if ($sourcePath) {
+ $sourcePath = trim($sourcePath, '/');
+ } else {
+ $sourcePath = $mount->getMountPoint();
+ }
+
+ if (!$mount instanceof MoveableMount) {
+ throw new ForbiddenException($l->t('Storage %s cannot be moved', [$sourcePath]), false);
+ }
+
+ if ($targetIsShared) {
+ if ($sourceMount instanceof SharedMount) {
+ throw new ForbiddenException($l->t('Moving a share (%s) into a shared folder is not allowed', [$sourcePath]), false);
+ } else {
+ throw new ForbiddenException($l->t('Moving a storage (%s) into a shared folder is not allowed', [$sourcePath]), false);
+ }
+ }
+
+ if ($sourceMount !== $targetMount) {
+ if ($sourceMount instanceof SharedMount) {
+ if ($targetMount instanceof SharedMount) {
+ throw new ForbiddenException($l->t('Moving a share (%s) into another share (%s) is not allowed', [$sourcePath, $targetPath]), false);
+ } else {
+ throw new ForbiddenException($l->t('Moving a share (%s) into another storage (%s) is not allowed', [$sourcePath, $targetPath]), false);
+ }
+ } else {
+ if ($targetMount instanceof SharedMount) {
+ throw new ForbiddenException($l->t('Moving a storage (%s) into a share (%s) is not allowed', [$sourcePath, $targetPath]), false);
+ } else {
+ throw new ForbiddenException($l->t('Moving a storage (%s) into another storage (%s) is not allowed', [$sourcePath, $targetPath]), false);
+ }
+ }
+ }
+ }
+ }
+
+ /**
* Copy a file/folder from the source path to target path
*
* @param string $source source path
@@ -860,7 +938,7 @@ class View {
try {
$exists = $this->file_exists($target);
- if ($this->shouldEmitHooks()) {
+ if ($this->shouldEmitHooks($target)) {
\OC_Hook::emit(
Filesystem::CLASSNAME,
Filesystem::signal_copy,
@@ -893,12 +971,14 @@ class View {
$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
}
- $this->writeUpdate($storage2, $internalPath2);
+ if ($result) {
+ $this->copyUpdate($storage1, $storage2, $internalPath1, $internalPath2);
+ }
$this->changeLock($target, ILockingProvider::LOCK_SHARED);
$lockTypePath2 = ILockingProvider::LOCK_SHARED;
- if ($this->shouldEmitHooks() && $result !== false) {
+ if ($this->shouldEmitHooks($target) && $result !== false) {
\OC_Hook::emit(
Filesystem::CLASSNAME,
Filesystem::signal_post_copy,
@@ -1326,7 +1406,7 @@ class View {
*
* @param string $path
* @param bool|string $includeMountPoints true to add mountpoint sizes,
- * 'ext' to add only ext storage mount point sizes. Defaults to true.
+ * 'ext' to add only ext storage mount point sizes. Defaults to true.
* @return \OC\Files\FileInfo|false False if file does not exist
*/
public function getFileInfo($path, $includeMountPoints = true) {
@@ -1334,9 +1414,6 @@ class View {
if (!Filesystem::isValidPath($path)) {
return false;
}
- if (Cache\Scanner::isPartialFile($path)) {
- return $this->getPartFileInfo($path);
- }
$relativePath = $path;
$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
@@ -1347,15 +1424,23 @@ class View {
$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
if (!$data instanceof ICacheEntry) {
+ if (Cache\Scanner::isPartialFile($relativePath)) {
+ return $this->getPartFileInfo($relativePath);
+ }
+
return false;
}
if ($mount instanceof MoveableMount && $internalPath === '') {
$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
}
+ if ($internalPath === '' && $data['name']) {
+ $data['name'] = basename($path);
+ }
+
$ownerId = $storage->getOwner($internalPath);
$owner = null;
- if ($ownerId !== null && $ownerId !== false) {
+ if ($ownerId !== false) {
// ownerId might be null if files are accessed with an access token without file system access
$owner = $this->getUserObjectForOwner($ownerId);
}
@@ -1383,8 +1468,7 @@ class View {
public function addSubMounts(FileInfo $info, $extOnly = false): void {
$mounts = Filesystem::getMountManager()->findIn($info->getPath());
$info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
- $subStorage = $mount->getStorage();
- return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage);
+ return !($extOnly && $mount instanceof SharedMount);
}));
}
@@ -1441,7 +1525,12 @@ class View {
if ($sharingDisabled) {
$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
}
- $owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
+ $ownerId = $storage->getOwner($content['path']);
+ if ($ownerId !== false) {
+ $owner = $this->getUserObjectForOwner($ownerId);
+ } else {
+ $owner = null;
+ }
return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
}, $contents);
$files = array_combine($fileNames, $fileInfos);
@@ -1487,6 +1576,32 @@ class View {
if ($pos = strpos($relativePath, '/')) {
//mountpoint inside subfolder add size to the correct folder
$entryName = substr($relativePath, 0, $pos);
+
+ // Create parent folders if the mountpoint is inside a subfolder that doesn't exist yet
+ if (!isset($files[$entryName])) {
+ try {
+ [$storage, ] = $this->resolvePath($path . '/' . $entryName);
+ // make sure we can create the mountpoint folder, even if the user has a quota of 0
+ if ($storage->instanceOfStorage(Quota::class)) {
+ $storage->enableQuota(false);
+ }
+
+ if ($this->mkdir($path . '/' . $entryName) !== false) {
+ $info = $this->getFileInfo($path . '/' . $entryName);
+ if ($info !== false) {
+ $files[$entryName] = $info;
+ }
+ }
+
+ if ($storage->instanceOfStorage(Quota::class)) {
+ $storage->enableQuota(true);
+ }
+ } catch (\Exception $e) {
+ // Creating the parent folder might not be possible, for example due to a lack of permissions.
+ $this->logger->debug('Failed to create non-existent parent', ['exception' => $e, 'path' => $path . '/' . $entryName]);
+ }
+ }
+
if (isset($files[$entryName])) {
$files[$entryName]->addSubEntry($rootEntry, $mountPoint);
}
@@ -1509,7 +1624,12 @@ class View {
$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
}
- $owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
+ $ownerId = $subStorage->getOwner('');
+ if ($ownerId !== false) {
+ $owner = $this->getUserObjectForOwner($ownerId);
+ } else {
+ $owner = null;
+ }
$files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
}
}
@@ -1626,7 +1746,12 @@ class View {
$internalPath = $result['path'];
$path = $mountPoint . $result['path'];
$result['path'] = substr($mountPoint . $result['path'], $rootLength);
- $owner = $userManager->get($storage->getOwner($internalPath));
+ $ownerId = $storage->getOwner($internalPath);
+ if ($ownerId !== false) {
+ $owner = $userManager->get($ownerId);
+ } else {
+ $owner = null;
+ }
$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
}
}
@@ -1645,7 +1770,12 @@ class View {
$internalPath = $result['path'];
$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
$path = rtrim($mountPoint . $internalPath, '/');
- $owner = $userManager->get($storage->getOwner($internalPath));
+ $ownerId = $storage->getOwner($internalPath);
+ if ($ownerId !== false) {
+ $owner = $userManager->get($ownerId);
+ } else {
+ $owner = null;
+ }
$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
}
}
@@ -1658,11 +1788,9 @@ class View {
/**
* Get the owner for a file or folder
*
- * @param string $path
- * @return string the user id of the owner
* @throws NotFoundException
*/
- public function getOwner($path) {
+ public function getOwner(string $path): string {
$info = $this->getFileInfo($path);
if (!$info) {
throw new NotFoundException($path . ' not found while trying to get owner');
@@ -1700,43 +1828,25 @@ class View {
* @return string
* @throws NotFoundException
*/
- public function getPath($id, ?int $storageId = null) {
+ public function getPath($id, ?int $storageId = null): string {
$id = (int)$id;
- $manager = Filesystem::getMountManager();
- $mounts = $manager->findIn($this->fakeRoot);
- $mounts[] = $manager->find($this->fakeRoot);
- $mounts = array_filter($mounts);
- // reverse the array, so we start with the storage this view is in
- // which is the most likely to contain the file we're looking for
- $mounts = array_reverse($mounts);
-
- // put non-shared mounts in front of the shared mount
- // this prevents unneeded recursion into shares
- usort($mounts, function (IMountPoint $a, IMountPoint $b) {
- return $a instanceof SharedMount && (!$b instanceof SharedMount) ? 1 : -1;
- });
+ $rootFolder = Server::get(Files\IRootFolder::class);
- if (!is_null($storageId)) {
- $mounts = array_filter($mounts, function (IMountPoint $mount) use ($storageId) {
- return $mount->getNumericStorageId() === $storageId;
- });
+ $node = $rootFolder->getFirstNodeByIdInPath($id, $this->getRoot());
+ if ($node) {
+ if ($storageId === null || $storageId === $node->getStorage()->getCache()->getNumericStorageId()) {
+ return $this->getRelativePath($node->getPath()) ?? '';
+ }
+ } else {
+ throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
}
- foreach ($mounts as $mount) {
- /**
- * @var \OC\Files\Mount\MountPoint $mount
- */
- if ($mount->getStorage()) {
- $cache = $mount->getStorage()->getCache();
- $internalPath = $cache->getPathById($id);
- if (is_string($internalPath)) {
- $fullPath = $mount->getMountPoint() . $internalPath;
- if (!is_null($path = $this->getRelativePath($fullPath))) {
- return $path;
- }
- }
+ foreach ($rootFolder->getByIdInPath($id, $this->getRoot()) as $node) {
+ if ($storageId === $node->getStorage()->getCache()->getNumericStorageId()) {
+ return $this->getRelativePath($node->getPath()) ?? '';
}
}
+
throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
}
@@ -1776,7 +1886,8 @@ class View {
}, $providers));
foreach ($shares as $share) {
- if (str_starts_with($targetPath, $share->getNode()->getPath())) {
+ $sharedPath = $share->getNode()->getPath();
+ if ($targetPath === $sharedPath || str_starts_with($targetPath, $sharedPath . '/')) {
$this->logger->debug(
'It is not allowed to move one mount point into a shared folder',
['app' => 'files']);
@@ -1794,7 +1905,12 @@ class View {
$mount = $this->getMount($path);
$storage = $mount->getStorage();
$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
- $owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
+ $ownerId = $storage->getOwner($internalPath);
+ if ($ownerId !== false) {
+ $owner = Server::get(IUserManager::class)->get($ownerId);
+ } else {
+ $owner = null;
+ }
return new FileInfo(
$this->getAbsolutePath($path),
$storage,
@@ -1817,22 +1933,39 @@ class View {
/**
* @param string $path
* @param string $fileName
+ * @param bool $readonly Check only if the path is allowed for read-only access
* @throws InvalidPathException
*/
- public function verifyPath($path, $fileName): void {
+ public function verifyPath($path, $fileName, $readonly = false): void {
+ // All of the view's functions disallow '..' in the path so we can short cut if the path is invalid
+ if (!Filesystem::isValidPath($path ?: '/')) {
+ $l = \OCP\Util::getL10N('lib');
+ throw new InvalidPathException($l->t('Path contains invalid segments'));
+ }
+
+ // Short cut for read-only validation
+ if ($readonly) {
+ $validator = Server::get(FilenameValidator::class);
+ if ($validator->isForbidden($fileName)) {
+ $l = \OCP\Util::getL10N('lib');
+ throw new InvalidPathException($l->t('Filename is a reserved word'));
+ }
+ return;
+ }
+
try {
/** @type \OCP\Files\Storage $storage */
[$storage, $internalPath] = $this->resolvePath($path);
$storage->verifyPath($internalPath, $fileName);
} catch (ReservedWordException $ex) {
$l = \OCP\Util::getL10N('lib');
- throw new InvalidPathException($l->t('File name is a reserved word'));
+ throw new InvalidPathException($ex->getMessage() ?: $l->t('Filename is a reserved word'));
} catch (InvalidCharacterInPathException $ex) {
$l = \OCP\Util::getL10N('lib');
- throw new InvalidPathException($l->t('File name contains at least one invalid character'));
+ throw new InvalidPathException($ex->getMessage() ?: $l->t('Filename contains at least one invalid character'));
} catch (FileNameTooLongException $ex) {
$l = \OCP\Util::getL10N('lib');
- throw new InvalidPathException($l->t('File name is too long'));
+ throw new InvalidPathException($l->t('Filename is too long'));
} catch (InvalidDirectoryException $ex) {
$l = \OCP\Util::getL10N('lib');
throw new InvalidPathException($l->t('Dot files are not allowed'));
@@ -1874,7 +2007,7 @@ class View {
*
* @param string $absolutePath absolute path
* @param bool $useParentMount true to return parent mount instead of whatever
- * is mounted directly on the given path, false otherwise
+ * is mounted directly on the given path, false otherwise
* @return IMountPoint mount point for which to apply locks
*/
private function getMountForLock(string $absolutePath, bool $useParentMount = false): IMountPoint {
@@ -1920,9 +2053,9 @@ class View {
);
}
} catch (LockedException $e) {
- // rethrow with the a human-readable path
+ // rethrow with the human-readable path
throw new LockedException(
- $this->getPathRelativeToFiles($absolutePath),
+ $path,
$e,
$e->getExistingLock()
);
@@ -1960,20 +2093,12 @@ class View {
);
}
} catch (LockedException $e) {
- try {
- // rethrow with the a human-readable path
- throw new LockedException(
- $this->getPathRelativeToFiles($absolutePath),
- $e,
- $e->getExistingLock()
- );
- } catch (\InvalidArgumentException $ex) {
- throw new LockedException(
- $absolutePath,
- $ex,
- $e->getExistingLock()
- );
- }
+ // rethrow with the a human-readable path
+ throw new LockedException(
+ $path,
+ $e,
+ $e->getExistingLock()
+ );
}
return true;
@@ -2088,7 +2213,7 @@ class View {
* @param string $absolutePath absolute path which is under "files"
*
* @return string path relative to "files" with trimmed slashes or null
- * if the path was NOT relative to files
+ * if the path was NOT relative to files
*
* @throws \InvalidArgumentException if the given path was not under "files"
* @since 8.1.0