Completed
Pull Request — master (#32044)
by Thomas
19:39
created

View::readfilePart()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 5
nop 3
dl 0
loc 23
rs 8.9297
c 0
b 0
f 0
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 function GuzzleHttp\Psr7\stream_for;
49
use Icewind\Streams\CallbackWrapper;
50
use OC\Files\Mount\MoveableMount;
51
use OC\Files\Storage\Storage;
52
use OC\User\RemoteUser;
53
use OCP\Constants;
54
use OCP\Events\EventEmitterTrait;
55
use OCP\Files\Cache\ICacheEntry;
56
use OCP\Files\FileNameTooLongException;
57
use OCP\Files\InvalidCharacterInPathException;
58
use OCP\Files\InvalidPathException;
59
use OCP\Files\NotFoundException;
60
use OCP\Files\ReservedWordException;
61
use OCP\IUser;
62
use OCP\Lock\ILockingProvider;
63
use OCP\Lock\LockedException;
64
use OCA\Files_Sharing\SharedMount;
65
use OCP\Util;
66
use Psr\Http\Message\StreamInterface;
67
use Symfony\Component\EventDispatcher\GenericEvent;
68
69
/**
70
 * Class to provide access to ownCloud filesystem via a "view", and methods for
71
 * working with files within that view (e.g. read, write, delete, etc.). Each
72
 * view is restricted to a set of directories via a virtual root. The default view
73
 * uses the currently logged in user's data directory as root (parts of
74
 * OC_Filesystem are merely a wrapper for OC\Files\View).
75
 *
76
 * Apps that need to access files outside of the user data folders (to modify files
77
 * belonging to a user other than the one currently logged in, for example) should
78
 * use this class directly rather than using OC_Filesystem, or making use of PHP's
79
 * built-in file manipulation functions. This will ensure all hooks and proxies
80
 * are triggered correctly.
81
 *
82
 * Filesystem functions are not called directly; they are passed to the correct
83
 * \OC\Files\Storage\Storage object
84
 */
85
class View {
86
	use EventEmitterTrait;
87
	/** @var string */
88
	private $fakeRoot = '';
89
90
	/**
91
	 * @var \OCP\Lock\ILockingProvider
92
	 */
93
	private $lockingProvider;
94
95
	/** @var bool  */
96
	private $lockingEnabled;
97
98
	/** @var bool  */
99
	private $updaterEnabled = true;
100
101
	/** @var \OC\User\Manager  */
102
	private $userManager;
103
104
	/** @var \OCP\ILogger  */
105
	private $logger;
106
107
	private $eventDispatcher;
108
109
	/**
110
	 * @param string $root
111
	 * @throws \Exception If $root contains an invalid path
112
	 */
113
	public function __construct($root = '') {
114
		if ($root === null) {
115
			throw new \InvalidArgumentException('Root can\'t be null');
116
		}
117
		if (!Filesystem::isValidPath($root)) {
118
			throw new \Exception();
119
		}
120
121
		$this->fakeRoot = $root;
122
		$this->lockingProvider = \OC::$server->getLockingProvider();
123
		$this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider);
124
		$this->userManager = \OC::$server->getUserManager();
125
		$this->logger = \OC::$server->getLogger();
126
		$this->eventDispatcher = \OC::$server->getEventDispatcher();
127
	}
128
129
	public function getAbsolutePath($path = '/') {
130
		if ($path === null) {
131
			return null;
132
		}
133
		$this->assertPathLength($path);
134
		if ($path === '') {
135
			$path = '/';
136
		}
137
		if ($path[0] !== '/') {
138
			$path = '/' . $path;
139
		}
140
		return $this->fakeRoot . $path;
141
	}
142
143
	/**
144
	 * change the root to a fake root
145
	 *
146
	 * @param string $fakeRoot
147
	 * @return boolean|null
148
	 */
149
	public function chroot($fakeRoot) {
150
		if (!$fakeRoot == '') {
151
			if ($fakeRoot[0] !== '/') {
152
				$fakeRoot = '/' . $fakeRoot;
153
			}
154
		}
155
		$this->fakeRoot = $fakeRoot;
156
	}
157
158
	/**
159
	 * get the fake root
160
	 *
161
	 * @return string
162
	 */
163
	public function getRoot() {
164
		return $this->fakeRoot;
165
	}
166
167
	/**
168
	 * get path relative to the root of the view
169
	 *
170
	 * @param string $path
171
	 * @return string
172
	 */
173
	public function getRelativePath($path) {
174
		$this->assertPathLength($path);
175
		if ($this->fakeRoot == '') {
176
			return $path;
177
		}
178
179
		if (\rtrim($path, '/') === \rtrim($this->fakeRoot, '/')) {
180
			return '/';
181
		}
182
183
		// missing slashes can cause wrong matches!
184
		$root = \rtrim($this->fakeRoot, '/') . '/';
185
186
		if (\strpos($path, $root) !== 0) {
187
			return null;
188
		} else {
189
			$path = \substr($path, \strlen($this->fakeRoot));
190
			if (\strlen($path) === 0) {
191
				return '/';
192
			} else {
193
				return $path;
194
			}
195
		}
196
	}
197
198
	/**
199
	 * get the mountpoint of the storage object for a path
200
	 * ( note: because a storage is not always mounted inside the fakeroot, the
201
	 * returned mountpoint is relative to the absolute root of the filesystem
202
	 * and does not take the chroot into account )
203
	 *
204
	 * @param string $path
205
	 * @return string
206
	 */
207
	public function getMountPoint($path) {
208
		return Filesystem::getMountPoint($this->getAbsolutePath($path));
209
	}
210
211
	/**
212
	 * get the mountpoint of the storage object for a path
213
	 * ( note: because a storage is not always mounted inside the fakeroot, the
214
	 * returned mountpoint is relative to the absolute root of the filesystem
215
	 * and does not take the chroot into account )
216
	 *
217
	 * @param string $path
218
	 * @return \OCP\Files\Mount\IMountPoint
219
	 */
220
	public function getMount($path) {
221
		return Filesystem::getMountManager()->find($this->getAbsolutePath($path));
222
	}
223
224
	/**
225
	 * resolve a path to a storage and internal path
226
	 *
227
	 * @param string $path
228
	 * @return array an array consisting of the storage and the internal path
229
	 */
230
	public function resolvePath($path) {
231
		$a = $this->getAbsolutePath($path);
232
		$p = Filesystem::normalizePath($a);
233
		return Filesystem::resolvePath($p);
234
	}
235
236
	/**
237
	 * return the path to a local version of the file
238
	 * we need this because we can't know if a file is stored local or not from
239
	 * outside the filestorage and for some purposes a local file is needed
240
	 *
241
	 * @param string $path
242
	 * @return string
243
	 */
244 View Code Duplication
	public function getLocalFile($path) {
245
		$parent = \substr($path, 0, \strrpos($path, '/'));
246
		$path = $this->getAbsolutePath($path);
247
		list($storage, $internalPath) = Filesystem::resolvePath($path);
248
		if (Filesystem::isValidPath($parent) and $storage) {
249
			return $storage->getLocalFile($internalPath);
250
		} else {
251
			return null;
252
		}
253
	}
254
255
	/**
256
	 * @param string $path
257
	 * @return string
258
	 */
259 View Code Duplication
	public function getLocalFolder($path) {
260
		$parent = \substr($path, 0, \strrpos($path, '/'));
261
		$path = $this->getAbsolutePath($path);
262
		list($storage, $internalPath) = Filesystem::resolvePath($path);
263
		if (Filesystem::isValidPath($parent) and $storage) {
264
			return $storage->getLocalFolder($internalPath);
265
		} else {
266
			return null;
267
		}
268
	}
269
270
	/**
271
	 * the following functions operate with arguments and return values identical
272
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
273
	 * for \OC\Files\Storage\Storage via basicOperation().
274
	 */
275
	public function mkdir($path) {
276
		return $this->emittingCall(function () use (&$path) {
277
			$result = $this->basicOperation('mkdir', $path, ['create', 'write']);
278
			return $result;
279
		}, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'create');
280
	}
281
282
	/**
283
	 * remove mount point
284
	 *
285
	 * @param \OC\Files\Mount\MoveableMount $mount
286
	 * @param string $path relative to data/
287
	 * @return boolean
288
	 */
289
	protected function removeMount($mount, $path) {
290
		if ($mount instanceof MoveableMount) {
291
			// cut of /user/files to get the relative path to data/user/files
292
			$pathParts = \explode('/', $path, 4);
293
			$relPath = '/' . $pathParts[3];
294
			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
295
			\OC_Hook::emit(
296
				Filesystem::CLASSNAME, "umount",
297
				[Filesystem::signal_param_path => $relPath]
298
			);
299
			$this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
300
			$result = $mount->removeMount();
301
			$this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
302
			if ($result) {
303
				\OC_Hook::emit(
304
					Filesystem::CLASSNAME, "post_umount",
305
					[Filesystem::signal_param_path => $relPath]
306
				);
307
			}
308
			$this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
309
			return $result;
310
		} else {
311
			// do not allow deleting the storage's root / the mount point
312
			// because for some storages it might delete the whole contents
313
			// but isn't supposed to work that way
314
			return false;
315
		}
316
	}
317
318
	public function disableCacheUpdate() {
319
		$this->updaterEnabled = false;
320
	}
321
322
	public function enableCacheUpdate() {
323
		$this->updaterEnabled = true;
324
	}
325
326
	protected function writeUpdate(Storage $storage, $internalPath, $time = null) {
327
		if ($this->updaterEnabled) {
328
			if ($time === null) {
329
				$time = \time();
330
			}
331
			$storage->getUpdater()->update($internalPath, $time);
332
		}
333
	}
334
335
	protected function removeUpdate(Storage $storage, $internalPath) {
336
		if ($this->updaterEnabled) {
337
			$storage->getUpdater()->remove($internalPath);
338
		}
339
	}
340
341
	protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) {
342
		if ($this->updaterEnabled) {
343
			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
344
		}
345
	}
