Completed
Pull Request — master (#31)
by Blizzz
13:09 queued 04:36
created

View   F

Complexity

Total Complexity 360

Size/Duplication

Total Lines 2030
Duplicated Lines 9.7 %

Coupling/Cohesion

Components 1
Dependencies 29

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 197
loc 2030
rs 0.5217
wmc 360
lcom 1
cbo 29

81 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 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 28 3
A disableCacheUpdate() 0 3 1
A enableCacheUpdate() 0 3 1
A writeUpdate() 0 8 3
A removeUpdate() 0 5 2
A renameUpdate() 0 5 2
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 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
F rename() 0 111 29
D copy() 23 88 13
D fopen() 0 32 17
A toTmpFile() 0 16 3
B fromTmpFile() 0 30 5
A getMimeType() 0 4 1
B hash() 0 23 6
A free_space() 0 4 1
F basicOperation() 15 82 38
A getHookPath() 0 6 2
C shouldEmitHooks() 0 22 8
B runHooks() 0 29 6
A hasUpdated() 0 3 1
A getUserObjectForOwner() 0 8 2
C getCacheEntry() 0 30 7
C getFileInfo() 0 53 15
D getDirectoryContent() 0 124 27
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 44 8
A getOwner() 0 7 2
A getETag() 0 12 2
B getPath() 15 25 5
A assertPathLength() 0 9 2
B isTargetAllowed() 0 35 4
A getPartFileInfo() 0 23 1
C verifyPath() 0 34 7
A getParents() 0 20 4
A getMountForLock() 0 18 4
B lockPath() 29 29 5
B changeLock() 30 30 5
A unlockPath() 0 21 4
A lockFile() 16 16 3
A unlockFile() 16 16 3
A shouldLockFile() 0 11 3
A getPathRelativeToFiles() 0 12 4
A getUidAndFilename() 0 17 4
A readfile() 0 15 3
B rmdir() 5 19 5
A opendir() 0 3 1
B readfilePart() 0 24 6
B unlink() 8 21 8
A deleteAll() 0 3 1

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

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

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

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

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
338
		}
339
		if ($this->is_dir($path)) {
340
			$result = $this->basicOperation('rmdir', $path, array('delete'));
341
		} else {
342
			$result = false;
343
		}
344
345 View Code Duplication
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
346
			$storage = $mount->getStorage();
347
			$internalPath = $mount->getInternalPath($absolutePath);
348
			$storage->getUpdater()->remove($internalPath);
349
		}
350
		return $result;
351
	}
352
353
	/**
354
	 * @param string $path
355
	 * @return resource
356
	 */
357
	public function opendir($path) {
358
		return $this->basicOperation('opendir', $path, array('read'));
359
	}
360
361
	/**
362
	 * @param $handle
363
	 * @return mixed
364
	 */
365
	public function readdir($handle) {
366
		$fsLocal = new Storage\Local(array('datadir' => '/'));
367
		return $fsLocal->readdir($handle);
368
	}
369
370
	/**
371
	 * @param string $path
372
	 * @return bool|mixed
373
	 */
374
	public function is_dir($path) {
375
		if ($path == '/') {
376
			return true;
377
		}
378
		return $this->basicOperation('is_dir', $path);
379
	}
380
381
	/**
382
	 * @param string $path
383
	 * @return bool|mixed
384
	 */
385
	public function is_file($path) {
386
		if ($path == '/') {
387
			return false;
388
		}
389
		return $this->basicOperation('is_file', $path);
390
	}
391
392
	/**
393
	 * @param string $path
394
	 * @return mixed
395
	 */
396
	public function stat($path) {
397
		return $this->basicOperation('stat', $path);
398
	}
399
400
	/**
401
	 * @param string $path
402
	 * @return mixed
403
	 */
404
	public function filetype($path) {
405
		return $this->basicOperation('filetype', $path);
406
	}
407
408
	/**
409
	 * @param string $path
410
	 * @return mixed
411
	 */
412
	public function filesize($path) {
413
		return $this->basicOperation('filesize', $path);
414
	}
415
416
	/**
417
	 * @param string $path
418
	 * @return bool|mixed
419
	 * @throws \OCP\Files\InvalidPathException
420
	 */
