Passed
Push — master ( 9be5ca...356350 )
by Morris
13:26 queued 10s
created

Share::tryHttpPostToShareEndpoint()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 21
nc 7
nop 3
dl 0
loc 32
rs 9.2728
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',
87
				'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
88
				.' is already registered for '.$itemType,
89
				ILogger::WARN);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::WARN has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

89
				/** @scrutinizer ignore-deprecated */ ILogger::WARN);

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

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

Loading history...
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());
108
	}
109
110
	/**
111
	 * Get the items of item type shared with a user
112
	 * @param string $itemType
113
	 * @param string $user id for which user we want the shares
114
	 * @param int $format (optional) Format type must be defined by the backend
115
	 * @param mixed $parameters (optional)
116
	 * @param int $limit Number of items to return (optional) Returns all by default
117
	 * @param boolean $includeCollections (optional)
118
	 * @return mixed Return depends on format
119
	 * @deprecated TESTS ONLY - this methods is only used by tests
120
	 * called like this:
121
	 * \OC\Share\Share::getItemsSharedWithUser('test', $shareWith); (tests/lib/Share/Backend.php)
122
	 */
123
	public static function getItemsSharedWithUser($itemType, $user) {
0 ignored issues
show
Unused Code introduced by
The parameter $itemType is not used and could be removed. ( Ignorable by Annotation )

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

123
	public static function getItemsSharedWithUser(/** @scrutinizer ignore-unused */ $itemType, $user) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
124
		return self::getItems('test', null, self::$shareTypeUserAndGroups, $user);
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
		$result->closeCursor();
198
199
		//if didn't found a result than let's look for a group share.
200
		if (empty($shares) && $user !== null) {
201
			$userObject = \OC::$server->getUserManager()->get($user);
202
			$groups = [];
203
			if ($userObject) {
204
				$groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
205
			}
206
207
			if (!empty($groups)) {
208
				$where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
209
				$arguments = [$itemSource, $itemType, $groups];
210
				$types = [null, null, IQueryBuilder::PARAM_STR_ARRAY];
211
212
				if ($owner !== null) {
213
					$where .= ' AND `uid_owner` = ?';
214
					$arguments[] = $owner;
215
					$types[] = null;
216
				}
217
218
				// TODO: inject connection, hopefully one day in the future when this
219
				// class isn't static anymore...
220
				$conn = \OC::$server->getDatabaseConnection();
221
				$result = $conn->executeQuery(
222
					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
223
					$arguments,
224
					$types
225
				);
226
227
				while ($row = $result->fetch()) {
228
					$shares[] = $row;
229
				}
230
			}
231
		}
232
233
		return $shares;
234
	}
235
236
	/**
237
	 * Get the shared item of item type owned by the current user
238
	 * @param string $itemType
239
	 * @param string $itemSource
240
	 * @param int $format (optional) Format type must be defined by the backend
241
	 * @param mixed $parameters
242
	 * @param boolean $includeCollections
243
	 * @return mixed Return depends on format
244
	 *
245
	 * Refactoring notes:
246
	 *   * defacto $parameters and $format is always the default and therefore is removed in the subsequent call
247
	 */
248
	public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
0 ignored issues
show
Unused Code introduced by
The parameter $format is not used and could be removed. ( Ignorable by Annotation )

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

248
	public static function getItemShared($itemType, $itemSource, /** @scrutinizer ignore-unused */ $format = self::FORMAT_NONE,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
249
										 $parameters = null, $includeCollections = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $parameters is not used and could be removed. ( Ignorable by Annotation )

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

249
										 /** @scrutinizer ignore-unused */ $parameters = null, $includeCollections = false) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
250
		return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), self::FORMAT_NONE,
251
			null, -1, $includeCollections);
252
	}
253
254
	/**
255
	 * Get the backend class for the specified item type
256
	 * @param string $itemType
257
	 * @throws \Exception
258
	 * @return \OCP\Share_Backend
259
	 */
