Completed
Push — master ( 0c0ce2...26eda9 )
by Joas
117:18 queued 101:40
created

View::readfilePart()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

Loading history...
250
		$parent = substr($path, 0, strrpos($path, '/'));
251
		$path = $this->getAbsolutePath($path);
252
		list($storage, $internalPath) = Filesystem::resolvePath($path);
253
		if (Filesystem::isValidPath($parent) and $storage) {
254
			return $storage->getLocalFolder($internalPath);
255
		} else {
256
			return null;
257
		}
258
	}
259
260
	/**
261
	 * the following functions operate with arguments and return values identical
262
	 * to those of their PHP built-in equivalents. Mostly they are merely wrappers
263
	 * for \OC\Files\Storage\Storage via basicOperation().
264
	 */
265
	public function mkdir($path) {
266
		return $this->basicOperation('mkdir', $path, array('create', 'write'));
267
	}
268
269
	/**
270
	 * remove mount point
271
	 *
272
	 * @param \OC\Files\Mount\MoveableMount $mount
273
	 * @param string $path relative to data/
274
	 * @return boolean
275
	 */
276
	protected function removeMount($mount, $path) {
277
		if ($mount instanceof MoveableMount) {
278
			// cut of /user/files to get the relative path to data/user/files
279
			$pathParts = explode('/', $path, 4);
280
			$relPath = '/' . $pathParts[3];
281
			$this->lockFile($relPath, ILockingProvider::LOCK_SHARED, true);
282
			\OC_Hook::emit(
283
				Filesystem::CLASSNAME, "umount",
284
				array(Filesystem::signal_param_path => $relPath)
285
			);
286
			$this->changeLock($relPath, ILockingProvider::LOCK_EXCLUSIVE, true);
287
			$result = $mount->removeMount();
288
			$this->changeLock($relPath, ILockingProvider::LOCK_SHARED, true);
289
			if ($result) {
290
				\OC_Hook::emit(
291
					Filesystem::CLASSNAME, "post_umount",
292
					array(Filesystem::signal_param_path => $relPath)
293
				);
294
			}
295
			$this->unlockFile($relPath, ILockingProvider::LOCK_SHARED, true);
296
			return $result;
297
		} else {
298
			// do not allow deleting the storage's root / the mount point
299
			// because for some storages it might delete the whole contents
300
			// but isn't supposed to work that way
301
			return false;
302
		}
303
	}
304
305
	public function disableCacheUpdate() {
306
		$this->updaterEnabled = false;
307
	}
308
309
	public function enableCacheUpdate() {
310
		$this->updaterEnabled = true;
311
	}
312
313
	protected function writeUpdate(Storage $storage, $internalPath, $time = null) {
314
		if ($this->updaterEnabled) {
315
			if (is_null($time)) {
316
				$time = time();
317
			}
318
			$storage->getUpdater()->update($internalPath, $time);
319
		}
320
	}
321
322
	protected function removeUpdate(Storage $storage, $internalPath) {
323
		if ($this->updaterEnabled) {
324
			$storage->getUpdater()->remove($internalPath);
325
		}
326
	}
327
328
	protected function renameUpdate(Storage $sourceStorage, Storage $targetStorage, $sourceInternalPath, $targetInternalPath) {
329
		if ($this->updaterEnabled) {
330
			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
331
		}
332
	}
333
334
	/**
335
	 * @param string $path
336
	 * @return bool|mixed
337
	 */
338
	public function rmdir($path) {
339
		$absolutePath = $this->getAbsolutePath($path);
340
		$mount = Filesystem::getMountManager()->find($absolutePath);
341
		if ($mount->getInternalPath($absolutePath) === '') {
342
			return $this->removeMount($mount, $absolutePath);
0 ignored issues
show
Documentation introduced by
$mount is of type object<OC\Files\Mount\MountPoint>|null, but the function expects a object<OC\Files\Mount\MoveableMount>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
343
		}
344
		if ($this->is_dir($path)) {
345
			$result = $this->basicOperation('rmdir', $path, array('delete'));
346
		} else {
347
			$result = false;
348
		}
349
350 View Code Duplication
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
351
			$storage = $mount->getStorage();
352
			$internalPath = $mount->getInternalPath($absolutePath);
353
			$storage->getUpdater()->remove($internalPath);
354
		}
355
		return $result;
356
	}
357
358
	/**
359
	 * @param string $path
360
	 * @return resource
361
	 */
362
	public function opendir($path) {
363
		return $this->basicOperation('opendir', $path, array('read'));
364
	}
365
366
	/**
367
	 * @param $handle
368
	 * @return mixed
369
	 */
370
	public function readdir($handle) {
371
		$fsLocal = new Storage\Local(array('datadir' => '/'));
372
		return $fsLocal->readdir($handle);
373
	}
374
375
	/**
376
	 * @param string $path
377
	 * @return bool|mixed
378
	 */
379
	public function is_dir($path) {
380
		if ($path == '/') {
381
			return true;
382
		}
383
		return $this->basicOperation('is_dir', $path);
384
	}
385
386
	/**
387
	 * @param string $path
388
	 * @return bool|mixed
389
	 */
390
	public function is_file($path) {
391
		if ($path == '/') {
392
			return false;
393
		}
394
		return $this->basicOperation('is_file', $path);
395
	}
396
397
	/**
398
	 * @param string $path
399
	 * @return mixed
400
	 */
401
	public function stat($path) {
402
		return $this->basicOperation('stat', $path);
403
	}
404
405
	/**
406
	 * @param string $path
407
	 * @return mixed
408
	 */
409
	public function filetype($path) {
410
		return $this->basicOperation('filetype', $path);
411
	}
412
413
	/**
414
	 * @param string $path
415
	 * @return mixed
416
	 */
417
	public function filesize($path) {
418
		return $this->basicOperation('filesize', $path);
419
	}
420
421
	/**
422
	 * @param string $path
423
	 * @return bool|mixed
424
	 * @throws \OCP\Files\InvalidPathException
425
	 */
426
	public function readfile($path) {
427
		$this->assertPathLength($path);
428
		@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
429
		$handle = $this->fopen($path, 'rb');
430
		if ($handle) {
431
			$chunkSize = 8192; // 8 kB chunks
432
			while (!feof($handle)) {
433
				echo fread($handle, $chunkSize);
434
				flush();
435
			}
436
			fclose($handle);
437
			$size = $this->filesize($path);
438
			return $size;
439
		}
440
		return false;
441
	}
442
443
	/**
444
	 * @param string $path
445
	 * @param int $from
446
	 * @param int $to
447
	 * @return bool|mixed
448
	 * @throws \OCP\Files\InvalidPathException
449
	 * @throws \OCP\Files\UnseekableException
450
	 */
451
	public function readfilePart($path, $from, $to) {
452
		$this->assertPathLength($path);
453
		@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
454
		$handle = $this->fopen($path, 'rb');
455
		if ($handle) {
456
			if (fseek($handle, $from) === 0) {
457
				$chunkSize = 8192; // 8 kB chunks
458
				$end = $to + 1;
459
				while (!feof($handle) && ftell($handle) < $end) {
460
					$len = $end - ftell($handle);
461
					if ($len > $chunkSize) {
462
						$len = $chunkSize;
463
					}
464
					echo fread($handle, $len);
465
					flush();
466
				}
467
				$size = ftell($handle) - $from;
468
				return $size;
469
			}
470
471
			throw new \OCP\Files\UnseekableException('fseek error');
472
		}
473
		return false;
474
	}
475
476
	/**
477
	 * @param string $path
478
	 * @return mixed
479
	 */