421
	public function readfile($path) {
422
		$this->assertPathLength($path);
423
		@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
424
		$handle = $this->fopen($path, 'rb');
425
		if ($handle) {
426
			$chunkSize = 8192; // 8 kB chunks
427
			while (!feof($handle)) {
428
				echo fread($handle, $chunkSize);
429
				flush();
430
			}
431
			$size = $this->filesize($path);
432
			return $size;
433
		}
434
		return false;
435
	}
436
437
	/**
438
	 * @param string $path
439
	 * @param int $from
440
	 * @param int $to
441
	 * @return bool|mixed
442
	 * @throws \OCP\Files\InvalidPathException
443
	 * @throws \OCP\Files\UnseekableException
444
	 */
445
	public function readfilePart($path, $from, $to) {
446
		$this->assertPathLength($path);
447
		@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

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

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

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

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

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
688
		}
689
		$result = $this->basicOperation('unlink', $path, array('delete'));
690 View Code Duplication
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

Loading history...
838
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
839
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
840
		$result = false;
841
		if (
842
			Filesystem::isValidPath($path2)
843
			and Filesystem::isValidPath($path1)
844
			and !Filesystem::isFileBlacklisted($path2)
845
		) {
846
			$path1 = $this->getRelativePath($absolutePath1);
847
			$path2 = $this->getRelativePath($absolutePath2);
848
849
			if ($path1 == null or $path2 == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $path1 of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
Bug introduced by
It seems like you are loosely comparing $path2 of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
850
				return false;
851
			}
852
			$run = true;
853
854
			$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
855
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
856
			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
857
			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
858
859
			try {
860
861
				$exists = $this->file_exists($path2);
862 View Code Duplication
				if ($this->shouldEmitHooks()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1264
		}
1265
	}
1266
1267
	/**
1268
	 * Get file info from cache
1269
	 *
1270
	 * If the file is not in cached it will be scanned
1271
	 * If the file has changed on storage the cache will be updated
1272
	 *
1273
	 * @param \OC\Files\Storage\Storage $storage
1274
	 * @param string $internalPath
1275
	 * @param string $relativePath
1276
	 * @return array|bool
1277
	 */
1278
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1279
		$cache = $storage->getCache($internalPath);
1280
		$data = $cache->get($internalPath);
1281
		$watcher = $storage->getWatcher($internalPath);
1282
1283
		try {
1284
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1285
			if (!$data || $data['size'] === -1) {
1286
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1287
				if (!$storage->file_exists($internalPath)) {
1288
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1289
					return false;
1290
				}
1291
				$scanner = $storage->getScanner($internalPath);
1292
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1293
				$data = $cache->get($internalPath);
1294
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1295
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1296
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1297
				$watcher->update($internalPath, $data);
1298
				$storage->getPropagator()->propagateChange($internalPath, time());
1299
				$data = $cache->get($internalPath);
1300
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1301
			}
1302
		} catch (LockedException $e) {
1303
			// if the file is locked we just use the old cache info
1304
		}
1305
1306
		return $data;
1307
	}
1308
1309
	/**
1310
	 * get the filesystem info
1311
	 *
1312
	 * @param string $path
1313
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1314
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1315
	 * defaults to true
1316
	 * @return \OC\Files\FileInfo|false False if file does not exist
1317
	 */
1318
	public function getFileInfo($path, $includeMountPoints = true) {
1319
		$this->assertPathLength($path);
1320
		if (!Filesystem::isValidPath($path)) {
1321
			return false;
1322
		}
1323
		if (Cache\Scanner::isPartialFile($path)) {
1324
			return $this->getPartFileInfo($path);
1325
		}
1326
		$relativePath = $path;
1327
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1328
1329
		$mount = Filesystem::getMountManager()->find($path);
1330
		$storage = $mount->getStorage();
1331
		$internalPath = $mount->getInternalPath($path);
1332
		if ($storage) {
1333
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1334
1335
			if (!$data instanceof ICacheEntry) {
1336
				return false;
1337
			}
1338
1339
			if ($mount instanceof MoveableMount && $internalPath === '') {
1340
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1341
			}
1342
1343
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1344
			$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 1329 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...
1345
1346
			if ($data and isset($data['fileid'])) {
1347
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1348
					//add the sizes of other mount points to the folder
1349
					$extOnly = ($includeMountPoints === 'ext');
1350
					$mounts = Filesystem::getMountManager()->findIn($path);
1351
					foreach ($mounts as $mount) {
1352
						$subStorage = $mount->getStorage();
1353
						if ($subStorage) {
1354
							// exclude shared storage ?
1355
							if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) {
1356
								continue;
1357
							}
1358
							$subCache = $subStorage->getCache('');
1359
							$rootEntry = $subCache->get('');
1360
							$info->addSubEntry($rootEntry, $mount->getMountPoint());
1361
						}
1362
					}
1363
				}
1364
			}
