Completed
Pull Request — master (#26608)
by
unknown
13:19
created

Share::createSelectStatement()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Bernhard Reiter <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Christopher Schäpers <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Daniel Hansson <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Michael Kuhn <[email protected]>
14
 * @author Morris Jobke <[email protected]>
15
 * @author Robin Appelman <[email protected]>
16
 * @author Robin McCorkell <[email protected]>
17
 * @author Roeland Jago Douma <[email protected]>
18
 * @author Sebastian Döll <[email protected]>
19
 * @author Stefan Weil <[email protected]>
20
 * @author Thomas Müller <[email protected]>
21
 * @author Torben Dannhauer <[email protected]>
22
 * @author Vincent Petry <[email protected]>
23
 * @author Volkan Gezer <[email protected]>
24
 *
25
 * @copyright Copyright (c) 2018, ownCloud GmbH
26
 * @license AGPL-3.0
27
 *
28
 * This code is free software: you can redistribute it and/or modify
29
 * it under the terms of the GNU Affero General Public License, version 3,
30
 * as published by the Free Software Foundation.
31
 *
32
 * This program is distributed in the hope that it will be useful,
33
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
 * GNU Affero General Public License for more details.
36
 *
37
 * You should have received a copy of the GNU Affero General Public License, version 3,
38
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
39
 *
40
 */
41
42
namespace OC\Share;
43
44
use OC\Files\Filesystem;
45
use OC\Group\Group;
46
use OCA\FederatedFileSharing\DiscoveryManager;
47
use OCP\DB\QueryBuilder\IQueryBuilder;
48
use OCP\IUser;
49
use OCP\IUserSession;
50
use OCP\IDBConnection;
51
use OCP\IConfig;
52
use Symfony\Component\EventDispatcher\GenericEvent;
53
54
/**
55
 * This class provides the ability for apps to share their content between users.
56
 * Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
57
 *
58
 * It provides the following hooks:
59
 *  - post_shared
60
 */
61
class Share extends Constants {
62
63
	/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
64
	 * Construct permissions for share() and setPermissions with Or (|) e.g.
65
	 * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
66
	 *
67
	 * Check if permission is granted with And (&) e.g. Check if delete is
68
	 * granted: if ($permissions & PERMISSION_DELETE)
69
	 *
70
	 * Remove permissions with And (&) and Not (~) e.g. Remove the update
71
	 * permission: $permissions &= ~PERMISSION_UPDATE
72
	 *
73
	 * Apps are required to handle permissions on their own, this class only
74
	 * stores and manages the permissions of shares
75
	 * @see lib/public/constants.php
76
	 */
77
78
	/**
79
	 * Check if the Share API is enabled
80
	 * @return boolean true if enabled or false
81
	 *
82
	 * The Share API is enabled by default if not configured
83
	 */
84
	public static function isEnabled() {
85
		if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_enabled', 'yes') == 'yes') {
86
			return true;
87
		}
88
		return false;
89
	}
90
91
	/**
92
	 * Find which users can access a shared item
93
	 * @param string $path to the file
94
	 * @param string $ownerUser owner of the file
95
	 * @param boolean $includeOwner include owner to the list of users with access to the file
96
	 * @param boolean $returnUserPaths Return an array with the user => path map
97
	 * @param boolean $recursive take all parent folders into account (default true)
98
	 * @return array
99
	 * @note $path needs to be relative to user data dir, e.g. 'file.txt'
100
	 *       not '/admin/data/file.txt'
101
	 */
102
	public static function getUsersSharingFile($path, $ownerUser, $includeOwner = false, $returnUserPaths = false, $recursive = true) {
103
		// FIXME: make ths use IShareProvider::getSharesByPath and extract users
104
		$userManager = \OC::$server->getUserManager();
105
		$userObject = $userManager->get($ownerUser);
106
107 View Code Duplication
		if ($userObject === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
108
			$msg = "Backends provided no user object for $ownerUser";
109
			\OC::$server->getLogger()->error($msg, ['app' => __CLASS__]);
110
			throw new \OC\User\NoUserException($msg);
111
		}
112
113
		$ownerUser = $userObject->getUID();
114
115
		Filesystem::initMountPoints($ownerUser);
116
		$shares = $sharePaths = $fileTargets = [];
117
		$publicShare = false;
118
		$remoteShare = false;
119
		$source = -1;
120
		$cache = $mountPath = false;
121
122
		$view = new \OC\Files\View('/' . $ownerUser . '/files');
123
		$meta = $view->getFileInfo($path);
124
		if ($meta) {
125
			$path = \substr($meta->getPath(), \strlen('/' . $ownerUser . '/files'));
126
		} else {
127
			// if the file doesn't exists yet we start with the parent folder
128
			$meta = $view->getFileInfo(\dirname($path));
129
		}
130
131
		if ($meta !== false) {
132
			$source = $meta['fileid'];
133
			$cache = new \OC\Files\Cache\Cache($meta['storage']);
134
135
			$mountPath = $meta->getMountPoint()->getMountPoint();
136
			if ($mountPath !== false) {
137
				$mountPath = \substr($mountPath, \strlen('/' . $ownerUser . '/files'));
138
			}
139
		}
140
141
		$paths = [];
142
		while ($source !== -1) {
143
			// Fetch all shares with another user
144
			if (!$returnUserPaths) {
145
				$query = \OC_DB::prepare(
146
					'SELECT `share_with`, `file_source`, `file_target`
147
					FROM
148
					`*PREFIX*share`
149
					WHERE
150
					`item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')'
151
				);
152
				$result = $query->execute([$source, self::SHARE_TYPE_USER]);
153
			} else {
154
				$query = \OC_DB::prepare(
155
					'SELECT `share_with`, `file_source`, `file_target`
156
				FROM
157
				`*PREFIX*share`
158
				WHERE
159
				`item_source` = ? AND `share_type` IN (?, ?) AND `item_type` IN (\'file\', \'folder\')'
160
				);
161
				$result = $query->execute([$source, self::SHARE_TYPE_USER, self::$shareTypeGroupUserUnique]);
162
			}
163
164
			if (\OCP\DB::isError($result)) {
165
				\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
166
			} else {
167
				while ($row = $result->fetchRow()) {
168
					$shares[] = $row['share_with'];
169
					if ($returnUserPaths) {
170
						$fileTargets[(int) $row['file_source']][$row['share_with']] = $row;
171
					}
172
				}
173
			}
174
175
			// We also need to take group shares into account
176
			$query = \OC_DB::prepare(
177
				'SELECT `share_with`, `file_source`, `file_target`
178
				FROM
179
				`*PREFIX*share`
180
				WHERE
181
				`item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')'
182
			);
183
184
			$result = $query->execute([$source, self::SHARE_TYPE_GROUP]);
185
186
			if (\OCP\DB::isError($result)) {
187
				\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
188
			} else {
189
				while ($row = $result->fetchRow()) {
190
					$usersInGroup = self::usersInGroup($row['share_with']);
191
					$shares = \array_merge($shares, $usersInGroup);
192
					if ($returnUserPaths) {
193
						foreach ($usersInGroup as $user) {
194
							if (!isset($fileTargets[(int) $row['file_source']][$user])) {
195
								// When the user already has an entry for this file source
196
								// the file is either shared directly with him as well, or
197
								// he has an exception entry (because of naming conflict).
198
								$fileTargets[(int) $row['file_source']][$user] = $row;
199
							}
200
						}
201
					}
202
				}
203
			}
204
205
			//check for public link shares
206 View Code Duplication
			if (!$publicShare) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
207
				$query = \OC_DB::prepare('
208
					SELECT `share_with`
209
					FROM `*PREFIX*share`
210
					WHERE `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')', 1
211
				);
212
213
				$result = $query->execute([$source, self::SHARE_TYPE_LINK]);
214
215
				if (\OCP\DB::isError($result)) {
216
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
217
				} else {
218
					if ($result->fetchRow()) {
219
						$publicShare = true;
220
					}
221
				}
222
			}
223
224
			//check for remote share
225 View Code Duplication
			if (!$remoteShare) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226
				$query = \OC_DB::prepare('
227
					SELECT `share_with`
228
					FROM `*PREFIX*share`
229
					WHERE `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')', 1
230
				);
231
232
				$result = $query->execute([$source, self::SHARE_TYPE_REMOTE]);
233
234
				if (\OCP\DB::isError($result)) {
235
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
236
				} else {
237
					if ($result->fetchRow()) {
238
						$remoteShare = true;
239
					}
240
				}
241
			}
242
243
			// let's get the parent for the next round
244
			$meta = $cache->get((int)$source);
245
			if ($recursive === true && $meta !== false) {
246
				$paths[$source] = $meta['path'];
247
				$source = (int)$meta['parent'];
248
			} else {
249
				$source = -1;
250
			}
251
		}
252
253
		// Include owner in list of users, if requested
254
		if ($includeOwner) {
255
			$shares[] = $ownerUser;
256
		}
257
258
		if ($returnUserPaths) {
259
			$fileTargetIDs = \array_keys($fileTargets);
260
			$fileTargetIDs = \array_unique($fileTargetIDs);
261
262
			if (!empty($fileTargetIDs)) {
263
				$query = \OC_DB::prepare(
264
					'SELECT `fileid`, `path`
265
					FROM `*PREFIX*filecache`
266
					WHERE `fileid` IN (' . \implode(',', $fileTargetIDs) . ')'
267
				);
268
				$result = $query->execute();
269
270
				if (\OCP\DB::isError($result)) {
271
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
272
				} else {
273
					while ($row = $result->fetchRow()) {
274
						foreach ($fileTargets[$row['fileid']] as $uid => $shareData) {
275
							if ($mountPath !== false) {
276
								$sharedPath = $shareData['file_target'];
277
								$sharedPath .= \substr($path, \strlen($mountPath) + \strlen($paths[$row['fileid']]));
278
								$sharePaths[$uid] = $sharedPath;
279
							} else {
280
								$sharedPath = $shareData['file_target'];
281
								$sharedPath .= \substr($path, \strlen($row['path']) -5);
282
								$sharePaths[$uid] = $sharedPath;
283
							}
284
						}
285
					}
286
				}
287
			}
288
289
			if ($includeOwner) {
290
				$sharePaths[$ownerUser] = $path;
291
			} else {
292
				unset($sharePaths[$ownerUser]);
293
			}
294
295
			return $sharePaths;
296
		}
297
298
		return ['users' => \array_unique($shares), 'public' => $publicShare, 'remote' => $remoteShare];
299
	}
300
301
	/**
302
	 * Get the items of item type shared with the current user
303
	 * @param string $itemType
304
	 * @param int $format (optional) Format type must be defined by the backend
305
	 * @param mixed $parameters (optional)
306
	 * @param int $limit Number of items to return (optional) Returns all by default
307
	 * @param boolean $includeCollections (optional)
308
	 * @return mixed Return depends on format
309
	 */
310
	public static function getItemsSharedWith($itemType, $format = self::FORMAT_NONE,
311
											  $parameters = null, $limit = -1, $includeCollections = false) {
312
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format,
0 ignored issues
show
Bug introduced by
It seems like \OC_User::getUser() targeting OC_User::getUser() can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
313
			$parameters, $limit, $includeCollections);
314
	}
