Completed
Push — master ( 9e95a8...cb32d2 )
by Robin
08:26 queued 38s
created

View::fopen()   C

Complexity

Conditions 11
Paths 18

Size

Total Lines 29
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 24
nc 18
nop 2
dl 0
loc 29
rs 5.2653
c 0
b 0
f 0

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

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

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

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

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

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

Loading history...
250
		$parent = substr($path, 0, strrpos($path, '/'));
251
		$path = $this->getAbsolutePath($path);
252
		list($storage, $internalPath) = Filesystem::resolvePath($path);
253
		if (Filesystem::isValidPath($parent) and $storage) {
254
			return $storage->getLocalFolder($internalPath);
255
		} else {
256
			return null;
257
		}
258
	}
259
260
	/**
261
	 * the following functions operate with arguments and return values identical
262
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
263
	 * for \OC\Files\Storage\Storage via basicOperation().
264
	 */
265
	public function mkdir($path) {
266
		return $this->basicOperation('mkdir', $path, array('create', 'write'));
267
	}
268
269
	/**
270
	 * remove mount point
271
	 *
272
	 * @param \OC\Files\Mount\MoveableMount $mount
273
	 * @param string $path relative to data/
274
	 * @return boolean
275
	 */
276
	protected function removeMount($mount, $path) {
277
		if ($mount instanceof MoveableMount) {
278
			// cut of /user/files to get the relative path to data/user/files
279
			$pathParts = explode('/', $path, 4);
280
			$relPath = '/' . $pathParts[3];
281
			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
282
			\OC_Hook::emit(
283
				Filesystem::CLASSNAME, "umount",
284
				array(Filesystem::signal_param_path => $relPath)
285
			);
286
			$this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
287
			$result = $mount->removeMount();
288
			$this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
289
			if ($result) {
290
				\OC_Hook::emit(
291
					Filesystem::CLASSNAME, "post_umount",
292
					array(Filesystem::signal_param_path => $relPath)
293
				);
294
			}
295
			$this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
296
			return $result;
297
		} else {
298
			// do not allow deleting the storage's root / the mount point
299
			// because for some storages it might delete the whole contents
300
			// but isn't supposed to work that way
301
			return false;
302
		}
303
	}
304
305
	public function disableCacheUpdate() {
306
		$this->updaterEnabled = false;
307
	}
308
309
	public function enableCacheUpdate() {
310
		$this->updaterEnabled = true;
311
	}
312
313
	protected function writeUpdate(Storage $storage, $internalPath, $time = null) {
314
		if ($this->updaterEnabled) {
315
			if (is_null($time)) {
316
				$time = time();
317
			}
318
			$storage->getUpdater()->update($internalPath, $time);
319
		}
320
	}
321
322
	protected function removeUpdate(Storage $storage, $internalPath) {
323
		if ($this->updaterEnabled) {
324
			$storage->getUpdater()->remove($internalPath);
325
		}
326
	}
327
328
	protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) {
329
		if ($this->updaterEnabled) {
330
			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
331
		}
332
	}
333
334
	/**
335
	 * @param string $path
336
	 * @return bool|mixed
337
	 */
338
	public function rmdir($path) {
339
		$absolutePath = $this->getAbsolutePath($path);
340
		$mount = Filesystem::getMountManager()->find($absolutePath);
341
		if ($mount->getInternalPath($absolutePath) === '') {
342
			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...
343
		}
344
		if ($this->is_dir($path)) {
345
			$result = $this->basicOperation('rmdir', $path, array('delete'));
346
		} else {
347
			$result = false;
348
		}
349
350 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...
351
			$storage = $mount->getStorage();
352
			$internalPath = $mount->getInternalPath($absolutePath);
353
			$storage->getUpdater()->remove($internalPath);
354
		}
355
		return $result;
356
	}
357
358
	/**
359
	 * @param string $path
360
	 * @return resource
361
	 */
362
	public function opendir($path) {
363
		return $this->basicOperation('opendir', $path, array('read'));
364
	}
365
366
	/**
367
	 * @param $handle
368
	 * @return mixed
369
	 */
370
	public function readdir($handle) {
371
		$fsLocal = new Storage\Local(array('datadir' => '/'));
372
		return $fsLocal->readdir($handle);
373
	}
374
375
	/**
376
	 * @param string $path
377
	 * @return bool|mixed
378
	 */
379
	public function is_dir($path) {
380
		if ($path == '/') {
381
			return true;
382
		}
383
		return $this->basicOperation('is_dir', $path);
384
	}
385
386
	/**
387
	 * @param string $path
388
	 * @return bool|mixed
389
	 */
390
	public function is_file($path) {
391
		if ($path == '/') {
392
			return false;
393
		}
394
		return $this->basicOperation('is_file', $path);
395
	}
396
397
	/**
398
	 * @param string $path
399
	 * @return mixed
400
	 */
401
	public function stat($path) {
402
		return $this->basicOperation('stat', $path);
403
	}
404
405
	/**
406
	 * @param string $path
407
	 * @return mixed
408
	 */
409
	public function filetype($path) {
410
		return $this->basicOperation('filetype', $path);
411
	}
412
413
	/**
414
	 * @param string $path
415
	 * @return mixed
416
	 */
417
	public function filesize($path) {
418
		return $this->basicOperation('filesize', $path);
419
	}
420
421
	/**
422
	 * @param string $path
423
	 * @return bool|mixed
424
	 * @throws \OCP\Files\InvalidPathException
425
	 */