1365
1366
			return $info;
1367
		}
1368
1369
		return false;
1370
	}
1371
1372
	/**
1373
	 * get the content of a directory
1374
	 *
1375
	 * @param string $directory path under datadirectory
1376
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1377
	 * @return FileInfo[]
1378
	 */
1379
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1380
		$this->assertPathLength($directory);
1381
		if (!Filesystem::isValidPath($directory)) {
1382
			return [];
1383
		}
1384
		$path = $this->getAbsolutePath($directory);
1385
		$path = Filesystem::normalizePath($path);
1386
		$mount = $this->getMount($directory);
1387
		$storage = $mount->getStorage();
1388
		$internalPath = $mount->getInternalPath($path);
1389
		if ($storage) {
1390
			$cache = $storage->getCache($internalPath);
1391
			$user = \OC_User::getUser();
1392
1393
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1394
1395
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1396
				return [];
1397
			}
1398
1399
			$folderId = $data['fileid'];
1400
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1401
1402
			$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
1403
			/**
1404
			 * @var \OC\Files\FileInfo[] $files
1405
			 */
1406
			$files = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1407
				if ($sharingDisabled) {
1408
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1409
				}
1410
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1411
				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 1386 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...
1412
			}, $contents);
1413
1414
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1415
			$mounts = Filesystem::getMountManager()->findIn($path);
1416
			$dirLength = strlen($path);
1417
			foreach ($mounts as $mount) {
1418
				$mountPoint = $mount->getMountPoint();
1419
				$subStorage = $mount->getStorage();
1420
				if ($subStorage) {
1421
					$subCache = $subStorage->getCache('');
1422
1423
					$rootEntry = $subCache->get('');
1424
					if (!$rootEntry) {
1425
						$subScanner = $subStorage->getScanner('');
1426
						try {
1427
							$subScanner->scanFile('');
1428
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1429
							continue;
1430
						} catch (\OCP\Files\StorageInvalidException $e) {
1431
							continue;
1432
						} catch (\Exception $e) {
1433
							// sometimes when the storage is not available it can be any exception
1434
							\OCP\Util::writeLog(
1435
								'core',
1436
								'Exception while scanning storage "' . $subStorage->getId() . '": ' .
1437
								get_class($e) . ': ' . $e->getMessage(),
1438
								\OCP\Util::ERROR
1439
							);
1440
							continue;
1441
						}
1442
						$rootEntry = $subCache->get('');
1443
					}
1444
1445
					if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1446
						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1447
						if ($pos = strpos($relativePath, '/')) {
1448
							//mountpoint inside subfolder add size to the correct folder
1449
							$entryName = substr($relativePath, 0, $pos);
1450
							foreach ($files as &$entry) {
1451
								if ($entry->getName() === $entryName) {
1452
									$entry->addSubEntry($rootEntry, $mountPoint);
1453
								}
1454
							}
1455
						} else { //mountpoint in this folder, add an entry for it
1456
							$rootEntry['name'] = $relativePath;
1457
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1458
							$permissions = $rootEntry['permissions'];
1459
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1460
							// for shared files/folders we use the permissions given by the owner
1461
							if ($mount instanceof MoveableMount) {
1462
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1463
							} else {
1464
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1465
							}
1466
1467
							//remove any existing entry with the same name
1468
							foreach ($files as $i => $file) {
1469
								if ($file['name'] === $rootEntry['name']) {
1470
									unset($files[$i]);
1471
									break;
1472
								}
1473
							}
1474
							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1475
1476
							// if sharing was disabled for the user we remove the share permissions
1477
							if (\OCP\Util::isSharingDisabledForUser()) {
1478
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1479
							}
1480
1481
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1482
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1483
						}
1484
					}
1485
				}
1486
			}
