Completed
Push — stable12 ( a63043...ca2f2c )
by Morris
15:51
created

View::getPath()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 6
nop 1
dl 0
loc 28
rs 8.8497
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 cmeh <[email protected]>
9
 * @author Florin Peter <[email protected]>
10
 * @author Jesús Macias <[email protected]>
11
 * @author Joas Schilling <[email protected]>
12
 * @author Jörn Friedrich Dreyer <[email protected]>
13
 * @author karakayasemi <[email protected]>
14
 * @author Klaas Freitag <[email protected]>
15
 * @author Lukas Reschke <[email protected]>
16
 * @author Luke Policinski <[email protected]>
17
 * @author Martin Mattel <[email protected]>
18
 * @author Michael Gapczynski <[email protected]>
19
 * @author Morris Jobke <[email protected]>
20
 * @author Petr Svoboda <[email protected]>
21
 * @author Piotr Filiciak <[email protected]>
22
 * @author Robin Appelman <[email protected]>
23
 * @author Robin McCorkell <[email protected]>
24
 * @author Roeland Jago Douma <[email protected]>
25
 * @author Sam Tuke <[email protected]>
26
 * @author Stefan Weil <[email protected]>
27
 * @author Thomas Müller <[email protected]>
28
 * @author Thomas Tanghus <[email protected]>
29
 * @author Vincent Petry <[email protected]>
30
 *
31
 * @license AGPL-3.0
32
 *
33
 * This code is free software: you can redistribute it and/or modify
34
 * it under the terms of the GNU Affero General Public License, version 3,
35
 * as published by the Free Software Foundation.
36
 *
37
 * This program is distributed in the hope that it will be useful,
38
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
39
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40
 * GNU Affero General Public License for more details.
41
 *
42
 * You should have received a copy of the GNU Affero General Public License, version 3,
43
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
44
 *
45
 */
46
47
48
namespace OC\Files;
49
50
use Icewind\Streams\CallbackWrapper;
51
use OC\Files\Mount\MoveableMount;
52
use OC\Files\Storage\Storage;
53
use OC\User\User;
54
use OCP\Constants;
55
use OCP\Files\Cache\ICacheEntry;
56
use OCP\Files\EmptyFileNameException;
57
use OCP\Files\FileNameTooLongException;
58
use OCP\Files\InvalidCharacterInPathException;
59
use OCP\Files\InvalidDirectoryException;
60
use OCP\Files\InvalidPathException;
61
use OCP\Files\Mount\IMountPoint;
62
use OCP\Files\NotFoundException;
63
use OCP\Files\ReservedWordException;
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)) {
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 View Code Duplication
		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 View Code Duplication
	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 View Code Duplication
	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) {
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);
0 ignored issues
show
Documentation introduced by
$mount is of type object<OC\Files\Mount\MountPoint>|null, but the function expects a object<OC\Files\Mount\MoveableMount>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
346
		}
347
		if ($this->is_dir($path)) {
348
			$result = $this->basicOperation('rmdir', $path, array('delete'));
349
		} else {
350
			$result = false;
351
		}
352
353 View Code Duplication
		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'));
367
	}
368
369
	/**
370
	 * @param $handle
371
	 * @return mixed
372
	 */
373
	public function readdir($handle) {
374
		$fsLocal = new Storage\Local(array('datadir' => '/'));
375
		return $fsLocal->readdir($handle);
376
	}
377
378
	/**
379
	 * @param string $path
380
	 * @return bool|mixed
381
	 */
382
	public function is_dir($path) {
383
		if ($path == '/') {
384
			return true;
385
		}
386
		return $this->basicOperation('is_dir', $path);
387
	}
388
389
	/**
390
	 * @param string $path
391
	 * @return bool|mixed
392
	 */
393
	public function is_file($path) {
394
		if ($path == '/') {
395
			return false;
396
		}
397
		return $this->basicOperation('is_file', $path);
398
	}
399
400
	/**
401
	 * @param string $path
402
	 * @return mixed
403
	 */
404
	public function stat($path) {
405
		return $this->basicOperation('stat', $path);
406
	}
407
408
	/**
409
	 * @param string $path
410
	 * @return mixed
411
	 */
412
	public function filetype($path) {
413
		return $this->basicOperation('filetype', $path);
414
	}
415
416
	/**
417
	 * @param string $path
418
	 * @return mixed
419
	 */
420
	public function filesize($path) {
421
		return $this->basicOperation('filesize', $path);
422
	}
423
424
	/**
425
	 * @param string $path
426
	 * @return bool|mixed
427
	 * @throws \OCP\Files\InvalidPathException
428
	 */
429
	public function readfile($path) {
430
		$this->assertPathLength($path);
431
		@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
432
		$handle = $this->fopen($path, 'rb');
433
		if ($handle) {
434
			$chunkSize = 8192; // 8 kB chunks
435
			while (!feof($handle)) {
436
				echo fread($handle, $chunkSize);
437
				flush();
438
			}
439
			fclose($handle);
440
			$size = $this->filesize($path);
441
			return $size;
442
		}
443
		return false;
444
	}
445
446
	/**
447
	 * @param string $path
448
	 * @param int $from
449
	 * @param int $to
450
	 * @return bool|mixed
451
	 * @throws \OCP\Files\InvalidPathException
452
	 * @throws \OCP\Files\UnseekableException
453
	 */
454
	public function readfilePart($path, $from, $to) {
455
		$this->assertPathLength($path);
456
		@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
717
		}
718
		if ($this->is_dir($path)) {
719
			$result = $this->basicOperation('rmdir', $path, ['delete']);
720
		} else {
721
			$result = $this->basicOperation('unlink', $path, ['delete']);
722
		}
723 View Code Duplication
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
724
			$storage = $mount->getStorage();
725
			$internalPath = $mount->getInternalPath($absolutePath);
726
			$storage->getUpdater()->remove($internalPath);
727
			return true;
728
		} else {
729
			return $result;
730
		}
731
	}
732
733
	/**
734
	 * @param string $directory
735
	 * @return bool|mixed
736
	 */
