Passed
Push — master ( c5c251...4e8552 )
by Morris
17:39 queued 06:15
created

Share::getSharedItemsOwners()   B

Complexity

Conditions 9
Paths 48

Size

Total Lines 40
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 18
nc 48
nop 4
dl 0
loc 40
rs 8.0555
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Bernhard Reiter <[email protected]>
8
 * @author Bjoern Schiessle <[email protected]>
9
 * @author Björn Schießle <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Joas Schilling <[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 Thomas Müller <[email protected]>
19
 * @author Vincent Petry <[email protected]>
20
 * @author Volkan Gezer <[email protected]>
21
 *
22
 * @license AGPL-3.0
23
 *
24
 * This code is free software: you can redistribute it and/or modify
25
 * it under the terms of the GNU Affero General Public License, version 3,
26
 * as published by the Free Software Foundation.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31
 * GNU Affero General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Affero General Public License, version 3,
34
 * along with this program. If not, see <http://www.gnu.org/licenses/>
35
 *
36
 */
37
38
namespace OC\Share;
39
40
use OCP\DB\QueryBuilder\IQueryBuilder;
41
use OCP\ILogger;
42
use OCP\Share\IShare;
43
44
/**
45
 * This class provides the ability for apps to share their content between users.
46
 * Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
47
 *
48
 * It provides the following hooks:
49
 *  - post_shared
50
 */
51
class Share extends Constants {
52
53
	/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
54
	 * Construct permissions for share() and setPermissions with Or (|) e.g.
55
	 * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
56
	 *
57
	 * Check if permission is granted with And (&) e.g. Check if delete is
58
	 * granted: if ($permissions & PERMISSION_DELETE)
59
	 *
60
	 * Remove permissions with And (&) and Not (~) e.g. Remove the update
61
	 * permission: $permissions &= ~PERMISSION_UPDATE
62
	 *
63
	 * Apps are required to handle permissions on their own, this class only
64
	 * stores and manages the permissions of shares
65
	 * @see lib/public/constants.php
66
	 */
67
68
	/**
69
	 * Register a sharing backend class that implements OCP\Share_Backend for an item type
70
	 * @param string $itemType Item type
71
	 * @param string $class Backend class
72
	 * @param string $collectionOf (optional) Depends on item type
73
	 * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
74
	 * @return boolean true if backend is registered or false if error
75
	 */
76
	public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
77
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') == 'yes') {
78
			if (!isset(self::$backendTypes[$itemType])) {
79
				self::$backendTypes[$itemType] = [
80
					'class' => $class,
81
					'collectionOf' => $collectionOf,
82
					'supportedFileExtensions' => $supportedFileExtensions
83
				];
84
				return true;
85
			}
86
			\OCP\Util::writeLog('OCP\Share',
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

86
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share',

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
87
				'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
88
				.' is already registered for '.$itemType,
89
				ILogger::WARN);
90
		}
91
		return false;
92
	}
93
94
	/**
95
	 * Get the items of item type shared with the current user
96
	 * @param string $itemType
97
	 * @param int $format (optional) Format type must be defined by the backend
98
	 * @param mixed $parameters (optional)
99
	 * @param int $limit Number of items to return (optional) Returns all by default
100
	 * @param boolean $includeCollections (optional)
101
	 * @return mixed Return depends on format
102
	 * @deprecated TESTS ONLY - this methods is only used by tests
103
	 * called like this:
104
	 * \OC\Share\Share::getItemsSharedWith('folder'); (apps/files_sharing/tests/UpdaterTest.php)
105
	 */
106
	public static function getItemsSharedWith() {
107
		return self::getItems('folder', null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, self::FORMAT_NONE,
108
			null, -1, false);
109
	}
110
111
	/**
112
	 * Get the items of item type shared with a user
113
	 * @param string $itemType
114
	 * @param string $user id for which user we want the shares
115
	 * @param int $format (optional) Format type must be defined by the backend
116
	 * @param mixed $parameters (optional)
117
	 * @param int $limit Number of items to return (optional) Returns all by default
118
	 * @param boolean $includeCollections (optional)
119
	 * @return mixed Return depends on format
120
	 */
121
	public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE,
122
												  $parameters = null, $limit = -1, $includeCollections = false) {
123
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format,
124
			$parameters, $limit, $includeCollections);
125
	}
126
127
	/**
128
	 * Get the item of item type shared with a given user by source
129
	 * @param string $itemType
130
	 * @param string $itemSource
131
	 * @param string $user User to whom the item was shared
132
	 * @param string $owner Owner of the share
133
	 * @param int $shareType only look for a specific share type
134
	 * @return array Return list of items with file_target, permissions and expiration
135
	 */
136
	public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
137
		$shares = [];
138
		$fileDependent = false;
139
140
		$where = 'WHERE';
141
		$fileDependentWhere = '';
142
		if ($itemType === 'file' || $itemType === 'folder') {
143
			$fileDependent = true;
144
			$column = 'file_source';
145
			$fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
146
			$fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
147
		} else {
148
			$column = 'item_source';
149
		}
150
151
		$select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
