Completed
Pull Request — master (#32044)
by Thomas
19:39
created

SharedStorage::isReadable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Bart Visscher <[email protected]>
4
 * @author Björn Schießle <[email protected]>
5
 * @author Joas Schilling <[email protected]>
6
 * @author Michael Gapczynski <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author Robin Appelman <[email protected]>
9
 * @author Robin McCorkell <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author scambra <[email protected]>
12
 * @author Sergio Bertolín <[email protected]>
13
 * @author Thomas Müller <[email protected]>
14
 * @author Vincent Petry <[email protected]>
15
 *
16
 * @copyright Copyright (c) 2018, ownCloud GmbH
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\FailedStorage;
38
use OC\User\NoUserException;
39
use OCP\Constants;
40
use OCP\Files\Cache\ICacheEntry;
41
use OCP\Files\NotFoundException;
42
use OCP\Files\NotPermittedException;
43
use OCP\Files\Storage\IStorage;
44
use OCP\Lock\ILockingProvider;
45
use Psr\Http\Message\StreamInterface;
46
47
/**
48
 * Convert target path to source path and pass the function call to the correct storage provider
49
 */
50
class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage {
51
52
	/** @var \OCP\Share\IShare */
53
	private $superShare;
54
55
	/** @var \OCP\Share\IShare[] */
56
	private $groupedShares;
57
58
	/**
59
	 * @var \OC\Files\View
60
	 */
61
	private $ownerView;
62
63
	private $initialized = false;
64
65
	/**
66
	 * @var ICacheEntry
67
	 */
68
	private $sourceRootInfo;
69
70
	/**
71
	 * @var IStorage
72
	 */
73
	private $sourceStorage;
74
75
	/** @var string */
76
	private $user;
77
78
	/**
79
	 * @var \OCP\ILogger
80
	 */
81
	private $logger;
82
83
	public function __construct($arguments) {
84
		$this->ownerView = $arguments['ownerView'];
85
		$this->logger = \OC::$server->getLogger();
86
87
		$this->superShare = $arguments['superShare'];
88
		$this->groupedShares = $arguments['groupedShares'];
89
90
		$this->user = $arguments['user'];
91
92
		parent::__construct([
93
			'storage' => null, // init later
94
			'root' => null, // init later
95
		]);
96
	}
97
98
	private function init() {
99
		if ($this->initialized) {
100
			return;
101
		}
102
		$this->initialized = true;
103
		try {
104
			Filesystem::initMountPoints($this->superShare->getShareOwner());
105
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId(), false);
106
			list($this->sourceStorage, $sourceInternalPath) = $this->ownerView->resolvePath($sourcePath);
107
			$this->sourceRootInfo = $this->sourceStorage->getCache()->get($sourceInternalPath);
108
			// adjust jail
109
			$this->rootPath = $sourceInternalPath;
110
		} catch (NotFoundException $e) {
111
			// original file not accessible or deleted, set FailedStorage
112
			$this->sourceStorage = new FailedStorage(['exception' => $e]);
113
		} catch (NoUserException $e) {
114
			// sharer user deleted, set FailedStorage
115
			$this->sourceStorage = new FailedStorage(['exception' => $e]);
116
		} catch (\Exception $e) {
117
			// something unexpected happened, log exception and set failed storage
118
			$this->sourceStorage = new FailedStorage(['exception' => $e]);
119
			$this->logger->logException($e);
120
		}
121
		$this->storage = $this->sourceStorage;
122
	}
123
124
	/**
125
	 * @inheritdoc
126
	 */
127
	public function instanceOfStorage($class) {
128
		if (\in_array($class, ['\OC\Files\Storage\Home', '\OC\Files\ObjectStore\HomeObjectStoreStorage'])) {
129
			return false;
130
		}
131
		return parent::instanceOfStorage($class);
132
	}
133
134
	/**
135
	 * @return string
136
	 */
137
	public function getShareId() {
138
		return $this->superShare->getId();
139
	}
140
141
	private function isValid() {
142
		$this->init();
143
		return $this->sourceRootInfo && ($this->sourceRootInfo->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
144
	}
145
146
	/**
147
	 * get id of the mount point
148
	 *
149
	 * @return string
150
	 */
151
	public function getId() {
152
		return 'shared::' . $this->getMountPoint();
153
	}
154
155
	/**
156
	 * Get the permissions granted for a shared file
157
	 *
158
	 * @param string $target Shared target file path
159
	 * @return int CRUDS permissions granted
160
	 */
161
	public function getPermissions($target = '') {
162
		if (!$this->isValid()) {
163
			return 0;
164
		}
165
		$permissions = $this->superShare->getPermissions();
166
		// part files and the mount point always have delete permissions
167
		if ($target === '' || \pathinfo($target, PATHINFO_EXTENSION) === 'part') {
168
			$permissions |= \OCP\Constants::PERMISSION_DELETE;
169
		}
170
171
		if (\OCP\Util::isSharingDisabledForUser()) {
172
			$permissions &= ~\OCP\Constants::PERMISSION_SHARE;
173
		}
174
175
		return $permissions;
176
	}
177
178
	public function isCreatable($path) {
179
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE);
180
	}
181
182
	public function isReadable($path) {
183
		if (!$this->isValid()) {
184
			return false;
185
		}
186
		if (!$this->file_exists($path)) {
187
			return false;
188
		}
189
		list($storage, $internalPath) = $this->resolvePath($path);
190
		return $storage->isReadable($internalPath);
191
	}
192
193
	public function isUpdatable($path) {
194
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE);
195
	}
