Completed
Pull Request — master (#29676)
by Tom
09:20
created

View::filesize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Björn Schießle <[email protected]>
6
 * @author cmeh <[email protected]>
7
 * @author Florin Peter <[email protected]>
8
 * @author Jesús Macias <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Jörn Friedrich Dreyer <[email protected]>
11
 * @author karakayasemi <[email protected]>
12
 * @author Klaas Freitag <[email protected]>
13
 * @author Lukas Reschke <[email protected]>
14
 * @author Luke Policinski <[email protected]>
15
 * @author Martin Mattel <[email protected]>
16
 * @author Michael Gapczynski <[email protected]>
17
 * @author Morris Jobke <[email protected]>
18
 * @author Petr Svoboda <[email protected]>
19
 * @author Piotr Filiciak <[email protected]>
20
 * @author Robin Appelman <[email protected]>
21
 * @author Robin McCorkell <[email protected]>
22
 * @author Roeland Jago Douma <[email protected]>
23
 * @author Sam Tuke <[email protected]>
24
 * @author Stefan Weil <[email protected]>
25
 * @author Thomas Müller <[email protected]>
26
 * @author Thomas Tanghus <[email protected]>
27
 * @author Vincent Petry <[email protected]>
28
 *
29
 * @copyright Copyright (c) 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 string $path
365
	 * @return bool|mixed
366
	 */
367
	public function is_dir($path) {
368
		if ($path == '/') {
369
			return true;
370
		}
371
		return $this->basicOperation('is_dir', $path);
372
	}
373
374
	/**
375
	 * @param string $path
376
	 * @return bool|mixed
377
	 */
378
	public function is_file($path) {
379
		if ($path == '/') {
380
			return false;
381
		}
382
		return $this->basicOperation('is_file', $path);
383
	}
384
385
	/**
386
	 * @param string $path
387
	 * @return mixed
388
	 */
389
	public function stat($path) {
390
		return $this->basicOperation('stat', $path);
391
	}
392
393
	/**
394
	 * @param string $path
395
	 * @return mixed
396
	 */
397
	public function filetype($path) {
398
		return $this->basicOperation('filetype', $path);
399
	}
400
401
	/**
402
	 * @param string $path
403
	 * @return mixed
404
	 */
405
	public function filesize($path) {
406
		return $this->basicOperation('filesize', $path);
407
	}
408
409
	/**
410
	 * @param string $path
411
	 * @return bool|mixed
412
	 * @throws \OCP\Files\InvalidPathException
413
	 */
414
	public function readfile($path) {
415
		$this->assertPathLength($path);
416
		@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...
417
		$handle = $this->fopen($path, 'rb');
418
		if ($handle) {
419
			$chunkSize = 8192; // 8 kB chunks
420
			while (!feof($handle)) {
421
				echo fread($handle, $chunkSize);
422
				flush();
423
			}
424
			$size = $this->filesize($path);
425
			return $size;
426
		}
427
		return false;
428
	}
429
430
	/**
431
	 * @param string $path
432
	 * @param int $from
433
	 * @param int $to
434
	 * @return bool|mixed
435
	 * @throws \OCP\Files\InvalidPathException
436
	 * @throws \OCP\Files\UnseekableException
437
	 */
438
	public function readfilePart($path, $from, $to) {
439
		$this->assertPathLength($path);
440
		@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...
441
		$handle = $this->fopen($path, 'rb');
442
		if ($handle) {
443
			if (fseek($handle, $from) === 0) {
444
				$chunkSize = 8192; // 8 kB chunks
445
				$end = $to + 1;
446
				while (!feof($handle) && ftell($handle) < $end) {
447
					$len = $end - ftell($handle);
448
					if ($len > $chunkSize) {
449
						$len = $chunkSize;
450
					}
451
					echo fread($handle, $len);
452
					flush();
453
				}
454
				$size = ftell($handle) - $from;
455
				return $size;
456
			}
457
458
			throw new \OCP\Files\UnseekableException('fseek error');
459
		}
460
		return false;
461
	}
462
463
	/**
464
	 * @param string $path
465
	 * @return mixed
466
	 */
467
	public function isCreatable($path) {
468
		return $this->basicOperation('isCreatable', $path);
469
	}
470
471
	/**
472
	 * @param string $path
473
	 * @return mixed
474
	 */
475
	public function isReadable($path) {
476
		return $this->basicOperation('isReadable', $path);
477
	}
478
479
	/**
480
	 * @param string $path
481
	 * @return mixed
482
	 */
483
	public function isUpdatable($path) {
484
		return $this->basicOperation('isUpdatable', $path);
485
	}
486
487
	/**
488
	 * @param string $path
489
	 * @return bool|mixed
490
	 */
491
	public function isDeletable($path) {
492
		$absolutePath = $this->getAbsolutePath($path);
493
		$mount = Filesystem::getMountManager()->find($absolutePath);
494
		if ($mount->getInternalPath($absolutePath) === '') {
495
			return $mount instanceof MoveableMount;
496
		}
497
		return $this->basicOperation('isDeletable', $path);
498
	}
499
500
	/**
501
	 * @param string $path
502
	 * @return mixed
503
	 */
504
	public function isSharable($path) {
505
		return $this->basicOperation('isSharable', $path);
506
	}
507
508
	/**
509
	 * @param string $path
510
	 * @return bool|mixed
511
	 */
512
	public function file_exists($path) {
513
		if ($path == '/') {
514
			return true;
515
		}
516
		return $this->basicOperation('file_exists', $path);
517
	}
518
519
	/**
520
	 * @param string $path
521
	 * @return mixed
522
	 */
523
	public function filemtime($path) {
524
		return $this->basicOperation('filemtime', $path);
525
	}
526
527
	/**
528
	 * @param string $path
529
	 * @param int|string $mtime
530
	 * @return bool
531
	 */
532
	public function touch($path, $mtime = null) {
533
		if (!is_null($mtime) and !is_numeric($mtime)) {
534
			$mtime = strtotime($mtime);
535
		}
536
537
		$hooks = ['touch'];
538
539
		if (!$this->file_exists($path)) {
540
			$hooks[] = 'create';
541
			$hooks[] = 'write';
542
		}
543
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
544
		if (!$result) {
545
			// If create file fails because of permissions on external storage like SMB folders,
546
			// check file exists and return false if not.
547
			if (!$this->file_exists($path)) {
548
				return false;
549
			}
550
			if (is_null($mtime)) {
551
				$mtime = time();
552
			}
553
			//if native touch fails, we emulate it by changing the mtime in the cache
554
			$this->putFileInfo($path, ['mtime' => $mtime]);
555
		}
556
		return true;
557
	}
558
559
	/**
560
	 * @param string $path
561
	 * @return mixed
562
	 */
563
	public function file_get_contents($path) {
564
		return $this->basicOperation('file_get_contents', $path, ['read']);
565
	}
566
567
	/**
568
	 * @param bool $exists
569
	 * @param string $path
570
	 * @param bool $run
571
	 */
572
	protected function emit_file_hooks_pre($exists, $path, &$run) {
573 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...
574
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
575
				Filesystem::signal_param_path => $this->getHookPath($path),
576
				Filesystem::signal_param_run => &$run,
577
			]);
