Completed
Push — master ( e55e53...c0bb70 )
by
unknown
05:47 queued 05:18
created

View::setIgnorePartFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 3
rs 10
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 Icewind\Streams\CallbackWrapper;
49
use OC\Files\Mount\MoveableMount;
50
use OC\Files\Storage\Storage;
51
use OC\User\RemoteUser;
52
use OCP\Constants;
53
use OCP\Events\EventEmitterTrait;
54
use OCP\Files\Cache\ICacheEntry;
55
use OCP\Files\FileNameTooLongException;
56
use OCP\Files\InvalidCharacterInPathException;
57
use OCP\Files\InvalidPathException;
58
use OCP\Files\NotFoundException;
59
use OCP\Files\ReservedWordException;
60
use OCP\IUser;
61
use OCP\Lock\ILockingProvider;
62
use OCP\Lock\LockedException;
63
use OCA\Files_Sharing\SharedMount;
64
use OCP\Util;
65
use Symfony\Component\EventDispatcher\GenericEvent;
66
67
/**
68
 * Class to provide access to ownCloud filesystem via a "view", and methods for
69
 * working with files within that view (e.g. read, write, delete, etc.). Each
70
 * view is restricted to a set of directories via a virtual root. The default view
71
 * uses the currently logged in user's data directory as root (parts of
72
 * OC_Filesystem are merely a wrapper for OC\Files\View).
73
 *
74
 * Apps that need to access files outside of the user data folders (to modify files
75
 * belonging to a user other than the one currently logged in, for example) should
76
 * use this class directly rather than using OC_Filesystem, or making use of PHP's
77
 * built-in file manipulation functions. This will ensure all hooks and proxies
78
 * are triggered correctly.
79
 *
80
 * Filesystem functions are not called directly; they are passed to the correct
81
 * \OC\Files\Storage\Storage object
82
 */
83
class View {
84
	use EventEmitterTrait;
85
	/** @var string */
86
	private $fakeRoot = '';
87
88
	/**
89
	 * @var \OCP\Lock\ILockingProvider
90
	 */
91
	private $lockingProvider;
92
93
	/** @var bool  */
94
	private $lockingEnabled;
95
96
	/** @var bool  */
97
	private $updaterEnabled = true;
98
99
	/** @var \OC\User\Manager  */
100
	private $userManager;
101
102
	/** @var \OCP\ILogger  */
103
	private $logger;
104
105
	private $eventDispatcher;
106
107
	private static $ignorePartFile = false;
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 readfile($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->fopen($path, 'rb');
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->fopen($path, 'rb');
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
				$size = \ftell($handle) - $from;
474
				return $size;
475
			}
476
477
			throw new \OCP\Files\UnseekableException('fseek error');
478
		}
479
		return false;
480
	}
481
482
	/**
483
	 * @param string $path
484
	 * @return mixed
485
	 */
486
	public function isCreatable($path) {
487
		return $this->basicOperation('isCreatable', $path);
488
	}
489
490
	/**
491
	 * @param string $path
492
	 * @return mixed
493
	 */
494
	public function isReadable($path) {
495
		return $this->basicOperation('isReadable', $path);
496
	}
497
498
	/**
499
	 * @param string $path
500
	 * @return mixed
501
	 */
502
	public function isUpdatable($path) {
503
		return $this->basicOperation('isUpdatable', $path);
504
	}
505
506
	/**
507
	 * @param string $path
508
	 * @return bool|mixed
509
	 */
510
	public function isDeletable($path) {
511
		$absolutePath = $this->getAbsolutePath($path);
512
		$mount = Filesystem::getMountManager()->find($absolutePath);
513
		if ($mount->getInternalPath($absolutePath) === '') {
514
			return $mount instanceof MoveableMount;
515
		}
516
		return $this->basicOperation('isDeletable', $path);
517
	}
518
519
	/**
520
	 * @param string $path
521
	 * @return mixed
522
	 */
523
	public function isSharable($path) {
524
		return $this->basicOperation('isSharable', $path);
525
	}
526
527
	/**
528
	 * @param string $path
529
	 * @return bool|mixed
530
	 */
531
	public function file_exists($path) {
532
		if ($path == '/') {
533
			return true;
534
		}
535
		return $this->basicOperation('file_exists', $path);
536
	}
537
538
	/**
539
	 * @param string $path
540
	 * @return mixed
541
	 */
542
	public function filemtime($path) {
543
		return $this->basicOperation('filemtime', $path);
544
	}
545
546
	/**
547
	 * @param string $path
548
	 * @param int|string $mtime
549
	 * @return bool
550
	 */
551
	public function touch($path, $mtime = null) {
552
		if ($mtime !== null and !\is_numeric($mtime)) {
553
			$mtime = \strtotime($mtime);
554
		}
555
556
		$hooks = ['touch'];
557
558
		if (!$this->file_exists($path)) {
559
			$hooks[] = 'create';
560
			$hooks[] = 'write';
561
		}
562
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
563
		if (!$result) {
564
			// If create file fails because of permissions on external storage like SMB folders,
565
			// check file exists and return false if not.
566
			if (!$this->file_exists($path)) {
567
				return false;
568
			}
569
			if ($mtime === null) {
570
				$mtime = \time();
571
			}
572
			//if native touch fails, we emulate it by changing the mtime in the cache
573
			$this->putFileInfo($path, ['mtime' => $mtime]);
574
		}
575
		return true;
576
	}
577
578
	/**
579
	 * @param string $path
580
	 * @return mixed
581
	 */
582
	public function file_get_contents($path) {
583
		return $this->emittingCall(function () use (&$path) {
584
			return $this->basicOperation('file_get_contents', $path, ['read']);
585
		}, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'read');
586
	}
587
588
	/**
589
	 * @param bool $exists
590
	 * @param string $path
591
	 * @param bool $run
592
	 */
593
	protected function emit_file_hooks_pre($exists, $path, &$run) {
594
		$event = new GenericEvent(null);
595
		if (!$exists) {
596
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
597
				Filesystem::signal_param_path => $this->getHookPath($path),
598
				Filesystem::signal_param_run => &$run,
599
			]);
600 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...
601
				$event->setArgument('run', $run);
602
				$this->eventDispatcher->dispatch('file.beforeCreate', $event);
603
				if ($event->getArgument('run') === false) {
604
					$run = $event->getArgument('run');
605
				}
606
			}
607
		} else {
608
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
609
				Filesystem::signal_param_path => $this->getHookPath($path),
610
				Filesystem::signal_param_run => &$run,
611
			]);
612 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...
613
				$event->setArgument('run', $run);
614
				$this->eventDispatcher->dispatch('file.beforeUpdate', $event);
615
				if ($event->getArgument('run') === false) {
616
					$run = $event->getArgument('run');
617
				}
618
			}
619
		}
620
621
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
622
			Filesystem::signal_param_path => $this->getHookPath($path),
623
			Filesystem::signal_param_run => &$run,
624
		]);
625 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...
626
			$event->setArgument('run', $run);
627
			$this->eventDispatcher->dispatch('file.beforeWrite', $event);
628
			if ($event->getArgument('run') === false) {
629
				$run = $event->getArgument('run');
630
			}
631
		}
632
	}
633
634
	/**
635
	 * @param bool $exists
636
	 * @param string $path
637
	 */