1487
1488
			if ($mimetype_filter) {
1489
				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1490
					if (strpos($mimetype_filter, '/')) {
1491
						return $file->getMimetype() === $mimetype_filter;
1492
					} else {
1493
						return $file->getMimePart() === $mimetype_filter;
1494
					}
1495
				});
1496
			}
1497
1498
			return $files;
1499
		} else {
1500
			return [];
1501
		}
1502
	}
1503
1504
	/**
1505
	 * change file metadata
1506
	 *
1507
	 * @param string $path
1508
	 * @param array|\OCP\Files\FileInfo $data
1509
	 * @return int
1510
	 *
1511
	 * returns the fileid of the updated file
1512
	 */
1513
	public function putFileInfo($path, $data) {
1514
		$this->assertPathLength($path);
1515
		if ($data instanceof FileInfo) {
1516
			$data = $data->getData();
1517
		}
1518
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1519
		/**
1520
		 * @var \OC\Files\Storage\Storage $storage
1521
		 * @var string $internalPath
1522
		 */
1523
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1524
		if ($storage) {
1525
			$cache = $storage->getCache($path);
1526
1527
			if (!$cache->inCache($internalPath)) {
1528
				$scanner = $storage->getScanner($internalPath);
1529
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1530
			}
1531
1532
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1513 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...
1533
		} else {
1534
			return -1;
1535
		}
1536
	}
1537
1538
	/**
1539
	 * search for files with the name matching $query
1540
	 *
1541
	 * @param string $query
1542
	 * @return FileInfo[]
1543
	 */
1544
	public function search($query) {
1545
		return $this->searchCommon('search', array('%' . $query . '%'));
1546
	}
1547
1548
	/**
1549
	 * search for files with the name matching $query
1550
	 *
1551
	 * @param string $query
1552
	 * @return FileInfo[]
1553
	 */
1554
	public function searchRaw($query) {
1555
		return $this->searchCommon('search', array($query));
1556
	}
1557
1558
	/**
1559
	 * search for files by mimetype
1560
	 *
1561
	 * @param string $mimetype
1562
	 * @return FileInfo[]
1563
	 */
1564
	public function searchByMime($mimetype) {
1565
		return $this->searchCommon('searchByMime', array($mimetype));
1566
	}
1567
1568
	/**
1569
	 * search for files by tag
1570
	 *
1571
	 * @param string|int $tag name or tag id
1572
	 * @param string $userId owner of the tags
1573
	 * @return FileInfo[]
1574
	 */
1575
	public function searchByTag($tag, $userId) {
1576
		return $this->searchCommon('searchByTag', array($tag, $userId));
1577
	}
1578
1579
	/**
1580
	 * @param string $method cache method
1581
	 * @param array $args
1582
	 * @return FileInfo[]
1583
	 */
1584
	private function searchCommon($method, $args) {
1585
		$files = array();
1586
		$rootLength = strlen($this->fakeRoot);
1587
1588
		$mount = $this->getMount('');
1589
		$mountPoint = $mount->getMountPoint();
1590
		$storage = $mount->getStorage();
1591
		if ($storage) {
1592
			$cache = $storage->getCache('');
1593
1594
			$results = call_user_func_array(array($cache, $method), $args);
1595
			foreach ($results as $result) {
1596
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1597
					$internalPath = $result['path'];
1598
					$path = $mountPoint . $result['path'];
1599
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1600
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1601
					$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 1588 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...
1602
				}
1603
			}
1604
1605
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1606
			foreach ($mounts as $mount) {
1607
				$mountPoint = $mount->getMountPoint();
1608
				$storage = $mount->getStorage();
1609
				if ($storage) {
1610
					$cache = $storage->getCache('');
1611
1612
					$relativeMountPoint = substr($mountPoint, $rootLength);
1613
					$results = call_user_func_array(array($cache, $method), $args);
1614
					if ($results) {
1615
						foreach ($results as $result) {
1616
							$internalPath = $result['path'];
1617
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1618
							$path = rtrim($mountPoint . $internalPath, '/');
1619
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1620
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1621
						}
1622
					}
1623
				}
1624
			}
1625
		}
1626
		return $files;
1627
	}
1628
1629
	/**
1630
	 * Get the owner for a file or folder
1631
	 *
1632
	 * @param string $path
1633
	 * @return string the user id of the owner
1634
	 * @throws NotFoundException
1635
	 */