152
153
		$where .= ' `' . $column . '` = ? AND `item_type` = ? ';
154
		$arguments = [$itemSource, $itemType];
155
		// for link shares $user === null
156
		if ($user !== null) {
0 ignored issues
show
introduced by
The condition $user !== null is always true.
Loading history...
157
			$where .= ' AND `share_with` = ? ';
158
			$arguments[] = $user;
159
		}
160
161
		if ($shareType !== null) {
162
			$where .= ' AND `share_type` = ? ';
163
			$arguments[] = $shareType;
164
		}
165
166
		if ($owner !== null) {
167
			$where .= ' AND `uid_owner` = ? ';
168
			$arguments[] = $owner;
169
		}
170
171
		$query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
172
173
		$result = \OC_DB::executeAudited($query, $arguments);
174
175
		while ($row = $result->fetchRow()) {
176
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
177
				continue;
178
			}
179
			if ($fileDependent && (int)$row['file_parent'] === -1) {
180
				// if it is a mount point we need to get the path from the mount manager
181
				$mountManager = \OC\Files\Filesystem::getMountManager();
182
				$mountPoint = $mountManager->findByStorageId($row['storage_id']);
183
				if (!empty($mountPoint)) {
184
					$path = $mountPoint[0]->getMountPoint();
185
					$path = trim($path, '/');
186
					$path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
187
					$row['path'] = $path;
188
				} else {
189
					\OC::$server->getLogger()->warning(
190
						'Could not resolve mount point for ' . $row['storage_id'],
191
						['app' => 'OCP\Share']
192
					);
193
				}
194
			}
195
			$shares[] = $row;
196
		}
197
198
		//if didn't found a result than let's look for a group share.
199
		if (empty($shares) && $user !== null) {
200
			$userObject = \OC::$server->getUserManager()->get($user);
201
			$groups = [];
202
			if ($userObject) {
203
				$groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
204
			}
205
206
			if (!empty($groups)) {
207
				$where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
208
				$arguments = [$itemSource, $itemType, $groups];
209
				$types = [null, null, IQueryBuilder::PARAM_STR_ARRAY];
210
211
				if ($owner !== null) {
212
					$where .= ' AND `uid_owner` = ?';
213
					$arguments[] = $owner;
214
					$types[] = null;
215
				}
216
217
				// TODO: inject connection, hopefully one day in the future when this
218
				// class isn't static anymore...
219
				$conn = \OC::$server->getDatabaseConnection();
220
				$result = $conn->executeQuery(
221
					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
222
					$arguments,
223
					$types
224
				);
225
226
				while ($row = $result->fetch()) {
227
					$shares[] = $row;
228
				}
229
			}
230
		}
231
232
		return $shares;
233
	}
234
235
	/**
236
	 * Get the item of item type shared with the current user by source
237
	 * @param string $itemType
238
	 * @param string $itemSource
239
	 * @param int $format (optional) Format type must be defined by the backend
240
	 * @param mixed $parameters
241
	 * @param boolean $includeCollections
242
	 * @param string $shareWith (optional) define against which user should be checked, default: current user
243
	 * @return array
244
	 */
245
	public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE,
246
													 $parameters = null, $includeCollections = false, $shareWith = null) {
247
		$shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith;
248
		return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format,
249
			$parameters, 1, $includeCollections, true);
250
	}
251
252
	/**
253
	 * Get the shared item of item type owned by the current user
254
	 * @param string $itemType
255
	 * @param string $itemSource
256
	 * @param int $format (optional) Format type must be defined by the backend
257
	 * @param mixed $parameters
258
	 * @param boolean $includeCollections
259
	 * @return mixed Return depends on format
260
	 */
261
	public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
262
										 $parameters = null, $includeCollections = false) {
263
		return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format,
264
			$parameters, -1, $includeCollections);
265
	}
266
267
	/**
268
	 * Unshare an item from a user, group, or delete a private link
269
	 * @param string $itemType
270
	 * @param string $itemSource
271
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
272
	 * @param string $shareWith User or group the item is being shared with
273
	 * @param string $owner owner of the share, if null the current user is used
274
	 * @return boolean true on success or false on failure
275
	 */
276
	public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) {
277
278
		// check if it is a valid itemType
279
		self::getBackend($itemType);
280
281
		$items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType);
282
283
		$toDelete = [];
284
		$newParent = null;
285
		$currentUser = $owner ? $owner : \OC_User::getUser();
286
		foreach ($items as $item) {
287
			// delete the item with the expected share_type and owner
288
			if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) {
289
				$toDelete = $item;
290
			// if there is more then one result we don't have to delete the children
291
				// but update their parent. For group shares the new parent should always be
292
				// the original group share and not the db entry with the unique name
293
			} elseif ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
294
				$newParent = $item['parent'];
295
			} else {
296
				$newParent = $item['id'];
297
			}
298
		}
299
300
		if (!empty($toDelete)) {
301
			self::unshareItem($toDelete, $newParent);
302
			return true;
303
		}
304
		return false;
305
	}
