Completed
Push — master ( 8931ba...9baa96 )
by Roeland
47:53
created

View::filesize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
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 Roeland Jago Douma <[email protected]>
20
 * @author Roman Geber <[email protected]>
21
 * @author Sam Tuke <[email protected]>
22
 * @author Thomas Müller <[email protected]>
23
 * @author Thomas Tanghus <[email protected]>
24
 * @author Vincent Petry <[email protected]>
25
 *
26
 * @copyright Copyright (c) 2015, ownCloud, Inc.
27
 * @license AGPL-3.0
28
 *
29
 * This code is free software: you can redistribute it and/or modify
30
 * it under the terms of the GNU Affero General Public License, version 3,
31
 * as published by the Free Software Foundation.
32
 *
33
 * This program is distributed in the hope that it will be useful,
34
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36
 * GNU Affero General Public License for more details.
37
 *
38
 * You should have received a copy of the GNU Affero General Public License, version 3,
39
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
40
 *
41
 */
42
43
44
namespace OC\Files;
45
46
use Icewind\Streams\CallbackWrapper;
47
use OC\Files\Cache\Updater;
48
use OC\Files\Mount\MoveableMount;
49
use OC\Files\Storage\Storage;
50
use OC\User\User;
51
use OCP\Files\FileNameTooLongException;
52
use OCP\Files\InvalidCharacterInPathException;
53
use OCP\Files\InvalidPathException;
54
use OCP\Files\NotFoundException;
55
use OCP\Files\ReservedWordException;
56
use OCP\IUser;
57
use OCP\Lock\ILockingProvider;
58
use OCP\Lock\LockedException;
59
60
/**
61
 * Class to provide access to ownCloud filesystem via a "view", and methods for
62
 * working with files within that view (e.g. read, write, delete, etc.). Each
63
 * view is restricted to a set of directories via a virtual root. The default view
64
 * uses the currently logged in user's data directory as root (parts of
65
 * OC_Filesystem are merely a wrapper for OC\Files\View).
66
 *
67
 * Apps that need to access files outside of the user data folders (to modify files
68
 * belonging to a user other than the one currently logged in, for example) should
69
 * use this class directly rather than using OC_Filesystem, or making use of PHP's
70
 * built-in file manipulation functions. This will ensure all hooks and proxies
71
 * are triggered correctly.
72
 *
73
 * Filesystem functions are not called directly; they are passed to the correct
74
 * \OC\Files\Storage\Storage object
75
 */
76
class View {
77
	/** @var string */
78
	private $fakeRoot = '';
79
80
	/**
81
	 * @var \OCP\Lock\ILockingProvider
82
	 */
83
	private $lockingProvider;
84
85
	private $lockingEnabled;
86
87
	private $updaterEnabled = true;
88
89
	/**
90
	 * @param string $root
91
	 * @throws \Exception If $root contains an invalid path
92
	 */
93 1392
	public function __construct($root = '') {
94 1392
		if (is_null($root)) {
95 1
			throw new \InvalidArgumentException('Root can\'t be null');
96
		}
97 1392
		if (!Filesystem::isValidPath($root)) {
98 3
			throw new \Exception();
99
		}
100
101 1392
		$this->fakeRoot = $root;
102 1392
		$this->lockingProvider = \OC::$server->getLockingProvider();
103 1392
		$this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
104 1392
	}
105
106 965
	public function getAbsolutePath($path = '/') {
107 965
		if ($path === null) {
108 3
			return null;
109
		}
110 965
		$this->assertPathLength($path);
111 965
		if ($path === '') {
112 100
			$path = '/';
113 100
		}
114 965
		if ($path[0] !== '/') {
115 480
			$path = '/' . $path;
116 480
		}
117 965
		return $this->fakeRoot . $path;
118
	}
119
120
	/**
121
	 * change the root to a fake root
122
	 *
123
	 * @param string $fakeRoot
124
	 * @return boolean|null
125
	 */
126 50
	public function chroot($fakeRoot) {
127 50
		if (!$fakeRoot == '') {
128 48
			if ($fakeRoot[0] !== '/') {
129 13
				$fakeRoot = '/' . $fakeRoot;
130 13
			}
131 48
		}
132 50
		$this->fakeRoot = $fakeRoot;
133 50
	}
134
135
	/**
136
	 * get the fake root
137
	 *
138
	 * @return string
139
	 */
140 938
	public function getRoot() {
141 938
		return $this->fakeRoot;
142
	}
143
144
	/**
145
	 * get path relative to the root of the view
146
	 *
147
	 * @param string $path
148
	 * @return string
149
	 */
150 948
	public function getRelativePath($path) {
151 948
		$this->assertPathLength($path);
152 948
		if ($this->fakeRoot == '') {
153 921
			return $path;
154
		}
155
156 941
		if (rtrim($path, '/') === rtrim($this->fakeRoot, '/')) {
157 913
			return '/';
158
		}
159
160
		// missing slashes can cause wrong matches!
161 941
		$root = rtrim($this->fakeRoot, '/') . '/';
162
163 941
		if (strpos($path, $root) !== 0) {
164 920
			return null;
165
		} else {
166 805
			$path = substr($path, strlen($this->fakeRoot));
167 805
			if (strlen($path) === 0) {
168
				return '/';
169
			} else {
170 805
				return $path;
171
			}
172
		}
173
	}
174
175
	/**
176
	 * get the mountpoint of the storage object for a path
177
	 * ( note: because a storage is not always mounted inside the fakeroot, the
178
	 * returned mountpoint is relative to the absolute root of the filesystem
179
	 * and does not take the chroot into account )
180
	 *
181
	 * @param string $path
182
	 * @return string
183
	 */
184 1
	public function getMountPoint($path) {
185 1
		return Filesystem::getMountPoint($this->getAbsolutePath($path));
186
	}
187
188
	/**
189
	 * get the mountpoint of the storage object for a path
190
	 * ( note: because a storage is not always mounted inside the fakeroot, the
191
	 * returned mountpoint is relative to the absolute root of the filesystem
192
	 * and does not take the chroot into account )
193
	 *
194
	 * @param string $path
195
	 * @return \OCP\Files\Mount\IMountPoint
196
	 */
197 341
	public function getMount($path) {
198 341
		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
199
	}
200
201
	/**
202
	 * resolve a path to a storage and internal path
203
	 *
204
	 * @param string $path
205
	 * @return array an array consisting of the storage and the internal path
206
	 */
207 573
	public function resolvePath($path) {
208 573
		$a = $this->getAbsolutePath($path);
209 573
		$p = Filesystem::normalizePath($a);
210 573
		return Filesystem::resolvePath($p);
211
	}
212
213
	/**
214
	 * return the path to a local version of the file
215
	 * we need this because we can't know if a file is stored local or not from
216
	 * outside the filestorage and for some purposes a local file is needed
217
	 *
218
	 * @param string $path
219
	 * @return string
220
	 */
221 129 View Code Duplication
	public function getLocalFile($path) {
222 129
		$parent = substr($path, 0, strrpos($path, '/'));
223 129
		$path = $this->getAbsolutePath($path);
224 128
		list($storage, $internalPath) = Filesystem::resolvePath($path);
225 128
		if (Filesystem::isValidPath($parent) and $storage) {
226 128
			return $storage->getLocalFile($internalPath);
227
		} else {
228
			return null;
229
		}
230
	}
231
232
	/**
233
	 * @param string $path
234
	 * @return string
235
	 */
236 1 View Code Duplication
	public function getLocalFolder($path) {
237 1
		$parent = substr($path, 0, strrpos($path, '/'));
238 1
		$path = $this->getAbsolutePath($path);
239
		list($storage, $internalPath) = Filesystem::resolvePath($path);
240
		if (Filesystem::isValidPath($parent) and $storage) {
241
			return $storage->getLocalFolder($internalPath);
242
		} else {
243
			return null;
244
		}
245
	}
246
247
	/**
248
	 * the following functions operate with arguments and return values identical
249
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
250
	 * for \OC\Files\Storage\Storage via basicOperation().
251
	 */
252 796
	public function mkdir($path) {
253 796
		return $this->basicOperation('mkdir', $path, array('create', 'write'));
254
	}
255
256
	/**
257
	 * remove mount point
258
	 *
259
	 * @param \OC\Files\Mount\MoveableMount $mount
260
	 * @param string $path relative to data/
261
	 * @return boolean
262
	 */
263 8
	protected function removeMount($mount, $path) {
264 8
		if ($mount instanceof MoveableMount) {
265
			// cut of /user/files to get the relative path to data/user/files
266 6
			$pathParts = explode('/', $path, 4);
267 6
			$relPath = '/' . $pathParts[3];
268 6
			\OC_Hook::emit(
269 6
				Filesystem::CLASSNAME, "umount",
270 6
				array(Filesystem::signal_param_path => $relPath)
271 6
			);
272 6
			$result = $mount->removeMount();
273 6
			if ($result) {
274 6
				\OC_Hook::emit(
275 6
					Filesystem::CLASSNAME, "post_umount",
276 6
					array(Filesystem::signal_param_path => $relPath)
277 6
				);
278 6
			}
279 6
			return $result;
280
		} else {
281
			// do not allow deleting the storage's root / the mount point
282
			// because for some storages it might delete the whole contents
283
			// but isn't supposed to work that way
284 2
			return false;
285
		}
286
	}
287
288 4
	public function disableCacheUpdate() {
289 4
		$this->updaterEnabled = false;
290 4
	}
291
292
	public function enableCacheUpdate() {
293
		$this->updaterEnabled = true;
294
	}
295
296 811
	protected function writeUpdate(Storage $storage, $internalPath, $time = null) {
297 811
		if ($this->updaterEnabled) {
298 811
			if (is_null($time)) {
299 811
				$time = time();
300 811
			}
301 811
			$storage->getUpdater()->update($internalPath, $time);
302 811
		}
303 811
	}
304
305 272
	protected function removeUpdate(Storage $storage, $internalPath) {
306 272
		if ($this->updaterEnabled) {
307 272
			$storage->getUpdater()->remove($internalPath);
308 272
		}
309 272
	}
310
311 48
	protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) {
312 48
		if ($this->updaterEnabled) {
313 47
			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
314 47
		}
315 48
	}
