Completed
Pull Request — stable8.2 (#24656)
by Joas
12:22
created

Share::expireItem()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 5
Metric Value
dl 0
loc 31
ccs 19
cts 19
cp 1
rs 8.439
cc 5
eloc 16
nc 7
nop 1
crap 5
1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Bart Visscher <[email protected]>
5
 * @author Bernhard Reiter <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Christopher Schäpers <[email protected]>
8
 * @author Daniel Hansson <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Jörn Friedrich Dreyer <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 * @author Michael Kuhn <[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
 * @copyright Copyright (c) 2015, ownCloud, Inc.
23
 * @license AGPL-3.0
24
 *
25
 * This code is free software: you can redistribute it and/or modify
26
 * it under the terms of the GNU Affero General Public License, version 3,
27
 * as published by the Free Software Foundation.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32
 * GNU Affero General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU Affero General Public License, version 3,
35
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
36
 *
37
 */
38
39
namespace OC\Share;
40
41
use OC\Files\Filesystem;
42
use OCP\IUserSession;
43
use OCP\IDBConnection;
44
use OCP\IConfig;
45
46
/**
47
 * This class provides the ability for apps to share their content between users.
48
 * Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
49
 *
50
 * It provides the following hooks:
51
 *  - post_shared
52
 */
53
class Share extends Constants {
54
55
	/** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
56
	 * Construct permissions for share() and setPermissions with Or (|) e.g.
57
	 * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
58
	 *
59
	 * Check if permission is granted with And (&) e.g. Check if delete is
60
	 * granted: if ($permissions & PERMISSION_DELETE)
61
	 *
62
	 * Remove permissions with And (&) and Not (~) e.g. Remove the update
63
	 * permission: $permissions &= ~PERMISSION_UPDATE
64
	 *
65
	 * Apps are required to handle permissions on their own, this class only
66
	 * stores and manages the permissions of shares
67
	 * @see lib/public/constants.php
68
	 */
69
70
	/**
71
	 * Register a sharing backend class that implements OCP\Share_Backend for an item type
72
	 * @param string $itemType Item type
73
	 * @param string $class Backend class
74
	 * @param string $collectionOf (optional) Depends on item type
75
	 * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
76
	 * @return boolean true if backend is registered or false if error
77
	 */
78 67
	public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
79 67
		if (self::isEnabled()) {
80 67
			if (!isset(self::$backendTypes[$itemType])) {
81 1
				self::$backendTypes[$itemType] = array(
82 1
					'class' => $class,
83 1
					'collectionOf' => $collectionOf,
84
					'supportedFileExtensions' => $supportedFileExtensions
85 1
				);
86 1
				if(count(self::$backendTypes) === 1) {
87
					\OC_Util::addScript('core', 'shareconfigmodel');
88
					\OC_Util::addScript('core', 'shareitemmodel');
89
					\OC_Util::addScript('core', 'sharedialogresharerinfoview');
90
					\OC_Util::addScript('core', 'sharedialoglinkshareview');
91
					\OC_Util::addScript('core', 'sharedialogexpirationview');
92
					\OC_Util::addScript('core', 'sharedialogshareelistview');
93
					\OC_Util::addScript('core', 'sharedialogview');
94
					\OC_Util::addScript('core', 'share');
95
					\OC_Util::addStyle('core', 'share');
96
				}
97 1
				return true;
98
			}
99 66
			\OCP\Util::writeLog('OCP\Share',
100 66
				'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
101 66
				.' is already registered for '.$itemType,
102 66
				\OCP\Util::WARN);
103 66
		}
104 66
		return false;
105
	}
106
107
	/**
108
	 * Check if the Share API is enabled
109
	 * @return boolean true if enabled or false
110
	 *
111
	 * The Share API is enabled by default if not configured
112
	 */
113 509
	public static function isEnabled() {
114 509
		if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_enabled', 'yes') == 'yes') {
115 509
			return true;
116
		}
117
		return false;
118
	}
119
120
	/**
121
	 * Find which users can access a shared item
122
	 * @param string $path to the file
123
	 * @param string $ownerUser owner of the file
124
	 * @param boolean $includeOwner include owner to the list of users with access to the file
125
	 * @param boolean $returnUserPaths Return an array with the user => path map
126
	 * @return array
127
	 * @note $path needs to be relative to user data dir, e.g. 'file.txt'
128
	 *       not '/admin/data/file.txt'
129
	 */
130 11
	public static function getUsersSharingFile($path, $ownerUser, $includeOwner = false, $returnUserPaths = false) {
131
132 11
		Filesystem::initMountPoints($ownerUser);
133 11
		$shares = $sharePaths = $fileTargets = array();
134 11
		$publicShare = false;
135 11
		$remoteShare = false;
136 11
		$source = -1;
137 11
		$cache = false;
138
139 11
		$view = new \OC\Files\View('/' . $ownerUser . '/files');
140 11
		$meta = $view->getFileInfo($path);
141 11
		if ($meta) {
142 11
			$path = substr($meta->getPath(), strlen('/' . $ownerUser . '/files'));
143 11
		} else {
144
			// if the file doesn't exists yet we start with the parent folder
145 5
			$meta = $view->getFileInfo(dirname($path));
146
		}
147
148 11
		if($meta !== false) {
149 11
			$source = $meta['fileid'];
150 11
			$cache = new \OC\Files\Cache\Cache($meta['storage']);
151 11
		}
152
153 11
		while ($source !== -1) {
154
			// Fetch all shares with another user
155 11
			if (!$returnUserPaths) {
156 8
				$query = \OC_DB::prepare(
157
					'SELECT `share_with`, `file_source`, `file_target`
158
					FROM
159
					`*PREFIX*share`
160
					WHERE
161
					`item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')'
162 8
				);
163 8
				$result = $query->execute(array($source, self::SHARE_TYPE_USER));
164 8
			} else {
165 3
				$query = \OC_DB::prepare(
166
					'SELECT `share_with`, `file_source`, `file_target`
167
				FROM
168
				`*PREFIX*share`
169
				WHERE
170
				`item_source` = ? AND `share_type` IN (?, ?) AND `item_type` IN (\'file\', \'folder\')'
171 3
				);
172 3
				$result = $query->execute(array($source, self::SHARE_TYPE_USER, self::$shareTypeGroupUserUnique));
173
			}
174
175 11
			if (\OCP\DB::isError($result)) {
176
				\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
177
			} else {
178 11
				while ($row = $result->fetchRow()) {
179 3
					$shares[] = $row['share_with'];
180 3
					if ($returnUserPaths) {
181 3
						$fileTargets[(int) $row['file_source']][$row['share_with']] = $row;
182 3
					}
183 3
				}
184
			}
185
186
			// We also need to take group shares into account
187 11
			$query = \OC_DB::prepare(
188
				'SELECT `share_with`, `file_source`, `file_target`
189
				FROM
190
				`*PREFIX*share`
191
				WHERE
192
				`item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')'
193 11
			);
194
195 11
			$result = $query->execute(array($source, self::SHARE_TYPE_GROUP));
196
197 11
			if (\OCP\DB::isError($result)) {
198
				\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
199
			} else {
200 11
				while ($row = $result->fetchRow()) {
201 6
					$usersInGroup = \OC_Group::usersInGroup($row['share_with']);
202 6
					$shares = array_merge($shares, $usersInGroup);
203 6
					if ($returnUserPaths) {
204 3
						foreach ($usersInGroup as $user) {
205 3
							if (!isset($fileTargets[(int) $row['file_source']][$user])) {
206
								// When the user already has an entry for this file source
207
								// the file is either shared directly with him as well, or
208
								// he has an exception entry (because of naming conflict).
209 1
								$fileTargets[(int) $row['file_source']][$user] = $row;
210 1
							}
211 3
						}
212 3
					}
213 6
				}
214
			}
215
216
			//check for public link shares
217 11 View Code Duplication
			if (!$publicShare) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
218 11
				$query = \OC_DB::prepare('
219
					SELECT `share_with`
220
					FROM `*PREFIX*share`
221 11
					WHERE `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')', 1
222 11
				);
223
224 11
				$result = $query->execute(array($source, self::SHARE_TYPE_LINK));
225
226 11
				if (\OCP\DB::isError($result)) {
227
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
228
				} else {
229 11
					if ($result->fetchRow()) {
230 6
						$publicShare = true;
231 6
					}
232
				}
233 11
			}
234
235
			//check for remote share
236 11 View Code Duplication
			if (!$remoteShare) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
237 11
				$query = \OC_DB::prepare('
238
					SELECT `share_with`
239
					FROM `*PREFIX*share`
240 11
					WHERE `item_source` = ? AND `share_type` = ? AND `item_type` IN (\'file\', \'folder\')', 1
241 11
				);
242
243 11
				$result = $query->execute(array($source, self::SHARE_TYPE_REMOTE));
244
245 11
				if (\OCP\DB::isError($result)) {
246
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
247
				} else {
248 11
					if ($result->fetchRow()) {
249
						$remoteShare = true;
250
					}
251
				}
252 11
			}
253
254
			// let's get the parent for the next round
255 11
			$meta = $cache->get((int)$source);
256 11
			if($meta !== false) {
257 11
				$source = (int)$meta['parent'];
258 11
			} else {
259
				$source = -1;
260
			}
261 11
		}
262
263
		// Include owner in list of users, if requested
264 11
		if ($includeOwner) {
265 3
			$shares[] = $ownerUser;
266 3
		}
267
268 11
		if ($returnUserPaths) {
269 3
			$fileTargetIDs = array_keys($fileTargets);
270 3
			$fileTargetIDs = array_unique($fileTargetIDs);
271
272 3
			if (!empty($fileTargetIDs)) {
273 3
				$query = \OC_DB::prepare(
274
					'SELECT `fileid`, `path`
275
					FROM `*PREFIX*filecache`
276 3
					WHERE `fileid` IN (' . implode(',', $fileTargetIDs) . ')'
277 3
				);
278 3
				$result = $query->execute();
279
280 3
				if (\OCP\DB::isError($result)) {
281
					\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage(), \OCP\Util::ERROR);
282
				} else {
283 3
					while ($row = $result->fetchRow()) {
284 3
						foreach ($fileTargets[$row['fileid']] as $uid => $shareData) {
285 3
							$sharedPath = $shareData['file_target'];
286 3
							$sharedPath .= substr($path, strlen($row['path']) -5);
287 3
							$sharePaths[$uid] = $sharedPath;
288 3
						}
289 3
					}
290
				}
291 3
			}
292
293 3
			if ($includeOwner) {
294 2
				$sharePaths[$ownerUser] = $path;
295 2
			} else {
296 1
				unset($sharePaths[$ownerUser]);
297
			}
298
299 3
			return $sharePaths;
300
		}
301
302 8
		return array('users' => array_unique($shares), 'public' => $publicShare, 'remote' => $remoteShare);
303
	}
304
305
	/**
306
	 * Get the items of item type shared with the current user
307
	 * @param string $itemType
308
	 * @param int $format (optional) Format type must be defined by the backend
309
	 * @param mixed $parameters (optional)
310
	 * @param int $limit Number of items to return (optional) Returns all by default
311
	 * @param boolean $includeCollections (optional)
312
	 * @return mixed Return depends on format
313
	 */
314 7
	public static function getItemsSharedWith($itemType, $format = self::FORMAT_NONE,
315
											  $parameters = null, $limit = -1, $includeCollections = false) {
316 7
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format,
317 7
			$parameters, $limit, $includeCollections);
318
	}
319
320
	/**
321
	 * Get the items of item type shared with a user
322
	 * @param string $itemType
323
	 * @param string $user id for which user we want the shares
324
	 * @param int $format (optional) Format type must be defined by the backend
325
	 * @param mixed $parameters (optional)
326
	 * @param int $limit Number of items to return (optional) Returns all by default
327
	 * @param boolean $includeCollections (optional)
328
	 * @return mixed Return depends on format
329
	 */
330 470
	public static function getItemsSharedWithUser($itemType, $user, $format = self::FORMAT_NONE,
331
												  $parameters = null, $limit = -1, $includeCollections = false) {
332 470
		return self::getItems($itemType, null, self::$shareTypeUserAndGroups, $user, null, $format,
333 470
			$parameters, $limit, $includeCollections);
334
	}
335
336
	/**
337
	 * Get the item of item type shared with the current user
338
	 * @param string $itemType
339
	 * @param string $itemTarget
340
	 * @param int $format (optional) Format type must be defined by the backend
341
	 * @param mixed $parameters (optional)
342
	 * @param boolean $includeCollections (optional)
343
	 * @return mixed Return depends on format
344
	 */
345 11
	public static function getItemSharedWith($itemType, $itemTarget, $format = self::FORMAT_NONE,
346
											 $parameters = null, $includeCollections = false) {
347 11
		return self::getItems($itemType, $itemTarget, self::$shareTypeUserAndGroups, \OC_User::getUser(), null, $format,
348 11
			$parameters, 1, $includeCollections);
349
	}
350
351
	/**
352
	 * Get the item of item type shared with a given user by source
353
	 * @param string $itemType
354
	 * @param string $itemSource
355
	 * @param string $user User to whom the item was shared
356
	 * @param string $owner Owner of the share
357
	 * @param int $shareType only look for a specific share type
358
	 * @return array Return list of items with file_target, permissions and expiration
359
	 */
360 152
	public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
361 152
		$shares = array();
362 152
		$fileDependent = false;
363
364 152
		$where = 'WHERE';
365 152
		$fileDependentWhere = '';
366 152
		if ($itemType === 'file' || $itemType === 'folder') {
367 141
			$fileDependent = true;
368 141
			$column = 'file_source';
369 141
			$fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
370 141
			$fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
371 141
		} else {
372 11
			$column = 'item_source';
373
		}
374
375 152
		$select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
376
377 152
		$where .= ' `' . $column . '` = ? AND `item_type` = ? ';
378 152
		$arguments = array($itemSource, $itemType);
379
		// for link shares $user === null
380 152
		if ($user !== null) {
381 149
			$where .= ' AND `share_with` = ? ';
382 149
			$arguments[] = $user;
383 149
		}
384
385 152
		if ($shareType !== null) {
386 71
			$where .= ' AND `share_type` = ? ';
387 71
			$arguments[] = $shareType;
388 71
		}
389
390 152
		if ($owner !== null) {
391 48
			$where .= ' AND `uid_owner` = ? ';
392 48
			$arguments[] = $owner;
393 48
		}
394
395 152
		$query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
396
397 152
		$result = \OC_DB::executeAudited($query, $arguments);
398
399 152
		while ($row = $result->fetchRow()) {
400 95
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
401 1
				continue;
402
			}
403 95
			if ($fileDependent && (int)$row['file_parent'] === -1) {
404
				// if it is a mount point we need to get the path from the mount manager
405 1
				$mountManager = \OC\Files\Filesystem::getMountManager();
406 1
				$mountPoint = $mountManager->findByStorageId($row['storage_id']);
407 1
				if (!empty($mountPoint)) {
408 1
					$path = $mountPoint[0]->getMountPoint();
409 1
					$path = trim($path, '/');
410 1
					$path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
411 1
					$row['path'] = $path;
412 1
				} else {
413
					\OC::$server->getLogger()->warning(
414
						'Could not resolve mount point for ' . $row['storage_id'],
415
						['app' => 'OCP\Share']
416
					);
417
				}
418 1
			}
419 95
			$shares[] = $row;
420 95
		}
421
422
		//if didn't found a result than let's look for a group share.
423 152
		if(empty($shares) && $user !== null) {
424 140
			$groups = \OC_Group::getUserGroups($user);
425
426 140
			if (!empty($groups)) {
427 121
				$where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
428 121
				$arguments = array($itemSource, $itemType, $groups);
429 121
				$types = array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
430
431 121
				if ($owner !== null) {
432 1
					$where .= ' AND `uid_owner` = ?';
433 1
					$arguments[] = $owner;
434 1
					$types[] = null;
435 1
				}
436
437
				// TODO: inject connection, hopefully one day in the future when this
438
				// class isn't static anymore...
439 121
				$conn = \OC_DB::getConnection();
440 121
				$result = $conn->executeQuery(
441 121
					'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
442 121
					$arguments,
0 ignored issues
show
Documentation introduced by
$arguments is of type array<integer,string|array>, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
443
					$types
444 121
				);
445
446 121
				while ($row = $result->fetch()) {
447 5
					$shares[] = $row;
448 5
				}
449 121
			}
450 140
		}
451
452 152
		return $shares;
453
454
	}
455
456
	/**
457
	 * Get the item of item type shared with the current user by source
458
	 * @param string $itemType
459
	 * @param string $itemSource
460
	 * @param int $format (optional) Format type must be defined by the backend
461
	 * @param mixed $parameters
462
	 * @param boolean $includeCollections
463
	 * @param string $shareWith (optional) define against which user should be checked, default: current user
464
	 * @return array
465
	 */
466 161
	public static function getItemSharedWithBySource($itemType, $itemSource, $format = self::FORMAT_NONE,
467
													 $parameters = null, $includeCollections = false, $shareWith = null) {
468 161
		$shareWith = ($shareWith === null) ? \OC_User::getUser() : $shareWith;
469 161
		return self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups, $shareWith, null, $format,
470 161
			$parameters, 1, $includeCollections, true);
471
	}
472
473
	/**
474
	 * Get the item of item type shared by a link
475
	 * @param string $itemType
476
	 * @param string $itemSource
477
	 * @param string $uidOwner Owner of link
478
	 * @return array
479
	 */
480
	public static function getItemSharedWithByLink($itemType, $itemSource, $uidOwner) {
481
		return self::getItems($itemType, $itemSource, self::SHARE_TYPE_LINK, null, $uidOwner, self::FORMAT_NONE,
482
			null, 1);
483
	}
484
485
	/**
486
	 * Based on the given token the share information will be returned - password protected shares will be verified
487
	 * @param string $token
488
	 * @param bool $checkPasswordProtection
489
	 * @return array|boolean false will be returned in case the token is unknown or unauthorized
490
	 */
491 9
	public static function getShareByToken($token, $checkPasswordProtection = true) {
492 9
		$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `token` = ?', 1);
493 9
		$result = $query->execute(array($token));
494 9
		if (\OC_DB::isError($result)) {
495
			\OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', token=' . $token, \OCP\Util::ERROR);
496
		}
497 9
		$row = $result->fetchRow();
498 9
		if ($row === false) {
499 2
			return false;
500
		}
501 9
		if (is_array($row) and self::expireItem($row)) {
502 1
			return false;
503
		}
504
505
		// password protected shares need to be authenticated
506 9
		if ($checkPasswordProtection && !\OCP\Share::checkPasswordProtectedShare($row)) {
507
			return false;
508
		}
509
510 9
		return $row;
511
	}
512
513
	/**
514
	 * resolves reshares down to the last real share
515
	 * @param array $linkItem
516
	 * @return array file owner
517
	 */
518 4
	public static function resolveReShare($linkItem)
519
	{
520 4
		if (isset($linkItem['parent'])) {
521
			$parent = $linkItem['parent'];
522 View Code Duplication
			while (isset($parent)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
523
				$query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `id` = ?', 1);
524
				$item = $query->execute(array($parent))->fetchRow();
525
				if (isset($item['parent'])) {
526
					$parent = $item['parent'];
527
				} else {
528
					return $item;
529
				}
530
			}
531
		}
532 4
		return $linkItem;
533
	}
534
535
536
	/**
537
	 * Get the shared items of item type owned by the current user
538
	 * @param string $itemType
539
	 * @param int $format (optional) Format type must be defined by the backend
540
	 * @param mixed $parameters
541
	 * @param int $limit Number of items to return (optional) Returns all by default
542
	 * @param boolean $includeCollections
543
	 * @return mixed Return depends on format
544
	 */
545 5
	public static function getItemsShared($itemType, $format = self::FORMAT_NONE, $parameters = null,
546
										  $limit = -1, $includeCollections = false) {
547 5
		return self::getItems($itemType, null, null, null, \OC_User::getUser(), $format,
548 5
			$parameters, $limit, $includeCollections);
549
	}
550
551
	/**
552
	 * Get the shared item of item type owned by the current user
553
	 * @param string $itemType
554
	 * @param string $itemSource
555
	 * @param int $format (optional) Format type must be defined by the backend
556
	 * @param mixed $parameters
557
	 * @param boolean $includeCollections
558
	 * @return mixed Return depends on format
559
	 */
560 96
	public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
561
										 $parameters = null, $includeCollections = false) {
562 96
		return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), $format,
563 96
			$parameters, -1, $includeCollections);
564
	}
565
566
	/**
567
	 * Get all users an item is shared with
568
	 * @param string $itemType
569
	 * @param string $itemSource
570
	 * @param string $uidOwner
571
	 * @param boolean $includeCollections
572
	 * @param boolean $checkExpireDate
573
	 * @return array Return array of users
574
	 */
575
	public static function getUsersItemShared($itemType, $itemSource, $uidOwner, $includeCollections = false, $checkExpireDate = true) {
576
577
		$users = array();
578
		$items = self::getItems($itemType, $itemSource, null, null, $uidOwner, self::FORMAT_NONE, null, -1, $includeCollections, false, $checkExpireDate);
579
		if ($items) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $items of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
580
			foreach ($items as $item) {
581
				if ((int)$item['share_type'] === self::SHARE_TYPE_USER) {
582
					$users[] = $item['share_with'];
583
				} else if ((int)$item['share_type'] === self::SHARE_TYPE_GROUP) {
584
					$users = array_merge($users, \OC_Group::usersInGroup($item['share_with']));
585
				}
586
			}
587
		}
588
		return $users;
589
	}
590
591
	/**
592
	 * Share an item with a user, group, or via private link
593
	 * @param string $itemType
594
	 * @param string $itemSource
595
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
596
	 * @param string $shareWith User or group the item is being shared with
597
	 * @param int $permissions CRUDS
598
	 * @param string $itemSourceName
599
	 * @param \DateTime $expirationDate
600
	 * @param bool $passwordChanged
601
	 * @return boolean|string Returns true on success or false on failure, Returns token on success for links
602
	 * @throws \OC\HintException when the share type is remote and the shareWith is invalid
603
	 * @throws \Exception
604
	 */
605 175
	public static function shareItem($itemType, $itemSource, $shareType, $shareWith, $permissions, $itemSourceName = null, \DateTime $expirationDate = null, $passwordChanged = null) {
606
607 175
		$backend = self::getBackend($itemType);
608 174
		$l = \OC::$server->getL10N('lib');
609
610 174
		if ($backend->isShareTypeAllowed($shareType) === false) {
611
			$message = 'Sharing %s failed, because the backend does not allow shares from type %i';
612
			$message_t = $l->t('Sharing %s failed, because the backend does not allow shares from type %i', array($itemSourceName, $shareType));
613
			\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareType), \OCP\Util::DEBUG);
614
			throw new \Exception($message_t);
615
		}
616
617 174
		$uidOwner = \OC_User::getUser();
618 174
		$shareWithinGroupOnly = self::shareWithGroupMembersOnly();
619
620 174
		if (is_null($itemSourceName)) {
621 161
			$itemSourceName = $itemSource;
622 161
		}
623 174
		$itemName = $itemSourceName;
624
625
		// check if file can be shared
626 174
		if ($itemType === 'file' or $itemType === 'folder') {
627 143
			$path = \OC\Files\Filesystem::getPath($itemSource);
628 143
			$itemName = $path;
629
630
			// verify that the file exists before we try to share it
631 143 View Code Duplication
			if (!$path) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
632 2
				$message = 'Sharing %s failed, because the file does not exist';
633 2
				$message_t = $l->t('Sharing %s failed, because the file does not exist', array($itemSourceName));
634 2
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
635 2
				throw new \Exception($message_t);
636
			}
637
			// verify that the user has share permission
638 141 View Code Duplication
			if (!\OC\Files\Filesystem::isSharable($path) || \OCP\Util::isSharingDisabledForUser()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
639 1
				$message = 'You are not allowed to share %s';
640 1
				$message_t = $l->t('You are not allowed to share %s', [$path]);
641 1
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $path), \OCP\Util::DEBUG);
642 1
				throw new \Exception($message_t);
643
			}
644 141
		}
645
646
		//verify that we don't share a folder which already contains a share mount point
647 172
		if ($itemType === 'folder') {
648 107
			$path = '/' . $uidOwner . '/files' . \OC\Files\Filesystem::getPath($itemSource) . '/';
649 107
			$mountManager = \OC\Files\Filesystem::getMountManager();
650 107
			$mounts = $mountManager->findIn($path);
651 107
			foreach ($mounts as $mount) {
652 1
				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
653 1
					$message = 'Sharing "' . $itemSourceName . '" failed, because it contains files shared with you!';
654 1
					\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
655 1
					throw new \Exception($message);
656
				}
657
658 107
			}
659 107
		}
660
661
		// single file shares should never have delete permissions
662 172
		if ($itemType === 'file') {
663 76
			$permissions = (int)$permissions & ~\OCP\Constants::PERMISSION_DELETE;
664 76
		}
665
666
		//Validate expirationDate
667 172
		if ($expirationDate !== null) {
668
			try {
669
				/*
670
				 * Reuse the validateExpireDate.
671
				 * We have to pass time() since the second arg is the time
672
				 * the file was shared, since it is not shared yet we just use
673
				 * the current time.
674
				 */
675 4
				$expirationDate = self::validateExpireDate($expirationDate->format('Y-m-d'), time(), $itemType, $itemSource);
676 4
			} catch (\Exception $e) {
677 2
				throw new \OC\HintException($e->getMessage(), $e->getMessage(), 404);
678
			}
679 2
		}
680
681
		// Verify share type and sharing conditions are met
682 170
		if ($shareType === self::SHARE_TYPE_USER) {
683 119 View Code Duplication
			if ($shareWith == $uidOwner) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
684 2
				$message = 'Sharing %s failed, because you can not share with yourself';
685 2
				$message_t = $l->t('Sharing %s failed, because you can not share with yourself', [$itemName]);
686 2
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
687 2
				throw new \Exception($message_t);
688
			}
689 118 View Code Duplication
			if (!\OC_User::userExists($shareWith)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
690 1
				$message = 'Sharing %s failed, because the user %s does not exist';
691 1
				$message_t = $l->t('Sharing %s failed, because the user %s does not exist', array($itemSourceName, $shareWith));
692 1
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
693 1
				throw new \Exception($message_t);
694
			}
695 118
			if ($shareWithinGroupOnly) {
696
				$inGroup = array_intersect(\OC_Group::getUserGroups($uidOwner), \OC_Group::getUserGroups($shareWith));
697 View Code Duplication
				if (empty($inGroup)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
698
					$message = 'Sharing %s failed, because the user '
699
						.'%s is not a member of any groups that %s is a member of';
700
					$message_t = $l->t('Sharing %s failed, because the user %s is not a member of any groups that %s is a member of', array($itemName, $shareWith, $uidOwner));
701
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemName, $shareWith, $uidOwner), \OCP\Util::DEBUG);
702
					throw new \Exception($message_t);
703
				}
704
			}
705
			// Check if the item source is already shared with the user, either from the same owner or a different user
706 118 View Code Duplication
			if ($checkExists = self::getItems($itemType, $itemSource, self::$shareTypeUserAndGroups,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
707 118
				$shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
708
				// Only allow the same share to occur again if it is the same
709
				// owner and is not a user share, this use case is for increasing
710
				// permissions for a specific user
711 5
				if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
712 2
					$message = 'Sharing %s failed, because this item is already shared with %s';
713 2
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
714 2
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
715 2
					throw new \Exception($message_t);
716
				}
717 4
			}
718 118 View Code Duplication
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_USER,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
719 118
				$shareWith, null, self::FORMAT_NONE, null, 1, true, true)) {
720
				// Only allow the same share to occur again if it is the same
721
				// owner and is not a user share, this use case is for increasing
722
				// permissions for a specific user
723 1
				if ($checkExists['uid_owner'] != $uidOwner || $checkExists['share_type'] == $shareType) {
724
					$message = 'Sharing %s failed, because this item is already shared with user %s';
725
					$message_t = $l->t('Sharing %s failed, because this item is already shared with user %s', array($itemSourceName, $shareWith));
726
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::ERROR);
727
					throw new \Exception($message_t);
728
				}
729 1
			}
730 169
		} else if ($shareType === self::SHARE_TYPE_GROUP) {
731 26 View Code Duplication
			if (!\OC_Group::groupExists($shareWith)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
732 1
				$message = 'Sharing %s failed, because the group %s does not exist';
733 1
				$message_t = $l->t('Sharing %s failed, because the group %s does not exist', array($itemSourceName, $shareWith));
734 1
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
735 1
				throw new \Exception($message_t);
736
			}
737 26 View Code Duplication
			if ($shareWithinGroupOnly && !\OC_Group::inGroup($uidOwner, $shareWith)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
738
				$message = 'Sharing %s failed, because '
739 1
					.'%s is not a member of the group %s';
740 1
				$message_t = $l->t('Sharing %s failed, because %s is not a member of the group %s', array($itemSourceName, $uidOwner, $shareWith));
741 1
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $uidOwner, $shareWith), \OCP\Util::DEBUG);
742 1
				throw new \Exception($message_t);
743
			}
744
			// Check if the item source is already shared with the group, either from the same owner or a different user
745
			// The check for each user in the group is done inside the put() function
746 26
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_GROUP, $shareWith,
747 26
				null, self::FORMAT_NONE, null, 1, true, true)) {
748
749 1
				if ($checkExists['share_with'] === $shareWith && $checkExists['share_type'] === \OCP\Share::SHARE_TYPE_GROUP) {
750 1
					$message = 'Sharing %s failed, because this item is already shared with %s';
751 1
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
752 1
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
753 1
					throw new \Exception($message_t);
754
				}
755
			}
756
			// Convert share with into an array with the keys group and users
757 26
			$group = $shareWith;
758 26
			$shareWith = array();
759 26
			$shareWith['group'] = $group;
760 26
			$shareWith['users'] = array_diff(\OC_Group::usersInGroup($group), array($uidOwner));
761 74
		} else if ($shareType === self::SHARE_TYPE_LINK) {
762 40
			$updateExistingShare = false;
763 40
			if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_links', 'yes') == 'yes') {
764
765
				// IF the password is changed via the old ajax endpoint verify it before deleting the old share
766 40
				if ($passwordChanged === true) {
767
					self::verifyPassword($shareWith);
768
				}
769
770
				// when updating a link share
771
				// FIXME Don't delete link if we update it
772 40
				if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_LINK, null,
773 40
					$uidOwner, self::FORMAT_NONE, null, 1)) {
774
					// remember old token
775
					$oldToken = $checkExists['token'];
776
					$oldPermissions = $checkExists['permissions'];
777
					//delete the old share
778
					Helper::delete($checkExists['id']);
779
					$updateExistingShare = true;
780
				}
781
782 40
				if ($passwordChanged === null) {
783
					// Generate hash of password - same method as user passwords
784 40
					if (is_string($shareWith) && $shareWith !== '') {
785 8
						self::verifyPassword($shareWith);
786 8
						$shareWith = \OC::$server->getHasher()->hash($shareWith);
787 8
					} else {
788
						// reuse the already set password, but only if we change permissions
789
						// otherwise the user disabled the password protection
790 33
						if ($checkExists && (int)$permissions !== (int)$oldPermissions) {
0 ignored issues
show
Bug introduced by
The variable $oldPermissions does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug Best Practice introduced by
The expression $checkExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
791
							$shareWith = $checkExists['share_with'];
792
						}
793
					}
794 40
				} else {
795
					if ($passwordChanged === true) {
796
						if (is_string($shareWith) && $shareWith !== '') {
797
							self::verifyPassword($shareWith);
798
							$shareWith = \OC::$server->getHasher()->hash($shareWith);
799
						}
800
					} else if ($updateExistingShare) {
801
						$shareWith = $checkExists['share_with'];
802
					}
803
				}
804
805 40 View Code Duplication
				if (\OCP\Util::isPublicLinkPasswordRequired() && empty($shareWith)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
806 1
					$message = 'You need to provide a password to create a public link, only protected links are allowed';
807 1
					$message_t = $l->t('You need to provide a password to create a public link, only protected links are allowed');
808 1
					\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
809 1
					throw new \Exception($message_t);
810
				}
811
812 40
				if ($updateExistingShare === false &&
813 40
					self::isDefaultExpireDateEnabled() &&
814 40
					empty($expirationDate)) {
815 5
					$expirationDate = Helper::calcExpireDate();
816 5
				}
817
818
				// Generate token
819 40
				if (isset($oldToken)) {
820
					$token = $oldToken;
821
				} else {
822 40
					$token = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(self::TOKEN_LENGTH,
823 40
						\OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_UPPER.
824
						\OCP\Security\ISecureRandom::CHAR_DIGITS
825 40
					);
826
				}
827 40
				$result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions,
828 40
					null, $token, $itemSourceName, $expirationDate);
829 40
				if ($result) {
830 40
					return $token;
831
				} else {
832
					return false;
833
				}
834
			}
835
			$message = 'Sharing %s failed, because sharing with links is not allowed';
836
			$message_t = $l->t('Sharing %s failed, because sharing with links is not allowed', array($itemSourceName));
837
			\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
838
			throw new \Exception($message_t);
839 14
		} else if ($shareType === self::SHARE_TYPE_REMOTE) {
840
841
			/*
842
			 * Check if file is not already shared with the remote user
843
			 */
844 13
			if ($checkExists = self::getItems($itemType, $itemSource, self::SHARE_TYPE_REMOTE,
0 ignored issues
show
Unused Code introduced by
$checkExists is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
845 13
				$shareWith, $uidOwner, self::FORMAT_NONE, null, 1, true, true)) {
846 1
					$message = 'Sharing %s failed, because this item is already shared with %s';
847 1
					$message_t = $l->t('Sharing %s failed, because this item is already shared with %s', array($itemSourceName, $shareWith));
848 1
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
849 1
					throw new \Exception($message_t);
850
			}
851
852
			// don't allow federated shares if source and target server are the same
853 13
			list($user, $remote) = Helper::splitUserRemote($shareWith);
854 5
			$currentServer = self::removeProtocolFromUrl(\OC::$server->getURLGenerator()->getAbsoluteURL('/'));
855 5
			$currentUser = \OC::$server->getUserSession()->getUser()->getUID();
856 5 View Code Duplication
			if (Helper::isSameUserOnSameServer($user, $remote, $currentUser, $currentServer)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
857
				$message = 'Not allowed to create a federated share with the same user.';
858
				$message_t = $l->t('Not allowed to create a federated share with the same user');
859
				\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG);
860
				throw new \Exception($message_t);
861
			}
862
863 5
			$token = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(self::TOKEN_LENGTH, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_UPPER .
864 5
				\OCP\Security\ISecureRandom::CHAR_DIGITS);
865
866 5
			$shareWith = $user . '@' . $remote;
867 5
			$shareId = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, $token, $itemSourceName);
868
869 5
			$send = false;
870 5
			if ($shareId) {
871 5
				$send = self::sendRemoteShare($token, $shareWith, $itemSourceName, $shareId, $uidOwner);
872 5
			}
873
874 5
			if ($send === false) {
875
				$currentUser = \OC::$server->getUserSession()->getUser()->getUID();
876
				self::unshare($itemType, $itemSource, $shareType, $shareWith, $currentUser);
877
				$message_t = $l->t('Sharing %s failed, could not find %s, maybe the server is currently unreachable.', array($itemSourceName, $shareWith));
878
				throw new \Exception($message_t);
879
			}
880
881 5
			return $send;
882 View Code Duplication
		} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
883
			// Future share types need to include their own conditions
884 1
			$message = 'Share type %s is not valid for %s';
885 1
			$message_t = $l->t('Share type %s is not valid for %s', array($shareType, $itemSource));
886 1
			\OCP\Util::writeLog('OCP\Share', sprintf($message, $shareType, $itemSource), \OCP\Util::DEBUG);
887 1
			throw new \Exception($message_t);
888
		}
889
890
		// Put the item into the database
891 136
		$result = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, null, $itemSourceName, $expirationDate);
0 ignored issues
show
Bug introduced by
It seems like $shareWith defined by array() on line 758 can also be of type array<string,?,{"users":"array"}>; however, OC\Share\Share::put() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
892
893 136
		return $result ? true : false;
894
	}
895
896
	/**
897
	 * Unshare an item from a user, group, or delete a private link
898
	 * @param string $itemType
899
	 * @param string $itemSource
900
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
901
	 * @param string $shareWith User or group the item is being shared with
902
	 * @param string $owner owner of the share, if null the current user is used
903
	 * @return boolean true on success or false on failure
904
	 */
905 72
	public static function unshare($itemType, $itemSource, $shareType, $shareWith, $owner = null) {
906
907
		// check if it is a valid itemType
908 72
		self::getBackend($itemType);
909
910 71
		$items = self::getItemSharedWithUser($itemType, $itemSource, $shareWith, $owner, $shareType);
911
912 71
		$toDelete = array();
913 71
		$newParent = null;
914 71
		$currentUser = $owner ? $owner : \OC_User::getUser();
915 71
		foreach ($items as $item) {
916
			// delete the item with the expected share_type and owner
917 68
			if ((int)$item['share_type'] === (int)$shareType && $item['uid_owner'] === $currentUser) {
918 67
				$toDelete = $item;
919
				// if there is more then one result we don't have to delete the children
920
				// but update their parent. For group shares the new parent should always be
921
				// the original group share and not the db entry with the unique name
922 68
			} else if ((int)$item['share_type'] === self::$shareTypeGroupUserUnique) {
923
				$newParent = $item['parent'];
924
			} else {
925 1
				$newParent = $item['id'];
926
			}
927 71
		}
928
929 71
		if (!empty($toDelete)) {
930 67
			self::unshareItem($toDelete, $newParent);
931 67
			return true;
932
		}
933 4
		return false;
934
	}
935
936
	/**
937
	 * Unshare an item from all users, groups, and remove all links
938
	 * @param string $itemType
939
	 * @param string $itemSource
940
	 * @return boolean true on success or false on failure
941
	 */
942 3
	public static function unshareAll($itemType, $itemSource) {
943
		// Get all of the owners of shares of this item.
944 3
		$query = \OC_DB::prepare( 'SELECT `uid_owner` from `*PREFIX*share` WHERE `item_type`=? AND `item_source`=?' );
945 3
		$result = $query->execute(array($itemType, $itemSource));
946 3
		$shares = array();
947
		// Add each owner's shares to the array of all shares for this item.
948 3
		while ($row = $result->fetchRow()) {
949 3
			$shares = array_merge($shares, self::getItems($itemType, $itemSource, null, null, $row['uid_owner']));
950 3
		}
951 3
		if (!empty($shares)) {
952
			// Pass all the vars we have for now, they may be useful
953
			$hookParams = array(
954 3
				'itemType' => $itemType,
955 3
				'itemSource' => $itemSource,
956 3
				'shares' => $shares,
957 3
			);
958 3
			\OC_Hook::emit('OCP\Share', 'pre_unshareAll', $hookParams);
959 3
			foreach ($shares as $share) {
960 3
				self::unshareItem($share);
961 3
			}
962 3
			\OC_Hook::emit('OCP\Share', 'post_unshareAll', $hookParams);
963 3
			return true;
964
		}
965
		return false;
966
	}
967
968
	/**
969
	 * Unshare an item shared with the current user
970
	 * @param string $itemType
971
	 * @param string $itemOrigin Item target or source
972
	 * @param boolean $originIsSource true if $itemOrigin is the source, false if $itemOrigin is the target (optional)
973
	 * @return boolean true on success or false on failure
974
	 *
975
	 * Unsharing from self is not allowed for items inside collections
976
	 */
977 7
	public static function unshareFromSelf($itemType, $itemOrigin, $originIsSource = false) {
978 7
		$originType = ($originIsSource) ? 'source' : 'target';
979 7
		$uid = \OCP\User::getUser();
980
981 7
		if ($itemType === 'file' || $itemType === 'folder') {
982 5
			$statement = 'SELECT * FROM `*PREFIX*share` WHERE `item_type` = ? and `file_' . $originType . '` = ?';
983 5
		} else {
984 2
			$statement = 'SELECT * FROM `*PREFIX*share` WHERE `item_type` = ? and `item_' . $originType . '` = ?';
985
		}
986
987 7
		$query = \OCP\DB::prepare($statement);
988 7
		$result = $query->execute(array($itemType, $itemOrigin));
989
990 7
		$shares = $result->fetchAll();
991
992 7
		$listOfUnsharedItems = array();
993
994 7
		$itemUnshared = false;
995 7
		foreach ($shares as $share) {
996 7
			if ((int)$share['share_type'] === \OCP\Share::SHARE_TYPE_USER &&
997 7
				$share['share_with'] === $uid) {
998 6
				$deletedShares = Helper::delete($share['id']);
999
				$shareTmp = array(
1000 6
					'id' => $share['id'],
1001 6
					'shareWith' => $share['share_with'],
1002 6
					'itemTarget' => $share['item_target'],
1003 6
					'itemType' => $share['item_type'],
1004 6
					'shareType' => (int)$share['share_type'],
1005 6
				);
1006 6
				if (isset($share['file_target'])) {
1007 5
					$shareTmp['fileTarget'] = $share['file_target'];
1008 5
				}
1009 6
				$listOfUnsharedItems = array_merge($listOfUnsharedItems, $deletedShares, array($shareTmp));
1010 6
				$itemUnshared = true;
1011 6
				break;
1012 3
			} elseif ((int)$share['share_type'] === \OCP\Share::SHARE_TYPE_GROUP) {
1013 3
				if (\OC_Group::inGroup($uid, $share['share_with'])) {
1014 3
					$groupShare = $share;
1015 3
				}
1016 3
			} elseif ((int)$share['share_type'] === self::$shareTypeGroupUserUnique &&
1017 3
				$share['share_with'] === $uid) {
1018 2
				$uniqueGroupShare = $share;
1019 2
			}
1020 7
		}
1021
1022 7
		if (!$itemUnshared && isset($groupShare) && !isset($uniqueGroupShare)) {
1023 3
			$query = \OC_DB::prepare('INSERT INTO `*PREFIX*share`'
1024
				.' (`item_type`, `item_source`, `item_target`, `parent`, `share_type`,'
1025 3
				.' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`, `file_target`)'
1026 3
				.' VALUES (?,?,?,?,?,?,?,?,?,?,?)');
1027 3
			$query->execute(array($groupShare['item_type'], $groupShare['item_source'], $groupShare['item_target'],
1028 3
				$groupShare['id'], self::$shareTypeGroupUserUnique,
1029 3
				\OC_User::getUser(), $groupShare['uid_owner'], 0, $groupShare['stime'], $groupShare['file_source'],
1030 3
				$groupShare['file_target']));
1031
			$shareTmp = array(
1032 3
				'id' => $groupShare['id'],
1033 3
				'shareWith' => $groupShare['share_with'],
1034 3
				'itemTarget' => $groupShare['item_target'],
1035 3
				'itemType' => $groupShare['item_type'],
1036 3
				'shareType' => (int)$groupShare['share_type'],
1037 3
			);
1038 3
			if (isset($groupShare['file_target'])) {
1039 3
				$shareTmp['fileTarget'] = $groupShare['file_target'];
1040 3
			}
1041 3
			$listOfUnsharedItems = array_merge($listOfUnsharedItems, array($groupShare));
1042 3
			$itemUnshared = true;
1043 7
		} elseif (!$itemUnshared && isset($uniqueGroupShare)) {
1044 2
			$query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = ? WHERE `id` = ?');
1045 2
			$query->execute(array(0, $uniqueGroupShare['id']));
1046
			$shareTmp = array(
1047 2
				'id' => $uniqueGroupShare['id'],
1048 2
				'shareWith' => $uniqueGroupShare['share_with'],
1049 2
				'itemTarget' => $uniqueGroupShare['item_target'],
1050 2
				'itemType' => $uniqueGroupShare['item_type'],
1051 2
				'shareType' => (int)$uniqueGroupShare['share_type'],
1052 2
			);
1053 2
			if (isset($uniqueGroupShare['file_target'])) {
1054 2
				$shareTmp['fileTarget'] = $uniqueGroupShare['file_target'];
1055 2
			}
1056 2
			$listOfUnsharedItems = array_merge($listOfUnsharedItems, array($uniqueGroupShare));
1057 2
			$itemUnshared = true;
1058 2
		}
1059
1060 7
		if ($itemUnshared) {
1061 7
			\OC_Hook::emit('OCP\Share', 'post_unshareFromSelf',
1062 7
				array('unsharedItems' => $listOfUnsharedItems, 'itemType' => $itemType));
1063 7
		}
1064
1065 7
		return $itemUnshared;
1066
	}
1067
1068
	/**
1069
	 * sent status if users got informed by mail about share
1070
	 * @param string $itemType
1071
	 * @param string $itemSource
1072
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
1073
	 * @param string $recipient with whom was the file shared
1074
	 * @param boolean $status
1075
	 */
1076
	public static function setSendMailStatus($itemType, $itemSource, $shareType, $recipient, $status) {
1077
		$status = $status ? 1 : 0;
1078
1079
		$query = \OC_DB::prepare(
1080
			'UPDATE `*PREFIX*share`
1081
					SET `mail_send` = ?
1082
					WHERE `item_type` = ? AND `item_source` = ? AND `share_type` = ? AND `share_with` = ?');
1083
1084
		$result = $query->execute(array($status, $itemType, $itemSource, $shareType, $recipient));
1085
1086
		if($result === false) {
1087
			\OCP\Util::writeLog('OCP\Share', 'Couldn\'t set send mail status', \OCP\Util::ERROR);
1088
		}
1089
	}
1090
1091
	/**
1092
	 * Set the permissions of an item for a specific user or group
1093
	 * @param string $itemType
1094
	 * @param string $itemSource
1095
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
1096
	 * @param string $shareWith User or group the item is being shared with
1097
	 * @param int $permissions CRUDS permissions
1098
	 * @return boolean true on success or false on failure
1099
	 * @throws \Exception when trying to grant more permissions then the user has himself
1100
	 */
1101 6
	public static function setPermissions($itemType, $itemSource, $shareType, $shareWith, $permissions) {
1102 6
		$l = \OC::$server->getL10N('lib');
1103 6
		if ($item = self::getItems($itemType, $itemSource, $shareType, $shareWith,
1104 6
			\OC_User::getUser(), self::FORMAT_NONE, null, 1, false)) {
1105
			// Check if this item is a reshare and verify that the permissions
1106
			// granted don't exceed the parent shared item
1107 5
			if (isset($item['parent'])) {
1108 1
				$query = \OC_DB::prepare('SELECT `permissions` FROM `*PREFIX*share` WHERE `id` = ?', 1);
1109 1
				$result = $query->execute(array($item['parent']))->fetchRow();
1110 1
				if (~(int)$result['permissions'] & $permissions) {
1111
					$message = 'Setting permissions for %s failed,'
1112 1
						.' because the permissions exceed permissions granted to %s';
1113 1
					$message_t = $l->t('Setting permissions for %s failed, because the permissions exceed permissions granted to %s', array($itemSource, \OC_User::getUser()));
1114 1
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSource, \OC_User::getUser()), \OCP\Util::DEBUG);
1115 1
					throw new \Exception($message_t);
1116
				}
1117
			}
1118 5
			$query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = ? WHERE `id` = ?');
1119 5
			$query->execute(array($permissions, $item['id']));
1120 5
			if ($itemType === 'file' || $itemType === 'folder') {
1121 4
				\OC_Hook::emit('OCP\Share', 'post_update_permissions', array(
1122 4
					'itemType' => $itemType,
1123 4
					'itemSource' => $itemSource,
1124 4
					'shareType' => $shareType,
1125 4
					'shareWith' => $shareWith,
1126 4
					'uidOwner' => \OC_User::getUser(),
1127 4
					'permissions' => $permissions,
1128 4
					'path' => $item['path'],
1129
					'share' => $item
1130 4
				));
1131 4
			}
1132
			// Check if permissions were removed
1133 5
			if ($item['permissions'] & ~$permissions) {
1134
				// If share permission is removed all reshares must be deleted
1135 4
				if (($item['permissions'] & \OCP\Constants::PERMISSION_SHARE) && (~$permissions & \OCP\Constants::PERMISSION_SHARE)) {
1136
					// delete all shares, keep parent and group children
1137 3
					Helper::delete($item['id'], true, null, null, true);
1138 3
				} else {
1139 2
					$ids = array();
1140 2
					$items = [];
1141 2
					$parents = array($item['id']);
1142 2
					while (!empty($parents)) {
1143 2
						$parents = "'".implode("','", $parents)."'";
1144 2
						$query = \OC_DB::prepare('SELECT `id`, `permissions`, `item_type` FROM `*PREFIX*share`'
1145 2
							.' WHERE `parent` IN ('.$parents.')');
1146 2
						$result = $query->execute();
1147
						// Reset parents array, only go through loop again if
1148
						// items are found that need permissions removed
1149 2
						$parents = array();
1150 2
						while ($item = $result->fetchRow()) {
1151 2
							$items[] = $item;
1152
							// Check if permissions need to be removed
1153 2
							if ($item['permissions'] & ~$permissions) {
1154
								// Add to list of items that need permissions removed
1155 2
								$ids[] = $item['id'];
1156 2
								$parents[] = $item['id'];
1157 2
							}
1158 2
						}
1159 2
					}
1160
					// Remove the permissions for all reshares of this item
1161 2
					if (!empty($ids)) {
1162 2
						$ids = "'".implode("','", $ids)."'";
1163
						// TODO this should be done with Doctrine platform objects
1164 2
						if (\OC_Config::getValue( "dbtype") === 'oci') {
1165
							$andOp = 'BITAND(`permissions`, ?)';
1166
						} else {
1167 2
							$andOp = '`permissions` & ?';
1168
						}
1169 2
						$query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = '.$andOp
1170 2
							.' WHERE `id` IN ('.$ids.')');
1171 2
						$query->execute(array($permissions));
1172 2
					}
1173
1174 2
					foreach ($items as $item) {
1175 2
						\OC_Hook::emit('OCP\Share', 'post_update_permissions', ['share' => $item]);
1176 2
					}
1177
				}
1178 4
			}
1179
1180 5
			return true;
1181
		}