306
307
	/**
308
	 * Checks whether a share has expired, calls unshareItem() if yes.
309
	 * @param array $item Share data (usually database row)
310
	 * @return boolean True if item was expired, false otherwise.
311
	 */
312
	protected static function expireItem(array $item) {
313
		$result = false;
314
315
		// only use default expiration date for link shares
316
		if ((int) $item['share_type'] === IShare::TYPE_LINK) {
317
318
			// calculate expiration date
319
			if (!empty($item['expiration'])) {
320
				$userDefinedExpire = new \DateTime($item['expiration']);
321
				$expires = $userDefinedExpire->getTimestamp();
322
			} else {
323
				$expires = null;
324
			}
325
326
327
			// get default expiration settings
328
			$defaultSettings = Helper::getDefaultExpireSetting();
329
			$expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
330
331
332
			if (is_int($expires)) {
333
				$now = time();
334
				if ($now > $expires) {
335
					self::unshareItem($item);
336
					$result = true;
337
				}
338
			}
339
		}
340
		return $result;
341
	}
342
343
	/**
344
	 * Unshares a share given a share data array
345
	 * @param array $item Share data (usually database row)
346
	 * @param int $newParent parent ID
347
	 * @return null
348
	 */
349
	protected static function unshareItem(array $item, $newParent = null) {
350
		$shareType = (int)$item['share_type'];
351
		$shareWith = null;
352
		if ($shareType !== IShare::TYPE_LINK) {
353
			$shareWith = $item['share_with'];
354
		}
355
356
		// Pass all the vars we have for now, they may be useful
357
		$hookParams = [
358
			'id'            => $item['id'],
359
			'itemType'      => $item['item_type'],
360
			'itemSource'    => $item['item_source'],
361
			'shareType'     => $shareType,
362
			'shareWith'     => $shareWith,
363
			'itemParent'    => $item['parent'],
364
			'uidOwner'      => $item['uid_owner'],
365
		];
366
		if ($item['item_type'] === 'file' || $item['item_type'] === 'folder') {
367
			$hookParams['fileSource'] = $item['file_source'];
368
			$hookParams['fileTarget'] = $item['file_target'];
369
		}
370
371
		\OC_Hook::emit(\OCP\Share::class, 'pre_unshare', $hookParams);
372
		$deletedShares = Helper::delete($item['id'], false, null, $newParent);
373
		$deletedShares[] = $hookParams;
374
		$hookParams['deletedShares'] = $deletedShares;
375
		\OC_Hook::emit(\OCP\Share::class, 'post_unshare', $hookParams);
376
		if ((int)$item['share_type'] === IShare::TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
377
			list(, $remote) = Helper::splitUserRemote($item['share_with']);
378
			self::sendRemoteUnshare($remote, $item['id'], $item['token']);
379
		}
380
	}
381
382
	/**
383
	 * Get the backend class for the specified item type
384
	 * @param string $itemType
385
	 * @throws \Exception
386
	 * @return \OCP\Share_Backend
387
	 */
388
	public static function getBackend($itemType) {
389
		$l = \OC::$server->getL10N('lib');
390
		if (isset(self::$backends[$itemType])) {
391
			return self::$backends[$itemType];
392
		} elseif (isset(self::$backendTypes[$itemType]['class'])) {
393
			$class = self::$backendTypes[$itemType]['class'];
394
			if (class_exists($class)) {
395
				self::$backends[$itemType] = new $class;
396
				if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
397
					$message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
398
					$message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', [$class]);
399
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

399
					/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
400
					throw new \Exception($message_t);
401
				}
402
				return self::$backends[$itemType];
403
			} else {
404
				$message = 'Sharing backend %s not found';
405
				$message_t = $l->t('Sharing backend %s not found', [$class]);
406
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

406
				/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
407
				throw new \Exception($message_t);
408
			}
409
		}
410
		$message = 'Sharing backend for %s not found';
411
		$message_t = $l->t('Sharing backend for %s not found', [$itemType]);
412
		\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

412
		/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
413
		throw new \Exception($message_t);
414
	}
415
416
	/**
417
	 * Check if resharing is allowed
418
	 * @return boolean true if allowed or false
419
	 *
420
	 * Resharing is allowed by default if not configured
421
	 */
422
	public static function isResharingAllowed() {
423
		if (!isset(self::$isResharingAllowed)) {
424
			if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
425
				self::$isResharingAllowed = true;
426
			} else {
427
				self::$isResharingAllowed = false;
428
			}
429
		}
430
		return self::$isResharingAllowed;
431
	}
432
433
	/**
434
	 * Get a list of collection item types for the specified item type
435
	 * @param string $itemType
436
	 * @return array
437
	 */
438
	private static function getCollectionItemTypes($itemType) {
439
		$collectionTypes = [$itemType];
440
		foreach (self::$backendTypes as $type => $backend) {
441
			if (in_array($backend['collectionOf'], $collectionTypes)) {
442
				$collectionTypes[] = $type;
443
			}
444
		}
445
		// TODO Add option for collections to be collection of themselves, only 'folder' does it now...
446
		if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
447
			unset($collectionTypes[0]);
448
		}
449
		// Return array if collections were found or the item type is a
