Completed
Push — master ( 24a68b...bc84ac )
by Thomas
22:37 queued 09:54
created

SharedStorage::unlockNodePersistent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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