Completed
Push — master ( e56759...9aced2 )
by Morris
14:52
created

Share::isFileReachable()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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