Completed
Push — master ( cc4f99...caccf7 )
by Morris
21:49
created

Share::getCollectionItemTypes()   B

Complexity

Conditions 7
Paths 12

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 10
nc 12
nop 1
dl 0
loc 18
rs 8.2222
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 Bernhard Reiter <[email protected]>
7
 * @author Bjoern Schiessle <[email protected]>
8
 * @author Björn Schießle <[email protected]>
9
 * @author Christopher Schäpers <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Morris Jobke <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Robin McCorkell <[email protected]>
16
 * @author Roeland Jago Douma <[email protected]>
17
 * @author Sebastian Döll <[email protected]>
18
 * @author Stefan Weil <[email protected]>
19
 * @author Thomas Müller <[email protected]>
20
 * @author Torben Dannhauer <[email protected]>
21
 * @author Vincent Petry <[email protected]>
22
 * @author Volkan Gezer <[email protected]>
23
 *
24
 * @license AGPL-3.0
25
 *
26
 * This code is free software: you can redistribute it and/or modify
27
 * it under the terms of the GNU Affero General Public License, version 3,
28
 * as published by the Free Software Foundation.
29
 *
30
 * This program is distributed in the hope that it will be useful,
31
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33
 * GNU Affero General Public License for more details.
34
 *
35
 * You should have received a copy of the GNU Affero General Public License, version 3,
36
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
37
 *
38
 */
39
40
namespace OC\Share;
41
42
use OC\Files\Filesystem;
43
use OCP\DB\QueryBuilder\IQueryBuilder;
44
use OCP\ILogger;
45
use OCP\IUserManager;
46
use OCP\IUserSession;
47
use OCP\IDBConnection;
48
use OCP\IConfig;
49
use OCP\Util;
50
51
/**
52
 * This class provides the ability for apps to share their content between users.
53
 * Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
54
 *
55
 * It provides the following hooks:
56
 *  - post_shared
57
 */
58
class Share extends Constants {
59
60
	/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
61
	 * Construct permissions for share() and setPermissions with Or (|) e.g.
62
	 * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
63
	 *
64
	 * Check if permission is granted with And (&) e.g. Check if delete is
65
	 * granted: if ($permissions & PERMISSION_DELETE)
66
	 *
67
	 * Remove permissions with And (&) and Not (~) e.g. Remove the update
68
	 * permission: $permissions &= ~PERMISSION_UPDATE
69
	 *
70
	 * Apps are required to handle permissions on their own, this class only
71
	 * stores and manages the permissions of shares
72
	 * @see lib/public/constants.php
73
	 */
74
75
	/**
76
	 * Register a sharing backend class that implements OCP\Share_Backend for an item type
77
	 * @param string $itemType Item type
78
	 * @param string $class Backend class
79
	 * @param string $collectionOf (optional) Depends on item type
80
	 * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
81
	 * @return boolean true if backend is registered or false if error
82
	 */
83
	public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
84
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') == 'yes') {
85
			if (!isset(self::$backendTypes[$itemType])) {
86
				self::$backendTypes[$itemType] = array(
87
					'class' => $class,
88
					'collectionOf' => $collectionOf,
89
					'supportedFileExtensions' => $supportedFileExtensions
90
				);
91
				if(count(self::$backendTypes) === 1) {
92
					Util::addScript('core', 'merged-share-backend');
93
					\OC_Util::addStyle('core', 'share');
94
				}
95
				return true;
96
			}
97
			\OCP\Util::writeLog('OCP\Share',
98
				'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
99
				.' is already registered for '.$itemType,
100
				\OCP\Util::WARN);
101
		}
102
		return false;
103
	}
104
105
	/**
106
	 * Get the items of item type shared with the current user
107
	 * @param string $itemType
108
	 * @param int $format (optional) Format type must be defined by the backend
109
	 * @param mixed $parameters (optional)
110
	 * @param int $limit Number of items to return (optional) Returns all by default
111
	 * @param boolean $includeCollections (optional)
112
	 * @return mixed Return depends on format
113
	 */
114
	public static function getItemsSharedWith($itemType, $format = self::FORMAT_NONE,
115
											  $parameters = null, $limit = -1, $includeCollections = false) {
116
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format,
117
			$parameters, $limit, $includeCollections);
118
	}
119
120
	/**
121
	 * Get the items of item type shared with a user
122
	 * @param string $itemType
123
	 * @param string $user id for which user we want the shares
124
	 * @param int $format (optional) Format type must be defined by the backend
125
	 * @param mixed $parameters (optional)
126
	 * @param int $limit Number of items to return (optional) Returns all by default
127
	 * @param boolean $includeCollections (optional)
128
	 * @return mixed Return depends on format
129
	 */
130
	public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE,
131
												  $parameters = null, $limit = -1, $includeCollections = false) {
132
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format,
133
			$parameters, $limit, $includeCollections);
134
	}
135
136
	/**
137
	 * Get the item of item type shared with a given user by source
138
	 * @param string $itemType
139
	 * @param string $itemSource
140
	 * @param string $user User to whom the item was shared
141
	 * @param string $owner Owner of the share
142
	 * @param int $shareType only look for a specific share type
143
	 * @return array Return list of items with file_target, permissions and expiration
144
	 */
145
	public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
146
		$shares = array();
147
		$fileDependent = false;
148
149
		$where = 'WHERE';
150
		$fileDependentWhere = '';
151
		if ($itemType === 'file' || $itemType === 'folder') {
152
			$fileDependent = true;
153
			$column = 'file_source';
154
			$fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
155
			$fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
156
		} else {
157
			$column = 'item_source';
158
		}
159
160
		$select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
161
162
		$where .= ' `' . $column . '` = ? AND `item_type` = ? ';
163
		$arguments = array($itemSource, $itemType);
164
		// for link shares $user === null
165
		if ($user !== null) {
166
			$where .= ' AND `share_with` = ? ';
167
			$arguments[] = $user;
168
		}
169
170
		if ($shareType !== null) {
171
			$where .= ' AND `share_type` = ? ';
172
			$arguments[] = $shareType;
173
		}
174
175
		if ($owner !== null) {
176
			$where .= ' AND `uid_owner` = ? ';
177
			$arguments[] = $owner;
178
		}
179
180
		$query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
181
182
		$result = \OC_DB::executeAudited($query, $arguments);
183
184
		while ($row = $result->fetchRow()) {
185
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
186
				continue;
187
			}
188
			if ($fileDependent && (int)$row['file_parent'] === -1) {
189
				// if it is a mount point we need to get the path from the mount manager
190
				$mountManager = \OC\Files\Filesystem::getMountManager();
191
				$mountPoint = $mountManager->findByStorageId($row['storage_id']);
192
				if (!empty($mountPoint)) {
193
					$path = $mountPoint[0]->getMountPoint();
194
					$path = trim($path, '/');
195
					$path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
196
					$row['path'] = $path;
197
				} else {
198
					\OC::$server->getLogger()->warning(
199
						'Could not resolve mount point for ' . $row['storage_id'],
200
						['app' => 'OCP\Share']
201
					);
202
				}
203
			}
204
			$shares[] = $row;
205
		}
206
207
		//if didn't found a result than let's look for a group share.
208
		if(empty($shares) && $user !== null) {
209
			$userObject = \OC::$server->getUserManager()->get($user);
210
			$groups = [];
211
			if ($userObject) {
212
				$groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
213
			}
214
215
			if (!empty($groups)) {
216
				$where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
217
				$arguments = array($itemSource, $itemType, $groups);
218
				$types = array(null, null, IQueryBuilder::PARAM_STR_ARRAY);
219
220
				if ($owner !== null) {
221
					$where .= ' AND `uid_owner` = ?';
222
					$arguments[] = $owner;
223
					$types[] = null;
224
				}
225
226
				// TODO: inject connection, hopefully one day in the future when this
227
				// class isn't static anymore...
228
				$conn = \OC::$server->getDatabaseConnection();
229
				$result = $conn->executeQuery(
230
					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
231
					$arguments,
0 ignored issues
show
Documentation introduced by
$arguments is of type array<integer,string|array>, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
232
					$types
233
				);
234
235
				while ($row = $result->fetch()) {
236
					$shares[] = $row;
237
				}
238
			}
239
		}
240
241
		return $shares;
242
243
	}
244
245
	/**
246
	 * Get the item of item type shared with the current user by source
247
	 * @param string $itemType
248
	 * @param string $itemSource
249
	 * @param int $format (optional) Format type must be defined by the backend
250
	 * @param mixed $parameters
251
	 * @param boolean $includeCollections
252
	 * @param string $shareWith (optional) define against which user should be checked, default: current user
253
	 * @return array
254
	 */
255
	public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE,
256
													 $parameters = null, $includeCollections = false, $shareWith = null) {
257
		$shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith;
258
		return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format,
259
			$parameters, 1, $includeCollections, true);
260
	}
261
262
	/**
263
	 * Based on the given token the share information will be returned - password protected shares will be verified
264
	 * @param string $token
265
	 * @param bool $checkPasswordProtection
266
	 * @return array|boolean false will be returned in case the token is unknown or unauthorized
267
	 */
268
	public static function getShareByToken($token, $checkPasswordProtection = true) {
269
		$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `token` = ?', 1);
270
		$result = $query->execute(array($token));
271 View Code Duplication
		if ($result === false) {
272
			\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', token=' . $token, \OCP\Util::ERROR);
273
		}
274
		$row = $result->fetchRow();
275
		if ($row === false) {
276
			return false;
277
		}
278
		if (is_array($row) and self::expireItem($row)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
279
			return false;
280
		}
281
282
		// password protected shares need to be authenticated
283
		if ($checkPasswordProtection && !\OC\Share\Share::checkPasswordProtectedShare($row)) {
284
			return false;
285
		}
286
287
		return $row;
288
	}
289
290
	/**
291
	 * resolves reshares down to the last real share
292
	 * @param array $linkItem
293
	 * @return array file owner
294
	 */
295
	public static function resolveReShare($linkItem)
296
	{
297
		if (isset($linkItem['parent'])) {
298
			$parent = $linkItem['parent'];
299 View Code Duplication
			while (isset($parent)) {
300
				$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `id` = ?', 1);
301
				$item = $query->execute(array($parent))->fetchRow();
302
				if (isset($item['parent'])) {
303
					$parent = $item['parent'];
304
				} else {
305
					return $item;
306
				}
307
			}
308
		}
309
		return $linkItem;
310
	}