315
316
	/**
317
	 * Get the items of item type shared with a user
318
	 * @param string $itemType
319
	 * @param string $user id for which user we want the shares
320
	 * @param int $format (optional) Format type must be defined by the backend
321
	 * @param mixed $parameters (optional)
322
	 * @param int $limit Number of items to return (optional) Returns all by default
323
	 * @param boolean $includeCollections (optional)
324
	 * @return mixed Return depends on format
325
	 */
326
	public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE,
327
												  $parameters = null, $limit = -1, $includeCollections = false) {
328
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format,
329
			$parameters, $limit, $includeCollections);
330
	}
331
332
	/**
333
	 * Get the item of item type shared with the current user
334
	 * @param string $itemType
335
	 * @param string $itemTarget
336
	 * @param int $format (optional) Format type must be defined by the backend
337
	 * @param mixed $parameters (optional)
338
	 * @param boolean $includeCollections (optional)
339
	 * @return mixed Return depends on format
340
	 */
341
	public static function getItemSharedWith($itemType, $itemTarget, $format = self::FORMAT_NONE,
342
											 $parameters = null, $includeCollections = false) {
343
		return self::getItems($itemType, $itemTarget, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format,
0 ignored issues
show
Bug introduced by
It seems like \OC_User::getUser() targeting OC_User::getUser() can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
344
			$parameters, 1, $includeCollections);
345
	}
346
347
	/**
348
	 * Get the item of item type shared with a given user by source
349
	 * @param string $itemType
350
	 * @param string $itemSource
351
	 * @param string $user User to whom the item was shared
352
	 * @param string $owner Owner of the share
353
	 * @param int $shareType only look for a specific share type
354
	 * @return array Return list of items with item_target, permissions and expiration
355
	 */
356
	public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
357 View Code Duplication
		if ($itemType === 'file' || $itemType === 'folder') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
358
			throw new \InvalidArgumentException('Item type "' . $itemType . '" not supported by old share API any more');
359
		}
360
		$shares = [];
361
362
		$where = 'WHERE';
363
		$column = 'item_source';
364
365
		$select = self::createSelectStatement(self::FORMAT_NONE);
366
367
		$where .= ' `' . $column . '` = ? AND `item_type` = ? ';
368
		$arguments = [$itemSource, $itemType];
369
		// for link shares $user === null
370
		if ($user !== null) {
371
			$where .= ' AND `share_with` = ? ';
372
			$arguments[] = $user;
373
		}
374
375
		if ($shareType !== null) {
376
			$where .= ' AND `share_type` = ? ';
377
			$arguments[] = $shareType;
378
		}
379
380
		if ($owner !== null) {
381
			$where .= ' AND `uid_owner` = ? ';
382
			$arguments[] = $owner;
383
		}
384
385
		$query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where);
386
387
		$result = \OC_DB::executeAudited($query, $arguments);
388
389
		while ($row = $result->fetchRow()) {
390
			$shares[] = $row;
391
		}
392
393
		//if didn't found a result than let's look for a group share.
394
		if (empty($shares) && $user !== null) {
395
			$groups = self::getGroupsForUser($user);
396
397
			if (!empty($groups)) {
398
				$where = ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
399
				$arguments = [$itemSource, $itemType, $groups];
400
				$types = [null, null, IQueryBuilder::PARAM_STR_ARRAY];
401
402
				if ($owner !== null) {
403
					$where .= ' AND `uid_owner` = ?';
404
					$arguments[] = $owner;
405
					$types[] = null;
406
				}
407
408
				// TODO: inject connection, hopefully one day in the future when this
409
				// class isn't static anymore...
410
				$conn = \OC::$server->getDatabaseConnection();
411
				$result = $conn->executeQuery(
412
					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
413
					$arguments,
0 ignored issues
show
Documentation introduced by
$arguments is of type array<integer,string|arr...bject<OC\Group\Group>>>, 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...
414
					$types
415
				);
416
417
				while ($row = $result->fetch()) {
418
					$shares[] = $row;
419
				}
420
			}
421
		}
422
423
		return $shares;
424
	}
425
426
	/**
427
	 * Get the item of item type shared by a link
428
	 * @param string $itemType
429
	 * @param string $itemSource
430
	 * @param string $uidOwner Owner of link
431
	 * @return array
432
	 */
433
	public static function getItemSharedWithByLink($itemType, $itemSource, $uidOwner) {
434
		return self::getItems($itemType, $itemSource, self::SHARE_TYPE_LINK, null, $uidOwner, self::FORMAT_NONE,
435
			null, 1);
436
	}
437
438
	/**
439
	 * Based on the given token the share information will be returned - password protected shares will be verified
440
	 * @param string $token
441
	 * @param bool $checkPasswordProtection
442
	 * @return array|boolean false will be returned in case the token is unknown or unauthorized
443
	 */
444
	public static function getShareByToken($token, $checkPasswordProtection = true) {
445
		$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `token` = ?', 1);
446
		$result = $query->execute([$token]);
447 View Code Duplication
		if ($result === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
448
			\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', token=' . $token, \OCP\Util::ERROR);
449
		}
450
		$row = $result->fetchRow();
451
		if ($row === false) {
452
			return false;
453
		}
454
		if (\is_array($row) and self::expireItem($row)) {
455
			return false;
456
		}
457
458
		// password protected shares need to be authenticated
459
		if ($checkPasswordProtection && !\OCP\Share::checkPasswordProtectedShare($row)) {
460
			return false;
461
		}
462
463
		return $row;
464
	}
465
466
	/**
467
	 * resolves reshares down to the last real share
468
	 * @param array $linkItem
469
	 * @return array item owner
470
	 */
471
	public static function resolveReShare($linkItem) {
472
		if (isset($linkItem['parent'])) {
473
			$parent = $linkItem['parent'];
474
			while (isset($parent)) {
475
				$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `id` = ?', 1);
476
				$item = $query->execute([$parent])->fetchRow();
477
				if (isset($item['parent'])) {
478
					$parent = $item['parent'];
479
				} else {
480
					return $item;
481
				}
482
			}
483
		}
484
		return $linkItem;
485
	}