426
	public function readfile($path) {
427
		$this->assertPathLength($path);
428
		@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...
429
		$handle = $this->fopen($path, 'rb');
430
		if ($handle) {
431
			$chunkSize = 8192; // 8 kB chunks
432
			while (!feof($handle)) {
433
				echo fread($handle, $chunkSize);
434
				flush();
435
			}
436
			fclose($handle);
437
			$size = $this->filesize($path);
438
			return $size;
439
		}
440
		return false;
441
	}
442
443
	/**
444
	 * @param string $path
445
	 * @param int $from
446
	 * @param int $to
447
	 * @return bool|mixed
448
	 * @throws \OCP\Files\InvalidPathException
449
	 * @throws \OCP\Files\UnseekableException
450
	 */
451
	public function readfilePart($path, $from, $to) {
452
		$this->assertPathLength($path);
453
		@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...
454
		$handle = $this->fopen($path, 'rb');
455
		if ($handle) {
456
			if (fseek($handle, $from) === 0) {
457
				$chunkSize = 8192; // 8 kB chunks
458
				$end = $to + 1;
459
				while (!feof($handle) && ftell($handle) < $end) {
460
					$len = $end - ftell($handle);
461
					if ($len > $chunkSize) {
462
						$len = $chunkSize;
463
					}
464
					echo fread($handle, $len);
465
					flush();
466
				}
467
				$size = ftell($handle) - $from;
468
				return $size;
469
			}
470
471
			throw new \OCP\Files\UnseekableException('fseek error');
472
		}
473
		return false;
474
	}
475
476
	/**
477
	 * @param string $path
478
	 * @return mixed
479
	 */
480
	public function isCreatable($path) {
481
		return $this->basicOperation('isCreatable', $path);
482
	}
483
484
	/**
485
	 * @param string $path
486
	 * @return mixed
487
	 */
488
	public function isReadable($path) {
489
		return $this->basicOperation('isReadable', $path);
490
	}
491
492
	/**
493
	 * @param string $path
494
	 * @return mixed
495
	 */
496
	public function isUpdatable($path) {
497
		return $this->basicOperation('isUpdatable', $path);
498
	}
499
500
	/**
501
	 * @param string $path
502
	 * @return bool|mixed
503
	 */
504
	public function isDeletable($path) {
505
		$absolutePath = $this->getAbsolutePath($path);
506
		$mount = Filesystem::getMountManager()->find($absolutePath);
507
		if ($mount->getInternalPath($absolutePath) === '') {
508
			return $mount instanceof MoveableMount;
509
		}
510
		return $this->basicOperation('isDeletable', $path);
511
	}
512
513
	/**
514
	 * @param string $path
515
	 * @return mixed
516
	 */
517
	public function isSharable($path) {
518
		return $this->basicOperation('isSharable', $path);
519
	}
520
521
	/**
522
	 * @param string $path
523
	 * @return bool|mixed
524
	 */
525
	public function file_exists($path) {
526
		if ($path == '/') {
527
			return true;
528
		}
529
		return $this->basicOperation('file_exists', $path);
530
	}
531
532
	/**
533
	 * @param string $path
534
	 * @return mixed
535
	 */
536
	public function filemtime($path) {
537
		return $this->basicOperation('filemtime', $path);
538
	}
539
540
	/**
541
	 * @param string $path
542
	 * @param int|string $mtime
543
	 * @return bool
544
	 */
545
	public function touch($path, $mtime = null) {
546
		if (!is_null($mtime) and !is_numeric($mtime)) {
547
			$mtime = strtotime($mtime);
548
		}
549
550
		$hooks = array('touch');
551
552
		if (!$this->file_exists($path)) {
553
			$hooks[] = 'create';
554
			$hooks[] = 'write';
555
		}
556
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
557
		if (!$result) {
558
			// If create file fails because of permissions on external storage like SMB folders,
559
			// check file exists and return false if not.
560
			if (!$this->file_exists($path)) {
561
				return false;
562
			}
563
			if (is_null($mtime)) {
564
				$mtime = time();
565
			}
566
			//if native touch fails, we emulate it by changing the mtime in the cache
567
			$this->putFileInfo($path, array('mtime' => $mtime));
568
		}
569
		return true;
570
	}
571
572
	/**
573
	 * @param string $path
574
	 * @return mixed
575
	 */
576
	public function file_get_contents($path) {
577
		return $this->basicOperation('file_get_contents', $path, array('read'));
578
	}
579
580
	/**
581
	 * @param bool $exists
582
	 * @param string $path
583
	 * @param bool $run
584
	 */
585
	protected function emit_file_hooks_pre($exists, $path, &$run) {
586 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...
587
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array(
588
				Filesystem::signal_param_path => $this->getHookPath($path),
589
				Filesystem::signal_param_run => &$run,
590
			));
591
		} else {
592
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array(
593
				Filesystem::signal_param_path => $this->getHookPath($path),
594
				Filesystem::signal_param_run => &$run,
595
			));
596
		}
597
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array(
598
			Filesystem::signal_param_path => $this->getHookPath($path),
599
			Filesystem::signal_param_run => &$run,
600
		));
601
	}
602
603
	/**
604
	 * @param bool $exists
605
	 * @param string $path
606
	 */
607
	protected function emit_file_hooks_post($exists, $path) {
608 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...
609
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array(
610
				Filesystem::signal_param_path => $this->getHookPath($path),
611
			));