638
	protected function emit_file_hooks_post($exists, $path) {
639
		// A post event so no before event args required
640
		return $this->emittingCall(function () use (&$exists, &$path) {
641 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...
642
				\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
643
					Filesystem::signal_param_path => $this->getHookPath($path),
644
				]);
645
			} else {
646
				\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
647
					Filesystem::signal_param_path => $this->getHookPath($path),
648
				]);
649
			}
650
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
651
				Filesystem::signal_param_path => $this->getHookPath($path),
652
			]);
653
		}, ['before' => ['path' => $path], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'create');
654
	}
655
656
	/**
657
	 * @param string $path
658
	 * @param mixed $data
659
	 * @return bool|mixed
660
	 * @throws \Exception
661
	 */
662
	public function file_put_contents($path, $data) {
663
		return $this->emittingCall(function () use (&$path, &$data) {
664
			if (\is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
665
				$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
666
				if (Filesystem::isValidPath($path)
667
					and !Filesystem::isForbiddenFileOrDir($path)
668
				) {
669
					$path = $this->getRelativePath($absolutePath);
670
671
					$this->lockFile($path, ILockingProvider::LOCK_SHARED);
672
673
					$exists = $this->file_exists($path);
674
					$run = true;
675
					if ($this->shouldEmitHooks($path)) {
676
						$this->emit_file_hooks_pre($exists, $path, $run);
677
					}
678
					if (!$run) {
679
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
680
						return false;
681
					}
682
683
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
684
685
					/** @var \OC\Files\Storage\Storage $storage */
686
					list($storage, $internalPath) = $this->resolvePath($path);
687
					$target = $storage->fopen($internalPath, 'w');
688
					if ($target) {
689
						list(, $result) = \OC_Helper::streamCopy($data, $target);
690
						\fclose($target);
691
						\fclose($data);
692
693
						$this->writeUpdate($storage, $internalPath);
694
695
						$this->changeLock($path, ILockingProvider::LOCK_SHARED);
696
697
						if ($this->shouldEmitHooks($path) && $result !== false) {
698
							$this->emit_file_hooks_post($exists, $path);
699
						}
700
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
701
						return $result;
702
					} else {
703
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
704
						return false;
705
					}
706
				} else {
707
					return false;
708
				}
709
			} else {
710
				$hooks = ($this->file_exists($path)) ? ['update', 'write'] : ['create', 'write'];
711
				return $this->basicOperation('file_put_contents', $path, $hooks, $data);
712
			}
713
		}, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'update');
714
	}
715
716
	/**
717
	 * @param string $path
718
	 * @return bool|mixed
719
	 */
720
	public function unlink($path) {
721
		return $this->emittingCall(function () use (&$path) {
722
			if ($path === '' || $path === '/') {
723
				// do not allow deleting the root
724
				return false;
725
			}
726
			$postFix = (\substr($path, -1, 1) === '/') ? '/' : '';
727
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
728
			$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
729
			if ($mount and $mount->getInternalPath($absolutePath) === '') {
730
				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...
731
			}
732
			if ($this->is_dir($path)) {
733
				$result = $this->basicOperation('rmdir', $path, ['delete']);
734
			} else {
735
				$result = $this->basicOperation('unlink', $path, ['delete']);
736
			}
737
738 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...
739
				$storage = $mount->getStorage();
740
				$internalPath = $mount->getInternalPath($absolutePath);
741
				$storage->getUpdater()->remove($internalPath);
742
				return true;
743
			} else {
744
				return $result;
745
			}
746
		}, ['before' => ['path' => $this->getAbsolutePath($path)], 'after' => ['path' => $this->getAbsolutePath($path)]], 'file', 'delete');
747
	}
748
749
	/**
750
	 * @param string $directory
751
	 * @return bool|mixed
752
	 */
753
	public function deleteAll($directory) {
754
		return $this->rmdir($directory);
755
	}
756
757
	/**
758
	 * Rename/move a file or folder from the source path to target path.
759
	 *
760
	 * @param string $path1 source path
761
	 * @param string $path2 target path
762
	 *
763
	 * @return bool|mixed
764
	 */
765
	public function rename($path1, $path2) {
766
		return $this->emittingCall(function () use (&$path1, &$path2) {
767
			$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
768
			$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
769
			$result = false;
770
			if (
771
				Filesystem::isValidPath($path2)
772
				and Filesystem::isValidPath($path1)
773
				and !Filesystem::isForbiddenFileOrDir($path2)
774
			) {
775
				$path1 = $this->getRelativePath($absolutePath1);
776
				$path2 = $this->getRelativePath($absolutePath2);
777
				$exists = $this->file_exists($path2);
778
779
				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...
780
					return false;
781
				}
782
783
				$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
784
				try {
785
					$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
786
				} catch (LockedException $e) {
787
					$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
788
					throw $e;
789
				}
790
791
				$run = true;
792
				if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
793
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
794
					$this->emit_file_hooks_pre($exists, $path2, $run);
795
				} elseif ($this->shouldEmitHooks($path1)) {
796
					\OC_Hook::emit(
797
						Filesystem::CLASSNAME, Filesystem::signal_rename,
798
						[
799
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
800
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
801
							Filesystem::signal_param_run => &$run
802
						]
803
					);
804
				}
805
				if ($run) {
806
					$this->verifyPath(\dirname($path2), \basename($path2));
807
808
					$manager = Filesystem::getMountManager();
809
					$mount1 = $this->getMount($path1);
810
					$mount2 = $this->getMount($path2);
811
					$storage1 = $mount1->getStorage();
812
					$storage2 = $mount2->getStorage();
813
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
814
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
815
816
					$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
817
					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
818
819
					if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
820
						if ($this->canMove($mount1, $absolutePath2)) {
821
							/**
822
							 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
823
							 */
824
							$sourceMountPoint = $mount1->getMountPoint();
825
							$result = $mount1->moveMount($absolutePath2);
826
							$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
827
						} else {
828
							$result = false;
829
						}
830
						// moving a file/folder within the same mount point
831
					} elseif ($storage1 === $storage2) {
832
						if ($storage1) {
833
							$result = $storage1->rename($internalPath1, $internalPath2);
834
						} else {
835
							$result = false;
836
						}
837
						// moving a file/folder between storages (from $storage1 to $storage2)
838
					} else {
839
						$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
840
					}
841
842
					if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false && (self::$ignorePartFile === false)) {
843
						// if it was a rename from a part file to a regular file it was a write and not a rename operation
844
845
						$this->writeUpdate($storage2, $internalPath2);
846
					} elseif ($result) {
847
						if ($internalPath1 !== '') { // don't do a cache update for moved mounts
848
							$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
849
						}
850
					}
851
852
					$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
853
					$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
854
855
					if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
856
						if ($this->shouldEmitHooks()) {
857
							$this->emit_file_hooks_post($exists, $path2);
858
						}
859
					} elseif ($result) {
860
						if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
861
							\OC_Hook::emit(
862
								Filesystem::CLASSNAME,
863
								Filesystem::signal_post_rename,
864
								[
865
									Filesystem::signal_param_oldpath => $this->getHookPath($path1),
866
									Filesystem::signal_param_newpath => $this->getHookPath($path2)
867
								]
868
							);
869
						}