450
		// collection itself - collections can be inside collections
451
		if (count($collectionTypes) > 0) {
452
			return $collectionTypes;
453
		}
454
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
455
	}
456
457
	/**
458
	 * Get shared items from the database
459
	 * @param string $itemType
460
	 * @param string $item Item source or target (optional)
461
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
462
	 * @param string $shareWith User or group the item is being shared with
463
	 * @param string $uidOwner User that is the owner of shared items (optional)
464
	 * @param int $format Format to convert items to with formatItems() (optional)
465
	 * @param mixed $parameters to pass to formatItems() (optional)
466
	 * @param int $limit Number of items to return, -1 to return all matches (optional)
467
	 * @param boolean $includeCollections Include collection item types (optional)
468
	 * @param boolean $itemShareWithBySource (optional)
469
	 * @param boolean $checkExpireDate
470
	 * @return array
471
	 *
472
	 * See public functions getItem(s)... for parameter usage
473
	 *
474
	 */
475
	public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
476
									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
477
									$includeCollections = false, $itemShareWithBySource = false, $checkExpireDate  = true) {
478
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
479
			return [];
480
		}
481
		$backend = self::getBackend($itemType);
482
		$collectionTypes = false;
483
		// Get filesystem root to add it to the file target and remove from the
484
		// file source, match file_source with the file cache
485
		if ($itemType == 'file' || $itemType == 'folder') {
486
			if (!is_null($uidOwner)) {
487
				$root = \OC\Files\Filesystem::getRoot();
488
			} else {
489
				$root = '';
490
			}
491
			$where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
492
			if (!isset($item)) {
493
				$where .= ' AND `file_target` IS NOT NULL ';
494
			}
495
			$where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
496
			$fileDependent = true;
497
			$queryArgs = [];
498
		} else {
499
			$fileDependent = false;
500
			$root = '';
501
			$collectionTypes = self::getCollectionItemTypes($itemType);
502
			if ($includeCollections && !isset($item) && $collectionTypes) {
503
				// If includeCollections is true, find collections of this item type, e.g. a music album contains songs
504
				if (!in_array($itemType, $collectionTypes)) {
505
					$itemTypes = array_merge([$itemType], $collectionTypes);
506
				} else {
507
					$itemTypes = $collectionTypes;
508
				}
509
				$placeholders = implode(',', array_fill(0, count($itemTypes), '?'));
510
				$where = ' WHERE `item_type` IN ('.$placeholders.'))';
511
				$queryArgs = $itemTypes;
512
			} else {
513
				$where = ' WHERE `item_type` = ?';
514
				$queryArgs = [$itemType];
515
			}
516
		}
517
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
518
			$where .= ' AND `share_type` != ?';
519
			$queryArgs[] = IShare::TYPE_LINK;
520
		}
521
		if (isset($shareType)) {
522
			// Include all user and group items
523
			if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
524
				$where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
525
				$queryArgs[] = IShare::TYPE_USER;
526
				$queryArgs[] = self::$shareTypeGroupUserUnique;
527
				$queryArgs[] = $shareWith;
528
529
				$user = \OC::$server->getUserManager()->get($shareWith);
530
				$groups = [];
531
				if ($user) {
532
					$groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
533
				}
534
				if (!empty($groups)) {
535
					$placeholders = implode(',', array_fill(0, count($groups), '?'));
536
					$where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
537
					$queryArgs[] = IShare::TYPE_GROUP;
538
					$queryArgs = array_merge($queryArgs, $groups);
539
				}
540
				$where .= ')';
541
				// Don't include own group shares
542
				$where .= ' AND `uid_owner` != ?';
543
				$queryArgs[] = $shareWith;
544
			} else {
545
				$where .= ' AND `share_type` = ?';
546
				$queryArgs[] = $shareType;
547
				if (isset($shareWith)) {
548
					$where .= ' AND `share_with` = ?';
549
					$queryArgs[] = $shareWith;
550
				}
551
			}
552
		}
553
		if (isset($uidOwner)) {
554
			$where .= ' AND `uid_owner` = ?';
555
			$queryArgs[] = $uidOwner;
556
			if (!isset($shareType)) {
557
				// Prevent unique user targets for group shares from being selected
558
				$where .= ' AND `share_type` != ?';
559
				$queryArgs[] = self::$shareTypeGroupUserUnique;
560
			}
561
			if ($fileDependent) {
562
				$column = 'file_source';
563
			} else {
564
				$column = 'item_source';
565
			}
566
		} else {
567
			if ($fileDependent) {
568
				$column = 'file_target';
569
			} else {
570
				$column = 'item_target';
571
			}
572
		}
573
		if (isset($item)) {
574
			$collectionTypes = self::getCollectionItemTypes($itemType);
575
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
576
				$where .= ' AND (';
577
			} else {
578
				$where .= ' AND';
579
			}
580
			// If looking for own shared items, check item_source else check item_target