737
	public function deleteAll($directory) {
738
		return $this->rmdir($directory);
739
	}
740
741
	/**
742
	 * Rename/move a file or folder from the source path to target path.
743
	 *
744
	 * @param string $path1 source path
745
	 * @param string $path2 target path
746
	 *
747
	 * @return bool|mixed
748
	 */
749
	public function rename($path1, $path2) {
750
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
751
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
752
		$result = false;
753
		if (
754
			Filesystem::isValidPath($path2)
755
			and Filesystem::isValidPath($path1)
756
			and !Filesystem::isFileBlacklisted($path2)
757
		) {
758
			$path1 = $this->getRelativePath($absolutePath1);
759
			$path2 = $this->getRelativePath($absolutePath2);
760
			$exists = $this->file_exists($path2);
761
762
			if ($path1 == null or $path2 == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $path1 of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
Bug introduced by
It seems like you are loosely comparing $path2 of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
763
				return false;
764
			}
765
766
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
767
			try {
768
				$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
769
770
				$run = true;
771
				if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
772
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
773
					$this->emit_file_hooks_pre($exists, $path2, $run);
774
				} elseif ($this->shouldEmitHooks($path1)) {
775
					\OC_Hook::emit(
776
						Filesystem::CLASSNAME, Filesystem::signal_rename,
777
						array(
778
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
779
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
780
							Filesystem::signal_param_run => &$run
781
						)
782
					);
783
				}
784
				if ($run) {
785
					$this->verifyPath(dirname($path2), basename($path2));
786
787
					$manager = Filesystem::getMountManager();
788
					$mount1 = $this->getMount($path1);
789
					$mount2 = $this->getMount($path2);
790
					$storage1 = $mount1->getStorage();
791
					$storage2 = $mount2->getStorage();
792
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
793
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
794
795
					$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
796
					try {
797
						$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
798
799
						if ($internalPath1 === '') {
800
							if ($mount1 instanceof MoveableMount) {
801
								if ($this->isTargetAllowed($absolutePath2)) {
802
									/**
803
									 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
804
									 */
805
									$sourceMountPoint = $mount1->getMountPoint();
806
									$result = $mount1->moveMount($absolutePath2);
807
									$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
808
								} else {
809
									$result = false;
810
								}
811
							} else {
812
								$result = false;
813
							}
814
							// moving a file/folder within the same mount point
815
						} elseif ($storage1 === $storage2) {
816
							if ($storage1) {
817
								$result = $storage1->rename($internalPath1, $internalPath2);
818
							} else {
819
								$result = false;
820
							}
821
							// moving a file/folder between storages (from $storage1 to $storage2)
822
						} else {
823
							$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
824
						}
825
826
						if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
827
							// if it was a rename from a part file to a regular file it was a write and not a rename operation
828
							$this->writeUpdate($storage2, $internalPath2);
829
						} else if ($result) {
830
							if ($internalPath1 !== '') { // don't do a cache update for moved mounts
831
								$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
832
							}
833
						}
834
					} catch(\Exception $e) {
835
						throw $e;
836
					} finally {
837
						$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
838
						$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
839
					}
840
841
					if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
842
						if ($this->shouldEmitHooks()) {
843
							$this->emit_file_hooks_post($exists, $path2);
844
						}
845
					} elseif ($result) {
846
						if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
847
							\OC_Hook::emit(
848
								Filesystem::CLASSNAME,
849
								Filesystem::signal_post_rename,
850
								array(
851
									Filesystem::signal_param_oldpath => $this->getHookPath($path1),
852
									Filesystem::signal_param_newpath => $this->getHookPath($path2)
853
								)
854
							);
855
						}
856
					}
857
				}
858
			} catch(\Exception $e) {
859
				throw $e;
860
			} finally {
861
				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
862
				$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
863
			}
864
		}
865
		return $result;
866
	}
867
868
	/**
869
	 * Copy a file/folder from the source path to target path
870
	 *
871
	 * @param string $path1 source path
872
	 * @param string $path2 target path
873
	 * @param bool $preserveMtime whether to preserve mtime on the copy
874
	 *
875
	 * @return bool|mixed
876
	 */
877
	public function copy($path1, $path2, $preserveMtime = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $preserveMtime is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
878
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
879
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
880
		$result = false;
881
		if (
882
			Filesystem::isValidPath($path2)
883
			and Filesystem::isValidPath($path1)
884
			and !Filesystem::isFileBlacklisted($path2)
885
		) {
886
			$path1 = $this->getRelativePath($absolutePath1);
887
			$path2 = $this->getRelativePath($absolutePath2);
888
889
			if ($path1 == null or $path2 == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $path1 of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
Bug introduced by
It seems like you are loosely comparing $path2 of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
890
				return false;
891
			}
892
			$run = true;
893
894
			$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
895
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
896
			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
897
			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
898
899
			try {
900
901
				$exists = $this->file_exists($path2);
902 View Code Duplication
				if ($this->shouldEmitHooks()) {
903
					\OC_Hook::emit(
904
						Filesystem::CLASSNAME,
905
						Filesystem::signal_copy,
906
						array(
907
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
908
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
909
							Filesystem::signal_param_run => &$run
910
						)
911
					);
912
					$this->emit_file_hooks_pre($exists, $path2, $run);
913
				}
914
				if ($run) {
915
					$mount1 = $this->getMount($path1);
916
					$mount2 = $this->getMount($path2);
917
					$storage1 = $mount1->getStorage();
918
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
919
					$storage2 = $mount2->getStorage();
920
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
921
922
					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
923
					$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
924
925
					if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
926
						if ($storage1) {
927
							$result = $storage1->copy($internalPath1, $internalPath2);
928
						} else {
929
							$result = false;
930
						}
931
					} else {
932
						$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
933
					}
934
935
					$this->writeUpdate($storage2, $internalPath2);
936
937
					$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
938
					$lockTypePath2 = ILockingProvider::LOCK_SHARED;
939
940 View Code Duplication
					if ($this->shouldEmitHooks() && $result !== false) {
941
						\OC_Hook::emit(
942
							Filesystem::CLASSNAME,
943
							Filesystem::signal_post_copy,
944
							array(
945
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
946
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
947
							)
948
						);
949
						$this->emit_file_hooks_post($exists, $path2);
950
					}
951
952
				}
953
			} catch (\Exception $e) {
954
				$this->unlockFile($path2, $lockTypePath2);
955
				$this->unlockFile($path1, $lockTypePath1);
956
				throw $e;
957
			}
958
959
			$this->unlockFile($path2, $lockTypePath2);
960
			$this->unlockFile($path1, $lockTypePath1);
961
962
		}
