Completed
Push — master ( 24957e...d8c031 )
by Thomas
16:29 queued 07:32
created

View::copy()   D

Complexity

Conditions 13
Paths 70

Size

Total Lines 88
Code Lines 60

Duplication

Lines 23
Ratio 26.14 %

Importance

Changes 0
Metric Value
cc 13
eloc 60
nc 70
nop 3
dl 23
loc 88
rs 4.9922
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

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

Loading history...
907
						\OC_Hook::emit(
908
							Filesystem::CLASSNAME,
909
							Filesystem::signal_post_copy,
910
							[
911
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
912
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
913
							]
914
						);
915
						$this->emit_file_hooks_post($exists, $path2);
916
					}
917
918
				}
919
			} catch (\Exception $e) {
920
				$this->unlockFile($path2, $lockTypePath2);
921
				$this->unlockFile($path1, $lockTypePath1);
922
				throw $e;
923
			}
924
925
			$this->unlockFile($path2, $lockTypePath2);
926
			$this->unlockFile($path1, $lockTypePath1);
927
928
		}
929
		return $result;
930
	}
931
932
	/**
933
	 * @param string $path
934
	 * @param string $mode
935
	 * @return resource
936
	 */
937
	public function fopen($path, $mode) {
938
		$hooks = [];
939
		switch ($mode) {
940
			case 'r':
941
			case 'rb':
942
				$hooks[] = 'read';
943
				break;
944
			case 'r+':
945
			case 'rb+':
946
			case 'w+':
947
			case 'wb+':
948
			case 'x+':
949
			case 'xb+':
950
			case 'a+':
951
			case 'ab+':
952
				$hooks[] = 'read';
953
				$hooks[] = 'write';
954
				break;
955
			case 'w':
956
			case 'wb':
957
			case 'x':
958
			case 'xb':
959
			case 'a':
960
			case 'ab':
961
				$hooks[] = 'write';
962
				break;
963
			default:
964
				Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, Util::ERROR);
965
		}
966
967
		return $this->basicOperation('fopen', $path, $hooks, $mode);
968
	}
969
970
	/**
971
	 * @param string $path
972
	 * @return bool|string
973
	 * @throws \OCP\Files\InvalidPathException
974
	 */
975
	public function toTmpFile($path) {
976
		$this->assertPathLength($path);
977
		if (Filesystem::isValidPath($path)) {
978
			$source = $this->fopen($path, 'r');
979
			if ($source) {
980
				$extension = pathinfo($path, PATHINFO_EXTENSION);
981
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
982
				file_put_contents($tmpFile, $source);
983
				return $tmpFile;
984
			} else {
985
				return false;
986
			}
987
		} else {
988
			return false;
989
		}
990
	}
991
992
	/**
993
	 * @param string $tmpFile
994
	 * @param string $path
995
	 * @return bool|mixed
996
	 * @throws \OCP\Files\InvalidPathException
997
	 */
998
	public function fromTmpFile($tmpFile, $path) {
999
		$this->assertPathLength($path);
1000
		if (Filesystem::isValidPath($path)) {
1001
1002
			// Get directory that the file is going into
1003
			$filePath = dirname($path);
1004
1005
			// Create the directories if any
1006
			if (!$this->file_exists($filePath)) {
1007
				$result = $this->createParentDirectories($filePath);
1008
				if($result === false) {
1009
					return false;
1010
				}
1011
			}
1012
1013
			$source = fopen($tmpFile, 'r');
1014
			if ($source) {
1015
				$result = $this->file_put_contents($path, $source);
1016
				// $this->file_put_contents() might have already closed
1017
				// the resource, so we check it, before trying to close it
1018
				// to avoid messages in the error log.
1019
				if (is_resource($source)) {
1020
					fclose($source);
1021
				}
1022
				unlink($tmpFile);
1023
				return $result;
1024
			} else {
1025
				return false;
1026
			}
1027
		} else {
1028
			return false;
1029
		}
1030
	}
1031
1032
1033
	/**
1034
	 * @param string $path
1035
	 * @return mixed
1036
	 * @throws \OCP\Files\InvalidPathException
1037
	 */
1038
	public function getMimeType($path) {
1039
		$this->assertPathLength($path);
1040
		return $this->basicOperation('getMimeType', $path);
1041
	}
1042
1043
	/**
1044
	 * @param string $type
1045
	 * @param string $path
1046
	 * @param bool $raw
1047
	 * @return bool|null|string
1048
	 */
1049
	public function hash($type, $path, $raw = false) {
1050
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1051
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1052
		if (Filesystem::isValidPath($path)) {
1053
			$path = $this->getRelativePath($absolutePath);
1054
			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...
1055
				return false;
1056
			}
1057
			if ($this->shouldEmitHooks($path)) {
1058
				\OC_Hook::emit(
1059
					Filesystem::CLASSNAME,
1060
					Filesystem::signal_read,
1061
					[Filesystem::signal_param_path => $this->getHookPath($path)]
1062
				);
1063
			}
1064
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1065
			if ($storage) {
1066
				$result = $storage->hash($type, $internalPath, $raw);
1067
				return $result;
1068
			}
1069
		}
1070
		return null;
1071
	}
1072
1073
	/**
1074
	 * @param string $path
1075
	 * @return mixed
1076
	 * @throws \OCP\Files\InvalidPathException
1077
	 */
1078
	public function free_space($path = '/') {
1079
		$this->assertPathLength($path);
1080
		return $this->basicOperation('free_space', $path);
1081
	}
1082
1083
	/**
1084
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1085
	 *
1086
	 * @param string $operation
1087
	 * @param string $path
1088
	 * @param array $hooks (optional)
1089
	 * @param mixed $extraParam (optional)
1090
	 * @return mixed
1091
	 * @throws \Exception
1092
	 *
1093
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1094
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1095
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1096
	 */
1097
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1098
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1099
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1100
		if (Filesystem::isValidPath($path)
1101
			and !Filesystem::isForbiddenFileOrDir($path)
1102
		) {
1103
			$path = $this->getRelativePath($absolutePath);
1104
			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...
1105
				return false;
1106
			}
1107
1108
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1109
				// always a shared lock during pre-hooks so the hook can read the file
1110
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1111
			}
1112
1113
			$run = $this->runHooks($hooks, $path);
1114
			/** @var \OC\Files\Storage\Storage $storage */
1115
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1116
			if ($run and $storage) {
1117
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1118
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1119
				}
1120
				try {
1121
					if (!is_null($extraParam)) {
1122
						$result = $storage->$operation($internalPath, $extraParam);
1123
					} else {
1124
						$result = $storage->$operation($internalPath);
1125
					}
1126
				} catch (\Exception $e) {
1127 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...
1128
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1129
					} else if (in_array('read', $hooks)) {
1130
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1131
					}
1132
					throw $e;
1133
				}
1134
1135
				if (in_array('delete', $hooks) and $result) {
1136
					$this->removeUpdate($storage, $internalPath);
1137
				}
1138
				if (in_array('write', $hooks) and $operation !== 'fopen') {
1139
					$this->writeUpdate($storage, $internalPath);
1140
				}
1141
				if (in_array('touch', $hooks)) {
1142
					$this->writeUpdate($storage, $internalPath, $extraParam);
1143
				}
1144
1145
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1146
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1147
				}
1148
1149
				$unlockLater = false;
1150
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1151
					$unlockLater = true;
1152
					// make sure our unlocking callback will still be called if connection is aborted
1153
					ignore_user_abort(true);
1154
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1155 View Code Duplication
						if (in_array('write', $hooks)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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