Completed
Push — master ( 0274a4...fb0b56 )
by
unknown
47:02 queued 06:53
created
apps/files_sharing/lib/SharedStorage.php 2 patches
Indentation   +516 added lines, -516 removed lines patch added patch discarded remove patch
@@ -46,520 +46,520 @@
 block discarded – undo
46 46
  * Convert target path to source path and pass the function call to the correct storage provider
47 47
  */
48 48
 class SharedStorage extends Jail implements LegacyISharedStorage, ISharedStorage, IDisableEncryptionStorage {
49
-	/** @var IShare */
50
-	private $superShare;
51
-
52
-	/** @var IShare[] */
53
-	private $groupedShares;
54
-
55
-	/**
56
-	 * @var View
57
-	 */
58
-	private $ownerView;
59
-
60
-	private $initialized = false;
61
-
62
-	/**
63
-	 * @var ICacheEntry
64
-	 */
65
-	private $sourceRootInfo;
66
-
67
-	/** @var string */
68
-	private $user;
69
-
70
-	private LoggerInterface $logger;
71
-
72
-	/** @var IStorage */
73
-	private $nonMaskedStorage;
74
-
75
-	private array $mountOptions = [];
76
-
77
-	/** @var boolean */
78
-	private $sharingDisabledForUser;
79
-
80
-	/** @var ?Folder $ownerUserFolder */
81
-	private $ownerUserFolder = null;
82
-
83
-	private string $sourcePath = '';
84
-
85
-	private static int $initDepth = 0;
86
-
87
-	/**
88
-	 * @psalm-suppress NonInvariantDocblockPropertyType
89
-	 * @var ?Storage $storage
90
-	 */
91
-	protected $storage;
92
-
93
-	public function __construct(array $parameters) {
94
-		$this->ownerView = $parameters['ownerView'];
95
-		$this->logger = Server::get(LoggerInterface::class);
96
-
97
-		$this->superShare = $parameters['superShare'];
98
-		$this->groupedShares = $parameters['groupedShares'];
99
-
100
-		$this->user = $parameters['user'];
101
-		if (isset($parameters['sharingDisabledForUser'])) {
102
-			$this->sharingDisabledForUser = $parameters['sharingDisabledForUser'];
103
-		} else {
104
-			$this->sharingDisabledForUser = false;
105
-		}
106
-
107
-		parent::__construct([
108
-			'storage' => null,
109
-			'root' => null,
110
-		]);
111
-	}
112
-
113
-	/**
114
-	 * @return ICacheEntry
115
-	 */
116
-	private function getSourceRootInfo() {
117
-		if (is_null($this->sourceRootInfo)) {
118
-			if (is_null($this->superShare->getNodeCacheEntry())) {
119
-				$this->init();
120
-				$this->sourceRootInfo = $this->nonMaskedStorage->getCache()->get($this->rootPath);
121
-			} else {
122
-				$this->sourceRootInfo = $this->superShare->getNodeCacheEntry();
123
-			}
124
-		}
125
-		return $this->sourceRootInfo;
126
-	}
127
-
128
-	/**
129
-	 * @psalm-assert Storage $this->storage
130
-	 */
131
-	private function init() {
132
-		if ($this->initialized) {
133
-			if (!$this->storage) {
134
-				// marked as initialized but no storage set
135
-				// this is probably because some code path has caused recursion during the share setup
136
-				// we setup a "failed storage" so `getWrapperStorage` doesn't return null.
137
-				// If the share setup completes after this the "failed storage" will be overwritten by the correct one
138
-				$ex = new \Exception('Possible share setup recursion detected for share ' . $this->superShare->getId());
139
-				$this->logger->warning($ex->getMessage(), ['exception' => $ex, 'app' => 'files_sharing']);
140
-				$this->storage = new FailedStorage(['exception' => $ex]);
141
-				$this->cache = new FailedCache();
142
-				$this->rootPath = '';
143
-			}
144
-			return;
145
-		}
146
-
147
-		$this->initialized = true;
148
-		self::$initDepth++;
149
-
150
-		try {
151
-			if (self::$initDepth > 10) {
152
-				throw new \Exception('Maximum share depth reached');
153
-			}
154
-
155
-			/** @var IRootFolder $rootFolder */
156
-			$rootFolder = Server::get(IRootFolder::class);
157
-			$this->ownerUserFolder = $rootFolder->getUserFolder($this->superShare->getShareOwner());
158
-			$sourceId = $this->superShare->getNodeId();
159
-			$ownerNodes = $this->ownerUserFolder->getById($sourceId);
160
-
161
-			if (count($ownerNodes) === 0) {
162
-				$this->storage = new FailedStorage(['exception' => new NotFoundException("File by id $sourceId not found")]);
163
-				$this->cache = new FailedCache();
164
-				$this->rootPath = '';
165
-			} else {
166
-				foreach ($ownerNodes as $ownerNode) {
167
-					$nonMaskedStorage = $ownerNode->getStorage();
168
-
169
-					// check if potential source node would lead to a recursive share setup
170
-					if ($nonMaskedStorage instanceof Wrapper && $nonMaskedStorage->isWrapperOf($this)) {
171
-						continue;
172
-					}
173
-					$this->nonMaskedStorage = $nonMaskedStorage;
174
-					$this->sourcePath = $ownerNode->getPath();
175
-					$this->rootPath = $ownerNode->getInternalPath();
176
-					$this->cache = null;
177
-					break;
178
-				}
179
-				if (!$this->nonMaskedStorage) {
180
-					// all potential source nodes would have been recursive
181
-					throw new \Exception('recursive share detected');
182
-				}
183
-				$this->storage = new PermissionsMask([
184
-					'storage' => $this->nonMaskedStorage,
185
-					'mask' => $this->superShare->getPermissions(),
186
-				]);
187
-			}
188
-		} catch (NotFoundException $e) {
189
-			// original file not accessible or deleted, set FailedStorage
190
-			$this->storage = new FailedStorage(['exception' => $e]);
191
-			$this->cache = new FailedCache();
192
-			$this->rootPath = '';
193
-		} catch (NoUserException $e) {
194
-			// sharer user deleted, set FailedStorage
195
-			$this->storage = new FailedStorage(['exception' => $e]);
196
-			$this->cache = new FailedCache();
197
-			$this->rootPath = '';
198
-		} catch (\Exception $e) {
199
-			$this->storage = new FailedStorage(['exception' => $e]);
200
-			$this->cache = new FailedCache();
201
-			$this->rootPath = '';
202
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
203
-		}
204
-
205
-		if (!$this->nonMaskedStorage) {
206
-			$this->nonMaskedStorage = $this->storage;
207
-		}
208
-		self::$initDepth--;
209
-	}
210
-
211
-	public function instanceOfStorage(string $class): bool {
212
-		if ($class === '\OC\Files\Storage\Common' || $class == Common::class) {
213
-			return true;
214
-		}
215
-		if (in_array($class, [
216
-			'\OC\Files\Storage\Home',
217
-			'\OC\Files\ObjectStore\HomeObjectStoreStorage',
218
-			'\OCP\Files\IHomeStorage',
219
-			Home::class,
220
-			HomeObjectStoreStorage::class,
221
-			IHomeStorage::class
222
-		])) {
223
-			return false;
224
-		}
225
-		return parent::instanceOfStorage($class);
226
-	}
227
-
228
-	/**
229
-	 * @return string
230
-	 */
231
-	public function getShareId() {
232
-		return $this->superShare->getId();
233
-	}
234
-
235
-	private function isValid(): bool {
236
-		return $this->getSourceRootInfo() && ($this->getSourceRootInfo()->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
237
-	}
238
-
239
-	public function getId(): string {
240
-		return 'shared::' . $this->getMountPoint();
241
-	}
242
-
243
-	public function getPermissions(string $path = ''): int {
244
-		if (!$this->isValid()) {
245
-			return 0;
246
-		}
247
-		$permissions = parent::getPermissions($path) & $this->superShare->getPermissions();
248
-
249
-		// part files and the mount point always have delete permissions
250
-		if ($path === '' || pathinfo($path, PATHINFO_EXTENSION) === 'part') {
251
-			$permissions |= Constants::PERMISSION_DELETE;
252
-		}
253
-
254
-		if ($this->sharingDisabledForUser) {
255
-			$permissions &= ~Constants::PERMISSION_SHARE;
256
-		}
257
-
258
-		return $permissions;
259
-	}
260
-
261
-	public function isCreatable(string $path): bool {
262
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
263
-	}
264
-
265
-	public function isReadable(string $path): bool {
266
-		if (!$this->isValid()) {
267
-			return false;
268
-		}
269
-		if (!$this->file_exists($path)) {
270
-			return false;
271
-		}
272
-		/** @var IStorage $storage */
273
-		/** @var string $internalPath */
274
-		[$storage, $internalPath] = $this->resolvePath($path);
275
-		return $storage->isReadable($internalPath);
276
-	}
277
-
278
-	public function isUpdatable(string $path): bool {
279
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
280
-	}
281
-
282
-	public function isDeletable(string $path): bool {
283
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
284
-	}
285
-
286
-	public function isSharable(string $path): bool {
287
-		if (Util::isSharingDisabledForUser() || !Share::isResharingAllowed()) {
288
-			return false;
289
-		}
290
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
291
-	}
292
-
293
-	public function fopen(string $path, string $mode) {
294
-		$source = $this->getUnjailedPath($path);
295
-		switch ($mode) {
296
-			case 'r+':
297
-			case 'rb+':
298
-			case 'w+':
299
-			case 'wb+':
300
-			case 'x+':
301
-			case 'xb+':
302
-			case 'a+':
303
-			case 'ab+':
304
-			case 'w':
305
-			case 'wb':
306
-			case 'x':
307
-			case 'xb':
308
-			case 'a':
309
-			case 'ab':
310
-				$creatable = $this->isCreatable(dirname($path));
311
-				$updatable = $this->isUpdatable($path);
312
-				// if neither permissions given, no need to continue
313
-				if (!$creatable && !$updatable) {
314
-					if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
315
-						$updatable = $this->isUpdatable(dirname($path));
316
-					}
317
-
318
-					if (!$updatable) {
319
-						return false;
320
-					}
321
-				}
322
-
323
-				$exists = $this->file_exists($path);
324
-				// if a file exists, updatable permissions are required
325
-				if ($exists && !$updatable) {
326
-					return false;
327
-				}
328
-
329
-				// part file is allowed if !$creatable but the final file is $updatable
330
-				if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
331
-					if (!$exists && !$creatable) {
332
-						return false;
333
-					}
334
-				}
335
-		}
336
-		$info = [
337
-			'target' => $this->getMountPoint() . '/' . $path,
338
-			'source' => $source,
339
-			'mode' => $mode,
340
-		];
341
-		Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
342
-		return $this->nonMaskedStorage->fopen($this->getUnjailedPath($path), $mode);
343
-	}
344
-
345
-	public function rename(string $source, string $target): bool {
346
-		$this->init();
347
-		$isPartFile = pathinfo($source, PATHINFO_EXTENSION) === 'part';
348
-		$targetExists = $this->file_exists($target);
349
-		$sameFolder = dirname($source) === dirname($target);
350
-
351
-		if ($targetExists || ($sameFolder && !$isPartFile)) {
352
-			if (!$this->isUpdatable('')) {
353
-				return false;
354
-			}
355
-		} else {
356
-			if (!$this->isCreatable('')) {
357
-				return false;
358
-			}
359
-		}
360
-
361
-		return $this->nonMaskedStorage->rename($this->getUnjailedPath($source), $this->getUnjailedPath($target));
362
-	}
363
-
364
-	/**
365
-	 * return mount point of share, relative to data/user/files
366
-	 *
367
-	 * @return string
368
-	 */
369
-	public function getMountPoint(): string {
370
-		return $this->superShare->getTarget();
371
-	}
372
-
373
-	public function setMountPoint(string $path): void {
374
-		$this->superShare->setTarget($path);
375
-
376
-		foreach ($this->groupedShares as $share) {
377
-			$share->setTarget($path);
378
-		}
379
-	}
380
-
381
-	/**
382
-	 * get the user who shared the file
383
-	 *
384
-	 * @return string
385
-	 */
386
-	public function getSharedFrom(): string {
387
-		return $this->superShare->getShareOwner();
388
-	}
389
-
390
-	public function getShare(): IShare {
391
-		return $this->superShare;
392
-	}
393
-
394
-	/**
395
-	 * return share type, can be "file" or "folder"
396
-	 *
397
-	 * @return string
398
-	 */
399
-	public function getItemType(): string {
400
-		return $this->superShare->getNodeType();
401
-	}
402
-
403
-	public function getCache(string $path = '', ?IStorage $storage = null): ICache {
404
-		if ($this->cache) {
405
-			return $this->cache;
406
-		}
407
-		if (!$storage) {
408
-			$storage = $this;
409
-		}
410
-		$sourceRoot = $this->getSourceRootInfo();
411
-		if ($this->storage instanceof FailedStorage) {
412
-			return new FailedCache();
413
-		}
414
-
415
-		$this->cache = new Cache(
416
-			$storage,
417
-			$sourceRoot,
418
-			Server::get(CacheDependencies::class),
419
-			$this->getShare()
420
-		);
421
-		return $this->cache;
422
-	}
423
-
424
-	public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
425
-		if (!$storage) {
426
-			$storage = $this;
427
-		}
428
-		return new Scanner($storage);
429
-	}
430
-
431
-	public function getOwner(string $path): string|false {
432
-		return $this->superShare->getShareOwner();
433
-	}
434
-
435
-	public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher {
436
-		if ($this->watcher) {
437
-			return $this->watcher;
438
-		}
439
-
440
-		// Get node information
441
-		$node = $this->getShare()->getNodeCacheEntry();
442
-		if ($node instanceof CacheEntry) {
443
-			$storageId = $node->getData()['storage_string_id'] ?? null;
444
-			// for shares from the home storage we can rely on the home storage to keep itself up to date
445
-			// for other storages we need use the proper watcher
446
-			if ($storageId !== null && !(str_starts_with($storageId, 'home::') || str_starts_with($storageId, 'object::user'))) {
447
-				$cache = $this->getCache();
448
-				$this->watcher = parent::getWatcher($path, $storage);
449
-				if ($cache instanceof Cache) {
450
-					$this->watcher->onUpdate($cache->markRootChanged(...));
451
-				}
452
-				return $this->watcher;
453
-			}
454
-		}
455
-
456
-		// cache updating is handled by the share source
457
-		$this->watcher = new NullWatcher();
458
-		return $this->watcher;
459
-	}
460
-
461
-	/**
462
-	 * unshare complete storage, also the grouped shares
463
-	 *
464
-	 * @return bool
465
-	 */
466
-	public function unshareStorage(): bool {
467
-		foreach ($this->groupedShares as $share) {
468
-			Server::get(\OCP\Share\IManager::class)->deleteFromSelf($share, $this->user);
469
-		}
470
-		return true;
471
-	}
472
-
473
-	public function acquireLock(string $path, int $type, ILockingProvider $provider): void {
474
-		/** @var ILockingStorage $targetStorage */
475
-		[$targetStorage, $targetInternalPath] = $this->resolvePath($path);
476
-		$targetStorage->acquireLock($targetInternalPath, $type, $provider);
477
-		// lock the parent folders of the owner when locking the share as recipient
478
-		if ($path === '') {
479
-			$sourcePath = $this->ownerUserFolder->getRelativePath($this->sourcePath);
480
-			$this->ownerView->lockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
481
-		}
482
-	}
483
-
484
-	public function releaseLock(string $path, int $type, ILockingProvider $provider): void {
485
-		/** @var ILockingStorage $targetStorage */
486
-		[$targetStorage, $targetInternalPath] = $this->resolvePath($path);
487
-		$targetStorage->releaseLock($targetInternalPath, $type, $provider);
488
-		// unlock the parent folders of the owner when unlocking the share as recipient
489
-		if ($path === '') {
490
-			$sourcePath = $this->ownerUserFolder->getRelativePath($this->sourcePath);
491
-			$this->ownerView->unlockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
492
-		}
493
-	}
494
-
495
-	public function changeLock(string $path, int $type, ILockingProvider $provider): void {
496
-		/** @var ILockingStorage $targetStorage */
497
-		[$targetStorage, $targetInternalPath] = $this->resolvePath($path);
498
-		$targetStorage->changeLock($targetInternalPath, $type, $provider);
499
-	}
500
-
501
-	public function getAvailability(): array {
502
-		// shares do not participate in availability logic
503
-		return [
504
-			'available' => true,
505
-			'last_checked' => 0,
506
-		];
507
-	}
508
-
509
-	public function setAvailability(bool $isAvailable): void {
510
-		// shares do not participate in availability logic
511
-	}
512
-
513
-	public function getSourceStorage() {
514
-		$this->init();
515
-		return $this->nonMaskedStorage;
516
-	}
517
-
518
-	public function getWrapperStorage(): Storage {
519
-		$this->init();
520
-
521
-		/**
522
-		 * @psalm-suppress DocblockTypeContradiction
523
-		 */
524
-		if (!$this->storage) {
525
-			$message = 'no storage set after init for share ' . $this->getShareId();
526
-			$this->logger->error($message);
527
-			$this->storage = new FailedStorage(['exception' => new \Exception($message)]);
528
-		}
529
-
530
-		return $this->storage;
531
-	}
532
-
533
-	public function file_get_contents(string $path): string|false {
534
-		$info = [
535
-			'target' => $this->getMountPoint() . '/' . $path,
536
-			'source' => $this->getUnjailedPath($path),
537
-		];
538
-		Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
539
-		return parent::file_get_contents($path);
540
-	}
541
-
542
-	public function file_put_contents(string $path, mixed $data): int|float|false {
543
-		$info = [
544
-			'target' => $this->getMountPoint() . '/' . $path,
545
-			'source' => $this->getUnjailedPath($path),
546
-		];
547
-		Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
548
-		return parent::file_put_contents($path, $data);
549
-	}
550
-
551
-	public function setMountOptions(array $options): void {
552
-		/* Note: This value is never read */
553
-		$this->mountOptions = $options;
554
-	}
555
-
556
-	public function getUnjailedPath(string $path): string {
557
-		$this->init();
558
-		return parent::getUnjailedPath($path);
559
-	}
560
-
561
-	public function getDirectDownload(string $path): array|false {
562
-		// disable direct download for shares
563
-		return [];
564
-	}
49
+    /** @var IShare */
50
+    private $superShare;
51
+
52
+    /** @var IShare[] */
53
+    private $groupedShares;
54
+
55
+    /**
56
+     * @var View
57
+     */
58
+    private $ownerView;
59
+
60
+    private $initialized = false;
61
+
62
+    /**
63
+     * @var ICacheEntry
64
+     */
65
+    private $sourceRootInfo;
66
+
67
+    /** @var string */
68
+    private $user;
69
+
70
+    private LoggerInterface $logger;
71
+
72
+    /** @var IStorage */
73
+    private $nonMaskedStorage;
74
+
75
+    private array $mountOptions = [];
76
+
77
+    /** @var boolean */
78
+    private $sharingDisabledForUser;
79
+
80
+    /** @var ?Folder $ownerUserFolder */
81
+    private $ownerUserFolder = null;
82
+
83
+    private string $sourcePath = '';
84
+
85
+    private static int $initDepth = 0;
86
+
87
+    /**
88
+     * @psalm-suppress NonInvariantDocblockPropertyType
89
+     * @var ?Storage $storage
90
+     */
91
+    protected $storage;
92
+
93
+    public function __construct(array $parameters) {
94
+        $this->ownerView = $parameters['ownerView'];
95
+        $this->logger = Server::get(LoggerInterface::class);
96
+
97
+        $this->superShare = $parameters['superShare'];
98
+        $this->groupedShares = $parameters['groupedShares'];
99
+
100
+        $this->user = $parameters['user'];
101
+        if (isset($parameters['sharingDisabledForUser'])) {
102
+            $this->sharingDisabledForUser = $parameters['sharingDisabledForUser'];
103
+        } else {
104
+            $this->sharingDisabledForUser = false;
105
+        }
106
+
107
+        parent::__construct([
108
+            'storage' => null,
109
+            'root' => null,
110
+        ]);
111
+    }
112
+
113
+    /**
114
+     * @return ICacheEntry
115
+     */
116
+    private function getSourceRootInfo() {
117
+        if (is_null($this->sourceRootInfo)) {
118
+            if (is_null($this->superShare->getNodeCacheEntry())) {
119
+                $this->init();
120
+                $this->sourceRootInfo = $this->nonMaskedStorage->getCache()->get($this->rootPath);
121
+            } else {
122
+                $this->sourceRootInfo = $this->superShare->getNodeCacheEntry();
123
+            }
124
+        }
125
+        return $this->sourceRootInfo;
126
+    }
127
+
128
+    /**
129
+     * @psalm-assert Storage $this->storage
130
+     */
131
+    private function init() {
132
+        if ($this->initialized) {
133
+            if (!$this->storage) {
134
+                // marked as initialized but no storage set
135
+                // this is probably because some code path has caused recursion during the share setup
136
+                // we setup a "failed storage" so `getWrapperStorage` doesn't return null.
137
+                // If the share setup completes after this the "failed storage" will be overwritten by the correct one
138
+                $ex = new \Exception('Possible share setup recursion detected for share ' . $this->superShare->getId());
139
+                $this->logger->warning($ex->getMessage(), ['exception' => $ex, 'app' => 'files_sharing']);
140
+                $this->storage = new FailedStorage(['exception' => $ex]);
141
+                $this->cache = new FailedCache();
142
+                $this->rootPath = '';
143
+            }
144
+            return;
145
+        }
146
+
147
+        $this->initialized = true;
148
+        self::$initDepth++;
149
+
150
+        try {
151
+            if (self::$initDepth > 10) {
152
+                throw new \Exception('Maximum share depth reached');
153
+            }
154
+
155
+            /** @var IRootFolder $rootFolder */
156
+            $rootFolder = Server::get(IRootFolder::class);
157
+            $this->ownerUserFolder = $rootFolder->getUserFolder($this->superShare->getShareOwner());
158
+            $sourceId = $this->superShare->getNodeId();
159
+            $ownerNodes = $this->ownerUserFolder->getById($sourceId);
160
+
161
+            if (count($ownerNodes) === 0) {
162
+                $this->storage = new FailedStorage(['exception' => new NotFoundException("File by id $sourceId not found")]);
163
+                $this->cache = new FailedCache();
164
+                $this->rootPath = '';
165
+            } else {
166
+                foreach ($ownerNodes as $ownerNode) {
167
+                    $nonMaskedStorage = $ownerNode->getStorage();
168
+
169
+                    // check if potential source node would lead to a recursive share setup
170
+                    if ($nonMaskedStorage instanceof Wrapper && $nonMaskedStorage->isWrapperOf($this)) {
171
+                        continue;
172
+                    }
173
+                    $this->nonMaskedStorage = $nonMaskedStorage;
174
+                    $this->sourcePath = $ownerNode->getPath();
175
+                    $this->rootPath = $ownerNode->getInternalPath();
176
+                    $this->cache = null;
177
+                    break;
178
+                }
179
+                if (!$this->nonMaskedStorage) {
180
+                    // all potential source nodes would have been recursive
181
+                    throw new \Exception('recursive share detected');
182
+                }
183
+                $this->storage = new PermissionsMask([
184
+                    'storage' => $this->nonMaskedStorage,
185
+                    'mask' => $this->superShare->getPermissions(),
186
+                ]);
187
+            }
188
+        } catch (NotFoundException $e) {
189
+            // original file not accessible or deleted, set FailedStorage
190
+            $this->storage = new FailedStorage(['exception' => $e]);
191
+            $this->cache = new FailedCache();
192
+            $this->rootPath = '';
193
+        } catch (NoUserException $e) {
194
+            // sharer user deleted, set FailedStorage
195
+            $this->storage = new FailedStorage(['exception' => $e]);
196
+            $this->cache = new FailedCache();
197
+            $this->rootPath = '';
198
+        } catch (\Exception $e) {
199
+            $this->storage = new FailedStorage(['exception' => $e]);
200
+            $this->cache = new FailedCache();
201
+            $this->rootPath = '';
202
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
203
+        }
204
+
205
+        if (!$this->nonMaskedStorage) {
206
+            $this->nonMaskedStorage = $this->storage;
207
+        }
208
+        self::$initDepth--;
209
+    }
210
+
211
+    public function instanceOfStorage(string $class): bool {
212
+        if ($class === '\OC\Files\Storage\Common' || $class == Common::class) {
213
+            return true;
214
+        }
215
+        if (in_array($class, [
216
+            '\OC\Files\Storage\Home',
217
+            '\OC\Files\ObjectStore\HomeObjectStoreStorage',
218
+            '\OCP\Files\IHomeStorage',
219
+            Home::class,
220
+            HomeObjectStoreStorage::class,
221
+            IHomeStorage::class
222
+        ])) {
223
+            return false;
224
+        }
225
+        return parent::instanceOfStorage($class);
226
+    }
227
+
228
+    /**
229
+     * @return string
230
+     */
231
+    public function getShareId() {
232
+        return $this->superShare->getId();
233
+    }
234
+
235
+    private function isValid(): bool {
236
+        return $this->getSourceRootInfo() && ($this->getSourceRootInfo()->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
237
+    }
238
+
239
+    public function getId(): string {
240
+        return 'shared::' . $this->getMountPoint();
241
+    }
242
+
243
+    public function getPermissions(string $path = ''): int {
244
+        if (!$this->isValid()) {
245
+            return 0;
246
+        }
247
+        $permissions = parent::getPermissions($path) & $this->superShare->getPermissions();
248
+
249
+        // part files and the mount point always have delete permissions
250
+        if ($path === '' || pathinfo($path, PATHINFO_EXTENSION) === 'part') {
251
+            $permissions |= Constants::PERMISSION_DELETE;
252
+        }
253
+
254
+        if ($this->sharingDisabledForUser) {
255
+            $permissions &= ~Constants::PERMISSION_SHARE;
256
+        }
257
+
258
+        return $permissions;
259
+    }
260
+
261
+    public function isCreatable(string $path): bool {
262
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
263
+    }
264
+
265
+    public function isReadable(string $path): bool {
266
+        if (!$this->isValid()) {
267
+            return false;
268
+        }
269
+        if (!$this->file_exists($path)) {
270
+            return false;
271
+        }
272
+        /** @var IStorage $storage */
273
+        /** @var string $internalPath */
274
+        [$storage, $internalPath] = $this->resolvePath($path);
275
+        return $storage->isReadable($internalPath);
276
+    }
277
+
278
+    public function isUpdatable(string $path): bool {
279
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
280
+    }
281
+
282
+    public function isDeletable(string $path): bool {
283
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
284
+    }
285
+
286
+    public function isSharable(string $path): bool {
287
+        if (Util::isSharingDisabledForUser() || !Share::isResharingAllowed()) {
288
+            return false;
289
+        }
290
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
291
+    }
292
+
293
+    public function fopen(string $path, string $mode) {
294
+        $source = $this->getUnjailedPath($path);
295
+        switch ($mode) {
296
+            case 'r+':
297
+            case 'rb+':
298
+            case 'w+':
299
+            case 'wb+':
300
+            case 'x+':
301
+            case 'xb+':
302
+            case 'a+':
303
+            case 'ab+':
304
+            case 'w':
305
+            case 'wb':
306
+            case 'x':
307
+            case 'xb':
308
+            case 'a':
309
+            case 'ab':
310
+                $creatable = $this->isCreatable(dirname($path));
311
+                $updatable = $this->isUpdatable($path);
312
+                // if neither permissions given, no need to continue
313
+                if (!$creatable && !$updatable) {
314
+                    if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
315
+                        $updatable = $this->isUpdatable(dirname($path));
316
+                    }
317
+
318
+                    if (!$updatable) {
319
+                        return false;
320
+                    }
321
+                }
322
+
323
+                $exists = $this->file_exists($path);
324
+                // if a file exists, updatable permissions are required
325
+                if ($exists && !$updatable) {
326
+                    return false;
327
+                }
328
+
329
+                // part file is allowed if !$creatable but the final file is $updatable
330
+                if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
331
+                    if (!$exists && !$creatable) {
332
+                        return false;
333
+                    }
334
+                }
335
+        }
336
+        $info = [
337
+            'target' => $this->getMountPoint() . '/' . $path,
338
+            'source' => $source,
339
+            'mode' => $mode,
340
+        ];
341
+        Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
342
+        return $this->nonMaskedStorage->fopen($this->getUnjailedPath($path), $mode);
343
+    }
344
+
345
+    public function rename(string $source, string $target): bool {
346
+        $this->init();
347
+        $isPartFile = pathinfo($source, PATHINFO_EXTENSION) === 'part';
348
+        $targetExists = $this->file_exists($target);
349
+        $sameFolder = dirname($source) === dirname($target);
350
+
351
+        if ($targetExists || ($sameFolder && !$isPartFile)) {
352
+            if (!$this->isUpdatable('')) {
353
+                return false;
354
+            }
355
+        } else {
356
+            if (!$this->isCreatable('')) {
357
+                return false;
358
+            }
359
+        }
360
+
361
+        return $this->nonMaskedStorage->rename($this->getUnjailedPath($source), $this->getUnjailedPath($target));
362
+    }
363
+
364
+    /**
365
+     * return mount point of share, relative to data/user/files
366
+     *
367
+     * @return string
368
+     */
369
+    public function getMountPoint(): string {
370
+        return $this->superShare->getTarget();
371
+    }
372
+
373
+    public function setMountPoint(string $path): void {
374
+        $this->superShare->setTarget($path);
375
+
376
+        foreach ($this->groupedShares as $share) {
377
+            $share->setTarget($path);
378
+        }
379
+    }
380
+
381
+    /**
382
+     * get the user who shared the file
383
+     *
384
+     * @return string
385
+     */
386
+    public function getSharedFrom(): string {
387
+        return $this->superShare->getShareOwner();
388
+    }
389
+
390
+    public function getShare(): IShare {
391
+        return $this->superShare;
392
+    }
393
+
394
+    /**
395
+     * return share type, can be "file" or "folder"
396
+     *
397
+     * @return string
398
+     */
399
+    public function getItemType(): string {
400
+        return $this->superShare->getNodeType();
401
+    }
402
+
403
+    public function getCache(string $path = '', ?IStorage $storage = null): ICache {
404
+        if ($this->cache) {
405
+            return $this->cache;
406
+        }
407
+        if (!$storage) {
408
+            $storage = $this;
409
+        }
410
+        $sourceRoot = $this->getSourceRootInfo();
411
+        if ($this->storage instanceof FailedStorage) {
412
+            return new FailedCache();
413
+        }
414
+
415
+        $this->cache = new Cache(
416
+            $storage,
417
+            $sourceRoot,
418
+            Server::get(CacheDependencies::class),
419
+            $this->getShare()
420
+        );
421
+        return $this->cache;
422
+    }
423
+
424
+    public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
425
+        if (!$storage) {
426
+            $storage = $this;
427
+        }
428
+        return new Scanner($storage);
429
+    }
430
+
431
+    public function getOwner(string $path): string|false {
432
+        return $this->superShare->getShareOwner();
433
+    }
434
+
435
+    public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher {
436
+        if ($this->watcher) {
437
+            return $this->watcher;
438
+        }
439
+
440
+        // Get node information
441
+        $node = $this->getShare()->getNodeCacheEntry();
442
+        if ($node instanceof CacheEntry) {
443
+            $storageId = $node->getData()['storage_string_id'] ?? null;
444
+            // for shares from the home storage we can rely on the home storage to keep itself up to date
445
+            // for other storages we need use the proper watcher
446
+            if ($storageId !== null && !(str_starts_with($storageId, 'home::') || str_starts_with($storageId, 'object::user'))) {
447
+                $cache = $this->getCache();
448
+                $this->watcher = parent::getWatcher($path, $storage);
449
+                if ($cache instanceof Cache) {
450
+                    $this->watcher->onUpdate($cache->markRootChanged(...));
451
+                }
452
+                return $this->watcher;
453
+            }
454
+        }
455
+
456
+        // cache updating is handled by the share source
457
+        $this->watcher = new NullWatcher();
458
+        return $this->watcher;
459
+    }
460
+
461
+    /**
462
+     * unshare complete storage, also the grouped shares
463
+     *
464
+     * @return bool
465
+     */
466
+    public function unshareStorage(): bool {
467
+        foreach ($this->groupedShares as $share) {
468
+            Server::get(\OCP\Share\IManager::class)->deleteFromSelf($share, $this->user);
469
+        }
470
+        return true;
471
+    }
472
+
473
+    public function acquireLock(string $path, int $type, ILockingProvider $provider): void {
474
+        /** @var ILockingStorage $targetStorage */
475
+        [$targetStorage, $targetInternalPath] = $this->resolvePath($path);
476
+        $targetStorage->acquireLock($targetInternalPath, $type, $provider);
477
+        // lock the parent folders of the owner when locking the share as recipient
478
+        if ($path === '') {
479
+            $sourcePath = $this->ownerUserFolder->getRelativePath($this->sourcePath);
480
+            $this->ownerView->lockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
481
+        }
482
+    }
483
+
484
+    public function releaseLock(string $path, int $type, ILockingProvider $provider): void {
485
+        /** @var ILockingStorage $targetStorage */
486
+        [$targetStorage, $targetInternalPath] = $this->resolvePath($path);
487
+        $targetStorage->releaseLock($targetInternalPath, $type, $provider);
488
+        // unlock the parent folders of the owner when unlocking the share as recipient
489
+        if ($path === '') {
490
+            $sourcePath = $this->ownerUserFolder->getRelativePath($this->sourcePath);
491
+            $this->ownerView->unlockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
492
+        }
493
+    }
494
+
495
+    public function changeLock(string $path, int $type, ILockingProvider $provider): void {
496
+        /** @var ILockingStorage $targetStorage */
497
+        [$targetStorage, $targetInternalPath] = $this->resolvePath($path);
498
+        $targetStorage->changeLock($targetInternalPath, $type, $provider);
499
+    }
500
+
501
+    public function getAvailability(): array {
502
+        // shares do not participate in availability logic
503
+        return [
504
+            'available' => true,
505
+            'last_checked' => 0,
506
+        ];
507
+    }
508
+
509
+    public function setAvailability(bool $isAvailable): void {
510
+        // shares do not participate in availability logic
511
+    }
512
+
513
+    public function getSourceStorage() {
514
+        $this->init();
515
+        return $this->nonMaskedStorage;
516
+    }
517
+
518
+    public function getWrapperStorage(): Storage {
519
+        $this->init();
520
+
521
+        /**
522
+         * @psalm-suppress DocblockTypeContradiction
523
+         */
524
+        if (!$this->storage) {
525
+            $message = 'no storage set after init for share ' . $this->getShareId();
526
+            $this->logger->error($message);
527
+            $this->storage = new FailedStorage(['exception' => new \Exception($message)]);
528
+        }
529
+
530
+        return $this->storage;
531
+    }
532
+
533
+    public function file_get_contents(string $path): string|false {
534
+        $info = [
535
+            'target' => $this->getMountPoint() . '/' . $path,
536
+            'source' => $this->getUnjailedPath($path),
537
+        ];
538
+        Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
539
+        return parent::file_get_contents($path);
540
+    }
541
+
542
+    public function file_put_contents(string $path, mixed $data): int|float|false {
543
+        $info = [
544
+            'target' => $this->getMountPoint() . '/' . $path,
545
+            'source' => $this->getUnjailedPath($path),
546
+        ];
547
+        Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
548
+        return parent::file_put_contents($path, $data);
549
+    }
550
+
551
+    public function setMountOptions(array $options): void {
552
+        /* Note: This value is never read */
553
+        $this->mountOptions = $options;
554
+    }
555
+
556
+    public function getUnjailedPath(string $path): string {
557
+        $this->init();
558
+        return parent::getUnjailedPath($path);
559
+    }
560
+
561
+    public function getDirectDownload(string $path): array|false {
562
+        // disable direct download for shares
563
+        return [];
564
+    }
565 565
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -135,7 +135,7 @@  discard block
 block discarded – undo