963
		return $result;
964
	}
965
966
	/**
967
	 * @param string $path
968
	 * @param string $mode 'r' or 'w'
969
	 * @return resource
970
	 */
971
	public function fopen($path, $mode) {
972
		$mode = str_replace('b', '', $mode); // the binary flag is a windows only feature which we do not support
973
		$hooks = array();
974
		switch ($mode) {
975
			case 'r':
976
				$hooks[] = 'read';
977
				break;
978
			case 'r+':
979
			case 'w+':
980
			case 'x+':
981
			case 'a+':
982
				$hooks[] = 'read';
983
				$hooks[] = 'write';
984
				break;
985
			case 'w':
986
			case 'x':
987
			case 'a':
988
				$hooks[] = 'write';
989
				break;
990
			default:
991
				\OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, \OCP\Util::ERROR);
992
		}
993
994
		if ($mode !== 'r' && $mode !== 'w') {
995
			\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');
996
		}
997
998
		return $this->basicOperation('fopen', $path, $hooks, $mode);
999
	}
1000
1001
	/**
1002
	 * @param string $path
1003
	 * @return bool|string
1004
	 * @throws \OCP\Files\InvalidPathException
1005
	 */
1006
	public function toTmpFile($path) {
1007
		$this->assertPathLength($path);
1008
		if (Filesystem::isValidPath($path)) {
1009
			$source = $this->fopen($path, 'r');
1010
			if ($source) {
1011
				$extension = pathinfo($path, PATHINFO_EXTENSION);
1012
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
1013
				file_put_contents($tmpFile, $source);
1014
				return $tmpFile;
1015
			} else {
1016
				return false;
1017
			}
1018
		} else {
1019
			return false;
1020
		}
1021
	}
1022
1023
	/**
1024
	 * @param string $tmpFile
1025
	 * @param string $path
1026
	 * @return bool|mixed
1027
	 * @throws \OCP\Files\InvalidPathException
1028
	 */
1029
	public function fromTmpFile($tmpFile, $path) {
1030
		$this->assertPathLength($path);
1031
		if (Filesystem::isValidPath($path)) {
1032
1033
			// Get directory that the file is going into
1034
			$filePath = dirname($path);
1035
1036
			// Create the directories if any
1037
			if (!$this->file_exists($filePath)) {
1038
				$result = $this->createParentDirectories($filePath);
1039
				if ($result === false) {
1040
					return false;
1041
				}
1042
			}
1043
1044
			$source = fopen($tmpFile, 'r');
1045
			if ($source) {
1046
				$result = $this->file_put_contents($path, $source);
1047
				// $this->file_put_contents() might have already closed
1048
				// the resource, so we check it, before trying to close it
1049
				// to avoid messages in the error log.
1050
				if (is_resource($source)) {
1051
					fclose($source);
1052
				}
1053
				unlink($tmpFile);
1054
				return $result;
1055
			} else {
1056
				return false;
1057
			}
1058
		} else {
1059
			return false;
1060
		}
1061
	}
1062
1063
1064
	/**
1065
	 * @param string $path
1066
	 * @return mixed
1067
	 * @throws \OCP\Files\InvalidPathException
1068
	 */
1069
	public function getMimeType($path) {
1070
		$this->assertPathLength($path);
1071
		return $this->basicOperation('getMimeType', $path);
1072
	}
1073
1074
	/**
1075
	 * @param string $type
1076
	 * @param string $path
1077
	 * @param bool $raw
1078
	 * @return bool|null|string
1079
	 */
1080
	public function hash($type, $path, $raw = false) {
1081
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1082
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1083
		if (Filesystem::isValidPath($path)) {
1084
			$path = $this->getRelativePath($absolutePath);
1085
			if ($path == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $path of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1086
				return false;
1087
			}
1088
			if ($this->shouldEmitHooks($path)) {
1089
				\OC_Hook::emit(
1090
					Filesystem::CLASSNAME,
1091
					Filesystem::signal_read,
1092
					array(Filesystem::signal_param_path => $this->getHookPath($path))
1093
				);
1094
			}
1095
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1096
			if ($storage) {
1097
				$result = $storage->hash($type, $internalPath, $raw);
1098
				return $result;
1099
			}
1100
		}
1101
		return null;
1102
	}
1103
1104
	/**
1105
	 * @param string $path
1106
	 * @return mixed
1107
	 * @throws \OCP\Files\InvalidPathException
1108
	 */
1109
	public function free_space($path = '/') {
1110
		$this->assertPathLength($path);
1111
		$result = $this->basicOperation('free_space', $path);
1112
		if ($result === null) {
1113
			throw new InvalidPathException();
1114
		}
1115
		return $result;
1116
	}
1117
1118
	/**
1119
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1120
	 *
1121
	 * @param string $operation
1122
	 * @param string $path
1123
	 * @param array $hooks (optional)
1124
	 * @param mixed $extraParam (optional)
1125
	 * @return mixed
1126
	 * @throws \Exception
1127
	 *
1128
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1129
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1130
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1131
	 */
1132
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1133
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1134
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1135
		if (Filesystem::isValidPath($path)
1136
			and !Filesystem::isFileBlacklisted($path)
1137
		) {
1138
			$path = $this->getRelativePath($absolutePath);
1139
			if ($path == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $path of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1140
				return false;
1141
			}
1142
1143
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1144
				// always a shared lock during pre-hooks so the hook can read the file
1145
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1146
			}
1147
1148
			$run = $this->runHooks($hooks, $path);
1149
			/** @var \OC\Files\Storage\Storage $storage */