316
317
	/**
318
	 * @param string $path
319
	 * @return bool|mixed
320
	 */
321 223
	public function rmdir($path) {
322 223
		$absolutePath = $this->getAbsolutePath($path);
323 221
		$mount = Filesystem::getMountManager()->find($absolutePath);
324 221
		if ($mount->getInternalPath($absolutePath) === '') {
325 2
			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...
326
		}
327 219
		if ($this->is_dir($path)) {
328 210
			return $this->basicOperation('rmdir', $path, array('delete'));
329
		} else {
330 135
			return false;
331
		}
332
	}
333
334
	/**
335
	 * @param string $path
336
	 * @return resource
337
	 */
338 309
	public function opendir($path) {
339 309
		return $this->basicOperation('opendir', $path, array('read'));
340
	}
341
342
	/**
343
	 * @param $handle
344
	 * @return mixed
345
	 */
346
	public function readdir($handle) {
347
		$fsLocal = new Storage\Local(array('datadir' => '/'));
348
		return $fsLocal->readdir($handle);
349
	}
350
351
	/**
352
	 * @param string $path
353
	 * @return bool|mixed
354
	 */
355 560
	public function is_dir($path) {
356 560
		if ($path == '/') {
357 307
			return true;
358
		}
359 547
		return $this->basicOperation('is_dir', $path);
360
	}
361
362
	/**
363
	 * @param string $path
364
	 * @return bool|mixed
365
	 */
366 36
	public function is_file($path) {
367 36
		if ($path == '/') {
368
			return false;
369
		}
370 36
		return $this->basicOperation('is_file', $path);
371
	}
372
373
	/**
374
	 * @param string $path
375
	 * @return mixed
376
	 */
377 5
	public function stat($path) {
378 5
		return $this->basicOperation('stat', $path);
379
	}
380
381
	/**
382
	 * @param string $path
383
	 * @return mixed
384
	 */
385 4
	public function filetype($path) {
386 4
		return $this->basicOperation('filetype', $path);
387
	}
388
389
	/**
390
	 * @param string $path
391
	 * @return mixed
392
	 */
393 66
	public function filesize($path) {
394 66
		return $this->basicOperation('filesize', $path);
395
	}
396
397
	/**
398
	 * @param string $path
399
	 * @return bool|mixed
400
	 * @throws \OCP\Files\InvalidPathException
401
	 */
402 1
	public function readfile($path) {
403 1
		$this->assertPathLength($path);
404
		@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...
405
		$handle = $this->fopen($path, 'rb');
406
		if ($handle) {
407
			$chunkSize = 8192; // 8 kB chunks
408
			while (!feof($handle)) {
409
				echo fread($handle, $chunkSize);
410
				flush();
411
			}
412
			$size = $this->filesize($path);
413
			return $size;
414
		}
415
		return false;
416
	}
417
418
	/**
419
	 * @param string $path
420
	 * @return mixed
421
	 */
422 21
	public function isCreatable($path) {
423 21
		return $this->basicOperation('isCreatable', $path);
424
	}
425
426
	/**
427
	 * @param string $path
428
	 * @return mixed
429
	 */
430 31
	public function isReadable($path) {
431 31
		return $this->basicOperation('isReadable', $path);
432
	}
433
434
	/**
435
	 * @param string $path
436
	 * @return mixed
437
	 */
438 4
	public function isUpdatable($path) {
439 4
		return $this->basicOperation('isUpdatable', $path);
440
	}
441
442
	/**
443
	 * @param string $path
444
	 * @return bool|mixed
445
	 */
446 4
	public function isDeletable($path) {
447 4
		$absolutePath = $this->getAbsolutePath($path);
448 3
		$mount = Filesystem::getMountManager()->find($absolutePath);
449 3
		if ($mount->getInternalPath($absolutePath) === '') {
450
			return $mount instanceof MoveableMount;
451
		}
452 3
		return $this->basicOperation('isDeletable', $path);
453
	}
454
455
	/**
456
	 * @param string $path
457
	 * @return mixed
458
	 */
459 153
	public function isSharable($path) {
460 153
		return $this->basicOperation('isSharable', $path);
461
	}
462
463
	/**
464
	 * @param string $path
465
	 * @return bool|mixed
466
	 */
467 937
	public function file_exists($path) {
468 937
		if ($path == '/') {
469 163
			return true;
470
		}
471 937
		return $this->basicOperation('file_exists', $path);
472
	}
473
474
	/**
475
	 * @param string $path
476
	 * @return mixed
477
	 */
478 39
	public function filemtime($path) {
479 39
		return $this->basicOperation('filemtime', $path);
480
	}
481
482
	/**
483
	 * @param string $path
484
	 * @param int|string $mtime
485
	 * @return bool
486
	 */
487 508
	public function touch($path, $mtime = null) {
488 508
		if (!is_null($mtime) and !is_numeric($mtime)) {
489
			$mtime = strtotime($mtime);
490 1
		}
491
492 508
		$hooks = array('touch');
493
494 508
		if (!$this->file_exists($path)) {
495 500
			$hooks[] = 'create';
496 500
			$hooks[] = 'write';
497 500
		}
498 508
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
499 508
		if (!$result) {
500
			// If create file fails because of permissions on external storage like SMB folders,
501
			// check file exists and return false if not.
502 4
			if (!$this->file_exists($path)) {
503 2
				return false;
504
			}
505 2
			if (is_null($mtime)) {
506
				$mtime = time();
507
			}
508
			//if native touch fails, we emulate it by changing the mtime in the cache
509 2
			$this->putFileInfo($path, array('mtime' => $mtime));
510 2
		}
511 508
		return true;
512
	}
513
514
	/**
515
	 * @param string $path
516
	 * @return mixed
517
	 */
518 94
	public function file_get_contents($path) {
519 94
		return $this->basicOperation('file_get_contents', $path, array('read'));
520
	}
