Completed
Push — stable8.2 ( 91aa1b...3f16fa )
by Morris
100:33
created

View::getLocalFolder()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 8

Duplication

Lines 10
Ratio 100 %

Code Coverage

Tests 3
CRAP Score 4.679
Metric Value
dl 10
loc 10
ccs 3
cts 7
cp 0.4286
rs 9.4285
cc 3
eloc 8
nc 2
nop 1
crap 4.679
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 Jesus 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 Robin Appelman <[email protected]>
18
 * @author Robin McCorkell <[email protected]>
19
 * @author Roman Geber <[email protected]>
20
 * @author Sam Tuke <[email protected]>
21
 * @author Thomas Müller <[email protected]>
22
 * @author Thomas Tanghus <[email protected]>
23
 * @author Vincent Petry <[email protected]>
24
 *
25
 * @copyright Copyright (c) 2015, ownCloud, Inc.
26
 * @license AGPL-3.0
27
 *
28
 * This code is free software: you can redistribute it and/or modify
29
 * it under the terms of the GNU Affero General Public License, version 3,
30
 * as published by the Free Software Foundation.
31
 *
32
 * This program is distributed in the hope that it will be useful,
33
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
 * GNU Affero General Public License for more details.
36
 *
37
 * You should have received a copy of the GNU Affero General Public License, version 3,
38
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
39
 *
40
 */
41
42
43
namespace OC\Files;
44
45
use Icewind\Streams\CallbackWrapper;
46
use OC\Files\Cache\Updater;
47
use OC\Files\Mount\MoveableMount;
48
use OCP\Files\FileNameTooLongException;
49
use OCP\Files\InvalidCharacterInPathException;
50
use OCP\Files\InvalidPathException;
51
use OCP\Files\ReservedWordException;
52
use OCP\Lock\ILockingProvider;
53
use OCP\Lock\LockedException;
54
55
/**
56
 * Class to provide access to ownCloud filesystem via a "view", and methods for
57
 * working with files within that view (e.g. read, write, delete, etc.). Each
58
 * view is restricted to a set of directories via a virtual root. The default view
59
 * uses the currently logged in user's data directory as root (parts of
60
 * OC_Filesystem are merely a wrapper for OC\Files\View).
61
 *
62
 * Apps that need to access files outside of the user data folders (to modify files
63
 * belonging to a user other than the one currently logged in, for example) should
64
 * use this class directly rather than using OC_Filesystem, or making use of PHP's
65
 * built-in file manipulation functions. This will ensure all hooks and proxies
66
 * are triggered correctly.
67
 *
68
 * Filesystem functions are not called directly; they are passed to the correct
69
 * \OC\Files\Storage\Storage object
70
 */
71
class View {
72
	/** @var string */
73
	private $fakeRoot = '';
74
75
	/** @var \OC\Files\Cache\Updater */
76
	protected $updater;
77
78
	/**
79
	 * @var \OCP\Lock\ILockingProvider
80
	 */
81
	private $lockingProvider;
82
83
	private $lockingEnabled;
84
85
	/**
86
	 * @param string $root
87
	 * @throws \Exception If $root contains an invalid path
88
	 */
89 1410
	public function __construct($root = '') {
90 1410
		if (is_null($root)) {
91 1
			throw new \InvalidArgumentException('Root can\'t be null');
92
		}
93 1410
		if (!Filesystem::isValidPath($root)) {
94 3
			throw new \Exception();
95
		}
96
97 1410
		$this->fakeRoot = $root;
98 1410
		$this->updater = new Updater($this);
99 1410
		$this->lockingProvider = \OC::$server->getLockingProvider();
100 1410
		$this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
101 1410
	}
102
103 955
	public function getAbsolutePath($path = '/') {
104 955
		if ($path === null) {
105 3
			return null;
106
		}
107 955
		$this->assertPathLength($path);
108 955
		if ($path === '') {
109 94
			$path = '/';
110 94
		}
111 955
		if ($path[0] !== '/') {
112 506
			$path = '/' . $path;
113 506
		}
114 955
		return $this->fakeRoot . $path;
115
	}
116
117
	/**
118
	 * change the root to a fake root
119
	 *
120
	 * @param string $fakeRoot
121
	 * @return boolean|null
122
	 */
123 50
	public function chroot($fakeRoot) {
124 50
		if (!$fakeRoot == '') {
125 48
			if ($fakeRoot[0] !== '/') {
126 13
				$fakeRoot = '/' . $fakeRoot;
127 13
			}
128 48
		}
129 50
		$this->fakeRoot = $fakeRoot;
130 50
	}
131
132
	/**
133
	 * get the fake root
134
	 *
135
	 * @return string
136
	 */
137 917
	public function getRoot() {
138 917
		return $this->fakeRoot;
139
	}
140
141
	/**
142
	 * get path relative to the root of the view
143
	 *
144
	 * @param string $path
145
	 * @return string
146
	 */
147 926
	public function getRelativePath($path) {
148 926
		$this->assertPathLength($path);
149 926
		if ($this->fakeRoot == '') {
150 899
			return $path;
151
		}
152
153 921
		if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) {
154 891
			return '/';
155
		}
156
157
		// missing slashes can cause wrong matches!
158 921
		$root = rtrim($this->fakeRoot, '/') . '/';
159
160 921
		if (strpos($path, $root) !== 0) {
161 899
			return null;
162
		} else {
163 808
			$path = substr($path, strlen($this->fakeRoot));
164 808
			if (strlen($path) === 0) {
165
				return '/';
166
			} else {
167 808
				return $path;
168
			}
169
		}
170
	}
171
172
	/**
173
	 * get the mountpoint of the storage object for a path
174
	 * ( note: because a storage is not always mounted inside the fakeroot, the
175
	 * returned mountpoint is relative to the absolute root of the filesystem
176
	 * and does not take the chroot into account )
177
	 *
178
	 * @param string $path
179
	 * @return string
180
	 */
181 1
	public function getMountPoint($path) {
182 1
		return Filesystem::getMountPoint($this->getAbsolutePath($path));
183
	}
184
185
	/**
186
	 * get the mountpoint of the storage object for a path
187
	 * ( note: because a storage is not always mounted inside the fakeroot, the
188
	 * returned mountpoint is relative to the absolute root of the filesystem
189
	 * and does not take the chroot into account )
190
	 *
191
	 * @param string $path
192
	 * @return \OCP\Files\Mount\IMountPoint
193
	 */
194 412
	public function getMount($path) {
195 412
		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
196
	}
197
198
	/**
199
	 * resolve a path to a storage and internal path
200
	 *
201
	 * @param string $path
202
	 * @return array an array consisting of the storage and the internal path
203
	 */
204 831
	public function resolvePath($path) {
205 831
		$a = $this->getAbsolutePath($path);
206 831
		$p = Filesystem::normalizePath($a);
207 831
		return Filesystem::resolvePath($p);
208
	}
209
210
	/**
211
	 * return the path to a local version of the file
212
	 * we need this because we can't know if a file is stored local or not from
213
	 * outside the filestorage and for some purposes a local file is needed
214
	 *
215
	 * @param string $path
216
	 * @return string
217
	 */
218 129 View Code Duplication
	public function getLocalFile($path) {
219 129
		$parent = substr($path, 0, strrpos($path, '/'));
220 129
		$path = $this->getAbsolutePath($path);
221 128
		list($storage, $internalPath) = Filesystem::resolvePath($path);
222 128
		if (Filesystem::isValidPath($parent) and $storage) {
223 128
			return $storage->getLocalFile($internalPath);
224
		} else {
225
			return null;
226
		}
227
	}
228
229
	/**
230
	 * @param string $path
231
	 * @return string
232
	 */
233 1 View Code Duplication
	public function getLocalFolder($path) {
234 1
		$parent = substr($path, 0, strrpos($path, '/'));
235 1
		$path = $this->getAbsolutePath($path);
236
		list($storage, $internalPath) = Filesystem::resolvePath($path);
237
		if (Filesystem::isValidPath($parent) and $storage) {
238
			return $storage->getLocalFolder($internalPath);
239
		} else {
240
			return null;
241
		}
242
	}
