Completed
Pull Request — master (#32)
by Blizzz
09:50
created

View::rmdir()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 9
c 2
b 0
f 0
nc 3
nop 1
dl 0
loc 12
rs 9.4285
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 Klaas Freitag <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Luke Policinski <[email protected]>
14
 * @author Martin Mattel <[email protected]>
15
 * @author Michael Gapczynski <[email protected]>
16
 * @author Morris Jobke <[email protected]>
17
 * @author Petr Svoboda <[email protected]>
18
 * @author Piotr Filiciak <[email protected]>
19
 * @author Robin Appelman <[email protected]>
20
 * @author Robin McCorkell <[email protected]>
21
 * @author Roeland Jago Douma <[email protected]>
22
 * @author Sam Tuke <[email protected]>
23
 * @author Stefan Weil <[email protected]>
24
 * @author Thomas Müller <[email protected]>
25
 * @author Thomas Tanghus <[email protected]>
26
 * @author Vincent Petry <[email protected]>
27
 *
28
 * @copyright Copyright (c) 2016, ownCloud, Inc.
29
 * @license AGPL-3.0
30
 *
31
 * This code is free software: you can redistribute it and/or modify
32
 * it under the terms of the GNU Affero General Public License, version 3,
33
 * as published by the Free Software Foundation.
34
 *
35
 * This program is distributed in the hope that it will be useful,
36
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38
 * GNU Affero General Public License for more details.
39
 *
40
 * You should have received a copy of the GNU Affero General Public License, version 3,
41
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
42
 *
43
 */
44
45
46
namespace OC\Files;
47
48
use Icewind\Streams\CallbackWrapper;
49
use OC\Files\Mount\MoveableMount;
50
use OC\Files\Storage\Storage;
51
use OC\User\User;
52
use OCP\Constants;
53
use OCP\Files\Cache\ICacheEntry;
54
use OCP\Files\FileNameTooLongException;
55
use OCP\Files\InvalidCharacterInPathException;
56
use OCP\Files\InvalidPathException;
57
use OCP\Files\NotFoundException;
58
use OCP\Files\ReservedWordException;
59
use OCP\Files\UnseekableException;
60
use OCP\Files\Storage\ILockingStorage;
61
use OCP\IUser;
62
use OCP\Lock\ILockingProvider;
63
use OCP\Lock\LockedException;
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) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
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, array('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
				array(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
					array(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
			return $this->basicOperation('rmdir', $path, array('delete'));
341
		} else {
342
			return false;
343
		}
344
	}
345
346
	/**
347
	 * @param string $path
348
	 * @return resource
349
	 */
350
	public function opendir($path) {
351
		return $this->basicOperation('opendir', $path, array('read'));
352
	}
353
354
	/**
355
	 * @param $handle
356
	 * @return mixed
357
	 */
358
	public function readdir($handle) {
359
		$fsLocal = new Storage\Local(array('datadir' => '/'));
360
		return $fsLocal->readdir($handle);
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 = array('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, array('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, array('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, array(
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, array(
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, array(
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, array(
597
				Filesystem::signal_param_path => $this->getHookPath($path),
598
			));
599
		} else {
600
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
601
				Filesystem::signal_param_path => $this->getHookPath($path),
602
			));
603
		}
604
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
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::isFileBlacklisted($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)) ? array('update', 'write') : array('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
		return $this->basicOperation('unlink', $path, array('delete'));
683
	}
684
685
	/**
686
	 * @param string $directory
687
	 * @return bool|mixed
688
	 */
689
	public function deleteAll($directory) {
690
		return $this->rmdir($directory);
691
	}
692
693
	/**
694
	 * Rename/move a file or folder from the source path to target path.
695
	 *
696
	 * @param string $path1 source path
697
	 * @param string $path2 target path
698
	 *
699
	 * @return bool|mixed
700
	 */
701
	public function rename($path1, $path2) {
702
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
703
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
704
		$result = false;
705
		if (
706
			Filesystem::isValidPath($path2)
707
			and Filesystem::isValidPath($path1)
708
			and !Filesystem::isFileBlacklisted($path2)
709
		) {
710
			$path1 = $this->getRelativePath($absolutePath1);
711
			$path2 = $this->getRelativePath($absolutePath2);
712
			$exists = $this->file_exists($path2);
713
714
			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...
715
				return false;
716
			}
717
718
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
719
			try {
720
				$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
721
			} catch (LockedException $e) {
722
				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
723
				throw $e;
724
			}
725
726
			$run = true;
727
			if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
728
				// if it was a rename from a part file to a regular file it was a write and not a rename operation
729
				$this->emit_file_hooks_pre($exists, $path2, $run);
730
			} elseif ($this->shouldEmitHooks($path1)) {
731
				\OC_Hook::emit(
732
					Filesystem::CLASSNAME, Filesystem::signal_rename,
733
					array(
734
						Filesystem::signal_param_oldpath => $this->getHookPath($path1),
735
						Filesystem::signal_param_newpath => $this->getHookPath($path2),
736
						Filesystem::signal_param_run => &$run
737
					)
738
				);
739
			}
740
			if ($run) {
741
				$this->verifyPath(dirname($path2), basename($path2));
742
743
				$manager = Filesystem::getMountManager();
744
				$mount1 = $this->getMount($path1);
745
				$mount2 = $this->getMount($path2);
746
				$storage1 = $mount1->getStorage();
747
				$storage2 = $mount2->getStorage();
748
				$internalPath1 = $mount1->getInternalPath($absolutePath1);
749
				$internalPath2 = $mount2->getInternalPath($absolutePath2);
750
751
				$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
752
				$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
753
754
				if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
755
					if ($this->isTargetAllowed($absolutePath2)) {
756
						/**
757
						 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
758
						 */
759
						$sourceMountPoint = $mount1->getMountPoint();
760
						$result = $mount1->moveMount($absolutePath2);
761
						$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
762
					} else {
763
						$result = false;
764
					}
765
					// moving a file/folder within the same mount point
766
				} elseif ($storage1 === $storage2) {
767
					if ($storage1) {
768
						$result = $storage1->rename($internalPath1, $internalPath2);
769
					} else {
770
						$result = false;
771
					}
772
					// moving a file/folder between storages (from $storage1 to $storage2)
773
				} else {
774
					$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
775
				}
776
777
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
778
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
779
780
					$this->writeUpdate($storage2, $internalPath2);
781
				} else if ($result) {
782
					if ($internalPath1 !== '') { // don't do a cache update for moved mounts
783
						$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
784
					}
785
				}