521
522
	/**
523
	 * @param bool $exists
524
	 * @param string $path
525
	 * @param bool $run
526
	 */
527 7
	protected function emit_file_hooks_pre($exists, $path, &$run) {
528 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...
529 7
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array(
530 7
				Filesystem::signal_param_path => $this->getHookPath($path),
531 7
				Filesystem::signal_param_run => &$run,
532 7
			));
533 7
		} else {
534
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array(
535
				Filesystem::signal_param_path => $this->getHookPath($path),
536
				Filesystem::signal_param_run => &$run,
537
			));
538
		}
539 7
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array(
540 7
			Filesystem::signal_param_path => $this->getHookPath($path),
541 7
			Filesystem::signal_param_run => &$run,
542 7
		));
543 7
	}
544
545
	/**
546
	 * @param bool $exists
547
	 * @param string $path
548
	 */
549 8
	protected function emit_file_hooks_post($exists, $path) {
550 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...
551 7
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array(
552 7
				Filesystem::signal_param_path => $this->getHookPath($path),
553 7
			));
554 7
		} else {
555 1
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
556 1
				Filesystem::signal_param_path => $this->getHookPath($path),
557 1
			));
558
		}
559 8
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
560 8
			Filesystem::signal_param_path => $this->getHookPath($path),
561 8
		));
562 8
	}
563
564
	/**
565
	 * @param string $path
566
	 * @param mixed $data
567
	 * @return bool|mixed
568
	 * @throws \Exception
569
	 */
570 448
	public function file_put_contents($path, $data) {
571 448
		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
572 14
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
573 14
			if (Filesystem::isValidPath($path)
574 14
				and !Filesystem::isFileBlacklisted($path)
575 14
			) {
576 14
				$path = $this->getRelativePath($absolutePath);
577
578 14
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
579
580 14
				$exists = $this->file_exists($path);
581 14
				$run = true;
582 14
				if ($this->shouldEmitHooks($path)) {
583 1
					$this->emit_file_hooks_pre($exists, $path, $run);
584 1
				}
585 14
				if (!$run) {
586
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
587
					return false;
588
				}
589
590 14
				$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
591
592
				/** @var \OC\Files\Storage\Storage $storage */
593 14
				list($storage, $internalPath) = $this->resolvePath($path);
594 14
				$target = $storage->fopen($internalPath, 'w');
595 14
				if ($target) {
596 14
					list (, $result) = \OC_Helper::streamCopy($data, $target);
597 14
					fclose($target);
598 14
					fclose($data);
599
600 14
					$this->writeUpdate($storage, $internalPath);
601
602 14
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
603
604 14
					if ($this->shouldEmitHooks($path) && $result !== false) {
605 1
						$this->emit_file_hooks_post($exists, $path);
606 1
					}
607 14
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
608 14
					return $result;
609
				} else {
610
					$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
611
					return false;
612
				}
613
			} else {
614
				return false;
615
			}
616
		} else {
617 441
			$hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write');
618 440
			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
619
		}
620
	}
621
622
	/**
623
	 * @param string $path
624
	 * @return bool|mixed
625
	 */
626 138
	public function unlink($path) {
627 138
		if ($path === '' || $path === '/') {
628
			// do not allow deleting the root
629 1
			return false;
630
		}
631 138
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
632 138
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
633 137
		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
634 137
		if ($mount and $mount->getInternalPath($absolutePath) === '') {
635 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...
636
		}
637 135
		return $this->basicOperation('unlink', $path, array('delete'));
638
	}
639
640
	/**
641
	 * @param string $directory
642
	 * @return bool|mixed
643
	 */
644 209
	public function deleteAll($directory) {
645 209
		return $this->rmdir($directory);
646
	}
647
648
	/**
649
	 * Rename/move a file or folder from the source path to target path.
650
	 *
651
	 * @param string $path1 source path
652
	 * @param string $path2 target path
653
	 *
654
	 * @return bool|mixed
655
	 */
656 119
	public function rename($path1, $path2) {
657 119
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
658 118
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
659 118
		$result = false;
660
		if (
661 118
			Filesystem::isValidPath($path2)
662 118
			and Filesystem::isValidPath($path1)
663 118
			and !Filesystem::isFileBlacklisted($path2)
664 118
		) {
665 118
			$path1 = $this->getRelativePath($absolutePath1);
666 118
			$path2 = $this->getRelativePath($absolutePath2);
667 118
			$exists = $this->file_exists($path2);
668
669 118
			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...
670
				return false;
671
			}
672
673 118
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
674
			try {
675 118
				$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
676 118
			} catch (LockedException $e) {
677 1
				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
678 1
				throw $e;
679
			}
680
681 117
			$run = true;
682 117
			if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
683
				// if it was a rename from a part file to a regular file it was a write and not a rename operation
684
				$this->emit_file_hooks_pre($exists, $path2, $run);
685 117
			} elseif ($this->shouldEmitHooks($path1)) {
686 65
				\OC_Hook::emit(
687 65
					Filesystem::CLASSNAME, Filesystem::signal_rename,
688
					array(
689 65
						Filesystem::signal_param_oldpath => $this->getHookPath($path1),
690 65
						Filesystem::signal_param_newpath => $this->getHookPath($path2),
691 65
						Filesystem::signal_param_run => &$run
692 65
					)
693 65
				);
694 65
			}
695 117
			if ($run) {
696 117
				$this->verifyPath(dirname($path2), basename($path2));
697
698 117
				$manager = Filesystem::getMountManager();
699 117
				$mount1 = $this->getMount($path1);
700 117
				$mount2 = $this->getMount($path2);
701 117
				$storage1 = $mount1->getStorage();
702 117
				$storage2 = $mount2->getStorage();
703 117
				$internalPath1 = $mount1->getInternalPath($absolutePath1);
704 117
				$internalPath2 = $mount2->getInternalPath($absolutePath2);
705
706 117
				$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
707 116
				$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
708
709 116
				if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
710 45
					if ($this->isTargetAllowed($absolutePath2)) {
711
						/**
712
						 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
713
						 */
714 43
						$sourceMountPoint = $mount1->getMountPoint();
715 43
						$result = $mount1->moveMount($absolutePath2);
716 43
						$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
717 43
					} else {
718 2
						$result = false;
719
					}
720
					// moving a file/folder within the same mount point
721 116
				} elseif ($storage1 == $storage2) {
722 77
					if ($storage1) {
723 77
						$result = $storage1->rename($internalPath1, $internalPath2);
724 77
					} else {
725
						$result = false;
726
					}
727
					// moving a file/folder between storages (from $storage1 to $storage2)
728 77
				} else {
729 13
					$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
730
				}
731
732 116
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
733
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
734
735 34
					$this->writeUpdate($storage2, $internalPath2);
736 116
				} else if ($result) {
737 77
					if ($internalPath1 !== '') { // dont do a cache update for moved mounts
738 48
						$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
739 48
					}
740 77
				}
741
742 116
				$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
743 116
				$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
744
745 116
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
746 34
					if ($this->shouldEmitHooks()) {
747 2
						$this->emit_file_hooks_post($exists, $path2);
748 2
					}
749 116
				} elseif ($result) {
750 77
					if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
751 59
						\OC_Hook::emit(
752 59
							Filesystem::CLASSNAME,
753 59
							Filesystem::signal_post_rename,
754
							array(
755 59
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
756 59
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
757 59
							)
758 59
						);
759 59
					}
760 77
				}
761 116
			}
762 116
			$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
763 116
			$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
764 116
		}
765 116
		return $result;
766
	}
767
768
	/**
769
	 * Copy a file/folder from the source path to target path
770
	 *
771
	 * @param string $path1 source path
772
	 * @param string $path2 target path
773
	 * @param bool $preserveMtime whether to preserve mtime on the copy
774
	 *
775
	 * @return bool|mixed
776
	 */
777 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...
778 25
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
779 24
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
780 24
		$result = false;