486
487
	/**
488
	 * Get the shared item of item type owned by the current user
489
	 * @param string $itemType
490
	 * @param string $itemSource
491
	 * @param int $format (optional) Format type must be defined by the backend
492
	 * @param mixed $parameters
493
	 * @param boolean $includeCollections
494
	 * @return mixed Return depends on format
495
	 */
496
	public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
497
										 $parameters = null, $includeCollections = false) {
498
		return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format,
0 ignored issues
show
Bug introduced by
It seems like \OC_User::getUser() targeting OC_User::getUser() can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
499
			$parameters, -1, $includeCollections);
500
	}
501
502
	/**
503
	 * Get all users an item is shared with
504
	 * @param string $itemType
505
	 * @param string $itemSource
506
	 * @param string $uidOwner
507
	 * @param boolean $includeCollections
508
	 * @param boolean $checkExpireDate
509
	 * @return array Return array of users
510
	 */
511
	public static function getUsersItemShared($itemType, $itemSource, $uidOwner, $includeCollections = false, $checkExpireDate = true) {
512
		$users = [];
513
		$items = self::getItems($itemType, $itemSource, null, null, $uidOwner, self::FORMAT_NONE, null, -1, $includeCollections, false, $checkExpireDate);
514
		if ($items) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $items 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...
515
			foreach ($items as $item) {
516
				if ((int)$item['share_type'] === self::SHARE_TYPE_USER) {
517
					$users[] = $item['share_with'];
518
				} elseif ((int)$item['share_type'] === self::SHARE_TYPE_GROUP) {
519
					$users = \array_merge($users, self::usersInGroup($item['share_with']));
520
				}
521
			}
522
		}
523
		return $users;
524
	}
525
526
	/**
527
	 * sent status if users got informed by mail about share
528
	 * @param string $itemType
529
	 * @param string $itemSource
530
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
531
	 * @param string $recipient with whom was the file shared
532
	 * @param boolean $status
533
	 */
534
	public static function setSendMailStatus($itemType, $itemSource, $shareType, $recipient, $status) {
535
		$status = $status ? 1 : 0;
536
537
		$query = \OC_DB::prepare(
538
			'UPDATE `*PREFIX*share`
539
					SET `mail_send` = ?
540
					WHERE `item_type` = ? AND `item_source` = ? AND `share_type` = ? AND `share_with` = ?');
541
542
		$result = $query->execute([$status, $itemType, $itemSource, $shareType, $recipient]);
543
544
		if ($result === false) {
545
			\OCP\Util::writeLog('OCP\Share', 'Couldn\'t set send mail status', \OCP\Util::ERROR);
546
		}
547
	}
548
549
	/**
550
	 * Set the permissions of an item for a specific user or group
551
	 * @param string $itemType
552
	 * @param string $itemSource
553
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
554
	 * @param string $shareWith User or group the item is being shared with
555
	 * @param int $permissions CRUDS permissions
556
	 * @return boolean true on success or false on failure
557
	 * @throws \Exception when trying to grant more permissions then the user has himself
558
	 */
559
	public static function setPermissions($itemType, $itemSource, $shareType, $shareWith, $permissions) {
560
		$l = \OC::$server->getL10N('lib');
561
		$connection = \OC::$server->getDatabaseConnection();
562
563
		$intArrayToLiteralArray = function ($intArray, $eb) {
564
			return \array_map(function ($int) use ($eb) {
565
				return $eb->literal((int)$int, 'integer');
566
			}, $intArray);
567
		};
568
		$sanitizeItem = function ($item) {
569
			$item['id'] = (int)$item['id'];
570
			$item['premissions'] = (int)$item['permissions'];
571
			return $item;
572
		};
573
574
		if ($rootItem = self::getItems($itemType, $itemSource, $shareType, $shareWith,
575
			\OC_User::getUser(), self::FORMAT_NONE, null, 1, false)) {
0 ignored issues
show
Bug introduced by
It seems like \OC_User::getUser() targeting OC_User::getUser() can also be of type boolean; however, OC\Share\Share::getItems() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
576
			// Check if this item is a reshare and verify that the permissions
577
			// granted don't exceed the parent shared item
578
			if (isset($rootItem['parent'])) {
579
				$qb = $connection->getQueryBuilder();
580
				$qb->select('permissions')
581
					->from('share')
582
					->where($qb->expr()->eq('id', $qb->createParameter('id')))
583
					->setParameter(':id', $rootItem['parent']);
584
				$dbresult = $qb->execute();
585
586
				$result = $dbresult->fetch();
587
				$dbresult->closeCursor();
588
				if (~(int)$result['permissions'] & $permissions) {
589
					$message = 'Setting permissions for %s failed,'
590
						.' because the permissions exceed permissions granted to %s';
591
					$message_t = $l->t('Setting permissions for %s failed, because the permissions exceed permissions granted to %s', [$itemSource, \OC_User::getUser()]);
592
					\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSource, \OC_User::getUser()), \OCP\Util::DEBUG);
593
					throw new \Exception($message_t);
594
				}
595
			}
596
			$qb = $connection->getQueryBuilder();
597
			$qb->update('share')
598
				->set('permissions', $qb->createParameter('permissions'))
599
				->where($qb->expr()->eq('id', $qb->createParameter('id')))
600
				->setParameter(':id', $rootItem['id'])
601
				->setParameter(':permissions', $permissions);
602
			$qb->execute();
603
604
			// Share id's to update with the new permissions
605
			$ids = [];
606
			$items = [];
607
608
			// Check if permissions were removed
609
			if ((int)$rootItem['permissions'] & ~$permissions) {
610
				// If share permission is removed all reshares must be deleted
611
				if (($rootItem['permissions'] & \OCP\Constants::PERMISSION_SHARE) && (~$permissions & \OCP\Constants::PERMISSION_SHARE)) {
612
					// delete all shares, keep parent and group children
613
					Helper::delete($rootItem['id'], true, null, null, true);
614
				}
615
616
				// Remove permission from all children
617
				$parents = [$rootItem['id']];
618
				while (!empty($parents)) {
619
					$parents = $intArrayToLiteralArray($parents, $qb->expr());
620
					$qb = $connection->getQueryBuilder();
621
					$qb->select('id', 'permissions', 'item_type')
0 ignored issues
show
Unused Code introduced by
The call to IQueryBuilder::select() has too many arguments starting with 'permissions'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
622
						->from('share')
623
						->where($qb->expr()->in('parent', $parents));
624
					$result = $qb->execute();
625
					// Reset parents array, only go through loop again if
626
					// items are found that need permissions removed
627
					$parents = [];
628
					while ($item = $result->fetch()) {
629
						$item = $sanitizeItem($item);
630
631
						$items[] = $item;
632
						// Check if permissions need to be removed
633
						if ($item['permissions'] & ~$permissions) {
634
							// Add to list of items that need permissions removed
635
							$ids[] = $item['id'];
636
							$parents[] = $item['id'];
637
						}
638
					}
639
					$result->closeCursor();
640
				}
641
642
				// Remove the permissions for all reshares of this item
643
				if (!empty($ids)) {
644
					$ids = "'".\implode("','", $ids)."'";
645
					// TODO this should be done with Doctrine platform objects
646
					if (\OC::$server->getConfig()->getSystemValue("dbtype") === 'oci') {
647
						$andOp = 'BITAND(`permissions`, ?)';
648
					} else {
649
						$andOp = '`permissions` & ?';
650
					}
651
					$query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = '.$andOp
652
						.' WHERE `id` IN ('.$ids.')');
653
					$query->execute([$permissions]);
654
				}
655
			}
656
657
			/*
658
			 * Permissions were added
659
			 * Update all USERGROUP shares. (So group shares where the user moved their mountpoint).
660
			 */
