Passed
Push — master ( 62403d...0c3e2f )
by Joas
14:50 queued 14s
created

View::mkdir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Florin Peter <[email protected]>
9
 * @author Jesús Macias <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Julius Härtl <[email protected]>
13
 * @author karakayasemi <[email protected]>
14
 * @author Klaas Freitag <[email protected]>
15
 * @author korelstar <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author Luke Policinski <[email protected]>
18
 * @author Michael Gapczynski <[email protected]>
19
 * @author Morris Jobke <[email protected]>
20
 * @author Piotr Filiciak <[email protected]>
21
 * @author Robin Appelman <[email protected]>
22
 * @author Robin McCorkell <[email protected]>
23
 * @author Roeland Jago Douma <[email protected]>
24
 * @author Sam Tuke <[email protected]>
25
 * @author Thomas Müller <[email protected]>
26
 * @author Thomas Tanghus <[email protected]>
27
 * @author Vincent Petry <[email protected]>
28
 *
29
 * @license AGPL-3.0
30
 *
31
 * This code is free software: you can redistribute it and/or modify
32
 * it under the terms of the GNU Affero General Public License, version 3,
33
 * as published by the Free Software Foundation.
34
 *
35
 * This program is distributed in the hope that it will be useful,
36
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38
 * GNU Affero General Public License for more details.
39
 *
40
 * You should have received a copy of the GNU Affero General Public License, version 3,
41
 * along with this program. If not, see <http://www.gnu.org/licenses/>
42
 *
43
 */
44
45
namespace OC\Files;
46
47
use Icewind\Streams\CallbackWrapper;
48
use OC\Files\Mount\MoveableMount;
49
use OC\Files\Storage\Storage;
50
use OC\User\User;
51
use OCA\Files_Sharing\SharedMount;
52
use OCP\Constants;
53
use OCP\Files\Cache\ICacheEntry;
54
use OCP\Files\EmptyFileNameException;
55
use OCP\Files\FileNameTooLongException;
56
use OCP\Files\InvalidCharacterInPathException;
57
use OCP\Files\InvalidDirectoryException;
58
use OCP\Files\InvalidPathException;
59
use OCP\Files\Mount\IMountPoint;
60
use OCP\Files\NotFoundException;
61
use OCP\Files\ReservedWordException;
62
use OCP\Files\Storage\IStorage;
63
use OCP\ILogger;
64
use OCP\IUser;
65
use OCP\Lock\ILockingProvider;
66
use OCP\Lock\LockedException;
67
68
/**
69
 * Class to provide access to ownCloud filesystem via a "view", and methods for
70
 * working with files within that view (e.g. read, write, delete, etc.). Each
71
 * view is restricted to a set of directories via a virtual root. The default view
72
 * uses the currently logged in user's data directory as root (parts of
73
 * OC_Filesystem are merely a wrapper for OC\Files\View).
74
 *
75
 * Apps that need to access files outside of the user data folders (to modify files
76
 * belonging to a user other than the one currently logged in, for example) should
77
 * use this class directly rather than using OC_Filesystem, or making use of PHP's
78
 * built-in file manipulation functions. This will ensure all hooks and proxies
79
 * are triggered correctly.
80
 *
81
 * Filesystem functions are not called directly; they are passed to the correct
82
 * \OC\Files\Storage\Storage object
83
 */
84
class View {
85
	/** @var string */
86
	private $fakeRoot = '';
87
88
	/**
89
	 * @var \OCP\Lock\ILockingProvider
90
	 */
91
	protected $lockingProvider;
92
93
	private $lockingEnabled;
94
95
	private $updaterEnabled = true;
96
97
	/** @var \OC\User\Manager */
98
	private $userManager;
99
100
	/** @var \OCP\ILogger */
101
	private $logger;
102
103
	/**
104
	 * @param string $root
105
	 * @throws \Exception If $root contains an invalid path
106
	 */
107
	public function __construct($root = '') {
108
		if (is_null($root)) {
0 ignored issues
show
introduced by
The condition is_null($root) is always false.
Loading history...
109
			throw new \InvalidArgumentException('Root can\'t be null');
110
		}
111
		if (!Filesystem::isValidPath($root)) {
112
			throw new \Exception();
113
		}
114
115
		$this->fakeRoot = $root;
116
		$this->lockingProvider = \OC::$server->getLockingProvider();
117
		$this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
118
		$this->userManager = \OC::$server->getUserManager();
119
		$this->logger = \OC::$server->getLogger();
120
	}
121
122
	public function getAbsolutePath($path = '/') {
123
		if ($path === null) {
124
			return null;
125
		}
126
		$this->assertPathLength($path);
127
		if ($path === '') {
128
			$path = '/';
129
		}
130
		if ($path[0] !== '/') {
131
			$path = '/' . $path;
132
		}
133
		return $this->fakeRoot . $path;
134
	}
135
136
	/**
137
	 * change the root to a fake root
138
	 *
139
	 * @param string $fakeRoot
140
	 * @return boolean|null
141
	 */
142
	public function chroot($fakeRoot) {
143
		if (!$fakeRoot == '') {
144
			if ($fakeRoot[0] !== '/') {
145
				$fakeRoot = '/' . $fakeRoot;
146
			}
147
		}
148
		$this->fakeRoot = $fakeRoot;
149
	}
150
151
	/**
152
	 * get the fake root
153
	 *
154
	 * @return string
155
	 */
156
	public function getRoot() {
157
		return $this->fakeRoot;
158
	}
159
160
	/**
161
	 * get path relative to the root of the view
162
	 *
163
	 * @param string $path
164
	 * @return string
165
	 */
166
	public function getRelativePath($path) {
167
		$this->assertPathLength($path);
168
		if ($this->fakeRoot == '') {
169
			return $path;
170
		}
171
172
		if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) {
173
			return '/';
174
		}
175
176
		// missing slashes can cause wrong matches!
177
		$root = rtrim($this->fakeRoot, '/') . '/';
178
179
		if (strpos($path, $root) !== 0) {
180
			return null;
181
		} else {
182
			$path = substr($path, strlen($this->fakeRoot));
183
			if (strlen($path) === 0) {
184
				return '/';
185
			} else {
186
				return $path;
187
			}
188
		}
189
	}
190
191
	/**
192
	 * get the mountpoint of the storage object for a path
193
	 * ( note: because a storage is not always mounted inside the fakeroot, the
194
	 * returned mountpoint is relative to the absolute root of the filesystem
195
	 * and does not take the chroot into account )
196
	 *
197
	 * @param string $path
198
	 * @return string
199
	 */
200
	public function getMountPoint($path) {
201
		return Filesystem::getMountPoint($this->getAbsolutePath($path));
202
	}
203
204
	/**
205
	 * get the mountpoint of the storage object for a path
206
	 * ( note: because a storage is not always mounted inside the fakeroot, the
207
	 * returned mountpoint is relative to the absolute root of the filesystem
208
	 * and does not take the chroot into account )
209
	 *
210
	 * @param string $path
211
	 * @return \OCP\Files\Mount\IMountPoint
212
	 */
213
	public function getMount($path) {
214
		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
215
	}
216
217
	/**
218
	 * resolve a path to a storage and internal path
219
	 *
220
	 * @param string $path
221
	 * @return array an array consisting of the storage and the internal path
222
	 */
223
	public function resolvePath($path) {
224
		$a = $this->getAbsolutePath($path);
225
		$p = Filesystem::normalizePath($a);
226
		return Filesystem::resolvePath($p);
227
	}
228
229
	/**
230
	 * return the path to a local version of the file
231
	 * we need this because we can't know if a file is stored local or not from
232
	 * outside the filestorage and for some purposes a local file is needed
233
	 *
234
	 * @param string $path
235
	 * @return string
236
	 */
237
	public function getLocalFile($path) {
238
		$parent = substr($path, 0, strrpos($path, '/'));
239
		$path = $this->getAbsolutePath($path);
240
		list($storage, $internalPath) = Filesystem::resolvePath($path);
241
		if (Filesystem::isValidPath($parent) and $storage) {
242
			return $storage->getLocalFile($internalPath);
243
		} else {
244
			return null;
245
		}
246
	}
247
248
	/**
249
	 * @param string $path
250
	 * @return string
251
	 */
252
	public function getLocalFolder($path) {
253
		$parent = substr($path, 0, strrpos($path, '/'));
254
		$path = $this->getAbsolutePath($path);
255
		list($storage, $internalPath) = Filesystem::resolvePath($path);
256
		if (Filesystem::isValidPath($parent) and $storage) {
257
			return $storage->getLocalFolder($internalPath);
258
		} else {
259
			return null;
260
		}
261
	}
262
263
	/**
264
	 * the following functions operate with arguments and return values identical
265
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
266
	 * for \OC\Files\Storage\Storage via basicOperation().
267
	 */
268
	public function mkdir($path) {
269
		return $this->basicOperation('mkdir', $path, ['create', 'write']);
270
	}