346
347
	/**
348
	 * @param string $path
349
	 * @return bool|mixed
350
	 */
351
	public function rmdir($path) {
352
		return $this->emittingCall(function () use (&$path) {
353
			$absolutePath = $this->getAbsolutePath($path);
354
			$mount = Filesystem::getMountManager()->find($absolutePath);
355
			if ($mount->getInternalPath($absolutePath) === '') {
356
				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...
357
			}
358
			if ($this->is_dir($path)) {
359
				$result = $this->basicOperation('rmdir', $path, ['delete']);
360
			} else {
361
				$result = false;
362
			}
363
364 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...
365
				$storage = $mount->getStorage();
366
				$internalPath = $mount->getInternalPath($absolutePath);
367
				$storage->getUpdater()->remove($internalPath);
368
			}
369
370
			return $result;
371
		}, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'delete');
372
	}
373
374
	/**
375
	 * @param string $path
376
	 * @return resource
377
	 */
378
	public function opendir($path) {
379
		return $this->basicOperation('opendir', $path, ['read']);
380
	}
381
382
	/**
383
	 * @param string $path
384
	 * @return bool|mixed
385
	 */
386
	public function is_dir($path) {
387
		if ($path == '/') {
388
			return true;
389
		}
390
		return $this->basicOperation('is_dir', $path);
391
	}
392
393
	/**
394
	 * @param string $path
395
	 * @return bool|mixed
396
	 */
397
	public function is_file($path) {
398
		if ($path == '/') {
399
			return false;
400
		}
401
		return $this->basicOperation('is_file', $path);
402
	}
403
404
	/**
405
	 * @param string $path
406
	 * @return mixed
407
	 */
408
	public function stat($path) {
409
		return $this->basicOperation('stat', $path);
410
	}
411
412
	/**
413
	 * @param string $path
414
	 * @return mixed
415
	 */
416
	public function filetype($path) {
417
		return $this->basicOperation('filetype', $path);
418
	}
419
420
	/**
421
	 * @param string $path
422
	 * @return mixed
423
	 */
424
	public function filesize($path) {
425
		return $this->basicOperation('filesize', $path);
426
	}
427
428
	/**
429
	 * @param string $path
430
	 * @return bool|mixed
431
	 * @throws \OCP\Files\InvalidPathException
432
	 */
433
	public function readFileToOutput($path) {
434
		$this->assertPathLength($path);
435
		@\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...
436
		$handle = $this->readFile($path)->detach();
437
		if ($handle) {
438
			$chunkSize = 8192; // 8 kB chunks
439
			while (!\feof($handle)) {
440
				echo \fread($handle, $chunkSize);
441
				\flush();
442
			}
443
			$size = $this->filesize($path);
444
			return $size;
445
		}
446
		return false;
447
	}
448
449
	/**
450
	 * @param string $path
451
	 * @param int $from
452
	 * @param int $to
453
	 * @return bool|mixed
454
	 * @throws \OCP\Files\InvalidPathException
455
	 * @throws \OCP\Files\UnseekableException
456
	 */
457
	public function readfilePart($path, $from, $to) {
458
		$this->assertPathLength($path);
459
		@\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...
460
		$handle = $this->readFile($path)->detach();
461
		if ($handle) {
462
			if (\fseek($handle, $from) === 0) {
463
				$chunkSize = 8192; // 8 kB chunks
464
				$end = $to + 1;
465
				while (!\feof($handle) && \ftell($handle) < $end) {
466
					$len = $end - \ftell($handle);
467
					if ($len > $chunkSize) {
468
						$len = $chunkSize;
469
					}
470
					echo \fread($handle, $len);
471
					\flush();
472
				}
473
				return \ftell($handle) - $from;
474
			}
475
476
			throw new \OCP\Files\UnseekableException('fseek error');
477
		}
478
		return false;
479
	}
480
481
	/**
482
	 * @param string $path
483
	 * @return mixed
484
	 */
485
	public function isCreatable($path) {
486
		return $this->basicOperation('isCreatable', $path);
487
	}
488
489
	/**
490
	 * @param string $path
491
	 * @return mixed
492
	 */
493
	public function isReadable($path) {
494
		return $this->basicOperation('isReadable', $path);
495
	}
496
497
	/**
498
	 * @param string $path
499
	 * @return mixed
500
	 */
501
	public function isUpdatable($path) {
502
		return $this->basicOperation('isUpdatable', $path);
503
	}
504
505
	/**
506
	 * @param string $path
507
	 * @return bool|mixed
508
	 */
509
	public function isDeletable($path) {
510
		$absolutePath = $this->getAbsolutePath($path);
511
		$mount = Filesystem::getMountManager()->find($absolutePath);
512
		if ($mount->getInternalPath($absolutePath) === '') {
513
			return $mount instanceof MoveableMount;
514
		}
515
		return $this->basicOperation('isDeletable', $path);
516
	}
517
518
	/**
519
	 * @param string $path
520
	 * @return mixed
521
	 */
522
	public function isSharable($path) {
523
		return $this->basicOperation('isSharable', $path);
524
	}
525
526
	/**
527
	 * @param string $path
528
	 * @return bool|mixed
529
	 */
530
	public function file_exists($path) {
531
		if ($path == '/') {
532
			return true;
533
		}
534
		return $this->basicOperation('file_exists', $path);
535
	}
536
537
	/**
538
	 * @param string $path
539
	 * @return mixed
540
	 */
541
	public function filemtime($path) {
542
		return $this->basicOperation('filemtime', $path);
543
	}
544
545
	/**
546
	 * @param string $path
547
	 * @param int|string $mtime
548
	 * @return bool
549
	 */
550
	public function touch($path, $mtime = null) {
551
		if ($mtime !== null and !\is_numeric($mtime)) {
552
			$mtime = \strtotime($mtime);
553
		}
554
555
		$hooks = ['touch'];
556
557
		if (!$this->file_exists($path)) {
558
			$hooks[] = 'create';
559
			$hooks[] = 'write';
560
		}
561
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
562
		if (!$result) {
563
			// If create file fails because of permissions on external storage like SMB folders,
564
			// check file exists and return false if not.
565
			if (!$this->file_exists($path)) {
566
				return false;
567
			}
568
			if ($mtime === null) {
569
				$mtime = \time();
570
			}
571
			//if native touch fails, we emulate it by changing the mtime in the cache
572
			$this->putFileInfo($path, ['mtime' => $mtime]);
573
		}
574
		return true;
575
	}
576
577
	/**
578
	 * @param string $path
579
	 * @return mixed
580
	 */
581
	public function file_get_contents($path) {
582
		return $this->emittingCall(function () use (&$path) {
583
			return $this->basicOperation('file_get_contents', $path, ['read']);
584
		}, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'read');
585
	}
586
587
	public function readFile(string $path, array $options = []): StreamInterface {
588
		return $this->emittingCall(function () use ($path, $options) {
589
			return $this->basicOperation('readFile', $path, ['read'], $options);
590
		}, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'read');
591
	}