870
					}
871
				}
872
				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
873
				$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
874
			}
875
876
			return $result;
877
		}, [
878
			'before' => ['oldpath' => $this->getAbsolutePath($path1),
879
				'newpath' => $this->getAbsolutePath($path2)],
880
			'after' => ['oldpath' => $this->getAbsolutePath($path1),
881
				'newpath' => $this->getAbsolutePath($path2)]
882
		], 'file', 'rename');
883
	}
884
885
	/**
886
	 * Copy a file/folder from the source path to target path
887
	 *
888
	 * @param string $path1 source path
889
	 * @param string $path2 target path
890
	 * @param bool $preserveMtime whether to preserve mtime on the copy
891
	 *
892
	 * @return bool|mixed
893
	 */
894
895
	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...
896
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
897
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
898
		return $this->emittingCall(function () use ($absolutePath1, $absolutePath2) {
899
			$result = false;
900
			if (
901
				Filesystem::isValidPath($absolutePath2)
902
				&& Filesystem::isValidPath($absolutePath1)
903
				&& !Filesystem::isForbiddenFileOrDir($absolutePath2)
904
			) {
905
				$path1 = $this->getRelativePath($absolutePath1);
906
				$path2 = $this->getRelativePath($absolutePath2);
907
908
				if ($path1 === null || $path2 === null) {
909
					return false;
910
				}
911
				$run = true;
912
913
				$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
914
				$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
915
				$lockTypePath1 = ILockingProvider::LOCK_SHARED;
916
				$lockTypePath2 = ILockingProvider::LOCK_SHARED;
917
918
				try {
919
					$exists = $this->file_exists($path2);
920 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...
921
						\OC_Hook::emit(
922
							Filesystem::CLASSNAME,
923
							Filesystem::signal_copy,
924
							[
925
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
926
								Filesystem::signal_param_newpath => $this->getHookPath($path2),
927
								Filesystem::signal_param_run => &$run
928
							]
929
						);
930
						$this->emit_file_hooks_pre($exists, $path2, $run);
931
					}
932
					if ($run) {
933
						$mount1 = $this->getMount($path1);
934
						$mount2 = $this->getMount($path2);
935
						$storage1 = $mount1->getStorage();
936
						$internalPath1 = $mount1->getInternalPath($absolutePath1);
937
						$storage2 = $mount2->getStorage();
938
						$internalPath2 = $mount2->getInternalPath($absolutePath2);
939
940
						$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
941
						$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
942
943
						if ($mount1->getMountPoint() === $mount2->getMountPoint()) {
944
							if ($storage1) {
945
								$result = $storage1->copy($internalPath1, $internalPath2);
946
							} else {
947
								$result = false;
948
							}
949
						} else {
950
							$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
951
						}
952
953
						$this->writeUpdate($storage2, $internalPath2);
954
955
						$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
956
						$lockTypePath2 = ILockingProvider::LOCK_SHARED;
957
958 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...
959
							\OC_Hook::emit(
960
								Filesystem::CLASSNAME,
961
								Filesystem::signal_post_copy,
962
								[
963
									Filesystem::signal_param_oldpath => $this->getHookPath($path1),
964
									Filesystem::signal_param_newpath => $this->getHookPath($path2)
965
								]
966
							);
967
							$this->emit_file_hooks_post($exists, $path2);
968
						}
969
					}
970
				} catch (\Exception $e) {
971
					$this->unlockFile($path2, $lockTypePath2);
972
					$this->unlockFile($path1, $lockTypePath1);
973
					throw $e;
974
				}
975
976
				$this->unlockFile($path2, $lockTypePath2);
977
				$this->unlockFile($path1, $lockTypePath1);
978
			}
979
			return $result;
980
		}, [
981
			'before' => [
982
				'oldpath' => $absolutePath1,
983
				'newpath' => $absolutePath2
984
			],
985
			'after' => [
986
				'oldpath' => $absolutePath1,
987
				'newpath' => $absolutePath2
988
			]
989
		], 'file', 'copy');
990
	}
991
992
	/**
993
	 * @param string $path
994
	 * @param string $mode
995
	 * @return resource
996
	 */
997
	public function fopen($path, $mode) {
998
		$hooks = [];
999
		switch ($mode) {
1000
			case 'r':
1001
			case 'rb':
1002
				$hooks[] = 'read';
1003
				break;
1004
			case 'r+':
1005
			case 'rb+':
1006
			case 'w+':
1007
			case 'wb+':
1008
			case 'x+':
1009
			case 'xb+':
1010
			case 'a+':
1011
			case 'ab+':
1012
				$hooks[] = 'read';
1013
				$hooks[] = 'write';
1014
				break;
1015
			case 'w':
1016
			case 'wb':
1017
			case 'x':
1018
			case 'xb':
1019
			case 'a':
1020
			case 'ab':
1021
				$hooks[] = 'write';
1022
				break;
1023
			default:
1024
				Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, Util::ERROR);
1025
		}
1026
1027
		return $this->basicOperation('fopen', $path, $hooks, $mode);
1028
	}
1029
1030
	/**
1031
	 * @param string $path
1032
	 * @return bool|string
1033
	 * @throws \OCP\Files\InvalidPathException
1034
	 */
1035
	public function toTmpFile($path) {
1036
		$this->assertPathLength($path);
1037
		if (Filesystem::isValidPath($path)) {
1038
			$source = $this->fopen($path, 'r');
1039
			if ($source) {
1040
				$extension = \pathinfo($path, PATHINFO_EXTENSION);
1041
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
1042
				\file_put_contents($tmpFile, $source);
1043
				return $tmpFile;
1044
			} else {
1045
				return false;
1046
			}
1047
		} else {
1048
			return false;
1049
		}
1050
	}
1051
1052
	/**
1053
	 * @param string $tmpFile
1054
	 * @param string $path
1055
	 * @return bool|mixed
1056
	 * @throws \OCP\Files\InvalidPathException
1057
	 */
1058
	public function fromTmpFile($tmpFile, $path) {
1059
		$this->assertPathLength($path);
1060
		if (Filesystem::isValidPath($path)) {
1061
1062
			// Get directory that the file is going into
1063
			$filePath = \dirname($path);
1064
1065
			// Create the directories if any
1066
			if (!$this->file_exists($filePath)) {
1067
				$result = $this->createParentDirectories($filePath);
1068
				if ($result === false) {
1069
					return false;
1070
				}
1071
			}
1072
1073
			$source = \fopen($tmpFile, 'r');
1074
			if ($source) {
1075
				$result = $this->file_put_contents($path, $source);
1076
				// $this->file_put_contents() might have already closed
1077
				// the resource, so we check it, before trying to close it
1078
				// to avoid messages in the error log.
1079
				if (\is_resource($source)) {
1080
					\fclose($source);
1081
				}
1082
				\unlink($tmpFile);
1083
				return $result;
1084
			} else {
1085
				return false;
1086
			}
1087
		} else {
1088
			return false;
1089
		}
1090
	}
1091
1092
	/**
1093
	 * @param string $path
1094
	 * @return mixed
1095
	 * @throws \OCP\Files\InvalidPathException
1096
	 */
1097
	public function getMimeType($path) {
1098
		$this->assertPathLength($path);
1099
		return $this->basicOperation('getMimeType', $path);
1100
	}
