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