578
		} else {
579
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
580
				Filesystem::signal_param_path => $this->getHookPath($path),
581
				Filesystem::signal_param_run => &$run,
582
			]);
583
		}
584
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
585
			Filesystem::signal_param_path => $this->getHookPath($path),
586
			Filesystem::signal_param_run => &$run,
587
		]);
588
	}
589
590
	/**
591
	 * @param bool $exists
592
	 * @param string $path
593
	 */
594
	protected function emit_file_hooks_post($exists, $path) {
595 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...
596
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
597
				Filesystem::signal_param_path => $this->getHookPath($path),
598
			]);
599
		} else {
600
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
601
				Filesystem::signal_param_path => $this->getHookPath($path),
602
			]);
603
		}
604
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
605
			Filesystem::signal_param_path => $this->getHookPath($path),
606
		]);
607
	}
608
609
	/**
610
	 * @param string $path
611
	 * @param mixed $data
612
	 * @return bool|mixed
613
	 * @throws \Exception
614
	 */
615
	public function file_put_contents($path, $data) {
616
		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
617
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
618
			if (Filesystem::isValidPath($path)
619
				and !Filesystem::isForbiddenFileOrDir($path)
620
			) {
621
				$path = $this->getRelativePath($absolutePath);
622
623
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
624
625
				$exists = $this->file_exists($path);
626
				$run = true;
627
				if ($this->shouldEmitHooks($path)) {
628
					$this->emit_file_hooks_pre($exists, $path, $run);
629
				}
630
				if (!$run) {
631
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
632
					return false;
633
				}
634
635
				$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
636
637
				/** @var \OC\Files\Storage\Storage $storage */
638
				list($storage, $internalPath) = $this->resolvePath($path);
639
				$target = $storage->fopen($internalPath, 'w');
640
				if ($target) {
641
					list (, $result) = \OC_Helper::streamCopy($data, $target);
642
					fclose($target);
643
					fclose($data);
644
645
					$this->writeUpdate($storage, $internalPath);
646
647
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
648
649
					if ($this->shouldEmitHooks($path) && $result !== false) {
650
						$this->emit_file_hooks_post($exists, $path);
651
					}
652
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
653
					return $result;
654
				} else {
655
					$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
656
					return false;
657
				}
658
			} else {
659
				return false;
660
			}
661
		} else {
662
			$hooks = ($this->file_exists($path)) ? ['update', 'write'] : ['create', 'write'];
663
			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
664
		}
665
	}
666
667
	/**
668
	 * @param string $path
669
	 * @return bool|mixed
670
	 */
671
	public function unlink($path) {
672
		if ($path === '' || $path === '/') {
673
			// do not allow deleting the root
674
			return false;
675
		}
676
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
677
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
678
		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
679
		if ($mount and $mount->getInternalPath($absolutePath) === '') {
680
			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...
681
		}
682
		if ($this->is_dir($path)) {
683
			$result = $this->basicOperation('rmdir', $path, array('delete'));
684
		} else {
685
			$result = $this->basicOperation('unlink', $path, array('delete'));
686
		}
687 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...
688
			$storage = $mount->getStorage();
689
			$internalPath = $mount->getInternalPath($absolutePath);
690
			$storage->getUpdater()->remove($internalPath);
691
			return true;
692
		} else {
693
			return $result;
694
		}
695
	}
696
697
	/**
698
	 * @param string $directory
699
	 * @return bool|mixed
700
	 */
701
	public function deleteAll($directory) {
702
		return $this->rmdir($directory);
703
	}
704
705
	/**
706
	 * Rename/move a file or folder from the source path to target path.
707
	 *
708
	 * @param string $path1 source path
709
	 * @param string $path2 target path
710
	 *
711
	 * @return bool|mixed
712
	 */
713
	public function rename($path1, $path2) {
714
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
715
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
716
		$result = false;
717
		if (
718
			Filesystem::isValidPath($path2)
719
			and Filesystem::isValidPath($path1)
720
			and !Filesystem::isForbiddenFileOrDir($path2)
721
		) {
722
			$path1 = $this->getRelativePath($absolutePath1);
723
			$path2 = $this->getRelativePath($absolutePath2);
724
			$exists = $this->file_exists($path2);
725
726
			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...
727
				return false;
728
			}
729
730
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
731
			try {
732
				$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
733
			} catch (LockedException $e) {
734
				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
735
				throw $e;
736
			}
737
738
			$run = true;
739
			if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
740
				// if it was a rename from a part file to a regular file it was a write and not a rename operation
741
				$this->emit_file_hooks_pre($exists, $path2, $run);
742
			} elseif ($this->shouldEmitHooks($path1)) {
743
				\OC_Hook::emit(
744
					Filesystem::CLASSNAME, Filesystem::signal_rename,
745
					[
746
						Filesystem::signal_param_oldpath => $this->getHookPath($path1),
747
						Filesystem::signal_param_newpath => $this->getHookPath($path2),
748
						Filesystem::signal_param_run => &$run
749
					]
750
				);
751
			}
752
			if ($run) {
753
				$this->verifyPath(dirname($path2), basename($path2));
754
755
				$manager = Filesystem::getMountManager();
756
				$mount1 = $this->getMount($path1);
757
				$mount2 = $this->getMount($path2);
758
				$storage1 = $mount1->getStorage();
759
				$storage2 = $mount2->getStorage();
760
				$internalPath1 = $mount1->getInternalPath($absolutePath1);
761
				$internalPath2 = $mount2->getInternalPath($absolutePath2);
762
763
				$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
764
				$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
765
766
				if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
767
					if ($this->canMove($mount1, $absolutePath2)) {
768
						/**
769
						 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
770
						 */
771
						$sourceMountPoint = $mount1->getMountPoint();
772
						$result = $mount1->moveMount($absolutePath2);
773
						$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
774
					} else {
775
						$result = false;
776
					}
777
					// moving a file/folder within the same mount point
778
				} elseif ($storage1 === $storage2) {
779
					if ($storage1) {
780
						$result = $storage1->rename($internalPath1, $internalPath2);
781
					} else {
782
						$result = false;
783
					}
784
					// moving a file/folder between storages (from $storage1 to $storage2)
785
				} else {
786
					$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
787
				}
788
789
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
790
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
791
792
					$this->writeUpdate($storage2, $internalPath2);
