Completed
Push — master ( cc40f0...c5c23b )
by Morris
55s
created

Share::insertShare()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 29
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 23
nc 2
nop 1
dl 0
loc 29
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Bernhard Reiter <[email protected]>
8
 * @author Björn Schießle <[email protected]>
9
 * @author Christopher Schäpers <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Daniel Hansson <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author Jörn Friedrich Dreyer <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Michael Kuhn <[email protected]>
16
 * @author Morris Jobke <[email protected]>
17
 * @author Robin Appelman <[email protected]>
18
 * @author Robin McCorkell <[email protected]>
19
 * @author Roeland Jago Douma <[email protected]>
20
 * @author Sebastian Döll <[email protected]>
21
 * @author Stefan Weil <[email protected]>
22
 * @author Thomas Müller <[email protected]>
23
 * @author Torben Dannhauer <[email protected]>
24
 * @author Vincent Petry <[email protected]>
25
 * @author Volkan Gezer <[email protected]>
26
 *
27
 * @license AGPL-3.0
28
 *
29
 * This code is free software: you can redistribute it and/or modify
30
 * it under the terms of the GNU Affero General Public License, version 3,
31
 * as published by the Free Software Foundation.
32
 *
33
 * This program is distributed in the hope that it will be useful,
34
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36
 * GNU Affero General Public License for more details.
37
 *
38
 * You should have received a copy of the GNU Affero General Public License, version 3,
39
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
40
 *
41
 */
42
43
namespace OC\Share;
44
45
use OC\Files\Filesystem;
46
use OCP\DB\QueryBuilder\IQueryBuilder;
47
use OCP\ILogger;
48
use OCP\IUserManager;
49
use OCP\IUserSession;
50
use OCP\IDBConnection;
51
use OCP\IConfig;
52
use OCP\Util;
53
54
/**
55
 * This class provides the ability for apps to share their content between users.
56
 * Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
57
 *
58
 * It provides the following hooks:
59
 *  - post_shared
60
 */
61
class Share extends Constants {
62
63
	/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
64
	 * Construct permissions for share() and setPermissions with Or (|) e.g.
65
	 * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
66
	 *
67
	 * Check if permission is granted with And (&) e.g. Check if delete is
68
	 * granted: if ($permissions & PERMISSION_DELETE)
69
	 *
70
	 * Remove permissions with And (&) and Not (~) e.g. Remove the update
71
	 * permission: $permissions &= ~PERMISSION_UPDATE
72
	 *
73
	 * Apps are required to handle permissions on their own, this class only
74
	 * stores and manages the permissions of shares
75
	 * @see lib/public/constants.php
76
	 */
77
78
	/**
79
	 * Register a sharing backend class that implements OCP\Share_Backend for an item type
80
	 * @param string $itemType Item type
81
	 * @param string $class Backend class
82
	 * @param string $collectionOf (optional) Depends on item type
83
	 * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
84
	 * @return boolean true if backend is registered or false if error
85
	 */
86
	public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
87
		if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_enabled', 'yes') == 'yes') {
88
			if (!isset(self::$backendTypes[$itemType])) {
89
				self::$backendTypes[$itemType] = array(
90
					'class' => $class,
91
					'collectionOf' => $collectionOf,
92
					'supportedFileExtensions' => $supportedFileExtensions
93
				);
94
				if(count(self::$backendTypes) === 1) {
95
					Util::addScript('core', 'merged-share-backend');
96
					\OC_Util::addStyle('core', 'share');
97
				}
98
				return true;
99
			}
100
			\OCP\Util::writeLog('OCP\Share',
101
				'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
102
				.' is already registered for '.$itemType,
103
				\OCP\Util::WARN);
104
		}
105
		return false;
106
	}
107
108
	/**
109
	 * Get the items of item type shared with the current user
110
	 * @param string $itemType
111
	 * @param int $format (optional) Format type must be defined by the backend
112
	 * @param mixed $parameters (optional)
113
	 * @param int $limit Number of items to return (optional) Returns all by default
114
	 * @param boolean $includeCollections (optional)
115
	 * @return mixed Return depends on format
116
	 */
117
	public static function getItemsSharedWith($itemType, $format = self::FORMAT_NONE,
118
											  $parameters = null, $limit = -1, $includeCollections = false) {
119
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format,
120
			$parameters, $limit, $includeCollections);
121
	}
122
123
	/**
124
	 * Get the items of item type shared with a user
125
	 * @param string $itemType
126
	 * @param string $user id for which user we want the shares
127
	 * @param int $format (optional) Format type must be defined by the backend
128
	 * @param mixed $parameters (optional)
129
	 * @param int $limit Number of items to return (optional) Returns all by default
130
	 * @param boolean $includeCollections (optional)
131
	 * @return mixed Return depends on format
132
	 */
133
	public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE,
134
												  $parameters = null, $limit = -1, $includeCollections = false) {
135
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format,
136
			$parameters, $limit, $includeCollections);
137
	}
138
139
	/**
140
	 * Get the item of item type shared with a given user by source
141
	 * @param string $itemType
142
	 * @param string $itemSource
143
	 * @param string $user User to whom the item was shared
144
	 * @param string $owner Owner of the share
145
	 * @param int $shareType only look for a specific share type
146
	 * @return array Return list of items with file_target, permissions and expiration
147
	 */
148
	public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
149
		$shares = array();
150
		$fileDependent = false;
151
152
		$where = 'WHERE';
153
		$fileDependentWhere = '';
154
		if ($itemType === 'file' || $itemType === 'folder') {
155
			$fileDependent = true;
156
			$column = 'file_source';
157
			$fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
158
			$fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
159
		} else {
160
			$column = 'item_source';
161
		}
162
163
		$select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
164
165
		$where .= ' `' . $column . '` = ? AND `item_type` = ? ';
166
		$arguments = array($itemSource, $itemType);
167
		// for link shares $user === null
168
		if ($user !== null) {
169
			$where .= ' AND `share_with` = ? ';
170
			$arguments[] = $user;
171
		}
172
173
		if ($shareType !== null) {
174
			$where .= ' AND `share_type` = ? ';
175
			$arguments[] = $shareType;
176
		}
177
178
		if ($owner !== null) {
179
			$where .= ' AND `uid_owner` = ? ';
180
			$arguments[] = $owner;
181
		}
182
183
		$query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
184
185
		$result = \OC_DB::executeAudited($query, $arguments);
186
187
		while ($row = $result->fetchRow()) {
188
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
189
				continue;
190
			}
191
			if ($fileDependent && (int)$row['file_parent'] === -1) {
192
				// if it is a mount point we need to get the path from the mount manager
193
				$mountManager = \OC\Files\Filesystem::getMountManager();
194
				$mountPoint = $mountManager->findByStorageId($row['storage_id']);
195
				if (!empty($mountPoint)) {
196
					$path = $mountPoint[0]->getMountPoint();
197
					$path = trim($path, '/');
198
					$path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
199
					$row['path'] = $path;
200
				} else {
201
					\OC::$server->getLogger()->warning(
202
						'Could not resolve mount point for ' . $row['storage_id'],
203
						['app' => 'OCP\Share']
204
					);
205
				}
206
			}
207
			$shares[] = $row;
208
		}
209
210
		//if didn't found a result than let's look for a group share.
211
		if(empty($shares) && $user !== null) {
212
			$userObject = \OC::$server->getUserManager()->get($user);
213
			$groups = [];
214
			if ($userObject) {
215
				$groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
216
			}
217
218
			if (!empty($groups)) {
219
				$where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
220
				$arguments = array($itemSource, $itemType, $groups);
221
				$types = array(null, null, IQueryBuilder::PARAM_STR_ARRAY);
222
223
				if ($owner !== null) {
224
					$where .= ' AND `uid_owner` = ?';
225
					$arguments[] = $owner;
226
					$types[] = null;
227
				}
228
229
				// TODO: inject connection, hopefully one day in the future when this
230
				// class isn't static anymore...
231
				$conn = \OC::$server->getDatabaseConnection();
232
				$result = $conn->executeQuery(
233
					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
234
					$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...
235
					$types
236
				);
237
238
				while ($row = $result->fetch()) {
239
					$shares[] = $row;
240
				}
241
			}
242
		}
243
244
		return $shares;
245
246
	}
247
248
	/**
249
	 * Get the item of item type shared with the current user by source
250
	 * @param string $itemType
251
	 * @param string $itemSource
252
	 * @param int $format (optional) Format type must be defined by the backend
253
	 * @param mixed $parameters
254
	 * @param boolean $includeCollections
255
	 * @param string $shareWith (optional) define against which user should be checked, default: current user
256
	 * @return array
257
	 */
258
	public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE,
259
													 $parameters = null, $includeCollections = false, $shareWith = null) {
260
		$shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith;
261
		return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format,
262
			$parameters, 1, $includeCollections, true);
263
	}
264
265
	/**
266
	 * Based on the given token the share information will be returned - password protected shares will be verified
267
	 * @param string $token
268
	 * @param bool $checkPasswordProtection
269
	 * @return array|boolean false will be returned in case the token is unknown or unauthorized
270
	 */
271
	public static function getShareByToken($token, $checkPasswordProtection = true) {
272
		$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `token` = ?', 1);
273
		$result = $query->execute(array($token));
274 View Code Duplication
		if ($result === false) {
275
			\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', token=' . $token, \OCP\Util::ERROR);
276
		}
277
		$row = $result->fetchRow();
278
		if ($row === false) {
279
			return false;
280
		}
281
		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...
282
			return false;
283
		}
284
285
		// password protected shares need to be authenticated
286
		if ($checkPasswordProtection && !\OCP\Share::checkPasswordProtectedShare($row)) {
287
			return false;
288
		}
289
290
		return $row;
291
	}
292
293
	/**
294
	 * resolves reshares down to the last real share
295
	 * @param array $linkItem
296
	 * @return array file owner
297
	 */
298
	public static function resolveReShare($linkItem)
299
	{
300
		if (isset($linkItem['parent'])) {
301
			$parent = $linkItem['parent'];
302 View Code Duplication
			while (isset($parent)) {
303
				$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `id` = ?', 1);
304
				$item = $query->execute(array($parent))->fetchRow();
305
				if (isset($item['parent'])) {
306
					$parent = $item['parent'];
307
				} else {
308
					return $item;
309
				}
310
			}
311
		}
312
		return $linkItem;
313
	}
314
315
316
	/**
317
	 * Get the shared items of item type owned by the current user
318
	 * @param string $itemType
319
	 * @param int $format (optional) Format type must be defined by the backend
320
	 * @param mixed $parameters
321
	 * @param int $limit Number of items to return (optional) Returns all by default
322
	 * @param boolean $includeCollections
323
	 * @return mixed Return depends on format
324
	 */
