View   F
last analyzed

Complexity

Total Complexity 377

Size/Duplication

Total Lines 2128
Duplicated Lines 9.26 %

Coupling/Cohesion

Components 1
Dependencies 35

Importance

Changes 0
Metric Value
wmc 377
lcom 1
cbo 35
dl 197
loc 2128
rs 0.8
c 0
b 0
f 0

82 Methods

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

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1808
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1809
			Util::writeLog('files',
1810
				'It is not allowed to move one mount point into another one',
1811
				Util::DEBUG);
1812
			return false;
1813
		}
1814
1815
		return $mount1->isTargetAllowed($target);
1816
	}
1817
1818
	/**
1819
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1820
	 *
1821
	 * @param string $path
1822
	 * @return \OCP\Files\FileInfo
1823
	 */
1824
	private function getPartFileInfo($path) {
1825
		$mount = $this->getMount($path);
1826
		$storage = $mount->getStorage();
1827
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1828
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1829
		return new FileInfo(
1830
			$this->getAbsolutePath($path),
1831
			$storage,
1832
			$internalPath,
1833
			[
1834
				'fileid' => null,
1835
				'mimetype' => $storage->getMimeType($internalPath),
1836
				'name' => \basename($path),
1837
				'etag' => null,
1838
				'size' => $storage->filesize($internalPath),
1839
				'mtime' => $storage->filemtime($internalPath),
1840
				'encrypted' => false,
1841
				'permissions' => \OCP\Constants::PERMISSION_ALL
1842
			],
1843
			$mount,
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1825 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...
1844
			$owner
1845
		);
1846
	}
1847
1848
	/**
1849
	 * @param string $path
1850
	 * @param string $fileName
1851
	 * @throws InvalidPathException
1852
	 */
1853
	public function verifyPath($path, $fileName) {
1854
		$l10n = \OC::$server->getL10N('lib');
1855
1856
		// verify empty and dot files
1857
		$trimmed = \trim($fileName);
1858
		if ($trimmed === '') {
1859
			throw new InvalidPathException($l10n->t('Empty filename is not allowed'));
1860
		}
1861
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1862
			throw new InvalidPathException($l10n->t('Dot files are not allowed'));
1863
		}
1864
1865
		$matches = [];
1866
1867
		if (\preg_match('/' . FileInfo::BLACKLIST_FILES_REGEX . '/', $fileName, $matches) !== 0) {
1868
			throw new InvalidPathException(
1869
				"Can`t upload files with extension {$matches[0]} because these extensions are reserved for internal use."
1870
			);
1871
		}
1872
1873
		if (!\OC::$server->getDatabaseConnection()->allows4ByteCharacters()) {
1874
			// verify database - e.g. mysql only 3-byte chars
1875
			if (\preg_match('%(?:
1876
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1877
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1878
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1879
)%xs', $fileName)) {
1880
				throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names'));
1881
			}
1882
		}
1883
1884
		try {
1885
			/** @type \OCP\Files\Storage $storage */
1886
			list($storage, $internalPath) = $this->resolvePath($path);
1887
			$storage->verifyPath($internalPath, $fileName);
1888
		} catch (ReservedWordException $ex) {
1889
			throw new InvalidPathException($l10n->t('File name is a reserved word'));
1890
		} catch (InvalidCharacterInPathException $ex) {
1891
			throw new InvalidPathException($l10n->t('File name contains at least one invalid character'));
1892
		} catch (FileNameTooLongException $ex) {
1893
			throw new InvalidPathException($l10n->t('File name is too long'));
1894
		}
1895
	}
1896
1897
	/**
1898
	 * get all parent folders of $path
1899
	 *
1900
	 * @param string $path
1901
	 * @return string[]
1902
	 */
1903
	private function getParents($path) {
1904
		$path = \trim($path, '/');
1905
		if (!$path) {
1906
			return [];
1907
		}
1908
1909
		$parts = \explode('/', $path);
1910
1911
		// remove the single file
1912
		\array_pop($parts);
1913
		$result = ['/'];
1914
		$resultPath = '';
1915
		foreach ($parts as $part) {
1916
			if ($part) {
1917
				$resultPath .= '/' . $part;
1918
				$result[] = $resultPath;
1919
			}
1920
		}
1921
		return $result;
1922
	}
1923
1924
	/**
1925
	 * Returns the mount point for which to lock
1926
	 *
1927
	 * @param string $absolutePath absolute path
1928
	 * @param bool $useParentMount true to return parent mount instead of whatever
1929
	 * is mounted directly on the given path, false otherwise
1930
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1931
	 */