581
			if (isset($uidOwner) || $itemShareWithBySource) {
582
				// If item type is a file, file source needs to be checked in case the item was converted
583
				if ($fileDependent) {
584
					$where .= ' `file_source` = ?';
585
					$column = 'file_source';
586
				} else {
587
					$where .= ' `item_source` = ?';
588
					$column = 'item_source';
589
				}
590
			} else {
591
				if ($fileDependent) {
592
					$where .= ' `file_target` = ?';
593
					$item = \OC\Files\Filesystem::normalizePath($item);
594
				} else {
595
					$where .= ' `item_target` = ?';
596
				}
597
			}
598
			$queryArgs[] = $item;
599
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
600
				$placeholders = implode(',', array_fill(0, count($collectionTypes), '?'));
601
				$where .= ' OR `item_type` IN ('.$placeholders.'))';
602
				$queryArgs = array_merge($queryArgs, $collectionTypes);
603
			}
604
		}
605
606
		if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) {
607
			// Make sure the unique user target is returned if it exists,
608
			// unique targets should follow the group share in the database
609
			// If the limit is not 1, the filtering can be done later
610
			$where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
611
		} else {
612
			$where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
613
		}
614
615
		if ($limit != -1 && !$includeCollections) {
616
			// The limit must be at least 3, because filtering needs to be done
617
			if ($limit < 3) {
618
				$queryLimit = 3;
619
			} else {
620
				$queryLimit = $limit;
621
			}
622
		} else {
623
			$queryLimit = null;
624
		}
625
		$select = self::createSelectStatement($format, $fileDependent, $uidOwner);
626
		$root = strlen($root);
627
		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
628
		$result = $query->execute($queryArgs);
629
		if ($result === false) {
630
			\OCP\Util::writeLog('OCP\Share',
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

630
			/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share',

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
631
				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
632
				ILogger::ERROR);
633
		}
634
		$items = [];
635
		$targets = [];
636
		$switchedItems = [];
637
		$mounts = [];
638
		while ($row = $result->fetchRow()) {
639
			self::transformDBResults($row);
640
			// Filter out duplicate group shares for users with unique targets
641
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
642
				continue;
643
			}
644
			if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
645
				$row['share_type'] = IShare::TYPE_GROUP;
646
				$row['unique_name'] = true; // remember that we use a unique name for this user
647
				$row['share_with'] = $items[$row['parent']]['share_with'];
648
				// if the group share was unshared from the user we keep the permission, otherwise
649
				// we take the permission from the parent because this is always the up-to-date
650
				// permission for the group share
651
				if ($row['permissions'] > 0) {
652
					$row['permissions'] = $items[$row['parent']]['permissions'];
653
				}
654
				// Remove the parent group share
655
				unset($items[$row['parent']]);
656
				if ($row['permissions'] == 0) {
657
					continue;
658
				}
659
			} elseif (!isset($uidOwner)) {
660
				// Check if the same target already exists
661
				if (isset($targets[$row['id']])) {
662
					// Check if the same owner shared with the user twice
663
					// through a group and user share - this is allowed
664
					$id = $targets[$row['id']];
665
					if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
666
						// Switch to group share type to ensure resharing conditions aren't bypassed
667
						if ($items[$id]['share_type'] != IShare::TYPE_GROUP) {
668
							$items[$id]['share_type'] = IShare::TYPE_GROUP;
669
							$items[$id]['share_with'] = $row['share_with'];
670
						}
671
						// Switch ids if sharing permission is granted on only
672
						// one share to ensure correct parent is used if resharing
673
						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
674
							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
675
							$items[$row['id']] = $items[$id];
676
							$switchedItems[$id] = $row['id'];
677
							unset($items[$id]);
678
							$id = $row['id'];
679
						}
680
						$items[$id]['permissions'] |= (int)$row['permissions'];
681
					}
682
					continue;
683
				} elseif (!empty($row['parent'])) {
684
					$targets[$row['parent']] = $row['id'];
685
				}
686
			}
687
			// Remove root from file source paths if retrieving own shared items
688
			if (isset($uidOwner) && isset($row['path'])) {
689
				if (isset($row['parent'])) {
690
					$query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?');
691
					$parentResult = $query->execute([$row['parent']]);
692
					if ($result === false) {
693
						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::writeLog() has been deprecated: 13.0.0 use log of \OCP\ILogger ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

693
						/** @scrutinizer ignore-deprecated */ \OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
694
							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
695
							ILogger::ERROR);
696
					} else {
697
						$parentRow = $parentResult->fetchRow();
698
						$tmpPath = $parentRow['file_target'];
699
						// find the right position where the row path continues from the target path
700
						$pos = strrpos($row['path'], $parentRow['file_target']);
701
						$subPath = substr($row['path'], $pos);
702
						$splitPath = explode('/', $subPath);
703
						foreach (array_slice($splitPath, 2) as $pathPart) {
704
							$tmpPath = $tmpPath . '/' . $pathPart;
705
						}
706
						$row['path'] = $tmpPath;
707
					}
708
				} else {
709
					if (!isset($mounts[$row['storage']])) {
710
						$mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
711
						if (is_array($mountPoints) && !empty($mountPoints)) {
712
							$mounts[$row['storage']] = current($mountPoints);
713
						}
714
					}
715
					if (!empty($mounts[$row['storage']])) {
716
						$path = $mounts[$row['storage']]->getMountPoint().$row['path'];
717
						$relPath = substr($path, $root); // path relative to data/user
718
						$row['path'] = rtrim($relPath, '/');
719
					}
720
				}