271
272
	/**
273
	 * remove mount point
274
	 *
275
	 * @param \OC\Files\Mount\MoveableMount $mount
276
	 * @param string $path relative to data/
277
	 * @return boolean
278
	 */
279
	protected function removeMount($mount, $path) {
280
		if ($mount instanceof MoveableMount) {
0 ignored issues
show
introduced by
$mount is always a sub-type of OC\Files\Mount\MoveableMount.
Loading history...
281
			// cut of /user/files to get the relative path to data/user/files
282
			$pathParts = explode('/', $path, 4);
283
			$relPath = '/' . $pathParts[3];
284
			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
285
			\OC_Hook::emit(
286
				Filesystem::CLASSNAME, "umount",
287
				[Filesystem::signal_param_path => $relPath]
288
			);
289
			$this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
290
			$result = $mount->removeMount();
291
			$this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
292
			if ($result) {
293
				\OC_Hook::emit(
294
					Filesystem::CLASSNAME, "post_umount",
295
					[Filesystem::signal_param_path => $relPath]
296
				);
297
			}
298
			$this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
299
			return $result;
300
		} else {
301
			// do not allow deleting the storage's root / the mount point
302
			// because for some storages it might delete the whole contents
303
			// but isn't supposed to work that way
304
			return false;
305
		}
306
	}
307
308
	public function disableCacheUpdate() {
309
		$this->updaterEnabled = false;
310
	}
311
312
	public function enableCacheUpdate() {
313
		$this->updaterEnabled = true;
314
	}
315
316
	protected function writeUpdate(Storage $storage, $internalPath, $time = null) {
317
		if ($this->updaterEnabled) {
318
			if (is_null($time)) {
319
				$time = time();
320
			}
321
			$storage->getUpdater()->update($internalPath, $time);
322
		}
323
	}
324
325
	protected function removeUpdate(Storage $storage, $internalPath) {
326
		if ($this->updaterEnabled) {
327
			$storage->getUpdater()->remove($internalPath);
328
		}
329
	}
330
331
	protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) {
332
		if ($this->updaterEnabled) {
333
			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
334
		}
335
	}
336
337
	/**
338
	 * @param string $path
339
	 * @return bool|mixed
340
	 */
341
	public function rmdir($path) {
342
		$absolutePath = $this->getAbsolutePath($path);
343
		$mount = Filesystem::getMountManager()->find($absolutePath);
344
		if ($mount->getInternalPath($absolutePath) === '') {
345
			return $this->removeMount($mount, $absolutePath);
346
		}
347
		if ($this->is_dir($path)) {
348
			$result = $this->basicOperation('rmdir', $path, ['delete']);
349
		} else {
350
			$result = false;
351
		}
352
353
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
354
			$storage = $mount->getStorage();
355
			$internalPath = $mount->getInternalPath($absolutePath);
356
			$storage->getUpdater()->remove($internalPath);
357
		}
358
		return $result;
359
	}
360
361
	/**
362
	 * @param string $path
363
	 * @return resource
364
	 */
365
	public function opendir($path) {
366
		return $this->basicOperation('opendir', $path, ['read']);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->basicOpera..., $path, array('read')) could also return false which is incompatible with the documented return type resource. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
367
	}
368
369
	/**
370
	 * @param string $path
371
	 * @return bool|mixed
372
	 */
373
	public function is_dir($path) {
374
		if ($path == '/') {
375
			return true;
376
		}
377
		return $this->basicOperation('is_dir', $path);
378
	}
379
380
	/**
381
	 * @param string $path
382
	 * @return bool|mixed
383
	 */
384
	public function is_file($path) {
385
		if ($path == '/') {
386
			return false;
387
		}
388
		return $this->basicOperation('is_file', $path);
389
	}
390
391
	/**
392
	 * @param string $path
393
	 * @return mixed
394
	 */
395
	public function stat($path) {
396
		return $this->basicOperation('stat', $path);
397
	}
398
399
	/**
400
	 * @param string $path
401
	 * @return mixed
402
	 */
403
	public function filetype($path) {
404
		return $this->basicOperation('filetype', $path);
405
	}
406
407
	/**
408
	 * @param string $path
409
	 * @return mixed
410
	 */
411
	public function filesize($path) {
412
		return $this->basicOperation('filesize', $path);
413
	}
414
415
	/**
416
	 * @param string $path
417
	 * @return bool|mixed
418
	 * @throws \OCP\Files\InvalidPathException
419
	 */
420
	public function readfile($path) {
421
		$this->assertPathLength($path);
422
		@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ob_end_clean(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

422
		/** @scrutinizer ignore-unhandled */ @ob_end_clean();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
423
		$handle = $this->fopen($path, 'rb');
424
		if ($handle) {
0 ignored issues
show
introduced by
$handle is of type resource, thus it always evaluated to false.
Loading history...
425
			$chunkSize = 8192; // 8 kB chunks
426
			while (!feof($handle)) {
427
				echo fread($handle, $chunkSize);
428
				flush();
429
			}
430
			fclose($handle);
431
			return $this->filesize($path);
432
		}
433
		return false;
434
	}
435
436
	/**
437
	 * @param string $path
438
	 * @param int $from
439
	 * @param int $to
440
	 * @return bool|mixed
441
	 * @throws \OCP\Files\InvalidPathException
442
	 * @throws \OCP\Files\UnseekableException
443
	 */
444
	public function readfilePart($path, $from, $to) {
445
		$this->assertPathLength($path);
446
		@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ob_end_clean(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

446
		/** @scrutinizer ignore-unhandled */ @ob_end_clean();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
447
		$handle = $this->fopen($path, 'rb');
448
		if ($handle) {
0 ignored issues
show
introduced by
$handle is of type resource, thus it always evaluated to false.
Loading history...
449
			$chunkSize = 8192; // 8 kB chunks
450
			$startReading = true;
451
452
			if ($from !== 0 && $from !== '0' && fseek($handle, $from) !== 0) {
453
				// forward file handle via chunked fread because fseek seem to have failed
454
455
				$end = $from + 1;
456
				while (!feof($handle) && ftell($handle) < $end) {
457
					$len = $from - ftell($handle);
458
					if ($len > $chunkSize) {
459
						$len = $chunkSize;
460
					}
461
					$result = fread($handle, $len);
462
463
					if ($result === false) {
464
						$startReading = false;
465
						break;
466
					}
467
				}
468
			}
469
470
			if ($startReading) {
471
				$end = $to + 1;
472
				while (!feof($handle) && ftell($handle) < $end) {
473
					$len = $end - ftell($handle);
474
					if ($len > $chunkSize) {
475
						$len = $chunkSize;
476
					}
477
					echo fread($handle, $len);
478
					flush();
479
				}
480
				return ftell($handle) - $from;
481
			}
482
483
			throw new \OCP\Files\UnseekableException('fseek error');
484
		}
485
		return false;
486
	}
487
488
	/**
489
	 * @param string $path
490
	 * @return mixed
491
	 */
492
	public function isCreatable($path) {
493
		return $this->basicOperation('isCreatable', $path);
494
	}
495
496
	/**
497
	 * @param string $path
498
	 * @return mixed
499
	 */
500
	public function isReadable($path) {
501
		return $this->basicOperation('isReadable', $path);
502
	}
503
504
	/**
505
	 * @param string $path
506
	 * @return mixed
507
	 */
508
	public function isUpdatable($path) {
509
		return $this->basicOperation('isUpdatable', $path);
510
	}
511
512
	/**
513
	 * @param string $path
514
	 * @return bool|mixed
515
	 */
516
	public function isDeletable($path) {
517
		$absolutePath = $this->getAbsolutePath($path);
518
		$mount = Filesystem::getMountManager()->find($absolutePath);
519
		if ($mount->getInternalPath($absolutePath) === '') {
520
			return $mount instanceof MoveableMount;
521
		}
522
		return $this->basicOperation('isDeletable', $path);
523
	}
524
525
	/**
526
	 * @param string $path
527
	 * @return mixed
528
	 */
529
	public function isSharable($path) {
530
		return $this->basicOperation('isSharable', $path);
531
	}
532
533
	/**
534
	 * @param string $path
535
	 * @return bool|mixed
536
	 */
537
	public function file_exists($path) {
538
		if ($path == '/') {
539
			return true;
540
		}
541
		return $this->basicOperation('file_exists', $path);
542
	}
543
544
	/**
545
	 * @param string $path
546
	 * @return mixed
547
	 */
548
	public function filemtime($path) {
549
		return $this->basicOperation('filemtime', $path);
550
	}
551
552
	/**
553
	 * @param string $path
554
	 * @param int|string $mtime
555
	 * @return bool
556
	 */
557
	public function touch($path, $mtime = null) {
558
		if (!is_null($mtime) and !is_numeric($mtime)) {
559
			$mtime = strtotime($mtime);
560
		}
561
562
		$hooks = ['touch'];
563
564
		if (!$this->file_exists($path)) {
565
			$hooks[] = 'create';
566
			$hooks[] = 'write';
567
		}
568
		try {
569
			$result = $this->basicOperation('touch', $path, $hooks, $mtime);
570
		} catch (\Exception $e) {
571
			$this->logger->logException($e, ['level' => ILogger::INFO, 'message' => 'Error while setting modified time']);
572
			$result = false;
573
		}
574
		if (!$result) {
575
			// If create file fails because of permissions on external storage like SMB folders,
576
			// check file exists and return false if not.
577
			if (!$this->file_exists($path)) {
578
				return false;
579
			}
580
			if (is_null($mtime)) {
581
				$mtime = time();
582
			}
583
			//if native touch fails, we emulate it by changing the mtime in the cache
584
			$this->putFileInfo($path, ['mtime' => floor($mtime)]);
585
		}
586
		return true;
587
	}
588
589
	/**
590
	 * @param string $path
591
	 * @return mixed
592
	 * @throws LockedException
593
	 */
594
	public function file_get_contents($path) {
595
		return $this->basicOperation('file_get_contents', $path, ['read']);
596
	}
597
598
	/**
599
	 * @param bool $exists
600
	 * @param string $path
601
	 * @param bool $run
602
	 */
603
	protected function emit_file_hooks_pre($exists, $path, &$run) {
604
		if (!$exists) {
605
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
606
				Filesystem::signal_param_path => $this->getHookPath($path),
607
				Filesystem::signal_param_run => &$run,
608
			]);
609
		} else {
610
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
611
				Filesystem::signal_param_path => $this->getHookPath($path),
612
				Filesystem::signal_param_run => &$run,
613
			]);