325
	public static function getItemsShared($itemType, $format = self::FORMAT_NONE, $parameters = null,
326
										  $limit = -1, $includeCollections = false) {
327
		return self::getItems($itemType, null, null, null, \OC_User::getUser(), $format,
328
			$parameters, $limit, $includeCollections);
329
	}
330
331
	/**
332
	 * Get the shared item of item type owned by the current user
333
	 * @param string $itemType
334
	 * @param string $itemSource
335
	 * @param int $format (optional) Format type must be defined by the backend
336
	 * @param mixed $parameters
337
	 * @param boolean $includeCollections
338
	 * @return mixed Return depends on format
339
	 */
340
	public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
341
										 $parameters = null, $includeCollections = false) {
342
		return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format,
343
			$parameters, -1, $includeCollections);
344
	}
345
346
	/**
347
	 * Share an item with a user, group, or via private link
348
	 * @param string $itemType
349
	 * @param string $itemSource
350
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
351
	 * @param string $shareWith User or group the item is being shared with
352
	 * @param int $permissions CRUDS
353
	 * @param string $itemSourceName
354
	 * @param \DateTime $expirationDate
355
	 * @param bool $passwordChanged
356
	 * @return boolean|string Returns true on success or false on failure, Returns token on success for links
357
	 * @throws \OC\HintException when the share type is remote and the shareWith is invalid
358
	 * @throws \Exception
359
	 */
360
	public static function shareItem($itemType, $itemSource, $shareType, $shareWith, $permissions, $itemSourceName = null, \DateTime $expirationDate = null, $passwordChanged = null) {
361
362
		$backend = self::getBackend($itemType);
363
		$l = \OC::$server->getL10N('lib');
364
365
		if ($backend->isShareTypeAllowed($shareType) === false) {
366
			$message = 'Sharing %s failed, because the backend does not allow shares from type %i';
367
			$message_t = $l->t('Sharing %s failed, because the backend does not allow shares from type %i', array($itemSourceName, $shareType));
368
			\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareType), \OCP\Util::DEBUG);
369
			throw new \Exception($message_t);
370
		}
371
372
		$uidOwner = \OC_User::getUser();
373
		$shareWithinGroupOnly = self::shareWithGroupMembersOnly();
374
375
		if (is_null($itemSourceName)) {
376
			$itemSourceName = $itemSource;
377
		}
378
		$itemName = $itemSourceName;
379
380
		// check if file can be shared
381
		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...
382
			$path = \OC\Files\Filesystem::getPath($itemSource);
383
			$itemName = $path;
384
385
			// verify that the file exists before we try to share it
386 View Code Duplication
			if (!$path) {
387
				$message = 'Sharing %s failed, because the file does not exist';
388
				$message_t = $l->t('Sharing %s failed, because the file does not exist', array($itemSourceName));
389
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
390
				throw new \Exception($message_t);
391
			}
392
			// verify that the user has share permission
393 View Code Duplication
			if (!\OC\Files\Filesystem::isSharable($path) || \OCP\Util::isSharingDisabledForUser()) {
394
				$message = 'You are not allowed to share %s';
395
				$message_t = $l->t('You are not allowed to share %s', [$path]);
396
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $path), \OCP\Util::DEBUG);
397
				throw new \Exception($message_t);
398
			}
399
		}
400
401
		//verify that we don't share a folder which already contains a share mount point
402
		if ($itemType === 'folder') {
403
			$path = '/' . $uidOwner . '/files' . \OC\Files\Filesystem::getPath($itemSource) . '/';
404
			$mountManager = \OC\Files\Filesystem::getMountManager();
405
			$mounts = $mountManager->findIn($path);
406
			foreach ($mounts as $mount) {
407
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
408
					$message = 'Sharing "' . $itemSourceName . '" failed, because it contains files shared with you!';
409
					\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
410
					throw new \Exception($message);
411
				}
412
413
			}
414
		}
415
416
		// single file shares should never have delete permissions
417
		if ($itemType === 'file') {
418
			$permissions = (int)$permissions & ~\OCP\Constants::PERMISSION_DELETE;
419
		}
420
421
		//Validate expirationDate
422
		if ($expirationDate !== null) {
423
			try {
424
				/*
425
				 * Reuse the validateExpireDate.
426
				 * We have to pass time() since the second arg is the time
427
				 * the file was shared, since it is not shared yet we just use
428
				 * the current time.
429
				 */
430
				$expirationDate = self::validateExpireDate($expirationDate->format('Y-m-d'), time(), $itemType, $itemSource);
431
			} catch (\Exception $e) {
432
				throw new \OC\HintException($e->getMessage(), $e->getMessage(), 404);
433
			}
434
		}
435
436
		// Verify share type and sharing conditions are met
437
		if ($shareType === self::SHARE_TYPE_USER) {
438 View Code Duplication
			if ($shareWith == $uidOwner) {
439
				$message = 'Sharing %s failed, because you can not share with yourself';
440
				$message_t = $l->t('Sharing %s failed, because you can not share with yourself', [$itemName]);
441
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
442
				throw new \Exception($message_t);
443
			}
444 View Code Duplication
			if (!\OC_User::userExists($shareWith)) {
445
				$message = 'Sharing %s failed, because the user %s does not exist';
446
				$message_t = $l->t('Sharing %s failed, because the user %s does not exist', array($itemSourceName, $shareWith));
447
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
448
				throw new \Exception($message_t);
449
			}
450
			if ($shareWithinGroupOnly) {
451
				$userManager = \OC::$server->getUserManager();
452
				$groupManager = \OC::$server->getGroupManager();
453
				$userOwner = $userManager->get($uidOwner);
454
				$userShareWith = $userManager->get($shareWith);
455
				$groupsOwner = [];
456
				$groupsShareWith = [];
457
				if ($userOwner) {
458
					$groupsOwner = $groupManager->getUserGroupIds($userOwner);
459
				}
460
				if ($userShareWith) {
461
					$groupsShareWith = $groupManager->getUserGroupIds($userShareWith);
462
				}
463
				$inGroup = array_intersect($groupsOwner, $groupsShareWith);
464 View Code Duplication
				if (empty($inGroup)) {
465
					$message = 'Sharing %s failed, because the user '
466
						.'%s is not a member of any groups that %s is a member of';
467
					$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));
468
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemName, $shareWith, $uidOwner), \OCP\Util::DEBUG);
469
					throw new \Exception($message_t);
470
				}
471
			}
472
			// Check if the item source is already shared with the user, either from the same owner or a different user
473 View Code Duplication
			if ($checkExists = self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups,
474
				$shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
475
				// Only allow the same share to occur again if it is the same
476
				// owner and is not a user share, this use case is for increasing
477
				// permissions for a specific user
478
				if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
479
					$message = 'Sharing %s failed, because this item is already shared with %s';
480
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
481
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
482
					throw new \Exception($message_t);
483
				}
484
			}
485 View Code Duplication
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_USER,
486
				$shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
487
				// Only allow the same share to occur again if it is the same
488
				// owner and is not a user share, this use case is for increasing
489
				// permissions for a specific user
490
				if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
491
					$message = 'Sharing %s failed, because this item is already shared with user %s';
492
					$message_t = $l->t('Sharing %s failed, because this item is already shared with user %s', array($itemSourceName, $shareWith));
493
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::ERROR);
494
					throw new \Exception($message_t);
495
				}
496
			}
497
		} else if ($shareType === self::SHARE_TYPE_GROUP) {
498
			if (!\OC::$server->getGroupManager()->groupExists($shareWith)) {
499
				$message = 'Sharing %s failed, because the group %s does not exist';
500
				$message_t = $l->t('Sharing %s failed, because the group %s does not exist', array($itemSourceName, $shareWith));
501
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
502
				throw new \Exception($message_t);
503
			}
504
			if ($shareWithinGroupOnly) {
505
				$group = \OC::$server->getGroupManager()->get($shareWith);
506
				$user = \OC::$server->getUserManager()->get($uidOwner);
507
				if (!$group || !$user || !$group->inGroup($user)) {
508
					$message = 'Sharing %s failed, because '
509
						. '%s is not a member of the group %s';
510
					$message_t = $l->t('Sharing %s failed, because %s is not a member of the group %s', array($itemSourceName, $uidOwner, $shareWith));
511
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $uidOwner, $shareWith), \OCP\Util::DEBUG);
512
					throw new \Exception($message_t);
513
				}
514
			}
515
			// Check if the item source is already shared with the group, either from the same owner or a different user
516
			// The check for each user in the group is done inside the put() function
517
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_GROUP, $shareWith,
518
				null, self::FORMAT_NONE, null, 1, true, true)) {
519
520
				if ($checkExists['share_with'] === $shareWith && $checkExists['share_type'] === \OCP\Share::SHARE_TYPE_GROUP) {
521
					$message = 'Sharing %s failed, because this item is already shared with %s';
522
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
523
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
524
					throw new \Exception($message_t);
525
				}
526
			}
527
			// Convert share with into an array with the keys group and users
528
			$group = $shareWith;
529
			$shareWith = array();
530
			$shareWith['group'] = $group;
531
532
533
			$groupObject = \OC::$server->getGroupManager()->get($group);
534
			$userIds = [];
535
			if ($groupObject) {
536
				$users = $groupObject->searchUsers('', -1, 0);
537
				foreach ($users as $user) {
538
					$userIds[] = $user->getUID();
539
				}
540
			}
541
542
			$shareWith['users'] = array_diff($userIds, array($uidOwner));
543
		} else if ($shareType === self::SHARE_TYPE_LINK) {
544
			$updateExistingShare = false;
545
			if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_links', 'yes') == 'yes') {
546
547
				// IF the password is changed via the old ajax endpoint verify it before deleting the old share
548
				if ($passwordChanged === true) {
549
					self::verifyPassword($shareWith);
550
				}
551
552
				// when updating a link share
553
				// FIXME Don't delete link if we update it
554
				if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_LINK, null,
555
					$uidOwner, self::FORMAT_NONE, null, 1)) {
556
					// remember old token
557
					$oldToken = $checkExists['token'];
558
					$oldPermissions = $checkExists['permissions'];
559
					//delete the old share
560
					Helper::delete($checkExists['id']);
561
					$updateExistingShare = true;
562
				}
563
564
				if ($passwordChanged === null) {
565
					// Generate hash of password - same method as user passwords
566
					if (is_string($shareWith) && $shareWith !== '') {
567
						self::verifyPassword($shareWith);
568
						$shareWith = \OC::$server->getHasher()->hash($shareWith);
569
					} else {
570
						// reuse the already set password, but only if we change permissions
571
						// otherwise the user disabled the password protection
572
						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...
573
							$shareWith = $checkExists['share_with'];
574
						}
575
					}