1636
	public function getOwner($path) {
1637
		$info = $this->getFileInfo($path);
1638
		if (!$info) {
1639
			throw new NotFoundException($path . ' not found while trying to get owner');
1640
		}
1641
		return $info->getOwner()->getUID();
1642
	}
1643
1644
	/**
1645
	 * get the ETag for a file or folder
1646
	 *
1647
	 * @param string $path
1648
	 * @return string
1649
	 */
1650
	public function getETag($path) {
1651
		/**
1652
		 * @var Storage\Storage $storage
1653
		 * @var string $internalPath
1654
		 */
1655
		list($storage, $internalPath) = $this->resolvePath($path);
1656
		if ($storage) {
1657
			return $storage->getETag($internalPath);
1658
		} else {
1659
			return null;
1660
		}
1661
	}
1662
1663
	/**
1664
	 * Get the path of a file by id, relative to the view
1665
	 *
1666
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1667
	 *
1668
	 * @param int $id
1669
	 * @throws NotFoundException
1670
	 * @return string
1671
	 */
1672
	public function getPath($id) {
1673
		$id = (int)$id;
1674
		$manager = Filesystem::getMountManager();
1675
		$mounts = $manager->findIn($this->fakeRoot);
1676
		$mounts[] = $manager->find($this->fakeRoot);
1677
		// reverse the array so we start with the storage this view is in
1678
		// which is the most likely to contain the file we're looking for
1679
		$mounts = array_reverse($mounts);
1680 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...
1681
			/**
1682
			 * @var \OC\Files\Mount\MountPoint $mount
1683
			 */
1684
			if ($mount->getStorage()) {
1685
				$cache = $mount->getStorage()->getCache();
1686
				$internalPath = $cache->getPathById($id);
1687
				if (is_string($internalPath)) {
1688
					$fullPath = $mount->getMountPoint() . $internalPath;
1689
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1690
						return $path;
1691
					}
1692
				}
1693
			}
1694
		}
1695
		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1696
	}
1697
1698
	/**
1699
	 * @param string $path
1700
	 * @throws InvalidPathException
1701
	 */
1702
	private function assertPathLength($path) {
1703
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1704
		// Check for the string length - performed using isset() instead of strlen()
1705
		// because isset() is about 5x-40x faster.
1706
		if (isset($path[$maxLen])) {
1707
			$pathLen = strlen($path);
1708
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1709
		}
1710
	}
1711
1712
	/**
1713
	 * check if it is allowed to move a mount point to a given target.
1714
	 * It is not allowed to move a mount point into a different mount point or
1715
	 * into an already shared folder
1716
	 *
1717
	 * @param string $target path
1718
	 * @return boolean
1719
	 */
1720
	private function isTargetAllowed($target) {
1721
1722
		list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target);
1723
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1724
			\OCP\Util::writeLog('files',
1725
				'It is not allowed to move one mount point into another one',
1726
				\OCP\Util::DEBUG);
1727
			return false;
1728
		}
1729
1730
		// note: cannot use the view because the target is already locked
1731
		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1732
		if ($fileId === -1) {
1733
			// target might not exist, need to check parent instead
1734
			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1735
		}
1736
1737
		// check if any of the parents were shared by the current owner (include collections)
1738
		$shares = \OCP\Share::getItemShared(
1739
			'folder',
1740
			$fileId,
1741
			\OCP\Share::FORMAT_NONE,
1742
			null,
1743
			true
1744
		);
1745
1746
		if (count($shares) > 0) {
1747
			\OCP\Util::writeLog('files',
1748
				'It is not allowed to move one mount point into a shared folder',
1749
				\OCP\Util::DEBUG);
1750
			return false;
1751
		}
1752
1753
		return true;
1754
	}
1755
1756
	/**
1757
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1758
	 *
1759
	 * @param string $path
1760
	 * @return \OCP\Files\FileInfo
1761
	 */
