Passed
Push — master ( 5cdc85...37718d )
by Morris
38:53 queued 21:57
created

View::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 13
rs 9.9666
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 karakayasemi <[email protected]>
13
 * @author Klaas Freitag <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Luke Policinski <[email protected]>
16
 * @author Michael Gapczynski <[email protected]>
17
 * @author Morris Jobke <[email protected]>
18
 * @author Petr Svoboda <[email protected]>
19
 * @author Piotr Filiciak <[email protected]>
20
 * @author Robin Appelman <[email protected]>
21
 * @author Robin McCorkell <[email protected]>
22
 * @author Roeland Jago Douma <[email protected]>
23
 * @author Sam Tuke <[email protected]>
24
 * @author Stefan Weil <[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
46
namespace OC\Files;
47
48
use Icewind\Streams\CallbackWrapper;
49
use OC\Files\Mount\MoveableMount;
50
use OC\Files\Storage\Storage;
51
use OC\User\User;
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\ILogger;
63
use OCP\IUser;
64
use OCP\Lock\ILockingProvider;
65
use OCP\Lock\LockedException;
66
67
/**
68
 * Class to provide access to ownCloud filesystem via a "view", and methods for
69
 * working with files within that view (e.g. read, write, delete, etc.). Each
70
 * view is restricted to a set of directories via a virtual root. The default view
71
 * uses the currently logged in user's data directory as root (parts of
72
 * OC_Filesystem are merely a wrapper for OC\Files\View).
73
 *
74
 * Apps that need to access files outside of the user data folders (to modify files
75
 * belonging to a user other than the one currently logged in, for example) should
76
 * use this class directly rather than using OC_Filesystem, or making use of PHP's
77
 * built-in file manipulation functions. This will ensure all hooks and proxies
78
 * are triggered correctly.
79
 *
80
 * Filesystem functions are not called directly; they are passed to the correct
81
 * \OC\Files\Storage\Storage object
82
 */
83
class View {
84
	/** @var string */
85
	private $fakeRoot = '';
86
87
	/**
88
	 * @var \OCP\Lock\ILockingProvider
89
	 */
90
	protected $lockingProvider;
91
92
	private $lockingEnabled;
93
94
	private $updaterEnabled = true;
95
96
	/** @var \OC\User\Manager */
97
	private $userManager;
98
99
	/** @var \OCP\ILogger */
100
	private $logger;
101
102
	/**
103
	 * @param string $root
104
	 * @throws \Exception If $root contains an invalid path
105
	 */
106
	public function __construct($root = '') {
107
		if (is_null($root)) {
0 ignored issues
show
introduced by
The condition is_null($root) is always false.
Loading history...
108
			throw new \InvalidArgumentException('Root can\'t be null');
109
		}
110
		if (!Filesystem::isValidPath($root)) {
111
			throw new \Exception();
112
		}
113
114
		$this->fakeRoot = $root;
115
		$this->lockingProvider = \OC::$server->getLockingProvider();
116
		$this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
117
		$this->userManager = \OC::$server->getUserManager();
118
		$this->logger = \OC::$server->getLogger();
119
	}
120
121
	public function getAbsolutePath($path = '/') {
122
		if ($path === null) {
123
			return null;
124
		}
125
		$this->assertPathLength($path);
126
		if ($path === '') {
127
			$path = '/';
128
		}
129
		if ($path[0] !== '/') {
130
			$path = '/' . $path;
131
		}
132
		return $this->fakeRoot . $path;
133
	}
134
135
	/**
136
	 * change the root to a fake root
137
	 *
138
	 * @param string $fakeRoot
139
	 * @return boolean|null
140
	 */
141
	public function chroot($fakeRoot) {
142
		if (!$fakeRoot == '') {
143
			if ($fakeRoot[0] !== '/') {
144
				$fakeRoot = '/' . $fakeRoot;
145
			}
146
		}
147
		$this->fakeRoot = $fakeRoot;
148
	}
149
150
	/**
151
	 * get the fake root
152
	 *
153
	 * @return string
154
	 */
155
	public function getRoot() {
156
		return $this->fakeRoot;
157
	}
158
159
	/**
160
	 * get path relative to the root of the view
161
	 *
162
	 * @param string $path
163
	 * @return string
164
	 */
165
	public function getRelativePath($path) {
166
		$this->assertPathLength($path);
167
		if ($this->fakeRoot == '') {
168
			return $path;
169
		}
170
171
		if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) {
172
			return '/';
173
		}
174
175
		// missing slashes can cause wrong matches!
176
		$root = rtrim($this->fakeRoot, '/') . '/';
177
178
		if (strpos($path, $root) !== 0) {
179
			return null;
180
		} else {
181
			$path = substr($path, strlen($this->fakeRoot));
182
			if (strlen($path) === 0) {
183
				return '/';
184
			} else {
185
				return $path;
186
			}
187
		}
188
	}
189
190
	/**
191
	 * get the mountpoint of the storage object for a path
192
	 * ( note: because a storage is not always mounted inside the fakeroot, the
193
	 * returned mountpoint is relative to the absolute root of the filesystem
194
	 * and does not take the chroot into account )
195
	 *
196
	 * @param string $path
197
	 * @return string
198
	 */
199
	public function getMountPoint($path) {
200
		return Filesystem::getMountPoint($this->getAbsolutePath($path));
201
	}
202
203
	/**
204
	 * get the mountpoint of the storage object for a path
205
	 * ( note: because a storage is not always mounted inside the fakeroot, the
206
	 * returned mountpoint is relative to the absolute root of the filesystem
207
	 * and does not take the chroot into account )
208
	 *
209
	 * @param string $path
210
	 * @return \OCP\Files\Mount\IMountPoint
211
	 */
212
	public function getMount($path) {
213
		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
214
	}
215
216
	/**
217
	 * resolve a path to a storage and internal path
218
	 *
219
	 * @param string $path
220
	 * @return array an array consisting of the storage and the internal path
221
	 */
222
	public function resolvePath($path) {
223
		$a = $this->getAbsolutePath($path);
224
		$p = Filesystem::normalizePath($a);
225
		return Filesystem::resolvePath($p);
226
	}
227
228
	/**
229
	 * return the path to a local version of the file
230
	 * we need this because we can't know if a file is stored local or not from
231
	 * outside the filestorage and for some purposes a local file is needed
232
	 *
233
	 * @param string $path
234
	 * @return string
235
	 */
236
	public function getLocalFile($path) {
237
		$parent = substr($path, 0, strrpos($path, '/'));
238
		$path = $this->getAbsolutePath($path);
239
		list($storage, $internalPath) = Filesystem::resolvePath($path);
240
		if (Filesystem::isValidPath($parent) and $storage) {
241
			return $storage->getLocalFile($internalPath);
242
		} else {
243
			return null;
244
		}
245
	}
246
247
	/**
248
	 * @param string $path
249
	 * @return string
250
	 */
251
	public function getLocalFolder($path) {
252
		$parent = substr($path, 0, strrpos($path, '/'));
253
		$path = $this->getAbsolutePath($path);
254
		list($storage, $internalPath) = Filesystem::resolvePath($path);
255
		if (Filesystem::isValidPath($parent) and $storage) {
256
			return $storage->getLocalFolder($internalPath);
257
		} else {
258
			return null;
259
		}
260
	}
261
262
	/**
263
	 * the following functions operate with arguments and return values identical
264
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
265
	 * for \OC\Files\Storage\Storage via basicOperation().
266
	 */
267
	public function mkdir($path) {
268
		return $this->basicOperation('mkdir', $path, array('create', 'write'));
269
	}
270
271
	/**
272
	 * remove mount point
273
	 *
274
	 * @param \OC\Files\Mount\MoveableMount $mount
275
	 * @param string $path relative to data/
276
	 * @return boolean
277
	 */
278
	protected function removeMount($mount, $path) {
279
		if ($mount instanceof MoveableMount) {
0 ignored issues
show
introduced by
$mount is always a sub-type of OC\Files\Mount\MoveableMount.
Loading history...
280
			// cut of /user/files to get the relative path to data/user/files
281
			$pathParts = explode('/', $path, 4);
282
			$relPath = '/' . $pathParts[3];
283
			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
284
			\OC_Hook::emit(
285
				Filesystem::CLASSNAME, "umount",
286
				array(Filesystem::signal_param_path => $relPath)
287
			);
288
			$this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
289
			$result = $mount->removeMount();
290
			$this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
291
			if ($result) {
292
				\OC_Hook::emit(
293
					Filesystem::CLASSNAME, "post_umount",
294
					array(Filesystem::signal_param_path => $relPath)
295
				);
296
			}
297
			$this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
298
			return $result;
299
		} else {
300
			// do not allow deleting the storage's root / the mount point
301
			// because for some storages it might delete the whole contents
302
			// but isn't supposed to work that way
303
			return false;
304
		}
305
	}
306
307
	public function disableCacheUpdate() {
308
		$this->updaterEnabled = false;
309
	}
310
311
	public function enableCacheUpdate() {
312
		$this->updaterEnabled = true;
313
	}
314
315
	protected function writeUpdate(Storage $storage, $internalPath, $time = null) {
316
		if ($this->updaterEnabled) {
317
			if (is_null($time)) {
318
				$time = time();
319
			}
320
			$storage->getUpdater()->update($internalPath, $time);
321
		}
322
	}
323
324
	protected function removeUpdate(Storage $storage, $internalPath) {
325
		if ($this->updaterEnabled) {
326
			$storage->getUpdater()->remove($internalPath);
327
		}
328
	}
329
330
	protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) {
331
		if ($this->updaterEnabled) {
332
			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
333
		}
334
	}
