Completed
Push — master ( 45d9a3...41ef8e )
by Thomas
12:20
created

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