786
787
				$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
788
				$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
789
790
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
791
					if ($this->shouldEmitHooks()) {
792
						$this->emit_file_hooks_post($exists, $path2);
793
					}
794
				} elseif ($result) {
795
					if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
796
						\OC_Hook::emit(
797
							Filesystem::CLASSNAME,
798
							Filesystem::signal_post_rename,
799
							array(
800
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
801
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
802
							)
803
						);
804
					}
805
				}
806
			}
807
			$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
808
			$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
809
		}
810
		return $result;
811
	}
812
813
	/**
814
	 * Copy a file/folder from the source path to target path
815
	 *
816
	 * @param string $path1 source path
817
	 * @param string $path2 target path
818
	 * @param bool $preserveMtime whether to preserve mtime on the copy
819
	 *
820
	 * @return bool|mixed
821
	 */
822
	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...
823
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
824
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
825
		$result = false;
826
		if (
827
			Filesystem::isValidPath($path2)
828
			and Filesystem::isValidPath($path1)
829
			and !Filesystem::isFileBlacklisted($path2)
830
		) {
831
			$path1 = $this->getRelativePath($absolutePath1);
832
			$path2 = $this->getRelativePath($absolutePath2);
833
834
			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...
835
				return false;
836
			}
837
			$run = true;
838
839
			$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
840
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
841
			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
842
			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
843
844
			try {
845
846
				$exists = $this->file_exists($path2);
847 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...
848
					\OC_Hook::emit(
849
						Filesystem::CLASSNAME,
850
						Filesystem::signal_copy,
851
						array(
852
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
853
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
854
							Filesystem::signal_param_run => &$run
855
						)
856
					);
857
					$this->emit_file_hooks_pre($exists, $path2, $run);
858
				}
859
				if ($run) {
860
					$mount1 = $this->getMount($path1);
861
					$mount2 = $this->getMount($path2);
862
					$storage1 = $mount1->getStorage();
863
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
864
					$storage2 = $mount2->getStorage();
865
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
866
867
					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
868
					$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
869
870
					if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
871
						if ($storage1) {
872
							$result = $storage1->copy($internalPath1, $internalPath2);
873
						} else {
874
							$result = false;
875
						}
876
					} else {
877
						$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
878
					}
879
880
					$this->writeUpdate($storage2, $internalPath2);
881
882
					$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
883
					$lockTypePath2 = ILockingProvider::LOCK_SHARED;
884
885 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...
886
						\OC_Hook::emit(
887
							Filesystem::CLASSNAME,
888
							Filesystem::signal_post_copy,
889
							array(
890
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
891
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
892
							)
893
						);
894
						$this->emit_file_hooks_post($exists, $path2);
895
					}
896
897
				}
898
			} catch (\Exception $e) {
899
				$this->unlockFile($path2, $lockTypePath2);
900
				$this->unlockFile($path1, $lockTypePath1);
901
				throw $e;
902
			}
903
904
			$this->unlockFile($path2, $lockTypePath2);
905
			$this->unlockFile($path1, $lockTypePath1);
906
907
		}
908
		return $result;
909
	}
910
911
	/**
912
	 * @param string $path
913
	 * @param string $mode
914
	 * @return resource
915
	 */