335
336
	/**
337
	 * @param string $path
338
	 * @return bool|mixed
339
	 */
340
	public function rmdir($path) {
341
		$absolutePath = $this->getAbsolutePath($path);
342
		$mount = Filesystem::getMountManager()->find($absolutePath);
343
		if ($mount->getInternalPath($absolutePath) === '') {
344
			return $this->removeMount($mount, $absolutePath);
0 ignored issues
show
Bug introduced by
It seems like $mount can also be of type OC\Files\Mount\MountPoint; however, parameter $mount of OC\Files\View::removeMount() does only seem to accept OC\Files\Mount\MoveableMount, maybe add an additional type check? ( Ignorable by Annotation )

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

344
			return $this->removeMount(/** @scrutinizer ignore-type */ $mount, $absolutePath);
Loading history...
345
		}
346
		if ($this->is_dir($path)) {
347
			$result = $this->basicOperation('rmdir', $path, array('delete'));
348
		} else {
349
			$result = false;
350
		}
351
352
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
353
			$storage = $mount->getStorage();
354
			$internalPath = $mount->getInternalPath($absolutePath);
355
			$storage->getUpdater()->remove($internalPath);
356
		}
357
		return $result;
358
	}
359
360
	/**
361
	 * @param string $path
362
	 * @return resource
363
	 */