1150
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1151
			if ($run and $storage) {
1152
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1153
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1154
				}
1155
				try {
1156
					if (!is_null($extraParam)) {
1157
						$result = $storage->$operation($internalPath, $extraParam);
1158
					} else {
1159
						$result = $storage->$operation($internalPath);
1160
					}
1161
				} catch (\Exception $e) {
1162 View Code Duplication
					if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1163
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1164
					} else if (in_array('read', $hooks)) {
1165
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1166
					}
1167
					throw $e;
1168
				}
1169
1170
				if ($result && in_array('delete', $hooks) and $result) {
1171
					$this->removeUpdate($storage, $internalPath);
1172
				}
1173
				if ($result && in_array('write', $hooks) and $operation !== 'fopen') {
1174
					$this->writeUpdate($storage, $internalPath);
1175
				}
1176
				if ($result && in_array('touch', $hooks)) {
1177
					$this->writeUpdate($storage, $internalPath, $extraParam);
1178
				}
1179
1180
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1181
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1182
				}
1183
1184
				$unlockLater = false;
1185
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1186
					$unlockLater = true;
1187
					// make sure our unlocking callback will still be called if connection is aborted
1188
					ignore_user_abort(true);
1189
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1190 View Code Duplication
						if (in_array('write', $hooks)) {
1191
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1192
						} else if (in_array('read', $hooks)) {
1193
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1194
						}
1195
					});
1196
				}
1197
1198
				if ($this->shouldEmitHooks($path) && $result !== false) {
1199
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1200
						$this->runHooks($hooks, $path, true);
1201
					}
1202
				}
1203
1204 View Code Duplication
				if (!$unlockLater
1205
					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1206
				) {
1207
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1208
				}
1209
				return $result;
1210
			} else {
1211
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1212
			}
1213
		}
1214
		return null;
1215
	}
1216
1217
	/**
1218
	 * get the path relative to the default root for hook usage
1219
	 *
1220
	 * @param string $path
1221
	 * @return string
1222
	 */
1223
	private function getHookPath($path) {
1224
		if (!Filesystem::getView()) {
1225
			return $path;
1226
		}
1227
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1228
	}
1229
1230
	private function shouldEmitHooks($path = '') {
1231
		if ($path && Cache\Scanner::isPartialFile($path)) {
1232
			return false;
1233
		}
1234
		if (!Filesystem::$loaded) {
1235
			return false;
1236
		}
1237
		$defaultRoot = Filesystem::getRoot();
1238
		if ($defaultRoot === null) {
1239
			return false;
1240
		}
1241
		if ($this->fakeRoot === $defaultRoot) {
1242
			return true;
1243
		}
1244
		$fullPath = $this->getAbsolutePath($path);
1245
1246
		if ($fullPath === $defaultRoot) {
1247
			return true;
1248
		}
1249
1250
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1251
	}
1252
1253
	/**
1254
	 * @param string[] $hooks
1255
	 * @param string $path
1256
	 * @param bool $post
1257
	 * @return bool
1258
	 */
1259
	private function runHooks($hooks, $path, $post = false) {
1260
		$relativePath = $path;
1261
		$path = $this->getHookPath($path);
1262
		$prefix = ($post) ? 'post_' : '';
1263
		$run = true;
1264
		if ($this->shouldEmitHooks($relativePath)) {
1265
			foreach ($hooks as $hook) {
1266
				if ($hook != 'read') {
1267
					\OC_Hook::emit(
1268
						Filesystem::CLASSNAME,
1269
						$prefix . $hook,
1270
						array(
1271
							Filesystem::signal_param_run => &$run,
1272
							Filesystem::signal_param_path => $path
1273
						)
1274
					);
1275
				} elseif (!$post) {
1276
					\OC_Hook::emit(
1277
						Filesystem::CLASSNAME,
1278
						$prefix . $hook,
1279
						array(
1280
							Filesystem::signal_param_path => $path
1281
						)
1282
					);
1283
				}
1284
			}
1285
		}
1286
		return $run;
1287
	}
1288
1289
	/**
1290
	 * check if a file or folder has been updated since $time
1291
	 *
1292
	 * @param string $path
1293
	 * @param int $time
1294
	 * @return bool
1295
	 */
1296
	public function hasUpdated($path, $time) {
1297
		return $this->basicOperation('hasUpdated', $path, array(), $time);
1298
	}
1299
1300
	/**
1301
	 * @param string $ownerId
1302
	 * @return \OC\User\User
1303
	 */
1304
	private function getUserObjectForOwner($ownerId) {
1305
		$owner = $this->userManager->get($ownerId);
1306
		if ($owner instanceof IUser) {
1307
			return $owner;
1308
		} else {
1309
			return new User($ownerId, null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<OCP\UserInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1310
		}
1311
	}
1312
1313
	/**
1314
	 * Get file info from cache
1315
	 *
1316
	 * If the file is not in cached it will be scanned
1317
	 * If the file has changed on storage the cache will be updated
1318
	 *
1319
	 * @param \OC\Files\Storage\Storage $storage
1320
	 * @param string $internalPath
1321
	 * @param string $relativePath
1322
	 * @return array|bool
1323
	 */
1324
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1325
		$cache = $storage->getCache($internalPath);
1326
		$data = $cache->get($internalPath);
1327
		$watcher = $storage->getWatcher($internalPath);
1328
1329
		try {
1330
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1331
			if (!$data || $data['size'] === -1) {
1332
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1333
				if (!$storage->file_exists($internalPath)) {
1334
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1335
					return false;
1336
				}
1337
				$scanner = $storage->getScanner($internalPath);
1338
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1339
				$data = $cache->get($internalPath);
1340
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1341
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1342
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1343
				$watcher->update($internalPath, $data);
1344
				$storage->getPropagator()->propagateChange($internalPath, time());
1345
				$data = $cache->get($internalPath);
1346
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1347
			}
1348
		} catch (LockedException $e) {
1349
			// if the file is locked we just use the old cache info
1350
		}
1351
1352
		return $data;
1353
	}