311
312
313
	/**
314
	 * Get the shared items of item type owned by the current user
315
	 * @param string $itemType
316
	 * @param int $format (optional) Format type must be defined by the backend
317
	 * @param mixed $parameters
318
	 * @param int $limit Number of items to return (optional) Returns all by default
319
	 * @param boolean $includeCollections
320
	 * @return mixed Return depends on format
321
	 */
322
	public static function getItemsShared($itemType, $format = self::FORMAT_NONE, $parameters = null,
323
										  $limit = -1, $includeCollections = false) {
324
		return self::getItems($itemType, null, null, null, \OC_User::getUser(), $format,
325
			$parameters, $limit, $includeCollections);
326
	}
327
328
	/**
329
	 * Get the shared item of item type owned by the current user
330
	 * @param string $itemType
331
	 * @param string $itemSource
332
	 * @param int $format (optional) Format type must be defined by the backend
333
	 * @param mixed $parameters
334
	 * @param boolean $includeCollections
335
	 * @return mixed Return depends on format
336
	 */
337
	public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
338
										 $parameters = null, $includeCollections = false) {
339
		return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format,
340
			$parameters, -1, $includeCollections);
341
	}
342
343
	/**
344
	 * Share an item with a user, group, or via private link
345
	 * @param string $itemType
346
	 * @param string $itemSource
347
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
348
	 * @param string $shareWith User or group the item is being shared with
349
	 * @param int $permissions CRUDS
350
	 * @param string $itemSourceName
351
	 * @param \DateTime|null $expirationDate
352
	 * @param bool|null $passwordChanged
353
	 * @return boolean|string Returns true on success or false on failure, Returns token on success for links
354
	 * @throws \OC\HintException when the share type is remote and the shareWith is invalid
355
	 * @throws \Exception
356
	 * @since 5.0.0 - parameter $itemSourceName was added in 6.0.0, parameter $expirationDate was added in 7.0.0, parameter $passwordChanged added in 9.0.0
357
	 */
358
	public static function shareItem($itemType, $itemSource, $shareType, $shareWith, $permissions, $itemSourceName = null, \DateTime $expirationDate = null, $passwordChanged = null) {
359
360
		$backend = self::getBackend($itemType);
361
		$l = \OC::$server->getL10N('lib');
362
363
		if ($backend->isShareTypeAllowed($shareType) === false) {
364
			$message = 'Sharing %s failed, because the backend does not allow shares from type %i';
365
			$message_t = $l->t('Sharing %s failed, because the backend does not allow shares from type %i', array($itemSourceName, $shareType));
366
			\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareType), \OCP\Util::DEBUG);
367
			throw new \Exception($message_t);
368
		}
369
370
		$uidOwner = \OC_User::getUser();
371
		$shareWithinGroupOnly = self::shareWithGroupMembersOnly();
372
373
		if (is_null($itemSourceName)) {
374
			$itemSourceName = $itemSource;
375
		}
376
		$itemName = $itemSourceName;
377
378
		// check if file can be shared
379
		if ($itemType === 'file' or $itemType === 'folder') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
380
			$path = \OC\Files\Filesystem::getPath($itemSource);
381
			$itemName = $path;
382
383
			// verify that the file exists before we try to share it
384 View Code Duplication
			if (!$path) {
385
				$message = 'Sharing %s failed, because the file does not exist';
386
				$message_t = $l->t('Sharing %s failed, because the file does not exist', array($itemSourceName));
387
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
388
				throw new \Exception($message_t);
389
			}
390
			// verify that the user has share permission
391 View Code Duplication
			if (!\OC\Files\Filesystem::isSharable($path) || \OCP\Util::isSharingDisabledForUser()) {
392
				$message = 'You are not allowed to share %s';
393
				$message_t = $l->t('You are not allowed to share %s', [$path]);
394
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $path), \OCP\Util::DEBUG);
395
				throw new \Exception($message_t);
396
			}
397
		}
398
399
		//verify that we don't share a folder which already contains a share mount point
400
		if ($itemType === 'folder') {
401
			$path = '/' . $uidOwner . '/files' . \OC\Files\Filesystem::getPath($itemSource) . '/';
402
			$mountManager = \OC\Files\Filesystem::getMountManager();
403
			$mounts = $mountManager->findIn($path);
404
			foreach ($mounts as $mount) {
405
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
406
					$message = 'Sharing "' . $itemSourceName . '" failed, because it contains files shared with you!';
407
					\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
408
					throw new \Exception($message);
409
				}
410
411
			}
412
		}
413
414
		// single file shares should never have delete permissions
415
		if ($itemType === 'file') {
416
			$permissions = (int)$permissions & ~\OCP\Constants::PERMISSION_DELETE;
417
		}
418
419
		//Validate expirationDate
420
		if ($expirationDate !== null) {
421
			try {
422
				/*
423
				 * Reuse the validateExpireDate.
424
				 * We have to pass time() since the second arg is the time
425
				 * the file was shared, since it is not shared yet we just use
426
				 * the current time.
427
				 */
428
				$expirationDate = self::validateExpireDate($expirationDate->format('Y-m-d'), time(), $itemType, $itemSource);
429
			} catch (\Exception $e) {
430
				throw new \OC\HintException($e->getMessage(), $e->getMessage(), 404);
431
			}
432
		}
433
434
		// Verify share type and sharing conditions are met
435
		if ($shareType === self::SHARE_TYPE_USER) {
436 View Code Duplication
			if ($shareWith == $uidOwner) {
437
				$message = 'Sharing %s failed, because you can not share with yourself';
438
				$message_t = $l->t('Sharing %s failed, because you can not share with yourself', [$itemName]);
439
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
440
				throw new \Exception($message_t);
441
			}
442
			if (!\OC::$server->getUserManager()->userExists($shareWith)) {
443
				$message = 'Sharing %s failed, because the user %s does not exist';
444
				$message_t = $l->t('Sharing %s failed, because the user %s does not exist', array($itemSourceName, $shareWith));
445
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
446
				throw new \Exception($message_t);
447
			}
448
			if ($shareWithinGroupOnly) {
449
				$userManager = \OC::$server->getUserManager();
450
				$groupManager = \OC::$server->getGroupManager();
451
				$userOwner = $userManager->get($uidOwner);
452
				$userShareWith = $userManager->get($shareWith);
453
				$groupsOwner = [];
454
				$groupsShareWith = [];
455
				if ($userOwner) {
456
					$groupsOwner = $groupManager->getUserGroupIds($userOwner);
457
				}
458
				if ($userShareWith) {
459
					$groupsShareWith = $groupManager->getUserGroupIds($userShareWith);
460
				}
461
				$inGroup = array_intersect($groupsOwner, $groupsShareWith);
462 View Code Duplication
				if (empty($inGroup)) {
463
					$message = 'Sharing %s failed, because the user '
464
						.'%s is not a member of any groups that %s is a member of';
465
					$message_t = $l->t('Sharing %s failed, because the user %s is not a member of any groups that %s is a member of', array($itemName, $shareWith, $uidOwner));
466
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemName, $shareWith, $uidOwner), \OCP\Util::DEBUG);
467
					throw new \Exception($message_t);
468
				}
469
			}
470
			// Check if the item source is already shared with the user, either from the same owner or a different user
471 View Code Duplication
			if ($checkExists = self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups,
472
				$shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
473
				// Only allow the same share to occur again if it is the same
474
				// owner and is not a user share, this use case is for increasing
475
				// permissions for a specific user
476
				if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
477
					$message = 'Sharing %s failed, because this item is already shared with %s';
478
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
479
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
480
					throw new \Exception($message_t);
481
				}
482
			}
483 View Code Duplication
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_USER,
484
				$shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
485
				// Only allow the same share to occur again if it is the same
486
				// owner and is not a user share, this use case is for increasing
487
				// permissions for a specific user
488
				if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
489
					$message = 'Sharing %s failed, because this item is already shared with user %s';
490
					$message_t = $l->t('Sharing %s failed, because this item is already shared with user %s', array($itemSourceName, $shareWith));
491
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::ERROR);
492
					throw new \Exception($message_t);
493
				}
494
			}
495
		} else if ($shareType === self::SHARE_TYPE_GROUP) {
496
			if (!\OC::$server->getGroupManager()->groupExists($shareWith)) {
497
				$message = 'Sharing %s failed, because the group %s does not exist';
498
				$message_t = $l->t('Sharing %s failed, because the group %s does not exist', array($itemSourceName, $shareWith));
499
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
500
				throw new \Exception($message_t);
501
			}
502
			if ($shareWithinGroupOnly) {
503
				$group = \OC::$server->getGroupManager()->get($shareWith);
504
				$user = \OC::$server->getUserManager()->get($uidOwner);
505
				if (!$group || !$user || !$group->inGroup($user)) {
506
					$message = 'Sharing %s failed, because '
507
						. '%s is not a member of the group %s';
508
					$message_t = $l->t('Sharing %s failed, because %s is not a member of the group %s', array($itemSourceName, $uidOwner, $shareWith));
509
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $uidOwner, $shareWith), \OCP\Util::DEBUG);
510
					throw new \Exception($message_t);
511
				}
512
			}
513
			// Check if the item source is already shared with the group, either from the same owner or a different user
514
			// The check for each user in the group is done inside the put() function
515
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_GROUP, $shareWith,
516
				null, self::FORMAT_NONE, null, 1, true, true)) {
517
518
				if ($checkExists['share_with'] === $shareWith && $checkExists['share_type'] === \OCP\Share::SHARE_TYPE_GROUP) {
519
					$message = 'Sharing %s failed, because this item is already shared with %s';
520
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
521
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
522
					throw new \Exception($message_t);
523
				}
524
			}
525
			// Convert share with into an array with the keys group and users
526
			$group = $shareWith;
527
			$shareWith = array();
528
			$shareWith['group'] = $group;
529
530
531
			$groupObject = \OC::$server->getGroupManager()->get($group);
532
			$userIds = [];
533
			if ($groupObject) {
534
				$users = $groupObject->searchUsers('', -1, 0);
535
				foreach ($users as $user) {
536
					$userIds[] = $user->getUID();
537
				}
538
			}
539
540
			$shareWith['users'] = array_diff($userIds, array($uidOwner));