135 135
 				// this is probably because some code path has caused recursion during the share setup
136 136
 				// we setup a "failed storage" so `getWrapperStorage` doesn't return null.
137 137
 				// If the share setup completes after this the "failed storage" will be overwritten by the correct one
138
-				$ex = new \Exception('Possible share setup recursion detected for share ' . $this->superShare->getId());
138
+				$ex = new \Exception('Possible share setup recursion detected for share '.$this->superShare->getId());
139 139
 				$this->logger->warning($ex->getMessage(), ['exception' => $ex, 'app' => 'files_sharing']);
140 140
 				$this->storage = new FailedStorage(['exception' => $ex]);
141 141
 				$this->cache = new FailedCache();
@@ -237,7 +237,7 @@  discard block
 block discarded – undo
237 237
 	}
238 238
 
239 239
 	public function getId(): string {
240
-		return 'shared::' . $this->getMountPoint();
240
+		return 'shared::'.$this->getMountPoint();
241 241
 	}
242 242
 
243 243
 	public function getPermissions(string $path = ''): int {
@@ -259,7 +259,7 @@  discard block
 block discarded – undo
259 259
 	}
260 260
 
261 261
 	public function isCreatable(string $path): bool {
262
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
262
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_CREATE);
263 263
 	}
264 264
 
