Completed
Push — stable10 ( e5eff3...805fc2 )
by Lukas
11:09
created

Shared::getWrapperStorage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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