Completed
Push — master ( 1f48f6...828106 )
by Lukas
10:12
created

View::unlockPath()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
c 0
b 0
f 0
nc 4
nop 3
dl 0
loc 21
rs 8.7624
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Björn Schießle <[email protected]>
6
 * @author cmeh <[email protected]>
7
 * @author Florin Peter <[email protected]>
8
 * @author Jesús Macias <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Jörn Friedrich Dreyer <[email protected]>
11
 * @author Klaas Freitag <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Luke Policinski <[email protected]>
14
 * @author Martin Mattel <[email protected]>
15
 * @author Michael Gapczynski <[email protected]>
16
 * @author Morris Jobke <[email protected]>
17
 * @author Petr Svoboda <[email protected]>
18
 * @author Piotr Filiciak <[email protected]>
19
 * @author Robin Appelman <[email protected]>
20
 * @author Robin McCorkell <[email protected]>
21
 * @author Roeland Jago Douma <[email protected]>
22
 * @author Sam Tuke <[email protected]>
23
 * @author Stefan Weil <[email protected]>
24
 * @author Thomas Müller <[email protected]>
25
 * @author Thomas Tanghus <[email protected]>
26
 * @author Vincent Petry <[email protected]>
27
 *
28
 * @copyright Copyright (c) 2016, ownCloud, Inc.
29
 * @license AGPL-3.0
30
 *
31
 * This code is free software: you can redistribute it and/or modify
32
 * it under the terms of the GNU Affero General Public License, version 3,
33
 * as published by the Free Software Foundation.
34
 *
35
 * This program is distributed in the hope that it will be useful,
36
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38
 * GNU Affero General Public License for more details.
39
 *
40
 * You should have received a copy of the GNU Affero General Public License, version 3,
41
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
42
 *
43
 */
44
45
46
namespace OC\Files;
47
48
use Icewind\Streams\CallbackWrapper;
49
use OC\Files\Mount\MoveableMount;
50
use OC\Files\Storage\Storage;
51
use OC\User\User;
52
use OCP\Constants;
53
use OCP\Files\Cache\ICacheEntry;
54
use OCP\Files\FileNameTooLongException;
55
use OCP\Files\InvalidCharacterInPathException;
56
use OCP\Files\InvalidPathException;
57
use OCP\Files\NotFoundException;
58
use OCP\Files\ReservedWordException;
59
use OCP\Files\UnseekableException;
60
use OCP\Files\Storage\ILockingStorage;
61
use OCP\IUser;
62
use OCP\Lock\ILockingProvider;
63
use OCP\Lock\LockedException;
64
65
/**
66
 * Class to provide access to ownCloud filesystem via a "view", and methods for
67
 * working with files within that view (e.g. read, write, delete, etc.). Each
68
 * view is restricted to a set of directories via a virtual root. The default view
69
 * uses the currently logged in user's data directory as root (parts of
70
 * OC_Filesystem are merely a wrapper for OC\Files\View).
71
 *
72
 * Apps that need to access files outside of the user data folders (to modify files
73
 * belonging to a user other than the one currently logged in, for example) should
74
 * use this class directly rather than using OC_Filesystem, or making use of PHP's
75
 * built-in file manipulation functions. This will ensure all hooks and proxies
76
 * are triggered correctly.
77
 *
78
 * Filesystem functions are not called directly; they are passed to the correct
79
 * \OC\Files\Storage\Storage object
80
 */
81
class View {
82
	/** @var string */
83
	private $fakeRoot = '';
84
85
	/**
86
	 * @var \OCP\Lock\ILockingProvider
87
	 */
88
	private $lockingProvider;
89
90
	private $lockingEnabled;
91
92
	private $updaterEnabled = true;
93
94
	private $userManager;
95
96
	/**
97
	 * @param string $root
98
	 * @throws \Exception If $root contains an invalid path
99
	 */
100
	public function __construct($root = '') {
101
		if (is_null($root)) {
102
			throw new \InvalidArgumentException('Root can\'t be null');
103
		}
104
		if (!Filesystem::isValidPath($root)) {
105
			throw new \Exception();
106
		}
107
108
		$this->fakeRoot = $root;
109
		$this->lockingProvider = \OC::$server->getLockingProvider();
110
		$this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
111
		$this->userManager = \OC::$server->getUserManager();
112
	}
113
114
	public function getAbsolutePath($path = '/') {
115
		if ($path === null) {
116
			return null;
117
		}
118
		$this->assertPathLength($path);
119
		if ($path === '') {
120
			$path = '/';
121
		}
122
		if ($path[0] !== '/') {
123
			$path = '/' . $path;
124
		}
125
		return $this->fakeRoot . $path;
126
	}
127
128
	/**
129
	 * change the root to a fake root
130
	 *
131
	 * @param string $fakeRoot
132
	 * @return boolean|null
133
	 */
134
	public function chroot($fakeRoot) {
135
		if (!$fakeRoot == '') {
136
			if ($fakeRoot[0] !== '/') {
137
				$fakeRoot = '/' . $fakeRoot;
138
			}
139
		}
140
		$this->fakeRoot = $fakeRoot;
141
	}
142
143
	/**
144
	 * get the fake root
145
	 *
146
	 * @return string
147
	 */
148
	public function getRoot() {
149
		return $this->fakeRoot;
150
	}
151
152
	/**
153
	 * get path relative to the root of the view
154
	 *
155
	 * @param string $path
156
	 * @return string
157
	 */
158
	public function getRelativePath($path) {
159
		$this->assertPathLength($path);
160
		if ($this->fakeRoot == '') {
161
			return $path;
162
		}
163
164
		if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) {
165
			return '/';
166
		}
167
168
		// missing slashes can cause wrong matches!
169
		$root = rtrim($this->fakeRoot, '/') . '/';
170
171
		if (strpos($path, $root) !== 0) {
172
			return null;
173
		} else {
174
			$path = substr($path, strlen($this->fakeRoot));
175
			if (strlen($path) === 0) {
176
				return '/';
177
			} else {
178
				return $path;
179
			}
180
		}
181
	}
182
183
	/**
184
	 * get the mountpoint of the storage object for a path
185
	 * ( note: because a storage is not always mounted inside the fakeroot, the
186
	 * returned mountpoint is relative to the absolute root of the filesystem
187
	 * and does not take the chroot into account )
188
	 *
189
	 * @param string $path
190
	 * @return string
191
	 */
192
	public function getMountPoint($path) {
193
		return Filesystem::getMountPoint($this->getAbsolutePath($path));
194
	}
195
196
	/**
197
	 * get the mountpoint of the storage object for a path
198
	 * ( note: because a storage is not always mounted inside the fakeroot, the
199
	 * returned mountpoint is relative to the absolute root of the filesystem
200
	 * and does not take the chroot into account )
201
	 *
202
	 * @param string $path
203
	 * @return \OCP\Files\Mount\IMountPoint
204
	 */
205
	public function getMount($path) {
206
		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
207
	}
208
209
	/**
210
	 * resolve a path to a storage and internal path
211
	 *
212
	 * @param string $path
213
	 * @return array an array consisting of the storage and the internal path
214
	 */
215
	public function resolvePath($path) {
216
		$a = $this->getAbsolutePath($path);
217
		$p = Filesystem::normalizePath($a);
218
		return Filesystem::resolvePath($p);
219
	}
220
221
	/**
222
	 * return the path to a local version of the file
223
	 * we need this because we can't know if a file is stored local or not from
224
	 * outside the filestorage and for some purposes a local file is needed
225
	 *
226
	 * @param string $path
227
	 * @return string
228
	 */
229 View Code Duplication
	public function getLocalFile($path) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
230
		$parent = substr($path, 0, strrpos($path, '/'));
231
		$path = $this->getAbsolutePath($path);
232
		list($storage, $internalPath) = Filesystem::resolvePath($path);