592
593
	/**
594
	 * @param bool $exists
595
	 * @param string $path
596
	 * @param bool $run
597
	 */
598
	protected function emit_file_hooks_pre($exists, $path, &$run) {
599
		$event = new GenericEvent(null);
600
		if (!$exists) {
601
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
602
				Filesystem::signal_param_path => $this->getHookPath($path),
603
				Filesystem::signal_param_run => &$run,
604
			]);
605 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...
606
				$event->setArgument('run', $run);
607
				$this->eventDispatcher->dispatch('file.beforeCreate', $event);
608
				if ($event->getArgument('run') === false) {
609
					$run = $event->getArgument('run');
610
				}
611
			}
612
		} else {
613
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
614
				Filesystem::signal_param_path => $this->getHookPath($path),
615
				Filesystem::signal_param_run => &$run,
616
			]);
617 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...
618
				$event->setArgument('run', $run);
619
				$this->eventDispatcher->dispatch('file.beforeUpdate', $event);
620
				if ($event->getArgument('run') === false) {
621
					$run = $event->getArgument('run');
622
				}
623
			}
624
		}
625
626
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
627
			Filesystem::signal_param_path => $this->getHookPath($path),
628
			Filesystem::signal_param_run => &$run,
629
		]);
630 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...
631
			$event->setArgument('run', $run);
632
			$this->eventDispatcher->dispatch('file.beforeWrite', $event);
633
			if ($event->getArgument('run') === false) {
634
				$run = $event->getArgument('run');
635
			}
636
		}
637
	}
638
639
	/**
640
	 * @param bool $exists
641
	 * @param string $path
642
	 */
643
	protected function emit_file_hooks_post($exists, $path) {
644
		// A post event so no before event args required
645
		return $this->emittingCall(function () use (&$exists, &$path) {
646 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...
647
				\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
648
					Filesystem::signal_param_path => $this->getHookPath($path),
649
				]);
650
			} else {
651
				\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
652
					Filesystem::signal_param_path => $this->getHookPath($path),
653
				]);
654
			}
655
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
656
				Filesystem::signal_param_path => $this->getHookPath($path),
657
			]);
658
		}, ['before' => ['path' => $path], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'create');
659
	}
660
661
	/**
662
	 * @param string $path
663
	 * @param mixed $data
664
	 * @return bool|mixed
665
	 * @throws \Exception
666
	 */
667
	public function file_put_contents($path, $data) {
668
		return $this->emittingCall(function () use (&$path, &$data) {
669
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
670
			if (Filesystem::isValidPath($path)
671
				and !Filesystem::isForbiddenFileOrDir($path)
672
			) {
673
				$path = $this->getRelativePath($absolutePath);
674
675
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
676
677
				$exists = $this->file_exists($path);
678
				$run = true;
679
				if ($this->shouldEmitHooks($path)) {
680
					$this->emit_file_hooks_pre($exists, $path, $run);
681
				}
682
				if (!$run) {
683
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
684
					return false;
685
				}
686
687
				$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
688
				$hasExclusiveLock = true;
689
690
				/** @var \OC\Files\Storage\Storage $storage */
691
				list($storage, $internalPath) = $this->resolvePath($path);
692
				try {
693
					$result = $storage->writeFile($internalPath, stream_for($data));
694
					if (\is_resource($data)) {
695
						\fclose($data);
696
					}
697
698
					$this->writeUpdate($storage, $internalPath);
699
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
700
					$hasExclusiveLock = false;
701
702
					if ($this->shouldEmitHooks($path) && $result !== false) {
703
						$this->emit_file_hooks_post($exists, $path);
704
					}
705
				} finally {
706
					if ($hasExclusiveLock) {
707
						$this->changeLock($path, ILockingProvider::LOCK_SHARED);
708
					}
709
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
710
				}
711
712
				return $result;
713
			}
714
715
			return false;
716
		}, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'update');
717
	}
718
719
	/**
720
	 * @param string $path
721
	 * @param StreamInterface $stream
722
	 * @return int
723
	 * @throws \Exception
724
	 */
725
	public function writeFile(string $path, StreamInterface $stream): int {
726
		return $this->file_put_contents($path, $stream->detach());
727
	}
728
729
	/**
730
	 * @param string $path
731
	 * @return bool|mixed
732
	 */
733
	public function unlink($path) {
734
		return $this->emittingCall(function () use (&$path) {
735
			if ($path === '' || $path === '/') {
736
				// do not allow deleting the root
737
				return false;
738
			}
739
			$postFix = (\substr($path, -1, 1) === '/') ? '/' : '';
740
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
741
			$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
742
			if ($mount and $mount->getInternalPath($absolutePath) === '') {
743
				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...
744
			}
745
			if ($this->is_dir($path)) {
746
				$result = $this->basicOperation('rmdir', $path, ['delete']);
747
			} else {
748
				$result = $this->basicOperation('unlink', $path, ['delete']);
749
			}
750
751 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...
752
				$storage = $mount->getStorage();
753
				$internalPath = $mount->getInternalPath($absolutePath);
754
				$storage->getUpdater()->remove($internalPath);
755
				return true;
756
			} else {
757
				return $result;
758
			}
759
		}, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'delete');
760
	}
761
762
	/**
763
	 * @param string $directory
764
	 * @return bool|mixed
765
	 */
766
	public function deleteAll($directory) {
767
		return $this->rmdir($directory);
768
	}
769
770
	/**
771
	 * Rename/move a file or folder from the source path to target path.
772
	 *
773
	 * @param string $path1 source path
774
	 * @param string $path2 target path
775
	 *
776
	 * @return bool|mixed
777
	 */
778
	public function rename($path1, $path2) {
779
		return $this->emittingCall(function () use (&$path1, &$path2) {
780
			$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
781
			$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
782
			$result = false;
783
			if (
784
				Filesystem::isValidPath($path2)
785
				and Filesystem::isValidPath($path1)
786
				and !Filesystem::isForbiddenFileOrDir($path2)
787
			) {
788
				$path1 = $this->getRelativePath($absolutePath1);
789
				$path2 = $this->getRelativePath($absolutePath2);
790
				$exists = $this->file_exists($path2);
791
792
				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...
793
					return false;
794
				}
795
796
				$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
797
				try {
798
					$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
799
				} catch (LockedException $e) {
800
					$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
801
					throw $e;
802
				}
803
804
				$run = true;
805
				if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
806
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
807
					$this->emit_file_hooks_pre($exists, $path2, $run);
808
				} elseif ($this->shouldEmitHooks($path1)) {
809
					\OC_Hook::emit(
810
						Filesystem::CLASSNAME, Filesystem::signal_rename,
811
						[
812
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
813
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
814
							Filesystem::signal_param_run => &$run
815
						]
816
					);
817
				}
818
				if ($run) {
819
					$this->verifyPath(\dirname($path2), \basename($path2));
820
821
					$manager = Filesystem::getMountManager();
822
					$mount1 = $this->getMount($path1);
823
					$mount2 = $this->getMount($path2);
824
					$storage1 = $mount1->getStorage();
825
					$storage2 = $mount2->getStorage();
826
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
827
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
828
829
					$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
830
					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
831
832
					if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
833
						if ($this->canMove($mount1, $absolutePath2)) {
834
							/**
835
							 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
836
							 */
837
							$sourceMountPoint = $mount1->getMountPoint();
838
							$result = $mount1->moveMount($absolutePath2);
839
							$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
840
						} else {
841
							$result = false;
842
						}
843
						// moving a file/folder within the same mount point
844
					} elseif ($storage1 === $storage2) {
845
						if ($storage1) {
846
							$result = $storage1->rename($internalPath1, $internalPath2);
847
						} else {
848
							$result = false;
849
						}
850
						// moving a file/folder between storages (from $storage1 to $storage2)
851
					} else {
852
						$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
853
					}
854
855
					if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
856
						// if it was a rename from a part file to a regular file it was a write and not a rename operation
857
858
						$this->writeUpdate($storage2, $internalPath2);
859
					} elseif ($result) {
860
						if ($internalPath1 !== '') { // don't do a cache update for moved mounts
861
							$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
862
						}
863
					}
864
865
					$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
866
					$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
867
868
					if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
869
						if ($this->shouldEmitHooks()) {
870
							$this->emit_file_hooks_post($exists, $path2);
871
						}
872
					} elseif ($result) {
873
						if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
874
							\OC_Hook::emit(
875
								Filesystem::CLASSNAME,
876
								Filesystem::signal_post_rename,
877
								[
878
									Filesystem::signal_param_oldpath => $this->getHookPath($path1),
879
									Filesystem::signal_param_newpath => $this->getHookPath($path2)
880
								]
881
							);
882
						}