1101
1102
	/**
1103
	 * @param string $type
1104
	 * @param string $path
1105
	 * @param bool $raw
1106
	 * @return bool|null|string
1107
	 */
1108
	public function hash($type, $path, $raw = false) {
1109
		$postFix = (\substr($path, -1, 1) === '/') ? '/' : '';
1110
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1111
		if (Filesystem::isValidPath($path)) {
1112
			$path = $this->getRelativePath($absolutePath);
1113
			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...
1114
				return false;
1115
			}
1116
			if ($this->shouldEmitHooks($path)) {
1117
				\OC_Hook::emit(
1118
					Filesystem::CLASSNAME,
1119
					Filesystem::signal_read,
1120
					[Filesystem::signal_param_path => $this->getHookPath($path)]
1121
				);
1122
			}
1123
1124
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1125
			if ($storage) {
1126
				$result = $storage->hash($type, $internalPath, $raw);
1127
				return $result;
1128
			}
1129
		}
1130
		return null;
1131
	}
1132
1133
	/**
1134
	 * @param string $path
1135
	 * @return mixed
1136
	 * @throws \OCP\Files\InvalidPathException
1137
	 */
1138
	public function free_space($path = '/') {
1139
		$this->assertPathLength($path);
1140
		return $this->basicOperation('free_space', $path);
1141
	}
1142
1143
	/**
1144
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1145
	 *
1146
	 * @param string $operation
1147
	 * @param string $path
1148
	 * @param array $hooks (optional)
1149
	 * @param mixed $extraParam (optional)
1150
	 * @return mixed
1151
	 * @throws \Exception
1152
	 *
1153
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1154
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1155
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1156
	 */
1157
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1158
		$postFix = (\substr($path, -1, 1) === '/') ? '/' : '';
1159
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1160
		if (Filesystem::isValidPath($path)
1161
			and !Filesystem::isForbiddenFileOrDir($path)
1162
		) {
1163
			$path = $this->getRelativePath($absolutePath);
1164
			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...
1165
				return false;
1166
			}
1167
1168
			if (\in_array('write', $hooks) || \in_array('delete', $hooks) || \in_array('read', $hooks)) {
1169
				// always a shared lock during pre-hooks so the hook can read the file
1170
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1171
			}
1172
1173
			$run = $this->runHooks($hooks, $path);
1174
			/** @var \OC\Files\Storage\Storage $storage */
1175
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1176
			if ($run and $storage) {
1177
				if (\in_array('write', $hooks) || \in_array('delete', $hooks)) {
1178
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1179
				}
1180
				try {
1181
					if ($extraParam !== null) {
1182
						$result = $storage->$operation($internalPath, $extraParam);
1183
					} else {
1184
						$result = $storage->$operation($internalPath);
1185
					}
1186
				} catch (\Exception $e) {
1187 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...
1188
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1189
					} elseif (\in_array('read', $hooks)) {
1190
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1191
					}
1192
					throw $e;
1193
				}
1194
1195
				if (\in_array('delete', $hooks) and $result) {
1196
					$this->removeUpdate($storage, $internalPath);
1197
				}
1198
				if (\in_array('write', $hooks) and $operation !== 'fopen') {
1199
					$this->writeUpdate($storage, $internalPath);
1200
				}
1201
				if (\in_array('touch', $hooks)) {
1202
					$this->writeUpdate($storage, $internalPath, $extraParam);
1203
				}
1204
1205
				if ((\in_array('write', $hooks) || \in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1206
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1207
				}
1208
1209
				$unlockLater = false;
1210
				if ($this->lockingEnabled && $operation === 'fopen' && \is_resource($result)) {
1211
					$unlockLater = true;
1212
					// make sure our unlocking callback will still be called if connection is aborted
1213
					\ignore_user_abort(true);
1214
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1215
						if (\in_array('write', $hooks)) {
1216
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1217
						} elseif (\in_array('read', $hooks)) {
1218
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1219
						}
1220
					});
1221
				}
1222
1223
				if ($this->shouldEmitHooks($path) && $result !== false) {
1224
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1225
						$this->runHooks($hooks, $path, true);
1226
					}
1227
				}
1228
1229 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...
1230
					&& (\in_array('write', $hooks) || \in_array('delete', $hooks) || \in_array('read', $hooks))
1231
				) {
1232
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1233
				}
1234
				return $result;
1235
			} else {
1236
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1237
			}
1238
		}
1239
		return null;
1240
	}
1241
1242
	/**
1243
	 * get the path relative to the default root for hook usage
1244
	 *
1245
	 * @param string $path
1246
	 * @return string
1247
	 */
1248
	private function getHookPath($path) {
1249
		if (!Filesystem::getView()) {
1250
			return $path;
1251
		}
1252
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1253
	}
1254
1255
	private function shouldEmitHooks($path = '') {
1256
		if ($path && Cache\Scanner::isPartialFile($path)) {
1257
			return false;
1258
		}
1259
		if (!Filesystem::$loaded) {
1260
			return false;
1261
		}
1262
		$defaultRoot = Filesystem::getRoot();
1263
		if ($defaultRoot === null) {
1264
			return false;
1265
		}
1266
		if ($this->fakeRoot === $defaultRoot) {
1267
			return true;
1268
		}
1269
		$fullPath = $this->getAbsolutePath($path);
1270
1271
		if ($fullPath === $defaultRoot) {
1272
			return true;
1273
		}
1274
1275
		return (\strlen($fullPath) > \strlen($defaultRoot)) && (\substr($fullPath, 0, \strlen($defaultRoot) + 1) === $defaultRoot . '/');
1276
	}
1277
1278
	/**
1279
	 * @param string[] $hooks
1280
	 * @param string $path
1281
	 * @param bool $post
1282
	 * @return bool
1283
	 */
1284
	private function runHooks($hooks, $path, $post = false) {
1285
		if (empty($hooks)) {
1286
			return true;
1287
		}
1288
		$relativePath = $path;
1289
		$path = $this->getHookPath($path);
1290
		$prefix = ($post) ? 'post_' : '';
1291
		$run = true;
1292
		if ($this->shouldEmitHooks($relativePath)) {
1293
			foreach ($hooks as $hook) {
1294
				if ($hook != 'read') {
1295
					\OC_Hook::emit(
1296
						Filesystem::CLASSNAME,
1297
						$prefix . $hook,
1298
						[
1299
							Filesystem::signal_param_run => &$run,
1300
							Filesystem::signal_param_path => $path
1301
						]
1302
					);
1303
				} elseif (!$post) {
1304
					\OC_Hook::emit(
1305
						Filesystem::CLASSNAME,
1306
						$prefix . $hook,
1307
						[
1308
							Filesystem::signal_param_path => $path
1309
						]
1310
					);
1311
				}
1312
			}
1313
		}
1314
		return $run;
1315
	}
1316
1317
	/**
1318
	 * check if a file or folder has been updated since $time
1319
	 *
1320
	 * @param string $path
1321
	 * @param int $time
1322
	 * @return bool
1323
	 */
1324
	public function hasUpdated($path, $time) {
1325
		return $this->basicOperation('hasUpdated', $path, [], $time);
1326
	}