480
	public function isCreatable($path) {
481
		return $this->basicOperation('isCreatable', $path);
482
	}
483
484
	/**
485
	 * @param string $path
486
	 * @return mixed
487
	 */
488
	public function isReadable($path) {
489
		return $this->basicOperation('isReadable', $path);
490
	}
491
492
	/**
493
	 * @param string $path
494
	 * @return mixed
495
	 */
496
	public function isUpdatable($path) {
497
		return $this->basicOperation('isUpdatable', $path);
498
	}
499
500
	/**
501
	 * @param string $path
502
	 * @return bool|mixed
503
	 */
504
	public function isDeletable($path) {
505
		$absolutePath = $this->getAbsolutePath($path);
506
		$mount = Filesystem::getMountManager()->find($absolutePath);
507
		if ($mount->getInternalPath($absolutePath) === '') {
508
			return $mount instanceof MoveableMount;
509
		}
510
		return $this->basicOperation('isDeletable', $path);
511
	}
512
513
	/**
514
	 * @param string $path
515
	 * @return mixed
516
	 */
517
	public function isSharable($path) {
518
		return $this->basicOperation('isSharable', $path);
519
	}
520
521
	/**
522
	 * @param string $path
523
	 * @return bool|mixed
524
	 */
525
	public function file_exists($path) {
526
		if ($path == '/') {
527
			return true;
528
		}
529
		return $this->basicOperation('file_exists', $path);
530
	}
531
532
	/**
533
	 * @param string $path
534
	 * @return mixed
535
	 */
536
	public function filemtime($path) {
537
		return $this->basicOperation('filemtime', $path);
538
	}
539
540
	/**
541
	 * @param string $path
542
	 * @param int|string $mtime
543
	 * @return bool
544
	 */
545
	public function touch($path, $mtime = null) {
546
		if (!is_null($mtime) and !is_numeric($mtime)) {
547
			$mtime = strtotime($mtime);
548
		}
549
550
		$hooks = array('touch');
551
552
		if (!$this->file_exists($path)) {
553
			$hooks[] = 'create';
554
			$hooks[] = 'write';
555
		}
556
		$result = $this->basicOperation('touch', $path, $hooks, $mtime);
557
		if (!$result) {
558
			// If create file fails because of permissions on external storage like SMB folders,
559
			// check file exists and return false if not.
560
			if (!$this->file_exists($path)) {
561
				return false;
562
			}
563
			if (is_null($mtime)) {
564
				$mtime = time();
565
			}
566
			//if native touch fails, we emulate it by changing the mtime in the cache
567
			$this->putFileInfo($path, array('mtime' => $mtime));
568
		}
569
		return true;
570
	}
571
572
	/**
573
	 * @param string $path
574
	 * @return mixed
575
	 */
576
	public function file_get_contents($path) {
577
		return $this->basicOperation('file_get_contents', $path, array('read'));
578
	}
579
580
	/**
581
	 * @param bool $exists
582
	 * @param string $path
583
	 * @param bool $run
584
	 */
585
	protected function emit_file_hooks_pre($exists, $path, &$run) {
586 View Code Duplication
		if (!$exists) {
587
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array(
588
				Filesystem::signal_param_path => $this->getHookPath($path),
589
				Filesystem::signal_param_run => &$run,
590
			));
591
		} else {
592
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array(
593
				Filesystem::signal_param_path => $this->getHookPath($path),
594
				Filesystem::signal_param_run => &$run,
595
			));
596
		}
597
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array(
598
			Filesystem::signal_param_path => $this->getHookPath($path),
599
			Filesystem::signal_param_run => &$run,
600
		));
601
	}
602
603
	/**
604
	 * @param bool $exists
605
	 * @param string $path
606
	 */
607
	protected function emit_file_hooks_post($exists, $path) {
608 View Code Duplication
		if (!$exists) {
609
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array(
610
				Filesystem::signal_param_path => $this->getHookPath($path),
611
			));
612
		} else {
613
			\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
614
				Filesystem::signal_param_path => $this->getHookPath($path),
615
			));
616
		}
617
		\OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
618
			Filesystem::signal_param_path => $this->getHookPath($path),
619
		));
620
	}
621
622
	/**
623
	 * @param string $path
624
	 * @param mixed $data
625
	 * @return bool|mixed
626
	 * @throws \Exception
627
	 */
628
	public function file_put_contents($path, $data) {
629
		if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
630
			$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
631
			if (Filesystem::isValidPath($path)
632
				and !Filesystem::isFileBlacklisted($path)
633
			) {
634
				$path = $this->getRelativePath($absolutePath);
635
636
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
637
638
				$exists = $this->file_exists($path);
639
				$run = true;
640
				if ($this->shouldEmitHooks($path)) {
641
					$this->emit_file_hooks_pre($exists, $path, $run);
642
				}
643
				if (!$run) {
644
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
645
					return false;
646
				}
647
648
				$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
649
650
				/** @var \OC\Files\Storage\Storage $storage */
651
				list($storage, $internalPath) = $this->resolvePath($path);
652
				$target = $storage->fopen($internalPath, 'w');
653
				if ($target) {
654
					list (, $result) = \OC_Helper::streamCopy($data, $target);
655
					fclose($target);
656
					fclose($data);
657
658
					$this->writeUpdate($storage, $internalPath);
659
660
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
661
662
					if ($this->shouldEmitHooks($path) && $result !== false) {
663
						$this->emit_file_hooks_post($exists, $path);
664
					}
665
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
666
					return $result;
667
				} else {
668
					$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
669
					return false;
670
				}
671
			} else {
672
				return false;
673
			}
674
		} else {
675
			$hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write');
676
			return $this->basicOperation('file_put_contents', $path, $hooks, $data);
677
		}
678
	}
679
680
	/**
681
	 * @param string $path
682
	 * @return bool|mixed
683
	 */
684
	public function unlink($path) {
685
		if ($path === '' || $path === '/') {
686
			// do not allow deleting the root
687
			return false;
688
		}
689
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
690
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
691
		$mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
692
		if ($mount and $mount->getInternalPath($absolutePath) === '') {
693
			return $this->removeMount($mount, $absolutePath);
0 ignored issues
show
Documentation introduced by
$mount is of type object<OC\Files\Mount\MountPoint>, but the function expects a object<OC\Files\Mount\MoveableMount>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
694
		}
695
		if ($this->is_dir($path)) {
696
			$result = $this->basicOperation('rmdir', $path, ['delete']);
697
		} else {
698
			$result = $this->basicOperation('unlink', $path, ['delete']);
699
		}
700 View Code Duplication
		if (!$result && !$this->file_exists($path)) { //clear ghost files from the cache on delete
701
			$storage = $mount->getStorage();
702
			$internalPath = $mount->getInternalPath($absolutePath);
703
			$storage->getUpdater()->remove($internalPath);
704
			return true;
705
		} else {
706
			return $result;
707
		}
708
	}
709
710
	/**
711
	 * @param string $directory
712
	 * @return bool|mixed
713
	 */
714
	public function deleteAll($directory) {
715
		return $this->rmdir($directory);
716
	}
717
718
	/**
719
	 * Rename/move a file or folder from the source path to target path.
720
	 *
721
	 * @param string $path1 source path
722
	 * @param string $path2 target path
723
	 *
724
	 * @return bool|mixed
725
	 */