1932
	private function getMountForLock($absolutePath, $useParentMount = false) {
1933
		$results = [];
1934
		$mount = Filesystem::getMountManager()->find($absolutePath);
1935
		if (!$mount) {
1936
			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...
1937
		}
1938
1939
		if ($useParentMount) {
1940
			// find out if something is mounted directly on the path
1941
			$internalPath = $mount->getInternalPath($absolutePath);
1942
			if ($internalPath === '') {
1943
				// resolve the parent mount instead
1944
				$mount = Filesystem::getMountManager()->find(\dirname($absolutePath));
1945
			}
1946
		}
1947
1948
		return $mount;
1949
	}
1950
1951
	/**
1952
	 * Lock the given path
1953
	 *
1954
	 * @param string $path the path of the file to lock, relative to the view
1955
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1956
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1957
	 *
1958
	 * @return bool False if the path is excluded from locking, true otherwise
1959
	 * @throws \OCP\Lock\LockedException if the path is already locked
1960
	 */
1961 View Code Duplication
	private function lockPath($path, $type, $lockMountPoint = false) {
1962
		$absolutePath = $this->getAbsolutePath($path);
1963
		$absolutePath = Filesystem::normalizePath($absolutePath);
1964
		if (!$this->shouldLockFile($absolutePath)) {
1965
			return false;
1966
		}
1967
1968
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1969
		if ($mount) {
1970
			try {
1971
				$storage = $mount->getStorage();
1972
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1973
					$storage->acquireLock(
1974
						$mount->getInternalPath($absolutePath),
1975
						$type,
1976
						$this->lockingProvider
1977
					);
1978
				}
1979
			} catch (\OCP\Lock\LockedException $e) {
1980
				// rethrow with the a human-readable path
1981
				throw new \OCP\Lock\LockedException(
1982
					$this->getPathRelativeToFiles($absolutePath),
1983
					$e
1984
				);
1985
			}
1986
		}
1987
1988
		return true;
1989
	}
1990
1991
	/**
1992
	 * Change the lock type
1993
	 *
1994
	 * @param string $path the path of the file to lock, relative to the view
1995
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1996
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1997
	 *
1998
	 * @return bool False if the path is excluded from locking, true otherwise
1999
	 * @throws \OCP\Lock\LockedException if the path is already locked
2000
	 */
2001 View Code Duplication
	public function changeLock($path, $type, $lockMountPoint = false) {
2002
		$path = Filesystem::normalizePath($path);
2003
		$absolutePath = $this->getAbsolutePath($path);
2004
		$absolutePath = Filesystem::normalizePath($absolutePath);
2005
		if (!$this->shouldLockFile($absolutePath)) {
2006
			return false;
2007
		}
2008
2009
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2010
		if ($mount) {
2011
			try {
2012
				$storage = $mount->getStorage();
2013
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2014
					$storage->changeLock(
2015
						$mount->getInternalPath($absolutePath),
2016
						$type,
2017
						$this->lockingProvider
2018
					);
2019
				}
2020
			} catch (LockedException $e) {
2021
				// rethrow with the a human-readable path
2022
				throw new LockedException(
2023
					$this->getPathRelativeToFiles($absolutePath),
2024
					$e
2025
				);
2026
			}
2027
		}
2028
2029
		return true;
2030
	}
2031
2032
	/**
2033
	 * Unlock the given path
2034
	 *
2035
	 * @param string $path the path of the file to unlock, relative to the view
2036
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2037
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2038
	 *
2039
	 * @return bool False if the path is excluded from locking, true otherwise
2040
	 */
2041
	private function unlockPath($path, $type, $lockMountPoint = false) {
2042
		$absolutePath = $this->getAbsolutePath($path);
2043
		$absolutePath = Filesystem::normalizePath($absolutePath);
2044
		if (!$this->shouldLockFile($absolutePath)) {
2045
			return false;
2046
		}
2047
2048
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2049
		if ($mount) {
2050
			$storage = $mount->getStorage();
2051
			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2052
				$storage->releaseLock(
2053
					$mount->getInternalPath($absolutePath),
2054
					$type,
2055
					$this->lockingProvider
2056
				);
2057
			}
2058
		}
2059
2060
		return true;
2061
	}
2062
2063
	/**
2064
	 * Lock a path and all its parents up to the root of the view
2065
	 *
2066
	 * @param string $path the path of the file to lock relative to the view
2067
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2068
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2069
	 *
2070
	 * @return bool False if the path is excluded from locking, true otherwise
2071
	 */
2072 View Code Duplication
	public function lockFile($path, $type, $lockMountPoint = false) {
2073
		$absolutePath = $this->getAbsolutePath($path);
2074
		$absolutePath = Filesystem::normalizePath($absolutePath);
2075
		if (!$this->shouldLockFile($absolutePath)) {
2076
			return false;
2077
		}
2078
2079
		$this->lockPath($path, $type, $lockMountPoint);
2080
2081
		$parents = $this->getParents($path);
2082
		foreach ($parents as $parent) {
2083
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2084
		}
2085
2086
		return true;
2087
	}