1182
		$message = 'Setting permissions for %s failed, because the item was not found';
1183
		$message_t = $l->t('Setting permissions for %s failed, because the item was not found', array($itemSource));
1184
1185
		\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSource), \OCP\Util::DEBUG);
1186
		throw new \Exception($message_t);
1187
	}
1188
1189
	/**
1190
	 * validate expiration date if it meets all constraints
1191
	 *
1192
	 * @param string $expireDate well formate date string, e.g. "DD-MM-YYYY"
1193
	 * @param string $shareTime timestamp when the file was shared
1194
	 * @param string $itemType
1195
	 * @param string $itemSource
1196
	 * @return \DateTime validated date
1197
	 * @throws \Exception when the expire date is in the past or further in the future then the enforced date
1198
	 */
1199 9
	private static function validateExpireDate($expireDate, $shareTime, $itemType, $itemSource) {
1200 9
		$l = \OC::$server->getL10N('lib');
1201 9
		$date = new \DateTime($expireDate);
1202 9
		$today = new \DateTime('now');
1203
1204
		// if the user doesn't provide a share time we need to get it from the database
1205
		// fall-back mode to keep API stable, because the $shareTime parameter was added later
1206 9
		$defaultExpireDateEnforced = \OCP\Util::isDefaultExpireDateEnforced();
1207 9
		if ($defaultExpireDateEnforced && $shareTime === null) {
1208
			$items = self::getItemShared($itemType, $itemSource);
1209
			$firstItem = reset($items);
1210
			$shareTime = (int)$firstItem['stime'];
1211
		}
1212
1213 9
		if ($defaultExpireDateEnforced) {
1214
			// initialize max date with share time
1215 4
			$maxDate = new \DateTime();
1216 4
			$maxDate->setTimestamp($shareTime);
1217 4
			$maxDays = \OCP\Config::getAppValue('core', 'shareapi_expire_after_n_days', '7');
1218 4
			$maxDate->add(new \DateInterval('P' . $maxDays . 'D'));
1219 4
			if ($date > $maxDate) {
1220 2
				$warning = 'Cannot set expiration date. Shares cannot expire later than ' . $maxDays . ' after they have been shared';
1221 2
				$warning_t = $l->t('Cannot set expiration date. Shares cannot expire later than %s after they have been shared', array($maxDays));
1222 2
				\OCP\Util::writeLog('OCP\Share', $warning, \OCP\Util::WARN);
1223 2
				throw new \Exception($warning_t);
1224
			}
1225 3
		}
1226
1227 8 View Code Duplication
		if ($date < $today) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1228 2
			$message = 'Cannot set expiration date. Expiration date is in the past';
1229 2
			$message_t = $l->t('Cannot set expiration date. Expiration date is in the past');
1230 2
			\OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::WARN);