793
				} else if ($result) {
794
					if ($internalPath1 !== '') { // don't do a cache update for moved mounts
795
						$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
796
					}
797
				}
798
799
				$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
800
				$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
801
802
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
803
					if ($this->shouldEmitHooks()) {
804
						$this->emit_file_hooks_post($exists, $path2);
805
					}
806
				} elseif ($result) {
807
					if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
808
						\OC_Hook::emit(
809
							Filesystem::CLASSNAME,
810
							Filesystem::signal_post_rename,
811
							[
812
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
813
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
814
							]
815
						);
816
					}
817
				}
818
			}
819
			$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
820
			$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
821
		}
822
		return $result;
823
	}
824
825
	/**
826
	 * Copy a file/folder from the source path to target path
827
	 *
828
	 * @param string $path1 source path
829
	 * @param string $path2 target path
830
	 * @param bool $preserveMtime whether to preserve mtime on the copy
831
	 *
832
	 * @return bool|mixed
833
	 */
834
	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...
835
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
836
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
837
		$result = false;
838
		if (
839
			Filesystem::isValidPath($path2)
840
			and Filesystem::isValidPath($path1)
841
			and !Filesystem::isForbiddenFileOrDir($path2)
842
		) {
843
			$path1 = $this->getRelativePath($absolutePath1);
844
			$path2 = $this->getRelativePath($absolutePath2);
845
846
			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...
847
				return false;
848
			}
849
			$run = true;
850
851
			$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
852
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
853
			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
854
			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
855
856
			try {
857
858
				$exists = $this->file_exists($path2);
859 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...
860
					\OC_Hook::emit(
861
						Filesystem::CLASSNAME,
862
						Filesystem::signal_copy,
863
						[
864
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
865
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
866
							Filesystem::signal_param_run => &$run
867
						]
868
					);
869
					$this->emit_file_hooks_pre($exists, $path2, $run);
870
				}
871
				if ($run) {
872
					$mount1 = $this->getMount($path1);
873
					$mount2 = $this->getMount($path2);
874
					$storage1 = $mount1->getStorage();
875
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
876
					$storage2 = $mount2->getStorage();
877
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
878
879
					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
880
					$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
881
882
					if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
883
						if ($storage1) {
884
							$result = $storage1->copy($internalPath1, $internalPath2);
885
						} else {
886
							$result = false;
887
						}
888
					} else {
889
						$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
890
					}
891
892
					$this->writeUpdate($storage2, $internalPath2);
893
894
					$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
895
					$lockTypePath2 = ILockingProvider::LOCK_SHARED;
896
897 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...
898
						\OC_Hook::emit(
899
							Filesystem::CLASSNAME,
900
							Filesystem::signal_post_copy,
901
							[
902
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
903
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
904
							]
905
						);
906
						$this->emit_file_hooks_post($exists, $path2);
907
					}
908
909
				}
910
			} catch (\Exception $e) {
911
				$this->unlockFile($path2, $lockTypePath2);
912
				$this->unlockFile($path1, $lockTypePath1);
913
				throw $e;
914
			}
915
916
			$this->unlockFile($path2, $lockTypePath2);
917
			$this->unlockFile($path1, $lockTypePath1);
918
919
		}
920
		return $result;
921
	}
922
923
	/**
924
	 * @param string $path
925
	 * @param string $mode
926
	 * @return resource
927
	 */
928
	public function fopen($path, $mode) {
929
		$hooks = [];
930
		switch ($mode) {
931
			case 'r':
932
			case 'rb':
933
				$hooks[] = 'read';
934
				break;
935
			case 'r+':
936
			case 'rb+':
937
			case 'w+':
938
			case 'wb+':
939
			case 'x+':
940
			case 'xb+':
941
			case 'a+':
942
			case 'ab+':
943
				$hooks[] = 'read';
944
				$hooks[] = 'write';
945
				break;
946
			case 'w':
947
			case 'wb':
948
			case 'x':
949
			case 'xb':
950
			case 'a':
951
			case 'ab':
952
				$hooks[] = 'write';
953
				break;
954
			default:
955
				Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, Util::ERROR);
956
		}
957
958
		return $this->basicOperation('fopen', $path, $hooks, $mode);
959
	}
960
961
	/**
962
	 * @param string $path
963
	 * @return bool|string
964
	 * @throws \OCP\Files\InvalidPathException
965
	 */
966
	public function toTmpFile($path) {
967
		$this->assertPathLength($path);
968
		if (Filesystem::isValidPath($path)) {
969
			$source = $this->fopen($path, 'r');
970
			if ($source) {
971
				$extension = pathinfo($path, PATHINFO_EXTENSION);
972
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
973
				file_put_contents($tmpFile, $source);
974
				return $tmpFile;
975
			} else {
976
				return false;
977
			}
978
		} else {
979
			return false;
980
		}
981
	}
982
983
	/**
984
	 * @param string $tmpFile
985
	 * @param string $path
986
	 * @return bool|mixed
987
	 * @throws \OCP\Files\InvalidPathException
988
	 */
989
	public function fromTmpFile($tmpFile, $path) {
990
		$this->assertPathLength($path);
991
		if (Filesystem::isValidPath($path)) {
992
993
			// Get directory that the file is going into
994
			$filePath = dirname($path);
995
996
			// Create the directories if any
997
			if (!$this->file_exists($filePath)) {
998
				$result = $this->createParentDirectories($filePath);
999
				if($result === false) {
1000
					return false;
1001
				}
1002
			}
1003
1004
			$source = fopen($tmpFile, 'r');
1005
			if ($source) {
1006
				$result = $this->file_put_contents($path, $source);
1007
				// $this->file_put_contents() might have already closed
1008
				// the resource, so we check it, before trying to close it
1009
				// to avoid messages in the error log.
1010
				if (is_resource($source)) {
1011
					fclose($source);
1012
				}
1013
				unlink($tmpFile);
1014
				return $result;
1015
			} else {
1016
				return false;
1017
			}
1018
		} else {
1019
			return false;
1020
		}
1021
	}
1022
1023
1024
	/**
1025
	 * @param string $path
1026
	 * @return mixed
1027
	 * @throws \OCP\Files\InvalidPathException
1028
	 */
1029
	public function getMimeType($path) {
1030
		$this->assertPathLength($path);
1031
		return $this->basicOperation('getMimeType', $path);
1032
	}
1033
1034
	/**
1035
	 * @param string $type
1036
	 * @param string $path
1037
	 * @param bool $raw
1038
	 * @return bool|null|string
1039
	 */
