aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Files/Cache/Wrapper
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Files/Cache/Wrapper')
-rw-r--r--lib/private/Files/Cache/Wrapper/CacheJail.php329
-rw-r--r--lib/private/Files/Cache/Wrapper/CachePermissionsMask.php32
-rw-r--r--lib/private/Files/Cache/Wrapper/CacheWrapper.php315
-rw-r--r--lib/private/Files/Cache/Wrapper/JailPropagator.php28
-rw-r--r--lib/private/Files/Cache/Wrapper/JailWatcher.php61
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);
+ }
+}