1354
1355
	/**
1356
	 * get the filesystem info
1357
	 *
1358
	 * @param string $path
1359
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1360
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1361
	 * defaults to true
1362
	 * @return \OC\Files\FileInfo|false False if file does not exist
1363
	 */
1364
	public function getFileInfo($path, $includeMountPoints = true) {
1365
		$this->assertPathLength($path);
1366
		if (!Filesystem::isValidPath($path)) {
1367
			return false;
1368
		}
1369
		if (Cache\Scanner::isPartialFile($path)) {
1370
			return $this->getPartFileInfo($path);
1371
		}
1372
		$relativePath = $path;
1373
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1374
1375
		$mount = Filesystem::getMountManager()->find($path);
1376
		$storage = $mount->getStorage();
1377
		$internalPath = $mount->getInternalPath($path);
1378
		if ($storage) {
1379
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1380
1381
			if (!$data instanceof ICacheEntry) {
1382
				return false;
1383
			}
1384
1385
			if ($mount instanceof MoveableMount && $internalPath === '') {
1386
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1387
			}
1388
1389
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1390
			$info = new FileInfo($path, $storage, $internalPath, $data, $mount, $owner);
0 ignored issues
show
Bug introduced by
It seems like $mount defined by \OC\Files\Filesystem::ge...tManager()->find($path) on line 1375 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1391
1392
			if ($data and isset($data['fileid'])) {
1393
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1394
					//add the sizes of other mount points to the folder
1395
					$extOnly = ($includeMountPoints === 'ext');
1396
					$mounts = Filesystem::getMountManager()->findIn($path);
1397
					$info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1398
						$subStorage = $mount->getStorage();
1399
						return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage);
1400
					}));
1401
				}
1402
			}
1403
1404
			return $info;
1405
		}
1406
1407
		return false;
1408
	}
1409
1410
	/**
1411
	 * get the content of a directory
1412
	 *
1413
	 * @param string $directory path under datadirectory
1414
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1415
	 * @return FileInfo[]
1416
	 */
1417
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1418
		$this->assertPathLength($directory);
1419
		if (!Filesystem::isValidPath($directory)) {
1420
			return [];
1421
		}
1422
		$path = $this->getAbsolutePath($directory);
1423
		$path = Filesystem::normalizePath($path);
1424
		$mount = $this->getMount($directory);
1425
		$storage = $mount->getStorage();
1426
		$internalPath = $mount->getInternalPath($path);
1427
		if ($storage) {
1428
			$cache = $storage->getCache($internalPath);
1429
			$user = \OC_User::getUser();
1430
1431
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1432
1433
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1434
				return [];
1435
			}
1436
1437
			$folderId = $data['fileid'];
1438
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1439
1440
			$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
1441
1442
			$fileNames = array_map(function(ICacheEntry $content) {
1443
				return $content->getName();
1444
			}, $contents);
1445
			/**
1446
			 * @var \OC\Files\FileInfo[] $fileInfos
1447
			 */
1448
			$fileInfos = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1449
				if ($sharingDisabled) {
1450
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1451
				}
1452
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1453
				return new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount, $owner);
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($directory) on line 1424 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

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

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1519
						}
1520
					}
1521
				}
1522
			}
1523
1524
			if ($mimetype_filter) {
1525
				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1526
					if (strpos($mimetype_filter, '/')) {
1527
						return $file->getMimetype() === $mimetype_filter;
1528
					} else {
1529
						return $file->getMimePart() === $mimetype_filter;
1530
					}
1531
				});
1532
			}
1533
1534
			return array_values($files);
1535
		} else {
1536
			return [];
1537
		}
1538
	}
1539
1540
	/**
1541
	 * change file metadata
1542
	 *
1543
	 * @param string $path
1544
	 * @param array|\OCP\Files\FileInfo $data
1545
	 * @return int
1546
	 *
1547
	 * returns the fileid of the updated file
1548
	 */
1549
	public function putFileInfo($path, $data) {
1550
		$this->assertPathLength($path);
1551
		if ($data instanceof FileInfo) {
1552
			$data = $data->getData();
1553
		}
1554
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1555
		/**
1556
		 * @var \OC\Files\Storage\Storage $storage
1557
		 * @var string $internalPath
1558
		 */
1559
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1560
		if ($storage) {
1561
			$cache = $storage->getCache($path);
1562
1563
			if (!$cache->inCache($internalPath)) {
1564
				$scanner = $storage->getScanner($internalPath);
1565
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1566
			}
1567
1568
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1549 can also be of type object<OCP\Files\FileInfo>; however, OC\Files\Cache\Cache::put() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1569
		} else {
1570
			return -1;
1571
		}
1572
	}
1573
1574
	/**
1575
	 * search for files with the name matching $query
1576
	 *
1577
	 * @param string $query
1578
	 * @return FileInfo[]
1579
	 */
1580
	public function search($query) {
1581
		return $this->searchCommon('search', array('%' . $query . '%'));
1582
	}
1583
1584
	/**
1585
	 * search for files with the name matching $query
1586
	 *
1587
	 * @param string $query
1588
	 * @return FileInfo[]
1589
	 */
1590
	public function searchRaw($query) {
1591
		return $this->searchCommon('search', array($query));
1592
	}
1593
1594
	/**
1595
	 * search for files by mimetype
1596
	 *
1597
	 * @param string $mimetype
1598
	 * @return FileInfo[]
1599
	 */
1600
	public function searchByMime($mimetype) {
1601
		return $this->searchCommon('searchByMime', array($mimetype));
1602
	}
1603
1604
	/**
1605
	 * search for files by tag
1606
	 *
1607
	 * @param string|int $tag name or tag id
1608
	 * @param string $userId owner of the tags
1609
	 * @return FileInfo[]
1610
	 */
1611
	public function searchByTag($tag, $userId) {
1612
		return $this->searchCommon('searchByTag', array($tag, $userId));
1613
	}
1614
1615
	/**
1616
	 * @param string $method cache method
1617
	 * @param array $args
1618
	 * @return FileInfo[]
1619
	 */