883
					}
884
				}
885
				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
886
				$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
887
			}
888
889
			return $result;
890
		}, [
891
			'before' => ['oldpath' => $this->getAbsolutePath($path1),
892
				'newpath' => $this->getAbsolutePath($path2)],
893
			'after' => ['oldpath' => $this->getAbsolutePath($path1),
894
				'newpath' => $this->getAbsolutePath($path2)]
895
		], 'file', 'rename');
896
	}
897
898
	/**
899
	 * Copy a file/folder from the source path to target path
900
	 *
901
	 * @param string $path1 source path
902
	 * @param string $path2 target path
903
	 * @param bool $preserveMtime whether to preserve mtime on the copy
904
	 *
905
	 * @return bool|mixed
906
	 */
907
908
	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...
909
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
910
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
911
		return $this->emittingCall(function () use ($absolutePath1, $absolutePath2) {
912
			$result = false;
913
			if (
914
				Filesystem::isValidPath($absolutePath2)
915
				&& Filesystem::isValidPath($absolutePath1)
916
				&& !Filesystem::isForbiddenFileOrDir($absolutePath2)
917
			) {
918
				$path1 = $this->getRelativePath($absolutePath1);
919
				$path2 = $this->getRelativePath($absolutePath2);
920
921
				if ($path1 === null || $path2 === null) {
922
					return false;
923
				}
924
				$run = true;
925
926
				$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
927
				$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
928
				$lockTypePath1 = ILockingProvider::LOCK_SHARED;
929
				$lockTypePath2 = ILockingProvider::LOCK_SHARED;
930
931
				try {
932
					$exists = $this->file_exists($path2);
933 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...
934
						\OC_Hook::emit(
935
							Filesystem::CLASSNAME,
936
							Filesystem::signal_copy,
937
							[
938
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
939
								Filesystem::signal_param_newpath => $this->getHookPath($path2),
940
								Filesystem::signal_param_run => &$run
941
							]
942
						);
943
						$this->emit_file_hooks_pre($exists, $path2, $run);
944
					}
945
					if ($run) {
946
						$mount1 = $this->getMount($path1);
947
						$mount2 = $this->getMount($path2);
948
						$storage1 = $mount1->getStorage();
949
						$internalPath1 = $mount1->getInternalPath($absolutePath1);
950
						$storage2 = $mount2->getStorage();
951
						$internalPath2 = $mount2->getInternalPath($absolutePath2);
952
953
						$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
954
						$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
955
956
						if ($mount1->getMountPoint() === $mount2->getMountPoint()) {
957
							if ($storage1) {
958
								$result = $storage1->copy($internalPath1, $internalPath2);
959
							} else {
960
								$result = false;
961
							}
962
						} else {
963
							$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
964
						}
965
966
						$this->writeUpdate($storage2, $internalPath2);
967
968
						$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
969
						$lockTypePath2 = ILockingProvider::LOCK_SHARED;
970
971 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...
972
							\OC_Hook::emit(
973
								Filesystem::CLASSNAME,
974
								Filesystem::signal_post_copy,
975
								[
976
									Filesystem::signal_param_oldpath => $this->getHookPath($path1),
977
									Filesystem::signal_param_newpath => $this->getHookPath($path2)
978
								]
979
							);
980
							$this->emit_file_hooks_post($exists, $path2);
981
						}
982
					}
983
				} catch (\Exception $e) {
984
					$this->unlockFile($path2, $lockTypePath2);
985
					$this->unlockFile($path1, $lockTypePath1);
986
					throw $e;
987
				}
988
989
				$this->unlockFile($path2, $lockTypePath2);
990
				$this->unlockFile($path1, $lockTypePath1);
991
			}
992
			return $result;
993
		}, [
994
			'before' => [
995
				'oldpath' => $absolutePath1,
996
				'newpath' => $absolutePath2
997
			],
998
			'after' => [
999
				'oldpath' => $absolutePath1,
1000
				'newpath' => $absolutePath2
1001
			]
1002
		], 'file', 'copy');
1003
	}
1004
1005
	/**
1006
	 * @param string $path
1007
	 * @param string $mode
1008
	 * @return resource
1009
	 * @deprecated 11.0.0
1010
	 */
1011
	public function fopen($path, $mode) {
0 ignored issues
show
Unused Code introduced by
The parameter $path 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...
Unused Code introduced by
The parameter $mode 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...
1012
		throw new \BadMethodCallException('fopen is no longer allowed to be called');
1013
	}
1014
1015
	/**
1016
	 * @param string $path
1017
	 * @return bool|string
1018
	 * @throws \OCP\Files\InvalidPathException
1019
	 */
1020
	public function toTmpFile($path) {
1021
		$this->assertPathLength($path);
1022
		if (Filesystem::isValidPath($path)) {
1023
			$source = $this->readFile($path, 'r')->detach();
0 ignored issues
show
Documentation introduced by
'r' is of type string, but the function expects a array.

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...
1024
			if ($source) {
1025
				$extension = \pathinfo($path, PATHINFO_EXTENSION);
1026
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
1027
				\file_put_contents($tmpFile, $source);
1028
				return $tmpFile;
1029
			} else {
1030
				return false;
1031
			}
1032
		} else {
1033
			return false;
1034
		}
1035
	}
1036
1037
	/**
1038
	 * @param string $tmpFile
1039
	 * @param string $path
1040
	 * @return bool|mixed
1041
	 * @throws \OCP\Files\InvalidPathException
1042
	 */
1043
	public function fromTmpFile($tmpFile, $path) {
1044
		$this->assertPathLength($path);
1045
		if (Filesystem::isValidPath($path)) {
1046
1047
			// Get directory that the file is going into
1048
			$filePath = \dirname($path);
1049
1050
			// Create the directories if any
1051
			if (!$this->file_exists($filePath)) {
1052
				$result = $this->createParentDirectories($filePath);
1053
				if ($result === false) {
1054
					return false;
1055
				}
1056
			}
1057
1058
			$source = \fopen($tmpFile, 'r');
1059
			if ($source) {
1060
				$result = $this->file_put_contents($path, $source);
1061
				// $this->file_put_contents() might have already closed
1062
				// the resource, so we check it, before trying to close it
1063
				// to avoid messages in the error log.
1064
				if (\is_resource($source)) {
1065
					\fclose($source);
1066
				}
1067
				\unlink($tmpFile);
1068
				return $result;
1069
			} else {
1070
				return false;
1071
			}
1072
		} else {
1073
			return false;
1074
		}
1075
	}
1076
1077
	/**
1078
	 * @param string $path
1079
	 * @return mixed
1080
	 * @throws \OCP\Files\InvalidPathException
1081
	 */
1082
	public function getMimeType($path) {
1083
		$this->assertPathLength($path);
1084
		return $this->basicOperation('getMimeType', $path);
1085
	}
1086
1087
	/**
1088
	 * @param string $type
1089
	 * @param string $path
1090
	 * @param bool $raw
1091
	 * @return bool|null|string
1092
	 */
1093
	public function hash($type, $path, $raw = false) {
1094
		$postFix = (\substr($path, -1, 1) === '/') ? '/' : '';
1095
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1096
		if (Filesystem::isValidPath($path)) {
1097
			$path = $this->getRelativePath($absolutePath);
1098
			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...
1099
				return false;
1100
			}
1101
			if ($this->shouldEmitHooks($path)) {
1102
				\OC_Hook::emit(
1103
					Filesystem::CLASSNAME,
1104
					Filesystem::signal_read,
1105
					[Filesystem::signal_param_path => $this->getHookPath($path)]
1106
				);
1107
			}
1108
1109
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1110
			if ($storage) {
1111
				$result = $storage->hash($type, $internalPath, $raw);
1112
				return $result;
1113
			}
1114
		}
1115
		return null;
1116
	}
1117
1118
	/**
1119
	 * @param string $path
1120
	 * @return mixed
1121
	 * @throws \OCP\Files\InvalidPathException
1122
	 */
1123
	public function free_space($path = '/') {
1124
		$this->assertPathLength($path);
1125
		return $this->basicOperation('free_space', $path);
1126
	}
1127
1128
	/**
1129
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1130
	 *
1131
	 * @param string $operation
1132
	 * @param string $path
1133
	 * @param array $hooks (optional)
1134
	 * @param mixed $extraParam (optional)
1135
	 * @return mixed
1136
	 * @throws \Exception
1137
	 *
1138
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1139
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1140
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1141
	 */