612
		} else {
613
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
614
				Filesystem::signal_param_path => $this->getHookPath($path),
615
			));
616
		}
617
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
618
			Filesystem::signal_param_path => $this->getHookPath($path),
619
		));
620
	}
621
622
	/**
623
	 * @param string $path
624
	 * @param mixed $data
625
	 * @return bool|mixed
626
	 * @throws \Exception
627
	 */
628
	public function file_put_contents($path, $data) {
629
		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
630
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
631
			if (Filesystem::isValidPath($path)
632
				and !Filesystem::isFileBlacklisted($path)
633
			) {
634
				$path = $this->getRelativePath($absolutePath);
635
636
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
637
638
				$exists = $this->file_exists($path);
639
				$run = true;
640
				if ($this->shouldEmitHooks($path)) {
641
					$this->emit_file_hooks_pre($exists, $path, $run);
642
				}
643
				if (!$run) {
644
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
645
					return false;
646
				}
647
648
				$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
649
650
				/** @var \OC\Files\Storage\Storage $storage */
651
				list($storage, $internalPath) = $this->resolvePath($path);
652
				$target = $storage->fopen($internalPath, 'w');
653
				if ($target) {
654
					list (, $result) = \OC_Helper::streamCopy($data, $target);
655
					fclose($target);
656
					fclose($data);
657
658
					$this->writeUpdate($storage, $internalPath);
659
660
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
661
662
					if ($this->shouldEmitHooks($path) && $result !== false) {
663
						$this->emit_file_hooks_post($exists, $path);
664
					}
665
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
666
					return $result;
667
				} else {
668
					$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
669
					return false;
670
				}
671
			} else {
672
				return false;
673
			}
674
		} else {
675
			$hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write');
676
			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
677
		}
678
	}
679
680
	/**
681
	 * @param string $path
682
	 * @return bool|mixed
683
	 */