243
244
	/**
245
	 * the following functions operate with arguments and return values identical
246
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
247
	 * for \OC\Files\Storage\Storage via basicOperation().
248
	 */
249 797
	public function mkdir($path) {
250 797
		return $this->basicOperation('mkdir', $path, array('create', 'write'));
251
	}
252
253
	/**
254
	 * remove mount point
255
	 *
256
	 * @param \OC\Files\Mount\MoveableMount $mount
257
	 * @param string $path relative to data/
258
	 * @return boolean
259
	 */
260 7
	protected function removeMount($mount, $path) {
261 7
		if ($mount instanceof MoveableMount) {
262
			// cut of /user/files to get the relative path to data/user/files
263 5
			$pathParts = explode('/', $path, 4);
264 5
			$relPath = '/' . $pathParts[3];
265 5
			\OC_Hook::emit(
266 5
				Filesystem::CLASSNAME, "umount",
267 5
				array(Filesystem::signal_param_path => $relPath)
268 5
			);
269 5
			$result = $mount->removeMount();
270 5
			if ($result) {
271 5
				\OC_Hook::emit(
272 5
					Filesystem::CLASSNAME, "post_umount",
273 5
					array(Filesystem::signal_param_path => $relPath)
274 5
				);
275 5
			}
276 5
			return $result;
277
		} else {
278
			// do not allow deleting the storage's root / the mount point
279
			// because for some storages it might delete the whole contents
280
			// but isn't supposed to work that way
281 2
			return false;
282
		}
283
	}
284
285
	/**
286
	 * @param string $path
287
	 * @return bool|mixed
288
	 */
289 220
	public function rmdir($path) {
290 220
		$absolutePath = $this->getAbsolutePath($path);
291 218
		$mount = Filesystem::getMountManager()->find($absolutePath);
292 218
		if ($mount->getInternalPath($absolutePath) === '') {
293 1
			return $this->removeMount($mount, $path);
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...
294
		}
295 217
		if ($this->is_dir($path)) {
296 208
			return $this->basicOperation('rmdir', $path, array('delete'));
297
		} else {
298 135
			return false;
299
		}
300
	}
301
302
	/**
303
	 * @param string $path
304
	 * @return resource
305
	 */
306 302
	public function opendir($path) {
307 302
		return $this->basicOperation('opendir', $path, array('read'));
308
	}
309
310
	/**
311
	 * @param $handle
312
	 * @return mixed
313
	 */
314
	public function readdir($handle) {
315
		$fsLocal = new Storage\Local(array('datadir' => '/'));
316
		return $fsLocal->readdir($handle);
317
	}
318
319
	/**
320
	 * @param string $path
321
	 * @return bool|mixed
322
	 */
323 531
	public function is_dir($path) {
324 531
		if ($path == '/') {
325 298
			return true;
326
		}
327 520
		return $this->basicOperation('is_dir', $path);
328
	}
329
330
	/**
331
	 * @param string $path
332
	 * @return bool|mixed
333
	 */
334 31
	public function is_file($path) {
335 31
		if ($path == '/') {
336
			return false;
337 1
		}
338 31
		return $this->basicOperation('is_file', $path);
339
	}
340
341
	/**
342
	 * @param string $path
343
	 * @return mixed
344
	 */
345 5
	public function stat($path) {
346 5
		return $this->basicOperation('stat', $path);
347
	}
348
349
	/**
350
	 * @param string $path
351
	 * @return mixed
352
	 */
353 4
	public function filetype($path) {
354 4
		return $this->basicOperation('filetype', $path);
355
	}
356
357
	/**
358
	 * @param string $path
359
	 * @return mixed
360
	 */
361 61
	public function filesize($path) {
362 61
		return $this->basicOperation('filesize', $path);
363
	}
364
365
	/**
366
	 * @param string $path
367
	 * @return bool|mixed
368
	 * @throws \OCP\Files\InvalidPathException
369
	 */
370 1
	public function readfile($path) {
371 1
		$this->assertPathLength($path);
372
		@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...
373
		$handle = $this->fopen($path, 'rb');
374
		if ($handle) {
375
			$chunkSize = 8192; // 8 kB chunks
376
			while (!feof($handle)) {
377
				echo fread($handle, $chunkSize);
378
				flush();
379
			}
380
			$size = $this->filesize($path);
381
			return $size;
382
		}
383
		return false;
384
	}
385
386
	/**
387
	 * @param string $path
388
	 * @return mixed
389
	 */
390 17
	public function isCreatable($path) {
391 17
		return $this->basicOperation('isCreatable', $path);
392
	}
393
394
	/**
395
	 * @param string $path
396
	 * @return mixed
397
	 */
398 26
	public function isReadable($path) {
399 26
		return $this->basicOperation('isReadable', $path);
400
	}
401
402
	/**
403
	 * @param string $path
404
	 * @return mixed
405
	 */
406 4
	public function isUpdatable($path) {
407 4
		return $this->basicOperation('isUpdatable', $path);
408
	}
409
410
	/**
411
	 * @param string $path
412
	 * @return bool|mixed
413
	 */
414 4
	public function isDeletable($path) {
415 4
		$absolutePath = $this->getAbsolutePath($path);
416 3
		$mount = Filesystem::getMountManager()->find($absolutePath);
417 3
		if ($mount->getInternalPath($absolutePath) === '') {
418
			return $mount instanceof MoveableMount;
419
		}
420 3
		return $this->basicOperation('isDeletable', $path);
421
	}
422
423
	/**
424
	 * @param string $path
425
	 * @return mixed
426
	 */
427 145
	public function isSharable($path) {
428 145
		return $this->basicOperation('isSharable', $path);
429
	}
430
431
	/**
432
	 * @param string $path
433
	 * @return bool|mixed
434
	 */
435 919
	public function file_exists($path) {
436 919
		if ($path == '/') {
437 155
			return true;
438
		}
439 919
		return $this->basicOperation('file_exists', $path);
440
	}
441
442
	/**
443
	 * @param string $path
444
	 * @return mixed
445
	 */
446 35
	public function filemtime($path) {
447 35
		return $this->basicOperation('filemtime', $path);
448
	}
449
450
	/**
451
	 * @param string $path
452
	 * @param int|string $mtime
453
	 * @return bool
454
	 */
455 509
	public function touch($path, $mtime = null) {
456 508
		if (!is_null($mtime) and !is_numeric($mtime)) {
457
			$mtime = strtotime($mtime);
458
		}
459
460 508
		$hooks = array('touch');
461
462 509
		if (!$this->file_exists($path)) {
463 501
			$hooks[] = 'create';
464 501
			$hooks[] = 'write';
465 501
		}
466 508
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
467 508
		if (!$result) {
468
			// If create file fails because of permissions on external storage like SMB folders,
469
			// check file exists and return false if not.
470 4
			if (!$this->file_exists($path)) {
471 2
				return false;
472
			}
473 2
			if (is_null($mtime)) {
474
				$mtime = time();
475
			}
476
			//if native touch fails, we emulate it by changing the mtime in the cache
477 2
			$this->putFileInfo($path, array('mtime' => $mtime));
478 2
		}
479 508
		return true;
480
	}
481
482
	/**
483
	 * @param string $path
484
	 * @return mixed
485
	 */
486 96
	public function file_get_contents($path) {
487 96
		return $this->basicOperation('file_get_contents', $path, array('read'));
488
	}
489
490
	/**
491
	 * @param bool $exists
492
	 * @param string $path
493
	 * @param bool $run
494
	 */
495 7
	protected function emit_file_hooks_pre($exists, $path, &$run) {
496 7 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...
497 7
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array(
498 7
				Filesystem::signal_param_path => $this->getHookPath($path),
499 7
				Filesystem::signal_param_run => &$run,
500 7
			));
501 7
		} else {
502
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array(
503
				Filesystem::signal_param_path => $this->getHookPath($path),
504
				Filesystem::signal_param_run => &$run,
505
			));
506
		}
507 7
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array(
508 7
			Filesystem::signal_param_path => $this->getHookPath($path),
509 7
			Filesystem::signal_param_run => &$run,
510 7
		));