541
		} else if ($shareType === self::SHARE_TYPE_LINK) {
542
			$updateExistingShare = false;
543
			if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') == 'yes') {
544
545
				// IF the password is changed via the old ajax endpoint verify it before deleting the old share
546
				if ($passwordChanged === true) {
547
					self::verifyPassword($shareWith);
548
				}
549
550
				// when updating a link share
551
				// FIXME Don't delete link if we update it
552
				if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_LINK, null,
553
					$uidOwner, self::FORMAT_NONE, null, 1)) {
554
					// remember old token
555
					$oldToken = $checkExists['token'];
556
					$oldPermissions = $checkExists['permissions'];
557
					//delete the old share
558
					Helper::delete($checkExists['id']);
559
					$updateExistingShare = true;
560
				}
561
562
				if ($passwordChanged === null) {
563
					// Generate hash of password - same method as user passwords
564
					if (is_string($shareWith) && $shareWith !== '') {
565
						self::verifyPassword($shareWith);
566
						$shareWith = \OC::$server->getHasher()->hash($shareWith);
567
					} else {
568
						// reuse the already set password, but only if we change permissions
569
						// otherwise the user disabled the password protection
570
						if ($checkExists && (int)$permissions !== (int)$oldPermissions) {
0 ignored issues
show
Bug introduced by
The variable $oldPermissions does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug Best Practice introduced by
The expression $checkExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
571
							$shareWith = $checkExists['share_with'];
572
						}
573
					}
574
				} else {
575
					if ($passwordChanged === true) {
576
						if (is_string($shareWith) && $shareWith !== '') {
577
							self::verifyPassword($shareWith);
578
							$shareWith = \OC::$server->getHasher()->hash($shareWith);
579
						}
580
					} else if ($updateExistingShare) {
581
						$shareWith = $checkExists['share_with'];
582
					}
583
				}
584
585
				if (\OCP\Util::isPublicLinkPasswordRequired() && empty($shareWith)) {
586
					$message = 'You need to provide a password to create a public link, only protected links are allowed';
587
					$message_t = $l->t('You need to provide a password to create a public link, only protected links are allowed');
588
					\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
589
					throw new \Exception($message_t);
590
				}
591
592
				if ($updateExistingShare === false &&
593
					self::isDefaultExpireDateEnabled() &&
594
					empty($expirationDate)) {
595
					$expirationDate = Helper::calcExpireDate();
596
				}
597
598
				// Generate token
599
				if (isset($oldToken)) {
600
					$token = $oldToken;
601
				} else {
602
					$token = \OC::$server->getSecureRandom()->generate(self::TOKEN_LENGTH,
603
						\OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
604
					);
605
				}
606
				$result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions,
607
					null, $token, $itemSourceName, $expirationDate);
608
				if ($result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
609
					return $token;
610
				} else {
611
					return false;
612
				}
613
			}
614
			$message = 'Sharing %s failed, because sharing with links is not allowed';
615
			$message_t = $l->t('Sharing %s failed, because sharing with links is not allowed', array($itemSourceName));
616
			\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
617
			throw new \Exception($message_t);
618
		} else if ($shareType === self::SHARE_TYPE_REMOTE) {
619
620
			/*
621
			 * Check if file is not already shared with the remote user
622
			 */
623
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_REMOTE,
0 ignored issues
show
Unused Code introduced by
$checkExists is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
624
				$shareWith, $uidOwner, self::FORMAT_NONE, null, 1, true, true)) {
625
					$message = 'Sharing %s failed, because this item is already shared with %s';
626
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
627
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
628
					throw new \Exception($message_t);
629
			}
630
631
			// don't allow federated shares if source and target server are the same
632
			list($user, $remote) = Helper::splitUserRemote($shareWith);
633
			$currentServer = self::removeProtocolFromUrl(\OC::$server->getURLGenerator()->getAbsoluteURL('/'));
634
			$currentUser = \OC::$server->getUserSession()->getUser()->getUID();
635
			if (Helper::isSameUserOnSameServer($user, $remote, $currentUser, $currentServer)) {
636
				$message = 'Not allowed to create a federated share with the same user.';
637
				$message_t = $l->t('Not allowed to create a federated share with the same user');
638
				\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
639
				throw new \Exception($message_t);
640
			}
641
642
			$token = \OC::$server->getSecureRandom()->generate(self::TOKEN_LENGTH, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_UPPER .
643
				\OCP\Security\ISecureRandom::CHAR_DIGITS);
644
645
			$shareWith = $user . '@' . $remote;
646
			$shareId = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, $token, $itemSourceName);
647
648
			$send = false;
649
			if ($shareId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $shareId of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
650
				$send = self::sendRemoteShare($token, $shareWith, $itemSourceName, $shareId, $uidOwner);
651
			}
652
653
			if ($send === false) {
654
				$currentUser = \OC::$server->getUserSession()->getUser()->getUID();
655
				self::unshare($itemType, $itemSource, $shareType, $shareWith, $currentUser);
656
				$message_t = $l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable.', array($itemSourceName, $shareWith));
657
				throw new \Exception($message_t);
658
			}
659
660
			return $send;
661 View Code Duplication
		} else {
662
			// Future share types need to include their own conditions
663
			$message = 'Share type %s is not valid for %s';
664
			$message_t = $l->t('Share type %s is not valid for %s', array($shareType, $itemSource));
665
			\OCP\Util::writeLog('OCP\Share', sprintf($message, $shareType, $itemSource), \OCP\Util::DEBUG);
666
			throw new \Exception($message_t);
667
		}
668
669
		// Put the item into the database
670
		$result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, null, $itemSourceName, $expirationDate);
0 ignored issues
show
Bug introduced by
It seems like $shareWith defined by array() on line 527 can also be of type array<string,?,{"users":"array"}>; however, OC\Share\Share::put() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
671
672
		return $result ? true : false;
673
	}
674
675
	/**
676
	 * Unshare an item from a user, group, or delete a private link
677
	 * @param string $itemType
678
	 * @param string $itemSource
679
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
680
	 * @param string $shareWith User or group the item is being shared with
681
	 * @param string $owner owner of the share, if null the current user is used
682
	 * @return boolean true on success or false on failure
683
	 */
684
	public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) {
685
686
		// check if it is a valid itemType
687
		self::getBackend($itemType);
688
689
		$items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType);
690
691
		$toDelete = array();
692
		$newParent = null;
693
		$currentUser = $owner ? $owner : \OC_User::getUser();
694
		foreach ($items as $item) {
695
			// delete the item with the expected share_type and owner
696
			if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) {
697
				$toDelete = $item;
698
				// if there is more then one result we don't have to delete the children
699
				// but update their parent. For group shares the new parent should always be
700
				// the original group share and not the db entry with the unique name
701
			} else if ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
702
				$newParent = $item['parent'];
703
			} else {
704
				$newParent = $item['id'];
705
			}
706
		}
707
708
		if (!empty($toDelete)) {
709
			self::unshareItem($toDelete, $newParent);
710
			return true;
711
		}
712
		return false;
713
	}
714
715
	/**
716
	 * sent status if users got informed by mail about share
717
	 * @param string $itemType
718
	 * @param string $itemSource
719
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
720
	 * @param string $recipient with whom was the file shared
721
	 * @param boolean $status
722
	 */
723
	public static function setSendMailStatus($itemType, $itemSource, $shareType, $recipient, $status) {
724
		$status = $status ? 1 : 0;
725
726
		$query = \OC_DB::prepare(
727
			'UPDATE `*PREFIX*share`
728
					SET `mail_send` = ?
729
					WHERE `item_type` = ? AND `item_source` = ? AND `share_type` = ? AND `share_with` = ?');
730
731
		$result = $query->execute(array($status, $itemType, $itemSource, $shareType, $recipient));
732
733
		if($result === false) {
734
			\OCP\Util::writeLog('OCP\Share', 'Couldn\'t set send mail status', \OCP\Util::ERROR);
735
		}
736
	}
737
738
	/**
739
	 * validate expiration date if it meets all constraints
740
	 *
741
	 * @param string $expireDate well formatted date string, e.g. "DD-MM-YYYY"
742
	 * @param string $shareTime timestamp when the file was shared
743
	 * @param string $itemType
744
	 * @param string $itemSource
745
	 * @return \DateTime validated date
746
	 * @throws \Exception when the expire date is in the past or further in the future then the enforced date
747
	 */
748
	private static function validateExpireDate($expireDate, $shareTime, $itemType, $itemSource) {
749
		$l = \OC::$server->getL10N('lib');
750
		$date = new \DateTime($expireDate);
751
		$today = new \DateTime('now');
752
753
		// if the user doesn't provide a share time we need to get it from the database
754
		// fall-back mode to keep API stable, because the $shareTime parameter was added later
755
		$defaultExpireDateEnforced = \OCP\Util::isDefaultExpireDateEnforced();
756
		if ($defaultExpireDateEnforced && $shareTime === null) {
757
			$items = self::getItemShared($itemType, $itemSource);
758
			$firstItem = reset($items);
759
			$shareTime = (int)$firstItem['stime'];
760
		}
761
762
		if ($defaultExpireDateEnforced) {
763
			// initialize max date with share time
764
			$maxDate = new \DateTime();
765
			$maxDate->setTimestamp($shareTime);
766
			$maxDays = \OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
767
			$maxDate->add(new \DateInterval('P' . $maxDays . 'D'));
768
			if ($date > $maxDate) {
769
				$warning = 'Cannot set expiration date. Shares cannot expire later than ' . $maxDays . ' after they have been shared';
770
				$warning_t = $l->t('Cannot set expiration date. Shares cannot expire later than %s after they have been shared', array($maxDays));
771
				\OCP\Util::writeLog('OCP\Share', $warning, \OCP\Util::WARN);
772
				throw new \Exception($warning_t);
773
			}
774
		}
775
776 View Code Duplication
		if ($date < $today) {
777
			$message = 'Cannot set expiration date. Expiration date is in the past';
778
			$message_t = $l->t('Cannot set expiration date. Expiration date is in the past');
779
			\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::WARN);
780
			throw new \Exception($message_t);
781
		}
782
783
		return $date;
784
	}
785
786
	/**
787
	 * Checks whether a share has expired, calls unshareItem() if yes.
788
	 * @param array $item Share data (usually database row)
789
	 * @return boolean True if item was expired, false otherwise.
790
	 */