1142
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1143
		$postFix = (\substr($path, -1, 1) === '/') ? '/' : '';
1144
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1145
		if (Filesystem::isValidPath($path)
1146
			and !Filesystem::isForbiddenFileOrDir($path)
1147
		) {
1148
			$path = $this->getRelativePath($absolutePath);
1149
			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...
1150
				return false;
1151
			}
1152
1153
			if (\in_array('write', $hooks) || \in_array('delete', $hooks) || \in_array('read', $hooks)) {
1154
				// always a shared lock during pre-hooks so the hook can read the file
1155
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1156
			}
1157
1158
			$run = $this->runHooks($hooks, $path);
1159
			/** @var \OC\Files\Storage\Storage $storage */
1160
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1161
			if ($run and $storage) {
1162
				if (\in_array('write', $hooks) || \in_array('delete', $hooks)) {
1163
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1164
				}
1165
				try {
1166
					if ($extraParam !== null) {
1167
						$result = $storage->$operation($internalPath, $extraParam);
1168
					} else {
1169
						$result = $storage->$operation($internalPath);
1170
					}
1171
				} catch (\Exception $e) {
1172 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...
1173
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1174
					} elseif (\in_array('read', $hooks)) {
1175
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1176
					}
1177
					throw $e;
1178
				}
1179
1180
				if (\in_array('delete', $hooks) and $result) {
1181
					$this->removeUpdate($storage, $internalPath);
1182
				}
1183
				if (\in_array('write', $hooks) and $operation !== 'fopen') {
1184
					$this->writeUpdate($storage, $internalPath);
1185
				}
1186
				if (\in_array('touch', $hooks)) {
1187
					$this->writeUpdate($storage, $internalPath, $extraParam);
1188
				}
1189
1190
				if ((\in_array('write', $hooks) || \in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1191
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1192
				}
1193
1194
				$unlockLater = false;
1195
				if ($this->lockingEnabled && $operation === 'fopen' && \is_resource($result)) {
1196
					$unlockLater = true;
1197
					// make sure our unlocking callback will still be called if connection is aborted
1198
					\ignore_user_abort(true);
1199
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1200
						if (\in_array('write', $hooks)) {
1201
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1202
						} elseif (\in_array('read', $hooks)) {
1203
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1204
						}
1205
					});
1206
				}
1207
1208
				if ($this->shouldEmitHooks($path) && $result !== false) {
1209
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1210
						$this->runHooks($hooks, $path, true);
1211
					}
1212
				}
1213
1214 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...
1215
					&& (\in_array('write', $hooks) || \in_array('delete', $hooks) || \in_array('read', $hooks))
1216
				) {
1217
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1218
				}
1219
				return $result;
1220
			} else {
1221
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1222
			}
1223
		}
1224
		return null;
1225
	}
1226
1227
	/**
1228
	 * get the path relative to the default root for hook usage
1229
	 *
1230
	 * @param string $path
1231
	 * @return string
1232
	 */
1233
	private function getHookPath($path) {
1234
		if (!Filesystem::getView()) {
1235
			return $path;
1236
		}
1237
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1238
	}
1239
1240
	private function shouldEmitHooks($path = '') {
1241
		if ($path && Cache\Scanner::isPartialFile($path)) {
1242
			return false;
1243
		}
1244
		if (!Filesystem::$loaded) {
1245
			return false;
1246
		}
1247
		$defaultRoot = Filesystem::getRoot();
1248
		if ($defaultRoot === null) {
1249
			return false;
1250
		}
1251
		if ($this->fakeRoot === $defaultRoot) {
1252
			return true;
1253
		}
1254
		$fullPath = $this->getAbsolutePath($path);
1255
1256
		if ($fullPath === $defaultRoot) {
1257
			return true;
1258
		}
1259
1260
		return (\strlen($fullPath) > \strlen($defaultRoot)) && (\substr($fullPath, 0, \strlen($defaultRoot) + 1) === $defaultRoot . '/');
1261
	}
1262
1263
	/**
1264
	 * @param string[] $hooks
1265
	 * @param string $path
1266
	 * @param bool $post
1267
	 * @return bool
1268
	 */
1269
	private function runHooks($hooks, $path, $post = false) {
1270
		if (empty($hooks)) {
1271
			return true;
1272
		}
1273
		$relativePath = $path;
1274
		$path = $this->getHookPath($path);
1275
		$prefix = ($post) ? 'post_' : '';
1276
		$run = true;
1277
		if ($this->shouldEmitHooks($relativePath)) {
1278
			foreach ($hooks as $hook) {
1279
				if ($hook != 'read') {
1280
					\OC_Hook::emit(
1281
						Filesystem::CLASSNAME,
1282
						$prefix . $hook,
1283
						[
1284
							Filesystem::signal_param_run => &$run,
1285
							Filesystem::signal_param_path => $path
1286
						]
1287
					);
1288
				} elseif (!$post) {
1289
					\OC_Hook::emit(
1290
						Filesystem::CLASSNAME,
1291
						$prefix . $hook,
1292
						[
1293
							Filesystem::signal_param_path => $path
1294
						]
1295
					);
1296
				}
1297
			}
1298
		}
1299
		return $run;
1300
	}
1301
1302
	/**
1303
	 * check if a file or folder has been updated since $time
1304
	 *
1305
	 * @param string $path
1306
	 * @param int $time
1307
	 * @return bool
1308
	 */
1309
	public function hasUpdated($path, $time) {
1310
		return $this->basicOperation('hasUpdated', $path, [], $time);
1311
	}
1312
1313
	/**
1314
	 * @param string $ownerId
1315
	 * @return IUser
1316
	 */
1317
	private function getUserObjectForOwner($ownerId) {
1318
		$owner = $this->userManager->get($ownerId);
1319
		if (!$owner instanceof IUser) {
1320
			return new RemoteUser($ownerId);
1321
		}
1322
1323
		return $owner;
1324
	}
1325
1326
	/**
1327
	 * Get file info from cache
1328
	 *
1329
	 * If the file is not in cached it will be scanned
1330
	 * If the file has changed on storage the cache will be updated
1331
	 *
1332
	 * @param \OC\Files\Storage\Storage $storage
1333
	 * @param string $internalPath
1334
	 * @param string $relativePath
1335
	 * @return array|bool
1336
	 */
1337
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1338
		$cache = $storage->getCache($internalPath);
1339
		$data = $cache->get($internalPath);
1340
		$watcher = $storage->getWatcher($internalPath);
1341
1342
		try {
1343
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1344
			if (!$data || (isset($data['size']) && ($data['size'] === -1))) {
1345
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1346
				if (!$storage->file_exists($internalPath)) {
1347
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1348
					return false;
1349
				}
1350
				$scanner = $storage->getScanner($internalPath);
1351
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1352
				$data = $cache->get($internalPath);
1353
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1354
			} elseif (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1355
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1356
				$watcher->update($internalPath, $data);
1357
				$storage->getPropagator()->propagateChange($internalPath, \time());
1358
				$data = $cache->get($internalPath);
1359
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1360
			}
1361
		} catch (LockedException $e) {
1362
			// if the file is locked we just use the old cache info
1363
		}
1364
1365
		return $data;
1366
	}
1367
1368
	/**
1369
	 * get the filesystem info
1370
	 *
1371
	 * @param string $path
1372
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1373
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1374
	 * defaults to true
1375
	 * @return \OC\Files\FileInfo|false False if file does not exist
1376
	 */
1377
	public function getFileInfo($path, $includeMountPoints = true) {
1378
		$this->assertPathLength($path);
1379
		if (!Filesystem::isValidPath($path)) {
1380
			return false;
1381
		}
1382
		if (Cache\Scanner::isPartialFile($path)) {
1383
			return $this->getPartFileInfo($path);
1384
		}
1385
		$relativePath = $path;
1386
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1387
1388
		$mount = Filesystem::getMountManager()->find($path);
1389
		$storage = $mount->getStorage();
1390
		$internalPath = $mount->getInternalPath($path);
1391
		if ($storage) {
1392
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1393
1394
			if (!$data instanceof ICacheEntry) {
1395
				return false;
1396
			}
1397
1398
			if ($mount instanceof MoveableMount && $internalPath === '') {
1399
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1400
			}
1401
1402
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1403
			$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 1388 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...
1404
1405
			if ($data and isset($data['fileid'])) {
1406
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1407
					//add the sizes of other mount points to the folder
1408
					$extOnly = ($includeMountPoints === 'ext');
1409
					$mounts = Filesystem::getMountManager()->findIn($path);
1410
					foreach ($mounts as $mount) {
1411
						$subStorage = $mount->getStorage();
1412
						if ($subStorage) {
1413
							// exclude shared storage ?
1414
							if ($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage) {
1415
								continue;
1416
							}
1417
							$subCache = $subStorage->getCache('');
1418
							$rootEntry = $subCache->get('');
1419
							$info->addSubEntry($rootEntry, $mount->getMountPoint());
1420
						}
1421
					}
1422
				}
1423
			}
1424
1425
			return $info;
1426
		}
