diff options
Diffstat (limited to 'lib/private/Files/Storage/Wrapper')
-rw-r--r-- | lib/private/Files/Storage/Wrapper/Availability.php | 384 | ||||
-rw-r--r-- | lib/private/Files/Storage/Wrapper/Encoding.php | 353 | ||||
-rw-r--r-- | lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php | 31 | ||||
-rw-r--r-- | lib/private/Files/Storage/Wrapper/Encryption.php | 615 | ||||
-rw-r--r-- | lib/private/Files/Storage/Wrapper/Jail.php | 405 | ||||
-rw-r--r-- | lib/private/Files/Storage/Wrapper/KnownMtime.php | 146 | ||||
-rw-r--r-- | lib/private/Files/Storage/Wrapper/PermissionsMask.php | 92 | ||||
-rw-r--r-- | lib/private/Files/Storage/Wrapper/Quota.php | 221 | ||||
-rw-r--r-- | lib/private/Files/Storage/Wrapper/Wrapper.php | 499 |
9 files changed, 821 insertions, 1925 deletions
diff --git a/lib/private/Files/Storage/Wrapper/Availability.php b/lib/private/Files/Storage/Wrapper/Availability.php index 910ea369757..32c51a1b25e 100644 --- a/lib/private/Files/Storage/Wrapper/Availability.php +++ b/lib/private/Files/Storage/Wrapper/Availability.php @@ -1,28 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Files\Storage\Wrapper; @@ -42,12 +23,12 @@ class Availability extends Wrapper { /** @var IConfig */ protected $config; - public function __construct($parameters) { - $this->config = $parameters['config'] ?? \OC::$server->getConfig(); + public function __construct(array $parameters) { + $this->config = $parameters['config'] ?? \OCP\Server::get(IConfig::class); parent::__construct($parameters); } - public static function shouldRecheck($availability) { + public static function shouldRecheck($availability): bool { if (!$availability['available']) { // trigger a recheck if TTL reached if ((time() - $availability['last_checked']) > self::RECHECK_TTL_SEC) { @@ -59,10 +40,8 @@ class Availability extends Wrapper { /** * Only called if availability === false - * - * @return bool */ - private function updateAvailability() { + private function updateAvailability(): bool { // reset availability to false so that multiple requests don't recheck concurrently $this->setAvailability(false); try { @@ -74,10 +53,7 @@ class Availability extends Wrapper { return $result; } - /** - * @return bool - */ - private function isAvailable() { + private function isAvailable(): bool { $availability = $this->getAvailability(); if (self::shouldRecheck($availability)) { return $this->updateAvailability(); @@ -88,297 +64,137 @@ class Availability extends Wrapper { /** * @throws StorageNotAvailableException */ - private function checkAvailability() { + private function checkAvailability(): void { if (!$this->isAvailable()) { throw new StorageNotAvailableException(); } } - /** {@inheritdoc} */ - public function mkdir($path) { + /** + * Handles availability checks and delegates method calls dynamically + */ + private function handleAvailability(string $method, mixed ...$args): mixed { $this->checkAvailability(); try { - return parent::mkdir($path); + return call_user_func_array([parent::class, $method], $args); } catch (StorageNotAvailableException $e) { $this->setUnavailable($e); + return false; } } - /** {@inheritdoc} */ - public function rmdir($path) { - $this->checkAvailability(); - try { - return parent::rmdir($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function mkdir(string $path): bool { + return $this->handleAvailability('mkdir', $path); } - /** {@inheritdoc} */ - public function opendir($path) { - $this->checkAvailability(); - try { - return parent::opendir($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function rmdir(string $path): bool { + return $this->handleAvailability('rmdir', $path); } - /** {@inheritdoc} */ - public function is_dir($path) { - $this->checkAvailability(); - try { - return parent::is_dir($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function opendir(string $path) { + return $this->handleAvailability('opendir', $path); } - /** {@inheritdoc} */ - public function is_file($path) { - $this->checkAvailability(); - try { - return parent::is_file($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function is_dir(string $path): bool { + return $this->handleAvailability('is_dir', $path); } - /** {@inheritdoc} */ - public function stat($path) { - $this->checkAvailability(); - try { - return parent::stat($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function is_file(string $path): bool { + return $this->handleAvailability('is_file', $path); } - /** {@inheritdoc} */ - public function filetype($path) { - $this->checkAvailability(); - try { - return parent::filetype($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function stat(string $path): array|false { + return $this->handleAvailability('stat', $path); } - /** {@inheritdoc} */ - public function filesize($path) { - $this->checkAvailability(); - try { - return parent::filesize($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function filetype(string $path): string|false { + return $this->handleAvailability('filetype', $path); } - /** {@inheritdoc} */ - public function isCreatable($path) { - $this->checkAvailability(); - try { - return parent::isCreatable($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function filesize(string $path): int|float|false { + return $this->handleAvailability('filesize', $path); } - /** {@inheritdoc} */ - public function isReadable($path) { - $this->checkAvailability(); - try { - return parent::isReadable($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function isCreatable(string $path): bool { + return $this->handleAvailability('isCreatable', $path); } - /** {@inheritdoc} */ - public function isUpdatable($path) { - $this->checkAvailability(); - try { - return parent::isUpdatable($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function isReadable(string $path): bool { + return $this->handleAvailability('isReadable', $path); } - /** {@inheritdoc} */ - public function isDeletable($path) { - $this->checkAvailability(); - try { - return parent::isDeletable($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function isUpdatable(string $path): bool { + return $this->handleAvailability('isUpdatable', $path); } - /** {@inheritdoc} */ - public function isSharable($path) { - $this->checkAvailability(); - try { - return parent::isSharable($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function isDeletable(string $path): bool { + return $this->handleAvailability('isDeletable', $path); } - /** {@inheritdoc} */ - public function getPermissions($path) { - $this->checkAvailability(); - try { - return parent::getPermissions($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function isSharable(string $path): bool { + return $this->handleAvailability('isSharable', $path); } - /** {@inheritdoc} */ - public function file_exists($path) { - if ($path === '') { - return true; - } - $this->checkAvailability(); - try { - return parent::file_exists($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function getPermissions(string $path): int { + return $this->handleAvailability('getPermissions', $path); } - /** {@inheritdoc} */ - public function filemtime($path) { - $this->checkAvailability(); - try { - return parent::filemtime($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); + public function file_exists(string $path): bool { + if ($path === '') { + return true; } + return $this->handleAvailability('file_exists', $path); } - /** {@inheritdoc} */ - public function file_get_contents($path) { - $this->checkAvailability(); - try { - return parent::file_get_contents($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function filemtime(string $path): int|false { + return $this->handleAvailability('filemtime', $path); } - /** {@inheritdoc} */ - public function file_put_contents($path, $data) { - $this->checkAvailability(); - try { - return parent::file_put_contents($path, $data); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function file_get_contents(string $path): string|false { + return $this->handleAvailability('file_get_contents', $path); } - /** {@inheritdoc} */ - public function unlink($path) { - $this->checkAvailability(); - try { - return parent::unlink($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function file_put_contents(string $path, mixed $data): int|float|false { + return $this->handleAvailability('file_put_contents', $path, $data); } - /** {@inheritdoc} */ - public function rename($path1, $path2) { - $this->checkAvailability(); - try { - return parent::rename($path1, $path2); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function unlink(string $path): bool { + return $this->handleAvailability('unlink', $path); } - /** {@inheritdoc} */ - public function copy($path1, $path2) { - $this->checkAvailability(); - try { - return parent::copy($path1, $path2); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function rename(string $source, string $target): bool { + return $this->handleAvailability('rename', $source, $target); } - /** {@inheritdoc} */ - public function fopen($path, $mode) { - $this->checkAvailability(); - try { - return parent::fopen($path, $mode); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function copy(string $source, string $target): bool { + return $this->handleAvailability('copy', $source, $target); } - /** {@inheritdoc} */ - public function getMimeType($path) { - $this->checkAvailability(); - try { - return parent::getMimeType($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function fopen(string $path, string $mode) { + return $this->handleAvailability('fopen', $path, $mode); } - /** {@inheritdoc} */ - public function hash($type, $path, $raw = false) { - $this->checkAvailability(); - try { - return parent::hash($type, $path, $raw); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function getMimeType(string $path): string|false { + return $this->handleAvailability('getMimeType', $path); } - /** {@inheritdoc} */ - public function free_space($path) { - $this->checkAvailability(); - try { - return parent::free_space($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function hash(string $type, string $path, bool $raw = false): string|false { + return $this->handleAvailability('hash', $type, $path, $raw); } - /** {@inheritdoc} */ - public function search($query) { - $this->checkAvailability(); - try { - return parent::search($query); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function free_space(string $path): int|float|false { + return $this->handleAvailability('free_space', $path); } - /** {@inheritdoc} */ - public function touch($path, $mtime = null) { - $this->checkAvailability(); - try { - return parent::touch($path, $mtime); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function touch(string $path, ?int $mtime = null): bool { + return $this->handleAvailability('touch', $path, $mtime); } - /** {@inheritdoc} */ - public function getLocalFile($path) { - $this->checkAvailability(); - try { - return parent::getLocalFile($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function getLocalFile(string $path): string|false { + return $this->handleAvailability('getLocalFile', $path); } - /** {@inheritdoc} */ - public function hasUpdated($path, $time) { + public function hasUpdated(string $path, int $time): bool { if (!$this->isAvailable()) { return false; } @@ -391,66 +207,45 @@ class Availability extends Wrapper { } } - /** {@inheritdoc} */ - public function getOwner($path) { + public function getOwner(string $path): string|false { try { return parent::getOwner($path); } catch (StorageNotAvailableException $e) { $this->setUnavailable($e); + return false; } } - /** {@inheritdoc} */ - public function getETag($path) { - $this->checkAvailability(); - try { - return parent::getETag($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function getETag(string $path): string|false { + return $this->handleAvailability('getETag', $path); } - /** {@inheritdoc} */ - public function getDirectDownload($path) { - $this->checkAvailability(); - try { - return parent::getDirectDownload($path); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function getDirectDownload(string $path): array|false { + return $this->handleAvailability('getDirectDownload', $path); } - /** {@inheritdoc} */ - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - $this->checkAvailability(); - try { - return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { + return $this->handleAvailability('copyFromStorage', $sourceStorage, $sourceInternalPath, $targetInternalPath); } - /** {@inheritdoc} */ - public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { - $this->checkAvailability(); - try { - return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); - } catch (StorageNotAvailableException $e) { - $this->setUnavailable($e); - } + public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { + return $this->handleAvailability('moveFromStorage', $sourceStorage, $sourceInternalPath, $targetInternalPath); } - /** {@inheritdoc} */ - public function getMetaData($path) { + public function getMetaData(string $path): ?array { $this->checkAvailability(); try { return parent::getMetaData($path); } catch (StorageNotAvailableException $e) { $this->setUnavailable($e); + return null; } } /** + * @template T of StorageNotAvailableException|null + * @param T $e + * @psalm-return (T is null ? void : never) * @throws StorageNotAvailableException */ protected function setUnavailable(?StorageNotAvailableException $e): void { @@ -470,12 +265,13 @@ class Availability extends Wrapper { - public function getDirectoryContent($directory): \Traversable { + public function getDirectoryContent(string $directory): \Traversable { $this->checkAvailability(); try { return parent::getDirectoryContent($directory); } catch (StorageNotAvailableException $e) { $this->setUnavailable($e); + return new \EmptyIterator(); } } } diff --git a/lib/private/Files/Storage/Wrapper/Encoding.php b/lib/private/Files/Storage/Wrapper/Encoding.php index d6201dc8877..92e20cfb3df 100644 --- a/lib/private/Files/Storage/Wrapper/Encoding.php +++ b/lib/private/Files/Storage/Wrapper/Encoding.php @@ -1,35 +1,15 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author J0WI <J0WI@users.noreply.github.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Tigran Mkrtchyan <tigran.mkrtchyan@desy.de> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Files\Storage\Wrapper; -use OC\Cache\CappedMemoryCache; use OC\Files\Filesystem; +use OCP\Cache\CappedMemoryCache; +use OCP\Files\Cache\IScanner; use OCP\Files\Storage\IStorage; use OCP\ICache; @@ -40,7 +20,6 @@ use OCP\ICache; * the actual given name and then try its NFD form. */ class Encoding extends Wrapper { - /** * @var ICache */ @@ -49,19 +28,15 @@ class Encoding extends Wrapper { /** * @param array $parameters */ - public function __construct($parameters) { + public function __construct(array $parameters) { $this->storage = $parameters['storage']; $this->namesCache = new CappedMemoryCache(); } /** * Returns whether the given string is only made of ASCII characters - * - * @param string $str string - * - * @return bool true if the string is all ASCII, false otherwise */ - private function isAscii($str) { + private function isAscii(string $str): bool { return !preg_match('/[\\x80-\\xff]+/', $str); } @@ -70,11 +45,9 @@ class Encoding extends Wrapper { * each form for each path section and returns the correct form. * If no existing path found, returns the path as it was given. * - * @param string $fullPath path to check - * * @return string original or converted path */ - private function findPathToUse($fullPath) { + private function findPathToUse(string $fullPath): string { $cachedPath = $this->namesCache[$fullPath]; if ($cachedPath !== null) { return $cachedPath; @@ -98,12 +71,11 @@ class Encoding extends Wrapper { * Checks whether the last path section of the given path exists in NFC or NFD form * and returns the correct form. If no existing path found, returns null. * - * @param string $basePath base path to check * @param string $lastSection last section of the path to check for NFD/NFC variations * * @return string|null original or converted path, or null if none of the forms was found */ - private function findPathToUseLastSection($basePath, $lastSection) { + private function findPathToUseLastSection(string $basePath, string $lastSection): ?string { $fullPath = $basePath . $lastSection; if ($lastSection === '' || $this->isAscii($lastSection) || $this->storage->file_exists($fullPath)) { $this->namesCache[$fullPath] = $fullPath; @@ -127,13 +99,7 @@ class Encoding extends Wrapper { return null; } - /** - * see https://www.php.net/manual/en/function.mkdir.php - * - * @param string $path - * @return bool - */ - public function mkdir($path) { + public function mkdir(string $path): bool { // note: no conversion here, method should not be called with non-NFC names! $result = $this->storage->mkdir($path); if ($result) { @@ -142,13 +108,7 @@ class Encoding extends Wrapper { return $result; } - /** - * see https://www.php.net/manual/en/function.rmdir.php - * - * @param string $path - * @return bool - */ - public function rmdir($path) { + public function rmdir(string $path): bool { $result = $this->storage->rmdir($this->findPathToUse($path)); if ($result) { unset($this->namesCache[$path]); @@ -156,178 +116,72 @@ class Encoding extends Wrapper { return $result; } - /** - * see https://www.php.net/manual/en/function.opendir.php - * - * @param string $path - * @return resource|bool - */ - public function opendir($path) { + public function opendir(string $path) { $handle = $this->storage->opendir($this->findPathToUse($path)); return EncodingDirectoryWrapper::wrap($handle); } - /** - * see https://www.php.net/manual/en/function.is_dir.php - * - * @param string $path - * @return bool - */ - public function is_dir($path) { + public function is_dir(string $path): bool { return $this->storage->is_dir($this->findPathToUse($path)); } - /** - * see https://www.php.net/manual/en/function.is_file.php - * - * @param string $path - * @return bool - */ - public function is_file($path) { + public function is_file(string $path): bool { return $this->storage->is_file($this->findPathToUse($path)); } - /** - * see https://www.php.net/manual/en/function.stat.php - * only the following keys are required in the result: size and mtime - * - * @param string $path - * @return array|bool - */ - public function stat($path) { + public function stat(string $path): array|false { return $this->storage->stat($this->findPathToUse($path)); } - /** - * see https://www.php.net/manual/en/function.filetype.php - * - * @param string $path - * @return string|bool - */ - public function filetype($path) { + public function filetype(string $path): string|false { return $this->storage->filetype($this->findPathToUse($path)); } - /** - * see https://www.php.net/manual/en/function.filesize.php - * The result for filesize when called on a folder is required to be 0 - * - * @param string $path - * @return int|bool - */ - public function filesize($path) { + public function filesize(string $path): int|float|false { return $this->storage->filesize($this->findPathToUse($path)); } - /** - * check if a file can be created in $path - * - * @param string $path - * @return bool - */ - public function isCreatable($path) { + public function isCreatable(string $path): bool { return $this->storage->isCreatable($this->findPathToUse($path)); } - /** - * check if a file can be read - * - * @param string $path - * @return bool - */ - public function isReadable($path) { + public function isReadable(string $path): bool { return $this->storage->isReadable($this->findPathToUse($path)); } - /** - * check if a file can be written to - * - * @param string $path - * @return bool - */ - public function isUpdatable($path) { + public function isUpdatable(string $path): bool { return $this->storage->isUpdatable($this->findPathToUse($path)); } - /** - * check if a file can be deleted - * - * @param string $path - * @return bool - */ - public function isDeletable($path) { + public function isDeletable(string $path): bool { return $this->storage->isDeletable($this->findPathToUse($path)); } - /** - * check if a file can be shared - * - * @param string $path - * @return bool - */ - public function isSharable($path) { + public function isSharable(string $path): bool { return $this->storage->isSharable($this->findPathToUse($path)); } - /** - * get the full permissions of a path. - * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php - * - * @param string $path - * @return int - */ - public function getPermissions($path) { + public function getPermissions(string $path): int { return $this->storage->getPermissions($this->findPathToUse($path)); } - /** - * see https://www.php.net/manual/en/function.file_exists.php - * - * @param string $path - * @return bool - */ - public function file_exists($path) { + public function file_exists(string $path): bool { return $this->storage->file_exists($this->findPathToUse($path)); } - /** - * see https://www.php.net/manual/en/function.filemtime.php - * - * @param string $path - * @return int|bool - */ - public function filemtime($path) { + public function filemtime(string $path): int|false { return $this->storage->filemtime($this->findPathToUse($path)); } - /** - * see https://www.php.net/manual/en/function.file_get_contents.php - * - * @param string $path - * @return string|bool - */ - public function file_get_contents($path) { + public function file_get_contents(string $path): string|false { return $this->storage->file_get_contents($this->findPathToUse($path)); } - /** - * see https://www.php.net/manual/en/function.file_put_contents.php - * - * @param string $path - * @param mixed $data - * @return int|false - */ - public function file_put_contents($path, $data) { + public function file_put_contents(string $path, mixed $data): int|float|false { return $this->storage->file_put_contents($this->findPathToUse($path), $data); } - /** - * see https://www.php.net/manual/en/function.unlink.php - * - * @param string $path - * @return bool - */ - public function unlink($path) { + public function unlink(string $path): bool { $result = $this->storage->unlink($this->findPathToUse($path)); if ($result) { unset($this->namesCache[$path]); @@ -335,37 +189,16 @@ class Encoding extends Wrapper { return $result; } - /** - * see https://www.php.net/manual/en/function.rename.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function rename($path1, $path2) { + public function rename(string $source, string $target): bool { // second name always NFC - return $this->storage->rename($this->findPathToUse($path1), $this->findPathToUse($path2)); + return $this->storage->rename($this->findPathToUse($source), $this->findPathToUse($target)); } - /** - * see https://www.php.net/manual/en/function.copy.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function copy($path1, $path2) { - return $this->storage->copy($this->findPathToUse($path1), $this->findPathToUse($path2)); + public function copy(string $source, string $target): bool { + return $this->storage->copy($this->findPathToUse($source), $this->findPathToUse($target)); } - /** - * see https://www.php.net/manual/en/function.fopen.php - * - * @param string $path - * @param string $mode - * @return resource|bool - */ - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { $result = $this->storage->fopen($this->findPathToUse($path), $mode); if ($result && $mode !== 'r' && $mode !== 'rb') { unset($this->namesCache[$path]); @@ -373,131 +206,49 @@ class Encoding extends Wrapper { return $result; } - /** - * get the mimetype for a file or folder - * The mimetype for a folder is required to be "httpd/unix-directory" - * - * @param string $path - * @return string|bool - */ - public function getMimeType($path) { + public function getMimeType(string $path): string|false { return $this->storage->getMimeType($this->findPathToUse($path)); } - /** - * see https://www.php.net/manual/en/function.hash.php - * - * @param string $type - * @param string $path - * @param bool $raw - * @return string|bool - */ - public function hash($type, $path, $raw = false) { + public function hash(string $type, string $path, bool $raw = false): string|false { return $this->storage->hash($type, $this->findPathToUse($path), $raw); } - /** - * see https://www.php.net/manual/en/function.free_space.php - * - * @param string $path - * @return int|bool - */ - public function free_space($path) { + public function free_space(string $path): int|float|false { return $this->storage->free_space($this->findPathToUse($path)); } - /** - * search for occurrences of $query in file names - * - * @param string $query - * @return array|bool - */ - public function search($query) { - return $this->storage->search($query); - } - - /** - * see https://www.php.net/manual/en/function.touch.php - * If the backend does not support the operation, false should be returned - * - * @param string $path - * @param int $mtime - * @return bool - */ - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { return $this->storage->touch($this->findPathToUse($path), $mtime); } - /** - * get the path to a local version of the file. - * The local version of the file can be temporary and doesn't have to be persistent across requests - * - * @param string $path - * @return string|bool - */ - public function getLocalFile($path) { + public function getLocalFile(string $path): string|false { return $this->storage->getLocalFile($this->findPathToUse($path)); } - /** - * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @return bool - * - * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. - * returning true for other changes in the folder is optional - */ - public function hasUpdated($path, $time) { + public function hasUpdated(string $path, int $time): bool { return $this->storage->hasUpdated($this->findPathToUse($path), $time); } - /** - * get a cache instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache - * @return \OC\Files\Cache\Cache - */ - public function getCache($path = '', $storage = null) { + public function getCache(string $path = '', ?IStorage $storage = null): \OCP\Files\Cache\ICache { if (!$storage) { $storage = $this; } return $this->storage->getCache($this->findPathToUse($path), $storage); } - /** - * get a scanner instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner - * @return \OC\Files\Cache\Scanner - */ - public function getScanner($path = '', $storage = null) { + public function getScanner(string $path = '', ?IStorage $storage = null): IScanner { if (!$storage) { $storage = $this; } return $this->storage->getScanner($this->findPathToUse($path), $storage); } - /** - * get the ETag for a file or folder - * - * @param string $path - * @return string|bool - */ - public function getETag($path) { + public function getETag(string $path): string|false { return $this->storage->getETag($this->findPathToUse($path)); } - /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { if ($sourceStorage === $this) { return $this->copy($sourceInternalPath, $this->findPathToUse($targetInternalPath)); } @@ -509,13 +260,7 @@ class Encoding extends Wrapper { return $result; } - /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { if ($sourceStorage === $this) { $result = $this->rename($sourceInternalPath, $this->findPathToUse($targetInternalPath)); if ($result) { @@ -533,13 +278,15 @@ class Encoding extends Wrapper { return $result; } - public function getMetaData($path) { + public function getMetaData(string $path): ?array { $entry = $this->storage->getMetaData($this->findPathToUse($path)); - $entry['name'] = trim(Filesystem::normalizePath($entry['name']), '/'); + if ($entry !== null) { + $entry['name'] = trim(Filesystem::normalizePath($entry['name']), '/'); + } return $entry; } - public function getDirectoryContent($directory): \Traversable { + public function getDirectoryContent(string $directory): \Traversable { $entries = $this->storage->getDirectoryContent($this->findPathToUse($directory)); foreach ($entries as $entry) { $entry['name'] = trim(Filesystem::normalizePath($entry['name']), '/'); diff --git a/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php b/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php index 935a15af4cf..0a90b49f0f1 100644 --- a/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php +++ b/lib/private/Files/Storage/Wrapper/EncodingDirectoryWrapper.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2021, Nextcloud GmbH. - * - * @author Robin Appelman <robin@icewind.nl> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only */ - namespace OC\Files\Storage\Wrapper; use Icewind\Streams\DirectoryWrapper; @@ -30,10 +13,7 @@ use OC\Files\Filesystem; * Normalize file names while reading directory entries */ class EncodingDirectoryWrapper extends DirectoryWrapper { - /** - * @return string - */ - public function dir_readdir() { + public function dir_readdir(): string|false { $file = readdir($this->source); if ($file !== false && $file !== '.' && $file !== '..') { $file = trim(Filesystem::normalizePath($file), '/'); @@ -44,8 +24,7 @@ class EncodingDirectoryWrapper extends DirectoryWrapper { /** * @param resource $source - * @param callable $filter - * @return resource|bool + * @return resource|false */ public static function wrap($source) { return self::wrapSource($source, [ diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 4cfe932cc9f..58bd4dfddcf 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -1,53 +1,29 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author J0WI <J0WI@users.noreply.github.com> - * @author jknockaert <jasper@knockaert.nl> - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Piotr M <mrow4a@yahoo.com> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Tigran Mkrtchyan <tigran.mkrtchyan@desy.de> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Files\Storage\Wrapper; use OC\Encryption\Exceptions\ModuleDoesNotExistsException; -use OC\Encryption\Update; use OC\Encryption\Util; use OC\Files\Cache\CacheEntry; use OC\Files\Filesystem; use OC\Files\Mount\Manager; +use OC\Files\ObjectStore\ObjectStoreStorage; +use OC\Files\Storage\Common; use OC\Files\Storage\LocalTempFileTrait; use OC\Memcache\ArrayCache; -use OCP\Encryption\Exceptions\GenericEncryptionException; +use OCP\Cache\CappedMemoryCache; +use OCP\Encryption\Exceptions\InvalidHeaderException; use OCP\Encryption\IFile; use OCP\Encryption\IManager; use OCP\Encryption\Keys\IStorage; +use OCP\Files; use OCP\Files\Cache\ICacheEntry; +use OCP\Files\GenericFileException; use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage; use Psr\Log\LoggerInterface; @@ -55,107 +31,74 @@ use Psr\Log\LoggerInterface; class Encryption extends Wrapper { use LocalTempFileTrait; - /** @var string */ - private $mountPoint; - - /** @var \OC\Encryption\Util */ - private $util; - - /** @var \OCP\Encryption\IManager */ - private $encryptionManager; - - private LoggerInterface $logger; - - /** @var string */ - private $uid; - - /** @var array */ - protected $unencryptedSize; - - /** @var \OCP\Encryption\IFile */ - private $fileHelper; - - /** @var IMountPoint */ - private $mount; - - /** @var IStorage */ - private $keyStorage; - - /** @var Update */ - private $update; - - /** @var Manager */ - private $mountManager; - - /** @var array remember for which path we execute the repair step to avoid recursions */ - private $fixUnencryptedSizeOf = []; - - /** @var ArrayCache */ - private $arrayCache; + private string $mountPoint; + protected array $unencryptedSize = []; + private IMountPoint $mount; + /** for which path we execute the repair step to avoid recursions */ + private array $fixUnencryptedSizeOf = []; + /** @var CappedMemoryCache<bool> */ + private CappedMemoryCache $encryptedPaths; + private bool $enabled = true; /** * @param array $parameters */ public function __construct( - $parameters, - IManager $encryptionManager = null, - Util $util = null, - LoggerInterface $logger = null, - IFile $fileHelper = null, - $uid = null, - IStorage $keyStorage = null, - Update $update = null, - Manager $mountManager = null, - ArrayCache $arrayCache = null + array $parameters, + private IManager $encryptionManager, + private Util $util, + private LoggerInterface $logger, + private IFile $fileHelper, + private ?string $uid, + private IStorage $keyStorage, + private Manager $mountManager, + private ArrayCache $arrayCache, ) { $this->mountPoint = $parameters['mountPoint']; $this->mount = $parameters['mount']; - $this->encryptionManager = $encryptionManager; - $this->util = $util; - $this->logger = $logger; - $this->uid = $uid; - $this->fileHelper = $fileHelper; - $this->keyStorage = $keyStorage; - $this->unencryptedSize = []; - $this->update = $update; - $this->mountManager = $mountManager; - $this->arrayCache = $arrayCache; + $this->encryptedPaths = new CappedMemoryCache(); parent::__construct($parameters); } - /** - * see https://www.php.net/manual/en/function.filesize.php - * The result for filesize when called on a folder is required to be 0 - * - * @param string $path - * @return int - */ - public function filesize($path) { + public function filesize(string $path): int|float|false { $fullPath = $this->getFullPath($path); - /** @var CacheEntry $info */ $info = $this->getCache()->get($path); + if ($info === false) { + /* Pass call to wrapped storage, it may be a special file like a part file */ + return $this->storage->filesize($path); + } if (isset($this->unencryptedSize[$fullPath])) { $size = $this->unencryptedSize[$fullPath]; - // update file cache - if ($info instanceof ICacheEntry) { - $info = $info->getData(); - $info['encrypted'] = $info['encryptedVersion']; - } else { - if (!is_array($info)) { - $info = []; + + // Update file cache (only if file is already cached). + // Certain files are not cached (e.g. *.part). + if (isset($info['fileid'])) { + if ($info instanceof ICacheEntry) { + $info['encrypted'] = $info['encryptedVersion']; + } else { + /** + * @psalm-suppress RedundantCondition + */ + if (!is_array($info)) { + $info = []; + } + $info['encrypted'] = true; + $info = new CacheEntry($info); } - $info['encrypted'] = true; - } - $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); @@ -168,10 +111,12 @@ class Encryption extends Wrapper { if (isset($this->unencryptedSize[$fullPath])) { $data['encrypted'] = true; $data['size'] = $this->unencryptedSize[$fullPath]; + $data['unencrypted_size'] = $data['size']; } else { if (isset($info['fileid']) && $info['encrypted']) { - $data['size'] = $this->verifyUnencryptedSize($path, $info['size']); + $data['size'] = $this->verifyUnencryptedSize($path, $info->getUnencryptedSize()); $data['encrypted'] = true; + $data['unencrypted_size'] = $data['size']; } } @@ -182,7 +127,7 @@ class Encryption extends Wrapper { return $data; } - public function getMetaData($path) { + public function getMetaData(string $path): ?array { $data = $this->storage->getMetaData($path); if (is_null($data)) { return null; @@ -190,24 +135,18 @@ class Encryption extends Wrapper { return $this->modifyMetaData($path, $data); } - public function getDirectoryContent($directory): \Traversable { + public function getDirectoryContent(string $directory): \Traversable { $parent = rtrim($directory, '/'); foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) { yield $this->modifyMetaData($parent . '/' . $data['name'], $data); } } - /** - * see https://www.php.net/manual/en/function.file_get_contents.php - * - * @param string $path - * @return string - */ - public function file_get_contents($path) { + public function file_get_contents(string $path): string|false { $encryptionModule = $this->getEncryptionModule($path); if ($encryptionModule) { - $handle = $this->fopen($path, "r"); + $handle = $this->fopen($path, 'r'); if (!$handle) { return false; } @@ -218,14 +157,7 @@ class Encryption extends Wrapper { return $this->storage->file_get_contents($path); } - /** - * see https://www.php.net/manual/en/function.file_put_contents.php - * - * @param string $path - * @param mixed $data - * @return int|false - */ - public function file_put_contents($path, $data) { + public function file_put_contents(string $path, mixed $data): int|float|false { // file put content will always be translated to a stream write $handle = $this->fopen($path, 'w'); if (is_resource($handle)) { @@ -237,13 +169,7 @@ class Encryption extends Wrapper { return false; } - /** - * see https://www.php.net/manual/en/function.unlink.php - * - * @param string $path - * @return bool - */ - public function unlink($path) { + public function unlink(string $path): bool { $fullPath = $this->getFullPath($path); if ($this->util->isExcluded($fullPath)) { return $this->storage->unlink($path); @@ -257,31 +183,24 @@ class Encryption extends Wrapper { return $this->storage->unlink($path); } - /** - * see https://www.php.net/manual/en/function.rename.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function rename($path1, $path2) { - $result = $this->storage->rename($path1, $path2); + public function rename(string $source, string $target): bool { + $result = $this->storage->rename($source, $target); - if ($result && + if ($result // versions always use the keys from the original file, so we can skip // this step for versions - $this->isVersion($path2) === false && - $this->encryptionManager->isEnabled()) { - $source = $this->getFullPath($path1); - if (!$this->util->isExcluded($source)) { - $target = $this->getFullPath($path2); - if (isset($this->unencryptedSize[$source])) { - $this->unencryptedSize[$target] = $this->unencryptedSize[$source]; + && $this->isVersion($target) === false + && $this->encryptionManager->isEnabled()) { + $sourcePath = $this->getFullPath($source); + if (!$this->util->isExcluded($sourcePath)) { + $targetPath = $this->getFullPath($target); + if (isset($this->unencryptedSize[$sourcePath])) { + $this->unencryptedSize[$targetPath] = $this->unencryptedSize[$sourcePath]; } - $this->keyStorage->renameKeys($source, $target); - $module = $this->getEncryptionModule($path2); + $this->keyStorage->renameKeys($sourcePath, $targetPath); + $module = $this->getEncryptionModule($target); if ($module) { - $module->update($target, $this->uid, []); + $module->update($targetPath, $this->uid, []); } } } @@ -289,18 +208,12 @@ class Encryption extends Wrapper { return $result; } - /** - * see https://www.php.net/manual/en/function.rmdir.php - * - * @param string $path - * @return bool - */ - public function rmdir($path) { + public function rmdir(string $path): bool { $result = $this->storage->rmdir($path); $fullPath = $this->getFullPath($path); - if ($result && - $this->util->isExcluded($fullPath) === false && - $this->encryptionManager->isEnabled() + if ($result + && $this->util->isExcluded($fullPath) === false + && $this->encryptionManager->isEnabled() ) { $this->keyStorage->deleteAllFileKeys($fullPath); } @@ -308,20 +221,14 @@ class Encryption extends Wrapper { return $result; } - /** - * check if a file can be read - * - * @param string $path - * @return bool - */ - public function isReadable($path) { + public function isReadable(string $path): bool { $isReadable = true; $metaData = $this->getMetaData($path); if ( - !$this->is_dir($path) && - isset($metaData['encrypted']) && - $metaData['encrypted'] === true + !$this->is_dir($path) + && isset($metaData['encrypted']) + && $metaData['encrypted'] === true ) { $fullPath = $this->getFullPath($path); $module = $this->getEncryptionModule($path); @@ -331,37 +238,20 @@ class Encryption extends Wrapper { return $this->storage->isReadable($path) && $isReadable; } - /** - * see https://www.php.net/manual/en/function.copy.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function copy($path1, $path2) { - $source = $this->getFullPath($path1); + public function copy(string $source, string $target): bool { + $sourcePath = $this->getFullPath($source); - if ($this->util->isExcluded($source)) { - return $this->storage->copy($path1, $path2); + if ($this->util->isExcluded($sourcePath)) { + return $this->storage->copy($source, $target); } // need to stream copy file by file in case we copy between a encrypted // and a unencrypted storage - $this->unlink($path2); - return $this->copyFromStorage($this, $path1, $path2); + $this->unlink($target); + return $this->copyFromStorage($this, $source, $target); } - /** - * see https://www.php.net/manual/en/function.fopen.php - * - * @param string $path - * @param string $mode - * @return resource|bool - * @throws GenericEncryptionException - * @throws ModuleDoesNotExistsException - */ - public function fopen($path, $mode) { - + public function fopen(string $path, string $mode) { // check if the file is stored in the array cache, this means that we // copy a file over to the versions folder, in this case we don't want to // decrypt it @@ -370,6 +260,10 @@ class Encryption extends Wrapper { return $this->storage->fopen($path, $mode); } + if (!$this->enabled) { + return $this->storage->fopen($path, $mode); + } + $encryptionEnabled = $this->encryptionManager->isEnabled(); $shouldEncrypt = false; $encryptionModule = null; @@ -410,10 +304,8 @@ class Encryption extends Wrapper { // if we update a encrypted file with a un-encrypted one we change the db flag if ($targetIsEncrypted && $encryptionEnabled === false) { $cache = $this->storage->getCache(); - if ($cache) { - $entry = $cache->get($path); - $cache->update($entry->getId(), ['encrypted' => 0]); - } + $entry = $cache->get($path); + $cache->update($entry->getId(), ['encrypted' => 0]); } if ($encryptionEnabled) { // if $encryptionModuleId is empty, the default module will be used @@ -428,7 +320,7 @@ class Encryption extends Wrapper { if (!empty($encryptionModuleId)) { $encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId); $shouldEncrypt = true; - } elseif (empty($encryptionModuleId) && $info['encrypted'] === true) { + } elseif ($info !== false && $info['encrypted'] === true) { // we come from a old installation. No header and/or no module defined // but the file is encrypted. In this case we need to use the // OC_DEFAULT_MODULE to read the file @@ -452,7 +344,18 @@ class Encryption extends Wrapper { } if ($shouldEncrypt === true && $encryptionModule !== null) { + $this->encryptedPaths->set($this->util->stripPartialFileExtension($path), true); $headerSize = $this->getHeaderSize($path); + if ($mode === 'r' && $headerSize === 0) { + $firstBlock = $this->readFirstBlock($path); + if (!$firstBlock) { + throw new InvalidHeaderException("Unable to get header block for $path"); + } elseif (!str_starts_with($firstBlock, Util::HEADER_START)) { + throw new InvalidHeaderException("Unable to get header size for $path, file doesn't start with encryption header"); + } else { + throw new InvalidHeaderException("Unable to get header size for $path, even though file does start with encryption header"); + } + } $source = $this->storage->fopen($path, $mode); if (!is_resource($source)) { return false; @@ -470,7 +373,7 @@ class Encryption extends Wrapper { /** - * perform some plausibility checks if the the unencrypted size is correct. + * perform some plausibility checks if the unencrypted size is correct. * If not, we calculate the correct unencrypted size and return it * * @param string $path internal path relative to the storage root @@ -478,12 +381,13 @@ 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; - if ($unencryptedSize < 0 || - ($size > 0 && $unencryptedSize === $size) + if ($unencryptedSize < 0 + || ($size > 0 && $unencryptedSize === $size) + || $unencryptedSize > $size ) { // check if we already calculate the unencrypted size for the // given path to avoid recursions @@ -507,10 +411,8 @@ class Encryption extends Wrapper { * @param string $path internal path relative to the storage root * @param int $size size of the physical file * @param int $unencryptedSize size of the unencrypted file - * - * @return int calculated unencrypted size */ - protected function fixUnencryptedSize($path, $size, $unencryptedSize) { + protected function fixUnencryptedSize(string $path, int $size, int $unencryptedSize): int|float { $headerSize = $this->getHeaderSize($path); $header = $this->getHeader($path); $encryptionModule = $this->getEncryptionModule($path); @@ -579,10 +481,10 @@ class Encryption extends Wrapper { // write to cache if applicable $cache = $this->storage->getCache(); - if ($cache) { - $entry = $cache->get($path); - $cache->update($entry['fileid'], ['size' => $newUnencryptedSize]); - } + $entry = $cache->get($path); + $cache->update($entry['fileid'], [ + 'unencrypted_size' => $newUnencryptedSize + ]); return $newUnencryptedSize; } @@ -596,7 +498,7 @@ class Encryption extends Wrapper { * This is required as stream_read only returns smaller chunks of data when the stream fetches from a * remote storage over the internet and it does not care about the given $blockSize. * - * @param handle the stream to read from + * @param resource $handle the stream to read from * @param int $blockSize Length of requested data block in bytes * @return string Data fetched from stream. */ @@ -614,14 +516,12 @@ class Encryption extends Wrapper { return $data; } - /** - * @param Storage\IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @param bool $preserveMtime - * @return bool - */ - public function moveFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = true) { + public function moveFromStorage( + Storage\IStorage $sourceStorage, + string $sourceInternalPath, + string $targetInternalPath, + $preserveMtime = true, + ): bool { if ($sourceStorage === $this) { return $this->rename($sourceInternalPath, $targetInternalPath); } @@ -638,26 +538,34 @@ class Encryption extends Wrapper { $result = $this->copyBetweenStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, true); if ($result) { - if ($sourceStorage->is_dir($sourceInternalPath)) { - $result &= $sourceStorage->rmdir($sourceInternalPath); - } else { - $result &= $sourceStorage->unlink($sourceInternalPath); + $setPreserveCacheOnDelete = $sourceStorage->instanceOfStorage(ObjectStoreStorage::class) && !$this->instanceOfStorage(ObjectStoreStorage::class); + if ($setPreserveCacheOnDelete) { + /** @var ObjectStoreStorage $sourceStorage */ + $sourceStorage->setPreserveCacheOnDelete(true); + } + try { + if ($sourceStorage->is_dir($sourceInternalPath)) { + $result = $sourceStorage->rmdir($sourceInternalPath); + } else { + $result = $sourceStorage->unlink($sourceInternalPath); + } + } finally { + if ($setPreserveCacheOnDelete) { + /** @var ObjectStoreStorage $sourceStorage */ + $sourceStorage->setPreserveCacheOnDelete(false); + } } } return $result; } - - /** - * @param Storage\IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @param bool $preserveMtime - * @param bool $isRename - * @return bool - */ - public function copyFromStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false, $isRename = false) { - + public function copyFromStorage( + Storage\IStorage $sourceStorage, + string $sourceInternalPath, + string $targetInternalPath, + $preserveMtime = false, + $isRename = false, + ): bool { // TODO clean this up once the underlying moveFromStorage in OC\Files\Storage\Wrapper\Common is fixed: // - call $this->storage->copyFromStorage() instead of $this->copyBetweenStorage // - copy the file cache update from $this->copyBetweenStorage to this method @@ -669,14 +577,14 @@ class Encryption extends Wrapper { /** * Update the encrypted cache version in the database - * - * @param Storage\IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @param bool $isRename - * @param bool $keepEncryptionVersion */ - private function updateEncryptedVersion(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $isRename, $keepEncryptionVersion) { + private function updateEncryptedVersion( + Storage\IStorage $sourceStorage, + string $sourceInternalPath, + string $targetInternalPath, + bool $isRename, + bool $keepEncryptionVersion, + ): void { $isEncrypted = $this->encryptionManager->isEnabled() && $this->shouldEncrypt($targetInternalPath); $cacheInformation = [ 'encrypted' => $isEncrypted, @@ -716,21 +624,19 @@ class Encryption extends Wrapper { /** * copy file between two storages - * - * @param Storage\IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @param bool $preserveMtime - * @param bool $isRename - * @return bool * @throws \Exception */ - private function copyBetweenStorage(Storage\IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime, $isRename) { - + private function copyBetweenStorage( + Storage\IStorage $sourceStorage, + string $sourceInternalPath, + string $targetInternalPath, + bool $preserveMtime, + bool $isRename, + ): bool { // for versions we have nothing to do, because versions should always use the // key from the original file. Just create a 1:1 copy and done - if ($this->isVersion($targetInternalPath) || - $this->isVersion($sourceInternalPath)) { + if ($this->isVersion($targetInternalPath) + || $this->isVersion($sourceInternalPath)) { // remember that we try to create a version so that we can detect it during // fopen($sourceInternalPath) and by-pass the encryption in order to // create a 1:1 copy of the file @@ -743,7 +649,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); @@ -753,9 +659,8 @@ class Encryption extends Wrapper { // first copy the keys that we reuse the existing file key on the target location // and don't create a new one which would break versions for example. - $mount = $this->mountManager->findByStorageId($sourceStorage->getId()); - if (count($mount) === 1) { - $mountPoint = $mount[0]->getMountPoint(); + if ($sourceStorage->instanceOfStorage(Common::class) && $sourceStorage->getMountOption('mount_point')) { + $mountPoint = $sourceStorage->getMountOption('mount_point'); $source = $mountPoint . '/' . $sourceInternalPath; $target = $this->getFullPath($targetInternalPath); $this->copyKeys($source, $target); @@ -771,9 +676,9 @@ class Encryption extends Wrapper { $result = true; } if (is_resource($dh)) { - while ($result and ($file = readdir($dh)) !== false) { + while ($result && ($file = readdir($dh)) !== false) { if (!Filesystem::isIgnoredDir($file)) { - $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename); + $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, $preserveMtime, $isRename); } } } @@ -781,17 +686,18 @@ class Encryption extends Wrapper { try { $source = $sourceStorage->fopen($sourceInternalPath, 'r'); $target = $this->fopen($targetInternalPath, 'w'); - [, $result] = \OC_Helper::streamCopy($source, $target); - fclose($source); - fclose($target); - } catch (\Exception $e) { - if (is_resource($source)) { + if ($source === false || $target === false) { + $result = false; + } else { + [, $result] = Files::streamCopy($source, $target, true); + } + } finally { + if (isset($source) && $source !== false) { fclose($source); } - if (is_resource($target)) { + if (isset($target) && $target !== false) { fclose($target); } - throw $e; } if ($result) { if ($preserveMtime) { @@ -808,14 +714,7 @@ 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) { + public function getLocalFile(string $path): string|false { if ($this->encryptionManager->isEnabled()) { $cachedFile = $this->getCachedFile($path); if (is_string($cachedFile)) { @@ -825,27 +724,18 @@ 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() { + public function isLocal(): bool { if ($this->encryptionManager->isEnabled()) { return false; } 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) { + public function stat(string $path): array|false { $stat = $this->storage->stat($path); + if (!$stat) { + return false; + } $fileSize = $this->filesize($path); $stat['size'] = $fileSize; $stat[7] = $fileSize; @@ -853,16 +743,11 @@ 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) { + public function hash(string $type, string $path, bool $raw = false): string|false { $fh = $this->fopen($path, 'rb'); + if ($fh === false) { + return false; + } $ctx = hash_init($type); hash_update_stream($ctx, $fh); fclose($fh); @@ -875,21 +760,21 @@ class Encryption extends Wrapper { * @param string $path relative to mount point * @return string full path including mount point */ - protected function getFullPath($path) { + protected function getFullPath(string $path): string { return Filesystem::normalizePath($this->mountPoint . '/' . $path); } /** * read first block of encrypted file, typically this will contain the * encryption header - * - * @param string $path - * @return string */ - protected function readFirstBlock($path) { + protected function readFirstBlock(string $path): string { $firstBlock = ''; if ($this->storage->is_file($path)) { $handle = $this->storage->fopen($path, 'r'); + if ($handle === false) { + return ''; + } $firstBlock = fread($handle, $this->util->getHeaderSize()); fclose($handle); } @@ -898,11 +783,8 @@ class Encryption extends Wrapper { /** * return header size of given file - * - * @param string $path - * @return int */ - protected function getHeaderSize($path) { + protected function getHeaderSize(string $path): int { $headerSize = 0; $realFile = $this->util->stripPartialFileExtension($path); if ($this->storage->is_file($realFile)) { @@ -910,7 +792,7 @@ class Encryption extends Wrapper { } $firstBlock = $this->readFirstBlock($path); - if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) { + if (str_starts_with($firstBlock, Util::HEADER_START)) { $headerSize = $this->util->getHeaderSize(); } @@ -918,40 +800,9 @@ class Encryption extends Wrapper { } /** - * parse raw header to array - * - * @param string $rawHeader - * @return array - */ - protected function parseRawHeader($rawHeader) { - $result = []; - if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) { - $header = $rawHeader; - $endAt = strpos($header, Util::HEADER_END); - if ($endAt !== false) { - $header = substr($header, 0, $endAt + strlen(Util::HEADER_END)); - - // +1 to not start with an ':' which would result in empty element at the beginning - $exploded = explode(':', substr($header, strlen(Util::HEADER_START) + 1)); - - $element = array_shift($exploded); - while ($element !== Util::HEADER_END) { - $result[$element] = array_shift($exploded); - $element = array_shift($exploded); - } - } - } - - return $result; - } - - /** * read header from file - * - * @param string $path - * @return array */ - protected function getHeader($path) { + protected function getHeader(string $path): array { $realFile = $this->util->stripPartialFileExtension($path); $exists = $this->storage->is_file($realFile); if ($exists) { @@ -960,13 +811,15 @@ class Encryption extends Wrapper { $result = []; - // first check if it is an encrypted file at all - // We would do query to filecache only if we know that entry in filecache exists + $isEncrypted = $this->encryptedPaths->get($realFile); + if (is_null($isEncrypted)) { + $info = $this->getCache()->get($path); + $isEncrypted = isset($info['encrypted']) && $info['encrypted'] === true; + } - $info = $this->getCache()->get($path); - if (isset($info['encrypted']) && $info['encrypted'] === true) { + if ($isEncrypted) { $firstBlock = $this->readFirstBlock($path); - $result = $this->parseRawHeader($firstBlock); + $result = $this->util->parseRawHeader($firstBlock); // if the header doesn't contain a encryption module we check if it is a // legacy file. If true, we add the default encryption module @@ -981,12 +834,10 @@ class Encryption extends Wrapper { /** * read encryption module needed to read/write the file located at $path * - * @param string $path - * @return null|\OCP\Encryption\IEncryptionModule * @throws ModuleDoesNotExistsException * @throws \Exception */ - protected function getEncryptionModule($path) { + protected function getEncryptionModule(string $path): ?\OCP\Encryption\IEncryptionModule { $encryptionModule = null; $header = $this->getHeader($path); $encryptionModuleId = $this->util->getEncryptionModuleId($header); @@ -1002,11 +853,7 @@ class Encryption extends Wrapper { return $encryptionModule; } - /** - * @param string $path - * @param int $unencryptedSize - */ - public function updateUnencryptedSize($path, $unencryptedSize) { + public function updateUnencryptedSize(string $path, int|float $unencryptedSize): void { $this->unencryptedSize[$path] = $unencryptedSize; } @@ -1015,9 +862,8 @@ class Encryption extends Wrapper { * * @param string $source path relative to data/ * @param string $target path relative to data/ - * @return bool */ - protected function copyKeys($source, $target) { + protected function copyKeys(string $source, string $target): bool { if (!$this->util->isExcluded($source)) { return $this->keyStorage->copyKeys($source, $target); } @@ -1027,22 +873,16 @@ class Encryption extends Wrapper { /** * check if path points to a files version - * - * @param $path - * @return bool */ - protected function isVersion($path) { + protected function isVersion(string $path): bool { $normalized = Filesystem::normalizePath($path); return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/'; } /** * check if the given storage should be encrypted or not - * - * @param $path - * @return bool */ - protected function shouldEncrypt($path) { + protected function shouldEncrypt(string $path): bool { $fullPath = $this->getFullPath($path); $mountPointConfig = $this->mount->getOption('encrypt', true); if ($mountPointConfig === false) { @@ -1062,12 +902,45 @@ class Encryption extends Wrapper { return $encryptionModule->shouldEncrypt($fullPath); } - public function writeStream(string $path, $stream, int $size = null): int { + public function writeStream(string $path, $stream, ?int $size = null): int { // always fall back to fopen $target = $this->fopen($path, 'w'); - [$count, $result] = \OC_Helper::streamCopy($stream, $target); + if ($target === false) { + throw new GenericFileException("Failed to open $path for writing"); + } + [$count, $result] = Files::streamCopy($stream, $target, true); fclose($stream); fclose($target); + + // 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->shouldEncrypt($path)) { + $this->getCache()->put($path, ['unencrypted_size' => $count]); + } + return $count; } + + public function clearIsEncryptedCache(): void { + $this->encryptedPaths->clear(); + } + + /** + * Allow temporarily disabling the wrapper + */ + public function setEnabled(bool $enabled): void { + $this->enabled = $enabled; + } + + /** + * Check if the on-disk data for a file has a valid encrypted header + * + * @param string $path + * @return bool + */ + public function hasValidHeader(string $path): bool { + $firstBlock = $this->readFirstBlock($path); + $header = $this->util->parseRawHeader($firstBlock); + return (count($header) > 0); + } } diff --git a/lib/private/Files/Storage/Wrapper/Jail.php b/lib/private/Files/Storage/Wrapper/Jail.php index 65ee6f1181a..38b113cef88 100644 --- a/lib/private/Files/Storage/Wrapper/Jail.php +++ b/lib/private/Files/Storage/Wrapper/Jail.php @@ -1,36 +1,20 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author J0WI <J0WI@users.noreply.github.com> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Tigran Mkrtchyan <tigran.mkrtchyan@desy.de> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Files\Storage\Wrapper; use OC\Files\Cache\Wrapper\CacheJail; use OC\Files\Cache\Wrapper\JailPropagator; +use OC\Files\Cache\Wrapper\JailWatcher; use OC\Files\Filesystem; +use OCP\Files; +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\IPropagator; +use OCP\Files\Cache\IWatcher; use OCP\Files\Storage\IStorage; use OCP\Files\Storage\IWriteStreamStorage; use OCP\Lock\ILockingProvider; @@ -47,32 +31,32 @@ class Jail extends Wrapper { protected $rootPath; /** - * @param array $arguments ['storage' => $storage, 'root' => $root] + * @param array $parameters ['storage' => $storage, 'root' => $root] * * $storage: The storage that will be wrapper * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage */ - public function __construct($arguments) { - parent::__construct($arguments); - $this->rootPath = $arguments['root']; + public function __construct(array $parameters) { + parent::__construct($parameters); + $this->rootPath = $parameters['root']; } - public function getUnjailedPath($path) { + public function getUnjailedPath(string $path): string { return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/'); } /** * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper */ - public function getUnjailedStorage() { + public function getUnjailedStorage(): IStorage { return $this->storage; } - public function getJailedPath($path) { + public function getJailedPath(string $path): ?string { $root = rtrim($this->rootPath, '/') . '/'; - if ($path !== $this->rootPath && strpos($path, $root) !== 0) { + if ($path !== $this->rootPath && !str_starts_with($path, $root)) { return null; } else { $path = substr($path, strlen($this->rootPath)); @@ -80,435 +64,178 @@ class Jail extends Wrapper { } } - public function getId() { + public function getId(): string { return parent::getId(); } - /** - * see https://www.php.net/manual/en/function.mkdir.php - * - * @param string $path - * @return bool - */ - public function mkdir($path) { + public function mkdir(string $path): bool { return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.rmdir.php - * - * @param string $path - * @return bool - */ - public function rmdir($path) { + public function rmdir(string $path): bool { return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.opendir.php - * - * @param string $path - * @return resource|bool - */ - public function opendir($path) { + public function opendir(string $path) { return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.is_dir.php - * - * @param string $path - * @return bool - */ - public function is_dir($path) { + public function is_dir(string $path): bool { return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.is_file.php - * - * @param string $path - * @return bool - */ - public function is_file($path) { + public function is_file(string $path): bool { return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.stat.php - * only the following keys are required in the result: size and mtime - * - * @param string $path - * @return array|bool - */ - public function stat($path) { + public function stat(string $path): array|false { return $this->getWrapperStorage()->stat($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.filetype.php - * - * @param string $path - * @return bool - */ - public function filetype($path) { + public function filetype(string $path): string|false { return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.filesize.php - * The result for filesize when called on a folder is required to be 0 - * - * @param string $path - * @return int|bool - */ - public function filesize($path) { + public function filesize(string $path): int|float|false { return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path)); } - /** - * check if a file can be created in $path - * - * @param string $path - * @return bool - */ - public function isCreatable($path) { + public function isCreatable(string $path): bool { return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path)); } - /** - * check if a file can be read - * - * @param string $path - * @return bool - */ - public function isReadable($path) { + public function isReadable(string $path): bool { return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path)); } - /** - * check if a file can be written to - * - * @param string $path - * @return bool - */ - public function isUpdatable($path) { + public function isUpdatable(string $path): bool { return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path)); } - /** - * check if a file can be deleted - * - * @param string $path - * @return bool - */ - public function isDeletable($path) { + public function isDeletable(string $path): bool { return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path)); } - /** - * check if a file can be shared - * - * @param string $path - * @return bool - */ - public function isSharable($path) { + public function isSharable(string $path): bool { return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path)); } - /** - * get the full permissions of a path. - * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php - * - * @param string $path - * @return int - */ - public function getPermissions($path) { + public function getPermissions(string $path): int { return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.file_exists.php - * - * @param string $path - * @return bool - */ - public function file_exists($path) { + public function file_exists(string $path): bool { return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.filemtime.php - * - * @param string $path - * @return int|bool - */ - public function filemtime($path) { + public function filemtime(string $path): int|false { return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.file_get_contents.php - * - * @param string $path - * @return string|bool - */ - public function file_get_contents($path) { + public function file_get_contents(string $path): string|false { return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.file_put_contents.php - * - * @param string $path - * @param mixed $data - * @return int|false - */ - public function file_put_contents($path, $data) { + public function file_put_contents(string $path, mixed $data): int|float|false { return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data); } - /** - * see https://www.php.net/manual/en/function.unlink.php - * - * @param string $path - * @return bool - */ - public function unlink($path) { + public function unlink(string $path): bool { return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.rename.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function rename($path1, $path2) { - return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2)); + public function rename(string $source, string $target): bool { + return $this->getWrapperStorage()->rename($this->getUnjailedPath($source), $this->getUnjailedPath($target)); } - /** - * see https://www.php.net/manual/en/function.copy.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function copy($path1, $path2) { - return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2)); + public function copy(string $source, string $target): bool { + return $this->getWrapperStorage()->copy($this->getUnjailedPath($source), $this->getUnjailedPath($target)); } - /** - * see https://www.php.net/manual/en/function.fopen.php - * - * @param string $path - * @param string $mode - * @return resource|bool - */ - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode); } - /** - * get the mimetype for a file or folder - * The mimetype for a folder is required to be "httpd/unix-directory" - * - * @param string $path - * @return string|bool - */ - public function getMimeType($path) { + public function getMimeType(string $path): string|false { return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path)); } - /** - * see https://www.php.net/manual/en/function.hash.php - * - * @param string $type - * @param string $path - * @param bool $raw - * @return string|bool - */ - public function hash($type, $path, $raw = false) { + public function hash(string $type, string $path, bool $raw = false): string|false { return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw); } - /** - * see https://www.php.net/manual/en/function.free_space.php - * - * @param string $path - * @return int|bool - */ - public function free_space($path) { + public function free_space(string $path): int|float|false { return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path)); } - /** - * search for occurrences of $query in file names - * - * @param string $query - * @return array|bool - */ - public function search($query) { - return $this->getWrapperStorage()->search($query); - } - - /** - * see https://www.php.net/manual/en/function.touch.php - * If the backend does not support the operation, false should be returned - * - * @param string $path - * @param int $mtime - * @return bool - */ - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime); } - /** - * get the path to a local version of the file. - * The local version of the file can be temporary and doesn't have to be persistent across requests - * - * @param string $path - * @return string|bool - */ - public function getLocalFile($path) { + public function getLocalFile(string $path): string|false { return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path)); } - /** - * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @return bool - * - * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. - * returning true for other changes in the folder is optional - */ - public function hasUpdated($path, $time) { + public function hasUpdated(string $path, int $time): bool { return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time); } - /** - * get a cache instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage|null (optional) the storage to pass to the cache - * @return \OC\Files\Cache\Cache - */ - public function getCache($path = '', $storage = null) { - if (!$storage) { - $storage = $this->getWrapperStorage(); - } - $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage); + public function getCache(string $path = '', ?IStorage $storage = null): ICache { + $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path)); return new CacheJail($sourceCache, $this->rootPath); } - /** - * get the user id of the owner of a file or folder - * - * @param string $path - * @return string - */ - public function getOwner($path) { + public function getOwner(string $path): string|false { return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path)); } - /** - * get a watcher instance for the cache - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher - * @return \OC\Files\Cache\Watcher - */ - public function getWatcher($path = '', $storage = null) { - if (!$storage) { - $storage = $this; - } - return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage); + public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher { + $sourceWatcher = $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $this->getWrapperStorage()); + return new JailWatcher($sourceWatcher, $this->rootPath); } - /** - * get the ETag for a file or folder - * - * @param string $path - * @return string|bool - */ - public function getETag($path) { + public function getETag(string $path): string|false { return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path)); } - public function getMetaData($path) { + public function getMetaData(string $path): ?array { return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path)); } - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - */ - public function acquireLock($path, $type, ILockingProvider $provider) { + public function acquireLock(string $path, int $type, ILockingProvider $provider): void { $this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider); } - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function releaseLock($path, $type, ILockingProvider $provider) { + public function releaseLock(string $path, int $type, ILockingProvider $provider): void { $this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider); } - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function changeLock($path, $type, ILockingProvider $provider) { + public function changeLock(string $path, int $type, ILockingProvider $provider): void { $this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider); } /** * Resolve the path for the source of the share - * - * @param string $path - * @return array */ - public function resolvePath($path) { + public function resolvePath(string $path): array { return [$this->getWrapperStorage(), $this->getUnjailedPath($path)]; } - /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { if ($sourceStorage === $this) { return $this->copy($sourceInternalPath, $targetInternalPath); } return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath)); } - /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { if ($sourceStorage === $this) { return $this->rename($sourceInternalPath, $targetInternalPath); } return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath)); } - public function getPropagator($storage = null) { + public function getPropagator(?IStorage $storage = null): IPropagator { if (isset($this->propagator)) { return $this->propagator; } @@ -520,21 +247,21 @@ class Jail extends Wrapper { return $this->propagator; } - public function writeStream(string $path, $stream, int $size = null): int { + public function writeStream(string $path, $stream, ?int $size = null): int { $storage = $this->getWrapperStorage(); if ($storage->instanceOfStorage(IWriteStreamStorage::class)) { /** @var IWriteStreamStorage $storage */ return $storage->writeStream($this->getUnjailedPath($path), $stream, $size); } else { $target = $this->fopen($path, 'w'); - [$count, $result] = \OC_Helper::streamCopy($stream, $target); + $count = Files::streamCopy($stream, $target); fclose($stream); fclose($target); return $count; } } - public function getDirectoryContent($directory): \Traversable { + public function getDirectoryContent(string $directory): \Traversable { return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory)); } } diff --git a/lib/private/Files/Storage/Wrapper/KnownMtime.php b/lib/private/Files/Storage/Wrapper/KnownMtime.php new file mode 100644 index 00000000000..657c6c9250c --- /dev/null +++ b/lib/private/Files/Storage/Wrapper/KnownMtime.php @@ -0,0 +1,146 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Files\Storage\Wrapper; + +use OCP\Cache\CappedMemoryCache; +use OCP\Files\Storage\IStorage; +use Psr\Clock\ClockInterface; + +/** + * Wrapper that overwrites the mtime return by stat/getMetaData if the returned value + * is lower than when we last modified the file. + * + * This is useful because some storage servers can return an outdated mtime right after writes + */ +class KnownMtime extends Wrapper { + private CappedMemoryCache $knowMtimes; + private ClockInterface $clock; + + public function __construct(array $parameters) { + parent::__construct($parameters); + $this->knowMtimes = new CappedMemoryCache(); + $this->clock = $parameters['clock']; + } + + public function file_put_contents(string $path, mixed $data): int|float|false { + $result = parent::file_put_contents($path, $data); + if ($result) { + $now = $this->clock->now()->getTimestamp(); + $this->knowMtimes->set($path, $this->clock->now()->getTimestamp()); + } + return $result; + } + + public function stat(string $path): array|false { + $stat = parent::stat($path); + if ($stat) { + $this->applyKnownMtime($path, $stat); + } + return $stat; + } + + public function getMetaData(string $path): ?array { + $stat = parent::getMetaData($path); + if ($stat) { + $this->applyKnownMtime($path, $stat); + } + return $stat; + } + + private function applyKnownMtime(string $path, array &$stat): void { + if (isset($stat['mtime'])) { + $knownMtime = $this->knowMtimes->get($path) ?? 0; + $stat['mtime'] = max($stat['mtime'], $knownMtime); + } + } + + public function filemtime(string $path): int|false { + $knownMtime = $this->knowMtimes->get($path) ?? 0; + return max(parent::filemtime($path), $knownMtime); + } + + public function mkdir(string $path): bool { + $result = parent::mkdir($path); + if ($result) { + $this->knowMtimes->set($path, $this->clock->now()->getTimestamp()); + } + return $result; + } + + public function rmdir(string $path): bool { + $result = parent::rmdir($path); + if ($result) { + $this->knowMtimes->set($path, $this->clock->now()->getTimestamp()); + } + return $result; + } + + public function unlink(string $path): bool { + $result = parent::unlink($path); + if ($result) { + $this->knowMtimes->set($path, $this->clock->now()->getTimestamp()); + } + return $result; + } + + public function rename(string $source, string $target): bool { + $result = parent::rename($source, $target); + if ($result) { + $this->knowMtimes->set($target, $this->clock->now()->getTimestamp()); + $this->knowMtimes->set($source, $this->clock->now()->getTimestamp()); + } + return $result; + } + + public function copy(string $source, string $target): bool { + $result = parent::copy($source, $target); + if ($result) { + $this->knowMtimes->set($target, $this->clock->now()->getTimestamp()); + } + return $result; + } + + public function fopen(string $path, string $mode) { + $result = parent::fopen($path, $mode); + if ($result && $mode === 'w') { + $this->knowMtimes->set($path, $this->clock->now()->getTimestamp()); + } + return $result; + } + + public function touch(string $path, ?int $mtime = null): bool { + $result = parent::touch($path, $mtime); + if ($result) { + $this->knowMtimes->set($path, $mtime ?? $this->clock->now()->getTimestamp()); + } + return $result; + } + + public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { + $result = parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + if ($result) { + $this->knowMtimes->set($targetInternalPath, $this->clock->now()->getTimestamp()); + } + return $result; + } + + public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { + $result = parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + if ($result) { + $this->knowMtimes->set($targetInternalPath, $this->clock->now()->getTimestamp()); + } + return $result; + } + + public function writeStream(string $path, $stream, ?int $size = null): int { + $result = parent::writeStream($path, $stream, $size); + if ($result) { + $this->knowMtimes->set($path, $this->clock->now()->getTimestamp()); + } + return $result; + } +} diff --git a/lib/private/Files/Storage/Wrapper/PermissionsMask.php b/lib/private/Files/Storage/Wrapper/PermissionsMask.php index e54d3bb721a..684040146ba 100644 --- a/lib/private/Files/Storage/Wrapper/PermissionsMask.php +++ b/lib/private/Files/Storage/Wrapper/PermissionsMask.php @@ -1,34 +1,15 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Stefan Weil <sw@weilnetz.de> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Files\Storage\Wrapper; use OC\Files\Cache\Wrapper\CachePermissionsMask; use OCP\Constants; +use OCP\Files\Storage\IStorage; /** * Mask the permissions of a storage @@ -44,75 +25,75 @@ class PermissionsMask extends Wrapper { private $mask; /** - * @param array $arguments ['storage' => $storage, 'mask' => $mask] + * @param array $parameters ['storage' => $storage, 'mask' => $mask] * * $storage: The storage the permissions mask should be applied on * $mask: The permission bits that should be kept, a combination of the \OCP\Constant::PERMISSION_ constants */ - public function __construct($arguments) { - parent::__construct($arguments); - $this->mask = $arguments['mask']; + public function __construct(array $parameters) { + parent::__construct($parameters); + $this->mask = $parameters['mask']; } - private function checkMask($permissions) { + private function checkMask(int $permissions): bool { return ($this->mask & $permissions) === $permissions; } - public function isUpdatable($path) { + public function isUpdatable(string $path): bool { return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::isUpdatable($path); } - public function isCreatable($path) { + public function isCreatable(string $path): bool { return $this->checkMask(Constants::PERMISSION_CREATE) and parent::isCreatable($path); } - public function isDeletable($path) { + public function isDeletable(string $path): bool { return $this->checkMask(Constants::PERMISSION_DELETE) and parent::isDeletable($path); } - public function isSharable($path) { + public function isSharable(string $path): bool { return $this->checkMask(Constants::PERMISSION_SHARE) and parent::isSharable($path); } - public function getPermissions($path) { + public function getPermissions(string $path): int { return $this->storage->getPermissions($path) & $this->mask; } - public function rename($path1, $path2) { + public function rename(string $source, string $target): bool { //This is a rename of the transfer file to the original file - if (dirname($path1) === dirname($path2) && strpos($path1, '.ocTransferId') > 0) { - return $this->checkMask(Constants::PERMISSION_CREATE) and parent::rename($path1, $path2); + if (dirname($source) === dirname($target) && strpos($source, '.ocTransferId') > 0) { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::rename($source, $target); } - return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::rename($path1, $path2); + return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::rename($source, $target); } - public function copy($path1, $path2) { - return $this->checkMask(Constants::PERMISSION_CREATE) and parent::copy($path1, $path2); + public function copy(string $source, string $target): bool { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::copy($source, $target); } - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; return $this->checkMask($permissions) and parent::touch($path, $mtime); } - public function mkdir($path) { + public function mkdir(string $path): bool { return $this->checkMask(Constants::PERMISSION_CREATE) and parent::mkdir($path); } - public function rmdir($path) { + public function rmdir(string $path): bool { return $this->checkMask(Constants::PERMISSION_DELETE) and parent::rmdir($path); } - public function unlink($path) { + public function unlink(string $path): bool { return $this->checkMask(Constants::PERMISSION_DELETE) and parent::unlink($path); } - public function file_put_contents($path, $data) { + public function file_put_contents(string $path, mixed $data): int|float|false { $permissions = $this->file_exists($path) ? Constants::PERMISSION_UPDATE : Constants::PERMISSION_CREATE; return $this->checkMask($permissions) ? parent::file_put_contents($path, $data) : false; } - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { if ($mode === 'r' or $mode === 'rb') { return parent::fopen($path, $mode); } else { @@ -121,14 +102,7 @@ class PermissionsMask extends Wrapper { } } - /** - * get a cache instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the cache - * @return \OC\Files\Cache\Cache - */ - public function getCache($path = '', $storage = null) { + public function getCache(string $path = '', ?IStorage $storage = null): \OCP\Files\Cache\ICache { if (!$storage) { $storage = $this; } @@ -136,26 +110,26 @@ class PermissionsMask extends Wrapper { return new CachePermissionsMask($sourceCache, $this->mask); } - public function getMetaData($path) { + public function getMetaData(string $path): ?array { $data = parent::getMetaData($path); if ($data && isset($data['permissions'])) { - $data['scan_permissions'] = isset($data['scan_permissions']) ? $data['scan_permissions'] : $data['permissions']; + $data['scan_permissions'] = $data['scan_permissions'] ?? $data['permissions']; $data['permissions'] &= $this->mask; } return $data; } - public function getScanner($path = '', $storage = null) { + public function getScanner(string $path = '', ?IStorage $storage = null): \OCP\Files\Cache\IScanner { if (!$storage) { $storage = $this->storage; } return parent::getScanner($path, $storage); } - public function getDirectoryContent($directory): \Traversable { + public function getDirectoryContent(string $directory): \Traversable { foreach ($this->getWrapperStorage()->getDirectoryContent($directory) as $data) { - $data['scan_permissions'] = isset($data['scan_permissions']) ? $data['scan_permissions'] : $data['permissions']; + $data['scan_permissions'] = $data['scan_permissions'] ?? $data['permissions']; $data['permissions'] &= $this->mask; yield $data; diff --git a/lib/private/Files/Storage/Wrapper/Quota.php b/lib/private/Files/Storage/Wrapper/Quota.php index 4cd0a5e0b4a..35a265f8c8e 100644 --- a/lib/private/Files/Storage/Wrapper/Quota.php +++ b/lib/private/Files/Storage/Wrapper/Quota.php @@ -1,177 +1,137 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author J0WI <J0WI@users.noreply.github.com> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Tigran Mkrtchyan <tigran.mkrtchyan@desy.de> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Files\Storage\Wrapper; use OC\Files\Filesystem; +use OC\SystemConfig; use OCP\Files\Cache\ICacheEntry; +use OCP\Files\FileInfo; use OCP\Files\Storage\IStorage; class Quota extends Wrapper { - - /** - * @var int $quota - */ - protected $quota; - - /** - * @var string $sizeRoot - */ - protected $sizeRoot; - - private $config; + /** @var callable|null */ + protected $quotaCallback; + /** @var int|float|null int on 64bits, float on 32bits for bigint */ + protected int|float|null $quota; + protected string $sizeRoot; + private SystemConfig $config; + private bool $quotaIncludeExternalStorage; + private bool $enabled = true; /** * @param array $parameters */ - public function __construct($parameters) { + public function __construct(array $parameters) { parent::__construct($parameters); - $this->quota = $parameters['quota']; - $this->sizeRoot = isset($parameters['root']) ? $parameters['root'] : ''; - $this->config = \OC::$server->getSystemConfig(); + $this->quota = $parameters['quota'] ?? null; + $this->quotaCallback = $parameters['quotaCallback'] ?? null; + $this->sizeRoot = $parameters['root'] ?? ''; + $this->quotaIncludeExternalStorage = $parameters['include_external_storage'] ?? false; } - /** - * @return int quota value - */ - public function getQuota() { + public function getQuota(): int|float { + if ($this->quota === null) { + $quotaCallback = $this->quotaCallback; + if ($quotaCallback === null) { + throw new \Exception('No quota or quota callback provider'); + } + $this->quota = $quotaCallback(); + } + return $this->quota; } - /** - * @param string $path - * @param \OC\Files\Storage\Storage $storage - */ - protected function getSize($path, $storage = null) { - if ($this->config->getValue('quota_include_external_storage', false)) { + private function hasQuota(): bool { + if (!$this->enabled) { + return false; + } + return $this->getQuota() !== FileInfo::SPACE_UNLIMITED; + } + + protected function getSize(string $path, ?IStorage $storage = null): int|float { + if ($this->quotaIncludeExternalStorage) { $rootInfo = Filesystem::getFileInfo('', 'ext'); if ($rootInfo) { return $rootInfo->getSize(true); } - return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; + return FileInfo::SPACE_NOT_COMPUTED; } else { - if (is_null($storage)) { - $cache = $this->getCache(); - } else { - $cache = $storage->getCache(); - } + $cache = is_null($storage) ? $this->getCache() : $storage->getCache(); $data = $cache->get($path); - if ($data instanceof ICacheEntry and isset($data['size'])) { + if ($data instanceof ICacheEntry && isset($data['size'])) { return $data['size']; } else { - return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; + return FileInfo::SPACE_NOT_COMPUTED; } } } - /** - * Get free space as limited by the quota - * - * @param string $path - * @return int|bool - */ - public function free_space($path) { - if ($this->quota < 0 || strpos($path, 'cache') === 0 || strpos($path, 'uploads') === 0) { + public function free_space(string $path): int|float|false { + if (!$this->hasQuota()) { + return $this->storage->free_space($path); + } + if ($this->getQuota() < 0 || str_starts_with($path, 'cache') || str_starts_with($path, 'uploads')) { return $this->storage->free_space($path); } else { $used = $this->getSize($this->sizeRoot); if ($used < 0) { - return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; + return FileInfo::SPACE_NOT_COMPUTED; } else { $free = $this->storage->free_space($path); - $quotaFree = max($this->quota - $used, 0); + $quotaFree = max($this->getQuota() - $used, 0); // if free space is known - if ($free >= 0) { - $free = min($free, $quotaFree); - } else { - $free = $quotaFree; - } + $free = $free >= 0 ? min($free, $quotaFree) : $quotaFree; return $free; } } } - /** - * see https://www.php.net/manual/en/function.file_put_contents.php - * - * @param string $path - * @param mixed $data - * @return int|false - */ - public function file_put_contents($path, $data) { + public function file_put_contents(string $path, mixed $data): int|float|false { + if (!$this->hasQuota()) { + return $this->storage->file_put_contents($path, $data); + } $free = $this->free_space($path); - if ($free < 0 or strlen($data) < $free) { + if ($free < 0 || strlen($data) < $free) { return $this->storage->file_put_contents($path, $data); } else { return false; } } - /** - * see https://www.php.net/manual/en/function.copy.php - * - * @param string $source - * @param string $target - * @return bool - */ - public function copy($source, $target) { + public function copy(string $source, string $target): bool { + if (!$this->hasQuota()) { + return $this->storage->copy($source, $target); + } $free = $this->free_space($target); - if ($free < 0 or $this->getSize($source) < $free) { + if ($free < 0 || $this->getSize($source) < $free) { return $this->storage->copy($source, $target); } else { return false; } } - /** - * see https://www.php.net/manual/en/function.fopen.php - * - * @param string $path - * @param string $mode - * @return resource|bool - */ - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { + if (!$this->hasQuota()) { + return $this->storage->fopen($path, $mode); + } $source = $this->storage->fopen($path, $mode); // don't apply quota for part files if (!$this->isPartFile($path)) { $free = $this->free_space($path); - if ($source && is_int($free) && $free >= 0 && $mode !== 'r' && $mode !== 'rb') { + if ($source && (is_int($free) || is_float($free)) && $free >= 0 && $mode !== 'r' && $mode !== 'rb') { // only apply quota for files, not metadata, trash or others if ($this->shouldApplyQuota($path)) { return \OC\Files\Stream\Quota::wrap($source, $free); } } } + return $source; } @@ -179,10 +139,9 @@ class Quota extends Wrapper { * Checks whether the given path is a part file * * @param string $path Path that may identify a .part file - * @return string File path without .part extension * @note this is needed for reusing keys */ - private function isPartFile($path) { + private function isPartFile(string $path): bool { $extension = pathinfo($path, PATHINFO_EXTENSION); return ($extension === 'part'); @@ -191,55 +150,59 @@ class Quota extends Wrapper { /** * Only apply quota for files, not metadata, trash or others */ - private function shouldApplyQuota(string $path): bool { - return strpos(ltrim($path, '/'), 'files/') === 0; + protected function shouldApplyQuota(string $path): bool { + return str_starts_with(ltrim($path, '/'), 'files/'); } - /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { + if (!$this->hasQuota()) { + return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } $free = $this->free_space($targetInternalPath); - if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) { + if ($free < 0 || $this->getSize($sourceInternalPath, $sourceStorage) < $free) { return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); } else { return false; } } - /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { + if (!$this->hasQuota()) { + return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + } $free = $this->free_space($targetInternalPath); - if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) { + if ($free < 0 || $this->getSize($sourceInternalPath, $sourceStorage) < $free) { return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); } else { return false; } } - public function mkdir($path) { + public function mkdir(string $path): bool { + if (!$this->hasQuota()) { + return $this->storage->mkdir($path); + } $free = $this->free_space($path); - if ($this->shouldApplyQuota($path) && $free === 0.0) { + if ($this->shouldApplyQuota($path) && $free == 0) { return false; } return parent::mkdir($path); } - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { + if (!$this->hasQuota()) { + return $this->storage->touch($path, $mtime); + } $free = $this->free_space($path); - if ($free === 0.0) { + if ($free == 0) { return false; } return parent::touch($path, $mtime); } + + public function enableQuota(bool $enabled): void { + $this->enabled = $enabled; + } } diff --git a/lib/private/Files/Storage/Wrapper/Wrapper.php b/lib/private/Files/Storage/Wrapper/Wrapper.php index 6bc66bf9c89..7af11dd5ef7 100644 --- a/lib/private/Files/Storage/Wrapper/Wrapper.php +++ b/lib/private/Files/Storage/Wrapper/Wrapper.php @@ -1,41 +1,26 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author J0WI <J0WI@users.noreply.github.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Tigran Mkrtchyan <tigran.mkrtchyan@desy.de> - * @author Vincent Petry <vincent@nextcloud.com> - * @author Vinicius Cubas Brand <vinicius@eita.org.br> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OC\Files\Storage\Wrapper; -use OCP\Files\InvalidPathException; +use OC\Files\Storage\FailedStorage; +use OC\Files\Storage\Storage; +use OCP\Files; +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\IPropagator; +use OCP\Files\Cache\IScanner; +use OCP\Files\Cache\IUpdater; +use OCP\Files\Cache\IWatcher; use OCP\Files\Storage\ILockingStorage; use OCP\Files\Storage\IStorage; use OCP\Files\Storage\IWriteStreamStorage; use OCP\Lock\ILockingProvider; +use OCP\Server; +use Psr\Log\LoggerInterface; class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStreamStorage { /** @@ -52,444 +37,192 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea /** * @param array $parameters */ - public function __construct($parameters) { + public function __construct(array $parameters) { $this->storage = $parameters['storage']; } - /** - * @return \OC\Files\Storage\Storage - */ - public function getWrapperStorage() { + public function getWrapperStorage(): Storage { + if (!$this->storage) { + $message = 'storage wrapper ' . get_class($this) . " doesn't have a wrapped storage set"; + $logger = Server::get(LoggerInterface::class); + $logger->error($message); + $this->storage = new FailedStorage(['exception' => new \Exception($message)]); + } return $this->storage; } - /** - * Get the identifier for the storage, - * the returned id should be the same for every storage object that is created with the same parameters - * and two storage objects with the same id should refer to two storages that display the same files. - * - * @return string - */ - public function getId() { + public function getId(): string { return $this->getWrapperStorage()->getId(); } - /** - * see https://www.php.net/manual/en/function.mkdir.php - * - * @param string $path - * @return bool - */ - public function mkdir($path) { + public function mkdir(string $path): bool { return $this->getWrapperStorage()->mkdir($path); } - /** - * see https://www.php.net/manual/en/function.rmdir.php - * - * @param string $path - * @return bool - */ - public function rmdir($path) { + public function rmdir(string $path): bool { return $this->getWrapperStorage()->rmdir($path); } - /** - * see https://www.php.net/manual/en/function.opendir.php - * - * @param string $path - * @return resource|bool - */ - public function opendir($path) { + public function opendir(string $path) { return $this->getWrapperStorage()->opendir($path); } - /** - * see https://www.php.net/manual/en/function.is_dir.php - * - * @param string $path - * @return bool - */ - public function is_dir($path) { + public function is_dir(string $path): bool { return $this->getWrapperStorage()->is_dir($path); } - /** - * see https://www.php.net/manual/en/function.is_file.php - * - * @param string $path - * @return bool - */ - public function is_file($path) { + public function is_file(string $path): bool { return $this->getWrapperStorage()->is_file($path); } - /** - * see https://www.php.net/manual/en/function.stat.php - * only the following keys are required in the result: size and mtime - * - * @param string $path - * @return array|bool - */ - public function stat($path) { + public function stat(string $path): array|false { return $this->getWrapperStorage()->stat($path); } - /** - * see https://www.php.net/manual/en/function.filetype.php - * - * @param string $path - * @return string|bool - */ - public function filetype($path) { + public function filetype(string $path): string|false { return $this->getWrapperStorage()->filetype($path); } - /** - * see https://www.php.net/manual/en/function.filesize.php - * The result for filesize when called on a folder is required to be 0 - * - * @param string $path - * @return int|bool - */ - public function filesize($path) { + public function filesize(string $path): int|float|false { return $this->getWrapperStorage()->filesize($path); } - /** - * check if a file can be created in $path - * - * @param string $path - * @return bool - */ - public function isCreatable($path) { + public function isCreatable(string $path): bool { return $this->getWrapperStorage()->isCreatable($path); } - /** - * check if a file can be read - * - * @param string $path - * @return bool - */ - public function isReadable($path) { + public function isReadable(string $path): bool { return $this->getWrapperStorage()->isReadable($path); } - /** - * check if a file can be written to - * - * @param string $path - * @return bool - */ - public function isUpdatable($path) { + public function isUpdatable(string $path): bool { return $this->getWrapperStorage()->isUpdatable($path); } - /** - * check if a file can be deleted - * - * @param string $path - * @return bool - */ - public function isDeletable($path) { + public function isDeletable(string $path): bool { return $this->getWrapperStorage()->isDeletable($path); } - /** - * check if a file can be shared - * - * @param string $path - * @return bool - */ - public function isSharable($path) { + public function isSharable(string $path): bool { return $this->getWrapperStorage()->isSharable($path); } - /** - * get the full permissions of a path. - * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php - * - * @param string $path - * @return int - */ - public function getPermissions($path) { + public function getPermissions(string $path): int { return $this->getWrapperStorage()->getPermissions($path); } - /** - * see https://www.php.net/manual/en/function.file_exists.php - * - * @param string $path - * @return bool - */ - public function file_exists($path) { + public function file_exists(string $path): bool { return $this->getWrapperStorage()->file_exists($path); } - /** - * see https://www.php.net/manual/en/function.filemtime.php - * - * @param string $path - * @return int|bool - */ - public function filemtime($path) { + public function filemtime(string $path): int|false { return $this->getWrapperStorage()->filemtime($path); } - /** - * see https://www.php.net/manual/en/function.file_get_contents.php - * - * @param string $path - * @return string|bool - */ - public function file_get_contents($path) { + public function file_get_contents(string $path): string|false { return $this->getWrapperStorage()->file_get_contents($path); } - /** - * see https://www.php.net/manual/en/function.file_put_contents.php - * - * @param string $path - * @param mixed $data - * @return int|false - */ - public function file_put_contents($path, $data) { + public function file_put_contents(string $path, mixed $data): int|float|false { return $this->getWrapperStorage()->file_put_contents($path, $data); } - /** - * see https://www.php.net/manual/en/function.unlink.php - * - * @param string $path - * @return bool - */ - public function unlink($path) { + public function unlink(string $path): bool { return $this->getWrapperStorage()->unlink($path); } - /** - * see https://www.php.net/manual/en/function.rename.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function rename($path1, $path2) { - return $this->getWrapperStorage()->rename($path1, $path2); + public function rename(string $source, string $target): bool { + return $this->getWrapperStorage()->rename($source, $target); } - /** - * see https://www.php.net/manual/en/function.copy.php - * - * @param string $path1 - * @param string $path2 - * @return bool - */ - public function copy($path1, $path2) { - return $this->getWrapperStorage()->copy($path1, $path2); + public function copy(string $source, string $target): bool { + return $this->getWrapperStorage()->copy($source, $target); } - /** - * see https://www.php.net/manual/en/function.fopen.php - * - * @param string $path - * @param string $mode - * @return resource|bool - */ - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { return $this->getWrapperStorage()->fopen($path, $mode); } - /** - * get the mimetype for a file or folder - * The mimetype for a folder is required to be "httpd/unix-directory" - * - * @param string $path - * @return string|bool - */ - public function getMimeType($path) { + public function getMimeType(string $path): string|false { return $this->getWrapperStorage()->getMimeType($path); } - /** - * see https://www.php.net/manual/en/function.hash.php - * - * @param string $type - * @param string $path - * @param bool $raw - * @return string|bool - */ - public function hash($type, $path, $raw = false) { + public function hash(string $type, string $path, bool $raw = false): string|false { return $this->getWrapperStorage()->hash($type, $path, $raw); } - /** - * see https://www.php.net/manual/en/function.free_space.php - * - * @param string $path - * @return int|bool - */ - public function free_space($path) { + public function free_space(string $path): int|float|false { return $this->getWrapperStorage()->free_space($path); } - /** - * search for occurrences of $query in file names - * - * @param string $query - * @return array|bool - */ - public function search($query) { - return $this->getWrapperStorage()->search($query); - } - - /** - * see https://www.php.net/manual/en/function.touch.php - * If the backend does not support the operation, false should be returned - * - * @param string $path - * @param int $mtime - * @return bool - */ - public function touch($path, $mtime = null) { + public function touch(string $path, ?int $mtime = null): bool { return $this->getWrapperStorage()->touch($path, $mtime); } - /** - * get the path to a local version of the file. - * The local version of the file can be temporary and doesn't have to be persistent across requests - * - * @param string $path - * @return string|bool - */ - public function getLocalFile($path) { + public function getLocalFile(string $path): string|false { return $this->getWrapperStorage()->getLocalFile($path); } - /** - * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @return bool - * - * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. - * returning true for other changes in the folder is optional - */ - public function hasUpdated($path, $time) { + public function hasUpdated(string $path, int $time): bool { return $this->getWrapperStorage()->hasUpdated($path, $time); } - /** - * get a cache instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage|null (optional) the storage to pass to the cache - * @return \OC\Files\Cache\Cache - */ - public function getCache($path = '', $storage = null) { + public function getCache(string $path = '', ?IStorage $storage = null): ICache { if (!$storage) { $storage = $this; } return $this->getWrapperStorage()->getCache($path, $storage); } - /** - * get a scanner instance for the storage - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner - * @return \OC\Files\Cache\Scanner - */ - public function getScanner($path = '', $storage = null) { + public function getScanner(string $path = '', ?IStorage $storage = null): IScanner { if (!$storage) { $storage = $this; } return $this->getWrapperStorage()->getScanner($path, $storage); } - - /** - * get the user id of the owner of a file or folder - * - * @param string $path - * @return string - */ - public function getOwner($path) { + public function getOwner(string $path): string|false { return $this->getWrapperStorage()->getOwner($path); } - /** - * get a watcher instance for the cache - * - * @param string $path - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher - * @return \OC\Files\Cache\Watcher - */ - public function getWatcher($path = '', $storage = null) { + public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher { if (!$storage) { $storage = $this; } return $this->getWrapperStorage()->getWatcher($path, $storage); } - public function getPropagator($storage = null) { + public function getPropagator(?IStorage $storage = null): IPropagator { if (!$storage) { $storage = $this; } return $this->getWrapperStorage()->getPropagator($storage); } - public function getUpdater($storage = null) { + public function getUpdater(?IStorage $storage = null): IUpdater { if (!$storage) { $storage = $this; } return $this->getWrapperStorage()->getUpdater($storage); } - /** - * @return \OC\Files\Cache\Storage - */ - public function getStorageCache() { + public function getStorageCache(): \OC\Files\Cache\Storage { return $this->getWrapperStorage()->getStorageCache(); } - /** - * get the ETag for a file or folder - * - * @param string $path - * @return string|bool - */ - public function getETag($path) { + public function getETag(string $path): string|false { return $this->getWrapperStorage()->getETag($path); } - /** - * Returns true - * - * @return true - */ - public function test() { + public function test(): bool { return $this->getWrapperStorage()->test(); } - /** - * Returns the wrapped storage's value for isLocal() - * - * @return bool wrapped storage's isLocal() value - */ - public function isLocal() { + public function isLocal(): bool { return $this->getWrapperStorage()->isLocal(); } - /** - * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class - * - * @param class-string<IStorage> $class - * @return bool - */ - public function instanceOfStorage($class) { + public function instanceOfStorage(string $class): bool { if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') { // FIXME Temporary fix to keep existing checks working $class = '\OCA\Files_Sharing\SharedStorage'; @@ -502,7 +235,7 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea * @psalm-param class-string<T> $class * @psalm-return T|null */ - public function getInstanceOfStorage(string $class) { + public function getInstanceOfStorage(string $class): ?IStorage { $storage = $this; while ($storage instanceof Wrapper) { if ($storage instanceof $class) { @@ -519,61 +252,29 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea /** * Pass any methods custom to specific storage implementations to the wrapped storage * - * @param string $method - * @param array $args * @return mixed */ - public function __call($method, $args) { + public function __call(string $method, array $args) { return call_user_func_array([$this->getWrapperStorage(), $method], $args); } - /** - * A custom storage implementation can return an url for direct download of a give file. - * - * For now the returned array can hold the parameter url - in future more attributes might follow. - * - * @param string $path - * @return array|bool - */ - public function getDirectDownload($path) { + public function getDirectDownload(string $path): array|false { return $this->getWrapperStorage()->getDirectDownload($path); } - /** - * Get availability of the storage - * - * @return array [ available, last_checked ] - */ - public function getAvailability() { + public function getAvailability(): array { return $this->getWrapperStorage()->getAvailability(); } - /** - * Set availability of the storage - * - * @param bool $isAvailable - */ - public function setAvailability($isAvailable) { + public function setAvailability(bool $isAvailable): void { $this->getWrapperStorage()->setAvailability($isAvailable); } - /** - * @param string $path the path of the target folder - * @param string $fileName the name of the file itself - * @return void - * @throws InvalidPathException - */ - public function verifyPath($path, $fileName) { + public function verifyPath(string $path, string $fileName): void { $this->getWrapperStorage()->verifyPath($path, $fileName); } - /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { if ($sourceStorage === $this) { return $this->copy($sourceInternalPath, $targetInternalPath); } @@ -581,13 +282,7 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); } - /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - */ - public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool { if ($sourceStorage === $this) { return $this->rename($sourceInternalPath, $targetInternalPath); } @@ -595,66 +290,62 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStrea return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); } - public function getMetaData($path) { + public function getMetaData(string $path): ?array { return $this->getWrapperStorage()->getMetaData($path); } - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - */ - public function acquireLock($path, $type, ILockingProvider $provider) { + public function acquireLock(string $path, int $type, ILockingProvider $provider): void { if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { $this->getWrapperStorage()->acquireLock($path, $type, $provider); } } - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function releaseLock($path, $type, ILockingProvider $provider) { + public function releaseLock(string $path, int $type, ILockingProvider $provider): void { if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { $this->getWrapperStorage()->releaseLock($path, $type, $provider); } } - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function changeLock($path, $type, ILockingProvider $provider) { + public function changeLock(string $path, int $type, ILockingProvider $provider): void { if ($this->getWrapperStorage()->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { $this->getWrapperStorage()->changeLock($path, $type, $provider); } } - /** - * @return bool - */ - public function needsPartFile() { + public function needsPartFile(): bool { return $this->getWrapperStorage()->needsPartFile(); } - public function writeStream(string $path, $stream, int $size = null): int { + public function writeStream(string $path, $stream, ?int $size = null): int { $storage = $this->getWrapperStorage(); if ($storage->instanceOfStorage(IWriteStreamStorage::class)) { /** @var IWriteStreamStorage $storage */ return $storage->writeStream($path, $stream, $size); } else { $target = $this->fopen($path, 'w'); - [$count, $result] = \OC_Helper::streamCopy($stream, $target); + $count = Files::streamCopy($stream, $target); fclose($stream); fclose($target); return $count; } } - public function getDirectoryContent($directory): \Traversable { + public function getDirectoryContent(string $directory): \Traversable { return $this->getWrapperStorage()->getDirectoryContent($directory); } + + public function isWrapperOf(IStorage $storage): bool { + $wrapped = $this->getWrapperStorage(); + if ($wrapped === $storage) { + return true; + } + if ($wrapped instanceof Wrapper) { + return $wrapped->isWrapperOf($storage); + } + return false; + } + + public function setOwner(?string $user): void { + $this->getWrapperStorage()->setOwner($user); + } } |