511 7
	}
512
513
	/**
514
	 * @param bool $exists
515
	 * @param string $path
516
	 */
517 8
	protected function emit_file_hooks_post($exists, $path) {
518 8 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...
519 7
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array(
520 7
				Filesystem::signal_param_path => $this->getHookPath($path),
521 7
			));
522 7
		} else {
523 1
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
524 1
				Filesystem::signal_param_path => $this->getHookPath($path),
525 1
			));
526
		}
527 8
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
528 8
			Filesystem::signal_param_path => $this->getHookPath($path),
529 8
		));
530 8
	}
531
532
	/**
533
	 * @param string $path
534
	 * @param mixed $data
535
	 * @return bool|mixed
536
	 */
537 442
	public function file_put_contents($path, $data) {
538 442
		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
539 10
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
540 10
			if (Filesystem::isValidPath($path)
541 10
				and !Filesystem::isFileBlacklisted($path)
542 10
			) {
543 10
				$path = $this->getRelativePath($absolutePath);
544
545 10
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
546
547 10
				$exists = $this->file_exists($path);
548 10
				$run = true;
549 10
				if ($this->shouldEmitHooks($path)) {
550 1
					$this->emit_file_hooks_pre($exists, $path, $run);
551 1
				}
552 10
				if (!$run) {
553
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
554
					return false;
555
				}
556
557 10
				$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
558
559
				/** @var \OC\Files\Storage\Storage $storage */
560 10
				list($storage, $internalPath) = $this->resolvePath($path);
561 10
				$target = $storage->fopen($internalPath, 'w');
562 10
				if ($target) {
563 10
					list (, $result) = \OC_Helper::streamCopy($data, $target);
564 10
					fclose($target);
565 10
					fclose($data);
566
567 10
					$this->updater->update($path);
568
569 10
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
570
571 10
					if ($this->shouldEmitHooks($path) && $result !== false) {
572 1
						$this->emit_file_hooks_post($exists, $path);
573 1
					}
574 10
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
575 10
					return $result;
576
				} else {
577
					$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
578
					return false;
579
				}
580
			} else {
581
				return false;
582
			}
583
		} else {
584 437
			$hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write');
585 436
			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
586
		}
587
	}
588
589
	/**
590
	 * @param string $path
591
	 * @return bool|mixed
592
	 */
593 139
	public function unlink($path) {
594 139
		if ($path === '' || $path === '/') {
595
			// do not allow deleting the root
596 1
			return false;
597
		}
598 139
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
599 139
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
600 138
		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
601 138
		if ($mount and $mount->getInternalPath($absolutePath) === '') {
602 6
			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...
603
		}
604 136
		return $this->basicOperation('unlink', $path, array('delete'));
605
	}
606
607
	/**
608
	 * @param string $directory
609
	 * @return bool|mixed
610
	 */
611 207
	public function deleteAll($directory) {
612 207
		return $this->rmdir($directory);
613
	}
614
615
	/**
616
	 * Rename/move a file or folder from the source path to target path.
617
	 *
618
	 * @param string $path1 source path
619
	 * @param string $path2 target path
620
	 *
621
	 * @return bool|mixed
622
	 */
623 116
	public function rename($path1, $path2) {
624 116
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
625 115
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
626 115
		$result = false;
627
		if (
628 115
			Filesystem::isValidPath($path2)
629 115
			and Filesystem::isValidPath($path1)
630 115
			and !Filesystem::isFileBlacklisted($path2)
631 115
		) {
632 115
			$path1 = $this->getRelativePath($absolutePath1);
633 115
			$path2 = $this->getRelativePath($absolutePath2);
634 115
			$exists = $this->file_exists($path2);
635
636 115
			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...
637
				return false;
638
			}
639
640 115
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
641
			try {
642 115
				$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
643 115
			} catch (LockedException $e) {
644 1
				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
645 1
				throw $e;
646
			}
647
648 114
			$run = true;
649 114
			if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
650
				// if it was a rename from a part file to a regular file it was a write and not a rename operation
651
				$this->emit_file_hooks_pre($exists, $path2, $run);
652 114
			} elseif ($this->shouldEmitHooks($path1)) {
653 66
				\OC_Hook::emit(
654 66
					Filesystem::CLASSNAME, Filesystem::signal_rename,
655
					array(
656 66
						Filesystem::signal_param_oldpath => $this->getHookPath($path1),
657 66
						Filesystem::signal_param_newpath => $this->getHookPath($path2),
658 66
						Filesystem::signal_param_run => &$run
659 66
					)
660 66
				);
661 66
			}
662 114
			if ($run) {
663 114
				$this->verifyPath(dirname($path2), basename($path2));
664
665 114
				$manager = Filesystem::getMountManager();
666 114
				$mount1 = $this->getMount($path1);
667 114
				$mount2 = $this->getMount($path2);
668 114
				$storage1 = $mount1->getStorage();
669 114
				$storage2 = $mount2->getStorage();
670 114
				$internalPath1 = $mount1->getInternalPath($absolutePath1);
671 114
				$internalPath2 = $mount2->getInternalPath($absolutePath2);
672
673 114
				$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
674 113
				$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
675
676 113
				if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
677 46
					if ($this->isTargetAllowed($absolutePath2)) {
678
						/**
679
						 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
680
						 */
681 44
						$sourceMountPoint = $mount1->getMountPoint();
682 44
						$result = $mount1->moveMount($absolutePath2);
683 44
						$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
684 44
					} else {
685 2
						$result = false;
686
					}
687
				// moving a file/folder within the same mount point
688 113
				} elseif ($storage1 == $storage2) {
689 72
					if ($storage1) {
690 72
						$result = $storage1->rename($internalPath1, $internalPath2);
691 72
					} else {
692
						$result = false;
693
					}
694
				// moving a file/folder between storages (from $storage1 to $storage2)
695 72
				} else {
696 14
					$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
697
				}
698
699 113
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
700
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
701 29
					$this->updater->update($path2);
702 113
				} else if ($result) {
703 79
					if ($internalPath1 !== '') { // dont do a cache update for moved mounts
704 49
						$this->updater->rename($path1, $path2);
705 49
					} else { // only do etag propagation
706 44
						$this->getUpdater()->getPropagator()->addChange($path1);
707 44
						$this->getUpdater()->getPropagator()->addChange($path2);
708 44
						$this->getUpdater()->getPropagator()->propagateChanges();
709
					}
710 79
				}
711
712 113
				$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
713 113
				$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
714
715 113
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
716 29
					if ($this->shouldEmitHooks()) {
717 2
						$this->emit_file_hooks_post($exists, $path2);
718 2
					}
719 113
				} elseif ($result) {
720 79
					if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
721 60
						\OC_Hook::emit(
722 60
							Filesystem::CLASSNAME,
723 60
							Filesystem::signal_post_rename,
724
							array(
725 60
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
726 60
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
727 60
							)
728 60
						);
729 60
					}
730 79
				}
731 113
			}
732 113
			$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
733 113
			$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
734 113
		}
735 113
		return $result;
736
	}
737
738
	/**
739
	 * Copy a file/folder from the source path to target path
740
	 *
741
	 * @param string $path1 source path
742
	 * @param string $path2 target path
743
	 * @param bool $preserveMtime whether to preserve mtime on the copy
744
	 *
745
	 * @return bool|mixed
746
	 */
747 25
	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...
748 25
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
749 24
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
750 24
		$result = false;
751
		if (
752 24
			Filesystem::isValidPath($path2)
753 24
			and Filesystem::isValidPath($path1)
754 24
			and !Filesystem::isFileBlacklisted($path2)
755 24
		) {
756 24
			$path1 = $this->getRelativePath($absolutePath1);
757 24
			$path2 = $this->getRelativePath($absolutePath2);
758
759 24
			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...
760
				return false;
761
			}
762 24
			$run = true;
763
764 24
			$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
765 24
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
766 24
			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
767 24
			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