364
	public function opendir($path) {
365
		return $this->basicOperation('opendir', $path, array('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...
366
	}
367
368
	/**
369
	 * @param string $path
370
	 * @return bool|mixed
371
	 */
372
	public function is_dir($path) {
373
		if ($path == '/') {
374
			return true;
375
		}
376
		return $this->basicOperation('is_dir', $path);
377
	}
378
379
	/**
380
	 * @param string $path
381
	 * @return bool|mixed
382
	 */
383
	public function is_file($path) {
384
		if ($path == '/') {
385
			return false;
386
		}
387
		return $this->basicOperation('is_file', $path);
388
	}
389
390
	/**
391
	 * @param string $path
392
	 * @return mixed
393
	 */
394
	public function stat($path) {
395
		return $this->basicOperation('stat', $path);
396
	}
397
398
	/**
399
	 * @param string $path
400
	 * @return mixed
401
	 */
402
	public function filetype($path) {
403
		return $this->basicOperation('filetype', $path);
404
	}
405
406
	/**
407
	 * @param string $path
408
	 * @return mixed
409
	 */
410
	public function filesize($path) {
411
		return $this->basicOperation('filesize', $path);
412
	}
413
414
	/**
415
	 * @param string $path
416
	 * @return bool|mixed
417
	 * @throws \OCP\Files\InvalidPathException
418
	 */
419
	public function readfile($path) {
420
		$this->assertPathLength($path);
421
		@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

421
		/** @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...
422
		$handle = $this->fopen($path, 'rb');
423
		if ($handle) {
0 ignored issues
show
introduced by
$handle is of type resource, thus it always evaluated to false.
Loading history...
424
			$chunkSize = 8192; // 8 kB chunks
425
			while (!feof($handle)) {
426
				echo fread($handle, $chunkSize);
427
				flush();
428
			}
429
			fclose($handle);
430
			return $this->filesize($path);
431
		}
432
		return false;
433
	}
434
435
	/**
436
	 * @param string $path
437
	 * @param int $from
438
	 * @param int $to
439
	 * @return bool|mixed
440
	 * @throws \OCP\Files\InvalidPathException
441
	 * @throws \OCP\Files\UnseekableException
442
	 */
443
	public function readfilePart($path, $from, $to) {
444
		$this->assertPathLength($path);
445
		@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

445
		/** @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...
446
		$handle = $this->fopen($path, 'rb');
447
		if ($handle) {
0 ignored issues
show
introduced by
$handle is of type resource, thus it always evaluated to false.
Loading history...
448
			$chunkSize = 8192; // 8 kB chunks
449
			$startReading = true;
450
451
			if ($from !== 0 && $from !== '0' && fseek($handle, $from) !== 0) {
452
				// forward file handle via chunked fread because fseek seem to have failed
453
454
				$end = $from + 1;
455
				while (!feof($handle) && ftell($handle) < $end) {
456
					$len = $from - ftell($handle);
457
					if ($len > $chunkSize) {
458
						$len = $chunkSize;
459
					}
460
					$result = fread($handle, $len);
461
462
					if ($result === false) {
463
						$startReading = false;
464
						break;
465
					}
466
				}
467
			}
468
469
			if ($startReading) {
470
				$end = $to + 1;
471
				while (!feof($handle) && ftell($handle) < $end) {
472
					$len = $end - ftell($handle);
473
					if ($len > $chunkSize) {
474
						$len = $chunkSize;
475
					}
476
					echo fread($handle, $len);
477
					flush();
478
				}
479
				return ftell($handle) - $from;
480
			}
481
482
			throw new \OCP\Files\UnseekableException('fseek error');
483
		}
484
		return false;
485
	}
486
487
	/**
488
	 * @param string $path
489
	 * @return mixed
490
	 */
491
	public function isCreatable($path) {
492
		return $this->basicOperation('isCreatable', $path);
493
	}
494
495
	/**
496
	 * @param string $path
497
	 * @return mixed
498
	 */
499
	public function isReadable($path) {
500
		return $this->basicOperation('isReadable', $path);
501
	}
502
503
	/**
504
	 * @param string $path
505
	 * @return mixed
506
	 */
507
	public function isUpdatable($path) {
508
		return $this->basicOperation('isUpdatable', $path);
509
	}
510
511
	/**
512
	 * @param string $path
513
	 * @return bool|mixed
514
	 */
515
	public function isDeletable($path) {
516
		$absolutePath = $this->getAbsolutePath($path);
517
		$mount = Filesystem::getMountManager()->find($absolutePath);
518
		if ($mount->getInternalPath($absolutePath) === '') {
519
			return $mount instanceof MoveableMount;
520
		}
521
		return $this->basicOperation('isDeletable', $path);
522
	}
523
524
	/**
525
	 * @param string $path
526
	 * @return mixed
527
	 */
528
	public function isSharable($path) {
529
		return $this->basicOperation('isSharable', $path);
530
	}
531
532
	/**
533
	 * @param string $path
534
	 * @return bool|mixed
535
	 */
536
	public function file_exists($path) {
537
		if ($path == '/') {
538
			return true;
539
		}
540
		return $this->basicOperation('file_exists', $path);
541
	}
542
543
	/**
544
	 * @param string $path
545
	 * @return mixed
546
	 */
547
	public function filemtime($path) {
548
		return $this->basicOperation('filemtime', $path);
549
	}
550
551
	/**
552
	 * @param string $path
553
	 * @param int|string $mtime
554
	 * @return bool
555
	 */
556
	public function touch($path, $mtime = null) {
557
		if (!is_null($mtime) and !is_numeric($mtime)) {
558
			$mtime = strtotime($mtime);
559
		}
560
561
		$hooks = array('touch');
562
563
		if (!$this->file_exists($path)) {
564
			$hooks[] = 'create';
565
			$hooks[] = 'write';
566
		}
567
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
568
		if (!$result) {
569
			// If create file fails because of permissions on external storage like SMB folders,
570
			// check file exists and return false if not.
571
			if (!$this->file_exists($path)) {
572
				return false;
573
			}
574
			if (is_null($mtime)) {
575
				$mtime = time();
576
			}
577
			//if native touch fails, we emulate it by changing the mtime in the cache
578
			$this->putFileInfo($path, array('mtime' => floor($mtime)));
0 ignored issues
show
Bug introduced by
It seems like $mtime can also be of type string; however, parameter $value of floor() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

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

578
			$this->putFileInfo($path, array('mtime' => floor(/** @scrutinizer ignore-type */ $mtime)));
Loading history...
579
		}
580
		return true;
581
	}
582
583
	/**
584
	 * @param string $path
585
	 * @return mixed
586
	 */
587
	public function file_get_contents($path) {
588
		return $this->basicOperation('file_get_contents', $path, array('read'));
589
	}
590
591
	/**
592
	 * @param bool $exists
593
	 * @param string $path
594
	 * @param bool $run
595
	 */
596
	protected function emit_file_hooks_pre($exists, $path, &$run) {
597
		if (!$exists) {
598
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array(
599
				Filesystem::signal_param_path => $this->getHookPath($path),
600
				Filesystem::signal_param_run => &$run,
601
			));
602
		} else {
603
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array(
604
				Filesystem::signal_param_path => $this->getHookPath($path),
605
				Filesystem::signal_param_run => &$run,
606
			));
607
		}
608
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array(
609
			Filesystem::signal_param_path => $this->getHookPath($path),
610
			Filesystem::signal_param_run => &$run,
611
		));
612
	}
613
614
	/**
615
	 * @param bool $exists
616
	 * @param string $path
617
	 */
618
	protected function emit_file_hooks_post($exists, $path) {
619
		if (!$exists) {
620
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array(
621
				Filesystem::signal_param_path => $this->getHookPath($path),
622
			));
623
		} else {
624
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
625
				Filesystem::signal_param_path => $this->getHookPath($path),
626
			));