1231 2
			throw new \Exception($message_t);
1232
		}
1233
1234 6
		return $date;
1235
	}
1236
1237
	/**
1238
	 * Set expiration date for a share
1239
	 * @param string $itemType
1240
	 * @param string $itemSource
1241
	 * @param string $date expiration date
1242
	 * @param int $shareTime timestamp from when the file was shared
1243
	 * @return boolean
1244
	 * @throws \Exception when the expire date is not set, in the past or further in the future then the enforced date
1245
	 */
1246 6
	public static function setExpirationDate($itemType, $itemSource, $date, $shareTime = null) {
1247 6
		$user = \OC_User::getUser();
1248 6
		$l = \OC::$server->getL10N('lib');
1249
1250 6
		if ($date == '') {
1251 2 View Code Duplication
			if (\OCP\Util::isDefaultExpireDateEnforced()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1252 2
				$warning = 'Cannot clear expiration date. Shares are required to have an expiration date.';
1253 2
				$warning_t = $l->t('Cannot clear expiration date. Shares are required to have an expiration date.');
1254 2
				\OCP\Util::writeLog('OCP\Share', $warning, \OCP\Util::WARN);
1255 2
				throw new \Exception($warning_t);
1256
			} else {
1257
				$date = null;
1258
			}
1259
		} else {
1260 5
			$date = self::validateExpireDate($date, $shareTime, $itemType, $itemSource);
1261
		}
1262 4
		$query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `expiration` = ? WHERE `item_type` = ? AND `item_source` = ?  AND `uid_owner` = ? AND `share_type` = ?');
1263 4
		$query->bindValue(1, $date, 'datetime');
1264 4
		$query->bindValue(2, $itemType);
1265 4
		$query->bindValue(3, $itemSource);
1266 4
		$query->bindValue(4, $user);
1267 4
		$query->bindValue(5, \OCP\Share::SHARE_TYPE_LINK);
1268
1269 4
		$query->execute();
1270
1271 4
		\OC_Hook::emit('OCP\Share', 'post_set_expiration_date', array(
1272 4
			'itemType' => $itemType,
1273 4
			'itemSource' => $itemSource,
1274 4
			'date' => $date,
1275
			'uidOwner' => $user
1276 4
		));
1277
1278 4
		return true;
1279
	}