260
	public static function getBackend($itemType) {
261
		$l = \OC::$server->getL10N('lib');
262
		if (isset(self::$backends[$itemType])) {
263
			return self::$backends[$itemType];
264
		} elseif (isset(self::$backendTypes[$itemType]['class'])) {
265
			$class = self::$backendTypes[$itemType]['class'];
266
			if (class_exists($class)) {
267
				self::$backends[$itemType] = new $class;
268
				if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
269
					$message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
270
					$message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', [$class]);
271
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

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

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

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

Loading history...
272
					throw new \Exception($message_t);
273
				}
274
				return self::$backends[$itemType];
275
			} else {
276
				$message = 'Sharing backend %s not found';
277
				$message_t = $l->t('Sharing backend %s not found', [$class]);
278
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

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

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

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

Loading history...
279
				throw new \Exception($message_t);
280
			}
281
		}
282
		$message = 'Sharing backend for %s not found';
283
		$message_t = $l->t('Sharing backend for %s not found', [$itemType]);
284
		\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

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

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

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

Loading history...
285
		throw new \Exception($message_t);
286
	}
287
288
	/**
289
	 * Check if resharing is allowed
290
	 * @return boolean true if allowed or false
291
	 *
292
	 * Resharing is allowed by default if not configured
293
	 */
294
	public static function isResharingAllowed() {
295
		if (!isset(self::$isResharingAllowed)) {
296
			if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
297
				self::$isResharingAllowed = true;
298
			} else {
299
				self::$isResharingAllowed = false;
300
			}
301
		}
302
		return self::$isResharingAllowed;
303
	}
304
305
	/**
306
	 * Get a list of collection item types for the specified item type
307
	 * @param string $itemType
308
	 * @return array
309
	 */
310
	private static function getCollectionItemTypes($itemType) {
311
		$collectionTypes = [$itemType];
312
		foreach (self::$backendTypes as $type => $backend) {
313
			if (in_array($backend['collectionOf'], $collectionTypes)) {
314
				$collectionTypes[] = $type;
315
			}
316
		}
317
		// TODO Add option for collections to be collection of themselves, only 'folder' does it now...
318
		if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
319
			unset($collectionTypes[0]);
320
		}
321
		// Return array if collections were found or the item type is a
322
		// collection itself - collections can be inside collections
323
		if (count($collectionTypes) > 0) {
324
			return $collectionTypes;
325
		}
326
		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...
327
	}
328
329
	/**
330
	 * Get shared items from the database
331
	 * @param string $itemType
332
	 * @param string $item Item source or target (optional)
333
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
334
	 * @param string $shareWith User or group the item is being shared with
335
	 * @param string $uidOwner User that is the owner of shared items (optional)
336
	 * @param int $format Format to convert items to with formatItems() (optional)
337
	 * @param mixed $parameters to pass to formatItems() (optional)
338
	 * @param int $limit Number of items to return, -1 to return all matches (optional)
339
	 * @param boolean $includeCollections Include collection item types (optional)
340
	 * @param boolean $itemShareWithBySource (optional)
341
	 * @param boolean $checkExpireDate
342
	 * @return array
343
	 *
344
	 * See public functions getItem(s)... for parameter usage
345
	 *
346
	 * Refactoring notes:
347
	 *   * defacto $limit, $itemsShareWithBySource, $checkExpireDate, $parameters and $format is always the default and therefore is removed in the subsequent call
348
	 */
349
	public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
350
									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
0 ignored issues
show
Unused Code introduced by
The parameter $format is not used and could be removed. ( Ignorable by Annotation )

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

350
									$uidOwner = null, /** @scrutinizer ignore-unused */ $format = self::FORMAT_NONE, $parameters = null, $limit = -1,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $parameters is not used and could be removed. ( Ignorable by Annotation )

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

350
									$uidOwner = null, $format = self::FORMAT_NONE, /** @scrutinizer ignore-unused */ $parameters = null, $limit = -1,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $limit is not used and could be removed. ( Ignorable by Annotation )

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

350
									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, /** @scrutinizer ignore-unused */ $limit = -1,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
351
									$includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $itemShareWithBySource is not used and could be removed. ( Ignorable by Annotation )

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