196
197
	public function isDeletable($path) {
198
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE);
199
	}
200
201
	public function isSharable($path) {
202
		if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
203
			return false;
204
		}
205
		return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
206
	}
207
208
	public function fopen($path, $mode) {
209
		if ($source = $this->getSourcePath($path)) {
210
			switch ($mode) {
211
				case 'r+':
212
				case 'rb+':
213
				case 'w+':
214
				case 'wb+':
215
				case 'x+':
216
				case 'xb+':
217
				case 'a+':
218
				case 'ab+':
219
				case 'w':
220
				case 'wb':
221
				case 'x':
222
				case 'xb':
223
				case 'a':
224
				case 'ab':
225
					$creatable = $this->isCreatable($path);
226
					$updatable = $this->isUpdatable($path);
227
					// if neither permissions given, no need to continue
228
					if (!$creatable && !$updatable) {
229
						return false;
230
					}
231
232
					$exists = $this->file_exists($path);
233
					// if a file exists, updatable permissions are required
234
					if ($exists && !$updatable) {
235
						return false;
236
					}
237
238
					// part file is allowed if !$creatable but the final file is $updatable
239 View Code Duplication
					if (\pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
240
						if (!$exists && !$creatable) {
241
							return false;
242
						}
243
					}
244
			}
245
			$info = [
246
				'target' => $this->getMountPoint() . $path,
247
				'source' => $source,
248
				'mode' => $mode,
249
			];
250
			\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
251
			return parent::fopen($path, $mode);
252
		}
253
		return false;
254
	}
255
256
	/**
257
	 * see http://php.net/manual/en/function.rename.php
258
	 *
259
	 * @param string $path1
260
	 * @param string $path2
261
	 * @return bool
262
	 */
263
	public function rename($path1, $path2) {
264
		$isPartFile = \pathinfo($path1, PATHINFO_EXTENSION) === 'part';
265
		$targetExists = $this->file_exists($path2);
266
		$sameFodler = \dirname($path1) === \dirname($path2);
267
268
		if ($targetExists || ($sameFodler && !$isPartFile)) {
269
			if (!$this->isUpdatable('')) {
270
				return false;
271
			}
272
		} else {
273
			if (!$this->isCreatable('')) {
274
				return false;
275
			}
276
		}
277
278
		return parent::rename($path1, $path2);
279
	}
280
281
	/**
282
	 * return mount point of share, relative to data/user/files
283
	 *
284
	 * @return string
285
	 */
286
	public function getMountPoint() {
287
		return $this->superShare->getTarget();
288
	}
289
290
	/**
291
	 * @param string $path
292
	 */
293
	public function setMountPoint($path) {
294
		$this->superShare->setTarget($path);
295
296
		foreach ($this->groupedShares as $share) {
297
			$share->setTarget($path);
298
		}
299
	}
300
301
	/**
302
	 * get the user who shared the file
303
	 *
304
	 * @return string
305
	 */
306
	public function getSharedFrom() {
307
		return $this->superShare->getShareOwner();
308
	}
309
310
	/**
311
	 * @return \OCP\Share\IShare
312
	 */
313
	public function getShare() {
314
		return $this->superShare;
315
	}
316
317
	/**
318
	 * return share type, can be "file" or "folder"
319
	 *
320
	 * @return string
321
	 */
322
	public function getItemType() {
323
		return $this->superShare->getNodeType();
324
	}