726
	public function rename($path1, $path2) {
727
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
728
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
729
		$result = false;
730
		if (
731
			Filesystem::isValidPath($path2)
732
			and Filesystem::isValidPath($path1)
733
			and !Filesystem::isFileBlacklisted($path2)
734
		) {
735
			$path1 = $this->getRelativePath($absolutePath1);
736
			$path2 = $this->getRelativePath($absolutePath2);
737
			$exists = $this->file_exists($path2);
738
739
			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...
740
				return false;
741
			}
742
743
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED, true);
744
			try {
745
				$this->lockFile($path2, ILockingProvider::LOCK_SHARED, true);
746
			} catch (LockedException $e) {
747
				$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
748
				throw $e;
749
			}
750
751
			$run = true;
752
			if ($this->shouldEmitHooks($path1) && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
753
				// if it was a rename from a part file to a regular file it was a write and not a rename operation
754
				$this->emit_file_hooks_pre($exists, $path2, $run);
755
			} elseif ($this->shouldEmitHooks($path1)) {
756
				\OC_Hook::emit(
757
					Filesystem::CLASSNAME, Filesystem::signal_rename,
758
					array(
759
						Filesystem::signal_param_oldpath => $this->getHookPath($path1),
760
						Filesystem::signal_param_newpath => $this->getHookPath($path2),
761
						Filesystem::signal_param_run => &$run
762
					)
763
				);
764
			}
765
			if ($run) {
766
				$this->verifyPath(dirname($path2), basename($path2));
767
768
				$manager = Filesystem::getMountManager();
769
				$mount1 = $this->getMount($path1);
770
				$mount2 = $this->getMount($path2);
771
				$storage1 = $mount1->getStorage();
772
				$storage2 = $mount2->getStorage();
773
				$internalPath1 = $mount1->getInternalPath($absolutePath1);
774
				$internalPath2 = $mount2->getInternalPath($absolutePath2);
775
776
				$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE, true);
777
				$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE, true);
778
779
				if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
780
					if ($this->isTargetAllowed($absolutePath2)) {
781
						/**
782
						 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
783
						 */
784
						$sourceMountPoint = $mount1->getMountPoint();
785
						$result = $mount1->moveMount($absolutePath2);
786
						$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
787
					} else {
788
						$result = false;
789
					}
790
					// moving a file/folder within the same mount point
791
				} elseif ($storage1 === $storage2) {
792
					if ($storage1) {
793
						$result = $storage1->rename($internalPath1, $internalPath2);
794
					} else {
795
						$result = false;
796
					}
797
					// moving a file/folder between storages (from $storage1 to $storage2)
798
				} else {
799
					$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
800
				}
801
802
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
803
					// if it was a rename from a part file to a regular file it was a write and not a rename operation
804
805
					$this->writeUpdate($storage2, $internalPath2);
806
				} else if ($result) {
807
					if ($internalPath1 !== '') { // don't do a cache update for moved mounts
808
						$this->renameUpdate($storage1, $storage2, $internalPath1, $internalPath2);
809
					}
810
				}
811
812
				$this->changeLock($path1, ILockingProvider::LOCK_SHARED, true);
813
				$this->changeLock($path2, ILockingProvider::LOCK_SHARED, true);
814
815
				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
816
					if ($this->shouldEmitHooks()) {
817
						$this->emit_file_hooks_post($exists, $path2);
818
					}
819
				} elseif ($result) {
820
					if ($this->shouldEmitHooks($path1) and $this->shouldEmitHooks($path2)) {
821
						\OC_Hook::emit(
822
							Filesystem::CLASSNAME,
823
							Filesystem::signal_post_rename,
824
							array(
825
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
826
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
827
							)
828
						);
829
					}
830
				}
831
			}
832
			$this->unlockFile($path1, ILockingProvider::LOCK_SHARED, true);
833
			$this->unlockFile($path2, ILockingProvider::LOCK_SHARED, true);
834
		}
835
		return $result;
836
	}
837
838
	/**
839
	 * Copy a file/folder from the source path to target path
840
	 *
841
	 * @param string $path1 source path
842
	 * @param string $path2 target path
843
	 * @param bool $preserveMtime whether to preserve mtime on the copy
844
	 *
845
	 * @return bool|mixed
846
	 */
847
	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...
848
		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
849
		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
850
		$result = false;
851
		if (
852
			Filesystem::isValidPath($path2)
853
			and Filesystem::isValidPath($path1)
854
			and !Filesystem::isFileBlacklisted($path2)
855
		) {
856
			$path1 = $this->getRelativePath($absolutePath1);
857
			$path2 = $this->getRelativePath($absolutePath2);
858
859
			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...
860
				return false;
861
			}
862
			$run = true;
863
864
			$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
865
			$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
866
			$lockTypePath1 = ILockingProvider::LOCK_SHARED;
867
			$lockTypePath2 = ILockingProvider::LOCK_SHARED;
868
869
			try {
870
871
				$exists = $this->file_exists($path2);
872 View Code Duplication
				if ($this->shouldEmitHooks()) {
873
					\OC_Hook::emit(
874
						Filesystem::CLASSNAME,
875
						Filesystem::signal_copy,
876
						array(
877
							Filesystem::signal_param_oldpath => $this->getHookPath($path1),
878
							Filesystem::signal_param_newpath => $this->getHookPath($path2),
879
							Filesystem::signal_param_run => &$run
880
						)
881
					);
882
					$this->emit_file_hooks_pre($exists, $path2, $run);
883
				}
884
				if ($run) {
885
					$mount1 = $this->getMount($path1);
886
					$mount2 = $this->getMount($path2);
887
					$storage1 = $mount1->getStorage();
888
					$internalPath1 = $mount1->getInternalPath($absolutePath1);
889
					$storage2 = $mount2->getStorage();
890
					$internalPath2 = $mount2->getInternalPath($absolutePath2);
891
892
					$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
893
					$lockTypePath2 = ILockingProvider::LOCK_EXCLUSIVE;
894
895
					if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
896
						if ($storage1) {
897
							$result = $storage1->copy($internalPath1, $internalPath2);
898
						} else {
899
							$result = false;
900
						}
901
					} else {
902
						$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
903
					}
904
905
					$this->writeUpdate($storage2, $internalPath2);
906
907
					$this->changeLock($path2, ILockingProvider::LOCK_SHARED);
908
					$lockTypePath2 = ILockingProvider::LOCK_SHARED;
909
910 View Code Duplication
					if ($this->shouldEmitHooks() && $result !== false) {
911
						\OC_Hook::emit(
912
							Filesystem::CLASSNAME,
913
							Filesystem::signal_post_copy,
914
							array(
915
								Filesystem::signal_param_oldpath => $this->getHookPath($path1),
916
								Filesystem::signal_param_newpath => $this->getHookPath($path2)
917
							)
918
						);
919
						$this->emit_file_hooks_post($exists, $path2);
920
					}
921
922
				}
923
			} catch (\Exception $e) {
924
				$this->unlockFile($path2, $lockTypePath2);
925
				$this->unlockFile($path1, $lockTypePath1);
926
				throw $e;
927
			}
928
929
			$this->unlockFile($path2, $lockTypePath2);
930
			$this->unlockFile($path1, $lockTypePath1);
931
932
		}
933
		return $result;
934
	}
935
936
	/**
937
	 * @param string $path
938
	 * @param string $mode 'r' or 'w'
939
	 * @return resource
940
	 */