576
				} else {
577
					if ($passwordChanged === true) {
578
						if (is_string($shareWith) && $shareWith !== '') {
579
							self::verifyPassword($shareWith);
580
							$shareWith = \OC::$server->getHasher()->hash($shareWith);
581
						}
582
					} else if ($updateExistingShare) {
583
						$shareWith = $checkExists['share_with'];
584
					}
585
				}
586
587
				if (\OCP\Util::isPublicLinkPasswordRequired() && empty($shareWith)) {
588
					$message = 'You need to provide a password to create a public link, only protected links are allowed';
589
					$message_t = $l->t('You need to provide a password to create a public link, only protected links are allowed');
590
					\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
591
					throw new \Exception($message_t);
592
				}
593
594
				if ($updateExistingShare === false &&
595
					self::isDefaultExpireDateEnabled() &&
596
					empty($expirationDate)) {
597
					$expirationDate = Helper::calcExpireDate();
598
				}
599
600
				// Generate token
601
				if (isset($oldToken)) {
602
					$token = $oldToken;
603
				} else {
604
					$token = \OC::$server->getSecureRandom()->generate(self::TOKEN_LENGTH,
605
						\OCP\Security\ISecureRandom::CHAR_HUMAN_READABLE
606
					);
607
				}
608
				$result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions,
609
					null, $token, $itemSourceName, $expirationDate);
610
				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...
611
					return $token;
612
				} else {
613
					return false;
614
				}
615
			}
616
			$message = 'Sharing %s failed, because sharing with links is not allowed';
617
			$message_t = $l->t('Sharing %s failed, because sharing with links is not allowed', array($itemSourceName));
618
			\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
619
			throw new \Exception($message_t);
620
		} else if ($shareType === self::SHARE_TYPE_REMOTE) {
621
622
			/*
623
			 * Check if file is not already shared with the remote user
624
			 */
625
			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...
626
				$shareWith, $uidOwner, self::FORMAT_NONE, null, 1, true, true)) {
627
					$message = 'Sharing %s failed, because this item is already shared with %s';
628
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
629
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
630
					throw new \Exception($message_t);
631
			}
632
633
			// don't allow federated shares if source and target server are the same
634
			list($user, $remote) = Helper::splitUserRemote($shareWith);
635
			$currentServer = self::removeProtocolFromUrl(\OC::$server->getURLGenerator()->getAbsoluteURL('/'));
636
			$currentUser = \OC::$server->getUserSession()->getUser()->getUID();
637
			if (Helper::isSameUserOnSameServer($user, $remote, $currentUser, $currentServer)) {
638
				$message = 'Not allowed to create a federated share with the same user.';
639
				$message_t = $l->t('Not allowed to create a federated share with the same user');
640
				\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
641
				throw new \Exception($message_t);
642
			}
643
644
			$token = \OC::$server->getSecureRandom()->generate(self::TOKEN_LENGTH, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_UPPER .
645
				\OCP\Security\ISecureRandom::CHAR_DIGITS);
646
647
			$shareWith = $user . '@' . $remote;
648
			$shareId = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, $token, $itemSourceName);
649
650
			$send = false;
651
			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...
652
				$send = self::sendRemoteShare($token, $shareWith, $itemSourceName, $shareId, $uidOwner);
653
			}
654
655
			if ($send === false) {
656
				$currentUser = \OC::$server->getUserSession()->getUser()->getUID();
657
				self::unshare($itemType, $itemSource, $shareType, $shareWith, $currentUser);
658
				$message_t = $l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable.', array($itemSourceName, $shareWith));
659
				throw new \Exception($message_t);
660
			}
661
662
			return $send;
663 View Code Duplication
		} else {
664
			// Future share types need to include their own conditions
665
			$message = 'Share type %s is not valid for %s';
666
			$message_t = $l->t('Share type %s is not valid for %s', array($shareType, $itemSource));
667
			\OCP\Util::writeLog('OCP\Share', sprintf($message, $shareType, $itemSource), \OCP\Util::DEBUG);
668
			throw new \Exception($message_t);
669
		}
670
671
		// Put the item into the database
672
		$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 529 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...
673
674
		return $result ? true : false;
675
	}
676
677
	/**
678
	 * Unshare an item from a user, group, or delete a private link
679
	 * @param string $itemType
680
	 * @param string $itemSource
681
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
682
	 * @param string $shareWith User or group the item is being shared with
683
	 * @param string $owner owner of the share, if null the current user is used
684
	 * @return boolean true on success or false on failure
685
	 */
686
	public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) {
687
688
		// check if it is a valid itemType
689
		self::getBackend($itemType);
690
691
		$items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType);
692
693
		$toDelete = array();
694
		$newParent = null;
695
		$currentUser = $owner ? $owner : \OC_User::getUser();
696
		foreach ($items as $item) {
697
			// delete the item with the expected share_type and owner
698
			if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) {
699
				$toDelete = $item;
700
				// if there is more then one result we don't have to delete the children
701
				// but update their parent. For group shares the new parent should always be
702
				// the original group share and not the db entry with the unique name
703
			} else if ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
704
				$newParent = $item['parent'];
705
			} else {
706
				$newParent = $item['id'];
707
			}
708
		}
709
710
		if (!empty($toDelete)) {
711
			self::unshareItem($toDelete, $newParent);
712
			return true;
713
		}
714
		return false;
715
	}
716
717
	/**
718
	 * sent status if users got informed by mail about share
719
	 * @param string $itemType
720
	 * @param string $itemSource
721
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
722
	 * @param string $recipient with whom was the file shared
723
	 * @param boolean $status
724
	 */
725
	public static function setSendMailStatus($itemType, $itemSource, $shareType, $recipient, $status) {
726
		$status = $status ? 1 : 0;
727
728
		$query = \OC_DB::prepare(
729
			'UPDATE `*PREFIX*share`
730
					SET `mail_send` = ?
731
					WHERE `item_type` = ? AND `item_source` = ? AND `share_type` = ? AND `share_with` = ?');
732
733
		$result = $query->execute(array($status, $itemType, $itemSource, $shareType, $recipient));
734
735
		if($result === false) {
736
			\OCP\Util::writeLog('OCP\Share', 'Couldn\'t set send mail status', \OCP\Util::ERROR);
737
		}
738
	}
739
740
	/**
741
	 * validate expiration date if it meets all constraints
742
	 *
743
	 * @param string $expireDate well formatted date string, e.g. "DD-MM-YYYY"
744
	 * @param string $shareTime timestamp when the file was shared
745
	 * @param string $itemType
746
	 * @param string $itemSource
747
	 * @return \DateTime validated date
748
	 * @throws \Exception when the expire date is in the past or further in the future then the enforced date
749
	 */
750
	private static function validateExpireDate($expireDate, $shareTime, $itemType, $itemSource) {
751
		$l = \OC::$server->getL10N('lib');
752
		$date = new \DateTime($expireDate);
753
		$today = new \DateTime('now');
754
755
		// if the user doesn't provide a share time we need to get it from the database
756
		// fall-back mode to keep API stable, because the $shareTime parameter was added later
757
		$defaultExpireDateEnforced = \OCP\Util::isDefaultExpireDateEnforced();
758
		if ($defaultExpireDateEnforced && $shareTime === null) {
759
			$items = self::getItemShared($itemType, $itemSource);
760
			$firstItem = reset($items);
761
			$shareTime = (int)$firstItem['stime'];
762
		}
763
764
		if ($defaultExpireDateEnforced) {
765
			// initialize max date with share time
766
			$maxDate = new \DateTime();
767
			$maxDate->setTimestamp($shareTime);
768
			$maxDays = \OCP\Config::getAppValue('core', 'shareapi_expire_after_n_days', '7');
769
			$maxDate->add(new \DateInterval('P' . $maxDays . 'D'));
770
			if ($date > $maxDate) {
771
				$warning = 'Cannot set expiration date. Shares cannot expire later than ' . $maxDays . ' after they have been shared';
772
				$warning_t = $l->t('Cannot set expiration date. Shares cannot expire later than %s after they have been shared', array($maxDays));
773
				\OCP\Util::writeLog('OCP\Share', $warning, \OCP\Util::WARN);
774
				throw new \Exception($warning_t);
775
			}
776
		}
777
778 View Code Duplication
		if ($date < $today) {
779
			$message = 'Cannot set expiration date. Expiration date is in the past';
780
			$message_t = $l->t('Cannot set expiration date. Expiration date is in the past');
781
			\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::WARN);
782
			throw new \Exception($message_t);
783
		}
784
785
		return $date;
786
	}
787
788
	/**
789
	 * Retrieve the owner of a connection
790
	 *
791
	 * @param IDBConnection $connection
792
	 * @param int $shareId
793
	 * @throws \Exception
794
	 * @return string uid of share owner
795
	 */
796
	private static function getShareOwner(IDBConnection $connection, $shareId) {
797
		$qb = $connection->getQueryBuilder();
798
799
		$qb->select('uid_owner')
800
			->from('share')
801
			->where($qb->expr()->eq('id', $qb->createParameter('shareId')))
802
			->setParameter(':shareId', $shareId);
803
		$dbResult = $qb->execute();
804
		$result = $dbResult->fetch();
805
		$dbResult->closeCursor();
806
807
		if (empty($result)) {
808
			throw new \Exception('Share not found');
809
		}
810
811
		return $result['uid_owner'];
812
	}
813
814
	/**
815
	 * Set password for a public link share
816
	 *
817
	 * @param IUserSession $userSession
818
	 * @param IDBConnection $connection
819
	 * @param IConfig $config
820
	 * @param int $shareId
821
	 * @param string $password
822
	 * @throws \Exception
823
	 * @return boolean
824
	 */
825
	public static function setPassword(IUserSession $userSession,
826
	                                   IDBConnection $connection,
827
	                                   IConfig $config,
828
	                                   $shareId, $password) {
829
		$user = $userSession->getUser();
830
		if (is_null($user)) {
831
			throw new \Exception("User not logged in");
832
		}
833
834
		$uid = self::getShareOwner($connection, $shareId);
835
836
		if ($uid !== $user->getUID()) {
837
			throw new \Exception('Cannot update share of a different user');
838
		}
839
840
		if ($password === '') {
841
			$password = null;
842
		}
843
844
		//If passwords are enforced the password can't be null
845
		if (self::enforcePassword($config) && is_null($password)) {
846
			throw new \Exception('Cannot remove password');
847
		}
848
849
		self::verifyPassword($password);
850
851
		$qb = $connection->getQueryBuilder();
852
		$qb->update('share')
853
			->set('share_with', $qb->createParameter('pass'))
854
			->where($qb->expr()->eq('id', $qb->createParameter('shareId')))
855
			->setParameter(':pass', is_null($password) ? null : \OC::$server->getHasher()->hash($password))
856
			->setParameter(':shareId', $shareId);
857
858
		$qb->execute();
859
860
		return true;
861
	}