233
		if (Filesystem::isValidPath($parent) and $storage) {
234
			return $storage->getLocalFile($internalPath);
235
		} else {
236
			return null;
237
		}
238
	}
239
240
	/**
241
	 * @param string $path
242
	 * @return string
243
	 */
244 View Code Duplication
	public function getLocalFolder($path) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
245
		$parent = substr($path, 0, strrpos($path, '/'));
246
		$path = $this->getAbsolutePath($path);
247
		list($storage, $internalPath) = Filesystem::resolvePath($path);
248
		if (Filesystem::isValidPath($parent) and $storage) {
249
			return $storage->getLocalFolder($internalPath);
250
		} else {
251
			return null;
252
		}
253
	}
254
255
	/**
256
	 * the following functions operate with arguments and return values identical
257
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
258
	 * for \OC\Files\Storage\Storage via basicOperation().
259
	 */
260
	public function mkdir($path) {
261
		return $this->basicOperation('mkdir', $path, array('create', 'write'));
262
	}
263
264
	/**
265
	 * remove mount point
266
	 *
267
	 * @param \OC\Files\Mount\MoveableMount $mount
268
	 * @param string $path relative to data/
269
	 * @return boolean
270
	 */
271
	protected function removeMount($mount, $path) {
272
		if ($mount instanceof MoveableMount) {
273
			// cut of /user/files to get the relative path to data/user/files
274
			$pathParts = explode('/', $path, 4);
275
			$relPath = '/' . $pathParts[3];
276
			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
277
			\OC_Hook::emit(
278
				Filesystem::CLASSNAME, "umount",
279
				array(Filesystem::signal_param_path => $relPath)
280
			);
281
			$this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
282
			$result = $mount->removeMount();
283
			$this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
284
			if ($result) {
285
				\OC_Hook::emit(
286
					Filesystem::CLASSNAME, "post_umount",
287
					array(Filesystem::signal_param_path => $relPath)
288
				);
289
			}
290
			$this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
291
			return $result;
292
		} else {
293
			// do not allow deleting the storage's root / the mount point
294
			// because for some storages it might delete the whole contents
295
			// but isn't supposed to work that way
296
			return false;
297
		}
298
	}
299
300
	public function disableCacheUpdate() {
301
		$this->updaterEnabled = false;
302
	}
303
304
	public function enableCacheUpdate() {
305
		$this->updaterEnabled = true;
306
	}
307
308
	protected function writeUpdate(Storage $storage, $internalPath, $time = null) {
309
		if ($this->updaterEnabled) {
310
			if (is_null($time)) {
311
				$time = time();
312
			}
313
			$storage->getUpdater()->update($internalPath, $time);
314
		}
315
	}
316
317
	protected function removeUpdate(Storage $storage, $internalPath) {
318
		if ($this->updaterEnabled) {
319
			$storage->getUpdater()->remove($internalPath);
320
		}
321
	}
322
323
	protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) {
324
		if ($this->updaterEnabled) {
325
			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
326
		}
327
	}
328
329
	/**
330
	 * @param string $path
331
	 * @return bool|mixed
332
	 */
333
	public function rmdir($path) {
334
		$absolutePath = $this->getAbsolutePath($path);
335
		$mount = Filesystem::getMountManager()->find($absolutePath);
336
		if ($mount->getInternalPath($absolutePath) === '') {
337
			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...
338
		}
339
		if ($this->is_dir($path)) {
340
			$result = $this->basicOperation('rmdir', $path, array('delete'));
341
		} else {
342
			$result = false;
343
		}
344
345 View Code Duplication
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
346
			$storage = $mount->getStorage();
347
			$internalPath = $mount->getInternalPath($absolutePath);
348
			$storage->getUpdater()->remove($internalPath);
349
		}
350
		return $result;
351
	}
352
353
	/**
354
	 * @param string $path
355
	 * @return resource
356
	 */
357
	public function opendir($path) {
358
		return $this->basicOperation('opendir', $path, array('read'));
359
	}
360
361
	/**
362
	 * @param $handle
363
	 * @return mixed
364
	 */
365
	public function readdir($handle) {
366
		$fsLocal = new Storage\Local(array('datadir' => '/'));
367
		return $fsLocal->readdir($handle);
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 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...
424
		$handle = $this->fopen($path, 'rb');
425
		if ($handle) {
426
			$chunkSize = 8192; // 8 kB chunks
427
			while (!feof($handle)) {
428
				echo fread($handle, $chunkSize);
429
				flush();
430
			}
431
			$size = $this->filesize($path);
432
			return $size;
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 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...
448
		$handle = $this->fopen($path, 'rb');
449
		if ($handle) {
450
			if (fseek($handle, $from) === 0) {
451
				$chunkSize = 8192; // 8 kB chunks
452
				$end = $to + 1;
453
				while (!feof($handle) && ftell($handle) < $end) {
454
					$len = $end - ftell($handle);
455
					if ($len > $chunkSize) {
456
						$len = $chunkSize;
457
					}
458
					echo fread($handle, $len);
459
					flush();
460
				}
461
				$size = ftell($handle) - $from;
462
				return $size;
463
			}
464
465
			throw new \OCP\Files\UnseekableException('fseek error');
466
		}
467
		return false;
468
	}
469
470
	/**
471
	 * @param string $path
472
	 * @return mixed
473
	 */
474
	public function isCreatable($path) {
475
		return $this->basicOperation('isCreatable', $path);
476
	}
477
478
	/**
479
	 * @param string $path
480
	 * @return mixed
481
	 */
482
	public function isReadable($path) {
483
		return $this->basicOperation('isReadable', $path);
484
	}
485
486
	/**
487
	 * @param string $path
488
	 * @return mixed
489
	 */
490
	public function isUpdatable($path) {
491
		return $this->basicOperation('isUpdatable', $path);
492
	}
493
494
	/**
495
	 * @param string $path
496
	 * @return bool|mixed
497
	 */
498
	public function isDeletable($path) {
499
		$absolutePath = $this->getAbsolutePath($path);
500
		$mount = Filesystem::getMountManager()->find($absolutePath);
501
		if ($mount->getInternalPath($absolutePath) === '') {
502
			return $mount instanceof MoveableMount;
503
		}
504
		return $this->basicOperation('isDeletable', $path);
505
	}
506
507
	/**
508
	 * @param string $path
509
	 * @return mixed
510
	 */
511
	public function isSharable($path) {
512
		return $this->basicOperation('isSharable', $path);
513
	}
514
515
	/**
516
	 * @param string $path
517
	 * @return bool|mixed
518
	 */
519
	public function file_exists($path) {
520
		if ($path == '/') {
521
			return true;
522
		}
523
		return $this->basicOperation('file_exists', $path);
524
	}
525
526
	/**
527
	 * @param string $path
528
	 * @return mixed
529
	 */
530
	public function filemtime($path) {
531
		return $this->basicOperation('filemtime', $path);
532
	}
533
534
	/**
535
	 * @param string $path
536
	 * @param int|string $mtime
537
	 * @return bool
538
	 */
539
	public function touch($path, $mtime = null) {
540
		if (!is_null($mtime) and !is_numeric($mtime)) {
541
			$mtime = strtotime($mtime);
542
		}
543
544
		$hooks = array('touch');
545
546
		if (!$this->file_exists($path)) {
547
			$hooks[] = 'create';
548
			$hooks[] = 'write';
549
		}
550
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
551
		if (!$result) {
552
			// If create file fails because of permissions on external storage like SMB folders,
553
			// check file exists and return false if not.
554
			if (!$this->file_exists($path)) {
555
				return false;
556
			}
557
			if (is_null($mtime)) {
558
				$mtime = time();
559
			}
560
			//if native touch fails, we emulate it by changing the mtime in the cache
561
			$this->putFileInfo($path, array('mtime' => $mtime));
562
		}
563
		return true;
564
	}
565
566
	/**
567
	 * @param string $path
568
	 * @return mixed
569
	 */
570
	public function file_get_contents($path) {
571
		return $this->basicOperation('file_get_contents', $path, array('read'));
572
	}
573
574
	/**
575
	 * @param bool $exists
576
	 * @param string $path
577
	 * @param bool $run
578
	 */
579
	protected function emit_file_hooks_pre($exists, $path, &$run) {
580 View Code Duplication
		if (!$exists) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
581
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array(
582
				Filesystem::signal_param_path => $this->getHookPath($path),
583
				Filesystem::signal_param_run => &$run,
584
			));
585
		} else {
586
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array(
587
				Filesystem::signal_param_path => $this->getHookPath($path),
588
				Filesystem::signal_param_run => &$run,
589
			));