614
		}
615
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
616
			Filesystem::signal_param_path => $this->getHookPath($path),
617
			Filesystem::signal_param_run => &$run,
618
		]);
619
	}
620
621
	/**
622
	 * @param bool $exists
623
	 * @param string $path
624
	 */
625
	protected function emit_file_hooks_post($exists, $path) {
626
		if (!$exists) {
627
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
628
				Filesystem::signal_param_path => $this->getHookPath($path),
629
			]);
630
		} else {
631
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
632
				Filesystem::signal_param_path => $this->getHookPath($path),
633
			]);
634
		}
635
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
636
			Filesystem::signal_param_path => $this->getHookPath($path),
637
		]);
638
	}
639
640
	/**
641
	 * @param string $path
642
	 * @param string|resource $data
643
	 * @return bool|mixed
644
	 * @throws LockedException
645
	 */
646
	public function file_put_contents($path, $data) {
647
		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
648
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
649
			if (Filesystem::isValidPath($path)
650
				and !Filesystem::isFileBlacklisted($path)
651
			) {
652
				$path = $this->getRelativePath($absolutePath);
653
654
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
655
656
				$exists = $this->file_exists($path);
657
				$run = true;
658
				if ($this->shouldEmitHooks($path)) {
659
					$this->emit_file_hooks_pre($exists, $path, $run);
660
				}
661
				if (!$run) {
662
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
663
					return false;
664
				}
665
666
				$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
667
668
				/** @var \OC\Files\Storage\Storage $storage */
669
				list($storage, $internalPath) = $this->resolvePath($path);
670
				$target = $storage->fopen($internalPath, 'w');
671
				if ($target) {
0 ignored issues
show
introduced by
$target is of type false|resource, thus it always evaluated to false.
Loading history...
672
					list (, $result) = \OC_Helper::streamCopy($data, $target);
673
					fclose($target);
674
					fclose($data);
675
676
					$this->writeUpdate($storage, $internalPath);
677
678
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
679
680
					if ($this->shouldEmitHooks($path) && $result !== false) {
681
						$this->emit_file_hooks_post($exists, $path);
682
					}
683
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
684
					return $result;
685
				} else {
686
					$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
687
					return false;
688
				}
689
			} else {
690
				return false;
691
			}
692
		} else {
693
			$hooks = $this->file_exists($path) ? ['update', 'write'] : ['create', 'write'];
694
			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
695
		}
696
	}
697
698
	/**
699
	 * @param string $path
700
	 * @return bool|mixed
701
	 */
702
	public function unlink($path) {
703
		if ($path === '' || $path === '/') {
704
			// do not allow deleting the root
705
			return false;
706
		}
707
		$postFix = (substr($path, -1) === '/') ? '/' : '';
708
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
709
		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
710
		if ($mount and $mount->getInternalPath($absolutePath) === '') {
711
			return $this->removeMount($mount, $absolutePath);
0 ignored issues
show
Bug introduced by
$mount of type OC\Files\Mount\MountPoint is incompatible with the type OC\Files\Mount\MoveableMount expected by parameter $mount of OC\Files\View::removeMount(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

711
			return $this->removeMount(/** @scrutinizer ignore-type */ $mount, $absolutePath);
Loading history...
712
		}
713
		if ($this->is_dir($path)) {
714
			$result = $this->basicOperation('rmdir', $path, ['delete']);
715
		} else {
716
			$result = $this->basicOperation('unlink', $path, ['delete']);
717
		}
718
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
719
			$storage = $mount->getStorage();
720
			$internalPath = $mount->getInternalPath($absolutePath);
721
			$storage->getUpdater()->remove($internalPath);
722
			return true;
723
		} else {
724
			return $result;
725
		}
726
	}
727
728
	/**
729
	 * @param string $directory
730
	 * @return bool|mixed
731
	 */
732
	public function deleteAll($directory) {
733
		return $this->rmdir($directory);
734
	}
735
736
	/**
737
	 * Rename/move a file or folder from the source path to target path.
738
	 *
739
	 * @param string $path1 source path
740
	 * @param string $path2 target path
741
	 *
742
	 * @return bool|mixed
743
	 * @throws LockedException
744
	 */
745
	public function rename($path1, $path2) {
746
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
747
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
748
		$result = false;
749
		if (
750
			Filesystem::isValidPath($path2)
751
			and Filesystem::isValidPath($path1)
752
			and !Filesystem::isFileBlacklisted($path2)
753
		) {
754
			$path1 = $this->getRelativePath($absolutePath1);
755
			$path2 = $this->getRelativePath($absolutePath2);
756
			$exists = $this->file_exists($path2);
757
758
			if ($path1 == null or $path2 == null) {
759
				return false;
760
			}
761
762
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
763
			try {
764
				$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
765
766
				$run = true;
767
				if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
768
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
769
					$this->emit_file_hooks_pre($exists, $path2, $run);
770
				} elseif ($this->shouldEmitHooks($path1)) {
771
					\OC_Hook::emit(
772
						Filesystem::CLASSNAME, Filesystem::signal_rename,
773
						[
774
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
775
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
776
							Filesystem::signal_param_run => &$run
777
						]
778
					);
779
				}
780
				if ($run) {
781
					$this->verifyPath(dirname($path2), basename($path2));
782
783
					$manager = Filesystem::getMountManager();
784
					$mount1 = $this->getMount($path1);
785
					$mount2 = $this->getMount($path2);
786
					$storage1 = $mount1->getStorage();
787
					$storage2 = $mount2->getStorage();
788
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
789
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
790
791
					$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
792
					try {
793
						$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
794
795
						if ($internalPath1 === '') {
796
							if ($mount1 instanceof MoveableMount) {
797
								$sourceParentMount = $this->getMount(dirname($path1));
798
								if ($sourceParentMount === $mount2 && $this->targetIsNotShared($storage2, $internalPath2)) {
799
									/**
800
									 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
801
									 */
802
									$sourceMountPoint = $mount1->getMountPoint();
0 ignored issues
show
Bug introduced by
The method getMountPoint() does not exist on OC\Files\Mount\MoveableMount. Since it exists in all sub-types, consider adding an abstract or default implementation to OC\Files\Mount\MoveableMount. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

802
									/** @scrutinizer ignore-call */ 
803
         $sourceMountPoint = $mount1->getMountPoint();
Loading history...
803
									$result = $mount1->moveMount($absolutePath2);
0 ignored issues
show
Bug introduced by
The method moveMount() does not exist on OC\Files\Mount\MountPoint. It seems like you code against a sub-type of said class. However, the method does not exist in OCA\Files_External\Config\ExternalMountPoint. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

803
									/** @scrutinizer ignore-call */ 
804
         $result = $mount1->moveMount($absolutePath2);
Loading history...
804
									$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
805
								} else {
806
									$result = false;
807
								}
808
							} else {
809
								$result = false;
810
							}
811
							// moving a file/folder within the same mount point
812
						} elseif ($storage1 === $storage2) {
813
							if ($storage1) {
0 ignored issues
show
introduced by
$storage1 is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
814
								$result = $storage1->rename($internalPath1, $internalPath2);
815
							} else {
816
								$result = false;
817
							}
818
							// moving a file/folder between storages (from $storage1 to $storage2)
819
						} else {
820
							$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
821
						}
822
823
						if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
824
							// if it was a rename from a part file to a regular file it was a write and not a rename operation
825
							$this->writeUpdate($storage2, $internalPath2);
826
						} else if ($result) {
827
							if ($internalPath1 !== '') { // don't do a cache update for moved mounts
828
								$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
829
							}
830
						}
831
					} catch(\Exception $e) {
832
						throw $e;
833
					} finally {
834
						$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
835
						$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
836
					}
837
838
					if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
839
						if ($this->shouldEmitHooks()) {
840
							$this->emit_file_hooks_post($exists, $path2);
841
						}
842
					} elseif ($result) {
843
						if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
844
							\OC_Hook::emit(
845
								Filesystem::CLASSNAME,
846
								Filesystem::signal_post_rename,
847
								[
848
									Filesystem::signal_param_oldpath => $this->getHookPath($path1),
849
									Filesystem::signal_param_newpath => $this->getHookPath($path2)
850
								]
851
							);
852
						}
853
					}