768
769
			try {
770
771 24
				$exists = $this->file_exists($path2);
772 24 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...
773 6
					\OC_Hook::emit(
774 6
						Filesystem::CLASSNAME,
775 6
						Filesystem::signal_copy,
776
						array(
777 6
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
778 6
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
779 6
							Filesystem::signal_param_run => &$run
780 6
						)
781 6
					);
782 6
					$this->emit_file_hooks_pre($exists, $path2, $run);
783 6
				}
784 24
				if ($run) {
785 24
					$mount1 = $this->getMount($path1);
786 24
					$mount2 = $this->getMount($path2);
787 24
					$storage1 = $mount1->getStorage();
788 24
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
789 24
					$storage2 = $mount2->getStorage();
790 24
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
791
792 24
					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
793 24
					$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
794
795 24
					if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
796 16
						if ($storage1) {
797 16
							$result = $storage1->copy($internalPath1, $internalPath2);
798 15
						} else {
799
							$result = false;
800
						}
801 15
					} else {
802 10
						$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
803
					}
804
805 23
					$this->updater->update($path2);
806
807 23
					$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
808 23
					$lockTypePath2 = ILockingProvider::LOCK_SHARED;
809
810 23 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...
811 5
						\OC_Hook::emit(
812 5
							Filesystem::CLASSNAME,
813 5
							Filesystem::signal_post_copy,
814
							array(
815 5
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
816 5
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
817 5
							)
818 5
						);
819 5
						$this->emit_file_hooks_post($exists, $path2);
820 5
					}
821
822 23
				}
823 24
			} catch (\Exception $e) {
824 1
				$this->unlockFile($path2, $lockTypePath2);
825 1
				$this->unlockFile($path1, $lockTypePath1);
826 1
				throw $e;
827
			}
828
829 23
			$this->unlockFile($path2, $lockTypePath2);
830 23
			$this->unlockFile($path1, $lockTypePath1);
831
832 23
		}
833 23
		return $result;
834
	}
835
836
	/**
837
	 * @param string $path
838
	 * @param string $mode
839
	 * @return resource
840
	 */
841 567
	public function fopen($path, $mode) {
842 567
		$hooks = array();
843
		switch ($mode) {
844 567
			case 'r':
845 567
			case 'rb':
846 91
				$hooks[] = 'read';
847 91
				break;
848 501
			case 'r+':
849 501
			case 'rb+':
850 501
			case 'w+':
851 501
			case 'wb+':
852 501
			case 'x+':
853 501
			case 'xb+':
854 501
			case 'a+':
855 501
			case 'ab+':
856
				$hooks[] = 'read';
857
				$hooks[] = 'write';
858
				break;
859 501
			case 'w':
860 501
			case 'wb':
861 501
			case 'x':
862 501
			case 'xb':
863 501
			case 'a':
864 501
			case 'ab':
865 501
				$hooks[] = 'write';
866 501
				break;
867
			default:
868
				\OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, \OCP\Util::ERROR);
869
		}
870
871 567
		return $this->basicOperation('fopen', $path, $hooks, $mode);
872
	}
873
874
	/**
875
	 * @param string $path
876
	 * @return bool|string
877
	 * @throws \OCP\Files\InvalidPathException
878
	 */
879 49
	public function toTmpFile($path) {
880 49
		$this->assertPathLength($path);
881 48
		if (Filesystem::isValidPath($path)) {
882 48
			$source = $this->fopen($path, 'r');
883 48
			if ($source) {
884 48
				$extension = pathinfo($path, PATHINFO_EXTENSION);
885 48
				$tmpFile = \OC_Helper::tmpFile($extension);
886 48
				file_put_contents($tmpFile, $source);
887 48
				return $tmpFile;
888
			} else {
889
				return false;
890
			}
891
		} else {
892
			return false;
893
		}
894
	}
895
896
	/**
897
	 * @param string $tmpFile
898
	 * @param string $path
899
	 * @return bool|mixed
900
	 * @throws \OCP\Files\InvalidPathException
901
	 */
902 1
	public function fromTmpFile($tmpFile, $path) {
903 1
		$this->assertPathLength($path);
904
		if (Filesystem::isValidPath($path)) {
905
906
			// Get directory that the file is going into
907
			$filePath = dirname($path);
908
909
			// Create the directories if any
910
			if (!$this->file_exists($filePath)) {
911
				$this->mkdir($filePath);
912
			}
913
914
			$source = fopen($tmpFile, 'r');
915
			if ($source) {
916
				$result = $this->file_put_contents($path, $source);
917
				// $this->file_put_contents() might have already closed
918
				// the resource, so we check it, before trying to close it
919
				// to avoid messages in the error log.
920
				if (is_resource($source)) {
921
					fclose($source);
922
				}
923
				unlink($tmpFile);
924
				return $result;
925
			} else {
926
				return false;
927
			}
928
		} else {
929
			return false;
930
		}
931
	}
932
933
934
	/**
935
	 * @param string $path
936
	 * @return mixed
937
	 * @throws \OCP\Files\InvalidPathException
938
	 */
939 2
	public function getMimeType($path) {
940 2
		$this->assertPathLength($path);
941 1
		return $this->basicOperation('getMimeType', $path);
942
	}
943
944
	/**
945
	 * @param string $type
946
	 * @param string $path
947
	 * @param bool $raw
948
	 * @return bool|null|string
949
	 */
950 1
	public function hash($type, $path, $raw = false) {
951 1
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
952 1
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
953
		if (Filesystem::isValidPath($path)) {
954
			$path = $this->getRelativePath($absolutePath);
955
			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...
956
				return false;
957
			}
958
			if ($this->shouldEmitHooks($path)) {
959
				\OC_Hook::emit(
960
					Filesystem::CLASSNAME,
961
					Filesystem::signal_read,
962
					array(Filesystem::signal_param_path => $this->getHookPath($path))
963
				);
964
			}
965
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
966
			if ($storage) {
967
				$result = $storage->hash($type, $internalPath, $raw);
968
				return $result;
969
			}
970
		}
971
		return null;
972
	}
973
974
	/**
975
	 * @param string $path
976
	 * @return mixed
977
	 * @throws \OCP\Files\InvalidPathException
978
	 */
979 48
	public function free_space($path = '/') {
980 48
		$this->assertPathLength($path);
981 47
		return $this->basicOperation('free_space', $path);
982
	}
983
984
	/**
985
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
986
	 *
987
	 * @param string $operation
988
	 * @param string $path
989
	 * @param array $hooks (optional)
990
	 * @param mixed $extraParam (optional)
991
	 * @return mixed
992
	 *
993
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
994
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
995
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
996
	 */
997 926
	private function basicOperation($operation, $path, $hooks = array(), $extraParam = null) {
998 926
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
999 926
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1000 926
		if (Filesystem::isValidPath($path)
1001 926
			and !Filesystem::isFileBlacklisted($path)
1002 926
		) {
1003 926
			$path = $this->getRelativePath($absolutePath);
1004 926
			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...
1005 13
				return false;
1006
			}
1007
1008 926
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1009
				// always a shared lock during pre-hooks so the hook can read the file
1010 925
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1011 925
			}
1012
1013 926
			$run = $this->runHooks($hooks, $path);
1014 926
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1015 926
			if ($run and $storage) {
1016 926
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1017 816
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1018 816
				}
1019
				try {
1020 926
					if (!is_null($extraParam)) {
1021 797
						$result = $storage->$operation($internalPath, $extraParam);
1022 797
					} else {
1023 926
						$result = $storage->$operation($internalPath);
1024
					}
1025 926
				} catch (\Exception $e) {
1026 24 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...
1027 8
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1028 24
					} else if (in_array('read', $hooks)) {
1029 3
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1030 3
					}
1031 24
					throw $e;
1032
				}
1033
1034 926
				if (in_array('delete', $hooks) and $result) {
1035 272
					$this->updater->remove($path);
1036 272
				}
1037 926
				if (in_array('write', $hooks) and $operation !== 'fopen') {
1038 815
					$this->updater->update($path);
1039 815
				}
1040 926
				if (in_array('touch', $hooks)) {
1041 508
					$this->updater->update($path, $extraParam);
1042 508
				}
1043
1044 926
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1045 816
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1046 816
				}
1047
1048 926
				$unlockLater = false;