590
		}
591
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array(
592
			Filesystem::signal_param_path => $this->getHookPath($path),
593
			Filesystem::signal_param_run => &$run,
594
		));
595
	}
596
597
	/**
598
	 * @param bool $exists
599
	 * @param string $path
600
	 */
601
	protected function emit_file_hooks_post($exists, $path) {
602 View Code Duplication
		if (!$exists) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
603
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array(
604
				Filesystem::signal_param_path => $this->getHookPath($path),
605
			));
606
		} else {
607
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
608
				Filesystem::signal_param_path => $this->getHookPath($path),
609
			));
610
		}
611
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
612
			Filesystem::signal_param_path => $this->getHookPath($path),
613
		));
614
	}
615
616
	/**
617
	 * @param string $path
618
	 * @param mixed $data
619
	 * @return bool|mixed
620
	 * @throws \Exception
621
	 */
622
	public function file_put_contents($path, $data) {
623
		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
624
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
625
			if (Filesystem::isValidPath($path)
626
				and !Filesystem::isFileBlacklisted($path)
627
			) {
628
				$path = $this->getRelativePath($absolutePath);
629
630
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
631
632
				$exists = $this->file_exists($path);
633
				$run = true;
634
				if ($this->shouldEmitHooks($path)) {
635
					$this->emit_file_hooks_pre($exists, $path, $run);
636
				}
637
				if (!$run) {
638
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
639
					return false;
640
				}
641
642
				$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
643
644
				/** @var \OC\Files\Storage\Storage $storage */
645
				list($storage, $internalPath) = $this->resolvePath($path);
646
				$target = $storage->fopen($internalPath, 'w');
647
				if ($target) {
648
					list (, $result) = \OC_Helper::streamCopy($data, $target);
649
					fclose($target);
650
					fclose($data);
651
652
					$this->writeUpdate($storage, $internalPath);
653
654
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
655
656
					if ($this->shouldEmitHooks($path) && $result !== false) {
657
						$this->emit_file_hooks_post($exists, $path);
658
					}
659
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
660
					return $result;
661
				} else {
662
					$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
663
					return false;
664
				}
665
			} else {
666
				return false;
667
			}
668
		} else {
669
			$hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write');
670
			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
671
		}
672
	}
673
674
	/**
675
	 * @param string $path
676
	 * @return bool|mixed
677
	 */
678
	public function unlink($path) {
679
		if ($path === '' || $path === '/') {
680
			// do not allow deleting the root
681
			return false;
682
		}
683
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
684
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
685
		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
686
		if ($mount and $mount->getInternalPath($absolutePath) === '') {
687
			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...
688
		}
689
		$result = $this->basicOperation('unlink', $path, array('delete'));
690 View Code Duplication
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
691
			$storage = $mount->getStorage();
692
			$internalPath = $mount->getInternalPath($absolutePath);
693
			$storage->getUpdater()->remove($internalPath);
694
			return true;
695
		} else {
696
			return $result;
697
		}
698
	}
699
700
	/**
701
	 * @param string $directory
702
	 * @return bool|mixed
703
	 */
704
	public function deleteAll($directory) {
705
		return $this->rmdir($directory);
706
	}
707
708
	/**
709
	 * Rename/move a file or folder from the source path to target path.
710
	 *
711
	 * @param string $path1 source path
712
	 * @param string $path2 target path
713
	 *
714
	 * @return bool|mixed
715
	 */
716
	public function rename($path1, $path2) {
717
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
718
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
719
		$result = false;
720
		if (
721
			Filesystem::isValidPath($path2)
722
			and Filesystem::isValidPath($path1)
723
			and !Filesystem::isFileBlacklisted($path2)
724
		) {
725
			$path1 = $this->getRelativePath($absolutePath1);
726
			$path2 = $this->getRelativePath($absolutePath2);
727
			$exists = $this->file_exists($path2);
728
729
			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...
730
				return false;
731
			}
732
733
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
734
			try {
735
				$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
736
			} catch (LockedException $e) {
737
				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
738
				throw $e;
739
			}
740
741
			$run = true;
742
			if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
743
				// if it was a rename from a part file to a regular file it was a write and not a rename operation
744
				$this->emit_file_hooks_pre($exists, $path2, $run);
745
			} elseif ($this->shouldEmitHooks($path1)) {
746
				\OC_Hook::emit(
747
					Filesystem::CLASSNAME, Filesystem::signal_rename,
748
					array(
749
						Filesystem::signal_param_oldpath => $this->getHookPath($path1),
750
						Filesystem::signal_param_newpath => $this->getHookPath($path2),
751
						Filesystem::signal_param_run => &$run
752
					)
753
				);
754
			}
755
			if ($run) {
756
				$this->verifyPath(dirname($path2), basename($path2));
757
758
				$manager = Filesystem::getMountManager();
759
				$mount1 = $this->getMount($path1);
760
				$mount2 = $this->getMount($path2);
761
				$storage1 = $mount1->getStorage();
762
				$storage2 = $mount2->getStorage();
763
				$internalPath1 = $mount1->getInternalPath($absolutePath1);
764
				$internalPath2 = $mount2->getInternalPath($absolutePath2);
765
766
				$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
767
				$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
768
769
				if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
770
					if ($this->isTargetAllowed($absolutePath2)) {
771
						/**
772
						 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
773
						 */
774
						$sourceMountPoint = $mount1->getMountPoint();
775
						$result = $mount1->moveMount($absolutePath2);
776
						$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
777
					} else {
778
						$result = false;
779
					}
780
					// moving a file/folder within the same mount point
781
				} elseif ($storage1 === $storage2) {
782
					if ($storage1) {
783
						$result = $storage1->rename($internalPath1, $internalPath2);
784
					} else {
785
						$result = false;
786
					}
787
					// moving a file/folder between storages (from $storage1 to $storage2)
788
				} else {
789
					$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
790
				}
791
792
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
793
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
794
795
					$this->writeUpdate($storage2, $internalPath2);
796
				} else if ($result) {
797
					if ($internalPath1 !== '') { // don't do a cache update for moved mounts
798
						$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
799
					}
800
				}
801
802
				$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
803
				$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
804
805
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
806
					if ($this->shouldEmitHooks()) {
807
						$this->emit_file_hooks_post($exists, $path2);
808
					}
809
				} elseif ($result) {
810
					if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
811
						\OC_Hook::emit(
812
							Filesystem::CLASSNAME,
813
							Filesystem::signal_post_rename,
814
							array(
815
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
816
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
817
							)
818
						);
819
					}
820
				}
821
			}
822
			$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
823
			$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
824
		}
825
		return $result;
826
	}
827
828
	/**
829
	 * Copy a file/folder from the source path to target path
830
	 *
831
	 * @param string $path1 source path
832
	 * @param string $path2 target path
833
	 * @param bool $preserveMtime whether to preserve mtime on the copy
834
	 *
835
	 * @return bool|mixed
836
	 */
837
	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...
838
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
839
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
840
		$result = false;
841
		if (
842
			Filesystem::isValidPath($path2)
843
			and Filesystem::isValidPath($path1)
844
			and !Filesystem::isFileBlacklisted($path2)
845
		) {
846
			$path1 = $this->getRelativePath($absolutePath1);
847
			$path2 = $this->getRelativePath($absolutePath2);
848
849
			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...
850
				return false;
851
			}