2088
2089
	/**
2090
	 * Unlock a path and all its parents up to the root of the view
2091
	 *
2092
	 * @param string $path the path of the file to lock relative to the view
2093
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2094
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2095
	 *
2096
	 * @return bool False if the path is excluded from locking, true otherwise
2097
	 */
2098 View Code Duplication
	public function unlockFile($path, $type, $lockMountPoint = false) {
2099
		$absolutePath = $this->getAbsolutePath($path);
2100
		$absolutePath = Filesystem::normalizePath($absolutePath);
2101
		if (!$this->shouldLockFile($absolutePath)) {
2102
			return false;
2103
		}
2104
2105
		$this->unlockPath($path, $type, $lockMountPoint);
2106
2107
		$parents = $this->getParents($path);
2108
		foreach ($parents as $parent) {
2109
			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2110
		}
2111
2112
		return true;
2113
	}
2114
2115
	/**
2116
	 * Only lock files in data/user/files/
2117
	 *
2118
	 * @param string $path Absolute path to the file/folder we try to (un)lock
2119
	 * @return bool
2120
	 */
2121
	protected function shouldLockFile($path) {
2122
		$path = Filesystem::normalizePath($path);
2123
2124
		$pathSegments = \explode('/', $path);
2125
		if (isset($pathSegments[2])) {
2126
			// E.g.: /username/files/path-to-file
2127
			return ($pathSegments[2] === 'files') && (\count($pathSegments) > 3);
2128
		}
2129
2130
		return true;
2131
	}
2132
2133
	/**
2134
	 * Shortens the given absolute path to be relative to
2135
	 * "$user/files".
2136
	 *
2137
	 * @param string $absolutePath absolute path which is under "files"
2138
	 *
2139
	 * @return string path relative to "files" with trimmed slashes or null
2140
	 * if the path was NOT relative to files
2141
	 *
2142
	 * @throws \InvalidArgumentException if the given path was not under "files"
2143
	 * @since 8.1.0
2144
	 */
2145
	public function getPathRelativeToFiles($absolutePath) {
2146
		$path = Filesystem::normalizePath($absolutePath);
2147
		$parts = \explode('/', \trim($path, '/'), 3);
2148
		// "$user", "files", "path/to/dir"
2149
		if (!isset($parts[1]) || $parts[1] !== 'files') {
2150
			throw new \InvalidArgumentException('"' . $absolutePath . '" must be relative to "files"');
2151
		}
2152
		if (isset($parts[2])) {
2153
			return $parts[2];
2154
		}
2155
		return '';
2156
	}
2157
2158
	/**
2159
	 * @param string $filename
2160
	 * @return array
2161
	 * @throws \OC\User\NoUserException
2162
	 * @throws NotFoundException
2163
	 */
2164
	public function getUidAndFilename($filename) {
2165
		$info = $this->getFileInfo($filename);
2166
		if (!$info instanceof \OCP\Files\FileInfo) {
2167
			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2168
		}
2169
		$uid = $info->getOwner()->getUID();
2170
		if ($uid != \OCP\User::getUser()) {
2171
			Filesystem::initMountPoints($uid);
2172
			$ownerView = new View('/' . $uid . '/files');
2173
			try {
2174
				$filename = $ownerView->getPath($info['fileid']);
2175
			} catch (NotFoundException $e) {
2176
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2177
			}
2178
		}
2179
		return [$uid, $filename];
2180
	}
2181
2182
	/**
2183
	 * Creates parent non-existing folders
2184
	 *
2185
	 * @param string $filePath
2186
	 * @return bool
2187
	 */
2188
	private function createParentDirectories($filePath) {
2189
		$parentDirectory = \dirname($filePath);
2190
		while (!$this->file_exists($parentDirectory)) {
2191
			$result = $this->createParentDirectories($parentDirectory);
2192
			if ($result === false) {
2193
				return false;
2194
			}
2195
		}
2196
		$this->mkdir($filePath);
2197
		return true;
2198
	}
2199
2200
	/**
2201
	 * User can create part files example to a call for rename(), in effect
2202
	 * it might not be a part file. So for better control in such cases this
2203
	 * method would help to let the method in rename() to know if it is a
2204
	 * part file.
2205
	 *
2206
	 * @param bool $isIgnored
2207
	 */
2208
	public static function setIgnorePartFile($isIgnored) {
2209
		self::$ignorePartFile = $isIgnored;
2210
	}
2211
}
2212