aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_sharing/lib/SharedStorage.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files_sharing/lib/SharedStorage.php')
-rw-r--r--apps/files_sharing/lib/SharedStorage.php326
1 files changed, 162 insertions, 164 deletions
diff --git a/apps/files_sharing/lib/SharedStorage.php b/apps/files_sharing/lib/SharedStorage.php
index 2c1ddf9af4a..e310c5f3138 100644
--- a/apps/files_sharing/lib/SharedStorage.php
+++ b/apps/files_sharing/lib/SharedStorage.php
@@ -1,72 +1,59 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Björn Schießle <bjoern@schiessle.org>
- * @author J0WI <J0WI@users.noreply.github.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Michael Gapczynski <GapczynskiM@gmail.com>
- * @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 scambra <sergio@entrecables.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @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 OCA\Files_Sharing;
+use OC\Files\Cache\CacheDependencies;
+use OC\Files\Cache\CacheEntry;
use OC\Files\Cache\FailedCache;
use OC\Files\Cache\NullWatcher;
-use OC\Files\Cache\Watcher;
use OC\Files\ObjectStore\HomeObjectStoreStorage;
use OC\Files\Storage\Common;
-use OC\Files\Storage\Home;
-use OC\User\DisplayNameCache;
-use OCP\Files\Folder;
-use OCP\Files\IHomeStorage;
-use OCP\Files\Node;
use OC\Files\Storage\FailedStorage;
+use OC\Files\Storage\Home;
+use OC\Files\Storage\Storage;
+use OC\Files\Storage\Wrapper\Jail;
use OC\Files\Storage\Wrapper\PermissionsMask;
+use OC\Files\Storage\Wrapper\Wrapper;
+use OC\Files\View;
+use OC\Share\Share;
use OC\User\NoUserException;
-use OCA\Files_External\Config\ExternalMountPoint;
+use OCA\Files_Sharing\ISharedStorage as LegacyISharedStorage;
use OCP\Constants;
+use OCP\Files\Cache\ICache;
use OCP\Files\Cache\ICacheEntry;
+use OCP\Files\Cache\IScanner;
+use OCP\Files\Cache\IWatcher;
+use OCP\Files\Folder;
+use OCP\Files\IHomeStorage;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IDisableEncryptionStorage;
+use OCP\Files\Storage\ILockingStorage;
+use OCP\Files\Storage\ISharedStorage;
use OCP\Files\Storage\IStorage;
use OCP\Lock\ILockingProvider;
+use OCP\Server;
use OCP\Share\IShare;
+use OCP\Util;
+use Psr\Log\LoggerInterface;
/**
* Convert target path to source path and pass the function call to the correct storage provider
*/
-class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage, IDisableEncryptionStorage {
- /** @var \OCP\Share\IShare */
+class SharedStorage extends Jail implements LegacyISharedStorage, ISharedStorage, IDisableEncryptionStorage {
+ /** @var IShare */
private $superShare;
- /** @var \OCP\Share\IShare[] */
+ /** @var IShare[] */
private $groupedShares;
/**
- * @var \OC\Files\View
+ * @var View
*/
private $ownerView;
@@ -80,12 +67,9 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
/** @var string */
private $user;
- /**
- * @var \OCP\ILogger
- */
- private $logger;
+ private LoggerInterface $logger;
- /** @var IStorage */
+ /** @var IStorage */
private $nonMaskedStorage;
private array $mountOptions = [];
@@ -98,16 +82,24 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
private string $sourcePath = '';
- public function __construct($arguments) {
- $this->ownerView = $arguments['ownerView'];
- $this->logger = \OC::$server->getLogger();
+ private static int $initDepth = 0;
+
+ /**
+ * @psalm-suppress NonInvariantDocblockPropertyType
+ * @var ?Storage $storage
+ */
+ protected $storage;
+
+ public function __construct(array $parameters) {
+ $this->ownerView = $parameters['ownerView'];
+ $this->logger = Server::get(LoggerInterface::class);
- $this->superShare = $arguments['superShare'];
- $this->groupedShares = $arguments['groupedShares'];
+ $this->superShare = $parameters['superShare'];
+ $this->groupedShares = $parameters['groupedShares'];
- $this->user = $arguments['user'];
- if (isset($arguments['sharingDisabledForUser'])) {
- $this->sharingDisabledForUser = $arguments['sharingDisabledForUser'];
+ $this->user = $parameters['user'];
+ if (isset($parameters['sharingDisabledForUser'])) {
+ $this->sharingDisabledForUser = $parameters['sharingDisabledForUser'];
} else {
$this->sharingDisabledForUser = false;
}
@@ -133,27 +125,60 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
return $this->sourceRootInfo;
}
+ /**
+ * @psalm-assert Storage $this->storage
+ */
private function init() {
if ($this->initialized) {
+ if (!$this->storage) {
+ // marked as initialized but no storage set
+ // this is probably because some code path has caused recursion during the share setup
+ // we setup a "failed storage" so `getWrapperStorage` doesn't return null.
+ // If the share setup completes after this the "failed storage" will be overwritten by the correct one
+ $this->logger->warning('Possible share setup recursion detected');
+ $this->storage = new FailedStorage(['exception' => new \Exception('Possible share setup recursion detected')]);
+ $this->cache = new FailedCache();
+ $this->rootPath = '';
+ }
return;
}
+
$this->initialized = true;
+ self::$initDepth++;
+
try {
+ if (self::$initDepth > 10) {
+ throw new \Exception('Maximum share depth reached');
+ }
+
/** @var IRootFolder $rootFolder */
- $rootFolder = \OC::$server->get(IRootFolder::class);
+ $rootFolder = Server::get(IRootFolder::class);
$this->ownerUserFolder = $rootFolder->getUserFolder($this->superShare->getShareOwner());
$sourceId = $this->superShare->getNodeId();
$ownerNodes = $this->ownerUserFolder->getById($sourceId);
- /** @var Node|false $ownerNode */
- $ownerNode = current($ownerNodes);
- if (!$ownerNode) {
+
+ if (count($ownerNodes) === 0) {
$this->storage = new FailedStorage(['exception' => new NotFoundException("File by id $sourceId not found")]);
$this->cache = new FailedCache();
$this->rootPath = '';
} else {
- $this->nonMaskedStorage = $ownerNode->getStorage();
- $this->sourcePath = $ownerNode->getPath();
- $this->rootPath = $ownerNode->getInternalPath();
+ foreach ($ownerNodes as $ownerNode) {
+ $nonMaskedStorage = $ownerNode->getStorage();
+
+ // check if potential source node would lead to a recursive share setup
+ if ($nonMaskedStorage instanceof Wrapper && $nonMaskedStorage->isWrapperOf($this)) {
+ continue;
+ }
+ $this->nonMaskedStorage = $nonMaskedStorage;
+ $this->sourcePath = $ownerNode->getPath();
+ $this->rootPath = $ownerNode->getInternalPath();
+ $this->cache = null;
+ break;
+ }
+ if (!$this->nonMaskedStorage) {
+ // all potential source nodes would have been recursive
+ throw new \Exception('recursive share detected');
+ }
$this->storage = new PermissionsMask([
'storage' => $this->nonMaskedStorage,
'mask' => $this->superShare->getPermissions(),
@@ -173,18 +198,16 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
$this->storage = new FailedStorage(['exception' => $e]);
$this->cache = new FailedCache();
$this->rootPath = '';
- $this->logger->logException($e);
+ $this->logger->error($e->getMessage(), ['exception' => $e]);
}
if (!$this->nonMaskedStorage) {
$this->nonMaskedStorage = $this->storage;
}
+ self::$initDepth--;
}
- /**
- * @inheritdoc
- */
- public function instanceOfStorage($class): bool {
+ public function instanceOfStorage(string $class): bool {
if ($class === '\OC\Files\Storage\Common' || $class == Common::class) {
return true;
}
@@ -212,44 +235,33 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
return $this->getSourceRootInfo() && ($this->getSourceRootInfo()->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
}
- /**
- * get id of the mount point
- *
- * @return string
- */
public function getId(): string {
return 'shared::' . $this->getMountPoint();
}
- /**
- * Get the permissions granted for a shared file
- *
- * @param string $target Shared target file path
- * @return int CRUDS permissions granted
- */
- public function getPermissions($target = ''): int {
+ public function getPermissions(string $path = ''): int {
if (!$this->isValid()) {
return 0;
}
- $permissions = parent::getPermissions($target) & $this->superShare->getPermissions();
+ $permissions = parent::getPermissions($path) & $this->superShare->getPermissions();
// part files and the mount point always have delete permissions
- if ($target === '' || pathinfo($target, PATHINFO_EXTENSION) === 'part') {
- $permissions |= \OCP\Constants::PERMISSION_DELETE;
+ if ($path === '' || pathinfo($path, PATHINFO_EXTENSION) === 'part') {
+ $permissions |= Constants::PERMISSION_DELETE;
}
if ($this->sharingDisabledForUser) {
- $permissions &= ~\OCP\Constants::PERMISSION_SHARE;
+ $permissions &= ~Constants::PERMISSION_SHARE;
}
return $permissions;
}
- public function isCreatable($path): bool {
- return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE);
+ public function isCreatable(string $path): bool {
+ return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
}
- public function isReadable($path): bool {
+ public function isReadable(string $path): bool {
if (!$this->isValid()) {
return false;
}
@@ -262,22 +274,22 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
return $storage->isReadable($internalPath);
}
- public function isUpdatable($path): bool {
- return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE);
+ public function isUpdatable(string $path): bool {
+ return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
}
- public function isDeletable($path): bool {
- return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE);
+ public function isDeletable(string $path): bool {
+ return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
}
- public function isSharable($path): bool {
- if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
+ public function isSharable(string $path): bool {
+ if (Util::isSharingDisabledForUser() || !Share::isResharingAllowed()) {
return false;
}
- return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
+ return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
}
- public function fopen($path, $mode) {
+ public function fopen(string $path, string $mode) {
$source = $this->getUnjailedPath($path);
switch ($mode) {
case 'r+':
@@ -325,18 +337,11 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
'source' => $source,
'mode' => $mode,
];
- \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
+ Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
return $this->nonMaskedStorage->fopen($this->getUnjailedPath($path), $mode);
}
- /**
- * see https://www.php.net/manual/en/function.rename.php
- *
- * @param string $source
- * @param string $target
- * @return bool
- */
- public function rename($source, $target): bool {
+ public function rename(string $source, string $target): bool {
$this->init();
$isPartFile = pathinfo($source, PATHINFO_EXTENSION) === 'part';
$targetExists = $this->file_exists($target);
@@ -364,10 +369,7 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
return $this->superShare->getTarget();
}
- /**
- * @param string $path
- */
- public function setMountPoint($path): void {
+ public function setMountPoint(string $path): void {
$this->superShare->setTarget($path);
foreach ($this->groupedShares as $share) {
@@ -384,9 +386,6 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
return $this->superShare->getShareOwner();
}
- /**
- * @return \OCP\Share\IShare
- */
public function getShare(): IShare {
return $this->superShare;
}
@@ -400,7 +399,7 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
return $this->superShare->getNodeType();
}
- public function getCache($path = '', $storage = null) {
+ public function getCache(string $path = '', ?IStorage $storage = null): ICache {
if ($this->cache) {
return $this->cache;
}
@@ -412,41 +411,50 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
return new FailedCache();
}
- $this->cache = new \OCA\Files_Sharing\Cache(
+ $this->cache = new Cache(
$storage,
$sourceRoot,
- \OC::$server->get(DisplayNameCache::class)
+ Server::get(CacheDependencies::class),
+ $this->getShare()
);
return $this->cache;
}
- public function getScanner($path = '', $storage = null) {
+ public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
if (!$storage) {
$storage = $this;
}
- return new \OCA\Files_Sharing\Scanner($storage);
+ return new Scanner($storage);
}
- public function getOwner($path): string {
+ public function getOwner(string $path): string|false {
return $this->superShare->getShareOwner();
}
- public function getWatcher($path = '', $storage = null): Watcher {
- $mountManager = \OC::$server->getMountManager();
+ public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher {
+ if ($this->watcher) {
+ return $this->watcher;
+ }
// Get node information
$node = $this->getShare()->getNodeCacheEntry();
- if ($node) {
- $mount = $mountManager->findByNumericId($node->getStorageId());
- // If the share is originating from an external storage
- if (count($mount) > 0 && $mount[0] instanceof ExternalMountPoint) {
- // Propagate original storage scan
- return parent::getWatcher($path, $storage);
+ if ($node instanceof CacheEntry) {
+ $storageId = $node->getData()['storage_string_id'] ?? null;
+ // for shares from the home storage we can rely on the home storage to keep itself up to date
+ // for other storages we need use the proper watcher
+ if ($storageId !== null && !(str_starts_with($storageId, 'home::') || str_starts_with($storageId, 'object::user'))) {
+ $cache = $this->getCache();
+ $this->watcher = parent::getWatcher($path, $storage);
+ if ($cache instanceof Cache) {
+ $this->watcher->onUpdate($cache->markRootChanged(...));
+ }
+ return $this->watcher;
}
}
// cache updating is handled by the share source
- return new NullWatcher();
+ $this->watcher = new NullWatcher();
+ return $this->watcher;
}
/**
@@ -456,19 +464,13 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
*/
public function unshareStorage(): bool {
foreach ($this->groupedShares as $share) {
- \OC::$server->getShareManager()->deleteFromSelf($share, $this->user);
+ Server::get(\OCP\Share\IManager::class)->deleteFromSelf($share, $this->user);
}
return true;
}
- /**
- * @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) {
- /** @var \OCP\Files\Storage $targetStorage */
+ public function acquireLock(string $path, int $type, ILockingProvider $provider): void {
+ /** @var ILockingStorage $targetStorage */
[$targetStorage, $targetInternalPath] = $this->resolvePath($path);
$targetStorage->acquireLock($targetInternalPath, $type, $provider);
// lock the parent folders of the owner when locking the share as recipient
@@ -478,13 +480,8 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
}
}
- /**
- * @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) {
- /** @var \OCP\Files\Storage $targetStorage */
+ public function releaseLock(string $path, int $type, ILockingProvider $provider): void {
+ /** @var ILockingStorage $targetStorage */
[$targetStorage, $targetInternalPath] = $this->resolvePath($path);
$targetStorage->releaseLock($targetInternalPath, $type, $provider);
// unlock the parent folders of the owner when unlocking the share as recipient
@@ -494,21 +491,13 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
}
}
- /**
- * @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) {
- /** @var \OCP\Files\Storage $targetStorage */
+ public function changeLock(string $path, int $type, ILockingProvider $provider): void {
+ /** @var ILockingStorage $targetStorage */
[$targetStorage, $targetInternalPath] = $this->resolvePath($path);
$targetStorage->changeLock($targetInternalPath, $type, $provider);
}
- /**
- * @return array [ available, last_checked ]
- */
- public function getAvailability() {
+ public function getAvailability(): array {
// shares do not participate in availability logic
return [
'available' => true,
@@ -516,10 +505,7 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
];
}
- /**
- * @param bool $available
- */
- public function setAvailability($available) {
+ public function setAvailability(bool $isAvailable): void {
// shares do not participate in availability logic
}
@@ -528,39 +514,51 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto
return $this->nonMaskedStorage;
}
- public function getWrapperStorage() {
+ public function getWrapperStorage(): Storage {
$this->init();
+
+ /**
+ * @psalm-suppress DocblockTypeContradiction
+ */
+ if (!$this->storage) {
+ $message = 'no storage set after init for share ' . $this->getShareId();
+ $this->logger->error($message);
+ $this->storage = new FailedStorage(['exception' => new \Exception($message)]);
+ }
+
return $this->storage;
}
- public function file_get_contents($path) {
+ public function file_get_contents(string $path): string|false {
$info = [
'target' => $this->getMountPoint() . '/' . $path,
'source' => $this->getUnjailedPath($path),
];
- \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
+ Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
return parent::file_get_contents($path);
}
- public function file_put_contents($path, $data) {
+ public function file_put_contents(string $path, mixed $data): int|float|false {
$info = [
'target' => $this->getMountPoint() . '/' . $path,
'source' => $this->getUnjailedPath($path),
];
- \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
+ Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
return parent::file_put_contents($path, $data);
}
- /**
- * @return void
- */
- public function setMountOptions(array $options) {
+ public function setMountOptions(array $options): void {
/* Note: This value is never read */
$this->mountOptions = $options;
}
- public function getUnjailedPath($path) {
+ public function getUnjailedPath(string $path): string {
$this->init();
return parent::getUnjailedPath($path);
}
+
+ public function getDirectDownload(string $path): array|false {
+ // disable direct download for shares
+ return [];
+ }
}