627
		}
628
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
629
			Filesystem::signal_param_path => $this->getHookPath($path),
630
		));
631
	}
632
633
	/**
634
	 * @param string $path
635
	 * @param string|resource $data
636
	 * @return bool|mixed
637
	 * @throws \Exception
638
	 */
639
	public function file_put_contents($path, $data) {
640
		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
641
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
642
			if (Filesystem::isValidPath($path)
643
				and !Filesystem::isFileBlacklisted($path)
644
			) {
645
				$path = $this->getRelativePath($absolutePath);
646
647
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
648
649
				$exists = $this->file_exists($path);
650
				$run = true;
651
				if ($this->shouldEmitHooks($path)) {
652
					$this->emit_file_hooks_pre($exists, $path, $run);
653
				}
654
				if (!$run) {
655
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
656
					return false;
657
				}
658
659
				$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
660
661
				/** @var \OC\Files\Storage\Storage $storage */
662
				list($storage, $internalPath) = $this->resolvePath($path);
663
				$target = $storage->fopen($internalPath, 'w');
664
				if ($target) {
0 ignored issues
show
introduced by
$target is of type false|resource, thus it always evaluated to false.
Loading history...
665
					list (, $result) = \OC_Helper::streamCopy($data, $target);
666
					fclose($target);
667
					fclose($data);
668
669
					$this->writeUpdate($storage, $internalPath);
670
671
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
672
673
					if ($this->shouldEmitHooks($path) && $result !== false) {
674
						$this->emit_file_hooks_post($exists, $path);
675
					}
676
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
677
					return $result;
678
				} else {
679
					$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
680
					return false;
681
				}
682
			} else {
683
				return false;
684
			}
685
		} else {
686
			$hooks = $this->file_exists($path) ? array('update', 'write') : array('create', 'write');
687
			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
688
		}
689
	}
690
691
	/**
692
	 * @param string $path
693
	 * @return bool|mixed
694
	 */
695
	public function unlink($path) {
696
		if ($path === '' || $path === '/') {
697
			// do not allow deleting the root
698
			return false;
699
		}
700
		$postFix = (substr($path, -1) === '/') ? '/' : '';
701
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
702
		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
703
		if ($mount and $mount->getInternalPath($absolutePath) === '') {
704
			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

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

793
									/** @scrutinizer ignore-call */ 
794
         $sourceMountPoint = $mount1->getMountPoint();
Loading history...
794
									$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

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

979
				/** @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...
980
		}
981
982
		if ($mode !== 'r' && $mode !== 'w') {
983
			\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');
984
		}
985
986
		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...
987
	}
988
989
	/**
990
	 * @param string $path
991
	 * @return bool|string
992
	 * @throws \OCP\Files\InvalidPathException
993
	 */
994
	public function toTmpFile($path) {
995
		$this->assertPathLength($path);
996
		if (Filesystem::isValidPath($path)) {
997
			$source = $this->fopen($path, 'r');
998
			if ($source) {
0 ignored issues
show
introduced by
$source is of type resource, thus it always evaluated to false.
Loading history...
999
				$extension = pathinfo($path, PATHINFO_EXTENSION);
1000
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
1001
				file_put_contents($tmpFile, $source);
1002
				return $tmpFile;
1003
			} else {
1004
				return false;
1005
			}
1006
		} else {
1007
			return false;
1008
		}
1009
	}
1010
1011
	/**
1012
	 * @param string $tmpFile
1013
	 * @param string $path
1014
	 * @return bool|mixed
1015
	 * @throws \OCP\Files\InvalidPathException
1016
	 */
1017
	public function fromTmpFile($tmpFile, $path) {
1018
		$this->assertPathLength($path);
1019
		if (Filesystem::isValidPath($path)) {
1020
1021
			// Get directory that the file is going into
1022
			$filePath = dirname($path);
1023
1024
			// Create the directories if any
1025
			if (!$this->file_exists($filePath)) {
1026
				$result = $this->createParentDirectories($filePath);
1027
				if ($result === false) {
1028
					return false;
1029
				}
1030
			}
1031
1032
			$source = fopen($tmpFile, 'r');
1033
			if ($source) {
0 ignored issues
show
introduced by
$source is of type false|resource, thus it always evaluated to false.
Loading history...
1034
				$result = $this->file_put_contents($path, $source);
1035
				// $this->file_put_contents() might have already closed
1036
				// the resource, so we check it, before trying to close it
1037
				// to avoid messages in the error log.
1038
				if (is_resource($source)) {
1039
					fclose($source);
1040
				}
1041
				unlink($tmpFile);
1042
				return $result;
1043
			} else {
1044
				return false;
1045
			}
1046
		} else {
1047
			return false;
1048
		}
1049
	}
1050
1051
1052
	/**
1053
	 * @param string $path
1054
	 * @return mixed
1055
	 * @throws \OCP\Files\InvalidPathException
1056
	 */
1057
	public function getMimeType($path) {
1058
		$this->assertPathLength($path);
1059
		return $this->basicOperation('getMimeType', $path);
1060
	}
1061
1062
	/**
1063
	 * @param string $type
1064
	 * @param string $path
1065
	 * @param bool $raw
1066
	 * @return bool|null|string
1067
	 */
1068
	public function hash($type, $path, $raw = false) {
1069
		$postFix = (substr($path, -1) === '/') ? '/' : '';
1070
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1071
		if (Filesystem::isValidPath($path)) {
1072
			$path = $this->getRelativePath($absolutePath);
1073
			if ($path == null) {
1074
				return false;
1075
			}
1076
			if ($this->shouldEmitHooks($path)) {
1077
				\OC_Hook::emit(
1078
					Filesystem::CLASSNAME,
1079
					Filesystem::signal_read,
1080
					array(Filesystem::signal_param_path => $this->getHookPath($path))
1081
				);
1082
			}
1083
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1084
			if ($storage) {
1085
				return $storage->hash($type, $internalPath, $raw);
1086
			}
1087
		}
1088
		return null;
1089
	}
1090
1091
	/**
1092
	 * @param string $path
1093
	 * @return mixed
1094
	 * @throws \OCP\Files\InvalidPathException
1095
	 */
1096
	public function free_space($path = '/') {
1097
		$this->assertPathLength($path);
1098
		$result = $this->basicOperation('free_space', $path);
1099
		if ($result === null) {
1100
			throw new InvalidPathException();
1101
		}
1102
		return $result;
1103
	}
1104
1105
	/**
1106
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1107
	 *
1108
	 * @param string $operation
1109
	 * @param string $path
1110
	 * @param array $hooks (optional)
1111
	 * @param mixed $extraParam (optional)
1112
	 * @return mixed
1113
	 * @throws \Exception
1114
	 *
1115
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1116
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1117
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1118
	 */
1119
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1120
		$postFix = (substr($path, -1) === '/') ? '/' : '';
1121
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1122
		if (Filesystem::isValidPath($path)
1123
			and !Filesystem::isFileBlacklisted($path)
1124
		) {
1125
			$path = $this->getRelativePath($absolutePath);
1126
			if ($path == null) {
1127
				return false;
1128
			}
1129
1130
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1131
				// always a shared lock during pre-hooks so the hook can read the file
1132
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1133
			}
1134
1135
			$run = $this->runHooks($hooks, $path);
1136
			/** @var \OC\Files\Storage\Storage $storage */
1137
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1138
			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...
1139
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1140
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1141
				}
1142
				try {
1143
					if (!is_null($extraParam)) {
1144
						$result = $storage->$operation($internalPath, $extraParam);
1145
					} else {
1146
						$result = $storage->$operation($internalPath);
1147
					}
1148
				} catch (\Exception $e) {
1149
					if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1150
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1151
					} else if (in_array('read', $hooks)) {
1152
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1153
					}
1154
					throw $e;
1155
				}