661
			if ($permissions & ~(int)$rootItem['permissions']) {
662
				$qb = $connection->getQueryBuilder();
663
				$qb->select('id', 'permissions', 'item_type')
0 ignored issues
show
Unused Code introduced by
The call to IQueryBuilder::select() has too many arguments starting with 'permissions'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
664
					->from('share')
665
					->where($qb->expr()->eq('parent', $qb->createParameter('parent')))
666
					->andWhere($qb->expr()->eq('share_type', $qb->createParameter('share_type')))
667
					->andWhere($qb->expr()->neq('permissions', $qb->createParameter('shareDeleted')))
668
					->setParameter(':parent', (int)$rootItem['id'])
669
					->setParameter(':share_type', 2)
670
					->setParameter(':shareDeleted', 0);
671
				$result = $qb->execute();
672
673
				$ids = [];
674
				while ($item = $result->fetch()) {
675
					$item = $sanitizeItem($item);
676
					$items[] = $item;
677
					$ids[] = $item['id'];
678
				}
679
				$result->closeCursor();
680
681
				// Add permssions for all USERGROUP shares of this item
682
				if (!empty($ids)) {
683
					$ids = $intArrayToLiteralArray($ids, $qb->expr());
684
685
					$qb = $connection->getQueryBuilder();
686
					$qb->update('share')
687
						->set('permissions', $qb->createParameter('permissions'))
688
						->where($qb->expr()->in('id', $ids))
689
						->setParameter(':permissions', $permissions);
690
					$qb->execute();
691
				}
692
			}
693
694
			foreach ($items as $item) {
695
				\OC_Hook::emit('OCP\Share', 'post_update_permissions', ['share' => $item]);
696
			}
697
698
			return true;
699
		}
700
		$message = 'Setting permissions for %s failed, because the item was not found';
701
		$message_t = $l->t('Setting permissions for %s failed, because the item was not found', [$itemSource]);
702
703
		\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemSource), \OCP\Util::DEBUG);
704
		throw new \Exception($message_t);
705
	}
706
707
	/**
708
	 * validate expiration date if it meets all constraints
709
	 *
710
	 * @param string $expireDate well formatted date string, e.g. "DD-MM-YYYY"
711
	 * @param string $shareTime timestamp when the file was shared
712
	 * @param string $itemType
713
	 * @param string $itemSource
714
	 * @return \DateTime validated date
715
	 * @throws \Exception when the expire date is in the past or further in the future then the enforced date
716
	 */
717
	private static function validateExpireDate($expireDate, $shareTime, $itemType, $itemSource) {
718
		$l = \OC::$server->getL10N('lib');
719
		$date = new \DateTime($expireDate);
720
		$today = new \DateTime('now');
721
722
		// if the user doesn't provide a share time we need to get it from the database
723
		// fall-back mode to keep API stable, because the $shareTime parameter was added later
724
		$defaultExpireDateEnforced = \OCP\Util::isDefaultExpireDateEnforced();
725
		if ($defaultExpireDateEnforced && $shareTime === null) {
726
			$items = self::getItemShared($itemType, $itemSource);
727
			$firstItem = \reset($items);
728
			$shareTime = (int)$firstItem['stime'];
729
		}
730
731
		if ($defaultExpireDateEnforced) {
732
			// initialize max date with share time
733
			$maxDate = new \DateTime();
734
			$maxDate->setTimestamp($shareTime);
735
			$maxDays = \OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
736
			$maxDate->add(new \DateInterval('P' . $maxDays . 'D'));
737
			if ($date > $maxDate) {
738
				$warning = 'Cannot set expiration date. Shares cannot expire later than ' . $maxDays . ' after they have been shared';
739
				$warning_t = $l->t('Cannot set expiration date. Shares cannot expire later than %s after they have been shared', [$maxDays]);
740
				\OCP\Util::writeLog('OCP\Share', $warning, \OCP\Util::WARN);
741
				throw new \Exception($warning_t);
742
			}
743
		}
744
745 View Code Duplication
		if ($date < $today) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
746
			$message = 'Cannot set expiration date. Expiration date is in the past';
747
			$message_t = $l->t('Cannot set expiration date. Expiration date is in the past');
748
			\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::WARN);
749
			throw new \Exception($message_t);
750
		}
751
752
		return $date;
753
	}
754
755
	/**
756
	 * Set expiration date for a share
757
	 * @param string $itemType
758
	 * @param string $itemSource
759
	 * @param string $date expiration date
760
	 * @param int $shareTime timestamp from when the file was shared
761
	 * @return boolean
762
	 * @throws \Exception when the expire date is not set, in the past or further in the future then the enforced date
763
	 */
764
	public static function setExpirationDate($itemType, $itemSource, $date, $shareTime = null) {
765
		$user = \OC_User::getUser();
766
		$l = \OC::$server->getL10N('lib');
767
768
		if ($date == '') {
769
			if (\OCP\Util::isDefaultExpireDateEnforced()) {
770
				$warning = 'Cannot clear expiration date. Shares are required to have an expiration date.';
771
				$warning_t = $l->t('Cannot clear expiration date. Shares are required to have an expiration date.');
772
				\OCP\Util::writeLog('OCP\Share', $warning, \OCP\Util::WARN);
773
				throw new \Exception($warning_t);
774
			} else {
775
				$date = null;
776
			}
777
		} else {
778
			$date = self::validateExpireDate($date, $shareTime, $itemType, $itemSource);
779
		}
780
		$query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `expiration` = ? WHERE `item_type` = ? AND `item_source` = ?  AND `uid_owner` = ? AND `share_type` = ?');
781
		$query->bindValue(1, $date, 'datetime');
782
		$query->bindValue(2, $itemType);
783
		$query->bindValue(3, $itemSource);
784
		$query->bindValue(4, $user);
785
		$query->bindValue(5, \OCP\Share::SHARE_TYPE_LINK);
786
787
		$query->execute();
788
789
		\OC_Hook::emit('OCP\Share', 'post_set_expiration_date', [
790
			'itemType' => $itemType,
791
			'itemSource' => $itemSource,
792
			'date' => $date,
793
			'uidOwner' => $user
794
		]);
795
796
		return true;
797
	}
798
799
	/**
800
	 * Retrieve the owner of a connection
801
	 *
802
	 * @param IDBConnection $connection
803
	 * @param int $shareId
804
	 * @throws \Exception
805
	 * @return string uid of share owner
806
	 */
807
	private static function getShareOwner(IDBConnection $connection, $shareId) {
808
		$qb = $connection->getQueryBuilder();
809
810
		$qb->select('uid_owner')
811
			->from('share')
812
			->where($qb->expr()->eq('id', $qb->createParameter('shareId')))
813
			->setParameter(':shareId', $shareId);
814
		$result = $qb->execute();
815
		$result = $result->fetch();
816
817
		if (empty($result)) {
818
			throw new \Exception('Share not found');
819
		}
820
821
		return $result['uid_owner'];
822
	}
823
824
	/**
825
	 * Set password for a public link share
826
	 *
827
	 * @param IUserSession $userSession
828
	 * @param IDBConnection $connection
829
	 * @param IConfig $config
830
	 * @param int $shareId
831
	 * @param string $password
832
	 * @throws \Exception
833
	 * @return boolean
834
	 */
835
	public static function setPassword(IUserSession $userSession,
836
									   IDBConnection $connection,
837
									   IConfig $config,
838
									   $shareId, $password) {
839
		$user = $userSession->getUser();
840
		if ($user === null) {
841
			throw new \Exception("User not logged in");
842
		}
843
844
		$uid = self::getShareOwner($connection, $shareId);
845
846
		if ($uid !== $user->getUID()) {
847
			throw new \Exception('Cannot update share of a different user');
848
		}
849
850
		if ($password === '') {
851
			$password = null;
852
		}
853
854
		//If passwords are enforced the password can't be null
855
		if (self::enforcePassword($config) && $password === null) {
856
			throw new \Exception('Cannot remove password');
857
		}
858
859
		self::verifyPassword($password);
860
861
		$qb = $connection->getQueryBuilder();
862
		$qb->update('share')
863
			->set('share_with', $qb->createParameter('pass'))
864
			->where($qb->expr()->eq('id', $qb->createParameter('shareId')))
865
			->setParameter(':pass', $password === null ? null : \OC::$server->getHasher()->hash($password))
866
			->setParameter(':shareId', $shareId);
867
868
		$qb->execute();
869
870
		return true;
871
	}
872
873
	/**
874
	 * Checks whether a share has expired, calls unshareItem() if yes.
875
	 * @param array $item Share data (usually database row)
876
	 * @return boolean True if item was expired, false otherwise.
877
	 */
878
	protected static function expireItem(array $item) {
879
		$result = false;
880
881
		// only use default expiration date for link shares
882
		if ((int) $item['share_type'] === self::SHARE_TYPE_LINK) {
883
884
			// calculate expiration date
885
			if (!empty($item['expiration'])) {
886
				$userDefinedExpire = new \DateTime($item['expiration']);
887
				$expires = $userDefinedExpire->getTimestamp();
888
			} else {
889
				$expires = null;
890
			}
891
892
			// get default expiration settings
893
			$defaultSettings = Helper::getDefaultExpireSetting();
894
			$expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
895
896
			if (\is_int($expires)) {
897
				$now = \time();
898
				if ($now > $expires) {
899
					self::unshareItem($item);
900
					$result = true;
901
				}
902
			}
903
		}
904
		return $result;
905
	}