1049 926
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1050 565
					$unlockLater = true;
1051 565
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1052 565 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...
1053 499
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1054 565
						} else if (in_array('read', $hooks)) {
1055 86
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1056 86
						}
1057 565
					});
1058 565
				}
1059
1060 926
				if ($this->shouldEmitHooks($path) && $result !== false) {
1061 908
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1062 908
						$this->runHooks($hooks, $path, true);
1063 908
					}
1064 908
				}
1065
1066 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...
1067 926
					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1068 926
				) {
1069 925
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1070 925
				}
1071 926
				return $result;
1072
			} else {
1073 6
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1074
			}
1075 6
		}
1076 8
		return null;
1077
	}
1078
1079
	/**
1080
	 * get the path relative to the default root for hook usage
1081
	 *
1082
	 * @param string $path
1083
	 * @return string
1084
	 */
1085 926
	private function getHookPath($path) {
1086 926
		if (!Filesystem::getView()) {
1087 10
			return $path;
1088
		}
1089 917
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1090
	}
1091
1092 926
	private function shouldEmitHooks($path = '') {
1093 926
		if ($path && Cache\Scanner::isPartialFile($path)) {
1094 32
			return false;
1095
		}
1096 926
		if (!Filesystem::$loaded) {
1097 2
			return false;
1098
		}
1099 924
		$defaultRoot = Filesystem::getRoot();
1100 924
		if ($defaultRoot === null) {
1101 8
			return false;
1102
		}
1103 917
		if ($this->fakeRoot === $defaultRoot) {
1104 249
			return true;
1105
		}
1106 899
		$fullPath = $this->getAbsolutePath($path);
1107
1108 899
		if ($fullPath === $defaultRoot) {
1109 885
			return true;
1110
		}
1111
1112 899
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1113
	}
1114
1115
	/**
1116
	 * @param string[] $hooks
1117
	 * @param string $path
1118
	 * @param bool $post
1119
	 * @return bool
1120
	 */
1121 926
	private function runHooks($hooks, $path, $post = false) {
1122 926
		$relativePath = $path;
1123 926
		$path = $this->getHookPath($path);
1124 926
		$prefix = ($post) ? 'post_' : '';
1125 926
		$run = true;
1126 926
		if ($this->shouldEmitHooks($relativePath)) {
1127 908
			foreach ($hooks as $hook) {
1128 794
				if ($hook != 'read') {
1129 794
					\OC_Hook::emit(
1130 794
						Filesystem::CLASSNAME,
1131 794
						$prefix . $hook,
1132
						array(
1133 794
							Filesystem::signal_param_run => &$run,
1134 794
							Filesystem::signal_param_path => $path
1135 794
						)
1136 794
					);
1137 794
				} elseif (!$post) {
1138 89
					\OC_Hook::emit(
1139 89
						Filesystem::CLASSNAME,
1140 89
						$prefix . $hook,
1141
						array(
1142 89
							Filesystem::signal_param_path => $path
1143 89
						)
1144 89
					);
1145 89
				}
1146 908
			}
1147 908
		}
1148 926
		return $run;
1149
	}
1150
1151
	/**
1152
	 * check if a file or folder has been updated since $time
1153
	 *
1154
	 * @param string $path
1155
	 * @param int $time
1156
	 * @return bool
1157
	 */
1158 1
	public function hasUpdated($path, $time) {
1159 1
		return $this->basicOperation('hasUpdated', $path, array(), $time);
1160
	}
1161
1162
	/**
1163
	 * get the filesystem info
1164
	 *
1165
	 * @param string $path
1166
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1167
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1168
	 * defaults to true
1169
	 * @return \OC\Files\FileInfo|false
1170
	 */
1171 809
	public function getFileInfo($path, $includeMountPoints = true) {
1172 809
		$this->assertPathLength($path);
1173 809
		$data = array();
1174 809
		if (!Filesystem::isValidPath($path)) {
1175
			return $data;
1176
		}
1177 809
		if (Cache\Scanner::isPartialFile($path)) {
1178 1
			return $this->getPartFileInfo($path);
1179
		}
1180 809
		$relativePath = $path;
1181 809
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1182
1183 809
		$mount = Filesystem::getMountManager()->find($path);
1184 809
		$storage = $mount->getStorage();
1185 809
		$internalPath = $mount->getInternalPath($path);
1186 809
		$data = null;
1187 809
		if ($storage) {
1188 809
			$cache = $storage->getCache($internalPath);
1189
1190 809
			$data = $cache->get($internalPath);
1191 809
			$watcher = $storage->getWatcher($internalPath);
1192
1193
			try {
1194
				// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1195 809
				if (!$data) {
1196 194
					$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1197 192
					if (!$storage->file_exists($internalPath)) {
1198 178
						$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1199 178
						return false;
1200
					}
1201 29
					$scanner = $storage->getScanner($internalPath);
1202 29
					$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1203 29
					$data = $cache->get($internalPath);
1204 29
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1205 807 View Code Duplication
				} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
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...
1206 3
					$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1207 3
					$watcher->update($internalPath, $data);
1208 3
					$this->updater->propagate($path);
1209 3
					$data = $cache->get($internalPath);
1210 3
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1211 3
				}
1212 807
			} catch (LockedException $e) {
1213
				// if the file is locked we just use the old cache info
1214
			}
1215
1216 807
			if ($data and isset($data['fileid'])) {
1217
				// upgrades from oc6 or lower might not have the permissions set in the file cache
1218 807 View Code Duplication
				if ($data['permissions'] === 0) {
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...
1219
					$data['permissions'] = $storage->getPermissions($data['path']);
1220
					$cache->update($data['fileid'], array('permissions' => $data['permissions']));
1221
				}
1222 807
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1223
					//add the sizes of other mount points to the folder
1224 618
					$extOnly = ($includeMountPoints === 'ext');
1225 618
					$mountPoints = Filesystem::getMountPoints($path);
1226 618
					foreach ($mountPoints as $mountPoint) {
1227 58
						$subStorage = Filesystem::getStorage($mountPoint);
1228 58
						if ($subStorage) {
1229
							// exclude shared storage ?
1230 58
							if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) {
1231
								continue;
1232
							}
1233 58
							$subCache = $subStorage->getCache('');
1234 58
							$rootEntry = $subCache->get('');
1235 58
							$data['size'] += isset($rootEntry['size']) ? $rootEntry['size'] : 0;
1236 58
						}
1237 618
					}
1238 618
				}
1239 807
			}
1240 807
		}
1241 807
		if (!$data) {
1242 2
			return false;
1243
		}
1244
1245 807
		if ($mount instanceof MoveableMount && $internalPath === '') {
1246 30
			$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1247 30
		}
1248
1249 807
		return new FileInfo($path, $storage, $internalPath, $data, $mount);
0 ignored issues
show
Bug introduced by
It seems like $mount defined by \OC\Files\Filesystem::ge...tManager()->find($path) on line 1183 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...
1250
	}
1251
1252
	/**
1253
	 * get the content of a directory
1254
	 *
1255
	 * @param string $directory path under datadirectory
1256
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1257
	 * @return FileInfo[]
1258
	 */
1259 200
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1260 200
		$this->assertPathLength($directory);
1261 199
		$result = array();
1262 199
		if (!Filesystem::isValidPath($directory)) {
1263
			return $result;
1264
		}
1265 199
		$path = $this->getAbsolutePath($directory);
1266 199
		$path = Filesystem::normalizePath($path);
1267 199
		$mount = $this->getMount($directory);
1268 199
		$storage = $mount->getStorage();
1269 199
		$internalPath = $mount->getInternalPath($path);
