diff options
Diffstat (limited to 'lib/private/Files/Cache/Wrapper')
-rw-r--r-- | lib/private/Files/Cache/Wrapper/CacheJail.php | 329 | ||||
-rw-r--r-- | lib/private/Files/Cache/Wrapper/CachePermissionsMask.php | 32 | ||||
-rw-r--r-- | lib/private/Files/Cache/Wrapper/CacheWrapper.php | 315 | ||||
-rw-r--r-- | lib/private/Files/Cache/Wrapper/JailPropagator.php | 28 | ||||
-rw-r--r-- | lib/private/Files/Cache/Wrapper/JailWatcher.php | 61 |
5 files changed, 765 insertions, 0 deletions
diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php new file mode 100644 index 00000000000..5bc4ee8529d --- /dev/null +++ b/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -0,0 +1,329 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Files\Cache\Wrapper; + +use OC\Files\Cache\Cache; +use OC\Files\Cache\CacheDependencies; +use OC\Files\Search\SearchBinaryOperator; +use OC\Files\Search\SearchComparison; +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Search\ISearchBinaryOperator; +use OCP\Files\Search\ISearchComparison; +use OCP\Files\Search\ISearchOperator; + +/** + * Jail to a subdirectory of the wrapped cache + */ +class CacheJail extends CacheWrapper { + + protected string $unjailedRoot; + + public function __construct( + ?ICache $cache, + protected string $root, + ?CacheDependencies $dependencies = null, + ) { + parent::__construct($cache, $dependencies); + + $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; + } + + /** + * Get the root path with any nested jails resolved + * + * @return string + */ + public function getGetUnjailedRoot() { + return $this->unjailedRoot; + } + + /** + * @return string + */ + protected function getSourcePath(string $path) { + if ($path === '') { + return $this->getRoot(); + } else { + return $this->getRoot() . '/' . ltrim($path, '/'); + } + } + + /** + * @param string $path + * @param null|string $root + * @return null|string the jailed path or null if the path is outside the jail + */ + protected function getJailedPath(string $path, ?string $root = null) { + if ($root === null) { + $root = $this->getRoot(); + } + if ($root === '') { + return $path; + } + $rootLength = strlen($root) + 1; + if ($path === $root) { + return ''; + } elseif (substr($path, 0, $rootLength) === $root . '/') { + return substr($path, $rootLength); + } else { + return null; + } + } + + protected function formatCacheEntry($entry) { + if (isset($entry['path'])) { + $entry['path'] = $this->getJailedPath($entry['path']); + } + return $entry; + } + + /** + * get the stored metadata of a file or folder + * + * @param string|int $file + * @return ICacheEntry|false + */ + public function get($file) { + if (is_string($file) or $file == '') { + $file = $this->getSourcePath($file); + } + return parent::get($file); + } + + /** + * insert meta data for a new file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function insert($file, array $data) { + return $this->getCache()->insert($this->getSourcePath($file), $data); + } + + /** + * update the metadata in the cache + * + * @param int $id + * @param array $data + */ + public function update($id, array $data) { + $this->getCache()->update($id, $data); + } + + /** + * get the file id for a file + * + * @param string $file + * @return int + */ + public function getId($file) { + return $this->getCache()->getId($this->getSourcePath($file)); + } + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + */ + public function getParentId($file) { + return $this->getCache()->getParentId($this->getSourcePath($file)); + } + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + return $this->getCache()->inCache($this->getSourcePath($file)); + } + + /** + * remove a file or folder from the cache + * + * @param string $file + */ + public function remove($file) { + $this->getCache()->remove($this->getSourcePath($file)); + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + $this->getCache()->move($this->getSourcePath($source), $this->getSourcePath($target)); + } + + /** + * Get the storage id and path needed for a move + * + * @param string $path + * @return array [$storageId, $internalPath] + */ + protected function getMoveInfo($path) { + return [$this->getNumericStorageId(), $this->getSourcePath($path)]; + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + $this->getCache()->remove($this->getRoot()); + } + + /** + * @param string $file + * + * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + return $this->getCache()->getStatus($this->getSourcePath($file)); + } + + /** + * update the folder size and the size of all parent folders + * + * @param array|ICacheEntry|null $data (optional) meta data of the folder + */ + 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); + } + } + + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @param array|null|ICacheEntry $entry (optional) meta data of the folder + * @return int|float + */ + public function calculateFolderSize($path, $entry = null) { + $cache = $this->getCache(); + if ($cache instanceof Cache) { + return $cache->calculateFolderSize($this->getSourcePath($path), $entry); + } else { + return 0; + } + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + // not supported + return []; + } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiply incomplete folders are in the cache, the one with the highest id will be returned, + * 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|false the path of the folder or false when no folder matched + */ + public function getIncomplete() { + // not supported + return false; + } + + /** + * get the path of a file on this storage by it's id + * + * @param int $id + * @return string|null + */ + public function getPathById($id) { + $path = $this->getCache()->getPathById($id); + if ($path === null) { + return null; + } + + return $this->getJailedPath($path); + } + + /** + * Move a file or folder in the cache + * + * Note that this should make sure the entries are removed from the source cache + * + * @param \OCP\Files\Cache\ICache $sourceCache + * @param string $sourcePath + * @param string $targetPath + */ + public function moveFromCache(\OCP\Files\Cache\ICache $sourceCache, $sourcePath, $targetPath) { + if ($sourceCache === $this) { + return $this->move($sourcePath, $targetPath); + } + return $this->getCache()->moveFromCache($sourceCache, $sourcePath, $this->getSourcePath($targetPath)); + } + + public function getQueryFilterForStorage(): ISearchOperator { + return $this->addJailFilterQuery($this->getCache()->getQueryFilterForStorage()); + } + + protected function addJailFilterQuery(ISearchOperator $filter): ISearchOperator { + if ($this->getGetUnjailedRoot() !== '' && $this->getGetUnjailedRoot() !== '/') { + return new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, + [ + $filter, + new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, + [ + new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'path', $this->getGetUnjailedRoot()), + new SearchComparison(ISearchComparison::COMPARE_LIKE_CASE_SENSITIVE, 'path', SearchComparison::escapeLikeParameter($this->getGetUnjailedRoot()) . '/%'), + ], + ) + ] + ); + } else { + return $filter; + } + } + + public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry { + if ($this->getGetUnjailedRoot() === '' || str_starts_with($rawEntry->getPath(), $this->getGetUnjailedRoot())) { + $rawEntry = $this->getCache()->getCacheEntryFromSearchResult($rawEntry); + if ($rawEntry) { + $jailedPath = $this->getJailedPath($rawEntry->getPath()); + if ($jailedPath !== null) { + return $this->formatCacheEntry(clone $rawEntry) ?: null; + } + } + } + + return null; + } +} diff --git a/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php b/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php new file mode 100644 index 00000000000..ff17cb79ac7 --- /dev/null +++ b/lib/private/Files/Cache/Wrapper/CachePermissionsMask.php @@ -0,0 +1,32 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Files\Cache\Wrapper; + +class CachePermissionsMask extends CacheWrapper { + /** + * @var int + */ + protected $mask; + + /** + * @param \OCP\Files\Cache\ICache $cache + * @param int $mask + */ + public function __construct($cache, $mask) { + parent::__construct($cache); + $this->mask = $mask; + } + + protected function formatCacheEntry($entry) { + if (isset($entry['permissions'])) { + $entry['scan_permissions'] = $entry['permissions']; + $entry['permissions'] &= $this->mask; + } + return $entry; + } +} diff --git a/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/lib/private/Files/Cache/Wrapper/CacheWrapper.php new file mode 100644 index 00000000000..f2f1036d6a3 --- /dev/null +++ b/lib/private/Files/Cache/Wrapper/CacheWrapper.php @@ -0,0 +1,315 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Files\Cache\Wrapper; + +use OC\Files\Cache\Cache; +use OC\Files\Cache\CacheDependencies; +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Search\ISearchOperator; +use OCP\Files\Search\ISearchQuery; +use OCP\Server; + +class CacheWrapper extends Cache { + /** + * @var ?ICache + */ + protected $cache; + + public function __construct(?ICache $cache, ?CacheDependencies $dependencies = null) { + $this->cache = $cache; + if (!$dependencies && $cache instanceof Cache) { + $this->mimetypeLoader = $cache->mimetypeLoader; + $this->connection = $cache->connection; + $this->querySearchHelper = $cache->querySearchHelper; + } else { + if (!$dependencies) { + $dependencies = Server::get(CacheDependencies::class); + } + $this->mimetypeLoader = $dependencies->getMimeTypeLoader(); + $this->connection = $dependencies->getConnection(); + $this->querySearchHelper = $dependencies->getQuerySearchHelper(); + } + } + + protected function getCache() { + return $this->cache; + } + + protected function hasEncryptionWrapper(): bool { + $cache = $this->getCache(); + if ($cache instanceof Cache) { + return $cache->hasEncryptionWrapper(); + } else { + return false; + } + } + + /** + * Make it easy for wrappers to modify every returned cache entry + * + * @param ICacheEntry $entry + * @return ICacheEntry|false + */ + protected function formatCacheEntry($entry) { + return $entry; + } + + /** + * get the stored metadata of a file or folder + * + * @param string|int $file + * @return ICacheEntry|false + */ + public function get($file) { + $result = $this->getCache()->get($file); + if ($result instanceof ICacheEntry) { + $result = $this->formatCacheEntry($result); + } + return $result; + } + + /** + * get the metadata of all files stored in $folder + * + * @param string $folder + * @return ICacheEntry[] + */ + public function getFolderContents($folder) { + // can't do a simple $this->getCache()->.... call here since getFolderContentsById needs to be called on this + // and not the wrapped cache + $fileId = $this->getId($folder); + return $this->getFolderContentsById($fileId); + } + + /** + * get the metadata of all files stored in $folder + * + * @param int $fileId the file id of the folder + * @return array + */ + public function getFolderContentsById($fileId) { + $results = $this->getCache()->getFolderContentsById($fileId); + return array_map([$this, 'formatCacheEntry'], $results); + } + + /** + * insert or update meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function put($file, array $data) { + if (($id = $this->getId($file)) > -1) { + $this->update($id, $data); + return $id; + } else { + return $this->insert($file, $data); + } + } + + /** + * insert meta data for a new file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + */ + public function insert($file, array $data) { + return $this->getCache()->insert($file, $data); + } + + /** + * update the metadata in the cache + * + * @param int $id + * @param array $data + */ + public function update($id, array $data) { + $this->getCache()->update($id, $data); + } + + /** + * get the file id for a file + * + * @param string $file + * @return int + */ + public function getId($file) { + return $this->getCache()->getId($file); + } + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + */ + public function getParentId($file) { + return $this->getCache()->getParentId($file); + } + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + return $this->getCache()->inCache($file); + } + + /** + * remove a file or folder from the cache + * + * @param string $file + */ + public function remove($file) { + $this->getCache()->remove($file); + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + $this->getCache()->move($source, $target); + } + + protected function getMoveInfo($path) { + /** @var Cache $cache */ + $cache = $this->getCache(); + return $cache->getMoveInfo($path); + } + + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + $this->getCache()->moveFromCache($sourceCache, $sourcePath, $targetPath); + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + $this->getCache()->clear(); + } + + /** + * @param string $file + * + * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + return $this->getCache()->getStatus($file); + } + + public function searchQuery(ISearchQuery $query) { + return current($this->querySearchHelper->searchInCaches($query, [$this])); + } + + /** + * update the folder size and the size of all parent folders + * + * @param array|ICacheEntry|null $data (optional) meta data of the folder + */ + public function correctFolderSize(string $path, $data = null, bool $isBackgroundScan = false): void { + $cache = $this->getCache(); + if ($cache instanceof Cache) { + $cache->correctFolderSize($path, $data, $isBackgroundScan); + } + } + + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @param array|null|ICacheEntry $entry (optional) meta data of the folder + * @return int|float + */ + public function calculateFolderSize($path, $entry = null) { + $cache = $this->getCache(); + if ($cache instanceof Cache) { + return $cache->calculateFolderSize($path, $entry); + } else { + return 0; + } + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + return $this->getCache()->getAll(); + } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiple incomplete folders are in the cache, the one with the highest id will be returned, + * 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|false the path of the folder or false when no folder matched + */ + public function getIncomplete() { + return $this->getCache()->getIncomplete(); + } + + /** + * get the path of a file on this storage by it's id + * + * @param int $id + * @return string|null + */ + public function getPathById($id) { + return $this->getCache()->getPathById($id); + } + + /** + * Returns the numeric storage id + * + * @return int + */ + public function getNumericStorageId() { + return $this->getCache()->getNumericStorageId(); + } + + /** + * get the storage id of the storage for a file and the internal path of the file + * unlike getPathById this does not limit the search to files on this storage and + * instead does a global search in the cache table + * + * @param int $id + * @return array first element holding the storage id, second the path + */ + public static function getById($id) { + return parent::getById($id); + } + + public function getQueryFilterForStorage(): ISearchOperator { + return $this->getCache()->getQueryFilterForStorage(); + } + + public function getCacheEntryFromSearchResult(ICacheEntry $rawEntry): ?ICacheEntry { + $rawEntry = $this->getCache()->getCacheEntryFromSearchResult($rawEntry); + if ($rawEntry) { + $entry = $this->formatCacheEntry(clone $rawEntry); + return $entry ?: null; + } + + return null; + } +} diff --git a/lib/private/Files/Cache/Wrapper/JailPropagator.php b/lib/private/Files/Cache/Wrapper/JailPropagator.php new file mode 100644 index 00000000000..d6409b7875e --- /dev/null +++ b/lib/private/Files/Cache/Wrapper/JailPropagator.php @@ -0,0 +1,28 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Files\Cache\Wrapper; + +use OC\Files\Cache\Propagator; +use OC\Files\Storage\Wrapper\Jail; + +class JailPropagator extends Propagator { + /** + * @var Jail + */ + protected $storage; + + /** + * @param string $internalPath + * @param int $time + * @param int $sizeDifference + */ + public function propagateChange($internalPath, $time, $sizeDifference = 0) { + /** @var \OC\Files\Storage\Storage $storage */ + [$storage, $sourceInternalPath] = $this->storage->resolvePath($internalPath); + $storage->getPropagator()->propagateChange($sourceInternalPath, $time, $sizeDifference); + } +} diff --git a/lib/private/Files/Cache/Wrapper/JailWatcher.php b/lib/private/Files/Cache/Wrapper/JailWatcher.php new file mode 100644 index 00000000000..b1ae516654a --- /dev/null +++ b/lib/private/Files/Cache/Wrapper/JailWatcher.php @@ -0,0 +1,61 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Files\Cache\Wrapper; + +use OC\Files\Cache\Watcher; + +class JailWatcher extends Watcher { + private string $root; + private Watcher $watcher; + + public function __construct(Watcher $watcher, string $root) { + $this->watcher = $watcher; + $this->root = $root; + } + + protected function getRoot(): string { + return $this->root; + } + + protected function getSourcePath($path): string { + if ($path === '') { + return $this->getRoot(); + } else { + return $this->getRoot() . '/' . ltrim($path, '/'); + } + } + + public function setPolicy($policy) { + $this->watcher->setPolicy($policy); + } + + public function getPolicy() { + return $this->watcher->getPolicy(); + } + + + public function checkUpdate($path, $cachedEntry = null) { + return $this->watcher->checkUpdate($this->getSourcePath($path), $cachedEntry); + } + + public function update($path, $cachedData) { + $this->watcher->update($this->getSourcePath($path), $cachedData); + } + + public function needsUpdate($path, $cachedData) { + return $this->watcher->needsUpdate($this->getSourcePath($path), $cachedData); + } + + public function cleanFolder($path) { + $this->watcher->cleanFolder($this->getSourcePath($path)); + } + + public function onUpdate(callable $callback): void { + $this->watcher->onUpdate($callback); + } +} |