1427
1428
		return false;
1429
	}
1430
1431
	/**
1432
	 * get the content of a directory
1433
	 *
1434
	 * @param string $directory path under datadirectory
1435
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1436
	 * @return FileInfo[]
1437
	 */
1438
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1439
		$this->assertPathLength($directory);
1440
		if (!Filesystem::isValidPath($directory)) {
1441
			return [];
1442
		}
1443
		$path = $this->getAbsolutePath($directory);
1444
		$path = Filesystem::normalizePath($path);
1445
		$mount = $this->getMount($directory);
1446
		$storage = $mount->getStorage();
1447
		$internalPath = $mount->getInternalPath($path);
1448
		if ($storage) {
1449
			$cache = $storage->getCache($internalPath);
1450
			$user = \OC_User::getUser();
1451
1452
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1453
1454
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1455
				return [];
1456
			}
1457
1458
			$folderId = $data['fileid'];
1459
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1460
1461
			$sharingDisabled = Util::isSharingDisabledForUser();
1462
			/**
1463
			 * @var \OC\Files\FileInfo[] $files
1464
			 */
1465
			$files = \array_filter($contents, function (ICacheEntry $content) {
1466
				return (!\OC\Files\Filesystem::isForbiddenFileOrDir($content['path']));
1467
			});
1468
			$files = \array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1469
				if ($sharingDisabled) {
1470
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1471
				}
1472
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1473
				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 1445 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...
1474
			}, $files);
1475
1476
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1477
			$mounts = Filesystem::getMountManager()->findIn($path);
1478
			$dirLength = \strlen($path);
1479
			foreach ($mounts as $mount) {
1480
				$mountPoint = $mount->getMountPoint();
1481
				$subStorage = $mount->getStorage();
1482
				if ($subStorage) {
1483
					$subCache = $subStorage->getCache('');
1484
1485
					$rootEntry = $subCache->get('');
1486
					if (!$rootEntry) {
1487
						$subScanner = $subStorage->getScanner('');
1488
						try {
1489
							$subScanner->scanFile('');
1490
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1491
							continue;
1492
						} catch (\OCP\Files\StorageInvalidException $e) {
1493
							continue;
1494
						} catch (\Exception $e) {
1495
							// sometimes when the storage is not available it can be any exception
1496
							Util::writeLog(
1497
								'core',
1498
								'Exception while scanning storage "' . $subStorage->getId() . '": ' .
1499
								\get_class($e) . ': ' . $e->getMessage(),
1500
								Util::ERROR
1501
							);
1502
							continue;
1503
						}
1504
						$rootEntry = $subCache->get('');
1505
					}
1506
1507
					if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1508
						$relativePath = \trim(\substr($mountPoint, $dirLength), '/');
1509
						if ($pos = \strpos($relativePath, '/')) {
1510
							//mountpoint inside subfolder add size to the correct folder
1511
							$entryName = \substr($relativePath, 0, $pos);
1512
							foreach ($files as &$entry) {
1513
								if ($entry->getName() === $entryName) {
1514
									$entry->addSubEntry($rootEntry, $mountPoint);
1515
								}
1516
							}
1517
						} else { //mountpoint in this folder, add an entry for it
1518
							$rootEntry['name'] = $relativePath;
1519
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1520
							$permissions = $rootEntry['permissions'];
1521
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1522
							// for shared files/folders we use the permissions given by the owner
1523
							if ($mount instanceof MoveableMount) {
1524
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1525
							} else {
1526
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1527
							}
1528
1529
							//remove any existing entry with the same name
1530
							foreach ($files as $i => $file) {
1531
								if ($file['name'] === $rootEntry['name']) {
1532
									unset($files[$i]);
1533
									break;
1534
								}
1535
							}
1536
							$rootEntry['path'] = \substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), \strlen($user) + 2); // full path without /$user/
1537
1538
							// if sharing was disabled for the user we remove the share permissions
1539
							if (Util::isSharingDisabledForUser()) {
1540
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1541
							}
1542
1543
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1544
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1545
						}
1546
					}
1547
				}
1548
			}
1549
1550
			if ($mimetype_filter) {
1551
				$files = \array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1552
					if (\strpos($mimetype_filter, '/')) {
1553
						return $file->getMimetype() === $mimetype_filter;
1554
					} else {
1555
						return $file->getMimePart() === $mimetype_filter;
1556
					}
1557
				});
1558
			}
1559
1560
			return $files;
1561
		} else {
1562
			return [];
1563
		}
1564
	}
1565
1566
	/**
1567
	 * change file metadata
1568
	 *
1569
	 * @param string $path
1570
	 * @param array|\OCP\Files\FileInfo $data
1571
	 * @return int
1572
	 *
1573
	 * returns the fileid of the updated file
1574
	 */
1575
	public function putFileInfo($path, $data) {
1576
		$this->assertPathLength($path);
1577
		if ($data instanceof FileInfo) {
1578
			$data = $data->getData();
1579
		}
1580
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1581
		/**
1582
		 * @var \OC\Files\Storage\Storage $storage
1583
		 * @var string $internalPath
1584
		 */
1585
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1586
		if ($storage) {
1587
			$cache = $storage->getCache($path);
1588
1589
			if (!$cache->inCache($internalPath)) {
1590
				$scanner = $storage->getScanner($internalPath);
1591
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1592
			}
1593
1594
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1575 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...
1595
		} else {
1596
			return -1;
1597
		}
1598
	}
1599
1600
	/**
1601
	 * search for files with the name matching $query
1602
	 *
1603
	 * @param string $query
1604
	 * @return FileInfo[]
1605
	 */
1606
	public function search($query) {
1607
		return $this->searchCommon('search', ['%' . $query . '%']);
1608
	}
1609
1610
	/**
1611
	 * search for files with the name matching $query
1612
	 *
1613
	 * @param string $query
1614
	 * @return FileInfo[]
1615
	 */
1616
	public function searchRaw($query) {
1617
		return $this->searchCommon('search', [$query]);
1618
	}
1619
1620
	/**
1621
	 * search for files by mimetype
1622
	 *
1623
	 * @param string $mimetype
1624
	 * @return FileInfo[]
1625
	 */
1626
	public function searchByMime($mimetype) {
1627
		return $this->searchCommon('searchByMime', [$mimetype]);
1628
	}
1629
1630
	/**
1631
	 * search for files by tag
1632
	 *
1633
	 * @param string|int $tag name or tag id
1634
	 * @param string $userId owner of the tags
1635
	 * @return FileInfo[]
1636
	 */
1637
	public function searchByTag($tag, $userId) {
1638
		return $this->searchCommon('searchByTag', [$tag, $userId]);
1639
	}
1640
1641
	/**
1642
	 * @param string $method cache method
1643
	 * @param array $args
1644
	 * @return FileInfo[]
1645
	 */
1646
	private function searchCommon($method, $args) {
1647
		$files = [];
1648
		$rootLength = \strlen($this->fakeRoot);
1649
1650
		$mount = $this->getMount('');
1651
		$mountPoint = $mount->getMountPoint();
1652
		$storage = $mount->getStorage();
1653
		if ($storage) {
1654
			$cache = $storage->getCache('');
1655
1656
			$results = \call_user_func_array([$cache, $method], $args);
1657
			foreach ($results as $result) {
1658
				if (\substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1659
					$internalPath = $result['path'];
1660
					$path = $mountPoint . $result['path'];
1661
					$result['path'] = \substr($mountPoint . $result['path'], $rootLength);
1662
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1663
					$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 1650 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...
1664
				}
1665
			}
1666
1667
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1668
			foreach ($mounts as $mount) {
1669
				$mountPoint = $mount->getMountPoint();
1670
				$storage = $mount->getStorage();
1671
				if ($storage) {
1672
					$cache = $storage->getCache('');
1673
1674
					$relativeMountPoint = \substr($mountPoint, $rootLength);
1675
					$results = \call_user_func_array([$cache, $method], $args);
1676
					if ($results) {
1677
						foreach ($results as $result) {
1678
							$internalPath = $result['path'];
1679
							$result['path'] = \rtrim($relativeMountPoint . $result['path'], '/');
1680
							$path = \rtrim($mountPoint . $internalPath, '/');
1681
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1682
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1683
						}
1684
					}
1685
				}
1686
			}
1687
		}
1688
		return $files;
1689
	}