916
	public function fopen($path, $mode) {
917
		$hooks = array();
918
		switch ($mode) {
919
			case 'r':
920
			case 'rb':
921
				$hooks[] = 'read';
922
				break;
923
			case 'r+':
924
			case 'rb+':
925
			case 'w+':
926
			case 'wb+':
927
			case 'x+':
928
			case 'xb+':
929
			case 'a+':
930
			case 'ab+':
931
				$hooks[] = 'read';
932
				$hooks[] = 'write';
933
				break;
934
			case 'w':
935
			case 'wb':
936
			case 'x':
937
			case 'xb':
938
			case 'a':
939
			case 'ab':
940
				$hooks[] = 'write';
941
				break;
942
			default:
943
				\OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, \OCP\Util::ERROR);
944
		}
945
946
		return $this->basicOperation('fopen', $path, $hooks, $mode);
947
	}
948
949
	/**
950
	 * @param string $path
951
	 * @return bool|string
952
	 * @throws \OCP\Files\InvalidPathException
953
	 */
954
	public function toTmpFile($path) {
955
		$this->assertPathLength($path);
956
		if (Filesystem::isValidPath($path)) {
957
			$source = $this->fopen($path, 'r');
958
			if ($source) {
959
				$extension = pathinfo($path, PATHINFO_EXTENSION);
960
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
961
				file_put_contents($tmpFile, $source);
962
				return $tmpFile;
963
			} else {
964
				return false;
965
			}
966
		} else {
967
			return false;
968
		}
969
	}
970
971
	/**
972
	 * @param string $tmpFile
973
	 * @param string $path
974
	 * @return bool|mixed
975
	 * @throws \OCP\Files\InvalidPathException
976
	 */
977
	public function fromTmpFile($tmpFile, $path) {
978
		$this->assertPathLength($path);
979
		if (Filesystem::isValidPath($path)) {
980
981
			// Get directory that the file is going into
982
			$filePath = dirname($path);
983
984
			// Create the directories if any
985
			if (!$this->file_exists($filePath)) {
986
				$this->mkdir($filePath);
987
			}
988
989
			$source = fopen($tmpFile, 'r');
990
			if ($source) {
991
				$result = $this->file_put_contents($path, $source);
992
				// $this->file_put_contents() might have already closed
993
				// the resource, so we check it, before trying to close it
994
				// to avoid messages in the error log.
995
				if (is_resource($source)) {
996
					fclose($source);
997
				}
998
				unlink($tmpFile);
999
				return $result;
1000
			} else {
1001
				return false;
1002
			}
1003
		} else {
1004
			return false;
1005
		}
1006
	}
1007
1008
1009
	/**
1010
	 * @param string $path
1011
	 * @return mixed
1012
	 * @throws \OCP\Files\InvalidPathException
1013
	 */
1014
	public function getMimeType($path) {
1015
		$this->assertPathLength($path);
1016
		return $this->basicOperation('getMimeType', $path);
1017
	}
1018
1019
	/**
1020
	 * @param string $type
1021
	 * @param string $path
1022
	 * @param bool $raw
1023
	 * @return bool|null|string
1024
	 */
1025
	public function hash($type, $path, $raw = false) {
1026
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1027
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1028
		if (Filesystem::isValidPath($path)) {
1029
			$path = $this->getRelativePath($absolutePath);
1030
			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...
1031
				return false;
1032
			}
1033
			if ($this->shouldEmitHooks($path)) {
1034
				\OC_Hook::emit(
1035
					Filesystem::CLASSNAME,
1036
					Filesystem::signal_read,
1037
					array(Filesystem::signal_param_path => $this->getHookPath($path))
1038
				);
1039
			}
1040
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1041
			if ($storage) {
1042
				$result = $storage->hash($type, $internalPath, $raw);
1043
				return $result;
1044
			}
1045
		}
1046
		return null;
1047
	}
1048
1049
	/**
1050
	 * @param string $path
1051
	 * @return mixed
1052
	 * @throws \OCP\Files\InvalidPathException
1053
	 */
1054
	public function free_space($path = '/') {
1055
		$this->assertPathLength($path);
1056
		return $this->basicOperation('free_space', $path);
1057
	}
1058
1059
	/**
1060
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1061
	 *
1062
	 * @param string $operation
1063
	 * @param string $path
1064
	 * @param array $hooks (optional)
1065
	 * @param mixed $extraParam (optional)
1066
	 * @return mixed
1067
	 * @throws \Exception
1068
	 *
1069
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1070
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1071
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1072
	 */
1073
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1074
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1075
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1076
		if (Filesystem::isValidPath($path)
1077
			and !Filesystem::isFileBlacklisted($path)
1078
		) {
1079
			$path = $this->getRelativePath($absolutePath);
1080
			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...
1081
				return false;
1082
			}
1083
1084
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1085
				// always a shared lock during pre-hooks so the hook can read the file
1086
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1087
			}
1088
1089
			$run = $this->runHooks($hooks, $path);
1090
			/** @var \OC\Files\Storage\Storage $storage */
1091
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1092
			if ($run and $storage) {
1093
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1094
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1095
				}
