Completed
Push — master ( d8f554...06141b )
by Joas
17:58
created

SharedStorage::fopen()   F

Complexity

Conditions 25
Paths 198

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 25
nc 198
nop 2
dl 0
loc 53
rs 3.35
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Michael Gapczynski <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Robin McCorkell <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 * @author scambra <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 * @author Vincent Petry <[email protected]>
16
 *
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OCA\Files_Sharing;
34
35
use OC\Files\Cache\FailedCache;
36
use OC\Files\Filesystem;
37
use OC\Files\Storage\Wrapper\PermissionsMask;
38
use OC\Files\Storage\FailedStorage;
39
use OCP\Constants;
40
use OCP\Files\Cache\ICacheEntry;
41
use OCP\Files\NotFoundException;
42
use OCP\Files\Storage\IStorage;
43
use OCP\Lock\ILockingProvider;
44
use OC\User\NoUserException;
45
46
/**
47
 * Convert target path to source path and pass the function call to the correct storage provider
48
 */
49
class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
50
51
	/** @var \OCP\Share\IShare */
52
	private $superShare;
53
54
	/** @var \OCP\Share\IShare[] */
55
	private $groupedShares;
56
57
	/**
58
	 * @var \OC\Files\View
59
	 */
60
	private $ownerView;
61
62
	private $initialized = false;
63
64
	/**
65
	 * @var ICacheEntry
66
	 */
67
	private $sourceRootInfo;
68
69
	/** @var string */
70
	private $user;
71
72
	/**
73
	 * @var \OCP\ILogger
74
	 */
75
	private $logger;
76
77
	/** @var  IStorage */
78
	private $nonMaskedStorage;
79
80
	private $options;
81
82
	/** @var boolean */
83
	private $sharingDisabledForUser;
84
85
	public function __construct($arguments) {
86
		$this->ownerView = $arguments['ownerView'];
87
		$this->logger = \OC::$server->getLogger();
88
89
		$this->superShare = $arguments['superShare'];
90
		$this->groupedShares = $arguments['groupedShares'];
91
92
		$this->user = $arguments['user'];
93
		if (isset($arguments['sharingDisabledForUser'])) {
94
			$this->sharingDisabledForUser = $arguments['sharingDisabledForUser'];
95
		} else {
96
			$this->sharingDisabledForUser = false;
97
		}
98
99
		parent::__construct([
100
			'storage' => null,
101
			'root' => null,
102
		]);
103
	}
104
105
	/**
106
	 * @return ICacheEntry
107
	 */
108
	private function getSourceRootInfo() {
109
		if (is_null($this->sourceRootInfo)) {
110
			if (is_null($this->superShare->getNodeCacheEntry())) {
111
				$this->init();
112
				$this->sourceRootInfo = $this->nonMaskedStorage->getCache()->get($this->rootPath);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->nonMaskedStorage-...)->get($this->rootPath) can also be of type false. However, the property $sourceRootInfo is declared as type object<OCP\Files\Cache\ICacheEntry>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
113
			} else {
114
				$this->sourceRootInfo = $this->superShare->getNodeCacheEntry();
115
			}
116
		}
117
		return $this->sourceRootInfo;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->sourceRootInfo; of type OCP\Files\Cache\ICacheEntry|false adds false to the return on line 117 which is incompatible with the return type documented by OCA\Files_Sharing\SharedStorage::getSourceRootInfo of type OCP\Files\Cache\ICacheEntry. It seems like you forgot to handle an error condition.
Loading history...
118
	}