1270 199
		if ($storage) {
1271 199
			$cache = $storage->getCache($internalPath);
1272 199
			$user = \OC_User::getUser();
1273
1274
			/**
1275
			 * @var \OC\Files\FileInfo[] $files
1276
			 */
1277 199
			$files = array();
1278
1279 199
			$data = $cache->get($internalPath);
1280 199
			$watcher = $storage->getWatcher($internalPath);
1281
			try {
1282 199
				if (!$data or $data['size'] === -1) {
1283 177
					$this->lockFile($directory, ILockingProvider::LOCK_SHARED);
1284 177
					if (!$storage->file_exists($internalPath)) {
1285 174
						$this->unlockFile($directory, ILockingProvider::LOCK_SHARED);
1286 174
						return array();
1287
					}
1288 3
					$scanner = $storage->getScanner($internalPath);
1289 3
					$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1290 3
					$data = $cache->get($internalPath);
1291 3
					$this->unlockFile($directory, ILockingProvider::LOCK_SHARED);
1292 197 View Code Duplication
				} else if ($watcher->needsUpdate($internalPath, $data)) {
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...
1293
					$this->lockFile($directory, ILockingProvider::LOCK_SHARED);
1294
					$watcher->update($internalPath, $data);
1295
					$this->updater->propagate($path);
1296
					$data = $cache->get($internalPath);
1297
					$this->unlockFile($directory, ILockingProvider::LOCK_SHARED);
1298
				}
1299 197
			} catch (LockedException $e) {
1300
				// if the file is locked we just use the old cache info
1301
			}
1302
1303 197
			$folderId = $data['fileid'];
1304 197
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1305
1306 197
			foreach ($contents as $content) {
1307 127 View Code Duplication
				if ($content['permissions'] === 0) {
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...
1308
					$content['permissions'] = $storage->getPermissions($content['path']);
1309
					$cache->update($content['fileid'], array('permissions' => $content['permissions']));
1310
				}
1311
				// if sharing was disabled for the user we remove the share permissions
1312 127
				if (\OCP\Util::isSharingDisabledForUser()) {
1313 1
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1314 1
				}
1315 127
				$files[] = new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content, $mount);
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($directory) on line 1267 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...
1316 197
			}
1317
1318
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1319 197
			$mounts = Filesystem::getMountManager()->findIn($path);
1320 197
			$dirLength = strlen($path);
1321 197
			foreach ($mounts as $mount) {
1322 11
				$mountPoint = $mount->getMountPoint();
1323 11
				$subStorage = $mount->getStorage();
1324 11
				if ($subStorage) {
1325 11
					$subCache = $subStorage->getCache('');
1326
1327 11
					if ($subCache->getStatus('') === Cache\Cache::NOT_FOUND) {
1328 2
						$subScanner = $subStorage->getScanner('');
1329
						try {
1330 2
							$subScanner->scanFile('');
1331 2
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1332
							continue;
1333
						} catch (\OCP\Files\StorageInvalidException $e) {
1334
							continue;
1335
						} catch (\Exception $e) {
1336
							// sometimes when the storage is not available it can be any exception
1337
							\OCP\Util::writeLog(
1338
								'core',
1339
								'Exception while scanning storage "' . $subStorage->getId() . '": ' .
1340
								get_class($e) . ': ' . $e->getMessage(),
1341
								\OCP\Util::ERROR
1342
							);
1343
							continue;
1344
						}
1345 2
					}
1346
1347 11
					$rootEntry = $subCache->get('');
1348 11
					if ($rootEntry) {
1349 11
						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1350 11
						if ($pos = strpos($relativePath, '/')) {
1351
							//mountpoint inside subfolder add size to the correct folder
1352 1
							$entryName = substr($relativePath, 0, $pos);
1353 1
							foreach ($files as &$entry) {
1354 1
								if ($entry['name'] === $entryName) {
1355 1
									$entry['size'] += $rootEntry['size'];
1356 1
								}
1357 1
							}
1358 1
						} else { //mountpoint in this folder, add an entry for it
1359 11
							$rootEntry['name'] = $relativePath;
1360 11
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1361 11
							$permissions = $rootEntry['permissions'];
1362
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1363
							// for shared files/folders we use the permissions given by the owner
1364 11
							if ($mount instanceof MoveableMount) {
1365 5
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1366 5
							} else {
1367 6
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1368
							}
1369
1370
							//remove any existing entry with the same name
1371 11
							foreach ($files as $i => $file) {
1372 11
								if ($file['name'] === $rootEntry['name']) {
1373 1
									unset($files[$i]);
1374 1
									break;
1375
								}
1376 11
							}
1377 11
							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1378
1379
							// if sharing was disabled for the user we remove the share permissions
1380 11
							if (\OCP\Util::isSharingDisabledForUser()) {
1381 1
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1382 1
							}
1383
1384 11
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount);
1385
						}
1386 11
					}
1387 11
				}
1388 197
			}
1389
1390 197
			if ($mimetype_filter) {
1391
				foreach ($files as $file) {
1392
					if (strpos($mimetype_filter, '/')) {
1393
						if ($file['mimetype'] === $mimetype_filter) {
1394
							$result[] = $file;
1395
						}
1396
					} else {
1397
						if ($file['mimepart'] === $mimetype_filter) {
1398
							$result[] = $file;
1399
						}
1400
					}
1401
				}
1402
			} else {
1403 197
				$result = $files;
1404
			}
1405 197
		}
1406
1407 197
		return $result;
1408
	}
1409
1410
	/**
1411
	 * change file metadata
1412
	 *
1413
	 * @param string $path
1414
	 * @param array|\OCP\Files\FileInfo $data
1415
	 * @return int
1416
	 *
1417
	 * returns the fileid of the updated file
1418
	 */
1419 32
	public function putFileInfo($path, $data) {
1420 32
		$this->assertPathLength($path);
1421 31
		if ($data instanceof FileInfo) {
1422
			$data = $data->getData();
1423
		}
1424 31
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1425
		/**
1426
		 * @var \OC\Files\Storage\Storage $storage
1427
		 * @var string $internalPath
1428
		 */
1429 31
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1430 31
		if ($storage) {
1431 31
			$cache = $storage->getCache($path);
1432
1433 31
			if (!$cache->inCache($internalPath)) {
1434
				$scanner = $storage->getScanner($internalPath);
1435
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1436
			}
1437
1438 31
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1419 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...
1439
		} else {
1440
			return -1;
1441
		}
1442
	}
1443
1444
	/**
1445
	 * search for files with the name matching $query
1446
	 *
1447
	 * @param string $query
1448
	 * @return FileInfo[]
1449
	 */
1450 2
	public function search($query) {
1451 2
		return $this->searchCommon('search', array('%' . $query . '%'));
1452
	}
1453
1454
	/**
1455
	 * search for files with the name matching $query
1456
	 *
1457
	 * @param string $query
1458
	 * @return FileInfo[]
1459
	 */
1460 10
	public function searchRaw($query) {
1461 10
		return $this->searchCommon('search', array($query));
1462
	}
1463
1464
	/**
1465
	 * search for files by mimetype
1466
	 *
1467
	 * @param string $mimetype
1468
	 * @return FileInfo[]
1469
	 */
1470 1
	public function searchByMime($mimetype) {
1471 1
		return $this->searchCommon('searchByMime', array($mimetype));
1472
	}
1473
1474
	/**
1475
	 * search for files by tag
1476
	 *
1477
	 * @param string|int $tag name or tag id
1478
	 * @param string $userId owner of the tags
1479
	 * @return FileInfo[]
1480
	 */
1481
	public function searchByTag($tag, $userId) {
1482
		return $this->searchCommon('searchByTag', array($tag, $userId));
1483
	}
1484
1485
	/**
1486
	 * @param string $method cache method
1487
	 * @param array $args
1488
	 * @return FileInfo[]
1489
	 */
1490 12
	private function searchCommon($method, $args) {
1491 12
		$files = array();
1492 12
		$rootLength = strlen($this->fakeRoot);
1493
1494 12
		$mount = $this->getMount('');
1495 12
		$mountPoint = $mount->getMountPoint();
1496 12
		$storage = $mount->getStorage();
1497 12
		if ($storage) {
1498 12
			$cache = $storage->getCache('');
1499
1500 12
			$results = call_user_func_array(array($cache, $method), $args);
1501 12
			foreach ($results as $result) {
1502 2
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1503 2
					$internalPath = $result['path'];
1504 2
					$path = $mountPoint . $result['path'];
1505 2
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1506 2
					$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount);
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount('') on line 1494 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...
1507 2
				}
1508 12
			}