941
	public function fopen($path, $mode) {
942
		$mode = str_replace('b', '', $mode); // the binary flag is a windows only feature which we do not support
943
		$hooks = array();
944
		switch ($mode) {
945
			case 'r':
946
				$hooks[] = 'read';
947
				break;
948
			case 'r+':
949
			case 'w+':
950
			case 'x+':
951
			case 'a+':
952
				$hooks[] = 'read';
953
				$hooks[] = 'write';
954
				break;
955
			case 'w':
956
			case 'x':
957
			case 'a':
958
				$hooks[] = 'write';
959
				break;
960
			default:
961
				\OCP\Util::writeLog('core', 'invalid mode (' . $mode . ') for ' . $path, \OCP\Util::ERROR);
962
		}
963
964
		if ($mode !== 'r' && $mode !== 'w') {
965
			\OC::$server->getLogger()->info('Trying to open a file with a mode other than "r" or "w" can cause severe performance issues with some backends');
966
		}
967
968
		return $this->basicOperation('fopen', $path, $hooks, $mode);
969
	}
970
971
	/**
972
	 * @param string $path
973
	 * @return bool|string
974
	 * @throws \OCP\Files\InvalidPathException
975
	 */
976
	public function toTmpFile($path) {
977
		$this->assertPathLength($path);
978
		if (Filesystem::isValidPath($path)) {
979
			$source = $this->fopen($path, 'r');
980
			if ($source) {
981
				$extension = pathinfo($path, PATHINFO_EXTENSION);
982
				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension);
983
				file_put_contents($tmpFile, $source);
984
				return $tmpFile;
985
			} else {
986
				return false;
987
			}
988
		} else {
989
			return false;
990
		}
991
	}
992
993
	/**
994
	 * @param string $tmpFile
995
	 * @param string $path
996
	 * @return bool|mixed
997
	 * @throws \OCP\Files\InvalidPathException
998
	 */
999
	public function fromTmpFile($tmpFile, $path) {
1000
		$this->assertPathLength($path);
1001
		if (Filesystem::isValidPath($path)) {
1002
1003
			// Get directory that the file is going into
1004
			$filePath = dirname($path);
1005
1006
			// Create the directories if any
1007
			if (!$this->file_exists($filePath)) {
1008
				$result = $this->createParentDirectories($filePath);
1009
				if ($result === false) {
1010
					return false;
1011
				}
1012
			}
1013
1014
			$source = fopen($tmpFile, 'r');
1015
			if ($source) {
1016
				$result = $this->file_put_contents($path, $source);
1017
				// $this->file_put_contents() might have already closed
1018
				// the resource, so we check it, before trying to close it
1019
				// to avoid messages in the error log.
1020
				if (is_resource($source)) {
1021
					fclose($source);
1022
				}
1023
				unlink($tmpFile);
1024
				return $result;
1025
			} else {
1026
				return false;
1027
			}
1028
		} else {
1029
			return false;
1030
		}
1031
	}
1032
1033
1034
	/**
1035
	 * @param string $path
1036
	 * @return mixed
1037
	 * @throws \OCP\Files\InvalidPathException
1038
	 */
1039
	public function getMimeType($path) {
1040
		$this->assertPathLength($path);
1041
		return $this->basicOperation('getMimeType', $path);
1042
	}
1043
1044
	/**
1045
	 * @param string $type
1046
	 * @param string $path
1047
	 * @param bool $raw
1048
	 * @return bool|null|string
1049
	 */
1050
	public function hash($type, $path, $raw = false) {
1051
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1052
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1053
		if (Filesystem::isValidPath($path)) {
1054
			$path = $this->getRelativePath($absolutePath);
1055
			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...
1056
				return false;
1057
			}
1058
			if ($this->shouldEmitHooks($path)) {
1059
				\OC_Hook::emit(
1060
					Filesystem::CLASSNAME,
1061
					Filesystem::signal_read,
1062
					array(Filesystem::signal_param_path => $this->getHookPath($path))
1063
				);
1064
			}
1065
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1066
			if ($storage) {
1067
				$result = $storage->hash($type, $internalPath, $raw);
1068
				return $result;
1069
			}
1070
		}
1071
		return null;
1072
	}
1073
1074
	/**
1075
	 * @param string $path
1076
	 * @return mixed
1077
	 * @throws \OCP\Files\InvalidPathException
1078
	 */
1079
	public function free_space($path = '/') {
1080
		$this->assertPathLength($path);
1081
		return $this->basicOperation('free_space', $path);
1082
	}
1083
1084
	/**
1085
	 * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
1086
	 *
1087
	 * @param string $operation
1088
	 * @param string $path
1089
	 * @param array $hooks (optional)
1090
	 * @param mixed $extraParam (optional)
1091
	 * @return mixed
1092
	 * @throws \Exception
1093
	 *
1094
	 * This method takes requests for basic filesystem functions (e.g. reading & writing
1095
	 * files), processes hooks and proxies, sanitises paths, and finally passes them on to
1096
	 * \OC\Files\Storage\Storage for delegation to a storage backend for execution
1097
	 */
1098
	private function basicOperation($operation, $path, $hooks = [], $extraParam = null) {
1099
		$postFix = (substr($path, -1, 1) === '/') ? '/' : '';
1100
		$absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
1101
		if (Filesystem::isValidPath($path)
1102
			and !Filesystem::isFileBlacklisted($path)
1103
		) {
1104
			$path = $this->getRelativePath($absolutePath);
1105
			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...
1106
				return false;
1107
			}
1108
1109
			if (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
1110
				// always a shared lock during pre-hooks so the hook can read the file
1111
				$this->lockFile($path, ILockingProvider::LOCK_SHARED);
1112
			}
1113
1114
			$run = $this->runHooks($hooks, $path);
1115
			/** @var \OC\Files\Storage\Storage $storage */
1116
			list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
1117
			if ($run and $storage) {
1118
				if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1119
					$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
1120
				}
1121
				try {
1122
					if (!is_null($extraParam)) {
1123
						$result = $storage->$operation($internalPath, $extraParam);
1124
					} else {
1125
						$result = $storage->$operation($internalPath);
1126
					}
1127
				} catch (\Exception $e) {
1128 View Code Duplication
					if (in_array('write', $hooks) || in_array('delete', $hooks)) {
1129
						$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1130
					} else if (in_array('read', $hooks)) {
1131
						$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1132
					}
1133
					throw $e;
1134
				}
1135
1136
				if ($result && in_array('delete', $hooks) and $result) {
1137
					$this->removeUpdate($storage, $internalPath);
1138
				}
1139
				if ($result && in_array('write', $hooks) and $operation !== 'fopen') {
1140
					$this->writeUpdate($storage, $internalPath);
1141
				}
1142
				if ($result && in_array('touch', $hooks)) {
1143
					$this->writeUpdate($storage, $internalPath, $extraParam);
1144
				}
1145
1146
				if ((in_array('write', $hooks) || in_array('delete', $hooks)) && ($operation !== 'fopen' || $result === false)) {
1147
					$this->changeLock($path, ILockingProvider::LOCK_SHARED);
1148
				}
1149
1150
				$unlockLater = false;
1151
				if ($this->lockingEnabled && $operation === 'fopen' && is_resource($result)) {
1152
					$unlockLater = true;
1153
					// make sure our unlocking callback will still be called if connection is aborted
1154
					ignore_user_abort(true);
1155
					$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
1156 View Code Duplication
						if (in_array('write', $hooks)) {
1157
							$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
1158
						} else if (in_array('read', $hooks)) {
1159
							$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1160
						}
1161
					});
1162
				}
1163
1164
				if ($this->shouldEmitHooks($path) && $result !== false) {
1165
					if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
1166
						$this->runHooks($hooks, $path, true);
1167
					}
1168
				}
1169
1170 View Code Duplication
				if (!$unlockLater
1171
					&& (in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks))
1172
				) {
1173
					$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1174
				}
1175
				return $result;
1176
			} else {
1177
				$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
1178
			}
1179
		}
1180
		return null;
1181
	}