1280
1281
	/**
1282
	 * Retrieve the owner of a connection
1283
	 *
1284
	 * @param IDBConnection $connection
1285
	 * @param int $shareId
1286
	 * @throws \Exception
1287
	 * @return string uid of share owner
1288
	 */
1289 7
	private static function getShareOwner(IDBConnection $connection, $shareId) {
1290 7
		$qb = $connection->getQueryBuilder();
1291
1292 7
		$qb->select('uid_owner')
1293 7
			->from('share')
1294 7
			->where($qb->expr()->eq('id', $qb->createParameter('shareId')))
1295 7
			->setParameter(':shareId', $shareId);
1296 7
		$result = $qb->execute();
1297 7
		$result = $result->fetch();
1298
1299 7
		if (empty($result)) {
1300 1
			throw new \Exception('Share not found');
1301
		}
1302
1303 6
		return $result['uid_owner'];
1304
	}
1305
1306
	/**
1307
	 * Set password for a public link share
1308
	 *
1309
	 * @param IUserSession $userSession
1310
	 * @param IDBConnection $connection
1311
	 * @param IConfig $config
1312
	 * @param int $shareId
1313
	 * @param string $password
1314
	 * @throws \Exception
1315
	 * @return boolean
1316
	 */
1317 8
	public static function setPassword(IUserSession $userSession,
1318
	                                   IDBConnection $connection,
1319
	                                   IConfig $config,
1320
	                                   $shareId, $password) {
1321 8
		$user = $userSession->getUser();
1322 8
		if (is_null($user)) {
1323 1
			throw new \Exception("User not logged in");
1324
		}
1325
1326 7
		$uid = self::getShareOwner($connection, $shareId);
1327
1328 6
		if ($uid !== $user->getUID()) {
1329 1
			throw new \Exception('Cannot update share of a different user');
1330
		}
1331
1332 5
		if ($password === '') {
1333 1
			$password = null;
1334 1
		}
1335
1336
		//If passwords are enforced the password can't be null
1337 5
		if (self::enforcePassword($config) && is_null($password)) {
1338 2
			throw new \Exception('Cannot remove password');
1339
		}
1340
1341 4
		self::verifyPassword($password);
1342
1343 4
		$qb = $connection->getQueryBuilder();
1344 4
		$qb->update('share')
1345 4
			->set('share_with', $qb->createParameter('pass'))
1346 4
			->where($qb->expr()->eq('id', $qb->createParameter('shareId')))
1347 4
			->setParameter(':pass', is_null($password) ? null : \OC::$server->getHasher()->hash($password))
1348 4
			->setParameter(':shareId', $shareId);
1349
1350 4
		$qb->execute();
1351
1352 4
		return true;
1353
	}
1354
1355
	/**
1356
	 * Checks whether a share has expired, calls unshareItem() if yes.
1357
	 * @param array $item Share data (usually database row)
1358
	 * @return boolean True if item was expired, false otherwise.
1359
	 */