852
			$run = true;
853
854
			$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
855
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
856
			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
857
			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
858
859
			try {
860
861
				$exists = $this->file_exists($path2);
862 View Code Duplication
				if ($this->shouldEmitHooks()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
863
					\OC_Hook::emit(
864
						Filesystem::CLASSNAME,
865
						Filesystem::signal_copy,
866
						array(
867
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
868
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
869
							Filesystem::signal_param_run => &$run
870
						)
871
					);
872
					$this->emit_file_hooks_pre($exists, $path2, $run);
873
				}
874
				if ($run) {
875
					$mount1 = $this->getMount($path1);
876
					$mount2 = $this->getMount($path2);
877
					$storage1 = $mount1->getStorage();
878
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
879
					$storage2 = $mount2->getStorage();
880
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
881
882
					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
883
					$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
884
885
					if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
886
						if ($storage1) {
887
							$result = $storage1->copy($internalPath1, $internalPath2);
888
						} else {
889
							$result = false;
890
						}
891
					} else {
892
						$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
893
					}
894
895
					$this->writeUpdate($storage2, $internalPath2);
896
897
					$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
898
					$lockTypePath2 = ILockingProvider::LOCK_SHARED;
899
900 View Code Duplication
					if ($this->shouldEmitHooks() && $result !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
901
						\OC_Hook::emit(
902
							Filesystem::CLASSNAME,
903
							Filesystem::signal_post_copy,
904
							array(
905
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
906
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
907
							)
908
						);
909
						$this->emit_file_hooks_post($exists, $path2);
910
					}
911
912
				}
913
			} catch (\Exception $e) {
914
				$this->unlockFile($path2, $lockTypePath2);
915
				$this->unlockFile($path1, $lockTypePath1);
916
				throw $e;
917
			}
918
919
			$this->unlockFile($path2, $lockTypePath2);
920
			$this->unlockFile($path1, $lockTypePath1);
921
922
		}
923
		return $result;
924
	}
925
926
	/**
927
	 * @param string $path
928
	 * @param string $mode
929
	 * @return resource
930
	 */
931
	public function fopen($path, $mode) {
932
		$hooks = array();
933
		switch ($mode) {
934
			case 'r':
935
			case 'rb':
936
				$hooks[] = 'read';
937
				break;
938
			case 'r+':
939
			case 'rb+':
940
			case 'w+':
941
			case 'wb+':
942
			case 'x+':
943
			case 'xb+':
944
			case 'a+':
945
			case 'ab+':
946
				$hooks[] = 'read';
947
				$hooks[] = 'write';
948
				break;
949
			case 'w':
950
			case 'wb':
951
			case 'x':
952
			case 'xb':
953
			case 'a':
954
			case 'ab':
955
				$hooks[] = 'write';
956
				break;
957
			default:
958
				\OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, \OCP\Util::ERROR);
959
		}
960
961
		return $this->basicOperation('fopen', $path, $hooks, $mode);
962
	}
963
964
	/**
965
	 * @param string $path
966
	 * @return bool|string
967
	 * @throws \OCP\Files\InvalidPathException
968
	 */
969
	public function toTmpFile($path) {
970
		$this->assertPathLength($path);
971
		if (Filesystem::isValidPath($path)) {
972
			$source = $this->fopen($path, 'r');
973
			if ($source) {
974
				$extension = pathinfo($path, PATHINFO_EXTENSION);
975
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
976
				file_put_contents($tmpFile, $source);
977
				return $tmpFile;
978
			} else {
979
				return false;
980
			}
981
		} else {
982
			return false;
983
		}
984
	}
985
986
	/**
987
	 * @param string $tmpFile
988
	 * @param string $path
989
	 * @return bool|mixed
990
	 * @throws \OCP\Files\InvalidPathException
991
	 */
992
	public function fromTmpFile($tmpFile, $path) {
993
		$this->assertPathLength($path);
994
		if (Filesystem::isValidPath($path)) {
995
996
			// Get directory that the file is going into
997
			$filePath = dirname($path);
998
999
			// Create the directories if any
1000
			if (!$this->file_exists($filePath)) {
1001
				$result = $this->createParentDirectories($filePath);
1002
				if($result === false) {
1003
					return false;
1004
				}
1005
			}
1006
1007
			$source = fopen($tmpFile, 'r');
1008
			if ($source) {
1009
				$result = $this->file_put_contents($path, $source);
1010
				// $this->file_put_contents() might have already closed
1011
				// the resource, so we check it, before trying to close it
1012
				// to avoid messages in the error log.
1013
				if (is_resource($source)) {
1014
					fclose($source);
1015
				}
1016
				unlink($tmpFile);
1017
				return $result;
1018
			} else {
1019
				return false;
1020
			}
1021
		} else {
1022
			return false;
1023
		}
1024
	}
1025
1026
1027
	/**
1028
	 * @param string $path
1029
	 * @return mixed
1030
	 * @throws \OCP\Files\InvalidPathException
1031
	 */
1032
	public function getMimeType($path) {
1033
		$this->assertPathLength($path);
1034
		return $this->basicOperation('getMimeType', $path);
1035
	}
1036
1037
	/**
1038
	 * @param string $type
1039
	 * @param string $path
1040
	 * @param bool $raw
1041
	 * @return bool|null|string
1042
	 */
1043
	public function hash($type, $path, $raw = false) {
1044
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1045
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1046
		if (Filesystem::isValidPath($path)) {
1047
			$path = $this->getRelativePath($absolutePath);
1048
			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...
1049
				return false;
1050
			}
1051
			if ($this->shouldEmitHooks($path)) {
1052
				\OC_Hook::emit(
1053
					Filesystem::CLASSNAME,
1054
					Filesystem::signal_read,
1055
					array(Filesystem::signal_param_path => $this->getHookPath($path))
1056
				);
1057
			}
1058
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1059
			if ($storage) {
1060
				$result = $storage->hash($type, $internalPath, $raw);
1061
				return $result;
1062
			}
1063
		}
1064
		return null;
1065
	}
1066
1067
	/**
1068
	 * @param string $path
1069
	 * @return mixed
1070
	 * @throws \OCP\Files\InvalidPathException
1071
	 */
1072
	public function free_space($path = '/') {
1073
		$this->assertPathLength($path);
1074
		return $this->basicOperation('free_space', $path);
1075
	}
1076
1077
	/**
1078
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1079
	 *
1080
	 * @param string $operation
1081
	 * @param string $path
1082
	 * @param array $hooks (optional)
1083
	 * @param mixed $extraParam (optional)
1084
	 * @return mixed
1085
	 * @throws \Exception
1086
	 *
1087
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1088
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1089
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1090
	 */
1091
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1092
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1093
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1094
		if (Filesystem::isValidPath($path)
1095
			and !Filesystem::isFileBlacklisted($path)
1096
		) {
1097
			$path = $this->getRelativePath($absolutePath);
1098
			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...
1099
				return false;
1100
			}
1101
1102
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1103
				// always a shared lock during pre-hooks so the hook can read the file
1104
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1105
			}
1106
1107
			$run = $this->runHooks($hooks, $path);
1108
			/** @var \OC\Files\Storage\Storage $storage */
1109
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1110
			if ($run and $storage) {
1111
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1112
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1113
				}
1114
				try {
1115
					if (!is_null($extraParam)) {
1116
						$result = $storage->$operation($internalPath, $extraParam);
1117
					} else {
1118
						$result = $storage->$operation($internalPath);
1119
					}
1120
				} catch (\Exception $e) {
1121 View Code Duplication
					if (in_array('write', $hooks) || in_array('delete', $hooks)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1122
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1123
					} else if (in_array('read', $hooks)) {
1124
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1125
					}
1126
					throw $e;
1127
				}