854
				}
855
			} catch(\Exception $e) {
856
				throw $e;
857
			} finally {
858
				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
859
				$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
860
			}
861
		}
862
		return $result;
863
	}
864
865
	/**
866
	 * Copy a file/folder from the source path to target path
867
	 *
868
	 * @param string $path1 source path
869
	 * @param string $path2 target path
870
	 * @param bool $preserveMtime whether to preserve mtime on the copy
871
	 *
872
	 * @return bool|mixed
873
	 */
874
	public function copy($path1, $path2, $preserveMtime = false) {
875
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
876
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
877
		$result = false;
878
		if (
879
			Filesystem::isValidPath($path2)
880
			and Filesystem::isValidPath($path1)
881
			and !Filesystem::isFileBlacklisted($path2)
882
		) {
883
			$path1 = $this->getRelativePath($absolutePath1);
884
			$path2 = $this->getRelativePath($absolutePath2);
885
886
			if ($path1 == null or $path2 == null) {
887
				return false;
888
			}
889
			$run = true;
890
891
			$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
892
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
893
			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
894
			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
895
896
			try {
897
898
				$exists = $this->file_exists($path2);
899
				if ($this->shouldEmitHooks()) {
900
					\OC_Hook::emit(
901
						Filesystem::CLASSNAME,
902
						Filesystem::signal_copy,
903
						[
904
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
905
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
906
							Filesystem::signal_param_run => &$run
907
						]
908
					);
909
					$this->emit_file_hooks_pre($exists, $path2, $run);
910
				}
911
				if ($run) {
912
					$mount1 = $this->getMount($path1);
913
					$mount2 = $this->getMount($path2);
914
					$storage1 = $mount1->getStorage();
915
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
916
					$storage2 = $mount2->getStorage();
917
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
918
919
					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
920
					$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
921
922
					if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
923
						if ($storage1) {
0 ignored issues
show
introduced by
$storage1 is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
924
							$result = $storage1->copy($internalPath1, $internalPath2);
925
						} else {
926
							$result = false;
927
						}
928
					} else {
929
						$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
930
					}
931
932
					$this->writeUpdate($storage2, $internalPath2);
933
934
					$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
935
					$lockTypePath2 = ILockingProvider::LOCK_SHARED;
936
937
					if ($this->shouldEmitHooks() && $result !== false) {
938
						\OC_Hook::emit(
939
							Filesystem::CLASSNAME,
940
							Filesystem::signal_post_copy,
941
							[
942
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
943
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
944
							]
945
						);
946
						$this->emit_file_hooks_post($exists, $path2);
947
					}
948
949
				}
950
			} catch (\Exception $e) {
951
				$this->unlockFile($path2, $lockTypePath2);
952
				$this->unlockFile($path1, $lockTypePath1);
953
				throw $e;
954
			}
955
956
			$this->unlockFile($path2, $lockTypePath2);
957
			$this->unlockFile($path1, $lockTypePath1);
958
959
		}
960
		return $result;
961
	}
962
963
	/**
964
	 * @param string $path
965
	 * @param string $mode 'r' or 'w'
966
	 * @return resource
967
	 * @throws LockedException
968
	 */
969
	public function fopen($path, $mode) {
970
		$mode = str_replace('b', '', $mode); // the binary flag is a windows only feature which we do not support
971
		$hooks = [];
972
		switch ($mode) {
973
			case 'r':
974
				$hooks[] = 'read';
975
				break;
976
			case 'r+':
977
			case 'w+':
978
			case 'x+':
979
			case 'a+':
980
				$hooks[] = 'read';
981
				$hooks[] = 'write';
982
				break;
983
			case 'w':
984
			case 'x':
985
			case 'a':
986
				$hooks[] = 'write';
987
				break;
988
			default:
989
				\OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

989
				/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
990
		}
991
992
		if ($mode !== 'r' && $mode !== 'w') {
993
			\OC::$server->getLogger()->info('Trying to open a file with a mode other than "r" or "w" can cause severe performance issues with some backends');
994
		}
995
996
		return $this->basicOperation('fopen', $path, $hooks, $mode);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->basicOpera..., $path, $hooks, $mode) could also return false which is incompatible with the documented return type resource. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
997
	}
998
999
	/**
1000
	 * @param string $path
1001
	 * @return bool|string
1002
	 * @throws \OCP\Files\InvalidPathException
1003
	 */
1004
	public function toTmpFile($path) {
1005
		$this->assertPathLength($path);
1006
		if (Filesystem::isValidPath($path)) {
1007
			$source = $this->fopen($path, 'r');
1008
			if ($source) {
0 ignored issues
show
introduced by
$source is of type resource, thus it always evaluated to false.
Loading history...
1009
				$extension = pathinfo($path, PATHINFO_EXTENSION);
1010
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
1011
				file_put_contents($tmpFile, $source);
1012
				return $tmpFile;
1013
			} else {
1014
				return false;
1015
			}
1016
		} else {
1017
			return false;
1018
		}
1019
	}
1020
1021
	/**
1022
	 * @param string $tmpFile
1023
	 * @param string $path
1024
	 * @return bool|mixed
1025
	 * @throws \OCP\Files\InvalidPathException
1026
	 */
1027
	public function fromTmpFile($tmpFile, $path) {
1028
		$this->assertPathLength($path);
1029
		if (Filesystem::isValidPath($path)) {
1030
1031
			// Get directory that the file is going into
1032
			$filePath = dirname($path);
1033
1034
			// Create the directories if any
1035
			if (!$this->file_exists($filePath)) {
1036
				$result = $this->createParentDirectories($filePath);
1037
				if ($result === false) {
1038
					return false;
1039
				}
1040
			}
1041
1042
			$source = fopen($tmpFile, 'r');
1043
			if ($source) {
0 ignored issues
show
introduced by
$source is of type false|resource, thus it always evaluated to false.
Loading history...
1044
				$result = $this->file_put_contents($path, $source);
1045
				// $this->file_put_contents() might have already closed
1046
				// the resource, so we check it, before trying to close it
1047
				// to avoid messages in the error log.
1048
				if (is_resource($source)) {
1049
					fclose($source);
1050
				}
1051
				unlink($tmpFile);
1052
				return $result;
1053
			} else {
1054
				return false;
1055
			}
1056
		} else {
1057
			return false;
1058
		}
1059
	}
1060
1061
1062
	/**
1063
	 * @param string $path
1064
	 * @return mixed
1065
	 * @throws \OCP\Files\InvalidPathException
1066
	 */
1067
	public function getMimeType($path) {
1068
		$this->assertPathLength($path);
1069
		return $this->basicOperation('getMimeType', $path);
1070
	}
1071
1072
	/**
1073
	 * @param string $type
1074
	 * @param string $path
1075
	 * @param bool $raw
1076
	 * @return bool|null|string
1077
	 */
1078
	public function hash($type, $path, $raw = false) {
1079
		$postFix = (substr($path, -1) === '/') ? '/' : '';
1080
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1081
		if (Filesystem::isValidPath($path)) {
1082
			$path = $this->getRelativePath($absolutePath);
1083
			if ($path == null) {
1084
				return false;
1085
			}
1086
			if ($this->shouldEmitHooks($path)) {
1087
				\OC_Hook::emit(
1088
					Filesystem::CLASSNAME,
1089
					Filesystem::signal_read,
1090
					[Filesystem::signal_param_path => $this->getHookPath($path)]
1091
				);
1092
			}
1093
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1094
			if ($storage) {
1095
				return $storage->hash($type, $internalPath, $raw);
1096
			}
1097
		}
1098
		return null;
1099
	}
1100
1101
	/**
1102
	 * @param string $path
1103
	 * @return mixed
1104
	 * @throws \OCP\Files\InvalidPathException
1105
	 */
1106
	public function free_space($path = '/') {
1107
		$this->assertPathLength($path);
1108
		$result = $this->basicOperation('free_space', $path);
1109
		if ($result === null) {
1110
			throw new InvalidPathException();
1111
		}
1112
		return $result;
1113
	}
1114
1115
	/**
1116
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1117
	 *
1118
	 * @param string $operation
1119
	 * @param string $path
1120
	 * @param array $hooks (optional)
1121
	 * @param mixed $extraParam (optional)
1122
	 * @return mixed
1123
	 * @throws LockedException
1124
	 *
1125
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1126
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1127
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1128
	 */