1327
1328
	/**
1329
	 * @param string $ownerId
1330
	 * @return IUser
1331
	 */
1332
	private function getUserObjectForOwner($ownerId) {
1333
		$owner = $this->userManager->get($ownerId);
1334
		if (!$owner instanceof IUser) {
1335
			return new RemoteUser($ownerId);
1336
		}
1337
1338
		return $owner;
1339
	}
1340
1341
	/**
1342
	 * Get file info from cache
1343
	 *
1344
	 * If the file is not in cached it will be scanned
1345
	 * If the file has changed on storage the cache will be updated
1346
	 *
1347
	 * @param \OC\Files\Storage\Storage $storage
1348
	 * @param string $internalPath
1349
	 * @param string $relativePath
1350
	 * @return array|bool
1351
	 */
1352
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1353
		$cache = $storage->getCache($internalPath);
1354
		$data = $cache->get($internalPath);
1355
		$watcher = $storage->getWatcher($internalPath);
1356
1357
		try {
1358
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1359
			if (!$data || (isset($data['size']) && ($data['size'] === -1))) {
1360
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1361
				if (!$storage->file_exists($internalPath)) {
1362
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1363
					return false;
1364
				}
1365
				$scanner = $storage->getScanner($internalPath);
1366
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1367
				$data = $cache->get($internalPath);
1368
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1369
			} elseif (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1370
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1371
				$watcher->update($internalPath, $data);
1372
				$storage->getPropagator()->propagateChange($internalPath, \time());
1373
				$data = $cache->get($internalPath);
1374
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1375
			}
1376
		} catch (LockedException $e) {
1377
			// if the file is locked we just use the old cache info
1378
		}
1379
1380
		return $data;
1381
	}
1382
1383
	/**
1384
	 * get the filesystem info
1385
	 *
1386
	 * @param string $path
1387
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1388
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1389
	 * defaults to true
1390
	 * @return \OC\Files\FileInfo|false False if file does not exist
1391
	 */
1392
	public function getFileInfo($path, $includeMountPoints = true) {
1393
		$this->assertPathLength($path);
1394
		if (!Filesystem::isValidPath($path)) {
1395
			return false;
1396
		}
1397
		if (Cache\Scanner::isPartialFile($path)) {
1398
			return $this->getPartFileInfo($path);
1399
		}
1400
		$relativePath = $path;
1401
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1402
1403
		$mount = Filesystem::getMountManager()->find($path);
1404
		$storage = $mount->getStorage();
1405
		$internalPath = $mount->getInternalPath($path);
1406
		if ($storage) {
1407
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1408
1409
			if (!$data instanceof ICacheEntry) {
1410
				return false;
1411
			}
1412
1413
			if ($mount instanceof MoveableMount && $internalPath === '') {
1414
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1415
			}
1416
1417
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1418
			$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 1403 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...
1419
1420
			if ($data and isset($data['fileid'])) {
1421
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1422
					//add the sizes of other mount points to the folder
1423
					$extOnly = ($includeMountPoints === 'ext');
1424
					$mounts = Filesystem::getMountManager()->findIn($path);
1425
					foreach ($mounts as $mount) {
1426
						$subStorage = $mount->getStorage();
1427
						if ($subStorage) {
1428
							// exclude shared storage ?
1429
							if ($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage) {
1430
								continue;
1431
							}
1432
							$subCache = $subStorage->getCache('');
1433
							$rootEntry = $subCache->get('');
1434
							$info->addSubEntry($rootEntry, $mount->getMountPoint());
1435
						}
1436
					}
1437
				}
1438
			}
1439
1440
			return $info;
1441
		}
1442
1443
		return false;
1444
	}
1445
1446
	/**
1447
	 * get the content of a directory
1448
	 *
1449
	 * @param string $directory path under datadirectory
1450
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1451
	 * @return FileInfo[]
1452
	 */
1453
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1454
		$this->assertPathLength($directory);
1455
		if (!Filesystem::isValidPath($directory)) {
1456
			return [];
1457
		}
1458
		$path = $this->getAbsolutePath($directory);
1459
		$path = Filesystem::normalizePath($path);
1460
		$mount = $this->getMount($directory);
1461
		$storage = $mount->getStorage();
1462
		$internalPath = $mount->getInternalPath($path);
1463
		if ($storage) {
1464
			$cache = $storage->getCache($internalPath);
1465
			$user = \OC_User::getUser();
1466
1467
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1468
1469
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1470
				return [];
1471
			}
1472
1473
			$folderId = $data['fileid'];
1474
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1475
1476
			$sharingDisabled = Util::isSharingDisabledForUser();
1477
			/**
1478
			 * @var \OC\Files\FileInfo[] $files
1479
			 */
1480
			$files = \array_filter($contents, function (ICacheEntry $content) {
1481
				return (!\OC\Files\Filesystem::isForbiddenFileOrDir($content['path']));
1482
			});
1483
			$files = \array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1484
				if ($sharingDisabled) {
1485
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1486
				}
1487
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1488
				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 1460 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...
1489
			}, $files);
1490
1491
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1492
			$mounts = Filesystem::getMountManager()->findIn($path);
1493
			$dirLength = \strlen($path);
1494
			foreach ($mounts as $mount) {
1495
				$mountPoint = $mount->getMountPoint();
1496
				$subStorage = $mount->getStorage();
1497
				if ($subStorage) {
1498
					$subCache = $subStorage->getCache('');
1499
1500
					$rootEntry = $subCache->get('');
1501
					if (!$rootEntry) {
1502
						$subScanner = $subStorage->getScanner('');
1503
						try {
1504
							$subScanner->scanFile('');
1505
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1506
							continue;
1507
						} catch (\OCP\Files\StorageInvalidException $e) {
1508
							continue;
1509
						} catch (\Exception $e) {
1510
							// sometimes when the storage is not available it can be any exception
1511
							Util::writeLog(
1512
								'core',
1513
								'Exception while scanning storage "' . $subStorage->getId() . '": ' .
1514
								\get_class($e) . ': ' . $e->getMessage(),
1515
								Util::ERROR
1516
							);
1517
							continue;
1518
						}
1519
						$rootEntry = $subCache->get('');
1520
					}
1521
1522
					if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1523
						$relativePath = \trim(\substr($mountPoint, $dirLength), '/');
1524
						if ($pos = \strpos($relativePath, '/')) {
1525
							//mountpoint inside subfolder add size to the correct folder
1526
							$entryName = \substr($relativePath, 0, $pos);
1527
							foreach ($files as &$entry) {
1528
								if ($entry->getName() === $entryName) {
1529
									$entry->addSubEntry($rootEntry, $mountPoint);
1530
								}
1531
							}
1532
						} else { //mountpoint in this folder, add an entry for it
1533
							$rootEntry['name'] = $relativePath;
1534
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1535
							$permissions = $rootEntry['permissions'];
1536
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1537
							// for shared files/folders we use the permissions given by the owner
1538
							if ($mount instanceof MoveableMount) {
1539
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1540
							} else {
1541
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1542
							}
1543
1544
							//remove any existing entry with the same name
1545
							foreach ($files as $i => $file) {
1546
								if ($file['name'] === $rootEntry['name']) {
1547
									unset($files[$i]);
1548
									break;
1549
								}
1550
							}
1551
							$rootEntry['path'] = \substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), \strlen($user) + 2); // full path without /$user/
1552
1553
							// if sharing was disabled for the user we remove the share permissions
1554
							if (Util::isSharingDisabledForUser()) {
1555
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1556
							}
1557
1558
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1559
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1560
						}
