Passed
Push — master ( 96b15d...58fe0b )
by Roeland
21:08 queued 10:04
created

View::getPath()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nc 5
nop 1
dl 0
loc 31
rs 8.8333
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 OCA\Files_Sharing\SharedMount;
53
use OCP\Constants;
54
use OCP\Files\Cache\ICacheEntry;
55
use OCP\Files\EmptyFileNameException;
56
use OCP\Files\FileNameTooLongException;
57
use OCP\Files\InvalidCharacterInPathException;
58
use OCP\Files\InvalidDirectoryException;
59
use OCP\Files\InvalidPathException;
60
use OCP\Files\Mount\IMountPoint;
61
use OCP\Files\NotFoundException;
62
use OCP\Files\ReservedWordException;
63
use OCP\Files\Storage\IStorage;
64
use OCP\ILogger;
65
use OCP\IUser;
66
use OCP\Lock\ILockingProvider;
67
use OCP\Lock\LockedException;
68
69
/**
70
 * Class to provide access to ownCloud filesystem via a "view", and methods for
71
 * working with files within that view (e.g. read, write, delete, etc.). Each
72
 * view is restricted to a set of directories via a virtual root. The default view
73
 * uses the currently logged in user's data directory as root (parts of
74
 * OC_Filesystem are merely a wrapper for OC\Files\View).
75
 *
76
 * Apps that need to access files outside of the user data folders (to modify files
77
 * belonging to a user other than the one currently logged in, for example) should
78
 * use this class directly rather than using OC_Filesystem, or making use of PHP's
79
 * built-in file manipulation functions. This will ensure all hooks and proxies
80
 * are triggered correctly.
81
 *
82
 * Filesystem functions are not called directly; they are passed to the correct
83
 * \OC\Files\Storage\Storage object
84
 */
85
class View {
86
	/** @var string */
87
	private $fakeRoot = '';
88
89
	/**
90
	 * @var \OCP\Lock\ILockingProvider
91
	 */
92
	protected $lockingProvider;
93
94
	private $lockingEnabled;
95
96
	private $updaterEnabled = true;
97
98
	/** @var \OC\User\Manager */
99
	private $userManager;
100
101
	/** @var \OCP\ILogger */
102
	private $logger;
103
104
	/**
105
	 * @param string $root
106
	 * @throws \Exception If $root contains an invalid path
107
	 */
108
	public function __construct($root = '') {
109
		if (is_null($root)) {
0 ignored issues
show
introduced by
The condition is_null($root) is always false.
Loading history...
110
			throw new \InvalidArgumentException('Root can\'t be null');
111
		}
112
		if (!Filesystem::isValidPath($root)) {
113
			throw new \Exception();
114
		}
115
116
		$this->fakeRoot = $root;
117
		$this->lockingProvider = \OC::$server->getLockingProvider();
118
		$this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
119
		$this->userManager = \OC::$server->getUserManager();
120
		$this->logger = \OC::$server->getLogger();
121
	}
122
123
	public function getAbsolutePath($path = '/') {
124
		if ($path === null) {
125
			return null;
126
		}
127
		$this->assertPathLength($path);
128
		if ($path === '') {
129
			$path = '/';
130
		}
131
		if ($path[0] !== '/') {
132
			$path = '/' . $path;
133
		}
134
		return $this->fakeRoot . $path;
135
	}
136
137
	/**
138
	 * change the root to a fake root
139
	 *
140
	 * @param string $fakeRoot
141
	 * @return boolean|null
142
	 */
143
	public function chroot($fakeRoot) {
144
		if (!$fakeRoot == '') {
145
			if ($fakeRoot[0] !== '/') {
146
				$fakeRoot = '/' . $fakeRoot;
147
			}
148
		}
149
		$this->fakeRoot = $fakeRoot;
150
	}
151
152
	/**
153
	 * get the fake root
154
	 *
155
	 * @return string
156
	 */
157
	public function getRoot() {
158
		return $this->fakeRoot;
159
	}
160
161
	/**
162
	 * get path relative to the root of the view
163
	 *
164
	 * @param string $path
165
	 * @return string
166
	 */
167
	public function getRelativePath($path) {
168
		$this->assertPathLength($path);
169
		if ($this->fakeRoot == '') {
170
			return $path;
171
		}
172
173
		if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) {
174
			return '/';
175
		}
176
177
		// missing slashes can cause wrong matches!
178
		$root = rtrim($this->fakeRoot, '/') . '/';
179
180
		if (strpos($path, $root) !== 0) {
181
			return null;
182
		} else {
183
			$path = substr($path, strlen($this->fakeRoot));
184
			if (strlen($path) === 0) {
185
				return '/';
186
			} else {
187
				return $path;
188
			}
189
		}
190
	}