1620
	private function searchCommon($method, $args) {
1621
		$files = array();
1622
		$rootLength = strlen($this->fakeRoot);
1623
1624
		$mount = $this->getMount('');
1625
		$mountPoint = $mount->getMountPoint();
1626
		$storage = $mount->getStorage();
1627
		if ($storage) {
1628
			$cache = $storage->getCache('');
1629
1630
			$results = call_user_func_array(array($cache, $method), $args);
1631
			foreach ($results as $result) {
1632
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1633
					$internalPath = $result['path'];
1634
					$path = $mountPoint . $result['path'];
1635
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1636
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1637
					$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount('') on line 1624 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1638
				}
1639
			}
1640
1641
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1642
			foreach ($mounts as $mount) {
1643
				$mountPoint = $mount->getMountPoint();
1644
				$storage = $mount->getStorage();
1645
				if ($storage) {
1646
					$cache = $storage->getCache('');
1647
1648
					$relativeMountPoint = substr($mountPoint, $rootLength);
1649
					$results = call_user_func_array(array($cache, $method), $args);
1650
					if ($results) {
1651
						foreach ($results as $result) {
1652
							$internalPath = $result['path'];
1653
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1654
							$path = rtrim($mountPoint . $internalPath, '/');
1655
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1656
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1657
						}
1658
					}
1659
				}
1660
			}
1661
		}
1662
		return $files;
1663
	}
1664
1665
	/**
1666
	 * Get the owner for a file or folder
1667
	 *
1668
	 * @param string $path
1669
	 * @return string the user id of the owner
1670
	 * @throws NotFoundException
1671
	 */
1672
	public function getOwner($path) {
1673
		$info = $this->getFileInfo($path);
1674
		if (!$info) {
1675
			throw new NotFoundException($path . ' not found while trying to get owner');
1676
		}
1677
		return $info->getOwner()->getUID();
1678
	}
1679
1680
	/**
1681
	 * get the ETag for a file or folder
1682
	 *
1683
	 * @param string $path
1684
	 * @return string
1685
	 */
1686
	public function getETag($path) {
1687
		/**
1688
		 * @var Storage\Storage $storage
1689
		 * @var string $internalPath
1690
		 */
1691
		list($storage, $internalPath) = $this->resolvePath($path);
1692
		if ($storage) {
1693
			return $storage->getETag($internalPath);
1694
		} else {
1695
			return null;
1696
		}
1697
	}
1698
1699
	/**
1700
	 * Get the path of a file by id, relative to the view
1701
	 *
1702
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1703
	 *
1704
	 * @param int $id
1705
	 * @throws NotFoundException
1706
	 * @return string
1707
	 */
1708
	public function getPath($id) {
1709
		$id = (int)$id;
1710
		$manager = Filesystem::getMountManager();
1711
		$mounts = $manager->findIn($this->fakeRoot);
1712
		$mounts[] = $manager->find($this->fakeRoot);
1713
		// reverse the array so we start with the storage this view is in
1714
		// which is the most likely to contain the file we're looking for
1715
		$mounts = array_reverse($mounts);
1716
		foreach ($mounts as $mount) {
1717
			/**
1718
			 * @var \OC\Files\Mount\MountPoint $mount
1719
			 */
1720
			if ($mount->getStorage()) {
1721
				$cache = $mount->getStorage()->getCache();
1722
				if (!$cache) {
1723
					throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1724
				}
1725
				$internalPath = $cache->getPathById($id);
1726
				if (is_string($internalPath)) {
1727
					$fullPath = $mount->getMountPoint() . $internalPath;
1728
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1729
						return $path;
1730
					}
1731
				}
1732
			}
1733
		}
1734
		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1735
	}
1736
1737
	/**
1738
	 * @param string $path
1739
	 * @throws InvalidPathException
1740
	 */
1741
	private function assertPathLength($path) {
1742
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1743
		// Check for the string length - performed using isset() instead of strlen()
1744
		// because isset() is about 5x-40x faster.
1745
		if (isset($path[$maxLen])) {
1746
			$pathLen = strlen($path);
1747
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1748
		}
1749
	}
1750
1751
	/**
1752
	 * check if it is allowed to move a mount point to a given target.
1753
	 * It is not allowed to move a mount point into a different mount point or
1754
	 * into an already shared folder
1755
	 *
1756
	 * @param string $target path
1757
	 * @return boolean
1758
	 */
1759
	private function isTargetAllowed($target) {
1760
1761
		list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target);
1762
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1763
			\OCP\Util::writeLog('files',
1764
				'It is not allowed to move one mount point into another one',
1765
				\OCP\Util::DEBUG);
1766
			return false;
1767
		}
1768
1769
		// note: cannot use the view because the target is already locked
1770
		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1771
		if ($fileId === -1) {
1772
			// target might not exist, need to check parent instead
1773
			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1774
		}
1775
1776
		// check if any of the parents were shared by the current owner (include collections)
1777
		$shares = \OCP\Share::getItemShared(
1778
			'folder',
1779
			$fileId,
1780
			\OCP\Share::FORMAT_NONE,
1781
			null,
1782
			true
1783
		);
1784
1785
		if (count($shares) > 0) {
1786
			\OCP\Util::writeLog('files',
1787
				'It is not allowed to move one mount point into a shared folder',
1788
				\OCP\Util::DEBUG);
1789
			return false;
1790
		}
1791
1792
		return true;
1793
	}
1794
1795
	/**
1796
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1797
	 *
1798
	 * @param string $path
1799
	 * @return \OCP\Files\FileInfo
1800
	 */
1801
	private function getPartFileInfo($path) {
1802
		$mount = $this->getMount($path);
1803
		$storage = $mount->getStorage();
1804
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1805
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1806
		return new FileInfo(
1807
			$this->getAbsolutePath($path),
1808
			$storage,
1809
			$internalPath,
1810
			[
1811
				'fileid' => null,
1812
				'mimetype' => $storage->getMimeType($internalPath),
1813
				'name' => basename($path),
1814
				'etag' => null,
1815
				'size' => $storage->filesize($internalPath),
1816
				'mtime' => $storage->filemtime($internalPath),
1817
				'encrypted' => false,
1818
				'permissions' => \OCP\Constants::PERMISSION_ALL
1819
			],
1820
			$mount,
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1802 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1821
			$owner
1822
		);
1823
	}