721
			}
722
723
			if ($checkExpireDate) {
724
				if (self::expireItem($row)) {
725
					continue;
726
				}
727
			}
728
			// Check if resharing is allowed, if not remove share permission
729
			if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\Util::isSharingDisabledForUser() has been deprecated: 9.1.0 Use \OC::$server->getShareManager()->sharingDisabledForUser ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

729
			if (isset($row['permissions']) && (!self::isResharingAllowed() | /** @scrutinizer ignore-deprecated */ \OCP\Util::isSharingDisabledForUser())) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
730
				$row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
731
			}
732
			// Add display names to result
733
			$row['share_with_displayname'] = $row['share_with'];
734
			if (isset($row['share_with']) && $row['share_with'] != '' &&
735
				$row['share_type'] === IShare::TYPE_USER) {
736
				$shareWithUser = \OC::$server->getUserManager()->get($row['share_with']);
737
				$row['share_with_displayname'] = $shareWithUser === null ? $row['share_with'] : $shareWithUser->getDisplayName();
738
			} elseif (isset($row['share_with']) && $row['share_with'] != '' &&
739
				$row['share_type'] === IShare::TYPE_REMOTE) {
740
				$addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
741
				foreach ($addressBookEntries as $entry) {
742
					foreach ($entry['CLOUD'] as $cloudID) {
743
						if ($cloudID === $row['share_with']) {
744
							$row['share_with_displayname'] = $entry['FN'];
745
						}
746
					}
747
				}
748
			}
749
			if (isset($row['uid_owner']) && $row['uid_owner'] != '') {
750
				$ownerUser = \OC::$server->getUserManager()->get($row['uid_owner']);
751
				$row['displayname_owner'] = $ownerUser === null ? $row['uid_owner'] : $ownerUser->getDisplayName();
752
			}
753
754
			if ($row['permissions'] > 0) {
755
				$items[$row['id']] = $row;
756
			}
757
		}
758
759
		// group items if we are looking for items shared with the current user
760
		if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
0 ignored issues
show
Deprecated Code introduced by
The function OCP\User::getUser() has been deprecated: 8.0.0 Use \OC::$server->getUserSession()->getUser()->getUID() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

760
		if (isset($shareWith) && $shareWith === /** @scrutinizer ignore-deprecated */ \OCP\User::getUser()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
761
			$items = self::groupItems($items, $itemType);
762
		}
763
764
		if (!empty($items)) {
765
			$collectionItems = [];
766
			foreach ($items as &$row) {
767
				// Return only the item instead of a 2-dimensional array
768
				if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) {
769
					if ($format == self::FORMAT_NONE) {
770
						return $row;
771
					} else {
772
						break;
773
					}
774
				}
775
				// Check if this is a collection of the requested item type
776
				if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
777
					if (($collectionBackend = self::getBackend($row['item_type']))
778
						&& $collectionBackend instanceof \OCP\Share_Backend_Collection) {
779
						// Collections can be inside collections, check if the item is a collection
780
						if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
781
							$collectionItems[] = $row;
782
						} else {
783
							$collection = [];
784
							$collection['item_type'] = $row['item_type'];
785
							if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
786
								$collection['path'] = basename($row['path']);
787
							}
788
							$row['collection'] = $collection;
789
							// Fetch all of the children sources
790
							$children = $collectionBackend->getChildren($row[$column]);
791
							foreach ($children as $child) {
792
								$childItem = $row;
793
								$childItem['item_type'] = $itemType;
794
								if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
795
									$childItem['item_source'] = $child['source'];
796
									$childItem['item_target'] = $child['target'];
797
								}
798
								if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
799
									if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
800
										$childItem['file_source'] = $child['source'];
801
									} else { // TODO is this really needed if we already know that we use the file backend?
802
										$meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
803
										$childItem['file_source'] = $meta['fileid'];
804
									}
805
									$childItem['file_target'] =
806
										\OC\Files\Filesystem::normalizePath($child['file_path']);
807
								}
808
								if (isset($item)) {
809
									if ($childItem[$column] == $item) {
810
										// Return only the item instead of a 2-dimensional array
811
										if ($limit == 1) {
812
											if ($format == self::FORMAT_NONE) {
813
												return $childItem;
814
											} else {
815
												// Unset the items array and break out of both loops
816
												$items = [];
817
												$items[] = $childItem;
818
												break 2;
819
											}
820
										} else {
821
											$collectionItems[] = $childItem;
822
										}
823
									}
824
								} else {
825
									$collectionItems[] = $childItem;
826
								}
827
							}
828
						}
829
					}
830
					// Remove collection item
831
					$toRemove = $row['id'];
832
					if (array_key_exists($toRemove, $switchedItems)) {
833
						$toRemove = $switchedItems[$toRemove];
834
					}
835
					unset($items[$toRemove]);