1156
1157
				if ($result && in_array('delete', $hooks) and $result) {
1158
					$this->removeUpdate($storage, $internalPath);
1159
				}
1160
				if ($result && in_array('write', $hooks) and $operation !== 'fopen') {
1161
					$this->writeUpdate($storage, $internalPath);
1162
				}
1163
				if ($result && in_array('touch', $hooks)) {
1164
					$this->writeUpdate($storage, $internalPath, $extraParam);
1165
				}
1166
1167
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1168
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1169
				}
1170
1171
				$unlockLater = false;
1172
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1173
					$unlockLater = true;
1174
					// make sure our unlocking callback will still be called if connection is aborted
1175
					ignore_user_abort(true);
1176
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1177
						if (in_array('write', $hooks)) {
1178
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1179
						} else if (in_array('read', $hooks)) {
1180
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1181
						}
1182
					});
1183
				}
1184
1185
				if ($this->shouldEmitHooks($path) && $result !== false) {
1186
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1187
						$this->runHooks($hooks, $path, true);
1188
					}
1189
				}
1190
1191
				if (!$unlockLater
1192
					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1193
				) {
1194
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1195
				}
1196
				return $result;
1197
			} else {
1198
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1199
			}
1200
		}
1201
		return null;
1202
	}
1203
1204
	/**
1205
	 * get the path relative to the default root for hook usage
1206
	 *
1207
	 * @param string $path
1208
	 * @return string
1209
	 */
1210
	private function getHookPath($path) {
1211
		if (!Filesystem::getView()) {
1212
			return $path;
1213
		}
1214
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1215
	}
1216
1217
	private function shouldEmitHooks($path = '') {
1218
		if ($path && Cache\Scanner::isPartialFile($path)) {
1219
			return false;
1220
		}
1221
		if (!Filesystem::$loaded) {
1222
			return false;
1223
		}
1224
		$defaultRoot = Filesystem::getRoot();
1225
		if ($defaultRoot === null) {
0 ignored issues
show
introduced by
The condition $defaultRoot === null is always false.
Loading history...
1226
			return false;
1227
		}
1228
		if ($this->fakeRoot === $defaultRoot) {
1229
			return true;
1230
		}
1231
		$fullPath = $this->getAbsolutePath($path);
1232
1233
		if ($fullPath === $defaultRoot) {
1234
			return true;
1235
		}
1236
1237
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1238
	}
1239
1240
	/**
1241
	 * @param string[] $hooks
1242
	 * @param string $path
1243
	 * @param bool $post
1244
	 * @return bool
1245
	 */
1246
	private function runHooks($hooks, $path, $post = false) {
1247
		$relativePath = $path;
1248
		$path = $this->getHookPath($path);
1249
		$prefix = $post ? 'post_' : '';
1250
		$run = true;
1251
		if ($this->shouldEmitHooks($relativePath)) {
1252
			foreach ($hooks as $hook) {
1253
				if ($hook != 'read') {
1254
					\OC_Hook::emit(
1255
						Filesystem::CLASSNAME,
1256
						$prefix . $hook,
1257
						array(
1258
							Filesystem::signal_param_run => &$run,
1259
							Filesystem::signal_param_path => $path
1260
						)
1261
					);
1262
				} elseif (!$post) {
1263
					\OC_Hook::emit(
1264
						Filesystem::CLASSNAME,
1265
						$prefix . $hook,
1266
						array(
1267
							Filesystem::signal_param_path => $path
1268
						)
1269
					);
1270
				}
1271
			}
1272
		}
1273
		return $run;
1274
	}