1128
1129
				if (in_array('delete', $hooks) and $result) {
1130
					$this->removeUpdate($storage, $internalPath);
1131
				}
1132
				if (in_array('write', $hooks) and $operation !== 'fopen') {
1133
					$this->writeUpdate($storage, $internalPath);
1134
				}
1135
				if (in_array('touch', $hooks)) {
1136
					$this->writeUpdate($storage, $internalPath, $extraParam);
1137
				}
1138
1139
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1140
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1141
				}
1142
1143
				$unlockLater = false;
1144
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1145
					$unlockLater = true;
1146
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1147 View Code Duplication
						if (in_array('write', $hooks)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1148
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1149
						} else if (in_array('read', $hooks)) {
1150
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1151
						}
1152
					});
1153
				}
1154
1155
				if ($this->shouldEmitHooks($path) && $result !== false) {
1156
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1157
						$this->runHooks($hooks, $path, true);
1158
					}
1159
				}
1160
1161 View Code Duplication
				if (!$unlockLater
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1162
					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1163
				) {
1164
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1165
				}
1166
				return $result;
1167
			} else {
1168
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1169
			}
1170
		}
1171
		return null;
1172
	}
1173
1174
	/**
1175
	 * get the path relative to the default root for hook usage
1176
	 *
1177
	 * @param string $path
1178
	 * @return string
1179
	 */
1180
	private function getHookPath($path) {
1181
		if (!Filesystem::getView()) {
1182
			return $path;
1183
		}
1184
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1185
	}
1186
1187
	private function shouldEmitHooks($path = '') {
1188
		if ($path && Cache\Scanner::isPartialFile($path)) {
1189
			return false;
1190
		}
1191
		if (!Filesystem::$loaded) {
1192
			return false;
1193
		}
1194
		$defaultRoot = Filesystem::getRoot();
1195
		if ($defaultRoot === null) {
1196
			return false;
1197
		}
1198
		if ($this->fakeRoot === $defaultRoot) {
1199
			return true;
1200
		}
1201
		$fullPath = $this->getAbsolutePath($path);
1202
1203
		if ($fullPath === $defaultRoot) {
1204
			return true;
1205
		}
1206
1207
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1208
	}
1209
1210
	/**
1211
	 * @param string[] $hooks
1212
	 * @param string $path
1213
	 * @param bool $post
1214
	 * @return bool
1215
	 */
1216
	private function runHooks($hooks, $path, $post = false) {
1217
		$relativePath = $path;
1218
		$path = $this->getHookPath($path);
1219
		$prefix = ($post) ? 'post_' : '';
1220
		$run = true;
1221
		if ($this->shouldEmitHooks($relativePath)) {
1222
			foreach ($hooks as $hook) {
1223
				if ($hook != 'read') {
1224
					\OC_Hook::emit(
1225
						Filesystem::CLASSNAME,
1226
						$prefix . $hook,
1227
						array(
1228
							Filesystem::signal_param_run => &$run,
1229
							Filesystem::signal_param_path => $path
1230
						)
1231
					);
1232
				} elseif (!$post) {
1233
					\OC_Hook::emit(
1234
						Filesystem::CLASSNAME,
1235
						$prefix . $hook,
1236
						array(
1237
							Filesystem::signal_param_path => $path
1238
						)
1239
					);
1240
				}
1241
			}
1242
		}
1243
		return $run;
1244
	}
1245
1246
	/**
1247
	 * check if a file or folder has been updated since $time
1248
	 *
1249
	 * @param string $path
1250
	 * @param int $time
1251
	 * @return bool
1252
	 */
1253
	public function hasUpdated($path, $time) {
1254
		return $this->basicOperation('hasUpdated', $path, array(), $time);
1255
	}
1256
1257
	/**
1258
	 * @param string $ownerId
1259
	 * @return \OC\User\User
1260
	 */
1261
	private function getUserObjectForOwner($ownerId) {
1262
		$owner = $this->userManager->get($ownerId);
1263
		if ($owner instanceof IUser) {
1264
			return $owner;
1265
		} else {
1266
			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...
1267
		}
1268
	}
1269
1270
	/**
1271
	 * Get file info from cache
1272
	 *
1273
	 * If the file is not in cached it will be scanned
1274
	 * If the file has changed on storage the cache will be updated
1275
	 *
1276
	 * @param \OC\Files\Storage\Storage $storage
1277
	 * @param string $internalPath
1278
	 * @param string $relativePath
1279
	 * @return array|bool
1280
	 */
1281
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1282
		$cache = $storage->getCache($internalPath);
1283
		$data = $cache->get($internalPath);
1284
		$watcher = $storage->getWatcher($internalPath);
1285
1286
		try {
1287
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1288
			if (!$data || $data['size'] === -1) {
1289
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1290
				if (!$storage->file_exists($internalPath)) {
1291
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1292
					return false;
1293
				}
1294
				$scanner = $storage->getScanner($internalPath);
1295
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1296
				$data = $cache->get($internalPath);
1297
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1298
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1299
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1300
				$watcher->update($internalPath, $data);
1301
				$storage->getPropagator()->propagateChange($internalPath, time());
1302
				$data = $cache->get($internalPath);
1303
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1304
			}
1305
		} catch (LockedException $e) {
1306
			// if the file is locked we just use the old cache info
1307
		}
1308
1309
		return $data;
1310
	}
1311
1312
	/**
1313
	 * get the filesystem info
1314
	 *
1315
	 * @param string $path
1316
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1317
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1318
	 * defaults to true
1319
	 * @return \OC\Files\FileInfo|false False if file does not exist
1320
	 */
1321
	public function getFileInfo($path, $includeMountPoints = true) {
1322
		$this->assertPathLength($path);
1323
		if (!Filesystem::isValidPath($path)) {
1324
			return false;
1325
		}
1326
		if (Cache\Scanner::isPartialFile($path)) {
1327
			return $this->getPartFileInfo($path);
1328
		}
1329
		$relativePath = $path;
1330
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1331
1332
		$mount = Filesystem::getMountManager()->find($path);
1333
		$storage = $mount->getStorage();
1334
		$internalPath = $mount->getInternalPath($path);
1335
		if ($storage) {
1336
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1337
1338
			if (!$data instanceof ICacheEntry) {
1339
				return false;
1340
			}
1341
1342
			if ($mount instanceof MoveableMount && $internalPath === '') {
1343
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1344
			}
1345
1346
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1347
			$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 1332 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...
1348
1349
			if ($data and isset($data['fileid'])) {
1350
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1351
					//add the sizes of other mount points to the folder
1352
					$extOnly = ($includeMountPoints === 'ext');
1353
					$mounts = Filesystem::getMountManager()->findIn($path);
1354
					foreach ($mounts as $mount) {
1355
						$subStorage = $mount->getStorage();
1356
						if ($subStorage) {
1357
							// exclude shared storage ?
1358
							if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) {
1359
								continue;
1360
							}
1361
							$subCache = $subStorage->getCache('');
1362
							$rootEntry = $subCache->get('');
1363
							$info->addSubEntry($rootEntry, $mount->getMountPoint());
1364
						}
1365
					}
1366
				}
1367
			}
1368
1369
			return $info;
1370
		}
1371
1372
		return false;
1373
	}
1374
1375
	/**
1376
	 * get the content of a directory
1377
	 *
1378
	 * @param string $directory path under datadirectory
1379
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1380
	 * @return FileInfo[]
1381
	 */
1382
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1383
		$this->assertPathLength($directory);
1384
		if (!Filesystem::isValidPath($directory)) {
1385
			return [];
1386
		}
1387
		$path = $this->getAbsolutePath($directory);
1388
		$path = Filesystem::normalizePath($path);
1389
		$mount = $this->getMount($directory);
1390
		$storage = $mount->getStorage();
1391
		$internalPath = $mount->getInternalPath($path);