862
863
	/**
864
	 * Checks whether a share has expired, calls unshareItem() if yes.
865
	 * @param array $item Share data (usually database row)
866
	 * @return boolean True if item was expired, false otherwise.
867
	 */
868
	protected static function expireItem(array $item) {
869
870
		$result = false;
871
872
		// only use default expiration date for link shares
873
		if ((int) $item['share_type'] === self::SHARE_TYPE_LINK) {
874
875
			// calculate expiration date
876
			if (!empty($item['expiration'])) {
877
				$userDefinedExpire = new \DateTime($item['expiration']);
878
				$expires = $userDefinedExpire->getTimestamp();
879
			} else {
880
				$expires = null;
881
			}
882
883
884
			// get default expiration settings
885
			$defaultSettings = Helper::getDefaultExpireSetting();
886
			$expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
887
888
889
			if (is_int($expires)) {
890
				$now = time();
891
				if ($now > $expires) {
892
					self::unshareItem($item);
893
					$result = true;
894
				}
895
			}
896
		}
897
		return $result;
898
	}
899
900
	/**
901
	 * Unshares a share given a share data array
902
	 * @param array $item Share data (usually database row)
903
	 * @param int $newParent parent ID
904
	 * @return null
905
	 */
906
	protected static function unshareItem(array $item, $newParent = null) {
907
908
		$shareType = (int)$item['share_type'];
909
		$shareWith = null;
910
		if ($shareType !== \OCP\Share::SHARE_TYPE_LINK) {
911
			$shareWith = $item['share_with'];
912
		}
913
914
		// Pass all the vars we have for now, they may be useful
915
		$hookParams = array(
916
			'id'            => $item['id'],
917
			'itemType'      => $item['item_type'],
918
			'itemSource'    => $item['item_source'],
919
			'shareType'     => $shareType,
920
			'shareWith'     => $shareWith,
921
			'itemParent'    => $item['parent'],
922
			'uidOwner'      => $item['uid_owner'],
923
		);
924
		if($item['item_type'] === 'file' || $item['item_type'] === 'folder') {
925
			$hookParams['fileSource'] = $item['file_source'];
926
			$hookParams['fileTarget'] = $item['file_target'];
927
		}
928
929
		\OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
930
		$deletedShares = Helper::delete($item['id'], false, null, $newParent);
931
		$deletedShares[] = $hookParams;
932
		$hookParams['deletedShares'] = $deletedShares;
933
		\OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
934
		if ((int)$item['share_type'] === \OCP\Share::SHARE_TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
935
			list(, $remote) = Helper::splitUserRemote($item['share_with']);
936
			self::sendRemoteUnshare($remote, $item['id'], $item['token']);
937
		}
938
	}
939
940
	/**
941
	 * Get the backend class for the specified item type
942
	 * @param string $itemType
943
	 * @throws \Exception
944
	 * @return \OCP\Share_Backend
945
	 */
946
	public static function getBackend($itemType) {
947
		$l = \OC::$server->getL10N('lib');
948
		if (isset(self::$backends[$itemType])) {
949
			return self::$backends[$itemType];
950
		} else if (isset(self::$backendTypes[$itemType]['class'])) {
951
			$class = self::$backendTypes[$itemType]['class'];
952
			if (class_exists($class)) {
953
				self::$backends[$itemType] = new $class;
954 View Code Duplication
				if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
955
					$message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
956
					$message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', array($class));
957
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), \OCP\Util::ERROR);
958
					throw new \Exception($message_t);
959
				}
960
				return self::$backends[$itemType];
961 View Code Duplication
			} else {
962
				$message = 'Sharing backend %s not found';
963
				$message_t = $l->t('Sharing backend %s not found', array($class));
964
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), \OCP\Util::ERROR);
965
				throw new \Exception($message_t);
966
			}
967
		}
968
		$message = 'Sharing backend for %s not found';
969
		$message_t = $l->t('Sharing backend for %s not found', array($itemType));
970
		\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), \OCP\Util::ERROR);
971
		throw new \Exception($message_t);
972
	}
973
974
	/**
975
	 * Check if resharing is allowed
976
	 * @return boolean true if allowed or false
977
	 *
978
	 * Resharing is allowed by default if not configured
979
	 */
980
	public static function isResharingAllowed() {
981
		if (!isset(self::$isResharingAllowed)) {
982
			if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
983
				self::$isResharingAllowed = true;
984
			} else {
985
				self::$isResharingAllowed = false;
986
			}
987
		}
988
		return self::$isResharingAllowed;
989
	}
990
991
	/**
992
	 * Get a list of collection item types for the specified item type
993
	 * @param string $itemType
994
	 * @return array
995
	 */
996
	private static function getCollectionItemTypes($itemType) {
997
		$collectionTypes = array($itemType);
998
		foreach (self::$backendTypes as $type => $backend) {
999
			if (in_array($backend['collectionOf'], $collectionTypes)) {
1000
				$collectionTypes[] = $type;
1001
			}
1002
		}
1003
		// TODO Add option for collections to be collection of themselves, only 'folder' does it now...
1004
		if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
1005
			unset($collectionTypes[0]);
1006
		}
1007
		// Return array if collections were found or the item type is a
1008
		// collection itself - collections can be inside collections
1009
		if (count($collectionTypes) > 0) {
1010
			return $collectionTypes;
1011
		}
1012
		return false;
1013
	}
1014
1015
	/**
1016
	 * Get the owners of items shared with a user.
1017
	 *
1018
	 * @param string $user The user the items are shared with.
1019
	 * @param string $type The type of the items shared with the user.
1020
	 * @param boolean $includeCollections Include collection item types (optional)
1021
	 * @param boolean $includeOwner include owner in the list of users the item is shared with (optional)
1022
	 * @return array
1023
	 */
1024
	public static function getSharedItemsOwners($user, $type, $includeCollections = false, $includeOwner = false) {
1025
		// First, we find out if $type is part of a collection (and if that collection is part of
1026
		// another one and so on).
1027
		$collectionTypes = array();
1028
		if (!$includeCollections || !$collectionTypes = self::getCollectionItemTypes($type)) {
1029
			$collectionTypes[] = $type;
1030
		}
1031
1032
		// Of these collection types, along with our original $type, we make a
1033
		// list of the ones for which a sharing backend has been registered.
1034
		// FIXME: Ideally, we wouldn't need to nest getItemsSharedWith in this loop but just call it
1035
		// with its $includeCollections parameter set to true. Unfortunately, this fails currently.
1036
		$allMaybeSharedItems = array();
1037
		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...
1038
			if (isset(self::$backends[$collectionType])) {
1039
				$allMaybeSharedItems[$collectionType] = self::getItemsSharedWithUser(
1040
					$collectionType,
1041
					$user,
1042
					self::FORMAT_NONE
1043
				);
1044
			}
1045
		}
1046
1047
		$owners = array();
1048
		if ($includeOwner) {
1049
			$owners[] = $user;
1050
		}
1051
1052
		// We take a look at all shared items of the given $type (or of the collections it is part of)
1053
		// and find out their owners. Then, we gather the tags for the original $type from all owners,
1054
		// and return them as elements of a list that look like "Tag (owner)".
1055
		foreach ($allMaybeSharedItems as $collectionType => $maybeSharedItems) {
1056
			foreach ($maybeSharedItems as $sharedItem) {
1057
				if (isset($sharedItem['id'])) { //workaround for https://github.com/owncloud/core/issues/2814
1058
					$owners[] = $sharedItem['uid_owner'];
1059
				}
1060
			}
1061
		}
1062
1063
		return $owners;
1064
	}
1065
1066
	/**
1067
	 * Get shared items from the database
1068
	 * @param string $itemType
1069
	 * @param string $item Item source or target (optional)
1070
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
1071
	 * @param string $shareWith User or group the item is being shared with
1072
	 * @param string $uidOwner User that is the owner of shared items (optional)
1073
	 * @param int $format Format to convert items to with formatItems() (optional)
1074
	 * @param mixed $parameters to pass to formatItems() (optional)
1075
	 * @param int $limit Number of items to return, -1 to return all matches (optional)
1076
	 * @param boolean $includeCollections Include collection item types (optional)
1077
	 * @param boolean $itemShareWithBySource (optional)
1078
	 * @param boolean $checkExpireDate
1079
	 * @return array
1080
	 *
1081
	 * See public functions getItem(s)... for parameter usage
1082
	 *
1083
	 */
1084
	public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
1085
									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
1086
									$includeCollections = false, $itemShareWithBySource = false, $checkExpireDate  = true) {
1087
		if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_enabled', 'yes') != 'yes') {
1088
			return array();
1089
		}
1090
		$backend = self::getBackend($itemType);
1091
		$collectionTypes = false;
1092
		// Get filesystem root to add it to the file target and remove from the
1093
		// file source, match file_source with the file cache
1094
		if ($itemType == 'file' || $itemType == 'folder') {
1095
			if(!is_null($uidOwner)) {
1096
				$root = \OC\Files\Filesystem::getRoot();
1097
			} else {
1098
				$root = '';
1099
			}
1100
			$where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
1101
			if (!isset($item)) {
1102
				$where .= ' AND `file_target` IS NOT NULL ';
1103
			}
1104
			$where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
1105
			$fileDependent = true;
1106
			$queryArgs = array();
1107
		} else {
1108
			$fileDependent = false;
1109
			$root = '';
1110
			$collectionTypes = self::getCollectionItemTypes($itemType);
1111
			if ($includeCollections && !isset($item) && $collectionTypes) {
1112
				// If includeCollections is true, find collections of this item type, e.g. a music album contains songs
1113
				if (!in_array($itemType, $collectionTypes)) {
1114
					$itemTypes = array_merge(array($itemType), $collectionTypes);
1115
				} else {
1116
					$itemTypes = $collectionTypes;
1117
				}
1118
				$placeholders = join(',', array_fill(0, count($itemTypes), '?'));
1119
				$where = ' WHERE `item_type` IN ('.$placeholders.'))';
1120
				$queryArgs = $itemTypes;
1121
			} else {
1122
				$where = ' WHERE `item_type` = ?';
1123
				$queryArgs = array($itemType);
1124
			}
1125
		}
1126
		if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1127
			$where .= ' AND `share_type` != ?';
1128
			$queryArgs[] = self::SHARE_TYPE_LINK;
1129
		}