1275
1276
	/**
1277
	 * check if a file or folder has been updated since $time
1278
	 *
1279
	 * @param string $path
1280
	 * @param int $time
1281
	 * @return bool
1282
	 */
1283
	public function hasUpdated($path, $time) {
1284
		return $this->basicOperation('hasUpdated', $path, array(), $time);
1285
	}
1286
1287
	/**
1288
	 * @param string $ownerId
1289
	 * @return \OC\User\User
1290
	 */
1291
	private function getUserObjectForOwner($ownerId) {
1292
		$owner = $this->userManager->get($ownerId);
1293
		if ($owner instanceof IUser) {
1294
			return $owner;
1295
		} else {
1296
			return new User($ownerId, null);
1297
		}
1298
	}
1299
1300
	/**
1301
	 * Get file info from cache
1302
	 *
1303
	 * If the file is not in cached it will be scanned
1304
	 * If the file has changed on storage the cache will be updated
1305
	 *
1306
	 * @param \OC\Files\Storage\Storage $storage
1307
	 * @param string $internalPath
1308
	 * @param string $relativePath
1309
	 * @return ICacheEntry|bool
1310
	 */
1311
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1312
		$cache = $storage->getCache($internalPath);
1313
		$data = $cache->get($internalPath);
1314
		$watcher = $storage->getWatcher($internalPath);
1315
1316
		try {
1317
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1318
			if (!$data || $data['size'] === -1) {
1319
				if (!$storage->file_exists($internalPath)) {
1320
					return false;
1321
				}
1322
				// don't need to get a lock here since the scanner does it's own locking
1323
				$scanner = $storage->getScanner($internalPath);
1324
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1325
				$data = $cache->get($internalPath);
1326
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1327
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1328
				$watcher->update($internalPath, $data);
1329
				$storage->getPropagator()->propagateChange($internalPath, time());
1330
				$data = $cache->get($internalPath);
1331
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1332
			}
1333
		} catch (LockedException $e) {
1334
			// if the file is locked we just use the old cache info
1335
		}
1336
1337
		return $data;
1338
	}
1339
1340
	/**
1341
	 * get the filesystem info
1342
	 *
1343
	 * @param string $path
1344
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1345
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1346
	 * defaults to true
1347
	 * @return \OC\Files\FileInfo|false False if file does not exist
1348
	 */
1349
	public function getFileInfo($path, $includeMountPoints = true) {
1350
		$this->assertPathLength($path);
1351
		if (!Filesystem::isValidPath($path)) {
1352
			return false;
1353
		}
1354
		if (Cache\Scanner::isPartialFile($path)) {
1355
			return $this->getPartFileInfo($path);
1356
		}
1357
		$relativePath = $path;
1358
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1359
1360
		$mount = Filesystem::getMountManager()->find($path);
1361
		if (!$mount) {
1362
			\OC::$server->getLogger()->warning('Mountpoint not found for path: ' . $path);
1363
			return false;
1364
		}
1365
		$storage = $mount->getStorage();
1366
		$internalPath = $mount->getInternalPath($path);
1367
		if ($storage) {
0 ignored issues
show
introduced by
$storage is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
1368
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1369
1370
			if (!$data instanceof ICacheEntry) {
1371
				\OC::$server->getLogger()->debug('No cache entry found for ' . $path . ' (storage: ' . $storage->getId() . ', internalPath: ' . $internalPath . ')');
1372
				return false;
1373
			}
1374
1375
			if ($mount instanceof MoveableMount && $internalPath === '') {
1376
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1377
			}
1378
1379
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1380
			$info = new FileInfo($path, $storage, $internalPath, $data, $mount, $owner);
1381
1382
			if ($data and isset($data['fileid'])) {
1383
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1384
					//add the sizes of other mount points to the folder
1385
					$extOnly = ($includeMountPoints === 'ext');
1386
					$mounts = Filesystem::getMountManager()->findIn($path);
1387
					$info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1388
						$subStorage = $mount->getStorage();
1389
						return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage);
1390
					}));
1391
				}
1392
			}
1393
1394
			return $info;
1395
		} else {
1396
			\OC::$server->getLogger()->warning('Storage not valid for mountpoint: ' . $mount->getMountPoint());
1397
		}
1398
1399
		return false;
1400
	}
1401
1402
	/**
1403
	 * get the content of a directory
1404
	 *
1405
	 * @param string $directory path under datadirectory
1406
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1407
	 * @return FileInfo[]
1408
	 */
1409
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1410
		$this->assertPathLength($directory);
1411
		if (!Filesystem::isValidPath($directory)) {
1412
			return [];
1413
		}
1414
		$path = $this->getAbsolutePath($directory);
1415
		$path = Filesystem::normalizePath($path);
1416
		$mount = $this->getMount($directory);
1417
		if (!$mount) {
0 ignored issues
show
introduced by
$mount is of type OC\Files\Mount\MountPoint, thus it always evaluated to true.
Loading history...
1418
			return [];
1419
		}
1420
		$storage = $mount->getStorage();
1421
		$internalPath = $mount->getInternalPath($path);
1422
		if ($storage) {
0 ignored issues
show
introduced by
$storage is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
1423
			$cache = $storage->getCache($internalPath);
1424
			$user = \OC_User::getUser();
1425
1426
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1427
1428
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1429
				return [];
1430
			}
1431
1432
			$folderId = $data['fileid'];
1433
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1434
1435
			$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

1435
			$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...
1436
1437
			$fileNames = array_map(function(ICacheEntry $content) {
1438
				return $content->getName();
1439
			}, $contents);