325
326
	public function getCache($path = '', $storage = null) {
327
		$this->init();
328
		if ($this->sourceStorage === null || $this->sourceStorage instanceof FailedStorage) {
329
			return new FailedCache(false);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \OC\Files\Cache\FailedCache(false); (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...
330
		}
331
		if (!$storage) {
332
			$storage = $this;
333
		}
334
		return new \OCA\Files_Sharing\Cache($storage, $this->sourceStorage, $this->sourceRootInfo);
335
	}
336
337
	public function getScanner($path = '', $storage = null) {
338
		if (!$storage) {
339
			$storage = $this;
340
		}
341
		return new \OCA\Files_Sharing\Scanner($storage);
342
	}
343
344 View Code Duplication
	public function getPropagator($storage = null) {
345
		if (isset($this->propagator)) {
346
			return $this->propagator;
347
		}
348
349
		if (!$storage) {
350
			$storage = $this;
351
		}
352
		$this->propagator = new \OCA\Files_Sharing\SharedPropagator($storage, \OC::$server->getDatabaseConnection());
353
		return $this->propagator;
354
	}
355
356
	public function getOwner($path) {
357
		return $this->superShare->getShareOwner();
358
	}
359
360
	/**
361
	 * unshare complete storage, also the grouped shares
362
	 *
363
	 * @return bool
364
	 */
365
	public function unshareStorage() {
366
		foreach ($this->groupedShares as $share) {
367
			\OC::$server->getShareManager()->deleteFromSelf($share, $this->user);
368
		}
369
		return true;
370
	}
371
372
	/**
373
	 * @param string $path
374
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
375
	 * @param \OCP\Lock\ILockingProvider $provider
376
	 * @throws \OCP\Lock\LockedException
377
	 */
378 View Code Duplication
	public function acquireLock($path, $type, ILockingProvider $provider) {
379
		/** @var \OCP\Files\Storage\IStorage $targetStorage */
380
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
381
		$targetStorage->acquireLock($targetInternalPath, $type, $provider);
382
		// lock the parent folders of the owner when locking the share as recipient
383
		if ($path === '') {
384
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
385
			$this->ownerView->lockFile(\dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
386
		}
387
	}
388
389
	/**
390
	 * @param string $path
391
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
392
	 * @param \OCP\Lock\ILockingProvider $provider
393
	 */
394 View Code Duplication
	public function releaseLock($path, $type, ILockingProvider $provider) {
395
		/** @var \OCP\Files\Storage\IStorage $targetStorage */
396
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
397
		$targetStorage->releaseLock($targetInternalPath, $type, $provider);
398
		// unlock the parent folders of the owner when unlocking the share as recipient
399
		if ($path === '') {
400
			$sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
401
			$this->ownerView->unlockFile(\dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
402
		}
403
	}
404
405
	/**
406
	 * @param string $path
407
	 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
408
	 * @param \OCP\Lock\ILockingProvider $provider
409
	 */
410
	public function changeLock($path, $type, ILockingProvider $provider) {
411
		/** @var \OCP\Files\Storage\IStorage $targetStorage */
412
		list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
413
		$targetStorage->changeLock($targetInternalPath, $type, $provider);
414
	}
415
416
	/**
417
	 * @return array [ available, last_checked ]
418
	 */
419
	public function getAvailability() {
420
		// shares do not participate in availability logic
421
		return [
422
			'available' => true,
423
			'last_checked' => 0
424
		];
425
	}
426
427
	/**
428
	 * @param bool $available
429
	 */
430
	public function setAvailability($available) {
431
		// shares do not participate in availability logic
432
	}
433
434
	public function getSourceStorage() {
435
		return $this->sourceStorage;
436
	}
437
438 View Code Duplication
	public function file_get_contents($path) {
439
		$info = [
440
			'target' => $this->getMountPoint() . '/' . $path,
441
			'source' => $this->getSourcePath($path),
442
		];
443
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
444
		return parent::file_get_contents($path);
445
	}
446
447 View Code Duplication
	public function file_put_contents($path, $data) {
448
		$info = [
449
			'target' => $this->getMountPoint() . '/' . $path,
450
			'source' => $this->getSourcePath($path),
451
		];
452
		\OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
453
		return parent::file_put_contents($path, $data);
454
	}
455
456
	public function getWrapperStorage() {
457
		$this->init();
458
459
		return $this->sourceStorage;
460
	}
461
462
	public function readFile(string $path, array $options = []): StreamInterface {
463
		if (!$this->file_exists($path)) {
464
			throw new NotFoundException();
465
		}
466
467
		return parent::readFile($path, $options);
468
	}
469
470
	public function writeFile(string $path, StreamInterface $stream): int {
471
		$creatable = $this->isCreatable($path);
472
		$updatable = $this->isUpdatable($path);
473
		// if neither permissions given, no need to continue
474
		if (!$creatable && !$updatable) {
475
			throw new NotPermittedException();
476
		}
477
478
		$exists = $this->file_exists($path);
479
		// if a file exists, updatable permissions are required
480
		if ($exists && !$updatable) {
481
			throw new NotPermittedException();
482
		}
483
484
		// part file is allowed if !$creatable but the final file is $updatable
485 View Code Duplication
		if (\pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
486
			if (!$exists && !$creatable) {
487
				throw new NotPermittedException();
488
			}
489
		}
490
491
		return parent::writeFile($path, $stream);
492
	}
493
}
494