191
192
	/**
193
	 * get the mountpoint of the storage object for a path
194
	 * ( note: because a storage is not always mounted inside the fakeroot, the
195
	 * returned mountpoint is relative to the absolute root of the filesystem
196
	 * and does not take the chroot into account )
197
	 *
198
	 * @param string $path
199
	 * @return string
200
	 */
201
	public function getMountPoint($path) {
202
		return Filesystem::getMountPoint($this->getAbsolutePath($path));
203
	}
204
205
	/**
206
	 * get the mountpoint of the storage object for a path
207
	 * ( note: because a storage is not always mounted inside the fakeroot, the
208
	 * returned mountpoint is relative to the absolute root of the filesystem
209
	 * and does not take the chroot into account )
210
	 *
211
	 * @param string $path
212
	 * @return \OCP\Files\Mount\IMountPoint
213
	 */
214
	public function getMount($path) {
215
		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
216
	}
217
218
	/**
219
	 * resolve a path to a storage and internal path
220
	 *
221
	 * @param string $path
222
	 * @return array an array consisting of the storage and the internal path
223
	 */
224
	public function resolvePath($path) {
225
		$a = $this->getAbsolutePath($path);
226
		$p = Filesystem::normalizePath($a);
227
		return Filesystem::resolvePath($p);
228
	}
229
230
	/**
231
	 * return the path to a local version of the file
232
	 * we need this because we can't know if a file is stored local or not from
233
	 * outside the filestorage and for some purposes a local file is needed
234
	 *
235
	 * @param string $path
236
	 * @return string
237
	 */
238
	public function getLocalFile($path) {
239
		$parent = substr($path, 0, strrpos($path, '/'));
240
		$path = $this->getAbsolutePath($path);
241
		list($storage, $internalPath) = Filesystem::resolvePath($path);
242
		if (Filesystem::isValidPath($parent) and $storage) {
243
			return $storage->getLocalFile($internalPath);
244
		} else {
245
			return null;
246
		}
247
	}
248
249
	/**
250
	 * @param string $path
251
	 * @return string
252
	 */
253
	public function getLocalFolder($path) {
254
		$parent = substr($path, 0, strrpos($path, '/'));
255
		$path = $this->getAbsolutePath($path);
256
		list($storage, $internalPath) = Filesystem::resolvePath($path);
257
		if (Filesystem::isValidPath($parent) and $storage) {
258
			return $storage->getLocalFolder($internalPath);
259
		} else {
260
			return null;
261
		}
262
	}
263
264
	/**
265
	 * the following functions operate with arguments and return values identical
266
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
267
	 * for \OC\Files\Storage\Storage via basicOperation().
268
	 */
269
	public function mkdir($path) {
270
		return $this->basicOperation('mkdir', $path, array('create', 'write'));
271
	}
272
273
	/**
274
	 * remove mount point
275
	 *
276
	 * @param \OC\Files\Mount\MoveableMount $mount
277
	 * @param string $path relative to data/
278
	 * @return boolean
279
	 */