1690
1691
	/**
1692
	 * Get the owner for a file or folder
1693
	 *
1694
	 * @param string $path
1695
	 * @return string the user id of the owner
1696
	 * @throws NotFoundException
1697
	 */
1698
	public function getOwner($path) {
1699
		$info = $this->getFileInfo($path);
1700
		if (!$info) {
1701
			throw new NotFoundException($path . ' not found while trying to get owner');
1702
		}
1703
		return $info->getOwner()->getUID();
1704
	}
1705
1706
	/**
1707
	 * get the ETag for a file or folder
1708
	 *
1709
	 * @param string $path
1710
	 * @return string
1711
	 */
1712
	public function getETag($path) {
1713
		/**
1714
		 * @var Storage\Storage $storage
1715
		 * @var string $internalPath
1716
		 */
1717
		list($storage, $internalPath) = $this->resolvePath($path);
1718
		if ($storage) {
1719
			return $storage->getETag($internalPath);
1720
		} else {
1721
			return null;
1722
		}
1723
	}
1724
1725
	/**
1726
	 * Get the path of a file by id, relative to the view
1727
	 *
1728
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1729
	 *
1730
	 * @param int $id
1731
	 * @param bool $includeShares whether to recurse into shared mounts
1732
	 * @throws NotFoundException
1733
	 * @return string
1734
	 */
1735
	public function getPath($id, $includeShares = true) {
1736
		$id = (int)$id;
1737
		$manager = Filesystem::getMountManager();
1738
		$mounts = $manager->findIn($this->fakeRoot);
1739
		$mounts[] = $manager->find($this->fakeRoot);
1740
		// reverse the array so we start with the storage this view is in
1741
		// which is the most likely to contain the file we're looking for
1742
		$mounts = \array_reverse($mounts);
1743
		foreach ($mounts as $mount) {
1744
			/**
1745
			 * @var \OC\Files\Mount\MountPoint $mount
1746
			 */
1747
			if (!$includeShares && $mount instanceof SharedMount) {
1748
				// prevent potential infinite loop when instantiating shared storages
1749
				// recursively
1750
				continue;
1751
			}
1752 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...
1753
				$cache = $mount->getStorage()->getCache();
1754
				$internalPath = $cache->getPathById($id);
1755
				if (\is_string($internalPath)) {
1756
					$fullPath = $mount->getMountPoint() . $internalPath;
1757
					if (($path = $this->getRelativePath($fullPath)) !== null) {
1758
						return $path;
1759
					}
1760
				}
1761
			}
1762
		}
1763
		throw new NotFoundException(\sprintf('File with id "%s" has not been found.', $id));
1764
	}
1765
1766
	/**
1767
	 * @param string $path
1768
	 * @throws InvalidPathException
1769
	 */
1770
	private function assertPathLength($path) {
1771
		$maxLen = \min(PHP_MAXPATHLEN, 4000);
1772
		// Check for the string length - performed using isset() instead of strlen()
1773
		// because isset() is about 5x-40x faster.
1774
		if (isset($path[$maxLen])) {
1775
			$pathLen = \strlen($path);
1776
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1777
		}
1778
	}
1779
1780
	/**
1781
	 * check if it is allowed to move a mount point to a given target.
1782
	 * It is not allowed to move a mount point into a different mount point or
1783
	 * into an already shared folder
1784
	 *
1785
	 * @param MoveableMount $mount1 moveable mount
1786
	 * @param string $target absolute target path
1787
	 * @return boolean
1788
	 */
1789
	private function canMove(MoveableMount $mount1, $target) {
1790
		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...
1791
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1792
			Util::writeLog('files',
1793
				'It is not allowed to move one mount point into another one',
1794
				Util::DEBUG);
1795
			return false;
1796
		}
1797
1798
		return $mount1->isTargetAllowed($target);
1799
	}
1800
1801
	/**
1802
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1803
	 *
1804
	 * @param string $path
1805
	 * @return \OCP\Files\FileInfo
1806
	 */
1807
	private function getPartFileInfo($path) {
1808
		$mount = $this->getMount($path);
1809
		$storage = $mount->getStorage();
1810
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1811
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1812
		return new FileInfo(
1813
			$this->getAbsolutePath($path),
1814
			$storage,
1815
			$internalPath,
1816
			[
1817
				'fileid' => null,
1818
				'mimetype' => $storage->getMimeType($internalPath),
1819
				'name' => \basename($path),
1820
				'etag' => null,
1821
				'size' => $storage->filesize($internalPath),
1822
				'mtime' => $storage->filemtime($internalPath),
1823
				'encrypted' => false,
1824
				'permissions' => \OCP\Constants::PERMISSION_ALL
1825
			],
1826
			$mount,
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1808 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...
1827
			$owner
1828
		);
1829
	}
1830
1831
	/**
1832
	 * @param string $path
1833
	 * @param string $fileName
1834
	 * @throws InvalidPathException
1835
	 */
1836
	public function verifyPath($path, $fileName) {
1837
		$l10n = \OC::$server->getL10N('lib');
1838
1839
		// verify empty and dot files
1840
		$trimmed = \trim($fileName);
1841
		if ($trimmed === '') {
1842
			throw new InvalidPathException($l10n->t('Empty filename is not allowed'));
1843
		}
1844
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1845
			throw new InvalidPathException($l10n->t('Dot files are not allowed'));
1846
		}
1847
1848
		$matches = [];
1849
1850
		if (\preg_match('/' . FileInfo::BLACKLIST_FILES_REGEX . '/', $fileName) !== 0) {
1851
			throw new InvalidPathException(
1852
				"Can`t upload files with extension {$matches[0]} because these extensions are reserved for internal use."
1853
			);
1854
		}
1855
1856
		if (!\OC::$server->getDatabaseConnection()->allows4ByteCharacters()) {
1857
			// verify database - e.g. mysql only 3-byte chars
1858
			if (\preg_match('%(?:
1859
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1860
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1861
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1862
)%xs', $fileName)) {
1863
				throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names'));
1864
			}
1865
		}
1866
1867
		try {
1868
			/** @type \OCP\Files\Storage $storage */
1869
			list($storage, $internalPath) = $this->resolvePath($path);
1870
			$storage->verifyPath($internalPath, $fileName);
1871
		} catch (ReservedWordException $ex) {
1872
			throw new InvalidPathException($l10n->t('File name is a reserved word'));
1873
		} catch (InvalidCharacterInPathException $ex) {
1874
			throw new InvalidPathException($l10n->t('File name contains at least one invalid character'));
1875
		} catch (FileNameTooLongException $ex) {
1876
			throw new InvalidPathException($l10n->t('File name is too long'));
1877
		}
1878
	}
1879
1880
	/**
1881
	 * get all parent folders of $path
1882
	 *
1883
	 * @param string $path
1884
	 * @return string[]
1885
	 */
1886
	private function getParents($path) {
1887
		$path = \trim($path, '/');
1888
		if (!$path) {
1889
			return [];
1890
		}
1891
1892
		$parts = \explode('/', $path);
1893
1894
		// remove the single file
1895
		\array_pop($parts);
1896
		$result = ['/'];
1897
		$resultPath = '';
1898
		foreach ($parts as $part) {
1899
			if ($part) {
1900
				$resultPath .= '/' . $part;
1901
				$result[] = $resultPath;
1902
			}
1903
		}
1904
		return $result;
1905
	}
1906
1907
	/**
1908
	 * Returns the mount point for which to lock
1909
	 *
1910
	 * @param string $absolutePath absolute path
1911
	 * @param bool $useParentMount true to return parent mount instead of whatever
1912
	 * is mounted directly on the given path, false otherwise
1913
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1914
	 */
1915
	private function getMountForLock($absolutePath, $useParentMount = false) {
1916
		$results = [];
1917
		$mount = Filesystem::getMountManager()->find($absolutePath);
1918
		if (!$mount) {
1919
			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...
1920
		}
1921
1922
		if ($useParentMount) {
1923
			// find out if something is mounted directly on the path
1924
			$internalPath = $mount->getInternalPath($absolutePath);
1925
			if ($internalPath === '') {
1926
				// resolve the parent mount instead
1927
				$mount = Filesystem::getMountManager()->find(\dirname($absolutePath));
1928
			}
1929
		}
1930
1931
		return $mount;
1932
	}