1392
		if ($storage) {
1393
			$cache = $storage->getCache($internalPath);
1394
			$user = \OC_User::getUser();
1395
1396
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1397
1398
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1399
				return [];
1400
			}
1401
1402
			$folderId = $data['fileid'];
1403
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1404
1405
			$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Util::isSharingDisabledForUser() has been deprecated with message: 9.1.0 Use \OC::$server->getShareManager()->sharingDisabledForUser

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

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

Loading history...
1406
			/**
1407
			 * @var \OC\Files\FileInfo[] $files
1408
			 */
1409
			$files = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1410
				if ($sharingDisabled) {
1411
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1412
				}
1413
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1414
				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 1389 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...
1415
			}, $contents);
1416
1417
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1418
			$mounts = Filesystem::getMountManager()->findIn($path);
1419
			$dirLength = strlen($path);
1420
			foreach ($mounts as $mount) {
1421
				$mountPoint = $mount->getMountPoint();
1422
				$subStorage = $mount->getStorage();
1423
				if ($subStorage) {
1424
					$subCache = $subStorage->getCache('');
1425
1426
					$rootEntry = $subCache->get('');
1427
					if (!$rootEntry) {
1428
						$subScanner = $subStorage->getScanner('');
1429
						try {
1430
							$subScanner->scanFile('');
1431
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1432
							continue;
1433
						} catch (\OCP\Files\StorageInvalidException $e) {
1434
							continue;
1435
						} catch (\Exception $e) {
1436
							// sometimes when the storage is not available it can be any exception
1437
							\OCP\Util::writeLog(
1438
								'core',
1439
								'Exception while scanning storage "' . $subStorage->getId() . '": ' .
1440
								get_class($e) . ': ' . $e->getMessage(),
1441
								\OCP\Util::ERROR
1442
							);
1443
							continue;
1444
						}
1445
						$rootEntry = $subCache->get('');
1446
					}
1447
1448
					if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1449
						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1450
						if ($pos = strpos($relativePath, '/')) {
1451
							//mountpoint inside subfolder add size to the correct folder
1452
							$entryName = substr($relativePath, 0, $pos);
1453
							foreach ($files as &$entry) {
1454
								if ($entry->getName() === $entryName) {
1455
									$entry->addSubEntry($rootEntry, $mountPoint);
1456
								}
1457
							}
1458
						} else { //mountpoint in this folder, add an entry for it
1459
							$rootEntry['name'] = $relativePath;
1460
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1461
							$permissions = $rootEntry['permissions'];
1462
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1463
							// for shared files/folders we use the permissions given by the owner
1464
							if ($mount instanceof MoveableMount) {
1465
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1466
							} else {
1467
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1468
							}
1469
1470
							//remove any existing entry with the same name
1471
							foreach ($files as $i => $file) {
1472
								if ($file['name'] === $rootEntry['name']) {
1473
									unset($files[$i]);
1474
									break;
1475
								}
1476
							}
1477
							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1478
1479
							// if sharing was disabled for the user we remove the share permissions
1480
							if (\OCP\Util::isSharingDisabledForUser()) {
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Util::isSharingDisabledForUser() has been deprecated with message: 9.1.0 Use \OC::$server->getShareManager()->sharingDisabledForUser

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

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

Loading history...
1481
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1482
							}
1483
1484
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1485
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1486
						}
1487
					}
1488
				}
1489
			}
1490
1491
			if ($mimetype_filter) {
1492
				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1493
					if (strpos($mimetype_filter, '/')) {
1494
						return $file->getMimetype() === $mimetype_filter;
1495
					} else {
1496
						return $file->getMimePart() === $mimetype_filter;
1497
					}
1498
				});
1499
			}
1500
1501
			return $files;
1502
		} else {
1503
			return [];
1504
		}
1505
	}
1506
1507
	/**
1508
	 * change file metadata
1509
	 *
1510
	 * @param string $path
1511
	 * @param array|\OCP\Files\FileInfo $data
1512
	 * @return int
1513
	 *
1514
	 * returns the fileid of the updated file
1515
	 */
1516
	public function putFileInfo($path, $data) {
1517
		$this->assertPathLength($path);
1518
		if ($data instanceof FileInfo) {
1519
			$data = $data->getData();
1520
		}
1521
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1522
		/**
1523
		 * @var \OC\Files\Storage\Storage $storage
1524
		 * @var string $internalPath
1525
		 */
1526
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1527
		if ($storage) {
1528
			$cache = $storage->getCache($path);
1529
1530
			if (!$cache->inCache($internalPath)) {
1531
				$scanner = $storage->getScanner($internalPath);
1532
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1533
			}
1534
1535
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1516 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...
1536
		} else {
1537
			return -1;
1538
		}
1539
	}
1540
1541
	/**
1542
	 * search for files with the name matching $query
1543
	 *
1544
	 * @param string $query
1545
	 * @return FileInfo[]
1546
	 */
1547
	public function search($query) {
1548
		return $this->searchCommon('search', array('%' . $query . '%'));
1549
	}
1550
1551
	/**
1552
	 * search for files with the name matching $query
1553
	 *
1554
	 * @param string $query
1555
	 * @return FileInfo[]
1556
	 */
1557
	public function searchRaw($query) {
1558
		return $this->searchCommon('search', array($query));
1559
	}
1560
1561
	/**
1562
	 * search for files by mimetype
1563
	 *
1564
	 * @param string $mimetype
1565
	 * @return FileInfo[]
1566
	 */
1567
	public function searchByMime($mimetype) {
1568
		return $this->searchCommon('searchByMime', array($mimetype));
1569
	}
1570
1571
	/**
1572
	 * search for files by tag
1573
	 *
1574
	 * @param string|int $tag name or tag id
1575
	 * @param string $userId owner of the tags
1576
	 * @return FileInfo[]
1577
	 */
1578
	public function searchByTag($tag, $userId) {
1579
		return $this->searchCommon('searchByTag', array($tag, $userId));
1580
	}
1581
1582
	/**
1583
	 * @param string $method cache method
1584
	 * @param array $args
1585
	 * @return FileInfo[]
1586
	 */
1587
	private function searchCommon($method, $args) {
1588
		$files = array();
1589
		$rootLength = strlen($this->fakeRoot);
1590
1591
		$mount = $this->getMount('');
1592
		$mountPoint = $mount->getMountPoint();
1593
		$storage = $mount->getStorage();
1594
		if ($storage) {
1595
			$cache = $storage->getCache('');
1596
1597
			$results = call_user_func_array(array($cache, $method), $args);
1598
			foreach ($results as $result) {
1599
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1600
					$internalPath = $result['path'];
1601
					$path = $mountPoint . $result['path'];
1602
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1603
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1604
					$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 1591 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...
1605
				}
1606
			}
1607
1608
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1609
			foreach ($mounts as $mount) {
1610
				$mountPoint = $mount->getMountPoint();
1611
				$storage = $mount->getStorage();
1612
				if ($storage) {
1613
					$cache = $storage->getCache('');
1614
1615
					$relativeMountPoint = substr($mountPoint, $rootLength);
1616
					$results = call_user_func_array(array($cache, $method), $args);
1617
					if ($results) {
1618
						foreach ($results as $result) {
1619
							$internalPath = $result['path'];
1620
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1621
							$path = rtrim($mountPoint . $internalPath, '/');
1622
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1623
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1624
						}
1625
					}
1626
				}
1627
			}
1628
		}
1629
		return $files;
1630
	}
1631
1632
	/**
1633
	 * Get the owner for a file or folder
1634
	 *
1635
	 * @param string $path
1636
	 * @return string the user id of the owner
1637
	 * @throws NotFoundException
1638
	 */
1639
	public function getOwner($path) {
1640
		$info = $this->getFileInfo($path);
1641
		if (!$info) {
1642
			throw new NotFoundException($path . ' not found while trying to get owner');
1643
		}
1644
		return $info->getOwner()->getUID();
1645
	}
