Passed
Push — master ( 05edd1...b0c04a )
by Roeland
36:27 queued 22:49
created

SharedStorage::fopen()   F

Complexity

Conditions 24
Paths 197

Size

Total Lines 50
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 24
eloc 35
nc 197
nop 2
dl 0
loc 50
rs 3.3583
c 1
b 0
f 0

How to fix   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\Cache\NullWatcher;
37
use OC\Files\Filesystem;
38
use OC\Files\Storage\FailedStorage;
39
use OC\Files\Storage\Wrapper\PermissionsMask;
40
use OC\User\NoUserException;
41
use OCP\Constants;
42
use OCP\Files\Cache\ICacheEntry;
43
use OCP\Files\NotFoundException;
44
use OCP\Files\Storage\IDisableEncryptionStorage;
45
use OCP\Files\Storage\IStorage;
46
use OCP\Lock\ILockingProvider;
47
48
/**
49
 * Convert target path to source path and pass the function call to the correct storage provider
50
 */
51
class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage, IDisableEncryptionStorage {
52
53
	/** @var \OCP\Share\IShare */
54
	private $superShare;
55
56
	/** @var \OCP\Share\IShare[] */
57
	private $groupedShares;
58
59
	/**
60
	 * @var \OC\Files\View
61
	 */
62
	private $ownerView;
63
64
	private $initialized = false;
65
66
	/**
67
	 * @var ICacheEntry
68
	 */
69
	private $sourceRootInfo;
70
71
	/** @var string */
72
	private $user;
73
74
	/**
75
	 * @var \OCP\ILogger
76
	 */
77
	private $logger;
78
79
	/** @var  IStorage */
80
	private $nonMaskedStorage;
81
82
	private $options;
0 ignored issues
show
introduced by
The private property $options is not used, and could be removed.
Loading history...
83
84
	/** @var boolean */
85
	private $sharingDisabledForUser;
86
87
	public function __construct($arguments) {
88
		$this->ownerView = $arguments['ownerView'];
89
		$this->logger = \OC::$server->getLogger();
90
91
		$this->superShare = $arguments['superShare'];
92
		$this->groupedShares = $arguments['groupedShares'];
93
94
		$this->user = $arguments['user'];
95
		if (isset($arguments['sharingDisabledForUser'])) {
96
			$this->sharingDisabledForUser = $arguments['sharingDisabledForUser'];
97
		} else {
98
			$this->sharingDisabledForUser = false;
99
		}
100
101
		parent::__construct([
102
			'storage' => null,
103
			'root' => null,
104
		]);
105
	}
106
107
	/**
108
	 * @return ICacheEntry
109
	 */
110
	private function getSourceRootInfo() {
111
		if (is_null($this->sourceRootInfo)) {
112
			if (is_null($this->superShare->getNodeCacheEntry())) {
113
				$this->init();
114
				$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 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...
115
			} else {
116
				$this->sourceRootInfo = $this->superShare->getNodeCacheEntry();
117
			}
118
		}
119
		return $this->sourceRootInfo;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->sourceRootInfo could also return false which is incompatible with the documented return type OCP\Files\Cache\ICacheEntry. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
120
	}
121
122
	private function init() {
123
		if ($this->initialized) {
124
			return;
125
		}
126
		$this->initialized = true;
127
		try {
128
			Filesystem::initMountPoints($this->superShare->getShareOwner());
129
			$storageId = $this->superShare->getNodeCacheEntry() ? $this->superShare->getNodeCacheEntry()->getStorageId() : null;
130
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId(), $storageId);
131
			[$this->nonMaskedStorage, $this->rootPath] = $this->ownerView->resolvePath($sourcePath);
132
			$this->storage = new PermissionsMask([
133
				'storage' => $this->nonMaskedStorage,
134
				'mask' => $this->superShare->getPermissions(),
135
			]);
136
		} catch (NotFoundException $e) {
137
			// original file not accessible or deleted, set FailedStorage
138
			$this->storage = new FailedStorage(['exception' => $e]);
139
			$this->cache = new FailedCache();
140
			$this->rootPath = '';
141
		} catch (NoUserException $e) {
142
			// sharer user deleted, set FailedStorage
143
			$this->storage = new FailedStorage(['exception' => $e]);
144
			$this->cache = new FailedCache();
145
			$this->rootPath = '';
146
		} catch (\Exception $e) {
147
			$this->storage = new FailedStorage(['exception' => $e]);
148
			$this->cache = new FailedCache();
149
			$this->rootPath = '';
150
			$this->logger->logException($e);
151
		}
152
153
		if (!$this->nonMaskedStorage) {
154
			$this->nonMaskedStorage = $this->storage;
155
		}
156
	}