684
	public function unlink($path) {
685
		if ($path === '' || $path === '/') {
686
			// do not allow deleting the root
687
			return false;
688
		}
689
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
690
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
691
		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
692
		if ($mount and $mount->getInternalPath($absolutePath) === '') {
693
			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...
694
		}
695
		$result = $this->basicOperation('unlink', $path, array('delete'));
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::isFileBlacklisted($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
					array(
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->isTargetAllowed($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
							array(
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::isFileBlacklisted($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
						array(
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
							array(
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 'r' or 'w'
935
	 * @return resource
936
	 */
937
	public function fopen($path, $mode) {
938
		$mode = str_replace('b', '', $mode); // the binary flag is a windows only feature which we do not support
939
		$hooks = array();
940
		switch ($mode) {
941
			case 'r':
942
				$hooks[] = 'read';
943
				break;
944
			case 'r+':
945
			case 'w+':
946
			case 'x+':
947
			case 'a+':
948
				$hooks[] = 'read';
949
				$hooks[] = 'write';
950
				break;
951
			case 'w':
952
			case 'x':
953
			case 'a':
954
				$hooks[] = 'write';
955
				break;
956
			default:
957
				\OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, \OCP\Util::ERROR);
958
		}
959
960
		if ($mode !== 'r' && $mode !== 'w') {
961
			\OC::$server->getLogger()->info('Trying to open a file with a mode other than "r" or "w" can cause severe performance issues with some backends');
962
		}
963
964
		return $this->basicOperation('fopen', $path, $hooks, $mode);
965
	}
966
967
	/**
968
	 * @param string $path
969
	 * @return bool|string
970
	 * @throws \OCP\Files\InvalidPathException
971
	 */
972
	public function toTmpFile($path) {
973
		$this->assertPathLength($path);
974
		if (Filesystem::isValidPath($path)) {
975
			$source = $this->fopen($path, 'r');
976
			if ($source) {
977
				$extension = pathinfo($path, PATHINFO_EXTENSION);
978
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
979
				file_put_contents($tmpFile, $source);
980
				return $tmpFile;
981
			} else {
982
				return false;
983
			}
984
		} else {
985
			return false;
986
		}
987
	}
988
989
	/**
990
	 * @param string $tmpFile
991
	 * @param string $path
992
	 * @return bool|mixed
993
	 * @throws \OCP\Files\InvalidPathException
994
	 */
995
	public function fromTmpFile($tmpFile, $path) {
996
		$this->assertPathLength($path);
997
		if (Filesystem::isValidPath($path)) {
998
999
			// Get directory that the file is going into
1000
			$filePath = dirname($path);
1001
1002
			// Create the directories if any
1003
			if (!$this->file_exists($filePath)) {
1004
				$result = $this->createParentDirectories($filePath);
1005
				if ($result === false) {
1006
					return false;
1007
				}
1008
			}
1009
1010
			$source = fopen($tmpFile, 'r');
1011
			if ($source) {
1012
				$result = $this->file_put_contents($path, $source);
1013
				// $this->file_put_contents() might have already closed
1014
				// the resource, so we check it, before trying to close it
1015
				// to avoid messages in the error log.
1016
				if (is_resource($source)) {
1017
					fclose($source);
1018
				}
1019
				unlink($tmpFile);
1020
				return $result;
1021
			} else {
1022
				return false;
1023
			}
1024
		} else {
1025
			return false;
1026
		}
1027
	}
1028
1029
1030
	/**
1031
	 * @param string $path
1032
	 * @return mixed
1033
	 * @throws \OCP\Files\InvalidPathException
1034
	 */
1035
	public function getMimeType($path) {
1036
		$this->assertPathLength($path);
1037
		return $this->basicOperation('getMimeType', $path);
1038
	}
1039
1040
	/**
1041
	 * @param string $type
1042
	 * @param string $path
1043
	 * @param bool $raw
1044
	 * @return bool|null|string
1045
	 */
1046
	public function hash($type, $path, $raw = false) {
1047
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1048
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1049
		if (Filesystem::isValidPath($path)) {
1050
			$path = $this->getRelativePath($absolutePath);
1051
			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...
1052
				return false;
1053
			}
1054
			if ($this->shouldEmitHooks($path)) {
1055
				\OC_Hook::emit(
1056
					Filesystem::CLASSNAME,
1057
					Filesystem::signal_read,
1058
					array(Filesystem::signal_param_path => $this->getHookPath($path))
1059
				);
1060
			}
1061
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1062
			if ($storage) {
1063
				$result = $storage->hash($type, $internalPath, $raw);
1064
				return $result;
1065
			}
1066
		}
1067
		return null;
1068
	}
1069
1070
	/**
1071
	 * @param string $path
1072
	 * @return mixed
1073
	 * @throws \OCP\Files\InvalidPathException
1074
	 */
1075
	public function free_space($path = '/') {
1076
		$this->assertPathLength($path);
1077
		return $this->basicOperation('free_space', $path);
1078
	}
1079
1080
	/**
1081
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1082
	 *
1083
	 * @param string $operation
1084
	 * @param string $path
1085
	 * @param array $hooks (optional)
1086
	 * @param mixed $extraParam (optional)
1087
	 * @return mixed
1088
	 * @throws \Exception
1089
	 *
1090
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1091
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1092
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1093
	 */
1094
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1095
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1096
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1097
		if (Filesystem::isValidPath($path)
1098
			and !Filesystem::isFileBlacklisted($path)
1099
		) {
1100
			$path = $this->getRelativePath($absolutePath);
1101
			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...
1102
				return false;
1103
			}
1104
1105
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1106
				// always a shared lock during pre-hooks so the hook can read the file
1107
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1108
			}
1109
1110
			$run = $this->runHooks($hooks, $path);
1111
			/** @var \OC\Files\Storage\Storage $storage */
1112
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1113
			if ($run and $storage) {
1114
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1115
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1116
				}
1117
				try {
1118
					if (!is_null($extraParam)) {
1119
						$result = $storage->$operation($internalPath, $extraParam);
1120
					} else {
1121
						$result = $storage->$operation($internalPath);
1122
					}
1123
				} catch (\Exception $e) {
1124 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...
1125
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1126
					} else if (in_array('read', $hooks)) {
1127
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1128
					}
1129
					throw $e;
1130
				}
1131
1132
				if ($result && in_array('delete', $hooks) and $result) {
1133
					$this->removeUpdate($storage, $internalPath);
1134
				}
1135
				if ($result && in_array('write', $hooks) and $operation !== 'fopen') {
1136
					$this->writeUpdate($storage, $internalPath);
1137
				}
1138
				if ($result && in_array('touch', $hooks)) {
1139
					$this->writeUpdate($storage, $internalPath, $extraParam);
1140
				}
1141
1142
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1143
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1144
				}
1145
1146
				$unlockLater = false;
1147
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1148
					$unlockLater = true;
1149
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1150 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...
1151
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1152
						} else if (in_array('read', $hooks)) {
1153
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1154
						}
1155
					});
1156
				}
1157
1158
				if ($this->shouldEmitHooks($path) && $result !== false) {
1159
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1160
						$this->runHooks($hooks, $path, true);
1161
					}
1162
				}
1163
1164 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...
1165
					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1166
				) {
1167
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1168
				}
1169
				return $result;
1170
			} else {
1171
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1172
			}
1173
		}
1174
		return null;
1175
	}
1176
1177
	/**
1178
	 * get the path relative to the default root for hook usage
1179
	 *
1180
	 * @param string $path
1181
	 * @return string
1182
	 */
1183
	private function getHookPath($path) {
1184
		if (!Filesystem::getView()) {
1185
			return $path;
1186
		}
1187
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1188
	}
1189
1190
	private function shouldEmitHooks($path = '') {
1191
		if ($path && Cache\Scanner::isPartialFile($path)) {
1192
			return false;
1193
		}
1194
		if (!Filesystem::$loaded) {
1195
			return false;
1196
		}
1197
		$defaultRoot = Filesystem::getRoot();
1198
		if ($defaultRoot === null) {
1199
			return false;
1200
		}
1201
		if ($this->fakeRoot === $defaultRoot) {
1202
			return true;
1203
		}
1204
		$fullPath = $this->getAbsolutePath($path);
1205
1206
		if ($fullPath === $defaultRoot) {
1207
			return true;
1208
		}
1209
1210
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1211
	}
1212
1213
	/**
1214
	 * @param string[] $hooks
1215
	 * @param string $path
1216
	 * @param bool $post
1217
	 * @return bool
1218
	 */