1646
1647
	/**
1648
	 * get the ETag for a file or folder
1649
	 *
1650
	 * @param string $path
1651
	 * @return string
1652
	 */
1653
	public function getETag($path) {
1654
		/**
1655
		 * @var Storage\Storage $storage
1656
		 * @var string $internalPath
1657
		 */
1658
		list($storage, $internalPath) = $this->resolvePath($path);
1659
		if ($storage) {
1660
			return $storage->getETag($internalPath);
1661
		} else {
1662
			return null;
1663
		}
1664
	}
1665
1666
	/**
1667
	 * Get the path of a file by id, relative to the view
1668
	 *
1669
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1670
	 *
1671
	 * @param int $id
1672
	 * @throws NotFoundException
1673
	 * @return string
1674
	 */
1675
	public function getPath($id) {
1676
		$id = (int)$id;
1677
		$manager = Filesystem::getMountManager();
1678
		$mounts = $manager->findIn($this->fakeRoot);
1679
		$mounts[] = $manager->find($this->fakeRoot);
1680
		// reverse the array so we start with the storage this view is in
1681
		// which is the most likely to contain the file we're looking for
1682
		$mounts = array_reverse($mounts);
1683 View Code Duplication
		foreach ($mounts as $mount) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1684
			/**
1685
			 * @var \OC\Files\Mount\MountPoint $mount
1686
			 */
1687
			if ($mount->getStorage()) {
1688
				$cache = $mount->getStorage()->getCache();
1689
				$internalPath = $cache->getPathById($id);
1690
				if (is_string($internalPath)) {
1691
					$fullPath = $mount->getMountPoint() . $internalPath;
1692
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1693
						return $path;
1694
					}
1695
				}
1696
			}
1697
		}
1698
		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1699
	}
1700
1701
	/**
1702
	 * @param string $path
1703
	 * @throws InvalidPathException
1704
	 */
1705
	private function assertPathLength($path) {
1706
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1707
		// Check for the string length - performed using isset() instead of strlen()
1708
		// because isset() is about 5x-40x faster.
1709
		if (isset($path[$maxLen])) {
1710
			$pathLen = strlen($path);
1711
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1712
		}
1713
	}
1714
1715
	/**
1716
	 * check if it is allowed to move a mount point to a given target.
1717
	 * It is not allowed to move a mount point into a different mount point or
1718
	 * into an already shared folder
1719
	 *
1720
	 * @param string $target path
1721
	 * @return boolean
1722
	 */
1723
	private function isTargetAllowed($target) {
1724
1725
		list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target);
1726
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1727
			\OCP\Util::writeLog('files',
1728
				'It is not allowed to move one mount point into another one',
1729
				\OCP\Util::DEBUG);
1730
			return false;
1731
		}
1732
1733
		// note: cannot use the view because the target is already locked
1734
		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1735
		if ($fileId === -1) {
1736
			// target might not exist, need to check parent instead
1737
			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1738
		}
1739
1740
		// check if any of the parents were shared by the current owner (include collections)
1741
		$shares = \OCP\Share::getItemShared(
1742
			'folder',
1743
			$fileId,
1744
			\OCP\Share::FORMAT_NONE,
1745
			null,
1746
			true
1747
		);
1748
1749
		if (count($shares) > 0) {
1750
			\OCP\Util::writeLog('files',
1751
				'It is not allowed to move one mount point into a shared folder',
1752
				\OCP\Util::DEBUG);
1753
			return false;
1754
		}
1755
1756
		return true;
1757
	}
1758
1759
	/**
1760
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1761
	 *
1762
	 * @param string $path
1763
	 * @return \OCP\Files\FileInfo
1764
	 */
1765
	private function getPartFileInfo($path) {
1766
		$mount = $this->getMount($path);
1767
		$storage = $mount->getStorage();
1768
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1769
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1770
		return new FileInfo(
1771
			$this->getAbsolutePath($path),
1772
			$storage,
1773
			$internalPath,
1774
			[
1775
				'fileid' => null,
1776
				'mimetype' => $storage->getMimeType($internalPath),
1777
				'name' => basename($path),
1778
				'etag' => null,
1779
				'size' => $storage->filesize($internalPath),
1780
				'mtime' => $storage->filemtime($internalPath),
1781
				'encrypted' => false,
1782
				'permissions' => \OCP\Constants::PERMISSION_ALL
1783
			],
1784
			$mount,
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1766 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...
1785
			$owner
1786
		);
1787
	}
1788
1789
	/**
1790
	 * @param string $path
1791
	 * @param string $fileName
1792
	 * @throws InvalidPathException
1793
	 */
1794
	public function verifyPath($path, $fileName) {
1795
1796
		$l10n = \OC::$server->getL10N('lib');
1797
1798
		// verify empty and dot files
1799
		$trimmed = trim($fileName);
1800
		if ($trimmed === '') {
1801
			throw new InvalidPathException($l10n->t('Empty filename is not allowed'));
1802
		}
1803
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1804
			throw new InvalidPathException($l10n->t('Dot files are not allowed'));
1805
		}
1806
1807
		// verify database - e.g. mysql only 3-byte chars
1808
		if (preg_match('%(?:
1809
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1810
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1811
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1812
)%xs', $fileName)) {
1813
			throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names'));
1814
		}
1815
1816
		try {
1817
			/** @type \OCP\Files\Storage $storage */
1818
			list($storage, $internalPath) = $this->resolvePath($path);
1819
			$storage->verifyPath($internalPath, $fileName);
1820
		} catch (ReservedWordException $ex) {
1821
			throw new InvalidPathException($l10n->t('File name is a reserved word'));
1822
		} catch (InvalidCharacterInPathException $ex) {
1823
			throw new InvalidPathException($l10n->t('File name contains at least one invalid character'));
1824
		} catch (FileNameTooLongException $ex) {
1825
			throw new InvalidPathException($l10n->t('File name is too long'));
1826
		}
1827
	}
1828
1829
	/**
1830
	 * get all parent folders of $path
1831
	 *
1832
	 * @param string $path
1833
	 * @return string[]
1834
	 */
1835
	private function getParents($path) {
1836
		$path = trim($path, '/');
1837
		if (!$path) {
1838
			return [];
1839
		}
1840
1841
		$parts = explode('/', $path);
1842
1843
		// remove the single file
1844
		array_pop($parts);
1845
		$result = array('/');
1846
		$resultPath = '';
1847
		foreach ($parts as $part) {
1848
			if ($part) {
1849
				$resultPath .= '/' . $part;
1850
				$result[] = $resultPath;
1851
			}
1852
		}
1853
		return $result;
1854
	}
1855
1856
	/**
1857
	 * Returns the mount point for which to lock
1858
	 *
1859
	 * @param string $absolutePath absolute path
1860
	 * @param bool $useParentMount true to return parent mount instead of whatever
1861
	 * is mounted directly on the given path, false otherwise
1862
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1863
	 */
1864
	private function getMountForLock($absolutePath, $useParentMount = false) {
1865
		$results = [];
1866
		$mount = Filesystem::getMountManager()->find($absolutePath);
1867
		if (!$mount) {
1868
			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...
1869
		}
1870
1871
		if ($useParentMount) {
1872
			// find out if something is mounted directly on the path
1873
			$internalPath = $mount->getInternalPath($absolutePath);
1874
			if ($internalPath === '') {
1875
				// resolve the parent mount instead
1876
				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1877
			}
1878
		}
1879
1880
		return $mount;
1881
	}
1882
1883
	/**
1884
	 * Lock the given path
1885
	 *
1886
	 * @param string $path the path of the file to lock, relative to the view
1887
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1888
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1889
	 *
1890
	 * @return bool False if the path is excluded from locking, true otherwise
1891
	 * @throws \OCP\Lock\LockedException if the path is already locked
1892
	 */