157
158
	/**
159
	 * @inheritdoc
160
	 */
161
	public function instanceOfStorage($class) {
162
		if ($class === '\OC\Files\Storage\Common') {
163
			return true;
164
		}
165
		if (in_array($class, ['\OC\Files\Storage\Home', '\OC\Files\ObjectStore\HomeObjectStoreStorage', '\OCP\Files\IHomeStorage'])) {
166
			return false;
167
		}
168
		return parent::instanceOfStorage($class);
169
	}
170
171
	/**
172
	 * @return string
173
	 */
174
	public function getShareId() {
175
		return $this->superShare->getId();
176
	}
177
178
	private function isValid() {
179
		return $this->getSourceRootInfo() && ($this->getSourceRootInfo()->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
180
	}
181
182
	/**
183
	 * get id of the mount point
184
	 *
185
	 * @return string
186
	 */
187
	public function getId() {
188
		return 'shared::' . $this->getMountPoint();
189
	}
190
191
	/**
192
	 * Get the permissions granted for a shared file
193
	 *
194
	 * @param string $target Shared target file path
195
	 * @return int CRUDS permissions granted
196
	 */
197
	public function getPermissions($target = '') {
198
		if (!$this->isValid()) {
199
			return 0;
200
		}
201
		$permissions = parent::getPermissions($target) & $this->superShare->getPermissions();
202
203
		// part files and the mount point always have delete permissions
204
		if ($target === '' || pathinfo($target, PATHINFO_EXTENSION) === 'part') {
205
			$permissions |= \OCP\Constants::PERMISSION_DELETE;
206
		}
207
208
		if ($this->sharingDisabledForUser) {
209
			$permissions &= ~\OCP\Constants::PERMISSION_SHARE;
210
		}
211
212
		return $permissions;
213
	}
214
215
	public function isCreatable($path) {
216
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getPermiss...ants::PERMISSION_CREATE returns the type integer which is incompatible with the return type mandated by OCP\Files\Storage::isCreatable() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
217
	}
218
219
	public function isReadable($path) {
220
		if (!$this->isValid()) {
221
			return false;
222
		}
223
		if (!$this->file_exists($path)) {
224
			return false;
225
		}
226
		/** @var IStorage $storage */
227
		/** @var string $internalPath */
228
		[$storage, $internalPath] = $this->resolvePath($path);
229
		return $storage->isReadable($internalPath);
230
	}
231
232
	public function isUpdatable($path) {
233
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getPermiss...ants::PERMISSION_UPDATE returns the type integer which is incompatible with the return type mandated by OCP\Files\Storage::isUpdatable() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
234
	}
235
236
	public function isDeletable($path) {
237
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getPermiss...ants::PERMISSION_DELETE returns the type integer which is incompatible with the return type mandated by OCP\Files\Storage::isDeletable() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
238
	}
239
240
	public function isSharable($path) {
241
		if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
242
			return false;
243
		}
244
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getPermiss...tants::PERMISSION_SHARE returns the type integer which is incompatible with the return type mandated by OCP\Files\Storage::isSharable() of boolean.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
245
	}