781
		if (
782 24
			Filesystem::isValidPath($path2)
783 24
			and Filesystem::isValidPath($path1)
784 24
			and !Filesystem::isFileBlacklisted($path2)
785 24
		) {
786 24
			$path1 = $this->getRelativePath($absolutePath1);
787 24
			$path2 = $this->getRelativePath($absolutePath2);
788
789 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...
790
				return false;
791
			}
792 24
			$run = true;
793
794 24
			$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
795 24
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
796 24
			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
797 24
			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
798
799
			try {
800
801 24
				$exists = $this->file_exists($path2);
802 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...
803 6
					\OC_Hook::emit(
804 6
						Filesystem::CLASSNAME,
805 6
						Filesystem::signal_copy,
806
						array(
807 6
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
808 6
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
809 6
							Filesystem::signal_param_run => &$run
810 6
						)
811 6
					);
812 6
					$this->emit_file_hooks_pre($exists, $path2, $run);
813 6
				}
814 24
				if ($run) {
815 24
					$mount1 = $this->getMount($path1);
816 24
					$mount2 = $this->getMount($path2);
817 24
					$storage1 = $mount1->getStorage();
818 24
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
819 24
					$storage2 = $mount2->getStorage();
820 24
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
821
822 24
					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
823 24
					$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
824
825 24
					if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
826 16
						if ($storage1) {
827 16
							$result = $storage1->copy($internalPath1, $internalPath2);
828 15
						} else {
829
							$result = false;
830
						}
831 15
					} else {
832 10
						$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
833
					}
834
835 23
					$this->writeUpdate($storage2, $internalPath2);
836
837 23
					$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
838 23
					$lockTypePath2 = ILockingProvider::LOCK_SHARED;
839
840 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...
841 5
						\OC_Hook::emit(
842 5
							Filesystem::CLASSNAME,
843 5
							Filesystem::signal_post_copy,
844
							array(
845 5
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
846 5
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
847 5
							)
848 5
						);
849 5
						$this->emit_file_hooks_post($exists, $path2);
850 5
					}
851
852 23
				}
853 24
			} catch (\Exception $e) {
854 1
				$this->unlockFile($path2, $lockTypePath2);
855 1
				$this->unlockFile($path1, $lockTypePath1);
856 1
				throw $e;
857
			}
858
859 23
			$this->unlockFile($path2, $lockTypePath2);
860 23
			$this->unlockFile($path1, $lockTypePath1);
861
862 23
		}
863 23
		return $result;
864
	}
865
866
	/**
867
	 * @param string $path
868
	 * @param string $mode
869
	 * @return resource
870
	 */
871 566
	public function fopen($path, $mode) {
872 566
		$hooks = array();
873
		switch ($mode) {
874 566
			case 'r':
875 566
			case 'rb':
876 91
				$hooks[] = 'read';
877 91
				break;
878 500
			case 'r+':
879 500
			case 'rb+':
880 500
			case 'w+':
881 500
			case 'wb+':
882 500
			case 'x+':
883 500
			case 'xb+':
884 500
			case 'a+':
885 500
			case 'ab+':
886
				$hooks[] = 'read';
887
				$hooks[] = 'write';
888
				break;
889 500
			case 'w':
890 500
			case 'wb':
891 500
			case 'x':
892 500
			case 'xb':
893 500
			case 'a':
894 500
			case 'ab':
895 500
				$hooks[] = 'write';
896 500
				break;
897
			default:
898
				\OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, \OCP\Util::ERROR);
899
		}
900
901 566
		return $this->basicOperation('fopen', $path, $hooks, $mode);
902
	}
903
904
	/**
905
	 * @param string $path
906
	 * @return bool|string
907
	 * @throws \OCP\Files\InvalidPathException
908
	 */
909 49
	public function toTmpFile($path) {
910 49
		$this->assertPathLength($path);
911 48
		if (Filesystem::isValidPath($path)) {
912 48
			$source = $this->fopen($path, 'r');
913 48
			if ($source) {
914 48
				$extension = pathinfo($path, PATHINFO_EXTENSION);
915 48
				$tmpFile = \OC_Helper::tmpFile($extension);
916 48
				file_put_contents($tmpFile, $source);
917 48
				return $tmpFile;
918
			} else {
919
				return false;
920
			}
921
		} else {
922
			return false;
923
		}
924
	}
925
926
	/**
927
	 * @param string $tmpFile
928
	 * @param string $path
929
	 * @return bool|mixed
930
	 * @throws \OCP\Files\InvalidPathException
931
	 */
932 1
	public function fromTmpFile($tmpFile, $path) {
933 1
		$this->assertPathLength($path);
934
		if (Filesystem::isValidPath($path)) {
935
936
			// Get directory that the file is going into
937
			$filePath = dirname($path);
938
939
			// Create the directories if any
940
			if (!$this->file_exists($filePath)) {
941
				$this->mkdir($filePath);
942
			}
943
944
			$source = fopen($tmpFile, 'r');
945
			if ($source) {
946
				$result = $this->file_put_contents($path, $source);
947
				// $this->file_put_contents() might have already closed
948
				// the resource, so we check it, before trying to close it
949
				// to avoid messages in the error log.
950
				if (is_resource($source)) {
951
					fclose($source);
952
				}
953
				unlink($tmpFile);
954
				return $result;
955
			} else {
956
				return false;
957
			}
958
		} else {
959
			return false;
960
		}
961
	}
962
963
964
	/**
965
	 * @param string $path
966
	 * @return mixed
967
	 * @throws \OCP\Files\InvalidPathException
968
	 */
969 2
	public function getMimeType($path) {
970 2
		$this->assertPathLength($path);
971 1
		return $this->basicOperation('getMimeType', $path);
972
	}
973
974
	/**
975
	 * @param string $type
976
	 * @param string $path
977
	 * @param bool $raw
978
	 * @return bool|null|string
979
	 */
980 1
	public function hash($type, $path, $raw = false) {
981 1
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
982 1
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
983
		if (Filesystem::isValidPath($path)) {
984
			$path = $this->getRelativePath($absolutePath);
985
			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...
986
				return false;
987
			}
988
			if ($this->shouldEmitHooks($path)) {
989
				\OC_Hook::emit(
990
					Filesystem::CLASSNAME,
991
					Filesystem::signal_read,
992
					array(Filesystem::signal_param_path => $this->getHookPath($path))
993
				);
994
			}
995
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
996
			if ($storage) {
997
				$result = $storage->hash($type, $internalPath, $raw);
998
				return $result;
999
			}
1000
		}
1001
		return null;
1002
	}
1003
1004
	/**
1005
	 * @param string $path
1006
	 * @return mixed
1007
	 * @throws \OCP\Files\InvalidPathException
1008
	 */
1009 48
	public function free_space($path = '/') {
1010 48
		$this->assertPathLength($path);
1011 47
		return $this->basicOperation('free_space', $path);
1012
	}
1013
1014
	/**
1015
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1016
	 *
1017
	 * @param string $operation
1018
	 * @param string $path
1019
	 * @param array $hooks (optional)
1020
	 * @param mixed $extraParam (optional)
1021
	 * @return mixed
1022
	 * @throws \Exception
1023
	 *
1024
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1025
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1026
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1027
	 */
1028 944
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1029 944
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1030 944
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1031 944
		if (Filesystem::isValidPath($path)
1032 944
			and !Filesystem::isFileBlacklisted($path)
1033 944
		) {
1034 944
			$path = $this->getRelativePath($absolutePath);
1035 944
			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...
1036 13
				return false;
1037
			}
1038
1039 944
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1040
				// always a shared lock during pre-hooks so the hook can read the file
1041 923
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1042 923
			}
1043
1044 944
			$run = $this->runHooks($hooks, $path);
1045
			/** @var \OC\Files\Storage\Storage $storage */
1046 944
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1047 944
			if ($run and $storage) {
1048 944
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1049 812
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1050 812
				}
1051
				try {
1052 944
					if (!is_null($extraParam)) {
1053 794
						$result = $storage->$operation($internalPath, $extraParam);
1054 794
					} else {
1055 944
						$result = $storage->$operation($internalPath);
1056
					}
1057 944
				} catch (\Exception $e) {
1058 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...
1059 8
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1060 24
					} else if (in_array('read', $hooks)) {
1061 3
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1062 3
					}