1129
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1130
		$postFix = (substr($path, -1) === '/') ? '/' : '';
1131
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1132
		if (Filesystem::isValidPath($path)
1133
			and !Filesystem::isFileBlacklisted($path)
1134
		) {
1135
			$path = $this->getRelativePath($absolutePath);
1136
			if ($path == null) {
1137
				return false;
1138
			}
1139
1140
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1141
				// always a shared lock during pre-hooks so the hook can read the file
1142
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1143
			}
1144
1145
			$run = $this->runHooks($hooks, $path);
1146
			/** @var \OC\Files\Storage\Storage $storage */
1147
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1148
			if ($run and $storage) {
0 ignored issues
show
introduced by
$storage is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
1149
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1150
					try {
1151
						$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1152
					} catch (LockedException $e) {
1153
						// release the shared lock we acquired before quiting
1154
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1155
						throw $e;
1156
					}
1157
				}
1158
				try {
1159
					if (!is_null($extraParam)) {
1160
						$result = $storage->$operation($internalPath, $extraParam);
1161
					} else {
1162
						$result = $storage->$operation($internalPath);
1163
					}
1164
				} catch (\Exception $e) {
1165
					if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1166
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1167
					} else if (in_array('read', $hooks)) {
1168
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1169
					}
1170
					throw $e;
1171
				}
1172
1173
				if ($result && in_array('delete', $hooks) and $result) {
1174
					$this->removeUpdate($storage, $internalPath);
1175
				}
1176
				if ($result && in_array('write', $hooks,  true) && $operation !== 'fopen' && $operation !== 'touch') {
1177
					$this->writeUpdate($storage, $internalPath);
1178
				}
1179
				if ($result && in_array('touch', $hooks)) {
1180
					$this->writeUpdate($storage, $internalPath, $extraParam);
1181
				}
1182
1183
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1184
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1185
				}
1186
1187
				$unlockLater = false;
1188
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1189
					$unlockLater = true;
1190
					// make sure our unlocking callback will still be called if connection is aborted
1191
					ignore_user_abort(true);
1192
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1193
						if (in_array('write', $hooks)) {
1194
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1195
						} else if (in_array('read', $hooks)) {
1196
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1197
						}
1198
					});
1199
				}
1200
1201
				if ($this->shouldEmitHooks($path) && $result !== false) {
1202
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1203
						$this->runHooks($hooks, $path, true);
1204
					}
1205
				}
1206
1207
				if (!$unlockLater
1208
					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1209
				) {
1210
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1211
				}
1212
				return $result;
1213
			} else {
1214
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1215
			}
1216
		}
1217
		return null;
1218
	}
1219
1220
	/**
1221
	 * get the path relative to the default root for hook usage
1222
	 *
1223
	 * @param string $path
1224
	 * @return string
1225
	 */
1226
	private function getHookPath($path) {
1227
		if (!Filesystem::getView()) {
1228
			return $path;
1229
		}
1230
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1231
	}
1232
1233
	private function shouldEmitHooks($path = '') {
1234
		if ($path && Cache\Scanner::isPartialFile($path)) {
1235
			return false;
1236
		}
1237
		if (!Filesystem::$loaded) {
1238
			return false;
1239
		}
1240
		$defaultRoot = Filesystem::getRoot();
1241
		if ($defaultRoot === null) {
0 ignored issues
show
introduced by
The condition $defaultRoot === null is always false.
Loading history...
1242
			return false;
1243
		}
1244
		if ($this->fakeRoot === $defaultRoot) {
1245
			return true;
1246
		}
1247
		$fullPath = $this->getAbsolutePath($path);
1248
1249
		if ($fullPath === $defaultRoot) {
1250
			return true;
1251
		}
1252
1253
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1254
	}
1255
1256
	/**
1257
	 * @param string[] $hooks
1258
	 * @param string $path
1259
	 * @param bool $post
1260
	 * @return bool
1261
	 */
1262
	private function runHooks($hooks, $path, $post = false) {
1263
		$relativePath = $path;
1264
		$path = $this->getHookPath($path);
1265
		$prefix = $post ? 'post_' : '';
1266
		$run = true;
1267
		if ($this->shouldEmitHooks($relativePath)) {
1268
			foreach ($hooks as $hook) {
1269
				if ($hook != 'read') {
1270
					\OC_Hook::emit(
1271
						Filesystem::CLASSNAME,
1272
						$prefix . $hook,
1273
						[
1274
							Filesystem::signal_param_run => &$run,
1275
							Filesystem::signal_param_path => $path
1276
						]
1277
					);
1278
				} elseif (!$post) {
1279
					\OC_Hook::emit(
1280
						Filesystem::CLASSNAME,
1281
						$prefix . $hook,
1282
						[
1283
							Filesystem::signal_param_path => $path
1284
						]
1285
					);
1286
				}
1287
			}
1288
		}
1289
		return $run;
1290
	}
1291
1292
	/**
1293
	 * check if a file or folder has been updated since $time
1294
	 *
1295
	 * @param string $path
1296
	 * @param int $time
1297
	 * @return bool
1298
	 */
1299
	public function hasUpdated($path, $time) {
1300
		return $this->basicOperation('hasUpdated', $path, [], $time);
1301
	}
1302
1303
	/**
1304
	 * @param string $ownerId
1305
	 * @return \OC\User\User
1306
	 */
1307
	private function getUserObjectForOwner($ownerId) {
1308
		$owner = $this->userManager->get($ownerId);
1309
		if ($owner instanceof IUser) {
1310
			return $owner;
1311
		} else {
1312
			return new User($ownerId, null, \OC::$server->getEventDispatcher());
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getEventDispatcher() has been deprecated: 18.0.0 use \OCP\EventDispatcher\IEventDispatcher ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1312
			return new User($ownerId, null, /** @scrutinizer ignore-deprecated */ \OC::$server->getEventDispatcher());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1313
		}
1314
	}
1315
1316
	/**
1317
	 * Get file info from cache
1318
	 *
1319
	 * If the file is not in cached it will be scanned
1320
	 * If the file has changed on storage the cache will be updated
1321
	 *
1322
	 * @param \OC\Files\Storage\Storage $storage
1323
	 * @param string $internalPath
1324
	 * @param string $relativePath
1325
	 * @return ICacheEntry|bool
1326
	 */
1327
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1328
		$cache = $storage->getCache($internalPath);
1329
		$data = $cache->get($internalPath);
1330
		$watcher = $storage->getWatcher($internalPath);
1331
1332
		try {
1333
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1334
			if (!$data || $data['size'] === -1) {
1335
				if (!$storage->file_exists($internalPath)) {
1336
					return false;
1337
				}
1338
				// don't need to get a lock here since the scanner does it's own locking
1339
				$scanner = $storage->getScanner($internalPath);
1340
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1341
				$data = $cache->get($internalPath);
1342
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1343
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1344
				$watcher->update($internalPath, $data);
1345
				$storage->getPropagator()->propagateChange($internalPath, time());
1346
				$data = $cache->get($internalPath);
1347
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1348
			}
1349
		} catch (LockedException $e) {
1350
			// if the file is locked we just use the old cache info
1351
		}
1352
1353
		return $data;
1354
	}
1355
1356
	/**
1357
	 * get the filesystem info
1358
	 *
1359
	 * @param string $path
1360
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1361
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1362
	 * defaults to true
1363
	 * @return \OC\Files\FileInfo|false False if file does not exist
1364
	 */
1365
	public function getFileInfo($path, $includeMountPoints = true) {
1366
		$this->assertPathLength($path);
1367
		if (!Filesystem::isValidPath($path)) {
1368
			return false;
1369
		}
1370
		if (Cache\Scanner::isPartialFile($path)) {
1371
			return $this->getPartFileInfo($path);
1372
		}
1373
		$relativePath = $path;
1374
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1375
1376
		$mount = Filesystem::getMountManager()->find($path);
1377
		if (!$mount) {
1378
			\OC::$server->getLogger()->warning('Mountpoint not found for path: ' . $path);
1379
			return false;
1380
		}
1381
		$storage = $mount->getStorage();
1382
		$internalPath = $mount->getInternalPath($path);
1383
		if ($storage) {
0 ignored issues
show
introduced by
$storage is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
1384
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1385
1386
			if (!$data instanceof ICacheEntry) {
1387
				return false;
1388
			}
1389
1390
			if ($mount instanceof MoveableMount && $internalPath === '') {
1391
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1392
			}
1393
			$ownerId = $storage->getOwner($internalPath);
1394
			$owner = null;
1395
			if ($ownerId !== null && $ownerId !== false) {
0 ignored issues
show
introduced by
The condition $ownerId !== false is always true.
Loading history...
1396
				// ownerId might be null if files are accessed with an access token without file system access
1397
				$owner = $this->getUserObjectForOwner($ownerId);
1398
			}
1399
			$info = new FileInfo($path, $storage, $internalPath, $data, $mount, $owner);
1400
1401
			if ($data and isset($data['fileid'])) {
1402
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1403
					//add the sizes of other mount points to the folder
1404
					$extOnly = ($includeMountPoints === 'ext');
1405
					$mounts = Filesystem::getMountManager()->findIn($path);
1406
					$info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1407
						$subStorage = $mount->getStorage();
1408
						return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage);
1409
					}));