1360 154
	protected static function expireItem(array $item) {
1361
1362 154
		$result = false;
1363
1364
		// only use default expiration date for link shares
1365 154
		if ((int) $item['share_type'] === self::SHARE_TYPE_LINK) {
1366
1367
			// calculate expiration date
1368 31
			if (!empty($item['expiration'])) {
1369 9
				$userDefinedExpire = new \DateTime($item['expiration']);
1370 9
				$expires = $userDefinedExpire->getTimestamp();
1371 9
			} else {
1372 23
				$expires = null;
1373
			}
1374
1375
1376
			// get default expiration settings
1377 31
			$defaultSettings = Helper::getDefaultExpireSetting();
1378 31
			$expires = Helper::calculateExpireDate($defaultSettings, $item['stime'], $expires);
1379
1380
1381 31
			if (is_int($expires)) {
1382 9
				$now = time();
1383 9
				if ($now > $expires) {
1384 3
					self::unshareItem($item);
1385 3
					$result = true;
1386 3
				}
1387 9
			}
1388 31
		}
1389 154
		return $result;
1390
	}
1391
1392
	/**
1393
	 * Unshares a share given a share data array
1394
	 * @param array $item Share data (usually database row)
1395
	 * @param int $newParent parent ID
1396
	 * @return null
1397
	 */
1398 72
	protected static function unshareItem(array $item, $newParent = null) {
1399
1400 72
		$shareType = (int)$item['share_type'];
1401 72
		$shareWith = null;
1402 72
		if ($shareType !== \OCP\Share::SHARE_TYPE_LINK) {
1403 64
			$shareWith = $item['share_with'];
1404 64
		}
1405
1406
		// Pass all the vars we have for now, they may be useful
1407
		$hookParams = array(
1408 72
			'id'            => $item['id'],
1409 72
			'itemType'      => $item['item_type'],
1410 72
			'itemSource'    => $item['item_source'],
1411 72
			'shareType'     => $shareType,
1412 72
			'shareWith'     => $shareWith,
1413 72
			'itemParent'    => $item['parent'],
1414 72
			'uidOwner'      => $item['uid_owner'],
1415 72
		);
1416 72
		if($item['item_type'] === 'file' || $item['item_type'] === 'folder') {
1417 58
			$hookParams['fileSource'] = $item['file_source'];
1418 58
			$hookParams['fileTarget'] = $item['file_target'];
1419 58
		}
1420
1421 72
		\OC_Hook::emit('OCP\Share', 'pre_unshare', $hookParams);
1422 72
		$deletedShares = Helper::delete($item['id'], false, null, $newParent);
1423 72
		$deletedShares[] = $hookParams;
1424 72
		$hookParams['deletedShares'] = $deletedShares;
1425 72
		\OC_Hook::emit('OCP\Share', 'post_unshare', $hookParams);
1426 72
		if ((int)$item['share_type'] === \OCP\Share::SHARE_TYPE_REMOTE && \OC::$server->getUserSession()->getUser()) {
1427 6
			list(, $remote) = Helper::splitUserRemote($item['share_with']);
1428 6
			self::sendRemoteUnshare($remote, $item['id'], $item['token']);
1429 6
		}
1430 72
	}
1431
1432
	/**
1433
	 * Get the backend class for the specified item type
1434
	 * @param string $itemType
1435
	 * @throws \Exception
1436
	 * @return \OCP\Share_Backend
1437
	 */
1438 486
	public static function getBackend($itemType) {
1439 486
		$l = \OC::$server->getL10N('lib');
1440 486
		if (isset(self::$backends[$itemType])) {
1441 483
			return self::$backends[$itemType];
1442 6
		} else if (isset(self::$backendTypes[$itemType]['class'])) {
1443 3
			$class = self::$backendTypes[$itemType]['class'];
1444 3
			if (class_exists($class)) {
1445 3
				self::$backends[$itemType] = new $class;
1446 3 View Code Duplication
				if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1447
					$message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
1448
					$message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', array($class));
1449
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), \OCP\Util::ERROR);
1450
					throw new \Exception($message_t);
1451
				}
1452 3
				return self::$backends[$itemType];
1453 View Code Duplication
			} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1454
				$message = 'Sharing backend %s not found';
1455
				$message_t = $l->t('Sharing backend %s not found', array($class));
1456
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $class), \OCP\Util::ERROR);
1457
				throw new \Exception($message_t);
1458
			}
1459
		}
1460 3
		$message = 'Sharing backend for %s not found';
1461 3
		$message_t = $l->t('Sharing backend for %s not found', array($itemType));
1462 3
		\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), \OCP\Util::ERROR);
1463 3
		throw new \Exception($message_t);
1464
	}
1465
1466
	/**
1467
	 * Check if resharing is allowed
1468
	 * @return boolean true if allowed or false
1469
	 *
1470
	 * Resharing is allowed by default if not configured
1471
	 */
1472 145
	public static function isResharingAllowed() {
1473 145
		if (!isset(self::$isResharingAllowed)) {
1474 1
			if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
1475 1
				self::$isResharingAllowed = true;
1476 1
			} else {
1477
				self::$isResharingAllowed = false;
1478
			}
1479 1
		}
1480 145
		return self::$isResharingAllowed;
1481
	}
1482
1483
	/**
1484
	 * Get a list of collection item types for the specified item type
1485
	 * @param string $itemType
1486
	 * @return array
1487
	 */
1488 170
	private static function getCollectionItemTypes($itemType) {
1489 170
		$collectionTypes = array($itemType);
1490 170
		foreach (self::$backendTypes as $type => $backend) {
1491 170
			if (in_array($backend['collectionOf'], $collectionTypes)) {
1492 86
				$collectionTypes[] = $type;
1493 86
			}
1494 170
		}
1495
		// TODO Add option for collections to be collection of themselves, only 'folder' does it now...
1496 170
		if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
1497 116
			unset($collectionTypes[0]);
1498 116
		}
1499
		// Return array if collections were found or the item type is a
1500
		// collection itself - collections can be inside collections
1501 170
		if (count($collectionTypes) > 0) {
1502 140
			return $collectionTypes;
1503
		}
1504 30
		return false;
1505
	}
1506
1507
	/**
1508
	 * Get the owners of items shared with a user.
1509
	 *
1510
	 * @param string $user The user the items are shared with.
1511
	 * @param string $type The type of the items shared with the user.
1512
	 * @param boolean $includeCollections Include collection item types (optional)
1513
	 * @param boolean $includeOwner include owner in the list of users the item is shared with (optional)
1514
	 * @return array
1515
	 */
1516 1
	public static function getSharedItemsOwners($user, $type, $includeCollections = false, $includeOwner = false) {
1517
		// First, we find out if $type is part of a collection (and if that collection is part of
1518
		// another one and so on).
1519 1
		$collectionTypes = array();
1520 1
		if (!$includeCollections || !$collectionTypes = self::getCollectionItemTypes($type)) {
1521 1
			$collectionTypes[] = $type;
1522 1
		}
1523
1524
		// Of these collection types, along with our original $type, we make a
1525
		// list of the ones for which a sharing backend has been registered.
1526
		// FIXME: Ideally, we wouldn't need to nest getItemsSharedWith in this loop but just call it
1527
		// with its $includeCollections parameter set to true. Unfortunately, this fails currently.
1528 1
		$allMaybeSharedItems = array();
1529 1
		foreach ($collectionTypes as $collectionType) {
0 ignored issues
show
Bug introduced by
The expression $collectionTypes of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1530 1
			if (isset(self::$backends[$collectionType])) {
1531 1
				$allMaybeSharedItems[$collectionType] = self::getItemsSharedWithUser(
1532 1
					$collectionType,
1533 1
					$user,
1534
					self::FORMAT_NONE
1535 1
				);
1536 1
			}
1537 1
		}
1538
1539 1
		$owners = array();
1540 1
		if ($includeOwner) {
1541
			$owners[] = $user;
1542
		}
1543
1544
		// We take a look at all shared items of the given $type (or of the collections it is part of)
1545
		// and find out their owners. Then, we gather the tags for the original $type from all owners,
1546
		// and return them as elements of a list that look like "Tag (owner)".
1547 1
		foreach ($allMaybeSharedItems as $collectionType => $maybeSharedItems) {
1548 1
			foreach ($maybeSharedItems as $sharedItem) {
1549 1
				if (isset($sharedItem['id'])) { //workaround for https://github.com/owncloud/core/issues/2814
1550 1
					$owners[] = $sharedItem['uid_owner'];
1551 1
				}
1552 1
			}
1553 1
		}
1554
1555 1
		return $owners;
1556
	}
1557
1558
	/**
1559
	 * Get shared items from the database
1560
	 * @param string $itemType
1561
	 * @param string $item Item source or target (optional)
1562
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
1563
	 * @param string $shareWith User or group the item is being shared with
1564
	 * @param string $uidOwner User that is the owner of shared items (optional)
1565
	 * @param int $format Format to convert items to with formatItems() (optional)
1566
	 * @param mixed $parameters to pass to formatItems() (optional)
1567
	 * @param int $limit Number of items to return, -1 to return all matches (optional)
1568
	 * @param boolean $includeCollections Include collection item types (optional)
1569
	 * @param boolean $itemShareWithBySource (optional)
1570
	 * @param boolean $checkExpireDate
1571
	 * @return array
1572
	 *
1573
	 * See public functions getItem(s)... for parameter usage
1574
	 *
1575
	 */
1576 484
	public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
1577
									$uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
1578
									$includeCollections = false, $itemShareWithBySource = false, $checkExpireDate  = true) {
1579 484
		if (!self::isEnabled()) {
1580
			return array();
1581
		}
1582 484
		$backend = self::getBackend($itemType);
1583 483
		$collectionTypes = false;
1584
		// Get filesystem root to add it to the file target and remove from the
1585
		// file source, match file_source with the file cache
1586 483
		if ($itemType == 'file' || $itemType == 'folder') {
1587 454
			if(!is_null($uidOwner)) {
1588 89
				$root = \OC\Files\Filesystem::getRoot();
1589 89
			} else {
1590 452
				$root = '';
1591
			}
1592 454
			$where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
1593 454
			if (!isset($item)) {
1594 449
				$where .= ' AND `file_target` IS NOT NULL ';
1595 449
			}
1596 454
			$where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
1597 454
			$fileDependent = true;
1598 454
			$queryArgs = array();
1599 454
		} else {
1600 30
			$fileDependent = false;
1601 30
			$root = '';
1602 30
			$collectionTypes = self::getCollectionItemTypes($itemType);
1603 30
			if ($includeCollections && !isset($item) && $collectionTypes) {
1604
				// If includeCollections is true, find collections of this item type, e.g. a music album contains songs
1605
				if (!in_array($itemType, $collectionTypes)) {
1606
					$itemTypes = array_merge(array($itemType), $collectionTypes);
1607
				} else {
1608
					$itemTypes = $collectionTypes;
1609
				}
1610
				$placeholders = join(',', array_fill(0, count($itemTypes), '?'));
1611
				$where = ' WHERE `item_type` IN ('.$placeholders.'))';
1612
				$queryArgs = $itemTypes;
1613
			} else {
1614 30
				$where = ' WHERE `item_type` = ?';
1615 30
				$queryArgs = array($itemType);
1616
			}
1617
		}
1618 483
		if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1619
			$where .= ' AND `share_type` != ?';
1620
			$queryArgs[] = self::SHARE_TYPE_LINK;
1621
		}
1622 483
		if (isset($shareType)) {
1623
			// Include all user and group items
1624 481
			if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
1625 473
				$where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
1626 473
				$queryArgs[] = self::SHARE_TYPE_USER;
1627 473
				$queryArgs[] = self::$shareTypeGroupUserUnique;
1628 473
				$queryArgs[] = $shareWith;
1629 473
				$groups = \OC_Group::getUserGroups($shareWith);
1630 473
				if (!empty($groups)) {
1631 319
					$placeholders = join(',', array_fill(0, count($groups), '?'));
1632 319
					$where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
1633 319
					$queryArgs[] = self::SHARE_TYPE_GROUP;
1634 319
					$queryArgs = array_merge($queryArgs, $groups);
1635 319
				}
1636 473
				$where .= ')';
1637
				// Don't include own group shares
1638 473
				$where .= ' AND `uid_owner` != ?';
1639 473
				$queryArgs[] = $shareWith;
1640 473
			} else {
1641 168
				$where .= ' AND `share_type` = ?';
1642 168
				$queryArgs[] = $shareType;
1643 168
				if (isset($shareWith)) {
1644 149
					$where .= ' AND `share_with` = ?';
1645 149
					$queryArgs[] = $shareWith;
1646 149
				}
1647
			}
1648 481
		}
1649 483
		if (isset($uidOwner)) {
1650 115
			$where .= ' AND `uid_owner` = ?';
1651 115
			$queryArgs[] = $uidOwner;
1652 115
			if (!isset($shareType)) {
1653
				// Prevent unique user targets for group shares from being selected
1654 96
				$where .= ' AND `share_type` != ?';
1655 96
				$queryArgs[] = self::$shareTypeGroupUserUnique;
1656 96
			}
1657 115
			if ($fileDependent) {
1658 89
				$column = 'file_source';
1659 89
			} else {
1660 26
				$column = 'item_source';
1661
			}
1662 115
		} else {
1663 473
			if ($fileDependent) {
1664 452
				$column = 'file_target';
1665 452
			} else {
1666 22
				$column = 'item_target';
1667
			}
1668
		}
1669 483
		if (isset($item)) {
1670 170
			$collectionTypes = self::getCollectionItemTypes($itemType);
1671 170
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
1672
				$where .= ' AND (';
1673
			} else {
1674 170
				$where .= ' AND';
1675
			}
1676
			// If looking for own shared items, check item_source else check item_target
1677 170
			if (isset($uidOwner) || $itemShareWithBySource) {
1678
				// If item type is a file, file source needs to be checked in case the item was converted
1679 170
				if ($fileDependent) {
1680 140
					$where .= ' `file_source` = ?';
1681 140
					$column = 'file_source';
1682 140
				} else {
1683 30
					$where .= ' `item_source` = ?';
1684 30
					$column = 'item_source';
1685
				}
1686 170
			} else {
1687 9
				if ($fileDependent) {
1688
					$where .= ' `file_target` = ?';
1689
					$item = \OC\Files\Filesystem::normalizePath($item);
1690
				} else {
1691 9
					$where .= ' `item_target` = ?';
1692
				}
1693
			}
1694 170
			$queryArgs[] = $item;
1695 170
			if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
1696
				$placeholders = join(',', array_fill(0, count($collectionTypes), '?'));
1697
				$where .= ' OR `item_type` IN ('.$placeholders.'))';
1698
				$queryArgs = array_merge($queryArgs, $collectionTypes);
1699
			}
1700 170
		}