1063 24
					throw $e;
1064
				}
1065
1066 944
				if (in_array('delete', $hooks) and $result) {
1067 272
					$this->removeUpdate($storage, $internalPath);
1068 272
				}
1069 944
				if (in_array('write', $hooks) and $operation !== 'fopen') {
1070 811
					$this->writeUpdate($storage, $internalPath);
1071 811
				}
1072 944
				if (in_array('touch', $hooks)) {
1073 508
					$this->writeUpdate($storage, $internalPath, $extraParam);
1074 508
				}
1075
1076 944
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1077 812
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1078 812
				}
1079
1080 944
				$unlockLater = false;
1081 944
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1082 564
					$unlockLater = true;
1083
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1084 564 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...
1085 498
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1086 564
						} else if (in_array('read', $hooks)) {
1087 86
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1088 86
						}
1089 564
					});
1090 564
				}
1091
1092 944
				if ($this->shouldEmitHooks($path) && $result !== false) {
1093 929
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1094 929
						$this->runHooks($hooks, $path, true);
1095 929
					}
1096 929
				}
1097
1098 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...
1099 944
					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1100 944
				) {
1101 923
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1102 923
				}
1103 944
				return $result;
1104
			} else {
1105 6
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1106
			}
1107 6
		}
1108 8
		return null;
1109
	}
1110
1111
	/**
1112
	 * get the path relative to the default root for hook usage
1113
	 *
1114
	 * @param string $path
1115
	 * @return string
1116
	 */
1117 944
	private function getHookPath($path) {
1118 944
		if (!Filesystem::getView()) {
1119 7
			return $path;
1120
		}
1121 938
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1122
	}
1123
1124 944
	private function shouldEmitHooks($path = '') {
1125 944
		if ($path && Cache\Scanner::isPartialFile($path)) {
1126 38
			return false;
1127
		}
1128 944
		if (!Filesystem::$loaded) {
1129
			return false;
1130
		}
1131 944
		$defaultRoot = Filesystem::getRoot();
1132 944
		if ($defaultRoot === null) {
1133 7
			return false;
1134
		}
1135 938
		if ($this->fakeRoot === $defaultRoot) {
1136 264
			return true;
1137
		}
1138 920
		$fullPath = $this->getAbsolutePath($path);
1139
1140 920
		if ($fullPath === $defaultRoot) {
1141 905
			return true;
1142
		}
1143
1144 920
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1145
	}
1146
1147
	/**
1148
	 * @param string[] $hooks
1149
	 * @param string $path
1150
	 * @param bool $post
1151
	 * @return bool
1152
	 */
1153 944
	private function runHooks($hooks, $path, $post = false) {
1154 944
		$relativePath = $path;
1155 944
		$path = $this->getHookPath($path);
1156 944
		$prefix = ($post) ? 'post_' : '';
1157 944
		$run = true;
1158 944
		if ($this->shouldEmitHooks($relativePath)) {
1159 929
			foreach ($hooks as $hook) {
1160 791
				if ($hook != 'read') {
1161 791
					\OC_Hook::emit(
1162 791
						Filesystem::CLASSNAME,
1163 791
						$prefix . $hook,
1164
						array(
1165 791
							Filesystem::signal_param_run => &$run,
1166 791
							Filesystem::signal_param_path => $path
1167 791
						)
1168 791
					);
1169 791
				} elseif (!$post) {
1170 89
					\OC_Hook::emit(
1171 89
						Filesystem::CLASSNAME,
1172 89
						$prefix . $hook,
1173
						array(
1174 89
							Filesystem::signal_param_path => $path
1175 89
						)
1176 89
					);
1177 89
				}
1178 929
			}
1179 929
		}
1180 944
		return $run;
1181
	}
1182
1183
	/**
1184
	 * check if a file or folder has been updated since $time
1185
	 *
1186
	 * @param string $path
1187
	 * @param int $time
1188
	 * @return bool
1189
	 */
1190 1
	public function hasUpdated($path, $time) {
1191 1
		return $this->basicOperation('hasUpdated', $path, array(), $time);
1192
	}
1193
1194
	/**
1195
	 * @param string $ownerId
1196
	 * @return \OC\User\User
1197
	 */
1198 797
	private function getUserObjectForOwner($ownerId) {
1199 797
		$owner = \OC::$server->getUserManager()->get($ownerId);
1200 797
		if ($owner instanceof IUser) {
1201 775
			return $owner;
1202
		} else {
1203 22
			return new User($ownerId, null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<OC_User_Interface>.

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...
1204
		}
1205
	}
1206
1207
	/**
1208
	 * Get file info from cache
1209
	 *
1210
	 * If the file is not in cached it will be scanned
1211
	 * If the file has changed on storage the cache will be updated
1212
	 *
1213
	 * @param \OC\Files\Storage\Storage $storage
1214
	 * @param string $internalPath
1215
	 * @param string $relativePath
1216
	 * @return array|bool
1217
	 */
1218 798
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1219 798
		$cache = $storage->getCache($internalPath);
1220 798
		$data = $cache->get($internalPath);
1221 798
		$watcher = $storage->getWatcher($internalPath);
1222
1223
		try {
1224
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1225 798
			if (!$data || $data['size'] === -1) {
1226 276
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1227 270
				if (!$storage->file_exists($internalPath)) {
1228 224
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1229 224
					return false;
1230
				}
1231 50
				$scanner = $storage->getScanner($internalPath);
1232 50
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1233 50
				$data = $cache->get($internalPath);
1234 50
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1235 797
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1236 3
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1237 3
				$watcher->update($internalPath, $data);
1238 3
				$storage->getPropagator()->propagateChange($internalPath, time());
1239 3
				$data = $cache->get($internalPath);
1240 3
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1241 3
			}
1242 797
		} catch (LockedException $e) {
1243
			// if the file is locked we just use the old cache info
1244
		}
1245
1246 797
		return $data;
1247
	}
1248
1249
	/**
1250
	 * get the filesystem info
1251
	 *
1252
	 * @param string $path
1253
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1254
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1255
	 * defaults to true
1256
	 * @return \OC\Files\FileInfo|bool False if file does not exist
1257
	 */
1258 798
	public function getFileInfo($path, $includeMountPoints = true) {
1259 798
		$this->assertPathLength($path);
1260 798
		if (!Filesystem::isValidPath($path)) {
1261
			return false;
1262
		}
1263 798
		if (Cache\Scanner::isPartialFile($path)) {
1264 1
			return $this->getPartFileInfo($path);
1265
		}
1266 798
		$relativePath = $path;
1267 798
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1268
1269 798
		$mount = Filesystem::getMountManager()->find($path);
1270 798
		$storage = $mount->getStorage();
1271 798
		$internalPath = $mount->getInternalPath($path);
1272 798
		if ($storage) {
1273 798
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1274
1275 798
			if (!is_array($data)) {
1276 62
				return false;
1277
			}
1278
1279 797
			if ($mount instanceof MoveableMount && $internalPath === '') {
1280 27
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1281 27
			}
1282
1283 797
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1284 797
			$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 1269 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...
1285
1286 797
			if ($data and isset($data['fileid'])) {
1287 797
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1288
					//add the sizes of other mount points to the folder
1289 584
					$extOnly = ($includeMountPoints === 'ext');
1290 584
					$mounts = Filesystem::getMountManager()->findIn($path);
1291 584
					foreach ($mounts as $mount) {
1292 60
						$subStorage = $mount->getStorage();
1293 60
						if ($subStorage) {
1294
							// exclude shared storage ?
1295 60
							if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) {
1296
								continue;
1297
							}
1298 60
							$subCache = $subStorage->getCache('');
1299 60
							$rootEntry = $subCache->get('');
1300 60
							$info->addSubEntry($rootEntry, $mount->getMountPoint());
1301 60
						}
1302 584
					}
1303 584
				}
1304 797
			}