265 265
 	public function isReadable(string $path): bool {
@@ -276,18 +276,18 @@  discard block
 block discarded – undo
276 276
 	}
277 277
 
278 278
 	public function isUpdatable(string $path): bool {
279
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
279
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
280 280
 	}
281 281
 
282 282
 	public function isDeletable(string $path): bool {
283
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
283
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_DELETE);
284 284
 	}
285 285
 
286 286
 	public function isSharable(string $path): bool {
287 287
 		if (Util::isSharingDisabledForUser() || !Share::isResharingAllowed()) {
288 288
 			return false;
289 289
 		}
290
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
290
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_SHARE);
291 291
 	}
292 292
 
293 293
 	public function fopen(string $path, string $mode) {
@@ -334,7 +334,7 @@  discard block
 block discarded – undo
334 334
 				}
335 335
 		}
336 336
 		$info = [
337
-			'target' => $this->getMountPoint() . '/' . $path,
337
+			'target' => $this->getMountPoint().'/'.$path,
338 338
 			'source' => $source,
339 339
 			'mode' => $mode,
340 340
 		];
@@ -428,7 +428,7 @@  discard block
 block discarded – undo
428 428
 		return new Scanner($storage);
429 429
 	}
430 430
 
431
-	public function getOwner(string $path): string|false {
431
+	public function getOwner(string $path): string | false {
432 432
 		return $this->superShare->getShareOwner();
433 433
 	}
434 434
 
@@ -522,7 +522,7 @@  discard block
 block discarded – undo
522 522
 		 * @psalm-suppress DocblockTypeContradiction
523 523
 		 */
524 524
 		if (!$this->storage) {
525
-			$message = 'no storage set after init for share ' . $this->getShareId();
525
+			$message = 'no storage set after init for share '.$this->getShareId();
526 526
 			$this->logger->error($message);
527 527
 			$this->storage = new FailedStorage(['exception' => new \Exception($message)]);
528 528
 		}
@@ -530,18 +530,18 @@  discard block
 block discarded – undo
530 530
 		return $this->storage;
531 531
 	}
