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