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