1701
1702 483
		if ($shareType == self::$shareTypeUserAndGroups && $limit === 1) {
1703
			// Make sure the unique user target is returned if it exists,
1704
			// unique targets should follow the group share in the database
1705
			// If the limit is not 1, the filtering can be done later
1706 160
			$where .= ' ORDER BY `*PREFIX*share`.`id` DESC';
1707 160
		} else {
1708 483
			$where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
1709
		}
1710
1711 483
		if ($limit != -1 && !$includeCollections) {
1712
			// The limit must be at least 3, because filtering needs to be done
1713 50
			if ($limit < 3) {
1714 50
				$queryLimit = 3;
1715 50
			} else {
1716
				$queryLimit = $limit;
1717
			}
1718 50
		} else {
1719 483
			$queryLimit = null;
1720
		}
1721 483
		$select = self::createSelectStatement($format, $fileDependent, $uidOwner);
1722 483
		$root = strlen($root);
1723 483
		$query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
1724 483
		$result = $query->execute($queryArgs);
1725 483 View Code Duplication
		if (\OC_DB::isError($result)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1726
			\OCP\Util::writeLog('OCP\Share',
1727
				\OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
1728
				\OCP\Util::ERROR);
1729
		}
1730 483
		$items = array();
1731 483
		$targets = array();
1732 483
		$switchedItems = array();
1733 483
		$mounts = array();
1734 483
		while ($row = $result->fetchRow()) {
1735 145
			self::transformDBResults($row);
1736
			// Filter out duplicate group shares for users with unique targets
1737 145
			if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
1738 1
				continue;
1739
			}
1740 145
			if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
1741 7
				$row['share_type'] = self::SHARE_TYPE_GROUP;
1742 7
				$row['unique_name'] = true; // remember that we use a unique name for this user
1743 7
				$row['share_with'] = $items[$row['parent']]['share_with'];
1744
				// if the group share was unshared from the user we keep the permission, otherwise
1745
				// we take the permission from the parent because this is always the up-to-date
1746
				// permission for the group share
1747 7
				if ($row['permissions'] > 0) {
1748 5
					$row['permissions'] = $items[$row['parent']]['permissions'];
1749 5
				}
1750
				// Remove the parent group share
1751 7
				unset($items[$row['parent']]);
1752 7
				if ($row['permissions'] == 0) {
1753 3
					continue;
1754
				}
1755 145
			} else if (!isset($uidOwner)) {
1756
				// Check if the same target already exists
1757 112
				if (isset($targets[$row['id']])) {
1758
					// Check if the same owner shared with the user twice
1759
					// through a group and user share - this is allowed
1760 3
					$id = $targets[$row['id']];
1761 3
					if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
1762
						// Switch to group share type to ensure resharing conditions aren't bypassed
1763 2
						if ($items[$id]['share_type'] != self::SHARE_TYPE_GROUP) {
1764 2
							$items[$id]['share_type'] = self::SHARE_TYPE_GROUP;
1765 2
							$items[$id]['share_with'] = $row['share_with'];
1766 2
						}
1767
						// Switch ids if sharing permission is granted on only
1768
						// one share to ensure correct parent is used if resharing
1769 2
						if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
1770 2
							&& (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
1771
							$items[$row['id']] = $items[$id];
1772
							$switchedItems[$id] = $row['id'];
1773
							unset($items[$id]);
1774
							$id = $row['id'];
1775
						}
1776 2
						$items[$id]['permissions'] |= (int)$row['permissions'];
1777
1778 2
					}
1779 3
					continue;
1780 112
				} elseif (!empty($row['parent'])) {
1781 35
					$targets[$row['parent']] = $row['id'];
1782 35
				}
1783 112
			}
1784
			// Remove root from file source paths if retrieving own shared items
1785 145
			if (isset($uidOwner) && isset($row['path'])) {
1786 39
				if (isset($row['parent'])) {
1787 6
					$query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?');
1788 6
					$parentResult = $query->execute(array($row['parent']));
1789 6
					if (\OC_DB::isError($result)) {
1790
						\OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
1791
							\OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
1792
							\OCP\Util::ERROR);
1793
					} else {
1794 6
						$parentRow = $parentResult->fetchRow();
1795 6
						$tmpPath = $parentRow['file_target'];
1796
						// find the right position where the row path continues from the target path
1797 6
						$pos = strrpos($row['path'], $parentRow['file_target']);
1798 6
						$subPath = substr($row['path'], $pos);
1799 6
						$splitPath = explode('/', $subPath);
1800 6
						foreach (array_slice($splitPath, 2) as $pathPart) {
1801 2
							$tmpPath = $tmpPath . '/' . $pathPart;
1802 6
						}
1803 6
						$row['path'] = $tmpPath;
1804
					}
1805 6
				} else {
1806 33
					if (!isset($mounts[$row['storage']])) {
1807 33
						$mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
1808 33
						if (is_array($mountPoints) && !empty($mountPoints)) {
1809 33
							$mounts[$row['storage']] = current($mountPoints);
1810 33
						}
1811 33
					}
1812 33
					if (!empty($mounts[$row['storage']])) {
1813 33
						$path = $mounts[$row['storage']]->getMountPoint().$row['path'];
1814 33
						$relPath = substr($path, $root); // path relative to data/user
1815 33
						$row['path'] = rtrim($relPath, '/');
1816 33
					}
1817
				}
1818 39
			}
1819
1820 145
			if($checkExpireDate) {
1821 145
				if (self::expireItem($row)) {
1822 2
					continue;
1823
				}
1824 145
			}
1825
			// Check if resharing is allowed, if not remove share permission
1826 145
			if (isset($row['permissions']) && (!self::isResharingAllowed() | \OC_Util::isSharingDisabledForUser())) {
1827 1
				$row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
1828 1
			}
1829
			// Add display names to result
1830 145
			$row['share_with_displayname'] = $row['share_with'];
1831 145
			if ( isset($row['share_with']) && $row['share_with'] != '' &&
1832 145
				$row['share_type'] === self::SHARE_TYPE_USER) {
1833 113
				$row['share_with_displayname'] = \OCP\User::getDisplayName($row['share_with']);
1834 145
			} else if(isset($row['share_with']) && $row['share_with'] != '' &&
1835 51
				$row['share_type'] === self::SHARE_TYPE_REMOTE) {
1836 5
				$addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD']);
1837 5
				foreach ($addressBookEntries as $entry) {
1838
					foreach ($entry['CLOUD'] as $cloudID) {
1839
						if ($cloudID === $row['share_with']) {
1840
							$row['share_with_displayname'] = $entry['FN'];
1841
						}
1842
					}
1843 5
				}
1844 5
			}
1845 145
			if ( isset($row['uid_owner']) && $row['uid_owner'] != '') {
1846 145
				$row['displayname_owner'] = \OCP\User::getDisplayName($row['uid_owner']);
1847 145
			}
1848
1849 145
			if ($row['permissions'] > 0) {
1850 145
				$items[$row['id']] = $row;
1851 145
			}
1852
1853 145
		}
1854
1855
		// group items if we are looking for items shared with the current user
1856 483
		if (isset($shareWith) && $shareWith === \OCP\User::getUser()) {
1857 447
			$items = self::groupItems($items, $itemType);
1858 447
		}
1859
1860 483
		if (!empty($items)) {
1861 145
			$collectionItems = array();
1862 145
			foreach ($items as &$row) {
1863
				// Return only the item instead of a 2-dimensional array
1864 145
				if ($limit == 1 && $row[$column] == $item && ($row['item_type'] == $itemType || $itemType == 'file')) {
1865 48
					if ($format == self::FORMAT_NONE) {
1866 44
						return $row;
1867
					} else {
1868 8
						break;
1869
					}
1870
				}
1871
				// Check if this is a collection of the requested item type
1872 144
				if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
1873
					if (($collectionBackend = self::getBackend($row['item_type']))
1874
						&& $collectionBackend instanceof \OCP\Share_Backend_Collection) {
1875
						// Collections can be inside collections, check if the item is a collection
1876
						if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
1877
							$collectionItems[] = $row;
1878
						} else {
1879
							$collection = array();
1880
							$collection['item_type'] = $row['item_type'];
1881
							if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
1882
								$collection['path'] = basename($row['path']);
1883
							}
1884
							$row['collection'] = $collection;
1885
							// Fetch all of the children sources
1886
							$children = $collectionBackend->getChildren($row[$column]);
0 ignored issues
show
Documentation introduced by
$row[$column] is of type array<string,?>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1887
							foreach ($children as $child) {
1888
								$childItem = $row;
1889
								$childItem['item_type'] = $itemType;
1890
								if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
1891
									$childItem['item_source'] = $child['source'];
1892
									$childItem['item_target'] = $child['target'];
1893
								}
1894
								if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
1895
									if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
1896
										$childItem['file_source'] = $child['source'];
1897
									} else { // TODO is this really needed if we already know that we use the file backend?
1898
										$meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
1899
										$childItem['file_source'] = $meta['fileid'];
1900
									}
1901
									$childItem['file_target'] =
1902
										\OC\Files\Filesystem::normalizePath($child['file_path']);
1903
								}
1904
								if (isset($item)) {
1905
									if ($childItem[$column] == $item) {
1906
										// Return only the item instead of a 2-dimensional array
1907
										if ($limit == 1) {
1908
											if ($format == self::FORMAT_NONE) {
1909
												return $childItem;
1910
											} else {
1911
												// Unset the items array and break out of both loops
1912
												$items = array();
1913
												$items[] = $childItem;
1914
												break 2;
1915
											}
1916
										} else {
1917
											$collectionItems[] = $childItem;
1918
										}
1919
									}
1920
								} else {
1921
									$collectionItems[] = $childItem;
1922
								}
1923
							}
1924
						}
1925
					}
1926
					// Remove collection item
1927
					$toRemove = $row['id'];
1928
					if (array_key_exists($toRemove, $switchedItems)) {
1929
						$toRemove = $switchedItems[$toRemove];
1930
					}
1931
					unset($items[$toRemove]);
1932 144
				} elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
1933
					// FIXME: Thats a dirty hack to improve file sharing performance,
1934
					// see github issue #10588 for more details
1935
					// Need to find a solution which works for all back-ends
1936 1
					$collectionBackend = self::getBackend($row['item_type']);
1937 1
					$sharedParents = $collectionBackend->getParents($row['item_source']);
1938 1
					foreach ($sharedParents as $parent) {
1939
						$collectionItems[] = $parent;
1940 1
					}
1941 1
				}
1942 144
			}
1943 144
			if (!empty($collectionItems)) {
1944
				$items = array_merge($items, $collectionItems);
1945
			}
1946
1947
			// filter out invalid items, these can appear when subshare entries exist
1948
			// for a group in which the requested user isn't a member any more
1949 144
			$items = array_filter($items, function($item) {
1950 144
				return $item['share_type'] !== self::$shareTypeGroupUserUnique;
1951 144
			});
1952
1953 144
			return self::formatResult($items, $column, $backend, $format, $parameters);
1954 483
		} elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
1955
			// FIXME: Thats a dirty hack to improve file sharing performance,
1956
			// see github issue #10588 for more details
1957
			// Need to find a solution which works for all back-ends
1958 140
			$collectionItems = array();
1959 140
			$collectionBackend = self::getBackend('folder');
1960 140
			$sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
1961 140
			foreach ($sharedParents as $parent) {
1962 33
				$collectionItems[] = $parent;
1963 140
			}
1964 140
			if ($limit === 1) {
1965 138
				return reset($collectionItems);
1966
			}
1967 45
			return self::formatResult($collectionItems, $column, $backend, $format, $parameters);
1968
		}
1969
1970 477
		return array();
1971
	}
1972
1973
	/**
1974
	 * group items with link to the same source
1975
	 *
1976
	 * @param array $items
1977
	 * @param string $itemType
1978
	 * @return array of grouped items
1979
	 */