119
120
	private function init() {
121
		if ($this->initialized) {
122
			return;
123
		}
124
		$this->initialized = true;
125
		try {
126
			Filesystem::initMountPoints($this->superShare->getShareOwner());
127
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
128
			list($this->nonMaskedStorage, $this->rootPath) = $this->ownerView->resolvePath($sourcePath);
129
			$this->storage = new PermissionsMask([
130
				'storage' => $this->nonMaskedStorage,
131
				'mask' => $this->superShare->getPermissions()
132
			]);
133
		} catch (NotFoundException $e) {
134
			// original file not accessible or deleted, set FailedStorage
135
			$this->storage = new FailedStorage(['exception' => $e]);
136
			$this->cache = new FailedCache();
137
			$this->rootPath = '';
138
		} catch (NoUserException $e) {
139
			// sharer user deleted, set FailedStorage
140
			$this->storage = new FailedStorage(['exception' => $e]);
141
			$this->cache = new FailedCache();
142
			$this->rootPath = '';
143
		} catch (\Exception $e) {
144
			$this->storage = new FailedStorage(['exception' => $e]);
145
			$this->cache = new FailedCache();
146
			$this->rootPath = '';
147
			$this->logger->logException($e);
0 ignored issues
show
Documentation introduced by
$e is of type object<Exception>, but the function expects a object<Throwable>.

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...
148
		}
149
150
		if (!$this->nonMaskedStorage) {
151
			$this->nonMaskedStorage = $this->storage;
152
		}
153
	}
154
155
	/**
156
	 * @inheritdoc
157
	 */
158
	public function instanceOfStorage($class) {
159
		if ($class === '\OC\Files\Storage\Common') {
160
			return true;
161
		}
162
		if (in_array($class, ['\OC\Files\Storage\Home', '\OC\Files\ObjectStore\HomeObjectStoreStorage'])) {
163
			return false;
164
		}
165
		return parent::instanceOfStorage($class);
166
	}
167
168
	/**
169
	 * @return string
170
	 */
171
	public function getShareId() {
172
		return $this->superShare->getId();
173
	}
174
175
	private function isValid() {
176
		return $this->getSourceRootInfo() && ($this->getSourceRootInfo()->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
177
	}
178
179
	/**
180
	 * get id of the mount point
181
	 *
182
	 * @return string
183
	 */
184
	public function getId() {
185
		return 'shared::' . $this->getMountPoint();
186
	}
187
188
	/**
189
	 * Get the permissions granted for a shared file
190
	 *
191
	 * @param string $target Shared target file path
192
	 * @return int CRUDS permissions granted
193
	 */
194
	public function getPermissions($target = '') {
195
		if (!$this->isValid()) {
196
			return 0;
197
		}
198
		$permissions = parent::getPermissions($target) & $this->superShare->getPermissions();
199
200
		// part files and the mount point always have delete permissions
201
		if ($target === '' || pathinfo($target, PATHINFO_EXTENSION) === 'part') {
202
			$permissions |= \OCP\Constants::PERMISSION_DELETE;
203
		}
204
205
		if ($this->sharingDisabledForUser) {
206
			$permissions &= ~\OCP\Constants::PERMISSION_SHARE;
207
		}
208
209
		return $permissions;
210
	}
211
212
	public function isCreatable($path) {
213
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE);
214
	}
215
216
	public function isReadable($path) {
217
		if (!$this->isValid()) {
218
			return false;
219
		}
220
		if (!$this->file_exists($path)) {
221
			return false;
222
		}
223
		/** @var IStorage $storage */
224
		/** @var string $internalPath */
225
		list($storage, $internalPath) = $this->resolvePath($path);
226
		return $storage->isReadable($internalPath);
227
	}
228
229
	public function isUpdatable($path) {
230
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE);
231
	}
232
233
	public function isDeletable($path) {
234
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE);
235
	}
236
237
	public function isSharable($path) {
238
		if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
239
			return false;
240
		}
241
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
242
	}