1219
	private function runHooks($hooks, $path, $post = false) {
1220
		$relativePath = $path;
1221
		$path = $this->getHookPath($path);
1222
		$prefix = ($post) ? 'post_' : '';
1223
		$run = true;
1224
		if ($this->shouldEmitHooks($relativePath)) {
1225
			foreach ($hooks as $hook) {
1226
				if ($hook != 'read') {
1227
					\OC_Hook::emit(
1228
						Filesystem::CLASSNAME,
1229
						$prefix . $hook,
1230
						array(
1231
							Filesystem::signal_param_run => &$run,
1232
							Filesystem::signal_param_path => $path
1233
						)
1234
					);
1235
				} elseif (!$post) {
1236
					\OC_Hook::emit(
1237
						Filesystem::CLASSNAME,
1238
						$prefix . $hook,
1239
						array(
1240
							Filesystem::signal_param_path => $path
1241
						)
1242
					);
1243
				}
1244
			}
1245
		}
1246
		return $run;
1247
	}
1248
1249
	/**
1250
	 * check if a file or folder has been updated since $time
1251
	 *
1252
	 * @param string $path
1253
	 * @param int $time
1254
	 * @return bool
1255
	 */
1256
	public function hasUpdated($path, $time) {
1257
		return $this->basicOperation('hasUpdated', $path, array(), $time);
1258
	}
1259
1260
	/**
1261
	 * @param string $ownerId
1262
	 * @return \OC\User\User
1263
	 */
1264
	private function getUserObjectForOwner($ownerId) {
1265
		$owner = $this->userManager->get($ownerId);
1266
		if ($owner instanceof IUser) {
1267
			return $owner;
1268
		} else {
1269
			return new User($ownerId, null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<OCP\UserInterface>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1270
		}
1271
	}
1272
1273
	/**
1274
	 * Get file info from cache
1275
	 *
1276
	 * If the file is not in cached it will be scanned
1277
	 * If the file has changed on storage the cache will be updated
1278
	 *
1279
	 * @param \OC\Files\Storage\Storage $storage
1280
	 * @param string $internalPath
1281
	 * @param string $relativePath
1282
	 * @return array|bool
1283
	 */
1284
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1285
		$cache = $storage->getCache($internalPath);
1286
		$data = $cache->get($internalPath);
1287
		$watcher = $storage->getWatcher($internalPath);
1288
1289
		try {
1290
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1291
			if (!$data || $data['size'] === -1) {
1292
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1293
				if (!$storage->file_exists($internalPath)) {
1294
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1295
					return false;
1296
				}
1297
				$scanner = $storage->getScanner($internalPath);
1298
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1299
				$data = $cache->get($internalPath);
1300
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1301
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1302
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1303
				$watcher->update($internalPath, $data);
1304
				$storage->getPropagator()->propagateChange($internalPath, time());
1305
				$data = $cache->get($internalPath);
1306
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1307
			}
1308
		} catch (LockedException $e) {
1309
			// if the file is locked we just use the old cache info
1310
		}
1311
1312
		return $data;
1313
	}
1314
1315
	/**
1316
	 * get the filesystem info
1317
	 *
1318
	 * @param string $path
1319
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1320
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1321
	 * defaults to true
1322
	 * @return \OC\Files\FileInfo|false False if file does not exist
1323
	 */
1324
	public function getFileInfo($path, $includeMountPoints = true) {
1325
		$this->assertPathLength($path);
1326
		if (!Filesystem::isValidPath($path)) {
1327
			return false;
1328
		}
1329
		if (Cache\Scanner::isPartialFile($path)) {
1330
			return $this->getPartFileInfo($path);
1331
		}
1332
		$relativePath = $path;
1333
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1334
1335
		$mount = Filesystem::getMountManager()->find($path);
1336
		$storage = $mount->getStorage();
1337
		$internalPath = $mount->getInternalPath($path);
1338
		if ($storage) {
1339
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1340
1341
			if (!$data instanceof ICacheEntry) {
1342
				return false;
1343
			}
1344
1345
			if ($mount instanceof MoveableMount && $internalPath === '') {
1346
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1347
			}
1348
1349
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1350
			$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 1335 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...
1351
1352
			if ($data and isset($data['fileid'])) {
1353
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1354
					//add the sizes of other mount points to the folder
1355
					$extOnly = ($includeMountPoints === 'ext');
1356
					$mounts = Filesystem::getMountManager()->findIn($path);
1357
					$info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1358
						$subStorage = $mount->getStorage();
1359
						return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage);
1360
					}));
1361
				}
1362
			}
1363
1364
			return $info;
1365
		}
1366
1367
		return false;
1368
	}
1369
1370
	/**
1371
	 * get the content of a directory
1372
	 *
1373
	 * @param string $directory path under datadirectory
1374
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1375
	 * @return FileInfo[]
1376
	 */
1377
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1378
		$this->assertPathLength($directory);
1379
		if (!Filesystem::isValidPath($directory)) {
1380
			return [];
1381
		}
1382
		$path = $this->getAbsolutePath($directory);
1383
		$path = Filesystem::normalizePath($path);
1384
		$mount = $this->getMount($directory);
1385
		$storage = $mount->getStorage();
1386
		$internalPath = $mount->getInternalPath($path);
1387
		if ($storage) {
1388
			$cache = $storage->getCache($internalPath);
1389
			$user = \OC_User::getUser();
1390
1391
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1392
1393
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1394
				return [];
1395
			}
1396
1397
			$folderId = $data['fileid'];
1398
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1399
1400
			$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Util::isSharingDisabledForUser() has been deprecated with message: 9.1.0 Use \OC::$server->getShareManager()->sharingDisabledForUser

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

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