1561
					}
1562
				}
1563
			}
1564
1565
			if ($mimetype_filter) {
1566
				$files = \array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1567
					if (\strpos($mimetype_filter, '/')) {
1568
						return $file->getMimetype() === $mimetype_filter;
1569
					} else {
1570
						return $file->getMimePart() === $mimetype_filter;
1571
					}
1572
				});
1573
			}
1574
1575
			return $files;
1576
		} else {
1577
			return [];
1578
		}
1579
	}
1580
1581
	/**
1582
	 * change file metadata
1583
	 *
1584
	 * @param string $path
1585
	 * @param array|\OCP\Files\FileInfo $data
1586
	 * @return int
1587
	 *
1588
	 * returns the fileid of the updated file
1589
	 */
1590
	public function putFileInfo($path, $data) {
1591
		$this->assertPathLength($path);
1592
		if ($data instanceof FileInfo) {
1593
			$data = $data->getData();
1594
		}
1595
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1596
		/**
1597
		 * @var \OC\Files\Storage\Storage $storage
1598
		 * @var string $internalPath
1599
		 */
1600
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1601
		if ($storage) {
1602
			$cache = $storage->getCache($path);
1603
1604
			if (!$cache->inCache($internalPath)) {
1605
				$scanner = $storage->getScanner($internalPath);
1606
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1607
			}
1608
1609
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1590 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...
1610
		} else {
1611
			return -1;
1612
		}
1613
	}
1614
1615
	/**
1616
	 * search for files with the name matching $query
1617
	 *
1618
	 * @param string $query
1619
	 * @return FileInfo[]
1620
	 */
1621
	public function search($query) {
1622
		return $this->searchCommon('search', ['%' . $query . '%']);
1623
	}
1624
1625
	/**
1626
	 * search for files with the name matching $query
1627
	 *
1628
	 * @param string $query
1629
	 * @return FileInfo[]
1630
	 */
1631
	public function searchRaw($query) {
1632
		return $this->searchCommon('search', [$query]);
1633
	}
1634
1635
	/**
1636
	 * search for files by mimetype
1637
	 *
1638
	 * @param string $mimetype
1639
	 * @return FileInfo[]
1640
	 */
1641
	public function searchByMime($mimetype) {
1642
		return $this->searchCommon('searchByMime', [$mimetype]);
1643
	}
1644
1645
	/**
1646
	 * search for files by tag
1647
	 *
1648
	 * @param string|int $tag name or tag id
1649
	 * @param string $userId owner of the tags
1650
	 * @return FileInfo[]
1651
	 */
1652
	public function searchByTag($tag, $userId) {
1653
		return $this->searchCommon('searchByTag', [$tag, $userId]);
1654
	}
1655
1656
	/**
1657
	 * @param string $method cache method
1658
	 * @param array $args
1659
	 * @return FileInfo[]
1660
	 */
1661
	private function searchCommon($method, $args) {
1662
		$files = [];
1663
		$rootLength = \strlen($this->fakeRoot);
1664
1665
		$mount = $this->getMount('');
1666
		$mountPoint = $mount->getMountPoint();
1667
		$storage = $mount->getStorage();
1668
		if ($storage) {
1669
			$cache = $storage->getCache('');
1670
1671
			$results = \call_user_func_array([$cache, $method], $args);
1672
			foreach ($results as $result) {
1673
				if (\substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1674
					$internalPath = $result['path'];
1675
					$path = $mountPoint . $result['path'];
1676
					$result['path'] = \substr($mountPoint . $result['path'], $rootLength);
1677
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1678
					$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 1665 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...
1679
				}
1680
			}
1681
1682
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1683
			foreach ($mounts as $mount) {
1684
				$mountPoint = $mount->getMountPoint();
1685
				$storage = $mount->getStorage();
1686
				if ($storage) {
1687
					$cache = $storage->getCache('');
1688
1689
					$relativeMountPoint = \substr($mountPoint, $rootLength);
1690
					$results = \call_user_func_array([$cache, $method], $args);
1691
					if ($results) {
1692
						foreach ($results as $result) {
1693
							$internalPath = $result['path'];
1694
							$result['path'] = \rtrim($relativeMountPoint . $result['path'], '/');
1695
							$path = \rtrim($mountPoint . $internalPath, '/');
1696
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1697
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1698
						}
1699
					}
1700
				}
1701
			}
1702
		}
1703
		return $files;
1704
	}
1705
1706
	/**
1707
	 * Get the owner for a file or folder
1708
	 *
1709
	 * @param string $path
1710
	 * @return string the user id of the owner
1711
	 * @throws NotFoundException
1712
	 */
1713
	public function getOwner($path) {
1714
		$info = $this->getFileInfo($path);
1715
		if (!$info) {
1716
			throw new NotFoundException($path . ' not found while trying to get owner');
1717
		}
1718
		return $info->getOwner()->getUID();
1719
	}
1720
1721
	/**
1722
	 * get the ETag for a file or folder
1723
	 *
1724
	 * @param string $path
1725
	 * @return string
1726
	 */
1727
	public function getETag($path) {
1728
		/**
1729
		 * @var Storage\Storage $storage
1730
		 * @var string $internalPath
1731
		 */
1732
		list($storage, $internalPath) = $this->resolvePath($path);
1733
		if ($storage) {
1734
			return $storage->getETag($internalPath);
1735
		} else {
1736
			return null;
1737
		}
1738
	}
1739
1740
	/**
1741
	 * Get the path of a file by id, relative to the view
1742
	 *
1743
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1744
	 *
1745
	 * @param int $id
1746
	 * @param bool $includeShares whether to recurse into shared mounts
1747
	 * @throws NotFoundException
1748
	 * @return string
1749
	 */
1750
	public function getPath($id, $includeShares = true) {
1751
		$id = (int)$id;
1752
		$manager = Filesystem::getMountManager();
1753
		$mounts = $manager->findIn($this->fakeRoot);
1754
		$mounts[] = $manager->find($this->fakeRoot);
1755
		// reverse the array so we start with the storage this view is in
1756
		// which is the most likely to contain the file we're looking for
1757
		$mounts = \array_reverse($mounts);
1758
		foreach ($mounts as $mount) {
1759
			/**
1760
			 * @var \OC\Files\Mount\MountPoint $mount
1761
			 */
1762
			if (!$includeShares && $mount instanceof SharedMount) {
1763
				// prevent potential infinite loop when instantiating shared storages
1764
				// recursively
1765
				continue;
1766
			}
1767 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...
1768
				$cache = $mount->getStorage()->getCache();
1769
				$internalPath = $cache->getPathById($id);
1770
				if (\is_string($internalPath)) {
1771
					$fullPath = $mount->getMountPoint() . $internalPath;
1772
					if (($path = $this->getRelativePath($fullPath)) !== null) {
1773
						return $path;
1774
					}
1775
				}
1776
			}
1777
		}
1778
		throw new NotFoundException(\sprintf('File with id "%s" has not been found.', $id));
1779
	}