243
244
	public function fopen($path, $mode) {
245
		if ($source = $this->getUnjailedPath($path)) {
246
			switch ($mode) {
247
				case 'r+':
248
				case 'rb+':
249
				case 'w+':
250
				case 'wb+':
251
				case 'x+':
252
				case 'xb+':
253
				case 'a+':
254
				case 'ab+':
255
				case 'w':
256
				case 'wb':
257
				case 'x':
258
				case 'xb':
259
				case 'a':
260
				case 'ab':
261
					$creatable = $this->isCreatable(dirname($path));
262
					$updatable = $this->isUpdatable($path);
263
					// if neither permissions given, no need to continue
264
					if (!$creatable && !$updatable) {
265
						if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
266
							$updatable = $this->isUpdatable(dirname($path));
267
						}
268
269
						if (!$updatable) {
270
							return false;
271
						}
272
					}
273
274
					$exists = $this->file_exists($path);
275
					// if a file exists, updatable permissions are required
276
					if ($exists && !$updatable) {
277
						return false;
278
					}
279
280
					// part file is allowed if !$creatable but the final file is $updatable
281
					if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
282
						if (!$exists && !$creatable) {
283
							return false;
284
						}
285
					}
286
			}
287
			$info = array(
288
				'target' => $this->getMountPoint() . $path,
289
				'source' => $source,
290
				'mode' => $mode,
291
			);
292
			\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
293
			return $this->nonMaskedStorage->fopen($this->getUnjailedPath($path), $mode);
294
		}
295
		return false;
296
	}
297
298
	/**
299
	 * see http://php.net/manual/en/function.rename.php
300
	 *
301
	 * @param string $path1
302
	 * @param string $path2
303
	 * @return bool
304
	 */
305
	public function rename($path1, $path2) {
306
		$this->init();
307
		$isPartFile = pathinfo($path1, PATHINFO_EXTENSION) === 'part';
308
		$targetExists = $this->file_exists($path2);
309
		$sameFodler = dirname($path1) === dirname($path2);
310
311
		if ($targetExists || ($sameFodler && !$isPartFile)) {
312
			if (!$this->isUpdatable('')) {
313
				return false;
314
			}
315
		} else {
316
			if (!$this->isCreatable('')) {
317
				return false;
318
			}
319
		}
320
321
		return $this->nonMaskedStorage->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
322
	}
323
324
	/**
325
	 * return mount point of share, relative to data/user/files
326
	 *
327
	 * @return string
328
	 */
329
	public function getMountPoint() {
330
		return $this->superShare->getTarget();
331
	}
332
333
	/**
334
	 * @param string $path
335
	 */
336
	public function setMountPoint($path) {
337
		$this->superShare->setTarget($path);
338
339
		foreach ($this->groupedShares as $share) {
340
			$share->setTarget($path);
341
		}
342
	}
343
344
	/**
345
	 * get the user who shared the file
346
	 *
347
	 * @return string
348
	 */
349
	public function getSharedFrom() {
350
		return $this->superShare->getShareOwner();
351
	}
352
353
	/**
354
	 * @return \OCP\Share\IShare
355
	 */
356
	public function getShare() {
357
		return $this->superShare;
358
	}
359
360
	/**
361
	 * return share type, can be "file" or "folder"
362
	 *
363
	 * @return string
364
	 */
365
	public function getItemType() {
366
		return $this->superShare->getNodeType();
367
	}
368
369
	/**
370
	 * @param string $path
371
	 * @param null $storage
372
	 * @return Cache
373
	 */
374
	public function getCache($path = '', $storage = null) {
375
		if ($this->cache) {
376
			return $this->cache;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->cache; (OC\Files\Cache\FailedCache) is incompatible with the return type declared by the interface OC\Files\Storage\Storage::getCache of type OC\Files\Cache\Cache.

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...
377
		}
378
		if (!$storage) {
379
			$storage = $this;
380
		}
381
		if ($this->storage instanceof FailedStorage) {
382
			return new FailedCache();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \OC\Files\Cache\FailedCache(); (OC\Files\Cache\FailedCache) is incompatible with the return type declared by the interface OC\Files\Storage\Storage::getCache of type OC\Files\Cache\Cache.

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...
383
		}
384
		$this->cache = new \OCA\Files_Sharing\Cache($storage, $this->getSourceRootInfo(), $this->superShare);
385
		return $this->cache;
386
	}