1096
				try {
1097
					if (!is_null($extraParam)) {
1098
						$result = $storage->$operation($internalPath, $extraParam);
1099
					} else {
1100
						$result = $storage->$operation($internalPath);
1101
					}
1102
				} catch (\Exception $e) {
1103 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...
1104
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1105
					} else if (in_array('read', $hooks)) {
1106
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1107
					}
1108
					throw $e;
1109
				}
1110
1111
				if (in_array('delete', $hooks) and $result) {
1112
					$this->removeUpdate($storage, $internalPath);
1113
				}
1114
				if (in_array('write', $hooks) and $operation !== 'fopen') {
1115
					$this->writeUpdate($storage, $internalPath);
1116
				}
1117
				if (in_array('touch', $hooks)) {
1118
					$this->writeUpdate($storage, $internalPath, $extraParam);
1119
				}
1120
1121
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1122
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1123
				}
1124
1125
				$unlockLater = false;
1126
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1127
					$unlockLater = true;
1128
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1129 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...
1130
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1131
						} else if (in_array('read', $hooks)) {
1132
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1133
						}
1134
					});
1135
				}
1136
1137
				if ($this->shouldEmitHooks($path) && $result !== false) {
1138
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1139
						$this->runHooks($hooks, $path, true);
1140
					}
1141
				}
1142
1143 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...
1144
					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1145
				) {
1146
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1147
				}
1148
				return $result;
1149
			} else {
1150
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1151
			}
1152
		}
1153
		return null;
1154
	}
1155
1156
	/**
1157
	 * get the path relative to the default root for hook usage
1158
	 *
1159
	 * @param string $path
1160
	 * @return string
1161
	 */
1162
	private function getHookPath($path) {
1163
		if (!Filesystem::getView()) {
1164
			return $path;
1165
		}
1166
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1167
	}
1168
1169
	private function shouldEmitHooks($path = '') {
1170
		if ($path && Cache\Scanner::isPartialFile($path)) {
1171
			return false;
1172
		}
1173
		if (!Filesystem::$loaded) {
1174
			return false;
1175
		}
1176
		$defaultRoot = Filesystem::getRoot();
1177
		if ($defaultRoot === null) {
1178
			return false;
1179
		}
1180
		if ($this->fakeRoot === $defaultRoot) {
1181
			return true;
1182
		}
1183
		$fullPath = $this->getAbsolutePath($path);
1184
1185
		if ($fullPath === $defaultRoot) {
1186
			return true;
1187
		}
1188
1189
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1190
	}
1191
1192
	/**
1193
	 * @param string[] $hooks
1194
	 * @param string $path
1195
	 * @param bool $post
1196
	 * @return bool
1197
	 */
1198
	private function runHooks($hooks, $path, $post = false) {
1199
		$relativePath = $path;
1200
		$path = $this->getHookPath($path);
1201
		$prefix = ($post) ? 'post_' : '';
1202
		$run = true;
1203
		if ($this->shouldEmitHooks($relativePath)) {
1204
			foreach ($hooks as $hook) {
1205
				if ($hook != 'read') {
1206
					\OC_Hook::emit(
1207
						Filesystem::CLASSNAME,
1208
						$prefix . $hook,
1209
						array(
1210
							Filesystem::signal_param_run => &$run,
1211
							Filesystem::signal_param_path => $path
1212
						)
1213
					);
1214
				} elseif (!$post) {
1215
					\OC_Hook::emit(
1216
						Filesystem::CLASSNAME,
1217
						$prefix . $hook,
1218
						array(
1219
							Filesystem::signal_param_path => $path
1220
						)
1221
					);
1222
				}
1223
			}
1224
		}
1225
		return $run;
1226
	}
1227
1228
	/**
1229
	 * check if a file or folder has been updated since $time
1230
	 *
1231
	 * @param string $path
1232
	 * @param int $time
1233
	 * @return bool
1234
	 */
1235
	public function hasUpdated($path, $time) {
1236
		return $this->basicOperation('hasUpdated', $path, array(), $time);
1237
	}
1238
1239
	/**
1240
	 * @param string $ownerId
1241
	 * @return \OC\User\User
1242
	 */
1243
	private function getUserObjectForOwner($ownerId) {
1244
		$owner = $this->userManager->get($ownerId);
1245
		if ($owner instanceof IUser) {
1246
			return $owner;
1247
		} else {
1248
			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...
1249
		}
1250
	}
1251
1252
	/**
1253
	 * Get file info from cache
1254
	 *
1255
	 * If the file is not in cached it will be scanned
1256
	 * If the file has changed on storage the cache will be updated
1257
	 *
1258
	 * @param \OC\Files\Storage\Storage $storage
1259
	 * @param string $internalPath
1260
	 * @param string $relativePath
1261
	 * @return array|bool
1262
	 */
1263
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1264
		$cache = $storage->getCache($internalPath);
1265
		$data = $cache->get($internalPath);
1266
		$watcher = $storage->getWatcher($internalPath);