906
907
	/**
908
	 * Unshares a share given a share data array
909
	 * @param array $item Share data (usually database row)
910
	 * @param int $newParent parent ID
911
	 * @return null
912
	 */
913
	protected static function unshareItem(array $item, $newParent = null) {
914
		$shareType = (int)$item['share_type'];
915
		$shareWith = null;
916
		if ($shareType !== \OCP\Share::SHARE_TYPE_LINK) {
917
			$shareWith = $item['share_with'];
918
		}
919
920
		// Pass all the vars we have for now, they may be useful
921
		$hookParams = [
922
			'id'            => $item['id'],
923
			'itemType'      => $item['item_type'],
924
			'itemSource'    => $item['item_source'],
925
			'shareType'     => $shareType,
926
			'shareWith'     => $shareWith,
927
			'itemParent'    => $item['parent'],
928
			'uidOwner'      => $item['uid_owner'],
929
		];
930
931
		\OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
932
		$deletedShares = Helper::delete($item['id'], false, null, $newParent);
933
		$deletedShares[] = $hookParams;
934
		$hookParams['deletedShares'] = $deletedShares;
935
		\OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
936
		if ((int)$item['share_type'] === \OCP\Share::SHARE_TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
937
			list(, $remote) = Helper::splitUserRemote($item['share_with']);
938
			self::sendRemoteUnshare($remote, $item['id'], $item['token']);
939
		}
940
	}
941
942
	/**
943
	 * Get the backend class for the specified item type
944
	 * @param string $itemType
945
	 * @throws \Exception
946
	 * @return \OCP\Share_Backend
947
	 */
948
	public static function getBackend($itemType) {
949
		$l = \OC::$server->getL10N('lib');
950
		if (isset(self::$backends[$itemType])) {
951
			return self::$backends[$itemType];
952
		} elseif (isset(self::$backendTypes[$itemType]['class'])) {
953
			$class = self::$backendTypes[$itemType]['class'];
954
			if (\class_exists($class)) {
955
				self::$backends[$itemType] = new $class;
956
				if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
957
					$message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
958
					$message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', [$class]);
959
					\OCP\Util::writeLog('OCP\Share', \sprintf($message, $class), \OCP\Util::ERROR);
960
					throw new \Exception($message_t);
961
				}
962
				return self::$backends[$itemType];
963 View Code Duplication
			} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
964
				$message = 'Sharing backend %s not found';
965
				$message_t = $l->t('Sharing backend %s not found', [$class]);
966
				\OCP\Util::writeLog('OCP\Share', \sprintf($message, $class), \OCP\Util::ERROR);
967
				throw new \Exception($message_t);
968
			}
969
		}
970
		$message = 'Sharing backend for %s not found';
971
		$message_t = $l->t('Sharing backend for %s not found', [$itemType]);
972
		\OCP\Util::writeLog('OCP\Share', \sprintf($message, $itemType), \OCP\Util::ERROR);
973
		throw new \Exception($message_t);
974
	}
975
976
	/**
977
	 * Check if resharing is allowed
978
	 * @return boolean true if allowed or false
979
	 *
980
	 * Resharing is allowed by default if not configured
981
	 */
982
	public static function isResharingAllowed() {
983
		if (!isset(self::$isResharingAllowed)) {
984
			if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
985
				self::$isResharingAllowed = true;
986
			} else {
987
				self::$isResharingAllowed = false;
988
			}
989
		}
990
		return self::$isResharingAllowed;
991
	}
992
993
	/**
994
	 * Get a list of collection item types for the specified item type
995
	 * @param string $itemType
996
	 * @return array
997
	 */
998
	private static function getCollectionItemTypes($itemType) {
999
		$collectionTypes = [$itemType];
1000
		foreach (self::$backendTypes as $type => $backend) {
1001
			if (\in_array($backend['collectionOf'], $collectionTypes)) {
1002
				$collectionTypes[] = $type;
1003
			}
1004
		}
1005
		// TODO Add option for collections to be collection of themselves...
1006
		if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection)) {
1007
			unset($collectionTypes[0]);
1008
		}
1009
		// Return array if collections were found or the item type is a
1010
		// collection itself - collections can be inside collections
1011
		if (\count($collectionTypes) > 0) {
1012
			return $collectionTypes;
1013
		}
1014
		return false;
1015
	}
1016
1017
	/**
1018
	 * Get the owners of items shared with a user.
1019
	 *
1020
	 * @param string $user The user the items are shared with.
1021
	 * @param string $type The type of the items shared with the user.
1022
	 * @param boolean $includeCollections Include collection item types (optional)
1023
	 * @param boolean $includeOwner include owner in the list of users the item is shared with (optional)
1024
	 * @return array
1025
	 */
1026
	public static function getSharedItemsOwners($user, $type, $includeCollections = false, $includeOwner = false) {
1027
		// First, we find out if $type is part of a collection (and if that collection is part of
1028
		// another one and so on).
1029
		$collectionTypes = [];
1030
		if (!$includeCollections || !$collectionTypes = self::getCollectionItemTypes($type)) {
1031
			$collectionTypes[] = $type;
1032
		}
1033
1034
		// Of these collection types, along with our original $type, we make a
1035
		// list of the ones for which a sharing backend has been registered.
1036
		// FIXME: Ideally, we wouldn't need to nest getItemsSharedWith in this loop but just call it
1037
		// with its $includeCollections parameter set to true. Unfortunately, this fails currently.
1038
		$allMaybeSharedItems = [];
1039
		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...
1040
			if (isset(self::$backends[$collectionType])) {
1041
				$allMaybeSharedItems[$collectionType] = self::getItemsSharedWithUser(
1042
					$collectionType,
1043
					$user,
1044
					self::FORMAT_NONE
1045
				);
1046
			}
1047
		}
1048
1049
		$owners = [];
1050
		if ($includeOwner) {
1051
			$owners[] = $user;
1052
		}
1053
1054
		// We take a look at all shared items of the given $type (or of the collections it is part of)
1055
		// and find out their owners. Then, we gather the tags for the original $type from all owners,
1056
		// and return them as elements of a list that look like "Tag (owner)".
1057
		foreach ($allMaybeSharedItems as $collectionType => $maybeSharedItems) {
1058
			foreach ($maybeSharedItems as $sharedItem) {
1059
				if (isset($sharedItem['id'])) { //workaround for https://github.com/owncloud/core/issues/2814
1060
					$owners[] = $sharedItem['uid_owner'];
1061
				}
1062
			}
1063
		}
1064
1065
		return $owners;
1066
	}
1067
1068
	/**
1069
	 * Get shared items from the database
1070
	 * @param string $itemType
1071
	 * @param string $item Item source or target (optional)
1072
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
1073
	 * @param string $shareWith User or group the item is being shared with
1074
	 * @param string $uidOwner User that is the owner of shared items (optional)
1075
	 * @param int $format Format to convert items to with formatItems() (optional)
1076
	 * @param mixed $parameters to pass to formatItems() (optional)
1077
	 * @param int $limit Number of items to return, -1 to return all matches (optional)
1078
	 * @param boolean $includeCollections Include collection item types (optional)
1079
	 * @param boolean $itemShareWithBySource (optional)
1080
	 * @param boolean $checkExpireDate
1081
	 * @return array
1082
	 *
1083
	 * See public functions getItem(s)... for parameter usage
1084
	 *
1085
	 */
1086
	public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
1087
									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
1088
									$includeCollections = false, $itemShareWithBySource = false, $checkExpireDate  = true) {
1089
		if (!self::isEnabled()) {
1090
			return [];
1091
		}
1092 View Code Duplication
		if ($itemType === 'file' || $itemType === 'folder') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1093
			throw new \InvalidArgumentException('Item type "' . $itemType . '" not supported by old share API any more');
1094
		}
1095
		$backend = self::getBackend($itemType);
1096
		$collectionTypes = false;
1097
		// Get filesystem root to add it to the file target and remove from the
1098
		// file source, match file_source with the file cache
1099
		$root = '';
1100
		$collectionTypes = self::getCollectionItemTypes($itemType);