1040
	public function hash($type, $path, $raw = false) {
1041
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1042
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1043
		if (Filesystem::isValidPath($path)) {
1044
			$path = $this->getRelativePath($absolutePath);
1045
			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...
1046
				return false;
1047
			}
1048
			if ($this->shouldEmitHooks($path)) {
1049
				\OC_Hook::emit(
1050
					Filesystem::CLASSNAME,
1051
					Filesystem::signal_read,
1052
					[Filesystem::signal_param_path => $this->getHookPath($path)]
1053
				);
1054
			}
1055
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1056
			if ($storage) {
1057
				$result = $storage->hash($type, $internalPath, $raw);
1058
				return $result;
1059
			}
1060
		}
1061
		return null;
1062
	}
1063
1064
	/**
1065
	 * @param string $path
1066
	 * @return mixed
1067
	 * @throws \OCP\Files\InvalidPathException
1068
	 */
1069
	public function free_space($path = '/') {
1070
		$this->assertPathLength($path);
1071
		return $this->basicOperation('free_space', $path);
1072
	}
1073
1074
	/**
1075
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1076
	 *
1077
	 * @param string $operation
1078
	 * @param string $path
1079
	 * @param array $hooks (optional)
1080
	 * @param mixed $extraParam (optional)
1081
	 * @return mixed
1082
	 * @throws \Exception
1083
	 *
1084
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1085
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1086
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1087
	 */
1088
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1089
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1090
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1091
		if (Filesystem::isValidPath($path)
1092
			and !Filesystem::isForbiddenFileOrDir($path)
1093
		) {
1094
			$path = $this->getRelativePath($absolutePath);
1095
			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...
1096
				return false;
1097
			}
1098
1099
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1100
				// always a shared lock during pre-hooks so the hook can read the file
1101
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1102
			}
1103
1104
			$run = $this->runHooks($hooks, $path);
1105
			/** @var \OC\Files\Storage\Storage $storage */
1106
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1107
			if ($run and $storage) {
1108
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1109
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1110
				}
1111
				try {
1112
					if (!is_null($extraParam)) {
1113
						$result = $storage->$operation($internalPath, $extraParam);
1114
					} else {
1115
						$result = $storage->$operation($internalPath);
1116
					}
1117
				} catch (\Exception $e) {
1118 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...
1119
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1120
					} else if (in_array('read', $hooks)) {
1121
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1122
					}
1123
					throw $e;
1124
				}
1125
1126
				if (in_array('delete', $hooks) and $result) {
1127
					$this->removeUpdate($storage, $internalPath);
1128
				}
1129
				if (in_array('write', $hooks) and $operation !== 'fopen') {
1130
					$this->writeUpdate($storage, $internalPath);
1131
				}
1132
				if (in_array('touch', $hooks)) {
1133
					$this->writeUpdate($storage, $internalPath, $extraParam);
1134
				}
1135
1136
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1137
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1138
				}
1139
1140
				$unlockLater = false;
1141
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1142
					$unlockLater = true;
1143
					// make sure our unlocking callback will still be called if connection is aborted
1144
					ignore_user_abort(true);
1145
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1146 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...
1147
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1148
						} else if (in_array('read', $hooks)) {
1149
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1150
						}
1151
					});
1152
				}
1153
1154
				if ($this->shouldEmitHooks($path) && $result !== false) {
1155
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1156
						$this->runHooks($hooks, $path, true);
1157
					}
1158
				}
1159
1160 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...
1161
					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1162
				) {
1163
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1164
				}
1165
				return $result;
1166
			} else {
1167
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1168
			}
1169
		}
1170
		return null;
1171
	}
1172
1173
	/**
1174
	 * get the path relative to the default root for hook usage
1175
	 *
1176
	 * @param string $path
1177
	 * @return string
1178
	 */
1179
	private function getHookPath($path) {
1180
		if (!Filesystem::getView()) {
1181
			return $path;
1182
		}
1183
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1184
	}
1185
1186
	private function shouldEmitHooks($path = '') {
1187
		if ($path && Cache\Scanner::isPartialFile($path)) {
1188
			return false;
1189
		}
1190
		if (!Filesystem::$loaded) {
1191
			return false;
1192
		}
1193
		$defaultRoot = Filesystem::getRoot();
1194
		if ($defaultRoot === null) {
1195
			return false;
1196
		}
1197
		if ($this->fakeRoot === $defaultRoot) {
1198
			return true;
1199
		}
1200
		$fullPath = $this->getAbsolutePath($path);
1201
1202
		if ($fullPath === $defaultRoot) {
1203
			return true;
1204
		}
1205
1206
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1207
	}
1208
1209
	/**
1210
	 * @param string[] $hooks
1211
	 * @param string $path
1212
	 * @param bool $post
1213
	 * @return bool
1214
	 */
1215
	private function runHooks($hooks, $path, $post = false) {
1216
		$relativePath = $path;
1217
		$path = $this->getHookPath($path);
1218
		$prefix = ($post) ? 'post_' : '';
1219
		$run = true;
1220
		if ($this->shouldEmitHooks($relativePath)) {
1221
			foreach ($hooks as $hook) {
1222
				if ($hook != 'read') {
1223
					\OC_Hook::emit(
1224
						Filesystem::CLASSNAME,
1225
						$prefix . $hook,
1226
						[
1227
							Filesystem::signal_param_run => &$run,
1228
							Filesystem::signal_param_path => $path
1229
						]
1230
					);
1231
				} elseif (!$post) {
1232
					\OC_Hook::emit(
1233
						Filesystem::CLASSNAME,
1234
						$prefix . $hook,
1235
						[
1236
							Filesystem::signal_param_path => $path
1237
						]
1238
					);
1239
				}
1240
			}
1241
		}
1242
		return $run;
1243
	}
1244
1245
	/**
1246
	 * check if a file or folder has been updated since $time
1247
	 *
1248
	 * @param string $path
1249
	 * @param int $time
1250
	 * @return bool
1251
	 */
1252
	public function hasUpdated($path, $time) {
1253
		return $this->basicOperation('hasUpdated', $path, [], $time);
1254
	}
1255
1256
	/**
1257
	 * @param string $ownerId
1258
	 * @return IUser
1259
	 */
1260
	private function getUserObjectForOwner($ownerId) {
1261
		$owner = $this->userManager->get($ownerId);
1262
		if (!$owner instanceof IUser) {
1263
			return new RemoteUser($ownerId);
1264
		}
1265
1266
		return $owner;
1267
	}
1268
1269
	/**
1270
	 * Get file info from cache
1271
	 *
1272
	 * If the file is not in cached it will be scanned
1273
	 * If the file has changed on storage the cache will be updated
1274
	 *
1275
	 * @param \OC\Files\Storage\Storage $storage
1276
	 * @param string $internalPath
1277
	 * @param string $relativePath
1278
	 * @return array|bool
1279
	 */
1280
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1281
		$cache = $storage->getCache($internalPath);
1282
		$data = $cache->get($internalPath);