1267
1268
		try {
1269
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1270
			if (!$data || $data['size'] === -1) {
1271
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1272
				if (!$storage->file_exists($internalPath)) {
1273
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1274
					return false;
1275
				}
1276
				$scanner = $storage->getScanner($internalPath);
1277
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1278
				$data = $cache->get($internalPath);
1279
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1280
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1281
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1282
				$watcher->update($internalPath, $data);
1283
				$storage->getPropagator()->propagateChange($internalPath, time());
1284
				$data = $cache->get($internalPath);
1285
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1286
			}
1287
		} catch (LockedException $e) {
1288
			// if the file is locked we just use the old cache info
1289
		}
1290
1291
		return $data;
1292
	}
1293
1294
	/**
1295
	 * get the filesystem info
1296
	 *
1297
	 * @param string $path
1298
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1299
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1300
	 * defaults to true
1301
	 * @return \OC\Files\FileInfo|false False if file does not exist
1302
	 */
1303
	public function getFileInfo($path, $includeMountPoints = true) {
1304
		$this->assertPathLength($path);
1305
		if (!Filesystem::isValidPath($path)) {
1306
			return false;
1307
		}
1308
		if (Cache\Scanner::isPartialFile($path)) {
1309
			return $this->getPartFileInfo($path);
1310
		}
1311
		$relativePath = $path;
1312
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1313
1314
		$mount = Filesystem::getMountManager()->find($path);
1315
		$storage = $mount->getStorage();
1316
		$internalPath = $mount->getInternalPath($path);
1317
		if ($storage) {
1318
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1319
1320
			if (!$data instanceof ICacheEntry) {
1321
				return false;
1322
			}
1323
1324
			if ($mount instanceof MoveableMount && $internalPath === '') {
1325
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1326
			}
1327
1328
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1329
			$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 1314 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...
1330
1331
			if ($data and isset($data['fileid'])) {
1332
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1333
					//add the sizes of other mount points to the folder
1334
					$extOnly = ($includeMountPoints === 'ext');
1335
					$mounts = Filesystem::getMountManager()->findIn($path);
1336
					foreach ($mounts as $mount) {
1337
						$subStorage = $mount->getStorage();
1338
						if ($subStorage) {
1339
							// exclude shared storage ?
1340
							if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) {
1341
								continue;
1342
							}
1343
							$subCache = $subStorage->getCache('');
1344
							$rootEntry = $subCache->get('');
1345
							$info->addSubEntry($rootEntry, $mount->getMountPoint());
1346
						}
1347
					}
1348
				}
1349
			}
1350
1351
			return $info;
1352
		}
1353
1354
		return false;
1355
	}
1356
1357
	/**
1358
	 * get the content of a directory
1359
	 *
1360
	 * @param string $directory path under datadirectory
1361
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1362
	 * @return FileInfo[]
1363
	 */
1364
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1365
		$this->assertPathLength($directory);
1366
		if (!Filesystem::isValidPath($directory)) {
1367
			return [];
1368
		}
1369
		$path = $this->getAbsolutePath($directory);
1370
		$path = Filesystem::normalizePath($path);
1371
		$mount = $this->getMount($directory);
1372
		$storage = $mount->getStorage();
1373
		$internalPath = $mount->getInternalPath($path);
1374
		if ($storage) {
1375
			$cache = $storage->getCache($internalPath);
1376
			$user = \OC_User::getUser();
1377
1378
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1379
1380
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1381
				return [];
1382
			}
1383
1384
			$folderId = $data['fileid'];
1385
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1386
1387
			$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
1388
			/**
1389
			 * @var \OC\Files\FileInfo[] $files
1390
			 */
1391
			$files = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) {
1392
				if ($sharingDisabled) {
1393
					$content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1394
				}
1395
				$owner = $this->getUserObjectForOwner($storage->getOwner($content['path']));
1396
				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 1371 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...
1397
			}, $contents);
1398
1399
			//add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
1400
			$mounts = Filesystem::getMountManager()->findIn($path);
1401
			$dirLength = strlen($path);