351
									$includeCollections = false, /** @scrutinizer ignore-unused */ $itemShareWithBySource = false, $checkExpireDate = true) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $checkExpireDate is not used and could be removed. ( Ignorable by Annotation )

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

351
									$includeCollections = false, $itemShareWithBySource = false, /** @scrutinizer ignore-unused */ $checkExpireDate = true) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
352
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
353
			return [];
354
		}
355
		$backend = self::getBackend($itemType);
356
		$collectionTypes = false;
357
		// Get filesystem root to add it to the file target and remove from the
358
		// file source, match file_source with the file cache
359
		if ($itemType == 'file' || $itemType == 'folder') {
360
			if (!is_null($uidOwner)) {
361
				$root = \OC\Files\Filesystem::getRoot();
362
			} else {
363
				$root = '';
364
			}
365
			$where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
366
			if (!isset($item)) {
367
				$where .= ' AND `file_target` IS NOT NULL ';
368
			}
369
			$where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
370
			$fileDependent = true;
371
			$queryArgs = [];
372
		} else {
373
			$fileDependent = false;
374
			$root = '';
375
			$collectionTypes = self::getCollectionItemTypes($itemType);
376
			if ($includeCollections && !isset($item) && $collectionTypes) {
377
				// If includeCollections is true, find collections of this item type, e.g. a music album contains songs
378
				if (!in_array($itemType, $collectionTypes)) {
379
					$itemTypes = array_merge([$itemType], $collectionTypes);
380
				} else {
381
					$itemTypes = $collectionTypes;
382
				}
383
				$placeholders = implode(',', array_fill(0, count($itemTypes), '?'));
384
				$where = ' WHERE `item_type` IN ('.$placeholders.'))';
385
				$queryArgs = $itemTypes;
386
			} else {
387
				$where = ' WHERE `item_type` = ?';
388
				$queryArgs = [$itemType];
389
			}
390
		}
391
		if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
392
			$where .= ' AND `share_type` != ?';
393
			$queryArgs[] = IShare::TYPE_LINK;
394
		}
395
		if (isset($shareType)) {
396
			// Include all user and group items
397
			if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
398
				$where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
399
				$queryArgs[] = IShare::TYPE_USER;
400
				$queryArgs[] = self::$shareTypeGroupUserUnique;
401
				$queryArgs[] = $shareWith;
402
403
				$user = \OC::$server->getUserManager()->get($shareWith);
404
				$groups = [];
405
				if ($user) {
406
					$groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
407
				}
408
				if (!empty($groups)) {
409
					$placeholders = implode(',', array_fill(0, count($groups), '?'));
410
					$where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
411
					$queryArgs[] = IShare::TYPE_GROUP;
412
					$queryArgs = array_merge($queryArgs, $groups);
413
				}
414
				$where .= ')';
415
				// Don't include own group shares
416
				$where .= ' AND `uid_owner` != ?';
417
				$queryArgs[] = $shareWith;
418
			} else {
419
				$where .= ' AND `share_type` = ?';
420
				$queryArgs[] = $shareType;
421
				if (isset($shareWith)) {
422
					$where .= ' AND `share_with` = ?';
423
					$queryArgs[] = $shareWith;
424
				}
425
			}
426
		}
427
		if (isset($uidOwner)) {
428
			$where .= ' AND `uid_owner` = ?';
429
			$queryArgs[] = $uidOwner;
430
			if (!isset($shareType)) {
431
				// Prevent unique user targets for group shares from being selected
432
				$where .= ' AND `share_type` != ?';
433
				$queryArgs[] = self::$shareTypeGroupUserUnique;
434
			}
435
			if ($fileDependent) {
436
				$column = 'file_source';
437
			} else {
438
				$column = 'item_source';
439
			}
440
		} else {
441
			if ($fileDependent) {
442
				$column = 'file_target';
443
			} else {
444
				$column = 'item_target';
445
			}
446
		}
447
		if (isset($item)) {
448
			$collectionTypes = self::getCollectionItemTypes($itemType);
449
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
450
				$where .= ' AND (';
451
			} else {
452
				$where .= ' AND';
453
			}