1130
		if (isset($shareType)) {
1131
			// Include all user and group items
1132
			if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
1133
				$where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
1134
				$queryArgs[] = self::SHARE_TYPE_USER;
1135
				$queryArgs[] = self::$shareTypeGroupUserUnique;
1136
				$queryArgs[] = $shareWith;
1137
1138
				$user = \OC::$server->getUserManager()->get($shareWith);
1139
				$groups = [];
1140
				if ($user) {
1141
					$groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
1142
				}
1143
				if (!empty($groups)) {
1144
					$placeholders = join(',', array_fill(0, count($groups), '?'));
1145
					$where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
1146
					$queryArgs[] = self::SHARE_TYPE_GROUP;
1147
					$queryArgs = array_merge($queryArgs, $groups);
1148
				}
1149
				$where .= ')';
1150
				// Don't include own group shares
1151
				$where .= ' AND `uid_owner` != ?';
1152
				$queryArgs[] = $shareWith;
1153
			} else {
1154
				$where .= ' AND `share_type` = ?';
1155
				$queryArgs[] = $shareType;
1156
				if (isset($shareWith)) {
1157
					$where .= ' AND `share_with` = ?';
1158
					$queryArgs[] = $shareWith;
1159
				}
1160
			}
1161
		}
1162
		if (isset($uidOwner)) {
1163
			$where .= ' AND `uid_owner` = ?';
1164
			$queryArgs[] = $uidOwner;
1165
			if (!isset($shareType)) {
1166
				// Prevent unique user targets for group shares from being selected
1167
				$where .= ' AND `share_type` != ?';
1168
				$queryArgs[] = self::$shareTypeGroupUserUnique;
1169
			}
1170
			if ($fileDependent) {
1171
				$column = 'file_source';
1172
			} else {
1173
				$column = 'item_source';
1174
			}
1175
		} else {
1176
			if ($fileDependent) {
1177
				$column = 'file_target';
1178
			} else {
1179
				$column = 'item_target';
1180
			}
1181
		}
1182
		if (isset($item)) {
1183
			$collectionTypes = self::getCollectionItemTypes($itemType);
1184
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
1185
				$where .= ' AND (';
1186
			} else {
1187
				$where .= ' AND';
1188
			}
1189
			// If looking for own shared items, check item_source else check item_target
1190
			if (isset($uidOwner) || $itemShareWithBySource) {
1191
				// If item type is a file, file source needs to be checked in case the item was converted
1192
				if ($fileDependent) {
1193
					$where .= ' `file_source` = ?';
1194
					$column = 'file_source';
1195
				} else {
1196
					$where .= ' `item_source` = ?';
1197
					$column = 'item_source';
1198
				}
1199
			} else {
1200
				if ($fileDependent) {
1201
					$where .= ' `file_target` = ?';
1202
					$item = \OC\Files\Filesystem::normalizePath($item);
1203
				} else {
1204
					$where .= ' `item_target` = ?';
1205
				}
1206
			}
1207
			$queryArgs[] = $item;
1208
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
1209
				$placeholders = join(',', array_fill(0, count($collectionTypes), '?'));
1210
				$where .= ' OR `item_type` IN ('.$placeholders.'))';
1211
				$queryArgs = array_merge($queryArgs, $collectionTypes);
1212
			}
1213
		}
1214
1215
		if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) {
1216
			// Make sure the unique user target is returned if it exists,
1217
			// unique targets should follow the group share in the database
1218
			// If the limit is not 1, the filtering can be done later
1219
			$where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
1220
		} else {
1221
			$where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
1222
		}
1223
1224
		if ($limit != -1 && !$includeCollections) {
1225
			// The limit must be at least 3, because filtering needs to be done
1226
			if ($limit < 3) {
1227
				$queryLimit = 3;
1228
			} else {
1229
				$queryLimit = $limit;
1230
			}
1231
		} else {
1232
			$queryLimit = null;
1233
		}
1234
		$select = self::createSelectStatement($format, $fileDependent, $uidOwner);
1235
		$root = strlen($root);
1236
		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
1237
		$result = $query->execute($queryArgs);
1238 View Code Duplication
		if ($result === false) {
1239
			\OCP\Util::writeLog('OCP\Share',
1240
				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
1241
				\OCP\Util::ERROR);
1242
		}
1243
		$items = array();
1244
		$targets = array();
1245
		$switchedItems = array();
1246
		$mounts = array();
1247
		while ($row = $result->fetchRow()) {
1248
			self::transformDBResults($row);
1249
			// Filter out duplicate group shares for users with unique targets
1250
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
1251
				continue;
1252
			}
1253
			if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
1254
				$row['share_type'] = self::SHARE_TYPE_GROUP;
1255
				$row['unique_name'] = true; // remember that we use a unique name for this user
1256
				$row['share_with'] = $items[$row['parent']]['share_with'];
1257
				// if the group share was unshared from the user we keep the permission, otherwise
1258
				// we take the permission from the parent because this is always the up-to-date
1259
				// permission for the group share
1260
				if ($row['permissions'] > 0) {
1261
					$row['permissions'] = $items[$row['parent']]['permissions'];
1262
				}
1263
				// Remove the parent group share
1264
				unset($items[$row['parent']]);
1265
				if ($row['permissions'] == 0) {
1266
					continue;
1267
				}
1268
			} else if (!isset($uidOwner)) {
1269
				// Check if the same target already exists
1270
				if (isset($targets[$row['id']])) {
1271
					// Check if the same owner shared with the user twice
1272
					// through a group and user share - this is allowed
1273
					$id = $targets[$row['id']];
1274
					if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
1275
						// Switch to group share type to ensure resharing conditions aren't bypassed
1276
						if ($items[$id]['share_type'] != self::SHARE_TYPE_GROUP) {
1277
							$items[$id]['share_type'] = self::SHARE_TYPE_GROUP;
1278
							$items[$id]['share_with'] = $row['share_with'];
1279
						}
1280
						// Switch ids if sharing permission is granted on only
1281
						// one share to ensure correct parent is used if resharing
1282
						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
1283
							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
1284
							$items[$row['id']] = $items[$id];
1285
							$switchedItems[$id] = $row['id'];
1286
							unset($items[$id]);
1287
							$id = $row['id'];
1288
						}
1289
						$items[$id]['permissions'] |= (int)$row['permissions'];
1290
1291
					}
1292
					continue;
1293
				} elseif (!empty($row['parent'])) {
1294
					$targets[$row['parent']] = $row['id'];
1295
				}
1296
			}
1297
			// Remove root from file source paths if retrieving own shared items
1298
			if (isset($uidOwner) && isset($row['path'])) {
1299
				if (isset($row['parent'])) {
1300
					$query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?');
1301
					$parentResult = $query->execute(array($row['parent']));
1302
					if ($result === false) {
1303
						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
1304
							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
1305
							\OCP\Util::ERROR);
1306
					} else {
1307
						$parentRow = $parentResult->fetchRow();
1308
						$tmpPath = $parentRow['file_target'];
1309
						// find the right position where the row path continues from the target path
1310
						$pos = strrpos($row['path'], $parentRow['file_target']);
1311
						$subPath = substr($row['path'], $pos);
1312
						$splitPath = explode('/', $subPath);
1313
						foreach (array_slice($splitPath, 2) as $pathPart) {
1314
							$tmpPath = $tmpPath . '/' . $pathPart;
1315
						}
1316
						$row['path'] = $tmpPath;
1317
					}
1318
				} else {
1319
					if (!isset($mounts[$row['storage']])) {
1320
						$mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
1321
						if (is_array($mountPoints) && !empty($mountPoints)) {
1322
							$mounts[$row['storage']] = current($mountPoints);
1323
						}
1324
					}
1325
					if (!empty($mounts[$row['storage']])) {
1326
						$path = $mounts[$row['storage']]->getMountPoint().$row['path'];
1327
						$relPath = substr($path, $root); // path relative to data/user
1328
						$row['path'] = rtrim($relPath, '/');
1329
					}
1330
				}
1331
			}
1332
1333
			if($checkExpireDate) {
1334
				if (self::expireItem($row)) {
1335
					continue;
1336
				}
1337
			}
1338
			// Check if resharing is allowed, if not remove share permission
1339
			if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
1340
				$row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
1341
			}
1342
			// Add display names to result
1343
			$row['share_with_displayname'] = $row['share_with'];
1344
			if ( isset($row['share_with']) && $row['share_with'] != '' &&
1345
				$row['share_type'] === self::SHARE_TYPE_USER) {
1346
				$row['share_with_displayname'] = \OCP\User::getDisplayName($row['share_with']);
1347
			} else if(isset($row['share_with']) && $row['share_with'] != '' &&
1348
				$row['share_type'] === self::SHARE_TYPE_REMOTE) {
1349
				$addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
1350 View Code Duplication
				foreach ($addressBookEntries as $entry) {
1351
					foreach ($entry['CLOUD'] as $cloudID) {
1352
						if ($cloudID === $row['share_with']) {
1353
							$row['share_with_displayname'] = $entry['FN'];
1354
						}
1355
					}
1356
				}
1357
			}
1358
			if ( isset($row['uid_owner']) && $row['uid_owner'] != '') {
1359
				$row['displayname_owner'] = \OCP\User::getDisplayName($row['uid_owner']);
1360
			}
1361
1362
			if ($row['permissions'] > 0) {
1363
				$items[$row['id']] = $row;
1364
			}
1365
1366
		}
1367
1368
		// group items if we are looking for items shared with the current user
1369
		if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
1370
			$items = self::groupItems($items, $itemType);
1371
		}
1372
1373
		if (!empty($items)) {
1374
			$collectionItems = array();
1375
			foreach ($items as &$row) {
1376
				// Return only the item instead of a 2-dimensional array
1377
				if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) {
1378
					if ($format == self::FORMAT_NONE) {
1379
						return $row;
1380
					} else {
1381
						break;
1382
					}
1383
				}
1384
				// Check if this is a collection of the requested item type
1385
				if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
1386
					if (($collectionBackend = self::getBackend($row['item_type']))
1387
						&& $collectionBackend instanceof \OCP\Share_Backend_Collection) {
1388
						// Collections can be inside collections, check if the item is a collection
1389
						if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
1390
							$collectionItems[] = $row;
1391
						} else {
1392
							$collection = array();
1393
							$collection['item_type'] = $row['item_type'];
1394
							if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
1395
								$collection['path'] = basename($row['path']);
1396
							}
1397
							$row['collection'] = $collection;
1398
							// Fetch all of the children sources
1399
							$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...
1400
							foreach ($children as $child) {
1401
								$childItem = $row;
1402
								$childItem['item_type'] = $itemType;
1403
								if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
1404
									$childItem['item_source'] = $child['source'];
1405
									$childItem['item_target'] = $child['target'];
1406
								}
1407
								if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
1408
									if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
1409
										$childItem['file_source'] = $child['source'];
1410
									} else { // TODO is this really needed if we already know that we use the file backend?
1411
										$meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
1412
										$childItem['file_source'] = $meta['fileid'];
1413
									}
