Completed
Push — master ( 3b2a30...86c2dd )
by
unknown
32:26
created
lib/private/Files/View.php 2 patches
Indentation   +2228 added lines, -2228 removed lines patch added patch discarded remove patch
@@ -60,2232 +60,2232 @@
 block discarded – undo
60 60
  * @internal Since 33.0.0. use IRootFolder and the Folder/File/Node API instead in new code.
61 61
  */
62 62
 class View {
63
-	private string $fakeRoot = '';
64
-	private ILockingProvider $lockingProvider;
65
-	private bool $lockingEnabled;
66
-	private bool $updaterEnabled = true;
67
-	private UserManager $userManager;
68
-	private LoggerInterface $logger;
69
-
70
-	/**
71
-	 * @throws \Exception If $root contains an invalid path
72
-	 */
73
-	public function __construct(string $root = '') {
74
-		if (!Filesystem::isValidPath($root)) {
75
-			throw new \Exception();
76
-		}
77
-
78
-		$this->fakeRoot = $root;
79
-		$this->lockingProvider = \OC::$server->get(ILockingProvider::class);
80
-		$this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
81
-		$this->userManager = \OC::$server->getUserManager();
82
-		$this->logger = \OC::$server->get(LoggerInterface::class);
83
-	}
84
-
85
-	/**
86
-	 * @param ?string $path
87
-	 * @psalm-template S as string|null
88
-	 * @psalm-param S $path
89
-	 * @psalm-return (S is string ? string : null)
90
-	 */
91
-	public function getAbsolutePath($path = '/'): ?string {
92
-		if ($path === null) {
93
-			return null;
94
-		}
95
-		$this->assertPathLength($path);
96
-		return PathHelper::normalizePath($this->fakeRoot . '/' . $path);
97
-	}
98
-
99
-	/**
100
-	 * Change the root to a fake root
101
-	 *
102
-	 * @param string $fakeRoot
103
-	 */
104
-	public function chroot($fakeRoot): void {
105
-		if (!$fakeRoot == '') {
106
-			if ($fakeRoot[0] !== '/') {
107
-				$fakeRoot = '/' . $fakeRoot;
108
-			}
109
-		}
110
-		$this->fakeRoot = $fakeRoot;
111
-	}
112
-
113
-	/**
114
-	 * Get the fake root
115
-	 */
116
-	public function getRoot(): string {
117
-		return $this->fakeRoot;
118
-	}
119
-
120
-	/**
121
-	 * get path relative to the root of the view
122
-	 *
123
-	 * @param string $path
124
-	 */
125
-	public function getRelativePath($path): ?string {
126
-		$this->assertPathLength($path);
127
-		if ($this->fakeRoot == '') {
128
-			return $path;
129
-		}
130
-
131
-		if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) {
132
-			return '/';
133
-		}
134
-
135
-		// missing slashes can cause wrong matches!
136
-		$root = rtrim($this->fakeRoot, '/') . '/';
137
-
138
-		if (!str_starts_with($path, $root)) {
139
-			return null;
140
-		} else {
141
-			$path = substr($path, strlen($this->fakeRoot));
142
-			if (strlen($path) === 0) {
143
-				return '/';
144
-			} else {
145
-				return $path;
146
-			}
147
-		}
148
-	}
149
-
150
-	/**
151
-	 * Get the mountpoint of the storage object for a path
152
-	 * ( note: because a storage is not always mounted inside the fakeroot, the
153
-	 * returned mountpoint is relative to the absolute root of the filesystem
154
-	 * and does not take the chroot into account )
155
-	 *
156
-	 * @param string $path
157
-	 */
158
-	public function getMountPoint($path): string {
159
-		return Filesystem::getMountPoint($this->getAbsolutePath($path));
160
-	}
161
-
162
-	/**
163
-	 * Get the mountpoint of the storage object for a path
164
-	 * ( note: because a storage is not always mounted inside the fakeroot, the
165
-	 * returned mountpoint is relative to the absolute root of the filesystem
166
-	 * and does not take the chroot into account )
167
-	 *
168
-	 * @param string $path
169
-	 */
170
-	public function getMount($path): IMountPoint {
171
-		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
172
-	}
173
-
174
-	/**
175
-	 * Resolve a path to a storage and internal path
176
-	 *
177
-	 * @param string $path
178
-	 * @return array{?\OCP\Files\Storage\IStorage, string} an array consisting of the storage and the internal path
179
-	 */
180
-	public function resolvePath($path): array {
181
-		$a = $this->getAbsolutePath($path);
182
-		$p = Filesystem::normalizePath($a);
183
-		return Filesystem::resolvePath($p);
184
-	}
185
-
186
-	/**
187
-	 * Return the path to a local version of the file
188
-	 * we need this because we can't know if a file is stored local or not from
189
-	 * outside the filestorage and for some purposes a local file is needed
190
-	 *
191
-	 * @param string $path
192
-	 */
193
-	public function getLocalFile($path): string|false {
194
-		$parent = substr($path, 0, strrpos($path, '/') ?: 0);
195
-		$path = $this->getAbsolutePath($path);
196
-		[$storage, $internalPath] = Filesystem::resolvePath($path);
197
-		if (Filesystem::isValidPath($parent) && $storage) {
198
-			return $storage->getLocalFile($internalPath);
199
-		} else {
200
-			return false;
201
-		}
202
-	}
203
-
204
-	/**
205
-	 * the following functions operate with arguments and return values identical
206
-	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
207
-	 * for \OC\Files\Storage\Storage via basicOperation().
208
-	 */
209
-	public function mkdir($path) {
210
-		return $this->basicOperation('mkdir', $path, ['create', 'write']);
211
-	}
212
-
213
-	/**
214
-	 * remove mount point
215
-	 *
216
-	 * @param IMountPoint $mount
217
-	 * @param string $path relative to data/
218
-	 */
219
-	protected function removeMount($mount, $path): bool {
220
-		if ($mount instanceof MoveableMount) {
221
-			// cut of /user/files to get the relative path to data/user/files
222
-			$pathParts = explode('/', $path, 4);
223
-			$relPath = '/' . $pathParts[3];
224
-			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
225
-			\OC_Hook::emit(
226
-				Filesystem::CLASSNAME, 'umount',
227
-				[Filesystem::signal_param_path => $relPath]
228
-			);
229
-			$this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
230
-			$result = $mount->removeMount();
231
-			$this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
232
-			if ($result) {
233
-				\OC_Hook::emit(
234
-					Filesystem::CLASSNAME, 'post_umount',
235
-					[Filesystem::signal_param_path => $relPath]
236
-				);
237
-			}
238
-			$this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
239
-			return $result;
240
-		} else {
241
-			// do not allow deleting the storage's root / the mount point
242
-			// because for some storages it might delete the whole contents
243
-			// but isn't supposed to work that way
244
-			return false;
245
-		}
246
-	}
247
-
248
-	public function disableCacheUpdate(): void {
249
-		$this->updaterEnabled = false;
250
-	}
251
-
252
-	public function enableCacheUpdate(): void {
253
-		$this->updaterEnabled = true;
254
-	}
255
-
256
-	protected function writeUpdate(Storage $storage, string $internalPath, ?int $time = null, ?int $sizeDifference = null): void {
257
-		if ($this->updaterEnabled) {
258
-			if (is_null($time)) {
259
-				$time = time();
260
-			}
261
-			$storage->getUpdater()->update($internalPath, $time, $sizeDifference);
262
-		}
263
-	}
264
-
265
-	protected function removeUpdate(Storage $storage, string $internalPath): void {
266
-		if ($this->updaterEnabled) {
267
-			$storage->getUpdater()->remove($internalPath);
268
-		}
269
-	}
270
-
271
-	protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, string $sourceInternalPath, string $targetInternalPath): void {
272
-		if ($this->updaterEnabled) {
273
-			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
274
-		}
275
-	}
276
-
277
-	protected function copyUpdate(Storage $sourceStorage, Storage $targetStorage, string $sourceInternalPath, string $targetInternalPath): void {
278
-		if ($this->updaterEnabled) {
279
-			$targetStorage->getUpdater()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
280
-		}
281
-	}
282
-
283
-	/**
284
-	 * @param string $path
285
-	 * @return bool|mixed
286
-	 */
287
-	public function rmdir($path) {
288
-		$absolutePath = $this->getAbsolutePath($path);
289
-		$mount = Filesystem::getMountManager()->find($absolutePath);
290
-		if ($mount->getInternalPath($absolutePath) === '') {
291
-			return $this->removeMount($mount, $absolutePath);
292
-		}
293
-		if ($this->is_dir($path)) {
294
-			$result = $this->basicOperation('rmdir', $path, ['delete']);
295
-		} else {
296
-			$result = false;
297
-		}
298
-
299
-		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
300
-			$storage = $mount->getStorage();
301
-			$internalPath = $mount->getInternalPath($absolutePath);
302
-			$storage->getUpdater()->remove($internalPath);
303
-		}
304
-		return $result;
305
-	}
306
-
307
-	/**
308
-	 * @param string $path
309
-	 * @return resource|false
310
-	 */
311
-	public function opendir($path) {
312
-		return $this->basicOperation('opendir', $path, ['read']);
313
-	}
314
-
315
-	/**
316
-	 * @param string $path
317
-	 * @return bool|mixed
318
-	 */
319
-	public function is_dir($path) {
320
-		if ($path == '/') {
321
-			return true;
322
-		}
323
-		return $this->basicOperation('is_dir', $path);
324
-	}
325
-
326
-	/**
327
-	 * @param string $path
328
-	 * @return bool|mixed
329
-	 */
330
-	public function is_file($path) {
331
-		if ($path == '/') {
332
-			return false;
333
-		}
334
-		return $this->basicOperation('is_file', $path);
335
-	}
336
-
337
-	/**
338
-	 * @param string $path
339
-	 * @return mixed
340
-	 */
341
-	public function stat($path) {
342
-		return $this->basicOperation('stat', $path);
343
-	}
344
-
345
-	/**
346
-	 * @param string $path
347
-	 * @return mixed
348
-	 */
349
-	public function filetype($path) {
350
-		return $this->basicOperation('filetype', $path);
351
-	}
352
-
353
-	/**
354
-	 * @param string $path
355
-	 * @return mixed
356
-	 */
357
-	public function filesize(string $path) {
358
-		return $this->basicOperation('filesize', $path);
359
-	}
360
-
361
-	/**
362
-	 * @param string $path
363
-	 * @return bool|mixed
364
-	 * @throws InvalidPathException
365
-	 */
366
-	public function readfile($path) {
367
-		$this->assertPathLength($path);
368
-		if (ob_get_level()) {
369
-			ob_end_clean();
370
-		}
371
-		$handle = $this->fopen($path, 'rb');
372
-		if ($handle) {
373
-			$chunkSize = 524288; // 512 kiB chunks
374
-			while (!feof($handle)) {
375
-				echo fread($handle, $chunkSize);
376
-				flush();
377
-				$this->checkConnectionStatus();
378
-			}
379
-			fclose($handle);
380
-			return $this->filesize($path);
381
-		}
382
-		return false;
383
-	}
384
-
385
-	/**
386
-	 * @param string $path
387
-	 * @param int $from
388
-	 * @param int $to
389
-	 * @return bool|mixed
390
-	 * @throws InvalidPathException
391
-	 * @throws \OCP\Files\UnseekableException
392
-	 */
393
-	public function readfilePart($path, $from, $to) {
394
-		$this->assertPathLength($path);
395
-		if (ob_get_level()) {
396
-			ob_end_clean();
397
-		}
398
-		$handle = $this->fopen($path, 'rb');
399
-		if ($handle) {
400
-			$chunkSize = 524288; // 512 kiB chunks
401
-			$startReading = true;
402
-
403
-			if ($from !== 0 && $from !== '0' && fseek($handle, $from) !== 0) {
404
-				// forward file handle via chunked fread because fseek seem to have failed
405
-
406
-				$end = $from + 1;
407
-				while (!feof($handle) && ftell($handle) < $end && ftell($handle) !== $from) {
408
-					$len = $from - ftell($handle);
409
-					if ($len > $chunkSize) {
410
-						$len = $chunkSize;
411
-					}
412
-					$result = fread($handle, $len);
413
-
414
-					if ($result === false) {
415
-						$startReading = false;
416
-						break;
417
-					}
418
-				}
419
-			}
420
-
421
-			if ($startReading) {
422
-				$end = $to + 1;
423
-				while (!feof($handle) && ftell($handle) < $end) {
424
-					$len = $end - ftell($handle);
425
-					if ($len > $chunkSize) {
426
-						$len = $chunkSize;
427
-					}
428
-					echo fread($handle, $len);
429
-					flush();
430
-					$this->checkConnectionStatus();
431
-				}
432
-				return ftell($handle) - $from;
433
-			}
434
-
435
-			throw new \OCP\Files\UnseekableException('fseek error');
436
-		}
437
-		return false;
438
-	}
439
-
440
-	private function checkConnectionStatus(): void {
441
-		$connectionStatus = \connection_status();
442
-		if ($connectionStatus !== CONNECTION_NORMAL) {
443
-			throw new ConnectionLostException("Connection lost. Status: $connectionStatus");
444
-		}
445
-	}
446
-
447
-	/**
448
-	 * @param string $path
449
-	 * @return mixed
450
-	 */
451
-	public function isCreatable($path) {
452
-		return $this->basicOperation('isCreatable', $path);
453
-	}
454
-
455
-	/**
456
-	 * @param string $path
457
-	 * @return mixed
458
-	 */
459
-	public function isReadable($path) {
460
-		return $this->basicOperation('isReadable', $path);
461
-	}
462
-
463
-	/**
464
-	 * @param string $path
465
-	 * @return mixed
466
-	 */
467
-	public function isUpdatable($path) {
468
-		return $this->basicOperation('isUpdatable', $path);
469
-	}
470
-
471
-	/**
472
-	 * @param string $path
473
-	 * @return bool|mixed
474
-	 */
475
-	public function isDeletable($path) {
476
-		$absolutePath = $this->getAbsolutePath($path);
477
-		$mount = Filesystem::getMountManager()->find($absolutePath);
478
-		if ($mount->getInternalPath($absolutePath) === '') {
479
-			return $mount instanceof MoveableMount;
480
-		}
481
-		return $this->basicOperation('isDeletable', $path);
482
-	}
483
-
484
-	/**
485
-	 * @param string $path
486
-	 * @return mixed
487
-	 */
488
-	public function isSharable($path) {
489
-		return $this->basicOperation('isSharable', $path);
490
-	}
491
-
492
-	/**
493
-	 * @param string $path
494
-	 * @return bool|mixed
495
-	 */
496
-	public function file_exists($path) {
497
-		if ($path == '/') {
498
-			return true;
499
-		}
500
-		return $this->basicOperation('file_exists', $path);
501
-	}
502
-
503
-	/**
504
-	 * @param string $path
505
-	 * @return mixed
506
-	 */
507
-	public function filemtime($path) {
508
-		return $this->basicOperation('filemtime', $path);
509
-	}
510
-
511
-	/**
512
-	 * @param string $path
513
-	 * @param int|string $mtime
514
-	 */
515
-	public function touch($path, $mtime = null): bool {
516
-		if (!is_null($mtime) && !is_numeric($mtime)) {
517
-			$mtime = strtotime($mtime);
518
-		}
519
-
520
-		$hooks = ['touch'];
521
-
522
-		if (!$this->file_exists($path)) {
523
-			$hooks[] = 'create';
524
-			$hooks[] = 'write';
525
-		}
526
-		try {
527
-			$result = $this->basicOperation('touch', $path, $hooks, $mtime);
528
-		} catch (\Exception $e) {
529
-			$this->logger->info('Error while setting modified time', ['app' => 'core', 'exception' => $e]);
530
-			$result = false;
531
-		}
532
-		if (!$result) {
533
-			// If create file fails because of permissions on external storage like SMB folders,
534
-			// check file exists and return false if not.
535
-			if (!$this->file_exists($path)) {
536
-				return false;
537
-			}
538
-			if (is_null($mtime)) {
539
-				$mtime = time();
540
-			}
541
-			//if native touch fails, we emulate it by changing the mtime in the cache
542
-			$this->putFileInfo($path, ['mtime' => floor($mtime)]);
543
-		}
544
-		return true;
545
-	}
546
-
547
-	/**
548
-	 * @param string $path
549
-	 * @return string|false
550
-	 * @throws LockedException
551
-	 */
552
-	public function file_get_contents($path) {
553
-		return $this->basicOperation('file_get_contents', $path, ['read']);
554
-	}
555
-
556
-	protected function emit_file_hooks_pre(bool $exists, string $path, bool &$run): void {
557
-		if (!$exists) {
558
-			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
559
-				Filesystem::signal_param_path => $this->getHookPath($path),
560
-				Filesystem::signal_param_run => &$run,
561
-			]);
562
-		} else {
563
-			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
564
-				Filesystem::signal_param_path => $this->getHookPath($path),
565
-				Filesystem::signal_param_run => &$run,
566
-			]);
567
-		}
568
-		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
569
-			Filesystem::signal_param_path => $this->getHookPath($path),
570
-			Filesystem::signal_param_run => &$run,
571
-		]);
572
-	}
573
-
574
-	protected function emit_file_hooks_post(bool $exists, string $path): void {
575
-		if (!$exists) {
576
-			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
577
-				Filesystem::signal_param_path => $this->getHookPath($path),
578
-			]);
579
-		} else {
580
-			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
581
-				Filesystem::signal_param_path => $this->getHookPath($path),
582
-			]);
583
-		}
584
-		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
585
-			Filesystem::signal_param_path => $this->getHookPath($path),
586
-		]);
587
-	}
588
-
589
-	/**
590
-	 * @param string $path
591
-	 * @param string|resource $data
592
-	 * @return bool|mixed
593
-	 * @throws LockedException
594
-	 */
595
-	public function file_put_contents($path, $data) {
596
-		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
597
-			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
598
-			if (Filesystem::isValidPath($path)
599
-				&& !Filesystem::isFileBlacklisted($path)
600
-			) {
601
-				$path = $this->getRelativePath($absolutePath);
602
-				if ($path === null) {
603
-					throw new InvalidPathException("Path $absolutePath is not in the expected root");
604
-				}
605
-
606
-				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
607
-
608
-				$exists = $this->file_exists($path);
609
-				if ($this->shouldEmitHooks($path)) {
610
-					$run = true;
611
-					$this->emit_file_hooks_pre($exists, $path, $run);
612
-					if (!$run) {
613
-						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
614
-						return false;
615
-					}
616
-				}
617
-
618
-				try {
619
-					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
620
-				} catch (\Exception $e) {
621
-					// Release the shared lock before throwing.
622
-					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
623
-					throw $e;
624
-				}
625
-
626
-				/** @var Storage $storage */
627
-				[$storage, $internalPath] = $this->resolvePath($path);
628
-				$target = $storage->fopen($internalPath, 'w');
629
-				if ($target) {
630
-					[, $result] = Files::streamCopy($data, $target, true);
631
-					fclose($target);
632
-					fclose($data);
633
-
634
-					$this->writeUpdate($storage, $internalPath);
635
-
636
-					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
637
-
638
-					if ($this->shouldEmitHooks($path) && $result !== false) {
639
-						$this->emit_file_hooks_post($exists, $path);
640
-					}
641
-					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
642
-					return $result;
643
-				} else {
644
-					$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
645
-					return false;
646
-				}
647
-			} else {
648
-				return false;
649
-			}
650
-		} else {
651
-			$hooks = $this->file_exists($path) ? ['update', 'write'] : ['create', 'write'];
652
-			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
653
-		}
654
-	}
655
-
656
-	/**
657
-	 * @param string $path
658
-	 * @return bool|mixed
659
-	 */
660
-	public function unlink($path) {
661
-		if ($path === '' || $path === '/') {
662
-			// do not allow deleting the root
663
-			return false;
664
-		}
665
-		$postFix = (substr($path, -1) === '/') ? '/' : '';
666
-		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
667
-		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
668
-		if ($mount->getInternalPath($absolutePath) === '') {
669
-			return $this->removeMount($mount, $absolutePath);
670
-		}
671
-		if ($this->is_dir($path)) {
672
-			$result = $this->basicOperation('rmdir', $path, ['delete']);
673
-		} else {
674
-			$result = $this->basicOperation('unlink', $path, ['delete']);
675
-		}
676
-		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
677
-			$storage = $mount->getStorage();
678
-			$internalPath = $mount->getInternalPath($absolutePath);
679
-			$storage->getUpdater()->remove($internalPath);
680
-			return true;
681
-		} else {
682
-			return $result;
683
-		}
684
-	}
685
-
686
-	/**
687
-	 * @param string $directory
688
-	 * @return bool|mixed
689
-	 */
690
-	public function deleteAll($directory) {
691
-		return $this->rmdir($directory);
692
-	}
693
-
694
-	/**
695
-	 * Rename/move a file or folder from the source path to target path.
696
-	 *
697
-	 * @param string $source source path
698
-	 * @param string $target target path
699
-	 * @param array $options
700
-	 *
701
-	 * @return bool|mixed
702
-	 * @throws LockedException
703
-	 */
704
-	public function rename($source, $target, array $options = []) {
705
-		$checkSubMounts = $options['checkSubMounts'] ?? true;
706
-
707
-		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($source));
708
-		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($target));
709
-
710
-		if (str_starts_with($absolutePath2, $absolutePath1 . '/')) {
711
-			throw new ForbiddenException('Moving a folder into a child folder is forbidden', false);
712
-		}
713
-
714
-		/** @var IMountManager $mountManager */
715
-		$mountManager = \OC::$server->get(IMountManager::class);
716
-
717
-		$targetParts = explode('/', $absolutePath2);
718
-		$targetUser = $targetParts[1] ?? null;
719
-		$result = false;
720
-		if (
721
-			Filesystem::isValidPath($target)
722
-			&& Filesystem::isValidPath($source)
723
-			&& !Filesystem::isFileBlacklisted($target)
724
-		) {
725
-			$source = $this->getRelativePath($absolutePath1);
726
-			$target = $this->getRelativePath($absolutePath2);
727
-			$exists = $this->file_exists($target);
728
-
729
-			if ($source == null || $target == null) {
730
-				return false;
731
-			}
732
-
733
-			try {
734
-				$this->verifyPath(dirname($target), basename($target));
735
-			} catch (InvalidPathException) {
736
-				return false;
737
-			}
738
-
739
-			$this->lockFile($source, ILockingProvider::LOCK_SHARED, true);
740
-			try {
741
-				$this->lockFile($target, ILockingProvider::LOCK_SHARED, true);
742
-
743
-				$run = true;
744
-				if ($this->shouldEmitHooks($source) && (Cache\Scanner::isPartialFile($source) && !Cache\Scanner::isPartialFile($target))) {
745
-					// if it was a rename from a part file to a regular file it was a write and not a rename operation
746
-					$this->emit_file_hooks_pre($exists, $target, $run);
747
-				} elseif ($this->shouldEmitHooks($source)) {
748
-					$sourcePath = $this->getHookPath($source);
749
-					$targetPath = $this->getHookPath($target);
750
-					if ($sourcePath !== null && $targetPath !== null) {
751
-						\OC_Hook::emit(
752
-							Filesystem::CLASSNAME, Filesystem::signal_rename,
753
-							[
754
-								Filesystem::signal_param_oldpath => $sourcePath,
755
-								Filesystem::signal_param_newpath => $targetPath,
756
-								Filesystem::signal_param_run => &$run
757
-							]
758
-						);
759
-					}
760
-				}
761
-				if ($run) {
762
-					$manager = Filesystem::getMountManager();
763
-					$mount1 = $this->getMount($source);
764
-					$mount2 = $this->getMount($target);
765
-					$storage1 = $mount1->getStorage();
766
-					$storage2 = $mount2->getStorage();
767
-					$internalPath1 = $mount1->getInternalPath($absolutePath1);
768
-					$internalPath2 = $mount2->getInternalPath($absolutePath2);
769
-
770
-					$this->changeLock($source, ILockingProvider::LOCK_EXCLUSIVE, true);
771
-					try {
772
-						$this->changeLock($target, ILockingProvider::LOCK_EXCLUSIVE, true);
773
-
774
-						if ($checkSubMounts) {
775
-							$movedMounts = $mountManager->findIn($this->getAbsolutePath($source));
776
-						} else {
777
-							$movedMounts = [];
778
-						}
779
-
780
-						if ($internalPath1 === '') {
781
-							$sourceParentMount = $this->getMount(dirname($source));
782
-							$movedMounts[] = $mount1;
783
-							$this->validateMountMove($movedMounts, $sourceParentMount, $mount2, !$this->targetIsNotShared($targetUser, $absolutePath2));
784
-							/**
785
-							 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
786
-							 */
787
-							$sourceMountPoint = $mount1->getMountPoint();
788
-							$result = $mount1->moveMount($absolutePath2);
789
-							$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
790
-
791
-							// moving a file/folder within the same mount point
792
-						} elseif ($storage1 === $storage2) {
793
-							if (count($movedMounts) > 0) {
794
-								$this->validateMountMove($movedMounts, $mount1, $mount2, !$this->targetIsNotShared($targetUser, $absolutePath2));
795
-							}
796
-							if ($storage1) {
797
-								$result = $storage1->rename($internalPath1, $internalPath2);
798
-							} else {
799
-								$result = false;
800
-							}
801
-							// moving a file/folder between storages (from $storage1 to $storage2)
802
-						} else {
803
-							if (count($movedMounts) > 0) {
804
-								$this->validateMountMove($movedMounts, $mount1, $mount2, !$this->targetIsNotShared($targetUser, $absolutePath2));
805
-							}
806
-							$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
807
-						}
808
-
809
-						if ((Cache\Scanner::isPartialFile($source) && !Cache\Scanner::isPartialFile($target)) && $result !== false) {
810
-							// if it was a rename from a part file to a regular file it was a write and not a rename operation
811
-							$this->writeUpdate($storage2, $internalPath2);
812
-						} elseif ($result) {
813
-							if ($internalPath1 !== '') { // don't do a cache update for moved mounts
814
-								$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
815
-							}
816
-						}
817
-					} catch (\Exception $e) {
818
-						throw $e;
819
-					} finally {
820
-						$this->changeLock($source, ILockingProvider::LOCK_SHARED, true);
821
-						$this->changeLock($target, ILockingProvider::LOCK_SHARED, true);
822
-					}
823
-
824
-					if ((Cache\Scanner::isPartialFile($source) && !Cache\Scanner::isPartialFile($target)) && $result !== false) {
825
-						if ($this->shouldEmitHooks()) {
826
-							$this->emit_file_hooks_post($exists, $target);
827
-						}
828
-					} elseif ($result) {
829
-						if ($this->shouldEmitHooks($source) && $this->shouldEmitHooks($target)) {
830
-							$sourcePath = $this->getHookPath($source);
831
-							$targetPath = $this->getHookPath($target);
832
-							if ($sourcePath !== null && $targetPath !== null) {
833
-								\OC_Hook::emit(
834
-									Filesystem::CLASSNAME,
835
-									Filesystem::signal_post_rename,
836
-									[
837
-										Filesystem::signal_param_oldpath => $sourcePath,
838
-										Filesystem::signal_param_newpath => $targetPath,
839
-									]
840
-								);
841
-							}
842
-						}
843
-					}
844
-				}
845
-			} catch (\Exception $e) {
846
-				throw $e;
847
-			} finally {
848
-				$this->unlockFile($source, ILockingProvider::LOCK_SHARED, true);
849
-				$this->unlockFile($target, ILockingProvider::LOCK_SHARED, true);
850
-			}
851
-		}
852
-		return $result;
853
-	}
854
-
855
-	/**
856
-	 * @throws ForbiddenException
857
-	 */
858
-	private function validateMountMove(array $mounts, IMountPoint $sourceMount, IMountPoint $targetMount, bool $targetIsShared): void {
859
-		$targetPath = $this->getRelativePath($targetMount->getMountPoint());
860
-		if ($targetPath) {
861
-			$targetPath = trim($targetPath, '/');
862
-		} else {
863
-			$targetPath = $targetMount->getMountPoint();
864
-		}
865
-
866
-		$l = \OC::$server->get(IFactory::class)->get('files');
867
-		foreach ($mounts as $mount) {
868
-			$sourcePath = $this->getRelativePath($mount->getMountPoint());
869
-			if ($sourcePath) {
870
-				$sourcePath = trim($sourcePath, '/');
871
-			} else {
872
-				$sourcePath = $mount->getMountPoint();
873
-			}
874
-
875
-			if (!$mount instanceof MoveableMount) {
876
-				throw new ForbiddenException($l->t('Storage %s cannot be moved', [$sourcePath]), false);
877
-			}
878
-
879
-			if ($targetIsShared) {
880
-				if ($sourceMount instanceof SharedMount) {
881
-					throw new ForbiddenException($l->t('Moving a share (%s) into a shared folder is not allowed', [$sourcePath]), false);
882
-				} else {
883
-					throw new ForbiddenException($l->t('Moving a storage (%s) into a shared folder is not allowed', [$sourcePath]), false);
884
-				}
885
-			}
886
-
887
-			if ($sourceMount !== $targetMount) {
888
-				if ($sourceMount instanceof SharedMount) {
889
-					if ($targetMount instanceof SharedMount) {
890
-						throw new ForbiddenException($l->t('Moving a share (%s) into another share (%s) is not allowed', [$sourcePath, $targetPath]), false);
891
-					} else {
892
-						throw new ForbiddenException($l->t('Moving a share (%s) into another storage (%s) is not allowed', [$sourcePath, $targetPath]), false);
893
-					}
894
-				} else {
895
-					if ($targetMount instanceof SharedMount) {
896
-						throw new ForbiddenException($l->t('Moving a storage (%s) into a share (%s) is not allowed', [$sourcePath, $targetPath]), false);
897
-					} else {
898
-						throw new ForbiddenException($l->t('Moving a storage (%s) into another storage (%s) is not allowed', [$sourcePath, $targetPath]), false);
899
-					}
900
-				}
901
-			}
902
-		}
903
-	}
904
-
905
-	/**
906
-	 * Copy a file/folder from the source path to target path
907
-	 *
908
-	 * @param string $source source path
909
-	 * @param string $target target path
910
-	 * @param bool $preserveMtime whether to preserve mtime on the copy
911
-	 *
912
-	 * @return bool|mixed
913
-	 */
914
-	public function copy($source, $target, $preserveMtime = false) {
915
-		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($source));
916
-		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($target));
917
-		$result = false;
918
-		if (
919
-			Filesystem::isValidPath($target)
920
-			&& Filesystem::isValidPath($source)
921
-			&& !Filesystem::isFileBlacklisted($target)
922
-		) {
923
-			$source = $this->getRelativePath($absolutePath1);
924
-			$target = $this->getRelativePath($absolutePath2);
925
-
926
-			if ($source == null || $target == null) {
927
-				return false;
928
-			}
929
-			$run = true;
930
-
931
-			$this->lockFile($target, ILockingProvider::LOCK_SHARED);
932
-			$this->lockFile($source, ILockingProvider::LOCK_SHARED);
933
-			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
934
-			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
935
-
936
-			try {
937
-				$exists = $this->file_exists($target);
938
-				if ($this->shouldEmitHooks($target)) {
939
-					\OC_Hook::emit(
940
-						Filesystem::CLASSNAME,
941
-						Filesystem::signal_copy,
942
-						[
943
-							Filesystem::signal_param_oldpath => $this->getHookPath($source),
944
-							Filesystem::signal_param_newpath => $this->getHookPath($target),
945
-							Filesystem::signal_param_run => &$run
946
-						]
947
-					);
948
-					$this->emit_file_hooks_pre($exists, $target, $run);
949
-				}
950
-				if ($run) {
951
-					$mount1 = $this->getMount($source);
952
-					$mount2 = $this->getMount($target);
953
-					$storage1 = $mount1->getStorage();
954
-					$internalPath1 = $mount1->getInternalPath($absolutePath1);
955
-					$storage2 = $mount2->getStorage();
956
-					$internalPath2 = $mount2->getInternalPath($absolutePath2);
957
-
958
-					$this->changeLock($target, ILockingProvider::LOCK_EXCLUSIVE);
959
-					$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
960
-
961
-					if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
962
-						if ($storage1) {
963
-							$result = $storage1->copy($internalPath1, $internalPath2);
964
-						} else {
965
-							$result = false;
966
-						}
967
-					} else {
968
-						$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
969
-					}
970
-
971
-					if ($result) {
972
-						$this->copyUpdate($storage1, $storage2, $internalPath1, $internalPath2);
973
-					}
974
-
975
-					$this->changeLock($target, ILockingProvider::LOCK_SHARED);
976
-					$lockTypePath2 = ILockingProvider::LOCK_SHARED;
977
-
978
-					if ($this->shouldEmitHooks($target) && $result !== false) {
979
-						\OC_Hook::emit(
980
-							Filesystem::CLASSNAME,
981
-							Filesystem::signal_post_copy,
982
-							[
983
-								Filesystem::signal_param_oldpath => $this->getHookPath($source),
984
-								Filesystem::signal_param_newpath => $this->getHookPath($target)
985
-							]
986
-						);
987
-						$this->emit_file_hooks_post($exists, $target);
988
-					}
989
-				}
990
-			} catch (\Exception $e) {
991
-				$this->unlockFile($target, $lockTypePath2);
992
-				$this->unlockFile($source, $lockTypePath1);
993
-				throw $e;
994
-			}
995
-
996
-			$this->unlockFile($target, $lockTypePath2);
997
-			$this->unlockFile($source, $lockTypePath1);
998
-		}
999
-		return $result;
1000
-	}
1001
-
1002
-	/**
1003
-	 * @param string $path
1004
-	 * @param string $mode 'r' or 'w'
1005
-	 * @return resource|false
1006
-	 * @throws LockedException
1007
-	 */
1008
-	public function fopen($path, $mode) {
1009
-		$mode = str_replace('b', '', $mode); // the binary flag is a windows only feature which we do not support
1010
-		$hooks = [];
1011
-		switch ($mode) {
1012
-			case 'r':
1013
-				$hooks[] = 'read';
1014
-				break;
1015
-			case 'r+':
1016
-			case 'w+':
1017
-			case 'x+':
1018
-			case 'a+':
1019
-				$hooks[] = 'read';
1020
-				$hooks[] = 'write';
1021
-				break;
1022
-			case 'w':
1023
-			case 'x':
1024
-			case 'a':
1025
-				$hooks[] = 'write';
1026
-				break;
1027
-			default:
1028
-				$this->logger->error('invalid mode (' . $mode . ') for ' . $path, ['app' => 'core']);
1029
-		}
1030
-
1031
-		if ($mode !== 'r' && $mode !== 'w') {
1032
-			$this->logger->info('Trying to open a file with a mode other than "r" or "w" can cause severe performance issues with some backends', ['app' => 'core']);
1033
-		}
1034
-
1035
-		$handle = $this->basicOperation('fopen', $path, $hooks, $mode);
1036
-		if (!is_resource($handle) && $mode === 'r') {
1037
-			// trying to read a file that isn't on disk, check if the cache is out of sync and rescan if needed
1038
-			$mount = $this->getMount($path);
1039
-			$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1040
-			$storage = $mount->getStorage();
1041
-			if ($storage->getCache()->inCache($internalPath) && !$storage->file_exists($path)) {
1042
-				$this->writeUpdate($storage, $internalPath);
1043
-			}
1044
-		}
1045
-		return $handle;
1046
-	}
1047
-
1048
-	/**
1049
-	 * @param string $path
1050
-	 * @throws InvalidPathException
1051
-	 */
1052
-	public function toTmpFile($path): string|false {
1053
-		$this->assertPathLength($path);
1054
-		if (Filesystem::isValidPath($path)) {
1055
-			$source = $this->fopen($path, 'r');
1056
-			if ($source) {
1057
-				$extension = pathinfo($path, PATHINFO_EXTENSION);
1058
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
1059
-				file_put_contents($tmpFile, $source);
1060
-				return $tmpFile;
1061
-			} else {
1062
-				return false;
1063
-			}
1064
-		} else {
1065
-			return false;
1066
-		}
1067
-	}
1068
-
1069
-	/**
1070
-	 * @param string $tmpFile
1071
-	 * @param string $path
1072
-	 * @return bool|mixed
1073
-	 * @throws InvalidPathException
1074
-	 */
1075
-	public function fromTmpFile($tmpFile, $path) {
1076
-		$this->assertPathLength($path);
1077
-		if (Filesystem::isValidPath($path)) {
1078
-			// Get directory that the file is going into
1079
-			$filePath = dirname($path);
1080
-
1081
-			// Create the directories if any
1082
-			if (!$this->file_exists($filePath)) {
1083
-				$result = $this->createParentDirectories($filePath);
1084
-				if ($result === false) {
1085
-					return false;
1086
-				}
1087
-			}
1088
-
1089
-			$source = fopen($tmpFile, 'r');
1090
-			if ($source) {
1091
-				$result = $this->file_put_contents($path, $source);
1092
-				/**
1093
-				 * $this->file_put_contents() might have already closed
1094
-				 * the resource, so we check it, before trying to close it
1095
-				 * to avoid messages in the error log.
1096
-				 * @psalm-suppress RedundantCondition false-positive
1097
-				 */
1098
-				if (is_resource($source)) {
1099
-					fclose($source);
1100
-				}
1101
-				unlink($tmpFile);
1102
-				return $result;
1103
-			} else {
1104
-				return false;
1105
-			}
1106
-		} else {
1107
-			return false;
1108
-		}
1109
-	}
1110
-
1111
-
1112
-	/**
1113
-	 * @param string $path
1114
-	 * @return mixed
1115
-	 * @throws InvalidPathException
1116
-	 */
1117
-	public function getMimeType($path) {
1118
-		$this->assertPathLength($path);
1119
-		return $this->basicOperation('getMimeType', $path);
1120
-	}
1121
-
1122
-	/**
1123
-	 * @param string $type
1124
-	 * @param string $path
1125
-	 * @param bool $raw
1126
-	 */
1127
-	public function hash($type, $path, $raw = false): string|bool {
1128
-		$postFix = (substr($path, -1) === '/') ? '/' : '';
1129
-		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1130
-		if (Filesystem::isValidPath($path)) {
1131
-			$path = $this->getRelativePath($absolutePath);
1132
-			if ($path == null) {
1133
-				return false;
1134
-			}
1135
-			if ($this->shouldEmitHooks($path)) {
1136
-				\OC_Hook::emit(
1137
-					Filesystem::CLASSNAME,
1138
-					Filesystem::signal_read,
1139
-					[Filesystem::signal_param_path => $this->getHookPath($path)]
1140
-				);
1141
-			}
1142
-			/** @var Storage|null $storage */
1143
-			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1144
-			if ($storage) {
1145
-				return $storage->hash($type, $internalPath, $raw);
1146
-			}
1147
-		}
1148
-		return false;
1149
-	}
1150
-
1151
-	/**
1152
-	 * @param string $path
1153
-	 * @return mixed
1154
-	 * @throws InvalidPathException
1155
-	 */
1156
-	public function free_space($path = '/') {
1157
-		$this->assertPathLength($path);
1158
-		$result = $this->basicOperation('free_space', $path);
1159
-		if ($result === null) {
1160
-			throw new InvalidPathException();
1161
-		}
1162
-		return $result;
1163
-	}
1164
-
1165
-	/**
1166
-	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1167
-	 *
1168
-	 * @param mixed $extraParam (optional)
1169
-	 * @return mixed
1170
-	 * @throws LockedException
1171
-	 *
1172
-	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1173
-	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1174
-	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1175
-	 */
1176
-	private function basicOperation(string $operation, string $path, array $hooks = [], $extraParam = null) {
1177
-		$postFix = (substr($path, -1) === '/') ? '/' : '';
1178
-		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1179
-		if (Filesystem::isValidPath($path)
1180
-			&& !Filesystem::isFileBlacklisted($path)
1181
-		) {
1182
-			$path = $this->getRelativePath($absolutePath);
1183
-			if ($path == null) {
1184
-				return false;
1185
-			}
1186
-
1187
-			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1188
-				// always a shared lock during pre-hooks so the hook can read the file
1189
-				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1190
-			}
1191
-
1192
-			$run = $this->runHooks($hooks, $path);
1193
-			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1194
-			if ($run && $storage) {
1195
-				/** @var Storage $storage */
1196
-				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1197
-					try {
1198
-						$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1199
-					} catch (LockedException $e) {
1200
-						// release the shared lock we acquired before quitting
1201
-						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1202
-						throw $e;
1203
-					}
1204
-				}
1205
-				try {
1206
-					if (!is_null($extraParam)) {
1207
-						$result = $storage->$operation($internalPath, $extraParam);
1208
-					} else {
1209
-						$result = $storage->$operation($internalPath);
1210
-					}
1211
-				} catch (\Exception $e) {
1212
-					if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1213
-						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1214
-					} elseif (in_array('read', $hooks)) {
1215
-						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1216
-					}
1217
-					throw $e;
1218
-				}
1219
-
1220
-				if ($result !== false && in_array('delete', $hooks)) {
1221
-					$this->removeUpdate($storage, $internalPath);
1222
-				}
1223
-				if ($result !== false && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') {
1224
-					$isCreateOperation = $operation === 'mkdir' || ($operation === 'file_put_contents' && in_array('create', $hooks, true));
1225
-					$sizeDifference = $operation === 'mkdir' ? 0 : $result;
1226
-					$this->writeUpdate($storage, $internalPath, null, $isCreateOperation ? $sizeDifference : null);
1227
-				}
1228
-				if ($result !== false && in_array('touch', $hooks)) {
1229
-					$this->writeUpdate($storage, $internalPath, $extraParam, 0);
1230
-				}
1231
-
1232
-				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1233
-					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1234
-				}
1235
-
1236
-				$unlockLater = false;
1237
-				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1238
-					$unlockLater = true;
1239
-					// make sure our unlocking callback will still be called if connection is aborted
1240
-					ignore_user_abort(true);
1241
-					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1242
-						if (in_array('write', $hooks)) {
1243
-							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1244
-						} elseif (in_array('read', $hooks)) {
1245
-							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1246
-						}
1247
-					});
1248
-				}
1249
-
1250
-				if ($this->shouldEmitHooks($path) && $result !== false) {
1251
-					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1252
-						$this->runHooks($hooks, $path, true);
1253
-					}
1254
-				}
1255
-
1256
-				if (!$unlockLater
1257
-					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1258
-				) {
1259
-					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1260
-				}
1261
-				return $result;
1262
-			} else {
1263
-				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1264
-			}
1265
-		}
1266
-		return null;
1267
-	}
1268
-
1269
-	/**
1270
-	 * get the path relative to the default root for hook usage
1271
-	 *
1272
-	 * @param string $path
1273
-	 * @return ?string
1274
-	 */
1275
-	private function getHookPath($path): ?string {
1276
-		$view = Filesystem::getView();
1277
-		if (!$view) {
1278
-			return $path;
1279
-		}
1280
-		return $view->getRelativePath($this->getAbsolutePath($path));
1281
-	}
1282
-
1283
-	private function shouldEmitHooks(string $path = ''): bool {
1284
-		if ($path && Cache\Scanner::isPartialFile($path)) {
1285
-			return false;
1286
-		}
1287
-		if (!Filesystem::$loaded) {
1288
-			return false;
1289
-		}
1290
-		$defaultRoot = Filesystem::getRoot();
1291
-		if ($defaultRoot === null) {
1292
-			return false;
1293
-		}
1294
-		if ($this->fakeRoot === $defaultRoot) {
1295
-			return true;
1296
-		}
1297
-		$fullPath = $this->getAbsolutePath($path);
1298
-
1299
-		if ($fullPath === $defaultRoot) {
1300
-			return true;
1301
-		}
1302
-
1303
-		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1304
-	}
1305
-
1306
-	/**
1307
-	 * @param string[] $hooks
1308
-	 * @param string $path
1309
-	 * @param bool $post
1310
-	 * @return bool
1311
-	 */
1312
-	private function runHooks($hooks, $path, $post = false) {
1313
-		$relativePath = $path;
1314
-		$path = $this->getHookPath($path);
1315
-		$prefix = $post ? 'post_' : '';
1316
-		$run = true;
1317
-		if ($this->shouldEmitHooks($relativePath)) {
1318
-			foreach ($hooks as $hook) {
1319
-				if ($hook != 'read') {
1320
-					\OC_Hook::emit(
1321
-						Filesystem::CLASSNAME,
1322
-						$prefix . $hook,
1323
-						[
1324
-							Filesystem::signal_param_run => &$run,
1325
-							Filesystem::signal_param_path => $path
1326
-						]
1327
-					);
1328
-				} elseif (!$post) {
1329
-					\OC_Hook::emit(
1330
-						Filesystem::CLASSNAME,
1331
-						$prefix . $hook,
1332
-						[
1333
-							Filesystem::signal_param_path => $path
1334
-						]
1335
-					);
1336
-				}
1337
-			}
1338
-		}
1339
-		return $run;
1340
-	}
1341
-
1342
-	/**
1343
-	 * check if a file or folder has been updated since $time
1344
-	 *
1345
-	 * @param string $path
1346
-	 * @param int $time
1347
-	 * @return bool
1348
-	 */
1349
-	public function hasUpdated($path, $time) {
1350
-		return $this->basicOperation('hasUpdated', $path, [], $time);
1351
-	}
1352
-
1353
-	/**
1354
-	 * @param string $ownerId
1355
-	 * @return IUser
1356
-	 */
1357
-	private function getUserObjectForOwner(string $ownerId) {
1358
-		return new LazyUser($ownerId, $this->userManager);
1359
-	}
1360
-
1361
-	/**
1362
-	 * Get file info from cache
1363
-	 *
1364
-	 * If the file is not in cached it will be scanned
1365
-	 * If the file has changed on storage the cache will be updated
1366
-	 *
1367
-	 * @param Storage $storage
1368
-	 * @param string $internalPath
1369
-	 * @param string $relativePath
1370
-	 * @return ICacheEntry|bool
1371
-	 */
1372
-	private function getCacheEntry($storage, $internalPath, $relativePath) {
1373
-		$cache = $storage->getCache($internalPath);
1374
-		$data = $cache->get($internalPath);
1375
-		$watcher = $storage->getWatcher($internalPath);
1376
-
1377
-		try {
1378
-			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1379
-			if (!$data || (isset($data['size']) && $data['size'] === -1)) {
1380
-				if (!$storage->file_exists($internalPath)) {
1381
-					return false;
1382
-				}
1383
-				// don't need to get a lock here since the scanner does it's own locking
1384
-				$scanner = $storage->getScanner($internalPath);
1385
-				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1386
-				$data = $cache->get($internalPath);
1387
-			} elseif (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1388
-				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1389
-				$watcher->update($internalPath, $data);
1390
-				$storage->getPropagator()->propagateChange($internalPath, time());
1391
-				$data = $cache->get($internalPath);
1392
-				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1393
-			}
1394
-		} catch (LockedException $e) {
1395
-			// if the file is locked we just use the old cache info
1396
-		}
1397
-
1398
-		return $data;
1399
-	}
1400
-
1401
-	/**
1402
-	 * get the filesystem info
1403
-	 *
1404
-	 * @param string $path
1405
-	 * @param bool|string $includeMountPoints true to add mountpoint sizes,
1406
-	 *                                        'ext' to add only ext storage mount point sizes. Defaults to true.
1407
-	 * @return \OC\Files\FileInfo|false False if file does not exist
1408
-	 */
1409
-	public function getFileInfo($path, $includeMountPoints = true) {
1410
-		$this->assertPathLength($path);
1411
-		if (!Filesystem::isValidPath($path)) {
1412
-			return false;
1413
-		}
1414
-		$relativePath = $path;
1415
-		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1416
-
1417
-		$mount = Filesystem::getMountManager()->find($path);
1418
-		$storage = $mount->getStorage();
1419
-		$internalPath = $mount->getInternalPath($path);
1420
-		if ($storage) {
1421
-			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1422
-
1423
-			if (!$data instanceof ICacheEntry) {
1424
-				if (Cache\Scanner::isPartialFile($relativePath)) {
1425
-					return $this->getPartFileInfo($relativePath);
1426
-				}
1427
-
1428
-				return false;
1429
-			}
1430
-
1431
-			if ($mount instanceof MoveableMount && $internalPath === '') {
1432
-				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1433
-			}
1434
-			if ($internalPath === '' && $data['name']) {
1435
-				$data['name'] = basename($path);
1436
-			}
1437
-
1438
-			$ownerId = $storage->getOwner($internalPath);
1439
-			$owner = null;
1440
-			if ($ownerId !== false) {
1441
-				// ownerId might be null if files are accessed with an access token without file system access
1442
-				$owner = $this->getUserObjectForOwner($ownerId);
1443
-			}
1444
-			$info = new FileInfo($path, $storage, $internalPath, $data, $mount, $owner);
1445
-
1446
-			if (isset($data['fileid'])) {
1447
-				if ($includeMountPoints && $data['mimetype'] === 'httpd/unix-directory') {
1448
-					//add the sizes of other mount points to the folder
1449
-					$extOnly = ($includeMountPoints === 'ext');
1450
-					$this->addSubMounts($info, $extOnly);
1451
-				}
1452
-			}
1453
-
1454
-			return $info;
1455
-		} else {
1456
-			$this->logger->warning('Storage not valid for mountpoint: ' . $mount->getMountPoint(), ['app' => 'core']);
1457
-		}
1458
-
1459
-		return false;
1460
-	}
1461
-
1462
-	/**
1463
-	 * Extend a FileInfo that was previously requested with `$includeMountPoints = false` to include the sub mounts
1464
-	 */
1465
-	public function addSubMounts(FileInfo $info, $extOnly = false): void {
1466
-		$mounts = Filesystem::getMountManager()->findIn($info->getPath());
1467
-		$info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1468
-			return !($extOnly && $mount instanceof SharedMount);
1469
-		}));
1470
-	}
1471
-
1472
-	/**
1473
-	 * get the content of a directory
1474
-	 *
1475
-	 * @param string $directory path under datadirectory
1476
-	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1477
-	 * @return FileInfo[]
1478
-	 */
1479
-	public function getDirectoryContent($directory, $mimetype_filter = '', ?\OCP\Files\FileInfo $directoryInfo = null) {
1480
-		$this->assertPathLength($directory);
1481
-		if (!Filesystem::isValidPath($directory)) {
1482
-			return [];
1483
-		}
1484
-
1485
-		$path = $this->getAbsolutePath($directory);
1486
-		$path = Filesystem::normalizePath($path);
1487
-		$mount = $this->getMount($directory);
1488
-		$storage = $mount->getStorage();
1489
-		$internalPath = $mount->getInternalPath($path);
1490
-		if (!$storage) {
1491
-			return [];
1492
-		}
1493
-
1494
-		$cache = $storage->getCache($internalPath);
1495
-		$user = \OC_User::getUser();
1496
-
1497
-		if (!$directoryInfo) {
1498
-			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1499
-			if (!$data instanceof ICacheEntry || !isset($data['fileid'])) {
1500
-				return [];
1501
-			}
1502
-		} else {
1503
-			$data = $directoryInfo;
1504
-		}
1505
-
1506
-		if (!($data->getPermissions() & Constants::PERMISSION_READ)) {
1507
-			return [];
1508
-		}
1509
-
1510
-		$folderId = $data->getId();
1511
-		$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1512
-
1513
-		$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
1514
-
1515
-		$fileNames = array_map(function (ICacheEntry $content) {
1516
-			return $content->getName();
1517
-		}, $contents);
1518
-		/**
1519
-		 * @var \OC\Files\FileInfo[] $fileInfos
1520
-		 */
1521
-		$fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1522
-			if ($sharingDisabled) {
1523
-				$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1524
-			}
1525
-			$ownerId = $storage->getOwner($content['path']);
1526
-			if ($ownerId !== false) {
1527
-				$owner = $this->getUserObjectForOwner($ownerId);
1528
-			} else {
1529
-				$owner = null;
1530
-			}
1531
-			return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
1532
-		}, $contents);
1533
-		$files = array_combine($fileNames, $fileInfos);
1534
-
1535
-		//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1536
-		$mounts = Filesystem::getMountManager()->findIn($path);
1537
-
1538
-		// make sure nested mounts are sorted after their parent mounts
1539
-		// otherwise doesn't propagate the etag across storage boundaries correctly
1540
-		usort($mounts, function (IMountPoint $a, IMountPoint $b) {
1541
-			return $a->getMountPoint() <=> $b->getMountPoint();
1542
-		});
1543
-
1544
-		$dirLength = strlen($path);
1545
-		foreach ($mounts as $mount) {
1546
-			$mountPoint = $mount->getMountPoint();
1547
-			$subStorage = $mount->getStorage();
1548
-			if ($subStorage) {
1549
-				$subCache = $subStorage->getCache('');
1550
-
1551
-				$rootEntry = $subCache->get('');
1552
-				if (!$rootEntry) {
1553
-					$subScanner = $subStorage->getScanner();
1554
-					try {
1555
-						$subScanner->scanFile('');
1556
-					} catch (\OCP\Files\StorageNotAvailableException $e) {
1557
-						continue;
1558
-					} catch (\OCP\Files\StorageInvalidException $e) {
1559
-						continue;
1560
-					} catch (\Exception $e) {
1561
-						// sometimes when the storage is not available it can be any exception
1562
-						$this->logger->error('Exception while scanning storage "' . $subStorage->getId() . '"', [
1563
-							'exception' => $e,
1564
-							'app' => 'core',
1565
-						]);
1566
-						continue;
1567
-					}
1568
-					$rootEntry = $subCache->get('');
1569
-				}
1570
-
1571
-				if ($rootEntry && ($rootEntry->getPermissions() & Constants::PERMISSION_READ)) {
1572
-					$relativePath = trim(substr($mountPoint, $dirLength), '/');
1573
-					if ($pos = strpos($relativePath, '/')) {
1574
-						//mountpoint inside subfolder add size to the correct folder
1575
-						$entryName = substr($relativePath, 0, $pos);
1576
-
1577
-						// Create parent folders if the mountpoint is inside a subfolder that doesn't exist yet
1578
-						if (!isset($files[$entryName])) {
1579
-							try {
1580
-								[$storage, ] = $this->resolvePath($path . '/' . $entryName);
1581
-								// make sure we can create the mountpoint folder, even if the user has a quota of 0
1582
-								if ($storage->instanceOfStorage(Quota::class)) {
1583
-									$storage->enableQuota(false);
1584
-								}
1585
-
1586
-								if ($this->mkdir($path . '/' . $entryName) !== false) {
1587
-									$info = $this->getFileInfo($path . '/' . $entryName);
1588
-									if ($info !== false) {
1589
-										$files[$entryName] = $info;
1590
-									}
1591
-								}
1592
-
1593
-								if ($storage->instanceOfStorage(Quota::class)) {
1594
-									$storage->enableQuota(true);
1595
-								}
1596
-							} catch (\Exception $e) {
1597
-								// Creating the parent folder might not be possible, for example due to a lack of permissions.
1598
-								$this->logger->debug('Failed to create non-existent parent', ['exception' => $e, 'path' => $path . '/' . $entryName]);
1599
-							}
1600
-						}
1601
-
1602
-						if (isset($files[$entryName])) {
1603
-							$files[$entryName]->addSubEntry($rootEntry, $mountPoint);
1604
-						}
1605
-					} else { //mountpoint in this folder, add an entry for it
1606
-						$rootEntry['name'] = $relativePath;
1607
-						$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1608
-						$permissions = $rootEntry['permissions'];
1609
-						// do not allow renaming/deleting the mount point if they are not shared files/folders
1610
-						// for shared files/folders we use the permissions given by the owner
1611
-						if ($mount instanceof MoveableMount) {
1612
-							$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1613
-						} else {
1614
-							$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1615
-						}
1616
-
1617
-						$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1618
-
1619
-						// if sharing was disabled for the user we remove the share permissions
1620
-						if ($sharingDisabled) {
1621
-							$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1622
-						}
1623
-
1624
-						$ownerId = $subStorage->getOwner('');
1625
-						if ($ownerId !== false) {
1626
-							$owner = $this->getUserObjectForOwner($ownerId);
1627
-						} else {
1628
-							$owner = null;
1629
-						}
1630
-						$files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1631
-					}
1632
-				}
1633
-			}
1634
-		}
1635
-
1636
-		if ($mimetype_filter) {
1637
-			$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1638
-				if (strpos($mimetype_filter, '/')) {
1639
-					return $file->getMimetype() === $mimetype_filter;
1640
-				} else {
1641
-					return $file->getMimePart() === $mimetype_filter;
1642
-				}
1643
-			});
1644
-		}
1645
-
1646
-		return array_values($files);
1647
-	}
1648
-
1649
-	/**
1650
-	 * change file metadata
1651
-	 *
1652
-	 * @param string $path
1653
-	 * @param array|\OCP\Files\FileInfo $data
1654
-	 * @return int
1655
-	 *
1656
-	 * returns the fileid of the updated file
1657
-	 */
1658
-	public function putFileInfo($path, $data) {
1659
-		$this->assertPathLength($path);
1660
-		if ($data instanceof FileInfo) {
1661
-			$data = $data->getData();
1662
-		}
1663
-		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1664
-		/**
1665
-		 * @var Storage $storage
1666
-		 * @var string $internalPath
1667
-		 */
1668
-		[$storage, $internalPath] = Filesystem::resolvePath($path);
1669
-		if ($storage) {
1670
-			$cache = $storage->getCache($path);
1671
-
1672
-			if (!$cache->inCache($internalPath)) {
1673
-				$scanner = $storage->getScanner($internalPath);
1674
-				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1675
-			}
1676
-
1677
-			return $cache->put($internalPath, $data);
1678
-		} else {
1679
-			return -1;
1680
-		}
1681
-	}
1682
-
1683
-	/**
1684
-	 * search for files with the name matching $query
1685
-	 *
1686
-	 * @param string $query
1687
-	 * @return FileInfo[]
1688
-	 */
1689
-	public function search($query) {
1690
-		return $this->searchCommon('search', ['%' . $query . '%']);
1691
-	}
1692
-
1693
-	/**
1694
-	 * search for files with the name matching $query
1695
-	 *
1696
-	 * @param string $query
1697
-	 * @return FileInfo[]
1698
-	 */
1699
-	public function searchRaw($query) {
1700
-		return $this->searchCommon('search', [$query]);
1701
-	}
1702
-
1703
-	/**
1704
-	 * search for files by mimetype
1705
-	 *
1706
-	 * @param string $mimetype
1707
-	 * @return FileInfo[]
1708
-	 */
1709
-	public function searchByMime($mimetype) {
1710
-		return $this->searchCommon('searchByMime', [$mimetype]);
1711
-	}
1712
-
1713
-	/**
1714
-	 * search for files by tag
1715
-	 *
1716
-	 * @param string|int $tag name or tag id
1717
-	 * @param string $userId owner of the tags
1718
-	 * @return FileInfo[]
1719
-	 */
1720
-	public function searchByTag($tag, $userId) {
1721
-		return $this->searchCommon('searchByTag', [$tag, $userId]);
1722
-	}
1723
-
1724
-	/**
1725
-	 * @param string $method cache method
1726
-	 * @param array $args
1727
-	 * @return FileInfo[]
1728
-	 */
1729
-	private function searchCommon($method, $args) {
1730
-		$files = [];
1731
-		$rootLength = strlen($this->fakeRoot);
1732
-
1733
-		$mount = $this->getMount('');
1734
-		$mountPoint = $mount->getMountPoint();
1735
-		$storage = $mount->getStorage();
1736
-		$userManager = \OC::$server->getUserManager();
1737
-		if ($storage) {
1738
-			$cache = $storage->getCache('');
1739
-
1740
-			$results = call_user_func_array([$cache, $method], $args);
1741
-			foreach ($results as $result) {
1742
-				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1743
-					$internalPath = $result['path'];
1744
-					$path = $mountPoint . $result['path'];
1745
-					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1746
-					$ownerId = $storage->getOwner($internalPath);
1747
-					if ($ownerId !== false) {
1748
-						$owner = $userManager->get($ownerId);
1749
-					} else {
1750
-						$owner = null;
1751
-					}
1752
-					$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1753
-				}
1754
-			}
1755
-
1756
-			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1757
-			foreach ($mounts as $mount) {
1758
-				$mountPoint = $mount->getMountPoint();
1759
-				$storage = $mount->getStorage();
1760
-				if ($storage) {
1761
-					$cache = $storage->getCache('');
1762
-
1763
-					$relativeMountPoint = substr($mountPoint, $rootLength);
1764
-					$results = call_user_func_array([$cache, $method], $args);
1765
-					if ($results) {
1766
-						foreach ($results as $result) {
1767
-							$internalPath = $result['path'];
1768
-							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1769
-							$path = rtrim($mountPoint . $internalPath, '/');
1770
-							$ownerId = $storage->getOwner($internalPath);
1771
-							if ($ownerId !== false) {
1772
-								$owner = $userManager->get($ownerId);
1773
-							} else {
1774
-								$owner = null;
1775
-							}
1776
-							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1777
-						}
1778
-					}
1779
-				}
1780
-			}
1781
-		}
1782
-		return $files;
1783
-	}
1784
-
1785
-	/**
1786
-	 * Get the owner for a file or folder
1787
-	 *
1788
-	 * @throws NotFoundException
1789
-	 */
1790
-	public function getOwner(string $path): string {
1791
-		$info = $this->getFileInfo($path);
1792
-		if (!$info) {
1793
-			throw new NotFoundException($path . ' not found while trying to get owner');
1794
-		}
1795
-
1796
-		if ($info->getOwner() === null) {
1797
-			throw new NotFoundException($path . ' has no owner');
1798
-		}
1799
-
1800
-		return $info->getOwner()->getUID();
1801
-	}
1802
-
1803
-	/**
1804
-	 * get the ETag for a file or folder
1805
-	 *
1806
-	 * @param string $path
1807
-	 * @return string|false
1808
-	 */
1809
-	public function getETag($path) {
1810
-		[$storage, $internalPath] = $this->resolvePath($path);
1811
-		if ($storage) {
1812
-			return $storage->getETag($internalPath);
1813
-		} else {
1814
-			return false;
1815
-		}
1816
-	}
1817
-
1818
-	/**
1819
-	 * Get the path of a file by id, relative to the view
1820
-	 *
1821
-	 * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file
1822
-	 *
1823
-	 * @param int $id
1824
-	 * @param int|null $storageId
1825
-	 * @return string
1826
-	 * @throws NotFoundException
1827
-	 */
1828
-	public function getPath($id, ?int $storageId = null): string {
1829
-		$id = (int)$id;
1830
-		$rootFolder = Server::get(Files\IRootFolder::class);
1831
-
1832
-		$node = $rootFolder->getFirstNodeByIdInPath($id, $this->getRoot());
1833
-		if ($node) {
1834
-			if ($storageId === null || $storageId === $node->getStorage()->getCache()->getNumericStorageId()) {
1835
-				return $this->getRelativePath($node->getPath()) ?? '';
1836
-			}
1837
-		} else {
1838
-			throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1839
-		}
1840
-
1841
-		foreach ($rootFolder->getByIdInPath($id, $this->getRoot()) as $node) {
1842
-			if ($storageId === $node->getStorage()->getCache()->getNumericStorageId()) {
1843
-				return $this->getRelativePath($node->getPath()) ?? '';
1844
-			}
1845
-		}
1846
-
1847
-		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1848
-	}
1849
-
1850
-	/**
1851
-	 * @param string $path
1852
-	 * @throws InvalidPathException
1853
-	 */
1854
-	private function assertPathLength($path): void {
1855
-		$maxLen = min(PHP_MAXPATHLEN, 4000);
1856
-		// Check for the string length - performed using isset() instead of strlen()
1857
-		// because isset() is about 5x-40x faster.
1858
-		if (isset($path[$maxLen])) {
1859
-			$pathLen = strlen($path);
1860
-			throw new InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1861
-		}
1862
-	}
1863
-
1864
-	/**
1865
-	 * check if it is allowed to move a mount point to a given target.
1866
-	 * It is not allowed to move a mount point into a different mount point or
1867
-	 * into an already shared folder
1868
-	 */
1869
-	private function targetIsNotShared(string $user, string $targetPath): bool {
1870
-		$providers = [
1871
-			IShare::TYPE_USER,
1872
-			IShare::TYPE_GROUP,
1873
-			IShare::TYPE_EMAIL,
1874
-			IShare::TYPE_CIRCLE,
1875
-			IShare::TYPE_ROOM,
1876
-			IShare::TYPE_DECK,
1877
-			IShare::TYPE_SCIENCEMESH
1878
-		];
1879
-		$shareManager = Server::get(IManager::class);
1880
-		/** @var IShare[] $shares */
1881
-		$shares = array_merge(...array_map(function (int $type) use ($shareManager, $user) {
1882
-			return $shareManager->getSharesBy($user, $type);
1883
-		}, $providers));
1884
-
1885
-		foreach ($shares as $share) {
1886
-			try {
1887
-				$sharedPath = $share->getNode()->getPath();
1888
-			} catch (NotFoundException $e) {
1889
-				// node is not found, ignoring
1890
-				$this->logger->debug(
1891
-					'Could not find the node linked to a share',
1892
-					['app' => 'files', 'exception' => $e]);
1893
-				continue;
1894
-			}
1895
-			if ($targetPath === $sharedPath || str_starts_with($targetPath, $sharedPath . '/')) {
1896
-				$this->logger->debug(
1897
-					'It is not allowed to move one mount point into a shared folder',
1898
-					['app' => 'files']);
1899
-				return false;
1900
-			}
1901
-		}
1902
-
1903
-		return true;
1904
-	}
1905
-
1906
-	/**
1907
-	 * Get a fileinfo object for files that are ignored in the cache (part files)
1908
-	 */
1909
-	private function getPartFileInfo(string $path): \OC\Files\FileInfo {
1910
-		$mount = $this->getMount($path);
1911
-		$storage = $mount->getStorage();
1912
-		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1913
-		$ownerId = $storage->getOwner($internalPath);
1914
-		if ($ownerId !== false) {
1915
-			$owner = Server::get(IUserManager::class)->get($ownerId);
1916
-		} else {
1917
-			$owner = null;
1918
-		}
1919
-		return new FileInfo(
1920
-			$this->getAbsolutePath($path),
1921
-			$storage,
1922
-			$internalPath,
1923
-			[
1924
-				'fileid' => null,
1925
-				'mimetype' => $storage->getMimeType($internalPath),
1926
-				'name' => basename($path),
1927
-				'etag' => null,
1928
-				'size' => $storage->filesize($internalPath),
1929
-				'mtime' => $storage->filemtime($internalPath),
1930
-				'encrypted' => false,
1931
-				'permissions' => \OCP\Constants::PERMISSION_ALL
1932
-			],
1933
-			$mount,
1934
-			$owner
1935
-		);
1936
-	}
1937
-
1938
-	/**
1939
-	 * @param string $path
1940
-	 * @param string $fileName
1941
-	 * @param bool $readonly Check only if the path is allowed for read-only access
1942
-	 * @throws InvalidPathException
1943
-	 */
1944
-	public function verifyPath($path, $fileName, $readonly = false): void {
1945
-		// All of the view's functions disallow '..' in the path so we can short cut if the path is invalid
1946
-		if (!Filesystem::isValidPath($path ?: '/')) {
1947
-			$l = \OCP\Util::getL10N('lib');
1948
-			throw new InvalidPathException($l->t('Path contains invalid segments'));
1949
-		}
1950
-
1951
-		// Short cut for read-only validation
1952
-		if ($readonly) {
1953
-			$validator = Server::get(FilenameValidator::class);
1954
-			if ($validator->isForbidden($fileName)) {
1955
-				$l = \OCP\Util::getL10N('lib');
1956
-				throw new InvalidPathException($l->t('Filename is a reserved word'));
1957
-			}
1958
-			return;
1959
-		}
1960
-
1961
-		try {
1962
-			/** @type \OCP\Files\Storage $storage */
1963
-			[$storage, $internalPath] = $this->resolvePath($path);
1964
-			$storage->verifyPath($internalPath, $fileName);
1965
-		} catch (ReservedWordException $ex) {
1966
-			$l = \OCP\Util::getL10N('lib');
1967
-			throw new InvalidPathException($ex->getMessage() ?: $l->t('Filename is a reserved word'));
1968
-		} catch (InvalidCharacterInPathException $ex) {
1969
-			$l = \OCP\Util::getL10N('lib');
1970
-			throw new InvalidPathException($ex->getMessage() ?: $l->t('Filename contains at least one invalid character'));
1971
-		} catch (FileNameTooLongException $ex) {
1972
-			$l = \OCP\Util::getL10N('lib');
1973
-			throw new InvalidPathException($l->t('Filename is too long'));
1974
-		} catch (InvalidDirectoryException $ex) {
1975
-			$l = \OCP\Util::getL10N('lib');
1976
-			throw new InvalidPathException($l->t('Dot files are not allowed'));
1977
-		} catch (EmptyFileNameException $ex) {
1978
-			$l = \OCP\Util::getL10N('lib');
1979
-			throw new InvalidPathException($l->t('Empty filename is not allowed'));
1980
-		}
1981
-	}
1982
-
1983
-	/**
1984
-	 * get all parent folders of $path
1985
-	 *
1986
-	 * @param string $path
1987
-	 * @return string[]
1988
-	 */
1989
-	private function getParents($path) {
1990
-		$path = trim($path, '/');
1991
-		if (!$path) {
1992
-			return [];
1993
-		}
1994
-
1995
-		$parts = explode('/', $path);
1996
-
1997
-		// remove the single file
1998
-		array_pop($parts);
1999
-		$result = ['/'];
2000
-		$resultPath = '';
2001
-		foreach ($parts as $part) {
2002
-			if ($part) {
2003
-				$resultPath .= '/' . $part;
2004
-				$result[] = $resultPath;
2005
-			}
2006
-		}
2007
-		return $result;
2008
-	}
2009
-
2010
-	/**
2011
-	 * Returns the mount point for which to lock
2012
-	 *
2013
-	 * @param string $absolutePath absolute path
2014
-	 * @param bool $useParentMount true to return parent mount instead of whatever
2015
-	 *                             is mounted directly on the given path, false otherwise
2016
-	 * @return IMountPoint mount point for which to apply locks
2017
-	 */
2018
-	private function getMountForLock(string $absolutePath, bool $useParentMount = false): IMountPoint {
2019
-		$mount = Filesystem::getMountManager()->find($absolutePath);
2020
-
2021
-		if ($useParentMount) {
2022
-			// find out if something is mounted directly on the path
2023
-			$internalPath = $mount->getInternalPath($absolutePath);
2024
-			if ($internalPath === '') {
2025
-				// resolve the parent mount instead
2026
-				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
2027
-			}
2028
-		}
2029
-
2030
-		return $mount;
2031
-	}
2032
-
2033
-	/**
2034
-	 * Lock the given path
2035
-	 *
2036
-	 * @param string $path the path of the file to lock, relative to the view
2037
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2038
-	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2039
-	 *
2040
-	 * @return bool False if the path is excluded from locking, true otherwise
2041
-	 * @throws LockedException if the path is already locked
2042
-	 */
2043
-	private function lockPath($path, $type, $lockMountPoint = false) {
2044
-		$absolutePath = $this->getAbsolutePath($path);
2045
-		$absolutePath = Filesystem::normalizePath($absolutePath);
2046
-		if (!$this->shouldLockFile($absolutePath)) {
2047
-			return false;
2048
-		}
2049
-
2050
-		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2051
-		try {
2052
-			$storage = $mount->getStorage();
2053
-			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2054
-				$storage->acquireLock(
2055
-					$mount->getInternalPath($absolutePath),
2056
-					$type,
2057
-					$this->lockingProvider
2058
-				);
2059
-			}
2060
-		} catch (LockedException $e) {
2061
-			// rethrow with the human-readable path
2062
-			throw new LockedException(
2063
-				$path,
2064
-				$e,
2065
-				$e->getExistingLock()
2066
-			);
2067
-		}
2068
-
2069
-		return true;
2070
-	}
2071
-
2072
-	/**
2073
-	 * Change the lock type
2074
-	 *
2075
-	 * @param string $path the path of the file to lock, relative to the view
2076
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2077
-	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2078
-	 *
2079
-	 * @return bool False if the path is excluded from locking, true otherwise
2080
-	 * @throws LockedException if the path is already locked
2081
-	 */
2082
-	public function changeLock($path, $type, $lockMountPoint = false) {
2083
-		$path = Filesystem::normalizePath($path);
2084
-		$absolutePath = $this->getAbsolutePath($path);
2085
-		$absolutePath = Filesystem::normalizePath($absolutePath);
2086
-		if (!$this->shouldLockFile($absolutePath)) {
2087
-			return false;
2088
-		}
2089
-
2090
-		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2091
-		try {
2092
-			$storage = $mount->getStorage();
2093
-			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2094
-				$storage->changeLock(
2095
-					$mount->getInternalPath($absolutePath),
2096
-					$type,
2097
-					$this->lockingProvider
2098
-				);
2099
-			}
2100
-		} catch (LockedException $e) {
2101
-			// rethrow with the a human-readable path
2102
-			throw new LockedException(
2103
-				$path,
2104
-				$e,
2105
-				$e->getExistingLock()
2106
-			);
2107
-		}
2108
-
2109
-		return true;
2110
-	}
2111
-
2112
-	/**
2113
-	 * Unlock the given path
2114
-	 *
2115
-	 * @param string $path the path of the file to unlock, relative to the view
2116
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2117
-	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2118
-	 *
2119
-	 * @return bool False if the path is excluded from locking, true otherwise
2120
-	 * @throws LockedException
2121
-	 */
2122
-	private function unlockPath($path, $type, $lockMountPoint = false) {
2123
-		$absolutePath = $this->getAbsolutePath($path);
2124
-		$absolutePath = Filesystem::normalizePath($absolutePath);
2125
-		if (!$this->shouldLockFile($absolutePath)) {
2126
-			return false;
2127
-		}
2128
-
2129
-		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2130
-		$storage = $mount->getStorage();
2131
-		if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2132
-			$storage->releaseLock(
2133
-				$mount->getInternalPath($absolutePath),
2134
-				$type,
2135
-				$this->lockingProvider
2136
-			);
2137
-		}
2138
-
2139
-		return true;
2140
-	}
2141
-
2142
-	/**
2143
-	 * Lock a path and all its parents up to the root of the view
2144
-	 *
2145
-	 * @param string $path the path of the file to lock relative to the view
2146
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2147
-	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2148
-	 *
2149
-	 * @return bool False if the path is excluded from locking, true otherwise
2150
-	 * @throws LockedException
2151
-	 */
2152
-	public function lockFile($path, $type, $lockMountPoint = false) {
2153
-		$absolutePath = $this->getAbsolutePath($path);
2154
-		$absolutePath = Filesystem::normalizePath($absolutePath);
2155
-		if (!$this->shouldLockFile($absolutePath)) {
2156
-			return false;
2157
-		}
2158
-
2159
-		$this->lockPath($path, $type, $lockMountPoint);
2160
-
2161
-		$parents = $this->getParents($path);
2162
-		foreach ($parents as $parent) {
2163
-			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2164
-		}
2165
-
2166
-		return true;
2167
-	}
2168
-
2169
-	/**
2170
-	 * Unlock a path and all its parents up to the root of the view
2171
-	 *
2172
-	 * @param string $path the path of the file to lock relative to the view
2173
-	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2174
-	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2175
-	 *
2176
-	 * @return bool False if the path is excluded from locking, true otherwise
2177
-	 * @throws LockedException
2178
-	 */
2179
-	public function unlockFile($path, $type, $lockMountPoint = false) {
2180
-		$absolutePath = $this->getAbsolutePath($path);
2181
-		$absolutePath = Filesystem::normalizePath($absolutePath);
2182
-		if (!$this->shouldLockFile($absolutePath)) {
2183
-			return false;
2184
-		}
2185
-
2186
-		$this->unlockPath($path, $type, $lockMountPoint);
2187
-
2188
-		$parents = $this->getParents($path);
2189
-		foreach ($parents as $parent) {
2190
-			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2191
-		}
2192
-
2193
-		return true;
2194
-	}
2195
-
2196
-	/**
2197
-	 * Only lock files in data/user/files/
2198
-	 *
2199
-	 * @param string $path Absolute path to the file/folder we try to (un)lock
2200
-	 * @return bool
2201
-	 */
2202
-	protected function shouldLockFile($path) {
2203
-		$path = Filesystem::normalizePath($path);
2204
-
2205
-		$pathSegments = explode('/', $path);
2206
-		if (isset($pathSegments[2])) {
2207
-			// E.g.: /username/files/path-to-file
2208
-			return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
2209
-		}
2210
-
2211
-		return !str_starts_with($path, '/appdata_');
2212
-	}
2213
-
2214
-	/**
2215
-	 * Shortens the given absolute path to be relative to
2216
-	 * "$user/files".
2217
-	 *
2218
-	 * @param string $absolutePath absolute path which is under "files"
2219
-	 *
2220
-	 * @return string path relative to "files" with trimmed slashes or null
2221
-	 *                if the path was NOT relative to files
2222
-	 *
2223
-	 * @throws \InvalidArgumentException if the given path was not under "files"
2224
-	 * @since 8.1.0
2225
-	 */
2226
-	public function getPathRelativeToFiles($absolutePath) {
2227
-		$path = Filesystem::normalizePath($absolutePath);
2228
-		$parts = explode('/', trim($path, '/'), 3);
2229
-		// "$user", "files", "path/to/dir"
2230
-		if (!isset($parts[1]) || $parts[1] !== 'files') {
2231
-			$this->logger->error(
2232
-				'$absolutePath must be relative to "files", value is "{absolutePath}"',
2233
-				[
2234
-					'absolutePath' => $absolutePath,
2235
-				]
2236
-			);
2237
-			throw new \InvalidArgumentException('$absolutePath must be relative to "files"');
2238
-		}
2239
-		if (isset($parts[2])) {
2240
-			return $parts[2];
2241
-		}
2242
-		return '';
2243
-	}
2244
-
2245
-	/**
2246
-	 * @param string $filename
2247
-	 * @return array
2248
-	 * @throws \OC\User\NoUserException
2249
-	 * @throws NotFoundException
2250
-	 */
2251
-	public function getUidAndFilename($filename) {
2252
-		$info = $this->getFileInfo($filename);
2253
-		if (!$info instanceof \OCP\Files\FileInfo) {
2254
-			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2255
-		}
2256
-		$uid = $info->getOwner()->getUID();
2257
-		if ($uid != \OC_User::getUser()) {
2258
-			Filesystem::initMountPoints($uid);
2259
-			$ownerView = new View('/' . $uid . '/files');
2260
-			try {
2261
-				$filename = $ownerView->getPath($info['fileid']);
2262
-			} catch (NotFoundException $e) {
2263
-				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2264
-			}
2265
-		}
2266
-		return [$uid, $filename];
2267
-	}
2268
-
2269
-	/**
2270
-	 * Creates parent non-existing folders
2271
-	 *
2272
-	 * @param string $filePath
2273
-	 * @return bool
2274
-	 */
2275
-	private function createParentDirectories($filePath) {
2276
-		$directoryParts = explode('/', $filePath);
2277
-		$directoryParts = array_filter($directoryParts);
2278
-		foreach ($directoryParts as $key => $part) {
2279
-			$currentPathElements = array_slice($directoryParts, 0, $key);
2280
-			$currentPath = '/' . implode('/', $currentPathElements);
2281
-			if ($this->is_file($currentPath)) {
2282
-				return false;
2283
-			}
2284
-			if (!$this->file_exists($currentPath)) {
2285
-				$this->mkdir($currentPath);
2286
-			}
2287
-		}
2288
-
2289
-		return true;
2290
-	}
63
+    private string $fakeRoot = '';
64
+    private ILockingProvider $lockingProvider;
65
+    private bool $lockingEnabled;
66
+    private bool $updaterEnabled = true;
67
+    private UserManager $userManager;
68
+    private LoggerInterface $logger;
69
+
70
+    /**
71
+     * @throws \Exception If $root contains an invalid path
72
+     */
73
+    public function __construct(string $root = '') {
74
+        if (!Filesystem::isValidPath($root)) {
75
+            throw new \Exception();
76
+        }
77
+
78
+        $this->fakeRoot = $root;
79
+        $this->lockingProvider = \OC::$server->get(ILockingProvider::class);
80
+        $this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
81
+        $this->userManager = \OC::$server->getUserManager();
82
+        $this->logger = \OC::$server->get(LoggerInterface::class);
83
+    }
84
+
85
+    /**
86
+     * @param ?string $path
87
+     * @psalm-template S as string|null
88
+     * @psalm-param S $path
89
+     * @psalm-return (S is string ? string : null)
90
+     */
91
+    public function getAbsolutePath($path = '/'): ?string {
92
+        if ($path === null) {
93
+            return null;
94
+        }
95
+        $this->assertPathLength($path);
96
+        return PathHelper::normalizePath($this->fakeRoot . '/' . $path);
97
+    }
98
+
99
+    /**
100
+     * Change the root to a fake root
101
+     *
102
+     * @param string $fakeRoot
103
+     */
104
+    public function chroot($fakeRoot): void {
105
+        if (!$fakeRoot == '') {
106
+            if ($fakeRoot[0] !== '/') {
107
+                $fakeRoot = '/' . $fakeRoot;
108
+            }
109
+        }
110
+        $this->fakeRoot = $fakeRoot;
111
+    }
112
+
113
+    /**
114
+     * Get the fake root
115
+     */
116
+    public function getRoot(): string {
117
+        return $this->fakeRoot;
118
+    }
119
+
120
+    /**
121
+     * get path relative to the root of the view
122
+     *
123
+     * @param string $path
124
+     */
125
+    public function getRelativePath($path): ?string {
126
+        $this->assertPathLength($path);
127
+        if ($this->fakeRoot == '') {
128
+            return $path;
129
+        }
130
+
131
+        if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) {
132
+            return '/';
133
+        }
134
+
135
+        // missing slashes can cause wrong matches!
136
+        $root = rtrim($this->fakeRoot, '/') . '/';
137
+
138
+        if (!str_starts_with($path, $root)) {
139
+            return null;
140
+        } else {
141
+            $path = substr($path, strlen($this->fakeRoot));
142
+            if (strlen($path) === 0) {
143
+                return '/';
144
+            } else {
145
+                return $path;
146
+            }
147
+        }
148
+    }
149
+
150
+    /**
151
+     * Get the mountpoint of the storage object for a path
152
+     * ( note: because a storage is not always mounted inside the fakeroot, the
153
+     * returned mountpoint is relative to the absolute root of the filesystem
154
+     * and does not take the chroot into account )
155
+     *
156
+     * @param string $path
157
+     */
158
+    public function getMountPoint($path): string {
159
+        return Filesystem::getMountPoint($this->getAbsolutePath($path));
160
+    }
161
+
162
+    /**
163
+     * Get the mountpoint of the storage object for a path
164
+     * ( note: because a storage is not always mounted inside the fakeroot, the
165
+     * returned mountpoint is relative to the absolute root of the filesystem
166
+     * and does not take the chroot into account )
167
+     *
168
+     * @param string $path
169
+     */
170
+    public function getMount($path): IMountPoint {
171
+        return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
172
+    }
173
+
174
+    /**
175
+     * Resolve a path to a storage and internal path
176
+     *
177
+     * @param string $path
178
+     * @return array{?\OCP\Files\Storage\IStorage, string} an array consisting of the storage and the internal path
179
+     */
180
+    public function resolvePath($path): array {
181
+        $a = $this->getAbsolutePath($path);
182
+        $p = Filesystem::normalizePath($a);
183
+        return Filesystem::resolvePath($p);
184
+    }
185
+
186
+    /**
187
+     * Return the path to a local version of the file
188
+     * we need this because we can't know if a file is stored local or not from
189
+     * outside the filestorage and for some purposes a local file is needed
190
+     *
191
+     * @param string $path
192
+     */
193
+    public function getLocalFile($path): string|false {
194
+        $parent = substr($path, 0, strrpos($path, '/') ?: 0);
195
+        $path = $this->getAbsolutePath($path);
196
+        [$storage, $internalPath] = Filesystem::resolvePath($path);
197
+        if (Filesystem::isValidPath($parent) && $storage) {
198
+            return $storage->getLocalFile($internalPath);
199
+        } else {
200
+            return false;
201
+        }
202
+    }
203
+
204
+    /**
205
+     * the following functions operate with arguments and return values identical
206
+     * to those of their PHP built-in equivalents. Mostly they are merely wrappers
207
+     * for \OC\Files\Storage\Storage via basicOperation().
208
+     */
209
+    public function mkdir($path) {
210
+        return $this->basicOperation('mkdir', $path, ['create', 'write']);
211
+    }
212
+
213
+    /**
214
+     * remove mount point
215
+     *
216
+     * @param IMountPoint $mount
217
+     * @param string $path relative to data/
218
+     */
219
+    protected function removeMount($mount, $path): bool {
220
+        if ($mount instanceof MoveableMount) {
221
+            // cut of /user/files to get the relative path to data/user/files
222
+            $pathParts = explode('/', $path, 4);
223
+            $relPath = '/' . $pathParts[3];
224
+            $this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
225
+            \OC_Hook::emit(
226
+                Filesystem::CLASSNAME, 'umount',
227
+                [Filesystem::signal_param_path => $relPath]
228
+            );
229
+            $this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
230
+            $result = $mount->removeMount();
231
+            $this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
232
+            if ($result) {
233
+                \OC_Hook::emit(
234
+                    Filesystem::CLASSNAME, 'post_umount',
235
+                    [Filesystem::signal_param_path => $relPath]
236
+                );
237
+            }
238
+            $this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
239
+            return $result;
240
+        } else {
241
+            // do not allow deleting the storage's root / the mount point
242
+            // because for some storages it might delete the whole contents
243
+            // but isn't supposed to work that way
244
+            return false;
245
+        }
246
+    }
247
+
248
+    public function disableCacheUpdate(): void {
249
+        $this->updaterEnabled = false;
250
+    }
251
+
252
+    public function enableCacheUpdate(): void {
253
+        $this->updaterEnabled = true;
254
+    }
255
+
256
+    protected function writeUpdate(Storage $storage, string $internalPath, ?int $time = null, ?int $sizeDifference = null): void {
257
+        if ($this->updaterEnabled) {
258
+            if (is_null($time)) {
259
+                $time = time();
260
+            }
261
+            $storage->getUpdater()->update($internalPath, $time, $sizeDifference);
262
+        }
263
+    }
264
+
265
+    protected function removeUpdate(Storage $storage, string $internalPath): void {
266
+        if ($this->updaterEnabled) {
267
+            $storage->getUpdater()->remove($internalPath);
268
+        }
269
+    }
270
+
271
+    protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, string $sourceInternalPath, string $targetInternalPath): void {
272
+        if ($this->updaterEnabled) {
273
+            $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
274
+        }
275
+    }
276
+
277
+    protected function copyUpdate(Storage $sourceStorage, Storage $targetStorage, string $sourceInternalPath, string $targetInternalPath): void {
278
+        if ($this->updaterEnabled) {
279
+            $targetStorage->getUpdater()->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
280
+        }
281
+    }
282
+
283
+    /**
284
+     * @param string $path
285
+     * @return bool|mixed
286
+     */
287
+    public function rmdir($path) {
288
+        $absolutePath = $this->getAbsolutePath($path);
289
+        $mount = Filesystem::getMountManager()->find($absolutePath);
290
+        if ($mount->getInternalPath($absolutePath) === '') {
291
+            return $this->removeMount($mount, $absolutePath);
292
+        }
293
+        if ($this->is_dir($path)) {
294
+            $result = $this->basicOperation('rmdir', $path, ['delete']);
295
+        } else {
296
+            $result = false;
297
+        }
298
+
299
+        if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
300
+            $storage = $mount->getStorage();
301
+            $internalPath = $mount->getInternalPath($absolutePath);
302
+            $storage->getUpdater()->remove($internalPath);
303
+        }
304
+        return $result;
305
+    }
306
+
307
+    /**
308
+     * @param string $path
309
+     * @return resource|false
310
+     */
311
+    public function opendir($path) {
312
+        return $this->basicOperation('opendir', $path, ['read']);
313
+    }
314
+
315
+    /**
316
+     * @param string $path
317
+     * @return bool|mixed
318
+     */
319
+    public function is_dir($path) {
320
+        if ($path == '/') {
321
+            return true;
322
+        }
323
+        return $this->basicOperation('is_dir', $path);
324
+    }
325
+
326
+    /**
327
+     * @param string $path
328
+     * @return bool|mixed
329
+     */
330
+    public function is_file($path) {
331
+        if ($path == '/') {
332
+            return false;
333
+        }
334
+        return $this->basicOperation('is_file', $path);
335
+    }
336
+
337
+    /**
338
+     * @param string $path
339
+     * @return mixed
340
+     */
341
+    public function stat($path) {
342
+        return $this->basicOperation('stat', $path);
343
+    }
344
+
345
+    /**
346
+     * @param string $path
347
+     * @return mixed
348
+     */
349
+    public function filetype($path) {
350
+        return $this->basicOperation('filetype', $path);
351
+    }
352
+
353
+    /**
354
+     * @param string $path
355
+     * @return mixed
356
+     */
357
+    public function filesize(string $path) {
358
+        return $this->basicOperation('filesize', $path);
359
+    }
360
+
361
+    /**
362
+     * @param string $path
363
+     * @return bool|mixed
364
+     * @throws InvalidPathException
365
+     */
366
+    public function readfile($path) {
367
+        $this->assertPathLength($path);
368
+        if (ob_get_level()) {
369
+            ob_end_clean();
370
+        }
371
+        $handle = $this->fopen($path, 'rb');
372
+        if ($handle) {
373
+            $chunkSize = 524288; // 512 kiB chunks
374
+            while (!feof($handle)) {
375
+                echo fread($handle, $chunkSize);
376
+                flush();
377
+                $this->checkConnectionStatus();
378
+            }
379
+            fclose($handle);
380
+            return $this->filesize($path);
381
+        }
382
+        return false;
383
+    }
384
+
385
+    /**
386
+     * @param string $path
387
+     * @param int $from
388
+     * @param int $to
389
+     * @return bool|mixed
390
+     * @throws InvalidPathException
391
+     * @throws \OCP\Files\UnseekableException
392
+     */
393
+    public function readfilePart($path, $from, $to) {
394
+        $this->assertPathLength($path);
395
+        if (ob_get_level()) {
396
+            ob_end_clean();
397
+        }
398
+        $handle = $this->fopen($path, 'rb');
399
+        if ($handle) {
400
+            $chunkSize = 524288; // 512 kiB chunks
401
+            $startReading = true;
402
+
403
+            if ($from !== 0 && $from !== '0' && fseek($handle, $from) !== 0) {
404
+                // forward file handle via chunked fread because fseek seem to have failed
405
+
406
+                $end = $from + 1;
407
+                while (!feof($handle) && ftell($handle) < $end && ftell($handle) !== $from) {
408
+                    $len = $from - ftell($handle);
409
+                    if ($len > $chunkSize) {
410
+                        $len = $chunkSize;
411
+                    }
412
+                    $result = fread($handle, $len);
413
+
414
+                    if ($result === false) {
415
+                        $startReading = false;
416
+                        break;
417
+                    }
418
+                }
419
+            }
420
+
421
+            if ($startReading) {
422
+                $end = $to + 1;
423
+                while (!feof($handle) && ftell($handle) < $end) {
424
+                    $len = $end - ftell($handle);
425
+                    if ($len > $chunkSize) {
426
+                        $len = $chunkSize;
427
+                    }
428
+                    echo fread($handle, $len);
429
+                    flush();
430
+                    $this->checkConnectionStatus();
431
+                }
432
+                return ftell($handle) - $from;
433
+            }
434
+
435
+            throw new \OCP\Files\UnseekableException('fseek error');
436
+        }
437
+        return false;
438
+    }
439
+
440
+    private function checkConnectionStatus(): void {
441
+        $connectionStatus = \connection_status();
442
+        if ($connectionStatus !== CONNECTION_NORMAL) {
443
+            throw new ConnectionLostException("Connection lost. Status: $connectionStatus");
444
+        }
445
+    }
446
+
447
+    /**
448
+     * @param string $path
449
+     * @return mixed
450
+     */
451
+    public function isCreatable($path) {
452
+        return $this->basicOperation('isCreatable', $path);
453
+    }
454
+
455
+    /**
456
+     * @param string $path
457
+     * @return mixed
458
+     */
459
+    public function isReadable($path) {
460
+        return $this->basicOperation('isReadable', $path);
461
+    }
462
+
463
+    /**
464
+     * @param string $path
465
+     * @return mixed
466
+     */
467
+    public function isUpdatable($path) {
468
+        return $this->basicOperation('isUpdatable', $path);
469
+    }
470
+
471
+    /**
472
+     * @param string $path
473
+     * @return bool|mixed
474
+     */
475
+    public function isDeletable($path) {
476
+        $absolutePath = $this->getAbsolutePath($path);
477
+        $mount = Filesystem::getMountManager()->find($absolutePath);
478
+        if ($mount->getInternalPath($absolutePath) === '') {
479
+            return $mount instanceof MoveableMount;
480
+        }
481
+        return $this->basicOperation('isDeletable', $path);
482
+    }
483
+
484
+    /**
485
+     * @param string $path
486
+     * @return mixed
487
+     */
488
+    public function isSharable($path) {
489
+        return $this->basicOperation('isSharable', $path);
490
+    }
491
+
492
+    /**
493
+     * @param string $path
494
+     * @return bool|mixed
495
+     */
496
+    public function file_exists($path) {
497
+        if ($path == '/') {
498
+            return true;
499
+        }
500
+        return $this->basicOperation('file_exists', $path);
501
+    }
502
+
503
+    /**
504
+     * @param string $path
505
+     * @return mixed
506
+     */
507
+    public function filemtime($path) {
508
+        return $this->basicOperation('filemtime', $path);
509
+    }
510
+
511
+    /**
512
+     * @param string $path
513
+     * @param int|string $mtime
514
+     */
515
+    public function touch($path, $mtime = null): bool {
516
+        if (!is_null($mtime) && !is_numeric($mtime)) {
517
+            $mtime = strtotime($mtime);
518
+        }
519
+
520
+        $hooks = ['touch'];
521
+
522
+        if (!$this->file_exists($path)) {
523
+            $hooks[] = 'create';
524
+            $hooks[] = 'write';
525
+        }
526
+        try {
527
+            $result = $this->basicOperation('touch', $path, $hooks, $mtime);
528
+        } catch (\Exception $e) {
529
+            $this->logger->info('Error while setting modified time', ['app' => 'core', 'exception' => $e]);
530
+            $result = false;
531
+        }
532
+        if (!$result) {
533
+            // If create file fails because of permissions on external storage like SMB folders,
534
+            // check file exists and return false if not.
535
+            if (!$this->file_exists($path)) {
536
+                return false;
537
+            }
538
+            if (is_null($mtime)) {
539
+                $mtime = time();
540
+            }
541
+            //if native touch fails, we emulate it by changing the mtime in the cache
542
+            $this->putFileInfo($path, ['mtime' => floor($mtime)]);
543
+        }
544
+        return true;
545
+    }
546
+
547
+    /**
548
+     * @param string $path
549
+     * @return string|false
550
+     * @throws LockedException
551
+     */
552
+    public function file_get_contents($path) {
553
+        return $this->basicOperation('file_get_contents', $path, ['read']);
554
+    }
555
+
556
+    protected function emit_file_hooks_pre(bool $exists, string $path, bool &$run): void {
557
+        if (!$exists) {
558
+            \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
559
+                Filesystem::signal_param_path => $this->getHookPath($path),
560
+                Filesystem::signal_param_run => &$run,
561
+            ]);
562
+        } else {
563
+            \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
564
+                Filesystem::signal_param_path => $this->getHookPath($path),
565
+                Filesystem::signal_param_run => &$run,
566
+            ]);
567
+        }
568
+        \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
569
+            Filesystem::signal_param_path => $this->getHookPath($path),
570
+            Filesystem::signal_param_run => &$run,
571
+        ]);
572
+    }
573
+
574
+    protected function emit_file_hooks_post(bool $exists, string $path): void {
575
+        if (!$exists) {
576
+            \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
577
+                Filesystem::signal_param_path => $this->getHookPath($path),
578
+            ]);
579
+        } else {
580
+            \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
581
+                Filesystem::signal_param_path => $this->getHookPath($path),
582
+            ]);
583
+        }
584
+        \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
585
+            Filesystem::signal_param_path => $this->getHookPath($path),
586
+        ]);
587
+    }
588
+
589
+    /**
590
+     * @param string $path
591
+     * @param string|resource $data
592
+     * @return bool|mixed
593
+     * @throws LockedException
594
+     */
595
+    public function file_put_contents($path, $data) {
596
+        if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
597
+            $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
598
+            if (Filesystem::isValidPath($path)
599
+                && !Filesystem::isFileBlacklisted($path)
600
+            ) {
601
+                $path = $this->getRelativePath($absolutePath);
602
+                if ($path === null) {
603
+                    throw new InvalidPathException("Path $absolutePath is not in the expected root");
604
+                }
605
+
606
+                $this->lockFile($path, ILockingProvider::LOCK_SHARED);
607
+
608
+                $exists = $this->file_exists($path);
609
+                if ($this->shouldEmitHooks($path)) {
610
+                    $run = true;
611
+                    $this->emit_file_hooks_pre($exists, $path, $run);
612
+                    if (!$run) {
613
+                        $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
614
+                        return false;
615
+                    }
616
+                }
617
+
618
+                try {
619
+                    $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
620
+                } catch (\Exception $e) {
621
+                    // Release the shared lock before throwing.
622
+                    $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
623
+                    throw $e;
624
+                }
625
+
626
+                /** @var Storage $storage */
627
+                [$storage, $internalPath] = $this->resolvePath($path);
628
+                $target = $storage->fopen($internalPath, 'w');
629
+                if ($target) {
630
+                    [, $result] = Files::streamCopy($data, $target, true);
631
+                    fclose($target);
632
+                    fclose($data);
633
+
634
+                    $this->writeUpdate($storage, $internalPath);
635
+
636
+                    $this->changeLock($path, ILockingProvider::LOCK_SHARED);
637
+
638
+                    if ($this->shouldEmitHooks($path) && $result !== false) {
639
+                        $this->emit_file_hooks_post($exists, $path);
640
+                    }
641
+                    $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
642
+                    return $result;
643
+                } else {
644
+                    $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
645
+                    return false;
646
+                }
647
+            } else {
648
+                return false;
649
+            }
650
+        } else {
651
+            $hooks = $this->file_exists($path) ? ['update', 'write'] : ['create', 'write'];
652
+            return $this->basicOperation('file_put_contents', $path, $hooks, $data);
653
+        }
654
+    }
655
+
656
+    /**
657
+     * @param string $path
658
+     * @return bool|mixed
659
+     */
660
+    public function unlink($path) {
661
+        if ($path === '' || $path === '/') {
662
+            // do not allow deleting the root
663
+            return false;
664
+        }
665
+        $postFix = (substr($path, -1) === '/') ? '/' : '';
666
+        $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
667
+        $mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
668
+        if ($mount->getInternalPath($absolutePath) === '') {
669
+            return $this->removeMount($mount, $absolutePath);
670
+        }
671
+        if ($this->is_dir($path)) {
672
+            $result = $this->basicOperation('rmdir', $path, ['delete']);
673
+        } else {
674
+            $result = $this->basicOperation('unlink', $path, ['delete']);
675
+        }
676
+        if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
677
+            $storage = $mount->getStorage();
678
+            $internalPath = $mount->getInternalPath($absolutePath);
679
+            $storage->getUpdater()->remove($internalPath);
680
+            return true;
681
+        } else {
682
+            return $result;
683
+        }
684
+    }
685
+
686
+    /**
687
+     * @param string $directory
688
+     * @return bool|mixed
689
+     */
690
+    public function deleteAll($directory) {
691
+        return $this->rmdir($directory);
692
+    }
693
+
694
+    /**
695
+     * Rename/move a file or folder from the source path to target path.
696
+     *
697
+     * @param string $source source path
698
+     * @param string $target target path
699
+     * @param array $options
700
+     *
701
+     * @return bool|mixed
702
+     * @throws LockedException
703
+     */
704
+    public function rename($source, $target, array $options = []) {
705
+        $checkSubMounts = $options['checkSubMounts'] ?? true;
706
+
707
+        $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($source));
708
+        $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($target));
709
+
710
+        if (str_starts_with($absolutePath2, $absolutePath1 . '/')) {
711
+            throw new ForbiddenException('Moving a folder into a child folder is forbidden', false);
712
+        }
713
+
714
+        /** @var IMountManager $mountManager */
715
+        $mountManager = \OC::$server->get(IMountManager::class);
716
+
717
+        $targetParts = explode('/', $absolutePath2);
718
+        $targetUser = $targetParts[1] ?? null;
719
+        $result = false;
720
+        if (
721
+            Filesystem::isValidPath($target)
722
+            && Filesystem::isValidPath($source)
723
+            && !Filesystem::isFileBlacklisted($target)
724
+        ) {
725
+            $source = $this->getRelativePath($absolutePath1);
726
+            $target = $this->getRelativePath($absolutePath2);
727
+            $exists = $this->file_exists($target);
728
+
729
+            if ($source == null || $target == null) {
730
+                return false;
731
+            }
732
+
733
+            try {
734
+                $this->verifyPath(dirname($target), basename($target));
735
+            } catch (InvalidPathException) {
736
+                return false;
737
+            }
738
+
739
+            $this->lockFile($source, ILockingProvider::LOCK_SHARED, true);
740
+            try {
741
+                $this->lockFile($target, ILockingProvider::LOCK_SHARED, true);
742
+
743
+                $run = true;
744
+                if ($this->shouldEmitHooks($source) && (Cache\Scanner::isPartialFile($source) && !Cache\Scanner::isPartialFile($target))) {
745
+                    // if it was a rename from a part file to a regular file it was a write and not a rename operation
746
+                    $this->emit_file_hooks_pre($exists, $target, $run);
747
+                } elseif ($this->shouldEmitHooks($source)) {
748
+                    $sourcePath = $this->getHookPath($source);
749
+                    $targetPath = $this->getHookPath($target);
750
+                    if ($sourcePath !== null && $targetPath !== null) {
751
+                        \OC_Hook::emit(
752
+                            Filesystem::CLASSNAME, Filesystem::signal_rename,
753
+                            [
754
+                                Filesystem::signal_param_oldpath => $sourcePath,
755
+                                Filesystem::signal_param_newpath => $targetPath,
756
+                                Filesystem::signal_param_run => &$run
757
+                            ]
758
+                        );
759
+                    }
760
+                }
761
+                if ($run) {
762
+                    $manager = Filesystem::getMountManager();
763
+                    $mount1 = $this->getMount($source);
764
+                    $mount2 = $this->getMount($target);
765
+                    $storage1 = $mount1->getStorage();
766
+                    $storage2 = $mount2->getStorage();
767
+                    $internalPath1 = $mount1->getInternalPath($absolutePath1);
768
+                    $internalPath2 = $mount2->getInternalPath($absolutePath2);
769
+
770
+                    $this->changeLock($source, ILockingProvider::LOCK_EXCLUSIVE, true);
771
+                    try {
772
+                        $this->changeLock($target, ILockingProvider::LOCK_EXCLUSIVE, true);
773
+
774
+                        if ($checkSubMounts) {
775
+                            $movedMounts = $mountManager->findIn($this->getAbsolutePath($source));
776
+                        } else {
777
+                            $movedMounts = [];
778
+                        }
779
+
780
+                        if ($internalPath1 === '') {
781
+                            $sourceParentMount = $this->getMount(dirname($source));
782
+                            $movedMounts[] = $mount1;
783
+                            $this->validateMountMove($movedMounts, $sourceParentMount, $mount2, !$this->targetIsNotShared($targetUser, $absolutePath2));
784
+                            /**
785
+                             * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
786
+                             */
787
+                            $sourceMountPoint = $mount1->getMountPoint();
788
+                            $result = $mount1->moveMount($absolutePath2);
789
+                            $manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
790
+
791
+                            // moving a file/folder within the same mount point
792
+                        } elseif ($storage1 === $storage2) {
793
+                            if (count($movedMounts) > 0) {
794
+                                $this->validateMountMove($movedMounts, $mount1, $mount2, !$this->targetIsNotShared($targetUser, $absolutePath2));
795
+                            }
796
+                            if ($storage1) {
797
+                                $result = $storage1->rename($internalPath1, $internalPath2);
798
+                            } else {
799
+                                $result = false;
800
+                            }
801
+                            // moving a file/folder between storages (from $storage1 to $storage2)
802
+                        } else {
803
+                            if (count($movedMounts) > 0) {
804
+                                $this->validateMountMove($movedMounts, $mount1, $mount2, !$this->targetIsNotShared($targetUser, $absolutePath2));
805
+                            }
806
+                            $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
807
+                        }
808
+
809
+                        if ((Cache\Scanner::isPartialFile($source) && !Cache\Scanner::isPartialFile($target)) && $result !== false) {
810
+                            // if it was a rename from a part file to a regular file it was a write and not a rename operation
811
+                            $this->writeUpdate($storage2, $internalPath2);
812
+                        } elseif ($result) {
813
+                            if ($internalPath1 !== '') { // don't do a cache update for moved mounts
814
+                                $this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
815
+                            }
816
+                        }
817
+                    } catch (\Exception $e) {
818
+                        throw $e;
819
+                    } finally {
820
+                        $this->changeLock($source, ILockingProvider::LOCK_SHARED, true);
821
+                        $this->changeLock($target, ILockingProvider::LOCK_SHARED, true);
822
+                    }
823
+
824
+                    if ((Cache\Scanner::isPartialFile($source) && !Cache\Scanner::isPartialFile($target)) && $result !== false) {
825
+                        if ($this->shouldEmitHooks()) {
826
+                            $this->emit_file_hooks_post($exists, $target);
827
+                        }
828
+                    } elseif ($result) {
829
+                        if ($this->shouldEmitHooks($source) && $this->shouldEmitHooks($target)) {
830
+                            $sourcePath = $this->getHookPath($source);
831
+                            $targetPath = $this->getHookPath($target);
832
+                            if ($sourcePath !== null && $targetPath !== null) {
833
+                                \OC_Hook::emit(
834
+                                    Filesystem::CLASSNAME,
835
+                                    Filesystem::signal_post_rename,
836
+                                    [
837
+                                        Filesystem::signal_param_oldpath => $sourcePath,
838
+                                        Filesystem::signal_param_newpath => $targetPath,
839
+                                    ]
840
+                                );
841
+                            }
842
+                        }
843
+                    }
844
+                }
845
+            } catch (\Exception $e) {
846
+                throw $e;
847
+            } finally {
848
+                $this->unlockFile($source, ILockingProvider::LOCK_SHARED, true);
849
+                $this->unlockFile($target, ILockingProvider::LOCK_SHARED, true);
850
+            }
851
+        }
852
+        return $result;
853
+    }
854
+
855
+    /**
856
+     * @throws ForbiddenException
857
+     */
858
+    private function validateMountMove(array $mounts, IMountPoint $sourceMount, IMountPoint $targetMount, bool $targetIsShared): void {
859
+        $targetPath = $this->getRelativePath($targetMount->getMountPoint());
860
+        if ($targetPath) {
861
+            $targetPath = trim($targetPath, '/');
862
+        } else {
863
+            $targetPath = $targetMount->getMountPoint();
864
+        }
865
+
866
+        $l = \OC::$server->get(IFactory::class)->get('files');
867
+        foreach ($mounts as $mount) {
868
+            $sourcePath = $this->getRelativePath($mount->getMountPoint());
869
+            if ($sourcePath) {
870
+                $sourcePath = trim($sourcePath, '/');
871
+            } else {
872
+                $sourcePath = $mount->getMountPoint();
873
+            }
874
+
875
+            if (!$mount instanceof MoveableMount) {
876
+                throw new ForbiddenException($l->t('Storage %s cannot be moved', [$sourcePath]), false);
877
+            }
878
+
879
+            if ($targetIsShared) {
880
+                if ($sourceMount instanceof SharedMount) {
881
+                    throw new ForbiddenException($l->t('Moving a share (%s) into a shared folder is not allowed', [$sourcePath]), false);
882
+                } else {
883
+                    throw new ForbiddenException($l->t('Moving a storage (%s) into a shared folder is not allowed', [$sourcePath]), false);
884
+                }
885
+            }
886
+
887
+            if ($sourceMount !== $targetMount) {
888
+                if ($sourceMount instanceof SharedMount) {
889
+                    if ($targetMount instanceof SharedMount) {
890
+                        throw new ForbiddenException($l->t('Moving a share (%s) into another share (%s) is not allowed', [$sourcePath, $targetPath]), false);
891
+                    } else {
892
+                        throw new ForbiddenException($l->t('Moving a share (%s) into another storage (%s) is not allowed', [$sourcePath, $targetPath]), false);
893
+                    }
894
+                } else {
895
+                    if ($targetMount instanceof SharedMount) {
896
+                        throw new ForbiddenException($l->t('Moving a storage (%s) into a share (%s) is not allowed', [$sourcePath, $targetPath]), false);
897
+                    } else {
898
+                        throw new ForbiddenException($l->t('Moving a storage (%s) into another storage (%s) is not allowed', [$sourcePath, $targetPath]), false);
899
+                    }
900
+                }
901
+            }
902
+        }
903
+    }
904
+
905
+    /**
906
+     * Copy a file/folder from the source path to target path
907
+     *
908
+     * @param string $source source path
909
+     * @param string $target target path
910
+     * @param bool $preserveMtime whether to preserve mtime on the copy
911
+     *
912
+     * @return bool|mixed
913
+     */
914
+    public function copy($source, $target, $preserveMtime = false) {
915
+        $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($source));
916
+        $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($target));
917
+        $result = false;
918
+        if (
919
+            Filesystem::isValidPath($target)
920
+            && Filesystem::isValidPath($source)
921
+            && !Filesystem::isFileBlacklisted($target)
922
+        ) {
923
+            $source = $this->getRelativePath($absolutePath1);
924
+            $target = $this->getRelativePath($absolutePath2);
925
+
926
+            if ($source == null || $target == null) {
927
+                return false;
928
+            }
929
+            $run = true;
930
+
931
+            $this->lockFile($target, ILockingProvider::LOCK_SHARED);
932
+            $this->lockFile($source, ILockingProvider::LOCK_SHARED);
933
+            $lockTypePath1 = ILockingProvider::LOCK_SHARED;
934
+            $lockTypePath2 = ILockingProvider::LOCK_SHARED;
935
+
936
+            try {
937
+                $exists = $this->file_exists($target);
938
+                if ($this->shouldEmitHooks($target)) {
939
+                    \OC_Hook::emit(
940
+                        Filesystem::CLASSNAME,
941
+                        Filesystem::signal_copy,
942
+                        [
943
+                            Filesystem::signal_param_oldpath => $this->getHookPath($source),
944
+                            Filesystem::signal_param_newpath => $this->getHookPath($target),
945
+                            Filesystem::signal_param_run => &$run
946
+                        ]
947
+                    );
948
+                    $this->emit_file_hooks_pre($exists, $target, $run);
949
+                }
950
+                if ($run) {
951
+                    $mount1 = $this->getMount($source);
952
+                    $mount2 = $this->getMount($target);
953
+                    $storage1 = $mount1->getStorage();
954
+                    $internalPath1 = $mount1->getInternalPath($absolutePath1);
955
+                    $storage2 = $mount2->getStorage();
956
+                    $internalPath2 = $mount2->getInternalPath($absolutePath2);
957
+
958
+                    $this->changeLock($target, ILockingProvider::LOCK_EXCLUSIVE);
959
+                    $lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
960
+
961
+                    if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
962
+                        if ($storage1) {
963
+                            $result = $storage1->copy($internalPath1, $internalPath2);
964
+                        } else {
965
+                            $result = false;
966
+                        }
967
+                    } else {
968
+                        $result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
969
+                    }
970
+
971
+                    if ($result) {
972
+                        $this->copyUpdate($storage1, $storage2, $internalPath1, $internalPath2);
973
+                    }
974
+
975
+                    $this->changeLock($target, ILockingProvider::LOCK_SHARED);
976
+                    $lockTypePath2 = ILockingProvider::LOCK_SHARED;
977
+
978
+                    if ($this->shouldEmitHooks($target) && $result !== false) {
979
+                        \OC_Hook::emit(
980
+                            Filesystem::CLASSNAME,
981
+                            Filesystem::signal_post_copy,
982
+                            [
983
+                                Filesystem::signal_param_oldpath => $this->getHookPath($source),
984
+                                Filesystem::signal_param_newpath => $this->getHookPath($target)
985
+                            ]
986
+                        );
987
+                        $this->emit_file_hooks_post($exists, $target);
988
+                    }
989
+                }
990
+            } catch (\Exception $e) {
991
+                $this->unlockFile($target, $lockTypePath2);
992
+                $this->unlockFile($source, $lockTypePath1);
993
+                throw $e;
994
+            }
995
+
996
+            $this->unlockFile($target, $lockTypePath2);
997
+            $this->unlockFile($source, $lockTypePath1);
998
+        }
999
+        return $result;
1000
+    }
1001
+
1002
+    /**
1003
+     * @param string $path
1004
+     * @param string $mode 'r' or 'w'
1005
+     * @return resource|false
1006
+     * @throws LockedException
1007
+     */
1008
+    public function fopen($path, $mode) {
1009
+        $mode = str_replace('b', '', $mode); // the binary flag is a windows only feature which we do not support
1010
+        $hooks = [];
1011
+        switch ($mode) {
1012
+            case 'r':
1013
+                $hooks[] = 'read';
1014
+                break;
1015
+            case 'r+':
1016
+            case 'w+':
1017
+            case 'x+':
1018
+            case 'a+':
1019
+                $hooks[] = 'read';
1020
+                $hooks[] = 'write';
1021
+                break;
1022
+            case 'w':
1023
+            case 'x':
1024
+            case 'a':
1025
+                $hooks[] = 'write';
1026
+                break;
1027
+            default:
1028
+                $this->logger->error('invalid mode (' . $mode . ') for ' . $path, ['app' => 'core']);
1029
+        }
1030
+
1031
+        if ($mode !== 'r' && $mode !== 'w') {
1032
+            $this->logger->info('Trying to open a file with a mode other than "r" or "w" can cause severe performance issues with some backends', ['app' => 'core']);
1033
+        }
1034
+
1035
+        $handle = $this->basicOperation('fopen', $path, $hooks, $mode);
1036
+        if (!is_resource($handle) && $mode === 'r') {
1037
+            // trying to read a file that isn't on disk, check if the cache is out of sync and rescan if needed
1038
+            $mount = $this->getMount($path);
1039
+            $internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1040
+            $storage = $mount->getStorage();
1041
+            if ($storage->getCache()->inCache($internalPath) && !$storage->file_exists($path)) {
1042
+                $this->writeUpdate($storage, $internalPath);
1043
+            }
1044
+        }
1045
+        return $handle;
1046
+    }
1047
+
1048
+    /**
1049
+     * @param string $path
1050
+     * @throws InvalidPathException
1051
+     */
1052
+    public function toTmpFile($path): string|false {
1053
+        $this->assertPathLength($path);
1054
+        if (Filesystem::isValidPath($path)) {
1055
+            $source = $this->fopen($path, 'r');
1056
+            if ($source) {
1057
+                $extension = pathinfo($path, PATHINFO_EXTENSION);
1058
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
1059
+                file_put_contents($tmpFile, $source);
1060
+                return $tmpFile;
1061
+            } else {
1062
+                return false;
1063
+            }
1064
+        } else {
1065
+            return false;
1066
+        }
1067
+    }
1068
+
1069
+    /**
1070
+     * @param string $tmpFile
1071
+     * @param string $path
1072
+     * @return bool|mixed
1073
+     * @throws InvalidPathException
1074
+     */
1075
+    public function fromTmpFile($tmpFile, $path) {
1076
+        $this->assertPathLength($path);
1077
+        if (Filesystem::isValidPath($path)) {
1078
+            // Get directory that the file is going into
1079
+            $filePath = dirname($path);
1080
+
1081
+            // Create the directories if any
1082
+            if (!$this->file_exists($filePath)) {
1083
+                $result = $this->createParentDirectories($filePath);
1084
+                if ($result === false) {
1085
+                    return false;
1086
+                }
1087
+            }
1088
+
1089
+            $source = fopen($tmpFile, 'r');
1090
+            if ($source) {
1091
+                $result = $this->file_put_contents($path, $source);
1092
+                /**
1093
+                 * $this->file_put_contents() might have already closed
1094
+                 * the resource, so we check it, before trying to close it
1095
+                 * to avoid messages in the error log.
1096
+                 * @psalm-suppress RedundantCondition false-positive
1097
+                 */
1098
+                if (is_resource($source)) {
1099
+                    fclose($source);
1100
+                }
1101
+                unlink($tmpFile);
1102
+                return $result;
1103
+            } else {
1104
+                return false;
1105
+            }
1106
+        } else {
1107
+            return false;
1108
+        }
1109
+    }
1110
+
1111
+
1112
+    /**
1113
+     * @param string $path
1114
+     * @return mixed
1115
+     * @throws InvalidPathException
1116
+     */
1117
+    public function getMimeType($path) {
1118
+        $this->assertPathLength($path);
1119
+        return $this->basicOperation('getMimeType', $path);
1120
+    }
1121
+
1122
+    /**
1123
+     * @param string $type
1124
+     * @param string $path
1125
+     * @param bool $raw
1126
+     */
1127
+    public function hash($type, $path, $raw = false): string|bool {
1128
+        $postFix = (substr($path, -1) === '/') ? '/' : '';
1129
+        $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1130
+        if (Filesystem::isValidPath($path)) {
1131
+            $path = $this->getRelativePath($absolutePath);
1132
+            if ($path == null) {
1133
+                return false;
1134
+            }
1135
+            if ($this->shouldEmitHooks($path)) {
1136
+                \OC_Hook::emit(
1137
+                    Filesystem::CLASSNAME,
1138
+                    Filesystem::signal_read,
1139
+                    [Filesystem::signal_param_path => $this->getHookPath($path)]
1140
+                );
1141
+            }
1142
+            /** @var Storage|null $storage */
1143
+            [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1144
+            if ($storage) {
1145
+                return $storage->hash($type, $internalPath, $raw);
1146
+            }
1147
+        }
1148
+        return false;
1149
+    }
1150
+
1151
+    /**
1152
+     * @param string $path
1153
+     * @return mixed
1154
+     * @throws InvalidPathException
1155
+     */
1156
+    public function free_space($path = '/') {
1157
+        $this->assertPathLength($path);
1158
+        $result = $this->basicOperation('free_space', $path);
1159
+        if ($result === null) {
1160
+            throw new InvalidPathException();
1161
+        }
1162
+        return $result;
1163
+    }
1164
+
1165
+    /**
1166
+     * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1167
+     *
1168
+     * @param mixed $extraParam (optional)
1169
+     * @return mixed
1170
+     * @throws LockedException
1171
+     *
1172
+     * This method takes requests for basic filesystem functions (e.g. reading & writing
1173
+     * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1174
+     * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1175
+     */
1176
+    private function basicOperation(string $operation, string $path, array $hooks = [], $extraParam = null) {
1177
+        $postFix = (substr($path, -1) === '/') ? '/' : '';
1178
+        $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1179
+        if (Filesystem::isValidPath($path)
1180
+            && !Filesystem::isFileBlacklisted($path)
1181
+        ) {
1182
+            $path = $this->getRelativePath($absolutePath);
1183
+            if ($path == null) {
1184
+                return false;
1185
+            }
1186
+
1187
+            if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1188
+                // always a shared lock during pre-hooks so the hook can read the file
1189
+                $this->lockFile($path, ILockingProvider::LOCK_SHARED);
1190
+            }
1191
+
1192
+            $run = $this->runHooks($hooks, $path);
1193
+            [$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1194
+            if ($run && $storage) {
1195
+                /** @var Storage $storage */
1196
+                if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1197
+                    try {
1198
+                        $this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1199
+                    } catch (LockedException $e) {
1200
+                        // release the shared lock we acquired before quitting
1201
+                        $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1202
+                        throw $e;
1203
+                    }
1204
+                }
1205
+                try {
1206
+                    if (!is_null($extraParam)) {
1207
+                        $result = $storage->$operation($internalPath, $extraParam);
1208
+                    } else {
1209
+                        $result = $storage->$operation($internalPath);
1210
+                    }
1211
+                } catch (\Exception $e) {
1212
+                    if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1213
+                        $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1214
+                    } elseif (in_array('read', $hooks)) {
1215
+                        $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1216
+                    }
1217
+                    throw $e;
1218
+                }
1219
+
1220
+                if ($result !== false && in_array('delete', $hooks)) {
1221
+                    $this->removeUpdate($storage, $internalPath);
1222
+                }
1223
+                if ($result !== false && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') {
1224
+                    $isCreateOperation = $operation === 'mkdir' || ($operation === 'file_put_contents' && in_array('create', $hooks, true));
1225
+                    $sizeDifference = $operation === 'mkdir' ? 0 : $result;
1226
+                    $this->writeUpdate($storage, $internalPath, null, $isCreateOperation ? $sizeDifference : null);
1227
+                }
1228
+                if ($result !== false && in_array('touch', $hooks)) {
1229
+                    $this->writeUpdate($storage, $internalPath, $extraParam, 0);
1230
+                }
1231
+
1232
+                if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1233
+                    $this->changeLock($path, ILockingProvider::LOCK_SHARED);
1234
+                }
1235
+
1236
+                $unlockLater = false;
1237
+                if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1238
+                    $unlockLater = true;
1239
+                    // make sure our unlocking callback will still be called if connection is aborted
1240
+                    ignore_user_abort(true);
1241
+                    $result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1242
+                        if (in_array('write', $hooks)) {
1243
+                            $this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1244
+                        } elseif (in_array('read', $hooks)) {
1245
+                            $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1246
+                        }
1247
+                    });
1248
+                }
1249
+
1250
+                if ($this->shouldEmitHooks($path) && $result !== false) {
1251
+                    if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1252
+                        $this->runHooks($hooks, $path, true);
1253
+                    }
1254
+                }
1255
+
1256
+                if (!$unlockLater
1257
+                    && (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1258
+                ) {
1259
+                    $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1260
+                }
1261
+                return $result;
1262
+            } else {
1263
+                $this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1264
+            }
1265
+        }
1266
+        return null;
1267
+    }
1268
+
1269
+    /**
1270
+     * get the path relative to the default root for hook usage
1271
+     *
1272
+     * @param string $path
1273
+     * @return ?string
1274
+     */
1275
+    private function getHookPath($path): ?string {
1276
+        $view = Filesystem::getView();
1277
+        if (!$view) {
1278
+            return $path;
1279
+        }
1280
+        return $view->getRelativePath($this->getAbsolutePath($path));
1281
+    }
1282
+
1283
+    private function shouldEmitHooks(string $path = ''): bool {
1284
+        if ($path && Cache\Scanner::isPartialFile($path)) {
1285
+            return false;
1286
+        }
1287
+        if (!Filesystem::$loaded) {
1288
+            return false;
1289
+        }
1290
+        $defaultRoot = Filesystem::getRoot();
1291
+        if ($defaultRoot === null) {
1292
+            return false;
1293
+        }
1294
+        if ($this->fakeRoot === $defaultRoot) {
1295
+            return true;
1296
+        }
1297
+        $fullPath = $this->getAbsolutePath($path);
1298
+
1299
+        if ($fullPath === $defaultRoot) {
1300
+            return true;
1301
+        }
1302
+
1303
+        return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1304
+    }
1305
+
1306
+    /**
1307
+     * @param string[] $hooks
1308
+     * @param string $path
1309
+     * @param bool $post
1310
+     * @return bool
1311
+     */
1312
+    private function runHooks($hooks, $path, $post = false) {
1313
+        $relativePath = $path;
1314
+        $path = $this->getHookPath($path);
1315
+        $prefix = $post ? 'post_' : '';
1316
+        $run = true;
1317
+        if ($this->shouldEmitHooks($relativePath)) {
1318
+            foreach ($hooks as $hook) {
1319
+                if ($hook != 'read') {
1320
+                    \OC_Hook::emit(
1321
+                        Filesystem::CLASSNAME,
1322
+                        $prefix . $hook,
1323
+                        [
1324
+                            Filesystem::signal_param_run => &$run,
1325
+                            Filesystem::signal_param_path => $path
1326
+                        ]
1327
+                    );
1328
+                } elseif (!$post) {
1329
+                    \OC_Hook::emit(
1330
+                        Filesystem::CLASSNAME,
1331
+                        $prefix . $hook,
1332
+                        [
1333
+                            Filesystem::signal_param_path => $path
1334
+                        ]
1335
+                    );
1336
+                }
1337
+            }
1338
+        }
1339
+        return $run;
1340
+    }
1341
+
1342
+    /**
1343
+     * check if a file or folder has been updated since $time
1344
+     *
1345
+     * @param string $path
1346
+     * @param int $time
1347
+     * @return bool
1348
+     */
1349
+    public function hasUpdated($path, $time) {
1350
+        return $this->basicOperation('hasUpdated', $path, [], $time);
1351
+    }
1352
+
1353
+    /**
1354
+     * @param string $ownerId
1355
+     * @return IUser
1356
+     */
1357
+    private function getUserObjectForOwner(string $ownerId) {
1358
+        return new LazyUser($ownerId, $this->userManager);
1359
+    }
1360
+
1361
+    /**
1362
+     * Get file info from cache
1363
+     *
1364
+     * If the file is not in cached it will be scanned
1365
+     * If the file has changed on storage the cache will be updated
1366
+     *
1367
+     * @param Storage $storage
1368
+     * @param string $internalPath
1369
+     * @param string $relativePath
1370
+     * @return ICacheEntry|bool
1371
+     */
1372
+    private function getCacheEntry($storage, $internalPath, $relativePath) {
1373
+        $cache = $storage->getCache($internalPath);
1374
+        $data = $cache->get($internalPath);
1375
+        $watcher = $storage->getWatcher($internalPath);
1376
+
1377
+        try {
1378
+            // if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1379
+            if (!$data || (isset($data['size']) && $data['size'] === -1)) {
1380
+                if (!$storage->file_exists($internalPath)) {
1381
+                    return false;
1382
+                }
1383
+                // don't need to get a lock here since the scanner does it's own locking
1384
+                $scanner = $storage->getScanner($internalPath);
1385
+                $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1386
+                $data = $cache->get($internalPath);
1387
+            } elseif (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1388
+                $this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1389
+                $watcher->update($internalPath, $data);
1390
+                $storage->getPropagator()->propagateChange($internalPath, time());
1391
+                $data = $cache->get($internalPath);
1392
+                $this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1393
+            }
1394
+        } catch (LockedException $e) {
1395
+            // if the file is locked we just use the old cache info
1396
+        }
1397
+
1398
+        return $data;
1399
+    }
1400
+
1401
+    /**
1402
+     * get the filesystem info
1403
+     *
1404
+     * @param string $path
1405
+     * @param bool|string $includeMountPoints true to add mountpoint sizes,
1406
+     *                                        'ext' to add only ext storage mount point sizes. Defaults to true.
1407
+     * @return \OC\Files\FileInfo|false False if file does not exist
1408
+     */
1409
+    public function getFileInfo($path, $includeMountPoints = true) {
1410
+        $this->assertPathLength($path);
1411
+        if (!Filesystem::isValidPath($path)) {
1412
+            return false;
1413
+        }
1414
+        $relativePath = $path;
1415
+        $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1416
+
1417
+        $mount = Filesystem::getMountManager()->find($path);
1418
+        $storage = $mount->getStorage();
1419
+        $internalPath = $mount->getInternalPath($path);
1420
+        if ($storage) {
1421
+            $data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1422
+
1423
+            if (!$data instanceof ICacheEntry) {
1424
+                if (Cache\Scanner::isPartialFile($relativePath)) {
1425
+                    return $this->getPartFileInfo($relativePath);
1426
+                }
1427
+
1428
+                return false;
1429
+            }
1430
+
1431
+            if ($mount instanceof MoveableMount && $internalPath === '') {
1432
+                $data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1433
+            }
1434
+            if ($internalPath === '' && $data['name']) {
1435
+                $data['name'] = basename($path);
1436
+            }
1437
+
1438
+            $ownerId = $storage->getOwner($internalPath);
1439
+            $owner = null;
1440
+            if ($ownerId !== false) {
1441
+                // ownerId might be null if files are accessed with an access token without file system access
1442
+                $owner = $this->getUserObjectForOwner($ownerId);
1443
+            }
1444
+            $info = new FileInfo($path, $storage, $internalPath, $data, $mount, $owner);
1445
+
1446
+            if (isset($data['fileid'])) {
1447
+                if ($includeMountPoints && $data['mimetype'] === 'httpd/unix-directory') {
1448
+                    //add the sizes of other mount points to the folder
1449
+                    $extOnly = ($includeMountPoints === 'ext');
1450
+                    $this->addSubMounts($info, $extOnly);
1451
+                }
1452
+            }
1453
+
1454
+            return $info;
1455
+        } else {
1456
+            $this->logger->warning('Storage not valid for mountpoint: ' . $mount->getMountPoint(), ['app' => 'core']);
1457
+        }
1458
+
1459
+        return false;
1460
+    }
1461
+
1462
+    /**
1463
+     * Extend a FileInfo that was previously requested with `$includeMountPoints = false` to include the sub mounts
1464
+     */
1465
+    public function addSubMounts(FileInfo $info, $extOnly = false): void {
1466
+        $mounts = Filesystem::getMountManager()->findIn($info->getPath());
1467
+        $info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1468
+            return !($extOnly && $mount instanceof SharedMount);
1469
+        }));
1470
+    }
1471
+
1472
+    /**
1473
+     * get the content of a directory
1474
+     *
1475
+     * @param string $directory path under datadirectory
1476
+     * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1477
+     * @return FileInfo[]
1478
+     */
1479
+    public function getDirectoryContent($directory, $mimetype_filter = '', ?\OCP\Files\FileInfo $directoryInfo = null) {
1480
+        $this->assertPathLength($directory);
1481
+        if (!Filesystem::isValidPath($directory)) {
1482
+            return [];
1483
+        }
1484
+
1485
+        $path = $this->getAbsolutePath($directory);
1486
+        $path = Filesystem::normalizePath($path);
1487
+        $mount = $this->getMount($directory);
1488
+        $storage = $mount->getStorage();
1489
+        $internalPath = $mount->getInternalPath($path);
1490
+        if (!$storage) {
1491
+            return [];
1492
+        }
1493
+
1494
+        $cache = $storage->getCache($internalPath);
1495
+        $user = \OC_User::getUser();
1496
+
1497
+        if (!$directoryInfo) {
1498
+            $data = $this->getCacheEntry($storage, $internalPath, $directory);
1499
+            if (!$data instanceof ICacheEntry || !isset($data['fileid'])) {
1500
+                return [];
1501
+            }
1502
+        } else {
1503
+            $data = $directoryInfo;
1504
+        }
1505
+
1506
+        if (!($data->getPermissions() & Constants::PERMISSION_READ)) {
1507
+            return [];
1508
+        }
1509
+
1510
+        $folderId = $data->getId();
1511
+        $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1512
+
1513
+        $sharingDisabled = \OCP\Util::isSharingDisabledForUser();
1514
+
1515
+        $fileNames = array_map(function (ICacheEntry $content) {
1516
+            return $content->getName();
1517
+        }, $contents);
1518
+        /**
1519
+         * @var \OC\Files\FileInfo[] $fileInfos
1520
+         */
1521
+        $fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1522
+            if ($sharingDisabled) {
1523
+                $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1524
+            }
1525
+            $ownerId = $storage->getOwner($content['path']);
1526
+            if ($ownerId !== false) {
1527
+                $owner = $this->getUserObjectForOwner($ownerId);
1528
+            } else {
1529
+                $owner = null;
1530
+            }
1531
+            return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
1532
+        }, $contents);
1533
+        $files = array_combine($fileNames, $fileInfos);
1534
+
1535
+        //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1536
+        $mounts = Filesystem::getMountManager()->findIn($path);
1537
+
1538
+        // make sure nested mounts are sorted after their parent mounts
1539
+        // otherwise doesn't propagate the etag across storage boundaries correctly
1540
+        usort($mounts, function (IMountPoint $a, IMountPoint $b) {
1541
+            return $a->getMountPoint() <=> $b->getMountPoint();
1542
+        });
1543
+
1544
+        $dirLength = strlen($path);
1545
+        foreach ($mounts as $mount) {
1546
+            $mountPoint = $mount->getMountPoint();
1547
+            $subStorage = $mount->getStorage();
1548
+            if ($subStorage) {
1549
+                $subCache = $subStorage->getCache('');
1550
+
1551
+                $rootEntry = $subCache->get('');
1552
+                if (!$rootEntry) {
1553
+                    $subScanner = $subStorage->getScanner();
1554
+                    try {
1555
+                        $subScanner->scanFile('');
1556
+                    } catch (\OCP\Files\StorageNotAvailableException $e) {
1557
+                        continue;
1558
+                    } catch (\OCP\Files\StorageInvalidException $e) {
1559
+                        continue;
1560
+                    } catch (\Exception $e) {
1561
+                        // sometimes when the storage is not available it can be any exception
1562
+                        $this->logger->error('Exception while scanning storage "' . $subStorage->getId() . '"', [
1563
+                            'exception' => $e,
1564
+                            'app' => 'core',
1565
+                        ]);
1566
+                        continue;
1567
+                    }
1568
+                    $rootEntry = $subCache->get('');
1569
+                }
1570
+
1571
+                if ($rootEntry && ($rootEntry->getPermissions() & Constants::PERMISSION_READ)) {
1572
+                    $relativePath = trim(substr($mountPoint, $dirLength), '/');
1573
+                    if ($pos = strpos($relativePath, '/')) {
1574
+                        //mountpoint inside subfolder add size to the correct folder
1575
+                        $entryName = substr($relativePath, 0, $pos);
1576
+
1577
+                        // Create parent folders if the mountpoint is inside a subfolder that doesn't exist yet
1578
+                        if (!isset($files[$entryName])) {
1579
+                            try {
1580
+                                [$storage, ] = $this->resolvePath($path . '/' . $entryName);
1581
+                                // make sure we can create the mountpoint folder, even if the user has a quota of 0
1582
+                                if ($storage->instanceOfStorage(Quota::class)) {
1583
+                                    $storage->enableQuota(false);
1584
+                                }
1585
+
1586
+                                if ($this->mkdir($path . '/' . $entryName) !== false) {
1587
+                                    $info = $this->getFileInfo($path . '/' . $entryName);
1588
+                                    if ($info !== false) {
1589
+                                        $files[$entryName] = $info;
1590
+                                    }
1591
+                                }
1592
+
1593
+                                if ($storage->instanceOfStorage(Quota::class)) {
1594
+                                    $storage->enableQuota(true);
1595
+                                }
1596
+                            } catch (\Exception $e) {
1597
+                                // Creating the parent folder might not be possible, for example due to a lack of permissions.
1598
+                                $this->logger->debug('Failed to create non-existent parent', ['exception' => $e, 'path' => $path . '/' . $entryName]);
1599
+                            }
1600
+                        }
1601
+
1602
+                        if (isset($files[$entryName])) {
1603
+                            $files[$entryName]->addSubEntry($rootEntry, $mountPoint);
1604
+                        }
1605
+                    } else { //mountpoint in this folder, add an entry for it
1606
+                        $rootEntry['name'] = $relativePath;
1607
+                        $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1608
+                        $permissions = $rootEntry['permissions'];
1609
+                        // do not allow renaming/deleting the mount point if they are not shared files/folders
1610
+                        // for shared files/folders we use the permissions given by the owner
1611
+                        if ($mount instanceof MoveableMount) {
1612
+                            $rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1613
+                        } else {
1614
+                            $rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1615
+                        }
1616
+
1617
+                        $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1618
+
1619
+                        // if sharing was disabled for the user we remove the share permissions
1620
+                        if ($sharingDisabled) {
1621
+                            $rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1622
+                        }
1623
+
1624
+                        $ownerId = $subStorage->getOwner('');
1625
+                        if ($ownerId !== false) {
1626
+                            $owner = $this->getUserObjectForOwner($ownerId);
1627
+                        } else {
1628
+                            $owner = null;
1629
+                        }
1630
+                        $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1631
+                    }
1632
+                }
1633
+            }
1634
+        }
1635
+
1636
+        if ($mimetype_filter) {
1637
+            $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1638
+                if (strpos($mimetype_filter, '/')) {
1639
+                    return $file->getMimetype() === $mimetype_filter;
1640
+                } else {
1641
+                    return $file->getMimePart() === $mimetype_filter;
1642
+                }
1643
+            });
1644
+        }
1645
+
1646
+        return array_values($files);
1647
+    }
1648
+
1649
+    /**
1650
+     * change file metadata
1651
+     *
1652
+     * @param string $path
1653
+     * @param array|\OCP\Files\FileInfo $data
1654
+     * @return int
1655
+     *
1656
+     * returns the fileid of the updated file
1657
+     */
1658
+    public function putFileInfo($path, $data) {
1659
+        $this->assertPathLength($path);
1660
+        if ($data instanceof FileInfo) {
1661
+            $data = $data->getData();
1662
+        }
1663
+        $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1664
+        /**
1665
+         * @var Storage $storage
1666
+         * @var string $internalPath
1667
+         */
1668
+        [$storage, $internalPath] = Filesystem::resolvePath($path);
1669
+        if ($storage) {
1670
+            $cache = $storage->getCache($path);
1671
+
1672
+            if (!$cache->inCache($internalPath)) {
1673
+                $scanner = $storage->getScanner($internalPath);
1674
+                $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1675
+            }
1676
+
1677
+            return $cache->put($internalPath, $data);
1678
+        } else {
1679
+            return -1;
1680
+        }
1681
+    }
1682
+
1683
+    /**
1684
+     * search for files with the name matching $query
1685
+     *
1686
+     * @param string $query
1687
+     * @return FileInfo[]
1688
+     */
1689
+    public function search($query) {
1690
+        return $this->searchCommon('search', ['%' . $query . '%']);
1691
+    }
1692
+
1693
+    /**
1694
+     * search for files with the name matching $query
1695
+     *
1696
+     * @param string $query
1697
+     * @return FileInfo[]
1698
+     */
1699
+    public function searchRaw($query) {
1700
+        return $this->searchCommon('search', [$query]);
1701
+    }
1702
+
1703
+    /**
1704
+     * search for files by mimetype
1705
+     *
1706
+     * @param string $mimetype
1707
+     * @return FileInfo[]
1708
+     */
1709
+    public function searchByMime($mimetype) {
1710
+        return $this->searchCommon('searchByMime', [$mimetype]);
1711
+    }
1712
+
1713
+    /**
1714
+     * search for files by tag
1715
+     *
1716
+     * @param string|int $tag name or tag id
1717
+     * @param string $userId owner of the tags
1718
+     * @return FileInfo[]
1719
+     */
1720
+    public function searchByTag($tag, $userId) {
1721
+        return $this->searchCommon('searchByTag', [$tag, $userId]);
1722
+    }
1723
+
1724
+    /**
1725
+     * @param string $method cache method
1726
+     * @param array $args
1727
+     * @return FileInfo[]
1728
+     */
1729
+    private function searchCommon($method, $args) {
1730
+        $files = [];
1731
+        $rootLength = strlen($this->fakeRoot);
1732
+
1733
+        $mount = $this->getMount('');
1734
+        $mountPoint = $mount->getMountPoint();
1735
+        $storage = $mount->getStorage();
1736
+        $userManager = \OC::$server->getUserManager();
1737
+        if ($storage) {
1738
+            $cache = $storage->getCache('');
1739
+
1740
+            $results = call_user_func_array([$cache, $method], $args);
1741
+            foreach ($results as $result) {
1742
+                if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1743
+                    $internalPath = $result['path'];
1744
+                    $path = $mountPoint . $result['path'];
1745
+                    $result['path'] = substr($mountPoint . $result['path'], $rootLength);
1746
+                    $ownerId = $storage->getOwner($internalPath);
1747
+                    if ($ownerId !== false) {
1748
+                        $owner = $userManager->get($ownerId);
1749
+                    } else {
1750
+                        $owner = null;
1751
+                    }
1752
+                    $files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1753
+                }
1754
+            }
1755
+
1756
+            $mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1757
+            foreach ($mounts as $mount) {
1758
+                $mountPoint = $mount->getMountPoint();
1759
+                $storage = $mount->getStorage();
1760
+                if ($storage) {
1761
+                    $cache = $storage->getCache('');
1762
+
1763
+                    $relativeMountPoint = substr($mountPoint, $rootLength);
1764
+                    $results = call_user_func_array([$cache, $method], $args);
1765
+                    if ($results) {
1766
+                        foreach ($results as $result) {
1767
+                            $internalPath = $result['path'];
1768
+                            $result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1769
+                            $path = rtrim($mountPoint . $internalPath, '/');
1770
+                            $ownerId = $storage->getOwner($internalPath);
1771
+                            if ($ownerId !== false) {
1772
+                                $owner = $userManager->get($ownerId);
1773
+                            } else {
1774
+                                $owner = null;
1775
+                            }
1776
+                            $files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1777
+                        }
1778
+                    }
1779
+                }
1780
+            }
1781
+        }
1782
+        return $files;
1783
+    }
1784
+
1785
+    /**
1786
+     * Get the owner for a file or folder
1787
+     *
1788
+     * @throws NotFoundException
1789
+     */
1790
+    public function getOwner(string $path): string {
1791
+        $info = $this->getFileInfo($path);
1792
+        if (!$info) {
1793
+            throw new NotFoundException($path . ' not found while trying to get owner');
1794
+        }
1795
+
1796
+        if ($info->getOwner() === null) {
1797
+            throw new NotFoundException($path . ' has no owner');
1798
+        }
1799
+
1800
+        return $info->getOwner()->getUID();
1801
+    }
1802
+
1803
+    /**
1804
+     * get the ETag for a file or folder
1805
+     *
1806
+     * @param string $path
1807
+     * @return string|false
1808
+     */
1809
+    public function getETag($path) {
1810
+        [$storage, $internalPath] = $this->resolvePath($path);
1811
+        if ($storage) {
1812
+            return $storage->getETag($internalPath);
1813
+        } else {
1814
+            return false;
1815
+        }
1816
+    }
1817
+
1818
+    /**
1819
+     * Get the path of a file by id, relative to the view
1820
+     *
1821
+     * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file
1822
+     *
1823
+     * @param int $id
1824
+     * @param int|null $storageId
1825
+     * @return string
1826
+     * @throws NotFoundException
1827
+     */
1828
+    public function getPath($id, ?int $storageId = null): string {
1829
+        $id = (int)$id;
1830
+        $rootFolder = Server::get(Files\IRootFolder::class);
1831
+
1832
+        $node = $rootFolder->getFirstNodeByIdInPath($id, $this->getRoot());
1833
+        if ($node) {
1834
+            if ($storageId === null || $storageId === $node->getStorage()->getCache()->getNumericStorageId()) {
1835
+                return $this->getRelativePath($node->getPath()) ?? '';
1836
+            }
1837
+        } else {
1838
+            throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1839
+        }
1840
+
1841
+        foreach ($rootFolder->getByIdInPath($id, $this->getRoot()) as $node) {
1842
+            if ($storageId === $node->getStorage()->getCache()->getNumericStorageId()) {
1843
+                return $this->getRelativePath($node->getPath()) ?? '';
1844
+            }
1845
+        }
1846
+
1847
+        throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1848
+    }
1849
+
1850
+    /**
1851
+     * @param string $path
1852
+     * @throws InvalidPathException
1853
+     */
1854
+    private function assertPathLength($path): void {
1855
+        $maxLen = min(PHP_MAXPATHLEN, 4000);
1856
+        // Check for the string length - performed using isset() instead of strlen()
1857
+        // because isset() is about 5x-40x faster.
1858
+        if (isset($path[$maxLen])) {
1859
+            $pathLen = strlen($path);
1860
+            throw new InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1861
+        }
1862
+    }
1863
+
1864
+    /**
1865
+     * check if it is allowed to move a mount point to a given target.
1866
+     * It is not allowed to move a mount point into a different mount point or
1867
+     * into an already shared folder
1868
+     */
1869
+    private function targetIsNotShared(string $user, string $targetPath): bool {
1870
+        $providers = [
1871
+            IShare::TYPE_USER,
1872
+            IShare::TYPE_GROUP,
1873
+            IShare::TYPE_EMAIL,
1874
+            IShare::TYPE_CIRCLE,
1875
+            IShare::TYPE_ROOM,
1876
+            IShare::TYPE_DECK,
1877
+            IShare::TYPE_SCIENCEMESH
1878
+        ];
1879
+        $shareManager = Server::get(IManager::class);
1880
+        /** @var IShare[] $shares */
1881
+        $shares = array_merge(...array_map(function (int $type) use ($shareManager, $user) {
1882
+            return $shareManager->getSharesBy($user, $type);
1883
+        }, $providers));
1884
+
1885
+        foreach ($shares as $share) {
1886
+            try {
1887
+                $sharedPath = $share->getNode()->getPath();
1888
+            } catch (NotFoundException $e) {
1889
+                // node is not found, ignoring
1890
+                $this->logger->debug(
1891
+                    'Could not find the node linked to a share',
1892
+                    ['app' => 'files', 'exception' => $e]);
1893
+                continue;
1894
+            }
1895
+            if ($targetPath === $sharedPath || str_starts_with($targetPath, $sharedPath . '/')) {
1896
+                $this->logger->debug(
1897
+                    'It is not allowed to move one mount point into a shared folder',
1898
+                    ['app' => 'files']);
1899
+                return false;
1900
+            }
1901
+        }
1902
+
1903
+        return true;
1904
+    }
1905
+
1906
+    /**
1907
+     * Get a fileinfo object for files that are ignored in the cache (part files)
1908
+     */
1909
+    private function getPartFileInfo(string $path): \OC\Files\FileInfo {
1910
+        $mount = $this->getMount($path);
1911
+        $storage = $mount->getStorage();
1912
+        $internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1913
+        $ownerId = $storage->getOwner($internalPath);
1914
+        if ($ownerId !== false) {
1915
+            $owner = Server::get(IUserManager::class)->get($ownerId);
1916
+        } else {
1917
+            $owner = null;
1918
+        }
1919
+        return new FileInfo(
1920
+            $this->getAbsolutePath($path),
1921
+            $storage,
1922
+            $internalPath,
1923
+            [
1924
+                'fileid' => null,
1925
+                'mimetype' => $storage->getMimeType($internalPath),
1926
+                'name' => basename($path),
1927
+                'etag' => null,
1928
+                'size' => $storage->filesize($internalPath),
1929
+                'mtime' => $storage->filemtime($internalPath),
1930
+                'encrypted' => false,
1931
+                'permissions' => \OCP\Constants::PERMISSION_ALL
1932
+            ],
1933
+            $mount,
1934
+            $owner
1935
+        );
1936
+    }
1937
+
1938
+    /**
1939
+     * @param string $path
1940
+     * @param string $fileName
1941
+     * @param bool $readonly Check only if the path is allowed for read-only access
1942
+     * @throws InvalidPathException
1943
+     */
1944
+    public function verifyPath($path, $fileName, $readonly = false): void {
1945
+        // All of the view's functions disallow '..' in the path so we can short cut if the path is invalid
1946
+        if (!Filesystem::isValidPath($path ?: '/')) {
1947
+            $l = \OCP\Util::getL10N('lib');
1948
+            throw new InvalidPathException($l->t('Path contains invalid segments'));
1949
+        }
1950
+
1951
+        // Short cut for read-only validation
1952
+        if ($readonly) {
1953
+            $validator = Server::get(FilenameValidator::class);
1954
+            if ($validator->isForbidden($fileName)) {
1955
+                $l = \OCP\Util::getL10N('lib');
1956
+                throw new InvalidPathException($l->t('Filename is a reserved word'));
1957
+            }
1958
+            return;
1959
+        }
1960
+
1961
+        try {
1962
+            /** @type \OCP\Files\Storage $storage */
1963
+            [$storage, $internalPath] = $this->resolvePath($path);
1964
+            $storage->verifyPath($internalPath, $fileName);
1965
+        } catch (ReservedWordException $ex) {
1966
+            $l = \OCP\Util::getL10N('lib');
1967
+            throw new InvalidPathException($ex->getMessage() ?: $l->t('Filename is a reserved word'));
1968
+        } catch (InvalidCharacterInPathException $ex) {
1969
+            $l = \OCP\Util::getL10N('lib');
1970
+            throw new InvalidPathException($ex->getMessage() ?: $l->t('Filename contains at least one invalid character'));
1971
+        } catch (FileNameTooLongException $ex) {
1972
+            $l = \OCP\Util::getL10N('lib');
1973
+            throw new InvalidPathException($l->t('Filename is too long'));
1974
+        } catch (InvalidDirectoryException $ex) {
1975
+            $l = \OCP\Util::getL10N('lib');
1976
+            throw new InvalidPathException($l->t('Dot files are not allowed'));
1977
+        } catch (EmptyFileNameException $ex) {
1978
+            $l = \OCP\Util::getL10N('lib');
1979
+            throw new InvalidPathException($l->t('Empty filename is not allowed'));
1980
+        }
1981
+    }
1982
+
1983
+    /**
1984
+     * get all parent folders of $path
1985
+     *
1986
+     * @param string $path
1987
+     * @return string[]
1988
+     */
1989
+    private function getParents($path) {
1990
+        $path = trim($path, '/');
1991
+        if (!$path) {
1992
+            return [];
1993
+        }
1994
+
1995
+        $parts = explode('/', $path);
1996
+
1997
+        // remove the single file
1998
+        array_pop($parts);
1999
+        $result = ['/'];
2000
+        $resultPath = '';
2001
+        foreach ($parts as $part) {
2002
+            if ($part) {
2003
+                $resultPath .= '/' . $part;
2004
+                $result[] = $resultPath;
2005
+            }
2006
+        }
2007
+        return $result;
2008
+    }
2009
+
2010
+    /**
2011
+     * Returns the mount point for which to lock
2012
+     *
2013
+     * @param string $absolutePath absolute path
2014
+     * @param bool $useParentMount true to return parent mount instead of whatever
2015
+     *                             is mounted directly on the given path, false otherwise
2016
+     * @return IMountPoint mount point for which to apply locks
2017
+     */
2018
+    private function getMountForLock(string $absolutePath, bool $useParentMount = false): IMountPoint {
2019
+        $mount = Filesystem::getMountManager()->find($absolutePath);
2020
+
2021
+        if ($useParentMount) {
2022
+            // find out if something is mounted directly on the path
2023
+            $internalPath = $mount->getInternalPath($absolutePath);
2024
+            if ($internalPath === '') {
2025
+                // resolve the parent mount instead
2026
+                $mount = Filesystem::getMountManager()->find(dirname($absolutePath));
2027
+            }
2028
+        }
2029
+
2030
+        return $mount;
2031
+    }
2032
+
2033
+    /**
2034
+     * Lock the given path
2035
+     *
2036
+     * @param string $path the path of the file to lock, relative to the view
2037
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2038
+     * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2039
+     *
2040
+     * @return bool False if the path is excluded from locking, true otherwise
2041
+     * @throws LockedException if the path is already locked
2042
+     */
2043
+    private function lockPath($path, $type, $lockMountPoint = false) {
2044
+        $absolutePath = $this->getAbsolutePath($path);
2045
+        $absolutePath = Filesystem::normalizePath($absolutePath);
2046
+        if (!$this->shouldLockFile($absolutePath)) {
2047
+            return false;
2048
+        }
2049
+
2050
+        $mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2051
+        try {
2052
+            $storage = $mount->getStorage();
2053
+            if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2054
+                $storage->acquireLock(
2055
+                    $mount->getInternalPath($absolutePath),
2056
+                    $type,
2057
+                    $this->lockingProvider
2058
+                );
2059
+            }
2060
+        } catch (LockedException $e) {
2061
+            // rethrow with the human-readable path
2062
+            throw new LockedException(
2063
+                $path,
2064
+                $e,
2065
+                $e->getExistingLock()
2066
+            );
2067
+        }
2068
+
2069
+        return true;
2070
+    }
2071
+
2072
+    /**
2073
+     * Change the lock type
2074
+     *
2075
+     * @param string $path the path of the file to lock, relative to the view
2076
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2077
+     * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2078
+     *
2079
+     * @return bool False if the path is excluded from locking, true otherwise
2080
+     * @throws LockedException if the path is already locked
2081
+     */
2082
+    public function changeLock($path, $type, $lockMountPoint = false) {
2083
+        $path = Filesystem::normalizePath($path);
2084
+        $absolutePath = $this->getAbsolutePath($path);
2085
+        $absolutePath = Filesystem::normalizePath($absolutePath);
2086
+        if (!$this->shouldLockFile($absolutePath)) {
2087
+            return false;
2088
+        }
2089
+
2090
+        $mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2091
+        try {
2092
+            $storage = $mount->getStorage();
2093
+            if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2094
+                $storage->changeLock(
2095
+                    $mount->getInternalPath($absolutePath),
2096
+                    $type,
2097
+                    $this->lockingProvider
2098
+                );
2099
+            }
2100
+        } catch (LockedException $e) {
2101
+            // rethrow with the a human-readable path
2102
+            throw new LockedException(
2103
+                $path,
2104
+                $e,
2105
+                $e->getExistingLock()
2106
+            );
2107
+        }
2108
+
2109
+        return true;
2110
+    }
2111
+
2112
+    /**
2113
+     * Unlock the given path
2114
+     *
2115
+     * @param string $path the path of the file to unlock, relative to the view
2116
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2117
+     * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2118
+     *
2119
+     * @return bool False if the path is excluded from locking, true otherwise
2120
+     * @throws LockedException
2121
+     */
2122
+    private function unlockPath($path, $type, $lockMountPoint = false) {
2123
+        $absolutePath = $this->getAbsolutePath($path);
2124
+        $absolutePath = Filesystem::normalizePath($absolutePath);
2125
+        if (!$this->shouldLockFile($absolutePath)) {
2126
+            return false;
2127
+        }
2128
+
2129
+        $mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2130
+        $storage = $mount->getStorage();
2131
+        if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2132
+            $storage->releaseLock(
2133
+                $mount->getInternalPath($absolutePath),
2134
+                $type,
2135
+                $this->lockingProvider
2136
+            );
2137
+        }
2138
+
2139
+        return true;
2140
+    }
2141
+
2142
+    /**
2143
+     * Lock a path and all its parents up to the root of the view
2144
+     *
2145
+     * @param string $path the path of the file to lock relative to the view
2146
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2147
+     * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2148
+     *
2149
+     * @return bool False if the path is excluded from locking, true otherwise
2150
+     * @throws LockedException
2151
+     */
2152
+    public function lockFile($path, $type, $lockMountPoint = false) {
2153
+        $absolutePath = $this->getAbsolutePath($path);
2154
+        $absolutePath = Filesystem::normalizePath($absolutePath);
2155
+        if (!$this->shouldLockFile($absolutePath)) {
2156
+            return false;
2157
+        }
2158
+
2159
+        $this->lockPath($path, $type, $lockMountPoint);
2160
+
2161
+        $parents = $this->getParents($path);
2162
+        foreach ($parents as $parent) {
2163
+            $this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2164
+        }
2165
+
2166
+        return true;
2167
+    }
2168
+
2169
+    /**
2170
+     * Unlock a path and all its parents up to the root of the view
2171
+     *
2172
+     * @param string $path the path of the file to lock relative to the view
2173
+     * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2174
+     * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2175
+     *
2176
+     * @return bool False if the path is excluded from locking, true otherwise
2177
+     * @throws LockedException
2178
+     */
2179
+    public function unlockFile($path, $type, $lockMountPoint = false) {
2180
+        $absolutePath = $this->getAbsolutePath($path);
2181
+        $absolutePath = Filesystem::normalizePath($absolutePath);
2182
+        if (!$this->shouldLockFile($absolutePath)) {
2183
+            return false;
2184
+        }
2185
+
2186
+        $this->unlockPath($path, $type, $lockMountPoint);
2187
+
2188
+        $parents = $this->getParents($path);
2189
+        foreach ($parents as $parent) {
2190
+            $this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2191
+        }
2192
+
2193
+        return true;
2194
+    }
2195
+
2196
+    /**
2197
+     * Only lock files in data/user/files/
2198
+     *
2199
+     * @param string $path Absolute path to the file/folder we try to (un)lock
2200
+     * @return bool
2201
+     */
2202
+    protected function shouldLockFile($path) {
2203
+        $path = Filesystem::normalizePath($path);
2204
+
2205
+        $pathSegments = explode('/', $path);
2206
+        if (isset($pathSegments[2])) {
2207
+            // E.g.: /username/files/path-to-file
2208
+            return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
2209
+        }
2210
+
2211
+        return !str_starts_with($path, '/appdata_');
2212
+    }
2213
+
2214
+    /**
2215
+     * Shortens the given absolute path to be relative to
2216
+     * "$user/files".
2217
+     *
2218
+     * @param string $absolutePath absolute path which is under "files"
2219
+     *
2220
+     * @return string path relative to "files" with trimmed slashes or null
2221
+     *                if the path was NOT relative to files
2222
+     *
2223
+     * @throws \InvalidArgumentException if the given path was not under "files"
2224
+     * @since 8.1.0
2225
+     */
2226
+    public function getPathRelativeToFiles($absolutePath) {
2227
+        $path = Filesystem::normalizePath($absolutePath);
2228
+        $parts = explode('/', trim($path, '/'), 3);
2229
+        // "$user", "files", "path/to/dir"
2230
+        if (!isset($parts[1]) || $parts[1] !== 'files') {
2231
+            $this->logger->error(
2232
+                '$absolutePath must be relative to "files", value is "{absolutePath}"',
2233
+                [
2234
+                    'absolutePath' => $absolutePath,
2235
+                ]
2236
+            );
2237
+            throw new \InvalidArgumentException('$absolutePath must be relative to "files"');
2238
+        }
2239
+        if (isset($parts[2])) {
2240
+            return $parts[2];
2241
+        }
2242
+        return '';
2243
+    }
2244
+
2245
+    /**
2246
+     * @param string $filename
2247
+     * @return array
2248
+     * @throws \OC\User\NoUserException
2249
+     * @throws NotFoundException
2250
+     */
2251
+    public function getUidAndFilename($filename) {
2252
+        $info = $this->getFileInfo($filename);
2253
+        if (!$info instanceof \OCP\Files\FileInfo) {
2254
+            throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2255
+        }
2256
+        $uid = $info->getOwner()->getUID();
2257
+        if ($uid != \OC_User::getUser()) {
2258
+            Filesystem::initMountPoints($uid);
2259
+            $ownerView = new View('/' . $uid . '/files');
2260
+            try {
2261
+                $filename = $ownerView->getPath($info['fileid']);
2262
+            } catch (NotFoundException $e) {
2263
+                throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2264
+            }
2265
+        }
2266
+        return [$uid, $filename];
2267
+    }
2268
+
2269
+    /**
2270
+     * Creates parent non-existing folders
2271
+     *
2272
+     * @param string $filePath
2273
+     * @return bool
2274
+     */
2275
+    private function createParentDirectories($filePath) {
2276
+        $directoryParts = explode('/', $filePath);
2277
+        $directoryParts = array_filter($directoryParts);
2278
+        foreach ($directoryParts as $key => $part) {
2279
+            $currentPathElements = array_slice($directoryParts, 0, $key);
2280
+            $currentPath = '/' . implode('/', $currentPathElements);
2281
+            if ($this->is_file($currentPath)) {
2282
+                return false;
2283
+            }
2284
+            if (!$this->file_exists($currentPath)) {
2285
+                $this->mkdir($currentPath);
2286
+            }
2287
+        }
2288
+
2289
+        return true;
2290
+    }
2291 2291
 }