1410
				}
1411
			}
1412
1413
			return $info;
1414
		} else {
1415
			\OC::$server->getLogger()->warning('Storage not valid for mountpoint: ' . $mount->getMountPoint());
1416
		}
1417
1418
		return false;
1419
	}
1420
1421
	/**
1422
	 * get the content of a directory
1423
	 *
1424
	 * @param string $directory path under datadirectory
1425
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1426
	 * @return FileInfo[]
1427
	 */
1428
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1429
		$this->assertPathLength($directory);
1430
		if (!Filesystem::isValidPath($directory)) {
1431
			return [];
1432
		}
1433
		$path = $this->getAbsolutePath($directory);
1434
		$path = Filesystem::normalizePath($path);
1435
		$mount = $this->getMount($directory);
1436
		if (!$mount) {
0 ignored issues
show
introduced by
$mount is of type OC\Files\Mount\MountPoint, thus it always evaluated to true.
Loading history...
1437
			return [];
1438
		}
1439
		$storage = $mount->getStorage();
1440
		$internalPath = $mount->getInternalPath($path);
1441
		if ($storage) {
0 ignored issues
show
introduced by
$storage is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
1442
			$cache = $storage->getCache($internalPath);
1443
			$user = \OC_User::getUser();
1444
1445
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1446
1447
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1448
				return [];
1449
			}
1450
1451
			$folderId = $data['fileid'];
1452
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1453
1454
			$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::isSharingDisabledForUser() has been deprecated: 9.1.0 Use \OC::$server->getShareManager()->sharingDisabledForUser ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1454
			$sharingDisabled = /** @scrutinizer ignore-deprecated */ \OCP\Util::isSharingDisabledForUser();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1455
1456
			$fileNames = array_map(function(ICacheEntry $content) {
1457
				return $content->getName();
1458
			}, $contents);
1459
			/**
1460
			 * @var \OC\Files\FileInfo[] $fileInfos
1461
			 */
1462
			$fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1463
				if ($sharingDisabled) {
1464
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1465
				}
1466
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1467
				return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
1468
			}, $contents);
1469
			$files = array_combine($fileNames, $fileInfos);
1470
1471
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1472
			$mounts = Filesystem::getMountManager()->findIn($path);
1473
			$dirLength = strlen($path);
1474
			foreach ($mounts as $mount) {
1475
				$mountPoint = $mount->getMountPoint();
1476
				$subStorage = $mount->getStorage();
1477
				if ($subStorage) {
1478
					$subCache = $subStorage->getCache('');
1479
1480
					$rootEntry = $subCache->get('');
1481
					if (!$rootEntry) {
1482
						$subScanner = $subStorage->getScanner('');
1483
						try {
1484
							$subScanner->scanFile('');
1485
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1486
							continue;
1487
						} catch (\OCP\Files\StorageInvalidException $e) {
1488
							continue;
1489
						} catch (\Exception $e) {
1490
							// sometimes when the storage is not available it can be any exception
1491
							\OC::$server->getLogger()->logException($e, [
1492
								'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"',
1493
								'level' => ILogger::ERROR,
1494
								'app' => 'lib',
1495
							]);
1496
							continue;
1497
						}
1498
						$rootEntry = $subCache->get('');
1499
					}
1500
1501
					if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1502
						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1503
						if ($pos = strpos($relativePath, '/')) {
1504
							//mountpoint inside subfolder add size to the correct folder
1505
							$entryName = substr($relativePath, 0, $pos);
1506
							foreach ($files as &$entry) {
1507
								if ($entry->getName() === $entryName) {
1508
									$entry->addSubEntry($rootEntry, $mountPoint);
1509
								}
1510
							}
1511
						} else { //mountpoint in this folder, add an entry for it
1512
							$rootEntry['name'] = $relativePath;
1513
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1514
							$permissions = $rootEntry['permissions'];
1515
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1516
							// for shared files/folders we use the permissions given by the owner
1517
							if ($mount instanceof MoveableMount) {
1518
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1519
							} else {
1520
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1521
							}
1522
1523
							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1524
1525
							// if sharing was disabled for the user we remove the share permissions
1526
							if (\OCP\Util::isSharingDisabledForUser()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::isSharingDisabledForUser() has been deprecated: 9.1.0 Use \OC::$server->getShareManager()->sharingDisabledForUser ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1526
							if (/** @scrutinizer ignore-deprecated */ \OCP\Util::isSharingDisabledForUser()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1527
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1528
							}
1529
1530
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1531
							$files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1532
						}
1533
					}
1534
				}
1535
			}
1536
1537
			if ($mimetype_filter) {
1538
				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1539
					if (strpos($mimetype_filter, '/')) {
1540
						return $file->getMimetype() === $mimetype_filter;
1541
					} else {
1542
						return $file->getMimePart() === $mimetype_filter;
1543
					}
1544
				});
1545
			}
1546
1547
			return array_values($files);
1548
		} else {
1549
			return [];
1550
		}
1551
	}
1552
1553
	/**
1554
	 * change file metadata
1555
	 *
1556
	 * @param string $path
1557
	 * @param array|\OCP\Files\FileInfo $data
1558
	 * @return int
1559
	 *
1560
	 * returns the fileid of the updated file
1561
	 */
1562
	public function putFileInfo($path, $data) {
1563
		$this->assertPathLength($path);
1564
		if ($data instanceof FileInfo) {
1565
			$data = $data->getData();
1566
		}
1567
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1568
		/**
1569
		 * @var \OC\Files\Storage\Storage $storage
1570
		 * @var string $internalPath
1571
		 */
1572
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1573
		if ($storage) {
0 ignored issues
show
introduced by
$storage is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
1574
			$cache = $storage->getCache($path);
1575
1576
			if (!$cache->inCache($internalPath)) {
1577
				$scanner = $storage->getScanner($internalPath);
1578
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1579
			}
1580
1581
			return $cache->put($internalPath, $data);
1582
		} else {
1583
			return -1;
1584
		}
1585
	}
1586
1587
	/**
1588
	 * search for files with the name matching $query
1589
	 *
1590
	 * @param string $query
1591
	 * @return FileInfo[]
1592
	 */
1593
	public function search($query) {
1594
		return $this->searchCommon('search', ['%' . $query . '%']);
1595
	}
1596
1597
	/**
1598
	 * search for files with the name matching $query
1599
	 *
1600
	 * @param string $query
1601
	 * @return FileInfo[]
1602
	 */
1603
	public function searchRaw($query) {
1604
		return $this->searchCommon('search', [$query]);
1605
	}
1606
1607
	/**
1608
	 * search for files by mimetype
1609
	 *
1610
	 * @param string $mimetype
1611
	 * @return FileInfo[]
1612
	 */
1613
	public function searchByMime($mimetype) {
1614
		return $this->searchCommon('searchByMime', [$mimetype]);
1615
	}
1616
1617
	/**
1618
	 * search for files by tag
1619
	 *
1620
	 * @param string|int $tag name or tag id
1621
	 * @param string $userId owner of the tags
1622
	 * @return FileInfo[]
1623
	 */
1624
	public function searchByTag($tag, $userId) {
1625
		return $this->searchCommon('searchByTag', [$tag, $userId]);
1626
	}
1627
1628
	/**
1629
	 * @param string $method cache method
1630
	 * @param array $args
1631
	 * @return FileInfo[]
1632
	 */
1633
	private function searchCommon($method, $args) {
1634
		$files = [];
1635
		$rootLength = strlen($this->fakeRoot);
1636
1637
		$mount = $this->getMount('');
1638
		$mountPoint = $mount->getMountPoint();
1639
		$storage = $mount->getStorage();
1640
		if ($storage) {
0 ignored issues
show
introduced by
$storage is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
1641
			$cache = $storage->getCache('');
1642
1643
			$results = call_user_func_array([$cache, $method], $args);
1644
			foreach ($results as $result) {
1645
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1646
					$internalPath = $result['path'];
1647
					$path = $mountPoint . $result['path'];
1648
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1649
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1650
					$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1651
				}
1652
			}
1653
1654
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1655
			foreach ($mounts as $mount) {
1656
				$mountPoint = $mount->getMountPoint();
1657
				$storage = $mount->getStorage();
1658
				if ($storage) {
1659
					$cache = $storage->getCache('');
1660
1661
					$relativeMountPoint = substr($mountPoint, $rootLength);
1662
					$results = call_user_func_array([$cache, $method], $args);
1663
					if ($results) {
1664
						foreach ($results as $result) {
1665
							$internalPath = $result['path'];
1666
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1667
							$path = rtrim($mountPoint . $internalPath, '/');
1668
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1669
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1670
						}
1671
					}
1672
				}
1673
			}
1674
		}