1283
		$watcher = $storage->getWatcher($internalPath);
1284
1285
		try {
1286
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1287
			if (!$data || $data['size'] === -1) {
1288
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1289
				if (!$storage->file_exists($internalPath)) {
1290
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1291
					return false;
1292
				}
1293
				$scanner = $storage->getScanner($internalPath);
1294
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1295
				$data = $cache->get($internalPath);
1296
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1297
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1298
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1299
				$watcher->update($internalPath, $data);
1300
				$storage->getPropagator()->propagateChange($internalPath, time());
1301
				$data = $cache->get($internalPath);
1302
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1303
			}
1304
		} catch (LockedException $e) {
1305
			// if the file is locked we just use the old cache info
1306
		}
1307
1308
		return $data;
1309
	}
1310
1311
	/**
1312
	 * get the filesystem info
1313
	 *
1314
	 * @param string $path
1315
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1316
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1317
	 * defaults to true
1318
	 * @return \OC\Files\FileInfo|false False if file does not exist
1319
	 */
1320
	public function getFileInfo($path, $includeMountPoints = true) {
1321
		$this->assertPathLength($path);
1322
		if (!Filesystem::isValidPath($path)) {
1323
			return false;
1324
		}
1325
		if (Cache\Scanner::isPartialFile($path)) {
1326
			return $this->getPartFileInfo($path);
1327
		}
1328
		$relativePath = $path;
1329
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1330
1331
		$mount = Filesystem::getMountManager()->find($path);
1332
		$storage = $mount->getStorage();
1333
		$internalPath = $mount->getInternalPath($path);
1334
		if ($storage) {
1335
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1336
1337
			if (!$data instanceof ICacheEntry) {
1338
				return false;
1339
			}
1340
1341
			if ($mount instanceof MoveableMount && $internalPath === '') {
1342
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1343
			}
1344
1345
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1346
			$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 1331 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...
1347
1348
			if ($data and isset($data['fileid'])) {
1349
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1350
					//add the sizes of other mount points to the folder
1351
					$extOnly = ($includeMountPoints === 'ext');
1352
					$mounts = Filesystem::getMountManager()->findIn($path);
1353
					foreach ($mounts as $mount) {
1354
						$subStorage = $mount->getStorage();
1355
						if ($subStorage) {
1356
							// exclude shared storage ?
1357
							if ($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage) {
1358
								continue;
1359
							}
1360
							$subCache = $subStorage->getCache('');
1361
							$rootEntry = $subCache->get('');
1362
							$info->addSubEntry($rootEntry, $mount->getMountPoint());
1363
						}
1364
					}
1365
				}
1366
			}
1367
1368
			return $info;
1369
		}
1370
1371
		return false;
1372
	}
1373
1374
	/**
1375
	 * get the content of a directory
1376
	 *
1377
	 * @param string $directory path under datadirectory
1378
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1379
	 * @return FileInfo[]
1380
	 */
1381
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1382
		$this->assertPathLength($directory);
1383
		if (!Filesystem::isValidPath($directory)) {
1384
			return [];
1385
		}
1386
		$path = $this->getAbsolutePath($directory);
1387
		$path = Filesystem::normalizePath($path);
1388
		$mount = $this->getMount($directory);
1389
		$storage = $mount->getStorage();
1390
		$internalPath = $mount->getInternalPath($path);
1391
		if ($storage) {
1392
			$cache = $storage->getCache($internalPath);
1393
			$user = \OC_User::getUser();
1394
1395
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1396
1397
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1398
				return [];
1399
			}
1400
1401
			$folderId = $data['fileid'];
1402
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1403
1404
			$sharingDisabled = Util::isSharingDisabledForUser();
1405
			/**
1406
			 * @var \OC\Files\FileInfo[] $files
1407
			 */
1408
			$files = array_filter($contents, function(ICacheEntry $content) {
1409
				return (!\OC\Files\Filesystem::isForbiddenFileOrDir($content['path']));
1410
			});
1411
			$files = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1412
				if ($sharingDisabled) {
1413
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1414
				}
1415
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1416
				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 1388 can be null; however, OC\Files\FileInfo::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1417
			}, $files);
1418
1419
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1420
			$mounts = Filesystem::getMountManager()->findIn($path);
1421
			$dirLength = strlen($path);
1422
			foreach ($mounts as $mount) {
1423
				$mountPoint = $mount->getMountPoint();
1424
				$subStorage = $mount->getStorage();
1425
				if ($subStorage) {
1426
					$subCache = $subStorage->getCache('');
1427
1428
					$rootEntry = $subCache->get('');
1429
					if (!$rootEntry) {
1430
						$subScanner = $subStorage->getScanner('');
1431
						try {
1432
							$subScanner->scanFile('');
1433
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1434
							continue;
1435
						} catch (\OCP\Files\StorageInvalidException $e) {
1436
							continue;
1437
						} catch (\Exception $e) {
1438
							// sometimes when the storage is not available it can be any exception
1439
							Util::writeLog(
1440
								'core',
1441
								'Exception while scanning storage "' . $subStorage->getId() . '": ' .
1442
								get_class($e) . ': ' . $e->getMessage(),
1443
								Util::ERROR
1444
							);
1445
							continue;
1446
						}
1447
						$rootEntry = $subCache->get('');
1448
					}
1449
1450
					if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1451
						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1452
						if ($pos = strpos($relativePath, '/')) {
1453
							//mountpoint inside subfolder add size to the correct folder
1454
							$entryName = substr($relativePath, 0, $pos);
1455
							foreach ($files as &$entry) {
1456
								if ($entry->getName() === $entryName) {
1457
									$entry->addSubEntry($rootEntry, $mountPoint);
1458
								}
1459
							}
1460
						} else { //mountpoint in this folder, add an entry for it
1461
							$rootEntry['name'] = $relativePath;
1462
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1463
							$permissions = $rootEntry['permissions'];
1464
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1465
							// for shared files/folders we use the permissions given by the owner
1466
							if ($mount instanceof MoveableMount) {
1467
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1468
							} else {
1469
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1470
							}
1471
1472
							//remove any existing entry with the same name
1473
							foreach ($files as $i => $file) {
1474
								if ($file['name'] === $rootEntry['name']) {
1475
									unset($files[$i]);
1476
									break;
1477
								}
1478
							}
1479
							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1480
1481
							// if sharing was disabled for the user we remove the share permissions
1482
							if (Util::isSharingDisabledForUser()) {
1483
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1484
							}
1485
1486
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1487
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1488
						}
1489
					}
1490
				}
1491
			}
1492
1493
			if ($mimetype_filter) {
1494
				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1495
					if (strpos($mimetype_filter, '/')) {
1496
						return $file->getMimetype() === $mimetype_filter;
1497
					} else {
1498
						return $file->getMimePart() === $mimetype_filter;
1499
					}
1500
				});