1414
									$childItem['file_target'] =
1415
										\OC\Files\Filesystem::normalizePath($child['file_path']);
1416
								}
1417
								if (isset($item)) {
1418
									if ($childItem[$column] == $item) {
1419
										// Return only the item instead of a 2-dimensional array
1420
										if ($limit == 1) {
1421
											if ($format == self::FORMAT_NONE) {
1422
												return $childItem;
1423
											} else {
1424
												// Unset the items array and break out of both loops
1425
												$items = array();
1426
												$items[] = $childItem;
1427
												break 2;
1428
											}
1429
										} else {
1430
											$collectionItems[] = $childItem;
1431
										}
1432
									}
1433
								} else {
1434
									$collectionItems[] = $childItem;
1435
								}
1436
							}
1437
						}
1438
					}
1439
					// Remove collection item
1440
					$toRemove = $row['id'];
1441
					if (array_key_exists($toRemove, $switchedItems)) {
1442
						$toRemove = $switchedItems[$toRemove];
1443
					}
1444
					unset($items[$toRemove]);
1445
				} elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
1446
					// FIXME: Thats a dirty hack to improve file sharing performance,
1447
					// see github issue #10588 for more details
1448
					// Need to find a solution which works for all back-ends
1449
					$collectionBackend = self::getBackend($row['item_type']);
1450
					$sharedParents = $collectionBackend->getParents($row['item_source']);
1451
					foreach ($sharedParents as $parent) {
1452
						$collectionItems[] = $parent;
1453
					}
1454
				}
1455
			}
1456
			if (!empty($collectionItems)) {
1457
				$collectionItems = array_unique($collectionItems, SORT_REGULAR);
1458
				$items = array_merge($items, $collectionItems);
1459
			}
1460
1461
			// filter out invalid items, these can appear when subshare entries exist
1462
			// for a group in which the requested user isn't a member any more
1463
			$items = array_filter($items, function($item) {
1464
				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
1465
			});
1466
1467
			return self::formatResult($items, $column, $backend, $format, $parameters);
1468
		} elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
1469
			// FIXME: Thats a dirty hack to improve file sharing performance,
1470
			// see github issue #10588 for more details
1471
			// Need to find a solution which works for all back-ends
1472
			$collectionItems = array();
1473
			$collectionBackend = self::getBackend('folder');
1474
			$sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
1475
			foreach ($sharedParents as $parent) {
1476
				$collectionItems[] = $parent;
1477
			}
1478
			if ($limit === 1) {
1479
				return reset($collectionItems);
1480
			}
1481
			return self::formatResult($collectionItems, $column, $backend, $format, $parameters);
1482
		}
1483
1484
		return array();
1485
	}
1486
1487
	/**
1488
	 * group items with link to the same source
1489
	 *
1490
	 * @param array $items
1491
	 * @param string $itemType
1492
	 * @return array of grouped items
1493
	 */
1494
	protected static function groupItems($items, $itemType) {
1495
1496
		$fileSharing = ($itemType === 'file' || $itemType === 'folder') ? true : false;
1497
1498
		$result = array();
1499
1500
		foreach ($items as $item) {
1501
			$grouped = false;
1502
			foreach ($result as $key => $r) {
1503
				// for file/folder shares we need to compare file_source, otherwise we compare item_source
1504
				// only group shares if they already point to the same target, otherwise the file where shared
1505
				// before grouping of shares was added. In this case we don't group them toi avoid confusions
1506
				if (( $fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
1507
					(!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
1508
					// add the first item to the list of grouped shares
1509
					if (!isset($result[$key]['grouped'])) {
1510
						$result[$key]['grouped'][] = $result[$key];
1511
					}
1512
					$result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
1513
					$result[$key]['grouped'][] = $item;
1514
					$grouped = true;
1515
					break;
1516
				}
1517
			}
1518
1519
			if (!$grouped) {
1520
				$result[] = $item;
1521
			}
1522
1523
		}
1524
1525
		return $result;
1526
	}
1527
1528
	/**
1529
	 * Put shared item into the database
1530
	 * @param string $itemType Item type
1531
	 * @param string $itemSource Item source
1532
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
1533
	 * @param string $shareWith User or group the item is being shared with
1534
	 * @param string $uidOwner User that is the owner of shared item
1535
	 * @param int $permissions CRUDS permissions
1536
	 * @param boolean|array $parentFolder Parent folder target (optional)
1537
	 * @param string $token (optional)
1538
	 * @param string $itemSourceName name of the source item (optional)
1539
	 * @param \DateTime $expirationDate (optional)
1540
	 * @throws \Exception
1541
	 * @return mixed id of the new share or false
1542
	 */
1543
	private static function put($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
1544
								$permissions, $parentFolder = null, $token = null, $itemSourceName = null, \DateTime $expirationDate = null) {
1545
1546
		$queriesToExecute = array();
1547
		$suggestedItemTarget = null;
1548
		$groupFileTarget = $fileTarget = $suggestedFileTarget = $filePath = '';
1549
		$groupItemTarget = $itemTarget = $fileSource = $parent = 0;
1550
1551
		$result = self::checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate);
1552
		if(!empty($result)) {
1553
			$parent = $result['parent'];
1554
			$itemSource = $result['itemSource'];
1555
			$fileSource = $result['fileSource'];
1556
			$suggestedItemTarget = $result['suggestedItemTarget'];
1557
			$suggestedFileTarget = $result['suggestedFileTarget'];
1558
			$filePath = $result['filePath'];
1559
		}
1560
1561
		$isGroupShare = false;
1562
		if ($shareType == self::SHARE_TYPE_GROUP) {
1563
			$isGroupShare = true;
1564
			if (isset($shareWith['users'])) {
1565
				$users = $shareWith['users'];
1566
			} else {
1567
				$group = \OC::$server->getGroupManager()->get($shareWith['group']);
1568
				if ($group) {
1569
					$users = $group->searchUsers('', -1, 0);
1570
					$userIds = [];
1571
					foreach ($users as $user) {
1572
						$userIds[] = $user->getUID();
1573
					}
1574
					$users = $userIds;
1575
				} else {
1576
					$users = [];
1577
				}
1578
			}
1579
			// remove current user from list
1580
			if (in_array(\OCP\User::getUser(), $users)) {
1581
				unset($users[array_search(\OCP\User::getUser(), $users)]);
1582
			}
1583
			$groupItemTarget = Helper::generateTarget($itemType, $itemSource,
1584
				$shareType, $shareWith['group'], $uidOwner, $suggestedItemTarget);
1585
			$groupFileTarget = Helper::generateTarget($itemType, $itemSource,
1586
				$shareType, $shareWith['group'], $uidOwner, $filePath);
1587
1588
			// add group share to table and remember the id as parent
1589
			$queriesToExecute['groupShare'] = array(
1590
				'itemType'			=> $itemType,
1591
				'itemSource'		=> $itemSource,
1592
				'itemTarget'		=> $groupItemTarget,
1593
				'shareType'			=> $shareType,
1594
				'shareWith'			=> $shareWith['group'],
1595
				'uidOwner'			=> $uidOwner,
1596
				'permissions'		=> $permissions,
1597
				'shareTime'			=> time(),
1598
				'fileSource'		=> $fileSource,
1599
				'fileTarget'		=> $groupFileTarget,
1600
				'token'				=> $token,
1601
				'parent'			=> $parent,
1602
				'expiration'		=> $expirationDate,
1603
			);
1604
1605
		} else {
1606
			$users = array($shareWith);
1607
			$itemTarget = Helper::generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
1608
				$suggestedItemTarget);
1609
		}
1610
1611
		$run = true;
1612
		$error = '';
1613
		$preHookData = array(
1614
			'itemType' => $itemType,
1615
			'itemSource' => $itemSource,
1616
			'shareType' => $shareType,
1617
			'uidOwner' => $uidOwner,
1618
			'permissions' => $permissions,
1619
			'fileSource' => $fileSource,
1620
			'expiration' => $expirationDate,
1621
			'token' => $token,
1622
			'run' => &$run,
1623
			'error' => &$error
1624
		);
1625
1626
		$preHookData['itemTarget'] = ($isGroupShare) ? $groupItemTarget : $itemTarget;
1627
		$preHookData['shareWith'] = ($isGroupShare) ? $shareWith['group'] : $shareWith;
1628
1629
		\OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData);
1630
1631
		if ($run === false) {
1632
			throw new \Exception($error);
1633
		}
1634
1635
		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...
1636
			$sourceId = ($itemType === 'file' || $itemType === 'folder') ? $fileSource : $itemSource;
1637
			$sourceExists = self::getItemSharedWithBySource($itemType, $sourceId, self::FORMAT_NONE, null, true, $user);
1638
1639
			$userShareType = ($isGroupShare) ? self::$shareTypeGroupUserUnique : $shareType;
1640
1641
			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...
1642
				$fileTarget = $sourceExists['file_target'];
1643
				$itemTarget = $sourceExists['item_target'];
1644
1645
				// for group shares we don't need a additional entry if the target is the same
1646
				if($isGroupShare && $groupItemTarget === $itemTarget) {
1647
					continue;
1648
				}
1649
1650
			} 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...
1651
1652
				$itemTarget = Helper::generateTarget($itemType, $itemSource, $userShareType, $user,
1653
					$uidOwner, $suggestedItemTarget, $parent);
1654
				if (isset($fileSource)) {
1655
					if ($parentFolder) {
1656
						if ($parentFolder === true) {
1657
							$fileTarget = Helper::generateTarget('file', $filePath, $userShareType, $user,
1658
								$uidOwner, $suggestedFileTarget, $parent);
1659
							if ($fileTarget != $groupFileTarget) {
1660
								$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...
1661
							}
1662
						} else if (isset($parentFolder[$user])) {
1663
							$fileTarget = $parentFolder[$user]['folder'].$itemSource;
1664
							$parent = $parentFolder[$user]['id'];
1665
						}
1666
					} else {
1667
						$fileTarget = Helper::generateTarget('file', $filePath, $userShareType,
1668
							$user, $uidOwner, $suggestedFileTarget, $parent);
1669
					}
1670
				} else {
1671
					$fileTarget = null;
1672
				}
