Completed
Pull Request — master (#5715)
by Morris
51:53 queued 29:36
created

SharedStorage::getScanner()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
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 Vincent Petry <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OCA\Files_Sharing;
33
34
use OC\Files\Cache\FailedCache;
35
use OC\Files\Filesystem;
36
use OC\Files\Storage\Wrapper\PermissionsMask;
37
use OC\Files\Storage\FailedStorage;
38
use OCP\Constants;
39
use OCP\Files\Cache\ICacheEntry;
40
use OCP\Files\NotFoundException;
41
use OCP\Files\Storage\IStorage;
42
use OCP\Lock\ILockingProvider;
43
use OC\User\NoUserException;
44
45
/**
46
 * Convert target path to source path and pass the function call to the correct storage provider
47
 */
48
class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
49
50
	/** @var \OCP\Share\IShare */
51
	private $superShare;
52
53
	/** @var \OCP\Share\IShare[] */
54
	private $groupedShares;
55
56
	/**
57
	 * @var \OC\Files\View
58
	 */
59
	private $ownerView;
60
61
	private $initialized = false;
62
63
	/**
64
	 * @var ICacheEntry
65
	 */
66
	private $sourceRootInfo;
67
68
	/** @var string */
69
	private $user;
70
71
	/**
72
	 * @var \OCP\ILogger
73
	 */
74
	private $logger;
75
76
	/** @var  IStorage */
77
	private $nonMaskedStorage;
78
79
	private $options;
0 ignored issues
show
Unused Code introduced by
The property $options is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
80
81
	public function __construct($arguments) {
82
		$this->ownerView = $arguments['ownerView'];
83
		$this->logger = \OC::$server->getLogger();
84
85
		$this->superShare = $arguments['superShare'];
86
		$this->groupedShares = $arguments['groupedShares'];
87
88
		$this->user = $arguments['user'];
89
90
		parent::__construct([
91
			'storage' => null,
92
			'root' => null,
93
		]);
94
	}
95
96
	/**
97
	 * @return ICacheEntry
98
	 */
99
	private function getSourceRootInfo() {
100
		if (is_null($this->sourceRootInfo)) {
101
			if (is_null($this->superShare->getNodeCacheEntry())) {
102
				$this->init();
103
				$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...
104
			} else {
105
				$this->sourceRootInfo = $this->superShare->getNodeCacheEntry();
106
			}
107
		}
108
		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 108 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...
109
	}
110
111
	private function init() {
112
		if ($this->initialized) {
113
			return;
114
		}
115
		$this->initialized = true;
116
		try {
117
			Filesystem::initMountPoints($this->superShare->getShareOwner());
118
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
119
			list($this->nonMaskedStorage, $this->rootPath) = $this->ownerView->resolvePath($sourcePath);
120
			$this->storage = new PermissionsMask([
121
				'storage' => $this->nonMaskedStorage,
122
				'mask' => $this->superShare->getPermissions()
123
			]);
124
		} catch (NotFoundException $e) {
125
			// original file not accessible or deleted, set FailedStorage
126
			$this->storage = new FailedStorage(['exception' => $e]);
127
			$this->cache = new FailedCache();
128
			$this->rootPath = '';
129
		} catch (NoUserException $e) {
130
			// sharer user deleted, set FailedStorage
131
			$this->storage = new FailedStorage(['exception' => $e]);
132
			$this->cache = new FailedCache();
133
			$this->rootPath = '';
134
		} catch (\Exception $e) {
135
			$this->storage = new FailedStorage(['exception' => $e]);
136
			$this->cache = new FailedCache();
137
			$this->rootPath = '';
138
			$this->logger->logException($e);
139
		}
140
141
		if (!$this->nonMaskedStorage) {
142
			$this->nonMaskedStorage = $this->storage;
143
		}
144
	}
145
146
	/**
147
	 * @inheritdoc
148
	 */
149
	public function instanceOfStorage($class) {
150
		if ($class === '\OC\Files\Storage\Common') {
151
			return true;
152
		}
153
		if (in_array($class, ['\OC\Files\Storage\Home', '\OC\Files\ObjectStore\HomeObjectStoreStorage'])) {
154
			return false;
155
		}
156
		return parent::instanceOfStorage($class);
157
	}
158
159
	/**
160
	 * @return string
161
	 */
162
	public function getShareId() {
163
		return $this->superShare->getId();
164
	}
165
166
	private function isValid() {
167
		return $this->getSourceRootInfo() && ($this->getSourceRootInfo()->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
168
	}
169
170
	/**
171
	 * get id of the mount point
172
	 *
173
	 * @return string
174
	 */
175
	public function getId() {
176
		return 'shared::' . $this->getMountPoint();
177
	}
178
179
	/**
180
	 * Get the permissions granted for a shared file
181
	 *
182
	 * @param string $target Shared target file path
183
	 * @return int CRUDS permissions granted
184
	 */
185
	public function getPermissions($target = '') {
186
		if (!$this->isValid()) {
187
			return 0;
188
		}
189
		$permissions = $this->superShare->getPermissions();
190
		// part files and the mount point always have delete permissions
191
		if ($target === '' || pathinfo($target, PATHINFO_EXTENSION) === 'part') {
192
			$permissions |= \OCP\Constants::PERMISSION_DELETE;
193
		}
194
195
		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...
196
			$permissions &= ~\OCP\Constants::PERMISSION_SHARE;
197
		}
198
199
		return $permissions;
200
	}
201
202
	public function isCreatable($path) {
203
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE);
204
	}