1501
			}
1502
1503
			return $files;
1504
		} else {
1505
			return [];
1506
		}
1507
	}
1508
1509
	/**
1510
	 * change file metadata
1511
	 *
1512
	 * @param string $path
1513
	 * @param array|\OCP\Files\FileInfo $data
1514
	 * @return int
1515
	 *
1516
	 * returns the fileid of the updated file
1517
	 */
1518
	public function putFileInfo($path, $data) {
1519
		$this->assertPathLength($path);
1520
		if ($data instanceof FileInfo) {
1521
			$data = $data->getData();
1522
		}
1523
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1524
		/**
1525
		 * @var \OC\Files\Storage\Storage $storage
1526
		 * @var string $internalPath
1527
		 */
1528
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1529
		if ($storage) {
1530
			$cache = $storage->getCache($path);
1531
1532
			if (!$cache->inCache($internalPath)) {
1533
				$scanner = $storage->getScanner($internalPath);
1534
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1535
			}
1536
1537
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1518 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...
1538
		} else {
1539
			return -1;
1540
		}
1541
	}
1542
1543
	/**
1544
	 * search for files with the name matching $query
1545
	 *
1546
	 * @param string $query
1547
	 * @return FileInfo[]
1548
	 */
1549
	public function search($query) {
1550
		return $this->searchCommon('search', ['%' . $query . '%']);
1551
	}
1552
1553
	/**
1554
	 * search for files with the name matching $query
1555
	 *
1556
	 * @param string $query
1557
	 * @return FileInfo[]
1558
	 */
1559
	public function searchRaw($query) {
1560
		return $this->searchCommon('search', [$query]);
1561
	}
1562
1563
	/**
1564
	 * search for files by mimetype
1565
	 *
1566
	 * @param string $mimetype
1567
	 * @return FileInfo[]
1568
	 */
1569
	public function searchByMime($mimetype) {
1570
		return $this->searchCommon('searchByMime', [$mimetype]);
1571
	}
1572
1573
	/**
1574
	 * search for files by tag
1575
	 *
1576
	 * @param string|int $tag name or tag id
1577
	 * @param string $userId owner of the tags
1578
	 * @return FileInfo[]
1579
	 */
1580
	public function searchByTag($tag, $userId) {
1581
		return $this->searchCommon('searchByTag', [$tag, $userId]);
1582
	}
1583
1584
	/**
1585
	 * @param string $method cache method
1586
	 * @param array $args
1587
	 * @return FileInfo[]
1588
	 */
1589
	private function searchCommon($method, $args) {
1590
		$files = [];
1591
		$rootLength = strlen($this->fakeRoot);
1592
1593
		$mount = $this->getMount('');
1594
		$mountPoint = $mount->getMountPoint();
1595
		$storage = $mount->getStorage();
1596
		if ($storage) {
1597
			$cache = $storage->getCache('');
1598
1599
			$results = call_user_func_array([$cache, $method], $args);
1600
			foreach ($results as $result) {
1601
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1602
					$internalPath = $result['path'];
1603
					$path = $mountPoint . $result['path'];
1604
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1605
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1606
					$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 1593 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...
1607
				}
1608
			}
1609
1610
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1611
			foreach ($mounts as $mount) {
1612
				$mountPoint = $mount->getMountPoint();
1613
				$storage = $mount->getStorage();
1614
				if ($storage) {
1615
					$cache = $storage->getCache('');
1616
1617
					$relativeMountPoint = substr($mountPoint, $rootLength);
1618
					$results = call_user_func_array([$cache, $method], $args);
1619
					if ($results) {
1620
						foreach ($results as $result) {
1621
							$internalPath = $result['path'];
1622
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1623
							$path = rtrim($mountPoint . $internalPath, '/');
1624
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1625
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1626
						}
1627
					}
1628
				}
1629
			}
1630
		}
1631
		return $files;
1632
	}
1633
1634
	/**
1635
	 * Get the owner for a file or folder
1636
	 *
1637
	 * @param string $path
1638
	 * @return string the user id of the owner
1639
	 * @throws NotFoundException
1640
	 */
1641
	public function getOwner($path) {
1642
		$info = $this->getFileInfo($path);
1643
		if (!$info) {
1644
			throw new NotFoundException($path . ' not found while trying to get owner');
1645
		}
1646
		return $info->getOwner()->getUID();
1647
	}
1648
1649
	/**
1650
	 * get the ETag for a file or folder
1651
	 *
1652
	 * @param string $path
1653
	 * @return string
1654
	 */
1655
	public function getETag($path) {
1656
		/**
1657
		 * @var Storage\Storage $storage
1658
		 * @var string $internalPath
1659
		 */
1660
		list($storage, $internalPath) = $this->resolvePath($path);
1661
		if ($storage) {
1662
			return $storage->getETag($internalPath);
1663
		} else {
1664
			return null;
1665
		}
1666
	}
1667
1668
	/**
1669
	 * Get the path of a file by id, relative to the view
1670
	 *
1671
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1672
	 *
1673
	 * @param int $id
1674
	 * @param bool $includeShares whether to recurse into shared mounts
1675
	 * @throws NotFoundException
1676
	 * @return string
1677
	 */
1678
	public function getPath($id, $includeShares = true) {
1679
		$id = (int)$id;
1680
		$manager = Filesystem::getMountManager();
1681
		$mounts = $manager->findIn($this->fakeRoot);
1682
		$mounts[] = $manager->find($this->fakeRoot);
1683
		// reverse the array so we start with the storage this view is in
1684
		// which is the most likely to contain the file we're looking for
1685
		$mounts = array_reverse($mounts);
1686
		foreach ($mounts as $mount) {
1687
			/**
1688
			 * @var \OC\Files\Mount\MountPoint $mount
1689
			 */
1690
			if (!$includeShares && $mount instanceof SharedMount) {
1691
				// prevent potential infinite loop when instantiating shared storages
1692
				// recursively
1693
				continue;
1694
			}
1695
			if ($mount->getStorage()) {
1696
				$cache = $mount->getStorage()->getCache();
1697
				$internalPath = $cache->getPathById($id);
1698
				if (is_string($internalPath)) {
1699
					$fullPath = $mount->getMountPoint() . $internalPath;
1700
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1701
						return $path;
1702
					}
1703
				}
1704
			}
1705
		}
1706
		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1707
	}
1708
1709
	/**
1710
	 * @param string $path
1711
	 * @throws InvalidPathException
1712
	 */
1713
	private function assertPathLength($path) {
1714
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1715
		// Check for the string length - performed using isset() instead of strlen()
1716
		// because isset() is about 5x-40x faster.
1717
		if (isset($path[$maxLen])) {
1718
			$pathLen = strlen($path);
1719
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1720
		}
1721
	}