791
	protected static function expireItem(array $item) {
792
793
		$result = false;
794
795
		// only use default expiration date for link shares
796
		if ((int) $item['share_type'] === self::SHARE_TYPE_LINK) {
797
798
			// calculate expiration date
799
			if (!empty($item['expiration'])) {
800
				$userDefinedExpire = new \DateTime($item['expiration']);
801
				$expires = $userDefinedExpire->getTimestamp();
802
			} else {
803
				$expires = null;
804
			}
805
806
807
			// get default expiration settings
808
			$defaultSettings = Helper::getDefaultExpireSetting();
809
			$expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
810
811
812
			if (is_int($expires)) {
813
				$now = time();
814
				if ($now > $expires) {
815
					self::unshareItem($item);
816
					$result = true;
817
				}
818
			}
819
		}
820
		return $result;
821
	}
822
823
	/**
824
	 * Unshares a share given a share data array
825
	 * @param array $item Share data (usually database row)
826
	 * @param int $newParent parent ID
827
	 * @return null
828
	 */
829
	protected static function unshareItem(array $item, $newParent = null) {
830
831
		$shareType = (int)$item['share_type'];
832
		$shareWith = null;
833
		if ($shareType !== \OCP\Share::SHARE_TYPE_LINK) {
834
			$shareWith = $item['share_with'];
835
		}
836
837
		// Pass all the vars we have for now, they may be useful
838
		$hookParams = array(
839
			'id'            => $item['id'],
840
			'itemType'      => $item['item_type'],
841
			'itemSource'    => $item['item_source'],
842
			'shareType'     => $shareType,
843
			'shareWith'     => $shareWith,
844
			'itemParent'    => $item['parent'],
845
			'uidOwner'      => $item['uid_owner'],
846
		);
847
		if($item['item_type'] === 'file' || $item['item_type'] === 'folder') {
848
			$hookParams['fileSource'] = $item['file_source'];
849
			$hookParams['fileTarget'] = $item['file_target'];
850
		}
851
852
		\OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
853
		$deletedShares = Helper::delete($item['id'], false, null, $newParent);
854
		$deletedShares[] = $hookParams;
855
		$hookParams['deletedShares'] = $deletedShares;
856
		\OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
857
		if ((int)$item['share_type'] === \OCP\Share::SHARE_TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
858
			list(, $remote) = Helper::splitUserRemote($item['share_with']);
859
			self::sendRemoteUnshare($remote, $item['id'], $item['token']);
860
		}
861
	}
862
863
	/**
864
	 * Get the backend class for the specified item type
865
	 * @param string $itemType
866
	 * @throws \Exception
867
	 * @return \OCP\Share_Backend
868
	 */
869
	public static function getBackend($itemType) {
870
		$l = \OC::$server->getL10N('lib');
871
		if (isset(self::$backends[$itemType])) {
872
			return self::$backends[$itemType];
873
		} else if (isset(self::$backendTypes[$itemType]['class'])) {
874
			$class = self::$backendTypes[$itemType]['class'];
875
			if (class_exists($class)) {
876
				self::$backends[$itemType] = new $class;
877 View Code Duplication
				if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
878
					$message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
879
					$message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', array($class));
880
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), \OCP\Util::ERROR);
881
					throw new \Exception($message_t);
882
				}
883
				return self::$backends[$itemType];
884 View Code Duplication
			} else {
885
				$message = 'Sharing backend %s not found';
886
				$message_t = $l->t('Sharing backend %s not found', array($class));
887
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), \OCP\Util::ERROR);
888
				throw new \Exception($message_t);
889
			}
890
		}
891
		$message = 'Sharing backend for %s not found';
892
		$message_t = $l->t('Sharing backend for %s not found', array($itemType));
893
		\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), \OCP\Util::ERROR);
894
		throw new \Exception($message_t);
895
	}
896
897
	/**
898
	 * Check if resharing is allowed
899
	 * @return boolean true if allowed or false
900
	 *
901
	 * Resharing is allowed by default if not configured
902
	 */
903
	public static function isResharingAllowed() {
904
		if (!isset(self::$isResharingAllowed)) {
905
			if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
906
				self::$isResharingAllowed = true;
907
			} else {
908
				self::$isResharingAllowed = false;
909
			}
910
		}
911
		return self::$isResharingAllowed;
912
	}
913
914
	/**
915
	 * Get a list of collection item types for the specified item type
916
	 * @param string $itemType
917
	 * @return array
918
	 */
919
	private static function getCollectionItemTypes($itemType) {
920
		$collectionTypes = array($itemType);
921
		foreach (self::$backendTypes as $type => $backend) {
922
			if (in_array($backend['collectionOf'], $collectionTypes)) {
923
				$collectionTypes[] = $type;
924
			}
925
		}
926
		// TODO Add option for collections to be collection of themselves, only 'folder' does it now...
927
		if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
928
			unset($collectionTypes[0]);
929
		}
930
		// Return array if collections were found or the item type is a
931
		// collection itself - collections can be inside collections
932
		if (count($collectionTypes) > 0) {
933
			return $collectionTypes;
934
		}
935
		return false;
936
	}
937
938
	/**
939
	 * Get the owners of items shared with a user.
940
	 *
941
	 * @param string $user The user the items are shared with.
942
	 * @param string $type The type of the items shared with the user.
943
	 * @param boolean $includeCollections Include collection item types (optional)
944
	 * @param boolean $includeOwner include owner in the list of users the item is shared with (optional)
945
	 * @return array
946
	 */
947
	public static function getSharedItemsOwners($user, $type, $includeCollections = false, $includeOwner = false) {
948
		// First, we find out if $type is part of a collection (and if that collection is part of
949
		// another one and so on).
950
		$collectionTypes = array();
951
		if (!$includeCollections || !$collectionTypes = self::getCollectionItemTypes($type)) {
952
			$collectionTypes[] = $type;
953
		}
954
955
		// Of these collection types, along with our original $type, we make a
956
		// list of the ones for which a sharing backend has been registered.
957
		// FIXME: Ideally, we wouldn't need to nest getItemsSharedWith in this loop but just call it
958
		// with its $includeCollections parameter set to true. Unfortunately, this fails currently.
959
		$allMaybeSharedItems = array();
960
		foreach ($collectionTypes as $collectionType) {
0 ignored issues
show
Bug introduced by
The expression $collectionTypes of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
961
			if (isset(self::$backends[$collectionType])) {
962
				$allMaybeSharedItems[$collectionType] = self::getItemsSharedWithUser(
963
					$collectionType,
964
					$user,
965
					self::FORMAT_NONE
966
				);
967
			}
968
		}
969
970
		$owners = array();
971
		if ($includeOwner) {
972
			$owners[] = $user;
973
		}
974
975
		// We take a look at all shared items of the given $type (or of the collections it is part of)
976
		// and find out their owners. Then, we gather the tags for the original $type from all owners,
977
		// and return them as elements of a list that look like "Tag (owner)".
978
		foreach ($allMaybeSharedItems as $collectionType => $maybeSharedItems) {
979
			foreach ($maybeSharedItems as $sharedItem) {
980
				if (isset($sharedItem['id'])) { //workaround for https://github.com/owncloud/core/issues/2814
981
					$owners[] = $sharedItem['uid_owner'];
982
				}
983
			}
984
		}
985
986
		return $owners;
987
	}
988
989
	/**
990
	 * Get shared items from the database
991
	 * @param string $itemType
992
	 * @param string $item Item source or target (optional)
993
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
994
	 * @param string $shareWith User or group the item is being shared with
995
	 * @param string $uidOwner User that is the owner of shared items (optional)
996
	 * @param int $format Format to convert items to with formatItems() (optional)
997
	 * @param mixed $parameters to pass to formatItems() (optional)
998
	 * @param int $limit Number of items to return, -1 to return all matches (optional)
999
	 * @param boolean $includeCollections Include collection item types (optional)
1000
	 * @param boolean $itemShareWithBySource (optional)
1001
	 * @param boolean $checkExpireDate
1002
	 * @return array
1003
	 *
1004
	 * See public functions getItem(s)... for parameter usage
1005
	 *
1006
	 */
1007
	public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
1008
									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
1009
									$includeCollections = false, $itemShareWithBySource = false, $checkExpireDate  = true) {
1010
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
1011
			return array();
1012
		}
1013
		$backend = self::getBackend($itemType);
1014
		$collectionTypes = false;
1015
		// Get filesystem root to add it to the file target and remove from the
1016
		// file source, match file_source with the file cache
1017
		if ($itemType == 'file' || $itemType == 'folder') {
1018
			if(!is_null($uidOwner)) {
1019
				$root = \OC\Files\Filesystem::getRoot();
1020
			} else {
1021
				$root = '';
1022
			}
1023
			$where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
1024
			if (!isset($item)) {
1025
				$where .= ' AND `file_target` IS NOT NULL ';
1026
			}
1027
			$where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
1028
			$fileDependent = true;
1029
			$queryArgs = array();
1030
		} else {
1031
			$fileDependent = false;
1032
			$root = '';
1033
			$collectionTypes = self::getCollectionItemTypes($itemType);
1034
			if ($includeCollections && !isset($item) && $collectionTypes) {
1035
				// If includeCollections is true, find collections of this item type, e.g. a music album contains songs
1036
				if (!in_array($itemType, $collectionTypes)) {
1037
					$itemTypes = array_merge(array($itemType), $collectionTypes);
1038
				} else {
1039
					$itemTypes = $collectionTypes;
1040
				}
1041
				$placeholders = join(',', array_fill(0, count($itemTypes), '?'));
1042
				$where = ' WHERE `item_type` IN ('.$placeholders.'))';
1043
				$queryArgs = $itemTypes;
1044
			} else {
1045
				$where = ' WHERE `item_type` = ?';
1046
				$queryArgs = array($itemType);
1047
			}
1048
		}
1049
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1050
			$where .= ' AND `share_type` != ?';
1051
			$queryArgs[] = self::SHARE_TYPE_LINK;
1052
		}
1053
		if (isset($shareType)) {
1054
			// Include all user and group items
1055
			if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
1056
				$where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
1057
				$queryArgs[] = self::SHARE_TYPE_USER;
1058
				$queryArgs[] = self::$shareTypeGroupUserUnique;
1059
				$queryArgs[] = $shareWith;
1060
1061
				$user = \OC::$server->getUserManager()->get($shareWith);
1062
				$groups = [];
1063
				if ($user) {
1064
					$groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
1065
				}
1066
				if (!empty($groups)) {
1067
					$placeholders = join(',', array_fill(0, count($groups), '?'));
1068
					$where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
1069
					$queryArgs[] = self::SHARE_TYPE_GROUP;
1070
					$queryArgs = array_merge($queryArgs, $groups);
1071
				}
1072
				$where .= ')';
1073
				// Don't include own group shares
1074
				$where .= ' AND `uid_owner` != ?';
1075
				$queryArgs[] = $shareWith;
1076
			} else {
1077
				$where .= ' AND `share_type` = ?';
1078
				$queryArgs[] = $shareType;
1079
				if (isset($shareWith)) {
1080
					$where .= ' AND `share_with` = ?';
1081
					$queryArgs[] = $shareWith;
1082
				}
1083
			}