1673
1674
			} else {
1675
1676
				// group share which doesn't exists until now, check if we need a unique target for this user
1677
1678
				$itemTarget = Helper::generateTarget($itemType, $itemSource, self::SHARE_TYPE_USER, $user,
1679
					$uidOwner, $suggestedItemTarget, $parent);
1680
1681
				// do we also need a file target
1682
				if (isset($fileSource)) {
1683
					$fileTarget = Helper::generateTarget('file', $filePath, self::SHARE_TYPE_USER, $user,
1684
						$uidOwner, $suggestedFileTarget, $parent);
1685
				} else {
1686
					$fileTarget = null;
1687
				}
1688
1689
				if (($itemTarget === $groupItemTarget) &&
1690
					(!isset($fileSource) || $fileTarget === $groupFileTarget)) {
1691
					continue;
1692
				}
1693
			}
1694
1695
			$queriesToExecute[] = array(
1696
				'itemType'			=> $itemType,
1697
				'itemSource'		=> $itemSource,
1698
				'itemTarget'		=> $itemTarget,
1699
				'shareType'			=> $userShareType,
1700
				'shareWith'			=> $user,
1701
				'uidOwner'			=> $uidOwner,
1702
				'permissions'		=> $permissions,
1703
				'shareTime'			=> time(),
1704
				'fileSource'		=> $fileSource,
1705
				'fileTarget'		=> $fileTarget,
1706
				'token'				=> $token,
1707
				'parent'			=> $parent,
1708
				'expiration'		=> $expirationDate,
1709
			);
1710
1711
		}
1712
1713
		$id = false;
1714
		if ($isGroupShare) {
1715
			$id = self::insertShare($queriesToExecute['groupShare']);
1716
			// Save this id, any extra rows for this group share will need to reference it
1717
			$parent = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share');
1718
			unset($queriesToExecute['groupShare']);
1719
		}
1720
1721
		foreach ($queriesToExecute as $shareQuery) {
1722
			$shareQuery['parent'] = $parent;
1723
			$id = self::insertShare($shareQuery);
1724
		}
1725
1726
		$postHookData = array(
1727
			'itemType' => $itemType,
1728
			'itemSource' => $itemSource,
1729
			'parent' => $parent,
1730
			'shareType' => $shareType,
1731
			'uidOwner' => $uidOwner,
1732
			'permissions' => $permissions,
1733
			'fileSource' => $fileSource,
1734
			'id' => $parent,
1735
			'token' => $token,
1736
			'expirationDate' => $expirationDate,
1737
		);
1738
1739
		$postHookData['shareWith'] = ($isGroupShare) ? $shareWith['group'] : $shareWith;
1740
		$postHookData['itemTarget'] = ($isGroupShare) ? $groupItemTarget : $itemTarget;
1741
		$postHookData['fileTarget'] = ($isGroupShare) ? $groupFileTarget : $fileTarget;
1742
1743
		\OC_Hook::emit('OCP\Share', 'post_shared', $postHookData);
1744
1745
1746
		return $id ? $id : false;
1747
	}
1748
1749
	/**
1750
	 * @param string $itemType
1751
	 * @param string $itemSource
1752
	 * @param int $shareType
1753
	 * @param string $shareWith
1754
	 * @param string $uidOwner
1755
	 * @param int $permissions
1756
	 * @param string|null $itemSourceName
1757
	 * @param null|\DateTime $expirationDate
1758
	 */
1759
	private static function checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate) {
1760
		$backend = self::getBackend($itemType);
1761
1762
		$l = \OC::$server->getL10N('lib');
1763
		$result = array();
1764
1765
		$column = ($itemType === 'file' || $itemType === 'folder') ? 'file_source' : 'item_source';
1766
1767
		$checkReshare = self::getItemSharedWithBySource($itemType, $itemSource, self::FORMAT_NONE, null, true);
1768
		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...
1769
			// Check if attempting to share back to owner
1770
			if ($checkReshare['uid_owner'] == $shareWith && $shareType == self::SHARE_TYPE_USER) {
1771
				$message = 'Sharing %s failed, because the user %s is the original sharer';
1772
				$message_t = $l->t('Sharing failed, because the user %s is the original sharer', [$shareWith]);
1773
1774
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
1775
				throw new \Exception($message_t);
1776
			}
1777
		}
1778
1779
		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...
1780
			// Check if share permissions is granted
1781
			if (self::isResharingAllowed() && (int)$checkReshare['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
1782
				if (~(int)$checkReshare['permissions'] & $permissions) {
1783
					$message = 'Sharing %s failed, because the permissions exceed permissions granted to %s';
1784
					$message_t = $l->t('Sharing %s failed, because the permissions exceed permissions granted to %s', array($itemSourceName, $uidOwner));
1785
1786
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $uidOwner), \OCP\Util::DEBUG);
1787
					throw new \Exception($message_t);
1788
				} else {
1789
					// TODO Don't check if inside folder
1790
					$result['parent'] = $checkReshare['id'];
1791
1792
					$result['expirationDate'] = $expirationDate;
1793
					// $checkReshare['expiration'] could be null and then is always less than any value
1794
					if(isset($checkReshare['expiration']) && $checkReshare['expiration'] < $expirationDate) {
1795
						$result['expirationDate'] = $checkReshare['expiration'];
1796
					}
1797
1798
					// only suggest the same name as new target if it is a reshare of the
1799
					// same file/folder and not the reshare of a child
1800
					if ($checkReshare[$column] === $itemSource) {
1801
						$result['filePath'] = $checkReshare['file_target'];
1802
						$result['itemSource'] = $checkReshare['item_source'];
1803
						$result['fileSource'] = $checkReshare['file_source'];
1804
						$result['suggestedItemTarget'] = $checkReshare['item_target'];
1805
						$result['suggestedFileTarget'] = $checkReshare['file_target'];
1806
					} else {
1807
						$result['filePath'] = ($backend instanceof \OCP\Share_Backend_File_Dependent) ? $backend->getFilePath($itemSource, $uidOwner) : null;
1808
						$result['suggestedItemTarget'] = null;
1809
						$result['suggestedFileTarget'] = null;
1810
						$result['itemSource'] = $itemSource;
1811
						$result['fileSource'] = ($backend instanceof \OCP\Share_Backend_File_Dependent) ? $itemSource : null;
1812
					}
1813
				}
1814 View Code Duplication
			} else {
1815
				$message = 'Sharing %s failed, because resharing is not allowed';
1816
				$message_t = $l->t('Sharing %s failed, because resharing is not allowed', array($itemSourceName));
1817
1818
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
1819
				throw new \Exception($message_t);
1820
			}
1821
		} else {
1822
			$result['parent'] = null;
1823
			$result['suggestedItemTarget'] = null;
1824
			$result['suggestedFileTarget'] = null;
1825
			$result['itemSource'] = $itemSource;
1826
			$result['expirationDate'] = $expirationDate;
1827 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...
1828
				$message = 'Sharing %s failed, because the sharing backend for '
1829
					.'%s could not find its source';
1830
				$message_t = $l->t('Sharing %s failed, because the sharing backend for %s could not find its source', array($itemSource, $itemType));
1831
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSource, $itemType), \OCP\Util::DEBUG);
1832
				throw new \Exception($message_t);
1833
			}
1834
			if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
1835
				$result['filePath'] = $backend->getFilePath($itemSource, $uidOwner);
1836
				if ($itemType == 'file' || $itemType == 'folder') {
1837
					$result['fileSource'] = $itemSource;
1838
				} else {
1839
					$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...
1840
					$result['fileSource'] = $meta['fileid'];
1841
				}
1842 View Code Duplication
				if ($result['fileSource'] == -1) {
1843
					$message = 'Sharing %s failed, because the file could not be found in the file cache';
1844
					$message_t = $l->t('Sharing %s failed, because the file could not be found in the file cache', array($itemSource));
1845
1846
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSource), \OCP\Util::DEBUG);
1847
					throw new \Exception($message_t);
1848
				}
1849
			} else {
1850
				$result['filePath'] = null;
1851
				$result['fileSource'] = null;
1852
			}
1853
		}
1854
1855
		return $result;
1856
	}
1857
1858
	/**
1859
	 *
1860
	 * @param array $shareData
1861
	 * @return mixed false in case of a failure or the id of the new share
1862
	 */