1722
1723
	/**
1724
	 * check if it is allowed to move a mount point to a given target.
1725
	 * It is not allowed to move a mount point into a different mount point or
1726
	 * into an already shared folder
1727
	 *
1728
	 * @param MoveableMount $mount1 moveable mount
1729
	 * @param string $target absolute target path
1730
	 * @return boolean
1731
	 */
1732
	private function canMove(MoveableMount $mount1, $target) {
1733
1734
		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...
1735
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1736
			Util::writeLog('files',
1737
				'It is not allowed to move one mount point into another one',
1738
				Util::DEBUG);
1739
			return false;
1740
		}
1741
1742
		return $mount1->isTargetAllowed($target);
1743
	}
1744
1745
	/**
1746
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1747
	 *
1748
	 * @param string $path
1749
	 * @return \OCP\Files\FileInfo
1750
	 */
1751
	private function getPartFileInfo($path) {
1752
		$mount = $this->getMount($path);
1753
		$storage = $mount->getStorage();
1754
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1755
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1756
		return new FileInfo(
1757
			$this->getAbsolutePath($path),
1758
			$storage,
1759
			$internalPath,
1760
			[
1761
				'fileid' => null,
1762
				'mimetype' => $storage->getMimeType($internalPath),
1763
				'name' => basename($path),
1764
				'etag' => null,
1765
				'size' => $storage->filesize($internalPath),
1766
				'mtime' => $storage->filemtime($internalPath),
1767
				'encrypted' => false,
1768
				'permissions' => \OCP\Constants::PERMISSION_ALL
1769
			],
1770
			$mount,
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1752 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...
1771
			$owner
1772
		);
1773
	}
1774
1775
	/**
1776
	 * @param string $path
1777
	 * @param string $fileName
1778
	 * @throws InvalidPathException
1779
	 */
1780
	public function verifyPath($path, $fileName) {
1781
1782
		$l10n = \OC::$server->getL10N('lib');
1783
1784
		// verify empty and dot files
1785
		$trimmed = trim($fileName);
1786
		if ($trimmed === '') {
1787
			throw new InvalidPathException($l10n->t('Empty filename is not allowed'));
1788
		}
1789
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1790
			throw new InvalidPathException($l10n->t('Dot files are not allowed'));
1791
		}
1792
1793
		$matches = [];
1794
1795
		if (preg_match('/' . FileInfo::BLACKLIST_FILES_REGEX . '/', $fileName) !== 0) {
1796
			throw new InvalidPathException(
1797
				"Can`t upload files with extension {$matches[0]} because these extensions are reserved for internal use."
1798
			);
1799
		}
1800
1801
		if (!\OC::$server->getDatabaseConnection()->allows4ByteCharacters()) {
1802
			// verify database - e.g. mysql only 3-byte chars
1803
			if (preg_match('%(?:
1804
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1805
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1806
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1807
)%xs', $fileName)) {
1808
				throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names'));
1809
			}
1810
		}
1811
1812
		try {
1813
			/** @type \OCP\Files\Storage $storage */
1814
			list($storage, $internalPath) = $this->resolvePath($path);
1815
			$storage->verifyPath($internalPath, $fileName);
1816
		} catch (ReservedWordException $ex) {
1817
			throw new InvalidPathException($l10n->t('File name is a reserved word'));
1818
		} catch (InvalidCharacterInPathException $ex) {
1819
			throw new InvalidPathException($l10n->t('File name contains at least one invalid character'));
1820
		} catch (FileNameTooLongException $ex) {
1821
			throw new InvalidPathException($l10n->t('File name is too long'));
1822
		}
1823
	}
1824
1825
	/**
1826
	 * get all parent folders of $path
1827
	 *
1828
	 * @param string $path
1829
	 * @return string[]
1830
	 */
1831
	private function getParents($path) {
1832
		$path = trim($path, '/');
1833
		if (!$path) {
1834
			return [];
1835
		}
1836
1837
		$parts = explode('/', $path);
1838
1839
		// remove the single file
1840
		array_pop($parts);
1841
		$result = ['/'];
1842
		$resultPath = '';
1843
		foreach ($parts as $part) {
1844
			if ($part) {
1845
				$resultPath .= '/' . $part;
1846
				$result[] = $resultPath;
1847
			}
1848
		}
1849
		return $result;
1850
	}
1851
1852
	/**
1853
	 * Returns the mount point for which to lock
1854
	 *
1855
	 * @param string $absolutePath absolute path
1856
	 * @param bool $useParentMount true to return parent mount instead of whatever
1857
	 * is mounted directly on the given path, false otherwise
1858
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1859
	 */
1860
	private function getMountForLock($absolutePath, $useParentMount = false) {
1861
		$results = [];
1862
		$mount = Filesystem::getMountManager()->find($absolutePath);
1863
		if (!$mount) {
1864
			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...
1865
		}
1866
1867
		if ($useParentMount) {
1868
			// find out if something is mounted directly on the path
1869
			$internalPath = $mount->getInternalPath($absolutePath);
1870
			if ($internalPath === '') {
1871
				// resolve the parent mount instead
1872
				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1873
			}
1874
		}
1875
1876
		return $mount;
1877
	}
1878
1879
	/**
1880
	 * Lock the given path
1881
	 *
1882
	 * @param string $path the path of the file to lock, relative to the view
1883
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1884
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1885
	 *
1886
	 * @return bool False if the path is excluded from locking, true otherwise
1887
	 * @throws \OCP\Lock\LockedException if the path is already locked
1888
	 */
1889 View Code Duplication
	private function lockPath($path, $type, $lockMountPoint = false) {
1890
		$absolutePath = $this->getAbsolutePath($path);
1891
		$absolutePath = Filesystem::normalizePath($absolutePath);
1892
		if (!$this->shouldLockFile($absolutePath)) {
1893
			return false;
1894
		}
1895
1896
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1897
		if ($mount) {
1898
			try {
1899
				$storage = $mount->getStorage();
1900
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1901
					$storage->acquireLock(
1902
						$mount->getInternalPath($absolutePath),
1903
						$type,
1904
						$this->lockingProvider
1905
					);
1906
				}
1907
			} catch (\OCP\Lock\LockedException $e) {
1908
				// rethrow with the a human-readable path
1909
				throw new \OCP\Lock\LockedException(
1910
					$this->getPathRelativeToFiles($absolutePath),
1911
					$e
1912
				);
1913
			}
1914
		}
1915
1916
		return true;
1917
	}
1918
1919
	/**
1920
	 * Change the lock type
1921
	 *
1922
	 * @param string $path the path of the file to lock, relative to the view
1923
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1924
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1925
	 *
1926
	 * @return bool False if the path is excluded from locking, true otherwise
1927
	 * @throws \OCP\Lock\LockedException if the path is already locked
1928
	 */
