Passed
Push — master ( 21c726...6458dd )
by Roeland
13:09
created

View::shouldEmitHooks()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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