1084
		}
1085
		if (isset($uidOwner)) {
1086
			$where .= ' AND `uid_owner` = ?';
1087
			$queryArgs[] = $uidOwner;
1088
			if (!isset($shareType)) {
1089
				// Prevent unique user targets for group shares from being selected
1090
				$where .= ' AND `share_type` != ?';
1091
				$queryArgs[] = self::$shareTypeGroupUserUnique;
1092
			}
1093
			if ($fileDependent) {
1094
				$column = 'file_source';
1095
			} else {
1096
				$column = 'item_source';
1097
			}
1098
		} else {
1099
			if ($fileDependent) {
1100
				$column = 'file_target';
1101
			} else {
1102
				$column = 'item_target';
1103
			}
1104
		}
1105
		if (isset($item)) {
1106
			$collectionTypes = self::getCollectionItemTypes($itemType);
1107
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
1108
				$where .= ' AND (';
1109
			} else {
1110
				$where .= ' AND';
1111
			}
1112
			// If looking for own shared items, check item_source else check item_target
1113
			if (isset($uidOwner) || $itemShareWithBySource) {
1114
				// If item type is a file, file source needs to be checked in case the item was converted
1115
				if ($fileDependent) {
1116
					$where .= ' `file_source` = ?';
1117
					$column = 'file_source';
1118
				} else {
1119
					$where .= ' `item_source` = ?';
1120
					$column = 'item_source';
1121
				}
1122
			} else {
1123
				if ($fileDependent) {
1124
					$where .= ' `file_target` = ?';
1125
					$item = \OC\Files\Filesystem::normalizePath($item);
1126
				} else {
1127
					$where .= ' `item_target` = ?';
1128
				}
1129
			}
1130
			$queryArgs[] = $item;
1131
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
1132
				$placeholders = join(',', array_fill(0, count($collectionTypes), '?'));
1133
				$where .= ' OR `item_type` IN ('.$placeholders.'))';
1134
				$queryArgs = array_merge($queryArgs, $collectionTypes);
1135
			}
1136
		}
1137
1138
		if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) {
1139
			// Make sure the unique user target is returned if it exists,
1140
			// unique targets should follow the group share in the database
1141
			// If the limit is not 1, the filtering can be done later
1142
			$where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
1143
		} else {
1144
			$where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
1145
		}
1146
1147
		if ($limit != -1 && !$includeCollections) {
1148
			// The limit must be at least 3, because filtering needs to be done
1149
			if ($limit < 3) {
1150
				$queryLimit = 3;
1151
			} else {
1152
				$queryLimit = $limit;
1153
			}
1154
		} else {
1155
			$queryLimit = null;
1156
		}
1157
		$select = self::createSelectStatement($format, $fileDependent, $uidOwner);
1158
		$root = strlen($root);
1159
		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
1160
		$result = $query->execute($queryArgs);
1161 View Code Duplication
		if ($result === false) {
1162
			\OCP\Util::writeLog('OCP\Share',
1163
				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
1164
				\OCP\Util::ERROR);
1165
		}
1166
		$items = array();
1167
		$targets = array();
1168
		$switchedItems = array();
1169
		$mounts = array();
1170
		while ($row = $result->fetchRow()) {
1171
			self::transformDBResults($row);
1172
			// Filter out duplicate group shares for users with unique targets
1173
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
1174
				continue;
1175
			}
1176
			if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
1177
				$row['share_type'] = self::SHARE_TYPE_GROUP;
1178
				$row['unique_name'] = true; // remember that we use a unique name for this user
1179
				$row['share_with'] = $items[$row['parent']]['share_with'];
1180
				// if the group share was unshared from the user we keep the permission, otherwise
1181
				// we take the permission from the parent because this is always the up-to-date
1182
				// permission for the group share
1183
				if ($row['permissions'] > 0) {
1184
					$row['permissions'] = $items[$row['parent']]['permissions'];
1185
				}
1186
				// Remove the parent group share
1187
				unset($items[$row['parent']]);
1188
				if ($row['permissions'] == 0) {
1189
					continue;
1190
				}
1191
			} else if (!isset($uidOwner)) {
1192
				// Check if the same target already exists
1193
				if (isset($targets[$row['id']])) {
1194
					// Check if the same owner shared with the user twice
1195
					// through a group and user share - this is allowed
1196
					$id = $targets[$row['id']];
1197
					if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
1198
						// Switch to group share type to ensure resharing conditions aren't bypassed
1199
						if ($items[$id]['share_type'] != self::SHARE_TYPE_GROUP) {
1200
							$items[$id]['share_type'] = self::SHARE_TYPE_GROUP;
1201
							$items[$id]['share_with'] = $row['share_with'];
1202
						}
1203
						// Switch ids if sharing permission is granted on only
1204
						// one share to ensure correct parent is used if resharing
1205
						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
1206
							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
1207
							$items[$row['id']] = $items[$id];
1208
							$switchedItems[$id] = $row['id'];
1209
							unset($items[$id]);
1210
							$id = $row['id'];
1211
						}
1212
						$items[$id]['permissions'] |= (int)$row['permissions'];
1213
1214
					}
1215
					continue;
1216
				} elseif (!empty($row['parent'])) {
1217
					$targets[$row['parent']] = $row['id'];
1218
				}
1219
			}
1220
			// Remove root from file source paths if retrieving own shared items
1221
			if (isset($uidOwner) && isset($row['path'])) {
1222
				if (isset($row['parent'])) {
1223
					$query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?');
1224
					$parentResult = $query->execute(array($row['parent']));
1225
					if ($result === false) {
1226
						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
1227
							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
1228
							\OCP\Util::ERROR);
1229
					} else {
1230
						$parentRow = $parentResult->fetchRow();
1231
						$tmpPath = $parentRow['file_target'];
1232
						// find the right position where the row path continues from the target path
1233
						$pos = strrpos($row['path'], $parentRow['file_target']);
1234
						$subPath = substr($row['path'], $pos);
1235
						$splitPath = explode('/', $subPath);
1236
						foreach (array_slice($splitPath, 2) as $pathPart) {
1237
							$tmpPath = $tmpPath . '/' . $pathPart;
1238
						}
1239
						$row['path'] = $tmpPath;
1240
					}
1241
				} else {
1242
					if (!isset($mounts[$row['storage']])) {
1243
						$mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
1244
						if (is_array($mountPoints) && !empty($mountPoints)) {
1245
							$mounts[$row['storage']] = current($mountPoints);
1246
						}
1247
					}
1248
					if (!empty($mounts[$row['storage']])) {
1249
						$path = $mounts[$row['storage']]->getMountPoint().$row['path'];
1250
						$relPath = substr($path, $root); // path relative to data/user
1251
						$row['path'] = rtrim($relPath, '/');
1252
					}
1253
				}
1254
			}
1255
1256
			if($checkExpireDate) {
1257
				if (self::expireItem($row)) {
1258
					continue;
1259
				}
1260
			}
1261
			// Check if resharing is allowed, if not remove share permission
1262
			if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
1263
				$row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
1264
			}
1265
			// Add display names to result
1266
			$row['share_with_displayname'] = $row['share_with'];
1267
			if ( isset($row['share_with']) && $row['share_with'] != '' &&
1268
				$row['share_type'] === self::SHARE_TYPE_USER) {
1269
				$row['share_with_displayname'] = \OCP\User::getDisplayName($row['share_with']);
1270
			} else if(isset($row['share_with']) && $row['share_with'] != '' &&
1271
				$row['share_type'] === self::SHARE_TYPE_REMOTE) {
1272
				$addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
1273 View Code Duplication
				foreach ($addressBookEntries as $entry) {
1274
					foreach ($entry['CLOUD'] as $cloudID) {
1275
						if ($cloudID === $row['share_with']) {
1276
							$row['share_with_displayname'] = $entry['FN'];
1277
						}
1278
					}
1279
				}
1280
			}
1281
			if ( isset($row['uid_owner']) && $row['uid_owner'] != '') {
1282
				$row['displayname_owner'] = \OCP\User::getDisplayName($row['uid_owner']);
1283
			}
1284
1285
			if ($row['permissions'] > 0) {
1286
				$items[$row['id']] = $row;
1287
			}
1288
1289
		}
1290
1291
		// group items if we are looking for items shared with the current user
1292
		if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
1293
			$items = self::groupItems($items, $itemType);
1294
		}
1295
1296
		if (!empty($items)) {
1297
			$collectionItems = array();
1298
			foreach ($items as &$row) {
1299
				// Return only the item instead of a 2-dimensional array
1300
				if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) {
1301
					if ($format == self::FORMAT_NONE) {
1302
						return $row;
1303
					} else {
1304
						break;
1305
					}
1306
				}
1307
				// Check if this is a collection of the requested item type
1308
				if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
1309
					if (($collectionBackend = self::getBackend($row['item_type']))
1310
						&& $collectionBackend instanceof \OCP\Share_Backend_Collection) {
1311
						// Collections can be inside collections, check if the item is a collection
1312
						if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
1313
							$collectionItems[] = $row;
1314
						} else {
1315
							$collection = array();
1316
							$collection['item_type'] = $row['item_type'];
1317
							if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
1318
								$collection['path'] = basename($row['path']);
1319
							}
1320
							$row['collection'] = $collection;
1321
							// Fetch all of the children sources
1322
							$children = $collectionBackend->getChildren($row[$column]);
0 ignored issues
show
Documentation introduced by
$row[$column] is of type array<string,?>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1323
							foreach ($children as $child) {
1324
								$childItem = $row;
1325
								$childItem['item_type'] = $itemType;
1326
								if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
1327
									$childItem['item_source'] = $child['source'];
1328
									$childItem['item_target'] = $child['target'];
1329
								}
1330
								if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
1331
									if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
1332
										$childItem['file_source'] = $child['source'];
1333
									} else { // TODO is this really needed if we already know that we use the file backend?
1334
										$meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
1335
										$childItem['file_source'] = $meta['fileid'];
1336
									}