1305
1306 797
			return $info;
1307
		}
1308
1309
		return false;
1310
	}
1311
1312
	/**
1313
	 * get the content of a directory
1314
	 *
1315
	 * @param string $directory path under datadirectory
1316
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1317
	 * @return FileInfo[]
1318
	 */
1319 201
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1320 201
		$this->assertPathLength($directory);
1321 200
		if (!Filesystem::isValidPath($directory)) {
1322
			return [];
1323
		}
1324 200
		$path = $this->getAbsolutePath($directory);
1325 200
		$path = Filesystem::normalizePath($path);
1326 200
		$mount = $this->getMount($directory);
1327 200
		$storage = $mount->getStorage();
1328 200
		$internalPath = $mount->getInternalPath($path);
1329 200
		if ($storage) {
1330 200
			$cache = $storage->getCache($internalPath);
1331 200
			$user = \OC_User::getUser();
1332
1333 200
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1334
1335 200
			if (!is_array($data) || !isset($data['fileid'])) {
1336 174
				return [];
1337
			}
1338
1339 198
			$folderId = $data['fileid'];
1340 198
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1341
1342
			/**
1343
			 * @var \OC\Files\FileInfo[] $files
1344
			 */
1345
			$files = array_map(function (array $content) use ($path, $storage, $mount) {
1346 128
				if (\OCP\Util::isSharingDisabledForUser()) {
1347 1
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1348 1
				}
1349 128
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1350 128
				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 1326 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...
1351 198
			}, $contents);
1352
1353
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1354 198
			$mounts = Filesystem::getMountManager()->findIn($path);
1355 198
			$dirLength = strlen($path);
1356 198
			foreach ($mounts as $mount) {
1357 12
				$mountPoint = $mount->getMountPoint();
1358 12
				$subStorage = $mount->getStorage();
1359 12
				if ($subStorage) {
1360 12
					$subCache = $subStorage->getCache('');
1361
1362 12
					$rootEntry = $subCache->get('');
1363 12
					if (!$rootEntry) {
1364 3
						$subScanner = $subStorage->getScanner('');
1365
						try {
1366 3
							$subScanner->scanFile('');
1367 3
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1368
							continue;
1369
						} catch (\OCP\Files\StorageInvalidException $e) {
1370
							continue;
1371
						} catch (\Exception $e) {
1372
							// sometimes when the storage is not available it can be any exception
1373
							\OCP\Util::writeLog(
1374
								'core',
1375
								'Exception while scanning storage "' . $subStorage->getId() . '": ' .
1376
								get_class($e) . ': ' . $e->getMessage(),
1377
								\OCP\Util::ERROR
1378
							);
1379
							continue;
1380
						}
1381 3
						$rootEntry = $subCache->get('');
1382 3
					}
1383
1384 12
					if ($rootEntry) {
1385 12
						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1386 12
						if ($pos = strpos($relativePath, '/')) {
1387
							//mountpoint inside subfolder add size to the correct folder
1388 1
							$entryName = substr($relativePath, 0, $pos);
1389 1
							foreach ($files as &$entry) {
1390 1
								if ($entry->getName() === $entryName) {
1391 1
									$entry->addSubEntry($rootEntry, $mountPoint);
1392 1
								}
1393 1
							}
1394 1
						} else { //mountpoint in this folder, add an entry for it
1395 12
							$rootEntry['name'] = $relativePath;
1396 12
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1397 12
							$permissions = $rootEntry['permissions'];
1398
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1399
							// for shared files/folders we use the permissions given by the owner
1400 12
							if ($mount instanceof MoveableMount) {
1401 5
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1402 5
							} else {
1403 7
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1404
							}
1405
1406
							//remove any existing entry with the same name
1407 12
							foreach ($files as $i => $file) {
1408 12
								if ($file['name'] === $rootEntry['name']) {
1409 1
									unset($files[$i]);
1410 1
									break;
1411
								}
1412 12
							}
1413 12
							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1414
1415
							// if sharing was disabled for the user we remove the share permissions
1416 12
							if (\OCP\Util::isSharingDisabledForUser()) {
1417 1
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1418 1
							}
1419
1420 12
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1421 12
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1422
						}
1423 12
					}
1424 12
				}
1425 198
			}
1426
1427 198
			if ($mimetype_filter) {
1428
				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1429
					if (strpos($mimetype_filter, '/')) {
1430
						if ($file->getMimetype() === $mimetype_filter) {
1431
							$result[] = $file;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1432
						}
1433
					} else {
1434
						if ($file->getMimePart() === $mimetype_filter) {
1435
							$result[] = $file;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1436
						}
1437
					}
1438
				});
1439
			}
1440
1441 198
			return $files;
1442
		} else {
1443
			return [];
1444
		}
1445
	}
1446
1447
	/**
1448
	 * change file metadata
1449
	 *
1450
	 * @param string $path
1451
	 * @param array|\OCP\Files\FileInfo $data
1452
	 * @return int
1453
	 *
1454
	 * returns the fileid of the updated file
1455
	 */
1456 31
	public function putFileInfo($path, $data) {
1457 31
		$this->assertPathLength($path);
1458 30
		if ($data instanceof FileInfo) {
1459
			$data = $data->getData();
1460
		}
1461 30
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1462
		/**
1463
		 * @var \OC\Files\Storage\Storage $storage
1464
		 * @var string $internalPath
1465
		 */
1466 30
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1467 30
		if ($storage) {
1468 30
			$cache = $storage->getCache($path);
1469
1470 30
			if (!$cache->inCache($internalPath)) {
1471
				$scanner = $storage->getScanner($internalPath);
1472
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1473
			}
1474
1475 30
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1456 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...
1476
		} else {
1477
			return -1;
1478
		}
1479
	}
1480
1481
	/**
1482
	 * search for files with the name matching $query
1483
	 *
1484
	 * @param string $query
1485
	 * @return FileInfo[]
1486
	 */
1487 2
	public function search($query) {
1488 2
		return $this->searchCommon('search', array('%' . $query . '%'));
1489
	}
1490
1491
	/**
1492
	 * search for files with the name matching $query
1493
	 *
1494
	 * @param string $query
1495
	 * @return FileInfo[]
1496
	 */
1497 10
	public function searchRaw($query) {
1498 10
		return $this->searchCommon('search', array($query));
1499
	}
1500
1501
	/**
1502
	 * search for files by mimetype
1503
	 *
1504
	 * @param string $mimetype
1505
	 * @return FileInfo[]
1506
	 */
1507 1
	public function searchByMime($mimetype) {
1508 1
		return $this->searchCommon('searchByMime', array($mimetype));
1509
	}
1510
1511
	/**
1512
	 * search for files by tag
1513
	 *
1514
	 * @param string|int $tag name or tag id
1515
	 * @param string $userId owner of the tags
1516
	 * @return FileInfo[]
1517
	 */
1518
	public function searchByTag($tag, $userId) {
1519
		return $this->searchCommon('searchByTag', array($tag, $userId));
1520
	}
1521
1522
	/**
1523
	 * @param string $method cache method
1524
	 * @param array $args
1525
	 * @return FileInfo[]
1526
	 */
1527 12
	private function searchCommon($method, $args) {
1528 12
		$files = array();
1529 12
		$rootLength = strlen($this->fakeRoot);
1530
1531 12
		$mount = $this->getMount('');
1532 12
		$mountPoint = $mount->getMountPoint();
1533 12
		$storage = $mount->getStorage();
1534 12
		if ($storage) {
1535 12
			$cache = $storage->getCache('');
1536
1537 12
			$results = call_user_func_array(array($cache, $method), $args);
1538 12
			foreach ($results as $result) {
1539 2
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1540 2
					$internalPath = $result['path'];
1541 2
					$path = $mountPoint . $result['path'];
1542 2
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1543 2
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1544 2
					$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 1531 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...
1545 2
				}
1546 12
			}
1547
1548 12
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1549 12
			foreach ($mounts as $mount) {
1550 1
				$mountPoint = $mount->getMountPoint();
1551 1
				$storage = $mount->getStorage();
1552 1
				if ($storage) {
1553 1
					$cache = $storage->getCache('');
1554
1555 1
					$relativeMountPoint = substr($mountPoint, $rootLength);
1556 1
					$results = call_user_func_array(array($cache, $method), $args);
1557 1
					if ($results) {
1558 1
						foreach ($results as $result) {
1559 1
							$internalPath = $result['path'];
1560 1
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1561 1
							$path = rtrim($mountPoint . $internalPath, '/');
1562 1
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1563 1
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1564 1
						}
1565 1
					}
1566 1
				}
1567 12
			}
1568 12
		}