1101
		if ($includeCollections && !isset($item) && $collectionTypes) {
1102
			// If includeCollections is true, find collections of this item type, e.g. a music album contains songs
1103
			if (!in_array($itemType, $collectionTypes)) {
1104
				$itemTypes = array_merge([$itemType], $collectionTypes);
1105
			} else {
1106
				$itemTypes = $collectionTypes;
1107
			}
1108
			$placeholders = join(',', array_fill(0, count($itemTypes), '?'));
1109
			$where = ' WHERE `item_type` IN ('.$placeholders.'))';
1110
			$queryArgs = $itemTypes;
1111
		} else {
1112
			$where = ' WHERE `item_type` = ?';
1113
			$queryArgs = [$itemType];
1114
		}
1115
		if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1116
			$where .= ' AND `share_type` != ?';
1117
			$queryArgs[] = self::SHARE_TYPE_LINK;
1118
		}
1119
		if (isset($shareType)) {
1120
			// Include all user and group items
1121
			if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
1122
				$where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
1123
				$queryArgs[] = self::SHARE_TYPE_USER;
1124
				$queryArgs[] = self::$shareTypeGroupUserUnique;
1125
				$queryArgs[] = $shareWith;
1126
				$groups = self::getGroupsForUser($shareWith);
1127 View Code Duplication
				if (!empty($groups)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1128
					$placeholders = \join(',', \array_fill(0, \count($groups), '?'));
1129
					$where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
1130
					$queryArgs[] = self::SHARE_TYPE_GROUP;
1131
					$queryArgs = \array_merge($queryArgs, $groups);
1132
				}
1133
				$where .= ')';
1134
				// Don't include own group shares
1135
				$where .= ' AND `uid_owner` != ?';
1136
				$queryArgs[] = $shareWith;
1137
			} else {
1138
				$where .= ' AND `share_type` = ?';
1139
				$queryArgs[] = $shareType;
1140
				if (isset($shareWith)) {
1141
					$where .= ' AND `share_with` = ?';
1142
					$queryArgs[] = $shareWith;
1143
				}
1144
			}
1145
		}
1146
		if (isset($uidOwner)) {
1147
			$where .= ' AND `uid_owner` = ?';
1148
			$queryArgs[] = $uidOwner;
1149
			if (!isset($shareType)) {
1150
				// Prevent unique user targets for group shares from being selected
1151
				$where .= ' AND `share_type` != ?';
1152
				$queryArgs[] = self::$shareTypeGroupUserUnique;
1153
			}
1154
			$column = 'item_source';
1155
		} else {
1156
			$column = 'item_target';
1157
		}
1158
		if (isset($item)) {
1159
			$collectionTypes = self::getCollectionItemTypes($itemType);
1160
			if ($includeCollections && $collectionTypes) {
1161
				$where .= ' AND (';
1162
			} else {
1163
				$where .= ' AND';
1164
			}
1165
			// If looking for own shared items, check item_source else check item_target
1166
			if (isset($uidOwner) || $itemShareWithBySource) {
1167
				// If item type is a file, file source needs to be checked in case the item was converted
1168
				$where .= ' `item_source` = ?';
1169
				$column = 'item_source';
1170
			} else {
1171
				$where .= ' `item_target` = ?';
1172
			}
1173
			$queryArgs[] = $item;
1174 View Code Duplication
			if ($includeCollections && $collectionTypes) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1175
				$placeholders = join(',', array_fill(0, count($collectionTypes), '?'));
1176
				$where .= ' OR `item_type` IN ('.$placeholders.'))';
1177
				$queryArgs = \array_merge($queryArgs, $collectionTypes);
1178
			}
1179
		}
1180
1181
		if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) {
1182
			// Make sure the unique user target is returned if it exists,
1183
			// unique targets should follow the group share in the database
1184
			// If the limit is not 1, the filtering can be done later
1185
			$where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
1186
		} else {
1187
			$where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
1188
		}
1189
1190
		if ($limit != -1 && !$includeCollections) {
1191
			// The limit must be at least 3, because filtering needs to be done
1192
			if ($limit < 3) {
1193
				$queryLimit = 3;
1194
			} else {
1195
				$queryLimit = $limit;
1196
			}
1197
		} else {
1198
			$queryLimit = null;
1199
		}
1200
		$select = self::createSelectStatement($format, $uidOwner);
1201
		$root = strlen($root);
1202
		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
1203
		$result = $query->execute($queryArgs);
1204 View Code Duplication
		if ($result === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1205
			\OCP\Util::writeLog('OCP\Share',
1206
				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
1207
				\OCP\Util::ERROR);
1208
		}
1209
		$items = [];
1210
		$targets = [];
1211
		$switchedItems = [];
1212
		$mounts = [];
1213
		while ($row = $result->fetchRow()) {
1214
			self::transformDBResults($row);
1215
			// Filter out duplicate group shares for users with unique targets
1216
			if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
1217
				$row['share_type'] = self::SHARE_TYPE_GROUP;
1218
				$row['unique_name'] = true; // remember that we use a unique name for this user
1219
				$row['share_with'] = $items[$row['parent']]['share_with'];
1220
				// if the group share was unshared from the user we keep the permission, otherwise
1221
				// we take the permission from the parent because this is always the up-to-date
1222
				// permission for the group share
1223
				if ($row['permissions'] > 0) {
1224
					$row['permissions'] = $items[$row['parent']]['permissions'];
1225
				}
1226
				// Remove the parent group share
1227
				unset($items[$row['parent']]);
1228
				if ($row['permissions'] == 0) {
1229
					continue;
1230
				}
1231
			} elseif (!isset($uidOwner)) {
1232
				// Check if the same target already exists
1233
				if (isset($targets[$row['id']])) {
1234
					// Check if the same owner shared with the user twice
1235
					// through a group and user share - this is allowed
1236
					$id = $targets[$row['id']];
1237
					if (isset($items[$id]) && $row['uid_owner'] == $items[$id]['uid_owner']) {
1238
						// Switch to group share type to ensure resharing conditions aren't bypassed
1239
						if ($items[$id]['share_type'] != self::SHARE_TYPE_GROUP) {
1240
							$items[$id]['share_type'] = self::SHARE_TYPE_GROUP;
1241
							$items[$id]['share_with'] = $row['share_with'];
1242
						}
1243
						// Switch ids if sharing permission is granted on only
1244
						// one share to ensure correct parent is used if resharing
1245
						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
1246
							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
1247
							$items[$row['id']] = $items[$id];
1248
							$switchedItems[$id] = $row['id'];
1249
							unset($items[$id]);
1250
							$id = $row['id'];
1251
						}
1252
						$items[$id]['permissions'] |= (int)$row['permissions'];
1253
					}
1254
					continue;
1255
				} elseif (!empty($row['parent'])) {
1256
					$targets[$row['parent']] = $row['id'];
1257
				}
1258
			}
1259
			// Remove root from file source paths if retrieving own shared items
1260
			if (isset($uidOwner, $row['path'])) {
1261
				if (isset($row['parent'])) {
1262
					$query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?');
1263
					$parentResult = $query->execute([$row['parent']]);
1264
					if ($result === false) {
1265
						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
1266
							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
1267
							\OCP\Util::ERROR);
1268
					} else {
1269
						$parentRow = $parentResult->fetchRow();
1270
						$tmpPath = $parentRow['file_target'];
1271
						// find the right position where the row path continues from the target path
1272
						$pos = \strrpos($row['path'], $parentRow['file_target']);
1273
						$subPath = \substr($row['path'], $pos);
1274
						$splitPath = \explode('/', $subPath);
1275
						foreach (\array_slice($splitPath, 2) as $pathPart) {
1276
							$tmpPath = $tmpPath . '/' . $pathPart;
1277
						}
1278
						$row['path'] = $tmpPath;
1279
					}
1280
				} else {
1281
					if (!isset($mounts[$row['storage']])) {
1282
						$mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
1283
						if (\is_array($mountPoints) && !empty($mountPoints)) {
1284
							$mounts[$row['storage']] = \current($mountPoints);
1285
						}
1286
					}
1287
					if (!empty($mounts[$row['storage']])) {
1288
						$path = $mounts[$row['storage']]->getMountPoint().$row['path'];
1289
						$relPath = \substr($path, $root); // path relative to data/user
1290
						$row['path'] = \rtrim($relPath, '/');
1291
					}
1292
				}
1293
			}
1294
1295
			if ($checkExpireDate) {
1296
				if (self::expireItem($row)) {
1297
					continue;
1298
				}
1299
			}
1300
			// Check if resharing is allowed, if not remove share permission
1301
			if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
1302
				$row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
1303
			}
1304
			// Add display names to result
1305
			$row['share_with_displayname'] = $row['share_with'];