Loading history...
1401
			/**
1402
			 * @var \OC\Files\FileInfo[] $files
1403
			 */
1404
			$files = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1405
				if ($sharingDisabled) {
1406
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1407
				}
1408
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1409
				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 1384 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...
1410
			}, $contents);
1411
1412
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1413
			$mounts = Filesystem::getMountManager()->findIn($path);
1414
			$dirLength = strlen($path);
1415
			foreach ($mounts as $mount) {
1416
				$mountPoint = $mount->getMountPoint();
1417
				$subStorage = $mount->getStorage();
1418
				if ($subStorage) {
1419
					$subCache = $subStorage->getCache('');
1420
1421
					$rootEntry = $subCache->get('');
1422
					if (!$rootEntry) {
1423
						$subScanner = $subStorage->getScanner('');
1424
						try {
1425
							$subScanner->scanFile('');
1426
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1427
							continue;
1428
						} catch (\OCP\Files\StorageInvalidException $e) {
1429
							continue;
1430
						} catch (\Exception $e) {
1431
							// sometimes when the storage is not available it can be any exception
1432
							\OCP\Util::writeLog(
1433
								'core',
1434
								'Exception while scanning storage "' . $subStorage->getId() . '": ' .
1435
								get_class($e) . ': ' . $e->getMessage(),
1436
								\OCP\Util::ERROR
1437
							);
1438
							continue;
1439
						}
1440
						$rootEntry = $subCache->get('');
1441
					}
1442
1443
					if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1444
						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1445
						if ($pos = strpos($relativePath, '/')) {
1446
							//mountpoint inside subfolder add size to the correct folder
1447
							$entryName = substr($relativePath, 0, $pos);
1448
							foreach ($files as &$entry) {
1449
								if ($entry->getName() === $entryName) {
1450
									$entry->addSubEntry($rootEntry, $mountPoint);
1451
								}
1452
							}
1453
						} else { //mountpoint in this folder, add an entry for it
1454
							$rootEntry['name'] = $relativePath;
1455
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1456
							$permissions = $rootEntry['permissions'];
1457
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1458
							// for shared files/folders we use the permissions given by the owner
1459
							if ($mount instanceof MoveableMount) {
1460
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1461
							} else {
1462
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1463
							}
1464
1465
							//remove any existing entry with the same name
1466
							foreach ($files as $i => $file) {
1467
								if ($file['name'] === $rootEntry['name']) {
1468
									unset($files[$i]);
1469
									break;
1470
								}
1471
							}
1472
							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1473
1474
							// if sharing was disabled for the user we remove the share permissions
1475
							if (\OCP\Util::isSharingDisabledForUser()) {
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Util::isSharingDisabledForUser() has been deprecated with message: 9.1.0 Use \OC::$server->getShareManager()->sharingDisabledForUser

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

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

Loading history...
1476
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1477
							}
1478
1479
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1480
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1481
						}
1482
					}
1483
				}
1484
			}
1485
1486
			if ($mimetype_filter) {
1487
				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1488
					if (strpos($mimetype_filter, '/')) {
1489
						return $file->getMimetype() === $mimetype_filter;
1490
					} else {
1491
						return $file->getMimePart() === $mimetype_filter;
1492
					}
1493
				});
1494
			}
1495
1496
			return $files;
1497
		} else {
1498
			return [];
1499
		}
1500
	}
1501
1502
	/**
1503
	 * change file metadata
1504
	 *
1505
	 * @param string $path
1506
	 * @param array|\OCP\Files\FileInfo $data
1507
	 * @return int
1508
	 *
1509
	 * returns the fileid of the updated file
1510
	 */
1511
	public function putFileInfo($path, $data) {
1512
		$this->assertPathLength($path);
1513
		if ($data instanceof FileInfo) {
1514
			$data = $data->getData();
1515
		}
1516
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1517
		/**
1518
		 * @var \OC\Files\Storage\Storage $storage
1519
		 * @var string $internalPath
1520
		 */
1521
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1522
		if ($storage) {
1523
			$cache = $storage->getCache($path);
1524
1525
			if (!$cache->inCache($internalPath)) {
1526
				$scanner = $storage->getScanner($internalPath);
1527
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1528
			}
1529
1530
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1511 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...
1531
		} else {
1532
			return -1;
1533
		}
1534
	}
1535
1536
	/**
1537
	 * search for files with the name matching $query
1538
	 *
1539
	 * @param string $query
1540
	 * @return FileInfo[]
1541
	 */
1542
	public function search($query) {
1543
		return $this->searchCommon('search', array('%' . $query . '%'));
1544
	}
1545
1546
	/**
1547
	 * search for files with the name matching $query
1548
	 *
1549
	 * @param string $query
1550
	 * @return FileInfo[]
1551
	 */
1552
	public function searchRaw($query) {
1553
		return $this->searchCommon('search', array($query));
1554
	}
1555
1556
	/**
1557
	 * search for files by mimetype
1558
	 *
1559
	 * @param string $mimetype
1560
	 * @return FileInfo[]
1561
	 */
1562
	public function searchByMime($mimetype) {
1563
		return $this->searchCommon('searchByMime', array($mimetype));
1564
	}
1565
1566
	/**
1567
	 * search for files by tag
1568
	 *
1569
	 * @param string|int $tag name or tag id
1570
	 * @param string $userId owner of the tags
1571
	 * @return FileInfo[]
1572
	 */