1569 12
		return $files;
1570
	}
1571
1572
	/**
1573
	 * Get the owner for a file or folder
1574
	 *
1575
	 * @param string $path
1576
	 * @return string
1577
	 */
1578 77
	public function getOwner($path) {
1579 77
		return $this->basicOperation('getOwner', $path);
1580
	}
1581
1582
	/**
1583
	 * get the ETag for a file or folder
1584
	 *
1585
	 * @param string $path
1586
	 * @return string
1587
	 */
1588 27
	public function getETag($path) {
1589
		/**
1590
		 * @var Storage\Storage $storage
1591
		 * @var string $internalPath
1592
		 */
1593 27
		list($storage, $internalPath) = $this->resolvePath($path);
1594 26
		if ($storage) {
1595 26
			return $storage->getETag($internalPath);
1596
		} else {
1597
			return null;
1598
		}
1599
	}
1600
1601
	/**
1602
	 * Get the path of a file by id, relative to the view
1603
	 *
1604
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1605
	 *
1606
	 * @param int $id
1607
	 * @throws NotFoundException
1608
	 * @return string
1609
	 */
1610 156
	public function getPath($id) {
1611 156
		$id = (int)$id;
1612 156
		$manager = Filesystem::getMountManager();
1613 156
		$mounts = $manager->findIn($this->fakeRoot);
1614 156
		$mounts[] = $manager->find($this->fakeRoot);
1615
		// reverse the array so we start with the storage this view is in
1616
		// which is the most likely to contain the file we're looking for
1617 156
		$mounts = array_reverse($mounts);
1618 156 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...
1619
			/**
1620
			 * @var \OC\Files\Mount\MountPoint $mount
1621
			 */
1622 156
			if ($mount->getStorage()) {
1623 156
				$cache = $mount->getStorage()->getCache();
1624 156
				$internalPath = $cache->getPathById($id);
1625 156
				if (is_string($internalPath)) {
1626 153
					$fullPath = $mount->getMountPoint() . $internalPath;
1627 153
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1628 152
						return $path;
1629
					}
1630 1
				}
1631 41
			}
1632 41
		}
1633 8
		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1634
	}
1635
1636
	/**
1637
	 * @param string $path
1638
	 * @throws InvalidPathException
1639
	 */
1640 965
	private function assertPathLength($path) {
1641 965
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1642
		// Check for the string length - performed using isset() instead of strlen()
1643
		// because isset() is about 5x-40x faster.
1644 965
		if (isset($path[$maxLen])) {
1645 41
			$pathLen = strlen($path);
1646 41
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1647
		}
1648 965
	}
1649
1650
	/**
1651
	 * check if it is allowed to move a mount point to a given target.
1652
	 * It is not allowed to move a mount point into a different mount point or
1653
	 * into an already shared folder
1654
	 *
1655
	 * @param string $target path
1656
	 * @return boolean
1657
	 */
1658 45
	private function isTargetAllowed($target) {
1659
1660 45
		list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target);
1661 45
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1662 1
			\OCP\Util::writeLog('files',
1663 1
				'It is not allowed to move one mount point into another one',
1664 1
				\OCP\Util::DEBUG);
1665 1
			return false;
1666
		}
1667
1668
		// note: cannot use the view because the target is already locked
1669 44
		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1670 44
		if ($fileId === -1) {
1671
			// target might not exist, need to check parent instead
1672 44
			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1673 44
		}
1674
1675
		// check if any of the parents were shared by the current owner (include collections)
1676 44
		$shares = \OCP\Share::getItemShared(
1677 44
			'folder',
1678 44
			$fileId,
1679 44
			\OCP\Share::FORMAT_NONE,
1680 44
			null,
1681
			true
1682 44
		);
1683
1684 44
		if (count($shares) > 0) {
1685 1
			\OCP\Util::writeLog('files',
1686 1
				'It is not allowed to move one mount point into a shared folder',
1687 1
				\OCP\Util::DEBUG);
1688 1
			return false;
1689
		}
1690
1691 43
		return true;
1692
	}
1693
1694
	/**
1695
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1696
	 *
1697
	 * @param string $path
1698
	 * @return \OCP\Files\FileInfo
1699
	 */
1700 1
	private function getPartFileInfo($path) {
1701 1
		$mount = $this->getMount($path);
1702 1
		$storage = $mount->getStorage();
1703 1
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1704 1
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1705 1
		return new FileInfo(
1706 1
			$this->getAbsolutePath($path),
1707 1
			$storage,
1708 1
			$internalPath,
1709
			[
1710 1
				'fileid' => null,
1711 1
				'mimetype' => $storage->getMimeType($internalPath),
1712 1
				'name' => basename($path),
1713 1
				'etag' => null,
1714 1
				'size' => $storage->filesize($internalPath),
1715 1
				'mtime' => $storage->filemtime($internalPath),
1716 1
				'encrypted' => false,
1717
				'permissions' => \OCP\Constants::PERMISSION_ALL
1718 1
			],
1719 1
			$mount,
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1701 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...
1720
			$owner
1721 1
		);
1722
	}
1723
1724
	/**
1725
	 * @param string $path
1726
	 * @param string $fileName
1727
	 * @throws InvalidPathException
1728
	 */
1729 169
	public function verifyPath($path, $fileName) {
1730
1731 169
		$l10n = \OC::$server->getL10N('lib');
1732
1733
		// verify empty and dot files
1734 169
		$trimmed = trim($fileName);
1735 169
		if ($trimmed === '') {
1736 2
			throw new InvalidPathException($l10n->t('Empty filename is not allowed'));
1737
		}
1738 167
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1739 8
			throw new InvalidPathException($l10n->t('Dot files are not allowed'));
1740
		}
1741
1742
		// verify database - e.g. mysql only 3-byte chars
1743 159
		if (preg_match('%(?:
1744
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1745
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1746
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1747 159
)%xs', $fileName)) {
1748 5
			throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names'));
1749
		}
1750
1751
		try {
1752
			/** @type \OCP\Files\Storage $storage */
1753 154
			list($storage, $internalPath) = $this->resolvePath($path);
1754 154
			$storage->verifyPath($internalPath, $fileName);
1755 154
		} catch (ReservedWordException $ex) {
1756 3
			throw new InvalidPathException($l10n->t('File name is a reserved word'));
1757 2
		} catch (InvalidCharacterInPathException $ex) {
1758 1
			throw new InvalidPathException($l10n->t('File name contains at least one invalid character'));
1759 1
		} catch (FileNameTooLongException $ex) {
1760 1
			throw new InvalidPathException($l10n->t('File name is too long'));
1761
		}
1762 149
	}
1763
1764
	/**
1765
	 * get all parent folders of $path
1766
	 *
1767
	 * @param string $path
1768
	 * @return string[]
1769
	 */
1770 805
	private function getParents($path) {
1771 805
		$path = trim($path, '/');
1772 805
		if (!$path) {
1773 4
			return [];
1774
		}
1775
1776 805
		$parts = explode('/', $path);
1777
1778
		// remove the single file
1779 805
		array_pop($parts);
1780 805
		$result = array('/');
1781 805
		$resultPath = '';
1782 805
		foreach ($parts as $part) {
1783 776
			if ($part) {
1784 776
				$resultPath .= '/' . $part;
1785 776
				$result[] = $resultPath;
1786 776
			}
1787 805
		}
1788 805
		return $result;
1789
	}