836
				} elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
837
					// FIXME: Thats a dirty hack to improve file sharing performance,
838
					// see github issue #10588 for more details
839
					// Need to find a solution which works for all back-ends
840
					$collectionBackend = self::getBackend($row['item_type']);
841
					$sharedParents = $collectionBackend->getParents($row['item_source']);
0 ignored issues
show
Bug introduced by
The method getParents() does not exist on OCP\Share_Backend. It seems like you code against a sub-type of OCP\Share_Backend such as OCA\Files_Sharing\ShareBackend\Folder or OCA\Files_Sharing\ShareBackend\Folder. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

841
					/** @scrutinizer ignore-call */ 
842
     $sharedParents = $collectionBackend->getParents($row['item_source']);
Loading history...
842
					foreach ($sharedParents as $parent) {
843
						$collectionItems[] = $parent;
844
					}
845
				}
846
			}
847
			if (!empty($collectionItems)) {
848
				$collectionItems = array_unique($collectionItems, SORT_REGULAR);
849
				$items = array_merge($items, $collectionItems);
850
			}
851
852
			// filter out invalid items, these can appear when subshare entries exist
853
			// for a group in which the requested user isn't a member any more
854
			$items = array_filter($items, function ($item) {
855
				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
856
			});
857
858
			return self::formatResult($items, $column, $backend, $format, $parameters);
859
		} elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
860
			// FIXME: Thats a dirty hack to improve file sharing performance,
861
			// see github issue #10588 for more details
862
			// Need to find a solution which works for all back-ends
863
			$collectionItems = [];
864
			$collectionBackend = self::getBackend('folder');
865
			$sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
866
			foreach ($sharedParents as $parent) {
867
				$collectionItems[] = $parent;
868
			}
869
			if ($limit === 1) {
870
				return reset($collectionItems);
871
			}
872
			return self::formatResult($collectionItems, $column, $backend, $format, $parameters);
873
		}
874
875
		return [];
876
	}
877
878
	/**
879
	 * group items with link to the same source
880
	 *
881
	 * @param array $items
882
	 * @param string $itemType
883
	 * @return array of grouped items
884
	 */
885
	protected static function groupItems($items, $itemType) {
886
		$fileSharing = $itemType === 'file' || $itemType === 'folder';
887
888
		$result = [];
889
890
		foreach ($items as $item) {
891
			$grouped = false;
892
			foreach ($result as $key => $r) {
893
				// for file/folder shares we need to compare file_source, otherwise we compare item_source
894
				// only group shares if they already point to the same target, otherwise the file where shared
895
				// before grouping of shares was added. In this case we don't group them toi avoid confusions
896
				if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
897
					(!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
898
					// add the first item to the list of grouped shares
899
					if (!isset($result[$key]['grouped'])) {
900
						$result[$key]['grouped'][] = $result[$key];
901
					}
902
					$result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
903
					$result[$key]['grouped'][] = $item;
904
					$grouped = true;
905
					break;
906
				}
907
			}
908
909
			if (!$grouped) {
910
				$result[] = $item;
911
			}
912
		}
913
914
		return $result;
915
	}
916
917
	/**
918
	 * construct select statement
919
	 * @param int $format
920
	 * @param boolean $fileDependent ist it a file/folder share or a generla share
921
	 * @param string $uidOwner
922
	 * @return string select statement
923
	 */
924
	private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
925
		$select = '*';
926
		if ($format == self::FORMAT_STATUSES) {
927
			if ($fileDependent) {
928
				$select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
929
					. '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
930
					. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
931
					. '`uid_initiator`';
932
			} else {
933
				$select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
934
			}
935
		} else {
936
			if (isset($uidOwner)) {
937
				if ($fileDependent) {
938
					$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
939
						. ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
940
						. ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
941
						. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
942
				} else {
943
					$select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
944
						. ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
945
				}
946
			} else {
947
				if ($fileDependent) {
948
					if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
949
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
950
							. '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
951
							. '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
952
							. '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
953
					} else {
954
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
955
							. '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
956
							. '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
957
							. '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
958
							. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
959
					}
960
				}
961
			}
962
		}
963
		return $select;
964
	}
965
966
967
	/**
968
	 * transform db results
969
	 * @param array $row result
970
	 */
971
	private static function transformDBResults(&$row) {
972
		if (isset($row['id'])) {
973
			$row['id'] = (int) $row['id'];
974
		}
975
		if (isset($row['share_type'])) {
976
			$row['share_type'] = (int) $row['share_type'];
977
		}
978
		if (isset($row['parent'])) {
979
			$row['parent'] = (int) $row['parent'];
980
		}
981
		if (isset($row['file_parent'])) {
982
			$row['file_parent'] = (int) $row['file_parent'];
983
		}
984
		if (isset($row['file_source'])) {
985
			$row['file_source'] = (int) $row['file_source'];
986
		}
987
		if (isset($row['permissions'])) {
988
			$row['permissions'] = (int) $row['permissions'];
989
		}
990
		if (isset($row['storage'])) {
991
			$row['storage'] = (int) $row['storage'];
992
		}
993
		if (isset($row['stime'])) {
994
			$row['stime'] = (int) $row['stime'];
995
		}
996
		if (isset($row['expiration']) && $row['share_type'] !== IShare::TYPE_LINK) {
997
			// discard expiration date for non-link shares, which might have been
998
			// set by ancient bugs
999
			$row['expiration'] = null;
1000
		}
1001
	}