454
			// If looking for own shared items, check item_source else check item_target
455
			if (isset($uidOwner)) {
456
				// If item type is a file, file source needs to be checked in case the item was converted
457
				if ($fileDependent) {
458
					$where .= ' `file_source` = ?';
459
					$column = 'file_source';
460
				} else {
461
					$where .= ' `item_source` = ?';
462
					$column = 'item_source';
463
				}
464
			} else {
465
				if ($fileDependent) {
466
					$where .= ' `file_target` = ?';
467
					$item = \OC\Files\Filesystem::normalizePath($item);
468
				} else {
469
					$where .= ' `item_target` = ?';
470
				}
471
			}
472
			$queryArgs[] = $item;
473
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
474
				$placeholders = implode(',', array_fill(0, count($collectionTypes), '?'));
475
				$where .= ' OR `item_type` IN ('.$placeholders.'))';
476
				$queryArgs = array_merge($queryArgs, $collectionTypes);
477
			}
478
		}
479
480
		$where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
481
482
		$queryLimit = null;
483
		$select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent, $uidOwner);
484
		$root = strlen($root);
485
		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
486
		$result = $query->execute($queryArgs);
487
		if ($result === false) {
488
			\OCP\Util::writeLog('OCP\Share',
489
				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
490
				ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

490
				/** @scrutinizer ignore-deprecated */ ILogger::ERROR);

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

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

Loading history...
491
		}
492
		$items = [];
493
		$targets = [];
494
		$switchedItems = [];
495
		$mounts = [];
496
		while ($row = $result->fetchRow()) {
497
			self::transformDBResults($row);
498
			// Filter out duplicate group shares for users with unique targets
499
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
500
				continue;
501
			}
502
			if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
503
				$row['share_type'] = IShare::TYPE_GROUP;
504
				$row['unique_name'] = true; // remember that we use a unique name for this user
505
				$row['share_with'] = $items[$row['parent']]['share_with'];
506
				// if the group share was unshared from the user we keep the permission, otherwise
507
				// we take the permission from the parent because this is always the up-to-date
508
				// permission for the group share
509
				if ($row['permissions'] > 0) {
510
					$row['permissions'] = $items[$row['parent']]['permissions'];
511
				}
512
				// Remove the parent group share
513
				unset($items[$row['parent']]);
514
				if ($row['permissions'] == 0) {
515
					continue;
516
				}
517
			} elseif (!isset($uidOwner)) {
518
				// Check if the same target already exists
519
				if (isset($targets[$row['id']])) {
520
					// Check if the same owner shared with the user twice
521
					// through a group and user share - this is allowed
522
					$id = $targets[$row['id']];
523
					if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
524
						// Switch to group share type to ensure resharing conditions aren't bypassed
525
						if ($items[$id]['share_type'] != IShare::TYPE_GROUP) {
526
							$items[$id]['share_type'] = IShare::TYPE_GROUP;
527
							$items[$id]['share_with'] = $row['share_with'];
528
						}
529
						// Switch ids if sharing permission is granted on only
530
						// one share to ensure correct parent is used if resharing
531
						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
532
							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
533
							$items[$row['id']] = $items[$id];
534
							$switchedItems[$id] = $row['id'];
535
							unset($items[$id]);
536
							$id = $row['id'];
537
						}
538
						$items[$id]['permissions'] |= (int)$row['permissions'];
539
					}
540
					continue;
541
				} elseif (!empty($row['parent'])) {
542
					$targets[$row['parent']] = $row['id'];
543
				}
544
			}
545
			// Remove root from file source paths if retrieving own shared items