1824
1825
	/**
1826
	 * @param string $path
1827
	 * @param string $fileName
1828
	 * @throws InvalidPathException
1829
	 */
1830
	public function verifyPath($path, $fileName) {
1831
		try {
1832
			/** @type \OCP\Files\Storage $storage */
1833
			list($storage, $internalPath) = $this->resolvePath($path);
1834
			$storage->verifyPath($internalPath, $fileName);
1835
		} catch (ReservedWordException $ex) {
1836
			$l = \OC::$server->getL10N('lib');
1837
			throw new InvalidPathException($l->t('File name is a reserved word'));
1838
		} catch (InvalidCharacterInPathException $ex) {
1839
			$l = \OC::$server->getL10N('lib');
1840
			throw new InvalidPathException($l->t('File name contains at least one invalid character'));
1841
		} catch (FileNameTooLongException $ex) {
1842
			$l = \OC::$server->getL10N('lib');
1843
			throw new InvalidPathException($l->t('File name is too long'));
1844
		} catch (InvalidDirectoryException $ex) {
1845
			$l = \OC::$server->getL10N('lib');
1846
			throw new InvalidPathException($l->t('Dot files are not allowed'));
1847
		} catch (EmptyFileNameException $ex) {
1848
			$l = \OC::$server->getL10N('lib');
1849
			throw new InvalidPathException($l->t('Empty filename is not allowed'));
1850
		}
1851
	}
1852
1853
	/**
1854
	 * get all parent folders of $path
1855
	 *
1856
	 * @param string $path
1857
	 * @return string[]
1858
	 */
1859
	private function getParents($path) {
1860
		$path = trim($path, '/');
1861
		if (!$path) {
1862
			return [];
1863
		}
1864
1865
		$parts = explode('/', $path);
1866
1867
		// remove the single file
1868
		array_pop($parts);
1869
		$result = array('/');
1870
		$resultPath = '';
1871
		foreach ($parts as $part) {
1872
			if ($part) {
1873
				$resultPath .= '/' . $part;
1874
				$result[] = $resultPath;
1875
			}
1876
		}
1877
		return $result;
1878
	}
1879
1880
	/**
1881
	 * Returns the mount point for which to lock
1882
	 *
1883
	 * @param string $absolutePath absolute path
1884
	 * @param bool $useParentMount true to return parent mount instead of whatever
1885
	 * is mounted directly on the given path, false otherwise
1886
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1887
	 */
1888
	private function getMountForLock($absolutePath, $useParentMount = false) {
1889
		$results = [];
1890
		$mount = Filesystem::getMountManager()->find($absolutePath);
1891
		if (!$mount) {
1892
			return $results;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $results; (array) is incompatible with the return type documented by OC\Files\View::getMountForLock of type OC\Files\Mount\MountPoint|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1893
		}
1894
1895
		if ($useParentMount) {
1896
			// find out if something is mounted directly on the path
1897
			$internalPath = $mount->getInternalPath($absolutePath);
1898
			if ($internalPath === '') {
1899
				// resolve the parent mount instead
1900
				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1901
			}
1902
		}
1903
1904
		return $mount;
1905
	}
1906
1907
	/**
1908
	 * Lock the given path
1909
	 *
1910
	 * @param string $path the path of the file to lock, relative to the view
1911
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1912
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1913
	 *
1914
	 * @return bool False if the path is excluded from locking, true otherwise
1915
	 * @throws \OCP\Lock\LockedException if the path is already locked
1916
	 */
1917
	private function lockPath($path, $type, $lockMountPoint = false) {
1918
		$absolutePath = $this->getAbsolutePath($path);
1919
		$absolutePath = Filesystem::normalizePath($absolutePath);
1920
		if (!$this->shouldLockFile($absolutePath)) {
1921
			return false;
1922
		}
1923
1924
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1925
		if ($mount) {
1926
			try {
1927
				$storage = $mount->getStorage();
1928
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1929
					$storage->acquireLock(
1930
						$mount->getInternalPath($absolutePath),
1931
						$type,
1932
						$this->lockingProvider
1933
					);
1934
				}
1935
			} catch (\OCP\Lock\LockedException $e) {
1936
				// rethrow with the a human-readable path
1937
				throw new \OCP\Lock\LockedException(
1938
					$this->getPathRelativeToFiles($absolutePath),
1939
					$e
1940
				);
1941
			}
1942
		}
1943
1944
		return true;
1945
	}
1946
1947
	/**
1948
	 * Change the lock type
1949
	 *
1950
	 * @param string $path the path of the file to lock, relative to the view
1951
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1952
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1953
	 *
1954
	 * @return bool False if the path is excluded from locking, true otherwise
1955
	 * @throws \OCP\Lock\LockedException if the path is already locked
1956
	 */
1957
	public function changeLock($path, $type, $lockMountPoint = false) {
1958
		$path = Filesystem::normalizePath($path);
1959
		$absolutePath = $this->getAbsolutePath($path);
1960
		$absolutePath = Filesystem::normalizePath($absolutePath);
1961
		if (!$this->shouldLockFile($absolutePath)) {
1962
			return false;
1963
		}
1964
1965
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1966
		if ($mount) {
1967
			try {
1968
				$storage = $mount->getStorage();
1969
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1970
					$storage->changeLock(
1971
						$mount->getInternalPath($absolutePath),
1972
						$type,
1973
						$this->lockingProvider
1974
					);
1975
				}
1976
			} catch (\OCP\Lock\LockedException $e) {
1977
				try {
1978
					// rethrow with the a human-readable path
1979
					throw new \OCP\Lock\LockedException(
1980
						$this->getPathRelativeToFiles($absolutePath),
1981
						$e
1982
					);
1983
				} catch (\InvalidArgumentException $e) {
1984
					throw new \OCP\Lock\LockedException(
1985
						$absolutePath,
1986
						$e
1987
					);
1988
				}
1989
			}
1990
		}
1991
1992
		return true;
1993
	}