1440
			/**
1441
			 * @var \OC\Files\FileInfo[] $fileInfos
1442
			 */
1443
			$fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1444
				if ($sharingDisabled) {
1445
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1446
				}
1447
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1448
				return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
1449
			}, $contents);
1450
			$files = array_combine($fileNames, $fileInfos);
1451
1452
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1453
			$mounts = Filesystem::getMountManager()->findIn($path);
1454
			$dirLength = strlen($path);
1455
			foreach ($mounts as $mount) {
1456
				$mountPoint = $mount->getMountPoint();
1457
				$subStorage = $mount->getStorage();
1458
				if ($subStorage) {
1459
					$subCache = $subStorage->getCache('');
1460
1461
					$rootEntry = $subCache->get('');
1462
					if (!$rootEntry) {
1463
						$subScanner = $subStorage->getScanner('');
1464
						try {
1465
							$subScanner->scanFile('');
1466
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1467
							continue;
1468
						} catch (\OCP\Files\StorageInvalidException $e) {
1469
							continue;
1470
						} catch (\Exception $e) {
1471
							// sometimes when the storage is not available it can be any exception
1472
							\OC::$server->getLogger()->logException($e, [
1473
								'message' => 'Exception while scanning storage "' . $subStorage->getId() . '"',
1474
								'level' => ILogger::ERROR,
1475
								'app' => 'lib',
1476
							]);
1477
							continue;
1478
						}
1479
						$rootEntry = $subCache->get('');
1480
					}
1481
1482
					if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1483
						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1484
						if ($pos = strpos($relativePath, '/')) {
1485
							//mountpoint inside subfolder add size to the correct folder
1486
							$entryName = substr($relativePath, 0, $pos);
1487
							foreach ($files as &$entry) {
1488
								if ($entry->getName() === $entryName) {
1489
									$entry->addSubEntry($rootEntry, $mountPoint);
1490
								}
1491
							}
1492
						} else { //mountpoint in this folder, add an entry for it
1493
							$rootEntry['name'] = $relativePath;
1494
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1495
							$permissions = $rootEntry['permissions'];
1496
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1497
							// for shared files/folders we use the permissions given by the owner
1498
							if ($mount instanceof MoveableMount) {
1499
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1500
							} else {
1501
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1502
							}
1503
1504
							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1505
1506
							// if sharing was disabled for the user we remove the share permissions
1507
							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

1507
							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...
1508
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1509
							}
1510
1511
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1512
							$files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1513
						}
1514
					}
1515
				}
1516
			}
1517
1518
			if ($mimetype_filter) {
1519
				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1520
					if (strpos($mimetype_filter, '/')) {
1521
						return $file->getMimetype() === $mimetype_filter;
1522
					} else {
1523
						return $file->getMimePart() === $mimetype_filter;
1524
					}
1525
				});
1526
			}
1527
1528
			return array_values($files);
1529
		} else {
1530
			return [];
1531
		}
1532
	}
1533
1534
	/**
1535
	 * change file metadata
1536
	 *
1537
	 * @param string $path
1538
	 * @param array|\OCP\Files\FileInfo $data
1539
	 * @return int
1540
	 *
1541
	 * returns the fileid of the updated file
1542
	 */
1543
	public function putFileInfo($path, $data) {
1544
		$this->assertPathLength($path);
1545
		if ($data instanceof FileInfo) {
1546
			$data = $data->getData();
1547
		}
1548
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1549
		/**
1550
		 * @var \OC\Files\Storage\Storage $storage
1551
		 * @var string $internalPath
1552
		 */
1553
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1554
		if ($storage) {
0 ignored issues
show
introduced by
$storage is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
1555
			$cache = $storage->getCache($path);
1556
1557
			if (!$cache->inCache($internalPath)) {
1558
				$scanner = $storage->getScanner($internalPath);
1559
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1560
			}
1561
1562
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type OCP\Files\FileInfo; however, parameter $data of OC\Files\Cache\Cache::put() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

1562
			return $cache->put($internalPath, /** @scrutinizer ignore-type */ $data);
Loading history...
1563
		} else {
1564
			return -1;
1565
		}
1566
	}
1567
1568
	/**
1569
	 * search for files with the name matching $query
1570
	 *
1571
	 * @param string $query
1572
	 * @return FileInfo[]
1573
	 */
1574
	public function search($query) {
1575
		return $this->searchCommon('search', array('%' . $query . '%'));
1576
	}
1577
1578
	/**
1579
	 * search for files with the name matching $query
1580
	 *
1581
	 * @param string $query
1582
	 * @return FileInfo[]
1583
	 */
1584
	public function searchRaw($query) {
1585
		return $this->searchCommon('search', array($query));
1586
	}
1587
1588
	/**
1589
	 * search for files by mimetype
1590
	 *
1591
	 * @param string $mimetype
1592
	 * @return FileInfo[]
1593
	 */
1594
	public function searchByMime($mimetype) {
1595
		return $this->searchCommon('searchByMime', array($mimetype));
1596
	}
1597
1598
	/**
1599
	 * search for files by tag
1600
	 *
1601
	 * @param string|int $tag name or tag id
1602
	 * @param string $userId owner of the tags
1603
	 * @return FileInfo[]
1604
	 */
1605
	public function searchByTag($tag, $userId) {
1606
		return $this->searchCommon('searchByTag', array($tag, $userId));
1607
	}
1608
1609
	/**
1610
	 * @param string $method cache method
1611
	 * @param array $args
1612
	 * @return FileInfo[]
1613
	 */