205
206
	public function isReadable($path) {
207
		if (!$this->isValid()) {
208
			return false;
209
		}
210
		if (!$this->file_exists($path)) {
211
			return false;
212
		}
213
		/** @var IStorage $storage */
214
		/** @var string $internalPath */
215
		list($storage, $internalPath) = $this->resolvePath($path);
216
		return $storage->isReadable($internalPath);
217
	}
218
219
	public function isUpdatable($path) {
220
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE);
221
	}
222
223
	public function isDeletable($path) {
224
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE);
225
	}
226
227
	public function isSharable($path) {
228
		if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
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...
229
			return false;
230
		}
231
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
232
	}
233
234
	public function fopen($path, $mode) {
235
		if ($source = $this->getUnjailedPath($path)) {
236
			switch ($mode) {
237
				case 'r+':
238
				case 'rb+':
239
				case 'w+':
240
				case 'wb+':
241
				case 'x+':
242
				case 'xb+':
243
				case 'a+':
244
				case 'ab+':
245
				case 'w':
246
				case 'wb':
247
				case 'x':
248
				case 'xb':
249
				case 'a':
250
				case 'ab':
251
					$creatable = $this->isCreatable($path);
252
					$updatable = $this->isUpdatable($path);
253
					// if neither permissions given, no need to continue
254
					if (!$creatable && !$updatable) {
255
						return false;
256
					}
257
258
					$exists = $this->file_exists($path);
259
					// if a file exists, updatable permissions are required
260
					if ($exists && !$updatable) {
261
						return false;
262
					}
263
264
					// part file is allowed if !$creatable but the final file is $updatable
265
					if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
266
						if (!$exists && !$creatable) {
267
							return false;
268
						}
269
					}
270
			}
271
			$info = array(
272
				'target' => $this->getMountPoint() . $path,
273
				'source' => $source,
274
				'mode' => $mode,
275
			);
276
			\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
277
			return $this->nonMaskedStorage->fopen($this->getUnjailedPath($path), $mode);
278
		}
279
		return false;
280
	}
281
282
	/**
283
	 * see http://php.net/manual/en/function.rename.php
284
	 *
285
	 * @param string $path1
286
	 * @param string $path2
287
	 * @return bool
288
	 */
289
	public function rename($path1, $path2) {
290
		$this->init();
291
		$isPartFile = pathinfo($path1, PATHINFO_EXTENSION) === 'part';
292
		$targetExists = $this->file_exists($path2);
293
		$sameFodler = dirname($path1) === dirname($path2);
294
295
		if ($targetExists || ($sameFodler && !$isPartFile)) {
296
			if (!$this->isUpdatable('')) {
297
				return false;
298
			}
299
		} else {
300
			if (!$this->isCreatable('')) {
301
				return false;
302
			}
303
		}
304
305
		return $this->nonMaskedStorage->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
306
	}
307
308
	/**
309
	 * return mount point of share, relative to data/user/files
310
	 *
311
	 * @return string
312
	 */
313
	public function getMountPoint() {
314
		return $this->superShare->getTarget();
315
	}
316
317
	/**
318
	 * @param string $path
319
	 */
320
	public function setMountPoint($path) {
321
		$this->superShare->setTarget($path);
322
323
		foreach ($this->groupedShares as $share) {
324
			$share->setTarget($path);
325
		}
326
	}