1980 451
	protected static function groupItems($items, $itemType) {
1981
1982 451
		$fileSharing = ($itemType === 'file' || $itemType === 'folder') ? true : false;
1983
1984 451
		$result = array();
1985
1986 451
		foreach ($items as $item) {
1987 113
			$grouped = false;
1988 113
			foreach ($result as $key => $r) {
1989
				// for file/folder shares we need to compare file_source, otherwise we compare item_source
1990
				// only group shares if they already point to the same target, otherwise the file where shared
1991
				// before grouping of shares was added. In this case we don't group them toi avoid confusions
1992 49
				if (( $fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
1993 49
					(!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
1994
					// add the first item to the list of grouped shares
1995 7
					if (!isset($result[$key]['grouped'])) {
1996 7
						$result[$key]['grouped'][] = $result[$key];
1997 7
					}
1998 7
					$result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
1999 7
					$result[$key]['grouped'][] = $item;
2000 7
					$grouped = true;
2001 7
					break;
2002
				}
2003 113
			}
2004
2005 113
			if (!$grouped) {
2006 113
				$result[] = $item;
2007 113
			}
2008
2009 451
		}
2010
2011 451
		return $result;
2012
	}
2013
2014
	/**
2015
	 * Put shared item into the database
2016
	 * @param string $itemType Item type
2017
	 * @param string $itemSource Item source
2018
	 * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, or SHARE_TYPE_LINK
2019
	 * @param string $shareWith User or group the item is being shared with
2020
	 * @param string $uidOwner User that is the owner of shared item
2021
	 * @param int $permissions CRUDS permissions
2022
	 * @param boolean|array $parentFolder Parent folder target (optional)
2023
	 * @param string $token (optional)
2024
	 * @param string $itemSourceName name of the source item (optional)
2025
	 * @param \DateTime $expirationDate (optional)
2026
	 * @throws \Exception
2027
	 * @return mixed id of the new share or false
2028
	 */
2029 160
	private static function put($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
2030
								$permissions, $parentFolder = null, $token = null, $itemSourceName = null, \DateTime $expirationDate = null) {
2031
2032 160
		$queriesToExecute = array();
2033 160
		$suggestedItemTarget = null;
2034 160
		$groupFileTarget = $fileTarget = $suggestedFileTarget = $filePath = '';
2035 160
		$groupItemTarget = $itemTarget = $fileSource = $parent = 0;
2036
2037 160
		$result = self::checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate);
2038 160
		if(!empty($result)) {
2039 160
			$parent = $result['parent'];
2040 160
			$itemSource = $result['itemSource'];
2041 160
			$fileSource = $result['fileSource'];
2042 160
			$suggestedItemTarget = $result['suggestedItemTarget'];
2043 160
			$suggestedFileTarget = $result['suggestedFileTarget'];
2044 160
			$filePath = $result['filePath'];
2045 160
		}
2046
2047 160
		$isGroupShare = false;
2048 160
		if ($shareType == self::SHARE_TYPE_GROUP) {
2049 26
			$isGroupShare = true;
2050 26
			if (isset($shareWith['users'])) {
2051 26
				$users = $shareWith['users'];
2052 26
			} else {
2053
				$users = \OC_Group::usersInGroup($shareWith['group']);
2054
			}
2055
			// remove current user from list
2056 26
			if (in_array(\OCP\User::getUser(), $users)) {
2057
				unset($users[array_search(\OCP\User::getUser(), $users)]);
2058
			}
2059 26
			$groupItemTarget = Helper::generateTarget($itemType, $itemSource,
2060 26
				$shareType, $shareWith['group'], $uidOwner, $suggestedItemTarget);
2061 26
			$groupFileTarget = Helper::generateTarget($itemType, $itemSource,
2062 26
				$shareType, $shareWith['group'], $uidOwner, $filePath);
2063
2064
			// add group share to table and remember the id as parent
2065 26
			$queriesToExecute['groupShare'] = array(
2066 26
				'itemType'			=> $itemType,
2067 26
				'itemSource'		=> $itemSource,
2068 26
				'itemTarget'		=> $groupItemTarget,
2069 26
				'shareType'			=> $shareType,
2070 26
				'shareWith'			=> $shareWith['group'],
2071 26
				'uidOwner'			=> $uidOwner,
2072 26
				'permissions'		=> $permissions,
2073 26
				'shareTime'			=> time(),
2074 26
				'fileSource'		=> $fileSource,
2075 26
				'fileTarget'		=> $groupFileTarget,
2076 26
				'token'				=> $token,
2077 26
				'parent'			=> $parent,
2078 26
				'expiration'		=> $expirationDate,
2079
			);
2080
2081 26
		} else {
2082 148
			$users = array($shareWith);
2083 148
			$itemTarget = Helper::generateTarget($itemType, $itemSource, $shareType, $shareWith, $uidOwner,
2084 148
				$suggestedItemTarget);
2085
		}
2086
2087 160
		$run = true;
2088 160
		$error = '';
2089
		$preHookData = array(
2090 160
			'itemType' => $itemType,
2091 160
			'itemSource' => $itemSource,
2092 160
			'shareType' => $shareType,
2093 160
			'uidOwner' => $uidOwner,
2094 160
			'permissions' => $permissions,
2095 160
			'fileSource' => $fileSource,
2096 160
			'expiration' => $expirationDate,
2097 160
			'token' => $token,
2098 160
			'run' => &$run,
2099
			'error' => &$error
2100 160
		);
2101
2102 160
		$preHookData['itemTarget'] = ($isGroupShare) ? $groupItemTarget : $itemTarget;
2103 160
		$preHookData['shareWith'] = ($isGroupShare) ? $shareWith['group'] : $shareWith;
2104
2105 160
		\OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData);
2106
2107 160
		if ($run === false) {
2108
			throw new \Exception($error);
2109
		}
2110
2111 160
		foreach ($users as $user) {
0 ignored issues
show
Bug introduced by
The expression $users of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2112 160
			$sourceId = ($itemType === 'file' || $itemType === 'folder') ? $fileSource : $itemSource;
2113 160
			$sourceExists = self::getItemSharedWithBySource($itemType, $sourceId, self::FORMAT_NONE, null, true, $user);
2114
2115 160
			$userShareType = ($isGroupShare) ? self::$shareTypeGroupUserUnique : $shareType;
2116
2117 160
			if ($sourceExists && $sourceExists['item_source'] === $itemSource) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sourceExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2118 7
				$fileTarget = $sourceExists['file_target'];
2119 7
				$itemTarget = $sourceExists['item_target'];
2120
2121
				// for group shares we don't need a additional entry if the target is the same
2122 7
				if($isGroupShare && $groupItemTarget === $itemTarget) {
2123 2
					continue;
2124
				}
2125
2126 160
			} elseif(!$sourceExists && !$isGroupShare)  {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sourceExists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2127
2128 146
				$itemTarget = Helper::generateTarget($itemType, $itemSource, $userShareType, $user,
2129 146
					$uidOwner, $suggestedItemTarget, $parent);
2130 146
				if (isset($fileSource)) {
2131 126
					if ($parentFolder) {
2132
						if ($parentFolder === true) {
2133
							$fileTarget = Helper::generateTarget('file', $filePath, $userShareType, $user,
2134
								$uidOwner, $suggestedFileTarget, $parent);
2135
							if ($fileTarget != $groupFileTarget) {
2136
								$parentFolders[$user]['folder'] = $fileTarget;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$parentFolders was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parentFolders = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2137
							}
2138
						} else if (isset($parentFolder[$user])) {
2139
							$fileTarget = $parentFolder[$user]['folder'].$itemSource;
2140
							$parent = $parentFolder[$user]['id'];
2141
						}
2142
					} else {
2143 126
						$fileTarget = Helper::generateTarget('file', $filePath, $userShareType,
2144 126
							$user, $uidOwner, $suggestedFileTarget, $parent);
2145
					}
2146 126
				} else {
2147 20
					$fileTarget = null;
2148
				}
2149
2150 146
			} else {
2151
2152
				// group share which doesn't exists until now, check if we need a unique target for this user
2153
2154 27
				$itemTarget = Helper::generateTarget($itemType, $itemSource, self::SHARE_TYPE_USER, $user,
2155 27
					$uidOwner, $suggestedItemTarget, $parent);
2156
2157
				// do we also need a file target
2158 27
				if (isset($fileSource)) {
2159 23
					$fileTarget = Helper::generateTarget('file', $filePath, self::SHARE_TYPE_USER, $user,
2160 23
						$uidOwner, $suggestedFileTarget, $parent);
2161 23
				} else {
2162 4
					$fileTarget = null;
2163
				}
2164
2165 27
				if (($itemTarget === $groupItemTarget) &&
2166 27
					(!isset($fileSource) || $fileTarget === $groupFileTarget)) {
2167 25
					continue;
2168
				}
2169
			}
2170
2171 149
			$queriesToExecute[] = array(
2172 149
				'itemType'			=> $itemType,
2173 149
				'itemSource'		=> $itemSource,
2174 149
				'itemTarget'		=> $itemTarget,
2175 149
				'shareType'			=> $userShareType,
2176 149
				'shareWith'			=> $user,
2177 149
				'uidOwner'			=> $uidOwner,
2178 149
				'permissions'		=> $permissions,
2179 149
				'shareTime'			=> time(),
2180 149
				'fileSource'		=> $fileSource,
2181 149
				'fileTarget'		=> $fileTarget,
2182 149
				'token'				=> $token,
2183 149
				'parent'			=> $parent,
2184 149
				'expiration'		=> $expirationDate,
2185
			);
2186
2187 160
		}
2188
2189 160
		$id = false;
2190 160
		if ($isGroupShare) {
2191 26
			$id = self::insertShare($queriesToExecute['groupShare']);
2192
			// Save this id, any extra rows for this group share will need to reference it
2193 26
			$parent = \OC_DB::insertid('*PREFIX*share');
2194 26
			unset($queriesToExecute['groupShare']);
2195 26
		}
2196
2197 160
		foreach ($queriesToExecute as $shareQuery) {
2198 149
			$shareQuery['parent'] = $parent;
2199 149
			$id = self::insertShare($shareQuery);
2200 160
		}
2201
2202
		$postHookData = array(
2203 160
			'itemType' => $itemType,
2204 160
			'itemSource' => $itemSource,
2205 160
			'parent' => $parent,
2206 160
			'shareType' => $shareType,
2207 160
			'uidOwner' => $uidOwner,
2208 160
			'permissions' => $permissions,
2209 160
			'fileSource' => $fileSource,
2210 160
			'id' => $parent,
2211 160
			'token' => $token,
2212 160
			'expirationDate' => $expirationDate,
2213 160
		);
2214
2215 160
		$postHookData['shareWith'] = ($isGroupShare) ? $shareWith['group'] : $shareWith;
2216 160
		$postHookData['itemTarget'] = ($isGroupShare) ? $groupItemTarget : $itemTarget;
2217 160
		$postHookData['fileTarget'] = ($isGroupShare) ? $groupFileTarget : $fileTarget;
2218
2219 160
		\OC_Hook::emit('OCP\Share', 'post_shared', $postHookData);
2220
2221
2222 160
		return $id ? $id : false;
2223
	}
2224
2225 160
	private static function checkReshare($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, $itemSourceName, $expirationDate) {
2226 160
		$backend = self::getBackend($itemType);
2227
2228 160
		$l = \OC::$server->getL10N('lib');
2229 160
		$result = array();
2230
2231 160
		$column = ($itemType === 'file' || $itemType === 'folder') ? 'file_source' : 'item_source';
2232
2233 160
		$checkReshare = self::getItemSharedWithBySource($itemType, $itemSource, self::FORMAT_NONE, null, true);
2234 160
		if ($checkReshare) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $checkReshare of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2235
			// Check if attempting to share back to owner
2236 39
			if ($checkReshare['uid_owner'] == $shareWith && $shareType == self::SHARE_TYPE_USER) {
2237 3
				$message = 'Sharing %s failed, because the user %s is the original sharer';
2238 3
				$message_t = $l->t('Sharing failed, because the user %s is the original sharer', [$shareWith]);
2239
2240 3
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $shareWith), \OCP\Util::DEBUG);
2241 3
				throw new \Exception($message_t);
2242
			}
2243 38
		}
2244
2245 160
		if ($checkReshare && $checkReshare['uid_owner'] !== \OC_User::getUser()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $checkReshare of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2246
			// Check if share permissions is granted
2247 37
			if (self::isResharingAllowed() && (int)$checkReshare['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
2248 37
				if (~(int)$checkReshare['permissions'] & $permissions) {
2249 1
					$message = 'Sharing %s failed, because the permissions exceed permissions granted to %s';
2250 1
					$message_t = $l->t('Sharing %s failed, because the permissions exceed permissions granted to %s', array($itemSourceName, $uidOwner));
2251
2252 1
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName, $uidOwner), \OCP\Util::DEBUG);
2253 1
					throw new \Exception($message_t);
2254
				} else {
2255
					// TODO Don't check if inside folder
2256 37
					$result['parent'] = $checkReshare['id'];
2257
2258 37
					$result['expirationDate'] = $expirationDate;
2259
					// $checkReshare['expiration'] could be null and then is always less than any value
2260 37
					if(isset($checkReshare['expiration']) && $checkReshare['expiration'] < $expirationDate) {
2261
						$result['expirationDate'] = $checkReshare['expiration'];
2262
					}
2263
2264
					// only suggest the same name as new target if it is a reshare of the
2265
					// same file/folder and not the reshare of a child
2266 37
					if ($checkReshare[$column] === $itemSource) {
2267 35
						$result['filePath'] = $checkReshare['file_target'];
2268 35
						$result['itemSource'] = $checkReshare['item_source'];
2269 35
						$result['fileSource'] = $checkReshare['file_source'];
2270 35
						$result['suggestedItemTarget'] = $checkReshare['item_target'];
2271 35
						$result['suggestedFileTarget'] = $checkReshare['file_target'];
2272 35
					} else {
2273 30
						$result['filePath'] = ($backend instanceof \OCP\Share_Backend_File_Dependent) ? $backend->getFilePath($itemSource, $uidOwner) : null;
2274 30
						$result['suggestedItemTarget'] = null;
2275 30
						$result['suggestedFileTarget'] = null;
2276 30
						$result['itemSource'] = $itemSource;
2277 30
						$result['fileSource'] = ($backend instanceof \OCP\Share_Backend_File_Dependent) ? $itemSource : null;
2278
					}
2279
				}
2280 37 View Code Duplication
			} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2281 1
				$message = 'Sharing %s failed, because resharing is not allowed';
2282 1
				$message_t = $l->t('Sharing %s failed, because resharing is not allowed', array($itemSourceName));
2283
2284 1
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSourceName), \OCP\Util::DEBUG);
2285 1
				throw new \Exception($message_t);
2286
			}
2287 37
		} else {
2288 160
			$result['parent'] = null;
2289 160
			$result['suggestedItemTarget'] = null;
2290 160
			$result['suggestedFileTarget'] = null;
2291 160
			$result['itemSource'] = $itemSource;
2292 160
			$result['expirationDate'] = $expirationDate;
2293 160 View Code Duplication
			if (!$backend->isValidSource($itemSource, $uidOwner)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $backend->isValidSource($itemSource, $uidOwner) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2294
				$message = 'Sharing %s failed, because the sharing backend for '
2295 1
					.'%s could not find its source';
2296 1
				$message_t = $l->t('Sharing %s failed, because the sharing backend for %s could not find its source', array($itemSource, $itemType));
2297 1
				\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSource, $itemType), \OCP\Util::DEBUG);
2298 1
				throw new \Exception($message_t);
2299
			}
2300 160
			if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
2301 138
				$result['filePath'] = $backend->getFilePath($itemSource, $uidOwner);
2302 138
				if ($itemType == 'file' || $itemType == 'folder') {
2303 138
					$result['fileSource'] = $itemSource;
2304 138
				} else {
2305
					$meta = \OC\Files\Filesystem::getFileInfo($result['filePath']);
0 ignored issues
show
Security Bug introduced by
It seems like $result['filePath'] can also be of type false; however, OC\Files\Filesystem::getFileInfo() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
2306
					$result['fileSource'] = $meta['fileid'];
2307
				}
2308 138 View Code Duplication
				if ($result['fileSource'] == -1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2309
					$message = 'Sharing %s failed, because the file could not be found in the file cache';
2310
					$message_t = $l->t('Sharing %s failed, because the file could not be found in the file cache', array($itemSource));
2311
2312
					\OCP\Util::writeLog('OCP\Share', sprintf($message, $itemSource), \OCP\Util::DEBUG);
2313
					throw new \Exception($message_t);
2314
				}
2315 138
			} else {
2316 22
				$result['filePath'] = null;
2317 22
				$result['fileSource'] = null;
2318
			}
2319
		}
2320
2321 160
		return $result;
2322
	}
2323
2324
	/**
2325
	 *
2326
	 * @param array $shareData
2327
	 * @return mixed false in case of a failure or the id of the new share
2328
	 */
2329 160
	private static function insertShare(array $shareData) {
2330
2331 160
		$query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` ('
2332
			.' `item_type`, `item_source`, `item_target`, `share_type`,'
2333 160
			.' `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,'
2334 160
			.' `file_target`, `token`, `parent`, `expiration`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)');
2335 160
		$query->bindValue(1, $shareData['itemType']);
2336 160
		$query->bindValue(2, $shareData['itemSource']);
2337 160
		$query->bindValue(3, $shareData['itemTarget']);
2338 160
		$query->bindValue(4, $shareData['shareType']);