546
			if (isset($uidOwner) && isset($row['path'])) {
547
				if (isset($row['parent'])) {
548
					$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
549
					$query->select('file_target')
550
						->from('share')
551
						->where($query->expr()->eq('id', $query->createNamedParameter($row['parent'])));
552
553
					$parentResult = $query->execute();
554
					$parentRow = $parentResult->fetch();
555
					$parentResult->closeCursor();
556
557
					if ($parentRow === false) {
558
						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
559
							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
560
							ILogger::ERROR);
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

560
							/** @scrutinizer ignore-deprecated */ ILogger::ERROR);

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

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

Loading history...
561
					} else {
562
						$tmpPath = $parentRow['file_target'];
563
						// find the right position where the row path continues from the target path
564
						$pos = strrpos($row['path'], $parentRow['file_target']);
565
						$subPath = substr($row['path'], $pos);
566
						$splitPath = explode('/', $subPath);
567
						foreach (array_slice($splitPath, 2) as $pathPart) {
568
							$tmpPath = $tmpPath . '/' . $pathPart;
569
						}
570
						$row['path'] = $tmpPath;
571
					}
572
				} else {
573
					if (!isset($mounts[$row['storage']])) {
574
						$mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
575
						if (is_array($mountPoints) && !empty($mountPoints)) {
576
							$mounts[$row['storage']] = current($mountPoints);
577
						}
578
					}
579
					if (!empty($mounts[$row['storage']])) {
580
						$path = $mounts[$row['storage']]->getMountPoint().$row['path'];
581
						$relPath = substr($path, $root); // path relative to data/user
582
						$row['path'] = rtrim($relPath, '/');
583
					}
584
				}
585
			}
586
587
			// Check if resharing is allowed, if not remove share permission
588
			if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
589
				$row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
590
			}
591
			// Add display names to result
592
			$row['share_with_displayname'] = $row['share_with'];
593
			if (isset($row['share_with']) && $row['share_with'] != '' &&
594
				$row['share_type'] === IShare::TYPE_USER) {
595
				$shareWithUser = \OC::$server->getUserManager()->get($row['share_with']);
596
				$row['share_with_displayname'] = $shareWithUser === null ? $row['share_with'] : $shareWithUser->getDisplayName();
597
			} elseif (isset($row['share_with']) && $row['share_with'] != '' &&
598
				$row['share_type'] === IShare::TYPE_REMOTE) {
599
				$addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
600
				foreach ($addressBookEntries as $entry) {
601
					foreach ($entry['CLOUD'] as $cloudID) {
602
						if ($cloudID === $row['share_with']) {
603
							$row['share_with_displayname'] = $entry['FN'];
604
						}
605
					}
606
				}
607
			}
608
			if (isset($row['uid_owner']) && $row['uid_owner'] != '') {
609
				$ownerUser = \OC::$server->getUserManager()->get($row['uid_owner']);
610
				$row['displayname_owner'] = $ownerUser === null ? $row['uid_owner'] : $ownerUser->getDisplayName();
611
			}
612
613
			if ($row['permissions'] > 0) {
614
				$items[$row['id']] = $row;
615
			}
616
		}
617
618
		// group items if we are looking for items shared with the current user
619
		if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
620
			$items = self::groupItems($items, $itemType);
621
		}
622
623
		if (!empty($items)) {
624
			$collectionItems = [];
625
			foreach ($items as &$row) {
626
				// Check if this is a collection of the requested item type
627
				if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
628
					if (($collectionBackend = self::getBackend($row['item_type']))
629
						&& $collectionBackend instanceof \OCP\Share_Backend_Collection) {
630
						// Collections can be inside collections, check if the item is a collection
631
						if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
632
							$collectionItems[] = $row;
633
						} else {
634
							$collection = [];
635
							$collection['item_type'] = $row['item_type'];
636
							if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
637
								$collection['path'] = basename($row['path']);
638
							}
639
							$row['collection'] = $collection;
640
							// Fetch all of the children sources
641
							$children = $collectionBackend->getChildren($row[$column]);
642
							foreach ($children as $child) {
643
								$childItem = $row;
644
								$childItem['item_type'] = $itemType;
645
								if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
646
									$childItem['item_source'] = $child['source'];
647
									$childItem['item_target'] = $child['target'];
648
								}
649
								if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
650
									if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
651
										$childItem['file_source'] = $child['source'];
652
									} else { // TODO is this really needed if we already know that we use the file backend?
653
										$meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
654
										$childItem['file_source'] = $meta['fileid'];
655
									}
656
									$childItem['file_target'] =
657
										\OC\Files\Filesystem::normalizePath($child['file_path']);
658
								}