1402
			foreach ($mounts as $mount) {
1403
				$mountPoint = $mount->getMountPoint();
1404
				$subStorage = $mount->getStorage();
1405
				if ($subStorage) {
1406
					$subCache = $subStorage->getCache('');
1407
1408
					$rootEntry = $subCache->get('');
1409
					if (!$rootEntry) {
1410
						$subScanner = $subStorage->getScanner('');
1411
						try {
1412
							$subScanner->scanFile('');
1413
						} catch (\OCP\Files\StorageNotAvailableException $e) {
1414
							continue;
1415
						} catch (\OCP\Files\StorageInvalidException $e) {
1416
							continue;
1417
						} catch (\Exception $e) {
1418
							// sometimes when the storage is not available it can be any exception
1419
							\OCP\Util::writeLog(
1420
								'core',
1421
								'Exception while scanning storage "' . $subStorage->getId() . '": ' .
1422
								get_class($e) . ': ' . $e->getMessage(),
1423
								\OCP\Util::ERROR
1424
							);
1425
							continue;
1426
						}
1427
						$rootEntry = $subCache->get('');
1428
					}
1429
1430
					if ($rootEntry && ($rootEntry->getPermissions() && Constants::PERMISSION_READ)) {
1431
						$relativePath = trim(substr($mountPoint, $dirLength), '/');
1432
						if ($pos = strpos($relativePath, '/')) {
1433
							//mountpoint inside subfolder add size to the correct folder
1434
							$entryName = substr($relativePath, 0, $pos);
1435
							foreach ($files as &$entry) {
1436
								if ($entry->getName() === $entryName) {
1437
									$entry->addSubEntry($rootEntry, $mountPoint);
1438
								}
1439
							}
1440
						} else { //mountpoint in this folder, add an entry for it
1441
							$rootEntry['name'] = $relativePath;
1442
							$rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
1443
							$permissions = $rootEntry['permissions'];
1444
							// do not allow renaming/deleting the mount point if they are not shared files/folders
1445
							// for shared files/folders we use the permissions given by the owner
1446
							if ($mount instanceof MoveableMount) {
1447
								$rootEntry['permissions'] = $permissions | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE;
1448
							} else {
1449
								$rootEntry['permissions'] = $permissions & (\OCP\Constants::PERMISSION_ALL - (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE));
1450
							}
1451
1452
							//remove any existing entry with the same name
1453
							foreach ($files as $i => $file) {
1454
								if ($file['name'] === $rootEntry['name']) {
1455
									unset($files[$i]);
1456
									break;
1457
								}
1458
							}
1459
							$rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/
1460
1461
							// if sharing was disabled for the user we remove the share permissions
1462
							if (\OCP\Util::isSharingDisabledForUser()) {
1463
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1464
							}
1465
1466
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1467
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1468
						}
1469
					}
1470
				}
1471
			}
1472
1473
			if ($mimetype_filter) {
1474
				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1475
					if (strpos($mimetype_filter, '/')) {
1476
						return $file->getMimetype() === $mimetype_filter;
1477
					} else {
1478
						return $file->getMimePart() === $mimetype_filter;
1479
					}
1480
				});
1481
			}
1482
1483
			return $files;
1484
		} else {
1485
			return [];
1486
		}
1487
	}
1488
1489
	/**
1490
	 * change file metadata
1491
	 *
1492
	 * @param string $path
1493
	 * @param array|\OCP\Files\FileInfo $data
1494
	 * @return int
1495
	 *
1496
	 * returns the fileid of the updated file
1497
	 */
1498
	public function putFileInfo($path, $data) {
1499
		$this->assertPathLength($path);
1500
		if ($data instanceof FileInfo) {
1501
			$data = $data->getData();
1502
		}
1503
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1504
		/**
1505
		 * @var \OC\Files\Storage\Storage $storage
1506
		 * @var string $internalPath
1507
		 */
1508
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1509
		if ($storage) {
1510
			$cache = $storage->getCache($path);
1511
1512
			if (!$cache->inCache($internalPath)) {
1513
				$scanner = $storage->getScanner($internalPath);
1514
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1515
			}
1516
1517
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1498 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...
1518
		} else {
1519
			return -1;
1520
		}
1521
	}
1522
1523
	/**
1524
	 * search for files with the name matching $query
1525
	 *
1526
	 * @param string $query
1527
	 * @return FileInfo[]
1528
	 */
1529
	public function search($query) {
1530
		return $this->searchCommon('search', array('%' . $query . '%'));
1531
	}
1532
1533
	/**
1534
	 * search for files with the name matching $query
1535
	 *
1536
	 * @param string $query
1537
	 * @return FileInfo[]
1538
	 */
1539
	public function searchRaw($query) {
1540
		return $this->searchCommon('search', array($query));
1541
	}
1542
1543
	/**
1544
	 * search for files by mimetype
1545
	 *
1546
	 * @param string $mimetype
1547
	 * @return FileInfo[]
1548
	 */
1549
	public function searchByMime($mimetype) {
1550
		return $this->searchCommon('searchByMime', array($mimetype));
1551
	}
1552
1553
	/**
1554
	 * search for files by tag
1555
	 *
1556
	 * @param string|int $tag name or tag id
1557
	 * @param string $userId owner of the tags
1558
	 * @return FileInfo[]
1559
	 */
1560
	public function searchByTag($tag, $userId) {
1561
		return $this->searchCommon('searchByTag', array($tag, $userId));
1562
	}
1563
1564
	/**
1565
	 * @param string $method cache method
1566
	 * @param array $args
1567
	 * @return FileInfo[]
1568
	 */