1509
1510 12
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1511 12
			foreach ($mounts as $mount) {
1512 1
				$mountPoint = $mount->getMountPoint();
1513 1
				$storage = $mount->getStorage();
1514 1
				if ($storage) {
1515 1
					$cache = $storage->getCache('');
1516
1517 1
					$relativeMountPoint = substr($mountPoint, $rootLength);
1518 1
					$results = call_user_func_array(array($cache, $method), $args);
1519 1
					if ($results) {
1520 1
						foreach ($results as $result) {
1521 1
							$internalPath = $result['path'];
1522 1
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1523 1
							$path = rtrim($mountPoint . $internalPath, '/');
1524 1
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount);
1525 1
						}
1526 1
					}
1527 1
				}
1528 12
			}
1529 12
		}
1530 12
		return $files;
1531
	}
1532
1533
	/**
1534
	 * Get the owner for a file or folder
1535
	 *
1536
	 * @param string $path
1537
	 * @return string
1538
	 */
1539 120
	public function getOwner($path) {
1540 120
		return $this->basicOperation('getOwner', $path);
1541
	}
1542
1543
	/**
1544
	 * get the ETag for a file or folder
1545
	 *
1546
	 * @param string $path
1547
	 * @return string
1548
	 */
1549 28
	public function getETag($path) {
1550
		/**
1551
		 * @var Storage\Storage $storage
1552
		 * @var string $internalPath
1553
		 */
1554 28
		list($storage, $internalPath) = $this->resolvePath($path);
1555 27
		if ($storage) {
1556 27
			return $storage->getETag($internalPath);
1557
		} else {
1558
			return null;
1559
		}
1560
	}
1561
1562
	/**
1563
	 * Get the path of a file by id, relative to the view
1564
	 *
1565
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1566
	 *
1567
	 * @param int $id
1568
	 * @return string|null
1569
	 */
1570 147
	public function getPath($id) {
1571 147
		$id = (int)$id;
1572 147
		$manager = Filesystem::getMountManager();
1573 147
		$mounts = $manager->findIn($this->fakeRoot);
1574 147
		$mounts[] = $manager->find($this->fakeRoot);
1575
		// reverse the array so we start with the storage this view is in
1576
		// which is the most likely to contain the file we're looking for
1577 147
		$mounts = array_reverse($mounts);
1578 147 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...
1579
			/**
1580
			 * @var \OC\Files\Mount\MountPoint $mount
1581
			 */
1582 147
			if ($mount->getStorage()) {
1583 147
				$cache = $mount->getStorage()->getCache();
1584 147
				$internalPath = $cache->getPathById($id);
1585 147
				if (is_string($internalPath)) {
1586 144
					$fullPath = $mount->getMountPoint() . $internalPath;
1587 144
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1588 144
						return $path;
1589
					}
1590 2
				}
1591 42
			}
1592 42
		}
1593 8
		return null;
1594
	}
1595
1596 955
	private function assertPathLength($path) {
1597 955
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1598
		// Check for the string length - performed using isset() instead of strlen()
1599
		// because isset() is about 5x-40x faster.
1600 955
		if (isset($path[$maxLen])) {
1601 41
			$pathLen = strlen($path);
1602 41
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1603
		}
1604 955
	}
1605
1606
	/**
1607
	 * check if it is allowed to move a mount point to a given target.
1608
	 * It is not allowed to move a mount point into a different mount point or
1609
	 * into an already shared folder
1610
	 *
1611
	 * @param string $target path
1612
	 * @return boolean
1613
	 */
1614 46
	private function isTargetAllowed($target) {
1615
1616 46
		list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target);
1617 46
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1618 1
			\OCP\Util::writeLog('files',
1619 1
				'It is not allowed to move one mount point into another one',
1620 1
				\OCP\Util::DEBUG);
1621 1
			return false;
1622
		}
1623
1624
		// note: cannot use the view because the target is already locked
1625 45
		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1626 45
		if ($fileId === -1) {
1627
			// target might not exist, need to check parent instead
1628 45
			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1629 45
		}
1630
1631
		// check if any of the parents were shared by the current owner (include collections)
1632 45
		$shares = \OCP\Share::getItemShared(
1633 45
			'folder',
1634 45
			$fileId,
1635 45
			\OCP\Share::FORMAT_NONE,
1636 45
			null,
1637
			true
1638 45
		);
1639
1640 45
		if (count($shares) > 0) {
1641 1
			\OCP\Util::writeLog('files',
1642 1
				'It is not allowed to move one mount point into a shared folder',
1643 1
				\OCP\Util::DEBUG);
1644 1
			return false;
1645
		}
1646
1647 44
		return true;
1648
	}
1649
1650
	/**
1651
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1652
	 *
1653
	 * @param string $path
1654
	 * @return \OCP\Files\FileInfo
1655
	 */
1656 1
	private function getPartFileInfo($path) {
1657 1
		$mount = $this->getMount($path);
1658 1
		$storage = $mount->getStorage();
1659 1
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1660 1
		return new FileInfo(
1661 1
			$this->getAbsolutePath($path),
1662 1
			$storage,
1663 1
			$internalPath,
1664
			[
1665 1
				'fileid' => null,
1666 1
				'mimetype' => $storage->getMimeType($internalPath),
1667 1
				'name' => basename($path),
1668 1
				'etag' => null,
1669 1
				'size' => $storage->filesize($internalPath),
1670 1
				'mtime' => $storage->filemtime($internalPath),
1671 1
				'encrypted' => false,
1672
				'permissions' => \OCP\Constants::PERMISSION_ALL
1673 1
			],
1674
			$mount
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1657 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...
1675 1
		);
1676
	}
1677
1678
	/**
1679
	 * @return Updater
1680
	 */
1681 421
	public function getUpdater() {
1682 421
		return $this->updater;
1683
	}
1684
1685
	/**
1686
	 * @param string $path
1687
	 * @param string $fileName
1688
	 * @throws InvalidPathException
1689
	 */
1690 165
	public function verifyPath($path, $fileName) {
1691
1692 165
		$l10n = \OC::$server->getL10N('lib');
1693
1694
		// verify empty and dot files
1695 165
		$trimmed = trim($fileName);
1696 165
		if ($trimmed === '') {
1697 2
			throw new InvalidPathException($l10n->t('Empty filename is not allowed'));
1698
		}
1699 163
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1700 8
			throw new InvalidPathException($l10n->t('Dot files are not allowed'));
1701
		}
1702
1703
		// verify database - e.g. mysql only 3-byte chars
1704 155
		if (preg_match('%(?:
1705
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1706
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1707
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1708 155
)%xs', $fileName)) {
1709 5
			throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names'));
1710
		}
1711
1712
		try {
1713
			/** @type \OCP\Files\Storage $storage */
1714 150
			list($storage, $internalPath) = $this->resolvePath($path);
1715 150
			$storage->verifyPath($internalPath, $fileName);
1716 150
		} catch (ReservedWordException $ex) {
1717 3
			throw new InvalidPathException($l10n->t('File name is a reserved word'));
1718 3
		} catch (InvalidCharacterInPathException $ex) {
1719 2
			throw new InvalidPathException($l10n->t('File name contains at least one invalid character'));
1720 1
		} catch (FileNameTooLongException $ex) {
1721 1
			throw new InvalidPathException($l10n->t('File name is too long'));
1722
		}
1723 144
	}
1724
1725
	/**
1726
	 * get all parent folders of $path
1727
	 *
1728
	 * @param string $path
1729
	 * @return string[]
1730
	 */
1731 809
	private function getParents($path) {
1732 809
		$path = trim($path, '/');
1733 809
		if (!$path) {
1734 4
			return [];
1735
		}
1736
1737 809
		$parts = explode('/', $path);
1738
1739
		// remove the single file
1740 809
		array_pop($parts);
1741 809
		$result = array('/');
1742 809
		$resultPath = '';
1743 809
		foreach ($parts as $part) {
1744 777
			if ($part) {
1745 777
				$resultPath .= '/' . $part;
1746 777
				$result[] = $resultPath;
1747 777
			}
1748 809
		}
1749 809
		return $result;
1750
	}