Please login to merge, or discard this patch.
Spacing   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -93,7 +93,7 @@  discard block
 block discarded – undo
93 93
 			return null;
94 94
 		}
95 95
 		$this->assertPathLength($path);
96
-		return PathHelper::normalizePath($this->fakeRoot . '/' . $path);
96
+		return PathHelper::normalizePath($this->fakeRoot.'/'.$path);
97 97
 	}
98 98
 
99 99
 	/**
@@ -104,7 +104,7 @@  discard block
 block discarded – undo
104 104
 	public function chroot($fakeRoot): void {
105 105
 		if (!$fakeRoot == '') {
106 106
 			if ($fakeRoot[0] !== '/') {
107
-				$fakeRoot = '/' . $fakeRoot;
107
+				$fakeRoot = '/'.$fakeRoot;
108 108
 			}
109 109
 		}
110 110
 		$this->fakeRoot = $fakeRoot;
@@ -133,7 +133,7 @@  discard block
 block discarded – undo
133 133
 		}
134 134
 
135 135
 		// missing slashes can cause wrong matches!
136
-		$root = rtrim($this->fakeRoot, '/') . '/';
136
+		$root = rtrim($this->fakeRoot, '/').'/';
137 137
 
138 138
 		if (!str_starts_with($path, $root)) {
139 139
 			return null;
@@ -190,7 +190,7 @@  discard block
 block discarded – undo
190 190
 	 *
191 191
 	 * @param string $path
192 192
 	 */