1780
1781
	/**
1782
	 * @param string $path
1783
	 * @throws InvalidPathException
1784
	 */
1785
	private function assertPathLength($path) {
1786
		$maxLen = \min(PHP_MAXPATHLEN, 4000);
1787
		// Check for the string length - performed using isset() instead of strlen()
1788
		// because isset() is about 5x-40x faster.
1789
		if (isset($path[$maxLen])) {
1790
			$pathLen = \strlen($path);
1791
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1792
		}
1793
	}
1794
1795
	/**
1796
	 * check if it is allowed to move a mount point to a given target.
1797
	 * It is not allowed to move a mount point into a different mount point or
1798
	 * into an already shared folder
1799
	 *
1800
	 * @param MoveableMount $mount1 moveable mount
1801
	 * @param string $target absolute target path
1802
	 * @return boolean
1803
	 */
1804
	private function canMove(MoveableMount $mount1, $target) {
1805
		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...
1806
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1807
			Util::writeLog('files',
1808
				'It is not allowed to move one mount point into another one',
1809
				Util::DEBUG);
1810
			return false;
1811
		}
1812
1813
		return $mount1->isTargetAllowed($target);
1814
	}
1815
1816
	/**
1817
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1818
	 *
1819
	 * @param string $path
1820
	 * @return \OCP\Files\FileInfo
1821
	 */
1822
	private function getPartFileInfo($path) {
1823
		$mount = $this->getMount($path);
1824
		$storage = $mount->getStorage();
1825
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1826
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1827
		return new FileInfo(
1828
			$this->getAbsolutePath($path),
1829
			$storage,
1830
			$internalPath,
1831
			[
1832
				'fileid' => null,
1833
				'mimetype' => $storage->getMimeType($internalPath),
1834
				'name' => \basename($path),
1835
				'etag' => null,
1836
				'size' => $storage->filesize($internalPath),
1837
				'mtime' => $storage->filemtime($internalPath),
1838
				'encrypted' => false,
1839
				'permissions' => \OCP\Constants::PERMISSION_ALL
1840
			],
1841
			$mount,
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1823 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...
1842
			$owner
1843
		);
1844
	}
1845
1846
	/**
1847
	 * @param string $path
1848
	 * @param string $fileName
1849
	 * @throws InvalidPathException
1850
	 */
1851
	public function verifyPath($path, $fileName) {
1852
		$l10n = \OC::$server->getL10N('lib');
1853
1854
		// verify empty and dot files
1855
		$trimmed = \trim($fileName);
1856
		if ($trimmed === '') {
1857
			throw new InvalidPathException($l10n->t('Empty filename is not allowed'));
1858
		}
1859
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1860
			throw new InvalidPathException($l10n->t('Dot files are not allowed'));
1861
		}
1862
1863
		$matches = [];
1864
1865
		if (\preg_match('/' . FileInfo::BLACKLIST_FILES_REGEX . '/', $fileName) !== 0) {
1866
			throw new InvalidPathException(
1867
				"Can`t upload files with extension {$matches[0]} because these extensions are reserved for internal use."
1868
			);
1869
		}
1870
1871
		if (!\OC::$server->getDatabaseConnection()->allows4ByteCharacters()) {
1872
			// verify database - e.g. mysql only 3-byte chars
1873
			if (\preg_match('%(?:
1874
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1875
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1876
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1877
)%xs', $fileName)) {
1878
				throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names'));
1879
			}
1880
		}
1881
1882
		try {
1883
			/** @type \OCP\Files\Storage $storage */
1884
			list($storage, $internalPath) = $this->resolvePath($path);
1885
			$storage->verifyPath($internalPath, $fileName);
1886
		} catch (ReservedWordException $ex) {
1887
			throw new InvalidPathException($l10n->t('File name is a reserved word'));
1888
		} catch (InvalidCharacterInPathException $ex) {
1889
			throw new InvalidPathException($l10n->t('File name contains at least one invalid character'));
1890
		} catch (FileNameTooLongException $ex) {
1891
			throw new InvalidPathException($l10n->t('File name is too long'));
1892
		}
1893
	}
1894
1895
	/**
1896
	 * get all parent folders of $path
1897
	 *
1898
	 * @param string $path
1899
	 * @return string[]
1900
	 */
1901
	private function getParents($path) {
1902
		$path = \trim($path, '/');
1903
		if (!$path) {
1904
			return [];
1905
		}
1906
1907
		$parts = \explode('/', $path);
1908
1909
		// remove the single file
1910
		\array_pop($parts);
1911
		$result = ['/'];
1912
		$resultPath = '';
1913
		foreach ($parts as $part) {
1914
			if ($part) {
1915
				$resultPath .= '/' . $part;
1916
				$result[] = $resultPath;
1917
			}
1918
		}
1919
		return $result;
1920
	}
1921
1922
	/**
1923
	 * Returns the mount point for which to lock
1924
	 *
1925
	 * @param string $absolutePath absolute path
1926
	 * @param bool $useParentMount true to return parent mount instead of whatever
1927
	 * is mounted directly on the given path, false otherwise
1928
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1929
	 */
1930
	private function getMountForLock($absolutePath, $useParentMount = false) {
1931
		$results = [];
1932
		$mount = Filesystem::getMountManager()->find($absolutePath);
1933
		if (!$mount) {
1934
			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...
1935
		}
1936
1937
		if ($useParentMount) {
1938
			// find out if something is mounted directly on the path
1939
			$internalPath = $mount->getInternalPath($absolutePath);
1940
			if ($internalPath === '') {
1941
				// resolve the parent mount instead
1942
				$mount = Filesystem::getMountManager()->find(\dirname($absolutePath));
1943
			}
1944
		}
1945
1946
		return $mount;
1947
	}
1948
1949
	/**
1950
	 * Lock the given path
1951
	 *
1952
	 * @param string $path the path of the file to lock, relative to the view
1953
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1954
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1955
	 *
1956
	 * @return bool False if the path is excluded from locking, true otherwise
1957
	 * @throws \OCP\Lock\LockedException if the path is already locked
1958
	 */
1959 View Code Duplication
	private function lockPath($path, $type, $lockMountPoint = false) {
1960
		$absolutePath = $this->getAbsolutePath($path);
1961
		$absolutePath = Filesystem::normalizePath($absolutePath);
1962
		if (!$this->shouldLockFile($absolutePath)) {
1963
			return false;
1964
		}
1965
1966
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1967
		if ($mount) {
1968
			try {
1969
				$storage = $mount->getStorage();
1970
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1971
					$storage->acquireLock(
1972
						$mount->getInternalPath($absolutePath),
1973
						$type,
1974
						$this->lockingProvider
1975
					);
1976
				}
1977
			} catch (\OCP\Lock\LockedException $e) {
1978
				// rethrow with the a human-readable path
1979
				throw new \OCP\Lock\LockedException(
1980
					$this->getPathRelativeToFiles($absolutePath),
1981
					$e
1982
				);
1983
			}
1984
		}
1985
1986
		return true;
1987
	}
1988
1989
	/**
1990
	 * Change the lock type
1991
	 *
1992
	 * @param string $path the path of the file to lock, relative to the view
1993
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1994
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1995
	 *
1996
	 * @return bool False if the path is excluded from locking, true otherwise
1997
	 * @throws \OCP\Lock\LockedException if the path is already locked
1998
	 */