659
								if (isset($item)) {
660
									if ($childItem[$column] == $item) {
661
										$collectionItems[] = $childItem;
662
									}
663
								} else {
664
									$collectionItems[] = $childItem;
665
								}
666
							}
667
						}
668
					}
669
					// Remove collection item
670
					$toRemove = $row['id'];
671
					if (array_key_exists($toRemove, $switchedItems)) {
672
						$toRemove = $switchedItems[$toRemove];
673
					}
674
					unset($items[$toRemove]);
675
				} elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
676
					// FIXME: Thats a dirty hack to improve file sharing performance,
677
					// see github issue #10588 for more details
678
					// Need to find a solution which works for all back-ends
679
					$collectionBackend = self::getBackend($row['item_type']);
680
					$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

680
					/** @scrutinizer ignore-call */ 
681
     $sharedParents = $collectionBackend->getParents($row['item_source']);
Loading history...
681
					foreach ($sharedParents as $parent) {
682
						$collectionItems[] = $parent;
683
					}
684
				}
685
			}
686
			if (!empty($collectionItems)) {
687
				$collectionItems = array_unique($collectionItems, SORT_REGULAR);
688
				$items = array_merge($items, $collectionItems);
689
			}
690
691
			// filter out invalid items, these can appear when subshare entries exist
692
			// for a group in which the requested user isn't a member any more
693
			$items = array_filter($items, function ($item) {
694
				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
695
			});
696
697
			return self::formatResult($items, $column, $backend);
698
		} elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
699
			// FIXME: Thats a dirty hack to improve file sharing performance,
700
			// see github issue #10588 for more details
701
			// Need to find a solution which works for all back-ends
702
			$collectionItems = [];
703
			$collectionBackend = self::getBackend('folder');
704
			$sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
705
			foreach ($sharedParents as $parent) {
706
				$collectionItems[] = $parent;
707
			}
708
			return self::formatResult($collectionItems, $column, $backend);
709
		}
710
711
		return [];
712
	}
713
714
	/**
715
	 * group items with link to the same source
716
	 *
717
	 * @param array $items
718
	 * @param string $itemType
719
	 * @return array of grouped items
720
	 */
721
	protected static function groupItems($items, $itemType) {
722
		$fileSharing = $itemType === 'file' || $itemType === 'folder';
723
724
		$result = [];
725
726
		foreach ($items as $item) {
727
			$grouped = false;
728
			foreach ($result as $key => $r) {
729
				// for file/folder shares we need to compare file_source, otherwise we compare item_source
730
				// only group shares if they already point to the same target, otherwise the file where shared
731
				// before grouping of shares was added. In this case we don't group them toi avoid confusions
732
				if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
733
					(!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
734
					// add the first item to the list of grouped shares
735
					if (!isset($result[$key]['grouped'])) {
736
						$result[$key]['grouped'][] = $result[$key];
737
					}
738
					$result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
739
					$result[$key]['grouped'][] = $item;
740
					$grouped = true;
741
					break;
742
				}
743
			}
744
745
			if (!$grouped) {
746
				$result[] = $item;
747
			}
748
		}
749
750
		return $result;
751
	}
752
753
	/**
754
	 * construct select statement
755
	 * @param int $format
756
	 * @param boolean $fileDependent ist it a file/folder share or a generla share
757
	 * @param string $uidOwner
758
	 * @return string select statement
759
	 */
760
	private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
761
		$select = '*';
762
		if ($format == self::FORMAT_STATUSES) {
763
			if ($fileDependent) {
764
				$select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
765
					. '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
766
					. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
767
					. '`uid_initiator`';
768
			} else {
769
				$select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
770
			}
771
		} else {
772
			if (isset($uidOwner)) {
773
				if ($fileDependent) {
774
					$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
775
						. ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
776
						. ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
777
						. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
778
				} else {
779
					$select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
780
						. ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
781
				}
782
			} else {
783
				if ($fileDependent) {
784
					if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
785
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
786
							. '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
787
							. '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
788
							. '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
789
					} else {
790
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
791
							. '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
792
							. '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
793
							. '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
794
							. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
795
					}
796
				}
797
			}