1182
1183
	/**
1184
	 * get the path relative to the default root for hook usage
1185
	 *
1186
	 * @param string $path
1187
	 * @return string
1188
	 */
1189
	private function getHookPath($path) {
1190
		if (!Filesystem::getView()) {
1191
			return $path;
1192
		}
1193
		return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
1194
	}
1195
1196
	private function shouldEmitHooks($path = '') {
1197
		if ($path && Cache\Scanner::isPartialFile($path)) {
1198
			return false;
1199
		}
1200
		if (!Filesystem::$loaded) {
1201
			return false;
1202
		}
1203
		$defaultRoot = Filesystem::getRoot();
1204
		if ($defaultRoot === null) {
1205
			return false;
1206
		}
1207
		if ($this->fakeRoot === $defaultRoot) {
1208
			return true;
1209
		}
1210
		$fullPath = $this->getAbsolutePath($path);
1211
1212
		if ($fullPath === $defaultRoot) {
1213
			return true;
1214
		}
1215
1216
		return (strlen($fullPath) > strlen($defaultRoot)) && (substr($fullPath, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
1217
	}
1218
1219
	/**
1220
	 * @param string[] $hooks
1221
	 * @param string $path
1222
	 * @param bool $post
1223
	 * @return bool
1224
	 */
1225
	private function runHooks($hooks, $path, $post = false) {
1226
		$relativePath = $path;
1227
		$path = $this->getHookPath($path);
1228
		$prefix = ($post) ? 'post_' : '';
1229
		$run = true;
1230
		if ($this->shouldEmitHooks($relativePath)) {
1231
			foreach ($hooks as $hook) {
1232
				if ($hook != 'read') {
1233
					\OC_Hook::emit(
1234
						Filesystem::CLASSNAME,
1235
						$prefix . $hook,
1236
						array(
1237
							Filesystem::signal_param_run => &$run,
1238
							Filesystem::signal_param_path => $path
1239
						)
1240
					);
1241
				} elseif (!$post) {
1242
					\OC_Hook::emit(
1243
						Filesystem::CLASSNAME,
1244
						$prefix . $hook,
1245
						array(
1246
							Filesystem::signal_param_path => $path
1247
						)
1248
					);
1249
				}
1250
			}
1251
		}
1252
		return $run;
1253
	}
1254
1255
	/**
1256
	 * check if a file or folder has been updated since $time
1257
	 *
1258
	 * @param string $path
1259
	 * @param int $time
1260
	 * @return bool
1261
	 */
1262
	public function hasUpdated($path, $time) {
1263
		return $this->basicOperation('hasUpdated', $path, array(), $time);
1264
	}
1265
1266
	/**
1267
	 * @param string $ownerId
1268
	 * @return \OC\User\User
1269
	 */
1270
	private function getUserObjectForOwner($ownerId) {
1271
		$owner = $this->userManager->get($ownerId);
1272
		if ($owner instanceof IUser) {
1273
			return $owner;
1274
		} else {
1275
			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...
1276
		}
1277
	}
1278
1279
	/**
1280
	 * Get file info from cache
1281
	 *
1282
	 * If the file is not in cached it will be scanned
1283
	 * If the file has changed on storage the cache will be updated
1284
	 *
1285
	 * @param \OC\Files\Storage\Storage $storage
1286
	 * @param string $internalPath
1287
	 * @param string $relativePath
1288
	 * @return array|bool
1289
	 */
1290
	private function getCacheEntry($storage, $internalPath, $relativePath) {
1291
		$cache = $storage->getCache($internalPath);
1292
		$data = $cache->get($internalPath);
1293
		$watcher = $storage->getWatcher($internalPath);
1294
1295
		try {
1296
			// if the file is not in the cache or needs to be updated, trigger the scanner and reload the data
1297
			if (!$data || $data['size'] === -1) {
1298
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1299
				if (!$storage->file_exists($internalPath)) {
1300
					$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1301
					return false;
1302
				}
1303
				$scanner = $storage->getScanner($internalPath);
1304
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1305
				$data = $cache->get($internalPath);
1306
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1307
			} else if (!Cache\Scanner::isPartialFile($internalPath) && $watcher->needsUpdate($internalPath, $data)) {
1308
				$this->lockFile($relativePath, ILockingProvider::LOCK_SHARED);
1309
				$watcher->update($internalPath, $data);
1310
				$storage->getPropagator()->propagateChange($internalPath, time());
1311
				$data = $cache->get($internalPath);
1312
				$this->unlockFile($relativePath, ILockingProvider::LOCK_SHARED);
1313
			}
1314
		} catch (LockedException $e) {
1315
			// if the file is locked we just use the old cache info
1316
		}
1317
1318
		return $data;
1319
	}
1320
1321
	/**
1322
	 * get the filesystem info
1323
	 *
1324
	 * @param string $path
1325
	 * @param boolean|string $includeMountPoints true to add mountpoint sizes,
1326
	 * 'ext' to add only ext storage mount point sizes. Defaults to true.
1327
	 * defaults to true
1328
	 * @return \OC\Files\FileInfo|false False if file does not exist
1329
	 */
1330
	public function getFileInfo($path, $includeMountPoints = true) {
1331
		$this->assertPathLength($path);
1332
		if (!Filesystem::isValidPath($path)) {
1333
			return false;
1334
		}
1335
		if (Cache\Scanner::isPartialFile($path)) {
1336
			return $this->getPartFileInfo($path);
1337
		}
1338
		$relativePath = $path;
1339
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1340
1341
		$mount = Filesystem::getMountManager()->find($path);
1342
		$storage = $mount->getStorage();
1343
		$internalPath = $mount->getInternalPath($path);
1344
		if ($storage) {
1345
			$data = $this->getCacheEntry($storage, $internalPath, $relativePath);
1346
1347
			if (!$data instanceof ICacheEntry) {
1348
				return false;
1349
			}
1350
1351
			if ($mount instanceof MoveableMount && $internalPath === '') {
1352
				$data['permissions'] |= \OCP\Constants::PERMISSION_DELETE;
1353
			}
1354
1355
			$owner = $this->getUserObjectForOwner($storage->getOwner($internalPath));
1356
			$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 1341 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...
1357
1358
			if ($data and isset($data['fileid'])) {
1359
				if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
1360
					//add the sizes of other mount points to the folder
1361
					$extOnly = ($includeMountPoints === 'ext');
1362
					$mounts = Filesystem::getMountManager()->findIn($path);
1363
					$info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) {
1364
						$subStorage = $mount->getStorage();
1365
						return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage);
1366
					}));
1367
				}
1368
			}
1369
1370
			return $info;
1371
		}
1372
1373
		return false;
1374
	}
1375
1376
	/**
1377
	 * get the content of a directory
1378
	 *
1379
	 * @param string $directory path under datadirectory
1380
	 * @param string $mimetype_filter limit returned content to this mimetype or mimepart
1381
	 * @return FileInfo[]
1382
	 */