1762
	private function getPartFileInfo($path) {
1763
		$mount = $this->getMount($path);
1764
		$storage = $mount->getStorage();
1765
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1766
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1767
		return new FileInfo(
1768
			$this->getAbsolutePath($path),
1769
			$storage,
1770
			$internalPath,
1771
			[
1772
				'fileid' => null,
1773
				'mimetype' => $storage->getMimeType($internalPath),
1774
				'name' => basename($path),
1775
				'etag' => null,
1776
				'size' => $storage->filesize($internalPath),
1777
				'mtime' => $storage->filemtime($internalPath),
1778
				'encrypted' => false,
1779
				'permissions' => \OCP\Constants::PERMISSION_ALL
1780
			],
1781
			$mount,
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1763 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...
1782
			$owner
1783
		);
1784
	}
1785
1786
	/**
1787
	 * @param string $path
1788
	 * @param string $fileName
1789
	 * @throws InvalidPathException
1790
	 */
1791
	public function verifyPath($path, $fileName) {
1792
1793
		$l10n = \OC::$server->getL10N('lib');
1794
1795
		// verify empty and dot files
1796
		$trimmed = trim($fileName);
1797
		if ($trimmed === '') {
1798
			throw new InvalidPathException($l10n->t('Empty filename is not allowed'));
1799
		}
1800
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1801
			throw new InvalidPathException($l10n->t('Dot files are not allowed'));
1802
		}
1803
1804
		// verify database - e.g. mysql only 3-byte chars
1805
		if (preg_match('%(?:
1806
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1807
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1808
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1809
)%xs', $fileName)) {
1810
			throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names'));
1811
		}
1812
1813
		try {
1814
			/** @type \OCP\Files\Storage $storage */
1815
			list($storage, $internalPath) = $this->resolvePath($path);
1816
			$storage->verifyPath($internalPath, $fileName);
1817
		} catch (ReservedWordException $ex) {
1818
			throw new InvalidPathException($l10n->t('File name is a reserved word'));
1819
		} catch (InvalidCharacterInPathException $ex) {
1820
			throw new InvalidPathException($l10n->t('File name contains at least one invalid character'));
1821
		} catch (FileNameTooLongException $ex) {
1822
			throw new InvalidPathException($l10n->t('File name is too long'));
1823
		}
1824
	}
1825
1826
	/**
1827
	 * get all parent folders of $path
1828
	 *
1829
	 * @param string $path
1830
	 * @return string[]
1831
	 */
1832
	private function getParents($path) {
1833
		$path = trim($path, '/');
1834
		if (!$path) {
1835
			return [];
1836
		}
1837
1838
		$parts = explode('/', $path);
1839
1840
		// remove the single file
1841
		array_pop($parts);
1842
		$result = array('/');
1843
		$resultPath = '';
1844
		foreach ($parts as $part) {
1845
			if ($part) {
1846
				$resultPath .= '/' . $part;
1847
				$result[] = $resultPath;
1848
			}
1849
		}
1850
		return $result;
1851
	}
1852
1853
	/**
1854
	 * Returns the mount point for which to lock
1855
	 *
1856
	 * @param string $absolutePath absolute path
1857
	 * @param bool $useParentMount true to return parent mount instead of whatever
1858
	 * is mounted directly on the given path, false otherwise
1859
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1860
	 */
1861
	private function getMountForLock($absolutePath, $useParentMount = false) {
1862
		$results = [];
1863
		$mount = Filesystem::getMountManager()->find($absolutePath);
1864
		if (!$mount) {
1865
			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...
1866
		}
1867
1868
		if ($useParentMount) {
1869
			// find out if something is mounted directly on the path
1870
			$internalPath = $mount->getInternalPath($absolutePath);
1871
			if ($internalPath === '') {
1872
				// resolve the parent mount instead
1873
				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1874
			}
1875
		}
1876
1877
		return $mount;
1878
	}
1879
1880
	/**
1881
	 * Lock the given path
1882
	 *
1883
	 * @param string $path the path of the file to lock, relative to the view
1884
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1885
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1886
	 *
1887
	 * @return bool False if the path is excluded from locking, true otherwise
1888
	 * @throws \OCP\Lock\LockedException if the path is already locked
1889
	 */
1890 View Code Duplication
	private function lockPath($path, $type, $lockMountPoint = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
2100
			Filesystem::initMountPoints($uid);
2101
			$ownerView = new View('/' . $uid . '/files');
2102
			try {
2103
				$filename = $ownerView->getPath($info['fileid']);
2104
			} catch (NotFoundException $e) {
2105
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2106
			}
2107
		}
2108
		return [$uid, $filename];
2109
	}
2110
}
2111