1675
		return $files;
1676
	}
1677
1678
	/**
1679
	 * Get the owner for a file or folder
1680
	 *
1681
	 * @param string $path
1682
	 * @return string the user id of the owner
1683
	 * @throws NotFoundException
1684
	 */
1685
	public function getOwner($path) {
1686
		$info = $this->getFileInfo($path);
1687
		if (!$info) {
1688
			throw new NotFoundException($path . ' not found while trying to get owner');
1689
		}
1690
1691
		if ($info->getOwner() === null) {
1692
			throw new NotFoundException($path . ' has no owner');
1693
		}
1694
1695
		return $info->getOwner()->getUID();
1696
	}
1697
1698
	/**
1699
	 * get the ETag for a file or folder
1700
	 *
1701
	 * @param string $path
1702
	 * @return string
1703
	 */
1704
	public function getETag($path) {
1705
		/**
1706
		 * @var Storage\Storage $storage
1707
		 * @var string $internalPath
1708
		 */
1709
		list($storage, $internalPath) = $this->resolvePath($path);
1710
		if ($storage) {
0 ignored issues
show
introduced by
$storage is of type OC\Files\Storage\Storage\Storage, thus it always evaluated to true.
Loading history...
1711
			return $storage->getETag($internalPath);
1712
		} else {
1713
			return null;
1714
		}
1715
	}
1716
1717
	/**
1718
	 * Get the path of a file by id, relative to the view
1719
	 *
1720
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1721
	 *
1722
	 * @param int $id
1723
	 * @throws NotFoundException
1724
	 * @return string
1725
	 */
1726
	public function getPath($id) {
1727
		$id = (int)$id;
1728
		$manager = Filesystem::getMountManager();
1729
		$mounts = $manager->findIn($this->fakeRoot);
1730
		$mounts[] = $manager->find($this->fakeRoot);
1731
		// reverse the array so we start with the storage this view is in
1732
		// which is the most likely to contain the file we're looking for
1733
		$mounts = array_reverse($mounts);
1734
1735
		// put non shared mounts in front of the shared mount
1736
		// this prevent unneeded recursion into shares
1737
		usort($mounts, function(IMountPoint $a, IMountPoint $b) {
1738
			return $a instanceof SharedMount && (!$b instanceof SharedMount) ? 1 : -1;
1739
		});
1740
1741
		foreach ($mounts as $mount) {
1742
			/**
1743
			 * @var \OC\Files\Mount\MountPoint $mount
1744
			 */
1745
			if ($mount->getStorage()) {
1746
				$cache = $mount->getStorage()->getCache();
1747
				$internalPath = $cache->getPathById($id);
1748
				if (is_string($internalPath)) {
1749
					$fullPath = $mount->getMountPoint() . $internalPath;
1750
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1751
						return $path;
1752
					}
1753
				}
1754
			}
1755
		}
1756
		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1757
	}
1758
1759
	/**
1760
	 * @param string $path
1761
	 * @throws InvalidPathException
1762
	 */
1763
	private function assertPathLength($path) {
1764
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1765
		// Check for the string length - performed using isset() instead of strlen()
1766
		// because isset() is about 5x-40x faster.
1767
		if (isset($path[$maxLen])) {
1768
			$pathLen = strlen($path);
1769
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1770
		}
1771
	}
1772
1773
	/**
1774
	 * check if it is allowed to move a mount point to a given target.
1775
	 * It is not allowed to move a mount point into a different mount point or
1776
	 * into an already shared folder
1777
	 *
1778
	 * @param IStorage $targetStorage
1779
	 * @param string $targetInternalPath
1780
	 * @return boolean
1781
	 */
1782
	private function targetIsNotShared(IStorage $targetStorage, string $targetInternalPath) {
1783
1784
		// note: cannot use the view because the target is already locked
1785
		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1786
		if ($fileId === -1) {
1787
			// target might not exist, need to check parent instead
1788
			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1789
		}
1790
1791
		// check if any of the parents were shared by the current owner (include collections)
1792
		$shares = \OCP\Share::getItemShared(
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Share::getItemShared() has been deprecated: 17.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1792
		$shares = /** @scrutinizer ignore-deprecated */ \OCP\Share::getItemShared(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1793
			'folder',
1794
			$fileId,
1795
			\OCP\Share::FORMAT_NONE,
1796
			null,
1797
			true
1798
		);
1799
1800
		if (count($shares) > 0) {
1801
			\OCP\Util::writeLog('files',
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1801
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('files',

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1802
				'It is not allowed to move one mount point into a shared folder',
1803
				ILogger::DEBUG);
1804
			return false;
1805
		}
1806
1807
		return true;
1808
	}
1809
1810
	/**
1811
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1812
	 *
1813
	 * @param string $path
1814
	 * @return \OCP\Files\FileInfo
1815
	 */
1816
	private function getPartFileInfo($path) {
1817
		$mount = $this->getMount($path);
1818
		$storage = $mount->getStorage();
1819
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1820
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1821
		return new FileInfo(
1822
			$this->getAbsolutePath($path),
1823
			$storage,
1824
			$internalPath,
1825
			[
1826
				'fileid' => null,
1827
				'mimetype' => $storage->getMimeType($internalPath),
1828
				'name' => basename($path),
1829
				'etag' => null,
1830
				'size' => $storage->filesize($internalPath),
1831
				'mtime' => $storage->filemtime($internalPath),
1832
				'encrypted' => false,
1833
				'permissions' => \OCP\Constants::PERMISSION_ALL
1834
			],
1835
			$mount,
1836
			$owner
1837
		);
1838
	}
1839
1840
	/**
1841
	 * @param string $path
1842
	 * @param string $fileName
1843
	 * @throws InvalidPathException
1844
	 */
1845
	public function verifyPath($path, $fileName) {
1846
		try {
1847
			/** @type \OCP\Files\Storage $storage */
1848
			list($storage, $internalPath) = $this->resolvePath($path);
1849
			$storage->verifyPath($internalPath, $fileName);
1850
		} catch (ReservedWordException $ex) {
1851
			$l = \OC::$server->getL10N('lib');
1852
			throw new InvalidPathException($l->t('File name is a reserved word'));
1853
		} catch (InvalidCharacterInPathException $ex) {
1854
			$l = \OC::$server->getL10N('lib');
1855
			throw new InvalidPathException($l->t('File name contains at least one invalid character'));
1856
		} catch (FileNameTooLongException $ex) {
1857
			$l = \OC::$server->getL10N('lib');
1858
			throw new InvalidPathException($l->t('File name is too long'));
1859
		} catch (InvalidDirectoryException $ex) {
1860
			$l = \OC::$server->getL10N('lib');
1861
			throw new InvalidPathException($l->t('Dot files are not allowed'));
1862
		} catch (EmptyFileNameException $ex) {
1863
			$l = \OC::$server->getL10N('lib');
1864
			throw new InvalidPathException($l->t('Empty filename is not allowed'));
1865
		}
1866
	}
1867
1868
	/**
1869
	 * get all parent folders of $path
1870
	 *
1871
	 * @param string $path
1872
	 * @return string[]
1873
	 */
1874
	private function getParents($path) {
1875
		$path = trim($path, '/');
1876
		if (!$path) {
1877
			return [];
1878
		}
1879
1880
		$parts = explode('/', $path);
1881
1882
		// remove the single file
1883
		array_pop($parts);
1884
		$result = ['/'];
1885
		$resultPath = '';
1886
		foreach ($parts as $part) {
1887
			if ($part) {
1888
				$resultPath .= '/' . $part;
1889
				$result[] = $resultPath;
1890
			}
1891
		}
1892
		return $result;
1893
	}
1894
1895
	/**
1896
	 * Returns the mount point for which to lock
1897
	 *
1898
	 * @param string $absolutePath absolute path
1899
	 * @param bool $useParentMount true to return parent mount instead of whatever
1900
	 * is mounted directly on the given path, false otherwise
1901
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1902
	 */
1903
	private function getMountForLock($absolutePath, $useParentMount = false) {
1904
		$results = [];
1905
		$mount = Filesystem::getMountManager()->find($absolutePath);
1906
		if (!$mount) {
1907
			return $results;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $results returns the type array which is incompatible with the documented return type OC\Files\Mount\MountPoint.
Loading history...
1908
		}
1909
1910
		if ($useParentMount) {
1911
			// find out if something is mounted directly on the path
1912
			$internalPath = $mount->getInternalPath($absolutePath);
1913
			if ($internalPath === '') {
1914
				// resolve the parent mount instead
1915
				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1916
			}
1917
		}
1918
1919
		return $mount;
1920
	}
1921
1922
	/**
1923
	 * Lock the given path
1924
	 *
1925
	 * @param string $path the path of the file to lock, relative to the view
1926
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1927
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1928
	 *
1929
	 * @return bool False if the path is excluded from locking, true otherwise
1930
	 * @throws LockedException if the path is already locked
1931
	 */
1932
	private function lockPath($path, $type, $lockMountPoint = false) {
1933
		$absolutePath = $this->getAbsolutePath($path);
1934
		$absolutePath = Filesystem::normalizePath($absolutePath);
1935
		if (!$this->shouldLockFile($absolutePath)) {
1936
			return false;
1937
		}
1938
1939
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1940
		if ($mount) {
0 ignored issues
show
introduced by
$mount is of type OC\Files\Mount\MountPoint, thus it always evaluated to true.
Loading history...
1941
			try {
1942
				$storage = $mount->getStorage();
1943
				if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1944
					$storage->acquireLock(
1945
						$mount->getInternalPath($absolutePath),
1946
						$type,
1947
						$this->lockingProvider
1948
					);
1949
				}
1950
			} catch (LockedException $e) {
1951
				// rethrow with the a human-readable path
1952
				throw new LockedException(
1953
					$this->getPathRelativeToFiles($absolutePath),
1954
					$e,
1955
					$e->getExistingLock()
1956
				);
1957
			}
1958
		}
1959
1960
		return true;
1961
	}
1962
1963
	/**
1964
	 * Change the lock type
1965
	 *
1966
	 * @param string $path the path of the file to lock, relative to the view
1967
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1968
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1969
	 *
1970
	 * @return bool False if the path is excluded from locking, true otherwise
1971
	 * @throws LockedException if the path is already locked
1972
	 */
1973
	public function changeLock($path, $type, $lockMountPoint = false) {
1974
		$path = Filesystem::normalizePath($path);
1975
		$absolutePath = $this->getAbsolutePath($path);
1976
		$absolutePath = Filesystem::normalizePath($absolutePath);
1977
		if (!$this->shouldLockFile($absolutePath)) {
1978
			return false;
1979
		}
1980
1981
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1982
		if ($mount) {
0 ignored issues
show
introduced by
$mount is of type OC\Files\Mount\MountPoint, thus it always evaluated to true.
Loading history...
1983
			try {
1984
				$storage = $mount->getStorage();
1985
				if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1986
					$storage->changeLock(
1987
						$mount->getInternalPath($absolutePath),
1988
						$type,
1989
						$this->lockingProvider
1990
					);
1991
				}
1992
			} catch (LockedException $e) {
1993
				try {
1994
					// rethrow with the a human-readable path
1995
					throw new LockedException(
1996
						$this->getPathRelativeToFiles($absolutePath),
1997
						$e,
1998
						$e->getExistingLock()
1999
					);
2000
				} catch (\InvalidArgumentException $ex) {
2001
					throw new LockedException(
2002
						$absolutePath,
2003
						$ex,
2004
						$e->getExistingLock()
2005
					);
2006
				}
2007
			}
2008
		}
2009
2010
		return true;
2011
	}