1383
	public function getDirectoryContent($directory, $mimetype_filter = '') {
1384
		$this->assertPathLength($directory);
1385
		if (!Filesystem::isValidPath($directory)) {
1386
			return [];
1387
		}
1388
		$path = $this->getAbsolutePath($directory);
1389
		$path = Filesystem::normalizePath($path);
1390
		$mount = $this->getMount($directory);
1391
		$storage = $mount->getStorage();
1392
		$internalPath = $mount->getInternalPath($path);
1393
		if ($storage) {
1394
			$cache = $storage->getCache($internalPath);
1395
			$user = \OC_User::getUser();
1396
1397
			$data = $this->getCacheEntry($storage, $internalPath, $directory);
1398
1399
			if (!$data instanceof ICacheEntry || !isset($data['fileid']) || !($data->getPermissions() && Constants::PERMISSION_READ)) {
1400
				return [];
1401
			}
1402
1403
			$folderId = $data['fileid'];
1404
			$contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter
1405
1406
			$sharingDisabled = \OCP\Util::isSharingDisabledForUser();
0 ignored issues
show
Deprecated Code introduced by
The method OCP\Util::isSharingDisabledForUser() has been deprecated with message: 9.1.0 Use \OC::$server->getShareManager()->sharingDisabledForUser

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

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

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

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

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

Loading history...
1482
								$rootEntry['permissions'] = $rootEntry['permissions'] & ~\OCP\Constants::PERMISSION_SHARE;
1483
							}
1484
1485
							$owner = $this->getUserObjectForOwner($subStorage->getOwner(''));
1486
							$files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner);
1487
						}
1488
					}
1489
				}
1490
			}
1491
1492
			if ($mimetype_filter) {
1493
				$files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) {
1494
					if (strpos($mimetype_filter, '/')) {
1495
						return $file->getMimetype() === $mimetype_filter;
1496
					} else {
1497
						return $file->getMimePart() === $mimetype_filter;
1498
					}
1499
				});
1500
			}
1501
1502
			return $files;
1503
		} else {
1504
			return [];
1505
		}
1506
	}
1507
1508
	/**
1509
	 * change file metadata
1510
	 *
1511
	 * @param string $path
1512
	 * @param array|\OCP\Files\FileInfo $data
1513
	 * @return int
1514
	 *
1515
	 * returns the fileid of the updated file
1516
	 */
1517
	public function putFileInfo($path, $data) {
1518
		$this->assertPathLength($path);
1519
		if ($data instanceof FileInfo) {
1520
			$data = $data->getData();
1521
		}
1522
		$path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
1523
		/**
1524
		 * @var \OC\Files\Storage\Storage $storage
1525
		 * @var string $internalPath
1526
		 */
1527
		list($storage, $internalPath) = Filesystem::resolvePath($path);
1528
		if ($storage) {
1529
			$cache = $storage->getCache($path);
1530
1531
			if (!$cache->inCache($internalPath)) {
1532
				$scanner = $storage->getScanner($internalPath);
1533
				$scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
1534
			}
1535
1536
			return $cache->put($internalPath, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 1517 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...
1537
		} else {
1538
			return -1;
1539
		}
1540
	}
1541
1542
	/**
1543
	 * search for files with the name matching $query
1544
	 *
1545
	 * @param string $query
1546
	 * @return FileInfo[]
1547
	 */
1548
	public function search($query) {
1549
		return $this->searchCommon('search', array('%' . $query . '%'));
1550
	}
1551
1552
	/**
1553
	 * search for files with the name matching $query
1554
	 *
1555
	 * @param string $query
1556
	 * @return FileInfo[]
1557
	 */
1558
	public function searchRaw($query) {
1559
		return $this->searchCommon('search', array($query));
1560
	}
1561
1562
	/**
1563
	 * search for files by mimetype
1564
	 *
1565
	 * @param string $mimetype
1566
	 * @return FileInfo[]
1567
	 */
1568
	public function searchByMime($mimetype) {
1569
		return $this->searchCommon('searchByMime', array($mimetype));
1570
	}
1571
1572
	/**
1573
	 * search for files by tag
1574
	 *
1575
	 * @param string|int $tag name or tag id
1576
	 * @param string $userId owner of the tags
1577
	 * @return FileInfo[]
1578
	 */
1579
	public function searchByTag($tag, $userId) {
1580
		return $this->searchCommon('searchByTag', array($tag, $userId));
1581
	}
1582
1583
	/**
1584
	 * @param string $method cache method
1585
	 * @param array $args
1586
	 * @return FileInfo[]
1587
	 */
1588
	private function searchCommon($method, $args) {
1589
		$files = array();
1590
		$rootLength = strlen($this->fakeRoot);
1591
1592
		$mount = $this->getMount('');
1593
		$mountPoint = $mount->getMountPoint();
1594
		$storage = $mount->getStorage();
1595
		if ($storage) {
1596
			$cache = $storage->getCache('');
1597
1598
			$results = call_user_func_array(array($cache, $method), $args);
1599
			foreach ($results as $result) {
1600
				if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
1601
					$internalPath = $result['path'];
1602
					$path = $mountPoint . $result['path'];
1603
					$result['path'] = substr($mountPoint . $result['path'], $rootLength);
1604
					$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1605
					$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 1592 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...
1606
				}
1607
			}
1608
1609
			$mounts = Filesystem::getMountManager()->findIn($this->fakeRoot);
1610
			foreach ($mounts as $mount) {
1611
				$mountPoint = $mount->getMountPoint();
1612
				$storage = $mount->getStorage();
1613
				if ($storage) {
1614
					$cache = $storage->getCache('');
1615
1616
					$relativeMountPoint = substr($mountPoint, $rootLength);
1617
					$results = call_user_func_array(array($cache, $method), $args);
1618
					if ($results) {
1619
						foreach ($results as $result) {
1620
							$internalPath = $result['path'];
1621
							$result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
1622
							$path = rtrim($mountPoint . $internalPath, '/');
1623
							$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1624
							$files[] = new FileInfo($path, $storage, $internalPath, $result, $mount, $owner);
1625
						}
1626
					}
1627
				}
1628
			}
1629
		}
1630
		return $files;
1631
	}
1632
1633
	/**
1634
	 * Get the owner for a file or folder
1635
	 *
1636
	 * @param string $path
1637
	 * @return string the user id of the owner
1638
	 * @throws NotFoundException
1639
	 */
1640
	public function getOwner($path) {
1641
		$info = $this->getFileInfo($path);
1642
		if (!$info) {
1643
			throw new NotFoundException($path . ' not found while trying to get owner');
1644
		}
1645
		return $info->getOwner()->getUID();
1646
	}
1647
1648
	/**
1649
	 * get the ETag for a file or folder
1650
	 *
1651
	 * @param string $path
1652
	 * @return string
1653
	 */
1654
	public function getETag($path) {
1655
		/**
1656
		 * @var Storage\Storage $storage
1657
		 * @var string $internalPath
1658
		 */
1659
		list($storage, $internalPath) = $this->resolvePath($path);
1660
		if ($storage) {
1661
			return $storage->getETag($internalPath);
1662
		} else {
1663
			return null;
1664
		}
1665
	}
1666
1667
	/**
1668
	 * Get the path of a file by id, relative to the view
1669
	 *
1670
	 * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
1671
	 *
1672
	 * @param int $id
1673
	 * @throws NotFoundException
1674
	 * @return string
1675
	 */
1676
	public function getPath($id) {
1677
		$id = (int)$id;
1678
		$manager = Filesystem::getMountManager();
1679
		$mounts = $manager->findIn($this->fakeRoot);
1680
		$mounts[] = $manager->find($this->fakeRoot);
1681
		// reverse the array so we start with the storage this view is in
1682
		// which is the most likely to contain the file we're looking for
1683
		$mounts = array_reverse($mounts);
1684
		foreach ($mounts as $mount) {
1685
			/**
1686
			 * @var \OC\Files\Mount\MountPoint $mount
1687
			 */
1688
			if ($mount->getStorage()) {
1689
				$cache = $mount->getStorage()->getCache();
1690
				$internalPath = $cache->getPathById($id);
1691
				if (is_string($internalPath)) {
1692
					$fullPath = $mount->getMountPoint() . $internalPath;
1693
					if (!is_null($path = $this->getRelativePath($fullPath))) {
1694
						return $path;
1695
					}
1696
				}
1697
			}
1698
		}
1699
		throw new NotFoundException(sprintf('File with id "%s" has not been found.', $id));
1700
	}