1569
	private function searchCommon($method, $args) {
1570
		$files = array();
1571
		$rootLength = strlen($this->fakeRoot);
1572
1573
		$mount = $this->getMount('');
1574
		$mountPoint = $mount->getMountPoint();
1575
		$storage = $mount->getStorage();
1576
		if ($storage) {
1577
			$cache = $storage->getCache('');
1578
1579
			$results = call_user_func_array(array($cache, $method), $args);
1580
			foreach ($results as $result) {
1581
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1582
					$internalPath = $result['path'];
1583
					$path = $mountPoint . $result['path'];
1584
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1585
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1586
					$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 1573 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...
1587
				}
1588
			}
1589
1590
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1591
			foreach ($mounts as $mount) {
1592
				$mountPoint = $mount->getMountPoint();
1593
				$storage = $mount->getStorage();
1594
				if ($storage) {
1595
					$cache = $storage->getCache('');
1596
1597
					$relativeMountPoint = substr($mountPoint, $rootLength);
1598
					$results = call_user_func_array(array($cache, $method), $args);
1599
					if ($results) {
1600
						foreach ($results as $result) {
1601
							$internalPath = $result['path'];
1602
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1603
							$path = rtrim($mountPoint . $internalPath, '/');
1604
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1605
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1606
						}
1607
					}
1608
				}
1609
			}
1610
		}
1611
		return $files;
1612
	}
1613
1614
	/**
1615
	 * Get the owner for a file or folder
1616
	 *
1617
	 * @param string $path
1618
	 * @return string the user id of the owner
1619
	 * @throws NotFoundException
1620
	 */
1621
	public function getOwner($path) {
1622
		$info = $this->getFileInfo($path);
1623
		if (!$info) {
1624
			throw new NotFoundException($path . ' not found while trying to get owner');
1625
		}
1626
		return $info->getOwner()->getUID();
1627
	}
1628
1629
	/**
1630
	 * get the ETag for a file or folder
1631
	 *
1632
	 * @param string $path
1633
	 * @return string
1634
	 */
1635
	public function getETag($path) {
1636
		/**
1637
		 * @var Storage\Storage $storage
1638
		 * @var string $internalPath
1639
		 */
1640
		list($storage, $internalPath) = $this->resolvePath($path);
1641
		if ($storage) {
1642
			return $storage->getETag($internalPath);
1643
		} else {
1644
			return null;
1645
		}
1646
	}
1647
1648
	/**
1649
	 * Get the path of a file by id, relative to the view
1650
	 *
1651
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1652
	 *
1653
	 * @param int $id
1654
	 * @throws NotFoundException
1655
	 * @return string
1656
	 */
1657
	public function getPath($id) {
1658
		$id = (int)$id;
1659
		$manager = Filesystem::getMountManager();
1660
		$mounts = $manager->findIn($this->fakeRoot);
1661
		$mounts[] = $manager->find($this->fakeRoot);
1662
		// reverse the array so we start with the storage this view is in
1663
		// which is the most likely to contain the file we're looking for
1664
		$mounts = array_reverse($mounts);
1665 View Code Duplication
		foreach ($mounts as $mount) {
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...
1666
			/**
1667
			 * @var \OC\Files\Mount\MountPoint $mount
1668
			 */
1669
			if ($mount->getStorage()) {
1670
				$cache = $mount->getStorage()->getCache();
1671
				$internalPath = $cache->getPathById($id);
1672
				if (is_string($internalPath)) {
1673
					$fullPath = $mount->getMountPoint() . $internalPath;
1674
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1675
						return $path;
1676
					}
1677
				}
1678
			}
1679
		}
1680
		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1681
	}
1682
1683
	/**
1684
	 * @param string $path
1685
	 * @throws InvalidPathException
1686
	 */
1687
	private function assertPathLength($path) {
1688
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1689
		// Check for the string length - performed using isset() instead of strlen()
1690
		// because isset() is about 5x-40x faster.
1691
		if (isset($path[$maxLen])) {
1692
			$pathLen = strlen($path);
1693
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1694
		}
1695
	}
1696
1697
	/**
1698
	 * check if it is allowed to move a mount point to a given target.
1699
	 * It is not allowed to move a mount point into a different mount point or
1700
	 * into an already shared folder
1701
	 *
1702
	 * @param string $target path
1703
	 * @return boolean
1704
	 */
1705
	private function isTargetAllowed($target) {
1706
1707
		list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target);
1708
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1709
			\OCP\Util::writeLog('files',
1710
				'It is not allowed to move one mount point into another one',
1711
				\OCP\Util::DEBUG);
1712
			return false;
1713
		}
1714
1715
		// note: cannot use the view because the target is already locked
1716
		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1717
		if ($fileId === -1) {
1718
			// target might not exist, need to check parent instead
1719
			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1720
		}
1721
1722
		// check if any of the parents were shared by the current owner (include collections)
1723
		$shares = \OCP\Share::getItemShared(
1724
			'folder',
1725
			$fileId,
1726
			\OCP\Share::FORMAT_NONE,
1727
			null,
1728
			true
1729
		);
1730
1731
		if (count($shares) > 0) {
1732
			\OCP\Util::writeLog('files',
1733
				'It is not allowed to move one mount point into a shared folder',
1734
				\OCP\Util::DEBUG);
1735
			return false;
1736
		}