1933
1934
	/**
1935
	 * Lock the given path
1936
	 *
1937
	 * @param string $path the path of the file to lock, relative to the view
1938
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1939
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1940
	 *
1941
	 * @return bool False if the path is excluded from locking, true otherwise
1942
	 * @throws \OCP\Lock\LockedException if the path is already locked
1943
	 */
1944 View Code Duplication
	private function lockPath($path, $type, $lockMountPoint = false) {
1945
		$absolutePath = $this->getAbsolutePath($path);
1946
		$absolutePath = Filesystem::normalizePath($absolutePath);
1947
		if (!$this->shouldLockFile($absolutePath)) {
1948
			return false;
1949
		}
1950
1951
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1952
		if ($mount) {
1953
			try {
1954
				$storage = $mount->getStorage();
1955
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1956
					$storage->acquireLock(
1957
						$mount->getInternalPath($absolutePath),
1958
						$type,
1959
						$this->lockingProvider
1960
					);
1961
				}
1962
			} catch (\OCP\Lock\LockedException $e) {
1963
				// rethrow with the a human-readable path
1964
				throw new \OCP\Lock\LockedException(
1965
					$this->getPathRelativeToFiles($absolutePath),
1966
					$e
1967
				);
1968
			}
1969
		}
1970
1971
		return true;
1972
	}
1973
1974
	/**
1975
	 * Change the lock type
1976
	 *
1977
	 * @param string $path the path of the file to lock, relative to the view
1978
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1979
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1980
	 *
1981
	 * @return bool False if the path is excluded from locking, true otherwise
1982
	 * @throws \OCP\Lock\LockedException if the path is already locked
1983
	 */
1984 View Code Duplication
	public function changeLock($path, $type, $lockMountPoint = false) {
1985
		$path = Filesystem::normalizePath($path);
1986
		$absolutePath = $this->getAbsolutePath($path);
1987
		$absolutePath = Filesystem::normalizePath($absolutePath);
1988
		if (!$this->shouldLockFile($absolutePath)) {
1989
			return false;
1990
		}
1991
1992
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1993
		if ($mount) {
1994
			try {
1995
				$storage = $mount->getStorage();
1996
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1997
					$storage->changeLock(
1998
						$mount->getInternalPath($absolutePath),
1999
						$type,
2000
						$this->lockingProvider
2001
					);
2002
				}
2003
			} catch (LockedException $e) {
2004
				// rethrow with the a human-readable path
2005
				throw new LockedException(
2006
					$this->getPathRelativeToFiles($absolutePath),
2007
					$e
2008
				);
2009
			}
2010
		}
2011
2012
		return true;
2013
	}
2014
2015
	/**
2016
	 * Unlock the given path
2017
	 *
2018
	 * @param string $path the path of the file to unlock, relative to the view
2019
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2020
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2021
	 *
2022
	 * @return bool False if the path is excluded from locking, true otherwise
2023
	 */
2024
	private function unlockPath($path, $type, $lockMountPoint = false) {
2025
		$absolutePath = $this->getAbsolutePath($path);
2026
		$absolutePath = Filesystem::normalizePath($absolutePath);
2027
		if (!$this->shouldLockFile($absolutePath)) {
2028
			return false;
2029
		}
2030
2031
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2032
		if ($mount) {
2033
			$storage = $mount->getStorage();
2034
			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2035
				$storage->releaseLock(
2036
					$mount->getInternalPath($absolutePath),
2037
					$type,
2038
					$this->lockingProvider
2039
				);
2040
			}
2041
		}
2042
2043
		return true;
2044
	}
2045
2046
	/**
2047
	 * Lock a path and all its parents up to the root of the view
2048
	 *
2049
	 * @param string $path the path of the file to lock relative to the view
2050
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2051
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2052
	 *
2053
	 * @return bool False if the path is excluded from locking, true otherwise
2054
	 */
2055 View Code Duplication
	public function lockFile($path, $type, $lockMountPoint = false) {
2056
		$absolutePath = $this->getAbsolutePath($path);
2057
		$absolutePath = Filesystem::normalizePath($absolutePath);
2058
		if (!$this->shouldLockFile($absolutePath)) {
2059
			return false;
2060
		}
2061
2062
		$this->lockPath($path, $type, $lockMountPoint);
2063
2064
		$parents = $this->getParents($path);
2065
		foreach ($parents as $parent) {
2066
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2067
		}
2068
2069
		return true;
2070
	}
2071
2072
	/**
2073
	 * Unlock a path and all its parents up to the root of the view
2074
	 *
2075
	 * @param string $path the path of the file to lock relative to the view
2076
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2077
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2078
	 *
2079
	 * @return bool False if the path is excluded from locking, true otherwise
2080
	 */
2081 View Code Duplication
	public function unlockFile($path, $type, $lockMountPoint = false) {
2082
		$absolutePath = $this->getAbsolutePath($path);
2083
		$absolutePath = Filesystem::normalizePath($absolutePath);
2084
		if (!$this->shouldLockFile($absolutePath)) {
2085
			return false;
2086
		}
2087
2088
		$this->unlockPath($path, $type, $lockMountPoint);
2089
2090
		$parents = $this->getParents($path);
2091
		foreach ($parents as $parent) {
2092
			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2093
		}
2094
2095
		return true;
2096
	}
2097
2098
	/**
2099
	 * Only lock files in data/user/files/
2100
	 *
2101
	 * @param string $path Absolute path to the file/folder we try to (un)lock
2102
	 * @return bool
2103
	 */
2104
	protected function shouldLockFile($path) {
2105
		$path = Filesystem::normalizePath($path);
2106
2107
		$pathSegments = \explode('/', $path);
2108
		if (isset($pathSegments[2])) {
2109
			// E.g.: /username/files/path-to-file
2110
			return ($pathSegments[2] === 'files') && (\count($pathSegments) > 3);
2111
		}
2112
2113
		return true;
2114
	}
2115
2116
	/**
2117
	 * Shortens the given absolute path to be relative to
2118
	 * "$user/files".
2119
	 *
2120
	 * @param string $absolutePath absolute path which is under "files"
2121
	 *
2122
	 * @return string path relative to "files" with trimmed slashes or null
2123
	 * if the path was NOT relative to files
2124
	 *
2125
	 * @throws \InvalidArgumentException if the given path was not under "files"
2126
	 * @since 8.1.0
2127
	 */
2128
	public function getPathRelativeToFiles($absolutePath) {
2129
		$path = Filesystem::normalizePath($absolutePath);
2130
		$parts = \explode('/', \trim($path, '/'), 3);
2131
		// "$user", "files", "path/to/dir"
2132
		if (!isset($parts[1]) || $parts[1] !== 'files') {
2133
			throw new \InvalidArgumentException('"' . $absolutePath . '" must be relative to "files"');
2134
		}
2135
		if (isset($parts[2])) {
2136
			return $parts[2];
2137
		}
2138
		return '';
2139
	}
2140
2141
	/**
2142
	 * @param string $filename
2143
	 * @return array
2144
	 * @throws \OC\User\NoUserException
2145
	 * @throws NotFoundException
2146
	 */
2147
	public function getUidAndFilename($filename) {
2148
		$info = $this->getFileInfo($filename);
2149
		if (!$info instanceof \OCP\Files\FileInfo) {
2150
			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2151
		}
2152
		$uid = $info->getOwner()->getUID();
2153
		if ($uid != \OCP\User::getUser()) {
2154
			Filesystem::initMountPoints($uid);
2155
			$ownerView = new View('/' . $uid . '/files');
2156
			try {
2157
				$filename = $ownerView->getPath($info['fileid']);
2158
			} catch (NotFoundException $e) {
2159
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2160
			}
2161
		}
2162
		return [$uid, $filename];
2163
	}
2164
2165
	/**
2166
	 * Creates parent non-existing folders
2167
	 *
2168
	 * @param string $filePath
2169
	 * @return bool
2170
	 */
2171
	private function createParentDirectories($filePath) {
2172
		$parentDirectory = \dirname($filePath);
2173
		while (!$this->file_exists($parentDirectory)) {
2174
			$result = $this->createParentDirectories($parentDirectory);
2175
			if ($result === false) {
2176
				return false;
2177
			}
2178
		}
2179
		$this->mkdir($filePath);
2180
		return true;
2181
	}
2182
}
2183