1701
1702
	/**
1703
	 * @param string $path
1704
	 * @throws InvalidPathException
1705
	 */
1706
	private function assertPathLength($path) {
1707
		$maxLen = min(PHP_MAXPATHLEN, 4000);
1708
		// Check for the string length - performed using isset() instead of strlen()
1709
		// because isset() is about 5x-40x faster.
1710
		if (isset($path[$maxLen])) {
1711
			$pathLen = strlen($path);
1712
			throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
1713
		}
1714
	}
1715
1716
	/**
1717
	 * check if it is allowed to move a mount point to a given target.
1718
	 * It is not allowed to move a mount point into a different mount point or
1719
	 * into an already shared folder
1720
	 *
1721
	 * @param string $target path
1722
	 * @return boolean
1723
	 */
1724
	private function isTargetAllowed($target) {
1725
1726
		list($targetStorage, $targetInternalPath) = \OC\Files\Filesystem::resolvePath($target);
1727
		if (!$targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
1728
			\OCP\Util::writeLog('files',
1729
				'It is not allowed to move one mount point into another one',
1730
				\OCP\Util::DEBUG);
1731
			return false;
1732
		}
1733
1734
		// note: cannot use the view because the target is already locked
1735
		$fileId = (int)$targetStorage->getCache()->getId($targetInternalPath);
1736
		if ($fileId === -1) {
1737
			// target might not exist, need to check parent instead
1738
			$fileId = (int)$targetStorage->getCache()->getId(dirname($targetInternalPath));
1739
		}
1740
1741
		// check if any of the parents were shared by the current owner (include collections)
1742
		$shares = \OCP\Share::getItemShared(
1743
			'folder',
1744
			$fileId,
1745
			\OCP\Share::FORMAT_NONE,
1746
			null,
1747
			true
1748
		);
1749
1750
		if (count($shares) > 0) {
1751
			\OCP\Util::writeLog('files',
1752
				'It is not allowed to move one mount point into a shared folder',
1753
				\OCP\Util::DEBUG);
1754
			return false;
1755
		}
1756
1757
		return true;
1758
	}
1759
1760
	/**
1761
	 * Get a fileinfo object for files that are ignored in the cache (part files)
1762
	 *
1763
	 * @param string $path
1764
	 * @return \OCP\Files\FileInfo
1765
	 */
1766
	private function getPartFileInfo($path) {
1767
		$mount = $this->getMount($path);
1768
		$storage = $mount->getStorage();
1769
		$internalPath = $mount->getInternalPath($this->getAbsolutePath($path));
1770
		$owner = \OC::$server->getUserManager()->get($storage->getOwner($internalPath));
1771
		return new FileInfo(
1772
			$this->getAbsolutePath($path),
1773
			$storage,
1774
			$internalPath,
1775
			[
1776
				'fileid' => null,
1777
				'mimetype' => $storage->getMimeType($internalPath),
1778
				'name' => basename($path),
1779
				'etag' => null,
1780
				'size' => $storage->filesize($internalPath),
1781
				'mtime' => $storage->filemtime($internalPath),
1782
				'encrypted' => false,
1783
				'permissions' => \OCP\Constants::PERMISSION_ALL
1784
			],
1785
			$mount,
0 ignored issues
show
Bug introduced by
It seems like $mount defined by $this->getMount($path) on line 1767 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...
1786
			$owner
1787
		);
1788
	}
1789
1790
	/**
1791
	 * @param string $path
1792
	 * @param string $fileName
1793
	 * @throws InvalidPathException
1794
	 */
1795
	public function verifyPath($path, $fileName) {
1796
		try {
1797
			/** @type \OCP\Files\Storage $storage */
1798
			list($storage, $internalPath) = $this->resolvePath($path);
1799
			$storage->verifyPath($internalPath, $fileName);
1800
		} catch (ReservedWordException $ex) {
1801
			$l = \OC::$server->getL10N('lib');
1802
			throw new InvalidPathException($l->t('File name is a reserved word'));
1803
		} catch (InvalidCharacterInPathException $ex) {
1804
			$l = \OC::$server->getL10N('lib');
1805
			throw new InvalidPathException($l->t('File name contains at least one invalid character'));
1806
		} catch (FileNameTooLongException $ex) {
1807
			$l = \OC::$server->getL10N('lib');
1808
			throw new InvalidPathException($l->t('File name is too long'));
1809
		} catch (InvalidDirectoryException $ex) {
1810
			$l = \OC::$server->getL10N('lib');
1811
			throw new InvalidPathException($l->t('Dot files are not allowed'));
1812
		} catch (EmptyFileNameException $ex) {
1813
			$l = \OC::$server->getL10N('lib');
1814
			throw new InvalidPathException($l->t('Empty filename is not allowed'));
1815
		}
1816
	}
1817
1818
	/**
1819
	 * get all parent folders of $path
1820
	 *
1821
	 * @param string $path
1822
	 * @return string[]
1823
	 */
1824
	private function getParents($path) {
1825
		$path = trim($path, '/');
1826
		if (!$path) {
1827
			return [];
1828
		}
1829
1830
		$parts = explode('/', $path);
1831
1832
		// remove the single file
1833
		array_pop($parts);
1834
		$result = array('/');
1835
		$resultPath = '';
1836
		foreach ($parts as $part) {
1837
			if ($part) {
1838
				$resultPath .= '/' . $part;
1839
				$result[] = $resultPath;
1840
			}
1841
		}
1842
		return $result;
1843
	}
1844
1845
	/**
1846
	 * Returns the mount point for which to lock
1847
	 *
1848
	 * @param string $absolutePath absolute path
1849
	 * @param bool $useParentMount true to return parent mount instead of whatever
1850
	 * is mounted directly on the given path, false otherwise
1851
	 * @return \OC\Files\Mount\MountPoint mount point for which to apply locks
1852
	 */
1853
	private function getMountForLock($absolutePath, $useParentMount = false) {
1854
		$results = [];
1855
		$mount = Filesystem::getMountManager()->find($absolutePath);
1856
		if (!$mount) {
1857
			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...
1858
		}
1859
1860
		if ($useParentMount) {
1861
			// find out if something is mounted directly on the path
1862
			$internalPath = $mount->getInternalPath($absolutePath);
1863
			if ($internalPath === '') {
1864
				// resolve the parent mount instead
1865
				$mount = Filesystem::getMountManager()->find(dirname($absolutePath));
1866
			}
1867
		}
1868
1869
		return $mount;
1870
	}
1871
1872
	/**
1873
	 * Lock the given path
1874
	 *
1875
	 * @param string $path the path of the file to lock, relative to the view
1876
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1877
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1878
	 *
1879
	 * @return bool False if the path is excluded from locking, true otherwise
1880
	 * @throws \OCP\Lock\LockedException if the path is already locked
1881
	 */
1882 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...
1883
		$absolutePath = $this->getAbsolutePath($path);
1884
		$absolutePath = Filesystem::normalizePath($absolutePath);
1885
		if (!$this->shouldLockFile($absolutePath)) {
1886
			return false;
1887
		}
1888
1889
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1890
		if ($mount) {
1891
			try {
1892
				$storage = $mount->getStorage();
1893
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1894
					$storage->acquireLock(
1895
						$mount->getInternalPath($absolutePath),
1896
						$type,
1897
						$this->lockingProvider
1898
					);
1899
				}
1900
			} catch (\OCP\Lock\LockedException $e) {
1901
				// rethrow with the a human-readable path
1902
				throw new \OCP\Lock\LockedException(
1903
					$this->getPathRelativeToFiles($absolutePath),
1904
					$e
1905
				);
1906
			}