1306
			if (isset($row['share_with']) && $row['share_with'] != '' &&
1307
				$row['share_type'] === self::SHARE_TYPE_USER) {
1308
				$row['share_with_displayname'] = \OCP\User::getDisplayName($row['share_with']);
1309
			} elseif (isset($row['share_with']) && $row['share_with'] != '' &&
1310
				$row['share_type'] === self::SHARE_TYPE_REMOTE) {
1311
				$addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
1312
				foreach ($addressBookEntries as $entry) {
1313
					foreach ($entry['CLOUD'] as $cloudID) {
1314
						if ($cloudID === $row['share_with']) {
1315
							$row['share_with_displayname'] = $entry['FN'];
1316
						}
1317
					}
1318
				}
1319
			}
1320
			if (isset($row['uid_owner']) && $row['uid_owner'] != '') {
1321
				$row['displayname_owner'] = \OCP\User::getDisplayName($row['uid_owner']);
1322
			}
1323
1324
			if ($row['permissions'] > 0) {
1325
				$items[$row['id']] = $row;
1326
			}
1327
		}
1328
1329
		// group items if we are looking for items shared with the current user
1330
		if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
1331
			$items = self::groupItems($items, $itemType);
1332
		}
1333
1334
		if (!empty($items)) {
1335
			$collectionItems = [];
1336
			foreach ($items as &$row) {
1337
				// Return only the item instead of a 2-dimensional array
1338
				if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType)) {
1339
					if ($format == self::FORMAT_NONE) {
1340
						return $row;
1341
					} else {
1342
						break;
1343
					}
1344
				}
1345
				// Check if this is a collection of the requested item type
1346
				if ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
1347
					if (($collectionBackend = self::getBackend($row['item_type']))
1348
						&& $collectionBackend instanceof \OCP\Share_Backend_Collection) {
1349
						// Collections can be inside collections, check if the item is a collection
1350
						if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
1351
							$collectionItems[] = $row;
1352
						} else {
1353
							$collection = [];
1354
							$collection['item_type'] = $row['item_type'];
1355
							$row['collection'] = $collection;
1356
							// Fetch all of the children sources
1357
							$children = $collectionBackend->getChildren($row[$column]);
0 ignored issues
show
Documentation introduced by
$row[$column] is of type array<string,?,{"item_type":"?"}>, 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...
1358
							foreach ($children as $child) {
1359
								$childItem = $row;
1360
								$childItem['item_type'] = $itemType;
1361
								$childItem['item_source'] = $child['source'];
1362
								$childItem['item_target'] = $child['target'];
1363
								if (isset($item)) {
1364
									if ($childItem[$column] == $item) {
1365
										// Return only the item instead of a 2-dimensional array
1366
										if ($limit == 1) {
1367
											if ($format == self::FORMAT_NONE) {
1368
												return $childItem;
1369
											} else {
1370
												// Unset the items array and break out of both loops
1371
												$items = [];
1372
												$items[] = $childItem;
1373
												break 2;
1374
											}
1375
										} else {
1376
											$collectionItems[] = $childItem;
1377
										}
1378
									}
1379
								} else {
1380
									$collectionItems[] = $childItem;
1381
								}
1382
							}
1383
						}
1384
					}
1385
					// Remove collection item
1386
					$toRemove = $row['id'];
1387
					if (\array_key_exists($toRemove, $switchedItems)) {
1388
						$toRemove = $switchedItems[$toRemove];
1389
					}
1390
					unset($items[$toRemove]);
1391
				} elseif ($includeCollections && $collectionTypes && \in_array($row['item_type'], $collectionTypes)) {
1392
					// FIXME: Thats a dirty hack to improve file sharing performance,
1393
					// see github issue #10588 for more details
1394
					// Need to find a solution which works for all back-ends
1395
					$collectionBackend = self::getBackend($row['item_type']);
1396
					$sharedParents = $collectionBackend->getParents($row['item_source']);
1397
					foreach ($sharedParents as $parent) {
1398
						$collectionItems[] = $parent;
1399
					}
1400
				}
1401
			}
1402
			if (!empty($collectionItems)) {
1403
				$collectionItems = \array_unique($collectionItems, SORT_REGULAR);
1404
				$items = \array_merge($items, $collectionItems);
1405
			}
1406
1407
			// filter out invalid items, these can appear when subshare entries exist
1408
			// for a group in which the requested user isn't a member any more
1409
			$items = \array_filter($items, function ($item) {
1410
				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
1411
			});
1412
1413
			return self::formatResult($items, $column, $backend, $format, $parameters);
1414
		}
1415
1416
		return [];
1417
	}
1418
1419
	/**
1420
	 * group items with link to the same source
1421
	 *
1422
	 * @param array $items
1423
	 * @param string $itemType
1424
	 * @return array of grouped items
1425
	 */
1426
	protected static function groupItems($items, $itemType) {
1427 View Code Duplication
		if ($itemType === 'file' || $itemType === 'folder') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1428
			throw new \InvalidArgumentException('Item type "' . $itemType . '" not supported by old share API any more');
1429
		}
1430
		$fileSharing = false;
1431
1432
		$result = [];
1433
1434
		foreach ($items as $item) {
1435
			$grouped = false;
1436
			foreach ($result as $key => $r) {
1437
				// for file/folder shares we need to compare file_source, otherwise we compare item_source
1438
				// only group shares if they already point to the same target, otherwise the file where shared
1439
				// before grouping of shares was added. In this case we don't group them toi avoid confusions
1440
				if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
1441
					(!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
1442
					// add the first item to the list of grouped shares
1443
					if (!isset($result[$key]['grouped'])) {
1444
						$result[$key]['grouped'][] = $result[$key];
1445
					}
1446
					$result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
1447
					$result[$key]['grouped'][] = $item;
1448
					$grouped = true;
1449
					break;
1450
				}
1451
			}
1452
1453
			if (!$grouped) {
1454
				$result[] = $item;
1455
			}
1456
		}
1457
1458
		return $result;
1459
	}
1460
1461
	/**
1462
	 * Delete all shares with type SHARE_TYPE_LINK
1463
	 */