1929 View Code Duplication
	public function changeLock($path, $type, $lockMountPoint = false) {
1930
		$path = Filesystem::normalizePath($path);
1931
		$absolutePath = $this->getAbsolutePath($path);
1932
		$absolutePath = Filesystem::normalizePath($absolutePath);
1933
		if (!$this->shouldLockFile($absolutePath)) {
1934
			return false;
1935
		}
1936
1937
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1938
		if ($mount) {
1939
			try {
1940
				$storage = $mount->getStorage();
1941
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1942
					$storage->changeLock(
1943
						$mount->getInternalPath($absolutePath),
1944
						$type,
1945
						$this->lockingProvider
1946
					);
1947
				}
1948
			} catch (LockedException $e) {
1949
				// rethrow with the a human-readable path
1950
				throw new LockedException(
1951
					$this->getPathRelativeToFiles($absolutePath),
1952
					$e
1953
				);
1954
			}
1955
		}
1956
1957
		return true;
1958
	}
1959
1960
	/**
1961
	 * Unlock the given path
1962
	 *
1963
	 * @param string $path the path of the file to unlock, relative to the view
1964
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1965
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1966
	 *
1967
	 * @return bool False if the path is excluded from locking, true otherwise
1968
	 */
1969
	private function unlockPath($path, $type, $lockMountPoint = false) {
1970
		$absolutePath = $this->getAbsolutePath($path);
1971
		$absolutePath = Filesystem::normalizePath($absolutePath);
1972
		if (!$this->shouldLockFile($absolutePath)) {
1973
			return false;
1974
		}
1975
1976
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1977
		if ($mount) {
1978
			$storage = $mount->getStorage();
1979
			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1980
				$storage->releaseLock(
1981
					$mount->getInternalPath($absolutePath),
1982
					$type,
1983
					$this->lockingProvider
1984
				);
1985
			}
1986
		}
1987
1988
		return true;
1989
	}
1990
1991
	/**
1992
	 * Lock a path and all its parents up to the root of the view
1993
	 *
1994
	 * @param string $path the path of the file to lock relative to the view
1995
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1996
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1997
	 *
1998
	 * @return bool False if the path is excluded from locking, true otherwise
1999
	 */
2000 View Code Duplication
	public function lockFile($path, $type, $lockMountPoint = false) {
2001
		$absolutePath = $this->getAbsolutePath($path);
2002
		$absolutePath = Filesystem::normalizePath($absolutePath);
2003
		if (!$this->shouldLockFile($absolutePath)) {
2004
			return false;
2005
		}
2006
2007
		$this->lockPath($path, $type, $lockMountPoint);
2008
2009
		$parents = $this->getParents($path);
2010
		foreach ($parents as $parent) {
2011
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2012
		}
2013
2014
		return true;
2015
	}
2016
2017
	/**
2018
	 * Unlock a path and all its parents up to the root of the view
2019
	 *
2020
	 * @param string $path the path of the file to lock relative to the view
2021
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2022
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2023
	 *
2024
	 * @return bool False if the path is excluded from locking, true otherwise
2025
	 */
2026 View Code Duplication
	public function unlockFile($path, $type, $lockMountPoint = false) {
2027
		$absolutePath = $this->getAbsolutePath($path);
2028
		$absolutePath = Filesystem::normalizePath($absolutePath);
2029
		if (!$this->shouldLockFile($absolutePath)) {
2030
			return false;
2031
		}
2032
2033
		$this->unlockPath($path, $type, $lockMountPoint);
2034
2035
		$parents = $this->getParents($path);
2036
		foreach ($parents as $parent) {
2037
			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2038
		}
2039
2040
		return true;
2041
	}
2042
2043
	/**
2044
	 * Only lock files in data/user/files/
2045
	 *
2046
	 * @param string $path Absolute path to the file/folder we try to (un)lock
2047
	 * @return bool
2048
	 */
2049
	protected function shouldLockFile($path) {
2050
		$path = Filesystem::normalizePath($path);
2051
2052
		$pathSegments = explode('/', $path);
2053
		if (isset($pathSegments[2])) {
2054
			// E.g.: /username/files/path-to-file
2055
			return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
2056
		}
2057
2058
		return true;
2059
	}
2060
2061
	/**
2062
	 * Shortens the given absolute path to be relative to
2063
	 * "$user/files".
2064
	 *
2065
	 * @param string $absolutePath absolute path which is under "files"
2066
	 *
2067
	 * @return string path relative to "files" with trimmed slashes or null
2068
	 * if the path was NOT relative to files
2069
	 *
2070
	 * @throws \InvalidArgumentException if the given path was not under "files"
2071
	 * @since 8.1.0
2072
	 */
2073
	public function getPathRelativeToFiles($absolutePath) {
2074
		$path = Filesystem::normalizePath($absolutePath);
2075
		$parts = explode('/', trim($path, '/'), 3);
2076
		// "$user", "files", "path/to/dir"
2077
		if (!isset($parts[1]) || $parts[1] !== 'files') {
2078
			throw new \InvalidArgumentException('"' . $absolutePath . '" must be relative to "files"');
2079
		}
2080
		if (isset($parts[2])) {
2081
			return $parts[2];
2082
		}
2083
		return '';
2084
	}
2085
2086
	/**
2087
	 * @param string $filename
2088
	 * @return array
2089
	 * @throws \OC\User\NoUserException
2090
	 * @throws NotFoundException
2091
	 */
2092
	public function getUidAndFilename($filename) {
2093
		$info = $this->getFileInfo($filename);
2094
		if (!$info instanceof \OCP\Files\FileInfo) {
2095
			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2096
		}
2097
		$uid = $info->getOwner()->getUID();
2098
		if ($uid != \OCP\User::getUser()) {
2099
			Filesystem::initMountPoints($uid);
2100
			$ownerView = new View('/' . $uid . '/files');
2101
			try {
2102
				$filename = $ownerView->getPath($info['fileid']);
2103
			} catch (NotFoundException $e) {
2104
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2105
			}
2106
		}
2107
		return [$uid, $filename];
2108
	}
2109
2110
	/**
2111
	 * Creates parent non-existing folders
2112
	 *
2113
	 * @param string $filePath
2114
	 * @return bool
2115
	 */
2116
	private function createParentDirectories($filePath) {
2117
		$parentDirectory = dirname($filePath);
2118
		while(!$this->file_exists($parentDirectory)) {
2119
			$result = $this->createParentDirectories($parentDirectory);
2120
			if($result === false) {
2121
				return false;
2122
			}
2123
		}
2124
		$this->mkdir($filePath);
2125
		return true;
2126
	}
2127
}
2128