2339 160
		$query->bindValue(5, $shareData['shareWith']);
2340 160
		$query->bindValue(6, $shareData['uidOwner']);
2341 160
		$query->bindValue(7, $shareData['permissions']);
2342 160
		$query->bindValue(8, $shareData['shareTime']);
2343 160
		$query->bindValue(9, $shareData['fileSource']);
2344 160
		$query->bindValue(10, $shareData['fileTarget']);
2345 160
		$query->bindValue(11, $shareData['token']);
2346 160
		$query->bindValue(12, $shareData['parent']);
2347 160
		$query->bindValue(13, $shareData['expiration'], 'datetime');
2348 160
		$result = $query->execute();
2349
2350 160
		$id = false;
2351 160
		if ($result) {
2352 160
			$id =  \OC::$server->getDatabaseConnection()->lastInsertId();
2353
			// Fallback, if lastInterId() doesn't work we need to perform a select
2354
			// to get the ID (seems to happen sometimes on Oracle)
2355 160
			if (!$id) {
2356
				$getId = \OC_DB::prepare('
2357
					SELECT `id`
2358
					FROM`*PREFIX*share`
2359
					WHERE `uid_owner` = ? AND `item_target` = ? AND `item_source` = ? AND `stime` = ?
2360
					');
2361
				$r = $getId->execute(array($shareData['uidOwner'], $shareData['itemTarget'], $shareData['itemSource'], $shareData['shareTime']));
2362
				if ($r) {
2363
					$row = $r->fetchRow();
2364
					$id = $row['id'];
2365
				}
2366
			}
2367
2368 160
		}
2369
2370 160
		return $id;
2371
2372
	}
2373
2374
	/**
2375
	 * Delete all shares with type SHARE_TYPE_LINK
2376
	 */
2377
	public static function removeAllLinkShares() {
2378
		// Delete any link shares
2379
		$query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*share` WHERE `share_type` = ?');
2380
		$result = $query->execute(array(self::SHARE_TYPE_LINK));
2381
		while ($item = $result->fetchRow()) {
2382
			Helper::delete($item['id']);
2383
		}
2384
	}
2385
2386
	/**
2387
	 * In case a password protected link is not yet authenticated this function will return false
2388
	 *
2389
	 * @param array $linkItem
2390
	 * @return boolean
2391
	 */
2392 11
	public static function checkPasswordProtectedShare(array $linkItem) {
2393 11
		if (!isset($linkItem['share_with'])) {
2394 4
			return true;
2395
		}
2396 7
		if (!isset($linkItem['share_type'])) {
2397 1
			return true;
2398
		}
2399 6
		if (!isset($linkItem['id'])) {
2400 2
			return true;
2401
		}
2402
2403 4
		if ($linkItem['share_type'] != \OCP\Share::SHARE_TYPE_LINK) {
2404
			return true;
2405
		}
2406
2407 4 View Code Duplication
		if ( \OC::$server->getSession()->exists('public_link_authenticated')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2408 4
			&& \OC::$server->getSession()->get('public_link_authenticated') === $linkItem['id'] ) {
2409 2
			return true;
2410
		}
2411
2412 2
		return false;
2413
	}
2414
2415
	/**
2416
	 * construct select statement
2417
	 * @param int $format
2418
	 * @param boolean $fileDependent ist it a file/folder share or a generla share
2419
	 * @param string $uidOwner
2420
	 * @return string select statement
2421
	 */
2422 485
	private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
2423 485
		$select = '*';
2424 485
		if ($format == self::FORMAT_STATUSES) {
2425
			if ($fileDependent) {
2426
				$select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
2427
					. '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
2428
					. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
2429
			} else {
2430
				$select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
2431
			}
2432
		} else {
2433 485
			if (isset($uidOwner)) {
2434 115
				if ($fileDependent) {
2435
					$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
2436
						. ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
2437 89
						. ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
2438 89
						. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
2439 89
				} else {
2440
					$select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
2441 26
						. ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
2442
				}
2443 115
			} else {
2444 477
				if ($fileDependent) {
2445 454
					if ($format == \OC_Share_Backend_File::FORMAT_GET_FOLDER_CONTENTS || $format == \OC_Share_Backend_File::FORMAT_FILE_APP_ROOT) {
2446
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
2447
							. '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
2448
							. '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
2449
							. '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
2450
					} else {
2451
						$select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
2452
							. '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
2453 454
							. '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
2454 454
						    . '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
2455 454
							. '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
2456
					}
2457 454
				}
2458
			}
2459
		}
2460 485
		return $select;
2461
	}
2462
2463
2464
	/**
2465
	 * transform db results
2466
	 * @param array $row result
2467
	 */
2468 145
	private static function transformDBResults(&$row) {
2469 145
		if (isset($row['id'])) {
2470 145
			$row['id'] = (int) $row['id'];
2471 145
		}
2472 145
		if (isset($row['share_type'])) {
2473 145
			$row['share_type'] = (int) $row['share_type'];
2474 145
		}
2475 145
		if (isset($row['parent'])) {
2476 43
			$row['parent'] = (int) $row['parent'];
2477 43
		}
2478 145
		if (isset($row['file_parent'])) {
2479 128
			$row['file_parent'] = (int) $row['file_parent'];
2480 128
		}
2481 145
		if (isset($row['file_source'])) {
2482 128
			$row['file_source'] = (int) $row['file_source'];
2483 128
		}
2484 145
		if (isset($row['permissions'])) {
2485 145
			$row['permissions'] = (int) $row['permissions'];
2486 145
		}
2487 145
		if (isset($row['storage'])) {
2488 128
			$row['storage'] = (int) $row['storage'];
2489 128
		}
2490 145
		if (isset($row['stime'])) {
2491 145
			$row['stime'] = (int) $row['stime'];
2492 145
		}
2493 145
		if (isset($row['expiration']) && $row['share_type'] !== self::SHARE_TYPE_LINK) {
2494
			// discard expiration date for non-link shares, which might have been
2495
			// set by ancient bugs
2496
			$row['expiration'] = null;
2497
		}
2498 145
	}
2499
2500
	/**
2501
	 * format result
2502
	 * @param array $items result
2503
	 * @param string $column is it a file share or a general share ('file_target' or 'item_target')
2504
	 * @param \OCP\Share_Backend $backend sharing backend
2505
	 * @param int $format
2506
	 * @param array $parameters additional format parameters
2507
	 * @return array format result
2508
	 */
2509 146
	private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE , $parameters = null) {
2510 146
		if ($format === self::FORMAT_NONE) {
2511 142
			return $items;
2512 12
		} else if ($format === self::FORMAT_STATUSES) {
2513
			$statuses = array();
2514
			foreach ($items as $item) {
2515
				if ($item['share_type'] === self::SHARE_TYPE_LINK) {
2516
					$statuses[$item[$column]]['link'] = true;
2517
				} else if (!isset($statuses[$item[$column]])) {
2518
					$statuses[$item[$column]]['link'] = false;
2519
				}
2520
				if (!empty($item['file_target'])) {
2521
					$statuses[$item[$column]]['path'] = $item['path'];
2522
				}
2523
			}
2524
			return $statuses;
2525
		} else {
2526 12
			return $backend->formatItems($items, $format, $parameters);
2527
		}
2528
	}
2529
2530
	/**
2531
	 * remove protocol from URL
2532
	 *
2533
	 * @param string $url
2534
	 * @return string
2535
	 */
2536 24 View Code Duplication
	public static function removeProtocolFromUrl($url) {
2537 24
		if (strpos($url, 'https://') === 0) {
2538 5
			return substr($url, strlen('https://'));
2539 23
		} else if (strpos($url, 'http://') === 0) {
2540 21
			return substr($url, strlen('http://'));
2541
		}
2542
2543 10
		return $url;
2544
	}
2545
2546
	/**
2547
	 * try http post first with https and then with http as a fallback
2548
	 *
2549
	 * @param string $url
2550
	 * @param array $fields post parameters
2551
	 * @return array
2552
	 */
2553 6
	private static function tryHttpPost($url, $fields) {
2554 6
		$protocol = 'https://';
2555
		$result = [
2556 6
			'success' => false,
2557 6
			'result' => '',
2558 6
		];
2559 6
		$try = 0;
2560 6
		while ($result['success'] === false && $try < 2) {
2561 6
			$result = \OC::$server->getHTTPHelper()->post($protocol . $url, $fields);
2562 6
			$try++;
2563 6
			$protocol = 'http://';
2564 6
		}
2565
2566 6
		return $result;
2567
	}
2568
2569
	/**
2570
	 * send server-to-server share to remote server
2571
	 *
2572
	 * @param string $token
2573
	 * @param string $shareWith
2574
	 * @param string $name
2575
	 * @param int $remote_id
2576
	 * @param string $owner
2577
	 * @return bool
2578
	 */
2579 5
	private static function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner) {
2580
2581 5
		list($user, $remote) = Helper::splitUserRemote($shareWith);
2582
2583 5
		if ($user && $remote) {
2584 5
			$url = $remote . self::BASE_PATH_TO_SHARE_API . '?format=' . self::RESPONSE_FORMAT;
2585
2586 5
			$local = \OC::$server->getURLGenerator()->getAbsoluteURL('/');
2587
2588
			$fields = array(
2589 5
				'shareWith' => $user,
2590 5
				'token' => $token,
2591 5
				'name' => $name,
2592 5
				'remoteId' => $remote_id,
2593 5
				'owner' => $owner,
2594 5
				'remote' => $local,
2595 5
			);
2596
2597 5
			$url = self::removeProtocolFromUrl($url);
2598 5
			$result = self::tryHttpPost($url, $fields);
2599 5
			$status = json_decode($result['result'], true);
2600
2601 5
			return ($result['success'] && $status['ocs']['meta']['statuscode'] === 100);
2602
2603
		}
2604
2605
		return false;
2606
	}
2607
2608
	/**
2609
	 * send server-to-server unshare to remote server
2610
	 *
2611
	 * @param string $remote url
2612
	 * @param int $id share id
2613
	 * @param string $token
2614
	 * @return bool
2615
	 */
2616 6
	private static function sendRemoteUnshare($remote, $id, $token) {
2617 6
		$url = rtrim($remote, '/') . self::BASE_PATH_TO_SHARE_API . '/' . $id . '/unshare?format=' . self::RESPONSE_FORMAT;
2618 6
		$fields = array('token' => $token, 'format' => 'json');
2619 6
		$url = self::removeProtocolFromUrl($url);
2620 6
		$result = self::tryHttpPost($url, $fields);
2621 6
		$status = json_decode($result['result'], true);
2622
2623 6
		return ($result['success'] && $status['ocs']['meta']['statuscode'] === 100);
2624
	}
2625
2626
	/**
2627
	 * check if user can only share with group members
2628
	 * @return bool
2629
	 */
2630 174
	public static function shareWithGroupMembersOnly() {
2631 174
		$value = \OC::$server->getAppConfig()->getValue('core', 'shareapi_only_share_with_group_members', 'no');
2632 174
		return ($value === 'yes') ? true : false;
2633
	}
2634
2635
	/**
2636
	 * @return bool
2637
	 */
2638 40
	public static function isDefaultExpireDateEnabled() {
2639 40
		$defaultExpireDateEnabled = \OCP\Config::getAppValue('core', 'shareapi_default_expire_date', 'no');
2640 40
		return ($defaultExpireDateEnabled === "yes") ? true : false;
2641
	}
2642
2643
	/**
2644
	 * @return bool
2645
	 */
2646
	public static function enforceDefaultExpireDate() {
2647
		$enforceDefaultExpireDate = \OCP\Config::getAppValue('core', 'shareapi_enforce_expire_date', 'no');
2648
		return ($enforceDefaultExpireDate === "yes") ? true : false;
2649
	}
2650
2651
	/**
2652
	 * @return int
2653
	 */
2654 5
	public static function getExpireInterval() {
2655 5
		return (int)\OCP\Config::getAppValue('core', 'shareapi_expire_after_n_days', '7');
2656
	}
2657
2658
	/**
2659
	 * Checks whether the given path is reachable for the given owner
2660
	 *
2661
	 * @param string $path path relative to files
2662
	 * @param string $ownerStorageId storage id of the owner
2663
	 *
2664
	 * @return boolean true if file is reachable, false otherwise
2665
	 */
2666 130
	private static function isFileReachable($path, $ownerStorageId) {
2667
		// if outside the home storage, file is always considered reachable
2668 130
		if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
2669 1
			substr($ownerStorageId, 0, 13) === 'object::user:'
2670 130
		)) {
2671 1
			return true;
2672
		}
2673
2674
		// if inside the home storage, the file has to be under "/files/"
2675 129
		$path = ltrim($path, '/');
2676 129
		if (substr($path, 0, 6) === 'files/') {
2677 129
			return true;
2678
		}
2679
2680 1
		return false;
2681
	}
2682
2683
	/**
2684
	 * @param IConfig $config
2685
	 * @return bool 
2686
	 */
2687 5
	public static function enforcePassword(IConfig $config) {
2688 5
		$enforcePassword = $config->getAppValue('core', 'shareapi_enforce_links_password', 'no');
2689 5
		return ($enforcePassword === "yes") ? true : false;
2690
	}
2691
2692
	/**
2693
	 * Get all share entries, including non-unique group items
2694
	 *
2695
	 * @param string $owner
2696
	 * @return array
2697
	 */
2698
	public static function getAllSharesForOwner($owner) {
2699
		$query = 'SELECT * FROM `*PREFIX*share` WHERE `uid_owner` = ?';
2700
		$result = \OC::$server->getDatabaseConnection()->executeQuery($query, [$owner]);
2701
		return $result->fetchAll();
2702
	}
2703
2704
	/**
2705
	 * Get all share entries, including non-unique group items for a file
2706
	 *
2707
	 * @param int $id
2708
	 * @return array
2709
	 */
2710 154
	public static function getAllSharesForFileId($id) {
2711 154
		$query = 'SELECT * FROM `*PREFIX*share` WHERE `file_source` = ?';
2712 154
		$result = \OC::$server->getDatabaseConnection()->executeQuery($query, [$id]);
2713 154
		return $result->fetchAll();
2714
	}
2715
2716
	/**
2717
	 * @param string $password
2718
	 * @throws \Exception
2719
	 */
2720 11
	private static function verifyPassword($password) {
2721
2722 11
		$accepted = true;
2723 11
		$message = '';
2724 11
		\OCP\Util::emitHook('\OC\Share', 'verifyPassword', [
2725 11
			'password' => $password,
2726 11
			'accepted' => &$accepted,
2727
			'message' => &$message
2728 11
		]);
2729
2730 11
		if (!$accepted) {
2731
			throw new \Exception($message);
2732
		}
2733 11
	}
2734
}
2735