1790
1791
	/**
1792
	 * Returns the mount point for which to lock
1793
	 *
1794
	 * @param string $absolutePath absolute path
1795
	 * @param bool $useParentMount true to return parent mount instead of whatever
1796
	 * is mounted directly on the given path, false otherwise
1797
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1798
	 */
1799 805
	private function getMountForLock($absolutePath, $useParentMount = false) {
1800 805
		$results = [];
1801 805
		$mount = Filesystem::getMountManager()->find($absolutePath);
1802 805
		if (!$mount) {
1803
			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...
1804
		}
1805
1806 805
		if ($useParentMount) {
1807
			// find out if something is mounted directly on the path
1808 83
			$internalPath = $mount->getInternalPath($absolutePath);
1809 83
			if ($internalPath === '') {
1810
				// resolve the parent mount instead
1811 46
				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1812 46
			}
1813 83
		}
1814
1815 805
		return $mount;
1816
	}
1817
1818
	/**
1819
	 * Lock the given path
1820
	 *
1821
	 * @param string $path the path of the file to lock, relative to the view
1822
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1823
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1824
	 *
1825
	 * @return bool False if the path is excluded from locking, true otherwise
1826
	 * @throws \OCP\Lock\LockedException if the path is already locked
1827
	 */
1828 805 View Code Duplication
	private function lockPath($path, $type, $lockMountPoint = false) {
1829 805
		$absolutePath = $this->getAbsolutePath($path);
1830 805
		$absolutePath = Filesystem::normalizePath($absolutePath);
1831 805
		if (!$this->shouldLockFile($absolutePath)) {
1832 796
			return false;
1833
		}
1834
1835 805
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1836 805
		if ($mount) {
1837
			try {
1838 805
				$mount->getStorage()->acquireLock(
1839 805
					$mount->getInternalPath($absolutePath),
1840 805
					$type,
1841 805
					$this->lockingProvider
1842 805
				);
1843 805
			} catch (\OCP\Lock\LockedException $e) {
1844
				// rethrow with the a human-readable path
1845 38
				throw new \OCP\Lock\LockedException(
1846 38
					$this->getPathRelativeToFiles($absolutePath),
1847
					$e
1848 38
				);
1849
			}
1850 805
		}
1851
1852 805
		return true;
1853
	}
1854
1855
	/**
1856
	 * Change the lock type
1857
	 *
1858
	 * @param string $path the path of the file to lock, relative to the view
1859
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1860
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1861
	 *
1862
	 * @return bool False if the path is excluded from locking, true otherwise
1863
	 * @throws \OCP\Lock\LockedException if the path is already locked
1864
	 */
1865 812 View Code Duplication
	public function changeLock($path, $type, $lockMountPoint = false) {
1866 812
		$path = Filesystem::normalizePath($path);
1867 812
		$absolutePath = $this->getAbsolutePath($path);
1868 812
		$absolutePath = Filesystem::normalizePath($absolutePath);
1869 812
		if (!$this->shouldLockFile($absolutePath)) {
1870 681
			return false;
1871
		}
1872
1873 805
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1874 805
		if ($mount) {
1875
			try {
1876 805
				$mount->getStorage()->changeLock(
1877 805
					$mount->getInternalPath($absolutePath),
1878 805
					$type,
1879 805
					$this->lockingProvider
1880 805
				);
1881 805
			} catch (\OCP\Lock\LockedException $e) {
1882
				// rethrow with the a human-readable path
1883 6
				throw new \OCP\Lock\LockedException(
1884 6
					$this->getPathRelativeToFiles($absolutePath),
1885
					$e
1886 6
				);
1887
			}
1888 805
		}
1889
1890 805
		return true;
1891
	}
1892
1893
	/**
1894
	 * Unlock the given path
1895
	 *
1896
	 * @param string $path the path of the file to unlock, relative to the view
1897
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1898
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1899
	 *
1900
	 * @return bool False if the path is excluded from locking, true otherwise
1901
	 */
1902 805 View Code Duplication
	private function unlockPath($path, $type, $lockMountPoint = false) {
1903 805
		$absolutePath = $this->getAbsolutePath($path);
1904 805
		$absolutePath = Filesystem::normalizePath($absolutePath);
1905 805
		if (!$this->shouldLockFile($absolutePath)) {
1906 796
			return false;
1907
		}
1908
1909 805
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1910 805
		if ($mount) {
1911 805
			$mount->getStorage()->releaseLock(
1912 805
				$mount->getInternalPath($absolutePath),
1913 805
				$type,
1914 805
				$this->lockingProvider
1915 805
			);
1916 805
		}
1917
1918 805
		return true;
1919
	}
1920
1921
	/**
1922
	 * Lock a path and all its parents up to the root of the view
1923
	 *
1924
	 * @param string $path the path of the file to lock relative to the view
1925
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1926
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1927
	 *
1928
	 * @return bool False if the path is excluded from locking, true otherwise
1929
	 */
1930 930 View Code Duplication
	public function lockFile($path, $type, $lockMountPoint = false) {
1931 930
		$absolutePath = $this->getAbsolutePath($path);
1932 930
		$absolutePath = Filesystem::normalizePath($absolutePath);
1933 930
		if (!$this->shouldLockFile($absolutePath)) {
1934 906
			return false;
1935
		}
1936
1937 805
		$this->lockPath($path, $type, $lockMountPoint);
1938
1939 805
		$parents = $this->getParents($path);
1940 805
		foreach ($parents as $parent) {
1941 805
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
1942 805
		}
1943
1944 805
		return true;
1945
	}
1946
1947
	/**
1948
	 * Unlock a path and all its parents up to the root of the view
1949
	 *
1950
	 * @param string $path the path of the file to lock relative to the view
1951
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1952
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1953
	 *
1954
	 * @return bool False if the path is excluded from locking, true otherwise
1955
	 */
1956 930 View Code Duplication
	public function unlockFile($path, $type, $lockMountPoint = false) {
1957 930
		$absolutePath = $this->getAbsolutePath($path);
1958 930
		$absolutePath = Filesystem::normalizePath($absolutePath);
1959 930
		if (!$this->shouldLockFile($absolutePath)) {
1960 906
			return false;
1961
		}
1962
1963 805
		$this->unlockPath($path, $type, $lockMountPoint);
1964
1965 805
		$parents = $this->getParents($path);
1966 805
		foreach ($parents as $parent) {
1967 805
			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
1968 805
		}
1969
1970 805
		return true;
1971
	}
1972
1973
	/**
1974
	 * Only lock files in data/user/files/
1975
	 *
1976
	 * @param string $path Absolute path to the file/folder we try to (un)lock
1977
	 * @return bool
1978
	 */
1979 930
	protected function shouldLockFile($path) {
1980 930
		$path = Filesystem::normalizePath($path);
1981
1982 930
		$pathSegments = explode('/', $path);
1983 930
		if (isset($pathSegments[2])) {
1984
			// E.g.: /username/files/path-to-file
1985 929
			return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
1986
		}
1987
1988 677
		return true;
1989
	}
1990
1991
	/**
1992
	 * Shortens the given absolute path to be relative to
1993
	 * "$user/files".
1994
	 *
1995
	 * @param string $absolutePath absolute path which is under "files"
1996
	 *
1997
	 * @return string path relative to "files" with trimmed slashes or null
1998
	 * if the path was NOT relative to files
1999
	 *
2000
	 * @throws \InvalidArgumentException if the given path was not under "files"
2001
	 * @since 8.1.0
2002
	 */
2003 55
	public function getPathRelativeToFiles($absolutePath) {
2004 55
		$path = Filesystem::normalizePath($absolutePath);
2005 55
		$parts = explode('/', trim($path, '/'), 3);
2006
		// "$user", "files", "path/to/dir"
2007 55
		if (!isset($parts[1]) || $parts[1] !== 'files') {
2008 5
			throw new \InvalidArgumentException('$absolutePath must be relative to "files"');
2009
		}
2010 50
		if (isset($parts[2])) {
2011 48
			return $parts[2];
2012
		}
2013 2
		return '';
2014
	}
2015
}
2016