1464
	public static function removeAllLinkShares() {
1465
		// Delete any link shares
1466
		$query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*share` WHERE `share_type` = ?');
1467
		$result = $query->execute([self::SHARE_TYPE_LINK]);
1468
		while ($item = $result->fetchRow()) {
1469
			Helper::delete($item['id']);
1470
		}
1471
	}
1472
1473
	/**
1474
	 * In case a password protected link is not yet authenticated this function will return false
1475
	 *
1476
	 * @param array $linkItem
1477
	 * @return boolean
1478
	 */
1479
	public static function checkPasswordProtectedShare(array $linkItem) {
1480
		if (!isset($linkItem['share_with'])) {
1481
			return true;
1482
		}
1483
		if (!isset($linkItem['share_type'])) {
1484
			return true;
1485
		}
1486
		if (!isset($linkItem['id'])) {
1487
			return true;
1488
		}
1489
1490
		if ($linkItem['share_type'] != \OCP\Share::SHARE_TYPE_LINK) {
1491
			return true;
1492
		}
1493
1494 View Code Duplication
		if (\OC::$server->getSession()->exists('public_link_authenticated')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1495
			&& \OC::$server->getSession()->get('public_link_authenticated') === (string)$linkItem['id']) {
1496
			return true;
1497
		}
1498
1499
		return false;
1500
	}
1501
1502
	/**
1503
	 * construct select statement
1504
	 * @param int $format
1505
	 * @param string $uidOwner
1506
	 * @return string select statement
1507
	 */
1508
	private static function createSelectStatement($format, $uidOwner = null) {
1509
		$select = '*';
1510
		if ($format == self::FORMAT_STATUSES) {
1511
			$select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
1512
		} else {
1513
			if (isset($uidOwner)) {
1514
				$select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
1515
					. ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
1516
			}
1517
		}
1518
		return $select;
1519
	}
1520
1521
	/**
1522
	 * transform db results
1523
	 * @param array $row result
1524
	 */
1525
	private static function transformDBResults(&$row) {
1526
		if (isset($row['id'])) {
1527
			$row['id'] = (int) $row['id'];
1528
		}
1529
		if (isset($row['share_type'])) {
1530
			$row['share_type'] = (int) $row['share_type'];
1531
		}
1532
		if (isset($row['parent'])) {
1533
			$row['parent'] = (int) $row['parent'];
1534
		}
1535
		if (isset($row['file_parent'])) {
1536
			$row['file_parent'] = (int) $row['file_parent'];
1537
		}
1538
		if (isset($row['file_source'])) {
1539
			$row['file_source'] = (int) $row['file_source'];
1540
		}
1541
		if (isset($row['permissions'])) {
1542
			$row['permissions'] = (int) $row['permissions'];
1543
		}
1544
		if (isset($row['storage'])) {
1545
			$row['storage'] = (int) $row['storage'];
1546
		}
1547
		if (isset($row['stime'])) {
1548
			$row['stime'] = (int) $row['stime'];
1549
		}
1550
		if (isset($row['expiration']) && $row['share_type'] !== self::SHARE_TYPE_LINK) {
1551
			// discard expiration date for non-link shares, which might have been
1552
			// set by ancient bugs
1553
			$row['expiration'] = null;
1554
		}
1555
	}
1556
1557
	/**
1558
	 * format result
1559
	 * @param array $items result
1560
	 * @param string $column is it a file share or a general share ('file_target' or 'item_target')
1561
	 * @param \OCP\Share_Backend $backend sharing backend
1562
	 * @param int $format
1563
	 * @param array $parameters additional format parameters
1564
	 * @return array format result
1565
	 */
1566
	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE, $parameters = null) {
1567
		if ($format === self::FORMAT_NONE) {
1568
			return $items;
1569
		} elseif ($format === self::FORMAT_STATUSES) {
1570
			$statuses = [];
1571
			foreach ($items as $item) {
1572
				if ($item['share_type'] === self::SHARE_TYPE_LINK) {
1573
					if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
1574
						continue;
1575
					}
1576
					$statuses[$item[$column]]['link'] = true;
1577
				} elseif (!isset($statuses[$item[$column]])) {
1578
					$statuses[$item[$column]]['link'] = false;
1579
				}
1580
				if (!empty($item['file_target'])) {
1581
					$statuses[$item[$column]]['path'] = $item['path'];
1582
				}
1583
			}
1584
			return $statuses;
1585
		} else {
1586
			return $backend->formatItems($items, $format, $parameters);
1587
		}
1588
	}
1589
1590
	/**
1591
	 * remove protocol from URL
1592
	 *
1593
	 * @param string $url
1594
	 * @return string
1595
	 */
1596 View Code Duplication
	public static function removeProtocolFromUrl($url) {
1597
		if (\strpos($url, 'https://') === 0) {
1598
			return \substr($url, \strlen('https://'));
1599
		} elseif (\strpos($url, 'http://') === 0) {
1600
			return \substr($url, \strlen('http://'));
1601
		}
1602
1603
		return $url;
1604
	}
1605
1606
	/**
1607
	 * try http post first with https and then with http as a fallback
1608
	 *
1609
	 * @param string $remoteDomain
1610
	 * @param string $urlSuffix
1611
	 * @param array $fields post parameters
1612
	 * @return array
1613
	 */
1614
	private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) {
1615
		$allowHttpFallback = \OC::$server->getConfig()->getSystemValue('sharing.federation.allowHttpFallback', false) === true;
1616
		// Always try https first
1617
		$protocol = 'https://';
1618
		$discoveryManager = new DiscoveryManager(
1619
			\OC::$server->getMemCacheFactory(),
1620
			\OC::$server->getHTTPClientService()
1621
		);
1622
1623
		$endpoint = $discoveryManager->getShareEndpoint($protocol . $remoteDomain);
1624
		// Try HTTPS
1625
		$result = \OC::$server->getHTTPHelper()->post(
1626
			$protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT,
1627
			$fields);
1628
1629
		if ($result['success'] === true) {
1630
			// Return if https worked
1631
			return $result;
1632
		} elseif ($result['success'] === false && $allowHttpFallback) {
1633
			// If https failed and we can try http - try that
1634
			$protocol = 'http://';
1635
			$result = \OC::$server->getHTTPHelper()->post(
1636
			$protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT,
1637
			$fields);
1638
			return $result;
1639
		} else {
1640
			// Else we just return the failure
1641
			return $result;
1642
		}
1643
	}
1644
1645
	/**
1646
	 * send server-to-server share to remote server
1647
	 *
1648
	 * @param string $token
1649
	 * @param string $shareWith
1650
	 * @param string $name
1651
	 * @param int $remote_id
1652
	 * @param string $owner
1653
	 * @return bool
1654
	 */
1655
	private static function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
1656
		list($user, $remote) = Helper::splitUserRemote($shareWith);
1657
1658
		if ($user && $remote) {
1659
			$url = $remote;
1660
1661
			$local = \OC::$server->getURLGenerator()->getAbsoluteURL('/');
1662
1663
			$fields = [
1664
				'shareWith' => $user,
1665
				'token' => $token,
1666
				'name' => $name,
1667
				'remoteId' => $remote_id,
1668
				'owner' => $owner,
1669
				'remote' => $local,
1670
			];
1671
1672
			$url = self::removeProtocolFromUrl($url);
1673
			$result = self::tryHttpPostToShareEndpoint($url, '', $fields);
1674
			$status = \json_decode($result['result'], true);
1675
1676
			if ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200)) {
1677
				\OC_Hook::emit('OCP\Share', 'federated_share_added', ['server' => $remote]);
1678
				return true;
1679
			}
1680
		}
1681
1682
		return false;
1683
	}
1684
1685
	/**
1686
	 * send server-to-server unshare to remote server
1687
	 *
1688
	 * @param string $remote url
1689
	 * @param int $id share id
1690
	 * @param string $token
1691
	 * @return bool
1692
	 */
1693
	private static function sendRemoteUnshare($remote, $id, $token) {
1694
		$url = \rtrim($remote, '/');
1695
		$fields = ['token' => $token, 'format' => 'json'];
1696
		$url = self::removeProtocolFromUrl($url);
1697
		$result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields);
1698
		$status = \json_decode($result['result'], true);
1699
1700
		return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200));
1701
	}
1702
1703
	/**
1704
	 * @param IConfig $config
1705
	 * @return bool
1706
	 */
1707
	public static function enforcePassword(IConfig $config) {
1708
		$enforcePassword = $config->getAppValue('core', 'shareapi_enforce_links_password', 'no');
1709
		return ($enforcePassword === "yes") ? true : false;
1710
	}
1711
1712
	/**
1713
	 * Get all share entries, including non-unique group items
1714
	 *
1715
	 * @param string $owner
1716
	 * @return array
1717
	 */
1718
	public static function getAllSharesForOwner($owner) {
1719
		$query = 'SELECT * FROM `*PREFIX*share` WHERE `uid_owner` = ?';
1720
		$result = \OC::$server->getDatabaseConnection()->executeQuery($query, [$owner]);
1721
		return $result->fetchAll();
1722
	}
1723
1724
	/**
1725
	 * Get all share entries, including non-unique group items for a file
1726
	 *
1727
	 * @param int $id
1728
	 * @return array
1729
	 */
1730
	public static function getAllSharesForFileId($id) {
1731
		$query = 'SELECT * FROM `*PREFIX*share` WHERE `file_source` = ?';
1732
		$result = \OC::$server->getDatabaseConnection()->executeQuery($query, [$id]);
1733
		return $result->fetchAll();
1734
	}
1735
1736
	/**
1737
	 * @param string $password
1738
	 * @throws \Exception
1739
	 */
1740
	private static function verifyPassword($password) {
1741
		$accepted = true;
1742
		$message = '';
1743
		\OCP\Util::emitHook('\OC\Share', 'verifyPassword', [
1744
			'password' => $password,
1745
			'accepted' => &$accepted,
1746
			'message' => &$message
1747
		]);
1748
1749
		if (!$accepted) {
1750
			throw new \Exception($message);
1751
		}
1752
1753
		\OC::$server->getEventDispatcher()->dispatch(
1754
			'OCP\Share::validatePassword',
1755
			new GenericEvent(null, ['password' => $password])
1756
		);
1757
	}
1758
1759
	/**
1760
	 * @param $user
1761
	 * @return Group[]
1762
	 */
1763
	private static function getGroupsForUser($user) {
1764
		$groups = \OC::$server->getGroupManager()->getUserIdGroups($user, 'sharing');
1765
		return \array_values(\array_map(function (Group $g) {
1766
			return $g->getGID();
1767
		}, $groups));
1768
	}
1769
1770
	/**
1771
	 * @param $group
1772
	 * @return mixed
1773
	 */
1774
	private static function usersInGroup($group) {
1775
		$g = \OC::$server->getGroupManager()->get($group);
1776
		if ($g === null) {
1777
			return [];
1778
		}
1779
		return \array_values(\array_map(function (IUser $u) {
1780
			return $u->getUID();
1781
		}, $g->getUsers()));
1782
	}
1783
}
1784