Completed
Push — master ( b2a187...fbb772 )
by John
18:18 queued 17s
created
build/integration/features/bootstrap/RateLimitingContext.php 1 patch
Indentation   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -6,25 +6,25 @@
 block discarded – undo
6 6
 use Behat\Behat\Context\Context;
7 7
 
8 8
 class RateLimitingContext implements Context {
9
-	use BasicStructure;
10
-	use CommandLine;
11
-	use Provisioning;
9
+    use BasicStructure;
10
+    use CommandLine;
11
+    use Provisioning;
12 12
 
13
-	/**
14
-	 * @BeforeScenario @RateLimiting
15
-	 */
16
-	public function enableRateLimiting() {
17
-		// Enable rate limiting for the tests.
18
-		// Ratelimiting is disabled by default, so we need to enable it
19
-		$this->runOcc(['config:system:set', 'ratelimit.protection.enabled', '--value', 'true', '--type', 'bool']);
20
-	}
13
+    /**
14
+     * @BeforeScenario @RateLimiting
15
+     */
16
+    public function enableRateLimiting() {
17
+        // Enable rate limiting for the tests.
18
+        // Ratelimiting is disabled by default, so we need to enable it
19
+        $this->runOcc(['config:system:set', 'ratelimit.protection.enabled', '--value', 'true', '--type', 'bool']);
20
+    }
21 21
 
22
-	/**
23
-	 * @AfterScenario @RateLimiting
24
-	 */
25
-	public function disableRateLimiting() {
26
-		// Restore the default rate limiting configuration.
27
-		// Ratelimiting is disabled by default, so we need to disable it
28
-		$this->runOcc(['config:system:set', 'ratelimit.protection.enabled', '--value', 'false', '--type', 'bool']);
29
-	}
22
+    /**
23
+     * @AfterScenario @RateLimiting
24
+     */
25
+    public function disableRateLimiting() {
26
+        // Restore the default rate limiting configuration.
27
+        // Ratelimiting is disabled by default, so we need to disable it
28
+        $this->runOcc(['config:system:set', 'ratelimit.protection.enabled', '--value', 'false', '--type', 'bool']);
29
+    }
30 30
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/Controller/ShareAPIController.php 1 patch
Indentation   +2150 added lines, -2150 removed lines patch added patch discarded remove patch
@@ -71,2157 +71,2157 @@
 block discarded – undo
71 71
  */
72 72
 class ShareAPIController extends OCSController {
73 73
 
74
-	private ?Node $lockedNode = null;
75
-
76
-	/**
77
-	 * Share20OCS constructor.
78
-	 */
79
-	public function __construct(
80
-		string $appName,
81
-		IRequest $request,
82
-		private IManager $shareManager,
83
-		private IGroupManager $groupManager,
84
-		private IUserManager $userManager,
85
-		private IRootFolder $rootFolder,
86
-		private IURLGenerator $urlGenerator,
87
-		private IL10N $l,
88
-		private IConfig $config,
89
-		private IAppManager $appManager,
90
-		private ContainerInterface $serverContainer,
91
-		private IUserStatusManager $userStatusManager,
92
-		private IPreview $previewManager,
93
-		private IDateTimeZone $dateTimeZone,
94
-		private LoggerInterface $logger,
95
-		private IProviderFactory $factory,
96
-		private IMailer $mailer,
97
-		private ?string $userId = null,
98
-	) {
99
-		parent::__construct($appName, $request);
100
-	}
101
-
102
-	/**
103
-	 * Convert an IShare to an array for OCS output
104
-	 *
105
-	 * @param IShare $share
106
-	 * @param Node|null $recipientNode
107
-	 * @return Files_SharingShare
108
-	 * @throws NotFoundException In case the node can't be resolved.
109
-	 *
110
-	 * @suppress PhanUndeclaredClassMethod
111
-	 */
112
-	protected function formatShare(IShare $share, ?Node $recipientNode = null): array {
113
-		$sharedBy = $this->userManager->get($share->getSharedBy());
114
-		$shareOwner = $this->userManager->get($share->getShareOwner());
115
-
116
-		$isOwnShare = false;
117
-		if ($shareOwner !== null) {
118
-			$isOwnShare = $shareOwner->getUID() === $this->userId;
119
-		}
120
-
121
-		$result = [
122
-			'id' => $share->getId(),
123
-			'share_type' => $share->getShareType(),
124
-			'uid_owner' => $share->getSharedBy(),
125
-			'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
126
-			// recipient permissions
127
-			'permissions' => $share->getPermissions(),
128
-			// current user permissions on this share
129
-			'can_edit' => $this->canEditShare($share),
130
-			'can_delete' => $this->canDeleteShare($share),
131
-			'stime' => $share->getShareTime()->getTimestamp(),
132
-			'parent' => null,
133
-			'expiration' => null,
134
-			'token' => null,
135
-			'uid_file_owner' => $share->getShareOwner(),
136
-			'note' => $share->getNote(),
137
-			'label' => $share->getLabel(),
138
-			'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
139
-		];
140
-
141
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
142
-		if ($recipientNode) {
143
-			$node = $recipientNode;
144
-		} else {
145
-			$node = $userFolder->getFirstNodeById($share->getNodeId());
146
-			if (!$node) {
147
-				// fallback to guessing the path
148
-				$node = $userFolder->get($share->getTarget());
149
-				if ($node === null || $share->getTarget() === '') {
150
-					throw new NotFoundException();
151
-				}
152
-			}
153
-		}
154
-
155
-		$result['path'] = $userFolder->getRelativePath($node->getPath());
156
-		if ($node instanceof Folder) {
157
-			$result['item_type'] = 'folder';
158
-		} else {
159
-			$result['item_type'] = 'file';
160
-		}
161
-
162
-		// Get the original node permission if the share owner is the current user
163
-		if ($isOwnShare) {
164
-			$result['item_permissions'] = $node->getPermissions();
165
-		}
166
-
167
-		// If we're on the recipient side, the node permissions
168
-		// are bound to the share permissions. So we need to
169
-		// adjust the permissions to the share permissions if necessary.
170
-		if (!$isOwnShare) {
171
-			$result['item_permissions'] = $share->getPermissions();
172
-
173
-			// For some reason, single files share are forbidden to have the delete permission
174
-			// since we have custom methods to check those, let's adjust straight away.
175
-			// DAV permissions does not have that issue though.
176
-			if ($this->canDeleteShare($share) || $this->canDeleteShareFromSelf($share)) {
177
-				$result['item_permissions'] |= Constants::PERMISSION_DELETE;
178
-			}
179
-			if ($this->canEditShare($share)) {
180
-				$result['item_permissions'] |= Constants::PERMISSION_UPDATE;
181
-			}
182
-		}
183
-
184
-		// See MOUNT_ROOT_PROPERTYNAME dav property
185
-		$result['is-mount-root'] = $node->getInternalPath() === '';
186
-		$result['mount-type'] = $node->getMountPoint()->getMountType();
187
-
188
-		$result['mimetype'] = $node->getMimetype();
189
-		$result['has_preview'] = $this->previewManager->isAvailable($node);
190
-		$result['storage_id'] = $node->getStorage()->getId();
191
-		$result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
192
-		$result['item_source'] = $node->getId();
193
-		$result['file_source'] = $node->getId();
194
-		$result['file_parent'] = $node->getParent()->getId();
195
-		$result['file_target'] = $share->getTarget();
196
-		$result['item_size'] = $node->getSize();
197
-		$result['item_mtime'] = $node->getMTime();
198
-
199
-		$expiration = $share->getExpirationDate();
200
-		if ($expiration !== null) {
201
-			$expiration->setTimezone($this->dateTimeZone->getTimeZone());
202
-			$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
203
-		}
204
-
205
-		if ($share->getShareType() === IShare::TYPE_USER) {
206
-			$sharedWith = $this->userManager->get($share->getSharedWith());
207
-			$result['share_with'] = $share->getSharedWith();
208
-			$result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
209
-			$result['share_with_displayname_unique'] = $sharedWith !== null ? (
210
-				!empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID()
211
-			) : $share->getSharedWith();
212
-
213
-			$userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
214
-			$userStatus = array_shift($userStatuses);
215
-			if ($userStatus) {
216
-				$result['status'] = [
217
-					'status' => $userStatus->getStatus(),
218
-					'message' => $userStatus->getMessage(),
219
-					'icon' => $userStatus->getIcon(),
220
-					'clearAt' => $userStatus->getClearAt()
221
-						? (int)$userStatus->getClearAt()->format('U')
222
-						: null,
223
-				];
224
-			}
225
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
226
-			$group = $this->groupManager->get($share->getSharedWith());
227
-			$result['share_with'] = $share->getSharedWith();
228
-			$result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
229
-		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
230
-
231
-			// "share_with" and "share_with_displayname" for passwords of link
232
-			// shares was deprecated in Nextcloud 15, use "password" instead.
233
-			$result['share_with'] = $share->getPassword();
234
-			$result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
235
-
236
-			$result['password'] = $share->getPassword();
237
-
238
-			$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
239
-
240
-			$result['token'] = $share->getToken();
241
-			$result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
242
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
243
-			$result['share_with'] = $share->getSharedWith();
244
-			$result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith());
245
-			$result['token'] = $share->getToken();
246
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
247
-			$result['share_with'] = $share->getSharedWith();
248
-			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
249
-			$result['token'] = $share->getToken();
250
-		} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
251
-			$result['share_with'] = $share->getSharedWith();
252
-			$result['password'] = $share->getPassword();
253
-			$result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null;
254
-			$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
255
-			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
256
-			$result['token'] = $share->getToken();
257
-		} elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
258
-			// getSharedWith() returns either "name (type, owner)" or
259
-			// "name (type, owner) [id]", depending on the Teams app version.
260
-			$hasCircleId = (substr($share->getSharedWith(), -1) === ']');
261
-
262
-			$result['share_with_displayname'] = $share->getSharedWithDisplayName();
263
-			if (empty($result['share_with_displayname'])) {
264
-				$displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
265
-				$result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
266
-			}
267
-
268
-			$result['share_with_avatar'] = $share->getSharedWithAvatar();
269
-
270
-			$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
271
-			$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
272
-			if ($shareWithLength === false) {
273
-				$result['share_with'] = substr($share->getSharedWith(), $shareWithStart);
274
-			} else {
275
-				$result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
276
-			}
277
-		} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
278
-			$result['share_with'] = $share->getSharedWith();
279
-			$result['share_with_displayname'] = '';
280
-
281
-			try {
282
-				/** @var array{share_with_displayname: string, share_with_link: string, share_with?: string, token?: string} $roomShare */
283
-				$roomShare = $this->getRoomShareHelper()->formatShare($share);
284
-				$result = array_merge($result, $roomShare);
285
-			} catch (ContainerExceptionInterface $e) {
286
-			}
287
-		} elseif ($share->getShareType() === IShare::TYPE_DECK) {
288
-			$result['share_with'] = $share->getSharedWith();
289
-			$result['share_with_displayname'] = '';
290
-
291
-			try {
292
-				/** @var array{share_with: string, share_with_displayname: string, share_with_link: string} $deckShare */
293
-				$deckShare = $this->getDeckShareHelper()->formatShare($share);
294
-				$result = array_merge($result, $deckShare);
295
-			} catch (ContainerExceptionInterface $e) {
296
-			}
297
-		} elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
298
-			$result['share_with'] = $share->getSharedWith();
299
-			$result['share_with_displayname'] = '';
300
-
301
-			try {
302
-				/** @var array{share_with: string, share_with_displayname: string, token: string} $scienceMeshShare */
303
-				$scienceMeshShare = $this->getSciencemeshShareHelper()->formatShare($share);
304
-				$result = array_merge($result, $scienceMeshShare);
305
-			} catch (ContainerExceptionInterface $e) {
306
-			}
307
-		}
308
-
309
-
310
-		$result['mail_send'] = $share->getMailSend() ? 1 : 0;
311
-		$result['hide_download'] = $share->getHideDownload() ? 1 : 0;
312
-
313
-		$result['attributes'] = null;
314
-		if ($attributes = $share->getAttributes()) {
315
-			$result['attributes'] = (string)\json_encode($attributes->toArray());
316
-		}
317
-
318
-		return $result;
319
-	}
320
-
321
-	/**
322
-	 * Check if one of the users address books knows the exact property, if
323
-	 * not we return the full name.
324
-	 *
325
-	 * @param string $query
326
-	 * @param string $property
327
-	 * @return string
328
-	 */
329
-	private function getDisplayNameFromAddressBook(string $query, string $property): string {
330
-		// FIXME: If we inject the contacts manager it gets initialized before any address books are registered
331
-		try {
332
-			$result = Server::get(\OCP\Contacts\IManager::class)->search($query, [$property], [
333
-				'limit' => 1,
334
-				'enumeration' => false,
335
-				'strict_search' => true,
336
-			]);
337
-		} catch (Exception $e) {
338
-			$this->logger->error(
339
-				$e->getMessage(),
340
-				['exception' => $e]
341
-			);
342
-			return $query;
343
-		}
344
-
345
-		foreach ($result as $r) {
346
-			foreach ($r[$property] as $value) {
347
-				if ($value === $query && $r['FN']) {
348
-					return $r['FN'];
349
-				}
350
-			}
351
-		}
352
-
353
-		return $query;
354
-	}
355
-
356
-
357
-	/**
358
-	 * @param list<Files_SharingShare> $shares
359
-	 * @param array<string, string>|null $updatedDisplayName
360
-	 *
361
-	 * @return list<Files_SharingShare>
362
-	 */
363
-	private function fixMissingDisplayName(array $shares, ?array $updatedDisplayName = null): array {
364
-		$userIds = $updated = [];
365
-		foreach ($shares as $share) {
366
-			// share is federated and share have no display name yet
367
-			if ($share['share_type'] === IShare::TYPE_REMOTE
368
-				&& ($share['share_with'] ?? '') !== ''
369
-				&& ($share['share_with_displayname'] ?? '') === '') {
370
-				$userIds[] = $userId = $share['share_with'];
371
-
372
-				if ($updatedDisplayName !== null && array_key_exists($userId, $updatedDisplayName)) {
373
-					$share['share_with_displayname'] = $updatedDisplayName[$userId];
374
-				}
375
-			}
376
-
377
-			// prepping userIds with displayName to be updated
378
-			$updated[] = $share;
379
-		}
380
-
381
-		// if $updatedDisplayName is not null, it means we should have already fixed displayNames of the shares
382
-		if ($updatedDisplayName !== null) {
383
-			return $updated;
384
-		}
385
-
386
-		// get displayName for the generated list of userId with no displayName
387
-		$displayNames = $this->retrieveFederatedDisplayName($userIds);
388
-
389
-		// if no displayName are updated, we exit
390
-		if (empty($displayNames)) {
391
-			return $updated;
392
-		}
393
-
394
-		// let's fix missing display name and returns all shares
395
-		return $this->fixMissingDisplayName($shares, $displayNames);
396
-	}
397
-
398
-
399
-	/**
400
-	 * get displayName of a list of userIds from the lookup-server; through the globalsiteselector app.
401
-	 * returns an array with userIds as keys and displayName as values.
402
-	 *
403
-	 * @param array $userIds
404
-	 * @param bool $cacheOnly - do not reach LUS, get data from cache.
405
-	 *
406
-	 * @return array
407
-	 * @throws ContainerExceptionInterface
408
-	 */
409
-	private function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = false): array {
410
-		// check if gss is enabled and available
411
-		if (count($userIds) === 0
412
-			|| !$this->appManager->isEnabledForAnyone('globalsiteselector')
413
-			|| !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
414
-			return [];
415
-		}
416
-
417
-		try {
418
-			$slaveService = Server::get(SlaveService::class);
419
-		} catch (\Throwable $e) {
420
-			$this->logger->error(
421
-				$e->getMessage(),
422
-				['exception' => $e]
423
-			);
424
-			return [];
425
-		}
426
-
427
-		return $slaveService->getUsersDisplayName($userIds, $cacheOnly);
428
-	}
429
-
430
-
431
-	/**
432
-	 * retrieve displayName from cache if available (should be used on federated shares)
433
-	 * if not available in cache/lus, try for get from address-book, else returns empty string.
434
-	 *
435
-	 * @param string $userId
436
-	 * @param bool $cacheOnly if true will not reach the lus but will only get data from cache
437
-	 *
438
-	 * @return string
439
-	 */
440
-	private function getCachedFederatedDisplayName(string $userId, bool $cacheOnly = true): string {
441
-		$details = $this->retrieveFederatedDisplayName([$userId], $cacheOnly);
442
-		if (array_key_exists($userId, $details)) {
443
-			return $details[$userId];
444
-		}
445
-
446
-		$displayName = $this->getDisplayNameFromAddressBook($userId, 'CLOUD');
447
-		return ($displayName === $userId) ? '' : $displayName;
448
-	}
449
-
450
-
451
-
452
-	/**
453
-	 * Get a specific share by id
454
-	 *
455
-	 * @param string $id ID of the share
456
-	 * @param bool $include_tags Include tags in the share
457
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
458
-	 * @throws OCSNotFoundException Share not found
459
-	 *
460
-	 * 200: Share returned
461
-	 */
462
-	#[NoAdminRequired]
463
-	public function getShare(string $id, bool $include_tags = false): DataResponse {
464
-		try {
465
-			$share = $this->getShareById($id);
466
-		} catch (ShareNotFound $e) {
467
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
468
-		}
469
-
470
-		try {
471
-			if ($this->canAccessShare($share)) {
472
-				$share = $this->formatShare($share);
473
-
474
-				if ($include_tags) {
475
-					$share = Helper::populateTags([$share], Server::get(ITagManager::class));
476
-				} else {
477
-					$share = [$share];
478
-				}
479
-
480
-				return new DataResponse($share);
481
-			}
482
-		} catch (NotFoundException $e) {
483
-			// Fall through
484
-		}
485
-
486
-		throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
487
-	}
488
-
489
-	/**
490
-	 * Delete a share
491
-	 *
492
-	 * @param string $id ID of the share
493
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
494
-	 * @throws OCSNotFoundException Share not found
495
-	 * @throws OCSForbiddenException Missing permissions to delete the share
496
-	 *
497
-	 * 200: Share deleted successfully
498
-	 */
499
-	#[NoAdminRequired]
500
-	public function deleteShare(string $id): DataResponse {
501
-		try {
502
-			$share = $this->getShareById($id);
503
-		} catch (ShareNotFound $e) {
504
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
505
-		}
506
-
507
-		try {
508
-			$this->lock($share->getNode());
509
-		} catch (LockedException $e) {
510
-			throw new OCSNotFoundException($this->l->t('Could not delete share'));
511
-		}
512
-
513
-		if (!$this->canAccessShare($share)) {
514
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
515
-		}
516
-
517
-		// if it's a group share or a room share
518
-		// we don't delete the share, but only the
519
-		// mount point. Allowing it to be restored
520
-		// from the deleted shares
521
-		if ($this->canDeleteShareFromSelf($share)) {
522
-			$this->shareManager->deleteFromSelf($share, $this->userId);
523
-		} else {
524
-			if (!$this->canDeleteShare($share)) {
525
-				throw new OCSForbiddenException($this->l->t('Could not delete share'));
526
-			}
527
-
528
-			$this->shareManager->deleteShare($share);
529
-		}
530
-
531
-		return new DataResponse();
532
-	}
533
-
534
-	/**
535
-	 * Create a share
536
-	 *
537
-	 * @param string|null $path Path of the share
538
-	 * @param int|null $permissions Permissions for the share
539
-	 * @param int $shareType Type of the share
540
-	 * @param ?string $shareWith The entity this should be shared with
541
-	 * @param 'true'|'false'|null $publicUpload If public uploading is allowed (deprecated)
542
-	 * @param string $password Password for the share
543
-	 * @param string|null $sendPasswordByTalk Send the password for the share over Talk
544
-	 * @param ?string $expireDate The expiry date of the share in the user's timezone at 00:00.
545
-	 *                            If $expireDate is not supplied or set to `null`, the system default will be used.
546
-	 * @param string $note Note for the share
547
-	 * @param string $label Label for the share (only used in link and email)
548
-	 * @param string|null $attributes Additional attributes for the share
549
-	 * @param 'false'|'true'|null $sendMail Send a mail to the recipient
550
-	 *
551
-	 * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
552
-	 * @throws OCSBadRequestException Unknown share type
553
-	 * @throws OCSException
554
-	 * @throws OCSForbiddenException Creating the share is not allowed
555
-	 * @throws OCSNotFoundException Creating the share failed
556
-	 * @suppress PhanUndeclaredClassMethod
557
-	 *
558
-	 * 200: Share created
559
-	 */
560
-	#[NoAdminRequired]
561
-	#[UserRateLimit(limit: 20, period: 600)]
562
-	public function createShare(
563
-		?string $path = null,
564
-		?int $permissions = null,
565
-		int $shareType = -1,
566
-		?string $shareWith = null,
567
-		?string $publicUpload = null,
568
-		string $password = '',
569
-		?string $sendPasswordByTalk = null,
570
-		?string $expireDate = null,
571
-		string $note = '',
572
-		string $label = '',
573
-		?string $attributes = null,
574
-		?string $sendMail = null,
575
-	): DataResponse {
576
-		assert($this->userId !== null);
577
-
578
-		$share = $this->shareManager->newShare();
579
-		$hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
580
-
581
-		// Verify path
582
-		if ($path === null) {
583
-			throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
584
-		}
585
-
586
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
587
-		try {
588
-			/** @var \OC\Files\Node\Node $node */
589
-			$node = $userFolder->get($path);
590
-		} catch (NotFoundException $e) {
591
-			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
592
-		}
593
-
594
-		// a user can have access to a file through different paths, with differing permissions
595
-		// combine all permissions to determine if the user can share this file
596
-		$nodes = $userFolder->getById($node->getId());
597
-		foreach ($nodes as $nodeById) {
598
-			/** @var \OC\Files\FileInfo $fileInfo */
599
-			$fileInfo = $node->getFileInfo();
600
-			$fileInfo['permissions'] |= $nodeById->getPermissions();
601
-		}
602
-
603
-		$share->setNode($node);
604
-
605
-		try {
606
-			$this->lock($share->getNode());
607
-		} catch (LockedException $e) {
608
-			throw new OCSNotFoundException($this->l->t('Could not create share'));
609
-		}
610
-
611
-		// Set permissions
612
-		if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_EMAIL) {
613
-			$permissions = $this->getLinkSharePermissions($permissions, $hasPublicUpload);
614
-			$this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
615
-		} else {
616
-			// Use default permissions only for non-link shares to keep legacy behavior
617
-			if ($permissions === null) {
618
-				$permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
619
-			}
620
-			// Non-link shares always require read permissions (link shares could be file drop)
621
-			$permissions |= Constants::PERMISSION_READ;
622
-		}
623
-
624
-		// For legacy reasons the API allows to pass PERMISSIONS_ALL even for single file shares (I look at you Talk)
625
-		if ($node instanceof File) {
626
-			// if this is a single file share we remove the DELETE and CREATE permissions
627
-			$permissions = $permissions & ~(Constants::PERMISSION_DELETE | Constants::PERMISSION_CREATE);
628
-		}
629
-
630
-		/**
631
-		 * Hack for https://github.com/owncloud/core/issues/22587
632
-		 * We check the permissions via webdav. But the permissions of the mount point
633
-		 * do not equal the share permissions. Here we fix that for federated mounts.
634
-		 */
635
-		if ($node->getStorage()->instanceOfStorage(Storage::class)) {
636
-			$permissions &= ~($permissions & ~$node->getPermissions());
637
-		}
638
-
639
-		if ($attributes !== null) {
640
-			$share = $this->setShareAttributes($share, $attributes);
641
-		}
642
-
643
-		// Expire date checks
644
-		// Normally, null means no expiration date but we still set the default for backwards compatibility
645
-		// If the client sends an empty string, we set noExpirationDate to true
646
-		if ($expireDate !== null) {
647
-			if ($expireDate !== '') {
648
-				try {
649
-					$expireDateTime = $this->parseDate($expireDate);
650
-					$share->setExpirationDate($expireDateTime);
651
-				} catch (\Exception $e) {
652
-					throw new OCSNotFoundException($e->getMessage(), $e);
653
-				}
654
-			} else {
655
-				// Client sent empty string for expire date.
656
-				// Set noExpirationDate to true so overwrite is prevented.
657
-				$share->setNoExpirationDate(true);
658
-			}
659
-		}
660
-
661
-		$share->setSharedBy($this->userId);
662
-
663
-		// Handle mail send
664
-		if (is_null($sendMail)) {
665
-			$allowSendMail = $this->config->getSystemValueBool('sharing.enable_share_mail', true);
666
-			if ($allowSendMail !== true || $shareType === IShare::TYPE_EMAIL) {
667
-				// Define a default behavior when sendMail is not provided
668
-				// For email shares with a valid recipient, the default is to send the mail
669
-				// For all other share types, the default is to not send the mail
670
-				$allowSendMail = ($shareType === IShare::TYPE_EMAIL && $shareWith !== null && $shareWith !== '');
671
-			}
672
-			$share->setMailSend($allowSendMail);
673
-		} else {
674
-			$share->setMailSend($sendMail === 'true');
675
-		}
676
-
677
-		if ($shareType === IShare::TYPE_USER) {
678
-			// Valid user is required to share
679
-			if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
680
-				throw new OCSNotFoundException($this->l->t('Please specify a valid account to share with'));
681
-			}
682
-			$share->setSharedWith($shareWith);
683
-			$share->setPermissions($permissions);
684
-		} elseif ($shareType === IShare::TYPE_GROUP) {
685
-			if (!$this->shareManager->allowGroupSharing()) {
686
-				throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator'));
687
-			}
688
-
689
-			// Valid group is required to share
690
-			if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) {
691
-				throw new OCSNotFoundException($this->l->t('Please specify a valid group'));
692
-			}
693
-			$share->setSharedWith($shareWith);
694
-			$share->setPermissions($permissions);
695
-		} elseif ($shareType === IShare::TYPE_LINK
696
-			|| $shareType === IShare::TYPE_EMAIL) {
697
-
698
-			// Can we even share links?
699
-			if (!$this->shareManager->shareApiAllowLinks()) {
700
-				throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
701
-			}
702
-
703
-			$this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
704
-			$share->setPermissions($permissions);
705
-
706
-			// Set password
707
-			if ($password !== '') {
708
-				$share->setPassword($password);
709
-			}
710
-
711
-			// Only share by mail have a recipient
712
-			if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) {
713
-				// If sending a mail have been requested, validate the mail address
714
-				if ($share->getMailSend() && !$this->mailer->validateMailAddress($shareWith)) {
715
-					throw new OCSNotFoundException($this->l->t('Please specify a valid email address'));
716
-				}
717
-				$share->setSharedWith($shareWith);
718
-			}
719
-
720
-			// If we have a label, use it
721
-			if ($label !== '') {
722
-				if (strlen($label) > 255) {
723
-					throw new OCSBadRequestException('Maximum label length is 255');
724
-				}
725
-				$share->setLabel($label);
726
-			}
727
-
728
-			if ($sendPasswordByTalk === 'true') {
729
-				if (!$this->appManager->isEnabledForUser('spreed')) {
730
-					throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$node->getPath()]));
731
-				}
732
-
733
-				$share->setSendPasswordByTalk(true);
734
-			}
735
-		} elseif ($shareType === IShare::TYPE_REMOTE) {
736
-			if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
737
-				throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
738
-			}
739
-
740
-			if ($shareWith === null) {
741
-				throw new OCSNotFoundException($this->l->t('Please specify a valid federated account ID'));
742
-			}
743
-
744
-			$share->setSharedWith($shareWith);
745
-			$share->setPermissions($permissions);
746
-			$share->setSharedWithDisplayName($this->getCachedFederatedDisplayName($shareWith, false));
747
-		} elseif ($shareType === IShare::TYPE_REMOTE_GROUP) {
748
-			if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
749
-				throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
750
-			}
751
-
752
-			if ($shareWith === null) {
753
-				throw new OCSNotFoundException($this->l->t('Please specify a valid federated group ID'));
754
-			}
755
-
756
-			$share->setSharedWith($shareWith);
757
-			$share->setPermissions($permissions);
758
-		} elseif ($shareType === IShare::TYPE_CIRCLE) {
759
-			if (!Server::get(IAppManager::class)->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
760
-				throw new OCSNotFoundException($this->l->t('You cannot share to a Team if the app is not enabled'));
761
-			}
762
-
763
-			$circle = Circles::detailsCircle($shareWith);
764
-
765
-			// Valid team is required to share
766
-			if ($circle === null) {
767
-				throw new OCSNotFoundException($this->l->t('Please specify a valid team'));
768
-			}
769
-			$share->setSharedWith($shareWith);
770
-			$share->setPermissions($permissions);
771
-		} elseif ($shareType === IShare::TYPE_ROOM) {
772
-			try {
773
-				$this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
774
-			} catch (ContainerExceptionInterface $e) {
775
-				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
776
-			}
777
-		} elseif ($shareType === IShare::TYPE_DECK) {
778
-			try {
779
-				$this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
780
-			} catch (ContainerExceptionInterface $e) {
781
-				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
782
-			}
783
-		} elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
784
-			try {
785
-				$this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
786
-			} catch (ContainerExceptionInterface $e) {
787
-				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support ScienceMesh shares', [$node->getPath()]));
788
-			}
789
-		} else {
790
-			throw new OCSBadRequestException($this->l->t('Unknown share type'));
791
-		}
792
-
793
-		$share->setShareType($shareType);
794
-		$this->checkInheritedAttributes($share);
795
-
796
-		if ($note !== '') {
797
-			$share->setNote($note);
798
-		}
799
-
800
-		try {
801
-			$share = $this->shareManager->createShare($share);
802
-		} catch (HintException $e) {
803
-			$code = $e->getCode() === 0 ? 403 : $e->getCode();
804
-			throw new OCSException($e->getHint(), $code);
805
-		} catch (GenericShareException|\InvalidArgumentException $e) {
806
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
807
-			throw new OCSForbiddenException($e->getMessage(), $e);
808
-		} catch (\Exception $e) {
809
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
810
-			throw new OCSForbiddenException('Failed to create share.', $e);
811
-		}
812
-
813
-		$output = $this->formatShare($share);
814
-
815
-		return new DataResponse($output);
816
-	}
817
-
818
-	/**
819
-	 * @param null|Node $node
820
-	 * @param boolean $includeTags
821
-	 *
822
-	 * @return list<Files_SharingShare>
823
-	 */
824
-	private function getSharedWithMe($node, bool $includeTags): array {
825
-		$userShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_USER, $node, -1, 0);
826
-		$groupShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_GROUP, $node, -1, 0);
827
-		$circleShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_CIRCLE, $node, -1, 0);
828
-		$roomShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_ROOM, $node, -1, 0);
829
-		$deckShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_DECK, $node, -1, 0);
830
-		$sciencemeshShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_SCIENCEMESH, $node, -1, 0);
831
-
832
-		$shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares);
833
-
834
-		$filteredShares = array_filter($shares, function (IShare $share) {
835
-			return $share->getShareOwner() !== $this->userId;
836
-		});
837
-
838
-		$formatted = [];
839
-		foreach ($filteredShares as $share) {
840
-			if ($this->canAccessShare($share)) {
841
-				try {
842
-					$formatted[] = $this->formatShare($share);
843
-				} catch (NotFoundException $e) {
844
-					// Ignore this share
845
-				}
846
-			}
847
-		}
848
-
849
-		if ($includeTags) {
850
-			$formatted = Helper::populateTags($formatted, Server::get(ITagManager::class));
851
-		}
852
-
853
-		return $formatted;
854
-	}
855
-
856
-	/**
857
-	 * @param Node $folder
858
-	 *
859
-	 * @return list<Files_SharingShare>
860
-	 * @throws OCSBadRequestException
861
-	 * @throws NotFoundException
862
-	 */
863
-	private function getSharesInDir(Node $folder): array {
864
-		if (!($folder instanceof Folder)) {
865
-			throw new OCSBadRequestException($this->l->t('Not a directory'));
866
-		}
867
-
868
-		$nodes = $folder->getDirectoryListing();
869
-
870
-		/** @var IShare[] $shares */
871
-		$shares = array_reduce($nodes, function ($carry, $node) {
872
-			$carry = array_merge($carry, $this->getAllShares($node, true));
873
-			return $carry;
874
-		}, []);
875
-
876
-		// filter out duplicate shares
877
-		$known = [];
878
-
879
-		$formatted = $miniFormatted = [];
880
-		$resharingRight = false;
881
-		$known = [];
882
-		foreach ($shares as $share) {
883
-			if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->userId) {
884
-				continue;
885
-			}
886
-
887
-			try {
888
-				$format = $this->formatShare($share);
889
-
890
-				$known[] = $share->getId();
891
-				$formatted[] = $format;
892
-				if ($share->getSharedBy() === $this->userId) {
893
-					$miniFormatted[] = $format;
894
-				}
895
-				if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $folder)) {
896
-					$resharingRight = true;
897
-				}
898
-			} catch (\Exception $e) {
899
-				//Ignore this share
900
-			}
901
-		}
902
-
903
-		if (!$resharingRight) {
904
-			$formatted = $miniFormatted;
905
-		}
906
-
907
-		return $formatted;
908
-	}
909
-
910
-	/**
911
-	 * Get shares of the current user
912
-	 *
913
-	 * @param string $shared_with_me Only get shares with the current user
914
-	 * @param string $reshares Only get shares by the current user and reshares
915
-	 * @param string $subfiles Only get all shares in a folder
916
-	 * @param string $path Get shares for a specific path
917
-	 * @param string $include_tags Include tags in the share
918
-	 *
919
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
920
-	 * @throws OCSNotFoundException The folder was not found or is inaccessible
921
-	 *
922
-	 * 200: Shares returned
923
-	 */
924
-	#[NoAdminRequired]
925
-	public function getShares(
926
-		string $shared_with_me = 'false',
927
-		string $reshares = 'false',
928
-		string $subfiles = 'false',
929
-		string $path = '',
930
-		string $include_tags = 'false',
931
-	): DataResponse {
932
-		$node = null;
933
-		if ($path !== '') {
934
-			$userFolder = $this->rootFolder->getUserFolder($this->userId);
935
-			try {
936
-				$node = $userFolder->get($path);
937
-				$this->lock($node);
938
-			} catch (NotFoundException $e) {
939
-				throw new OCSNotFoundException(
940
-					$this->l->t('Wrong path, file/folder does not exist')
941
-				);
942
-			} catch (LockedException $e) {
943
-				throw new OCSNotFoundException($this->l->t('Could not lock node'));
944
-			}
945
-		}
946
-
947
-		$shares = $this->getFormattedShares(
948
-			$this->userId,
949
-			$node,
950
-			($shared_with_me === 'true'),
951
-			($reshares === 'true'),
952
-			($subfiles === 'true'),
953
-			($include_tags === 'true')
954
-		);
955
-
956
-		return new DataResponse($shares);
957
-	}
958
-
959
-	private function getLinkSharePermissions(?int $permissions, ?bool $legacyPublicUpload): int {
960
-		$permissions = $permissions ?? Constants::PERMISSION_READ;
961
-
962
-		// Legacy option handling
963
-		if ($legacyPublicUpload !== null) {
964
-			$permissions = $legacyPublicUpload
965
-				? (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
966
-				: Constants::PERMISSION_READ;
967
-		}
968
-
969
-		// TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
970
-		if ($this->hasPermission($permissions, Constants::PERMISSION_READ)
971
-			&& $this->shareManager->outgoingServer2ServerSharesAllowed()) {
972
-			$permissions |= Constants::PERMISSION_SHARE;
973
-		}
974
-
975
-		return $permissions;
976
-	}
977
-
978
-	/**
979
-	 * Helper to check for legacy "publicUpload" handling.
980
-	 * If the value is set to `true` or `false` then true or false are returned.
981
-	 * Otherwise null is returned to indicate that the option was not (or wrong) set.
982
-	 *
983
-	 * @param null|string $legacyPublicUpload The value of `publicUpload`
984
-	 */
985
-	private function getLegacyPublicUpload(?string $legacyPublicUpload): ?bool {
986
-		if ($legacyPublicUpload === 'true') {
987
-			return true;
988
-		} elseif ($legacyPublicUpload === 'false') {
989
-			return false;
990
-		}
991
-		// Not set at all
992
-		return null;
993
-	}
994
-
995
-	/**
996
-	 * For link and email shares validate that only allowed combinations are set.
997
-	 *
998
-	 * @throw OCSBadRequestException If permission combination is invalid.
999
-	 * @throw OCSForbiddenException If public upload was forbidden by the administrator.
1000
-	 */
1001
-	private function validateLinkSharePermissions(Node $node, int $permissions, ?bool $legacyPublicUpload): void {
1002
-		if ($legacyPublicUpload && ($node instanceof File)) {
1003
-			throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
1004
-		}
1005
-
1006
-		// We need at least READ or CREATE (file drop)
1007
-		if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1008
-			&& !$this->hasPermission($permissions, Constants::PERMISSION_CREATE)) {
1009
-			throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
1010
-		}
1011
-
1012
-		// UPDATE and DELETE require a READ permission
1013
-		if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1014
-			&& ($this->hasPermission($permissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($permissions, Constants::PERMISSION_DELETE))) {
1015
-			throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
1016
-		}
1017
-
1018
-		// Check if public uploading was disabled
1019
-		if ($this->hasPermission($permissions, Constants::PERMISSION_CREATE)
1020
-			&& !$this->shareManager->shareApiLinkAllowPublicUpload()) {
1021
-			throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
1022
-		}
1023
-	}
1024
-
1025
-	/**
1026
-	 * @param string $viewer
1027
-	 * @param Node $node
1028
-	 * @param bool $sharedWithMe
1029
-	 * @param bool $reShares
1030
-	 * @param bool $subFiles
1031
-	 * @param bool $includeTags
1032
-	 *
1033
-	 * @return list<Files_SharingShare>
1034
-	 * @throws NotFoundException
1035
-	 * @throws OCSBadRequestException
1036
-	 */
1037
-	private function getFormattedShares(
1038
-		string $viewer,
1039
-		$node = null,
1040
-		bool $sharedWithMe = false,
1041
-		bool $reShares = false,
1042
-		bool $subFiles = false,
1043
-		bool $includeTags = false,
1044
-	): array {
1045
-		if ($sharedWithMe) {
1046
-			return $this->getSharedWithMe($node, $includeTags);
1047
-		}
1048
-
1049
-		if ($subFiles) {
1050
-			return $this->getSharesInDir($node);
1051
-		}
1052
-
1053
-		$shares = $this->getSharesFromNode($viewer, $node, $reShares);
1054
-
1055
-		$known = $formatted = $miniFormatted = [];
1056
-		$resharingRight = false;
1057
-		foreach ($shares as $share) {
1058
-			try {
1059
-				$share->getNode();
1060
-			} catch (NotFoundException $e) {
1061
-				/*
74
+    private ?Node $lockedNode = null;
75
+
76
+    /**
77
+     * Share20OCS constructor.
78
+     */
79
+    public function __construct(
80
+        string $appName,
81
+        IRequest $request,
82
+        private IManager $shareManager,
83
+        private IGroupManager $groupManager,
84
+        private IUserManager $userManager,
85
+        private IRootFolder $rootFolder,
86
+        private IURLGenerator $urlGenerator,
87
+        private IL10N $l,
88
+        private IConfig $config,
89
+        private IAppManager $appManager,
90
+        private ContainerInterface $serverContainer,
91
+        private IUserStatusManager $userStatusManager,
92
+        private IPreview $previewManager,
93
+        private IDateTimeZone $dateTimeZone,
94
+        private LoggerInterface $logger,
95
+        private IProviderFactory $factory,
96
+        private IMailer $mailer,
97
+        private ?string $userId = null,
98
+    ) {
99
+        parent::__construct($appName, $request);
100
+    }
101
+
102
+    /**
103
+     * Convert an IShare to an array for OCS output
104
+     *
105
+     * @param IShare $share
106
+     * @param Node|null $recipientNode
107
+     * @return Files_SharingShare
108
+     * @throws NotFoundException In case the node can't be resolved.
109
+     *
110
+     * @suppress PhanUndeclaredClassMethod
111
+     */
112
+    protected function formatShare(IShare $share, ?Node $recipientNode = null): array {
113
+        $sharedBy = $this->userManager->get($share->getSharedBy());
114
+        $shareOwner = $this->userManager->get($share->getShareOwner());
115
+
116
+        $isOwnShare = false;
117
+        if ($shareOwner !== null) {
118
+            $isOwnShare = $shareOwner->getUID() === $this->userId;
119
+        }
120
+
121
+        $result = [
122
+            'id' => $share->getId(),
123
+            'share_type' => $share->getShareType(),
124
+            'uid_owner' => $share->getSharedBy(),
125
+            'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
126
+            // recipient permissions
127
+            'permissions' => $share->getPermissions(),
128
+            // current user permissions on this share
129
+            'can_edit' => $this->canEditShare($share),
130
+            'can_delete' => $this->canDeleteShare($share),
131
+            'stime' => $share->getShareTime()->getTimestamp(),
132
+            'parent' => null,
133
+            'expiration' => null,
134
+            'token' => null,
135
+            'uid_file_owner' => $share->getShareOwner(),
136
+            'note' => $share->getNote(),
137
+            'label' => $share->getLabel(),
138
+            'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
139
+        ];
140
+
141
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
142
+        if ($recipientNode) {
143
+            $node = $recipientNode;
144
+        } else {
145
+            $node = $userFolder->getFirstNodeById($share->getNodeId());
146
+            if (!$node) {
147
+                // fallback to guessing the path
148
+                $node = $userFolder->get($share->getTarget());
149
+                if ($node === null || $share->getTarget() === '') {
150
+                    throw new NotFoundException();
151
+                }
152
+            }
153
+        }
154
+
155
+        $result['path'] = $userFolder->getRelativePath($node->getPath());
156
+        if ($node instanceof Folder) {
157
+            $result['item_type'] = 'folder';
158
+        } else {
159
+            $result['item_type'] = 'file';
160
+        }
161
+
162
+        // Get the original node permission if the share owner is the current user
163
+        if ($isOwnShare) {
164
+            $result['item_permissions'] = $node->getPermissions();
165
+        }
166
+
167
+        // If we're on the recipient side, the node permissions
168
+        // are bound to the share permissions. So we need to
169
+        // adjust the permissions to the share permissions if necessary.
170
+        if (!$isOwnShare) {
171
+            $result['item_permissions'] = $share->getPermissions();
172
+
173
+            // For some reason, single files share are forbidden to have the delete permission
174
+            // since we have custom methods to check those, let's adjust straight away.
175
+            // DAV permissions does not have that issue though.
176
+            if ($this->canDeleteShare($share) || $this->canDeleteShareFromSelf($share)) {
177
+                $result['item_permissions'] |= Constants::PERMISSION_DELETE;
178
+            }
179
+            if ($this->canEditShare($share)) {
180
+                $result['item_permissions'] |= Constants::PERMISSION_UPDATE;
181
+            }
182
+        }
183
+
184
+        // See MOUNT_ROOT_PROPERTYNAME dav property
185
+        $result['is-mount-root'] = $node->getInternalPath() === '';
186
+        $result['mount-type'] = $node->getMountPoint()->getMountType();
187
+
188
+        $result['mimetype'] = $node->getMimetype();
189
+        $result['has_preview'] = $this->previewManager->isAvailable($node);
190
+        $result['storage_id'] = $node->getStorage()->getId();
191
+        $result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
192
+        $result['item_source'] = $node->getId();
193
+        $result['file_source'] = $node->getId();
194
+        $result['file_parent'] = $node->getParent()->getId();
195
+        $result['file_target'] = $share->getTarget();
196
+        $result['item_size'] = $node->getSize();
197
+        $result['item_mtime'] = $node->getMTime();
198
+
199
+        $expiration = $share->getExpirationDate();
200
+        if ($expiration !== null) {
201
+            $expiration->setTimezone($this->dateTimeZone->getTimeZone());
202
+            $result['expiration'] = $expiration->format('Y-m-d 00:00:00');
203
+        }
204
+
205
+        if ($share->getShareType() === IShare::TYPE_USER) {
206
+            $sharedWith = $this->userManager->get($share->getSharedWith());
207
+            $result['share_with'] = $share->getSharedWith();
208
+            $result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
209
+            $result['share_with_displayname_unique'] = $sharedWith !== null ? (
210
+                !empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID()
211
+            ) : $share->getSharedWith();
212
+
213
+            $userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
214
+            $userStatus = array_shift($userStatuses);
215
+            if ($userStatus) {
216
+                $result['status'] = [
217
+                    'status' => $userStatus->getStatus(),
218
+                    'message' => $userStatus->getMessage(),
219
+                    'icon' => $userStatus->getIcon(),
220
+                    'clearAt' => $userStatus->getClearAt()
221
+                        ? (int)$userStatus->getClearAt()->format('U')
222
+                        : null,
223
+                ];
224
+            }
225
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
226
+            $group = $this->groupManager->get($share->getSharedWith());
227
+            $result['share_with'] = $share->getSharedWith();
228
+            $result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
229
+        } elseif ($share->getShareType() === IShare::TYPE_LINK) {
230
+
231
+            // "share_with" and "share_with_displayname" for passwords of link
232
+            // shares was deprecated in Nextcloud 15, use "password" instead.
233
+            $result['share_with'] = $share->getPassword();
234
+            $result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
235
+
236
+            $result['password'] = $share->getPassword();
237
+
238
+            $result['send_password_by_talk'] = $share->getSendPasswordByTalk();
239
+
240
+            $result['token'] = $share->getToken();
241
+            $result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
242
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
243
+            $result['share_with'] = $share->getSharedWith();
244
+            $result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith());
245
+            $result['token'] = $share->getToken();
246
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
247
+            $result['share_with'] = $share->getSharedWith();
248
+            $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
249
+            $result['token'] = $share->getToken();
250
+        } elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
251
+            $result['share_with'] = $share->getSharedWith();
252
+            $result['password'] = $share->getPassword();
253
+            $result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null;
254
+            $result['send_password_by_talk'] = $share->getSendPasswordByTalk();
255
+            $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
256
+            $result['token'] = $share->getToken();
257
+        } elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
258
+            // getSharedWith() returns either "name (type, owner)" or
259
+            // "name (type, owner) [id]", depending on the Teams app version.
260
+            $hasCircleId = (substr($share->getSharedWith(), -1) === ']');
261
+
262
+            $result['share_with_displayname'] = $share->getSharedWithDisplayName();
263
+            if (empty($result['share_with_displayname'])) {
264
+                $displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
265
+                $result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
266
+            }
267
+
268
+            $result['share_with_avatar'] = $share->getSharedWithAvatar();
269
+
270
+            $shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
271
+            $shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
272
+            if ($shareWithLength === false) {
273
+                $result['share_with'] = substr($share->getSharedWith(), $shareWithStart);
274
+            } else {
275
+                $result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
276
+            }
277
+        } elseif ($share->getShareType() === IShare::TYPE_ROOM) {
278
+            $result['share_with'] = $share->getSharedWith();
279
+            $result['share_with_displayname'] = '';
280
+
281
+            try {
282
+                /** @var array{share_with_displayname: string, share_with_link: string, share_with?: string, token?: string} $roomShare */
283
+                $roomShare = $this->getRoomShareHelper()->formatShare($share);
284
+                $result = array_merge($result, $roomShare);
285
+            } catch (ContainerExceptionInterface $e) {
286
+            }
287
+        } elseif ($share->getShareType() === IShare::TYPE_DECK) {
288
+            $result['share_with'] = $share->getSharedWith();
289
+            $result['share_with_displayname'] = '';
290
+
291
+            try {
292
+                /** @var array{share_with: string, share_with_displayname: string, share_with_link: string} $deckShare */
293
+                $deckShare = $this->getDeckShareHelper()->formatShare($share);
294
+                $result = array_merge($result, $deckShare);
295
+            } catch (ContainerExceptionInterface $e) {
296
+            }
297
+        } elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
298
+            $result['share_with'] = $share->getSharedWith();
299
+            $result['share_with_displayname'] = '';
300
+
301
+            try {
302
+                /** @var array{share_with: string, share_with_displayname: string, token: string} $scienceMeshShare */
303
+                $scienceMeshShare = $this->getSciencemeshShareHelper()->formatShare($share);
304
+                $result = array_merge($result, $scienceMeshShare);
305
+            } catch (ContainerExceptionInterface $e) {
306
+            }
307
+        }
308
+
309
+
310
+        $result['mail_send'] = $share->getMailSend() ? 1 : 0;
311
+        $result['hide_download'] = $share->getHideDownload() ? 1 : 0;
312
+
313
+        $result['attributes'] = null;
314
+        if ($attributes = $share->getAttributes()) {
315
+            $result['attributes'] = (string)\json_encode($attributes->toArray());
316
+        }
317
+
318
+        return $result;
319
+    }
320
+
321
+    /**
322
+     * Check if one of the users address books knows the exact property, if
323
+     * not we return the full name.
324
+     *
325
+     * @param string $query
326
+     * @param string $property
327
+     * @return string
328
+     */
329
+    private function getDisplayNameFromAddressBook(string $query, string $property): string {
330
+        // FIXME: If we inject the contacts manager it gets initialized before any address books are registered
331
+        try {
332
+            $result = Server::get(\OCP\Contacts\IManager::class)->search($query, [$property], [
333
+                'limit' => 1,
334
+                'enumeration' => false,
335
+                'strict_search' => true,
336
+            ]);
337
+        } catch (Exception $e) {
338
+            $this->logger->error(
339
+                $e->getMessage(),
340
+                ['exception' => $e]
341
+            );
342
+            return $query;
343
+        }
344
+
345
+        foreach ($result as $r) {
346
+            foreach ($r[$property] as $value) {
347
+                if ($value === $query && $r['FN']) {
348
+                    return $r['FN'];
349
+                }
350
+            }
351
+        }
352
+
353
+        return $query;
354
+    }
355
+
356
+
357
+    /**
358
+     * @param list<Files_SharingShare> $shares
359
+     * @param array<string, string>|null $updatedDisplayName
360
+     *
361
+     * @return list<Files_SharingShare>
362
+     */
363
+    private function fixMissingDisplayName(array $shares, ?array $updatedDisplayName = null): array {
364
+        $userIds = $updated = [];
365
+        foreach ($shares as $share) {
366
+            // share is federated and share have no display name yet
367
+            if ($share['share_type'] === IShare::TYPE_REMOTE
368
+                && ($share['share_with'] ?? '') !== ''
369
+                && ($share['share_with_displayname'] ?? '') === '') {
370
+                $userIds[] = $userId = $share['share_with'];
371
+
372
+                if ($updatedDisplayName !== null && array_key_exists($userId, $updatedDisplayName)) {
373
+                    $share['share_with_displayname'] = $updatedDisplayName[$userId];
374
+                }
375
+            }
376
+
377
+            // prepping userIds with displayName to be updated
378
+            $updated[] = $share;
379
+        }
380
+
381
+        // if $updatedDisplayName is not null, it means we should have already fixed displayNames of the shares
382
+        if ($updatedDisplayName !== null) {
383
+            return $updated;
384
+        }
385
+
386
+        // get displayName for the generated list of userId with no displayName
387
+        $displayNames = $this->retrieveFederatedDisplayName($userIds);
388
+
389
+        // if no displayName are updated, we exit
390
+        if (empty($displayNames)) {
391
+            return $updated;
392
+        }
393
+
394
+        // let's fix missing display name and returns all shares
395
+        return $this->fixMissingDisplayName($shares, $displayNames);
396
+    }
397
+
398
+
399
+    /**
400
+     * get displayName of a list of userIds from the lookup-server; through the globalsiteselector app.
401
+     * returns an array with userIds as keys and displayName as values.
402
+     *
403
+     * @param array $userIds
404
+     * @param bool $cacheOnly - do not reach LUS, get data from cache.
405
+     *
406
+     * @return array
407
+     * @throws ContainerExceptionInterface
408
+     */
409
+    private function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = false): array {
410
+        // check if gss is enabled and available
411
+        if (count($userIds) === 0
412
+            || !$this->appManager->isEnabledForAnyone('globalsiteselector')
413
+            || !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
414
+            return [];
415
+        }
416
+
417
+        try {
418
+            $slaveService = Server::get(SlaveService::class);
419
+        } catch (\Throwable $e) {
420
+            $this->logger->error(
421
+                $e->getMessage(),
422
+                ['exception' => $e]
423
+            );
424
+            return [];
425
+        }
426
+
427
+        return $slaveService->getUsersDisplayName($userIds, $cacheOnly);
428
+    }
429
+
430
+
431
+    /**
432
+     * retrieve displayName from cache if available (should be used on federated shares)
433
+     * if not available in cache/lus, try for get from address-book, else returns empty string.
434
+     *
435
+     * @param string $userId
436
+     * @param bool $cacheOnly if true will not reach the lus but will only get data from cache
437
+     *
438
+     * @return string
439
+     */
440
+    private function getCachedFederatedDisplayName(string $userId, bool $cacheOnly = true): string {
441
+        $details = $this->retrieveFederatedDisplayName([$userId], $cacheOnly);
442
+        if (array_key_exists($userId, $details)) {
443
+            return $details[$userId];
444
+        }
445
+
446
+        $displayName = $this->getDisplayNameFromAddressBook($userId, 'CLOUD');
447
+        return ($displayName === $userId) ? '' : $displayName;
448
+    }
449
+
450
+
451
+
452
+    /**
453
+     * Get a specific share by id
454
+     *
455
+     * @param string $id ID of the share
456
+     * @param bool $include_tags Include tags in the share
457
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
458
+     * @throws OCSNotFoundException Share not found
459
+     *
460
+     * 200: Share returned
461
+     */
462
+    #[NoAdminRequired]
463
+    public function getShare(string $id, bool $include_tags = false): DataResponse {
464
+        try {
465
+            $share = $this->getShareById($id);
466
+        } catch (ShareNotFound $e) {
467
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
468
+        }
469
+
470
+        try {
471
+            if ($this->canAccessShare($share)) {
472
+                $share = $this->formatShare($share);
473
+
474
+                if ($include_tags) {
475
+                    $share = Helper::populateTags([$share], Server::get(ITagManager::class));
476
+                } else {
477
+                    $share = [$share];
478
+                }
479
+
480
+                return new DataResponse($share);
481
+            }
482
+        } catch (NotFoundException $e) {
483
+            // Fall through
484
+        }
485
+
486
+        throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
487
+    }
488
+
489
+    /**
490
+     * Delete a share
491
+     *
492
+     * @param string $id ID of the share
493
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
494
+     * @throws OCSNotFoundException Share not found
495
+     * @throws OCSForbiddenException Missing permissions to delete the share
496
+     *
497
+     * 200: Share deleted successfully
498
+     */
499
+    #[NoAdminRequired]
500
+    public function deleteShare(string $id): DataResponse {
501
+        try {
502
+            $share = $this->getShareById($id);
503
+        } catch (ShareNotFound $e) {
504
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
505
+        }
506
+
507
+        try {
508
+            $this->lock($share->getNode());
509
+        } catch (LockedException $e) {
510
+            throw new OCSNotFoundException($this->l->t('Could not delete share'));
511
+        }
512
+
513
+        if (!$this->canAccessShare($share)) {
514
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
515
+        }
516
+
517
+        // if it's a group share or a room share
518
+        // we don't delete the share, but only the
519
+        // mount point. Allowing it to be restored
520
+        // from the deleted shares
521
+        if ($this->canDeleteShareFromSelf($share)) {
522
+            $this->shareManager->deleteFromSelf($share, $this->userId);
523
+        } else {
524
+            if (!$this->canDeleteShare($share)) {
525
+                throw new OCSForbiddenException($this->l->t('Could not delete share'));
526
+            }
527
+
528
+            $this->shareManager->deleteShare($share);
529
+        }
530
+
531
+        return new DataResponse();
532
+    }
533
+
534
+    /**
535
+     * Create a share
536
+     *
537
+     * @param string|null $path Path of the share
538
+     * @param int|null $permissions Permissions for the share
539
+     * @param int $shareType Type of the share
540
+     * @param ?string $shareWith The entity this should be shared with
541
+     * @param 'true'|'false'|null $publicUpload If public uploading is allowed (deprecated)
542
+     * @param string $password Password for the share
543
+     * @param string|null $sendPasswordByTalk Send the password for the share over Talk
544
+     * @param ?string $expireDate The expiry date of the share in the user's timezone at 00:00.
545
+     *                            If $expireDate is not supplied or set to `null`, the system default will be used.
546
+     * @param string $note Note for the share
547
+     * @param string $label Label for the share (only used in link and email)
548
+     * @param string|null $attributes Additional attributes for the share
549
+     * @param 'false'|'true'|null $sendMail Send a mail to the recipient
550
+     *
551
+     * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
552
+     * @throws OCSBadRequestException Unknown share type
553
+     * @throws OCSException
554
+     * @throws OCSForbiddenException Creating the share is not allowed
555
+     * @throws OCSNotFoundException Creating the share failed
556
+     * @suppress PhanUndeclaredClassMethod
557
+     *
558
+     * 200: Share created
559
+     */
560
+    #[NoAdminRequired]
561
+    #[UserRateLimit(limit: 20, period: 600)]
562
+    public function createShare(
563
+        ?string $path = null,
564
+        ?int $permissions = null,
565
+        int $shareType = -1,
566
+        ?string $shareWith = null,
567
+        ?string $publicUpload = null,
568
+        string $password = '',
569
+        ?string $sendPasswordByTalk = null,
570
+        ?string $expireDate = null,
571
+        string $note = '',
572
+        string $label = '',
573
+        ?string $attributes = null,
574
+        ?string $sendMail = null,
575
+    ): DataResponse {
576
+        assert($this->userId !== null);
577
+
578
+        $share = $this->shareManager->newShare();
579
+        $hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
580
+
581
+        // Verify path
582
+        if ($path === null) {
583
+            throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
584
+        }
585
+
586
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
587
+        try {
588
+            /** @var \OC\Files\Node\Node $node */
589
+            $node = $userFolder->get($path);
590
+        } catch (NotFoundException $e) {
591
+            throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
592
+        }
593
+
594
+        // a user can have access to a file through different paths, with differing permissions
595
+        // combine all permissions to determine if the user can share this file
596
+        $nodes = $userFolder->getById($node->getId());
597
+        foreach ($nodes as $nodeById) {
598
+            /** @var \OC\Files\FileInfo $fileInfo */
599
+            $fileInfo = $node->getFileInfo();
600
+            $fileInfo['permissions'] |= $nodeById->getPermissions();
601
+        }
602
+
603
+        $share->setNode($node);
604
+
605
+        try {
606
+            $this->lock($share->getNode());
607
+        } catch (LockedException $e) {
608
+            throw new OCSNotFoundException($this->l->t('Could not create share'));
609
+        }
610
+
611
+        // Set permissions
612
+        if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_EMAIL) {
613
+            $permissions = $this->getLinkSharePermissions($permissions, $hasPublicUpload);
614
+            $this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
615
+        } else {
616
+            // Use default permissions only for non-link shares to keep legacy behavior
617
+            if ($permissions === null) {
618
+                $permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
619
+            }
620
+            // Non-link shares always require read permissions (link shares could be file drop)
621
+            $permissions |= Constants::PERMISSION_READ;
622
+        }
623
+
624
+        // For legacy reasons the API allows to pass PERMISSIONS_ALL even for single file shares (I look at you Talk)
625
+        if ($node instanceof File) {
626
+            // if this is a single file share we remove the DELETE and CREATE permissions
627
+            $permissions = $permissions & ~(Constants::PERMISSION_DELETE | Constants::PERMISSION_CREATE);
628
+        }
629
+
630
+        /**
631
+         * Hack for https://github.com/owncloud/core/issues/22587
632
+         * We check the permissions via webdav. But the permissions of the mount point
633
+         * do not equal the share permissions. Here we fix that for federated mounts.
634
+         */
635
+        if ($node->getStorage()->instanceOfStorage(Storage::class)) {
636
+            $permissions &= ~($permissions & ~$node->getPermissions());
637
+        }
638
+
639
+        if ($attributes !== null) {
640
+            $share = $this->setShareAttributes($share, $attributes);
641
+        }
642
+
643
+        // Expire date checks
644
+        // Normally, null means no expiration date but we still set the default for backwards compatibility
645
+        // If the client sends an empty string, we set noExpirationDate to true
646
+        if ($expireDate !== null) {
647
+            if ($expireDate !== '') {
648
+                try {
649
+                    $expireDateTime = $this->parseDate($expireDate);
650
+                    $share->setExpirationDate($expireDateTime);
651
+                } catch (\Exception $e) {
652
+                    throw new OCSNotFoundException($e->getMessage(), $e);
653
+                }
654
+            } else {
655
+                // Client sent empty string for expire date.
656
+                // Set noExpirationDate to true so overwrite is prevented.
657
+                $share->setNoExpirationDate(true);
658
+            }
659
+        }
660
+
661
+        $share->setSharedBy($this->userId);
662
+
663
+        // Handle mail send
664
+        if (is_null($sendMail)) {
665
+            $allowSendMail = $this->config->getSystemValueBool('sharing.enable_share_mail', true);
666
+            if ($allowSendMail !== true || $shareType === IShare::TYPE_EMAIL) {
667
+                // Define a default behavior when sendMail is not provided
668
+                // For email shares with a valid recipient, the default is to send the mail
669
+                // For all other share types, the default is to not send the mail
670
+                $allowSendMail = ($shareType === IShare::TYPE_EMAIL && $shareWith !== null && $shareWith !== '');
671
+            }
672
+            $share->setMailSend($allowSendMail);
673
+        } else {
674
+            $share->setMailSend($sendMail === 'true');
675
+        }
676
+
677
+        if ($shareType === IShare::TYPE_USER) {
678
+            // Valid user is required to share
679
+            if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
680
+                throw new OCSNotFoundException($this->l->t('Please specify a valid account to share with'));
681
+            }
682
+            $share->setSharedWith($shareWith);
683
+            $share->setPermissions($permissions);
684
+        } elseif ($shareType === IShare::TYPE_GROUP) {
685
+            if (!$this->shareManager->allowGroupSharing()) {
686
+                throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator'));
687
+            }
688
+
689
+            // Valid group is required to share
690
+            if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) {
691
+                throw new OCSNotFoundException($this->l->t('Please specify a valid group'));
692
+            }
693
+            $share->setSharedWith($shareWith);
694
+            $share->setPermissions($permissions);
695
+        } elseif ($shareType === IShare::TYPE_LINK
696
+            || $shareType === IShare::TYPE_EMAIL) {
697
+
698
+            // Can we even share links?
699
+            if (!$this->shareManager->shareApiAllowLinks()) {
700
+                throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
701
+            }
702
+
703
+            $this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
704
+            $share->setPermissions($permissions);
705
+
706
+            // Set password
707
+            if ($password !== '') {
708
+                $share->setPassword($password);
709
+            }
710
+
711
+            // Only share by mail have a recipient
712
+            if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) {
713
+                // If sending a mail have been requested, validate the mail address
714
+                if ($share->getMailSend() && !$this->mailer->validateMailAddress($shareWith)) {
715
+                    throw new OCSNotFoundException($this->l->t('Please specify a valid email address'));
716
+                }
717
+                $share->setSharedWith($shareWith);
718
+            }
719
+
720
+            // If we have a label, use it
721
+            if ($label !== '') {
722
+                if (strlen($label) > 255) {
723
+                    throw new OCSBadRequestException('Maximum label length is 255');
724
+                }
725
+                $share->setLabel($label);
726
+            }
727
+
728
+            if ($sendPasswordByTalk === 'true') {
729
+                if (!$this->appManager->isEnabledForUser('spreed')) {
730
+                    throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$node->getPath()]));
731
+                }
732
+
733
+                $share->setSendPasswordByTalk(true);
734
+            }
735
+        } elseif ($shareType === IShare::TYPE_REMOTE) {
736
+            if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
737
+                throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
738
+            }
739
+
740
+            if ($shareWith === null) {
741
+                throw new OCSNotFoundException($this->l->t('Please specify a valid federated account ID'));
742
+            }
743
+
744
+            $share->setSharedWith($shareWith);
745
+            $share->setPermissions($permissions);
746
+            $share->setSharedWithDisplayName($this->getCachedFederatedDisplayName($shareWith, false));
747
+        } elseif ($shareType === IShare::TYPE_REMOTE_GROUP) {
748
+            if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
749
+                throw new OCSForbiddenException($this->l->t('Sharing %1$s failed because the back end does not allow shares from type %2$s', [$node->getPath(), $shareType]));
750
+            }
751
+
752
+            if ($shareWith === null) {
753
+                throw new OCSNotFoundException($this->l->t('Please specify a valid federated group ID'));
754
+            }
755
+
756
+            $share->setSharedWith($shareWith);
757
+            $share->setPermissions($permissions);
758
+        } elseif ($shareType === IShare::TYPE_CIRCLE) {
759
+            if (!Server::get(IAppManager::class)->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
760
+                throw new OCSNotFoundException($this->l->t('You cannot share to a Team if the app is not enabled'));
761
+            }
762
+
763
+            $circle = Circles::detailsCircle($shareWith);
764
+
765
+            // Valid team is required to share
766
+            if ($circle === null) {
767
+                throw new OCSNotFoundException($this->l->t('Please specify a valid team'));
768
+            }
769
+            $share->setSharedWith($shareWith);
770
+            $share->setPermissions($permissions);
771
+        } elseif ($shareType === IShare::TYPE_ROOM) {
772
+            try {
773
+                $this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
774
+            } catch (ContainerExceptionInterface $e) {
775
+                throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
776
+            }
777
+        } elseif ($shareType === IShare::TYPE_DECK) {
778
+            try {
779
+                $this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
780
+            } catch (ContainerExceptionInterface $e) {
781
+                throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
782
+            }
783
+        } elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
784
+            try {
785
+                $this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
786
+            } catch (ContainerExceptionInterface $e) {
787
+                throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support ScienceMesh shares', [$node->getPath()]));
788
+            }
789
+        } else {
790
+            throw new OCSBadRequestException($this->l->t('Unknown share type'));
791
+        }
792
+
793
+        $share->setShareType($shareType);
794
+        $this->checkInheritedAttributes($share);
795
+
796
+        if ($note !== '') {
797
+            $share->setNote($note);
798
+        }
799
+
800
+        try {
801
+            $share = $this->shareManager->createShare($share);
802
+        } catch (HintException $e) {
803
+            $code = $e->getCode() === 0 ? 403 : $e->getCode();
804
+            throw new OCSException($e->getHint(), $code);
805
+        } catch (GenericShareException|\InvalidArgumentException $e) {
806
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
807
+            throw new OCSForbiddenException($e->getMessage(), $e);
808
+        } catch (\Exception $e) {
809
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
810
+            throw new OCSForbiddenException('Failed to create share.', $e);
811
+        }
812
+
813
+        $output = $this->formatShare($share);
814
+
815
+        return new DataResponse($output);
816
+    }
817
+
818
+    /**
819
+     * @param null|Node $node
820
+     * @param boolean $includeTags
821
+     *
822
+     * @return list<Files_SharingShare>
823
+     */
824
+    private function getSharedWithMe($node, bool $includeTags): array {
825
+        $userShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_USER, $node, -1, 0);
826
+        $groupShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_GROUP, $node, -1, 0);
827
+        $circleShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_CIRCLE, $node, -1, 0);
828
+        $roomShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_ROOM, $node, -1, 0);
829
+        $deckShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_DECK, $node, -1, 0);
830
+        $sciencemeshShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_SCIENCEMESH, $node, -1, 0);
831
+
832
+        $shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares);
833
+
834
+        $filteredShares = array_filter($shares, function (IShare $share) {
835
+            return $share->getShareOwner() !== $this->userId;
836
+        });
837
+
838
+        $formatted = [];
839
+        foreach ($filteredShares as $share) {
840
+            if ($this->canAccessShare($share)) {
841
+                try {
842
+                    $formatted[] = $this->formatShare($share);
843
+                } catch (NotFoundException $e) {
844
+                    // Ignore this share
845
+                }
846
+            }
847
+        }
848
+
849
+        if ($includeTags) {
850
+            $formatted = Helper::populateTags($formatted, Server::get(ITagManager::class));
851
+        }
852
+
853
+        return $formatted;
854
+    }
855
+
856
+    /**
857
+     * @param Node $folder
858
+     *
859
+     * @return list<Files_SharingShare>
860
+     * @throws OCSBadRequestException
861
+     * @throws NotFoundException
862
+     */
863
+    private function getSharesInDir(Node $folder): array {
864
+        if (!($folder instanceof Folder)) {
865
+            throw new OCSBadRequestException($this->l->t('Not a directory'));
866
+        }
867
+
868
+        $nodes = $folder->getDirectoryListing();
869
+
870
+        /** @var IShare[] $shares */
871
+        $shares = array_reduce($nodes, function ($carry, $node) {
872
+            $carry = array_merge($carry, $this->getAllShares($node, true));
873
+            return $carry;
874
+        }, []);
875
+
876
+        // filter out duplicate shares
877
+        $known = [];
878
+
879
+        $formatted = $miniFormatted = [];
880
+        $resharingRight = false;
881
+        $known = [];
882
+        foreach ($shares as $share) {
883
+            if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->userId) {
884
+                continue;
885
+            }
886
+
887
+            try {
888
+                $format = $this->formatShare($share);
889
+
890
+                $known[] = $share->getId();
891
+                $formatted[] = $format;
892
+                if ($share->getSharedBy() === $this->userId) {
893
+                    $miniFormatted[] = $format;
894
+                }
895
+                if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $folder)) {
896
+                    $resharingRight = true;
897
+                }
898
+            } catch (\Exception $e) {
899
+                //Ignore this share
900
+            }
901
+        }
902
+
903
+        if (!$resharingRight) {
904
+            $formatted = $miniFormatted;
905
+        }
906
+
907
+        return $formatted;
908
+    }
909
+
910
+    /**
911
+     * Get shares of the current user
912
+     *
913
+     * @param string $shared_with_me Only get shares with the current user
914
+     * @param string $reshares Only get shares by the current user and reshares
915
+     * @param string $subfiles Only get all shares in a folder
916
+     * @param string $path Get shares for a specific path
917
+     * @param string $include_tags Include tags in the share
918
+     *
919
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
920
+     * @throws OCSNotFoundException The folder was not found or is inaccessible
921
+     *
922
+     * 200: Shares returned
923
+     */
924
+    #[NoAdminRequired]
925
+    public function getShares(
926
+        string $shared_with_me = 'false',
927
+        string $reshares = 'false',
928
+        string $subfiles = 'false',
929
+        string $path = '',
930
+        string $include_tags = 'false',
931
+    ): DataResponse {
932
+        $node = null;
933
+        if ($path !== '') {
934
+            $userFolder = $this->rootFolder->getUserFolder($this->userId);
935
+            try {
936
+                $node = $userFolder->get($path);
937
+                $this->lock($node);
938
+            } catch (NotFoundException $e) {
939
+                throw new OCSNotFoundException(
940
+                    $this->l->t('Wrong path, file/folder does not exist')
941
+                );
942
+            } catch (LockedException $e) {
943
+                throw new OCSNotFoundException($this->l->t('Could not lock node'));
944
+            }
945
+        }
946
+
947
+        $shares = $this->getFormattedShares(
948
+            $this->userId,
949
+            $node,
950
+            ($shared_with_me === 'true'),
951
+            ($reshares === 'true'),
952
+            ($subfiles === 'true'),
953
+            ($include_tags === 'true')
954
+        );
955
+
956
+        return new DataResponse($shares);
957
+    }
958
+
959
+    private function getLinkSharePermissions(?int $permissions, ?bool $legacyPublicUpload): int {
960
+        $permissions = $permissions ?? Constants::PERMISSION_READ;
961
+
962
+        // Legacy option handling
963
+        if ($legacyPublicUpload !== null) {
964
+            $permissions = $legacyPublicUpload
965
+                ? (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
966
+                : Constants::PERMISSION_READ;
967
+        }
968
+
969
+        // TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
970
+        if ($this->hasPermission($permissions, Constants::PERMISSION_READ)
971
+            && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
972
+            $permissions |= Constants::PERMISSION_SHARE;
973
+        }
974
+
975
+        return $permissions;
976
+    }
977
+
978
+    /**
979
+     * Helper to check for legacy "publicUpload" handling.
980
+     * If the value is set to `true` or `false` then true or false are returned.
981
+     * Otherwise null is returned to indicate that the option was not (or wrong) set.
982
+     *
983
+     * @param null|string $legacyPublicUpload The value of `publicUpload`
984
+     */
985
+    private function getLegacyPublicUpload(?string $legacyPublicUpload): ?bool {
986
+        if ($legacyPublicUpload === 'true') {
987
+            return true;
988
+        } elseif ($legacyPublicUpload === 'false') {
989
+            return false;
990
+        }
991
+        // Not set at all
992
+        return null;
993
+    }
994
+
995
+    /**
996
+     * For link and email shares validate that only allowed combinations are set.
997
+     *
998
+     * @throw OCSBadRequestException If permission combination is invalid.
999
+     * @throw OCSForbiddenException If public upload was forbidden by the administrator.
1000
+     */
1001
+    private function validateLinkSharePermissions(Node $node, int $permissions, ?bool $legacyPublicUpload): void {
1002
+        if ($legacyPublicUpload && ($node instanceof File)) {
1003
+            throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
1004
+        }
1005
+
1006
+        // We need at least READ or CREATE (file drop)
1007
+        if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1008
+            && !$this->hasPermission($permissions, Constants::PERMISSION_CREATE)) {
1009
+            throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
1010
+        }
1011
+
1012
+        // UPDATE and DELETE require a READ permission
1013
+        if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1014
+            && ($this->hasPermission($permissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($permissions, Constants::PERMISSION_DELETE))) {
1015
+            throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
1016
+        }
1017
+
1018
+        // Check if public uploading was disabled
1019
+        if ($this->hasPermission($permissions, Constants::PERMISSION_CREATE)
1020
+            && !$this->shareManager->shareApiLinkAllowPublicUpload()) {
1021
+            throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
1022
+        }
1023
+    }
1024
+
1025
+    /**
1026
+     * @param string $viewer
1027
+     * @param Node $node
1028
+     * @param bool $sharedWithMe
1029
+     * @param bool $reShares
1030
+     * @param bool $subFiles
1031
+     * @param bool $includeTags
1032
+     *
1033
+     * @return list<Files_SharingShare>
1034
+     * @throws NotFoundException
1035
+     * @throws OCSBadRequestException
1036
+     */
1037
+    private function getFormattedShares(
1038
+        string $viewer,
1039
+        $node = null,
1040
+        bool $sharedWithMe = false,
1041
+        bool $reShares = false,
1042
+        bool $subFiles = false,
1043
+        bool $includeTags = false,
1044
+    ): array {
1045
+        if ($sharedWithMe) {
1046
+            return $this->getSharedWithMe($node, $includeTags);
1047
+        }
1048
+
1049
+        if ($subFiles) {
1050
+            return $this->getSharesInDir($node);
1051
+        }
1052
+
1053
+        $shares = $this->getSharesFromNode($viewer, $node, $reShares);
1054
+
1055
+        $known = $formatted = $miniFormatted = [];
1056
+        $resharingRight = false;
1057
+        foreach ($shares as $share) {
1058
+            try {
1059
+                $share->getNode();
1060
+            } catch (NotFoundException $e) {
1061
+                /*
1062 1062
 				 * Ignore shares where we can't get the node
1063 1063
 				 * For example deleted shares
1064 1064
 				 */
1065
-				continue;
1066
-			}
1067
-
1068
-			if (in_array($share->getId(), $known)
1069
-				|| ($share->getSharedWith() === $this->userId && $share->getShareType() === IShare::TYPE_USER)) {
1070
-				continue;
1071
-			}
1072
-
1073
-			$known[] = $share->getId();
1074
-			try {
1075
-				/** @var IShare $share */
1076
-				$format = $this->formatShare($share, $node);
1077
-				$formatted[] = $format;
1078
-
1079
-				// let's also build a list of shares created
1080
-				// by the current user only, in case
1081
-				// there is no resharing rights
1082
-				if ($share->getSharedBy() === $this->userId) {
1083
-					$miniFormatted[] = $format;
1084
-				}
1085
-
1086
-				// check if one of those share is shared with me
1087
-				// and if I have resharing rights on it
1088
-				if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $node)) {
1089
-					$resharingRight = true;
1090
-				}
1091
-			} catch (InvalidPathException|NotFoundException $e) {
1092
-			}
1093
-		}
1094
-
1095
-		if (!$resharingRight) {
1096
-			$formatted = $miniFormatted;
1097
-		}
1098
-
1099
-		// fix eventual missing display name from federated shares
1100
-		$formatted = $this->fixMissingDisplayName($formatted);
1101
-
1102
-		if ($includeTags) {
1103
-			$formatted =
1104
-				Helper::populateTags($formatted, Server::get(ITagManager::class));
1105
-		}
1106
-
1107
-		return $formatted;
1108
-	}
1109
-
1110
-
1111
-	/**
1112
-	 * Get all shares relative to a file, including parent folders shares rights
1113
-	 *
1114
-	 * @param string $path Path all shares will be relative to
1115
-	 *
1116
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1117
-	 * @throws InvalidPathException
1118
-	 * @throws NotFoundException
1119
-	 * @throws OCSNotFoundException The given path is invalid
1120
-	 * @throws SharingRightsException
1121
-	 *
1122
-	 * 200: Shares returned
1123
-	 */
1124
-	#[NoAdminRequired]
1125
-	public function getInheritedShares(string $path): DataResponse {
1126
-		// get Node from (string) path.
1127
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1128
-		try {
1129
-			$node = $userFolder->get($path);
1130
-			$this->lock($node);
1131
-		} catch (NotFoundException $e) {
1132
-			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
1133
-		} catch (LockedException $e) {
1134
-			throw new OCSNotFoundException($this->l->t('Could not lock path'));
1135
-		}
1136
-
1137
-		if (!($node->getPermissions() & Constants::PERMISSION_SHARE)) {
1138
-			throw new SharingRightsException($this->l->t('no sharing rights on this item'));
1139
-		}
1140
-
1141
-		// The current top parent we have access to
1142
-		$parent = $node;
1143
-
1144
-		// initiate real owner.
1145
-		$owner = $node->getOwner()
1146
-			->getUID();
1147
-		if (!$this->userManager->userExists($owner)) {
1148
-			return new DataResponse([]);
1149
-		}
1150
-
1151
-		// get node based on the owner, fix owner in case of external storage
1152
-		$userFolder = $this->rootFolder->getUserFolder($owner);
1153
-		if ($node->getId() !== $userFolder->getId() && !$userFolder->isSubNode($node)) {
1154
-			$owner = $node->getOwner()
1155
-				->getUID();
1156
-			$userFolder = $this->rootFolder->getUserFolder($owner);
1157
-			$node = $userFolder->getFirstNodeById($node->getId());
1158
-		}
1159
-		$basePath = $userFolder->getPath();
1160
-
1161
-		// generate node list for each parent folders
1162
-		/** @var Node[] $nodes */
1163
-		$nodes = [];
1164
-		while (true) {
1165
-			$node = $node->getParent();
1166
-			if ($node->getPath() === $basePath) {
1167
-				break;
1168
-			}
1169
-			$nodes[] = $node;
1170
-		}
1171
-
1172
-		// The user that is requesting this list
1173
-		$currentUserFolder = $this->rootFolder->getUserFolder($this->userId);
1174
-
1175
-		// for each nodes, retrieve shares.
1176
-		$shares = [];
1177
-
1178
-		foreach ($nodes as $node) {
1179
-			$getShares = $this->getFormattedShares($owner, $node, false, true);
1180
-
1181
-			$currentUserNode = $currentUserFolder->getFirstNodeById($node->getId());
1182
-			if ($currentUserNode) {
1183
-				$parent = $currentUserNode;
1184
-			}
1185
-
1186
-			$subPath = $currentUserFolder->getRelativePath($parent->getPath());
1187
-			foreach ($getShares as &$share) {
1188
-				$share['via_fileid'] = $parent->getId();
1189
-				$share['via_path'] = $subPath;
1190
-			}
1191
-			$this->mergeFormattedShares($shares, $getShares);
1192
-		}
1193
-
1194
-		return new DataResponse(array_values($shares));
1195
-	}
1196
-
1197
-	/**
1198
-	 * Check whether a set of permissions contains the permissions to check.
1199
-	 */
1200
-	private function hasPermission(int $permissionsSet, int $permissionsToCheck): bool {
1201
-		return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
1202
-	}
1203
-
1204
-	/**
1205
-	 * Update a share
1206
-	 *
1207
-	 * @param string $id ID of the share
1208
-	 * @param int|null $permissions New permissions
1209
-	 * @param string|null $password New password
1210
-	 * @param string|null $sendPasswordByTalk New condition if the password should be send over Talk
1211
-	 * @param string|null $publicUpload New condition if public uploading is allowed
1212
-	 * @param string|null $expireDate New expiry date
1213
-	 * @param string|null $note New note
1214
-	 * @param string|null $label New label
1215
-	 * @param string|null $hideDownload New condition if the download should be hidden
1216
-	 * @param string|null $attributes New additional attributes
1217
-	 * @param string|null $sendMail if the share should be send by mail.
1218
-	 *                              Considering the share already exists, no mail will be send after the share is updated.
1219
-	 *                              You will have to use the sendMail action to send the mail.
1220
-	 * @param string|null $shareWith New recipient for email shares
1221
-	 * @param string|null $token New token
1222
-	 * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
1223
-	 * @throws OCSBadRequestException Share could not be updated because the requested changes are invalid
1224
-	 * @throws OCSForbiddenException Missing permissions to update the share
1225
-	 * @throws OCSNotFoundException Share not found
1226
-	 *
1227
-	 * 200: Share updated successfully
1228
-	 */
1229
-	#[NoAdminRequired]
1230
-	public function updateShare(
1231
-		string $id,
1232
-		?int $permissions = null,
1233
-		?string $password = null,
1234
-		?string $sendPasswordByTalk = null,
1235
-		?string $publicUpload = null,
1236
-		?string $expireDate = null,
1237
-		?string $note = null,
1238
-		?string $label = null,
1239
-		?string $hideDownload = null,
1240
-		?string $attributes = null,
1241
-		?string $sendMail = null,
1242
-		?string $token = null,
1243
-	): DataResponse {
1244
-		try {
1245
-			$share = $this->getShareById($id);
1246
-		} catch (ShareNotFound $e) {
1247
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1248
-		}
1249
-
1250
-		$this->lock($share->getNode());
1251
-
1252
-		if (!$this->canAccessShare($share, false)) {
1253
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1254
-		}
1255
-
1256
-		if (!$this->canEditShare($share)) {
1257
-			throw new OCSForbiddenException($this->l->t('You are not allowed to edit incoming shares'));
1258
-		}
1259
-
1260
-		if (
1261
-			$permissions === null &&
1262
-			$password === null &&
1263
-			$sendPasswordByTalk === null &&
1264
-			$publicUpload === null &&
1265
-			$expireDate === null &&
1266
-			$note === null &&
1267
-			$label === null &&
1268
-			$hideDownload === null &&
1269
-			$attributes === null &&
1270
-			$sendMail === null &&
1271
-			$token === null
1272
-		) {
1273
-			throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
1274
-		}
1275
-
1276
-		if ($note !== null) {
1277
-			$share->setNote($note);
1278
-		}
1279
-
1280
-		if ($attributes !== null) {
1281
-			$share = $this->setShareAttributes($share, $attributes);
1282
-		}
1283
-
1284
-		// Handle mail send
1285
-		if ($sendMail === 'true' || $sendMail === 'false') {
1286
-			$share->setMailSend($sendMail === 'true');
1287
-		}
1288
-
1289
-		/**
1290
-		 * expiration date, password and publicUpload only make sense for link shares
1291
-		 */
1292
-		if ($share->getShareType() === IShare::TYPE_LINK
1293
-			|| $share->getShareType() === IShare::TYPE_EMAIL) {
1294
-
1295
-			// Update hide download state
1296
-			$attributes = $share->getAttributes() ?? $share->newAttributes();
1297
-			if ($hideDownload === 'true') {
1298
-				$share->setHideDownload(true);
1299
-				$attributes->setAttribute('permissions', 'download', false);
1300
-			} elseif ($hideDownload === 'false') {
1301
-				$share->setHideDownload(false);
1302
-				$attributes->setAttribute('permissions', 'download', true);
1303
-			}
1304
-			$share->setAttributes($attributes);
1305
-
1306
-
1307
-			// If either manual permissions are specified or publicUpload
1308
-			// then we need to also update the permissions of the share
1309
-			if ($permissions !== null || $publicUpload !== null) {
1310
-				$hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
1311
-				$permissions = $this->getLinkSharePermissions($permissions ?? Constants::PERMISSION_READ, $hasPublicUpload);
1312
-				$this->validateLinkSharePermissions($share->getNode(), $permissions, $hasPublicUpload);
1313
-				$share->setPermissions($permissions);
1314
-			}
1315
-
1316
-			if ($password === '') {
1317
-				$share->setPassword(null);
1318
-			} elseif ($password !== null) {
1319
-				$share->setPassword($password);
1320
-			}
1321
-
1322
-			if ($label !== null) {
1323
-				if (strlen($label) > 255) {
1324
-					throw new OCSBadRequestException('Maximum label length is 255');
1325
-				}
1326
-				$share->setLabel($label);
1327
-			}
1328
-
1329
-			if ($sendPasswordByTalk === 'true') {
1330
-				if (!$this->appManager->isEnabledForUser('spreed')) {
1331
-					throw new OCSForbiddenException($this->l->t('"Sending the password by Nextcloud Talk" for sharing a file or folder failed because Nextcloud Talk is not enabled.'));
1332
-				}
1333
-
1334
-				$share->setSendPasswordByTalk(true);
1335
-			} elseif ($sendPasswordByTalk !== null) {
1336
-				$share->setSendPasswordByTalk(false);
1337
-			}
1338
-
1339
-			if ($token !== null) {
1340
-				if (!$this->shareManager->allowCustomTokens()) {
1341
-					throw new OCSForbiddenException($this->l->t('Custom share link tokens have been disabled by the administrator'));
1342
-				}
1343
-				if (!$this->validateToken($token)) {
1344
-					throw new OCSBadRequestException($this->l->t('Tokens must contain at least 1 character and may only contain letters, numbers, or a hyphen'));
1345
-				}
1346
-				$share->setToken($token);
1347
-			}
1348
-		}
1349
-
1350
-		// NOT A LINK SHARE
1351
-		else {
1352
-			if ($permissions !== null) {
1353
-				$share->setPermissions($permissions);
1354
-			}
1355
-		}
1356
-
1357
-		if ($expireDate === '') {
1358
-			$share->setExpirationDate(null);
1359
-		} elseif ($expireDate !== null) {
1360
-			try {
1361
-				$expireDateTime = $this->parseDate($expireDate);
1362
-				$share->setExpirationDate($expireDateTime);
1363
-			} catch (\Exception $e) {
1364
-				throw new OCSBadRequestException($e->getMessage(), $e);
1365
-			}
1366
-		}
1367
-
1368
-		try {
1369
-			$this->checkInheritedAttributes($share);
1370
-			$share = $this->shareManager->updateShare($share);
1371
-		} catch (HintException $e) {
1372
-			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1373
-			throw new OCSException($e->getHint(), (int)$code);
1374
-		} catch (\Exception $e) {
1375
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
1376
-			throw new OCSBadRequestException('Failed to update share.', $e);
1377
-		}
1378
-
1379
-		return new DataResponse($this->formatShare($share));
1380
-	}
1381
-
1382
-	private function validateToken(string $token): bool {
1383
-		if (mb_strlen($token) === 0) {
1384
-			return false;
1385
-		}
1386
-		if (!preg_match('/^[a-z0-9-]+$/i', $token)) {
1387
-			return false;
1388
-		}
1389
-		return true;
1390
-	}
1391
-
1392
-	/**
1393
-	 * Get all shares that are still pending
1394
-	 *
1395
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1396
-	 *
1397
-	 * 200: Pending shares returned
1398
-	 */
1399
-	#[NoAdminRequired]
1400
-	public function pendingShares(): DataResponse {
1401
-		$pendingShares = [];
1402
-
1403
-		$shareTypes = [
1404
-			IShare::TYPE_USER,
1405
-			IShare::TYPE_GROUP
1406
-		];
1407
-
1408
-		foreach ($shareTypes as $shareType) {
1409
-			$shares = $this->shareManager->getSharedWith($this->userId, $shareType, null, -1, 0);
1410
-
1411
-			foreach ($shares as $share) {
1412
-				if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
1413
-					$pendingShares[] = $share;
1414
-				}
1415
-			}
1416
-		}
1417
-
1418
-		$result = array_values(array_filter(array_map(function (IShare $share) {
1419
-			$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1420
-			$node = $userFolder->getFirstNodeById($share->getNodeId());
1421
-			if (!$node) {
1422
-				// fallback to guessing the path
1423
-				$node = $userFolder->get($share->getTarget());
1424
-				if ($node === null || $share->getTarget() === '') {
1425
-					return null;
1426
-				}
1427
-			}
1428
-
1429
-			try {
1430
-				$formattedShare = $this->formatShare($share, $node);
1431
-				$formattedShare['path'] = '/' . $share->getNode()->getName();
1432
-				$formattedShare['permissions'] = 0;
1433
-				return $formattedShare;
1434
-			} catch (NotFoundException $e) {
1435
-				return null;
1436
-			}
1437
-		}, $pendingShares), function ($entry) {
1438
-			return $entry !== null;
1439
-		}));
1440
-
1441
-		return new DataResponse($result);
1442
-	}
1443
-
1444
-	/**
1445
-	 * Accept a share
1446
-	 *
1447
-	 * @param string $id ID of the share
1448
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
1449
-	 * @throws OCSNotFoundException Share not found
1450
-	 * @throws OCSException
1451
-	 * @throws OCSBadRequestException Share could not be accepted
1452
-	 *
1453
-	 * 200: Share accepted successfully
1454
-	 */
1455
-	#[NoAdminRequired]
1456
-	public function acceptShare(string $id): DataResponse {
1457
-		try {
1458
-			$share = $this->getShareById($id);
1459
-		} catch (ShareNotFound $e) {
1460
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1461
-		}
1462
-
1463
-		if (!$this->canAccessShare($share)) {
1464
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1465
-		}
1466
-
1467
-		try {
1468
-			$this->shareManager->acceptShare($share, $this->userId);
1469
-		} catch (HintException $e) {
1470
-			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1471
-			throw new OCSException($e->getHint(), (int)$code);
1472
-		} catch (\Exception $e) {
1473
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
1474
-			throw new OCSBadRequestException('Failed to accept share.', $e);
1475
-		}
1476
-
1477
-		return new DataResponse();
1478
-	}
1479
-
1480
-	/**
1481
-	 * Does the user have read permission on the share
1482
-	 *
1483
-	 * @param IShare $share the share to check
1484
-	 * @param boolean $checkGroups check groups as well?
1485
-	 * @return boolean
1486
-	 * @throws NotFoundException
1487
-	 *
1488
-	 * @suppress PhanUndeclaredClassMethod
1489
-	 */
1490
-	protected function canAccessShare(IShare $share, bool $checkGroups = true): bool {
1491
-		// A file with permissions 0 can't be accessed by us. So Don't show it
1492
-		if ($share->getPermissions() === 0) {
1493
-			return false;
1494
-		}
1495
-
1496
-		// Owner of the file and the sharer of the file can always get share
1497
-		if ($share->getShareOwner() === $this->userId
1498
-			|| $share->getSharedBy() === $this->userId) {
1499
-			return true;
1500
-		}
1501
-
1502
-		// If the share is shared with you, you can access it!
1503
-		if ($share->getShareType() === IShare::TYPE_USER
1504
-			&& $share->getSharedWith() === $this->userId) {
1505
-			return true;
1506
-		}
1507
-
1508
-		// Have reshare rights on the shared file/folder ?
1509
-		// Does the currentUser have access to the shared file?
1510
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1511
-		$file = $userFolder->getFirstNodeById($share->getNodeId());
1512
-		if ($file && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1513
-			return true;
1514
-		}
1515
-
1516
-		// If in the recipient group, you can see the share
1517
-		if ($checkGroups && $share->getShareType() === IShare::TYPE_GROUP) {
1518
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
1519
-			$user = $this->userManager->get($this->userId);
1520
-			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1521
-				return true;
1522
-			}
1523
-		}
1524
-
1525
-		if ($share->getShareType() === IShare::TYPE_CIRCLE) {
1526
-			// TODO: have a sanity check like above?
1527
-			return true;
1528
-		}
1529
-
1530
-		if ($share->getShareType() === IShare::TYPE_ROOM) {
1531
-			try {
1532
-				return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1533
-			} catch (ContainerExceptionInterface $e) {
1534
-				return false;
1535
-			}
1536
-		}
1537
-
1538
-		if ($share->getShareType() === IShare::TYPE_DECK) {
1539
-			try {
1540
-				return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1541
-			} catch (ContainerExceptionInterface $e) {
1542
-				return false;
1543
-			}
1544
-		}
1545
-
1546
-		if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1547
-			try {
1548
-				return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1549
-			} catch (ContainerExceptionInterface $e) {
1550
-				return false;
1551
-			}
1552
-		}
1553
-
1554
-		return false;
1555
-	}
1556
-
1557
-	/**
1558
-	 * Does the user have edit permission on the share
1559
-	 *
1560
-	 * @param IShare $share the share to check
1561
-	 * @return boolean
1562
-	 */
1563
-	protected function canEditShare(IShare $share): bool {
1564
-		// A file with permissions 0 can't be accessed by us. So Don't show it
1565
-		if ($share->getPermissions() === 0) {
1566
-			return false;
1567
-		}
1568
-
1569
-		// The owner of the file and the creator of the share
1570
-		// can always edit the share
1571
-		if ($share->getShareOwner() === $this->userId ||
1572
-			$share->getSharedBy() === $this->userId
1573
-		) {
1574
-			return true;
1575
-		}
1576
-
1577
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1578
-		$file = $userFolder->getFirstNodeById($share->getNodeId());
1579
-		if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1580
-			return true;
1581
-		}
1582
-
1583
-		//! we do NOT support some kind of `admin` in groups.
1584
-		//! You cannot edit shares shared to a group you're
1585
-		//! a member of if you're not the share owner or the file owner!
1586
-
1587
-		return false;
1588
-	}
1589
-
1590
-	/**
1591
-	 * Does the user have delete permission on the share
1592
-	 *
1593
-	 * @param IShare $share the share to check
1594
-	 * @return boolean
1595
-	 */
1596
-	protected function canDeleteShare(IShare $share): bool {
1597
-		// A file with permissions 0 can't be accessed by us. So Don't show it
1598
-		if ($share->getPermissions() === 0) {
1599
-			return false;
1600
-		}
1601
-
1602
-		// if the user is the recipient, i can unshare
1603
-		// the share with self
1604
-		if ($share->getShareType() === IShare::TYPE_USER &&
1605
-			$share->getSharedWith() === $this->userId
1606
-		) {
1607
-			return true;
1608
-		}
1609
-
1610
-		// The owner of the file and the creator of the share
1611
-		// can always delete the share
1612
-		if ($share->getShareOwner() === $this->userId ||
1613
-			$share->getSharedBy() === $this->userId
1614
-		) {
1615
-			return true;
1616
-		}
1617
-
1618
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1619
-		$file = $userFolder->getFirstNodeById($share->getNodeId());
1620
-		if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1621
-			return true;
1622
-		}
1623
-
1624
-		return false;
1625
-	}
1626
-
1627
-	/**
1628
-	 * Does the user have delete permission on the share
1629
-	 * This differs from the canDeleteShare function as it only
1630
-	 * remove the share for the current user. It does NOT
1631
-	 * completely delete the share but only the mount point.
1632
-	 * It can then be restored from the deleted shares section.
1633
-	 *
1634
-	 * @param IShare $share the share to check
1635
-	 * @return boolean
1636
-	 *
1637
-	 * @suppress PhanUndeclaredClassMethod
1638
-	 */
1639
-	protected function canDeleteShareFromSelf(IShare $share): bool {
1640
-		if ($share->getShareType() !== IShare::TYPE_GROUP &&
1641
-			$share->getShareType() !== IShare::TYPE_ROOM &&
1642
-			$share->getShareType() !== IShare::TYPE_DECK &&
1643
-			$share->getShareType() !== IShare::TYPE_SCIENCEMESH
1644
-		) {
1645
-			return false;
1646
-		}
1647
-
1648
-		if ($share->getShareOwner() === $this->userId ||
1649
-			$share->getSharedBy() === $this->userId
1650
-		) {
1651
-			// Delete the whole share, not just for self
1652
-			return false;
1653
-		}
1654
-
1655
-		// If in the recipient group, you can delete the share from self
1656
-		if ($share->getShareType() === IShare::TYPE_GROUP) {
1657
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
1658
-			$user = $this->userManager->get($this->userId);
1659
-			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1660
-				return true;
1661
-			}
1662
-		}
1663
-
1664
-		if ($share->getShareType() === IShare::TYPE_ROOM) {
1665
-			try {
1666
-				return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1667
-			} catch (ContainerExceptionInterface $e) {
1668
-				return false;
1669
-			}
1670
-		}
1671
-
1672
-		if ($share->getShareType() === IShare::TYPE_DECK) {
1673
-			try {
1674
-				return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1675
-			} catch (ContainerExceptionInterface $e) {
1676
-				return false;
1677
-			}
1678
-		}
1679
-
1680
-		if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1681
-			try {
1682
-				return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1683
-			} catch (ContainerExceptionInterface $e) {
1684
-				return false;
1685
-			}
1686
-		}
1687
-
1688
-		return false;
1689
-	}
1690
-
1691
-	/**
1692
-	 * Make sure that the passed date is valid ISO 8601
1693
-	 * So YYYY-MM-DD
1694
-	 * If not throw an exception
1695
-	 *
1696
-	 * @param string $expireDate
1697
-	 *
1698
-	 * @throws \Exception
1699
-	 * @return \DateTime
1700
-	 */
1701
-	private function parseDate(string $expireDate): \DateTime {
1702
-		try {
1703
-			$date = new \DateTime(trim($expireDate, '"'), $this->dateTimeZone->getTimeZone());
1704
-			// Make sure it expires at midnight in owner timezone
1705
-			$date->setTime(0, 0, 0);
1706
-		} catch (\Exception $e) {
1707
-			throw new \Exception($this->l->t('Invalid date. Format must be YYYY-MM-DD'));
1708
-		}
1709
-
1710
-		return $date;
1711
-	}
1712
-
1713
-	/**
1714
-	 * Since we have multiple providers but the OCS Share API v1 does
1715
-	 * not support this we need to check all backends.
1716
-	 *
1717
-	 * @param string $id
1718
-	 * @return IShare
1719
-	 * @throws ShareNotFound
1720
-	 */
1721
-	private function getShareById(string $id): IShare {
1722
-		$share = null;
1723
-
1724
-		// First check if it is an internal share.
1725
-		try {
1726
-			$share = $this->shareManager->getShareById('ocinternal:' . $id, $this->userId);
1727
-			return $share;
1728
-		} catch (ShareNotFound $e) {
1729
-			// Do nothing, just try the other share type
1730
-		}
1731
-
1732
-
1733
-		try {
1734
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
1735
-				$share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->userId);
1736
-				return $share;
1737
-			}
1738
-		} catch (ShareNotFound $e) {
1739
-			// Do nothing, just try the other share type
1740
-		}
1741
-
1742
-		try {
1743
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
1744
-				$share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->userId);
1745
-				return $share;
1746
-			}
1747
-		} catch (ShareNotFound $e) {
1748
-			// Do nothing, just try the other share type
1749
-		}
1750
-
1751
-		try {
1752
-			$share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->userId);
1753
-			return $share;
1754
-		} catch (ShareNotFound $e) {
1755
-			// Do nothing, just try the other share type
1756
-		}
1757
-
1758
-		try {
1759
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
1760
-				$share = $this->shareManager->getShareById('deck:' . $id, $this->userId);
1761
-				return $share;
1762
-			}
1763
-		} catch (ShareNotFound $e) {
1764
-			// Do nothing, just try the other share type
1765
-		}
1766
-
1767
-		try {
1768
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
1769
-				$share = $this->shareManager->getShareById('sciencemesh:' . $id, $this->userId);
1770
-				return $share;
1771
-			}
1772
-		} catch (ShareNotFound $e) {
1773
-			// Do nothing, just try the other share type
1774
-		}
1775
-
1776
-		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1777
-			throw new ShareNotFound();
1778
-		}
1779
-		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->userId);
1780
-
1781
-		return $share;
1782
-	}
1783
-
1784
-	/**
1785
-	 * Lock a Node
1786
-	 *
1787
-	 * @param Node $node
1788
-	 * @throws LockedException
1789
-	 */
1790
-	private function lock(Node $node) {
1791
-		$node->lock(ILockingProvider::LOCK_SHARED);
1792
-		$this->lockedNode = $node;
1793
-	}
1794
-
1795
-	/**
1796
-	 * Cleanup the remaining locks
1797
-	 * @throws LockedException
1798
-	 */
1799
-	public function cleanup() {
1800
-		if ($this->lockedNode !== null) {
1801
-			$this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
1802
-		}
1803
-	}
1804
-
1805
-	/**
1806
-	 * Returns the helper of ShareAPIController for room shares.
1807
-	 *
1808
-	 * If the Talk application is not enabled or the helper is not available
1809
-	 * a ContainerExceptionInterface is thrown instead.
1810
-	 *
1811
-	 * @return \OCA\Talk\Share\Helper\ShareAPIController
1812
-	 * @throws ContainerExceptionInterface
1813
-	 */
1814
-	private function getRoomShareHelper() {
1815
-		if (!$this->appManager->isEnabledForUser('spreed')) {
1816
-			throw new QueryException();
1817
-		}
1818
-
1819
-		return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
1820
-	}
1821
-
1822
-	/**
1823
-	 * Returns the helper of ShareAPIHelper for deck shares.
1824
-	 *
1825
-	 * If the Deck application is not enabled or the helper is not available
1826
-	 * a ContainerExceptionInterface is thrown instead.
1827
-	 *
1828
-	 * @return \OCA\Deck\Sharing\ShareAPIHelper
1829
-	 * @throws ContainerExceptionInterface
1830
-	 */
1831
-	private function getDeckShareHelper() {
1832
-		if (!$this->appManager->isEnabledForUser('deck')) {
1833
-			throw new QueryException();
1834
-		}
1835
-
1836
-		return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
1837
-	}
1838
-
1839
-	/**
1840
-	 * Returns the helper of ShareAPIHelper for sciencemesh shares.
1841
-	 *
1842
-	 * If the sciencemesh application is not enabled or the helper is not available
1843
-	 * a ContainerExceptionInterface is thrown instead.
1844
-	 *
1845
-	 * @return \OCA\Deck\Sharing\ShareAPIHelper
1846
-	 * @throws ContainerExceptionInterface
1847
-	 */
1848
-	private function getSciencemeshShareHelper() {
1849
-		if (!$this->appManager->isEnabledForUser('sciencemesh')) {
1850
-			throw new QueryException();
1851
-		}
1852
-
1853
-		return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
1854
-	}
1855
-
1856
-	/**
1857
-	 * @param string $viewer
1858
-	 * @param Node $node
1859
-	 * @param bool $reShares
1860
-	 *
1861
-	 * @return IShare[]
1862
-	 */
1863
-	private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
1864
-		$providers = [
1865
-			IShare::TYPE_USER,
1866
-			IShare::TYPE_GROUP,
1867
-			IShare::TYPE_LINK,
1868
-			IShare::TYPE_EMAIL,
1869
-			IShare::TYPE_CIRCLE,
1870
-			IShare::TYPE_ROOM,
1871
-			IShare::TYPE_DECK,
1872
-			IShare::TYPE_SCIENCEMESH
1873
-		];
1874
-
1875
-		// Should we assume that the (currentUser) viewer is the owner of the node !?
1876
-		$shares = [];
1877
-		foreach ($providers as $provider) {
1878
-			if (!$this->shareManager->shareProviderExists($provider)) {
1879
-				continue;
1880
-			}
1881
-
1882
-			$providerShares =
1883
-				$this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
1884
-			$shares = array_merge($shares, $providerShares);
1885
-		}
1886
-
1887
-		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
1888
-			$federatedShares = $this->shareManager->getSharesBy(
1889
-				$this->userId, IShare::TYPE_REMOTE, $node, $reShares, -1, 0
1890
-			);
1891
-			$shares = array_merge($shares, $federatedShares);
1892
-		}
1893
-
1894
-		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
1895
-			$federatedShares = $this->shareManager->getSharesBy(
1896
-				$this->userId, IShare::TYPE_REMOTE_GROUP, $node, $reShares, -1, 0
1897
-			);
1898
-			$shares = array_merge($shares, $federatedShares);
1899
-		}
1900
-
1901
-		return $shares;
1902
-	}
1903
-
1904
-
1905
-	/**
1906
-	 * @param Node $node
1907
-	 *
1908
-	 * @throws SharingRightsException
1909
-	 */
1910
-	private function confirmSharingRights(Node $node): void {
1911
-		if (!$this->hasResharingRights($this->userId, $node)) {
1912
-			throw new SharingRightsException($this->l->t('No sharing rights on this item'));
1913
-		}
1914
-	}
1915
-
1916
-
1917
-	/**
1918
-	 * @param string $viewer
1919
-	 * @param Node $node
1920
-	 *
1921
-	 * @return bool
1922
-	 */
1923
-	private function hasResharingRights($viewer, $node): bool {
1924
-		if ($viewer === $node->getOwner()->getUID()) {
1925
-			return true;
1926
-		}
1927
-
1928
-		foreach ([$node, $node->getParent()] as $node) {
1929
-			$shares = $this->getSharesFromNode($viewer, $node, true);
1930
-			foreach ($shares as $share) {
1931
-				try {
1932
-					if ($this->shareProviderResharingRights($viewer, $share, $node)) {
1933
-						return true;
1934
-					}
1935
-				} catch (InvalidPathException|NotFoundException $e) {
1936
-				}
1937
-			}
1938
-		}
1939
-
1940
-		return false;
1941
-	}
1942
-
1943
-
1944
-	/**
1945
-	 * Returns if we can find resharing rights in an IShare object for a specific user.
1946
-	 *
1947
-	 * @suppress PhanUndeclaredClassMethod
1948
-	 *
1949
-	 * @param string $userId
1950
-	 * @param IShare $share
1951
-	 * @param Node $node
1952
-	 *
1953
-	 * @return bool
1954
-	 * @throws NotFoundException
1955
-	 * @throws InvalidPathException
1956
-	 */
1957
-	private function shareProviderResharingRights(string $userId, IShare $share, $node): bool {
1958
-		if ($share->getShareOwner() === $userId) {
1959
-			return true;
1960
-		}
1961
-
1962
-		// we check that current user have parent resharing rights on the current file
1963
-		if ($node !== null && ($node->getPermissions() & Constants::PERMISSION_SHARE) !== 0) {
1964
-			return true;
1965
-		}
1966
-
1967
-		if ((Constants::PERMISSION_SHARE & $share->getPermissions()) === 0) {
1968
-			return false;
1969
-		}
1970
-
1971
-		if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() === $userId) {
1972
-			return true;
1973
-		}
1974
-
1975
-		if ($share->getShareType() === IShare::TYPE_GROUP && $this->groupManager->isInGroup($userId, $share->getSharedWith())) {
1976
-			return true;
1977
-		}
1978
-
1979
-		if ($share->getShareType() === IShare::TYPE_CIRCLE && Server::get(IAppManager::class)->isEnabledForUser('circles')
1980
-			&& class_exists('\OCA\Circles\Api\v1\Circles')) {
1981
-			$hasCircleId = (str_ends_with($share->getSharedWith(), ']'));
1982
-			$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
1983
-			$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
1984
-			if ($shareWithLength === false) {
1985
-				$sharedWith = substr($share->getSharedWith(), $shareWithStart);
1986
-			} else {
1987
-				$sharedWith = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
1988
-			}
1989
-			try {
1990
-				$member = Circles::getMember($sharedWith, $userId, 1);
1991
-				if ($member->getLevel() >= 4) {
1992
-					return true;
1993
-				}
1994
-				return false;
1995
-			} catch (ContainerExceptionInterface $e) {
1996
-				return false;
1997
-			}
1998
-		}
1999
-
2000
-		return false;
2001
-	}
2002
-
2003
-	/**
2004
-	 * Get all the shares for the current user
2005
-	 *
2006
-	 * @param Node|null $path
2007
-	 * @param boolean $reshares
2008
-	 * @return IShare[]
2009
-	 */
2010
-	private function getAllShares(?Node $path = null, bool $reshares = false) {
2011
-		// Get all shares
2012
-		$userShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_USER, $path, $reshares, -1, 0);
2013
-		$groupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
2014
-		$linkShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_LINK, $path, $reshares, -1, 0);
2015
-
2016
-		// EMAIL SHARES
2017
-		$mailShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);
2018
-
2019
-		// TEAM SHARES
2020
-		$circleShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);
2021
-
2022
-		// TALK SHARES
2023
-		$roomShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_ROOM, $path, $reshares, -1, 0);
2024
-
2025
-		// DECK SHARES
2026
-		$deckShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_DECK, $path, $reshares, -1, 0);
2027
-
2028
-		// SCIENCEMESH SHARES
2029
-		$sciencemeshShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_SCIENCEMESH, $path, $reshares, -1, 0);
2030
-
2031
-		// FEDERATION
2032
-		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
2033
-			$federatedShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE, $path, $reshares, -1, 0);
2034
-		} else {
2035
-			$federatedShares = [];
2036
-		}
2037
-		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
2038
-			$federatedGroupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
2039
-		} else {
2040
-			$federatedGroupShares = [];
2041
-		}
2042
-
2043
-		return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares, $federatedShares, $federatedGroupShares);
2044
-	}
2045
-
2046
-
2047
-	/**
2048
-	 * merging already formatted shares.
2049
-	 * We'll make an associative array to easily detect duplicate Ids.
2050
-	 * Keys _needs_ to be removed after all shares are retrieved and merged.
2051
-	 *
2052
-	 * @param array $shares
2053
-	 * @param array $newShares
2054
-	 */
2055
-	private function mergeFormattedShares(array &$shares, array $newShares) {
2056
-		foreach ($newShares as $newShare) {
2057
-			if (!array_key_exists($newShare['id'], $shares)) {
2058
-				$shares[$newShare['id']] = $newShare;
2059
-			}
2060
-		}
2061
-	}
2062
-
2063
-	/**
2064
-	 * @param IShare $share
2065
-	 * @param string|null $attributesString
2066
-	 * @return IShare modified share
2067
-	 */
2068
-	private function setShareAttributes(IShare $share, ?string $attributesString) {
2069
-		$newShareAttributes = null;
2070
-		if ($attributesString !== null) {
2071
-			$newShareAttributes = $this->shareManager->newShare()->newAttributes();
2072
-			$formattedShareAttributes = \json_decode($attributesString, true);
2073
-			if (is_array($formattedShareAttributes)) {
2074
-				foreach ($formattedShareAttributes as $formattedAttr) {
2075
-					$newShareAttributes->setAttribute(
2076
-						$formattedAttr['scope'],
2077
-						$formattedAttr['key'],
2078
-						$formattedAttr['value'],
2079
-					);
2080
-				}
2081
-			} else {
2082
-				throw new OCSBadRequestException($this->l->t('Invalid share attributes provided: "%s"', [$attributesString]));
2083
-			}
2084
-		}
2085
-		$share->setAttributes($newShareAttributes);
2086
-
2087
-		return $share;
2088
-	}
2089
-
2090
-	private function checkInheritedAttributes(IShare $share): void {
2091
-		if (!$share->getSharedBy()) {
2092
-			return; // Probably in a test
2093
-		}
2094
-
2095
-		$canDownload = false;
2096
-		$hideDownload = true;
2097
-
2098
-		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
2099
-		$nodes = $userFolder->getById($share->getNodeId());
2100
-		foreach ($nodes as $node) {
2101
-			// Owner always can download it - so allow it and break
2102
-			if ($node->getOwner()?->getUID() === $share->getSharedBy()) {
2103
-				$canDownload = true;
2104
-				$hideDownload = false;
2105
-				break;
2106
-			}
2107
-
2108
-			if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
2109
-				$storage = $node->getStorage();
2110
-				if ($storage instanceof Wrapper) {
2111
-					$storage = $storage->getInstanceOfStorage(SharedStorage::class);
2112
-					if ($storage === null) {
2113
-						throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
2114
-					}
2115
-				} else {
2116
-					throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
2117
-				}
2118
-
2119
-				/** @var SharedStorage $storage */
2120
-				$originalShare = $storage->getShare();
2121
-				$inheritedAttributes = $originalShare->getAttributes();
2122
-				// hide if hidden and also the current share enforces hide (can only be false if one share is false or user is owner)
2123
-				$hideDownload = $hideDownload && $originalShare->getHideDownload();
2124
-				// allow download if already allowed by previous share or when the current share allows downloading
2125
-				$canDownload = $canDownload || $inheritedAttributes === null || $inheritedAttributes->getAttribute('permissions', 'download') !== false;
2126
-			}
2127
-		}
2128
-
2129
-		if ($hideDownload || !$canDownload) {
2130
-			$share->setHideDownload(true);
2131
-
2132
-			if (!$canDownload) {
2133
-				$attributes = $share->getAttributes() ?? $share->newAttributes();
2134
-				$attributes->setAttribute('permissions', 'download', false);
2135
-				$share->setAttributes($attributes);
2136
-			}
2137
-		}
2138
-	}
2139
-
2140
-	/**
2141
-	 * Send a mail notification again for a share.
2142
-	 * The mail_send option must be enabled for the given share.
2143
-	 * @param string $id the share ID
2144
-	 * @param string $password the password to check against. Necessary for password protected shares.
2145
-	 * @throws OCSNotFoundException Share not found
2146
-	 * @throws OCSForbiddenException You are not allowed to send mail notifications
2147
-	 * @throws OCSBadRequestException Invalid request or wrong password
2148
-	 * @throws OCSException Error while sending mail notification
2149
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
2150
-	 *
2151
-	 * 200: The email notification was sent successfully
2152
-	 */
2153
-	#[NoAdminRequired]
2154
-	#[UserRateLimit(limit: 10, period: 600)]
2155
-	public function sendShareEmail(string $id, $password = ''): DataResponse {
2156
-		try {
2157
-			$share = $this->getShareById($id);
2158
-
2159
-			if (!$this->canAccessShare($share, false)) {
2160
-				throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2161
-			}
2162
-
2163
-			if (!$this->canEditShare($share)) {
2164
-				throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2165
-			}
2166
-
2167
-			// For mail and link shares, the user must be
2168
-			// the owner of the share, not only the file owner.
2169
-			if ($share->getShareType() === IShare::TYPE_EMAIL
2170
-				|| $share->getShareType() === IShare::TYPE_LINK) {
2171
-				if ($share->getSharedBy() !== $this->userId) {
2172
-					throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2173
-				}
2174
-			}
2175
-
2176
-			try {
2177
-				$provider = $this->factory->getProviderForType($share->getShareType());
2178
-				if (!($provider instanceof IShareProviderWithNotification)) {
2179
-					throw new OCSBadRequestException($this->l->t('No mail notification configured for this share type'));
2180
-				}
2181
-
2182
-				// Circumvent the password encrypted data by
2183
-				// setting the password clear. We're not storing
2184
-				// the password clear, it is just a temporary
2185
-				// object manipulation. The password will stay
2186
-				// encrypted in the database.
2187
-				if ($share->getPassword() !== null && $share->getPassword() !== $password) {
2188
-					if (!$this->shareManager->checkPassword($share, $password)) {
2189
-						throw new OCSBadRequestException($this->l->t('Wrong password'));
2190
-					}
2191
-					$share = $share->setPassword($password);
2192
-				}
2193
-
2194
-				$provider->sendMailNotification($share);
2195
-				return new DataResponse();
2196
-			} catch (Exception $e) {
2197
-				$this->logger->error($e->getMessage(), ['exception' => $e]);
2198
-				throw new OCSException($this->l->t('Error while sending mail notification'));
2199
-			}
2200
-
2201
-		} catch (ShareNotFound $e) {
2202
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2203
-		}
2204
-	}
2205
-
2206
-	/**
2207
-	 * Get a unique share token
2208
-	 *
2209
-	 * @throws OCSException Failed to generate a unique token
2210
-	 *
2211
-	 * @return DataResponse<Http::STATUS_OK, array{token: string}, array{}>
2212
-	 *
2213
-	 * 200: Token generated successfully
2214
-	 */
2215
-	#[ApiRoute(verb: 'GET', url: '/api/v1/token')]
2216
-	#[NoAdminRequired]
2217
-	public function generateToken(): DataResponse {
2218
-		try {
2219
-			$token = $this->shareManager->generateToken();
2220
-			return new DataResponse([
2221
-				'token' => $token,
2222
-			]);
2223
-		} catch (ShareTokenException $e) {
2224
-			throw new OCSException($this->l->t('Failed to generate a unique token'));
2225
-		}
2226
-	}
1065
+                continue;
1066
+            }
1067
+
1068
+            if (in_array($share->getId(), $known)
1069
+                || ($share->getSharedWith() === $this->userId && $share->getShareType() === IShare::TYPE_USER)) {
1070
+                continue;
1071
+            }
1072
+
1073
+            $known[] = $share->getId();
1074
+            try {
1075
+                /** @var IShare $share */
1076
+                $format = $this->formatShare($share, $node);
1077
+                $formatted[] = $format;
1078
+
1079
+                // let's also build a list of shares created
1080
+                // by the current user only, in case
1081
+                // there is no resharing rights
1082
+                if ($share->getSharedBy() === $this->userId) {
1083
+                    $miniFormatted[] = $format;
1084
+                }
1085
+
1086
+                // check if one of those share is shared with me
1087
+                // and if I have resharing rights on it
1088
+                if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $node)) {
1089
+                    $resharingRight = true;
1090
+                }
1091
+            } catch (InvalidPathException|NotFoundException $e) {
1092
+            }
1093
+        }
1094
+
1095
+        if (!$resharingRight) {
1096
+            $formatted = $miniFormatted;
1097
+        }
1098
+
1099
+        // fix eventual missing display name from federated shares
1100
+        $formatted = $this->fixMissingDisplayName($formatted);
1101
+
1102
+        if ($includeTags) {
1103
+            $formatted =
1104
+                Helper::populateTags($formatted, Server::get(ITagManager::class));
1105
+        }
1106
+
1107
+        return $formatted;
1108
+    }
1109
+
1110
+
1111
+    /**
1112
+     * Get all shares relative to a file, including parent folders shares rights
1113
+     *
1114
+     * @param string $path Path all shares will be relative to
1115
+     *
1116
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1117
+     * @throws InvalidPathException
1118
+     * @throws NotFoundException
1119
+     * @throws OCSNotFoundException The given path is invalid
1120
+     * @throws SharingRightsException
1121
+     *
1122
+     * 200: Shares returned
1123
+     */
1124
+    #[NoAdminRequired]
1125
+    public function getInheritedShares(string $path): DataResponse {
1126
+        // get Node from (string) path.
1127
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1128
+        try {
1129
+            $node = $userFolder->get($path);
1130
+            $this->lock($node);
1131
+        } catch (NotFoundException $e) {
1132
+            throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
1133
+        } catch (LockedException $e) {
1134
+            throw new OCSNotFoundException($this->l->t('Could not lock path'));
1135
+        }
1136
+
1137
+        if (!($node->getPermissions() & Constants::PERMISSION_SHARE)) {
1138
+            throw new SharingRightsException($this->l->t('no sharing rights on this item'));
1139
+        }
1140
+
1141
+        // The current top parent we have access to
1142
+        $parent = $node;
1143
+
1144
+        // initiate real owner.
1145
+        $owner = $node->getOwner()
1146
+            ->getUID();
1147
+        if (!$this->userManager->userExists($owner)) {
1148
+            return new DataResponse([]);
1149
+        }
1150
+
1151
+        // get node based on the owner, fix owner in case of external storage
1152
+        $userFolder = $this->rootFolder->getUserFolder($owner);
1153
+        if ($node->getId() !== $userFolder->getId() && !$userFolder->isSubNode($node)) {
1154
+            $owner = $node->getOwner()
1155
+                ->getUID();
1156
+            $userFolder = $this->rootFolder->getUserFolder($owner);
1157
+            $node = $userFolder->getFirstNodeById($node->getId());
1158
+        }
1159
+        $basePath = $userFolder->getPath();
1160
+
1161
+        // generate node list for each parent folders
1162
+        /** @var Node[] $nodes */
1163
+        $nodes = [];
1164
+        while (true) {
1165
+            $node = $node->getParent();
1166
+            if ($node->getPath() === $basePath) {
1167
+                break;
1168
+            }
1169
+            $nodes[] = $node;
1170
+        }
1171
+
1172
+        // The user that is requesting this list
1173
+        $currentUserFolder = $this->rootFolder->getUserFolder($this->userId);
1174
+
1175
+        // for each nodes, retrieve shares.
1176
+        $shares = [];
1177
+
1178
+        foreach ($nodes as $node) {
1179
+            $getShares = $this->getFormattedShares($owner, $node, false, true);
1180
+
1181
+            $currentUserNode = $currentUserFolder->getFirstNodeById($node->getId());
1182
+            if ($currentUserNode) {
1183
+                $parent = $currentUserNode;
1184
+            }
1185
+
1186
+            $subPath = $currentUserFolder->getRelativePath($parent->getPath());
1187
+            foreach ($getShares as &$share) {
1188
+                $share['via_fileid'] = $parent->getId();
1189
+                $share['via_path'] = $subPath;
1190
+            }
1191
+            $this->mergeFormattedShares($shares, $getShares);
1192
+        }
1193
+
1194
+        return new DataResponse(array_values($shares));
1195
+    }
1196
+
1197
+    /**
1198
+     * Check whether a set of permissions contains the permissions to check.
1199
+     */
1200
+    private function hasPermission(int $permissionsSet, int $permissionsToCheck): bool {
1201
+        return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
1202
+    }
1203
+
1204
+    /**
1205
+     * Update a share
1206
+     *
1207
+     * @param string $id ID of the share
1208
+     * @param int|null $permissions New permissions
1209
+     * @param string|null $password New password
1210
+     * @param string|null $sendPasswordByTalk New condition if the password should be send over Talk
1211
+     * @param string|null $publicUpload New condition if public uploading is allowed
1212
+     * @param string|null $expireDate New expiry date
1213
+     * @param string|null $note New note
1214
+     * @param string|null $label New label
1215
+     * @param string|null $hideDownload New condition if the download should be hidden
1216
+     * @param string|null $attributes New additional attributes
1217
+     * @param string|null $sendMail if the share should be send by mail.
1218
+     *                              Considering the share already exists, no mail will be send after the share is updated.
1219
+     *                              You will have to use the sendMail action to send the mail.
1220
+     * @param string|null $shareWith New recipient for email shares
1221
+     * @param string|null $token New token
1222
+     * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
1223
+     * @throws OCSBadRequestException Share could not be updated because the requested changes are invalid
1224
+     * @throws OCSForbiddenException Missing permissions to update the share
1225
+     * @throws OCSNotFoundException Share not found
1226
+     *
1227
+     * 200: Share updated successfully
1228
+     */
1229
+    #[NoAdminRequired]
1230
+    public function updateShare(
1231
+        string $id,
1232
+        ?int $permissions = null,
1233
+        ?string $password = null,
1234
+        ?string $sendPasswordByTalk = null,
1235
+        ?string $publicUpload = null,
1236
+        ?string $expireDate = null,
1237
+        ?string $note = null,
1238
+        ?string $label = null,
1239
+        ?string $hideDownload = null,
1240
+        ?string $attributes = null,
1241
+        ?string $sendMail = null,
1242
+        ?string $token = null,
1243
+    ): DataResponse {
1244
+        try {
1245
+            $share = $this->getShareById($id);
1246
+        } catch (ShareNotFound $e) {
1247
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1248
+        }
1249
+
1250
+        $this->lock($share->getNode());
1251
+
1252
+        if (!$this->canAccessShare($share, false)) {
1253
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1254
+        }
1255
+
1256
+        if (!$this->canEditShare($share)) {
1257
+            throw new OCSForbiddenException($this->l->t('You are not allowed to edit incoming shares'));
1258
+        }
1259
+
1260
+        if (
1261
+            $permissions === null &&
1262
+            $password === null &&
1263
+            $sendPasswordByTalk === null &&
1264
+            $publicUpload === null &&
1265
+            $expireDate === null &&
1266
+            $note === null &&
1267
+            $label === null &&
1268
+            $hideDownload === null &&
1269
+            $attributes === null &&
1270
+            $sendMail === null &&
1271
+            $token === null
1272
+        ) {
1273
+            throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
1274
+        }
1275
+
1276
+        if ($note !== null) {
1277
+            $share->setNote($note);
1278
+        }
1279
+
1280
+        if ($attributes !== null) {
1281
+            $share = $this->setShareAttributes($share, $attributes);
1282
+        }
1283
+
1284
+        // Handle mail send
1285
+        if ($sendMail === 'true' || $sendMail === 'false') {
1286
+            $share->setMailSend($sendMail === 'true');
1287
+        }
1288
+
1289
+        /**
1290
+         * expiration date, password and publicUpload only make sense for link shares
1291
+         */
1292
+        if ($share->getShareType() === IShare::TYPE_LINK
1293
+            || $share->getShareType() === IShare::TYPE_EMAIL) {
1294
+
1295
+            // Update hide download state
1296
+            $attributes = $share->getAttributes() ?? $share->newAttributes();
1297
+            if ($hideDownload === 'true') {
1298
+                $share->setHideDownload(true);
1299
+                $attributes->setAttribute('permissions', 'download', false);
1300
+            } elseif ($hideDownload === 'false') {
1301
+                $share->setHideDownload(false);
1302
+                $attributes->setAttribute('permissions', 'download', true);
1303
+            }
1304
+            $share->setAttributes($attributes);
1305
+
1306
+
1307
+            // If either manual permissions are specified or publicUpload
1308
+            // then we need to also update the permissions of the share
1309
+            if ($permissions !== null || $publicUpload !== null) {
1310
+                $hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
1311
+                $permissions = $this->getLinkSharePermissions($permissions ?? Constants::PERMISSION_READ, $hasPublicUpload);
1312
+                $this->validateLinkSharePermissions($share->getNode(), $permissions, $hasPublicUpload);
1313
+                $share->setPermissions($permissions);
1314
+            }
1315
+
1316
+            if ($password === '') {
1317
+                $share->setPassword(null);
1318
+            } elseif ($password !== null) {
1319
+                $share->setPassword($password);
1320
+            }
1321
+
1322
+            if ($label !== null) {
1323
+                if (strlen($label) > 255) {
1324
+                    throw new OCSBadRequestException('Maximum label length is 255');
1325
+                }
1326
+                $share->setLabel($label);
1327
+            }
1328
+
1329
+            if ($sendPasswordByTalk === 'true') {
1330
+                if (!$this->appManager->isEnabledForUser('spreed')) {
1331
+                    throw new OCSForbiddenException($this->l->t('"Sending the password by Nextcloud Talk" for sharing a file or folder failed because Nextcloud Talk is not enabled.'));
1332
+                }
1333
+
1334
+                $share->setSendPasswordByTalk(true);
1335
+            } elseif ($sendPasswordByTalk !== null) {
1336
+                $share->setSendPasswordByTalk(false);
1337
+            }
1338
+
1339
+            if ($token !== null) {
1340
+                if (!$this->shareManager->allowCustomTokens()) {
1341
+                    throw new OCSForbiddenException($this->l->t('Custom share link tokens have been disabled by the administrator'));
1342
+                }
1343
+                if (!$this->validateToken($token)) {
1344
+                    throw new OCSBadRequestException($this->l->t('Tokens must contain at least 1 character and may only contain letters, numbers, or a hyphen'));
1345
+                }
1346
+                $share->setToken($token);
1347
+            }
1348
+        }
1349
+
1350
+        // NOT A LINK SHARE
1351
+        else {
1352
+            if ($permissions !== null) {
1353
+                $share->setPermissions($permissions);
1354
+            }
1355
+        }
1356
+
1357
+        if ($expireDate === '') {
1358
+            $share->setExpirationDate(null);
1359
+        } elseif ($expireDate !== null) {
1360
+            try {
1361
+                $expireDateTime = $this->parseDate($expireDate);
1362
+                $share->setExpirationDate($expireDateTime);
1363
+            } catch (\Exception $e) {
1364
+                throw new OCSBadRequestException($e->getMessage(), $e);
1365
+            }
1366
+        }
1367
+
1368
+        try {
1369
+            $this->checkInheritedAttributes($share);
1370
+            $share = $this->shareManager->updateShare($share);
1371
+        } catch (HintException $e) {
1372
+            $code = $e->getCode() === 0 ? 403 : $e->getCode();
1373
+            throw new OCSException($e->getHint(), (int)$code);
1374
+        } catch (\Exception $e) {
1375
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
1376
+            throw new OCSBadRequestException('Failed to update share.', $e);
1377
+        }
1378
+
1379
+        return new DataResponse($this->formatShare($share));
1380
+    }
1381
+
1382
+    private function validateToken(string $token): bool {
1383
+        if (mb_strlen($token) === 0) {
1384
+            return false;
1385
+        }
1386
+        if (!preg_match('/^[a-z0-9-]+$/i', $token)) {
1387
+            return false;
1388
+        }
1389
+        return true;
1390
+    }
1391
+
1392
+    /**
1393
+     * Get all shares that are still pending
1394
+     *
1395
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1396
+     *
1397
+     * 200: Pending shares returned
1398
+     */
1399
+    #[NoAdminRequired]
1400
+    public function pendingShares(): DataResponse {
1401
+        $pendingShares = [];
1402
+
1403
+        $shareTypes = [
1404
+            IShare::TYPE_USER,
1405
+            IShare::TYPE_GROUP
1406
+        ];
1407
+
1408
+        foreach ($shareTypes as $shareType) {
1409
+            $shares = $this->shareManager->getSharedWith($this->userId, $shareType, null, -1, 0);
1410
+
1411
+            foreach ($shares as $share) {
1412
+                if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
1413
+                    $pendingShares[] = $share;
1414
+                }
1415
+            }
1416
+        }
1417
+
1418
+        $result = array_values(array_filter(array_map(function (IShare $share) {
1419
+            $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1420
+            $node = $userFolder->getFirstNodeById($share->getNodeId());
1421
+            if (!$node) {
1422
+                // fallback to guessing the path
1423
+                $node = $userFolder->get($share->getTarget());
1424
+                if ($node === null || $share->getTarget() === '') {
1425
+                    return null;
1426
+                }
1427
+            }
1428
+
1429
+            try {
1430
+                $formattedShare = $this->formatShare($share, $node);
1431
+                $formattedShare['path'] = '/' . $share->getNode()->getName();
1432
+                $formattedShare['permissions'] = 0;
1433
+                return $formattedShare;
1434
+            } catch (NotFoundException $e) {
1435
+                return null;
1436
+            }
1437
+        }, $pendingShares), function ($entry) {
1438
+            return $entry !== null;
1439
+        }));
1440
+
1441
+        return new DataResponse($result);
1442
+    }
1443
+
1444
+    /**
1445
+     * Accept a share
1446
+     *
1447
+     * @param string $id ID of the share
1448
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
1449
+     * @throws OCSNotFoundException Share not found
1450
+     * @throws OCSException
1451
+     * @throws OCSBadRequestException Share could not be accepted
1452
+     *
1453
+     * 200: Share accepted successfully
1454
+     */
1455
+    #[NoAdminRequired]
1456
+    public function acceptShare(string $id): DataResponse {
1457
+        try {
1458
+            $share = $this->getShareById($id);
1459
+        } catch (ShareNotFound $e) {
1460
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1461
+        }
1462
+
1463
+        if (!$this->canAccessShare($share)) {
1464
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1465
+        }
1466
+
1467
+        try {
1468
+            $this->shareManager->acceptShare($share, $this->userId);
1469
+        } catch (HintException $e) {
1470
+            $code = $e->getCode() === 0 ? 403 : $e->getCode();
1471
+            throw new OCSException($e->getHint(), (int)$code);
1472
+        } catch (\Exception $e) {
1473
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
1474
+            throw new OCSBadRequestException('Failed to accept share.', $e);
1475
+        }
1476
+
1477
+        return new DataResponse();
1478
+    }
1479
+
1480
+    /**
1481
+     * Does the user have read permission on the share
1482
+     *
1483
+     * @param IShare $share the share to check
1484
+     * @param boolean $checkGroups check groups as well?
1485
+     * @return boolean
1486
+     * @throws NotFoundException
1487
+     *
1488
+     * @suppress PhanUndeclaredClassMethod
1489
+     */
1490
+    protected function canAccessShare(IShare $share, bool $checkGroups = true): bool {
1491
+        // A file with permissions 0 can't be accessed by us. So Don't show it
1492
+        if ($share->getPermissions() === 0) {
1493
+            return false;
1494
+        }
1495
+
1496
+        // Owner of the file and the sharer of the file can always get share
1497
+        if ($share->getShareOwner() === $this->userId
1498
+            || $share->getSharedBy() === $this->userId) {
1499
+            return true;
1500
+        }
1501
+
1502
+        // If the share is shared with you, you can access it!
1503
+        if ($share->getShareType() === IShare::TYPE_USER
1504
+            && $share->getSharedWith() === $this->userId) {
1505
+            return true;
1506
+        }
1507
+
1508
+        // Have reshare rights on the shared file/folder ?
1509
+        // Does the currentUser have access to the shared file?
1510
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1511
+        $file = $userFolder->getFirstNodeById($share->getNodeId());
1512
+        if ($file && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1513
+            return true;
1514
+        }
1515
+
1516
+        // If in the recipient group, you can see the share
1517
+        if ($checkGroups && $share->getShareType() === IShare::TYPE_GROUP) {
1518
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
1519
+            $user = $this->userManager->get($this->userId);
1520
+            if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1521
+                return true;
1522
+            }
1523
+        }
1524
+
1525
+        if ($share->getShareType() === IShare::TYPE_CIRCLE) {
1526
+            // TODO: have a sanity check like above?
1527
+            return true;
1528
+        }
1529
+
1530
+        if ($share->getShareType() === IShare::TYPE_ROOM) {
1531
+            try {
1532
+                return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1533
+            } catch (ContainerExceptionInterface $e) {
1534
+                return false;
1535
+            }
1536
+        }
1537
+
1538
+        if ($share->getShareType() === IShare::TYPE_DECK) {
1539
+            try {
1540
+                return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1541
+            } catch (ContainerExceptionInterface $e) {
1542
+                return false;
1543
+            }
1544
+        }
1545
+
1546
+        if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1547
+            try {
1548
+                return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1549
+            } catch (ContainerExceptionInterface $e) {
1550
+                return false;
1551
+            }
1552
+        }
1553
+
1554
+        return false;
1555
+    }
1556
+
1557
+    /**
1558
+     * Does the user have edit permission on the share
1559
+     *
1560
+     * @param IShare $share the share to check
1561
+     * @return boolean
1562
+     */
1563
+    protected function canEditShare(IShare $share): bool {
1564
+        // A file with permissions 0 can't be accessed by us. So Don't show it
1565
+        if ($share->getPermissions() === 0) {
1566
+            return false;
1567
+        }
1568
+
1569
+        // The owner of the file and the creator of the share
1570
+        // can always edit the share
1571
+        if ($share->getShareOwner() === $this->userId ||
1572
+            $share->getSharedBy() === $this->userId
1573
+        ) {
1574
+            return true;
1575
+        }
1576
+
1577
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1578
+        $file = $userFolder->getFirstNodeById($share->getNodeId());
1579
+        if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1580
+            return true;
1581
+        }
1582
+
1583
+        //! we do NOT support some kind of `admin` in groups.
1584
+        //! You cannot edit shares shared to a group you're
1585
+        //! a member of if you're not the share owner or the file owner!
1586
+
1587
+        return false;
1588
+    }
1589
+
1590
+    /**
1591
+     * Does the user have delete permission on the share
1592
+     *
1593
+     * @param IShare $share the share to check
1594
+     * @return boolean
1595
+     */
1596
+    protected function canDeleteShare(IShare $share): bool {
1597
+        // A file with permissions 0 can't be accessed by us. So Don't show it
1598
+        if ($share->getPermissions() === 0) {
1599
+            return false;
1600
+        }
1601
+
1602
+        // if the user is the recipient, i can unshare
1603
+        // the share with self
1604
+        if ($share->getShareType() === IShare::TYPE_USER &&
1605
+            $share->getSharedWith() === $this->userId
1606
+        ) {
1607
+            return true;
1608
+        }
1609
+
1610
+        // The owner of the file and the creator of the share
1611
+        // can always delete the share
1612
+        if ($share->getShareOwner() === $this->userId ||
1613
+            $share->getSharedBy() === $this->userId
1614
+        ) {
1615
+            return true;
1616
+        }
1617
+
1618
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1619
+        $file = $userFolder->getFirstNodeById($share->getNodeId());
1620
+        if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1621
+            return true;
1622
+        }
1623
+
1624
+        return false;
1625
+    }
1626
+
1627
+    /**
1628
+     * Does the user have delete permission on the share
1629
+     * This differs from the canDeleteShare function as it only
1630
+     * remove the share for the current user. It does NOT
1631
+     * completely delete the share but only the mount point.
1632
+     * It can then be restored from the deleted shares section.
1633
+     *
1634
+     * @param IShare $share the share to check
1635
+     * @return boolean
1636
+     *
1637
+     * @suppress PhanUndeclaredClassMethod
1638
+     */
1639
+    protected function canDeleteShareFromSelf(IShare $share): bool {
1640
+        if ($share->getShareType() !== IShare::TYPE_GROUP &&
1641
+            $share->getShareType() !== IShare::TYPE_ROOM &&
1642
+            $share->getShareType() !== IShare::TYPE_DECK &&
1643
+            $share->getShareType() !== IShare::TYPE_SCIENCEMESH
1644
+        ) {
1645
+            return false;
1646
+        }
1647
+
1648
+        if ($share->getShareOwner() === $this->userId ||
1649
+            $share->getSharedBy() === $this->userId
1650
+        ) {
1651
+            // Delete the whole share, not just for self
1652
+            return false;
1653
+        }
1654
+
1655
+        // If in the recipient group, you can delete the share from self
1656
+        if ($share->getShareType() === IShare::TYPE_GROUP) {
1657
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
1658
+            $user = $this->userManager->get($this->userId);
1659
+            if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1660
+                return true;
1661
+            }
1662
+        }
1663
+
1664
+        if ($share->getShareType() === IShare::TYPE_ROOM) {
1665
+            try {
1666
+                return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1667
+            } catch (ContainerExceptionInterface $e) {
1668
+                return false;
1669
+            }
1670
+        }
1671
+
1672
+        if ($share->getShareType() === IShare::TYPE_DECK) {
1673
+            try {
1674
+                return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1675
+            } catch (ContainerExceptionInterface $e) {
1676
+                return false;
1677
+            }
1678
+        }
1679
+
1680
+        if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1681
+            try {
1682
+                return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1683
+            } catch (ContainerExceptionInterface $e) {
1684
+                return false;
1685
+            }
1686
+        }
1687
+
1688
+        return false;
1689
+    }
1690
+
1691
+    /**
1692
+     * Make sure that the passed date is valid ISO 8601
1693
+     * So YYYY-MM-DD
1694
+     * If not throw an exception
1695
+     *
1696
+     * @param string $expireDate
1697
+     *
1698
+     * @throws \Exception
1699
+     * @return \DateTime
1700
+     */
1701
+    private function parseDate(string $expireDate): \DateTime {
1702
+        try {
1703
+            $date = new \DateTime(trim($expireDate, '"'), $this->dateTimeZone->getTimeZone());
1704
+            // Make sure it expires at midnight in owner timezone
1705
+            $date->setTime(0, 0, 0);
1706
+        } catch (\Exception $e) {
1707
+            throw new \Exception($this->l->t('Invalid date. Format must be YYYY-MM-DD'));
1708
+        }
1709
+
1710
+        return $date;
1711
+    }
1712
+
1713
+    /**
1714
+     * Since we have multiple providers but the OCS Share API v1 does
1715
+     * not support this we need to check all backends.
1716
+     *
1717
+     * @param string $id
1718
+     * @return IShare
1719
+     * @throws ShareNotFound
1720
+     */
1721
+    private function getShareById(string $id): IShare {
1722
+        $share = null;
1723
+
1724
+        // First check if it is an internal share.
1725
+        try {
1726
+            $share = $this->shareManager->getShareById('ocinternal:' . $id, $this->userId);
1727
+            return $share;
1728
+        } catch (ShareNotFound $e) {
1729
+            // Do nothing, just try the other share type
1730
+        }
1731
+
1732
+
1733
+        try {
1734
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
1735
+                $share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->userId);
1736
+                return $share;
1737
+            }
1738
+        } catch (ShareNotFound $e) {
1739
+            // Do nothing, just try the other share type
1740
+        }
1741
+
1742
+        try {
1743
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
1744
+                $share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->userId);
1745
+                return $share;
1746
+            }
1747
+        } catch (ShareNotFound $e) {
1748
+            // Do nothing, just try the other share type
1749
+        }
1750
+
1751
+        try {
1752
+            $share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->userId);
1753
+            return $share;
1754
+        } catch (ShareNotFound $e) {
1755
+            // Do nothing, just try the other share type
1756
+        }
1757
+
1758
+        try {
1759
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
1760
+                $share = $this->shareManager->getShareById('deck:' . $id, $this->userId);
1761
+                return $share;
1762
+            }
1763
+        } catch (ShareNotFound $e) {
1764
+            // Do nothing, just try the other share type
1765
+        }
1766
+
1767
+        try {
1768
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
1769
+                $share = $this->shareManager->getShareById('sciencemesh:' . $id, $this->userId);
1770
+                return $share;
1771
+            }
1772
+        } catch (ShareNotFound $e) {
1773
+            // Do nothing, just try the other share type
1774
+        }
1775
+
1776
+        if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1777
+            throw new ShareNotFound();
1778
+        }
1779
+        $share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->userId);
1780
+
1781
+        return $share;
1782
+    }
1783
+
1784
+    /**
1785
+     * Lock a Node
1786
+     *
1787
+     * @param Node $node
1788
+     * @throws LockedException
1789
+     */
1790
+    private function lock(Node $node) {
1791
+        $node->lock(ILockingProvider::LOCK_SHARED);
1792
+        $this->lockedNode = $node;
1793
+    }
1794
+
1795
+    /**
1796
+     * Cleanup the remaining locks
1797
+     * @throws LockedException
1798
+     */
1799
+    public function cleanup() {
1800
+        if ($this->lockedNode !== null) {
1801
+            $this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
1802
+        }
1803
+    }
1804
+
1805
+    /**
1806
+     * Returns the helper of ShareAPIController for room shares.
1807
+     *
1808
+     * If the Talk application is not enabled or the helper is not available
1809
+     * a ContainerExceptionInterface is thrown instead.
1810
+     *
1811
+     * @return \OCA\Talk\Share\Helper\ShareAPIController
1812
+     * @throws ContainerExceptionInterface
1813
+     */
1814
+    private function getRoomShareHelper() {
1815
+        if (!$this->appManager->isEnabledForUser('spreed')) {
1816
+            throw new QueryException();
1817
+        }
1818
+
1819
+        return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
1820
+    }
1821
+
1822
+    /**
1823
+     * Returns the helper of ShareAPIHelper for deck shares.
1824
+     *
1825
+     * If the Deck application is not enabled or the helper is not available
1826
+     * a ContainerExceptionInterface is thrown instead.
1827
+     *
1828
+     * @return \OCA\Deck\Sharing\ShareAPIHelper
1829
+     * @throws ContainerExceptionInterface
1830
+     */
1831
+    private function getDeckShareHelper() {
1832
+        if (!$this->appManager->isEnabledForUser('deck')) {
1833
+            throw new QueryException();
1834
+        }
1835
+
1836
+        return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
1837
+    }
1838
+
1839
+    /**
1840
+     * Returns the helper of ShareAPIHelper for sciencemesh shares.
1841
+     *
1842
+     * If the sciencemesh application is not enabled or the helper is not available
1843
+     * a ContainerExceptionInterface is thrown instead.
1844
+     *
1845
+     * @return \OCA\Deck\Sharing\ShareAPIHelper
1846
+     * @throws ContainerExceptionInterface
1847
+     */
1848
+    private function getSciencemeshShareHelper() {
1849
+        if (!$this->appManager->isEnabledForUser('sciencemesh')) {
1850
+            throw new QueryException();
1851
+        }
1852
+
1853
+        return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
1854
+    }
1855
+
1856
+    /**
1857
+     * @param string $viewer
1858
+     * @param Node $node
1859
+     * @param bool $reShares
1860
+     *
1861
+     * @return IShare[]
1862
+     */
1863
+    private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
1864
+        $providers = [
1865
+            IShare::TYPE_USER,
1866
+            IShare::TYPE_GROUP,
1867
+            IShare::TYPE_LINK,
1868
+            IShare::TYPE_EMAIL,
1869
+            IShare::TYPE_CIRCLE,
1870
+            IShare::TYPE_ROOM,
1871
+            IShare::TYPE_DECK,
1872
+            IShare::TYPE_SCIENCEMESH
1873
+        ];
1874
+
1875
+        // Should we assume that the (currentUser) viewer is the owner of the node !?
1876
+        $shares = [];
1877
+        foreach ($providers as $provider) {
1878
+            if (!$this->shareManager->shareProviderExists($provider)) {
1879
+                continue;
1880
+            }
1881
+
1882
+            $providerShares =
1883
+                $this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
1884
+            $shares = array_merge($shares, $providerShares);
1885
+        }
1886
+
1887
+        if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
1888
+            $federatedShares = $this->shareManager->getSharesBy(
1889
+                $this->userId, IShare::TYPE_REMOTE, $node, $reShares, -1, 0
1890
+            );
1891
+            $shares = array_merge($shares, $federatedShares);
1892
+        }
1893
+
1894
+        if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
1895
+            $federatedShares = $this->shareManager->getSharesBy(
1896
+                $this->userId, IShare::TYPE_REMOTE_GROUP, $node, $reShares, -1, 0
1897
+            );
1898
+            $shares = array_merge($shares, $federatedShares);
1899
+        }
1900
+
1901
+        return $shares;
1902
+    }
1903
+
1904
+
1905
+    /**
1906
+     * @param Node $node
1907
+     *
1908
+     * @throws SharingRightsException
1909
+     */
1910
+    private function confirmSharingRights(Node $node): void {
1911
+        if (!$this->hasResharingRights($this->userId, $node)) {
1912
+            throw new SharingRightsException($this->l->t('No sharing rights on this item'));
1913
+        }
1914
+    }
1915
+
1916
+
1917
+    /**
1918
+     * @param string $viewer
1919
+     * @param Node $node
1920
+     *
1921
+     * @return bool
1922
+     */
1923
+    private function hasResharingRights($viewer, $node): bool {
1924
+        if ($viewer === $node->getOwner()->getUID()) {
1925
+            return true;
1926
+        }
1927
+
1928
+        foreach ([$node, $node->getParent()] as $node) {
1929
+            $shares = $this->getSharesFromNode($viewer, $node, true);
1930
+            foreach ($shares as $share) {
1931
+                try {
1932
+                    if ($this->shareProviderResharingRights($viewer, $share, $node)) {
1933
+                        return true;
1934
+                    }
1935
+                } catch (InvalidPathException|NotFoundException $e) {
1936
+                }
1937
+            }
1938
+        }
1939
+
1940
+        return false;
1941
+    }
1942
+
1943
+
1944
+    /**
1945
+     * Returns if we can find resharing rights in an IShare object for a specific user.
1946
+     *
1947
+     * @suppress PhanUndeclaredClassMethod
1948
+     *
1949
+     * @param string $userId
1950
+     * @param IShare $share
1951
+     * @param Node $node
1952
+     *
1953
+     * @return bool
1954
+     * @throws NotFoundException
1955
+     * @throws InvalidPathException
1956
+     */
1957
+    private function shareProviderResharingRights(string $userId, IShare $share, $node): bool {
1958
+        if ($share->getShareOwner() === $userId) {
1959
+            return true;
1960
+        }
1961
+
1962
+        // we check that current user have parent resharing rights on the current file
1963
+        if ($node !== null && ($node->getPermissions() & Constants::PERMISSION_SHARE) !== 0) {
1964
+            return true;
1965
+        }
1966
+
1967
+        if ((Constants::PERMISSION_SHARE & $share->getPermissions()) === 0) {
1968
+            return false;
1969
+        }
1970
+
1971
+        if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() === $userId) {
1972
+            return true;
1973
+        }
1974
+
1975
+        if ($share->getShareType() === IShare::TYPE_GROUP && $this->groupManager->isInGroup($userId, $share->getSharedWith())) {
1976
+            return true;
1977
+        }
1978
+
1979
+        if ($share->getShareType() === IShare::TYPE_CIRCLE && Server::get(IAppManager::class)->isEnabledForUser('circles')
1980
+            && class_exists('\OCA\Circles\Api\v1\Circles')) {
1981
+            $hasCircleId = (str_ends_with($share->getSharedWith(), ']'));
1982
+            $shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
1983
+            $shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
1984
+            if ($shareWithLength === false) {
1985
+                $sharedWith = substr($share->getSharedWith(), $shareWithStart);
1986
+            } else {
1987
+                $sharedWith = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
1988
+            }
1989
+            try {
1990
+                $member = Circles::getMember($sharedWith, $userId, 1);
1991
+                if ($member->getLevel() >= 4) {
1992
+                    return true;
1993
+                }
1994
+                return false;
1995
+            } catch (ContainerExceptionInterface $e) {
1996
+                return false;
1997
+            }
1998
+        }
1999
+
2000
+        return false;
2001
+    }
2002
+
2003
+    /**
2004
+     * Get all the shares for the current user
2005
+     *
2006
+     * @param Node|null $path
2007
+     * @param boolean $reshares
2008
+     * @return IShare[]
2009
+     */
2010
+    private function getAllShares(?Node $path = null, bool $reshares = false) {
2011
+        // Get all shares
2012
+        $userShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_USER, $path, $reshares, -1, 0);
2013
+        $groupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
2014
+        $linkShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_LINK, $path, $reshares, -1, 0);
2015
+
2016
+        // EMAIL SHARES
2017
+        $mailShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);
2018
+
2019
+        // TEAM SHARES
2020
+        $circleShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);
2021
+
2022
+        // TALK SHARES
2023
+        $roomShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_ROOM, $path, $reshares, -1, 0);
2024
+
2025
+        // DECK SHARES
2026
+        $deckShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_DECK, $path, $reshares, -1, 0);
2027
+
2028
+        // SCIENCEMESH SHARES
2029
+        $sciencemeshShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_SCIENCEMESH, $path, $reshares, -1, 0);
2030
+
2031
+        // FEDERATION
2032
+        if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
2033
+            $federatedShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE, $path, $reshares, -1, 0);
2034
+        } else {
2035
+            $federatedShares = [];
2036
+        }
2037
+        if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
2038
+            $federatedGroupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
2039
+        } else {
2040
+            $federatedGroupShares = [];
2041
+        }
2042
+
2043
+        return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares, $federatedShares, $federatedGroupShares);
2044
+    }
2045
+
2046
+
2047
+    /**
2048
+     * merging already formatted shares.
2049
+     * We'll make an associative array to easily detect duplicate Ids.
2050
+     * Keys _needs_ to be removed after all shares are retrieved and merged.
2051
+     *
2052
+     * @param array $shares
2053
+     * @param array $newShares
2054
+     */
2055
+    private function mergeFormattedShares(array &$shares, array $newShares) {
2056
+        foreach ($newShares as $newShare) {
2057
+            if (!array_key_exists($newShare['id'], $shares)) {
2058
+                $shares[$newShare['id']] = $newShare;
2059
+            }
2060
+        }
2061
+    }
2062
+
2063
+    /**
2064
+     * @param IShare $share
2065
+     * @param string|null $attributesString
2066
+     * @return IShare modified share
2067
+     */
2068
+    private function setShareAttributes(IShare $share, ?string $attributesString) {
2069
+        $newShareAttributes = null;
2070
+        if ($attributesString !== null) {
2071
+            $newShareAttributes = $this->shareManager->newShare()->newAttributes();
2072
+            $formattedShareAttributes = \json_decode($attributesString, true);
2073
+            if (is_array($formattedShareAttributes)) {
2074
+                foreach ($formattedShareAttributes as $formattedAttr) {
2075
+                    $newShareAttributes->setAttribute(
2076
+                        $formattedAttr['scope'],
2077
+                        $formattedAttr['key'],
2078
+                        $formattedAttr['value'],
2079
+                    );
2080
+                }
2081
+            } else {
2082
+                throw new OCSBadRequestException($this->l->t('Invalid share attributes provided: "%s"', [$attributesString]));
2083
+            }
2084
+        }
2085
+        $share->setAttributes($newShareAttributes);
2086
+
2087
+        return $share;
2088
+    }
2089
+
2090
+    private function checkInheritedAttributes(IShare $share): void {
2091
+        if (!$share->getSharedBy()) {
2092
+            return; // Probably in a test
2093
+        }
2094
+
2095
+        $canDownload = false;
2096
+        $hideDownload = true;
2097
+
2098
+        $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
2099
+        $nodes = $userFolder->getById($share->getNodeId());
2100
+        foreach ($nodes as $node) {
2101
+            // Owner always can download it - so allow it and break
2102
+            if ($node->getOwner()?->getUID() === $share->getSharedBy()) {
2103
+                $canDownload = true;
2104
+                $hideDownload = false;
2105
+                break;
2106
+            }
2107
+
2108
+            if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
2109
+                $storage = $node->getStorage();
2110
+                if ($storage instanceof Wrapper) {
2111
+                    $storage = $storage->getInstanceOfStorage(SharedStorage::class);
2112
+                    if ($storage === null) {
2113
+                        throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
2114
+                    }
2115
+                } else {
2116
+                    throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
2117
+                }
2118
+
2119
+                /** @var SharedStorage $storage */
2120
+                $originalShare = $storage->getShare();
2121
+                $inheritedAttributes = $originalShare->getAttributes();
2122
+                // hide if hidden and also the current share enforces hide (can only be false if one share is false or user is owner)
2123
+                $hideDownload = $hideDownload && $originalShare->getHideDownload();
2124
+                // allow download if already allowed by previous share or when the current share allows downloading
2125
+                $canDownload = $canDownload || $inheritedAttributes === null || $inheritedAttributes->getAttribute('permissions', 'download') !== false;
2126
+            }
2127
+        }
2128
+
2129
+        if ($hideDownload || !$canDownload) {
2130
+            $share->setHideDownload(true);
2131
+
2132
+            if (!$canDownload) {
2133
+                $attributes = $share->getAttributes() ?? $share->newAttributes();
2134
+                $attributes->setAttribute('permissions', 'download', false);
2135
+                $share->setAttributes($attributes);
2136
+            }
2137
+        }
2138
+    }
2139
+
2140
+    /**
2141
+     * Send a mail notification again for a share.
2142
+     * The mail_send option must be enabled for the given share.
2143
+     * @param string $id the share ID
2144
+     * @param string $password the password to check against. Necessary for password protected shares.
2145
+     * @throws OCSNotFoundException Share not found
2146
+     * @throws OCSForbiddenException You are not allowed to send mail notifications
2147
+     * @throws OCSBadRequestException Invalid request or wrong password
2148
+     * @throws OCSException Error while sending mail notification
2149
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
2150
+     *
2151
+     * 200: The email notification was sent successfully
2152
+     */
2153
+    #[NoAdminRequired]
2154
+    #[UserRateLimit(limit: 10, period: 600)]
2155
+    public function sendShareEmail(string $id, $password = ''): DataResponse {
2156
+        try {
2157
+            $share = $this->getShareById($id);
2158
+
2159
+            if (!$this->canAccessShare($share, false)) {
2160
+                throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2161
+            }
2162
+
2163
+            if (!$this->canEditShare($share)) {
2164
+                throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2165
+            }
2166
+
2167
+            // For mail and link shares, the user must be
2168
+            // the owner of the share, not only the file owner.
2169
+            if ($share->getShareType() === IShare::TYPE_EMAIL
2170
+                || $share->getShareType() === IShare::TYPE_LINK) {
2171
+                if ($share->getSharedBy() !== $this->userId) {
2172
+                    throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2173
+                }
2174
+            }
2175
+
2176
+            try {
2177
+                $provider = $this->factory->getProviderForType($share->getShareType());
2178
+                if (!($provider instanceof IShareProviderWithNotification)) {
2179
+                    throw new OCSBadRequestException($this->l->t('No mail notification configured for this share type'));
2180
+                }
2181
+
2182
+                // Circumvent the password encrypted data by
2183
+                // setting the password clear. We're not storing
2184
+                // the password clear, it is just a temporary
2185
+                // object manipulation. The password will stay
2186
+                // encrypted in the database.
2187
+                if ($share->getPassword() !== null && $share->getPassword() !== $password) {
2188
+                    if (!$this->shareManager->checkPassword($share, $password)) {
2189
+                        throw new OCSBadRequestException($this->l->t('Wrong password'));
2190
+                    }
2191
+                    $share = $share->setPassword($password);
2192
+                }
2193
+
2194
+                $provider->sendMailNotification($share);
2195
+                return new DataResponse();
2196
+            } catch (Exception $e) {
2197
+                $this->logger->error($e->getMessage(), ['exception' => $e]);
2198
+                throw new OCSException($this->l->t('Error while sending mail notification'));
2199
+            }
2200
+
2201
+        } catch (ShareNotFound $e) {
2202
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2203
+        }
2204
+    }
2205
+
2206
+    /**
2207
+     * Get a unique share token
2208
+     *
2209
+     * @throws OCSException Failed to generate a unique token
2210
+     *
2211
+     * @return DataResponse<Http::STATUS_OK, array{token: string}, array{}>
2212
+     *
2213
+     * 200: Token generated successfully
2214
+     */
2215
+    #[ApiRoute(verb: 'GET', url: '/api/v1/token')]
2216
+    #[NoAdminRequired]
2217
+    public function generateToken(): DataResponse {
2218
+        try {
2219
+            $token = $this->shareManager->generateToken();
2220
+            return new DataResponse([
2221
+                'token' => $token,
2222
+            ]);
2223
+        } catch (ShareTokenException $e) {
2224
+            throw new OCSException($this->l->t('Failed to generate a unique token'));
2225
+        }
2226
+    }
2227 2227
 }
Please login to merge, or discard this patch.