1893 View Code Duplication
	private function lockPath($path, $type, $lockMountPoint = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1894
		$absolutePath = $this->getAbsolutePath($path);
1895
		$absolutePath = Filesystem::normalizePath($absolutePath);
1896
		if (!$this->shouldLockFile($absolutePath)) {
1897
			return false;
1898
		}
1899
1900
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1901
		if ($mount) {
1902
			try {
1903
				$storage = $mount->getStorage();
1904
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1905
					$storage->acquireLock(
1906
						$mount->getInternalPath($absolutePath),
1907
						$type,
1908
						$this->lockingProvider
1909
					);
1910
				}
1911
			} catch (\OCP\Lock\LockedException $e) {
1912
				// rethrow with the a human-readable path
1913
				throw new \OCP\Lock\LockedException(
1914
					$this->getPathRelativeToFiles($absolutePath),
1915
					$e
1916
				);
1917
			}
1918
		}
1919
1920
		return true;
1921
	}
1922
1923
	/**
1924
	 * Change the lock type
1925
	 *
1926
	 * @param string $path the path of the file to lock, relative to the view
1927
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1928
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1929
	 *
1930
	 * @return bool False if the path is excluded from locking, true otherwise
1931
	 * @throws \OCP\Lock\LockedException if the path is already locked
1932
	 */
1933 View Code Duplication
	public function changeLock($path, $type, $lockMountPoint = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1934
		$path = Filesystem::normalizePath($path);
1935
		$absolutePath = $this->getAbsolutePath($path);
1936
		$absolutePath = Filesystem::normalizePath($absolutePath);
1937
		if (!$this->shouldLockFile($absolutePath)) {
1938
			return false;
1939
		}
1940
1941
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1942
		if ($mount) {
1943
			try {
1944
				$storage = $mount->getStorage();
1945
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1946
					$storage->changeLock(
1947
						$mount->getInternalPath($absolutePath),
1948
						$type,
1949
						$this->lockingProvider
1950
					);
1951
				}
1952
			} catch (\OCP\Lock\LockedException $e) {
1953
				// rethrow with the a human-readable path
1954
				throw new \OCP\Lock\LockedException(
1955
					$this->getPathRelativeToFiles($absolutePath),
1956
					$e
1957
				);
1958
			}
1959
		}
1960
1961
		return true;
1962
	}
1963
1964
	/**
1965
	 * Unlock the given path
1966
	 *
1967
	 * @param string $path the path of the file to unlock, relative to the view
1968
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1969
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1970
	 *
1971
	 * @return bool False if the path is excluded from locking, true otherwise
1972
	 */
1973
	private function unlockPath($path, $type, $lockMountPoint = false) {
1974
		$absolutePath = $this->getAbsolutePath($path);
1975
		$absolutePath = Filesystem::normalizePath($absolutePath);
1976
		if (!$this->shouldLockFile($absolutePath)) {
1977
			return false;
1978
		}
1979
1980
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1981
		if ($mount) {
1982
			$storage = $mount->getStorage();
1983
			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1984
				$storage->releaseLock(
1985
					$mount->getInternalPath($absolutePath),
1986
					$type,
1987
					$this->lockingProvider
1988
				);
1989
			}
1990
		}
1991
1992
		return true;
1993
	}
1994
1995
	/**
1996
	 * Lock a path and all its parents up to the root of the view
1997
	 *
1998
	 * @param string $path the path of the file to lock 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 View Code Duplication
	public function lockFile($path, $type, $lockMountPoint = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2005
		$absolutePath = $this->getAbsolutePath($path);
2006
		$absolutePath = Filesystem::normalizePath($absolutePath);
2007
		if (!$this->shouldLockFile($absolutePath)) {
2008
			return false;
2009
		}
2010
2011
		$this->lockPath($path, $type, $lockMountPoint);
2012
2013
		$parents = $this->getParents($path);
2014
		foreach ($parents as $parent) {
2015
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2016
		}
2017
2018
		return true;
2019
	}
2020
2021
	/**
2022
	 * Unlock a path and all its parents up to the root of the view
2023
	 *
2024
	 * @param string $path the path of the file to lock relative to the view
2025
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2026
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2027
	 *
2028
	 * @return bool False if the path is excluded from locking, true otherwise
2029
	 */
2030 View Code Duplication
	public function unlockFile($path, $type, $lockMountPoint = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2031
		$absolutePath = $this->getAbsolutePath($path);
2032
		$absolutePath = Filesystem::normalizePath($absolutePath);
2033
		if (!$this->shouldLockFile($absolutePath)) {
2034
			return false;
2035
		}
2036
2037
		$this->unlockPath($path, $type, $lockMountPoint);
2038
2039
		$parents = $this->getParents($path);
2040
		foreach ($parents as $parent) {
2041
			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2042
		}
2043
2044
		return true;
2045
	}
2046
2047
	/**
2048
	 * Only lock files in data/user/files/
2049
	 *
2050
	 * @param string $path Absolute path to the file/folder we try to (un)lock
2051
	 * @return bool
2052
	 */
2053
	protected function shouldLockFile($path) {
2054
		$path = Filesystem::normalizePath($path);
2055
2056
		$pathSegments = explode('/', $path);
2057
		if (isset($pathSegments[2])) {
2058
			// E.g.: /username/files/path-to-file
2059
			return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
2060
		}
2061
2062
		return true;
2063
	}
2064
2065
	/**
2066
	 * Shortens the given absolute path to be relative to
2067
	 * "$user/files".
2068
	 *
2069
	 * @param string $absolutePath absolute path which is under "files"
2070
	 *
2071
	 * @return string path relative to "files" with trimmed slashes or null
2072
	 * if the path was NOT relative to files
2073
	 *
2074
	 * @throws \InvalidArgumentException if the given path was not under "files"
2075
	 * @since 8.1.0
2076
	 */
2077
	public function getPathRelativeToFiles($absolutePath) {
2078
		$path = Filesystem::normalizePath($absolutePath);
2079
		$parts = explode('/', trim($path, '/'), 3);
2080
		// "$user", "files", "path/to/dir"
2081
		if (!isset($parts[1]) || $parts[1] !== 'files') {
2082
			throw new \InvalidArgumentException('$absolutePath must be relative to "files"');
2083
		}
2084
		if (isset($parts[2])) {
2085
			return $parts[2];
2086
		}
2087
		return '';
2088
	}
2089
2090
	/**
2091
	 * @param string $filename
2092
	 * @return array
2093
	 * @throws \OC\User\NoUserException
2094
	 * @throws NotFoundException
2095
	 */
2096
	public function getUidAndFilename($filename) {
2097
		$info = $this->getFileInfo($filename);
2098
		if (!$info instanceof \OCP\Files\FileInfo) {
2099
			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2100
		}
2101
		$uid = $info->getOwner()->getUID();
2102
		if ($uid != \OCP\User::getUser()) {
0 ignored issues
show
Deprecated Code introduced by
The method OCP\User::getUser() has been deprecated with message: 8.0.0 Use \OC::$server->getUserSession()->getUser()->getUID()

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

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

Loading history...
2103
			Filesystem::initMountPoints($uid);
2104
			$ownerView = new View('/' . $uid . '/files');
2105
			try {
2106
				$filename = $ownerView->getPath($info['fileid']);
2107
			} catch (NotFoundException $e) {
2108
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2109
			}
2110
		}
2111
		return [$uid, $filename];
2112
	}
2113
2114
	/**
2115
	 * Creates parent non-existing folders
2116
	 *
2117
	 * @param string $filePath
2118
	 * @return bool
2119
	 */
2120
	private function createParentDirectories($filePath) {
2121
		$parentDirectory = dirname($filePath);
2122
		while(!$this->file_exists($parentDirectory)) {
2123
			$result = $this->createParentDirectories($parentDirectory);
2124
			if($result === false) {
2125
				return false;
2126
			}
2127
		}
2128
		$this->mkdir($filePath);
2129
		return true;
2130
	}
2131
}
2132