280
	protected function removeMount($mount, $path) {
281
		if ($mount instanceof MoveableMount) {
0 ignored issues
show
introduced by
$mount is always a sub-type of OC\Files\Mount\MoveableMount.
Loading history...
282
			// cut of /user/files to get the relative path to data/user/files
283
			$pathParts = explode('/', $path, 4);
284
			$relPath = '/' . $pathParts[3];
285
			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
286
			\OC_Hook::emit(
287
				Filesystem::CLASSNAME, "umount",
288
				array(Filesystem::signal_param_path => $relPath)
289
			);
290
			$this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
291
			$result = $mount->removeMount();
292
			$this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
293
			if ($result) {
294
				\OC_Hook::emit(
295
					Filesystem::CLASSNAME, "post_umount",
296
					array(Filesystem::signal_param_path => $relPath)
297
				);
298
			}
299
			$this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
300
			return $result;
301
		} else {
302
			// do not allow deleting the storage's root / the mount point
303
			// because for some storages it might delete the whole contents
304
			// but isn't supposed to work that way
305
			return false;
306
		}
307
	}
308
309
	public function disableCacheUpdate() {
310
		$this->updaterEnabled = false;
311
	}
312
313
	public function enableCacheUpdate() {
314
		$this->updaterEnabled = true;
315
	}
316
317
	protected function writeUpdate(Storage $storage, $internalPath, $time = null) {
318
		if ($this->updaterEnabled) {
319
			if (is_null($time)) {
320
				$time = time();
321
			}
322
			$storage->getUpdater()->update($internalPath, $time);
323
		}
324
	}
325
326
	protected function removeUpdate(Storage $storage, $internalPath) {
327
		if ($this->updaterEnabled) {
328
			$storage->getUpdater()->remove($internalPath);
329
		}
330
	}
331
332
	protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) {
333
		if ($this->updaterEnabled) {
334
			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
335
		}
336
	}
337
338
	/**
339
	 * @param string $path
340
	 * @return bool|mixed
341
	 */
342
	public function rmdir($path) {
343
		$absolutePath = $this->getAbsolutePath($path);
344
		$mount = Filesystem::getMountManager()->find($absolutePath);
345
		if ($mount->getInternalPath($absolutePath) === '') {
346
			return $this->removeMount($mount, $absolutePath);
347
		}
348
		if ($this->is_dir($path)) {
349
			$result = $this->basicOperation('rmdir', $path, array('delete'));
350
		} else {
351
			$result = false;
352
		}
353
354
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
355
			$storage = $mount->getStorage();
356
			$internalPath = $mount->getInternalPath($absolutePath);
357
			$storage->getUpdater()->remove($internalPath);
358
		}
359
		return $result;
360
	}
361
362
	/**
363
	 * @param string $path
364
	 * @return resource
365
	 */
366
	public function opendir($path) {
367
		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...
368
	}
369
370
	/**
371
	 * @param string $path
372
	 * @return bool|mixed
373
	 */
374
	public function is_dir($path) {
375
		if ($path == '/') {
376
			return true;
377
		}
378
		return $this->basicOperation('is_dir', $path);
379
	}
380
381
	/**
382
	 * @param string $path
383
	 * @return bool|mixed
384
	 */
385
	public function is_file($path) {
386
		if ($path == '/') {
387
			return false;
388
		}
389
		return $this->basicOperation('is_file', $path);
390
	}
391
392
	/**
393
	 * @param string $path
394
	 * @return mixed
395
	 */
396
	public function stat($path) {
397
		return $this->basicOperation('stat', $path);
398
	}
399
400
	/**
401
	 * @param string $path
402
	 * @return mixed
403
	 */
404
	public function filetype($path) {
405
		return $this->basicOperation('filetype', $path);
406
	}
407
408
	/**
409
	 * @param string $path
410
	 * @return mixed
411
	 */
412
	public function filesize($path) {
413
		return $this->basicOperation('filesize', $path);
414
	}
415
416
	/**
417
	 * @param string $path
418
	 * @return bool|mixed
419
	 * @throws \OCP\Files\InvalidPathException
420
	 */
421
	public function readfile($path) {
422
		$this->assertPathLength($path);
423
		@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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
1781
			'folder',
1782
			$fileId,
1783
			\OCP\Share::FORMAT_NONE,
1784
			null,
1785
			true
1786
		);
1787
1788
		if (count($shares) > 0) {
1789
			\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

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

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