798
		}
799
		return $select;
800
	}
801
802
803
	/**
804
	 * transform db results
805
	 * @param array $row result
806
	 */
807
	private static function transformDBResults(&$row) {
808
		if (isset($row['id'])) {
809
			$row['id'] = (int) $row['id'];
810
		}
811
		if (isset($row['share_type'])) {
812
			$row['share_type'] = (int) $row['share_type'];
813
		}
814
		if (isset($row['parent'])) {
815
			$row['parent'] = (int) $row['parent'];
816
		}
817
		if (isset($row['file_parent'])) {
818
			$row['file_parent'] = (int) $row['file_parent'];
819
		}
820
		if (isset($row['file_source'])) {
821
			$row['file_source'] = (int) $row['file_source'];
822
		}
823
		if (isset($row['permissions'])) {
824
			$row['permissions'] = (int) $row['permissions'];
825
		}
826
		if (isset($row['storage'])) {
827
			$row['storage'] = (int) $row['storage'];
828
		}
829
		if (isset($row['stime'])) {
830
			$row['stime'] = (int) $row['stime'];
831
		}
832
		if (isset($row['expiration']) && $row['share_type'] !== IShare::TYPE_LINK) {
833
			// discard expiration date for non-link shares, which might have been
834
			// set by ancient bugs
835
			$row['expiration'] = null;
836
		}
837
	}
838
839
	/**
840
	 * format result
841
	 * @param array $items result
842
	 * @param string $column is it a file share or a general share ('file_target' or 'item_target')
843
	 * @param \OCP\Share_Backend $backend sharing backend
844
	 * @param int $format
845
	 * @param array $parameters additional format parameters
846
	 * @return array format result
847
	 */
848
	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
849
		if ($format === self::FORMAT_NONE) {
850
			return $items;
851
		} elseif ($format === self::FORMAT_STATUSES) {
852
			$statuses = [];
853
			foreach ($items as $item) {
854
				if ($item['share_type'] === IShare::TYPE_LINK) {
855
					if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
856
						continue;
857
					}
858
					$statuses[$item[$column]]['link'] = true;
859
				} elseif (!isset($statuses[$item[$column]])) {
860
					$statuses[$item[$column]]['link'] = false;
861
				}
862
				if (!empty($item['file_target'])) {
863
					$statuses[$item[$column]]['path'] = $item['path'];
864
				}
865
			}
866
			return $statuses;
867
		} else {
868
			return $backend->formatItems($items, $format, $parameters);
869
		}
870
	}
871
872
	/**
873
	 * remove protocol from URL
874
	 *
875
	 * @param string $url
876
	 * @return string
877
	 */
878
	public static function removeProtocolFromUrl($url) {
879
		if (strpos($url, 'https://') === 0) {
880
			return substr($url, strlen('https://'));
881
		} elseif (strpos($url, 'http://') === 0) {
882
			return substr($url, strlen('http://'));
883
		}
884
885
		return $url;
886
	}
887
888
889
	/**
890
	 * @return int
891
	 */
892
	public static function getExpireInterval() {
893
		return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
894
	}
895
896
	/**
897
	 * Checks whether the given path is reachable for the given owner
898
	 *
899
	 * @param string $path path relative to files
900
	 * @param string $ownerStorageId storage id of the owner
901
	 *
902
	 * @return boolean true if file is reachable, false otherwise
903
	 */
904
	private static function isFileReachable($path, $ownerStorageId) {
905
		// if outside the home storage, file is always considered reachable
906
		if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
907
			substr($ownerStorageId, 0, 13) === 'object::user:'
908
		)) {
909
			return true;
910
		}
911
912
		// if inside the home storage, the file has to be under "/files/"
913
		$path = ltrim($path, '/');
914
		if (substr($path, 0, 6) === 'files/') {
915
			return true;
916
		}
917
918
		return false;
919
	}
920
}
921