387
388
	public function getScanner($path = '', $storage = null) {
389
		if (!$storage) {
390
			$storage = $this;
391
		}
392
		return new \OCA\Files_Sharing\Scanner($storage);
393
	}
394
395
	public function getOwner($path) {
396
		return $this->superShare->getShareOwner();
397
	}
398
399
	/**
400
	 * unshare complete storage, also the grouped shares
401
	 *
402
	 * @return bool
403
	 */
404
	public function unshareStorage() {
405
		foreach ($this->groupedShares as $share) {
406
			\OC::$server->getShareManager()->deleteFromSelf($share, $this->user);
407
		}
408
		return true;
409
	}
410
411
	/**
412
	 * @param string $path
413
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
414
	 * @param \OCP\Lock\ILockingProvider $provider
415
	 * @throws \OCP\Lock\LockedException
416
	 */
417 View Code Duplication
	public function acquireLock($path, $type, ILockingProvider $provider) {
418
		/** @var \OCP\Files\Storage $targetStorage */
419
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
420
		$targetStorage->acquireLock($targetInternalPath, $type, $provider);
421
		// lock the parent folders of the owner when locking the share as recipient
422
		if ($path === '') {
423
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
424
			$this->ownerView->lockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
425
		}
426
	}
427
428
	/**
429
	 * @param string $path
430
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
431
	 * @param \OCP\Lock\ILockingProvider $provider
432
	 */
433 View Code Duplication
	public function releaseLock($path, $type, ILockingProvider $provider) {
434
		/** @var \OCP\Files\Storage $targetStorage */
435
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
436
		$targetStorage->releaseLock($targetInternalPath, $type, $provider);
437
		// unlock the parent folders of the owner when unlocking the share as recipient
438
		if ($path === '') {
439
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
440
			$this->ownerView->unlockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
441
		}
442
	}
443
444
	/**
445
	 * @param string $path
446
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
447
	 * @param \OCP\Lock\ILockingProvider $provider
448
	 */
449
	public function changeLock($path, $type, ILockingProvider $provider) {
450
		/** @var \OCP\Files\Storage $targetStorage */
451
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
452
		$targetStorage->changeLock($targetInternalPath, $type, $provider);
453
	}
454
455
	/**
456
	 * @return array [ available, last_checked ]
457
	 */
458
	public function getAvailability() {
459
		// shares do not participate in availability logic
460
		return [
461
			'available' => true,
462
			'last_checked' => 0
463
		];
464
	}
465
466
	/**
467
	 * @param bool $available
468
	 */
469
	public function setAvailability($available) {
470
		// shares do not participate in availability logic
471
	}
472
473
	public function getSourceStorage() {
474
		$this->init();
475
		return $this->nonMaskedStorage;
476
	}
477
478
	public function getWrapperStorage() {
479
		$this->init();
480
		return $this->storage;
481
	}
482
483 View Code Duplication
	public function file_get_contents($path) {
484
		$info = [
485
			'target' => $this->getMountPoint() . '/' . $path,
486
			'source' => $this->getUnjailedPath($path),
487
		];
488
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
489
		return parent::file_get_contents($path);
490
	}
491
492 View Code Duplication
	public function file_put_contents($path, $data) {
493
		$info = [
494
			'target' => $this->getMountPoint() . '/' . $path,
495
			'source' => $this->getUnjailedPath($path),
496
		];
497
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
498
		return parent::file_put_contents($path, $data);
499
	}
500
501
	public function setMountOptions(array $options) {
502
		$this->mountOptions = $options;
0 ignored issues
show
Bug introduced by
The property mountOptions does not seem to exist. Did you mean options?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
503
	}
504
}
505