1907
		}
1908
1909
		return true;
1910
	}
1911
1912
	/**
1913
	 * Change the lock type
1914
	 *
1915
	 * @param string $path the path of the file to lock, relative to the view
1916
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1917
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1918
	 *
1919
	 * @return bool False if the path is excluded from locking, true otherwise
1920
	 * @throws \OCP\Lock\LockedException if the path is already locked
1921
	 */
1922 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...
1923
		$path = Filesystem::normalizePath($path);
1924
		$absolutePath = $this->getAbsolutePath($path);
1925
		$absolutePath = Filesystem::normalizePath($absolutePath);
1926
		if (!$this->shouldLockFile($absolutePath)) {
1927
			return false;
1928
		}
1929
1930
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1931
		if ($mount) {
1932
			try {
1933
				$storage = $mount->getStorage();
1934
				if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1935
					$storage->changeLock(
1936
						$mount->getInternalPath($absolutePath),
1937
						$type,
1938
						$this->lockingProvider
1939
					);
1940
				}
1941
			} catch (\OCP\Lock\LockedException $e) {
1942
				// rethrow with the a human-readable path
1943
				throw new \OCP\Lock\LockedException(
1944
					$this->getPathRelativeToFiles($absolutePath),
1945
					$e
1946
				);
1947
			}
1948
		}
1949
1950
		return true;
1951
	}
1952
1953
	/**
1954
	 * Unlock the given path
1955
	 *
1956
	 * @param string $path the path of the file to unlock, relative to the view
1957
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1958
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1959
	 *
1960
	 * @return bool False if the path is excluded from locking, true otherwise
1961
	 */
1962
	private function unlockPath($path, $type, $lockMountPoint = false) {
1963
		$absolutePath = $this->getAbsolutePath($path);
1964
		$absolutePath = Filesystem::normalizePath($absolutePath);
1965
		if (!$this->shouldLockFile($absolutePath)) {
1966
			return false;
1967
		}
1968
1969
		$mount = $this->getMountForLock($absolutePath, $lockMountPoint);
1970
		if ($mount) {
1971
			$storage = $mount->getStorage();
1972
			if ($storage && $storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) {
1973
				$storage->releaseLock(
1974
					$mount->getInternalPath($absolutePath),
1975
					$type,
1976
					$this->lockingProvider
1977
				);
1978
			}
1979
		}
1980
1981
		return true;
1982
	}
1983
1984
	/**
1985
	 * Lock a path and all its parents up to the root of the view
1986
	 *
1987
	 * @param string $path the path of the file to lock relative to the view
1988
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
1989
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
1990
	 *
1991
	 * @return bool False if the path is excluded from locking, true otherwise
1992
	 */
1993 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...
1994
		$absolutePath = $this->getAbsolutePath($path);
1995
		$absolutePath = Filesystem::normalizePath($absolutePath);
1996
		if (!$this->shouldLockFile($absolutePath)) {
1997
			return false;
1998
		}
1999
2000
		$this->lockPath($path, $type, $lockMountPoint);
2001
2002
		$parents = $this->getParents($path);
2003
		foreach ($parents as $parent) {
2004
			$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
2005
		}
2006
2007
		return true;
2008
	}
2009
2010
	/**
2011
	 * Unlock a path and all its parents up to the root of the view
2012
	 *
2013
	 * @param string $path the path of the file to lock relative to the view
2014
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
2015
	 * @param bool $lockMountPoint true to lock the mount point, false to lock the attached mount/storage
2016
	 *
2017
	 * @return bool False if the path is excluded from locking, true otherwise
2018
	 */
2019 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...
2020
		$absolutePath = $this->getAbsolutePath($path);
2021
		$absolutePath = Filesystem::normalizePath($absolutePath);
2022
		if (!$this->shouldLockFile($absolutePath)) {
2023
			return false;
2024
		}
2025
2026
		$this->unlockPath($path, $type, $lockMountPoint);
2027
2028
		$parents = $this->getParents($path);
2029
		foreach ($parents as $parent) {
2030
			$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
2031
		}
2032
2033
		return true;
2034
	}
2035
2036
	/**
2037
	 * Only lock files in data/user/files/
2038
	 *
2039
	 * @param string $path Absolute path to the file/folder we try to (un)lock
2040
	 * @return bool
2041
	 */
2042
	protected function shouldLockFile($path) {
2043
		$path = Filesystem::normalizePath($path);
2044
2045
		$pathSegments = explode('/', $path);
2046
		if (isset($pathSegments[2])) {
2047
			// E.g.: /username/files/path-to-file
2048
			return ($pathSegments[2] === 'files') && (count($pathSegments) > 3);
2049
		}
2050
2051
		return true;
2052
	}
2053
2054
	/**
2055
	 * Shortens the given absolute path to be relative to
2056
	 * "$user/files".
2057
	 *
2058
	 * @param string $absolutePath absolute path which is under "files"
2059
	 *
2060
	 * @return string path relative to "files" with trimmed slashes or null
2061
	 * if the path was NOT relative to files
2062
	 *
2063
	 * @throws \InvalidArgumentException if the given path was not under "files"
2064
	 * @since 8.1.0
2065
	 */
2066
	public function getPathRelativeToFiles($absolutePath) {
2067
		$path = Filesystem::normalizePath($absolutePath);
2068
		$parts = explode('/', trim($path, '/'), 3);
2069
		// "$user", "files", "path/to/dir"
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2070
		if (!isset($parts[1]) || $parts[1] !== 'files') {
2071
			throw new \InvalidArgumentException('$absolutePath must be relative to "files"');
2072
		}
2073
		if (isset($parts[2])) {
2074
			return $parts[2];
2075
		}
2076
		return '';
2077
	}
2078
2079
	/**
2080
	 * @param string $filename
2081
	 * @return array
2082
	 * @throws \OC\User\NoUserException
2083
	 * @throws NotFoundException
2084
	 */
2085
	public function getUidAndFilename($filename) {
2086
		$info = $this->getFileInfo($filename);
2087
		if (!$info instanceof \OCP\Files\FileInfo) {
2088
			throw new NotFoundException($this->getAbsolutePath($filename) . ' not found');
2089
		}
2090
		$uid = $info->getOwner()->getUID();
2091
		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...
2092
			Filesystem::initMountPoints($uid);
2093
			$ownerView = new View('/' . $uid . '/files');
2094
			try {
2095
				$filename = $ownerView->getPath($info['fileid']);
2096
			} catch (NotFoundException $e) {
2097
				throw new NotFoundException('File with id ' . $info['fileid'] . ' not found for user ' . $uid);
2098
			}
2099
		}
2100
		return [$uid, $filename];
2101
	}
2102
2103
	/**
2104
	 * Creates parent non-existing folders
2105
	 *
2106
	 * @param string $filePath
2107
	 * @return bool
2108
	 */
2109
	private function createParentDirectories($filePath) {
2110
		$directoryParts = explode('/', $filePath);
2111
		$directoryParts = array_filter($directoryParts);
2112
		foreach ($directoryParts as $key => $part) {
2113
			$currentPathElements = array_slice($directoryParts, 0, $key);
2114
			$currentPath = '/' . implode('/', $currentPathElements);
2115
			if ($this->is_file($currentPath)) {
2116
				return false;
2117
			}
2118
			if (!$this->file_exists($currentPath)) {
2119
				$this->mkdir($currentPath);
2120
			}
2121
		}
2122
2123
		return true;
2124
	}
2125
}
2126