1337
									$childItem['file_target'] =
1338
										\OC\Files\Filesystem::normalizePath($child['file_path']);
1339
								}
1340
								if (isset($item)) {
1341
									if ($childItem[$column] == $item) {
1342
										// Return only the item instead of a 2-dimensional array
1343
										if ($limit == 1) {
1344
											if ($format == self::FORMAT_NONE) {
1345
												return $childItem;
1346
											} else {
1347
												// Unset the items array and break out of both loops
1348
												$items = array();
1349
												$items[] = $childItem;
1350
												break 2;
1351
											}
1352
										} else {
1353
											$collectionItems[] = $childItem;
1354
										}
1355
									}
1356
								} else {
1357
									$collectionItems[] = $childItem;
1358
								}
1359
							}
1360
						}
1361
					}
1362
					// Remove collection item
1363
					$toRemove = $row['id'];
1364
					if (array_key_exists($toRemove, $switchedItems)) {
1365
						$toRemove = $switchedItems[$toRemove];
1366
					}
1367
					unset($items[$toRemove]);
1368
				} elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
1369
					// FIXME: Thats a dirty hack to improve file sharing performance,
1370
					// see github issue #10588 for more details
1371
					// Need to find a solution which works for all back-ends
1372
					$collectionBackend = self::getBackend($row['item_type']);
1373
					$sharedParents = $collectionBackend->getParents($row['item_source']);
1374
					foreach ($sharedParents as $parent) {
1375
						$collectionItems[] = $parent;
1376
					}
1377
				}
1378
			}
1379
			if (!empty($collectionItems)) {
1380
				$collectionItems = array_unique($collectionItems, SORT_REGULAR);
1381
				$items = array_merge($items, $collectionItems);
1382
			}
1383
1384
			// filter out invalid items, these can appear when subshare entries exist
1385
			// for a group in which the requested user isn't a member any more
1386
			$items = array_filter($items, function($item) {
1387
				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
1388
			});
1389
1390
			return self::formatResult($items, $column, $backend, $format, $parameters);
1391
		} elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
1392
			// FIXME: Thats a dirty hack to improve file sharing performance,
1393
			// see github issue #10588 for more details
1394
			// Need to find a solution which works for all back-ends
1395
			$collectionItems = array();
1396
			$collectionBackend = self::getBackend('folder');
1397
			$sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
1398
			foreach ($sharedParents as $parent) {
1399
				$collectionItems[] = $parent;
1400
			}
1401
			if ($limit === 1) {
1402
				return reset($collectionItems);
1403
			}
1404
			return self::formatResult($collectionItems, $column, $backend, $format, $parameters);
1405
		}
1406
1407
		return array();
1408
	}
1409
1410
	/**
1411
	 * group items with link to the same source
1412
	 *
1413
	 * @param array $items
1414
	 * @param string $itemType
1415
	 * @return array of grouped items
1416
	 */
1417
	protected static function groupItems($items, $itemType) {
1418
1419
		$fileSharing = ($itemType === 'file' || $itemType === 'folder') ? true : false;
1420
1421
		$result = array();
1422
1423
		foreach ($items as $item) {
1424
			$grouped = false;
1425
			foreach ($result as $key => $r) {
1426
				// for file/folder shares we need to compare file_source, otherwise we compare item_source
1427
				// only group shares if they already point to the same target, otherwise the file where shared
1428
				// before grouping of shares was added. In this case we don't group them toi avoid confusions
1429
				if (( $fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
1430
					(!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
1431
					// add the first item to the list of grouped shares
1432
					if (!isset($result[$key]['grouped'])) {
1433
						$result[$key]['grouped'][] = $result[$key];
1434
					}
1435
					$result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
1436
					$result[$key]['grouped'][] = $item;
1437
					$grouped = true;
1438
					break;
1439
				}
1440
			}
1441
1442
			if (!$grouped) {
1443
				$result[] = $item;
1444
			}
1445
1446
		}
1447
1448
		return $result;
1449
	}
1450
1451
	/**
1452
	 * Put shared item into the database
1453
	 * @param string $itemType Item type
1454
	 * @param string $itemSource Item source
1455
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
1456
	 * @param string $shareWith User or group the item is being shared with
1457
	 * @param string $uidOwner User that is the owner of shared item
1458
	 * @param int $permissions CRUDS permissions
1459
	 * @param boolean|array $parentFolder Parent folder target (optional)
1460
	 * @param string $token (optional)
1461
	 * @param string $itemSourceName name of the source item (optional)
1462
	 * @param \DateTime $expirationDate (optional)
1463
	 * @throws \Exception
1464
	 * @return mixed id of the new share or false
1465
	 */
1466
	private static function put($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
1467
								$permissions, $parentFolder = null, $token = null, $itemSourceName = null, \DateTime $expirationDate = null) {
1468
1469
		$queriesToExecute = array();
1470
		$suggestedItemTarget = null;
1471
		$groupFileTarget = $fileTarget = $suggestedFileTarget = $filePath = '';
1472
		$groupItemTarget = $itemTarget = $fileSource = $parent = 0;
1473
1474
		$result = self::checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate);
1475
		if(!empty($result)) {
1476
			$parent = $result['parent'];
1477
			$itemSource = $result['itemSource'];
1478
			$fileSource = $result['fileSource'];
1479
			$suggestedItemTarget = $result['suggestedItemTarget'];
1480
			$suggestedFileTarget = $result['suggestedFileTarget'];
1481
			$filePath = $result['filePath'];
1482
		}
1483
1484
		$isGroupShare = false;
1485
		if ($shareType == self::SHARE_TYPE_GROUP) {
1486
			$isGroupShare = true;
1487
			if (isset($shareWith['users'])) {
1488
				$users = $shareWith['users'];
1489
			} else {
1490
				$group = \OC::$server->getGroupManager()->get($shareWith['group']);
1491
				if ($group) {
1492
					$users = $group->searchUsers('', -1, 0);
1493
					$userIds = [];
1494
					foreach ($users as $user) {
1495
						$userIds[] = $user->getUID();
1496
					}
1497
					$users = $userIds;
1498
				} else {
1499
					$users = [];
1500
				}
1501
			}
1502
			// remove current user from list
1503
			if (in_array(\OCP\User::getUser(), $users)) {
1504
				unset($users[array_search(\OCP\User::getUser(), $users)]);
1505
			}
1506
			$groupItemTarget = Helper::generateTarget($itemType, $itemSource,
1507
				$shareType, $shareWith['group'], $uidOwner, $suggestedItemTarget);
1508
			$groupFileTarget = Helper::generateTarget($itemType, $itemSource,
1509
				$shareType, $shareWith['group'], $uidOwner, $filePath);
1510
1511
			// add group share to table and remember the id as parent
1512
			$queriesToExecute['groupShare'] = array(
1513
				'itemType'			=> $itemType,
1514
				'itemSource'		=> $itemSource,
1515
				'itemTarget'		=> $groupItemTarget,
1516
				'shareType'			=> $shareType,
1517
				'shareWith'			=> $shareWith['group'],
1518
				'uidOwner'			=> $uidOwner,
1519
				'permissions'		=> $permissions,
1520
				'shareTime'			=> time(),
1521
				'fileSource'		=> $fileSource,
1522
				'fileTarget'		=> $groupFileTarget,
1523
				'token'				=> $token,
1524
				'parent'			=> $parent,
1525
				'expiration'		=> $expirationDate,
1526
			);
1527
1528
		} else {
1529
			$users = array($shareWith);
1530
			$itemTarget = Helper::generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
1531
				$suggestedItemTarget);
1532
		}
1533
1534
		$run = true;
1535
		$error = '';
1536
		$preHookData = array(
1537
			'itemType' => $itemType,
1538
			'itemSource' => $itemSource,
1539
			'shareType' => $shareType,
1540
			'uidOwner' => $uidOwner,
1541
			'permissions' => $permissions,
1542
			'fileSource' => $fileSource,
1543
			'expiration' => $expirationDate,
1544
			'token' => $token,
1545
			'run' => &$run,
1546
			'error' => &$error
1547
		);
1548
1549
		$preHookData['itemTarget'] = ($isGroupShare) ? $groupItemTarget : $itemTarget;
1550
		$preHookData['shareWith'] = ($isGroupShare) ? $shareWith['group'] : $shareWith;
1551
1552
		\OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData);
1553
1554
		if ($run === false) {
1555
			throw new \Exception($error);
1556
		}
1557
1558
		foreach ($users as $user) {
0 ignored issues
show
Bug introduced by
The expression $users of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1559
			$sourceId = ($itemType === 'file' || $itemType === 'folder') ? $fileSource : $itemSource;
1560
			$sourceExists = self::getItemSharedWithBySource($itemType, $sourceId, self::FORMAT_NONE, null, true, $user);
1561
1562
			$userShareType = ($isGroupShare) ? self::$shareTypeGroupUserUnique : $shareType;
1563
1564
			if ($sourceExists && $sourceExists['item_source'] === $itemSource) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sourceExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1565
				$fileTarget = $sourceExists['file_target'];
1566
				$itemTarget = $sourceExists['item_target'];
1567
1568
				// for group shares we don't need a additional entry if the target is the same
1569
				if($isGroupShare && $groupItemTarget === $itemTarget) {
1570
					continue;
1571
				}
1572
1573
			} elseif(!$sourceExists && !$isGroupShare)  {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sourceExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1574
1575
				$itemTarget = Helper::generateTarget($itemType, $itemSource, $userShareType, $user,
1576
					$uidOwner, $suggestedItemTarget, $parent);
1577
				if (isset($fileSource)) {
1578
					if ($parentFolder) {
1579
						if ($parentFolder === true) {
1580
							$fileTarget = Helper::generateTarget('file', $filePath, $userShareType, $user,
1581
								$uidOwner, $suggestedFileTarget, $parent);
1582
							if ($fileTarget != $groupFileTarget) {
1583
								$parentFolders[$user]['folder'] = $fileTarget;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$parentFolders was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parentFolders = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1584
							}
1585
						} else if (isset($parentFolder[$user])) {
1586
							$fileTarget = $parentFolder[$user]['folder'].$itemSource;
1587
							$parent = $parentFolder[$user]['id'];
1588
						}
1589
					} else {
1590
						$fileTarget = Helper::generateTarget('file', $filePath, $userShareType,
1591
							$user, $uidOwner, $suggestedFileTarget, $parent);
1592
					}
1593
				} else {
1594
					$fileTarget = null;
1595
				}
1596
1597
			} else {
1598
1599
				// group share which doesn't exists until now, check if we need a unique target for this user
1600
1601
				$itemTarget = Helper::generateTarget($itemType, $itemSource, self::SHARE_TYPE_USER, $user,
1602
					$uidOwner, $suggestedItemTarget, $parent);
1603
1604
				// do we also need a file target
1605
				if (isset($fileSource)) {
1606
					$fileTarget = Helper::generateTarget('file', $filePath, self::SHARE_TYPE_USER, $user,
1607
						$uidOwner, $suggestedFileTarget, $parent);
1608
				} else {
1609
					$fileTarget = null;
1610
				}
1611
1612
				if (($itemTarget === $groupItemTarget) &&
1613
					(!isset($fileSource) || $fileTarget === $groupFileTarget)) {
1614
					continue;
1615
				}
1616
			}