532 532
 
533
-	public function file_get_contents(string $path): string|false {
533
+	public function file_get_contents(string $path): string | false {
534 534
 		$info = [
535
-			'target' => $this->getMountPoint() . '/' . $path,
535
+			'target' => $this->getMountPoint().'/'.$path,
536 536
 			'source' => $this->getUnjailedPath($path),
537 537
 		];
538 538
 		Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
539 539
 		return parent::file_get_contents($path);
540 540
 	}
541 541
 
542
-	public function file_put_contents(string $path, mixed $data): int|float|false {
542
+	public function file_put_contents(string $path, mixed $data): int | float | false {
543 543
 		$info = [
544
-			'target' => $this->getMountPoint() . '/' . $path,
544
+			'target' => $this->getMountPoint().'/'.$path,
545 545
 			'source' => $this->getUnjailedPath($path),
546 546
 		];
547 547
 		Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
@@ -558,7 +558,7 @@  discard block
 block discarded – undo
558 558
 		return parent::getUnjailedPath($path);
559 559
 	}
560 560
 
561
-	public function getDirectDownload(string $path): array|false {
561
+	public function getDirectDownload(string $path): array | false {
562 562
 		// disable direct download for shares
563 563
 		return [];
564 564
 	}
Please login to merge, or discard this patch.