Completed
Pull Request — stable8.2 (#24656)
by Joas
12:22
created

View   F

Complexity

Total Complexity 336

Size/Duplication

Total Lines 1907
Duplicated Lines 11.43 %

Coupling/Cohesion

Components 1
Dependencies 21

Test Coverage

Coverage 87.76%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 336
c 1
b 0
f 0
lcom 1
cbo 21
dl 218
loc 1907
ccs 903
cts 1029
cp 0.8776
rs 0.6314

73 Methods

Rating   Name   Duplication   Size   Complexity  
A getMimeType() 0 4 1
A free_space() 0 4 1
A __construct() 0 14 3
A getAbsolutePath() 0 13 4
A chroot() 0 8 3
A getRoot() 0 3 1
B getRelativePath() 0 24 5
A getMountPoint() 0 3 1
A getMount() 0 3 1
A resolvePath() 0 5 1
A getLocalFile() 10 10 3
A getLocalFolder() 10 10 3
A mkdir() 0 3 1
B removeMount() 0 24 3
A rmdir() 0 12 3
A opendir() 0 3 1
A readdir() 0 4 1
A is_dir() 0 6 2
A is_file() 0 6 2
A stat() 0 3 1
A filetype() 0 3 1
A filesize() 0 3 1
A readfile() 0 15 3
A isCreatable() 0 3 1
A isReadable() 0 3 1
A isUpdatable() 0 3 1
A isDeletable() 0 8 2
A isSharable() 0 3 1
A file_exists() 0 6 2
A filemtime() 0 3 1
C touch() 0 26 7
A file_get_contents() 0 3 1
A emit_file_hooks_pre() 11 17 2
A emit_file_hooks_post() 9 14 2
C file_put_contents() 0 51 10
B unlink() 0 13 6
A deleteAll() 0 3 1
F rename() 0 114 29
D copy() 23 88 13
D fopen() 0 32 17
A toTmpFile() 0 16 3
B fromTmpFile() 0 30 5
B hash() 0 23 6
F basicOperation() 15 81 38
A getHookPath() 0 6 2
C shouldEmitHooks() 0 22 8
B runHooks() 0 29 6
A hasUpdated() 0 3 1
F getFileInfo() 11 80 22
D getDirectoryContent() 11 150 31
B putFileInfo() 0 24 4
A search() 0 3 1
A searchRaw() 0 3 1
A searchByMime() 0 3 1
A searchByTag() 0 3 1
C searchCommon() 0 42 8
A getOwner() 0 3 1
A getETag() 0 12 2
B getPath() 15 25 5
A assertPathLength() 0 9 2
B isTargetAllowed() 0 35 4
A getPartFileInfo() 0 21 1
A getUpdater() 0 3 1
C verifyPath() 0 34 7
A getParents() 0 20 4
A getMountForLock() 0 18 4
B lockPath() 26 26 4
B changeLock() 27 27 4
A unlockPath() 18 18 3
A lockFile() 16 16 3
A unlockFile() 16 16 3
A shouldLockFile() 0 11 3
A getPathRelativeToFiles() 0 12 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like View often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use View, and based on these observations, apply Extract Interface, too.

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