1994
1995
	/**
1996
	 * Unlock the given path
1997
	 *
1998
	 * @param string $path the path of the file to unlock, relative to the view
1999
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2000
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2001
	 *
2002
	 * @return bool False if the path is excluded from locking, true otherwise
2003
	 */
2004
	private function unlockPath($path, $type, $lockMountPoint = false) {
2005
		$absolutePath = $this->getAbsolutePath($path);
2006
		$absolutePath = Filesystem::normalizePath($absolutePath);
2007
		if (!$this->shouldLockFile($absolutePath)) {
2008
			return false;
2009
		}
2010
2011
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2012
		if ($mount) {
2013
			$storage = $mount->getStorage();
2014
			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2015
				$storage->releaseLock(
2016
					$mount->getInternalPath($absolutePath),
2017
					$type,
2018
					$this->lockingProvider
2019
				);
2020
			}
2021
		}
2022
2023
		return true;
2024
	}
2025
2026
	/**
2027
	 * Lock a path and all its parents up to the root of the view
2028
	 *
2029
	 * @param string $path the path of the file to lock relative to the view
2030
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2031
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2032
	 *
2033
	 * @return bool False if the path is excluded from locking, true otherwise
2034
	 */
2035 View Code Duplication
	public function lockFile($path, $type, $lockMountPoint = false) {
2036
		$absolutePath = $this->getAbsolutePath($path);
2037
		$absolutePath = Filesystem::normalizePath($absolutePath);
2038
		if (!$this->shouldLockFile($absolutePath)) {
2039
			return false;
2040
		}
2041
2042
		$this->lockPath($path, $type, $lockMountPoint);
2043
2044
		$parents = $this->getParents($path);
2045
		foreach ($parents as $parent) {
2046
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2047
		}
2048
2049
		return true;
2050
	}
2051
2052
	/**
2053
	 * Unlock a path and all its parents up to the root of the view
2054
	 *
2055
	 * @param string $path the path of the file to lock relative to the view
2056
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2057
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2058
	 *
2059
	 * @return bool False if the path is excluded from locking, true otherwise
2060
	 */
2061 View Code Duplication
	public function unlockFile($path, $type, $lockMountPoint = false) {
2062
		$absolutePath = $this->getAbsolutePath($path);
2063
		$absolutePath = Filesystem::normalizePath($absolutePath);
2064
		if (!$this->shouldLockFile($absolutePath)) {
2065
			return false;
2066
		}
2067
2068
		$this->unlockPath($path, $type, $lockMountPoint);
2069
2070
		$parents = $this->getParents($path);
2071
		foreach ($parents as $parent) {
2072
			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2073
		}
2074
2075
		return true;
2076
	}
2077
2078
	/**
2079
	 * Only lock files in data/user/files/
2080
	 *
2081
	 * @param string $path Absolute path to the file/folder we try to (un)lock
2082
	 * @return bool
2083
	 */
2084
	protected function shouldLockFile($path) {
2085
		$path = Filesystem::normalizePath($path);
2086
2087
		$pathSegments = explode('/', $path);
2088
		if (isset($pathSegments[2])) {
2089
			// E.g.: /username/files/path-to-file
2090
			return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
2091
		}
2092
2093
		return strpos($path, '/appdata_') !== 0;
2094
	}
2095
2096
	/**
2097
	 * Shortens the given absolute path to be relative to
2098
	 * "$user/files".
2099
	 *
2100
	 * @param string $absolutePath absolute path which is under "files"
2101
	 *
2102
	 * @return string path relative to "files" with trimmed slashes or null
2103
	 * if the path was NOT relative to files
2104
	 *
2105
	 * @throws \InvalidArgumentException if the given path was not under "files"
2106
	 * @since 8.1.0
2107
	 */
2108
	public function getPathRelativeToFiles($absolutePath) {
2109
		$path = Filesystem::normalizePath($absolutePath);
2110
		$parts = explode('/', trim($path, '/'), 3);
2111
		// "$user", "files", "path/to/dir"
2112
		if (!isset($parts[1]) || $parts[1] !== 'files') {
2113
			$this->logger->error(
2114
				'$absolutePath must be relative to "files", value is "%s"',
2115
				[
2116
					$absolutePath
2117
				]
2118
			);
2119
			throw new \InvalidArgumentException('$absolutePath must be relative to "files"');
2120
		}
2121
		if (isset($parts[2])) {
2122
			return $parts[2];
2123
		}
2124
		return '';
2125
	}
2126
2127
	/**
2128
	 * @param string $filename
2129
	 * @return array
2130
	 * @throws \OC\User\NoUserException
2131
	 * @throws NotFoundException
2132
	 */
2133
	public function getUidAndFilename($filename) {
2134
		$info = $this->getFileInfo($filename);
2135
		if (!$info instanceof \OCP\Files\FileInfo) {
2136
			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2137
		}
2138
		$uid = $info->getOwner()->getUID();
2139
		if ($uid != \OCP\User::getUser()) {
2140
			Filesystem::initMountPoints($uid);
2141
			$ownerView = new View('/' . $uid . '/files');
2142
			try {
2143
				$filename = $ownerView->getPath($info['fileid']);
2144
			} catch (NotFoundException $e) {
2145
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2146
			}
2147
		}
2148
		return [$uid, $filename];
2149
	}
2150
2151
	/**
2152
	 * Creates parent non-existing folders
2153
	 *
2154
	 * @param string $filePath
2155
	 * @return bool
2156
	 */
2157
	private function createParentDirectories($filePath) {
2158
		$directoryParts = explode('/', $filePath);
2159
		$directoryParts = array_filter($directoryParts);
2160
		foreach ($directoryParts as $key => $part) {
2161
			$currentPathElements = array_slice($directoryParts, 0, $key);
2162
			$currentPath = '/' . implode('/', $currentPathElements);
2163
			if ($this->is_file($currentPath)) {
2164
				return false;
2165
			}
2166
			if (!$this->file_exists($currentPath)) {
2167
				$this->mkdir($currentPath);
2168
			}
2169
		}
2170
2171
		return true;
2172
	}
2173
}
2174