1617
1618
			$queriesToExecute[] = array(
1619
				'itemType'			=> $itemType,
1620
				'itemSource'		=> $itemSource,
1621
				'itemTarget'		=> $itemTarget,
1622
				'shareType'			=> $userShareType,
1623
				'shareWith'			=> $user,
1624
				'uidOwner'			=> $uidOwner,
1625
				'permissions'		=> $permissions,
1626
				'shareTime'			=> time(),
1627
				'fileSource'		=> $fileSource,
1628
				'fileTarget'		=> $fileTarget,
1629
				'token'				=> $token,
1630
				'parent'			=> $parent,
1631
				'expiration'		=> $expirationDate,
1632
			);
1633
1634
		}
1635
1636
		$id = false;
1637
		if ($isGroupShare) {
1638
			$id = self::insertShare($queriesToExecute['groupShare']);
1639
			// Save this id, any extra rows for this group share will need to reference it
1640
			$parent = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share');
1641
			unset($queriesToExecute['groupShare']);
1642
		}
1643
1644
		foreach ($queriesToExecute as $shareQuery) {
1645
			$shareQuery['parent'] = $parent;
1646
			$id = self::insertShare($shareQuery);
1647
		}
1648
1649
		$postHookData = array(
1650
			'itemType' => $itemType,
1651
			'itemSource' => $itemSource,
1652
			'parent' => $parent,
1653
			'shareType' => $shareType,
1654
			'uidOwner' => $uidOwner,
1655
			'permissions' => $permissions,
1656
			'fileSource' => $fileSource,
1657
			'id' => $parent,
1658
			'token' => $token,
1659
			'expirationDate' => $expirationDate,
1660
		);
1661
1662
		$postHookData['shareWith'] = ($isGroupShare) ? $shareWith['group'] : $shareWith;
1663
		$postHookData['itemTarget'] = ($isGroupShare) ? $groupItemTarget : $itemTarget;
1664
		$postHookData['fileTarget'] = ($isGroupShare) ? $groupFileTarget : $fileTarget;
1665
1666
		\OC_Hook::emit('OCP\Share', 'post_shared', $postHookData);
1667
1668
1669
		return $id ? $id : false;
1670
	}
1671
1672
	/**
1673
	 * @param string $itemType
1674
	 * @param string $itemSource
1675
	 * @param int $shareType
1676
	 * @param string $shareWith
1677
	 * @param string $uidOwner
1678
	 * @param int $permissions
1679
	 * @param string|null $itemSourceName
1680
	 * @param null|\DateTime $expirationDate
1681
	 */
1682
	private static function checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate) {
1683
		$backend = self::getBackend($itemType);
1684
1685
		$l = \OC::$server->getL10N('lib');
1686
		$result = array();
1687
1688
		$column = ($itemType === 'file' || $itemType === 'folder') ? 'file_source' : 'item_source';
1689
1690
		$checkReshare = self::getItemSharedWithBySource($itemType, $itemSource, self::FORMAT_NONE, null, true);
1691
		if ($checkReshare) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $checkReshare of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1692
			// Check if attempting to share back to owner
1693
			if ($checkReshare['uid_owner'] == $shareWith && $shareType == self::SHARE_TYPE_USER) {
1694
				$message = 'Sharing %s failed, because the user %s is the original sharer';
1695
				$message_t = $l->t('Sharing failed, because the user %s is the original sharer', [$shareWith]);
1696
1697
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
1698
				throw new \Exception($message_t);
1699
			}
1700
		}
1701
1702
		if ($checkReshare && $checkReshare['uid_owner'] !== \OC_User::getUser()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $checkReshare of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1703
			// Check if share permissions is granted
1704
			if (self::isResharingAllowed() && (int)$checkReshare['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
1705
				if (~(int)$checkReshare['permissions'] & $permissions) {
1706
					$message = 'Sharing %s failed, because the permissions exceed permissions granted to %s';
1707
					$message_t = $l->t('Sharing %s failed, because the permissions exceed permissions granted to %s', array($itemSourceName, $uidOwner));
1708
1709
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $uidOwner), \OCP\Util::DEBUG);
1710
					throw new \Exception($message_t);
1711
				} else {
1712
					// TODO Don't check if inside folder
1713
					$result['parent'] = $checkReshare['id'];
1714
1715
					$result['expirationDate'] = $expirationDate;
1716
					// $checkReshare['expiration'] could be null and then is always less than any value
1717
					if(isset($checkReshare['expiration']) && $checkReshare['expiration'] < $expirationDate) {
1718
						$result['expirationDate'] = $checkReshare['expiration'];
1719
					}
1720
1721
					// only suggest the same name as new target if it is a reshare of the
1722
					// same file/folder and not the reshare of a child
1723
					if ($checkReshare[$column] === $itemSource) {
1724
						$result['filePath'] = $checkReshare['file_target'];
1725
						$result['itemSource'] = $checkReshare['item_source'];
1726
						$result['fileSource'] = $checkReshare['file_source'];
1727
						$result['suggestedItemTarget'] = $checkReshare['item_target'];
1728
						$result['suggestedFileTarget'] = $checkReshare['file_target'];
1729
					} else {
1730
						$result['filePath'] = ($backend instanceof \OCP\Share_Backend_File_Dependent) ? $backend->getFilePath($itemSource, $uidOwner) : null;
1731
						$result['suggestedItemTarget'] = null;
1732
						$result['suggestedFileTarget'] = null;
1733
						$result['itemSource'] = $itemSource;
1734
						$result['fileSource'] = ($backend instanceof \OCP\Share_Backend_File_Dependent) ? $itemSource : null;
1735
					}
1736
				}
1737 View Code Duplication
			} else {
1738
				$message = 'Sharing %s failed, because resharing is not allowed';
1739
				$message_t = $l->t('Sharing %s failed, because resharing is not allowed', array($itemSourceName));
1740
1741
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
1742
				throw new \Exception($message_t);
1743
			}
1744
		} else {
1745
			$result['parent'] = null;
1746
			$result['suggestedItemTarget'] = null;
1747
			$result['suggestedFileTarget'] = null;
1748
			$result['itemSource'] = $itemSource;
1749
			$result['expirationDate'] = $expirationDate;
1750 View Code Duplication
			if (!$backend->isValidSource($itemSource, $uidOwner)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $backend->isValidSource($itemSource, $uidOwner) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
1751
				$message = 'Sharing %s failed, because the sharing backend for '
1752
					.'%s could not find its source';
1753
				$message_t = $l->t('Sharing %s failed, because the sharing backend for %s could not find its source', array($itemSource, $itemType));
1754
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSource, $itemType), \OCP\Util::DEBUG);
1755
				throw new \Exception($message_t);
1756
			}
1757
			if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
1758
				$result['filePath'] = $backend->getFilePath($itemSource, $uidOwner);
1759
				if ($itemType == 'file' || $itemType == 'folder') {
1760
					$result['fileSource'] = $itemSource;
1761
				} else {
1762
					$meta = \OC\Files\Filesystem::getFileInfo($result['filePath']);
0 ignored issues
show
Security Bug introduced by
It seems like $result['filePath'] can also be of type false; however, OC\Files\Filesystem::getFileInfo() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
1763
					$result['fileSource'] = $meta['fileid'];
1764
				}
1765 View Code Duplication
				if ($result['fileSource'] == -1) {
1766
					$message = 'Sharing %s failed, because the file could not be found in the file cache';
1767
					$message_t = $l->t('Sharing %s failed, because the file could not be found in the file cache', array($itemSource));
1768
1769
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSource), \OCP\Util::DEBUG);
1770
					throw new \Exception($message_t);
1771
				}
1772
			} else {
1773
				$result['filePath'] = null;
1774
				$result['fileSource'] = null;
1775
			}
1776
		}
1777
1778
		return $result;
1779
	}
1780
1781
	/**
1782
	 *
1783
	 * @param array $shareData
1784
	 * @return mixed false in case of a failure or the id of the new share
1785
	 */
