diff options
author | Carl Schwan <carl@carlschwan.eu> | 2022-08-15 15:28:30 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-15 15:28:30 +0200 |
commit | 51b9847fad73a1ca67dbf504358d90bd8f9e71d8 (patch) | |
tree | b104cf1c540dd1dd195ca5fd30c42b888012cbab /lib/private/Files | |
parent | 6d6662ec68c8e15c4c6bfdf1c694794badd412d7 (diff) | |
parent | cb97e8f15c75cc46e345ebfc79dcad1b9c48bd01 (diff) | |
download | nextcloud-server-51b9847fad73a1ca67dbf504358d90bd8f9e71d8.tar.gz nextcloud-server-51b9847fad73a1ca67dbf504358d90bd8f9e71d8.zip |
Merge branch 'master' into display-name-cache-public
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Diffstat (limited to 'lib/private/Files')
33 files changed, 303 insertions, 185 deletions
diff --git a/lib/private/Files/AppData/AppData.php b/lib/private/Files/AppData/AppData.php index 471de799c2f..237fcb42e03 100644 --- a/lib/private/Files/AppData/AppData.php +++ b/lib/private/Files/AppData/AppData.php @@ -26,7 +26,7 @@ declare(strict_types=1); */ namespace OC\Files\AppData; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\SimpleFS\SimpleFolder; use OC\SystemConfig; use OCP\Files\Folder; diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index 949079dfa22..f23635aa01b 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -37,6 +37,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ + namespace OC\Files\Cache; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; @@ -63,7 +64,7 @@ use Psr\Log\LoggerInterface; /** * Metadata cache for a storage * - * The cache stores the metadata for all files and folders in a storage and is kept up to date trough the following mechanisms: + * The cache stores the metadata for all files and folders in a storage and is kept up to date through the following mechanisms: * * - Scanner: scans the storage and updates the cache where needed * - Watcher: checks for changes made to the filesystem outside of the Nextcloud instance and rescans files and folder when a change is detected @@ -188,6 +189,7 @@ class Cache implements ICache { $data['fileid'] = (int)$data['fileid']; $data['parent'] = (int)$data['parent']; $data['size'] = 0 + $data['size']; + $data['unencrypted_size'] = 0 + ($data['unencrypted_size'] ?? 0); $data['mtime'] = (int)$data['mtime']; $data['storage_mtime'] = (int)$data['storage_mtime']; $data['encryptedVersion'] = (int)$data['encrypted']; @@ -428,7 +430,7 @@ class Cache implements ICache { protected function normalizeData(array $data): array { $fields = [ 'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted', - 'etag', 'permissions', 'checksum', 'storage']; + 'etag', 'permissions', 'checksum', 'storage', 'unencrypted_size']; $extensionFields = ['metadata_etag', 'creation_time', 'upload_time']; $doNotCopyStorageMTime = false; @@ -538,7 +540,7 @@ class Cache implements ICache { public function remove($file) { $entry = $this->get($file); - if ($entry) { + if ($entry instanceof ICacheEntry) { $query = $this->getQueryBuilder(); $query->delete('filecache') ->whereFileId($entry->getId()); @@ -580,7 +582,7 @@ class Cache implements ICache { $parentIds = [$entry->getId()]; $queue = [$entry->getId()]; - // we walk depth first trough the file tree, removing all filecache_extended attributes while we walk + // we walk depth first through the file tree, removing all filecache_extended attributes while we walk // and collecting all folder ids to later use to delete the filecache entries while ($entryId = array_pop($queue)) { $children = $this->getFolderContentsById($entryId); @@ -873,8 +875,16 @@ class Cache implements ICache { $id = $entry['fileid']; $query = $this->getQueryBuilder(); - $query->selectAlias($query->func()->sum('size'), 'f1') - ->selectAlias($query->func()->min('size'), 'f2') + $query->selectAlias($query->func()->sum('size'), 'size_sum') + ->selectAlias($query->func()->min('size'), 'size_min') + // in case of encryption being enabled after some files are already uploaded, some entries will have an unencrypted_size of 0 and a non-zero size + ->selectAlias($query->func()->sum( + $query->func()->case([ + ['when' => $query->expr()->eq('unencrypted_size', $query->expr()->literal(0, IQueryBuilder::PARAM_INT)), 'then' => 'size'], + ], 'unencrypted_size') + ), 'unencrypted_sum') + ->selectAlias($query->func()->min('unencrypted_size'), 'unencrypted_min') + ->selectAlias($query->func()->max('unencrypted_size'), 'unencrypted_max') ->from('filecache') ->whereStorageId($this->getNumericStorageId()) ->whereParent($id); @@ -884,7 +894,7 @@ class Cache implements ICache { $result->closeCursor(); if ($row) { - [$sum, $min] = array_values($row); + ['size_sum' => $sum, 'size_min' => $min, 'unencrypted_sum' => $unencryptedSum, 'unencrypted_min' => $unencryptedMin, 'unencrypted_max' => $unencryptedMax] = $row; $sum = 0 + $sum; $min = 0 + $min; if ($min === -1) { @@ -892,8 +902,23 @@ class Cache implements ICache { } else { $totalSize = $sum; } + if ($unencryptedMin === -1 || $min === -1) { + $unencryptedTotal = $unencryptedMin; + } else { + $unencryptedTotal = $unencryptedSum; + } if ($entry['size'] !== $totalSize) { - $this->update($id, ['size' => $totalSize]); + // only set unencrypted size for a folder if any child entries have it set + if ($unencryptedMax > 0) { + $this->update($id, [ + 'size' => $totalSize, + 'unencrypted_size' => $unencryptedTotal, + ]); + } else { + $this->update($id, [ + 'size' => $totalSize, + ]); + } } } } @@ -927,7 +952,7 @@ class Cache implements ICache { * use the one with the highest id gives the best result with the background scanner, since that is most * likely the folder where we stopped scanning previously * - * @return string|bool the path of the folder or false when no folder matched + * @return string|false the path of the folder or false when no folder matched */ public function getIncomplete() { $query = $this->getQueryBuilder(); diff --git a/lib/private/Files/Cache/CacheEntry.php b/lib/private/Files/Cache/CacheEntry.php index 12f0273fb6e..8ac76acf6d1 100644 --- a/lib/private/Files/Cache/CacheEntry.php +++ b/lib/private/Files/Cache/CacheEntry.php @@ -132,4 +132,12 @@ class CacheEntry implements ICacheEntry { public function __clone() { $this->data = array_merge([], $this->data); } + + public function getUnencryptedSize(): int { + if (isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) { + return $this->data['unencrypted_size']; + } else { + return $this->data['size']; + } + } } diff --git a/lib/private/Files/Cache/CacheQueryBuilder.php b/lib/private/Files/Cache/CacheQueryBuilder.php index b5a9101877c..496a8361d77 100644 --- a/lib/private/Files/Cache/CacheQueryBuilder.php +++ b/lib/private/Files/Cache/CacheQueryBuilder.php @@ -41,12 +41,16 @@ class CacheQueryBuilder extends QueryBuilder { parent::__construct($connection, $systemConfig, $logger); } - public function selectFileCache(string $alias = null) { + public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) { $name = $alias ? $alias : 'filecache'; - $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", 'name', 'mimetype', 'mimepart', 'size', 'mtime', - 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'metadata_etag', 'creation_time', 'upload_time') - ->from('filecache', $name) - ->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid')); + $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime', + 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size') + ->from('filecache', $name); + + if ($joinExtendedCache) { + $this->addSelect('metadata_etag', 'creation_time', 'upload_time'); + $this->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid')); + } $this->alias = $name; diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php index 270b2b013f5..a0953baa785 100644 --- a/lib/private/Files/Cache/Propagator.php +++ b/lib/private/Files/Cache/Propagator.php @@ -24,6 +24,7 @@ namespace OC\Files\Cache; +use OC\Files\Storage\Wrapper\Encryption; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Cache\IPropagator; use OCP\Files\Storage\IReliableEtagStorage; @@ -65,7 +66,7 @@ class Propagator implements IPropagator { * @param int $sizeDifference number of bytes the file has grown */ public function propagateChange($internalPath, $time, $sizeDifference = 0) { - // Do not propogate changes in ignored paths + // Do not propagate changes in ignored paths foreach ($this->ignore as $ignore) { if (strpos($internalPath, $ignore) === 0) { return; @@ -113,6 +114,20 @@ class Propagator implements IPropagator { ->andWhere($builder->expr()->in('path_hash', $hashParams)) ->andWhere($builder->expr()->gt('size', $builder->expr()->literal(-1, IQueryBuilder::PARAM_INT))); + if ($this->storage->instanceOfStorage(Encryption::class)) { + // in case of encryption being enabled after some files are already uploaded, some entries will have an unencrypted_size of 0 and a non-zero size + $builder->set('unencrypted_size', $builder->func()->greatest( + $builder->func()->add( + $builder->func()->case([ + ['when' => $builder->expr()->eq('unencrypted_size', $builder->expr()->literal(0, IQueryBuilder::PARAM_INT)), 'then' => 'size'] + ], 'unencrypted_size'), + $builder->createNamedParameter($sizeDifference) + ), + $builder->createNamedParameter(-1, IQueryBuilder::PARAM_INT) + )); + } + + $a = $builder->getSQL(); $builder->execute(); } } diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php index 3bf9abf3524..3529ede9746 100644 --- a/lib/private/Files/Cache/QuerySearchHelper.php +++ b/lib/private/Files/Cache/QuerySearchHelper.php @@ -28,6 +28,7 @@ namespace OC\Files\Cache; use OC\Files\Search\QueryOptimizer\QueryOptimizer; use OC\Files\Search\SearchBinaryOperator; use OC\SystemConfig; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; use OCP\Files\IMimeTypeLoader; @@ -102,7 +103,7 @@ class QuerySearchHelper { $builder = $this->getQueryBuilder(); - $query = $builder->selectFileCache('file'); + $query = $builder->selectFileCache('file', false); if ($this->searchBuilder->shouldJoinTags($searchQuery->getSearchOperation())) { $user = $searchQuery->getUser(); @@ -110,13 +111,21 @@ class QuerySearchHelper { throw new \InvalidArgumentException("Searching by tag requires the user to be set in the query"); } $query - ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid')) - ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX( + ->leftJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid')) + ->leftJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX( $builder->expr()->eq('tagmap.type', 'tag.type'), - $builder->expr()->eq('tagmap.categoryid', 'tag.id') + $builder->expr()->eq('tagmap.categoryid', 'tag.id'), + $builder->expr()->eq('tag.type', $builder->createNamedParameter('files')), + $builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID())) )) - ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files'))) - ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID()))); + ->leftJoin('file', 'systemtag_object_mapping', 'systemtagmap', $builder->expr()->andX( + $builder->expr()->eq('file.fileid', $builder->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)), + $builder->expr()->eq('systemtagmap.objecttype', $builder->createNamedParameter('files')) + )) + ->leftJoin('systemtagmap', 'systemtag', 'systemtag', $builder->expr()->andX( + $builder->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'), + $builder->expr()->eq('systemtag.visibility', $builder->createNamedParameter(true)) + )); } $storageFilters = array_values(array_map(function (ICache $cache) { @@ -149,7 +158,7 @@ class QuerySearchHelper { $result->closeCursor(); - // loop trough all caches for each result to see if the result matches that storage + // loop through all caches for each result to see if the result matches that storage // results are grouped by the same array keys as the caches argument to allow the caller to distringuish the source of the results $results = array_fill_keys(array_keys($caches), []); foreach ($rawEntries as $rawEntry) { diff --git a/lib/private/Files/Cache/SearchBuilder.php b/lib/private/Files/Cache/SearchBuilder.php index c8c442bcb8c..b5f548dd563 100644 --- a/lib/private/Files/Cache/SearchBuilder.php +++ b/lib/private/Files/Cache/SearchBuilder.php @@ -80,7 +80,7 @@ class SearchBuilder { return $shouldJoin || $this->shouldJoinTags($operator); }, false); } elseif ($operator instanceof ISearchComparison) { - return $operator->getField() === 'tagname' || $operator->getField() === 'favorite'; + return $operator->getField() === 'tagname' || $operator->getField() === 'favorite' || $operator->getField() === 'systemtag'; } return false; } @@ -163,8 +163,12 @@ class SearchBuilder { } elseif ($field === 'favorite') { $field = 'tag.category'; $value = self::TAG_FAVORITE; + } elseif ($field === 'name') { + $field = 'file.name'; } elseif ($field === 'tagname') { $field = 'tag.category'; + } elseif ($field === 'systemtag') { + $field = 'systemtag.name'; } elseif ($field === 'fileid') { $field = 'file.fileid'; } elseif ($field === 'path' && $type === ISearchComparison::COMPARE_EQUAL && $operator->getQueryHint(ISearchComparison::HINT_PATH_EQ_HASH, true)) { @@ -182,6 +186,7 @@ class SearchBuilder { 'path' => 'string', 'size' => 'integer', 'tagname' => 'string', + 'systemtag' => 'string', 'favorite' => 'boolean', 'fileid' => 'integer', 'storage' => 'integer', @@ -193,6 +198,7 @@ class SearchBuilder { 'path' => ['eq', 'like', 'clike'], 'size' => ['eq', 'gt', 'lt', 'gte', 'lte'], 'tagname' => ['eq', 'like'], + 'systemtag' => ['eq', 'like'], 'favorite' => ['eq'], 'fileid' => ['eq'], 'storage' => ['eq'], diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php index fb9e5500658..f77c9b71dd7 100644 --- a/lib/private/Files/Cache/Storage.php +++ b/lib/private/Files/Cache/Storage.php @@ -40,7 +40,7 @@ use Psr\Log\LoggerInterface; * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share') * and a numeric storage id which is referenced in the file cache * - * A mapping between the two storage ids is stored in the database and accessible trough this class + * A mapping between the two storage ids is stored in the database and accessible through this class * * @package OC\Files\Cache */ @@ -135,7 +135,7 @@ class Storage { * Get the numeric of the storage with the provided string id * * @param $storageId - * @return int|null either the numeric storage id or null if the storage id is not knwon + * @return int|null either the numeric storage id or null if the storage id is not known */ public static function getNumericStorageId($storageId) { $storageId = self::adjustStorageId($storageId); diff --git a/lib/private/Files/Cache/StorageGlobal.php b/lib/private/Files/Cache/StorageGlobal.php index a898c435415..74cbd5abdb2 100644 --- a/lib/private/Files/Cache/StorageGlobal.php +++ b/lib/private/Files/Cache/StorageGlobal.php @@ -33,7 +33,7 @@ use OCP\IDBConnection; * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share') * and a numeric storage id which is referenced in the file cache * - * A mapping between the two storage ids is stored in the database and accessible trough this class + * A mapping between the two storage ids is stored in the database and accessible through this class * * @package OC\Files\Cache */ diff --git a/lib/private/Files/Cache/Updater.php b/lib/private/Files/Cache/Updater.php index 98fb51fe264..f8c187996e6 100644 --- a/lib/private/Files/Cache/Updater.php +++ b/lib/private/Files/Cache/Updater.php @@ -73,14 +73,14 @@ class Updater implements IUpdater { } /** - * Disable updating the cache trough this updater + * Disable updating the cache through this updater */ public function disable() { $this->enabled = false; } /** - * Re-enable the updating of the cache trough this updater + * Re-enable the updating of the cache through this updater */ public function enable() { $this->enabled = true; diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php index 7183a6c0d2a..4053042edd9 100644 --- a/lib/private/Files/Cache/Wrapper/CacheJail.php +++ b/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -267,7 +267,7 @@ class CacheJail extends CacheWrapper { * use the one with the highest id gives the best result with the background scanner, since that is most * likely the folder where we stopped scanning previously * - * @return string|bool the path of the folder or false when no folder matched + * @return string|false the path of the folder or false when no folder matched */ public function getIncomplete() { // not supported diff --git a/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/lib/private/Files/Cache/Wrapper/CacheWrapper.php index e5300dc75f5..66ae83fd144 100644 --- a/lib/private/Files/Cache/Wrapper/CacheWrapper.php +++ b/lib/private/Files/Cache/Wrapper/CacheWrapper.php @@ -267,7 +267,7 @@ class CacheWrapper extends Cache { * use the one with the highest id gives the best result with the background scanner, since that is most * likely the folder where we stopped scanning previously * - * @return string|bool the path of the folder or false when no folder matched + * @return string|false the path of the folder or false when no folder matched */ public function getIncomplete() { return $this->getCache()->getIncomplete(); diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index c326eeb0b6c..685057a7860 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -28,7 +28,7 @@ */ namespace OC\Files\Config; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCA\Files_Sharing\SharedMount; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Config\ICachedMountFileInfo; @@ -42,7 +42,7 @@ use OCP\IUserManager; use Psr\Log\LoggerInterface; /** - * Cache mounts points per user in the cache so we can easilly look them up + * Cache mounts points per user in the cache so we can easily look them up */ class UserMountCache implements IUserMountCache { private IDBConnection $connection; diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 6389544184f..47c893ebbf1 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -101,7 +101,11 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { $this->data = $data; $this->mount = $mount; $this->owner = $owner; - $this->rawSize = $this->data['size'] ?? 0; + if (isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] !== 0) { + $this->rawSize = $this->data['unencrypted_size']; + } else { + $this->rawSize = $this->data['size'] ?? 0; + } } public function offsetSet($offset, $value): void { @@ -208,7 +212,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { public function getSize($includeMounts = true) { if ($includeMounts) { $this->updateEntryfromSubMounts(); - return isset($this->data['size']) ? 0 + $this->data['size'] : 0; + + if (isset($this->data['unencrypted_size']) && $this->data['unencrypted_size'] > 0) { + return $this->data['unencrypted_size']; + } else { + return isset($this->data['size']) ? 0 + $this->data['size'] : 0; + } } else { return $this->rawSize; } @@ -386,14 +395,26 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { * @param string $entryPath full path of the child entry */ public function addSubEntry($data, $entryPath) { - $this->data['size'] += isset($data['size']) ? $data['size'] : 0; + if (!$data) { + return; + } + $hasUnencryptedSize = isset($data['unencrypted_size']) && $data['unencrypted_size'] > 0; + if ($hasUnencryptedSize) { + $subSize = $data['unencrypted_size']; + } else { + $subSize = $data['size'] ?: 0; + } + $this->data['size'] += $subSize; + if ($hasUnencryptedSize) { + $this->data['unencrypted_size'] += $subSize; + } if (isset($data['mtime'])) { $this->data['mtime'] = max($this->data['mtime'], $data['mtime']); } if (isset($data['etag'])) { // prefix the etag with the relative path of the subentry to propagate etag on mount moves $relativeEntryPath = substr($entryPath, strlen($this->getPath())); - // attach the permissions to propagate etag on permision changes of submounts + // attach the permissions to propagate etag on permission changes of submounts $permissions = isset($data['permissions']) ? $data['permissions'] : 0; $this->childEtags[] = $relativeEntryPath . '/' . $data['etag'] . $permissions; } diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index 20b44e2736a..9542666b03c 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -37,7 +37,7 @@ */ namespace OC\Files; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Mount\MountPoint; use OC\User\NoUserException; use OCP\EventDispatcher\IEventDispatcher; diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php index 69285018d17..9ba0e504058 100644 --- a/lib/private/Files/Mount/Manager.php +++ b/lib/private/Files/Mount/Manager.php @@ -29,7 +29,7 @@ declare(strict_types=1); namespace OC\Files\Mount; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Filesystem; use OC\Files\SetupManager; use OC\Files\SetupManagerFactory; diff --git a/lib/private/Files/Mount/MoveableMount.php b/lib/private/Files/Mount/MoveableMount.php index 7ebb7fa2c87..a7372153d75 100644 --- a/lib/private/Files/Mount/MoveableMount.php +++ b/lib/private/Files/Mount/MoveableMount.php @@ -37,7 +37,6 @@ interface MoveableMount { /** * Remove the mount points * - * @return mixed * @return bool */ public function removeMount(); diff --git a/lib/private/Files/Node/File.php b/lib/private/Files/Node/File.php index e125715e6a8..d8a6741dc6e 100644 --- a/lib/private/Files/Node/File.php +++ b/lib/private/Files/Node/File.php @@ -131,7 +131,6 @@ class File extends Node implements \OCP\Files\File { $this->view->unlink($this->path); $nonExisting = new NonExistingFile($this->root, $this->view, $this->path, $fileInfo); $this->sendHooks(['postDelete'], [$nonExisting]); - $this->exists = false; $this->fileInfo = null; } else { throw new NotPermittedException(); diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 9c15f0edf41..42562c99bcb 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -262,7 +262,7 @@ class Folder extends Node implements \OCP\Files\Folder { $searchHelper = \OC::$server->get(QuerySearchHelper::class); $resultsPerCache = $searchHelper->searchInCaches($query, $caches); - // loop trough all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all + // loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all $files = array_merge(...array_map(function (array $results, $relativeMountPoint) use ($mountByMountPoint) { $mount = $mountByMountPoint[$relativeMountPoint]; return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) { @@ -388,7 +388,6 @@ class Folder extends Node implements \OCP\Files\Folder { $this->view->rmdir($this->path); $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo); $this->sendHooks(['postDelete'], [$nonExisting]); - $this->exists = false; } else { throw new NotPermittedException('No delete permission for path'); } diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index 6dd65a4291d..ca930c1002c 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -32,7 +32,7 @@ namespace OC\Files\Node; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\FileInfo; use OC\Files\Mount\Manager; use OC\Files\Mount\MountPoint; @@ -427,7 +427,7 @@ class Root extends Folder implements IRootFolder { $mountsContainingFile = $mountCache->getMountsForFileId($id, $user); } - // when a user has access trough the same storage trough multiple paths + // when a user has access through the same storage through multiple paths // (such as an external storage that is both mounted for a user and shared to the user) // the mount cache will only hold a single entry for the storage // this can lead to issues as the different ways the user has access to a storage can have different permissions diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index adb3928b28a..898f64d97c2 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -335,6 +335,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { $handle = fopen($tmpFile, $mode); return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { $this->writeBack($tmpFile, $path); + unlink($tmpFile); }); case 'a': case 'ab': @@ -352,6 +353,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { $handle = fopen($tmpFile, $mode); return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { $this->writeBack($tmpFile, $path); + unlink($tmpFile); }); } return false; diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php index c3836749c6d..8286321450d 100644 --- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php +++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php @@ -28,6 +28,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OC\Files\ObjectStore; use Aws\ClientResolver; @@ -121,15 +122,6 @@ trait S3ConnectionTrait { ) ); - // 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); - $certPath = $certManager->getAbsoluteBundlePath(); - } else { - $certPath = \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; - } - $options = [ 'version' => isset($this->params['version']) ? $this->params['version'] : 'latest', 'credentials' => $provider, @@ -139,7 +131,7 @@ trait S3ConnectionTrait { 'signature_provider' => \Aws\or_chain([self::class, 'legacySignatureProvider'], ClientResolver::_default_signature_provider()), 'csm' => false, 'use_arn_region' => false, - 'http' => ['verify' => $certPath], + 'http' => ['verify' => $this->getCertificateBundlePath()], ]; if ($this->getProxy()) { $options['http']['proxy'] = $this->getProxy(); @@ -152,7 +144,7 @@ trait S3ConnectionTrait { 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']); + ['app' => 'objectstore']); } if ($this->params['verify_bucket_exists'] && !$this->connection->doesBucketExist($this->bucket)) { @@ -203,7 +195,7 @@ trait S3ConnectionTrait { /** * This function creates a credential provider based on user parameter file */ - protected function paramCredentialProvider() : callable { + protected function paramCredentialProvider(): callable { return function () { $key = empty($this->params['key']) ? null : $this->params['key']; $secret = empty($this->params['secret']) ? null : $this->params['secret']; @@ -218,4 +210,19 @@ trait S3ConnectionTrait { return new RejectedPromise(new CredentialsException($msg)); }; } + + protected function getCertificateBundlePath(): ?string { + 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); + return $certManager->getAbsoluteBundlePath(); + } else { + return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt'; + } + } else { + return null; + } + } } diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index 4e54a26e98a..9d692e01a23 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -43,6 +43,8 @@ trait S3ObjectTrait { */ abstract protected function getConnection(); + abstract protected function getCertificateBundlePath(): ?string; + /** * @param string $urn the unified resource name used to identify the object * @return resource stream with the read data @@ -67,8 +69,14 @@ trait S3ObjectTrait { 'http' => [ 'protocol_version' => $request->getProtocolVersion(), 'header' => $headers, - ], + ] ]; + $bundle = $this->getCertificateBundlePath(); + if ($bundle) { + $opts['ssl'] = [ + 'cafile' => $bundle + ]; + } if ($this->getProxy()) { $opts['http']['proxy'] = $this->getProxy(); diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php index 040ba6b898f..5782a5a72a6 100644 --- a/lib/private/Files/SetupManager.php +++ b/lib/private/Files/SetupManager.php @@ -82,6 +82,7 @@ class SetupManager { private IConfig $config; private bool $listeningForProviders; private array $fullSetupRequired = []; + private bool $setupBuiltinWrappersDone = false; public function __construct( IEventLogger $eventLogger, @@ -121,6 +122,15 @@ class SetupManager { } private function setupBuiltinWrappers() { + if ($this->setupBuiltinWrappersDone) { + return; + } + $this->setupBuiltinWrappersDone = true; + + // load all filesystem apps before, so no setup-hook gets lost + OC_App::loadApps(['filesystem']); + $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); + Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) { if ($storage->instanceOfStorage(Common::class)) { $storage->setMountOptions($mount->getOptions()); @@ -188,6 +198,8 @@ class SetupManager { } return $storage; }); + + Filesystem::logWarningWhenAddingStorageWrapper($prevLogging); } /** @@ -223,6 +235,9 @@ class SetupManager { return; } $this->setupUsers[] = $user->getUID(); + + $this->setupBuiltinWrappers(); + $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]); @@ -321,14 +336,8 @@ class SetupManager { $this->eventLogger->start('setup_root_fs', 'Setup root filesystem'); - // load all filesystem apps before, so no setup-hook gets lost - OC_App::loadApps(['filesystem']); - $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false); - $this->setupBuiltinWrappers(); - Filesystem::logWarningWhenAddingStorageWrapper($prevLogging); - $rootMounts = $this->mountProviderCollection->getRootMounts(); foreach ($rootMounts as $rootMountProvider) { $this->mountManager->addMount($rootMountProvider); @@ -380,13 +389,9 @@ class SetupManager { return; } - // for the user's home folder, it's always the home mount - if (rtrim($path) === "/" . $user->getUID() . "/files") { - if ($includeChildren) { - $this->setupForUser($user); - } else { - $this->oneTimeUserSetup($user); - } + // for the user's home folder, and includes children we need everything always + if (rtrim($path) === "/" . $user->getUID() . "/files" && $includeChildren) { + $this->setupForUser($user); return; } @@ -403,6 +408,10 @@ class SetupManager { return; } + if (!$this->isSetupStarted($user)) { + $this->oneTimeUserSetup($user); + } + $mounts = []; if (!in_array($cachedMount->getMountProvider(), $setupProviders)) { $setupProviders[] = $cachedMount->getMountProvider(); @@ -554,10 +563,10 @@ class SetupManager { }); $genericEvents = [ - '\OCA\Circles::onCircleCreation', - '\OCA\Circles::onCircleDestruction', - '\OCA\Circles::onMemberNew', - '\OCA\Circles::onMemberLeaving', + 'OCA\Circles\Events\CreatingCircleEvent', + 'OCA\Circles\Events\DestroyingCircleEvent', + 'OCA\Circles\Events\AddingCircleMemberEvent', + 'OCA\Circles\Events\RemovingCircleMemberEvent', ]; foreach ($genericEvents as $genericEvent) { diff --git a/lib/private/Files/SimpleFS/NewSimpleFile.php b/lib/private/Files/SimpleFS/NewSimpleFile.php index 76fc69ebbe7..b2a183b7d29 100644 --- a/lib/private/Files/SimpleFS/NewSimpleFile.php +++ b/lib/private/Files/SimpleFS/NewSimpleFile.php @@ -34,15 +34,12 @@ use OCP\Files\NotPermittedException; use OCP\Files\SimpleFS\ISimpleFile; class NewSimpleFile implements ISimpleFile { - private $parentFolder; - private $name; - /** @var File|null */ - private $file = null; + private Folder $parentFolder; + private string $name; + private ?File $file = null; /** * File constructor. - * - * @param File $file */ public function __construct(Folder $parentFolder, string $name) { $this->parentFolder = $parentFolder; @@ -51,19 +48,15 @@ class NewSimpleFile implements ISimpleFile { /** * Get the name - * - * @return string */ - public function getName() { + public function getName(): string { return $this->name; } /** * Get the size in bytes - * - * @return int */ - public function getSize() { + public function getSize(): int { if ($this->file) { return $this->file->getSize(); } else { @@ -73,10 +66,8 @@ class NewSimpleFile implements ISimpleFile { /** * Get the ETag - * - * @return string */ - public function getETag() { + public function getETag(): string { if ($this->file) { return $this->file->getEtag(); } else { @@ -86,10 +77,8 @@ class NewSimpleFile implements ISimpleFile { /** * Get the last modification time - * - * @return int */ - public function getMTime() { + public function getMTime(): int { if ($this->file) { return $this->file->getMTime(); } else { @@ -100,11 +89,10 @@ class NewSimpleFile implements ISimpleFile { /** * Get the content * - * @return string * @throws NotFoundException * @throws NotPermittedException */ - public function getContent() { + public function getContent(): string { if ($this->file) { $result = $this->file->getContent(); @@ -125,7 +113,7 @@ class NewSimpleFile implements ISimpleFile { * @throws NotPermittedException * @throws NotFoundException */ - public function putContent($data) { + public function putContent($data): void { try { if ($this->file) { $this->file->putContent($data); @@ -139,7 +127,7 @@ class NewSimpleFile implements ISimpleFile { /** * Sometimes there are some issues with the AppData. Most of them are from - * user error. But we should handle them gracefull anyway. + * user error. But we should handle them gracefully anyway. * * If for some reason the current file can't be found. We remove it. * Then traverse up and check all folders if they exists. This so that the @@ -147,7 +135,7 @@ class NewSimpleFile implements ISimpleFile { * * @throws NotFoundException */ - private function checkFile() { + private function checkFile(): void { $cur = $this->file; while ($cur->stat() === false) { @@ -171,7 +159,7 @@ class NewSimpleFile implements ISimpleFile { * * @throws NotPermittedException */ - public function delete() { + public function delete(): void { if ($this->file) { $this->file->delete(); } @@ -182,7 +170,7 @@ class NewSimpleFile implements ISimpleFile { * * @return string */ - public function getMimeType() { + public function getMimeType(): string { if ($this->file) { return $this->file->getMimeType(); } else { diff --git a/lib/private/Files/SimpleFS/SimpleFile.php b/lib/private/Files/SimpleFS/SimpleFile.php index 21a2fd92dcb..a2571ac50e8 100644 --- a/lib/private/Files/SimpleFS/SimpleFile.php +++ b/lib/private/Files/SimpleFS/SimpleFile.php @@ -30,52 +30,37 @@ use OCP\Files\NotPermittedException; use OCP\Files\SimpleFS\ISimpleFile; class SimpleFile implements ISimpleFile { + private File $file; - /** @var File $file */ - private $file; - - /** - * File constructor. - * - * @param File $file - */ public function __construct(File $file) { $this->file = $file; } /** * Get the name - * - * @return string */ - public function getName() { + public function getName(): string { return $this->file->getName(); } /** * Get the size in bytes - * - * @return int */ - public function getSize() { + public function getSize(): int { return $this->file->getSize(); } /** * Get the ETag - * - * @return string */ - public function getETag() { + public function getETag(): string { return $this->file->getEtag(); } /** * Get the last modification time - * - * @return int */ - public function getMTime() { + public function getMTime(): int { return $this->file->getMTime(); } @@ -84,9 +69,8 @@ class SimpleFile implements ISimpleFile { * * @throws NotPermittedException * @throws NotFoundException - * @return string */ - public function getContent() { + public function getContent(): string { $result = $this->file->getContent(); if ($result === false) { @@ -103,9 +87,9 @@ class SimpleFile implements ISimpleFile { * @throws NotPermittedException * @throws NotFoundException */ - public function putContent($data) { + public function putContent($data): void { try { - return $this->file->putContent($data); + $this->file->putContent($data); } catch (NotFoundException $e) { $this->checkFile(); } @@ -113,7 +97,7 @@ class SimpleFile implements ISimpleFile { /** * Sometimes there are some issues with the AppData. Most of them are from - * user error. But we should handle them gracefull anyway. + * user error. But we should handle them gracefully anyway. * * If for some reason the current file can't be found. We remove it. * Then traverse up and check all folders if they exists. This so that the @@ -121,7 +105,7 @@ class SimpleFile implements ISimpleFile { * * @throws NotFoundException */ - private function checkFile() { + private function checkFile(): void { $cur = $this->file; while ($cur->stat() === false) { @@ -145,16 +129,14 @@ class SimpleFile implements ISimpleFile { * * @throws NotPermittedException */ - public function delete() { + public function delete(): void { $this->file->delete(); } /** * Get the MimeType - * - * @return string */ - public function getMimeType() { + public function getMimeType(): string { return $this->file->getMimeType(); } @@ -179,7 +161,7 @@ class SimpleFile implements ISimpleFile { /** * Open the file as stream for writing, resulting resource can be operated as stream like the result from php's own fopen * - * @return resource + * @return resource|false * @throws \OCP\Files\NotPermittedException * @since 14.0.0 */ diff --git a/lib/private/Files/SimpleFS/SimpleFolder.php b/lib/private/Files/SimpleFS/SimpleFolder.php index cd2a712019e..263c25a8873 100644 --- a/lib/private/Files/SimpleFS/SimpleFolder.php +++ b/lib/private/Files/SimpleFS/SimpleFolder.php @@ -29,6 +29,7 @@ use OCP\Files\Folder; use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\Files\SimpleFS\ISimpleFile; class SimpleFolder implements ISimpleFolder { @@ -44,11 +45,11 @@ class SimpleFolder implements ISimpleFolder { $this->folder = $folder; } - public function getName() { + public function getName(): string { return $this->folder->getName(); } - public function getDirectoryListing() { + public function getDirectoryListing(): array { $listing = $this->folder->getDirectoryListing(); $fileListing = array_map(function (Node $file) { @@ -63,15 +64,15 @@ class SimpleFolder implements ISimpleFolder { return array_values($fileListing); } - public function delete() { + public function delete(): void { $this->folder->delete(); } - public function fileExists($name) { + public function fileExists(string $name): bool { return $this->folder->nodeExists($name); } - public function getFile($name) { + public function getFile(string $name): ISimpleFile { $file = $this->folder->get($name); if (!($file instanceof File)) { @@ -81,7 +82,7 @@ class SimpleFolder implements ISimpleFolder { return new SimpleFile($file); } - public function newFile($name, $content = null) { + public function newFile(string $name, $content = null): ISimpleFile { if ($content === null) { // delay creating the file until it's written to return new NewSimpleFile($this->folder, $name); diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index 3c970ee75f5..a7bc44e10e2 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -228,6 +228,7 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage { while ($file = readdir($dir)) { if (!Filesystem::isIgnoredDir($file)) { if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) { + closedir($dir); return false; } } diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index ee8a8c7d161..4996572a40e 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -15,6 +15,7 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Klaas Freitag <freitag@owncloud.com> * @author Lukas Reschke <lukas@statuscode.ch> + * @author Martin Brugnara <martin@0x6d62.eu> * @author Michael Gapczynski <GapczynskiM@gmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> @@ -66,6 +67,8 @@ class Local extends \OC\Files\Storage\Common { private IMimeTypeDetector $mimeTypeDetector; + private $defUMask; + public function __construct($arguments) { if (!isset($arguments['datadir']) || !is_string($arguments['datadir'])) { throw new \InvalidArgumentException('No data directory set for local storage'); @@ -84,6 +87,7 @@ class Local extends \OC\Files\Storage\Common { $this->dataDirLength = strlen($this->realDataDir); $this->config = \OC::$server->get(IConfig::class); $this->mimeTypeDetector = \OC::$server->get(IMimeTypeDetector::class); + $this->defUMask = $this->config->getSystemValue('localstorage.umask', 0022); } public function __destruct() { @@ -95,7 +99,7 @@ class Local extends \OC\Files\Storage\Common { public function mkdir($path) { $sourcePath = $this->getSourcePath($path); - $oldMask = umask(022); + $oldMask = umask($this->defUMask); $result = @mkdir($sourcePath, 0777, true); umask($oldMask); return $result; @@ -273,7 +277,7 @@ class Local extends \OC\Files\Storage\Common { if ($this->file_exists($path) and !$this->isUpdatable($path)) { return false; } - $oldMask = umask(022); + $oldMask = umask($this->defUMask); if (!is_null($mtime)) { $result = @touch($this->getSourcePath($path), $mtime); } else { @@ -292,7 +296,7 @@ class Local extends \OC\Files\Storage\Common { } public function file_put_contents($path, $data) { - $oldMask = umask(022); + $oldMask = umask($this->defUMask); $result = file_put_contents($this->getSourcePath($path), $data); umask($oldMask); return $result; @@ -365,7 +369,7 @@ class Local extends \OC\Files\Storage\Common { if ($this->is_dir($path1)) { return parent::copy($path1, $path2); } else { - $oldMask = umask(022); + $oldMask = umask($this->defUMask); $result = copy($this->getSourcePath($path1), $this->getSourcePath($path2)); umask($oldMask); return $result; @@ -373,7 +377,7 @@ class Local extends \OC\Files\Storage\Common { } public function fopen($path, $mode) { - $oldMask = umask(022); + $oldMask = umask($this->defUMask); $result = fopen($this->getSourcePath($path), $mode); umask($oldMask); return $result; diff --git a/lib/private/Files/Storage/Wrapper/Encoding.php b/lib/private/Files/Storage/Wrapper/Encoding.php index d6201dc8877..ac9cc248ce6 100644 --- a/lib/private/Files/Storage/Wrapper/Encoding.php +++ b/lib/private/Files/Storage/Wrapper/Encoding.php @@ -28,7 +28,7 @@ */ namespace OC\Files\Storage\Wrapper; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OC\Files\Filesystem; use OCP\Files\Storage\IStorage; use OCP\ICache; diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 4cfe932cc9f..d5bf929101f 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -33,6 +33,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ + namespace OC\Files\Storage\Wrapper; use OC\Encryption\Exceptions\ModuleDoesNotExistsException; @@ -41,6 +42,7 @@ 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\LocalTempFileTrait; use OC\Memcache\ArrayCache; use OCP\Encryption\Exceptions\GenericEncryptionException; @@ -139,28 +141,36 @@ class Encryption extends Wrapper { $size = $this->unencryptedSize[$fullPath]; // update file cache if ($info instanceof ICacheEntry) { - $info = $info->getData(); $info['encrypted'] = $info['encryptedVersion']; } else { if (!is_array($info)) { $info = []; } $info['encrypted'] = true; + $info = new CacheEntry($info); } - $info['size'] = $size; - $this->getCache()->put($path, $info); + if ($size !== $info->getUnencryptedSize()) { + $this->getCache()->update($info->getId(), [ + 'unencrypted_size' => $size + ]); + } return $size; } if (isset($info['fileid']) && $info['encrypted']) { - return $this->verifyUnencryptedSize($path, $info['size']); + return $this->verifyUnencryptedSize($path, $info->getUnencryptedSize()); } 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); @@ -170,7 +180,7 @@ class Encryption extends Wrapper { $data['size'] = $this->unencryptedSize[$fullPath]; } else { if (isset($info['fileid']) && $info['encrypted']) { - $data['size'] = $this->verifyUnencryptedSize($path, $info['size']); + $data['size'] = $this->verifyUnencryptedSize($path, $info->getUnencryptedSize()); $data['encrypted'] = true; } } @@ -478,7 +488,7 @@ class Encryption extends Wrapper { * * @return int unencrypted size */ - protected function verifyUnencryptedSize($path, $unencryptedSize) { + protected function verifyUnencryptedSize(string $path, int $unencryptedSize): int { $size = $this->storage->filesize($path); $result = $unencryptedSize; @@ -510,7 +520,7 @@ class Encryption extends Wrapper { * * @return int calculated unencrypted size */ - protected function fixUnencryptedSize($path, $size, $unencryptedSize) { + protected function fixUnencryptedSize(string $path, int $size, int $unencryptedSize): int { $headerSize = $this->getHeaderSize($path); $header = $this->getHeader($path); $encryptionModule = $this->getEncryptionModule($path); @@ -581,7 +591,9 @@ class Encryption extends Wrapper { $cache = $this->storage->getCache(); if ($cache) { $entry = $cache->get($path); - $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]); + $cache->update($entry['fileid'], [ + 'unencrypted_size' => $newUnencryptedSize + ]); } return $newUnencryptedSize; @@ -621,7 +633,12 @@ class Encryption extends Wrapper { * @param bool $preserveMtime * @return bool */ - public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) { + public function moveFromStorage( + Storage\IStorage $sourceStorage, + $sourceInternalPath, + $targetInternalPath, + $preserveMtime = true + ) { if ($sourceStorage === $this) { return $this->rename($sourceInternalPath, $targetInternalPath); } @@ -656,7 +673,13 @@ class Encryption extends Wrapper { * @param bool $isRename * @return bool */ - public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) { + public function copyFromStorage( + Storage\IStorage $sourceStorage, + $sourceInternalPath, + $targetInternalPath, + $preserveMtime = false, + $isRename = false + ) { // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed: // - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage @@ -676,7 +699,13 @@ class Encryption extends Wrapper { * @param bool $isRename * @param bool $keepEncryptionVersion */ - private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) { + private function updateEncryptedVersion( + Storage\IStorage $sourceStorage, + $sourceInternalPath, + $targetInternalPath, + $isRename, + $keepEncryptionVersion + ) { $isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath); $cacheInformation = [ 'encrypted' => $isEncrypted, @@ -725,7 +754,13 @@ class Encryption extends Wrapper { * @return bool * @throws \Exception */ - private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) { + private function copyBetweenStorage( + Storage\IStorage $sourceStorage, + $sourceInternalPath, + $targetInternalPath, + $preserveMtime, + $isRename + ) { // 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 @@ -743,7 +778,7 @@ class Encryption extends Wrapper { if (isset($info['encrypted']) && $info['encrypted'] === true) { $this->updateUnencryptedSize( $this->getFullPath($targetInternalPath), - $info['size'] + $info->getUnencryptedSize() ); } $this->updateEncryptedVersion($sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, true); @@ -808,13 +843,6 @@ class Encryption extends Wrapper { return (bool)$result; } - /** - * 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 - */ public function getLocalFile($path) { if ($this->encryptionManager->isEnabled()) { $cachedFile = $this->getCachedFile($path); @@ -825,11 +853,6 @@ class Encryption extends Wrapper { return $this->storage->getLocalFile($path); } - /** - * Returns the wrapped storage's value for isLocal() - * - * @return bool wrapped storage's isLocal() value - */ public function isLocal() { if ($this->encryptionManager->isEnabled()) { return false; @@ -837,15 +860,11 @@ class Encryption extends Wrapper { return $this->storage->isLocal(); } - /** - * 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 - */ public function stat($path) { $stat = $this->storage->stat($path); + if (!$stat) { + return false; + } $fileSize = $this->filesize($path); $stat['size'] = $fileSize; $stat[7] = $fileSize; @@ -853,14 +872,6 @@ class Encryption extends Wrapper { return $stat; } - /** - * see https://www.php.net/manual/en/function.hash.php - * - * @param string $type - * @param string $path - * @param bool $raw - * @return string - */ public function hash($type, $path, $raw = false) { $fh = $this->fopen($path, 'rb'); $ctx = hash_init($type); @@ -1068,6 +1079,13 @@ class Encryption extends Wrapper { [$count, $result] = \OC_Helper::streamCopy($stream, $target); fclose($stream); fclose($target); + + // object store, stores the size after write and doesn't update this during scan + // manually store the unencrypted size + if ($result && $this->getWrapperStorage()->instanceOfStorage(ObjectStoreStorage::class)) { + $this->getCache()->put($path, ['unencrypted_size' => $count]); + } + return $count; } } diff --git a/lib/private/Files/Stream/SeekableHttpStream.php b/lib/private/Files/Stream/SeekableHttpStream.php index af797c7720d..820a681bd07 100644 --- a/lib/private/Files/Stream/SeekableHttpStream.php +++ b/lib/private/Files/Stream/SeekableHttpStream.php @@ -24,6 +24,7 @@ namespace OC\Files\Stream; use Icewind\Streams\File; +use Icewind\Streams\Wrapper; /** * A stream wrapper that uses http range requests to provide a seekable stream for http reading @@ -92,6 +93,18 @@ class SeekableHttpStream implements File { } $responseHead = stream_get_meta_data($this->current)['wrapper_data']; + + while ($responseHead instanceof Wrapper) { + $wrapperOptions = stream_context_get_options($responseHead->context); + foreach ($wrapperOptions as $options) { + if (isset($options['source']) && is_resource($options['source'])) { + $responseHead = stream_get_meta_data($options['source'])['wrapper_data']; + continue 2; + } + } + throw new \Exception("Failed to get source stream from stream wrapper of " . get_class($responseHead)); + } + $rangeHeaders = array_values(array_filter($responseHead, function ($v) { return preg_match('#^content-range:#i', $v) === 1; })); diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index a555aeb24f1..e5394e72ffe 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1159,7 +1159,7 @@ class View { try { $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE); } catch (LockedException $e) { - // release the shared lock we acquired before quiting + // release the shared lock we acquired before quitting $this->unlockFile($path, ILockingProvider::LOCK_SHARED); throw $e; } @@ -1720,7 +1720,7 @@ class View { /** * Get the path of a file by id, relative to the view * - * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file + * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file * * @param int $id * @param int|null $storageId |