1002
1003
	/**
1004
	 * format result
1005
	 * @param array $items result
1006
	 * @param string $column is it a file share or a general share ('file_target' or 'item_target')
1007
	 * @param \OCP\Share_Backend $backend sharing backend
1008
	 * @param int $format
1009
	 * @param array $parameters additional format parameters
1010
	 * @return array format result
1011
	 */
1012
	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
1013
		if ($format === self::FORMAT_NONE) {
1014
			return $items;
1015
		} elseif ($format === self::FORMAT_STATUSES) {
1016
			$statuses = [];
1017
			foreach ($items as $item) {
1018
				if ($item['share_type'] === IShare::TYPE_LINK) {
1019
					if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
1020
						continue;
1021
					}
1022
					$statuses[$item[$column]]['link'] = true;
1023
				} elseif (!isset($statuses[$item[$column]])) {
1024
					$statuses[$item[$column]]['link'] = false;
1025
				}
1026
				if (!empty($item['file_target'])) {
1027
					$statuses[$item[$column]]['path'] = $item['path'];
1028
				}
1029
			}
1030
			return $statuses;
1031
		} else {
1032
			return $backend->formatItems($items, $format, $parameters);
1033
		}
1034
	}
1035
1036
	/**
1037
	 * remove protocol from URL
1038
	 *
1039
	 * @param string $url
1040
	 * @return string
1041
	 */
1042
	public static function removeProtocolFromUrl($url) {
1043
		if (strpos($url, 'https://') === 0) {
1044
			return substr($url, strlen('https://'));
1045
		} elseif (strpos($url, 'http://') === 0) {
1046
			return substr($url, strlen('http://'));
1047
		}
1048
1049
		return $url;
1050
	}
1051
1052
	/**
1053
	 * try http post first with https and then with http as a fallback
1054
	 *
1055
	 * @param string $remoteDomain
1056
	 * @param string $urlSuffix
1057
	 * @param array $fields post parameters
1058
	 * @return array
1059
	 */
1060
	private static function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields) {
1061
		$protocol = 'https://';
1062
		$result = [
1063
			'success' => false,
1064
			'result' => '',
1065
		];
1066
		$try = 0;
1067
		$discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class);
1068
		while ($result['success'] === false && $try < 2) {
1069
			$federationEndpoints = $discoveryService->discover($protocol . $remoteDomain, 'FEDERATED_SHARING');
1070
			$endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
1071
			$client = \OC::$server->getHTTPClientService()->newClient();
1072
1073
			try {
1074
				$response = $client->post(
1075
					$protocol . $remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT,
1076
					[
1077
						'body' => $fields,
1078
						'connect_timeout' => 10,
1079
					]
1080
				);
1081
1082
				$result = ['success' => true, 'result' => $response->getBody()];
1083
			} catch (\Exception $e) {
1084
				$result = ['success' => false, 'result' => $e->getMessage()];
1085
			}
1086
1087
			$try++;
1088
			$protocol = 'http://';
1089
		}
1090
1091
		return $result;
1092
	}
1093
1094
	/**
1095
	 * send server-to-server unshare to remote server
1096
	 *
1097
	 * @param string $remote url
1098
	 * @param int $id share id
1099
	 * @param string $token
1100
	 * @return bool
1101
	 */
1102
	private static function sendRemoteUnshare($remote, $id, $token) {
1103
		$url = rtrim($remote, '/');
1104
		$fields = ['token' => $token, 'format' => 'json'];
1105
		$url = self::removeProtocolFromUrl($url);
1106
		$result = self::tryHttpPostToShareEndpoint($url, '/'.$id.'/unshare', $fields);
1107
		$status = json_decode($result['result'], true);
1108
1109
		return ($result['success'] && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200));
1110
	}
1111
1112
	/**
1113
	 * @return int
1114
	 */
1115
	public static function getExpireInterval() {
1116
		return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1117
	}
1118
1119
	/**
1120
	 * Checks whether the given path is reachable for the given owner
1121
	 *
1122
	 * @param string $path path relative to files
1123
	 * @param string $ownerStorageId storage id of the owner
1124
	 *
1125
	 * @return boolean true if file is reachable, false otherwise
1126
	 */
1127
	private static function isFileReachable($path, $ownerStorageId) {
1128
		// if outside the home storage, file is always considered reachable
1129
		if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
1130
			substr($ownerStorageId, 0, 13) === 'object::user:'
1131
		)) {
1132
			return true;
1133
		}
1134
1135
		// if inside the home storage, the file has to be under "/files/"
1136
		$path = ltrim($path, '/');
1137
		if (substr($path, 0, 6) === 'files/') {
1138
			return true;
1139
		}
1140
1141
		return false;
1142
	}
1143
}
1144