246
247
	public function fopen($path, $mode) {
248
		$source = $this->getUnjailedPath($path);
249
		switch ($mode) {
250
			case 'r+':
251
			case 'rb+':
252
			case 'w+':
253
			case 'wb+':
254
			case 'x+':
255
			case 'xb+':
256
			case 'a+':
257
			case 'ab+':
258
			case 'w':
259
			case 'wb':
260
			case 'x':
261
			case 'xb':
262
			case 'a':
263
			case 'ab':
264
				$creatable = $this->isCreatable(dirname($path));
265
				$updatable = $this->isUpdatable($path);
266
				// if neither permissions given, no need to continue
267
				if (!$creatable && !$updatable) {
268
					if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
269
						$updatable = $this->isUpdatable(dirname($path));
270
					}
271
272
					if (!$updatable) {
273
						return false;
274
					}
275
				}
276
277
				$exists = $this->file_exists($path);
278
				// if a file exists, updatable permissions are required
279
				if ($exists && !$updatable) {
280
					return false;
281
				}
282
283
				// part file is allowed if !$creatable but the final file is $updatable
284
				if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
285
					if (!$exists && !$creatable) {
286
						return false;
287
					}
288
				}
289
		}
290
		$info = [
291
			'target' => $this->getMountPoint() . $path,
292
			'source' => $source,
293
			'mode' => $mode,
294
		];
295
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
296
		return $this->nonMaskedStorage->fopen($this->getUnjailedPath($path), $mode);
297
	}
298
299
	/**
300
	 * see http://php.net/manual/en/function.rename.php
301
	 *
302
	 * @param string $path1
303
	 * @param string $path2
304
	 * @return bool
305
	 */
306
	public function rename($path1, $path2) {
307
		$this->init();
308
		$isPartFile = pathinfo($path1, PATHINFO_EXTENSION) === 'part';
309
		$targetExists = $this->file_exists($path2);
310
		$sameFodler = dirname($path1) === dirname($path2);
311
312
		if ($targetExists || ($sameFodler && !$isPartFile)) {
313
			if (!$this->isUpdatable('')) {
314
				return false;
315
			}
316
		} else {
317
			if (!$this->isCreatable('')) {
318
				return false;
319
			}
320
		}
321
322
		return $this->nonMaskedStorage->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
323
	}
324
325
	/**
326
	 * return mount point of share, relative to data/user/files
327
	 *
328
	 * @return string
329
	 */
330
	public function getMountPoint() {
331
		return $this->superShare->getTarget();
332
	}
333
334
	/**
335
	 * @param string $path
336
	 */
337
	public function setMountPoint($path) {
338
		$this->superShare->setTarget($path);
339
340
		foreach ($this->groupedShares as $share) {
341
			$share->setTarget($path);
342
		}
343
	}
344
345
	/**
346
	 * get the user who shared the file
347
	 *
348
	 * @return string
349
	 */
350
	public function getSharedFrom() {
351
		return $this->superShare->getShareOwner();
352
	}
353
354
	/**
355
	 * @return \OCP\Share\IShare
356
	 */
357
	public function getShare() {
358
		return $this->superShare;
359
	}
360
361
	/**
362
	 * return share type, can be "file" or "folder"
363
	 *
364
	 * @return string
365
	 */
366
	public function getItemType() {
367
		return $this->superShare->getNodeType();
368
	}
369
370
	/**
371
	 * @param string $path
372
	 * @param null $storage
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $storage is correct as it would always require null to be passed?
Loading history...
373
	 * @return Cache
374
	 */
375
	public function getCache($path = '', $storage = null) {
376
		if ($this->cache) {
377
			return $this->cache;
378
		}
379
		if (!$storage) {
0 ignored issues
show
introduced by
$storage is of type null, thus it always evaluated to false.
Loading history...
380
			$storage = $this;
381
		}
382
		$sourceRoot = $this->getSourceRootInfo();
383
		if ($this->storage instanceof FailedStorage) {
384
			return new FailedCache();
0 ignored issues
show
Bug Best Practice introduced by
The expression return new OC\Files\Cache\FailedCache() returns the type OC\Files\Cache\FailedCache which is incompatible with the documented return type OCA\Files_Sharing\Cache.
Loading history...
385
		}
386
387
		$this->cache = new \OCA\Files_Sharing\Cache($storage, $sourceRoot, $this->superShare);
0 ignored issues
show
Unused Code introduced by
The call to OCA\Files_Sharing\Cache::__construct() has too many arguments starting with $this->superShare. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

387
		$this->cache = /** @scrutinizer ignore-call */ new \OCA\Files_Sharing\Cache($storage, $sourceRoot, $this->superShare);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

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