1573
	public function searchByTag($tag, $userId) {
1574
		return $this->searchCommon('searchByTag', array($tag, $userId));
1575
	}
1576
1577
	/**
1578
	 * @param string $method cache method
1579
	 * @param array $args
1580
	 * @return FileInfo[]
1581
	 */
1582
	private function searchCommon($method, $args) {
1583
		$files = array();
1584
		$rootLength = strlen($this->fakeRoot);
1585
1586
		$mount = $this->getMount('');
1587
		$mountPoint = $mount->getMountPoint();
1588
		$storage = $mount->getStorage();
1589
		if ($storage) {
1590
			$cache = $storage->getCache('');
1591
1592
			$results = call_user_func_array(array($cache, $method), $args);
1593
			foreach ($results as $result) {
1594
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1595
					$internalPath = $result['path'];
1596
					$path = $mountPoint . $result['path'];
1597
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1598
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1599
					$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 1586 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...
1600
				}
1601
			}
1602
1603
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1604
			foreach ($mounts as $mount) {
1605
				$mountPoint = $mount->getMountPoint();
1606
				$storage = $mount->getStorage();
1607
				if ($storage) {
1608
					$cache = $storage->getCache('');
1609
1610
					$relativeMountPoint = substr($mountPoint, $rootLength);
1611
					$results = call_user_func_array(array($cache, $method), $args);
1612
					if ($results) {
1613
						foreach ($results as $result) {
1614
							$internalPath = $result['path'];
1615
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1616
							$path = rtrim($mountPoint . $internalPath, '/');
1617
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1618
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1619
						}
1620
					}
1621
				}
1622
			}
1623
		}
1624
		return $files;
1625
	}
1626
1627
	/**
1628
	 * Get the owner for a file or folder
1629
	 *
1630
	 * @param string $path
1631
	 * @return string the user id of the owner
1632
	 * @throws NotFoundException
1633
	 */
1634
	public function getOwner($path) {
1635
		$info = $this->getFileInfo($path);
1636
		if (!$info) {
1637
			throw new NotFoundException($path . ' not found while trying to get owner');
1638
		}
1639
		return $info->getOwner()->getUID();
1640
	}
1641
1642
	/**
1643
	 * get the ETag for a file or folder
1644
	 *
1645
	 * @param string $path
1646
	 * @return string
1647
	 */
1648
	public function getETag($path) {
1649
		/**
1650
		 * @var Storage\Storage $storage
1651
		 * @var string $internalPath
1652
		 */
1653
		list($storage, $internalPath) = $this->resolvePath($path);
1654
		if ($storage) {
1655
			return $storage->getETag($internalPath);
1656
		} else {
1657
			return null;
1658
		}
1659
	}
1660
1661
	/**
1662
	 * Get the path of a file by id, relative to the view
1663
	 *
1664
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1665
	 *
1666
	 * @param int $id
1667
	 * @throws NotFoundException
1668
	 * @return string
1669
	 */
1670
	public function getPath($id) {
1671
		$id = (int)$id;
1672
		$manager = Filesystem::getMountManager();
1673
		$mounts = $manager->findIn($this->fakeRoot);
1674
		$mounts[] = $manager->find($this->fakeRoot);
1675
		// reverse the array so we start with the storage this view is in
1676
		// which is the most likely to contain the file we're looking for
1677
		$mounts = array_reverse($mounts);
1678
		foreach ($mounts as $mount) {
1679
			/**
1680
			 * @var \OC\Files\Mount\MountPoint $mount
1681
			 */
1682
			if ($mount->getStorage()) {
1683
				$cache = $mount->getStorage()->getCache();
1684
				$internalPath = $cache->getPathById($id);
1685
				if (is_string($internalPath)) {
1686
					$fullPath = $mount->getMountPoint() . $internalPath;
1687
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1688
						return $path;
1689
					}
1690
				}
1691
			}
1692
		}
1693
		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1694
	}
1695
1696
	/**
1697
	 * @param string $path
1698
	 * @throws InvalidPathException
1699
	 */
1700
	private function assertPathLength($path) {
1701
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1702
		// Check for the string length - performed using isset() instead of strlen()
1703
		// because isset() is about 5x-40x faster.
1704
		if (isset($path[$maxLen])) {
1705
			$pathLen = strlen($path);
1706
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1707
		}
1708
	}
1709
1710
	/**
1711
	 * check if it is allowed to move a mount point to a given target.
1712
	 * It is not allowed to move a mount point into a different mount point or
1713
	 * into an already shared folder
1714
	 *
1715
	 * @param string $target path
1716
	 * @return boolean
1717
	 */
1718
	private function isTargetAllowed($target) {
1719
1720
		list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target);
1721
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1722
			\OCP\Util::writeLog('files',
1723
				'It is not allowed to move one mount point into another one',
1724
				\OCP\Util::DEBUG);
1725
			return false;
1726
		}
1727
1728
		// note: cannot use the view because the target is already locked
1729
		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1730
		if ($fileId === -1) {
1731
			// target might not exist, need to check parent instead
1732
			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1733
		}
1734
1735
		// check if any of the parents were shared by the current owner (include collections)
1736
		$shares = \OCP\Share::getItemShared(
1737
			'folder',
1738
			$fileId,
1739
			\OCP\Share::FORMAT_NONE,
1740
			null,
1741
			true
1742
		);
1743
1744
		if (count($shares) > 0) {
1745
			\OCP\Util::writeLog('files',
1746
				'It is not allowed to move one mount point into a shared folder',
1747
				\OCP\Util::DEBUG);
1748
			return false;
1749
		}