327
328
	/**
329
	 * get the user who shared the file
330
	 *
331
	 * @return string
332
	 */
333
	public function getSharedFrom() {
334
		return $this->superShare->getShareOwner();
335
	}
336
337
	/**
338
	 * @return \OCP\Share\IShare
339
	 */
340
	public function getShare() {
341
		return $this->superShare;
342
	}
343
344
	/**
345
	 * return share type, can be "file" or "folder"
346
	 *
347
	 * @return string
348
	 */
349
	public function getItemType() {
350
		return $this->superShare->getNodeType();
351
	}
352
353
	/**
354
	 * @param string $path
355
	 * @param null $storage
356
	 * @return Cache
357
	 */
358
	public function getCache($path = '', $storage = null) {
359
		if ($this->cache) {
360
			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...
361
		}
362
		if (!$storage) {
363
			$storage = $this;
364
		}
365
		if ($this->storage instanceof FailedStorage) {
366
			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...
367
		}
368
		$this->cache = new \OCA\Files_Sharing\Cache($storage, $this->getSourceRootInfo(), $this->superShare);
369
		return $this->cache;
370
	}
371
372
	public function getScanner($path = '', $storage = null) {
373
		if (!$storage) {
374
			$storage = $this;
375
		}
376
		return new \OCA\Files_Sharing\Scanner($storage);
377
	}
378
379
	public function getOwner($path) {
380
		return $this->superShare->getShareOwner();
381
	}
382
383
	/**
384
	 * unshare complete storage, also the grouped shares
385
	 *
386
	 * @return bool
387
	 */
388
	public function unshareStorage() {
389
		foreach ($this->groupedShares as $share) {
390
			\OC::$server->getShareManager()->deleteFromSelf($share, $this->user);
391
		}
392
		return true;
393
	}
394
395
	/**
396
	 * @param string $path
397
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
398
	 * @param \OCP\Lock\ILockingProvider $provider
399
	 * @throws \OCP\Lock\LockedException
400
	 */
401 View Code Duplication
	public function acquireLock($path, $type, ILockingProvider $provider) {
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...
402
		/** @var \OCP\Files\Storage $targetStorage */
403
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
404
		$targetStorage->acquireLock($targetInternalPath, $type, $provider);
405
		// lock the parent folders of the owner when locking the share as recipient
406
		if ($path === '') {
407
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
408
			$this->ownerView->lockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
409
		}
410
	}
411
412
	/**
413
	 * @param string $path
414
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
415
	 * @param \OCP\Lock\ILockingProvider $provider
416
	 */
417 View Code Duplication
	public function releaseLock($path, $type, ILockingProvider $provider) {
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...
418
		/** @var \OCP\Files\Storage $targetStorage */
419
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
420
		$targetStorage->releaseLock($targetInternalPath, $type, $provider);
421
		// unlock the parent folders of the owner when unlocking the share as recipient
422
		if ($path === '') {
423
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
424
			$this->ownerView->unlockFile(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
	public function changeLock($path, $type, ILockingProvider $provider) {
434
		/** @var \OCP\Files\Storage $targetStorage */
435
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
436
		$targetStorage->changeLock($targetInternalPath, $type, $provider);
437
	}
438
439
	/**
440
	 * @return array [ available, last_checked ]
441
	 */
442
	public function getAvailability() {
443
		// shares do not participate in availability logic
444
		return [
445
			'available' => true,
446
			'last_checked' => 0
447
		];
448
	}
449
450
	/**
451
	 * @param bool $available
452
	 */
453
	public function setAvailability($available) {
454
		// shares do not participate in availability logic
455
	}
456
457
	public function getSourceStorage() {
458
		$this->init();
459
		return $this->nonMaskedStorage;
460
	}
461
462
	public function getWrapperStorage() {
463
		$this->init();
464
		return $this->storage;
465
	}
466
467 View Code Duplication
	public function file_get_contents($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...
468
		$info = [
469
			'target' => $this->getMountPoint() . '/' . $path,
470
			'source' => $this->getUnjailedPath($path),
471
		];
472
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
473
		return parent::file_get_contents($path);
474
	}
475
476 View Code Duplication
	public function file_put_contents($path, $data) {
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...
477
		$info = [
478
			'target' => $this->getMountPoint() . '/' . $path,
479
			'source' => $this->getUnjailedPath($path),
480
		];
481
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
482
		return parent::file_put_contents($path, $data);
483
	}
484
485
	public function setMountOptions(array $options) {
486
		$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...
487
	}
488
}
489