1751
1752
	/**
1753
	 * Returns the mount point for which to lock
1754
	 *
1755
	 * @param string $absolutePath absolute path
1756
	 * @param bool $useParentMount true to return parent mount instead of whatever
1757
	 * is mounted directly on the given path, false otherwise
1758
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1759
	 */
1760 809
	private function getMountForLock($absolutePath, $useParentMount = false) {
1761 809
		$results = [];
1762 809
		$mount = Filesystem::getMountManager()->find($absolutePath);
1763 809
		if (!$mount) {
1764
			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...
1765
		}
1766
1767 809
		if ($useParentMount) {
1768
			// find out if something is mounted directly on the path
1769 86
			$internalPath = $mount->getInternalPath($absolutePath);
1770 86
			if ($internalPath === '') {
1771
				// resolve the parent mount instead
1772 48
				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1773 48
			}
1774 86
		}
1775
1776 809
		return $mount;
1777
	}
1778
1779
	/**
1780
	 * Lock the given path
1781
	 *
1782
	 * @param string $path the path of the file to lock, relative to the view
1783
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1784
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1785
	 *
1786
	 * @return bool False if the path is excluded from locking, true otherwise
1787
	 * @throws \OCP\Lock\LockedException if the path is already locked
1788
	 */
1789 809 View Code Duplication
	private function lockPath($path, $type, $lockMountPoint = false) {
1790 809
		$absolutePath = $this->getAbsolutePath($path);
1791 809
		$absolutePath = Filesystem::normalizePath($absolutePath);
1792 809
		if (!$this->shouldLockFile($absolutePath)) {
1793 798
			return false;
1794
		}
1795
1796 809
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1797 809
		if ($mount) {
1798
			try {
1799 809
				$mount->getStorage()->acquireLock(
1800 809
					$mount->getInternalPath($absolutePath),
1801 809
					$type,
1802 809
					$this->lockingProvider
1803 809
				);
1804 809
			} catch (\OCP\Lock\LockedException $e) {
1805
				// rethrow with the a human-readable path
1806 30
				throw new \OCP\Lock\LockedException(
1807 30
					$this->getPathRelativeToFiles($absolutePath),
1808
					$e
1809 30
				);
1810
			}
1811 809
		}
1812
1813 809
		return true;
1814
	}
1815
1816
	/**
1817
	 * Change the lock type
1818
	 *
1819
	 * @param string $path the path of the file to lock, relative to the view
1820
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1821
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1822
	 *
1823
	 * @return bool False if the path is excluded from locking, true otherwise
1824
	 * @throws \OCP\Lock\LockedException if the path is already locked
1825
	 */
1826 817 View Code Duplication
	public function changeLock($path, $type, $lockMountPoint = false) {
1827 817
		$path = Filesystem::normalizePath($path);
1828 817
		$absolutePath = $this->getAbsolutePath($path);
1829 817
		$absolutePath = Filesystem::normalizePath($absolutePath);
1830 817
		if (!$this->shouldLockFile($absolutePath)) {
1831 685
			return false;
1832
		}
1833
1834 809
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1835 809
		if ($mount) {
1836
			try {
1837 809
				$mount->getStorage()->changeLock(
1838 809
					$mount->getInternalPath($absolutePath),
1839 809
					$type,
1840 809
					$this->lockingProvider
1841 809
				);
1842 809
			} catch (\OCP\Lock\LockedException $e) {
1843
				// rethrow with the a human-readable path
1844 3
				throw new \OCP\Lock\LockedException(
1845 3
					$this->getPathRelativeToFiles($absolutePath),
1846
					$e
1847 3
				);
1848
			}
1849 809
		}
1850
1851 809
		return true;
1852
	}
1853
1854
	/**
1855
	 * Unlock the given path
1856
	 *
1857
	 * @param string $path the path of the file to unlock, relative to the view
1858
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1859
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1860
	 *
1861
	 * @return bool False if the path is excluded from locking, true otherwise
1862
	 */
1863 809 View Code Duplication
	private function unlockPath($path, $type, $lockMountPoint = false) {
1864 809
		$absolutePath = $this->getAbsolutePath($path);
1865 809
		$absolutePath = Filesystem::normalizePath($absolutePath);
1866 809
		if (!$this->shouldLockFile($absolutePath)) {
1867 798
			return false;
1868
		}
1869
1870 809
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1871 809
		if ($mount) {
1872 809
			$mount->getStorage()->releaseLock(
1873 809
				$mount->getInternalPath($absolutePath),
1874 809
				$type,
1875 809
				$this->lockingProvider
1876 809
			);
1877 809
		}
1878
1879 809
		return true;
1880
	}
1881
1882
	/**
1883
	 * Lock a path and all its parents up to the root of the view
1884
	 *
1885
	 * @param string $path the path of the file to lock relative to the view
1886
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1887
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1888
	 *
1889
	 * @return bool False if the path is excluded from locking, true otherwise
1890
	 */
1891 935 View Code Duplication
	public function lockFile($path, $type, $lockMountPoint = false) {
1892 935
		$absolutePath = $this->getAbsolutePath($path);
1893 935
		$absolutePath = Filesystem::normalizePath($absolutePath);
1894 935
		if (!$this->shouldLockFile($absolutePath)) {
1895 911
			return false;
1896
		}
1897
1898 809
		$this->lockPath($path, $type, $lockMountPoint);
1899
1900 809
		$parents = $this->getParents($path);
1901 809
		foreach ($parents as $parent) {
1902 809
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
1903 809
		}
1904
1905 809
		return true;
1906
	}
1907
1908
	/**
1909
	 * Unlock a path and all its parents up to the root of the view
1910
	 *
1911
	 * @param string $path the path of the file to lock relative to the view
1912
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1913
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1914
	 *
1915
	 * @return bool False if the path is excluded from locking, true otherwise
1916
	 */
1917 935 View Code Duplication
	public function unlockFile($path, $type, $lockMountPoint = false) {
1918 935
		$absolutePath = $this->getAbsolutePath($path);
1919 935
		$absolutePath = Filesystem::normalizePath($absolutePath);
1920 935
		if (!$this->shouldLockFile($absolutePath)) {
1921 911
			return false;
1922
		}
1923
1924 809
		$this->unlockPath($path, $type, $lockMountPoint);
1925
1926 809
		$parents = $this->getParents($path);
1927 809
		foreach ($parents as $parent) {
1928 809
			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
1929 809
		}
1930
1931 809
		return true;
1932
	}
1933
1934
	/**
1935
	 * Only lock files in data/user/files/
1936
	 *
1937
	 * @param string $path Absolute path to the file/folder we try to (un)lock
1938
	 * @return bool
1939
	 */
1940 935
	protected function shouldLockFile($path) {
1941 935
		$path = Filesystem::normalizePath($path);
1942
1943 935
		$pathSegments = explode('/', $path);
1944 935
		if (isset($pathSegments[2])) {
1945
			// E.g.: /username/files/path-to-file
1946 933
			return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
1947
		}
1948
1949 679
		return true;
1950
	}
1951
1952
	/**
1953
	 * Shortens the given absolute path to be relative to
1954
	 * "$user/files".
1955
	 *
1956
	 * @param string $absolutePath absolute path which is under "files"
1957
	 *
1958
	 * @return string path relative to "files" with trimmed slashes or null
1959
	 * if the path was NOT relative to files
1960
	 *
1961
	 * @throws \InvalidArgumentException if the given path was not under "files"
1962
	 * @since 8.1.0
1963
	 */
1964 45
	public function getPathRelativeToFiles($absolutePath) {
1965 45
		$path = Filesystem::normalizePath($absolutePath);
1966 45
		$parts = explode('/', trim($path, '/'), 3);
1967
		// "$user", "files", "path/to/dir"
1968 45
		if (!isset($parts[1]) || $parts[1] !== 'files') {
1969 5
			throw new \InvalidArgumentException('$absolutePath must be relative to "files"');
1970
		}
1971 40
		if (isset($parts[2])) {
1972 38
			return $parts[2];
1973
		}
1974 2
		return '';
1975
	}
1976
}
1977