2012
2013
	/**
2014
	 * Unlock the given path
2015
	 *
2016
	 * @param string $path the path of the file to unlock, relative to the view
2017
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2018
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2019
	 *
2020
	 * @return bool False if the path is excluded from locking, true otherwise
2021
	 * @throws LockedException
2022
	 */
2023
	private function unlockPath($path, $type, $lockMountPoint = false) {
2024
		$absolutePath = $this->getAbsolutePath($path);
2025
		$absolutePath = Filesystem::normalizePath($absolutePath);
2026
		if (!$this->shouldLockFile($absolutePath)) {
2027
			return false;
2028
		}
2029
2030
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2031
		if ($mount) {
0 ignored issues
show
introduced by
$mount is of type OC\Files\Mount\MountPoint, thus it always evaluated to true.
Loading history...
2032
			$storage = $mount->getStorage();
2033
			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2034
				$storage->releaseLock(
2035
					$mount->getInternalPath($absolutePath),
2036
					$type,
2037
					$this->lockingProvider
2038
				);
2039
			}
2040
		}
2041
2042
		return true;
2043
	}
2044
2045
	/**
2046
	 * Lock a path and all its parents up to the root of the view
2047
	 *
2048
	 * @param string $path the path of the file to lock relative to the view
2049
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2050
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2051
	 *
2052
	 * @return bool False if the path is excluded from locking, true otherwise
2053
	 * @throws LockedException
2054
	 */
2055
	public function lockFile($path, $type, $lockMountPoint = false) {
2056
		$absolutePath = $this->getAbsolutePath($path);
2057
		$absolutePath = Filesystem::normalizePath($absolutePath);
2058
		if (!$this->shouldLockFile($absolutePath)) {
2059
			return false;
2060
		}
2061
2062
		$this->lockPath($path, $type, $lockMountPoint);
2063
2064
		$parents = $this->getParents($path);
2065
		foreach ($parents as $parent) {
2066
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2067
		}
2068
2069
		return true;
2070
	}
2071
2072
	/**
2073
	 * Unlock a path and all its parents up to the root of the view
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
2081
	 */
2082
	public function unlockFile($path, $type, $lockMountPoint = false) {
2083
		$absolutePath = $this->getAbsolutePath($path);
2084
		$absolutePath = Filesystem::normalizePath($absolutePath);
2085
		if (!$this->shouldLockFile($absolutePath)) {
2086
			return false;
2087
		}
2088
2089
		$this->unlockPath($path, $type, $lockMountPoint);
2090
2091
		$parents = $this->getParents($path);
2092
		foreach ($parents as $parent) {
2093
			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2094
		}
2095
2096
		return true;
2097
	}
2098
2099
	/**
2100
	 * Only lock files in data/user/files/
2101
	 *
2102
	 * @param string $path Absolute path to the file/folder we try to (un)lock
2103
	 * @return bool
2104
	 */
2105
	protected function shouldLockFile($path) {
2106
		$path = Filesystem::normalizePath($path);
2107
2108
		$pathSegments = explode('/', $path);
2109
		if (isset($pathSegments[2])) {
2110
			// E.g.: /username/files/path-to-file
2111
			return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
2112
		}
2113
2114
		return strpos($path, '/appdata_') !== 0;
2115
	}
2116
2117
	/**
2118
	 * Shortens the given absolute path to be relative to
2119
	 * "$user/files".
2120
	 *
2121
	 * @param string $absolutePath absolute path which is under "files"
2122
	 *
2123
	 * @return string path relative to "files" with trimmed slashes or null
2124
	 * if the path was NOT relative to files
2125
	 *
2126
	 * @throws \InvalidArgumentException if the given path was not under "files"
2127
	 * @since 8.1.0
2128
	 */
2129
	public function getPathRelativeToFiles($absolutePath) {
2130
		$path = Filesystem::normalizePath($absolutePath);
2131
		$parts = explode('/', trim($path, '/'), 3);
2132
		// "$user", "files", "path/to/dir"
2133
		if (!isset($parts[1]) || $parts[1] !== 'files') {
2134
			$this->logger->error(
2135
				'$absolutePath must be relative to "files", value is "%s"',
2136
				[
2137
					$absolutePath
2138
				]
2139
			);
2140
			throw new \InvalidArgumentException('$absolutePath must be relative to "files"');
2141
		}
2142
		if (isset($parts[2])) {
2143
			return $parts[2];
2144
		}
2145
		return '';
2146
	}
2147
2148
	/**
2149
	 * @param string $filename
2150
	 * @return array
2151
	 * @throws \OC\User\NoUserException
2152
	 * @throws NotFoundException
2153
	 */
2154
	public function getUidAndFilename($filename) {
2155
		$info = $this->getFileInfo($filename);
2156
		if (!$info instanceof \OCP\Files\FileInfo) {
2157
			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2158
		}
2159
		$uid = $info->getOwner()->getUID();
2160
		if ($uid != \OCP\User::getUser()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\User::getUser() has been deprecated: 8.0.0 Use \OC::$server->getUserSession()->getUser()->getUID() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2160
		if ($uid != /** @scrutinizer ignore-deprecated */ \OCP\User::getUser()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
2161
			Filesystem::initMountPoints($uid);
2162
			$ownerView = new View('/' . $uid . '/files');
2163
			try {
2164
				$filename = $ownerView->getPath($info['fileid']);
2165
			} catch (NotFoundException $e) {
2166
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2167
			}
2168
		}
2169
		return [$uid, $filename];
2170
	}
2171
2172
	/**
2173
	 * Creates parent non-existing folders
2174
	 *
2175
	 * @param string $filePath
2176
	 * @return bool
2177
	 */
2178
	private function createParentDirectories($filePath) {
2179
		$directoryParts = explode('/', $filePath);
2180
		$directoryParts = array_filter($directoryParts);
2181
		foreach ($directoryParts as $key => $part) {
2182
			$currentPathElements = array_slice($directoryParts, 0, $key);
2183
			$currentPath = '/' . implode('/', $currentPathElements);
2184
			if ($this->is_file($currentPath)) {
2185
				return false;
2186
			}
2187
			if (!$this->file_exists($currentPath)) {
2188
				$this->mkdir($currentPath);
2189
			}
2190
		}
2191
2192
		return true;
2193
	}
2194
}
2195