1786
	private static function insertShare(array $shareData) {
1787
1788
		$query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` ('
1789
			.' `item_type`, `item_source`, `item_target`, `share_type`,'
1790
			.' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,'
1791
			.' `file_target`, `token`, `parent`, `expiration`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)');
1792
		$query->bindValue(1, $shareData['itemType']);
1793
		$query->bindValue(2, $shareData['itemSource']);
1794
		$query->bindValue(3, $shareData['itemTarget']);
1795
		$query->bindValue(4, $shareData['shareType']);
1796
		$query->bindValue(5, $shareData['shareWith']);
1797
		$query->bindValue(6, $shareData['uidOwner']);
1798
		$query->bindValue(7, $shareData['permissions']);
1799
		$query->bindValue(8, $shareData['shareTime']);
1800
		$query->bindValue(9, $shareData['fileSource']);
1801
		$query->bindValue(10, $shareData['fileTarget']);
1802
		$query->bindValue(11, $shareData['token']);
1803
		$query->bindValue(12, $shareData['parent']);
1804
		$query->bindValue(13, $shareData['expiration'], 'datetime');
1805
		$result = $query->execute();
1806
1807
		$id = false;
1808
		if ($result) {
1809
			$id =  \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share');
1810
		}
1811
1812
		return $id;
1813
1814
	}
1815
1816
	/**
1817
	 * In case a password protected link is not yet authenticated this function will return false
1818
	 *
1819
	 * @param array $linkItem
1820
	 * @return boolean
1821
	 */
1822
	public static function checkPasswordProtectedShare(array $linkItem) {
1823
		if (!isset($linkItem['share_with'])) {
1824
			return true;
1825
		}
1826
		if (!isset($linkItem['share_type'])) {
1827
			return true;
1828
		}
1829
		if (!isset($linkItem['id'])) {
1830
			return true;
1831
		}
1832
1833
		if ($linkItem['share_type'] != \OCP\Share::SHARE_TYPE_LINK) {
1834
			return true;
1835
		}
1836
1837 View Code Duplication
		if ( \OC::$server->getSession()->exists('public_link_authenticated')
1838
			&& \OC::$server->getSession()->get('public_link_authenticated') === (string)$linkItem['id'] ) {
1839
			return true;
1840
		}
1841
1842
		return false;
1843
	}
1844
1845
	/**
1846
	 * construct select statement
1847
	 * @param int $format
1848
	 * @param boolean $fileDependent ist it a file/folder share or a generla share
1849
	 * @param string $uidOwner
1850
	 * @return string select statement
1851
	 */
1852
	private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
1853
		$select = '*';
1854
		if ($format == self::FORMAT_STATUSES) {
1855
			if ($fileDependent) {
1856
				$select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
1857
					. '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
1858
					. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
1859
					. '`uid_initiator`';
1860
			} else {
1861
				$select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
1862
			}
1863
		} else {
1864
			if (isset($uidOwner)) {
1865
				if ($fileDependent) {
1866
					$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
1867
						. ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
1868
						. ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
1869
						. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
1870
				} else {
1871
					$select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
1872
						. ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
1873
				}
1874
			} else {
1875
				if ($fileDependent) {
1876
					if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
1877
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
1878
							. '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
1879
							. '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
1880
							. '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
1881
					} else {
1882
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
1883
							. '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
1884
							. '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
1885
						    . '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
1886
							. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
1887
					}
1888
				}
1889
			}
1890
		}
1891
		return $select;
1892
	}
1893
1894
1895
	/**
1896
	 * transform db results
1897
	 * @param array $row result
1898
	 */
1899
	private static function transformDBResults(&$row) {
1900
		if (isset($row['id'])) {
1901
			$row['id'] = (int) $row['id'];
1902
		}
1903
		if (isset($row['share_type'])) {
1904
			$row['share_type'] = (int) $row['share_type'];
1905
		}
1906
		if (isset($row['parent'])) {
1907
			$row['parent'] = (int) $row['parent'];
1908
		}
1909
		if (isset($row['file_parent'])) {
1910
			$row['file_parent'] = (int) $row['file_parent'];
1911
		}
1912
		if (isset($row['file_source'])) {
1913
			$row['file_source'] = (int) $row['file_source'];
1914
		}
1915
		if (isset($row['permissions'])) {
1916
			$row['permissions'] = (int) $row['permissions'];
1917
		}
1918
		if (isset($row['storage'])) {
1919
			$row['storage'] = (int) $row['storage'];
1920
		}
1921
		if (isset($row['stime'])) {
1922
			$row['stime'] = (int) $row['stime'];
1923
		}
1924
		if (isset($row['expiration']) && $row['share_type'] !== self::SHARE_TYPE_LINK) {
1925
			// discard expiration date for non-link shares, which might have been
1926
			// set by ancient bugs
1927
			$row['expiration'] = null;
1928
		}
1929
	}
1930
1931
	/**
1932
	 * format result
1933
	 * @param array $items result
1934
	 * @param string $column is it a file share or a general share ('file_target' or 'item_target')
1935
	 * @param \OCP\Share_Backend $backend sharing backend
1936
	 * @param int $format
1937
	 * @param array $parameters additional format parameters
1938
	 * @return array format result
1939
	 */
1940
	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
1941
		if ($format === self::FORMAT_NONE) {
1942
			return $items;
1943
		} else if ($format === self::FORMAT_STATUSES) {
1944
			$statuses = array();
1945
			foreach ($items as $item) {
1946
				if ($item['share_type'] === self::SHARE_TYPE_LINK) {
1947
					if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
1948
						continue;
1949
					}
1950
					$statuses[$item[$column]]['link'] = true;
1951
				} else if (!isset($statuses[$item[$column]])) {
1952
					$statuses[$item[$column]]['link'] = false;
1953
				}
1954
				if (!empty($item['file_target'])) {
1955
					$statuses[$item[$column]]['path'] = $item['path'];
1956
				}
1957
			}
1958
			return $statuses;
1959
		} else {
1960
			return $backend->formatItems($items, $format, $parameters);
1961
		}
1962
	}
1963
1964
	/**
1965
	 * remove protocol from URL
1966
	 *
1967
	 * @param string $url
1968
	 * @return string
1969
	 */
1970 View Code Duplication
	public static function removeProtocolFromUrl($url) {
1971
		if (strpos($url, 'https://') === 0) {
1972
			return substr($url, strlen('https://'));
1973
		} else if (strpos($url, 'http://') === 0) {
1974
			return substr($url, strlen('http://'));
1975
		}
1976
1977
		return $url;
1978
	}
1979
1980
	/**
1981
	 * try http post first with https and then with http as a fallback
1982
	 *
1983
	 * @param string $remoteDomain
1984
	 * @param string $urlSuffix
1985
	 * @param array $fields post parameters
1986
	 * @return array
1987
	 */
1988
	private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) {
1989
		$protocol = 'https://';
1990
		$result = [
1991
			'success' => false,
1992
			'result' => '',
1993
		];
1994
		$try = 0;
1995
		$discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class);
1996
		while ($result['success'] === false && $try < 2) {
1997
			$federationEndpoints = $discoveryService->discover($protocol . $remoteDomain, 'FEDERATED_SHARING');
1998
			$endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
1999
			$result = \OC::$server->getHTTPHelper()->post($protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT, $fields);
2000
			$try++;
2001
			$protocol = 'http://';
2002
		}
2003
2004
		return $result;
2005
	}
2006
2007
	/**
2008
	 * send server-to-server share to remote server
2009
	 *
2010
	 * @param string $token
2011
	 * @param string $shareWith
2012
	 * @param string $name
2013
	 * @param int $remote_id
2014
	 * @param string $owner
2015
	 * @return bool
2016
	 */
2017
	private static function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner) {
2018
2019
		list($user, $remote) = Helper::splitUserRemote($shareWith);
2020
2021
		if ($user && $remote) {
2022
			$url = $remote;
2023
2024
			$local = \OC::$server->getURLGenerator()->getAbsoluteURL('/');
2025
2026
			$fields = array(
2027
				'shareWith' => $user,
2028
				'token' => $token,
2029
				'name' => $name,
2030
				'remoteId' => $remote_id,
2031
				'owner' => $owner,
2032
				'remote' => $local,
2033
			);
2034
2035
			$url = self::removeProtocolFromUrl($url);
2036
			$result = self::tryHttpPostToShareEndpoint($url, '', $fields);
2037
			$status = json_decode($result['result'], true);
2038
2039 View Code Duplication
			if ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200)) {
2040
				\OC_Hook::emit('OCP\Share', 'federated_share_added', ['server' => $remote]);
2041
				return true;
2042
			}
2043
2044
		}
2045
2046
		return false;
2047
	}
2048
2049
	/**
2050
	 * send server-to-server unshare to remote server
2051
	 *
2052
	 * @param string $remote url
2053
	 * @param int $id share id
2054
	 * @param string $token
2055
	 * @return bool
2056
	 */
2057
	private static function sendRemoteUnshare($remote, $id, $token) {
2058
		$url = rtrim($remote, '/');
2059
		$fields = array('token' => $token, 'format' => 'json');
2060
		$url = self::removeProtocolFromUrl($url);
2061
		$result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields);
2062
		$status = json_decode($result['result'], true);
2063
2064
		return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200));
2065
	}
2066
2067
	/**
2068
	 * check if user can only share with group members
2069
	 * @return bool
2070
	 */
2071
	public static function shareWithGroupMembersOnly() {
2072
		$value = \OC::$server->getConfig()->getAppValue('core', 'shareapi_only_share_with_group_members', 'no');
2073
		return ($value === 'yes') ? true : false;
2074
	}
2075
2076
	/**
2077
	 * @return bool
2078
	 */
2079
	public static function isDefaultExpireDateEnabled() {
2080
		$defaultExpireDateEnabled = \OC::$server->getConfig()->getAppValue('core', 'shareapi_default_expire_date', 'no');
2081
		return ($defaultExpireDateEnabled === "yes") ? true : false;
2082
	}
2083
2084
	/**
2085
	 * @return int
2086
	 */
2087
	public static function getExpireInterval() {
2088
		return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
2089
	}
2090
2091
	/**
2092
	 * Checks whether the given path is reachable for the given owner
2093
	 *
2094
	 * @param string $path path relative to files
2095
	 * @param string $ownerStorageId storage id of the owner
2096
	 *
2097
	 * @return boolean true if file is reachable, false otherwise
2098
	 */
2099
	private static function isFileReachable($path, $ownerStorageId) {
2100
		// if outside the home storage, file is always considered reachable
2101
		if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
2102
			substr($ownerStorageId, 0, 13) === 'object::user:'
2103
		)) {
2104
			return true;
2105
		}
2106
2107
		// if inside the home storage, the file has to be under "/files/"
2108
		$path = ltrim($path, '/');
2109
		if (substr($path, 0, 6) === 'files/') {
2110
			return true;
2111
		}
2112
2113
		return false;
2114
	}
2115
2116
	/**
2117
	 * @param IConfig $config
2118
	 * @return bool
2119
	 */
2120
	public static function enforcePassword(IConfig $config) {
2121
		$enforcePassword = $config->getAppValue('core', 'shareapi_enforce_links_password', 'no');
2122
		return ($enforcePassword === "yes") ? true : false;
2123
	}
2124
2125
	/**
2126
	 * @param string $password
2127
	 * @throws \Exception
2128
	 */
2129
	private static function verifyPassword($password) {
2130
2131
		$accepted = true;
2132
		$message = '';
2133
		\OCP\Util::emitHook('\OC\Share', 'verifyPassword', [
2134
			'password' => $password,
2135
			'accepted' => &$accepted,
2136
			'message' => &$message
2137
		]);
2138
2139
		if (!$accepted) {
2140
			throw new \Exception($message);
2141
		}
2142
	}
2143
}
2144