1737
1738
		return true;
1739
	}
1740
1741
	/**
1742
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1743
	 *
1744
	 * @param string $path
1745
	 * @return \OCP\Files\FileInfo
1746
	 */
1747
	private function getPartFileInfo($path) {
1748
		$mount = $this->getMount($path);
1749
		$storage = $mount->getStorage();
1750
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1751
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1752
		return new FileInfo(
1753
			$this->getAbsolutePath($path),
1754
			$storage,
1755
			$internalPath,
1756
			[
1757
				'fileid' => null,
1758
				'mimetype' => $storage->getMimeType($internalPath),
1759
				'name' => basename($path),
1760
				'etag' => null,
1761
				'size' => $storage->filesize($internalPath),
1762
				'mtime' => $storage->filemtime($internalPath),
1763
				'encrypted' => false,
1764
				'permissions' => \OCP\Constants::PERMISSION_ALL
1765
			],
1766
			$mount,
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1748 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...
1767
			$owner
1768
		);
1769
	}
1770
1771
	/**
1772
	 * @param string $path
1773
	 * @param string $fileName
1774
	 * @throws InvalidPathException
1775
	 */
1776
	public function verifyPath($path, $fileName) {
1777
1778
		$l10n = \OC::$server->getL10N('lib');
1779
1780
		// verify empty and dot files
1781
		$trimmed = trim($fileName);
1782
		if ($trimmed === '') {
1783
			throw new InvalidPathException($l10n->t('Empty filename is not allowed'));
1784
		}
1785
		if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
1786
			throw new InvalidPathException($l10n->t('Dot files are not allowed'));
1787
		}
1788
1789
		// verify database - e.g. mysql only 3-byte chars
1790
		if (preg_match('%(?:
1791
      \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1792
    | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1793
    | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1794
)%xs', $fileName)) {
1795
			throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names'));
1796
		}
1797
1798
		try {
1799
			/** @type \OCP\Files\Storage $storage */
1800
			list($storage, $internalPath) = $this->resolvePath($path);
1801
			$storage->verifyPath($internalPath, $fileName);
1802
		} catch (ReservedWordException $ex) {
1803
			throw new InvalidPathException($l10n->t('File name is a reserved word'));
1804
		} catch (InvalidCharacterInPathException $ex) {
1805
			throw new InvalidPathException($l10n->t('File name contains at least one invalid character'));
1806
		} catch (FileNameTooLongException $ex) {
1807
			throw new InvalidPathException($l10n->t('File name is too long'));
1808
		}
1809
	}
1810
1811
	/**
1812
	 * get all parent folders of $path
1813
	 *
1814
	 * @param string $path
1815
	 * @return string[]
1816
	 */
1817
	private function getParents($path) {
1818
		$path = trim($path, '/');
1819
		if (!$path) {
1820
			return [];
1821
		}
1822
1823
		$parts = explode('/', $path);
1824
1825
		// remove the single file
1826
		array_pop($parts);
1827
		$result = array('/');
1828
		$resultPath = '';
1829
		foreach ($parts as $part) {
1830
			if ($part) {
1831
				$resultPath .= '/' . $part;
1832
				$result[] = $resultPath;
1833
			}
1834
		}
1835
		return $result;
1836
	}
1837
1838
	/**
1839
	 * Returns the mount point for which to lock
1840
	 *
1841
	 * @param string $absolutePath absolute path
1842
	 * @param bool $useParentMount true to return parent mount instead of whatever
1843
	 * is mounted directly on the given path, false otherwise
1844
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1845
	 */
1846
	private function getMountForLock($absolutePath, $useParentMount = false) {
1847
		$results = [];
1848
		$mount = Filesystem::getMountManager()->find($absolutePath);
1849
		if (!$mount) {
1850
			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...
1851
		}
1852
1853
		if ($useParentMount) {
1854
			// find out if something is mounted directly on the path
1855
			$internalPath = $mount->getInternalPath($absolutePath);
1856
			if ($internalPath === '') {
1857
				// resolve the parent mount instead
1858
				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1859
			}
1860
		}
1861
1862
		return $mount;
1863
	}
1864
1865
	/**
1866
	 * Lock the given path
1867
	 *
1868
	 * @param string $path the path of the file to lock, relative to the view
1869
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1870
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1871
	 *
1872
	 * @return bool False if the path is excluded from locking, true otherwise
1873
	 * @throws \OCP\Lock\LockedException if the path is already locked
1874
	 */
1875 View Code Duplication
	private function lockPath($path, $type, $lockMountPoint = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
2085
			Filesystem::initMountPoints($uid);
2086
			$ownerView = new View('/' . $uid . '/files');
2087
			try {
2088
				$filename = $ownerView->getPath($info['fileid']);
2089
			} catch (NotFoundException $e) {
2090
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2091
			}
2092
		}
2093
		return [$uid, $filename];
2094
	}
2095
}
2096