1614
	private function searchCommon($method, $args) {
1615
		$files = array();
1616
		$rootLength = strlen($this->fakeRoot);
1617
1618
		$mount = $this->getMount('');
1619
		$mountPoint = $mount->getMountPoint();
1620
		$storage = $mount->getStorage();
1621
		if ($storage) {
0 ignored issues
show
introduced by
$storage is of type OC\Files\Storage\Storage, thus it always evaluated to true.
Loading history...
1622
			$cache = $storage->getCache('');
1623
1624
			$results = call_user_func_array(array($cache, $method), $args);
1625
			foreach ($results as $result) {
1626
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1627
					$internalPath = $result['path'];
1628
					$path = $mountPoint . $result['path'];
1629
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1630
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1631
					$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1632
				}
1633
			}
1634
1635
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1636
			foreach ($mounts as $mount) {
1637
				$mountPoint = $mount->getMountPoint();
1638
				$storage = $mount->getStorage();
1639
				if ($storage) {
1640
					$cache = $storage->getCache('');
1641
1642
					$relativeMountPoint = substr($mountPoint, $rootLength);
1643
					$results = call_user_func_array(array($cache, $method), $args);
1644
					if ($results) {
1645
						foreach ($results as $result) {
1646
							$internalPath = $result['path'];
1647
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1648
							$path = rtrim($mountPoint . $internalPath, '/');
1649
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1650
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1651
						}
1652
					}
1653
				}
1654
			}
1655
		}
1656
		return $files;
1657
	}
1658
1659
	/**
1660
	 * Get the owner for a file or folder
1661
	 *
1662
	 * @param string $path
1663
	 * @return string the user id of the owner
1664
	 * @throws NotFoundException
1665
	 */
1666
	public function getOwner($path) {
1667
		$info = $this->getFileInfo($path);
1668
		if (!$info) {
1669
			throw new NotFoundException($path . ' not found while trying to get owner');
1670
		}
1671
		return $info->getOwner()->getUID();
1672
	}
1673
1674
	/**
1675
	 * get the ETag for a file or folder
1676
	 *
1677
	 * @param string $path
1678
	 * @return string
1679
	 */
1680
	public function getETag($path) {
1681
		/**
1682
		 * @var Storage\Storage $storage
1683
		 * @var string $internalPath
1684
		 */
1685
		list($storage, $internalPath) = $this->resolvePath($path);
1686
		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...
1687
			return $storage->getETag($internalPath);
1688
		} else {
1689
			return null;
1690
		}
1691
	}
1692
1693
	/**
1694
	 * Get the path of a file by id, relative to the view
1695
	 *
1696
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1697
	 *
1698
	 * @param int $id
1699
	 * @throws NotFoundException
1700
	 * @return string
1701
	 */
1702
	public function getPath($id) {
1703
		$id = (int)$id;
1704
		$manager = Filesystem::getMountManager();
1705
		$mounts = $manager->findIn($this->fakeRoot);
1706
		$mounts[] = $manager->find($this->fakeRoot);
1707
		// reverse the array so we start with the storage this view is in
1708
		// which is the most likely to contain the file we're looking for
1709
		$mounts = array_reverse($mounts);
1710
		foreach ($mounts as $mount) {
1711
			/**
1712
			 * @var \OC\Files\Mount\MountPoint $mount
1713
			 */
1714
			if ($mount->getStorage()) {
1715
				$cache = $mount->getStorage()->getCache();
1716
				$internalPath = $cache->getPathById($id);
1717
				if (is_string($internalPath)) {
1718
					$fullPath = $mount->getMountPoint() . $internalPath;
1719
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1720
						return $path;
1721
					}
1722
				}
1723
			}
1724
		}
1725
		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1726
	}
1727
1728
	/**
1729
	 * @param string $path
1730
	 * @throws InvalidPathException
1731
	 */
1732
	private function assertPathLength($path) {
1733
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1734
		// Check for the string length - performed using isset() instead of strlen()
1735
		// because isset() is about 5x-40x faster.
1736
		if (isset($path[$maxLen])) {
1737
			$pathLen = strlen($path);
1738
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1739
		}
1740
	}
1741
1742
	/**
1743
	 * check if it is allowed to move a mount point to a given target.
1744
	 * It is not allowed to move a mount point into a different mount point or
1745
	 * into an already shared folder
1746
	 *
1747
	 * @param string $target path
1748
	 * @return boolean
1749
	 */
1750
	private function isTargetAllowed($target) {
1751
1752
		list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target);
1753
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1754
			\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

1754
			/** @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...
1755
				'It is not allowed to move one mount point into another one',
1756
				ILogger::DEBUG);
1757
			return false;
1758
		}
1759
1760
		// note: cannot use the view because the target is already locked
1761
		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1762
		if ($fileId === -1) {
1763
			// target might not exist, need to check parent instead
1764
			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1765
		}
1766
1767
		// check if any of the parents were shared by the current owner (include collections)
1768
		$shares = \OCP\Share::getItemShared(
1769
			'folder',
1770
			$fileId,
1771
			\OCP\Share::FORMAT_NONE,
1772
			null,
1773
			true
1774
		);
1775
1776
		if (count($shares) > 0) {
1777
			\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

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

2130
		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...
2131
			Filesystem::initMountPoints($uid);
2132
			$ownerView = new View('/' . $uid . '/files');
2133
			try {
2134
				$filename = $ownerView->getPath($info['fileid']);
2135
			} catch (NotFoundException $e) {
2136
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2137
			}
2138
		}
2139
		return [$uid, $filename];
2140
	}
2141
2142
	/**
2143
	 * Creates parent non-existing folders
2144
	 *
2145
	 * @param string $filePath
2146
	 * @return bool
2147
	 */
2148
	private function createParentDirectories($filePath) {
2149
		$directoryParts = explode('/', $filePath);
2150
		$directoryParts = array_filter($directoryParts);
2151
		foreach ($directoryParts as $key => $part) {
2152
			$currentPathElements = array_slice($directoryParts, 0, $key);
2153
			$currentPath = '/' . implode('/', $currentPathElements);
2154
			if ($this->is_file($currentPath)) {
2155
				return false;
2156
			}
2157
			if (!$this->file_exists($currentPath)) {
2158
				$this->mkdir($currentPath);
2159
			}
2160
		}
2161
2162
		return true;
2163
	}
2164
}
2165