193
-	public function getLocalFile($path): string|false {
193
+	public function getLocalFile($path): string | false {
194 194
 		$parent = substr($path, 0, strrpos($path, '/') ?: 0);
195 195
 		$path = $this->getAbsolutePath($path);
196 196
 		[$storage, $internalPath] = Filesystem::resolvePath($path);
@@ -220,7 +220,7 @@  discard block
 block discarded – undo
220 220
 		if ($mount instanceof MoveableMount) {
221 221
 			// cut of /user/files to get the relative path to data/user/files
222 222
 			$pathParts = explode('/', $path, 4);
223
-			$relPath = '/' . $pathParts[3];
223
+			$relPath = '/'.$pathParts[3];
224 224
 			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
225 225
 			\OC_Hook::emit(
226 226
 				Filesystem::CLASSNAME, 'umount',
@@ -664,7 +664,7 @@  discard block
 block discarded – undo
664 664
 		}
665 665
 		$postFix = (substr($path, -1) === '/') ? '/' : '';
666 666
 		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
667
-		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
667
+		$mount = Filesystem::getMountManager()->find($absolutePath.$postFix);
668 668
 		if ($mount->getInternalPath($absolutePath) === '') {
669 669
 			return $this->removeMount($mount, $absolutePath);
670 670
 		}
@@ -707,7 +707,7 @@  discard block
 block discarded – undo
707 707
 		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($source));
708 708
 		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($target));
709 709
 
710
-		if (str_starts_with($absolutePath2, $absolutePath1 . '/')) {
710
+		if (str_starts_with($absolutePath2, $absolutePath1.'/')) {
711 711
 			throw new ForbiddenException('Moving a folder into a child folder is forbidden', false);
712 712
 		}
713 713
 
@@ -1025,7 +1025,7 @@  discard block
 block discarded – undo
1025 1025
 				$hooks[] = 'write';
1026 1026
 				break;
1027 1027
 			default:
1028
-				$this->logger->error('invalid mode (' . $mode . ') for ' . $path, ['app' => 'core']);
1028
+				$this->logger->error('invalid mode ('.$mode.') for '.$path, ['app' => 'core']);
1029 1029
 		}
1030 1030
 
1031 1031
 		if ($mode !== 'r' && $mode !== 'w') {
@@ -1049,7 +1049,7 @@  discard block
 block discarded – undo
1049 1049
 	 * @param string $path
1050 1050
 	 * @throws InvalidPathException
1051 1051
 	 */
1052
-	public function toTmpFile($path): string|false {
1052
+	public function toTmpFile($path): string | false {
1053 1053
 		$this->assertPathLength($path);
1054 1054
 		if (Filesystem::isValidPath($path)) {
1055 1055
 			$source = $this->fopen($path, 'r');
@@ -1124,7 +1124,7 @@  discard block
 block discarded – undo
1124 1124
 	 * @param string $path
1125 1125
 	 * @param bool $raw
1126 1126
 	 */
1127
-	public function hash($type, $path, $raw = false): string|bool {
1127
+	public function hash($type, $path, $raw = false): string | bool {
1128 1128
 		$postFix = (substr($path, -1) === '/') ? '/' : '';
1129 1129
 		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1130 1130
 		if (Filesystem::isValidPath($path)) {
@@ -1140,7 +1140,7 @@  discard block
 block discarded – undo
1140 1140
 				);
1141 1141
 			}
1142 1142
 			/** @var Storage|null $storage */
1143
-			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1143
+			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath.$postFix);
1144 1144
 			if ($storage) {
1145 1145
 				return $storage->hash($type, $internalPath, $raw);
1146 1146
 			}
@@ -1190,7 +1190,7 @@  discard block
 block discarded – undo
1190 1190
 			}
1191 1191
 
1192 1192
 			$run = $this->runHooks($hooks, $path);
1193
-			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath . $postFix);
1193
+			[$storage, $internalPath] = Filesystem::resolvePath($absolutePath.$postFix);
1194 1194
 			if ($run && $storage) {
1195 1195
 				/** @var Storage $storage */
1196 1196
 				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
@@ -1238,7 +1238,7 @@  discard block
 block discarded – undo
1238 1238
 					$unlockLater = true;
1239 1239
 					// make sure our unlocking callback will still be called if connection is aborted
1240 1240
 					ignore_user_abort(true);
1241
-					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1241
+					$result = CallbackWrapper::wrap($result, null, null, function() use ($hooks, $path) {
1242 1242
 						if (in_array('write', $hooks)) {
1243 1243
 							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1244 1244
 						} elseif (in_array('read', $hooks)) {
@@ -1300,7 +1300,7 @@  discard block
 block discarded – undo
1300 1300
 			return true;
1301 1301
 		}
1302 1302
 
1303
-		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1303
+		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot.'/');
1304 1304
 	}
1305 1305
 
1306 1306
 	/**
@@ -1319,7 +1319,7 @@  discard block
 block discarded – undo
1319 1319
 				if ($hook != 'read') {
1320 1320
 					\OC_Hook::emit(
1321 1321
 						Filesystem::CLASSNAME,
1322
-						$prefix . $hook,
1322
+						$prefix.$hook,
1323 1323
 						[
1324 1324
 							Filesystem::signal_param_run => &$run,
1325 1325
 							Filesystem::signal_param_path => $path
@@ -1328,7 +1328,7 @@  discard block
 block discarded – undo
1328 1328
 				} elseif (!$post) {
1329 1329
 					\OC_Hook::emit(
1330 1330
 						Filesystem::CLASSNAME,
1331
-						$prefix . $hook,
1331
+						$prefix.$hook,
1332 1332
 						[
1333 1333
 							Filesystem::signal_param_path => $path
1334 1334
 						]
@@ -1412,7 +1412,7 @@  discard block
 block discarded – undo
1412 1412
 			return false;
1413 1413
 		}
1414 1414
 		$relativePath = $path;
1415
-		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1415
+		$path = Filesystem::normalizePath($this->fakeRoot.'/'.$path);
1416 1416
 
1417 1417
 		$mount = Filesystem::getMountManager()->find($path);
1418 1418
 		$storage = $mount->getStorage();
@@ -1453,7 +1453,7 @@  discard block
 block discarded – undo
1453 1453
 
1454 1454
 			return $info;
1455 1455
 		} else {
1456
-			$this->logger->warning('Storage not valid for mountpoint: ' . $mount->getMountPoint(), ['app' => 'core']);
1456
+			$this->logger->warning('Storage not valid for mountpoint: '.$mount->getMountPoint(), ['app' => 'core']);
1457 1457
 		}
1458 1458
 
1459 1459
 		return false;
@@ -1464,7 +1464,7 @@  discard block
 block discarded – undo
1464 1464
 	 */
1465 1465
 	public function addSubMounts(FileInfo $info, $extOnly = false): void {
1466 1466
 		$mounts = Filesystem::getMountManager()->findIn($info->getPath());
1467
-		$info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1467
+		$info->setSubMounts(array_filter($mounts, function(IMountPoint $mount) use ($extOnly) {
1468 1468
 			return !($extOnly && $mount instanceof SharedMount);
1469 1469
 		}));
1470 1470
 	}
@@ -1512,13 +1512,13 @@  discard block
 block discarded – undo
1512 1512
 
1513 1513
 		$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
1514 1514
 
1515
-		$fileNames = array_map(function (ICacheEntry $content) {
1515
+		$fileNames = array_map(function(ICacheEntry $content) {
1516 1516
 			return $content->getName();
1517 1517
 		}, $contents);
1518 1518
 		/**
1519 1519
 		 * @var \OC\Files\FileInfo[] $fileInfos
1520 1520
 		 */
1521
-		$fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1521
+		$fileInfos = array_map(function(ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1522 1522
 			if ($sharingDisabled) {
1523 1523
 				$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1524 1524
 			}
@@ -1528,7 +1528,7 @@  discard block
 block discarded – undo
1528 1528
 			} else {
1529 1529
 				$owner = null;
1530 1530
 			}
1531
-			return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
1531
+			return new FileInfo($path.'/'.$content['name'], $storage, $content['path'], $content, $mount, $owner);
1532 1532
 		}, $contents);
1533 1533
 		$files = array_combine($fileNames, $fileInfos);
1534 1534
 
@@ -1537,7 +1537,7 @@  discard block
 block discarded – undo
1537 1537
 
1538 1538
 		// make sure nested mounts are sorted after their parent mounts
1539 1539
 		// otherwise doesn't propagate the etag across storage boundaries correctly
1540
-		usort($mounts, function (IMountPoint $a, IMountPoint $b) {
1540
+		usort($mounts, function(IMountPoint $a, IMountPoint $b) {
1541 1541
 			return $a->getMountPoint() <=> $b->getMountPoint();
1542 1542
 		});
1543 1543
 
@@ -1559,7 +1559,7 @@  discard block
 block discarded – undo
1559 1559
 						continue;
1560 1560
 					} catch (\Exception $e) {
1561 1561
 						// sometimes when the storage is not available it can be any exception
1562
-						$this->logger->error('Exception while scanning storage "' . $subStorage->getId() . '"', [
1562
+						$this->logger->error('Exception while scanning storage "'.$subStorage->getId().'"', [
1563 1563
 							'exception' => $e,
1564 1564
 							'app' => 'core',
1565 1565
 						]);
@@ -1577,14 +1577,14 @@  discard block
 block discarded – undo
1577 1577
 						// Create parent folders if the mountpoint is inside a subfolder that doesn't exist yet
1578 1578
 						if (!isset($files[$entryName])) {
1579 1579
 							try {
1580
-								[$storage, ] = $this->resolvePath($path . '/' . $entryName);
1580
+								[$storage, ] = $this->resolvePath($path.'/'.$entryName);
1581 1581
 								// make sure we can create the mountpoint folder, even if the user has a quota of 0
1582 1582
 								if ($storage->instanceOfStorage(Quota::class)) {
1583 1583
 									$storage->enableQuota(false);
1584 1584
 								}
1585 1585
 
1586
-								if ($this->mkdir($path . '/' . $entryName) !== false) {
1587
-									$info = $this->getFileInfo($path . '/' . $entryName);
1586
+								if ($this->mkdir($path.'/'.$entryName) !== false) {
1587
+									$info = $this->getFileInfo($path.'/'.$entryName);
1588 1588
 									if ($info !== false) {
1589 1589
 										$files[$entryName] = $info;
1590 1590
 									}
@@ -1595,7 +1595,7 @@  discard block
 block discarded – undo
1595 1595
 								}
1596 1596
 							} catch (\Exception $e) {
1597 1597
 								// Creating the parent folder might not be possible, for example due to a lack of permissions.
1598
-								$this->logger->debug('Failed to create non-existent parent', ['exception' => $e, 'path' => $path . '/' . $entryName]);
1598
+								$this->logger->debug('Failed to create non-existent parent', ['exception' => $e, 'path' => $path.'/'.$entryName]);
1599 1599
 							}
1600 1600
 						}
1601 1601
 
@@ -1614,7 +1614,7 @@  discard block
 block discarded – undo
1614 1614
 							$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1615 1615
 						}
1616 1616
 
1617
-						$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1617
+						$rootEntry['path'] = substr(Filesystem::normalizePath($path.'/'.$rootEntry['name']), strlen($user) + 2); // full path without /$user/
1618 1618
 
1619 1619
 						// if sharing was disabled for the user we remove the share permissions
1620 1620
 						if ($sharingDisabled) {
@@ -1627,14 +1627,14 @@  discard block
 block discarded – undo
1627 1627
 						} else {
1628 1628
 							$owner = null;
1629 1629
 						}
1630
-						$files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1630
+						$files[$rootEntry->getName()] = new FileInfo($path.'/'.$rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1631 1631
 					}
1632 1632
 				}
1633 1633
 			}
1634 1634
 		}
1635 1635
 
1636 1636
 		if ($mimetype_filter) {
1637
-			$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1637
+			$files = array_filter($files, function(FileInfo $file) use ($mimetype_filter) {
1638 1638
 				if (strpos($mimetype_filter, '/')) {
1639 1639
 					return $file->getMimetype() === $mimetype_filter;
1640 1640
 				} else {
@@ -1660,7 +1660,7 @@  discard block
 block discarded – undo
1660 1660
 		if ($data instanceof FileInfo) {
1661 1661
 			$data = $data->getData();
1662 1662
 		}
1663
-		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1663
+		$path = Filesystem::normalizePath($this->fakeRoot.'/'.$path);
1664 1664
 		/**
1665 1665
 		 * @var Storage $storage
1666 1666
 		 * @var string $internalPath
@@ -1687,7 +1687,7 @@  discard block
 block discarded – undo
1687 1687
 	 * @return FileInfo[]
1688 1688
 	 */
1689 1689
 	public function search($query) {
1690
-		return $this->searchCommon('search', ['%' . $query . '%']);
1690
+		return $this->searchCommon('search', ['%'.$query.'%']);
1691 1691
 	}
1692 1692
 
1693 1693
 	/**
@@ -1739,10 +1739,10 @@  discard block
 block discarded – undo
1739 1739
 
1740 1740
 			$results = call_user_func_array([$cache, $method], $args);
1741 1741
 			foreach ($results as $result) {
1742
-				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1742
+				if (substr($mountPoint.$result['path'], 0, $rootLength + 1) === $this->fakeRoot.'/') {
1743 1743
 					$internalPath = $result['path'];
1744
-					$path = $mountPoint . $result['path'];
1745
-					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1744
+					$path = $mountPoint.$result['path'];
1745
+					$result['path'] = substr($mountPoint.$result['path'], $rootLength);
1746 1746
 					$ownerId = $storage->getOwner($internalPath);
1747 1747
 					if ($ownerId !== false) {
1748 1748
 						$owner = $userManager->get($ownerId);
@@ -1765,8 +1765,8 @@  discard block
 block discarded – undo
1765 1765
 					if ($results) {
1766 1766
 						foreach ($results as $result) {
1767 1767
 							$internalPath = $result['path'];
1768
-							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1769
-							$path = rtrim($mountPoint . $internalPath, '/');
1768
+							$result['path'] = rtrim($relativeMountPoint.$result['path'], '/');
1769
+							$path = rtrim($mountPoint.$internalPath, '/');
1770 1770
 							$ownerId = $storage->getOwner($internalPath);
1771 1771
 							if ($ownerId !== false) {
1772 1772
 								$owner = $userManager->get($ownerId);
@@ -1790,11 +1790,11 @@  discard block
 block discarded – undo
1790 1790
 	public function getOwner(string $path): string {
1791 1791
 		$info = $this->getFileInfo($path);
1792 1792
 		if (!$info) {
1793
-			throw new NotFoundException($path . ' not found while trying to get owner');
1793
+			throw new NotFoundException($path.' not found while trying to get owner');
1794 1794
 		}
1795 1795
 
1796 1796
 		if ($info->getOwner() === null) {
1797
-			throw new NotFoundException($path . ' has no owner');
1797
+			throw new NotFoundException($path.' has no owner');
1798 1798
 		}
1799 1799
 
1800 1800
 		return $info->getOwner()->getUID();
@@ -1826,7 +1826,7 @@  discard block
 block discarded – undo
1826 1826
 	 * @throws NotFoundException
1827 1827
 	 */
1828 1828
 	public function getPath($id, ?int $storageId = null): string {
1829
-		$id = (int)$id;
1829
+		$id = (int) $id;
1830 1830
 		$rootFolder = Server::get(Files\IRootFolder::class);
1831 1831
 
1832 1832
 		$node = $rootFolder->getFirstNodeByIdInPath($id, $this->getRoot());
@@ -1878,7 +1878,7 @@  discard block
 block discarded – undo
1878 1878
 		];
1879 1879
 		$shareManager = Server::get(IManager::class);
1880 1880
 		/** @var IShare[] $shares */
1881
-		$shares = array_merge(...array_map(function (int $type) use ($shareManager, $user) {
1881
+		$shares = array_merge(...array_map(function(int $type) use ($shareManager, $user) {
1882 1882
 			return $shareManager->getSharesBy($user, $type);
1883 1883
 		}, $providers));
1884 1884
 
@@ -1892,7 +1892,7 @@  discard block
 block discarded – undo
1892 1892
 					['app' => 'files', 'exception' => $e]);
1893 1893
 				continue;
1894 1894
 			}
1895
-			if ($targetPath === $sharedPath || str_starts_with($targetPath, $sharedPath . '/')) {
1895
+			if ($targetPath === $sharedPath || str_starts_with($targetPath, $sharedPath.'/')) {
1896 1896
 				$this->logger->debug(
1897 1897
 					'It is not allowed to move one mount point into a shared folder',
1898 1898
 					['app' => 'files']);
@@ -2000,7 +2000,7 @@  discard block
 block discarded – undo
2000 2000
 		$resultPath = '';
2001 2001
 		foreach ($parts as $part) {
2002 2002
 			if ($part) {
2003
-				$resultPath .= '/' . $part;
2003
+				$resultPath .= '/'.$part;
2004 2004
 				$result[] = $resultPath;
2005 2005
 			}
2006 2006
 		}
@@ -2251,16 +2251,16 @@  discard block
 block discarded – undo
2251 2251
 	public function getUidAndFilename($filename) {
2252 2252
 		$info = $this->getFileInfo($filename);
2253 2253
 		if (!$info instanceof \OCP\Files\FileInfo) {
2254
-			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2254
+			throw new NotFoundException($this->getAbsolutePath($filename).' not found');
2255 2255
 		}
2256 2256
 		$uid = $info->getOwner()->getUID();
2257 2257
 		if ($uid != \OC_User::getUser()) {
2258 2258
 			Filesystem::initMountPoints($uid);
2259
-			$ownerView = new View('/' . $uid . '/files');
2259
+			$ownerView = new View('/'.$uid.'/files');
2260 2260
 			try {
2261 2261
 				$filename = $ownerView->getPath($info['fileid']);
2262 2262
 			} catch (NotFoundException $e) {
2263
-				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2263
+				throw new NotFoundException('File with id '.$info['fileid'].' not found for user '.$uid);
2264 2264
 			}
2265 2265
 		}
2266 2266
 		return [$uid, $filename];
@@ -2277,7 +2277,7 @@  discard block
 block discarded – undo
2277 2277
 		$directoryParts = array_filter($directoryParts);
2278 2278
 		foreach ($directoryParts as $key => $part) {
2279 2279
 			$currentPathElements = array_slice($directoryParts, 0, $key);
2280
-			$currentPath = '/' . implode('/', $currentPathElements);
2280
+			$currentPath = '/'.implode('/', $currentPathElements);
2281 2281
 			if ($this->is_file($currentPath)) {
2282 2282
 				return false;
2283 2283
 			}
Please login to merge, or discard this patch.