1750
1751
		return true;
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
		try {
1791
			/** @type \OCP\Files\Storage $storage */
1792
			list($storage, $internalPath) = $this->resolvePath($path);
1793
			$storage->verifyPath($internalPath, $fileName);
1794
		} catch (ReservedWordException $ex) {
1795
			$l = \OC::$server->getL10N('lib');
1796
			throw new InvalidPathException($l->t('File name is a reserved word'));
1797
		} catch (InvalidCharacterInPathException $ex) {
1798
			$l = \OC::$server->getL10N('lib');
1799
			throw new InvalidPathException($l->t('File name contains at least one invalid character'));
1800
		} catch (FileNameTooLongException $ex) {
1801
			$l = \OC::$server->getL10N('lib');
1802
			throw new InvalidPathException($l->t('File name is too long'));
1803
		} catch (InvalidDirectoryException $ex) {
1804
			$l = \OC::$server->getL10N('lib');
1805
			throw new InvalidPathException($l->t('Dot files are not allowed'));
1806
		} catch (EmptyFileNameException $ex) {
1807
			$l = \OC::$server->getL10N('lib');
1808
			throw new InvalidPathException($l->t('Empty filename is not allowed'));
1809
		}
1810
	}
1811
1812
	/**
1813
	 * get all parent folders of $path
1814
	 *
1815
	 * @param string $path
1816
	 * @return string[]
1817
	 */
1818
	private function getParents($path) {
1819
		$path = trim($path, '/');
1820
		if (!$path) {
1821
			return [];
1822
		}
1823
1824
		$parts = explode('/', $path);
1825
1826
		// remove the single file
1827
		array_pop($parts);
1828
		$result = array('/');
1829
		$resultPath = '';
1830
		foreach ($parts as $part) {
1831
			if ($part) {
1832
				$resultPath .= '/' . $part;
1833
				$result[] = $resultPath;
1834
			}
1835
		}
1836
		return $result;
1837
	}
1838
1839
	/**
1840
	 * Returns the mount point for which to lock
1841
	 *
1842
	 * @param string $absolutePath absolute path
1843
	 * @param bool $useParentMount true to return parent mount instead of whatever
1844
	 * is mounted directly on the given path, false otherwise
1845
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1846
	 */
1847
	private function getMountForLock($absolutePath, $useParentMount = false) {
1848
		$results = [];
1849
		$mount = Filesystem::getMountManager()->find($absolutePath);
1850
		if (!$mount) {
1851
			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...
1852
		}
1853
1854
		if ($useParentMount) {
1855
			// find out if something is mounted directly on the path
1856
			$internalPath = $mount->getInternalPath($absolutePath);
1857
			if ($internalPath === '') {
1858
				// resolve the parent mount instead
1859
				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1860
			}
1861
		}
1862
1863
		return $mount;
1864
	}
1865
1866
	/**
1867
	 * Lock the given path
1868
	 *
1869
	 * @param string $path the path of the file to lock, relative to the view
1870
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1871
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1872
	 *
1873
	 * @return bool False if the path is excluded from locking, true otherwise
1874
	 * @throws \OCP\Lock\LockedException if the path is already locked
1875
	 */
1876 View Code Duplication
	private function lockPath($path, $type, $lockMountPoint = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

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

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

Loading history...
1988
		$absolutePath = $this->getAbsolutePath($path);
1989
		$absolutePath = Filesystem::normalizePath($absolutePath);
1990
		if (!$this->shouldLockFile($absolutePath)) {
1991
			return false;
1992
		}
1993
1994
		$this->lockPath($path, $type, $lockMountPoint);
1995
1996
		$parents = $this->getParents($path);
1997
		foreach ($parents as $parent) {
1998
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
1999
		}
2000
2001
		return true;
2002
	}
2003
2004
	/**
2005
	 * Unlock a path and all its parents up to the root of the view
2006
	 *
2007
	 * @param string $path the path of the file to lock relative to the view
2008
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2009
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2010
	 *
2011
	 * @return bool False if the path is excluded from locking, true otherwise
2012
	 */
2013 View Code Duplication
	public function unlockFile($path, $type, $lockMountPoint = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

Loading history...
2086
			Filesystem::initMountPoints($uid);
2087
			$ownerView = new View('/' . $uid . '/files');
2088
			try {
2089
				$filename = $ownerView->getPath($info['fileid']);
2090
			} catch (NotFoundException $e) {
2091
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2092
			}
2093
		}
2094
		return [$uid, $filename];
2095
	}
2096
2097
	/**
2098
	 * Creates parent non-existing folders
2099
	 *
2100
	 * @param string $filePath
2101
	 * @return bool
2102
	 */
2103
	private function createParentDirectories($filePath) {
2104
		$directoryParts = explode('/', $filePath);
2105
		$directoryParts = array_filter($directoryParts);
2106
		foreach ($directoryParts as $key => $part) {
2107
			$currentPathElements = array_slice($directoryParts, 0, $key);
2108
			$currentPath = '/' . implode('/', $currentPathElements);
2109
			if ($this->is_file($currentPath)) {
2110
				return false;
2111
			}
2112
			if (!$this->file_exists($currentPath)) {
2113
				$this->mkdir($currentPath);
2114
			}
2115
		}
2116
2117
		return true;
2118
	}
2119
}
2120