1863
	private static function insertShare(array $shareData) {
1864
1865
		$query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` ('
1866
			.' `item_type`, `item_source`, `item_target`, `share_type`,'
1867
			.' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,'
1868
			.' `file_target`, `token`, `parent`, `expiration`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)');
1869
		$query->bindValue(1, $shareData['itemType']);
1870
		$query->bindValue(2, $shareData['itemSource']);
1871
		$query->bindValue(3, $shareData['itemTarget']);
1872
		$query->bindValue(4, $shareData['shareType']);
1873
		$query->bindValue(5, $shareData['shareWith']);
1874
		$query->bindValue(6, $shareData['uidOwner']);
1875
		$query->bindValue(7, $shareData['permissions']);
1876
		$query->bindValue(8, $shareData['shareTime']);
1877
		$query->bindValue(9, $shareData['fileSource']);
1878
		$query->bindValue(10, $shareData['fileTarget']);
1879
		$query->bindValue(11, $shareData['token']);
1880
		$query->bindValue(12, $shareData['parent']);
1881
		$query->bindValue(13, $shareData['expiration'], 'datetime');
1882
		$result = $query->execute();
1883
1884
		$id = false;
1885
		if ($result) {
1886
			$id =  \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share');
1887
		}
1888
1889
		return $id;
1890
1891
	}
1892
1893
	/**
1894
	 * In case a password protected link is not yet authenticated this function will return false
1895
	 *
1896
	 * @param array $linkItem
1897
	 * @return boolean
1898
	 */
1899
	public static function checkPasswordProtectedShare(array $linkItem) {
1900
		if (!isset($linkItem['share_with'])) {
1901
			return true;
1902
		}
1903
		if (!isset($linkItem['share_type'])) {
1904
			return true;
1905
		}
1906
		if (!isset($linkItem['id'])) {
1907
			return true;
1908
		}
1909
1910
		if ($linkItem['share_type'] != \OCP\Share::SHARE_TYPE_LINK) {
1911
			return true;
1912
		}
1913
1914 View Code Duplication
		if ( \OC::$server->getSession()->exists('public_link_authenticated')
1915
			&& \OC::$server->getSession()->get('public_link_authenticated') === (string)$linkItem['id'] ) {
1916
			return true;
1917
		}
1918
1919
		return false;
1920
	}
1921
1922
	/**
1923
	 * construct select statement
1924
	 * @param int $format
1925
	 * @param boolean $fileDependent ist it a file/folder share or a generla share
1926
	 * @param string $uidOwner
1927
	 * @return string select statement
1928
	 */
1929
	private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
1930
		$select = '*';
1931
		if ($format == self::FORMAT_STATUSES) {
1932
			if ($fileDependent) {
1933
				$select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
1934
					. '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
1935
					. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
1936
					. '`uid_initiator`';
1937
			} else {
1938
				$select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
1939
			}
1940
		} else {
1941
			if (isset($uidOwner)) {
1942
				if ($fileDependent) {
1943
					$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
1944
						. ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
1945
						. ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
1946
						. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
1947
				} else {
1948
					$select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
1949
						. ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
1950
				}
1951
			} else {
1952
				if ($fileDependent) {
1953
					if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
1954
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
1955
							. '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
1956
							. '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
1957
							. '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
1958
					} else {
1959
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
1960
							. '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
1961
							. '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
1962
						    . '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
1963
							. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
1964
					}
1965
				}
1966
			}
1967
		}
1968
		return $select;
1969
	}
1970
1971
1972
	/**
1973
	 * transform db results
1974
	 * @param array $row result
1975
	 */
1976
	private static function transformDBResults(&$row) {
1977
		if (isset($row['id'])) {
1978
			$row['id'] = (int) $row['id'];
1979
		}
1980
		if (isset($row['share_type'])) {
1981
			$row['share_type'] = (int) $row['share_type'];
1982
		}
1983
		if (isset($row['parent'])) {
1984
			$row['parent'] = (int) $row['parent'];
1985
		}
1986
		if (isset($row['file_parent'])) {
1987
			$row['file_parent'] = (int) $row['file_parent'];
1988
		}
1989
		if (isset($row['file_source'])) {
1990
			$row['file_source'] = (int) $row['file_source'];
1991
		}
1992
		if (isset($row['permissions'])) {
1993
			$row['permissions'] = (int) $row['permissions'];
1994
		}
1995
		if (isset($row['storage'])) {
1996
			$row['storage'] = (int) $row['storage'];
1997
		}
1998
		if (isset($row['stime'])) {
1999
			$row['stime'] = (int) $row['stime'];
2000
		}
2001
		if (isset($row['expiration']) && $row['share_type'] !== self::SHARE_TYPE_LINK) {
2002
			// discard expiration date for non-link shares, which might have been
2003
			// set by ancient bugs
2004
			$row['expiration'] = null;
2005
		}
2006
	}
2007
2008
	/**
2009
	 * format result
2010
	 * @param array $items result
2011
	 * @param string $column is it a file share or a general share ('file_target' or 'item_target')
2012
	 * @param \OCP\Share_Backend $backend sharing backend
2013
	 * @param int $format
2014
	 * @param array $parameters additional format parameters
2015
	 * @return array format result
2016
	 */
2017
	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
2018
		if ($format === self::FORMAT_NONE) {
2019
			return $items;
2020
		} else if ($format === self::FORMAT_STATUSES) {
2021
			$statuses = array();
2022
			foreach ($items as $item) {
2023
				if ($item['share_type'] === self::SHARE_TYPE_LINK) {
2024
					if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
2025
						continue;
2026
					}
2027
					$statuses[$item[$column]]['link'] = true;
2028
				} else if (!isset($statuses[$item[$column]])) {
2029
					$statuses[$item[$column]]['link'] = false;
2030
				}
2031
				if (!empty($item['file_target'])) {
2032
					$statuses[$item[$column]]['path'] = $item['path'];
2033
				}
2034
			}
2035
			return $statuses;
2036
		} else {
2037
			return $backend->formatItems($items, $format, $parameters);
2038
		}
2039
	}
2040
2041
	/**
2042
	 * remove protocol from URL
2043
	 *
2044
	 * @param string $url
2045
	 * @return string
2046
	 */
2047 View Code Duplication
	public static function removeProtocolFromUrl($url) {
2048
		if (strpos($url, 'https://') === 0) {
2049
			return substr($url, strlen('https://'));
2050
		} else if (strpos($url, 'http://') === 0) {
2051
			return substr($url, strlen('http://'));
2052
		}
2053
2054
		return $url;
2055
	}
2056
2057
	/**
2058
	 * try http post first with https and then with http as a fallback
2059
	 *
2060
	 * @param string $remoteDomain
2061
	 * @param string $urlSuffix
2062
	 * @param array $fields post parameters
2063
	 * @return array
2064
	 */
2065
	private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) {
2066
		$protocol = 'https://';
2067
		$result = [
2068
			'success' => false,
2069
			'result' => '',
2070
		];
2071
		$try = 0;
2072
		$discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class);
2073
		while ($result['success'] === false && $try < 2) {
2074
			$federationEndpoints = $discoveryService->discover($protocol . $remoteDomain, 'FEDERATED_SHARING');
2075
			$endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
2076
			$result = \OC::$server->getHTTPHelper()->post($protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT, $fields);
2077
			$try++;
2078
			$protocol = 'http://';
2079
		}
2080
2081
		return $result;
2082
	}
2083
2084
	/**
2085
	 * send server-to-server share to remote server
2086
	 *
2087
	 * @param string $token
2088
	 * @param string $shareWith
2089
	 * @param string $name
2090
	 * @param int $remote_id
2091
	 * @param string $owner
2092
	 * @return bool
2093
	 */
2094
	private static function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner) {
2095
2096
		list($user, $remote) = Helper::splitUserRemote($shareWith);
2097
2098
		if ($user && $remote) {
2099
			$url = $remote;
2100
2101
			$local = \OC::$server->getURLGenerator()->getAbsoluteURL('/');
2102
2103
			$fields = array(
2104
				'shareWith' => $user,
2105
				'token' => $token,
2106
				'name' => $name,
2107
				'remoteId' => $remote_id,
2108
				'owner' => $owner,
2109
				'remote' => $local,
2110
			);
2111
2112
			$url = self::removeProtocolFromUrl($url);
2113
			$result = self::tryHttpPostToShareEndpoint($url, '', $fields);
2114
			$status = json_decode($result['result'], true);
2115
2116 View Code Duplication
			if ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200)) {
2117
				\OC_Hook::emit('OCP\Share', 'federated_share_added', ['server' => $remote]);
2118
				return true;
2119
			}
2120
2121
		}
2122
2123
		return false;
2124
	}
2125
2126
	/**
2127
	 * send server-to-server unshare to remote server
2128
	 *
2129
	 * @param string $remote url
2130
	 * @param int $id share id
2131
	 * @param string $token
2132
	 * @return bool
2133
	 */
2134
	private static function sendRemoteUnshare($remote, $id, $token) {
2135
		$url = rtrim($remote, '/');
2136
		$fields = array('token' => $token, 'format' => 'json');
2137
		$url = self::removeProtocolFromUrl($url);
2138
		$result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields);
2139
		$status = json_decode($result['result'], true);
2140
2141
		return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200));
2142
	}
2143
2144
	/**
2145
	 * check if user can only share with group members
2146
	 * @return bool
2147
	 */
2148
	public static function shareWithGroupMembersOnly() {
2149
		$value = \OC::$server->getAppConfig()->getValue('core', 'shareapi_only_share_with_group_members', 'no');
2150
		return ($value === 'yes') ? true : false;
2151
	}
2152
2153
	/**
2154
	 * @return bool
2155
	 */
2156
	public static function isDefaultExpireDateEnabled() {
2157
		$defaultExpireDateEnabled = \OCP\Config::getAppValue('core', 'shareapi_default_expire_date', 'no');
2158
		return ($defaultExpireDateEnabled === "yes") ? true : false;
2159
	}
2160
2161
	/**
2162
	 * @return bool
2163
	 */
2164
	public static function enforceDefaultExpireDate() {
2165
		$enforceDefaultExpireDate = \OCP\Config::getAppValue('core', 'shareapi_enforce_expire_date', 'no');
2166
		return ($enforceDefaultExpireDate === "yes") ? true : false;
2167
	}
2168
2169
	/**
2170
	 * @return int
2171
	 */
2172
	public static function getExpireInterval() {
2173
		return (int)\OCP\Config::getAppValue('core', 'shareapi_expire_after_n_days', '7');
2174
	}
2175
2176
	/**
2177
	 * Checks whether the given path is reachable for the given owner
2178
	 *
2179
	 * @param string $path path relative to files
2180
	 * @param string $ownerStorageId storage id of the owner
2181
	 *
2182
	 * @return boolean true if file is reachable, false otherwise
2183
	 */
2184
	private static function isFileReachable($path, $ownerStorageId) {
2185
		// if outside the home storage, file is always considered reachable
2186
		if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
2187
			substr($ownerStorageId, 0, 13) === 'object::user:'
2188
		)) {
2189
			return true;
2190
		}
2191
2192
		// if inside the home storage, the file has to be under "/files/"
2193
		$path = ltrim($path, '/');
2194
		if (substr($path, 0, 6) === 'files/') {
2195
			return true;
2196
		}
2197
2198
		return false;
2199
	}
2200
2201
	/**
2202
	 * @param IConfig $config
2203
	 * @return bool
2204
	 */
2205
	public static function enforcePassword(IConfig $config) {
2206
		$enforcePassword = $config->getAppValue('core', 'shareapi_enforce_links_password', 'no');
2207
		return ($enforcePassword === "yes") ? true : false;
2208
	}
2209
2210
	/**
2211
	 * Get all share entries, including non-unique group items
2212
	 *
2213
	 * @param string $owner
2214
	 * @return array
2215
	 */
2216
	public static function getAllSharesForOwner($owner) {
2217
		$query = 'SELECT * FROM `*PREFIX*share` WHERE `uid_owner` = ?';
2218
		$result = \OC::$server->getDatabaseConnection()->executeQuery($query, [$owner]);
2219
		return $result->fetchAll();
2220
	}
2221
2222
	/**
2223
	 * Get all share entries, including non-unique group items for a file
2224
	 *
2225
	 * @param int $id
2226
	 * @return array
2227
	 */
2228
	public static function getAllSharesForFileId($id) {
2229
		$query = 'SELECT * FROM `*PREFIX*share` WHERE `file_source` = ?';
2230
		$result = \OC::$server->getDatabaseConnection()->executeQuery($query, [$id]);
2231
		return $result->fetchAll();
2232
	}
2233
2234
	/**
2235
	 * @param string $password
2236
	 * @throws \Exception
2237
	 */
2238
	private static function verifyPassword($password) {
2239
2240
		$accepted = true;
2241
		$message = '';
2242
		\OCP\Util::emitHook('\OC\Share', 'verifyPassword', [
2243
			'password' => $password,
2244
			'accepted' => &$accepted,
2245
			'message' => &$message
2246
		]);
2247
2248
		if (!$accepted) {
2249
			throw new \Exception($message);
2250
		}
2251
	}
2252
}
2253