1999 View Code Duplication
	public function changeLock($path, $type, $lockMountPoint = false) {
2000
		$path = Filesystem::normalizePath($path);
2001
		$absolutePath = $this->getAbsolutePath($path);
2002
		$absolutePath = Filesystem::normalizePath($absolutePath);
2003
		if (!$this->shouldLockFile($absolutePath)) {
2004
			return false;
2005
		}
2006
2007
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2008
		if ($mount) {
2009
			try {
2010
				$storage = $mount->getStorage();
2011
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2012
					$storage->changeLock(
2013
						$mount->getInternalPath($absolutePath),
2014
						$type,
2015
						$this->lockingProvider
2016
					);
2017
				}
2018
			} catch (LockedException $e) {
2019
				// rethrow with the a human-readable path
2020
				throw new LockedException(
2021
					$this->getPathRelativeToFiles($absolutePath),
2022
					$e
2023
				);
2024
			}
2025
		}
2026
2027
		return true;
2028
	}
2029
2030
	/**
2031
	 * Unlock the given path
2032
	 *
2033
	 * @param string $path the path of the file to unlock, relative to the view
2034
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2035
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2036
	 *
2037
	 * @return bool False if the path is excluded from locking, true otherwise
2038
	 */
2039
	private function unlockPath($path, $type, $lockMountPoint = false) {
2040
		$absolutePath = $this->getAbsolutePath($path);
2041
		$absolutePath = Filesystem::normalizePath($absolutePath);
2042
		if (!$this->shouldLockFile($absolutePath)) {
2043
			return false;
2044
		}
2045
2046
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
2047
		if ($mount) {
2048
			$storage = $mount->getStorage();
2049
			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
2050
				$storage->releaseLock(
2051
					$mount->getInternalPath($absolutePath),
2052
					$type,
2053
					$this->lockingProvider
2054
				);
2055
			}
2056
		}
2057
2058
		return true;
2059
	}
2060
2061
	/**
2062
	 * Lock a path and all its parents up to the root of the view
2063
	 *
2064
	 * @param string $path the path of the file to lock relative to the view
2065
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2066
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2067
	 *
2068
	 * @return bool False if the path is excluded from locking, true otherwise
2069
	 */
2070 View Code Duplication
	public function lockFile($path, $type, $lockMountPoint = false) {
2071
		$absolutePath = $this->getAbsolutePath($path);
2072
		$absolutePath = Filesystem::normalizePath($absolutePath);
2073
		if (!$this->shouldLockFile($absolutePath)) {
2074
			return false;
2075
		}
2076
2077
		$this->lockPath($path, $type, $lockMountPoint);
2078
2079
		$parents = $this->getParents($path);
2080
		foreach ($parents as $parent) {
2081
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2082
		}
2083
2084
		return true;
2085
	}
2086
2087
	/**
2088
	 * Unlock a path and all its parents up to the root of the view
2089
	 *
2090
	 * @param string $path the path of the file to lock relative to the view
2091
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2092
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2093
	 *
2094
	 * @return bool False if the path is excluded from locking, true otherwise
2095
	 */
2096 View Code Duplication
	public function unlockFile($path, $type, $lockMountPoint = false) {
2097
		$absolutePath = $this->getAbsolutePath($path);
2098
		$absolutePath = Filesystem::normalizePath($absolutePath);
2099
		if (!$this->shouldLockFile($absolutePath)) {
2100
			return false;
2101
		}
2102
2103
		$this->unlockPath($path, $type, $lockMountPoint);
2104
2105
		$parents = $this->getParents($path);
2106
		foreach ($parents as $parent) {
2107
			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2108
		}
2109
2110
		return true;
2111
	}
2112
2113
	/**
2114
	 * Only lock files in data/user/files/
2115
	 *
2116
	 * @param string $path Absolute path to the file/folder we try to (un)lock
2117
	 * @return bool
2118
	 */
2119
	protected function shouldLockFile($path) {
2120
		$path = Filesystem::normalizePath($path);
2121
2122
		$pathSegments = \explode('/', $path);
2123
		if (isset($pathSegments[2])) {
2124
			// E.g.: /username/files/path-to-file
2125
			return ($pathSegments[2] === 'files') && (\count($pathSegments) > 3);
2126
		}
2127
2128
		return true;
2129
	}
2130
2131
	/**
2132
	 * Shortens the given absolute path to be relative to
2133
	 * "$user/files".
2134
	 *
2135
	 * @param string $absolutePath absolute path which is under "files"
2136
	 *
2137
	 * @return string path relative to "files" with trimmed slashes or null
2138
	 * if the path was NOT relative to files
2139
	 *
2140
	 * @throws \InvalidArgumentException if the given path was not under "files"
2141
	 * @since 8.1.0
2142
	 */
2143
	public function getPathRelativeToFiles($absolutePath) {
2144
		$path = Filesystem::normalizePath($absolutePath);
2145
		$parts = \explode('/', \trim($path, '/'), 3);
2146
		// "$user", "files", "path/to/dir"
2147
		if (!isset($parts[1]) || $parts[1] !== 'files') {
2148
			throw new \InvalidArgumentException('"' . $absolutePath . '" must be relative to "files"');
2149
		}
2150
		if (isset($parts[2])) {
2151
			return $parts[2];
2152
		}
2153
		return '';
2154
	}
2155
2156
	/**
2157
	 * @param string $filename
2158
	 * @return array
2159
	 * @throws \OC\User\NoUserException
2160
	 * @throws NotFoundException
2161
	 */
2162
	public function getUidAndFilename($filename) {
2163
		$info = $this->getFileInfo($filename);
2164
		if (!$info instanceof \OCP\Files\FileInfo) {
2165
			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2166
		}
2167
		$uid = $info->getOwner()->getUID();
2168
		if ($uid != \OCP\User::getUser()) {
2169
			Filesystem::initMountPoints($uid);
2170
			$ownerView = new View('/' . $uid . '/files');
2171
			try {
2172
				$filename = $ownerView->getPath($info['fileid']);
2173
			} catch (NotFoundException $e) {
2174
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2175
			}
2176
		}
2177
		return [$uid, $filename];
2178
	}
2179
2180
	/**
2181
	 * Creates parent non-existing folders
2182
	 *
2183
	 * @param string $filePath
2184
	 * @return bool
2185
	 */
2186
	private function createParentDirectories($filePath) {
2187
		$parentDirectory = \dirname($filePath);
2188
		while (!$this->file_exists($parentDirectory)) {
2189
			$result = $this->createParentDirectories($parentDirectory);
2190
			if ($result === false) {
2191
				return false;
2192
			}
2193
		}
2194
		$this->mkdir($filePath);
2195
		return true;
2196
	}
2197
2198
	/**
2199
	 * User can create part files example to a call for rename(), in effect
2200
	 * it might not be a part file. So for better control in such cases this
2201
	 * method would help to let the method in rename() to know if it is a
2202
	 * part file.
2203
	 *
2204
	 * @param bool $isIgnored
2205
	 */
2206
	public static function setIgnorePartFile($isIgnored) {
2207
		self::$ignorePartFile = $isIgnored;
2208
	}
2209
}
2210