Completed
Push — master ( 2cbfdc...ebc5ae )
by
unknown
28:26 queued 14s
created
build/integration/features/bootstrap/SharingContext.php 1 patch
Indentation   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -14,25 +14,25 @@
 block discarded – undo
14 14
  * Features context.
15 15
  */
16 16
 class SharingContext implements Context, SnippetAcceptingContext {
17
-	use WebDav;
18
-	use Trashbin;
19
-	use AppConfiguration;
20
-	use CommandLine;
21
-	use Activity;
17
+    use WebDav;
18
+    use Trashbin;
19
+    use AppConfiguration;
20
+    use CommandLine;
21
+    use Activity;
22 22
 
23
-	protected function resetAppConfigs() {
24
-		$this->deleteServerConfig('core', 'shareapi_default_permissions');
25
-		$this->deleteServerConfig('core', 'shareapi_default_internal_expire_date');
26
-		$this->deleteServerConfig('core', 'shareapi_internal_expire_after_n_days');
27
-		$this->deleteServerConfig('core', 'internal_defaultExpDays');
28
-		$this->deleteServerConfig('core', 'shareapi_enforce_links_password');
29
-		$this->deleteServerConfig('core', 'shareapi_default_expire_date');
30
-		$this->deleteServerConfig('core', 'shareapi_expire_after_n_days');
31
-		$this->deleteServerConfig('core', 'link_defaultExpDays');
32
-		$this->deleteServerConfig('core', 'shareapi_allow_federation_on_public_shares');
33
-		$this->deleteServerConfig('files_sharing', 'outgoing_server2server_share_enabled');
34
-		$this->deleteServerConfig('core', 'shareapi_allow_view_without_download');
23
+    protected function resetAppConfigs() {
24
+        $this->deleteServerConfig('core', 'shareapi_default_permissions');
25
+        $this->deleteServerConfig('core', 'shareapi_default_internal_expire_date');
26
+        $this->deleteServerConfig('core', 'shareapi_internal_expire_after_n_days');
27
+        $this->deleteServerConfig('core', 'internal_defaultExpDays');
28
+        $this->deleteServerConfig('core', 'shareapi_enforce_links_password');
29
+        $this->deleteServerConfig('core', 'shareapi_default_expire_date');
30
+        $this->deleteServerConfig('core', 'shareapi_expire_after_n_days');
31
+        $this->deleteServerConfig('core', 'link_defaultExpDays');
32
+        $this->deleteServerConfig('core', 'shareapi_allow_federation_on_public_shares');
33
+        $this->deleteServerConfig('files_sharing', 'outgoing_server2server_share_enabled');
34
+        $this->deleteServerConfig('core', 'shareapi_allow_view_without_download');
35 35
 
36
-		$this->runOcc(['config:system:delete', 'share_folder']);
37
-	}
36
+        $this->runOcc(['config:system:delete', 'share_folder']);
37
+    }
38 38
 }
Please login to merge, or discard this patch.
build/integration/features/bootstrap/Sharing.php 1 patch
Indentation   +723 added lines, -723 removed lines patch added patch discarded remove patch
@@ -15,727 +15,727 @@
 block discarded – undo
15 15
 
16 16
 
17 17
 trait Sharing {
18
-	use Provisioning;
19
-
20
-	/** @var int */
21
-	private $sharingApiVersion = 1;
22
-
23
-	/** @var SimpleXMLElement */
24
-	private $lastShareData = null;
25
-
26
-	/** @var SimpleXMLElement[] */
27
-	private $storedShareData = [];
28
-
29
-	/** @var int */
30
-	private $savedShareId = null;
31
-
32
-	/** @var ResponseInterface */
33
-	private $response;
34
-
35
-	/**
36
-	 * @Given /^as "([^"]*)" creating a share with$/
37
-	 * @param string $user
38
-	 * @param TableNode|null $body
39
-	 */
40
-	public function asCreatingAShareWith($user, $body) {
41
-		$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares";
42
-		$client = new Client();
43
-		$options = [
44
-			'headers' => [
45
-				'OCS-APIREQUEST' => 'true',
46
-			],
47
-		];
48
-		if ($user === 'admin') {
49
-			$options['auth'] = $this->adminUser;
50
-		} else {
51
-			$options['auth'] = [$user, $this->regularUser];
52
-		}
53
-
54
-		if ($body instanceof TableNode) {
55
-			$fd = $body->getRowsHash();
56
-			if (array_key_exists('expireDate', $fd)) {
57
-				$dateModification = $fd['expireDate'];
58
-				if ($dateModification === 'null') {
59
-					$fd['expireDate'] = null;
60
-				} elseif (!empty($dateModification)) {
61
-					$fd['expireDate'] = date('Y-m-d', strtotime($dateModification));
62
-				} else {
63
-					$fd['expireDate'] = '';
64
-				}
65
-			}
66
-			$options['form_params'] = $fd;
67
-		}
68
-
69
-		try {
70
-			$this->response = $client->request('POST', $fullUrl, $options);
71
-		} catch (\GuzzleHttp\Exception\ClientException $ex) {
72
-			$this->response = $ex->getResponse();
73
-		}
74
-
75
-		$this->lastShareData = simplexml_load_string($this->response->getBody());
76
-	}
77
-
78
-	/**
79
-	 * @When /^save the last share data as "([^"]*)"$/
80
-	 */
81
-	public function saveLastShareData($name) {
82
-		$this->storedShareData[$name] = $this->lastShareData;
83
-	}
84
-
85
-	/**
86
-	 * @When /^restore the last share data from "([^"]*)"$/
87
-	 */
88
-	public function restoreLastShareData($name) {
89
-		$this->lastShareData = $this->storedShareData[$name];
90
-	}
91
-
92
-	/**
93
-	 * @When /^creating a share with$/
94
-	 * @param TableNode|null $body
95
-	 */
96
-	public function creatingShare($body) {
97
-		$this->asCreatingAShareWith($this->currentUser, $body);
98
-	}
99
-
100
-	/**
101
-	 * @When /^accepting last share$/
102
-	 */
103
-	public function acceptingLastShare() {
104
-		$share_id = $this->lastShareData->data[0]->id;
105
-		$url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/pending/$share_id";
106
-		$this->sendingToWith('POST', $url, null);
107
-
108
-		$this->theHTTPStatusCodeShouldBe('200');
109
-	}
110
-
111
-	/**
112
-	 * @When /^user "([^"]*)" accepts last share$/
113
-	 *
114
-	 * @param string $user
115
-	 */
116
-	public function userAcceptsLastShare(string $user) {
117
-		// "As userXXX" and "user userXXX accepts last share" steps are not
118
-		// expected to be used in the same scenario, but restore the user just
119
-		// in case.
120
-		$previousUser = $this->currentUser;
121
-
122
-		$this->currentUser = $user;
123
-
124
-		$share_id = $this->lastShareData->data[0]->id;
125
-		$url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/pending/$share_id";
126
-		$this->sendingToWith('POST', $url, null);
127
-
128
-		$this->currentUser = $previousUser;
129
-
130
-		$this->theHTTPStatusCodeShouldBe('200');
131
-	}
132
-
133
-	/**
134
-	 * @Then /^last link share can be downloaded$/
135
-	 */
136
-	public function lastLinkShareCanBeDownloaded() {
137
-		if (count($this->lastShareData->data->element) > 0) {
138
-			$url = $this->lastShareData->data[0]->url;
139
-		} else {
140
-			$url = $this->lastShareData->data->url;
141
-		}
142
-		$fullUrl = $url . '/download';
143
-		$this->checkDownload($fullUrl, null, 'text/plain');
144
-	}
145
-
146
-	/**
147
-	 * @Then /^last share can be downloaded$/
148
-	 */
149
-	public function lastShareCanBeDownloaded() {
150
-		if (count($this->lastShareData->data->element) > 0) {
151
-			$token = $this->lastShareData->data[0]->token;
152
-		} else {
153
-			$token = $this->lastShareData->data->token;
154
-		}
155
-
156
-		$fullUrl = substr($this->baseUrl, 0, -4) . 'index.php/s/' . $token . '/download';
157
-		$this->checkDownload($fullUrl, null, 'text/plain');
158
-	}
159
-
160
-	/**
161
-	 * @Then /^last share with password "([^"]*)" can be downloaded$/
162
-	 */
163
-	public function lastShareWithPasswordCanBeDownloaded($password) {
164
-		if (count($this->lastShareData->data->element) > 0) {
165
-			$token = $this->lastShareData->data[0]->token;
166
-		} else {
167
-			$token = $this->lastShareData->data->token;
168
-		}
169
-
170
-		$fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token/";
171
-		$this->checkDownload($fullUrl, ['', $password], 'text/plain');
172
-	}
173
-
174
-	private function checkDownload($url, $auth = null, $mimeType = null) {
175
-		if ($auth !== null) {
176
-			$options['auth'] = $auth;
177
-		}
178
-		$options['stream'] = true;
179
-
180
-		$client = new Client();
181
-		$this->response = $client->get($url, $options);
182
-		Assert::assertEquals(200, $this->response->getStatusCode());
183
-
184
-		$buf = '';
185
-		$body = $this->response->getBody();
186
-		while (!$body->eof()) {
187
-			// read everything
188
-			$buf .= $body->read(8192);
189
-		}
190
-		$body->close();
191
-
192
-		if ($mimeType !== null) {
193
-			$finfo = new finfo;
194
-			Assert::assertEquals($mimeType, $finfo->buffer($buf, FILEINFO_MIME_TYPE));
195
-		}
196
-	}
197
-
198
-	/**
199
-	 * @When /^Adding expiration date to last share$/
200
-	 */
201
-	public function addingExpirationDate() {
202
-		$share_id = (string)$this->lastShareData->data[0]->id;
203
-		$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
204
-		$client = new Client();
205
-		$options = [];
206
-		if ($this->currentUser === 'admin') {
207
-			$options['auth'] = $this->adminUser;
208
-		} else {
209
-			$options['auth'] = [$this->currentUser, $this->regularUser];
210
-		}
211
-		$date = date('Y-m-d', strtotime('+3 days'));
212
-		$options['form_params'] = ['expireDate' => $date];
213
-		$this->response = $this->response = $client->request('PUT', $fullUrl, $options);
214
-		Assert::assertEquals(200, $this->response->getStatusCode());
215
-	}
216
-
217
-	/**
218
-	 * @When /^Updating last share with$/
219
-	 * @param TableNode|null $body
220
-	 */
221
-	public function updatingLastShare($body) {
222
-		$share_id = (string)$this->lastShareData->data[0]->id;
223
-		$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
224
-		$client = new Client();
225
-		$options = [
226
-			'headers' => [
227
-				'OCS-APIREQUEST' => 'true',
228
-			],
229
-		];
230
-		if ($this->currentUser === 'admin') {
231
-			$options['auth'] = $this->adminUser;
232
-		} else {
233
-			$options['auth'] = [$this->currentUser, $this->regularUser];
234
-		}
235
-
236
-		if ($body instanceof TableNode) {
237
-			$fd = $body->getRowsHash();
238
-			if (array_key_exists('expireDate', $fd)) {
239
-				$dateModification = $fd['expireDate'];
240
-				$fd['expireDate'] = date('Y-m-d', strtotime($dateModification));
241
-			}
242
-			$options['form_params'] = $fd;
243
-		}
244
-
245
-		try {
246
-			$this->response = $client->request('PUT', $fullUrl, $options);
247
-		} catch (\GuzzleHttp\Exception\ClientException $ex) {
248
-			$this->response = $ex->getResponse();
249
-		}
250
-	}
251
-
252
-	public function createShare($user,
253
-		$path = null,
254
-		$shareType = null,
255
-		$shareWith = null,
256
-		$publicUpload = null,
257
-		$password = null,
258
-		$permissions = null,
259
-		$viewOnly = false) {
260
-		$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares";
261
-		$client = new Client();
262
-		$options = [
263
-			'headers' => [
264
-				'OCS-APIREQUEST' => 'true',
265
-			],
266
-		];
267
-
268
-		if ($user === 'admin') {
269
-			$options['auth'] = $this->adminUser;
270
-		} else {
271
-			$options['auth'] = [$user, $this->regularUser];
272
-		}
273
-		$body = [];
274
-		if (!is_null($path)) {
275
-			$body['path'] = $path;
276
-		}
277
-		if (!is_null($shareType)) {
278
-			$body['shareType'] = $shareType;
279
-		}
280
-		if (!is_null($shareWith)) {
281
-			$body['shareWith'] = $shareWith;
282
-		}
283
-		if (!is_null($publicUpload)) {
284
-			$body['publicUpload'] = $publicUpload;
285
-		}
286
-		if (!is_null($password)) {
287
-			$body['password'] = $password;
288
-		}
289
-		if (!is_null($permissions)) {
290
-			$body['permissions'] = $permissions;
291
-		}
292
-
293
-		if ($viewOnly === true) {
294
-			$body['attributes'] = json_encode([['scope' => 'permissions', 'key' => 'download', 'value' => false]]);
295
-		}
296
-
297
-		$options['form_params'] = $body;
298
-
299
-		try {
300
-			$this->response = $client->request('POST', $fullUrl, $options);
301
-			$this->lastShareData = simplexml_load_string($this->response->getBody());
302
-		} catch (\GuzzleHttp\Exception\ClientException $ex) {
303
-			$this->response = $ex->getResponse();
304
-			throw new \Exception($this->response->getBody());
305
-		}
306
-	}
307
-
308
-	public function isFieldInResponse($field, $contentExpected) {
309
-		$data = simplexml_load_string($this->response->getBody())->data[0];
310
-		if ((string)$field == 'expiration') {
311
-			if (!empty($contentExpected)) {
312
-				$contentExpected = date('Y-m-d', strtotime($contentExpected)) . ' 00:00:00';
313
-			}
314
-		}
315
-		if (count($data->element) > 0) {
316
-			foreach ($data as $element) {
317
-				if ($contentExpected == 'A_TOKEN') {
318
-					return (strlen((string)$element->$field) == 15);
319
-				} elseif ($contentExpected == 'A_NUMBER') {
320
-					return is_numeric((string)$element->$field);
321
-				} elseif ($contentExpected == 'AN_URL') {
322
-					return $this->isExpectedUrl((string)$element->$field, 'index.php/s/');
323
-				} elseif ((string)$element->$field == $contentExpected) {
324
-					return true;
325
-				} else {
326
-					print($element->$field);
327
-				}
328
-			}
329
-
330
-			return false;
331
-		} else {
332
-			if ($contentExpected == 'A_TOKEN') {
333
-				return (strlen((string)$data->$field) == 15);
334
-			} elseif ($contentExpected == 'A_NUMBER') {
335
-				return is_numeric((string)$data->$field);
336
-			} elseif ($contentExpected == 'AN_URL') {
337
-				return $this->isExpectedUrl((string)$data->$field, 'index.php/s/');
338
-			} elseif ($contentExpected == $data->$field) {
339
-				return true;
340
-			} else {
341
-				print($data->$field);
342
-			}
343
-			return false;
344
-		}
345
-	}
346
-
347
-	/**
348
-	 * @Then /^File "([^"]*)" should be included in the response$/
349
-	 *
350
-	 * @param string $filename
351
-	 */
352
-	public function checkSharedFileInResponse($filename) {
353
-		Assert::assertEquals(true, $this->isFieldInResponse('file_target', "/$filename"));
354
-	}
355
-
356
-	/**
357
-	 * @Then /^File "([^"]*)" should not be included in the response$/
358
-	 *
359
-	 * @param string $filename
360
-	 */
361
-	public function checkSharedFileNotInResponse($filename) {
362
-		Assert::assertEquals(false, $this->isFieldInResponse('file_target', "/$filename"));
363
-	}
364
-
365
-	/**
366
-	 * @Then /^User "([^"]*)" should be included in the response$/
367
-	 *
368
-	 * @param string $user
369
-	 */
370
-	public function checkSharedUserInResponse($user) {
371
-		Assert::assertEquals(true, $this->isFieldInResponse('share_with', "$user"));
372
-	}
373
-
374
-	/**
375
-	 * @Then /^User "([^"]*)" should not be included in the response$/
376
-	 *
377
-	 * @param string $user
378
-	 */
379
-	public function checkSharedUserNotInResponse($user) {
380
-		Assert::assertEquals(false, $this->isFieldInResponse('share_with', "$user"));
381
-	}
382
-
383
-	public function isUserOrGroupInSharedData($userOrGroup, $permissions = null) {
384
-		$data = simplexml_load_string($this->response->getBody())->data[0];
385
-		foreach ($data as $element) {
386
-			if ($element->share_with == $userOrGroup && ($permissions === null || $permissions == $element->permissions)) {
387
-				return true;
388
-			}
389
-		}
390
-		return false;
391
-	}
392
-
393
-	/**
394
-	 * @Given /^(file|folder|entry) "([^"]*)" of user "([^"]*)" is shared with user "([^"]*)"( with permissions ([\d]*))?( view-only)?$/
395
-	 *
396
-	 * @param string $filepath
397
-	 * @param string $user1
398
-	 * @param string $user2
399
-	 */
400
-	public function assureFileIsShared($entry, $filepath, $user1, $user2, $withPerms = null, $permissions = null, $viewOnly = null) {
401
-		// when view-only is set, permissions is empty string instead of null...
402
-		if ($permissions === '') {
403
-			$permissions = null;
404
-		}
405
-		$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares" . "?path=$filepath";
406
-		$client = new Client();
407
-		$options = [];
408
-		if ($user1 === 'admin') {
409
-			$options['auth'] = $this->adminUser;
410
-		} else {
411
-			$options['auth'] = [$user1, $this->regularUser];
412
-		}
413
-		$options['headers'] = [
414
-			'OCS-APIREQUEST' => 'true',
415
-		];
416
-		$this->response = $client->get($fullUrl, $options);
417
-		if ($this->isUserOrGroupInSharedData($user2, $permissions)) {
418
-			return;
419
-		} else {
420
-			$this->createShare($user1, $filepath, 0, $user2, null, null, $permissions, $viewOnly !== null);
421
-		}
422
-		$this->response = $client->get($fullUrl, $options);
423
-		Assert::assertEquals(true, $this->isUserOrGroupInSharedData($user2, $permissions));
424
-	}
425
-
426
-	/**
427
-	 * @Given /^(file|folder|entry) "([^"]*)" of user "([^"]*)" is shared with group "([^"]*)"( with permissions ([\d]*))?( view-only)?$/
428
-	 *
429
-	 * @param string $filepath
430
-	 * @param string $user
431
-	 * @param string $group
432
-	 */
433
-	public function assureFileIsSharedWithGroup($entry, $filepath, $user, $group, $withPerms = null, $permissions = null, $viewOnly = null) {
434
-		// when view-only is set, permissions is empty string instead of null...
435
-		if ($permissions === '') {
436
-			$permissions = null;
437
-		}
438
-		$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares" . "?path=$filepath";
439
-		$client = new Client();
440
-		$options = [];
441
-		if ($user === 'admin') {
442
-			$options['auth'] = $this->adminUser;
443
-		} else {
444
-			$options['auth'] = [$user, $this->regularUser];
445
-		}
446
-		$options['headers'] = [
447
-			'OCS-APIREQUEST' => 'true',
448
-		];
449
-		$this->response = $client->get($fullUrl, $options);
450
-		if ($this->isUserOrGroupInSharedData($group, $permissions)) {
451
-			return;
452
-		} else {
453
-			$this->createShare($user, $filepath, 1, $group, null, null, $permissions, $viewOnly !== null);
454
-		}
455
-		$this->response = $client->get($fullUrl, $options);
456
-		Assert::assertEquals(true, $this->isUserOrGroupInSharedData($group, $permissions));
457
-	}
458
-
459
-	/**
460
-	 * @When /^Deleting last share$/
461
-	 */
462
-	public function deletingLastShare() {
463
-		$share_id = $this->lastShareData->data[0]->id;
464
-		$url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
465
-		$this->sendingToWith('DELETE', $url, null);
466
-	}
467
-
468
-	/**
469
-	 * @When /^Getting info of last share$/
470
-	 */
471
-	public function gettingInfoOfLastShare() {
472
-		$share_id = $this->lastShareData->data[0]->id;
473
-		$url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
474
-		$this->sendingToWith('GET', $url, null);
475
-	}
476
-
477
-	/**
478
-	 * @Then /^last share_id is included in the answer$/
479
-	 */
480
-	public function checkingLastShareIDIsIncluded() {
481
-		$share_id = $this->lastShareData->data[0]->id;
482
-		if (!$this->isFieldInResponse('id', $share_id)) {
483
-			Assert::fail("Share id $share_id not found in response");
484
-		}
485
-	}
486
-
487
-	/**
488
-	 * @Then /^last share_id is not included in the answer$/
489
-	 */
490
-	public function checkingLastShareIDIsNotIncluded() {
491
-		$share_id = $this->lastShareData->data[0]->id;
492
-		if ($this->isFieldInResponse('id', $share_id)) {
493
-			Assert::fail("Share id $share_id has been found in response");
494
-		}
495
-	}
496
-
497
-	/**
498
-	 * @Then /^Share fields of last share match with$/
499
-	 * @param TableNode|null $body
500
-	 */
501
-	public function checkShareFields($body) {
502
-		if ($body instanceof TableNode) {
503
-			$fd = $body->getRowsHash();
504
-
505
-			foreach ($fd as $field => $value) {
506
-				if (substr($field, 0, 10) === 'share_with') {
507
-					$value = str_replace('REMOTE', substr($this->remoteBaseUrl, 0, -5), $value);
508
-					$value = str_replace('LOCAL', substr($this->localBaseUrl, 0, -5), $value);
509
-				}
510
-				if (substr($field, 0, 6) === 'remote') {
511
-					$value = str_replace('REMOTE', substr($this->remoteBaseUrl, 0, -4), $value);
512
-					$value = str_replace('LOCAL', substr($this->localBaseUrl, 0, -4), $value);
513
-				}
514
-				if (!$this->isFieldInResponse($field, $value)) {
515
-					Assert::fail("$field" . " doesn't have value " . "$value");
516
-				}
517
-			}
518
-		}
519
-	}
520
-
521
-	/**
522
-	 * @Then the list of returned shares has :count shares
523
-	 */
524
-	public function theListOfReturnedSharesHasShares(int $count) {
525
-		$this->theHTTPStatusCodeShouldBe('200');
526
-		$this->theOCSStatusCodeShouldBe('100');
527
-
528
-		$returnedShares = $this->getXmlResponse()->data[0];
529
-
530
-		Assert::assertEquals($count, count($returnedShares->element));
531
-	}
532
-
533
-	/**
534
-	 * @Then share :count is returned with
535
-	 *
536
-	 * @param int $number
537
-	 * @param TableNode $body
538
-	 */
539
-	public function shareXIsReturnedWith(int $number, TableNode $body) {
540
-		$this->theHTTPStatusCodeShouldBe('200');
541
-		$this->theOCSStatusCodeShouldBe('100');
542
-
543
-		if (!($body instanceof TableNode)) {
544
-			return;
545
-		}
546
-
547
-		$returnedShare = $this->getXmlResponse()->data[0];
548
-		if ($returnedShare->element) {
549
-			$returnedShare = $returnedShare->element[$number];
550
-		}
551
-
552
-		$defaultExpectedFields = [
553
-			'id' => 'A_NUMBER',
554
-			'permissions' => '19',
555
-			'stime' => 'A_NUMBER',
556
-			'parent' => '',
557
-			'expiration' => '',
558
-			'token' => '',
559
-			'storage' => 'A_NUMBER',
560
-			'item_source' => 'A_NUMBER',
561
-			'file_source' => 'A_NUMBER',
562
-			'file_parent' => 'A_NUMBER',
563
-			'mail_send' => '0'
564
-		];
565
-		$expectedFields = array_merge($defaultExpectedFields, $body->getRowsHash());
566
-
567
-		if (!array_key_exists('uid_file_owner', $expectedFields)
568
-				&& array_key_exists('uid_owner', $expectedFields)) {
569
-			$expectedFields['uid_file_owner'] = $expectedFields['uid_owner'];
570
-		}
571
-		if (!array_key_exists('displayname_file_owner', $expectedFields)
572
-				&& array_key_exists('displayname_owner', $expectedFields)) {
573
-			$expectedFields['displayname_file_owner'] = $expectedFields['displayname_owner'];
574
-		}
575
-
576
-		if (array_key_exists('share_type', $expectedFields)
577
-				&& $expectedFields['share_type'] == 10 /* IShare::TYPE_ROOM */
578
-				&& array_key_exists('share_with', $expectedFields)) {
579
-			if ($expectedFields['share_with'] === 'private_conversation') {
580
-				$expectedFields['share_with'] = 'REGEXP /^private_conversation_[0-9a-f]{6}$/';
581
-			} else {
582
-				$expectedFields['share_with'] = FeatureContext::getTokenForIdentifier($expectedFields['share_with']);
583
-			}
584
-		}
585
-
586
-		foreach ($expectedFields as $field => $value) {
587
-			$this->assertFieldIsInReturnedShare($field, $value, $returnedShare);
588
-		}
589
-	}
590
-
591
-	/**
592
-	 * @return SimpleXMLElement
593
-	 */
594
-	private function getXmlResponse(): \SimpleXMLElement {
595
-		return simplexml_load_string($this->response->getBody());
596
-	}
597
-
598
-	/**
599
-	 * @param string $field
600
-	 * @param string $contentExpected
601
-	 * @param \SimpleXMLElement $returnedShare
602
-	 */
603
-	private function assertFieldIsInReturnedShare(string $field, string $contentExpected, \SimpleXMLElement $returnedShare) {
604
-		if ($contentExpected === 'IGNORE') {
605
-			return;
606
-		}
607
-
608
-		if (!property_exists($returnedShare, $field)) {
609
-			Assert::fail("$field was not found in response");
610
-		}
611
-
612
-		if ($field === 'expiration' && !empty($contentExpected)) {
613
-			$contentExpected = date('Y-m-d', strtotime($contentExpected)) . ' 00:00:00';
614
-		}
615
-
616
-		if ($contentExpected === 'A_NUMBER') {
617
-			Assert::assertTrue(is_numeric((string)$returnedShare->$field), "Field '$field' is not a number: " . $returnedShare->$field);
618
-		} elseif ($contentExpected === 'A_TOKEN') {
619
-			// A token is composed by 15 characters from
620
-			// ISecureRandom::CHAR_HUMAN_READABLE.
621
-			Assert::assertRegExp('/^[abcdefgijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXYZ23456789]{15}$/', (string)$returnedShare->$field, "Field '$field' is not a token");
622
-		} elseif (strpos($contentExpected, 'REGEXP ') === 0) {
623
-			Assert::assertRegExp(substr($contentExpected, strlen('REGEXP ')), (string)$returnedShare->$field, "Field '$field' does not match");
624
-		} else {
625
-			Assert::assertEquals($contentExpected, (string)$returnedShare->$field, "Field '$field' does not match");
626
-		}
627
-	}
628
-
629
-	/**
630
-	 * @Then As :user remove all shares from the file named :fileName
631
-	 */
632
-	public function asRemoveAllSharesFromTheFileNamed($user, $fileName) {
633
-		$url = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares?format=json";
634
-		$client = new \GuzzleHttp\Client();
635
-		$res = $client->get(
636
-			$url,
637
-			[
638
-				'auth' => [
639
-					$user,
640
-					'123456',
641
-				],
642
-				'headers' => [
643
-					'Content-Type' => 'application/json',
644
-					'OCS-APIREQUEST' => 'true',
645
-				],
646
-			]
647
-		);
648
-		$json = json_decode($res->getBody()->getContents(), true);
649
-		$deleted = false;
650
-		foreach ($json['ocs']['data'] as $data) {
651
-			if (stripslashes($data['path']) === $fileName) {
652
-				$id = $data['id'];
653
-				$client->delete(
654
-					$this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/{$id}",
655
-					[
656
-						'auth' => [
657
-							$user,
658
-							'123456',
659
-						],
660
-						'headers' => [
661
-							'Content-Type' => 'application/json',
662
-							'OCS-APIREQUEST' => 'true',
663
-						],
664
-					]
665
-				);
666
-				$deleted = true;
667
-			}
668
-		}
669
-
670
-		if ($deleted === false) {
671
-			throw new \Exception("Could not delete file $fileName");
672
-		}
673
-	}
674
-
675
-	/**
676
-	 * @When save last share id
677
-	 */
678
-	public function saveLastShareId() {
679
-		$this->savedShareId = ($this->lastShareData['data']['id'] ?? null);
680
-	}
681
-
682
-	/**
683
-	 * @Then share ids should match
684
-	 */
685
-	public function shareIdsShouldMatch() {
686
-		if ($this->savedShareId !== ($this->lastShareData['data']['id'] ?? null)) {
687
-			throw new \Exception('Expected the same link share to be returned');
688
-		}
689
-	}
690
-
691
-	/**
692
-	 * @When /^getting sharees for$/
693
-	 * @param TableNode $body
694
-	 */
695
-	public function whenGettingShareesFor($body) {
696
-		$url = '/apps/files_sharing/api/v1/sharees';
697
-		if ($body instanceof TableNode) {
698
-			$parameters = [];
699
-			foreach ($body->getRowsHash() as $key => $value) {
700
-				$parameters[] = $key . '=' . $value;
701
-			}
702
-			if (!empty($parameters)) {
703
-				$url .= '?' . implode('&', $parameters);
704
-			}
705
-		}
706
-
707
-		$this->sendingTo('GET', $url);
708
-	}
709
-
710
-	/**
711
-	 * @Then /^"([^"]*)" sharees returned (are|is empty)$/
712
-	 * @param string $shareeType
713
-	 * @param string $isEmpty
714
-	 * @param TableNode|null $shareesList
715
-	 */
716
-	public function thenListOfSharees($shareeType, $isEmpty, $shareesList = null) {
717
-		if ($isEmpty !== 'is empty') {
718
-			$sharees = $shareesList->getRows();
719
-			$respondedArray = $this->getArrayOfShareesResponded($this->response, $shareeType);
720
-			Assert::assertEquals($sharees, $respondedArray);
721
-		} else {
722
-			$respondedArray = $this->getArrayOfShareesResponded($this->response, $shareeType);
723
-			Assert::assertEmpty($respondedArray);
724
-		}
725
-	}
726
-
727
-	public function getArrayOfShareesResponded(ResponseInterface $response, $shareeType) {
728
-		$elements = simplexml_load_string($response->getBody())->data;
729
-		$elements = json_decode(json_encode($elements), 1);
730
-		if (strpos($shareeType, 'exact ') === 0) {
731
-			$elements = $elements['exact'];
732
-			$shareeType = substr($shareeType, 6);
733
-		}
734
-
735
-		$sharees = [];
736
-		foreach ($elements[$shareeType] as $element) {
737
-			$sharees[] = [$element['label'], $element['value']['shareType'], $element['value']['shareWith']];
738
-		}
739
-		return $sharees;
740
-	}
18
+    use Provisioning;
19
+
20
+    /** @var int */
21
+    private $sharingApiVersion = 1;
22
+
23
+    /** @var SimpleXMLElement */
24
+    private $lastShareData = null;
25
+
26
+    /** @var SimpleXMLElement[] */
27
+    private $storedShareData = [];
28
+
29
+    /** @var int */
30
+    private $savedShareId = null;
31
+
32
+    /** @var ResponseInterface */
33
+    private $response;
34
+
35
+    /**
36
+     * @Given /^as "([^"]*)" creating a share with$/
37
+     * @param string $user
38
+     * @param TableNode|null $body
39
+     */
40
+    public function asCreatingAShareWith($user, $body) {
41
+        $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares";
42
+        $client = new Client();
43
+        $options = [
44
+            'headers' => [
45
+                'OCS-APIREQUEST' => 'true',
46
+            ],
47
+        ];
48
+        if ($user === 'admin') {
49
+            $options['auth'] = $this->adminUser;
50
+        } else {
51
+            $options['auth'] = [$user, $this->regularUser];
52
+        }
53
+
54
+        if ($body instanceof TableNode) {
55
+            $fd = $body->getRowsHash();
56
+            if (array_key_exists('expireDate', $fd)) {
57
+                $dateModification = $fd['expireDate'];
58
+                if ($dateModification === 'null') {
59
+                    $fd['expireDate'] = null;
60
+                } elseif (!empty($dateModification)) {
61
+                    $fd['expireDate'] = date('Y-m-d', strtotime($dateModification));
62
+                } else {
63
+                    $fd['expireDate'] = '';
64
+                }
65
+            }
66
+            $options['form_params'] = $fd;
67
+        }
68
+
69
+        try {
70
+            $this->response = $client->request('POST', $fullUrl, $options);
71
+        } catch (\GuzzleHttp\Exception\ClientException $ex) {
72
+            $this->response = $ex->getResponse();
73
+        }
74
+
75
+        $this->lastShareData = simplexml_load_string($this->response->getBody());
76
+    }
77
+
78
+    /**
79
+     * @When /^save the last share data as "([^"]*)"$/
80
+     */
81
+    public function saveLastShareData($name) {
82
+        $this->storedShareData[$name] = $this->lastShareData;
83
+    }
84
+
85
+    /**
86
+     * @When /^restore the last share data from "([^"]*)"$/
87
+     */
88
+    public function restoreLastShareData($name) {
89
+        $this->lastShareData = $this->storedShareData[$name];
90
+    }
91
+
92
+    /**
93
+     * @When /^creating a share with$/
94
+     * @param TableNode|null $body
95
+     */
96
+    public function creatingShare($body) {
97
+        $this->asCreatingAShareWith($this->currentUser, $body);
98
+    }
99
+
100
+    /**
101
+     * @When /^accepting last share$/
102
+     */
103
+    public function acceptingLastShare() {
104
+        $share_id = $this->lastShareData->data[0]->id;
105
+        $url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/pending/$share_id";
106
+        $this->sendingToWith('POST', $url, null);
107
+
108
+        $this->theHTTPStatusCodeShouldBe('200');
109
+    }
110
+
111
+    /**
112
+     * @When /^user "([^"]*)" accepts last share$/
113
+     *
114
+     * @param string $user
115
+     */
116
+    public function userAcceptsLastShare(string $user) {
117
+        // "As userXXX" and "user userXXX accepts last share" steps are not
118
+        // expected to be used in the same scenario, but restore the user just
119
+        // in case.
120
+        $previousUser = $this->currentUser;
121
+
122
+        $this->currentUser = $user;
123
+
124
+        $share_id = $this->lastShareData->data[0]->id;
125
+        $url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/pending/$share_id";
126
+        $this->sendingToWith('POST', $url, null);
127
+
128
+        $this->currentUser = $previousUser;
129
+
130
+        $this->theHTTPStatusCodeShouldBe('200');
131
+    }
132
+
133
+    /**
134
+     * @Then /^last link share can be downloaded$/
135
+     */
136
+    public function lastLinkShareCanBeDownloaded() {
137
+        if (count($this->lastShareData->data->element) > 0) {
138
+            $url = $this->lastShareData->data[0]->url;
139
+        } else {
140
+            $url = $this->lastShareData->data->url;
141
+        }
142
+        $fullUrl = $url . '/download';
143
+        $this->checkDownload($fullUrl, null, 'text/plain');
144
+    }
145
+
146
+    /**
147
+     * @Then /^last share can be downloaded$/
148
+     */
149
+    public function lastShareCanBeDownloaded() {
150
+        if (count($this->lastShareData->data->element) > 0) {
151
+            $token = $this->lastShareData->data[0]->token;
152
+        } else {
153
+            $token = $this->lastShareData->data->token;
154
+        }
155
+
156
+        $fullUrl = substr($this->baseUrl, 0, -4) . 'index.php/s/' . $token . '/download';
157
+        $this->checkDownload($fullUrl, null, 'text/plain');
158
+    }
159
+
160
+    /**
161
+     * @Then /^last share with password "([^"]*)" can be downloaded$/
162
+     */
163
+    public function lastShareWithPasswordCanBeDownloaded($password) {
164
+        if (count($this->lastShareData->data->element) > 0) {
165
+            $token = $this->lastShareData->data[0]->token;
166
+        } else {
167
+            $token = $this->lastShareData->data->token;
168
+        }
169
+
170
+        $fullUrl = substr($this->baseUrl, 0, -4) . "public.php/dav/files/$token/";
171
+        $this->checkDownload($fullUrl, ['', $password], 'text/plain');
172
+    }
173
+
174
+    private function checkDownload($url, $auth = null, $mimeType = null) {
175
+        if ($auth !== null) {
176
+            $options['auth'] = $auth;
177
+        }
178
+        $options['stream'] = true;
179
+
180
+        $client = new Client();
181
+        $this->response = $client->get($url, $options);
182
+        Assert::assertEquals(200, $this->response->getStatusCode());
183
+
184
+        $buf = '';
185
+        $body = $this->response->getBody();
186
+        while (!$body->eof()) {
187
+            // read everything
188
+            $buf .= $body->read(8192);
189
+        }
190
+        $body->close();
191
+
192
+        if ($mimeType !== null) {
193
+            $finfo = new finfo;
194
+            Assert::assertEquals($mimeType, $finfo->buffer($buf, FILEINFO_MIME_TYPE));
195
+        }
196
+    }
197
+
198
+    /**
199
+     * @When /^Adding expiration date to last share$/
200
+     */
201
+    public function addingExpirationDate() {
202
+        $share_id = (string)$this->lastShareData->data[0]->id;
203
+        $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
204
+        $client = new Client();
205
+        $options = [];
206
+        if ($this->currentUser === 'admin') {
207
+            $options['auth'] = $this->adminUser;
208
+        } else {
209
+            $options['auth'] = [$this->currentUser, $this->regularUser];
210
+        }
211
+        $date = date('Y-m-d', strtotime('+3 days'));
212
+        $options['form_params'] = ['expireDate' => $date];
213
+        $this->response = $this->response = $client->request('PUT', $fullUrl, $options);
214
+        Assert::assertEquals(200, $this->response->getStatusCode());
215
+    }
216
+
217
+    /**
218
+     * @When /^Updating last share with$/
219
+     * @param TableNode|null $body
220
+     */
221
+    public function updatingLastShare($body) {
222
+        $share_id = (string)$this->lastShareData->data[0]->id;
223
+        $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
224
+        $client = new Client();
225
+        $options = [
226
+            'headers' => [
227
+                'OCS-APIREQUEST' => 'true',
228
+            ],
229
+        ];
230
+        if ($this->currentUser === 'admin') {
231
+            $options['auth'] = $this->adminUser;
232
+        } else {
233
+            $options['auth'] = [$this->currentUser, $this->regularUser];
234
+        }
235
+
236
+        if ($body instanceof TableNode) {
237
+            $fd = $body->getRowsHash();
238
+            if (array_key_exists('expireDate', $fd)) {
239
+                $dateModification = $fd['expireDate'];
240
+                $fd['expireDate'] = date('Y-m-d', strtotime($dateModification));
241
+            }
242
+            $options['form_params'] = $fd;
243
+        }
244
+
245
+        try {
246
+            $this->response = $client->request('PUT', $fullUrl, $options);
247
+        } catch (\GuzzleHttp\Exception\ClientException $ex) {
248
+            $this->response = $ex->getResponse();
249
+        }
250
+    }
251
+
252
+    public function createShare($user,
253
+        $path = null,
254
+        $shareType = null,
255
+        $shareWith = null,
256
+        $publicUpload = null,
257
+        $password = null,
258
+        $permissions = null,
259
+        $viewOnly = false) {
260
+        $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares";
261
+        $client = new Client();
262
+        $options = [
263
+            'headers' => [
264
+                'OCS-APIREQUEST' => 'true',
265
+            ],
266
+        ];
267
+
268
+        if ($user === 'admin') {
269
+            $options['auth'] = $this->adminUser;
270
+        } else {
271
+            $options['auth'] = [$user, $this->regularUser];
272
+        }
273
+        $body = [];
274
+        if (!is_null($path)) {
275
+            $body['path'] = $path;
276
+        }
277
+        if (!is_null($shareType)) {
278
+            $body['shareType'] = $shareType;
279
+        }
280
+        if (!is_null($shareWith)) {
281
+            $body['shareWith'] = $shareWith;
282
+        }
283
+        if (!is_null($publicUpload)) {
284
+            $body['publicUpload'] = $publicUpload;
285
+        }
286
+        if (!is_null($password)) {
287
+            $body['password'] = $password;
288
+        }
289
+        if (!is_null($permissions)) {
290
+            $body['permissions'] = $permissions;
291
+        }
292
+
293
+        if ($viewOnly === true) {
294
+            $body['attributes'] = json_encode([['scope' => 'permissions', 'key' => 'download', 'value' => false]]);
295
+        }
296
+
297
+        $options['form_params'] = $body;
298
+
299
+        try {
300
+            $this->response = $client->request('POST', $fullUrl, $options);
301
+            $this->lastShareData = simplexml_load_string($this->response->getBody());
302
+        } catch (\GuzzleHttp\Exception\ClientException $ex) {
303
+            $this->response = $ex->getResponse();
304
+            throw new \Exception($this->response->getBody());
305
+        }
306
+    }
307
+
308
+    public function isFieldInResponse($field, $contentExpected) {
309
+        $data = simplexml_load_string($this->response->getBody())->data[0];
310
+        if ((string)$field == 'expiration') {
311
+            if (!empty($contentExpected)) {
312
+                $contentExpected = date('Y-m-d', strtotime($contentExpected)) . ' 00:00:00';
313
+            }
314
+        }
315
+        if (count($data->element) > 0) {
316
+            foreach ($data as $element) {
317
+                if ($contentExpected == 'A_TOKEN') {
318
+                    return (strlen((string)$element->$field) == 15);
319
+                } elseif ($contentExpected == 'A_NUMBER') {
320
+                    return is_numeric((string)$element->$field);
321
+                } elseif ($contentExpected == 'AN_URL') {
322
+                    return $this->isExpectedUrl((string)$element->$field, 'index.php/s/');
323
+                } elseif ((string)$element->$field == $contentExpected) {
324
+                    return true;
325
+                } else {
326
+                    print($element->$field);
327
+                }
328
+            }
329
+
330
+            return false;
331
+        } else {
332
+            if ($contentExpected == 'A_TOKEN') {
333
+                return (strlen((string)$data->$field) == 15);
334
+            } elseif ($contentExpected == 'A_NUMBER') {
335
+                return is_numeric((string)$data->$field);
336
+            } elseif ($contentExpected == 'AN_URL') {
337
+                return $this->isExpectedUrl((string)$data->$field, 'index.php/s/');
338
+            } elseif ($contentExpected == $data->$field) {
339
+                return true;
340
+            } else {
341
+                print($data->$field);
342
+            }
343
+            return false;
344
+        }
345
+    }
346
+
347
+    /**
348
+     * @Then /^File "([^"]*)" should be included in the response$/
349
+     *
350
+     * @param string $filename
351
+     */
352
+    public function checkSharedFileInResponse($filename) {
353
+        Assert::assertEquals(true, $this->isFieldInResponse('file_target', "/$filename"));
354
+    }
355
+
356
+    /**
357
+     * @Then /^File "([^"]*)" should not be included in the response$/
358
+     *
359
+     * @param string $filename
360
+     */
361
+    public function checkSharedFileNotInResponse($filename) {
362
+        Assert::assertEquals(false, $this->isFieldInResponse('file_target', "/$filename"));
363
+    }
364
+
365
+    /**
366
+     * @Then /^User "([^"]*)" should be included in the response$/
367
+     *
368
+     * @param string $user
369
+     */
370
+    public function checkSharedUserInResponse($user) {
371
+        Assert::assertEquals(true, $this->isFieldInResponse('share_with', "$user"));
372
+    }
373
+
374
+    /**
375
+     * @Then /^User "([^"]*)" should not be included in the response$/
376
+     *
377
+     * @param string $user
378
+     */
379
+    public function checkSharedUserNotInResponse($user) {
380
+        Assert::assertEquals(false, $this->isFieldInResponse('share_with', "$user"));
381
+    }
382
+
383
+    public function isUserOrGroupInSharedData($userOrGroup, $permissions = null) {
384
+        $data = simplexml_load_string($this->response->getBody())->data[0];
385
+        foreach ($data as $element) {
386
+            if ($element->share_with == $userOrGroup && ($permissions === null || $permissions == $element->permissions)) {
387
+                return true;
388
+            }
389
+        }
390
+        return false;
391
+    }
392
+
393
+    /**
394
+     * @Given /^(file|folder|entry) "([^"]*)" of user "([^"]*)" is shared with user "([^"]*)"( with permissions ([\d]*))?( view-only)?$/
395
+     *
396
+     * @param string $filepath
397
+     * @param string $user1
398
+     * @param string $user2
399
+     */
400
+    public function assureFileIsShared($entry, $filepath, $user1, $user2, $withPerms = null, $permissions = null, $viewOnly = null) {
401
+        // when view-only is set, permissions is empty string instead of null...
402
+        if ($permissions === '') {
403
+            $permissions = null;
404
+        }
405
+        $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares" . "?path=$filepath";
406
+        $client = new Client();
407
+        $options = [];
408
+        if ($user1 === 'admin') {
409
+            $options['auth'] = $this->adminUser;
410
+        } else {
411
+            $options['auth'] = [$user1, $this->regularUser];
412
+        }
413
+        $options['headers'] = [
414
+            'OCS-APIREQUEST' => 'true',
415
+        ];
416
+        $this->response = $client->get($fullUrl, $options);
417
+        if ($this->isUserOrGroupInSharedData($user2, $permissions)) {
418
+            return;
419
+        } else {
420
+            $this->createShare($user1, $filepath, 0, $user2, null, null, $permissions, $viewOnly !== null);
421
+        }
422
+        $this->response = $client->get($fullUrl, $options);
423
+        Assert::assertEquals(true, $this->isUserOrGroupInSharedData($user2, $permissions));
424
+    }
425
+
426
+    /**
427
+     * @Given /^(file|folder|entry) "([^"]*)" of user "([^"]*)" is shared with group "([^"]*)"( with permissions ([\d]*))?( view-only)?$/
428
+     *
429
+     * @param string $filepath
430
+     * @param string $user
431
+     * @param string $group
432
+     */
433
+    public function assureFileIsSharedWithGroup($entry, $filepath, $user, $group, $withPerms = null, $permissions = null, $viewOnly = null) {
434
+        // when view-only is set, permissions is empty string instead of null...
435
+        if ($permissions === '') {
436
+            $permissions = null;
437
+        }
438
+        $fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares" . "?path=$filepath";
439
+        $client = new Client();
440
+        $options = [];
441
+        if ($user === 'admin') {
442
+            $options['auth'] = $this->adminUser;
443
+        } else {
444
+            $options['auth'] = [$user, $this->regularUser];
445
+        }
446
+        $options['headers'] = [
447
+            'OCS-APIREQUEST' => 'true',
448
+        ];
449
+        $this->response = $client->get($fullUrl, $options);
450
+        if ($this->isUserOrGroupInSharedData($group, $permissions)) {
451
+            return;
452
+        } else {
453
+            $this->createShare($user, $filepath, 1, $group, null, null, $permissions, $viewOnly !== null);
454
+        }
455
+        $this->response = $client->get($fullUrl, $options);
456
+        Assert::assertEquals(true, $this->isUserOrGroupInSharedData($group, $permissions));
457
+    }
458
+
459
+    /**
460
+     * @When /^Deleting last share$/
461
+     */
462
+    public function deletingLastShare() {
463
+        $share_id = $this->lastShareData->data[0]->id;
464
+        $url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
465
+        $this->sendingToWith('DELETE', $url, null);
466
+    }
467
+
468
+    /**
469
+     * @When /^Getting info of last share$/
470
+     */
471
+    public function gettingInfoOfLastShare() {
472
+        $share_id = $this->lastShareData->data[0]->id;
473
+        $url = "/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/$share_id";
474
+        $this->sendingToWith('GET', $url, null);
475
+    }
476
+
477
+    /**
478
+     * @Then /^last share_id is included in the answer$/
479
+     */
480
+    public function checkingLastShareIDIsIncluded() {
481
+        $share_id = $this->lastShareData->data[0]->id;
482
+        if (!$this->isFieldInResponse('id', $share_id)) {
483
+            Assert::fail("Share id $share_id not found in response");
484
+        }
485
+    }
486
+
487
+    /**
488
+     * @Then /^last share_id is not included in the answer$/
489
+     */
490
+    public function checkingLastShareIDIsNotIncluded() {
491
+        $share_id = $this->lastShareData->data[0]->id;
492
+        if ($this->isFieldInResponse('id', $share_id)) {
493
+            Assert::fail("Share id $share_id has been found in response");
494
+        }
495
+    }
496
+
497
+    /**
498
+     * @Then /^Share fields of last share match with$/
499
+     * @param TableNode|null $body
500
+     */
501
+    public function checkShareFields($body) {
502
+        if ($body instanceof TableNode) {
503
+            $fd = $body->getRowsHash();
504
+
505
+            foreach ($fd as $field => $value) {
506
+                if (substr($field, 0, 10) === 'share_with') {
507
+                    $value = str_replace('REMOTE', substr($this->remoteBaseUrl, 0, -5), $value);
508
+                    $value = str_replace('LOCAL', substr($this->localBaseUrl, 0, -5), $value);
509
+                }
510
+                if (substr($field, 0, 6) === 'remote') {
511
+                    $value = str_replace('REMOTE', substr($this->remoteBaseUrl, 0, -4), $value);
512
+                    $value = str_replace('LOCAL', substr($this->localBaseUrl, 0, -4), $value);
513
+                }
514
+                if (!$this->isFieldInResponse($field, $value)) {
515
+                    Assert::fail("$field" . " doesn't have value " . "$value");
516
+                }
517
+            }
518
+        }
519
+    }
520
+
521
+    /**
522
+     * @Then the list of returned shares has :count shares
523
+     */
524
+    public function theListOfReturnedSharesHasShares(int $count) {
525
+        $this->theHTTPStatusCodeShouldBe('200');
526
+        $this->theOCSStatusCodeShouldBe('100');
527
+
528
+        $returnedShares = $this->getXmlResponse()->data[0];
529
+
530
+        Assert::assertEquals($count, count($returnedShares->element));
531
+    }
532
+
533
+    /**
534
+     * @Then share :count is returned with
535
+     *
536
+     * @param int $number
537
+     * @param TableNode $body
538
+     */
539
+    public function shareXIsReturnedWith(int $number, TableNode $body) {
540
+        $this->theHTTPStatusCodeShouldBe('200');
541
+        $this->theOCSStatusCodeShouldBe('100');
542
+
543
+        if (!($body instanceof TableNode)) {
544
+            return;
545
+        }
546
+
547
+        $returnedShare = $this->getXmlResponse()->data[0];
548
+        if ($returnedShare->element) {
549
+            $returnedShare = $returnedShare->element[$number];
550
+        }
551
+
552
+        $defaultExpectedFields = [
553
+            'id' => 'A_NUMBER',
554
+            'permissions' => '19',
555
+            'stime' => 'A_NUMBER',
556
+            'parent' => '',
557
+            'expiration' => '',
558
+            'token' => '',
559
+            'storage' => 'A_NUMBER',
560
+            'item_source' => 'A_NUMBER',
561
+            'file_source' => 'A_NUMBER',
562
+            'file_parent' => 'A_NUMBER',
563
+            'mail_send' => '0'
564
+        ];
565
+        $expectedFields = array_merge($defaultExpectedFields, $body->getRowsHash());
566
+
567
+        if (!array_key_exists('uid_file_owner', $expectedFields)
568
+                && array_key_exists('uid_owner', $expectedFields)) {
569
+            $expectedFields['uid_file_owner'] = $expectedFields['uid_owner'];
570
+        }
571
+        if (!array_key_exists('displayname_file_owner', $expectedFields)
572
+                && array_key_exists('displayname_owner', $expectedFields)) {
573
+            $expectedFields['displayname_file_owner'] = $expectedFields['displayname_owner'];
574
+        }
575
+
576
+        if (array_key_exists('share_type', $expectedFields)
577
+                && $expectedFields['share_type'] == 10 /* IShare::TYPE_ROOM */
578
+                && array_key_exists('share_with', $expectedFields)) {
579
+            if ($expectedFields['share_with'] === 'private_conversation') {
580
+                $expectedFields['share_with'] = 'REGEXP /^private_conversation_[0-9a-f]{6}$/';
581
+            } else {
582
+                $expectedFields['share_with'] = FeatureContext::getTokenForIdentifier($expectedFields['share_with']);
583
+            }
584
+        }
585
+
586
+        foreach ($expectedFields as $field => $value) {
587
+            $this->assertFieldIsInReturnedShare($field, $value, $returnedShare);
588
+        }
589
+    }
590
+
591
+    /**
592
+     * @return SimpleXMLElement
593
+     */
594
+    private function getXmlResponse(): \SimpleXMLElement {
595
+        return simplexml_load_string($this->response->getBody());
596
+    }
597
+
598
+    /**
599
+     * @param string $field
600
+     * @param string $contentExpected
601
+     * @param \SimpleXMLElement $returnedShare
602
+     */
603
+    private function assertFieldIsInReturnedShare(string $field, string $contentExpected, \SimpleXMLElement $returnedShare) {
604
+        if ($contentExpected === 'IGNORE') {
605
+            return;
606
+        }
607
+
608
+        if (!property_exists($returnedShare, $field)) {
609
+            Assert::fail("$field was not found in response");
610
+        }
611
+
612
+        if ($field === 'expiration' && !empty($contentExpected)) {
613
+            $contentExpected = date('Y-m-d', strtotime($contentExpected)) . ' 00:00:00';
614
+        }
615
+
616
+        if ($contentExpected === 'A_NUMBER') {
617
+            Assert::assertTrue(is_numeric((string)$returnedShare->$field), "Field '$field' is not a number: " . $returnedShare->$field);
618
+        } elseif ($contentExpected === 'A_TOKEN') {
619
+            // A token is composed by 15 characters from
620
+            // ISecureRandom::CHAR_HUMAN_READABLE.
621
+            Assert::assertRegExp('/^[abcdefgijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXYZ23456789]{15}$/', (string)$returnedShare->$field, "Field '$field' is not a token");
622
+        } elseif (strpos($contentExpected, 'REGEXP ') === 0) {
623
+            Assert::assertRegExp(substr($contentExpected, strlen('REGEXP ')), (string)$returnedShare->$field, "Field '$field' does not match");
624
+        } else {
625
+            Assert::assertEquals($contentExpected, (string)$returnedShare->$field, "Field '$field' does not match");
626
+        }
627
+    }
628
+
629
+    /**
630
+     * @Then As :user remove all shares from the file named :fileName
631
+     */
632
+    public function asRemoveAllSharesFromTheFileNamed($user, $fileName) {
633
+        $url = $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares?format=json";
634
+        $client = new \GuzzleHttp\Client();
635
+        $res = $client->get(
636
+            $url,
637
+            [
638
+                'auth' => [
639
+                    $user,
640
+                    '123456',
641
+                ],
642
+                'headers' => [
643
+                    'Content-Type' => 'application/json',
644
+                    'OCS-APIREQUEST' => 'true',
645
+                ],
646
+            ]
647
+        );
648
+        $json = json_decode($res->getBody()->getContents(), true);
649
+        $deleted = false;
650
+        foreach ($json['ocs']['data'] as $data) {
651
+            if (stripslashes($data['path']) === $fileName) {
652
+                $id = $data['id'];
653
+                $client->delete(
654
+                    $this->baseUrl . "v{$this->apiVersion}.php/apps/files_sharing/api/v{$this->sharingApiVersion}/shares/{$id}",
655
+                    [
656
+                        'auth' => [
657
+                            $user,
658
+                            '123456',
659
+                        ],
660
+                        'headers' => [
661
+                            'Content-Type' => 'application/json',
662
+                            'OCS-APIREQUEST' => 'true',
663
+                        ],
664
+                    ]
665
+                );
666
+                $deleted = true;
667
+            }
668
+        }
669
+
670
+        if ($deleted === false) {
671
+            throw new \Exception("Could not delete file $fileName");
672
+        }
673
+    }
674
+
675
+    /**
676
+     * @When save last share id
677
+     */
678
+    public function saveLastShareId() {
679
+        $this->savedShareId = ($this->lastShareData['data']['id'] ?? null);
680
+    }
681
+
682
+    /**
683
+     * @Then share ids should match
684
+     */
685
+    public function shareIdsShouldMatch() {
686
+        if ($this->savedShareId !== ($this->lastShareData['data']['id'] ?? null)) {
687
+            throw new \Exception('Expected the same link share to be returned');
688
+        }
689
+    }
690
+
691
+    /**
692
+     * @When /^getting sharees for$/
693
+     * @param TableNode $body
694
+     */
695
+    public function whenGettingShareesFor($body) {
696
+        $url = '/apps/files_sharing/api/v1/sharees';
697
+        if ($body instanceof TableNode) {
698
+            $parameters = [];
699
+            foreach ($body->getRowsHash() as $key => $value) {
700
+                $parameters[] = $key . '=' . $value;
701
+            }
702
+            if (!empty($parameters)) {
703
+                $url .= '?' . implode('&', $parameters);
704
+            }
705
+        }
706
+
707
+        $this->sendingTo('GET', $url);
708
+    }
709
+
710
+    /**
711
+     * @Then /^"([^"]*)" sharees returned (are|is empty)$/
712
+     * @param string $shareeType
713
+     * @param string $isEmpty
714
+     * @param TableNode|null $shareesList
715
+     */
716
+    public function thenListOfSharees($shareeType, $isEmpty, $shareesList = null) {
717
+        if ($isEmpty !== 'is empty') {
718
+            $sharees = $shareesList->getRows();
719
+            $respondedArray = $this->getArrayOfShareesResponded($this->response, $shareeType);
720
+            Assert::assertEquals($sharees, $respondedArray);
721
+        } else {
722
+            $respondedArray = $this->getArrayOfShareesResponded($this->response, $shareeType);
723
+            Assert::assertEmpty($respondedArray);
724
+        }
725
+    }
726
+
727
+    public function getArrayOfShareesResponded(ResponseInterface $response, $shareeType) {
728
+        $elements = simplexml_load_string($response->getBody())->data;
729
+        $elements = json_decode(json_encode($elements), 1);
730
+        if (strpos($shareeType, 'exact ') === 0) {
731
+            $elements = $elements['exact'];
732
+            $shareeType = substr($shareeType, 6);
733
+        }
734
+
735
+        $sharees = [];
736
+        foreach ($elements[$shareeType] as $element) {
737
+            $sharees[] = [$element['label'], $element['value']['shareType'], $element['value']['shareWith']];
738
+        }
739
+        return $sharees;
740
+    }
741 741
 }
Please login to merge, or discard this patch.
lib/private/AppFramework/Bootstrap/Coordinator.php 1 patch
Indentation   +149 added lines, -149 removed lines patch added patch discarded remove patch
@@ -28,163 +28,163 @@
 block discarded – undo
28 28
 use function in_array;
29 29
 
30 30
 class Coordinator {
31
-	/** @var RegistrationContext|null */
32
-	private $registrationContext;
33
-
34
-	/** @var array<string,true> */
35
-	private array $bootedApps = [];
36
-
37
-	public function __construct(
38
-		private IServerContainer $serverContainer,
39
-		private Registry $registry,
40
-		private IManager $dashboardManager,
41
-		private IEventDispatcher $eventDispatcher,
42
-		private IEventLogger $eventLogger,
43
-		private IAppManager $appManager,
44
-		private LoggerInterface $logger,
45
-	) {
46
-	}
47
-
48
-	public function runInitialRegistration(): void {
49
-		$apps = OC_App::getEnabledApps();
50
-		if (!empty($apps)) {
51
-			// make sure to also register the core app
52
-			$apps = ['core', ...$apps];
53
-		}
54
-
55
-		$this->registerApps($apps);
56
-	}
57
-
58
-	public function runLazyRegistration(string $appId): void {
59
-		$this->registerApps([$appId]);
60
-	}
61
-
62
-	/**
63
-	 * @param string[] $appIds
64
-	 */
65
-	private function registerApps(array $appIds): void {
66
-		$this->eventLogger->start('bootstrap:register_apps', '');
67
-		if ($this->registrationContext === null) {
68
-			$this->registrationContext = new RegistrationContext($this->logger);
69
-		}
70
-		$apps = [];
71
-		foreach ($appIds as $appId) {
72
-			$this->eventLogger->start("bootstrap:register_app:$appId", "Register $appId");
73
-			$this->eventLogger->start("bootstrap:register_app:$appId:autoloader", "Setup autoloader for $appId");
74
-			/*
31
+    /** @var RegistrationContext|null */
32
+    private $registrationContext;
33
+
34
+    /** @var array<string,true> */
35
+    private array $bootedApps = [];
36
+
37
+    public function __construct(
38
+        private IServerContainer $serverContainer,
39
+        private Registry $registry,
40
+        private IManager $dashboardManager,
41
+        private IEventDispatcher $eventDispatcher,
42
+        private IEventLogger $eventLogger,
43
+        private IAppManager $appManager,
44
+        private LoggerInterface $logger,
45
+    ) {
46
+    }
47
+
48
+    public function runInitialRegistration(): void {
49
+        $apps = OC_App::getEnabledApps();
50
+        if (!empty($apps)) {
51
+            // make sure to also register the core app
52
+            $apps = ['core', ...$apps];
53
+        }
54
+
55
+        $this->registerApps($apps);
56
+    }
57
+
58
+    public function runLazyRegistration(string $appId): void {
59
+        $this->registerApps([$appId]);
60
+    }
61
+
62
+    /**
63
+     * @param string[] $appIds
64
+     */
65
+    private function registerApps(array $appIds): void {
66
+        $this->eventLogger->start('bootstrap:register_apps', '');
67
+        if ($this->registrationContext === null) {
68
+            $this->registrationContext = new RegistrationContext($this->logger);
69
+        }
70
+        $apps = [];
71
+        foreach ($appIds as $appId) {
72
+            $this->eventLogger->start("bootstrap:register_app:$appId", "Register $appId");
73
+            $this->eventLogger->start("bootstrap:register_app:$appId:autoloader", "Setup autoloader for $appId");
74
+            /*
75 75
 			 * First, we have to enable the app's autoloader
76 76
 			 */
77
-			try {
78
-				$path = $this->appManager->getAppPath($appId);
79
-				OC_App::registerAutoloading($appId, $path);
80
-			} catch (AppPathNotFoundException) {
81
-				// Ignore
82
-				continue;
83
-			}
84
-			$this->eventLogger->end("bootstrap:register_app:$appId:autoloader");
85
-
86
-			/*
77
+            try {
78
+                $path = $this->appManager->getAppPath($appId);
79
+                OC_App::registerAutoloading($appId, $path);
80
+            } catch (AppPathNotFoundException) {
81
+                // Ignore
82
+                continue;
83
+            }
84
+            $this->eventLogger->end("bootstrap:register_app:$appId:autoloader");
85
+
86
+            /*
87 87
 			 * Next we check if there is an application class, and it implements
88 88
 			 * the \OCP\AppFramework\Bootstrap\IBootstrap interface
89 89
 			 */
90
-			if ($appId === 'core') {
91
-				$appNameSpace = 'OC\\Core';
92
-			} else {
93
-				$appNameSpace = App::buildAppNamespace($appId);
94
-			}
95
-			$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
96
-
97
-			try {
98
-				if (class_exists($applicationClassName) && is_a($applicationClassName, IBootstrap::class, true)) {
99
-					$this->eventLogger->start("bootstrap:register_app:$appId:application", "Load `Application` instance for $appId");
100
-					try {
101
-						/** @var IBootstrap&App $application */
102
-						$application = $this->serverContainer->query($applicationClassName);
103
-						$apps[$appId] = $application;
104
-					} catch (ContainerExceptionInterface $e) {
105
-						// Weird, but ok
106
-						$this->eventLogger->end("bootstrap:register_app:$appId");
107
-						continue;
108
-					}
109
-					$this->eventLogger->end("bootstrap:register_app:$appId:application");
110
-
111
-					$this->eventLogger->start("bootstrap:register_app:$appId:register", "`Application::register` for $appId");
112
-					$application->register($this->registrationContext->for($appId));
113
-					$this->eventLogger->end("bootstrap:register_app:$appId:register");
114
-				}
115
-			} catch (Throwable $e) {
116
-				$this->logger->emergency('Error during app service registration: ' . $e->getMessage(), [
117
-					'exception' => $e,
118
-					'app' => $appId,
119
-				]);
120
-				$this->eventLogger->end("bootstrap:register_app:$appId");
121
-				continue;
122
-			}
123
-			$this->eventLogger->end("bootstrap:register_app:$appId");
124
-		}
125
-
126
-		$this->eventLogger->start('bootstrap:register_apps:apply', 'Apply all the registered service by apps');
127
-		/**
128
-		 * Now that all register methods have been called, we can delegate the registrations
129
-		 * to the actual services
130
-		 */
131
-		$this->registrationContext->delegateCapabilityRegistrations($apps);
132
-		$this->registrationContext->delegateCrashReporterRegistrations($apps, $this->registry);
133
-		$this->registrationContext->delegateDashboardPanelRegistrations($this->dashboardManager);
134
-		$this->registrationContext->delegateEventListenerRegistrations($this->eventDispatcher);
135
-		$this->registrationContext->delegateContainerRegistrations($apps);
136
-		$this->eventLogger->end('bootstrap:register_apps:apply');
137
-		$this->eventLogger->end('bootstrap:register_apps');
138
-	}
139
-
140
-	public function getRegistrationContext(): ?RegistrationContext {
141
-		return $this->registrationContext;
142
-	}
143
-
144
-	public function bootApp(string $appId): void {
145
-		if (isset($this->bootedApps[$appId])) {
146
-			return;
147
-		}
148
-		$this->bootedApps[$appId] = true;
149
-
150
-		$appNameSpace = App::buildAppNamespace($appId);
151
-		$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
152
-		if (!class_exists($applicationClassName)) {
153
-			// Nothing to boot
154
-			return;
155
-		}
156
-
157
-		/*
90
+            if ($appId === 'core') {
91
+                $appNameSpace = 'OC\\Core';
92
+            } else {
93
+                $appNameSpace = App::buildAppNamespace($appId);
94
+            }
95
+            $applicationClassName = $appNameSpace . '\\AppInfo\\Application';
96
+
97
+            try {
98
+                if (class_exists($applicationClassName) && is_a($applicationClassName, IBootstrap::class, true)) {
99
+                    $this->eventLogger->start("bootstrap:register_app:$appId:application", "Load `Application` instance for $appId");
100
+                    try {
101
+                        /** @var IBootstrap&App $application */
102
+                        $application = $this->serverContainer->query($applicationClassName);
103
+                        $apps[$appId] = $application;
104
+                    } catch (ContainerExceptionInterface $e) {
105
+                        // Weird, but ok
106
+                        $this->eventLogger->end("bootstrap:register_app:$appId");
107
+                        continue;
108
+                    }
109
+                    $this->eventLogger->end("bootstrap:register_app:$appId:application");
110
+
111
+                    $this->eventLogger->start("bootstrap:register_app:$appId:register", "`Application::register` for $appId");
112
+                    $application->register($this->registrationContext->for($appId));
113
+                    $this->eventLogger->end("bootstrap:register_app:$appId:register");
114
+                }
115
+            } catch (Throwable $e) {
116
+                $this->logger->emergency('Error during app service registration: ' . $e->getMessage(), [
117
+                    'exception' => $e,
118
+                    'app' => $appId,
119
+                ]);
120
+                $this->eventLogger->end("bootstrap:register_app:$appId");
121
+                continue;
122
+            }
123
+            $this->eventLogger->end("bootstrap:register_app:$appId");
124
+        }
125
+
126
+        $this->eventLogger->start('bootstrap:register_apps:apply', 'Apply all the registered service by apps');
127
+        /**
128
+         * Now that all register methods have been called, we can delegate the registrations
129
+         * to the actual services
130
+         */
131
+        $this->registrationContext->delegateCapabilityRegistrations($apps);
132
+        $this->registrationContext->delegateCrashReporterRegistrations($apps, $this->registry);
133
+        $this->registrationContext->delegateDashboardPanelRegistrations($this->dashboardManager);
134
+        $this->registrationContext->delegateEventListenerRegistrations($this->eventDispatcher);
135
+        $this->registrationContext->delegateContainerRegistrations($apps);
136
+        $this->eventLogger->end('bootstrap:register_apps:apply');
137
+        $this->eventLogger->end('bootstrap:register_apps');
138
+    }
139
+
140
+    public function getRegistrationContext(): ?RegistrationContext {
141
+        return $this->registrationContext;
142
+    }
143
+
144
+    public function bootApp(string $appId): void {
145
+        if (isset($this->bootedApps[$appId])) {
146
+            return;
147
+        }
148
+        $this->bootedApps[$appId] = true;
149
+
150
+        $appNameSpace = App::buildAppNamespace($appId);
151
+        $applicationClassName = $appNameSpace . '\\AppInfo\\Application';
152
+        if (!class_exists($applicationClassName)) {
153
+            // Nothing to boot
154
+            return;
155
+        }
156
+
157
+        /*
158 158
 		 * Now it is time to fetch an instance of the App class. For classes
159 159
 		 * that implement \OCP\AppFramework\Bootstrap\IBootstrap this means
160 160
 		 * the instance was already created for register, but any other
161 161
 		 * (legacy) code will now do their magic via the constructor.
162 162
 		 */
163
-		$this->eventLogger->start('bootstrap:boot_app:' . $appId, "Call `Application::boot` for $appId");
164
-		try {
165
-			/** @var App $application */
166
-			$application = $this->serverContainer->query($applicationClassName);
167
-			if ($application instanceof IBootstrap) {
168
-				/** @var BootContext $context */
169
-				$context = new BootContext($application->getContainer());
170
-				$application->boot($context);
171
-			}
172
-		} catch (QueryException $e) {
173
-			$this->logger->error("Could not boot $appId: " . $e->getMessage(), [
174
-				'exception' => $e,
175
-			]);
176
-		} catch (Throwable $e) {
177
-			$this->logger->emergency("Could not boot $appId: " . $e->getMessage(), [
178
-				'exception' => $e,
179
-			]);
180
-		}
181
-		$this->eventLogger->end('bootstrap:boot_app:' . $appId);
182
-	}
183
-
184
-	public function isBootable(string $appId) {
185
-		$appNameSpace = App::buildAppNamespace($appId);
186
-		$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
187
-		return class_exists($applicationClassName)
188
-			&& in_array(IBootstrap::class, class_implements($applicationClassName), true);
189
-	}
163
+        $this->eventLogger->start('bootstrap:boot_app:' . $appId, "Call `Application::boot` for $appId");
164
+        try {
165
+            /** @var App $application */
166
+            $application = $this->serverContainer->query($applicationClassName);
167
+            if ($application instanceof IBootstrap) {
168
+                /** @var BootContext $context */
169
+                $context = new BootContext($application->getContainer());
170
+                $application->boot($context);
171
+            }
172
+        } catch (QueryException $e) {
173
+            $this->logger->error("Could not boot $appId: " . $e->getMessage(), [
174
+                'exception' => $e,
175
+            ]);
176
+        } catch (Throwable $e) {
177
+            $this->logger->emergency("Could not boot $appId: " . $e->getMessage(), [
178
+                'exception' => $e,
179
+            ]);
180
+        }
181
+        $this->eventLogger->end('bootstrap:boot_app:' . $appId);
182
+    }
183
+
184
+    public function isBootable(string $appId) {
185
+        $appNameSpace = App::buildAppNamespace($appId);
186
+        $applicationClassName = $appNameSpace . '\\AppInfo\\Application';
187
+        return class_exists($applicationClassName)
188
+            && in_array(IBootstrap::class, class_implements($applicationClassName), true);
189
+    }
190 190
 }
Please login to merge, or discard this patch.
lib/base.php 1 patch
Indentation   +1145 added lines, -1145 removed lines patch added patch discarded remove patch
@@ -40,1151 +40,1151 @@
 block discarded – undo
40 40
  * OC_autoload!
41 41
  */
42 42
 class OC {
43
-	/**
44
-	 * The installation path for Nextcloud  on the server (e.g. /srv/http/nextcloud)
45
-	 */
46
-	public static string $SERVERROOT = '';
47
-	/**
48
-	 * the current request path relative to the Nextcloud root (e.g. files/index.php)
49
-	 */
50
-	private static string $SUBURI = '';
51
-	/**
52
-	 * the Nextcloud root path for http requests (e.g. /nextcloud)
53
-	 */
54
-	public static string $WEBROOT = '';
55
-	/**
56
-	 * The installation path array of the apps folder on the server (e.g. /srv/http/nextcloud) 'path' and
57
-	 * web path in 'url'
58
-	 */
59
-	public static array $APPSROOTS = [];
60
-
61
-	public static string $configDir;
62
-
63
-	/**
64
-	 * requested app
65
-	 */
66
-	public static string $REQUESTEDAPP = '';
67
-
68
-	/**
69
-	 * check if Nextcloud runs in cli mode
70
-	 */
71
-	public static bool $CLI = false;
72
-
73
-	public static \Composer\Autoload\ClassLoader $composerAutoloader;
74
-
75
-	public static \OC\Server $server;
76
-
77
-	private static \OC\Config $config;
78
-
79
-	/**
80
-	 * @throws \RuntimeException when the 3rdparty directory is missing or
81
-	 *                           the app path list is empty or contains an invalid path
82
-	 */
83
-	public static function initPaths(): void {
84
-		if (defined('PHPUNIT_CONFIG_DIR')) {
85
-			self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/';
86
-		} elseif (defined('PHPUNIT_RUN') and PHPUNIT_RUN and is_dir(OC::$SERVERROOT . '/tests/config/')) {
87
-			self::$configDir = OC::$SERVERROOT . '/tests/config/';
88
-		} elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
89
-			self::$configDir = rtrim($dir, '/') . '/';
90
-		} else {
91
-			self::$configDir = OC::$SERVERROOT . '/config/';
92
-		}
93
-		self::$config = new \OC\Config(self::$configDir);
94
-
95
-		OC::$SUBURI = str_replace('\\', '/', substr(realpath($_SERVER['SCRIPT_FILENAME'] ?? ''), strlen(OC::$SERVERROOT)));
96
-		/**
97
-		 * FIXME: The following lines are required because we can't yet instantiate
98
-		 *        Server::get(\OCP\IRequest::class) since \OC::$server does not yet exist.
99
-		 */
100
-		$params = [
101
-			'server' => [
102
-				'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'] ?? null,
103
-				'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'] ?? null,
104
-			],
105
-		];
106
-		if (isset($_SERVER['REMOTE_ADDR'])) {
107
-			$params['server']['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
108
-		}
109
-		$fakeRequest = new \OC\AppFramework\Http\Request(
110
-			$params,
111
-			new \OC\AppFramework\Http\RequestId($_SERVER['UNIQUE_ID'] ?? '', new \OC\Security\SecureRandom()),
112
-			new \OC\AllConfig(new \OC\SystemConfig(self::$config))
113
-		);
114
-		$scriptName = $fakeRequest->getScriptName();
115
-		if (substr($scriptName, -1) == '/') {
116
-			$scriptName .= 'index.php';
117
-			//make sure suburi follows the same rules as scriptName
118
-			if (substr(OC::$SUBURI, -9) != 'index.php') {
119
-				if (substr(OC::$SUBURI, -1) != '/') {
120
-					OC::$SUBURI = OC::$SUBURI . '/';
121
-				}
122
-				OC::$SUBURI = OC::$SUBURI . 'index.php';
123
-			}
124
-		}
125
-
126
-		if (OC::$CLI) {
127
-			OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
128
-		} else {
129
-			if (substr($scriptName, 0 - strlen(OC::$SUBURI)) === OC::$SUBURI) {
130
-				OC::$WEBROOT = substr($scriptName, 0, 0 - strlen(OC::$SUBURI));
131
-
132
-				if (OC::$WEBROOT != '' && OC::$WEBROOT[0] !== '/') {
133
-					OC::$WEBROOT = '/' . OC::$WEBROOT;
134
-				}
135
-			} else {
136
-				// The scriptName is not ending with OC::$SUBURI
137
-				// This most likely means that we are calling from CLI.
138
-				// However some cron jobs still need to generate
139
-				// a web URL, so we use overwritewebroot as a fallback.
140
-				OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
141
-			}
142
-
143
-			// Resolve /nextcloud to /nextcloud/ to ensure to always have a trailing
144
-			// slash which is required by URL generation.
145
-			if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === \OC::$WEBROOT
146
-					&& substr($_SERVER['REQUEST_URI'], -1) !== '/') {
147
-				header('Location: ' . \OC::$WEBROOT . '/');
148
-				exit();
149
-			}
150
-		}
151
-
152
-		// search the apps folder
153
-		$config_paths = self::$config->getValue('apps_paths', []);
154
-		if (!empty($config_paths)) {
155
-			foreach ($config_paths as $paths) {
156
-				if (isset($paths['url']) && isset($paths['path'])) {
157
-					$paths['url'] = rtrim($paths['url'], '/');
158
-					$paths['path'] = rtrim($paths['path'], '/');
159
-					OC::$APPSROOTS[] = $paths;
160
-				}
161
-			}
162
-		} elseif (file_exists(OC::$SERVERROOT . '/apps')) {
163
-			OC::$APPSROOTS[] = ['path' => OC::$SERVERROOT . '/apps', 'url' => '/apps', 'writable' => true];
164
-		}
165
-
166
-		if (empty(OC::$APPSROOTS)) {
167
-			throw new \RuntimeException('apps directory not found! Please put the Nextcloud apps folder in the Nextcloud folder'
168
-				. '. You can also configure the location in the config.php file.');
169
-		}
170
-		$paths = [];
171
-		foreach (OC::$APPSROOTS as $path) {
172
-			$paths[] = $path['path'];
173
-			if (!is_dir($path['path'])) {
174
-				throw new \RuntimeException(sprintf('App directory "%s" not found! Please put the Nextcloud apps folder in the'
175
-					. ' Nextcloud folder. You can also configure the location in the config.php file.', $path['path']));
176
-			}
177
-		}
178
-
179
-		// set the right include path
180
-		set_include_path(
181
-			implode(PATH_SEPARATOR, $paths)
182
-		);
183
-	}
184
-
185
-	public static function checkConfig(): void {
186
-		// Create config if it does not already exist
187
-		$configFilePath = self::$configDir . '/config.php';
188
-		if (!file_exists($configFilePath)) {
189
-			@touch($configFilePath);
190
-		}
191
-
192
-		// Check if config is writable
193
-		$configFileWritable = is_writable($configFilePath);
194
-		$configReadOnly = Server::get(IConfig::class)->getSystemValueBool('config_is_read_only');
195
-		if (!$configFileWritable && !$configReadOnly
196
-			|| !$configFileWritable && \OCP\Util::needUpgrade()) {
197
-			$urlGenerator = Server::get(IURLGenerator::class);
198
-			$l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
199
-
200
-			if (self::$CLI) {
201
-				echo $l->t('Cannot write into "config" directory!') . "\n";
202
-				echo $l->t('This can usually be fixed by giving the web server write access to the config directory.') . "\n";
203
-				echo "\n";
204
-				echo $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . "\n";
205
-				echo $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]) . "\n";
206
-				exit;
207
-			} else {
208
-				Server::get(ITemplateManager::class)->printErrorPage(
209
-					$l->t('Cannot write into "config" directory!'),
210
-					$l->t('This can usually be fixed by giving the web server write access to the config directory.') . ' '
211
-					. $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . ' '
212
-					. $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]),
213
-					503
214
-				);
215
-			}
216
-		}
217
-	}
218
-
219
-	public static function checkInstalled(\OC\SystemConfig $systemConfig): void {
220
-		if (defined('OC_CONSOLE')) {
221
-			return;
222
-		}
223
-		// Redirect to installer if not installed
224
-		if (!$systemConfig->getValue('installed', false) && OC::$SUBURI !== '/index.php' && OC::$SUBURI !== '/status.php') {
225
-			if (OC::$CLI) {
226
-				throw new Exception('Not installed');
227
-			} else {
228
-				$url = OC::$WEBROOT . '/index.php';
229
-				header('Location: ' . $url);
230
-			}
231
-			exit();
232
-		}
233
-	}
234
-
235
-	public static function checkMaintenanceMode(\OC\SystemConfig $systemConfig): void {
236
-		// Allow ajax update script to execute without being stopped
237
-		if (((bool)$systemConfig->getValue('maintenance', false)) && OC::$SUBURI != '/core/ajax/update.php') {
238
-			// send http status 503
239
-			http_response_code(503);
240
-			header('X-Nextcloud-Maintenance-Mode: 1');
241
-			header('Retry-After: 120');
242
-
243
-			// render error page
244
-			$template = Server::get(ITemplateManager::class)->getTemplate('', 'update.user', 'guest');
245
-			\OCP\Util::addScript('core', 'maintenance');
246
-			\OCP\Util::addStyle('core', 'guest');
247
-			$template->printPage();
248
-			die();
249
-		}
250
-	}
251
-
252
-	/**
253
-	 * Prints the upgrade page
254
-	 */
255
-	private static function printUpgradePage(\OC\SystemConfig $systemConfig): void {
256
-		$cliUpgradeLink = $systemConfig->getValue('upgrade.cli-upgrade-link', '');
257
-		$disableWebUpdater = $systemConfig->getValue('upgrade.disable-web', false);
258
-		$tooBig = false;
259
-		if (!$disableWebUpdater) {
260
-			$apps = Server::get(\OCP\App\IAppManager::class);
261
-			if ($apps->isEnabledForAnyone('user_ldap')) {
262
-				$qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
263
-
264
-				$result = $qb->select($qb->func()->count('*', 'user_count'))
265
-					->from('ldap_user_mapping')
266
-					->executeQuery();
267
-				$row = $result->fetch();
268
-				$result->closeCursor();
269
-
270
-				$tooBig = ($row['user_count'] > 50);
271
-			}
272
-			if (!$tooBig && $apps->isEnabledForAnyone('user_saml')) {
273
-				$qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
274
-
275
-				$result = $qb->select($qb->func()->count('*', 'user_count'))
276
-					->from('user_saml_users')
277
-					->executeQuery();
278
-				$row = $result->fetch();
279
-				$result->closeCursor();
280
-
281
-				$tooBig = ($row['user_count'] > 50);
282
-			}
283
-			if (!$tooBig) {
284
-				// count users
285
-				$totalUsers = Server::get(\OCP\IUserManager::class)->countUsersTotal(51);
286
-				$tooBig = ($totalUsers > 50);
287
-			}
288
-		}
289
-		$ignoreTooBigWarning = isset($_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'])
290
-			&& $_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'] === 'IAmSuperSureToDoThis';
291
-
292
-		if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) {
293
-			// send http status 503
294
-			http_response_code(503);
295
-			header('Retry-After: 120');
296
-
297
-			$serverVersion = \OCP\Server::get(\OCP\ServerVersion::class);
298
-
299
-			// render error page
300
-			$template = Server::get(ITemplateManager::class)->getTemplate('', 'update.use-cli', 'guest');
301
-			$template->assign('productName', 'nextcloud'); // for now
302
-			$template->assign('version', $serverVersion->getVersionString());
303
-			$template->assign('tooBig', $tooBig);
304
-			$template->assign('cliUpgradeLink', $cliUpgradeLink);
305
-
306
-			$template->printPage();
307
-			die();
308
-		}
309
-
310
-		// check whether this is a core update or apps update
311
-		$installedVersion = $systemConfig->getValue('version', '0.0.0');
312
-		$currentVersion = implode('.', \OCP\Util::getVersion());
313
-
314
-		// if not a core upgrade, then it's apps upgrade
315
-		$isAppsOnlyUpgrade = version_compare($currentVersion, $installedVersion, '=');
316
-
317
-		$oldTheme = $systemConfig->getValue('theme');
318
-		$systemConfig->setValue('theme', '');
319
-		\OCP\Util::addScript('core', 'common');
320
-		\OCP\Util::addScript('core', 'main');
321
-		\OCP\Util::addTranslations('core');
322
-		\OCP\Util::addScript('core', 'update');
323
-
324
-		/** @var \OC\App\AppManager $appManager */
325
-		$appManager = Server::get(\OCP\App\IAppManager::class);
326
-
327
-		$tmpl = Server::get(ITemplateManager::class)->getTemplate('', 'update.admin', 'guest');
328
-		$tmpl->assign('version', \OCP\Server::get(\OCP\ServerVersion::class)->getVersionString());
329
-		$tmpl->assign('isAppsOnlyUpgrade', $isAppsOnlyUpgrade);
330
-
331
-		// get third party apps
332
-		$ocVersion = \OCP\Util::getVersion();
333
-		$ocVersion = implode('.', $ocVersion);
334
-		$incompatibleApps = $appManager->getIncompatibleApps($ocVersion);
335
-		$incompatibleOverwrites = $systemConfig->getValue('app_install_overwrite', []);
336
-		$incompatibleShippedApps = [];
337
-		$incompatibleDisabledApps = [];
338
-		foreach ($incompatibleApps as $appInfo) {
339
-			if ($appManager->isShipped($appInfo['id'])) {
340
-				$incompatibleShippedApps[] = $appInfo['name'] . ' (' . $appInfo['id'] . ')';
341
-			}
342
-			if (!in_array($appInfo['id'], $incompatibleOverwrites)) {
343
-				$incompatibleDisabledApps[] = $appInfo;
344
-			}
345
-		}
346
-
347
-		if (!empty($incompatibleShippedApps)) {
348
-			$l = Server::get(\OCP\L10N\IFactory::class)->get('core');
349
-			$hint = $l->t('Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory.', [implode(', ', $incompatibleShippedApps)]);
350
-			throw new \OCP\HintException('Application ' . implode(', ', $incompatibleShippedApps) . ' is not present or has a non-compatible version with this server. Please check the apps directory.', $hint);
351
-		}
352
-
353
-		$tmpl->assign('appsToUpgrade', $appManager->getAppsNeedingUpgrade($ocVersion));
354
-		$tmpl->assign('incompatibleAppsList', $incompatibleDisabledApps);
355
-		try {
356
-			$defaults = new \OC_Defaults();
357
-			$tmpl->assign('productName', $defaults->getName());
358
-		} catch (Throwable $error) {
359
-			$tmpl->assign('productName', 'Nextcloud');
360
-		}
361
-		$tmpl->assign('oldTheme', $oldTheme);
362
-		$tmpl->printPage();
363
-	}
364
-
365
-	public static function initSession(): void {
366
-		$request = Server::get(IRequest::class);
367
-
368
-		// TODO: Temporary disabled again to solve issues with CalDAV/CardDAV clients like DAVx5 that use cookies
369
-		// TODO: See https://github.com/nextcloud/server/issues/37277#issuecomment-1476366147 and the other comments
370
-		// TODO: for further information.
371
-		// $isDavRequest = strpos($request->getRequestUri(), '/remote.php/dav') === 0 || strpos($request->getRequestUri(), '/remote.php/webdav') === 0;
372
-		// if ($request->getHeader('Authorization') !== '' && is_null($request->getCookie('cookie_test')) && $isDavRequest && !isset($_COOKIE['nc_session_id'])) {
373
-		// setcookie('cookie_test', 'test', time() + 3600);
374
-		// // Do not initialize the session if a request is authenticated directly
375
-		// // unless there is a session cookie already sent along
376
-		// return;
377
-		// }
378
-
379
-		if ($request->getServerProtocol() === 'https') {
380
-			ini_set('session.cookie_secure', 'true');
381
-		}
382
-
383
-		// prevents javascript from accessing php session cookies
384
-		ini_set('session.cookie_httponly', 'true');
385
-
386
-		// Do not initialize sessions for 'status.php' requests
387
-		// Monitoring endpoints can quickly flood session handlers
388
-		// and 'status.php' doesn't require sessions anyway
389
-		if (str_ends_with($request->getScriptName(), '/status.php')) {
390
-			return;
391
-		}
392
-
393
-		// set the cookie path to the Nextcloud directory
394
-		$cookie_path = OC::$WEBROOT ? : '/';
395
-		ini_set('session.cookie_path', $cookie_path);
396
-
397
-		// set the cookie domain to the Nextcloud domain
398
-		$cookie_domain = self::$config->getValue('cookie_domain', '');
399
-		if ($cookie_domain) {
400
-			ini_set('session.cookie_domain', $cookie_domain);
401
-		}
402
-
403
-		// Let the session name be changed in the initSession Hook
404
-		$sessionName = OC_Util::getInstanceId();
405
-
406
-		try {
407
-			$logger = null;
408
-			if (Server::get(\OC\SystemConfig::class)->getValue('installed', false)) {
409
-				$logger = logger('core');
410
-			}
411
-
412
-			// set the session name to the instance id - which is unique
413
-			$session = new \OC\Session\Internal(
414
-				$sessionName,
415
-				$logger,
416
-			);
417
-
418
-			$cryptoWrapper = Server::get(\OC\Session\CryptoWrapper::class);
419
-			$session = $cryptoWrapper->wrapSession($session);
420
-			self::$server->setSession($session);
421
-
422
-			// if session can't be started break with http 500 error
423
-		} catch (Exception $e) {
424
-			Server::get(LoggerInterface::class)->error($e->getMessage(), ['app' => 'base','exception' => $e]);
425
-			//show the user a detailed error page
426
-			Server::get(ITemplateManager::class)->printExceptionErrorPage($e, 500);
427
-			die();
428
-		}
429
-
430
-		//try to set the session lifetime
431
-		$sessionLifeTime = self::getSessionLifeTime();
432
-
433
-		// session timeout
434
-		if ($session->exists('LAST_ACTIVITY') && (time() - $session->get('LAST_ACTIVITY') > $sessionLifeTime)) {
435
-			if (isset($_COOKIE[session_name()])) {
436
-				setcookie(session_name(), '', -1, self::$WEBROOT ? : '/');
437
-			}
438
-			Server::get(IUserSession::class)->logout();
439
-		}
440
-
441
-		if (!self::hasSessionRelaxedExpiry()) {
442
-			$session->set('LAST_ACTIVITY', time());
443
-		}
444
-		$session->close();
445
-	}
446
-
447
-	private static function getSessionLifeTime(): int {
448
-		return Server::get(\OC\AllConfig::class)->getSystemValueInt('session_lifetime', 60 * 60 * 24);
449
-	}
450
-
451
-	/**
452
-	 * @return bool true if the session expiry should only be done by gc instead of an explicit timeout
453
-	 */
454
-	public static function hasSessionRelaxedExpiry(): bool {
455
-		return Server::get(\OC\AllConfig::class)->getSystemValueBool('session_relaxed_expiry', false);
456
-	}
457
-
458
-	/**
459
-	 * Try to set some values to the required Nextcloud default
460
-	 */
461
-	public static function setRequiredIniValues(): void {
462
-		// Don't display errors and log them
463
-		@ini_set('display_errors', '0');
464
-		@ini_set('log_errors', '1');
465
-
466
-		// Try to configure php to enable big file uploads.
467
-		// This doesn't work always depending on the webserver and php configuration.
468
-		// Let's try to overwrite some defaults if they are smaller than 1 hour
469
-
470
-		if (intval(@ini_get('max_execution_time') ?: 0) < 3600) {
471
-			@ini_set('max_execution_time', strval(3600));
472
-		}
473
-
474
-		if (intval(@ini_get('max_input_time') ?: 0) < 3600) {
475
-			@ini_set('max_input_time', strval(3600));
476
-		}
477
-
478
-		// Try to set the maximum execution time to the largest time limit we have
479
-		if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
480
-			@set_time_limit(max(intval(@ini_get('max_execution_time')), intval(@ini_get('max_input_time'))));
481
-		}
482
-
483
-		@ini_set('default_charset', 'UTF-8');
484
-		@ini_set('gd.jpeg_ignore_warning', '1');
485
-	}
486
-
487
-	/**
488
-	 * Send the same site cookies
489
-	 */
490
-	private static function sendSameSiteCookies(): void {
491
-		$cookieParams = session_get_cookie_params();
492
-		$secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : '';
493
-		$policies = [
494
-			'lax',
495
-			'strict',
496
-		];
497
-
498
-		// Append __Host to the cookie if it meets the requirements
499
-		$cookiePrefix = '';
500
-		if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
501
-			$cookiePrefix = '__Host-';
502
-		}
503
-
504
-		foreach ($policies as $policy) {
505
-			header(
506
-				sprintf(
507
-					'Set-Cookie: %snc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s',
508
-					$cookiePrefix,
509
-					$policy,
510
-					$cookieParams['path'],
511
-					$policy
512
-				),
513
-				false
514
-			);
515
-		}
516
-	}
517
-
518
-	/**
519
-	 * Same Site cookie to further mitigate CSRF attacks. This cookie has to
520
-	 * be set in every request if cookies are sent to add a second level of
521
-	 * defense against CSRF.
522
-	 *
523
-	 * If the cookie is not sent this will set the cookie and reload the page.
524
-	 * We use an additional cookie since we want to protect logout CSRF and
525
-	 * also we can't directly interfere with PHP's session mechanism.
526
-	 */
527
-	private static function performSameSiteCookieProtection(IConfig $config): void {
528
-		$request = Server::get(IRequest::class);
529
-
530
-		// Some user agents are notorious and don't really properly follow HTTP
531
-		// specifications. For those, have an automated opt-out. Since the protection
532
-		// for remote.php is applied in base.php as starting point we need to opt out
533
-		// here.
534
-		$incompatibleUserAgents = $config->getSystemValue('csrf.optout');
535
-
536
-		// Fallback, if csrf.optout is unset
537
-		if (!is_array($incompatibleUserAgents)) {
538
-			$incompatibleUserAgents = [
539
-				// OS X Finder
540
-				'/^WebDAVFS/',
541
-				// Windows webdav drive
542
-				'/^Microsoft-WebDAV-MiniRedir/',
543
-			];
544
-		}
545
-
546
-		if ($request->isUserAgent($incompatibleUserAgents)) {
547
-			return;
548
-		}
549
-
550
-		if (count($_COOKIE) > 0) {
551
-			$requestUri = $request->getScriptName();
552
-			$processingScript = explode('/', $requestUri);
553
-			$processingScript = $processingScript[count($processingScript) - 1];
554
-
555
-			if ($processingScript === 'index.php' // index.php routes are handled in the middleware
556
-				|| $processingScript === 'cron.php' // and cron.php does not need any authentication at all
557
-				|| $processingScript === 'public.php' // For public.php, auth for password protected shares is done in the PublicAuth plugin
558
-			) {
559
-				return;
560
-			}
561
-
562
-			// All other endpoints require the lax and the strict cookie
563
-			if (!$request->passesStrictCookieCheck()) {
564
-				logger('core')->warning('Request does not pass strict cookie check');
565
-				self::sendSameSiteCookies();
566
-				// Debug mode gets access to the resources without strict cookie
567
-				// due to the fact that the SabreDAV browser also lives there.
568
-				if (!$config->getSystemValueBool('debug', false)) {
569
-					http_response_code(\OCP\AppFramework\Http::STATUS_PRECONDITION_FAILED);
570
-					header('Content-Type: application/json');
571
-					echo json_encode(['error' => 'Strict Cookie has not been found in request']);
572
-					exit();
573
-				}
574
-			}
575
-		} elseif (!isset($_COOKIE['nc_sameSiteCookielax']) || !isset($_COOKIE['nc_sameSiteCookiestrict'])) {
576
-			self::sendSameSiteCookies();
577
-		}
578
-	}
579
-
580
-	public static function init(): void {
581
-		// First handle PHP configuration and copy auth headers to the expected
582
-		// $_SERVER variable before doing anything Server object related
583
-		self::setRequiredIniValues();
584
-		self::handleAuthHeaders();
585
-
586
-		// prevent any XML processing from loading external entities
587
-		libxml_set_external_entity_loader(static function () {
588
-			return null;
589
-		});
590
-
591
-		// Set default timezone before the Server object is booted
592
-		if (!date_default_timezone_set('UTC')) {
593
-			throw new \RuntimeException('Could not set timezone to UTC');
594
-		}
595
-
596
-		// calculate the root directories
597
-		OC::$SERVERROOT = str_replace('\\', '/', substr(__DIR__, 0, -4));
598
-
599
-		// register autoloader
600
-		$loaderStart = microtime(true);
601
-
602
-		self::$CLI = (php_sapi_name() == 'cli');
603
-
604
-		// Add default composer PSR-4 autoloader, ensure apcu to be disabled
605
-		self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php';
606
-		self::$composerAutoloader->setApcuPrefix(null);
607
-
608
-
609
-		try {
610
-			self::initPaths();
611
-			// setup 3rdparty autoloader
612
-			$vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php';
613
-			if (!file_exists($vendorAutoLoad)) {
614
-				throw new \RuntimeException('Composer autoloader not found, unable to continue. Check the folder "3rdparty". Running "git submodule update --init" will initialize the git submodule that handles the subfolder "3rdparty".');
615
-			}
616
-			require_once $vendorAutoLoad;
617
-		} catch (\RuntimeException $e) {
618
-			if (!self::$CLI) {
619
-				http_response_code(503);
620
-			}
621
-			// we can't use the template error page here, because this needs the
622
-			// DI container which isn't available yet
623
-			print($e->getMessage());
624
-			exit();
625
-		}
626
-		$loaderEnd = microtime(true);
627
-
628
-		// Enable lazy loading if activated
629
-		\OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true);
630
-
631
-		// setup the basic server
632
-		self::$server = new \OC\Server(\OC::$WEBROOT, self::$config);
633
-		self::$server->boot();
634
-
635
-		try {
636
-			$profiler = new BuiltInProfiler(
637
-				Server::get(IConfig::class),
638
-				Server::get(IRequest::class),
639
-			);
640
-			$profiler->start();
641
-		} catch (\Throwable $e) {
642
-			logger('core')->error('Failed to start profiler: ' . $e->getMessage(), ['app' => 'base']);
643
-		}
644
-
645
-		if (self::$CLI && in_array('--' . \OCP\Console\ReservedOptions::DEBUG_LOG, $_SERVER['argv'])) {
646
-			\OC\Core\Listener\BeforeMessageLoggedEventListener::setup();
647
-		}
648
-
649
-		$eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class);
650
-		$eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd);
651
-		$eventLogger->start('boot', 'Initialize');
652
-
653
-		// Override php.ini and log everything if we're troubleshooting
654
-		if (self::$config->getValue('loglevel') === ILogger::DEBUG) {
655
-			error_reporting(E_ALL);
656
-		}
657
-
658
-		// initialize intl fallback if necessary
659
-		OC_Util::isSetLocaleWorking();
660
-
661
-		$config = Server::get(IConfig::class);
662
-		if (!defined('PHPUNIT_RUN')) {
663
-			$errorHandler = new OC\Log\ErrorHandler(
664
-				\OCP\Server::get(\Psr\Log\LoggerInterface::class),
665
-			);
666
-			$exceptionHandler = [$errorHandler, 'onException'];
667
-			if ($config->getSystemValueBool('debug', false)) {
668
-				set_error_handler([$errorHandler, 'onAll'], E_ALL);
669
-				if (\OC::$CLI) {
670
-					$exceptionHandler = [Server::get(ITemplateManager::class), 'printExceptionErrorPage'];
671
-				}
672
-			} else {
673
-				set_error_handler([$errorHandler, 'onError']);
674
-			}
675
-			register_shutdown_function([$errorHandler, 'onShutdown']);
676
-			set_exception_handler($exceptionHandler);
677
-		}
678
-
679
-		/** @var \OC\AppFramework\Bootstrap\Coordinator $bootstrapCoordinator */
680
-		$bootstrapCoordinator = Server::get(\OC\AppFramework\Bootstrap\Coordinator::class);
681
-		$bootstrapCoordinator->runInitialRegistration();
682
-
683
-		$eventLogger->start('init_session', 'Initialize session');
684
-
685
-		// Check for PHP SimpleXML extension earlier since we need it before our other checks and want to provide a useful hint for web users
686
-		// see https://github.com/nextcloud/server/pull/2619
687
-		if (!function_exists('simplexml_load_file')) {
688
-			throw new \OCP\HintException('The PHP SimpleXML/PHP-XML extension is not installed.', 'Install the extension or make sure it is enabled.');
689
-		}
690
-
691
-		$systemConfig = Server::get(\OC\SystemConfig::class);
692
-		$appManager = Server::get(\OCP\App\IAppManager::class);
693
-		if ($systemConfig->getValue('installed', false)) {
694
-			$appManager->loadApps(['session']);
695
-		}
696
-		if (!self::$CLI) {
697
-			self::initSession();
698
-		}
699
-		$eventLogger->end('init_session');
700
-		self::checkConfig();
701
-		self::checkInstalled($systemConfig);
702
-
703
-		OC_Response::addSecurityHeaders();
704
-
705
-		self::performSameSiteCookieProtection($config);
706
-
707
-		if (!defined('OC_CONSOLE')) {
708
-			$eventLogger->start('check_server', 'Run a few configuration checks');
709
-			$errors = OC_Util::checkServer($systemConfig);
710
-			if (count($errors) > 0) {
711
-				if (!self::$CLI) {
712
-					http_response_code(503);
713
-					Util::addStyle('guest');
714
-					try {
715
-						Server::get(ITemplateManager::class)->printGuestPage('', 'error', ['errors' => $errors]);
716
-						exit;
717
-					} catch (\Exception $e) {
718
-						// In case any error happens when showing the error page, we simply fall back to posting the text.
719
-						// This might be the case when e.g. the data directory is broken and we can not load/write SCSS to/from it.
720
-					}
721
-				}
722
-
723
-				// Convert l10n string into regular string for usage in database
724
-				$staticErrors = [];
725
-				foreach ($errors as $error) {
726
-					echo $error['error'] . "\n";
727
-					echo $error['hint'] . "\n\n";
728
-					$staticErrors[] = [
729
-						'error' => (string)$error['error'],
730
-						'hint' => (string)$error['hint'],
731
-					];
732
-				}
733
-
734
-				try {
735
-					$config->setAppValue('core', 'cronErrors', json_encode($staticErrors));
736
-				} catch (\Exception $e) {
737
-					echo('Writing to database failed');
738
-				}
739
-				exit(1);
740
-			} elseif (self::$CLI && $config->getSystemValueBool('installed', false)) {
741
-				$config->deleteAppValue('core', 'cronErrors');
742
-			}
743
-			$eventLogger->end('check_server');
744
-		}
745
-
746
-		// User and Groups
747
-		if (!$systemConfig->getValue('installed', false)) {
748
-			self::$server->getSession()->set('user_id', '');
749
-		}
750
-
751
-		$eventLogger->start('setup_backends', 'Setup group and user backends');
752
-		Server::get(\OCP\IUserManager::class)->registerBackend(new \OC\User\Database());
753
-		Server::get(\OCP\IGroupManager::class)->addBackend(new \OC\Group\Database());
754
-
755
-		// Subscribe to the hook
756
-		\OCP\Util::connectHook(
757
-			'\OCA\Files_Sharing\API\Server2Server',
758
-			'preLoginNameUsedAsUserName',
759
-			'\OC\User\Database',
760
-			'preLoginNameUsedAsUserName'
761
-		);
762
-
763
-		//setup extra user backends
764
-		if (!\OCP\Util::needUpgrade()) {
765
-			OC_User::setupBackends();
766
-		} else {
767
-			// Run upgrades in incognito mode
768
-			OC_User::setIncognitoMode(true);
769
-		}
770
-		$eventLogger->end('setup_backends');
771
-
772
-		self::registerCleanupHooks($systemConfig);
773
-		self::registerShareHooks($systemConfig);
774
-		self::registerEncryptionWrapperAndHooks();
775
-		self::registerAccountHooks();
776
-		self::registerResourceCollectionHooks();
777
-		self::registerFileReferenceEventListener();
778
-		self::registerRenderReferenceEventListener();
779
-		self::registerAppRestrictionsHooks();
780
-
781
-		// Make sure that the application class is not loaded before the database is setup
782
-		if ($systemConfig->getValue('installed', false)) {
783
-			$appManager->loadApp('settings');
784
-		}
785
-
786
-		//make sure temporary files are cleaned up
787
-		$tmpManager = Server::get(\OCP\ITempManager::class);
788
-		register_shutdown_function([$tmpManager, 'clean']);
789
-		$lockProvider = Server::get(\OCP\Lock\ILockingProvider::class);
790
-		register_shutdown_function([$lockProvider, 'releaseAll']);
791
-
792
-		// Check whether the sample configuration has been copied
793
-		if ($systemConfig->getValue('copied_sample_config', false)) {
794
-			$l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
795
-			Server::get(ITemplateManager::class)->printErrorPage(
796
-				$l->t('Sample configuration detected'),
797
-				$l->t('It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php'),
798
-				503
799
-			);
800
-			return;
801
-		}
802
-
803
-		$request = Server::get(IRequest::class);
804
-		$host = $request->getInsecureServerHost();
805
-		/**
806
-		 * if the host passed in headers isn't trusted
807
-		 * FIXME: Should not be in here at all :see_no_evil:
808
-		 */
809
-		if (!OC::$CLI
810
-			&& !Server::get(\OC\Security\TrustedDomainHelper::class)->isTrustedDomain($host)
811
-			&& $config->getSystemValueBool('installed', false)
812
-		) {
813
-			// Allow access to CSS resources
814
-			$isScssRequest = false;
815
-			if (strpos($request->getPathInfo() ?: '', '/css/') === 0) {
816
-				$isScssRequest = true;
817
-			}
818
-
819
-			if (substr($request->getRequestUri(), -11) === '/status.php') {
820
-				http_response_code(400);
821
-				header('Content-Type: application/json');
822
-				echo '{"error": "Trusted domain error.", "code": 15}';
823
-				exit();
824
-			}
825
-
826
-			if (!$isScssRequest) {
827
-				http_response_code(400);
828
-				Server::get(LoggerInterface::class)->info(
829
-					'Trusted domain error. "{remoteAddress}" tried to access using "{host}" as host.',
830
-					[
831
-						'app' => 'core',
832
-						'remoteAddress' => $request->getRemoteAddress(),
833
-						'host' => $host,
834
-					]
835
-				);
836
-
837
-				$tmpl = Server::get(ITemplateManager::class)->getTemplate('core', 'untrustedDomain', 'guest');
838
-				$tmpl->assign('docUrl', Server::get(IURLGenerator::class)->linkToDocs('admin-trusted-domains'));
839
-				$tmpl->printPage();
840
-
841
-				exit();
842
-			}
843
-		}
844
-		$eventLogger->end('boot');
845
-		$eventLogger->log('init', 'OC::init', $loaderStart, microtime(true));
846
-		$eventLogger->start('runtime', 'Runtime');
847
-		$eventLogger->start('request', 'Full request after boot');
848
-		register_shutdown_function(function () use ($eventLogger) {
849
-			$eventLogger->end('request');
850
-		});
851
-
852
-		register_shutdown_function(function () {
853
-			$memoryPeak = memory_get_peak_usage();
854
-			$logLevel = match (true) {
855
-				$memoryPeak > 500_000_000 => ILogger::FATAL,
856
-				$memoryPeak > 400_000_000 => ILogger::ERROR,
857
-				$memoryPeak > 300_000_000 => ILogger::WARN,
858
-				default => null,
859
-			};
860
-			if ($logLevel !== null) {
861
-				$message = 'Request used more than 300 MB of RAM: ' . Util::humanFileSize($memoryPeak);
862
-				$logger = Server::get(LoggerInterface::class);
863
-				$logger->log($logLevel, $message, ['app' => 'core']);
864
-			}
865
-		});
866
-	}
867
-
868
-	/**
869
-	 * register hooks for the cleanup of cache and bruteforce protection
870
-	 */
871
-	public static function registerCleanupHooks(\OC\SystemConfig $systemConfig): void {
872
-		//don't try to do this before we are properly setup
873
-		if ($systemConfig->getValue('installed', false) && !\OCP\Util::needUpgrade()) {
874
-			// NOTE: This will be replaced to use OCP
875
-			$userSession = Server::get(\OC\User\Session::class);
876
-			$userSession->listen('\OC\User', 'postLogin', function () use ($userSession) {
877
-				if (!defined('PHPUNIT_RUN') && $userSession->isLoggedIn()) {
878
-					// reset brute force delay for this IP address and username
879
-					$uid = $userSession->getUser()->getUID();
880
-					$request = Server::get(IRequest::class);
881
-					$throttler = Server::get(IThrottler::class);
882
-					$throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]);
883
-				}
884
-
885
-				try {
886
-					$cache = new \OC\Cache\File();
887
-					$cache->gc();
888
-				} catch (\OC\ServerNotAvailableException $e) {
889
-					// not a GC exception, pass it on
890
-					throw $e;
891
-				} catch (\OC\ForbiddenException $e) {
892
-					// filesystem blocked for this request, ignore
893
-				} catch (\Exception $e) {
894
-					// a GC exception should not prevent users from using OC,
895
-					// so log the exception
896
-					Server::get(LoggerInterface::class)->warning('Exception when running cache gc.', [
897
-						'app' => 'core',
898
-						'exception' => $e,
899
-					]);
900
-				}
901
-			});
902
-		}
903
-	}
904
-
905
-	private static function registerEncryptionWrapperAndHooks(): void {
906
-		/** @var \OC\Encryption\Manager */
907
-		$manager = Server::get(\OCP\Encryption\IManager::class);
908
-		Server::get(IEventDispatcher::class)->addListener(
909
-			BeforeFileSystemSetupEvent::class,
910
-			$manager->setupStorage(...),
911
-		);
912
-
913
-		$enabled = $manager->isEnabled();
914
-		if ($enabled) {
915
-			\OC\Encryption\EncryptionEventListener::register(Server::get(IEventDispatcher::class));
916
-		}
917
-	}
918
-
919
-	private static function registerAccountHooks(): void {
920
-		/** @var IEventDispatcher $dispatcher */
921
-		$dispatcher = Server::get(IEventDispatcher::class);
922
-		$dispatcher->addServiceListener(UserChangedEvent::class, \OC\Accounts\Hooks::class);
923
-	}
924
-
925
-	private static function registerAppRestrictionsHooks(): void {
926
-		/** @var \OC\Group\Manager $groupManager */
927
-		$groupManager = Server::get(\OCP\IGroupManager::class);
928
-		$groupManager->listen('\OC\Group', 'postDelete', function (\OCP\IGroup $group) {
929
-			$appManager = Server::get(\OCP\App\IAppManager::class);
930
-			$apps = $appManager->getEnabledAppsForGroup($group);
931
-			foreach ($apps as $appId) {
932
-				$restrictions = $appManager->getAppRestriction($appId);
933
-				if (empty($restrictions)) {
934
-					continue;
935
-				}
936
-				$key = array_search($group->getGID(), $restrictions);
937
-				unset($restrictions[$key]);
938
-				$restrictions = array_values($restrictions);
939
-				if (empty($restrictions)) {
940
-					$appManager->disableApp($appId);
941
-				} else {
942
-					$appManager->enableAppForGroups($appId, $restrictions);
943
-				}
944
-			}
945
-		});
946
-	}
947
-
948
-	private static function registerResourceCollectionHooks(): void {
949
-		\OC\Collaboration\Resources\Listener::register(Server::get(IEventDispatcher::class));
950
-	}
951
-
952
-	private static function registerFileReferenceEventListener(): void {
953
-		\OC\Collaboration\Reference\File\FileReferenceEventListener::register(Server::get(IEventDispatcher::class));
954
-	}
955
-
956
-	private static function registerRenderReferenceEventListener() {
957
-		\OC\Collaboration\Reference\RenderReferenceEventListener::register(Server::get(IEventDispatcher::class));
958
-	}
959
-
960
-	/**
961
-	 * register hooks for sharing
962
-	 */
963
-	public static function registerShareHooks(\OC\SystemConfig $systemConfig): void {
964
-		if ($systemConfig->getValue('installed')) {
965
-
966
-			$dispatcher = Server::get(IEventDispatcher::class);
967
-			$dispatcher->addServiceListener(UserRemovedEvent::class, UserRemovedListener::class);
968
-			$dispatcher->addServiceListener(GroupDeletedEvent::class, GroupDeletedListener::class);
969
-			$dispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedListener::class);
970
-		}
971
-	}
972
-
973
-	/**
974
-	 * Handle the request
975
-	 */
976
-	public static function handleRequest(): void {
977
-		Server::get(\OCP\Diagnostics\IEventLogger::class)->start('handle_request', 'Handle request');
978
-		$systemConfig = Server::get(\OC\SystemConfig::class);
979
-
980
-		// Check if Nextcloud is installed or in maintenance (update) mode
981
-		if (!$systemConfig->getValue('installed', false)) {
982
-			\OC::$server->getSession()->clear();
983
-			$controller = Server::get(\OC\Core\Controller\SetupController::class);
984
-			$controller->run($_POST);
985
-			exit();
986
-		}
987
-
988
-		$request = Server::get(IRequest::class);
989
-		$request->throwDecodingExceptionIfAny();
990
-		$requestPath = $request->getRawPathInfo();
991
-		if ($requestPath === '/heartbeat') {
992
-			return;
993
-		}
994
-		if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade
995
-			self::checkMaintenanceMode($systemConfig);
996
-
997
-			if (\OCP\Util::needUpgrade()) {
998
-				if (function_exists('opcache_reset')) {
999
-					opcache_reset();
1000
-				}
1001
-				if (!((bool)$systemConfig->getValue('maintenance', false))) {
1002
-					self::printUpgradePage($systemConfig);
1003
-					exit();
1004
-				}
1005
-			}
1006
-		}
1007
-
1008
-		$appManager = Server::get(\OCP\App\IAppManager::class);
1009
-
1010
-		// Always load authentication apps
1011
-		$appManager->loadApps(['authentication']);
1012
-		$appManager->loadApps(['extended_authentication']);
1013
-
1014
-		// Load minimum set of apps
1015
-		if (!\OCP\Util::needUpgrade()
1016
-			&& !((bool)$systemConfig->getValue('maintenance', false))) {
1017
-			// For logged-in users: Load everything
1018
-			if (Server::get(IUserSession::class)->isLoggedIn()) {
1019
-				$appManager->loadApps();
1020
-			} else {
1021
-				// For guests: Load only filesystem and logging
1022
-				$appManager->loadApps(['filesystem', 'logging']);
1023
-
1024
-				// Don't try to login when a client is trying to get a OAuth token.
1025
-				// OAuth needs to support basic auth too, so the login is not valid
1026
-				// inside Nextcloud and the Login exception would ruin it.
1027
-				if ($request->getRawPathInfo() !== '/apps/oauth2/api/v1/token') {
1028
-					try {
1029
-						self::handleLogin($request);
1030
-					} catch (DisabledUserException $e) {
1031
-						// Disabled users would not be seen as logged in and
1032
-						// trying to log them in would fail, so the login
1033
-						// exception is ignored for the themed stylesheets and
1034
-						// images.
1035
-						if ($request->getRawPathInfo() !== '/apps/theming/theme/default.css'
1036
-							&& $request->getRawPathInfo() !== '/apps/theming/theme/light.css'
1037
-							&& $request->getRawPathInfo() !== '/apps/theming/theme/dark.css'
1038
-							&& $request->getRawPathInfo() !== '/apps/theming/theme/light-highcontrast.css'
1039
-							&& $request->getRawPathInfo() !== '/apps/theming/theme/dark-highcontrast.css'
1040
-							&& $request->getRawPathInfo() !== '/apps/theming/theme/opendyslexic.css'
1041
-							&& $request->getRawPathInfo() !== '/apps/theming/image/background'
1042
-							&& $request->getRawPathInfo() !== '/apps/theming/image/logo'
1043
-							&& $request->getRawPathInfo() !== '/apps/theming/image/logoheader'
1044
-							&& !str_starts_with($request->getRawPathInfo(), '/apps/theming/favicon')
1045
-							&& !str_starts_with($request->getRawPathInfo(), '/apps/theming/icon')) {
1046
-							throw $e;
1047
-						}
1048
-					}
1049
-				}
1050
-			}
1051
-		}
1052
-
1053
-		if (!self::$CLI) {
1054
-			try {
1055
-				if (!\OCP\Util::needUpgrade()) {
1056
-					$appManager->loadApps(['filesystem', 'logging']);
1057
-					$appManager->loadApps();
1058
-				}
1059
-				Server::get(\OC\Route\Router::class)->match($request->getRawPathInfo());
1060
-				return;
1061
-			} catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
1062
-				//header('HTTP/1.0 404 Not Found');
1063
-			} catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
1064
-				http_response_code(405);
1065
-				return;
1066
-			}
1067
-		}
1068
-
1069
-		// Handle WebDAV
1070
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
1071
-			// not allowed any more to prevent people
1072
-			// mounting this root directly.
1073
-			// Users need to mount remote.php/webdav instead.
1074
-			http_response_code(405);
1075
-			return;
1076
-		}
1077
-
1078
-		// Handle requests for JSON or XML
1079
-		$acceptHeader = $request->getHeader('Accept');
1080
-		if (in_array($acceptHeader, ['application/json', 'application/xml'], true)) {
1081
-			http_response_code(404);
1082
-			return;
1083
-		}
1084
-
1085
-		// Handle resources that can't be found
1086
-		// This prevents browsers from redirecting to the default page and then
1087
-		// attempting to parse HTML as CSS and similar.
1088
-		$destinationHeader = $request->getHeader('Sec-Fetch-Dest');
1089
-		if (in_array($destinationHeader, ['font', 'script', 'style'])) {
1090
-			http_response_code(404);
1091
-			return;
1092
-		}
1093
-
1094
-		// Redirect to the default app or login only as an entry point
1095
-		if ($requestPath === '') {
1096
-			// Someone is logged in
1097
-			if (Server::get(IUserSession::class)->isLoggedIn()) {
1098
-				header('Location: ' . Server::get(IURLGenerator::class)->linkToDefaultPageUrl());
1099
-			} else {
1100
-				// Not handled and not logged in
1101
-				header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute('core.login.showLoginForm'));
1102
-			}
1103
-			return;
1104
-		}
1105
-
1106
-		try {
1107
-			Server::get(\OC\Route\Router::class)->match('/error/404');
1108
-		} catch (\Exception $e) {
1109
-			if (!$e instanceof MethodNotAllowedException) {
1110
-				logger('core')->emergency($e->getMessage(), ['exception' => $e]);
1111
-			}
1112
-			$l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
1113
-			Server::get(ITemplateManager::class)->printErrorPage(
1114
-				'404',
1115
-				$l->t('The page could not be found on the server.'),
1116
-				404
1117
-			);
1118
-		}
1119
-	}
1120
-
1121
-	/**
1122
-	 * Check login: apache auth, auth token, basic auth
1123
-	 */
1124
-	public static function handleLogin(OCP\IRequest $request): bool {
1125
-		if ($request->getHeader('X-Nextcloud-Federation')) {
1126
-			return false;
1127
-		}
1128
-		$userSession = Server::get(\OC\User\Session::class);
1129
-		if (OC_User::handleApacheAuth()) {
1130
-			return true;
1131
-		}
1132
-		if (self::tryAppAPILogin($request)) {
1133
-			return true;
1134
-		}
1135
-		if ($userSession->tryTokenLogin($request)) {
1136
-			return true;
1137
-		}
1138
-		if (isset($_COOKIE['nc_username'])
1139
-			&& isset($_COOKIE['nc_token'])
1140
-			&& isset($_COOKIE['nc_session_id'])
1141
-			&& $userSession->loginWithCookie($_COOKIE['nc_username'], $_COOKIE['nc_token'], $_COOKIE['nc_session_id'])) {
1142
-			return true;
1143
-		}
1144
-		if ($userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class))) {
1145
-			return true;
1146
-		}
1147
-		return false;
1148
-	}
1149
-
1150
-	protected static function handleAuthHeaders(): void {
1151
-		//copy http auth headers for apache+php-fcgid work around
1152
-		if (isset($_SERVER['HTTP_XAUTHORIZATION']) && !isset($_SERVER['HTTP_AUTHORIZATION'])) {
1153
-			$_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_XAUTHORIZATION'];
1154
-		}
1155
-
1156
-		// Extract PHP_AUTH_USER/PHP_AUTH_PW from other headers if necessary.
1157
-		$vars = [
1158
-			'HTTP_AUTHORIZATION', // apache+php-cgi work around
1159
-			'REDIRECT_HTTP_AUTHORIZATION', // apache+php-cgi alternative
1160
-		];
1161
-		foreach ($vars as $var) {
1162
-			if (isset($_SERVER[$var]) && is_string($_SERVER[$var]) && preg_match('/Basic\s+(.*)$/i', $_SERVER[$var], $matches)) {
1163
-				$credentials = explode(':', base64_decode($matches[1]), 2);
1164
-				if (count($credentials) === 2) {
1165
-					$_SERVER['PHP_AUTH_USER'] = $credentials[0];
1166
-					$_SERVER['PHP_AUTH_PW'] = $credentials[1];
1167
-					break;
1168
-				}
1169
-			}
1170
-		}
1171
-	}
1172
-
1173
-	protected static function tryAppAPILogin(OCP\IRequest $request): bool {
1174
-		if (!$request->getHeader('AUTHORIZATION-APP-API')) {
1175
-			return false;
1176
-		}
1177
-		$appManager = Server::get(OCP\App\IAppManager::class);
1178
-		if (!$appManager->isEnabledForAnyone('app_api')) {
1179
-			return false;
1180
-		}
1181
-		try {
1182
-			$appAPIService = Server::get(OCA\AppAPI\Service\AppAPIService::class);
1183
-			return $appAPIService->validateExAppRequestToNC($request);
1184
-		} catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
1185
-			return false;
1186
-		}
1187
-	}
43
+    /**
44
+     * The installation path for Nextcloud  on the server (e.g. /srv/http/nextcloud)
45
+     */
46
+    public static string $SERVERROOT = '';
47
+    /**
48
+     * the current request path relative to the Nextcloud root (e.g. files/index.php)
49
+     */
50
+    private static string $SUBURI = '';
51
+    /**
52
+     * the Nextcloud root path for http requests (e.g. /nextcloud)
53
+     */
54
+    public static string $WEBROOT = '';
55
+    /**
56
+     * The installation path array of the apps folder on the server (e.g. /srv/http/nextcloud) 'path' and
57
+     * web path in 'url'
58
+     */
59
+    public static array $APPSROOTS = [];
60
+
61
+    public static string $configDir;
62
+
63
+    /**
64
+     * requested app
65
+     */
66
+    public static string $REQUESTEDAPP = '';
67
+
68
+    /**
69
+     * check if Nextcloud runs in cli mode
70
+     */
71
+    public static bool $CLI = false;
72
+
73
+    public static \Composer\Autoload\ClassLoader $composerAutoloader;
74
+
75
+    public static \OC\Server $server;
76
+
77
+    private static \OC\Config $config;
78
+
79
+    /**
80
+     * @throws \RuntimeException when the 3rdparty directory is missing or
81
+     *                           the app path list is empty or contains an invalid path
82
+     */
83
+    public static function initPaths(): void {
84
+        if (defined('PHPUNIT_CONFIG_DIR')) {
85
+            self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/';
86
+        } elseif (defined('PHPUNIT_RUN') and PHPUNIT_RUN and is_dir(OC::$SERVERROOT . '/tests/config/')) {
87
+            self::$configDir = OC::$SERVERROOT . '/tests/config/';
88
+        } elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
89
+            self::$configDir = rtrim($dir, '/') . '/';
90
+        } else {
91
+            self::$configDir = OC::$SERVERROOT . '/config/';
92
+        }
93
+        self::$config = new \OC\Config(self::$configDir);
94
+
95
+        OC::$SUBURI = str_replace('\\', '/', substr(realpath($_SERVER['SCRIPT_FILENAME'] ?? ''), strlen(OC::$SERVERROOT)));
96
+        /**
97
+         * FIXME: The following lines are required because we can't yet instantiate
98
+         *        Server::get(\OCP\IRequest::class) since \OC::$server does not yet exist.
99
+         */
100
+        $params = [
101
+            'server' => [
102
+                'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'] ?? null,
103
+                'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'] ?? null,
104
+            ],
105
+        ];
106
+        if (isset($_SERVER['REMOTE_ADDR'])) {
107
+            $params['server']['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
108
+        }
109
+        $fakeRequest = new \OC\AppFramework\Http\Request(
110
+            $params,
111
+            new \OC\AppFramework\Http\RequestId($_SERVER['UNIQUE_ID'] ?? '', new \OC\Security\SecureRandom()),
112
+            new \OC\AllConfig(new \OC\SystemConfig(self::$config))
113
+        );
114
+        $scriptName = $fakeRequest->getScriptName();
115
+        if (substr($scriptName, -1) == '/') {
116
+            $scriptName .= 'index.php';
117
+            //make sure suburi follows the same rules as scriptName
118
+            if (substr(OC::$SUBURI, -9) != 'index.php') {
119
+                if (substr(OC::$SUBURI, -1) != '/') {
120
+                    OC::$SUBURI = OC::$SUBURI . '/';
121
+                }
122
+                OC::$SUBURI = OC::$SUBURI . 'index.php';
123
+            }
124
+        }
125
+
126
+        if (OC::$CLI) {
127
+            OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
128
+        } else {
129
+            if (substr($scriptName, 0 - strlen(OC::$SUBURI)) === OC::$SUBURI) {
130
+                OC::$WEBROOT = substr($scriptName, 0, 0 - strlen(OC::$SUBURI));
131
+
132
+                if (OC::$WEBROOT != '' && OC::$WEBROOT[0] !== '/') {
133
+                    OC::$WEBROOT = '/' . OC::$WEBROOT;
134
+                }
135
+            } else {
136
+                // The scriptName is not ending with OC::$SUBURI
137
+                // This most likely means that we are calling from CLI.
138
+                // However some cron jobs still need to generate
139
+                // a web URL, so we use overwritewebroot as a fallback.
140
+                OC::$WEBROOT = self::$config->getValue('overwritewebroot', '');
141
+            }
142
+
143
+            // Resolve /nextcloud to /nextcloud/ to ensure to always have a trailing
144
+            // slash which is required by URL generation.
145
+            if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === \OC::$WEBROOT
146
+                    && substr($_SERVER['REQUEST_URI'], -1) !== '/') {
147
+                header('Location: ' . \OC::$WEBROOT . '/');
148
+                exit();
149
+            }
150
+        }
151
+
152
+        // search the apps folder
153
+        $config_paths = self::$config->getValue('apps_paths', []);
154
+        if (!empty($config_paths)) {
155
+            foreach ($config_paths as $paths) {
156
+                if (isset($paths['url']) && isset($paths['path'])) {
157
+                    $paths['url'] = rtrim($paths['url'], '/');
158
+                    $paths['path'] = rtrim($paths['path'], '/');
159
+                    OC::$APPSROOTS[] = $paths;
160
+                }
161
+            }
162
+        } elseif (file_exists(OC::$SERVERROOT . '/apps')) {
163
+            OC::$APPSROOTS[] = ['path' => OC::$SERVERROOT . '/apps', 'url' => '/apps', 'writable' => true];
164
+        }
165
+
166
+        if (empty(OC::$APPSROOTS)) {
167
+            throw new \RuntimeException('apps directory not found! Please put the Nextcloud apps folder in the Nextcloud folder'
168
+                . '. You can also configure the location in the config.php file.');
169
+        }
170
+        $paths = [];
171
+        foreach (OC::$APPSROOTS as $path) {
172
+            $paths[] = $path['path'];
173
+            if (!is_dir($path['path'])) {
174
+                throw new \RuntimeException(sprintf('App directory "%s" not found! Please put the Nextcloud apps folder in the'
175
+                    . ' Nextcloud folder. You can also configure the location in the config.php file.', $path['path']));
176
+            }
177
+        }
178
+
179
+        // set the right include path
180
+        set_include_path(
181
+            implode(PATH_SEPARATOR, $paths)
182
+        );
183
+    }
184
+
185
+    public static function checkConfig(): void {
186
+        // Create config if it does not already exist
187
+        $configFilePath = self::$configDir . '/config.php';
188
+        if (!file_exists($configFilePath)) {
189
+            @touch($configFilePath);
190
+        }
191
+
192
+        // Check if config is writable
193
+        $configFileWritable = is_writable($configFilePath);
194
+        $configReadOnly = Server::get(IConfig::class)->getSystemValueBool('config_is_read_only');
195
+        if (!$configFileWritable && !$configReadOnly
196
+            || !$configFileWritable && \OCP\Util::needUpgrade()) {
197
+            $urlGenerator = Server::get(IURLGenerator::class);
198
+            $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
199
+
200
+            if (self::$CLI) {
201
+                echo $l->t('Cannot write into "config" directory!') . "\n";
202
+                echo $l->t('This can usually be fixed by giving the web server write access to the config directory.') . "\n";
203
+                echo "\n";
204
+                echo $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . "\n";
205
+                echo $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]) . "\n";
206
+                exit;
207
+            } else {
208
+                Server::get(ITemplateManager::class)->printErrorPage(
209
+                    $l->t('Cannot write into "config" directory!'),
210
+                    $l->t('This can usually be fixed by giving the web server write access to the config directory.') . ' '
211
+                    . $l->t('But, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it.') . ' '
212
+                    . $l->t('See %s', [ $urlGenerator->linkToDocs('admin-config') ]),
213
+                    503
214
+                );
215
+            }
216
+        }
217
+    }
218
+
219
+    public static function checkInstalled(\OC\SystemConfig $systemConfig): void {
220
+        if (defined('OC_CONSOLE')) {
221
+            return;
222
+        }
223
+        // Redirect to installer if not installed
224
+        if (!$systemConfig->getValue('installed', false) && OC::$SUBURI !== '/index.php' && OC::$SUBURI !== '/status.php') {
225
+            if (OC::$CLI) {
226
+                throw new Exception('Not installed');
227
+            } else {
228
+                $url = OC::$WEBROOT . '/index.php';
229
+                header('Location: ' . $url);
230
+            }
231
+            exit();
232
+        }
233
+    }
234
+
235
+    public static function checkMaintenanceMode(\OC\SystemConfig $systemConfig): void {
236
+        // Allow ajax update script to execute without being stopped
237
+        if (((bool)$systemConfig->getValue('maintenance', false)) && OC::$SUBURI != '/core/ajax/update.php') {
238
+            // send http status 503
239
+            http_response_code(503);
240
+            header('X-Nextcloud-Maintenance-Mode: 1');
241
+            header('Retry-After: 120');
242
+
243
+            // render error page
244
+            $template = Server::get(ITemplateManager::class)->getTemplate('', 'update.user', 'guest');
245
+            \OCP\Util::addScript('core', 'maintenance');
246
+            \OCP\Util::addStyle('core', 'guest');
247
+            $template->printPage();
248
+            die();
249
+        }
250
+    }
251
+
252
+    /**
253
+     * Prints the upgrade page
254
+     */
255
+    private static function printUpgradePage(\OC\SystemConfig $systemConfig): void {
256
+        $cliUpgradeLink = $systemConfig->getValue('upgrade.cli-upgrade-link', '');
257
+        $disableWebUpdater = $systemConfig->getValue('upgrade.disable-web', false);
258
+        $tooBig = false;
259
+        if (!$disableWebUpdater) {
260
+            $apps = Server::get(\OCP\App\IAppManager::class);
261
+            if ($apps->isEnabledForAnyone('user_ldap')) {
262
+                $qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
263
+
264
+                $result = $qb->select($qb->func()->count('*', 'user_count'))
265
+                    ->from('ldap_user_mapping')
266
+                    ->executeQuery();
267
+                $row = $result->fetch();
268
+                $result->closeCursor();
269
+
270
+                $tooBig = ($row['user_count'] > 50);
271
+            }
272
+            if (!$tooBig && $apps->isEnabledForAnyone('user_saml')) {
273
+                $qb = Server::get(\OCP\IDBConnection::class)->getQueryBuilder();
274
+
275
+                $result = $qb->select($qb->func()->count('*', 'user_count'))
276
+                    ->from('user_saml_users')
277
+                    ->executeQuery();
278
+                $row = $result->fetch();
279
+                $result->closeCursor();
280
+
281
+                $tooBig = ($row['user_count'] > 50);
282
+            }
283
+            if (!$tooBig) {
284
+                // count users
285
+                $totalUsers = Server::get(\OCP\IUserManager::class)->countUsersTotal(51);
286
+                $tooBig = ($totalUsers > 50);
287
+            }
288
+        }
289
+        $ignoreTooBigWarning = isset($_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'])
290
+            && $_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'] === 'IAmSuperSureToDoThis';
291
+
292
+        if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) {
293
+            // send http status 503
294
+            http_response_code(503);
295
+            header('Retry-After: 120');
296
+
297
+            $serverVersion = \OCP\Server::get(\OCP\ServerVersion::class);
298
+
299
+            // render error page
300
+            $template = Server::get(ITemplateManager::class)->getTemplate('', 'update.use-cli', 'guest');
301
+            $template->assign('productName', 'nextcloud'); // for now
302
+            $template->assign('version', $serverVersion->getVersionString());
303
+            $template->assign('tooBig', $tooBig);
304
+            $template->assign('cliUpgradeLink', $cliUpgradeLink);
305
+
306
+            $template->printPage();
307
+            die();
308
+        }
309
+
310
+        // check whether this is a core update or apps update
311
+        $installedVersion = $systemConfig->getValue('version', '0.0.0');
312
+        $currentVersion = implode('.', \OCP\Util::getVersion());
313
+
314
+        // if not a core upgrade, then it's apps upgrade
315
+        $isAppsOnlyUpgrade = version_compare($currentVersion, $installedVersion, '=');
316
+
317
+        $oldTheme = $systemConfig->getValue('theme');
318
+        $systemConfig->setValue('theme', '');
319
+        \OCP\Util::addScript('core', 'common');
320
+        \OCP\Util::addScript('core', 'main');
321
+        \OCP\Util::addTranslations('core');
322
+        \OCP\Util::addScript('core', 'update');
323
+
324
+        /** @var \OC\App\AppManager $appManager */
325
+        $appManager = Server::get(\OCP\App\IAppManager::class);
326
+
327
+        $tmpl = Server::get(ITemplateManager::class)->getTemplate('', 'update.admin', 'guest');
328
+        $tmpl->assign('version', \OCP\Server::get(\OCP\ServerVersion::class)->getVersionString());
329
+        $tmpl->assign('isAppsOnlyUpgrade', $isAppsOnlyUpgrade);
330
+
331
+        // get third party apps
332
+        $ocVersion = \OCP\Util::getVersion();
333
+        $ocVersion = implode('.', $ocVersion);
334
+        $incompatibleApps = $appManager->getIncompatibleApps($ocVersion);
335
+        $incompatibleOverwrites = $systemConfig->getValue('app_install_overwrite', []);
336
+        $incompatibleShippedApps = [];
337
+        $incompatibleDisabledApps = [];
338
+        foreach ($incompatibleApps as $appInfo) {
339
+            if ($appManager->isShipped($appInfo['id'])) {
340
+                $incompatibleShippedApps[] = $appInfo['name'] . ' (' . $appInfo['id'] . ')';
341
+            }
342
+            if (!in_array($appInfo['id'], $incompatibleOverwrites)) {
343
+                $incompatibleDisabledApps[] = $appInfo;
344
+            }
345
+        }
346
+
347
+        if (!empty($incompatibleShippedApps)) {
348
+            $l = Server::get(\OCP\L10N\IFactory::class)->get('core');
349
+            $hint = $l->t('Application %1$s is not present or has a non-compatible version with this server. Please check the apps directory.', [implode(', ', $incompatibleShippedApps)]);
350
+            throw new \OCP\HintException('Application ' . implode(', ', $incompatibleShippedApps) . ' is not present or has a non-compatible version with this server. Please check the apps directory.', $hint);
351
+        }
352
+
353
+        $tmpl->assign('appsToUpgrade', $appManager->getAppsNeedingUpgrade($ocVersion));
354
+        $tmpl->assign('incompatibleAppsList', $incompatibleDisabledApps);
355
+        try {
356
+            $defaults = new \OC_Defaults();
357
+            $tmpl->assign('productName', $defaults->getName());
358
+        } catch (Throwable $error) {
359
+            $tmpl->assign('productName', 'Nextcloud');
360
+        }
361
+        $tmpl->assign('oldTheme', $oldTheme);
362
+        $tmpl->printPage();
363
+    }
364
+
365
+    public static function initSession(): void {
366
+        $request = Server::get(IRequest::class);
367
+
368
+        // TODO: Temporary disabled again to solve issues with CalDAV/CardDAV clients like DAVx5 that use cookies
369
+        // TODO: See https://github.com/nextcloud/server/issues/37277#issuecomment-1476366147 and the other comments
370
+        // TODO: for further information.
371
+        // $isDavRequest = strpos($request->getRequestUri(), '/remote.php/dav') === 0 || strpos($request->getRequestUri(), '/remote.php/webdav') === 0;
372
+        // if ($request->getHeader('Authorization') !== '' && is_null($request->getCookie('cookie_test')) && $isDavRequest && !isset($_COOKIE['nc_session_id'])) {
373
+        // setcookie('cookie_test', 'test', time() + 3600);
374
+        // // Do not initialize the session if a request is authenticated directly
375
+        // // unless there is a session cookie already sent along
376
+        // return;
377
+        // }
378
+
379
+        if ($request->getServerProtocol() === 'https') {
380
+            ini_set('session.cookie_secure', 'true');
381
+        }
382
+
383
+        // prevents javascript from accessing php session cookies
384
+        ini_set('session.cookie_httponly', 'true');
385
+
386
+        // Do not initialize sessions for 'status.php' requests
387
+        // Monitoring endpoints can quickly flood session handlers
388
+        // and 'status.php' doesn't require sessions anyway
389
+        if (str_ends_with($request->getScriptName(), '/status.php')) {
390
+            return;
391
+        }
392
+
393
+        // set the cookie path to the Nextcloud directory
394
+        $cookie_path = OC::$WEBROOT ? : '/';
395
+        ini_set('session.cookie_path', $cookie_path);
396
+
397
+        // set the cookie domain to the Nextcloud domain
398
+        $cookie_domain = self::$config->getValue('cookie_domain', '');
399
+        if ($cookie_domain) {
400
+            ini_set('session.cookie_domain', $cookie_domain);
401
+        }
402
+
403
+        // Let the session name be changed in the initSession Hook
404
+        $sessionName = OC_Util::getInstanceId();
405
+
406
+        try {
407
+            $logger = null;
408
+            if (Server::get(\OC\SystemConfig::class)->getValue('installed', false)) {
409
+                $logger = logger('core');
410
+            }
411
+
412
+            // set the session name to the instance id - which is unique
413
+            $session = new \OC\Session\Internal(
414
+                $sessionName,
415
+                $logger,
416
+            );
417
+
418
+            $cryptoWrapper = Server::get(\OC\Session\CryptoWrapper::class);
419
+            $session = $cryptoWrapper->wrapSession($session);
420
+            self::$server->setSession($session);
421
+
422
+            // if session can't be started break with http 500 error
423
+        } catch (Exception $e) {
424
+            Server::get(LoggerInterface::class)->error($e->getMessage(), ['app' => 'base','exception' => $e]);
425
+            //show the user a detailed error page
426
+            Server::get(ITemplateManager::class)->printExceptionErrorPage($e, 500);
427
+            die();
428
+        }
429
+
430
+        //try to set the session lifetime
431
+        $sessionLifeTime = self::getSessionLifeTime();
432
+
433
+        // session timeout
434
+        if ($session->exists('LAST_ACTIVITY') && (time() - $session->get('LAST_ACTIVITY') > $sessionLifeTime)) {
435
+            if (isset($_COOKIE[session_name()])) {
436
+                setcookie(session_name(), '', -1, self::$WEBROOT ? : '/');
437
+            }
438
+            Server::get(IUserSession::class)->logout();
439
+        }
440
+
441
+        if (!self::hasSessionRelaxedExpiry()) {
442
+            $session->set('LAST_ACTIVITY', time());
443
+        }
444
+        $session->close();
445
+    }
446
+
447
+    private static function getSessionLifeTime(): int {
448
+        return Server::get(\OC\AllConfig::class)->getSystemValueInt('session_lifetime', 60 * 60 * 24);
449
+    }
450
+
451
+    /**
452
+     * @return bool true if the session expiry should only be done by gc instead of an explicit timeout
453
+     */
454
+    public static function hasSessionRelaxedExpiry(): bool {
455
+        return Server::get(\OC\AllConfig::class)->getSystemValueBool('session_relaxed_expiry', false);
456
+    }
457
+
458
+    /**
459
+     * Try to set some values to the required Nextcloud default
460
+     */
461
+    public static function setRequiredIniValues(): void {
462
+        // Don't display errors and log them
463
+        @ini_set('display_errors', '0');
464
+        @ini_set('log_errors', '1');
465
+
466
+        // Try to configure php to enable big file uploads.
467
+        // This doesn't work always depending on the webserver and php configuration.
468
+        // Let's try to overwrite some defaults if they are smaller than 1 hour
469
+
470
+        if (intval(@ini_get('max_execution_time') ?: 0) < 3600) {
471
+            @ini_set('max_execution_time', strval(3600));
472
+        }
473
+
474
+        if (intval(@ini_get('max_input_time') ?: 0) < 3600) {
475
+            @ini_set('max_input_time', strval(3600));
476
+        }
477
+
478
+        // Try to set the maximum execution time to the largest time limit we have
479
+        if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
480
+            @set_time_limit(max(intval(@ini_get('max_execution_time')), intval(@ini_get('max_input_time'))));
481
+        }
482
+
483
+        @ini_set('default_charset', 'UTF-8');
484
+        @ini_set('gd.jpeg_ignore_warning', '1');
485
+    }
486
+
487
+    /**
488
+     * Send the same site cookies
489
+     */
490
+    private static function sendSameSiteCookies(): void {
491
+        $cookieParams = session_get_cookie_params();
492
+        $secureCookie = ($cookieParams['secure'] === true) ? 'secure; ' : '';
493
+        $policies = [
494
+            'lax',
495
+            'strict',
496
+        ];
497
+
498
+        // Append __Host to the cookie if it meets the requirements
499
+        $cookiePrefix = '';
500
+        if ($cookieParams['secure'] === true && $cookieParams['path'] === '/') {
501
+            $cookiePrefix = '__Host-';
502
+        }
503
+
504
+        foreach ($policies as $policy) {
505
+            header(
506
+                sprintf(
507
+                    'Set-Cookie: %snc_sameSiteCookie%s=true; path=%s; httponly;' . $secureCookie . 'expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=%s',
508
+                    $cookiePrefix,
509
+                    $policy,
510
+                    $cookieParams['path'],
511
+                    $policy
512
+                ),
513
+                false
514
+            );
515
+        }
516
+    }
517
+
518
+    /**
519
+     * Same Site cookie to further mitigate CSRF attacks. This cookie has to
520
+     * be set in every request if cookies are sent to add a second level of
521
+     * defense against CSRF.
522
+     *
523
+     * If the cookie is not sent this will set the cookie and reload the page.
524
+     * We use an additional cookie since we want to protect logout CSRF and
525
+     * also we can't directly interfere with PHP's session mechanism.
526
+     */
527
+    private static function performSameSiteCookieProtection(IConfig $config): void {
528
+        $request = Server::get(IRequest::class);
529
+
530
+        // Some user agents are notorious and don't really properly follow HTTP
531
+        // specifications. For those, have an automated opt-out. Since the protection
532
+        // for remote.php is applied in base.php as starting point we need to opt out
533
+        // here.
534
+        $incompatibleUserAgents = $config->getSystemValue('csrf.optout');
535
+
536
+        // Fallback, if csrf.optout is unset
537
+        if (!is_array($incompatibleUserAgents)) {
538
+            $incompatibleUserAgents = [
539
+                // OS X Finder
540
+                '/^WebDAVFS/',
541
+                // Windows webdav drive
542
+                '/^Microsoft-WebDAV-MiniRedir/',
543
+            ];
544
+        }
545
+
546
+        if ($request->isUserAgent($incompatibleUserAgents)) {
547
+            return;
548
+        }
549
+
550
+        if (count($_COOKIE) > 0) {
551
+            $requestUri = $request->getScriptName();
552
+            $processingScript = explode('/', $requestUri);
553
+            $processingScript = $processingScript[count($processingScript) - 1];
554
+
555
+            if ($processingScript === 'index.php' // index.php routes are handled in the middleware
556
+                || $processingScript === 'cron.php' // and cron.php does not need any authentication at all
557
+                || $processingScript === 'public.php' // For public.php, auth for password protected shares is done in the PublicAuth plugin
558
+            ) {
559
+                return;
560
+            }
561
+
562
+            // All other endpoints require the lax and the strict cookie
563
+            if (!$request->passesStrictCookieCheck()) {
564
+                logger('core')->warning('Request does not pass strict cookie check');
565
+                self::sendSameSiteCookies();
566
+                // Debug mode gets access to the resources without strict cookie
567
+                // due to the fact that the SabreDAV browser also lives there.
568
+                if (!$config->getSystemValueBool('debug', false)) {
569
+                    http_response_code(\OCP\AppFramework\Http::STATUS_PRECONDITION_FAILED);
570
+                    header('Content-Type: application/json');
571
+                    echo json_encode(['error' => 'Strict Cookie has not been found in request']);
572
+                    exit();
573
+                }
574
+            }
575
+        } elseif (!isset($_COOKIE['nc_sameSiteCookielax']) || !isset($_COOKIE['nc_sameSiteCookiestrict'])) {
576
+            self::sendSameSiteCookies();
577
+        }
578
+    }
579
+
580
+    public static function init(): void {
581
+        // First handle PHP configuration and copy auth headers to the expected
582
+        // $_SERVER variable before doing anything Server object related
583
+        self::setRequiredIniValues();
584
+        self::handleAuthHeaders();
585
+
586
+        // prevent any XML processing from loading external entities
587
+        libxml_set_external_entity_loader(static function () {
588
+            return null;
589
+        });
590
+
591
+        // Set default timezone before the Server object is booted
592
+        if (!date_default_timezone_set('UTC')) {
593
+            throw new \RuntimeException('Could not set timezone to UTC');
594
+        }
595
+
596
+        // calculate the root directories
597
+        OC::$SERVERROOT = str_replace('\\', '/', substr(__DIR__, 0, -4));
598
+
599
+        // register autoloader
600
+        $loaderStart = microtime(true);
601
+
602
+        self::$CLI = (php_sapi_name() == 'cli');
603
+
604
+        // Add default composer PSR-4 autoloader, ensure apcu to be disabled
605
+        self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php';
606
+        self::$composerAutoloader->setApcuPrefix(null);
607
+
608
+
609
+        try {
610
+            self::initPaths();
611
+            // setup 3rdparty autoloader
612
+            $vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php';
613
+            if (!file_exists($vendorAutoLoad)) {
614
+                throw new \RuntimeException('Composer autoloader not found, unable to continue. Check the folder "3rdparty". Running "git submodule update --init" will initialize the git submodule that handles the subfolder "3rdparty".');
615
+            }
616
+            require_once $vendorAutoLoad;
617
+        } catch (\RuntimeException $e) {
618
+            if (!self::$CLI) {
619
+                http_response_code(503);
620
+            }
621
+            // we can't use the template error page here, because this needs the
622
+            // DI container which isn't available yet
623
+            print($e->getMessage());
624
+            exit();
625
+        }
626
+        $loaderEnd = microtime(true);
627
+
628
+        // Enable lazy loading if activated
629
+        \OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true);
630
+
631
+        // setup the basic server
632
+        self::$server = new \OC\Server(\OC::$WEBROOT, self::$config);
633
+        self::$server->boot();
634
+
635
+        try {
636
+            $profiler = new BuiltInProfiler(
637
+                Server::get(IConfig::class),
638
+                Server::get(IRequest::class),
639
+            );
640
+            $profiler->start();
641
+        } catch (\Throwable $e) {
642
+            logger('core')->error('Failed to start profiler: ' . $e->getMessage(), ['app' => 'base']);
643
+        }
644
+
645
+        if (self::$CLI && in_array('--' . \OCP\Console\ReservedOptions::DEBUG_LOG, $_SERVER['argv'])) {
646
+            \OC\Core\Listener\BeforeMessageLoggedEventListener::setup();
647
+        }
648
+
649
+        $eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class);
650
+        $eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd);
651
+        $eventLogger->start('boot', 'Initialize');
652
+
653
+        // Override php.ini and log everything if we're troubleshooting
654
+        if (self::$config->getValue('loglevel') === ILogger::DEBUG) {
655
+            error_reporting(E_ALL);
656
+        }
657
+
658
+        // initialize intl fallback if necessary
659
+        OC_Util::isSetLocaleWorking();
660
+
661
+        $config = Server::get(IConfig::class);
662
+        if (!defined('PHPUNIT_RUN')) {
663
+            $errorHandler = new OC\Log\ErrorHandler(
664
+                \OCP\Server::get(\Psr\Log\LoggerInterface::class),
665
+            );
666
+            $exceptionHandler = [$errorHandler, 'onException'];
667
+            if ($config->getSystemValueBool('debug', false)) {
668
+                set_error_handler([$errorHandler, 'onAll'], E_ALL);
669
+                if (\OC::$CLI) {
670
+                    $exceptionHandler = [Server::get(ITemplateManager::class), 'printExceptionErrorPage'];
671
+                }
672
+            } else {
673
+                set_error_handler([$errorHandler, 'onError']);
674
+            }
675
+            register_shutdown_function([$errorHandler, 'onShutdown']);
676
+            set_exception_handler($exceptionHandler);
677
+        }
678
+
679
+        /** @var \OC\AppFramework\Bootstrap\Coordinator $bootstrapCoordinator */
680
+        $bootstrapCoordinator = Server::get(\OC\AppFramework\Bootstrap\Coordinator::class);
681
+        $bootstrapCoordinator->runInitialRegistration();
682
+
683
+        $eventLogger->start('init_session', 'Initialize session');
684
+
685
+        // Check for PHP SimpleXML extension earlier since we need it before our other checks and want to provide a useful hint for web users
686
+        // see https://github.com/nextcloud/server/pull/2619
687
+        if (!function_exists('simplexml_load_file')) {
688
+            throw new \OCP\HintException('The PHP SimpleXML/PHP-XML extension is not installed.', 'Install the extension or make sure it is enabled.');
689
+        }
690
+
691
+        $systemConfig = Server::get(\OC\SystemConfig::class);
692
+        $appManager = Server::get(\OCP\App\IAppManager::class);
693
+        if ($systemConfig->getValue('installed', false)) {
694
+            $appManager->loadApps(['session']);
695
+        }
696
+        if (!self::$CLI) {
697
+            self::initSession();
698
+        }
699
+        $eventLogger->end('init_session');
700
+        self::checkConfig();
701
+        self::checkInstalled($systemConfig);
702
+
703
+        OC_Response::addSecurityHeaders();
704
+
705
+        self::performSameSiteCookieProtection($config);
706
+
707
+        if (!defined('OC_CONSOLE')) {
708
+            $eventLogger->start('check_server', 'Run a few configuration checks');
709
+            $errors = OC_Util::checkServer($systemConfig);
710
+            if (count($errors) > 0) {
711
+                if (!self::$CLI) {
712
+                    http_response_code(503);
713
+                    Util::addStyle('guest');
714
+                    try {
715
+                        Server::get(ITemplateManager::class)->printGuestPage('', 'error', ['errors' => $errors]);
716
+                        exit;
717
+                    } catch (\Exception $e) {
718
+                        // In case any error happens when showing the error page, we simply fall back to posting the text.
719
+                        // This might be the case when e.g. the data directory is broken and we can not load/write SCSS to/from it.
720
+                    }
721
+                }
722
+
723
+                // Convert l10n string into regular string for usage in database
724
+                $staticErrors = [];
725
+                foreach ($errors as $error) {
726
+                    echo $error['error'] . "\n";
727
+                    echo $error['hint'] . "\n\n";
728
+                    $staticErrors[] = [
729
+                        'error' => (string)$error['error'],
730
+                        'hint' => (string)$error['hint'],
731
+                    ];
732
+                }
733
+
734
+                try {
735
+                    $config->setAppValue('core', 'cronErrors', json_encode($staticErrors));
736
+                } catch (\Exception $e) {
737
+                    echo('Writing to database failed');
738
+                }
739
+                exit(1);
740
+            } elseif (self::$CLI && $config->getSystemValueBool('installed', false)) {
741
+                $config->deleteAppValue('core', 'cronErrors');
742
+            }
743
+            $eventLogger->end('check_server');
744
+        }
745
+
746
+        // User and Groups
747
+        if (!$systemConfig->getValue('installed', false)) {
748
+            self::$server->getSession()->set('user_id', '');
749
+        }
750
+
751
+        $eventLogger->start('setup_backends', 'Setup group and user backends');
752
+        Server::get(\OCP\IUserManager::class)->registerBackend(new \OC\User\Database());
753
+        Server::get(\OCP\IGroupManager::class)->addBackend(new \OC\Group\Database());
754
+
755
+        // Subscribe to the hook
756
+        \OCP\Util::connectHook(
757
+            '\OCA\Files_Sharing\API\Server2Server',
758
+            'preLoginNameUsedAsUserName',
759
+            '\OC\User\Database',
760
+            'preLoginNameUsedAsUserName'
761
+        );
762
+
763
+        //setup extra user backends
764
+        if (!\OCP\Util::needUpgrade()) {
765
+            OC_User::setupBackends();
766
+        } else {
767
+            // Run upgrades in incognito mode
768
+            OC_User::setIncognitoMode(true);
769
+        }
770
+        $eventLogger->end('setup_backends');
771
+
772
+        self::registerCleanupHooks($systemConfig);
773
+        self::registerShareHooks($systemConfig);
774
+        self::registerEncryptionWrapperAndHooks();
775
+        self::registerAccountHooks();
776
+        self::registerResourceCollectionHooks();
777
+        self::registerFileReferenceEventListener();
778
+        self::registerRenderReferenceEventListener();
779
+        self::registerAppRestrictionsHooks();
780
+
781
+        // Make sure that the application class is not loaded before the database is setup
782
+        if ($systemConfig->getValue('installed', false)) {
783
+            $appManager->loadApp('settings');
784
+        }
785
+
786
+        //make sure temporary files are cleaned up
787
+        $tmpManager = Server::get(\OCP\ITempManager::class);
788
+        register_shutdown_function([$tmpManager, 'clean']);
789
+        $lockProvider = Server::get(\OCP\Lock\ILockingProvider::class);
790
+        register_shutdown_function([$lockProvider, 'releaseAll']);
791
+
792
+        // Check whether the sample configuration has been copied
793
+        if ($systemConfig->getValue('copied_sample_config', false)) {
794
+            $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
795
+            Server::get(ITemplateManager::class)->printErrorPage(
796
+                $l->t('Sample configuration detected'),
797
+                $l->t('It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php'),
798
+                503
799
+            );
800
+            return;
801
+        }
802
+
803
+        $request = Server::get(IRequest::class);
804
+        $host = $request->getInsecureServerHost();
805
+        /**
806
+         * if the host passed in headers isn't trusted
807
+         * FIXME: Should not be in here at all :see_no_evil:
808
+         */
809
+        if (!OC::$CLI
810
+            && !Server::get(\OC\Security\TrustedDomainHelper::class)->isTrustedDomain($host)
811
+            && $config->getSystemValueBool('installed', false)
812
+        ) {
813
+            // Allow access to CSS resources
814
+            $isScssRequest = false;
815
+            if (strpos($request->getPathInfo() ?: '', '/css/') === 0) {
816
+                $isScssRequest = true;
817
+            }
818
+
819
+            if (substr($request->getRequestUri(), -11) === '/status.php') {
820
+                http_response_code(400);
821
+                header('Content-Type: application/json');
822
+                echo '{"error": "Trusted domain error.", "code": 15}';
823
+                exit();
824
+            }
825
+
826
+            if (!$isScssRequest) {
827
+                http_response_code(400);
828
+                Server::get(LoggerInterface::class)->info(
829
+                    'Trusted domain error. "{remoteAddress}" tried to access using "{host}" as host.',
830
+                    [
831
+                        'app' => 'core',
832
+                        'remoteAddress' => $request->getRemoteAddress(),
833
+                        'host' => $host,
834
+                    ]
835
+                );
836
+
837
+                $tmpl = Server::get(ITemplateManager::class)->getTemplate('core', 'untrustedDomain', 'guest');
838
+                $tmpl->assign('docUrl', Server::get(IURLGenerator::class)->linkToDocs('admin-trusted-domains'));
839
+                $tmpl->printPage();
840
+
841
+                exit();
842
+            }
843
+        }
844
+        $eventLogger->end('boot');
845
+        $eventLogger->log('init', 'OC::init', $loaderStart, microtime(true));
846
+        $eventLogger->start('runtime', 'Runtime');
847
+        $eventLogger->start('request', 'Full request after boot');
848
+        register_shutdown_function(function () use ($eventLogger) {
849
+            $eventLogger->end('request');
850
+        });
851
+
852
+        register_shutdown_function(function () {
853
+            $memoryPeak = memory_get_peak_usage();
854
+            $logLevel = match (true) {
855
+                $memoryPeak > 500_000_000 => ILogger::FATAL,
856
+                $memoryPeak > 400_000_000 => ILogger::ERROR,
857
+                $memoryPeak > 300_000_000 => ILogger::WARN,
858
+                default => null,
859
+            };
860
+            if ($logLevel !== null) {
861
+                $message = 'Request used more than 300 MB of RAM: ' . Util::humanFileSize($memoryPeak);
862
+                $logger = Server::get(LoggerInterface::class);
863
+                $logger->log($logLevel, $message, ['app' => 'core']);
864
+            }
865
+        });
866
+    }
867
+
868
+    /**
869
+     * register hooks for the cleanup of cache and bruteforce protection
870
+     */
871
+    public static function registerCleanupHooks(\OC\SystemConfig $systemConfig): void {
872
+        //don't try to do this before we are properly setup
873
+        if ($systemConfig->getValue('installed', false) && !\OCP\Util::needUpgrade()) {
874
+            // NOTE: This will be replaced to use OCP
875
+            $userSession = Server::get(\OC\User\Session::class);
876
+            $userSession->listen('\OC\User', 'postLogin', function () use ($userSession) {
877
+                if (!defined('PHPUNIT_RUN') && $userSession->isLoggedIn()) {
878
+                    // reset brute force delay for this IP address and username
879
+                    $uid = $userSession->getUser()->getUID();
880
+                    $request = Server::get(IRequest::class);
881
+                    $throttler = Server::get(IThrottler::class);
882
+                    $throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]);
883
+                }
884
+
885
+                try {
886
+                    $cache = new \OC\Cache\File();
887
+                    $cache->gc();
888
+                } catch (\OC\ServerNotAvailableException $e) {
889
+                    // not a GC exception, pass it on
890
+                    throw $e;
891
+                } catch (\OC\ForbiddenException $e) {
892
+                    // filesystem blocked for this request, ignore
893
+                } catch (\Exception $e) {
894
+                    // a GC exception should not prevent users from using OC,
895
+                    // so log the exception
896
+                    Server::get(LoggerInterface::class)->warning('Exception when running cache gc.', [
897
+                        'app' => 'core',
898
+                        'exception' => $e,
899
+                    ]);
900
+                }
901
+            });
902
+        }
903
+    }
904
+
905
+    private static function registerEncryptionWrapperAndHooks(): void {
906
+        /** @var \OC\Encryption\Manager */
907
+        $manager = Server::get(\OCP\Encryption\IManager::class);
908
+        Server::get(IEventDispatcher::class)->addListener(
909
+            BeforeFileSystemSetupEvent::class,
910
+            $manager->setupStorage(...),
911
+        );
912
+
913
+        $enabled = $manager->isEnabled();
914
+        if ($enabled) {
915
+            \OC\Encryption\EncryptionEventListener::register(Server::get(IEventDispatcher::class));
916
+        }
917
+    }
918
+
919
+    private static function registerAccountHooks(): void {
920
+        /** @var IEventDispatcher $dispatcher */
921
+        $dispatcher = Server::get(IEventDispatcher::class);
922
+        $dispatcher->addServiceListener(UserChangedEvent::class, \OC\Accounts\Hooks::class);
923
+    }
924
+
925
+    private static function registerAppRestrictionsHooks(): void {
926
+        /** @var \OC\Group\Manager $groupManager */
927
+        $groupManager = Server::get(\OCP\IGroupManager::class);
928
+        $groupManager->listen('\OC\Group', 'postDelete', function (\OCP\IGroup $group) {
929
+            $appManager = Server::get(\OCP\App\IAppManager::class);
930
+            $apps = $appManager->getEnabledAppsForGroup($group);
931
+            foreach ($apps as $appId) {
932
+                $restrictions = $appManager->getAppRestriction($appId);
933
+                if (empty($restrictions)) {
934
+                    continue;
935
+                }
936
+                $key = array_search($group->getGID(), $restrictions);
937
+                unset($restrictions[$key]);
938
+                $restrictions = array_values($restrictions);
939
+                if (empty($restrictions)) {
940
+                    $appManager->disableApp($appId);
941
+                } else {
942
+                    $appManager->enableAppForGroups($appId, $restrictions);
943
+                }
944
+            }
945
+        });
946
+    }
947
+
948
+    private static function registerResourceCollectionHooks(): void {
949
+        \OC\Collaboration\Resources\Listener::register(Server::get(IEventDispatcher::class));
950
+    }
951
+
952
+    private static function registerFileReferenceEventListener(): void {
953
+        \OC\Collaboration\Reference\File\FileReferenceEventListener::register(Server::get(IEventDispatcher::class));
954
+    }
955
+
956
+    private static function registerRenderReferenceEventListener() {
957
+        \OC\Collaboration\Reference\RenderReferenceEventListener::register(Server::get(IEventDispatcher::class));
958
+    }
959
+
960
+    /**
961
+     * register hooks for sharing
962
+     */
963
+    public static function registerShareHooks(\OC\SystemConfig $systemConfig): void {
964
+        if ($systemConfig->getValue('installed')) {
965
+
966
+            $dispatcher = Server::get(IEventDispatcher::class);
967
+            $dispatcher->addServiceListener(UserRemovedEvent::class, UserRemovedListener::class);
968
+            $dispatcher->addServiceListener(GroupDeletedEvent::class, GroupDeletedListener::class);
969
+            $dispatcher->addServiceListener(UserDeletedEvent::class, UserDeletedListener::class);
970
+        }
971
+    }
972
+
973
+    /**
974
+     * Handle the request
975
+     */
976
+    public static function handleRequest(): void {
977
+        Server::get(\OCP\Diagnostics\IEventLogger::class)->start('handle_request', 'Handle request');
978
+        $systemConfig = Server::get(\OC\SystemConfig::class);
979
+
980
+        // Check if Nextcloud is installed or in maintenance (update) mode
981
+        if (!$systemConfig->getValue('installed', false)) {
982
+            \OC::$server->getSession()->clear();
983
+            $controller = Server::get(\OC\Core\Controller\SetupController::class);
984
+            $controller->run($_POST);
985
+            exit();
986
+        }
987
+
988
+        $request = Server::get(IRequest::class);
989
+        $request->throwDecodingExceptionIfAny();
990
+        $requestPath = $request->getRawPathInfo();
991
+        if ($requestPath === '/heartbeat') {
992
+            return;
993
+        }
994
+        if (substr($requestPath, -3) !== '.js') { // we need these files during the upgrade
995
+            self::checkMaintenanceMode($systemConfig);
996
+
997
+            if (\OCP\Util::needUpgrade()) {
998
+                if (function_exists('opcache_reset')) {
999
+                    opcache_reset();
1000
+                }
1001
+                if (!((bool)$systemConfig->getValue('maintenance', false))) {
1002
+                    self::printUpgradePage($systemConfig);
1003
+                    exit();
1004
+                }
1005
+            }
1006
+        }
1007
+
1008
+        $appManager = Server::get(\OCP\App\IAppManager::class);
1009
+
1010
+        // Always load authentication apps
1011
+        $appManager->loadApps(['authentication']);
1012
+        $appManager->loadApps(['extended_authentication']);
1013
+
1014
+        // Load minimum set of apps
1015
+        if (!\OCP\Util::needUpgrade()
1016
+            && !((bool)$systemConfig->getValue('maintenance', false))) {
1017
+            // For logged-in users: Load everything
1018
+            if (Server::get(IUserSession::class)->isLoggedIn()) {
1019
+                $appManager->loadApps();
1020
+            } else {
1021
+                // For guests: Load only filesystem and logging
1022
+                $appManager->loadApps(['filesystem', 'logging']);
1023
+
1024
+                // Don't try to login when a client is trying to get a OAuth token.
1025
+                // OAuth needs to support basic auth too, so the login is not valid
1026
+                // inside Nextcloud and the Login exception would ruin it.
1027
+                if ($request->getRawPathInfo() !== '/apps/oauth2/api/v1/token') {
1028
+                    try {
1029
+                        self::handleLogin($request);
1030
+                    } catch (DisabledUserException $e) {
1031
+                        // Disabled users would not be seen as logged in and
1032
+                        // trying to log them in would fail, so the login
1033
+                        // exception is ignored for the themed stylesheets and
1034
+                        // images.
1035
+                        if ($request->getRawPathInfo() !== '/apps/theming/theme/default.css'
1036
+                            && $request->getRawPathInfo() !== '/apps/theming/theme/light.css'
1037
+                            && $request->getRawPathInfo() !== '/apps/theming/theme/dark.css'
1038
+                            && $request->getRawPathInfo() !== '/apps/theming/theme/light-highcontrast.css'
1039
+                            && $request->getRawPathInfo() !== '/apps/theming/theme/dark-highcontrast.css'
1040
+                            && $request->getRawPathInfo() !== '/apps/theming/theme/opendyslexic.css'
1041
+                            && $request->getRawPathInfo() !== '/apps/theming/image/background'
1042
+                            && $request->getRawPathInfo() !== '/apps/theming/image/logo'
1043
+                            && $request->getRawPathInfo() !== '/apps/theming/image/logoheader'
1044
+                            && !str_starts_with($request->getRawPathInfo(), '/apps/theming/favicon')
1045
+                            && !str_starts_with($request->getRawPathInfo(), '/apps/theming/icon')) {
1046
+                            throw $e;
1047
+                        }
1048
+                    }
1049
+                }
1050
+            }
1051
+        }
1052
+
1053
+        if (!self::$CLI) {
1054
+            try {
1055
+                if (!\OCP\Util::needUpgrade()) {
1056
+                    $appManager->loadApps(['filesystem', 'logging']);
1057
+                    $appManager->loadApps();
1058
+                }
1059
+                Server::get(\OC\Route\Router::class)->match($request->getRawPathInfo());
1060
+                return;
1061
+            } catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {
1062
+                //header('HTTP/1.0 404 Not Found');
1063
+            } catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {
1064
+                http_response_code(405);
1065
+                return;
1066
+            }
1067
+        }
1068
+
1069
+        // Handle WebDAV
1070
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
1071
+            // not allowed any more to prevent people
1072
+            // mounting this root directly.
1073
+            // Users need to mount remote.php/webdav instead.
1074
+            http_response_code(405);
1075
+            return;
1076
+        }
1077
+
1078
+        // Handle requests for JSON or XML
1079
+        $acceptHeader = $request->getHeader('Accept');
1080
+        if (in_array($acceptHeader, ['application/json', 'application/xml'], true)) {
1081
+            http_response_code(404);
1082
+            return;
1083
+        }
1084
+
1085
+        // Handle resources that can't be found
1086
+        // This prevents browsers from redirecting to the default page and then
1087
+        // attempting to parse HTML as CSS and similar.
1088
+        $destinationHeader = $request->getHeader('Sec-Fetch-Dest');
1089
+        if (in_array($destinationHeader, ['font', 'script', 'style'])) {
1090
+            http_response_code(404);
1091
+            return;
1092
+        }
1093
+
1094
+        // Redirect to the default app or login only as an entry point
1095
+        if ($requestPath === '') {
1096
+            // Someone is logged in
1097
+            if (Server::get(IUserSession::class)->isLoggedIn()) {
1098
+                header('Location: ' . Server::get(IURLGenerator::class)->linkToDefaultPageUrl());
1099
+            } else {
1100
+                // Not handled and not logged in
1101
+                header('Location: ' . Server::get(IURLGenerator::class)->linkToRouteAbsolute('core.login.showLoginForm'));
1102
+            }
1103
+            return;
1104
+        }
1105
+
1106
+        try {
1107
+            Server::get(\OC\Route\Router::class)->match('/error/404');
1108
+        } catch (\Exception $e) {
1109
+            if (!$e instanceof MethodNotAllowedException) {
1110
+                logger('core')->emergency($e->getMessage(), ['exception' => $e]);
1111
+            }
1112
+            $l = Server::get(\OCP\L10N\IFactory::class)->get('lib');
1113
+            Server::get(ITemplateManager::class)->printErrorPage(
1114
+                '404',
1115
+                $l->t('The page could not be found on the server.'),
1116
+                404
1117
+            );
1118
+        }
1119
+    }
1120
+
1121
+    /**
1122
+     * Check login: apache auth, auth token, basic auth
1123
+     */
1124
+    public static function handleLogin(OCP\IRequest $request): bool {
1125
+        if ($request->getHeader('X-Nextcloud-Federation')) {
1126
+            return false;
1127
+        }
1128
+        $userSession = Server::get(\OC\User\Session::class);
1129
+        if (OC_User::handleApacheAuth()) {
1130
+            return true;
1131
+        }
1132
+        if (self::tryAppAPILogin($request)) {
1133
+            return true;
1134
+        }
1135
+        if ($userSession->tryTokenLogin($request)) {
1136
+            return true;
1137
+        }
1138
+        if (isset($_COOKIE['nc_username'])
1139
+            && isset($_COOKIE['nc_token'])
1140
+            && isset($_COOKIE['nc_session_id'])
1141
+            && $userSession->loginWithCookie($_COOKIE['nc_username'], $_COOKIE['nc_token'], $_COOKIE['nc_session_id'])) {
1142
+            return true;
1143
+        }
1144
+        if ($userSession->tryBasicAuthLogin($request, Server::get(IThrottler::class))) {
1145
+            return true;
1146
+        }
1147
+        return false;
1148
+    }
1149
+
1150
+    protected static function handleAuthHeaders(): void {
1151
+        //copy http auth headers for apache+php-fcgid work around
1152
+        if (isset($_SERVER['HTTP_XAUTHORIZATION']) && !isset($_SERVER['HTTP_AUTHORIZATION'])) {
1153
+            $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_XAUTHORIZATION'];
1154
+        }
1155
+
1156
+        // Extract PHP_AUTH_USER/PHP_AUTH_PW from other headers if necessary.
1157
+        $vars = [
1158
+            'HTTP_AUTHORIZATION', // apache+php-cgi work around
1159
+            'REDIRECT_HTTP_AUTHORIZATION', // apache+php-cgi alternative
1160
+        ];
1161
+        foreach ($vars as $var) {
1162
+            if (isset($_SERVER[$var]) && is_string($_SERVER[$var]) && preg_match('/Basic\s+(.*)$/i', $_SERVER[$var], $matches)) {
1163
+                $credentials = explode(':', base64_decode($matches[1]), 2);
1164
+                if (count($credentials) === 2) {
1165
+                    $_SERVER['PHP_AUTH_USER'] = $credentials[0];
1166
+                    $_SERVER['PHP_AUTH_PW'] = $credentials[1];
1167
+                    break;
1168
+                }
1169
+            }
1170
+        }
1171
+    }
1172
+
1173
+    protected static function tryAppAPILogin(OCP\IRequest $request): bool {
1174
+        if (!$request->getHeader('AUTHORIZATION-APP-API')) {
1175
+            return false;
1176
+        }
1177
+        $appManager = Server::get(OCP\App\IAppManager::class);
1178
+        if (!$appManager->isEnabledForAnyone('app_api')) {
1179
+            return false;
1180
+        }
1181
+        try {
1182
+            $appAPIService = Server::get(OCA\AppAPI\Service\AppAPIService::class);
1183
+            return $appAPIService->validateExAppRequestToNC($request);
1184
+        } catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
1185
+            return false;
1186
+        }
1187
+    }
1188 1188
 }
1189 1189
 
1190 1190
 OC::init();
Please login to merge, or discard this patch.
core/AppInfo/Application.php 1 patch
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -41,50 +41,50 @@
 block discarded – undo
41 41
  */
42 42
 class Application extends App implements IBootstrap {
43 43
 
44
-	public const APP_ID = 'core';
44
+    public const APP_ID = 'core';
45 45
 
46
-	/**
47
-	 * Application constructor.
48
-	 */
49
-	public function __construct(array $urlParams = []) {
50
-		parent::__construct(self::APP_ID, $urlParams);
51
-	}
46
+    /**
47
+     * Application constructor.
48
+     */
49
+    public function __construct(array $urlParams = []) {
50
+        parent::__construct(self::APP_ID, $urlParams);
51
+    }
52 52
 
53
-	public function register(IRegistrationContext $context): void {
54
-		$context->registerService('defaultMailAddress', function () {
55
-			return Util::getDefaultEmailAddress('lostpassword-noreply');
56
-		});
53
+    public function register(IRegistrationContext $context): void {
54
+        $context->registerService('defaultMailAddress', function () {
55
+            return Util::getDefaultEmailAddress('lostpassword-noreply');
56
+        });
57 57
 
58
-		// register notifier
59
-		$context->registerNotifierService(CoreNotifier::class);
60
-		$context->registerNotifierService(AuthenticationNotifier::class);
58
+        // register notifier
59
+        $context->registerNotifierService(CoreNotifier::class);
60
+        $context->registerNotifierService(AuthenticationNotifier::class);
61 61
 
62
-		// register event listeners
63
-		$context->registerEventListener(AddMissingIndicesEvent::class, AddMissingIndicesListener::class);
64
-		$context->registerEventListener(AddMissingPrimaryKeyEvent::class, AddMissingPrimaryKeyListener::class);
65
-		$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
66
-		$context->registerEventListener(BeforeLoginTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
67
-		$context->registerEventListener(RemoteWipeStarted::class, RemoteWipeActivityListener::class);
68
-		$context->registerEventListener(RemoteWipeStarted::class, RemoteWipeNotificationsListener::class);
69
-		$context->registerEventListener(RemoteWipeStarted::class, RemoteWipeEmailListener::class);
70
-		$context->registerEventListener(RemoteWipeFinished::class, RemoteWipeActivityListener::class);
71
-		$context->registerEventListener(RemoteWipeFinished::class, RemoteWipeNotificationsListener::class);
72
-		$context->registerEventListener(RemoteWipeFinished::class, RemoteWipeEmailListener::class);
73
-		$context->registerEventListener(UserDeletedEvent::class, UserDeletedStoreCleanupListener::class);
74
-		$context->registerEventListener(UserDeletedEvent::class, UserDeletedTokenCleanupListener::class);
75
-		$context->registerEventListener(BeforeUserDeletedEvent::class, UserDeletedFilesCleanupListener::class);
76
-		$context->registerEventListener(UserDeletedEvent::class, UserDeletedFilesCleanupListener::class);
77
-		$context->registerEventListener(UserDeletedEvent::class, UserDeletedWebAuthnCleanupListener::class);
62
+        // register event listeners
63
+        $context->registerEventListener(AddMissingIndicesEvent::class, AddMissingIndicesListener::class);
64
+        $context->registerEventListener(AddMissingPrimaryKeyEvent::class, AddMissingPrimaryKeyListener::class);
65
+        $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
66
+        $context->registerEventListener(BeforeLoginTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
67
+        $context->registerEventListener(RemoteWipeStarted::class, RemoteWipeActivityListener::class);
68
+        $context->registerEventListener(RemoteWipeStarted::class, RemoteWipeNotificationsListener::class);
69
+        $context->registerEventListener(RemoteWipeStarted::class, RemoteWipeEmailListener::class);
70
+        $context->registerEventListener(RemoteWipeFinished::class, RemoteWipeActivityListener::class);
71
+        $context->registerEventListener(RemoteWipeFinished::class, RemoteWipeNotificationsListener::class);
72
+        $context->registerEventListener(RemoteWipeFinished::class, RemoteWipeEmailListener::class);
73
+        $context->registerEventListener(UserDeletedEvent::class, UserDeletedStoreCleanupListener::class);
74
+        $context->registerEventListener(UserDeletedEvent::class, UserDeletedTokenCleanupListener::class);
75
+        $context->registerEventListener(BeforeUserDeletedEvent::class, UserDeletedFilesCleanupListener::class);
76
+        $context->registerEventListener(UserDeletedEvent::class, UserDeletedFilesCleanupListener::class);
77
+        $context->registerEventListener(UserDeletedEvent::class, UserDeletedWebAuthnCleanupListener::class);
78 78
 
79
-		// Tags
80
-		$context->registerEventListener(UserDeletedEvent::class, TagManager::class);
79
+        // Tags
80
+        $context->registerEventListener(UserDeletedEvent::class, TagManager::class);
81 81
 
82
-		// config lexicon
83
-		$context->registerConfigLexicon(ConfigLexicon::class);
84
-	}
82
+        // config lexicon
83
+        $context->registerConfigLexicon(ConfigLexicon::class);
84
+    }
85 85
 
86
-	public function boot(IBootContext $context): void {
87
-		// ...
88
-	}
86
+    public function boot(IBootContext $context): void {
87
+        // ...
88
+    }
89 89
 
90 90
 }
Please login to merge, or discard this patch.
core/AppInfo/ConfigLexicon.php 1 patch
Indentation   +18 added lines, -18 removed lines patch added patch discarded remove patch
@@ -19,25 +19,25 @@
 block discarded – undo
19 19
  * Please Add & Manage your Config Keys in that file and keep the Lexicon up to date!
20 20
  */
21 21
 class ConfigLexicon implements IConfigLexicon {
22
-	public const SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES = 'shareapi_allow_federation_on_public_shares';
22
+    public const SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES = 'shareapi_allow_federation_on_public_shares';
23 23
 
24
-	public function getStrictness(): ConfigLexiconStrictness {
25
-		return ConfigLexiconStrictness::IGNORE;
26
-	}
24
+    public function getStrictness(): ConfigLexiconStrictness {
25
+        return ConfigLexiconStrictness::IGNORE;
26
+    }
27 27
 
28
-	public function getAppConfigs(): array {
29
-		return [
30
-			new ConfigLexiconEntry(
31
-				key: self::SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES,
32
-				type: ValueType::BOOL,
33
-				lazy: true,
34
-				defaultRaw: true,
35
-				definition: 'adds share permission to public shares to allow adding them to your Nextcloud (federation)',
36
-			),
37
-		];
38
-	}
28
+    public function getAppConfigs(): array {
29
+        return [
30
+            new ConfigLexiconEntry(
31
+                key: self::SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES,
32
+                type: ValueType::BOOL,
33
+                lazy: true,
34
+                defaultRaw: true,
35
+                definition: 'adds share permission to public shares to allow adding them to your Nextcloud (federation)',
36
+            ),
37
+        ];
38
+    }
39 39
 
40
-	public function getUserConfigs(): array {
41
-		return [];
42
-	}
40
+    public function getUserConfigs(): array {
41
+        return [];
42
+    }
43 43
 }
Please login to merge, or discard this patch.
apps/settings/lib/Settings/Admin/Sharing.php 1 patch
Indentation   +93 added lines, -93 removed lines patch added patch discarded remove patch
@@ -20,106 +20,106 @@
 block discarded – undo
20 20
 use OCP\Util;
21 21
 
22 22
 class Sharing implements IDelegatedSettings {
23
-	public function __construct(
24
-		private IConfig $config,
25
-		private IAppConfig $appConfig,
26
-		private IL10N $l,
27
-		private IManager $shareManager,
28
-		private IAppManager $appManager,
29
-		private IURLGenerator $urlGenerator,
30
-		private IInitialState $initialState,
31
-		private string $appName,
32
-	) {
33
-	}
23
+    public function __construct(
24
+        private IConfig $config,
25
+        private IAppConfig $appConfig,
26
+        private IL10N $l,
27
+        private IManager $shareManager,
28
+        private IAppManager $appManager,
29
+        private IURLGenerator $urlGenerator,
30
+        private IInitialState $initialState,
31
+        private string $appName,
32
+    ) {
33
+    }
34 34
 
35
-	/**
36
-	 * @return TemplateResponse
37
-	 */
38
-	public function getForm() {
39
-		$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
40
-		$linksExcludedGroups = $this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '');
41
-		$excludedPasswordGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
42
-		$onlyShareWithGroupMembersExcludeGroupList = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
35
+    /**
36
+     * @return TemplateResponse
37
+     */
38
+    public function getForm() {
39
+        $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
40
+        $linksExcludedGroups = $this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '');
41
+        $excludedPasswordGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
42
+        $onlyShareWithGroupMembersExcludeGroupList = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
43 43
 
44
-		$parameters = [
45
-			// Built-In Sharing
46
-			'enabled' => $this->getHumanBooleanConfig('core', 'shareapi_enabled', true),
47
-			'allowGroupSharing' => $this->getHumanBooleanConfig('core', 'shareapi_allow_group_sharing', true),
48
-			'allowLinks' => $this->getHumanBooleanConfig('core', 'shareapi_allow_links', true),
49
-			'allowLinksExcludeGroups' => json_decode($linksExcludedGroups, true) ?? [],
50
-			'allowPublicUpload' => $this->getHumanBooleanConfig('core', 'shareapi_allow_public_upload', true),
51
-			'allowResharing' => $this->getHumanBooleanConfig('core', 'shareapi_allow_resharing', true),
52
-			'allowShareDialogUserEnumeration' => $this->getHumanBooleanConfig('core', 'shareapi_allow_share_dialog_user_enumeration', true),
53
-			'allowFederationOnPublicShares' => $this->appConfig->getValueBool('core', ConfigLexicon::SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES),
54
-			'restrictUserEnumerationToGroup' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_to_group'),
55
-			'restrictUserEnumerationToPhone' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_to_phone'),
56
-			'restrictUserEnumerationFullMatch' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match', true),
57
-			'restrictUserEnumerationFullMatchUserId' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match_userid', true),
58
-			'restrictUserEnumerationFullMatchEmail' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match_email', true),
59
-			'restrictUserEnumerationFullMatchIgnoreSecondDN' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn'),
60
-			'enforceLinksPassword' => Util::isPublicLinkPasswordRequired(false),
61
-			'enforceLinksPasswordExcludedGroups' => json_decode($excludedPasswordGroups) ?? [],
62
-			'enforceLinksPasswordExcludedGroupsEnabled' => $this->config->getSystemValueBool('sharing.allow_disabled_password_enforcement_groups', false),
63
-			'onlyShareWithGroupMembers' => $this->shareManager->shareWithGroupMembersOnly(),
64
-			'onlyShareWithGroupMembersExcludeGroupList' => json_decode($onlyShareWithGroupMembersExcludeGroupList) ?? [],
65
-			'defaultExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_expire_date'),
66
-			'expireAfterNDays' => $this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'),
67
-			'enforceExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_expire_date'),
68
-			'excludeGroups' => $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no'),
69
-			'excludeGroupsList' => json_decode($excludedGroups, true) ?? [],
70
-			'publicShareDisclaimerText' => $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext'),
71
-			'enableLinkPasswordByDefault' => $this->getHumanBooleanConfig('core', 'shareapi_enable_link_password_by_default'),
72
-			'defaultPermissions' => (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL),
73
-			'defaultInternalExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_internal_expire_date'),
74
-			'internalExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7'),
75
-			'enforceInternalExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_internal_expire_date'),
76
-			'defaultRemoteExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_remote_expire_date'),
77
-			'remoteExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7'),
78
-			'enforceRemoteExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_remote_expire_date'),
79
-			'allowCustomTokens' => $this->shareManager->allowCustomTokens(),
80
-			'allowViewWithoutDownload' => $this->shareManager->allowViewWithoutDownload(),
81
-		];
44
+        $parameters = [
45
+            // Built-In Sharing
46
+            'enabled' => $this->getHumanBooleanConfig('core', 'shareapi_enabled', true),
47
+            'allowGroupSharing' => $this->getHumanBooleanConfig('core', 'shareapi_allow_group_sharing', true),
48
+            'allowLinks' => $this->getHumanBooleanConfig('core', 'shareapi_allow_links', true),
49
+            'allowLinksExcludeGroups' => json_decode($linksExcludedGroups, true) ?? [],
50
+            'allowPublicUpload' => $this->getHumanBooleanConfig('core', 'shareapi_allow_public_upload', true),
51
+            'allowResharing' => $this->getHumanBooleanConfig('core', 'shareapi_allow_resharing', true),
52
+            'allowShareDialogUserEnumeration' => $this->getHumanBooleanConfig('core', 'shareapi_allow_share_dialog_user_enumeration', true),
53
+            'allowFederationOnPublicShares' => $this->appConfig->getValueBool('core', ConfigLexicon::SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES),
54
+            'restrictUserEnumerationToGroup' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_to_group'),
55
+            'restrictUserEnumerationToPhone' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_to_phone'),
56
+            'restrictUserEnumerationFullMatch' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match', true),
57
+            'restrictUserEnumerationFullMatchUserId' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match_userid', true),
58
+            'restrictUserEnumerationFullMatchEmail' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match_email', true),
59
+            'restrictUserEnumerationFullMatchIgnoreSecondDN' => $this->getHumanBooleanConfig('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn'),
60
+            'enforceLinksPassword' => Util::isPublicLinkPasswordRequired(false),
61
+            'enforceLinksPasswordExcludedGroups' => json_decode($excludedPasswordGroups) ?? [],
62
+            'enforceLinksPasswordExcludedGroupsEnabled' => $this->config->getSystemValueBool('sharing.allow_disabled_password_enforcement_groups', false),
63
+            'onlyShareWithGroupMembers' => $this->shareManager->shareWithGroupMembersOnly(),
64
+            'onlyShareWithGroupMembersExcludeGroupList' => json_decode($onlyShareWithGroupMembersExcludeGroupList) ?? [],
65
+            'defaultExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_expire_date'),
66
+            'expireAfterNDays' => $this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'),
67
+            'enforceExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_expire_date'),
68
+            'excludeGroups' => $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no'),
69
+            'excludeGroupsList' => json_decode($excludedGroups, true) ?? [],
70
+            'publicShareDisclaimerText' => $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext'),
71
+            'enableLinkPasswordByDefault' => $this->getHumanBooleanConfig('core', 'shareapi_enable_link_password_by_default'),
72
+            'defaultPermissions' => (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL),
73
+            'defaultInternalExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_internal_expire_date'),
74
+            'internalExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7'),
75
+            'enforceInternalExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_internal_expire_date'),
76
+            'defaultRemoteExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_default_remote_expire_date'),
77
+            'remoteExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7'),
78
+            'enforceRemoteExpireDate' => $this->getHumanBooleanConfig('core', 'shareapi_enforce_remote_expire_date'),
79
+            'allowCustomTokens' => $this->shareManager->allowCustomTokens(),
80
+            'allowViewWithoutDownload' => $this->shareManager->allowViewWithoutDownload(),
81
+        ];
82 82
 
83
-		$this->initialState->provideInitialState('sharingAppEnabled', $this->appManager->isEnabledForUser('files_sharing'));
84
-		$this->initialState->provideInitialState('sharingDocumentation', $this->urlGenerator->linkToDocs('admin-sharing'));
85
-		$this->initialState->provideInitialState('sharingSettings', $parameters);
83
+        $this->initialState->provideInitialState('sharingAppEnabled', $this->appManager->isEnabledForUser('files_sharing'));
84
+        $this->initialState->provideInitialState('sharingDocumentation', $this->urlGenerator->linkToDocs('admin-sharing'));
85
+        $this->initialState->provideInitialState('sharingSettings', $parameters);
86 86
 
87
-		Util::addScript($this->appName, 'vue-settings-admin-sharing');
88
-		return new TemplateResponse($this->appName, 'settings/admin/sharing', [], '');
89
-	}
87
+        Util::addScript($this->appName, 'vue-settings-admin-sharing');
88
+        return new TemplateResponse($this->appName, 'settings/admin/sharing', [], '');
89
+    }
90 90
 
91
-	/**
92
-	 * Helper function to retrive boolean values from human readable strings ('yes' / 'no')
93
-	 */
94
-	private function getHumanBooleanConfig(string $app, string $key, bool $default = false): bool {
95
-		return $this->config->getAppValue($app, $key, $default ? 'yes' : 'no') === 'yes';
96
-	}
91
+    /**
92
+     * Helper function to retrive boolean values from human readable strings ('yes' / 'no')
93
+     */
94
+    private function getHumanBooleanConfig(string $app, string $key, bool $default = false): bool {
95
+        return $this->config->getAppValue($app, $key, $default ? 'yes' : 'no') === 'yes';
96
+    }
97 97
 
98
-	/**
99
-	 * @return string the section ID, e.g. 'sharing'
100
-	 */
101
-	public function getSection() {
102
-		return 'sharing';
103
-	}
98
+    /**
99
+     * @return string the section ID, e.g. 'sharing'
100
+     */
101
+    public function getSection() {
102
+        return 'sharing';
103
+    }
104 104
 
105
-	/**
106
-	 * @return int whether the form should be rather on the top or bottom of
107
-	 *             the admin section. The forms are arranged in ascending order of the
108
-	 *             priority values. It is required to return a value between 0 and 100.
109
-	 *
110
-	 * E.g.: 70
111
-	 */
112
-	public function getPriority() {
113
-		return 0;
114
-	}
105
+    /**
106
+     * @return int whether the form should be rather on the top or bottom of
107
+     *             the admin section. The forms are arranged in ascending order of the
108
+     *             priority values. It is required to return a value between 0 and 100.
109
+     *
110
+     * E.g.: 70
111
+     */
112
+    public function getPriority() {
113
+        return 0;
114
+    }
115 115
 
116
-	public function getAuthorizedAppConfig(): array {
117
-		return [
118
-			'core' => ['/shareapi_.*/'],
119
-		];
120
-	}
116
+    public function getAuthorizedAppConfig(): array {
117
+        return [
118
+            'core' => ['/shareapi_.*/'],
119
+        ];
120
+    }
121 121
 
122
-	public function getName(): ?string {
123
-		return null;
124
-	}
122
+    public function getName(): ?string {
123
+        return null;
124
+    }
125 125
 }
Please login to merge, or discard this patch.
apps/settings/tests/Settings/Admin/SharingTest.php 1 patch
Indentation   +222 added lines, -222 removed lines patch added patch discarded remove patch
@@ -20,245 +20,245 @@
 block discarded – undo
20 20
 use Test\TestCase;
21 21
 
22 22
 class SharingTest extends TestCase {
23
-	private Sharing $admin;
23
+    private Sharing $admin;
24 24
 
25
-	private IConfig&MockObject $config;
26
-	private IAppConfig&MockObject $appConfig;
27
-	private IL10N&MockObject $l10n;
28
-	private IManager&MockObject $shareManager;
29
-	private IAppManager&MockObject $appManager;
30
-	private IURLGenerator&MockObject $urlGenerator;
31
-	private IInitialState&MockObject $initialState;
25
+    private IConfig&MockObject $config;
26
+    private IAppConfig&MockObject $appConfig;
27
+    private IL10N&MockObject $l10n;
28
+    private IManager&MockObject $shareManager;
29
+    private IAppManager&MockObject $appManager;
30
+    private IURLGenerator&MockObject $urlGenerator;
31
+    private IInitialState&MockObject $initialState;
32 32
 
33
-	protected function setUp(): void {
34
-		parent::setUp();
35
-		$this->config = $this->createMock(IConfig::class);
36
-		$this->appConfig = $this->createMock(IAppConfig::class);
37
-		$this->l10n = $this->createMock(IL10N::class);
33
+    protected function setUp(): void {
34
+        parent::setUp();
35
+        $this->config = $this->createMock(IConfig::class);
36
+        $this->appConfig = $this->createMock(IAppConfig::class);
37
+        $this->l10n = $this->createMock(IL10N::class);
38 38
 
39
-		$this->shareManager = $this->createMock(IManager::class);
40
-		$this->appManager = $this->createMock(IAppManager::class);
41
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
42
-		$this->initialState = $this->createMock(IInitialState::class);
39
+        $this->shareManager = $this->createMock(IManager::class);
40
+        $this->appManager = $this->createMock(IAppManager::class);
41
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
42
+        $this->initialState = $this->createMock(IInitialState::class);
43 43
 
44
-		$this->admin = new Sharing(
45
-			$this->config,
46
-			$this->appConfig,
47
-			$this->l10n,
48
-			$this->shareManager,
49
-			$this->appManager,
50
-			$this->urlGenerator,
51
-			$this->initialState,
52
-			'settings',
53
-		);
54
-	}
44
+        $this->admin = new Sharing(
45
+            $this->config,
46
+            $this->appConfig,
47
+            $this->l10n,
48
+            $this->shareManager,
49
+            $this->appManager,
50
+            $this->urlGenerator,
51
+            $this->initialState,
52
+            'settings',
53
+        );
54
+    }
55 55
 
56
-	public function testGetFormWithoutExcludedGroups(): void {
57
-		$this->appConfig
58
-			->method('getValueBool')
59
-			->willReturnMap([
60
-				['core', 'shareapi_allow_federation_on_public_shares', false, false, true],
61
-			]);
56
+    public function testGetFormWithoutExcludedGroups(): void {
57
+        $this->appConfig
58
+            ->method('getValueBool')
59
+            ->willReturnMap([
60
+                ['core', 'shareapi_allow_federation_on_public_shares', false, false, true],
61
+            ]);
62 62
 
63
-		$this->config
64
-			->method('getAppValue')
65
-			->willReturnMap([
66
-				['core', 'shareapi_exclude_groups_list', '', ''],
67
-				['core', 'shareapi_allow_links_exclude_groups', '', ''],
68
-				['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
69
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
70
-				['core', 'shareapi_allow_public_upload', 'yes', 'yes'],
71
-				['core', 'shareapi_allow_resharing', 'yes', 'yes'],
72
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
73
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
74
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
75
-				['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
76
-				['core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes', 'yes'],
77
-				['core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes', 'yes'],
78
-				['core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no', 'no'],
79
-				['core', 'shareapi_enabled', 'yes', 'yes'],
80
-				['core', 'shareapi_default_expire_date', 'no', 'no'],
81
-				['core', 'shareapi_expire_after_n_days', '7', '7'],
82
-				['core', 'shareapi_enforce_expire_date', 'no', 'no'],
83
-				['core', 'shareapi_exclude_groups', 'no', 'no'],
84
-				['core', 'shareapi_public_link_disclaimertext', '', 'Lorem ipsum'],
85
-				['core', 'shareapi_enable_link_password_by_default', 'no', 'yes'],
86
-				['core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL, Constants::PERMISSION_ALL],
87
-				['core', 'shareapi_default_internal_expire_date', 'no', 'no'],
88
-				['core', 'shareapi_internal_expire_after_n_days', '7', '7'],
89
-				['core', 'shareapi_enforce_internal_expire_date', 'no', 'no'],
90
-				['core', 'shareapi_default_remote_expire_date', 'no', 'no'],
91
-				['core', 'shareapi_remote_expire_after_n_days', '7', '7'],
92
-				['core', 'shareapi_enforce_remote_expire_date', 'no', 'no'],
93
-				['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
94
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
95
-			]);
96
-		$this->shareManager->method('shareWithGroupMembersOnly')
97
-			->willReturn(false);
63
+        $this->config
64
+            ->method('getAppValue')
65
+            ->willReturnMap([
66
+                ['core', 'shareapi_exclude_groups_list', '', ''],
67
+                ['core', 'shareapi_allow_links_exclude_groups', '', ''],
68
+                ['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
69
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
70
+                ['core', 'shareapi_allow_public_upload', 'yes', 'yes'],
71
+                ['core', 'shareapi_allow_resharing', 'yes', 'yes'],
72
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
73
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
74
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
75
+                ['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
76
+                ['core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes', 'yes'],
77
+                ['core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes', 'yes'],
78
+                ['core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no', 'no'],
79
+                ['core', 'shareapi_enabled', 'yes', 'yes'],
80
+                ['core', 'shareapi_default_expire_date', 'no', 'no'],
81
+                ['core', 'shareapi_expire_after_n_days', '7', '7'],
82
+                ['core', 'shareapi_enforce_expire_date', 'no', 'no'],
83
+                ['core', 'shareapi_exclude_groups', 'no', 'no'],
84
+                ['core', 'shareapi_public_link_disclaimertext', '', 'Lorem ipsum'],
85
+                ['core', 'shareapi_enable_link_password_by_default', 'no', 'yes'],
86
+                ['core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL, Constants::PERMISSION_ALL],
87
+                ['core', 'shareapi_default_internal_expire_date', 'no', 'no'],
88
+                ['core', 'shareapi_internal_expire_after_n_days', '7', '7'],
89
+                ['core', 'shareapi_enforce_internal_expire_date', 'no', 'no'],
90
+                ['core', 'shareapi_default_remote_expire_date', 'no', 'no'],
91
+                ['core', 'shareapi_remote_expire_after_n_days', '7', '7'],
92
+                ['core', 'shareapi_enforce_remote_expire_date', 'no', 'no'],
93
+                ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
94
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
95
+            ]);
96
+        $this->shareManager->method('shareWithGroupMembersOnly')
97
+            ->willReturn(false);
98 98
 
99
-		$this->appManager->method('isEnabledForUser')->with('files_sharing')->willReturn(false);
99
+        $this->appManager->method('isEnabledForUser')->with('files_sharing')->willReturn(false);
100 100
 
101
-		$initialStateCalls = [];
102
-		$this->initialState
103
-			->expects($this->exactly(3))
104
-			->method('provideInitialState')
105
-			->willReturnCallback(function (string $key) use (&$initialStateCalls): void {
106
-				$initialStateCalls[$key] = func_get_args();
107
-			});
101
+        $initialStateCalls = [];
102
+        $this->initialState
103
+            ->expects($this->exactly(3))
104
+            ->method('provideInitialState')
105
+            ->willReturnCallback(function (string $key) use (&$initialStateCalls): void {
106
+                $initialStateCalls[$key] = func_get_args();
107
+            });
108 108
 
109
-		$expectedInitialStateCalls = [
110
-			'sharingAppEnabled' => false,
111
-			'sharingDocumentation' => '',
112
-			'sharingSettings' => [
113
-				'allowGroupSharing' => true,
114
-				'allowLinks' => true,
115
-				'allowPublicUpload' => true,
116
-				'allowResharing' => true,
117
-				'allowShareDialogUserEnumeration' => true,
118
-				'allowFederationOnPublicShares' => true,
119
-				'restrictUserEnumerationToGroup' => false,
120
-				'restrictUserEnumerationToPhone' => false,
121
-				'restrictUserEnumerationFullMatch' => true,
122
-				'restrictUserEnumerationFullMatchUserId' => true,
123
-				'restrictUserEnumerationFullMatchEmail' => true,
124
-				'restrictUserEnumerationFullMatchIgnoreSecondDN' => false,
125
-				'enforceLinksPassword' => false,
126
-				'onlyShareWithGroupMembers' => false,
127
-				'enabled' => true,
128
-				'defaultExpireDate' => false,
129
-				'expireAfterNDays' => '7',
130
-				'enforceExpireDate' => false,
131
-				'excludeGroups' => 'no',
132
-				'excludeGroupsList' => [],
133
-				'publicShareDisclaimerText' => 'Lorem ipsum',
134
-				'enableLinkPasswordByDefault' => true,
135
-				'defaultPermissions' => Constants::PERMISSION_ALL,
136
-				'defaultInternalExpireDate' => false,
137
-				'internalExpireAfterNDays' => '7',
138
-				'enforceInternalExpireDate' => false,
139
-				'defaultRemoteExpireDate' => false,
140
-				'remoteExpireAfterNDays' => '7',
141
-				'enforceRemoteExpireDate' => false,
142
-				'allowLinksExcludeGroups' => [],
143
-				'onlyShareWithGroupMembersExcludeGroupList' => [],
144
-				'enforceLinksPasswordExcludedGroups' => [],
145
-				'enforceLinksPasswordExcludedGroupsEnabled' => false,
146
-			]
147
-		];
109
+        $expectedInitialStateCalls = [
110
+            'sharingAppEnabled' => false,
111
+            'sharingDocumentation' => '',
112
+            'sharingSettings' => [
113
+                'allowGroupSharing' => true,
114
+                'allowLinks' => true,
115
+                'allowPublicUpload' => true,
116
+                'allowResharing' => true,
117
+                'allowShareDialogUserEnumeration' => true,
118
+                'allowFederationOnPublicShares' => true,
119
+                'restrictUserEnumerationToGroup' => false,
120
+                'restrictUserEnumerationToPhone' => false,
121
+                'restrictUserEnumerationFullMatch' => true,
122
+                'restrictUserEnumerationFullMatchUserId' => true,
123
+                'restrictUserEnumerationFullMatchEmail' => true,
124
+                'restrictUserEnumerationFullMatchIgnoreSecondDN' => false,
125
+                'enforceLinksPassword' => false,
126
+                'onlyShareWithGroupMembers' => false,
127
+                'enabled' => true,
128
+                'defaultExpireDate' => false,
129
+                'expireAfterNDays' => '7',
130
+                'enforceExpireDate' => false,
131
+                'excludeGroups' => 'no',
132
+                'excludeGroupsList' => [],
133
+                'publicShareDisclaimerText' => 'Lorem ipsum',
134
+                'enableLinkPasswordByDefault' => true,
135
+                'defaultPermissions' => Constants::PERMISSION_ALL,
136
+                'defaultInternalExpireDate' => false,
137
+                'internalExpireAfterNDays' => '7',
138
+                'enforceInternalExpireDate' => false,
139
+                'defaultRemoteExpireDate' => false,
140
+                'remoteExpireAfterNDays' => '7',
141
+                'enforceRemoteExpireDate' => false,
142
+                'allowLinksExcludeGroups' => [],
143
+                'onlyShareWithGroupMembersExcludeGroupList' => [],
144
+                'enforceLinksPasswordExcludedGroups' => [],
145
+                'enforceLinksPasswordExcludedGroupsEnabled' => false,
146
+            ]
147
+        ];
148 148
 
149
-		$expected = new TemplateResponse(
150
-			'settings',
151
-			'settings/admin/sharing',
152
-			[],
153
-			''
154
-		);
149
+        $expected = new TemplateResponse(
150
+            'settings',
151
+            'settings/admin/sharing',
152
+            [],
153
+            ''
154
+        );
155 155
 
156
-		$this->assertEquals($expected, $this->admin->getForm());
157
-		$this->assertEquals(sort($expectedInitialStateCalls), sort($initialStateCalls), 'Provided initial state does not match');
158
-	}
156
+        $this->assertEquals($expected, $this->admin->getForm());
157
+        $this->assertEquals(sort($expectedInitialStateCalls), sort($initialStateCalls), 'Provided initial state does not match');
158
+    }
159 159
 
160
-	public function testGetFormWithExcludedGroups(): void {
161
-		$this->config
162
-			->method('getAppValue')
163
-			->willReturnMap([
164
-				['core', 'shareapi_exclude_groups_list', '', '["NoSharers","OtherNoSharers"]'],
165
-				['core', 'shareapi_allow_links_exclude_groups', '', ''],
166
-				['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
167
-				['core', 'shareapi_allow_links', 'yes', 'yes'],
168
-				['core', 'shareapi_allow_public_upload', 'yes', 'yes'],
169
-				['core', 'shareapi_allow_resharing', 'yes', 'yes'],
170
-				['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
171
-				['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
172
-				['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
173
-				['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
174
-				['core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes', 'yes'],
175
-				['core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes', 'yes'],
176
-				['core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no', 'no'],
177
-				['core', 'shareapi_enabled', 'yes', 'yes'],
178
-				['core', 'shareapi_default_expire_date', 'no', 'no'],
179
-				['core', 'shareapi_expire_after_n_days', '7', '7'],
180
-				['core', 'shareapi_enforce_expire_date', 'no', 'no'],
181
-				['core', 'shareapi_exclude_groups', 'no', 'yes'],
182
-				['core', 'shareapi_public_link_disclaimertext', '', 'Lorem ipsum'],
183
-				['core', 'shareapi_enable_link_password_by_default', 'no', 'yes'],
184
-				['core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL, Constants::PERMISSION_ALL],
185
-				['core', 'shareapi_default_internal_expire_date', 'no', 'no'],
186
-				['core', 'shareapi_internal_expire_after_n_days', '7', '7'],
187
-				['core', 'shareapi_enforce_internal_expire_date', 'no', 'no'],
188
-				['core', 'shareapi_default_remote_expire_date', 'no', 'no'],
189
-				['core', 'shareapi_remote_expire_after_n_days', '7', '7'],
190
-				['core', 'shareapi_enforce_remote_expire_date', 'no', 'no'],
191
-				['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
192
-				['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
193
-			]);
194
-		$this->shareManager->method('shareWithGroupMembersOnly')
195
-			->willReturn(false);
160
+    public function testGetFormWithExcludedGroups(): void {
161
+        $this->config
162
+            ->method('getAppValue')
163
+            ->willReturnMap([
164
+                ['core', 'shareapi_exclude_groups_list', '', '["NoSharers","OtherNoSharers"]'],
165
+                ['core', 'shareapi_allow_links_exclude_groups', '', ''],
166
+                ['core', 'shareapi_allow_group_sharing', 'yes', 'yes'],
167
+                ['core', 'shareapi_allow_links', 'yes', 'yes'],
168
+                ['core', 'shareapi_allow_public_upload', 'yes', 'yes'],
169
+                ['core', 'shareapi_allow_resharing', 'yes', 'yes'],
170
+                ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
171
+                ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
172
+                ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
173
+                ['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'],
174
+                ['core', 'shareapi_restrict_user_enumeration_full_match_userid', 'yes', 'yes'],
175
+                ['core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes', 'yes'],
176
+                ['core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no', 'no'],
177
+                ['core', 'shareapi_enabled', 'yes', 'yes'],
178
+                ['core', 'shareapi_default_expire_date', 'no', 'no'],
179
+                ['core', 'shareapi_expire_after_n_days', '7', '7'],
180
+                ['core', 'shareapi_enforce_expire_date', 'no', 'no'],
181
+                ['core', 'shareapi_exclude_groups', 'no', 'yes'],
182
+                ['core', 'shareapi_public_link_disclaimertext', '', 'Lorem ipsum'],
183
+                ['core', 'shareapi_enable_link_password_by_default', 'no', 'yes'],
184
+                ['core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL, Constants::PERMISSION_ALL],
185
+                ['core', 'shareapi_default_internal_expire_date', 'no', 'no'],
186
+                ['core', 'shareapi_internal_expire_after_n_days', '7', '7'],
187
+                ['core', 'shareapi_enforce_internal_expire_date', 'no', 'no'],
188
+                ['core', 'shareapi_default_remote_expire_date', 'no', 'no'],
189
+                ['core', 'shareapi_remote_expire_after_n_days', '7', '7'],
190
+                ['core', 'shareapi_enforce_remote_expire_date', 'no', 'no'],
191
+                ['core', 'shareapi_enforce_links_password_excluded_groups', '', ''],
192
+                ['core', 'shareapi_only_share_with_group_members_exclude_group_list', '', '[]'],
193
+            ]);
194
+        $this->shareManager->method('shareWithGroupMembersOnly')
195
+            ->willReturn(false);
196 196
 
197
-		$this->appManager->method('isEnabledForUser')->with('files_sharing')->willReturn(true);
197
+        $this->appManager->method('isEnabledForUser')->with('files_sharing')->willReturn(true);
198 198
 
199
-		$initialStateCalls = [];
200
-		$this->initialState
201
-			->expects($this->exactly(3))
202
-			->method('provideInitialState')
203
-			->willReturnCallback(function (string $key) use (&$initialStateCalls): void {
204
-				$initialStateCalls[$key] = func_get_args();
205
-			});
199
+        $initialStateCalls = [];
200
+        $this->initialState
201
+            ->expects($this->exactly(3))
202
+            ->method('provideInitialState')
203
+            ->willReturnCallback(function (string $key) use (&$initialStateCalls): void {
204
+                $initialStateCalls[$key] = func_get_args();
205
+            });
206 206
 
207
-		$expectedInitialStateCalls = [
208
-			'sharingAppEnabled' => true,
209
-			'sharingDocumentation' => '',
210
-			'sharingSettings' => [
211
-				'allowGroupSharing' => true,
212
-				'allowLinks' => true,
213
-				'allowPublicUpload' => true,
214
-				'allowResharing' => true,
215
-				'allowShareDialogUserEnumeration' => true,
216
-				'restrictUserEnumerationToGroup' => false,
217
-				'restrictUserEnumerationToPhone' => false,
218
-				'restrictUserEnumerationFullMatch' => true,
219
-				'restrictUserEnumerationFullMatchUserId' => true,
220
-				'restrictUserEnumerationFullMatchEmail' => true,
221
-				'restrictUserEnumerationFullMatchIgnoreSecondDN' => false,
222
-				'enforceLinksPassword' => false,
223
-				'onlyShareWithGroupMembers' => false,
224
-				'enabled' => true,
225
-				'defaultExpireDate' => false,
226
-				'expireAfterNDays' => '7',
227
-				'enforceExpireDate' => false,
228
-				'excludeGroups' => 'yes',
229
-				'excludeGroupsList' => ['NoSharers','OtherNoSharers'],
230
-				'publicShareDisclaimerText' => 'Lorem ipsum',
231
-				'enableLinkPasswordByDefault' => true,
232
-				'defaultPermissions' => Constants::PERMISSION_ALL,
233
-				'defaultInternalExpireDate' => false,
234
-				'internalExpireAfterNDays' => '7',
235
-				'enforceInternalExpireDate' => false,
236
-				'defaultRemoteExpireDate' => false,
237
-				'remoteExpireAfterNDays' => '7',
238
-				'enforceRemoteExpireDate' => false,
239
-				'allowLinksExcludeGroups' => [],
240
-				'onlyShareWithGroupMembersExcludeGroupList' => [],
241
-				'enforceLinksPasswordExcludedGroups' => [],
242
-				'enforceLinksPasswordExcludedGroupsEnabled' => false,
243
-			],
244
-		];
207
+        $expectedInitialStateCalls = [
208
+            'sharingAppEnabled' => true,
209
+            'sharingDocumentation' => '',
210
+            'sharingSettings' => [
211
+                'allowGroupSharing' => true,
212
+                'allowLinks' => true,
213
+                'allowPublicUpload' => true,
214
+                'allowResharing' => true,
215
+                'allowShareDialogUserEnumeration' => true,
216
+                'restrictUserEnumerationToGroup' => false,
217
+                'restrictUserEnumerationToPhone' => false,
218
+                'restrictUserEnumerationFullMatch' => true,
219
+                'restrictUserEnumerationFullMatchUserId' => true,
220
+                'restrictUserEnumerationFullMatchEmail' => true,
221
+                'restrictUserEnumerationFullMatchIgnoreSecondDN' => false,
222
+                'enforceLinksPassword' => false,
223
+                'onlyShareWithGroupMembers' => false,
224
+                'enabled' => true,
225
+                'defaultExpireDate' => false,
226
+                'expireAfterNDays' => '7',
227
+                'enforceExpireDate' => false,
228
+                'excludeGroups' => 'yes',
229
+                'excludeGroupsList' => ['NoSharers','OtherNoSharers'],
230
+                'publicShareDisclaimerText' => 'Lorem ipsum',
231
+                'enableLinkPasswordByDefault' => true,
232
+                'defaultPermissions' => Constants::PERMISSION_ALL,
233
+                'defaultInternalExpireDate' => false,
234
+                'internalExpireAfterNDays' => '7',
235
+                'enforceInternalExpireDate' => false,
236
+                'defaultRemoteExpireDate' => false,
237
+                'remoteExpireAfterNDays' => '7',
238
+                'enforceRemoteExpireDate' => false,
239
+                'allowLinksExcludeGroups' => [],
240
+                'onlyShareWithGroupMembersExcludeGroupList' => [],
241
+                'enforceLinksPasswordExcludedGroups' => [],
242
+                'enforceLinksPasswordExcludedGroupsEnabled' => false,
243
+            ],
244
+        ];
245 245
 
246
-		$expected = new TemplateResponse(
247
-			'settings',
248
-			'settings/admin/sharing',
249
-			[],
250
-			''
251
-		);
246
+        $expected = new TemplateResponse(
247
+            'settings',
248
+            'settings/admin/sharing',
249
+            [],
250
+            ''
251
+        );
252 252
 
253
-		$this->assertEquals($expected, $this->admin->getForm());
254
-		$this->assertEquals(sort($expectedInitialStateCalls), sort($initialStateCalls), 'Provided initial state does not match');
255
-	}
253
+        $this->assertEquals($expected, $this->admin->getForm());
254
+        $this->assertEquals(sort($expectedInitialStateCalls), sort($initialStateCalls), 'Provided initial state does not match');
255
+    }
256 256
 
257
-	public function testGetSection(): void {
258
-		$this->assertSame('sharing', $this->admin->getSection());
259
-	}
257
+    public function testGetSection(): void {
258
+        $this->assertSame('sharing', $this->admin->getSection());
259
+    }
260 260
 
261
-	public function testGetPriority(): void {
262
-		$this->assertSame(0, $this->admin->getPriority());
263
-	}
261
+    public function testGetPriority(): void {
262
+        $this->assertSame(0, $this->admin->getPriority());
263
+    }
264 264
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/Controller/ShareAPIController.php 1 patch
Indentation   +2185 added lines, -2185 removed lines patch added patch discarded remove patch
@@ -75,2192 +75,2192 @@
 block discarded – undo
75 75
  */
76 76
 class ShareAPIController extends OCSController {
77 77
 
78
-	private ?Node $lockedNode = null;
79
-
80
-	/**
81
-	 * Share20OCS constructor.
82
-	 */
83
-	public function __construct(
84
-		string $appName,
85
-		IRequest $request,
86
-		private IManager $shareManager,
87
-		private IGroupManager $groupManager,
88
-		private IUserManager $userManager,
89
-		private IRootFolder $rootFolder,
90
-		private IURLGenerator $urlGenerator,
91
-		private IL10N $l,
92
-		private IConfig $config,
93
-		private IAppConfig $appConfig,
94
-		private IAppManager $appManager,
95
-		private ContainerInterface $serverContainer,
96
-		private IUserStatusManager $userStatusManager,
97
-		private IPreview $previewManager,
98
-		private IDateTimeZone $dateTimeZone,
99
-		private LoggerInterface $logger,
100
-		private IProviderFactory $factory,
101
-		private IMailer $mailer,
102
-		private ITagManager $tagManager,
103
-		private ?string $userId = null,
104
-	) {
105
-		parent::__construct($appName, $request);
106
-	}
107
-
108
-	/**
109
-	 * Convert an IShare to an array for OCS output
110
-	 *
111
-	 * @param IShare $share
112
-	 * @param Node|null $recipientNode
113
-	 * @return Files_SharingShare
114
-	 * @throws NotFoundException In case the node can't be resolved.
115
-	 *
116
-	 * @suppress PhanUndeclaredClassMethod
117
-	 */
118
-	protected function formatShare(IShare $share, ?Node $recipientNode = null): array {
119
-		$sharedBy = $this->userManager->get($share->getSharedBy());
120
-		$shareOwner = $this->userManager->get($share->getShareOwner());
121
-
122
-		$isOwnShare = false;
123
-		if ($shareOwner !== null) {
124
-			$isOwnShare = $shareOwner->getUID() === $this->userId;
125
-		}
126
-
127
-		$result = [
128
-			'id' => $share->getId(),
129
-			'share_type' => $share->getShareType(),
130
-			'uid_owner' => $share->getSharedBy(),
131
-			'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
132
-			// recipient permissions
133
-			'permissions' => $share->getPermissions(),
134
-			// current user permissions on this share
135
-			'can_edit' => $this->canEditShare($share),
136
-			'can_delete' => $this->canDeleteShare($share),
137
-			'stime' => $share->getShareTime()->getTimestamp(),
138
-			'parent' => null,
139
-			'expiration' => null,
140
-			'token' => null,
141
-			'uid_file_owner' => $share->getShareOwner(),
142
-			'note' => $share->getNote(),
143
-			'label' => $share->getLabel(),
144
-			'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
145
-		];
146
-
147
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
148
-		if ($recipientNode) {
149
-			$node = $recipientNode;
150
-		} else {
151
-			$node = $userFolder->getFirstNodeById($share->getNodeId());
152
-			if (!$node) {
153
-				// fallback to guessing the path
154
-				$node = $userFolder->get($share->getTarget());
155
-				if ($node === null || $share->getTarget() === '') {
156
-					throw new NotFoundException();
157
-				}
158
-			}
159
-		}
160
-
161
-		$result['path'] = $userFolder->getRelativePath($node->getPath());
162
-		if ($node instanceof Folder) {
163
-			$result['item_type'] = 'folder';
164
-		} else {
165
-			$result['item_type'] = 'file';
166
-		}
167
-
168
-		// Get the original node permission if the share owner is the current user
169
-		if ($isOwnShare) {
170
-			$result['item_permissions'] = $node->getPermissions();
171
-		}
172
-
173
-		// If we're on the recipient side, the node permissions
174
-		// are bound to the share permissions. So we need to
175
-		// adjust the permissions to the share permissions if necessary.
176
-		if (!$isOwnShare) {
177
-			$result['item_permissions'] = $share->getPermissions();
178
-
179
-			// For some reason, single files share are forbidden to have the delete permission
180
-			// since we have custom methods to check those, let's adjust straight away.
181
-			// DAV permissions does not have that issue though.
182
-			if ($this->canDeleteShare($share) || $this->canDeleteShareFromSelf($share)) {
183
-				$result['item_permissions'] |= Constants::PERMISSION_DELETE;
184
-			}
185
-			if ($this->canEditShare($share)) {
186
-				$result['item_permissions'] |= Constants::PERMISSION_UPDATE;
187
-			}
188
-		}
189
-
190
-		// See MOUNT_ROOT_PROPERTYNAME dav property
191
-		$result['is-mount-root'] = $node->getInternalPath() === '';
192
-		$result['mount-type'] = $node->getMountPoint()->getMountType();
193
-
194
-		$result['mimetype'] = $node->getMimetype();
195
-		$result['has_preview'] = $this->previewManager->isAvailable($node);
196
-		$result['storage_id'] = $node->getStorage()->getId();
197
-		$result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
198
-		$result['item_source'] = $node->getId();
199
-		$result['file_source'] = $node->getId();
200
-		$result['file_parent'] = $node->getParent()->getId();
201
-		$result['file_target'] = $share->getTarget();
202
-		$result['item_size'] = $node->getSize();
203
-		$result['item_mtime'] = $node->getMTime();
204
-
205
-		$expiration = $share->getExpirationDate();
206
-		if ($expiration !== null) {
207
-			$expiration->setTimezone($this->dateTimeZone->getTimeZone());
208
-			$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
209
-		}
210
-
211
-		if ($share->getShareType() === IShare::TYPE_USER) {
212
-			$sharedWith = $this->userManager->get($share->getSharedWith());
213
-			$result['share_with'] = $share->getSharedWith();
214
-			$result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
215
-			$result['share_with_displayname_unique'] = $sharedWith !== null ? (
216
-				!empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID()
217
-			) : $share->getSharedWith();
218
-
219
-			$userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
220
-			$userStatus = array_shift($userStatuses);
221
-			if ($userStatus) {
222
-				$result['status'] = [
223
-					'status' => $userStatus->getStatus(),
224
-					'message' => $userStatus->getMessage(),
225
-					'icon' => $userStatus->getIcon(),
226
-					'clearAt' => $userStatus->getClearAt()
227
-						? (int)$userStatus->getClearAt()->format('U')
228
-						: null,
229
-				];
230
-			}
231
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
232
-			$group = $this->groupManager->get($share->getSharedWith());
233
-			$result['share_with'] = $share->getSharedWith();
234
-			$result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
235
-		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
236
-
237
-			// "share_with" and "share_with_displayname" for passwords of link
238
-			// shares was deprecated in Nextcloud 15, use "password" instead.
239
-			$result['share_with'] = $share->getPassword();
240
-			$result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
241
-
242
-			$result['password'] = $share->getPassword();
243
-
244
-			$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
245
-
246
-			$result['token'] = $share->getToken();
247
-			$result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
248
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
249
-			$result['share_with'] = $share->getSharedWith();
250
-			$result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith());
251
-			$result['token'] = $share->getToken();
252
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
253
-			$result['share_with'] = $share->getSharedWith();
254
-			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
255
-			$result['token'] = $share->getToken();
256
-		} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
257
-			$result['share_with'] = $share->getSharedWith();
258
-			$result['password'] = $share->getPassword();
259
-			$result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null;
260
-			$result['send_password_by_talk'] = $share->getSendPasswordByTalk();
261
-			$result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
262
-			$result['token'] = $share->getToken();
263
-		} elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
264
-			// getSharedWith() returns either "name (type, owner)" or
265
-			// "name (type, owner) [id]", depending on the Teams app version.
266
-			$hasCircleId = (substr($share->getSharedWith(), -1) === ']');
267
-
268
-			$result['share_with_displayname'] = $share->getSharedWithDisplayName();
269
-			if (empty($result['share_with_displayname'])) {
270
-				$displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
271
-				$result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
272
-			}
273
-
274
-			$result['share_with_avatar'] = $share->getSharedWithAvatar();
275
-
276
-			$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
277
-			$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
278
-			if ($shareWithLength === false) {
279
-				$result['share_with'] = substr($share->getSharedWith(), $shareWithStart);
280
-			} else {
281
-				$result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
282
-			}
283
-		} elseif ($share->getShareType() === IShare::TYPE_ROOM) {
284
-			$result['share_with'] = $share->getSharedWith();
285
-			$result['share_with_displayname'] = '';
286
-
287
-			try {
288
-				/** @var array{share_with_displayname: string, share_with_link: string, share_with?: string, token?: string} $roomShare */
289
-				$roomShare = $this->getRoomShareHelper()->formatShare($share);
290
-				$result = array_merge($result, $roomShare);
291
-			} catch (ContainerExceptionInterface $e) {
292
-			}
293
-		} elseif ($share->getShareType() === IShare::TYPE_DECK) {
294
-			$result['share_with'] = $share->getSharedWith();
295
-			$result['share_with_displayname'] = '';
296
-
297
-			try {
298
-				/** @var array{share_with: string, share_with_displayname: string, share_with_link: string} $deckShare */
299
-				$deckShare = $this->getDeckShareHelper()->formatShare($share);
300
-				$result = array_merge($result, $deckShare);
301
-			} catch (ContainerExceptionInterface $e) {
302
-			}
303
-		} elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
304
-			$result['share_with'] = $share->getSharedWith();
305
-			$result['share_with_displayname'] = '';
306
-
307
-			try {
308
-				/** @var array{share_with: string, share_with_displayname: string, token: string} $scienceMeshShare */
309
-				$scienceMeshShare = $this->getSciencemeshShareHelper()->formatShare($share);
310
-				$result = array_merge($result, $scienceMeshShare);
311
-			} catch (ContainerExceptionInterface $e) {
312
-			}
313
-		}
314
-
315
-
316
-		$result['mail_send'] = $share->getMailSend() ? 1 : 0;
317
-		$result['hide_download'] = $share->getHideDownload() ? 1 : 0;
318
-
319
-		$result['attributes'] = null;
320
-		if ($attributes = $share->getAttributes()) {
321
-			$result['attributes'] = (string)\json_encode($attributes->toArray());
322
-		}
323
-
324
-		return $result;
325
-	}
326
-
327
-	/**
328
-	 * Check if one of the users address books knows the exact property, if
329
-	 * not we return the full name.
330
-	 *
331
-	 * @param string $query
332
-	 * @param string $property
333
-	 * @return string
334
-	 */
335
-	private function getDisplayNameFromAddressBook(string $query, string $property): string {
336
-		// FIXME: If we inject the contacts manager it gets initialized before any address books are registered
337
-		try {
338
-			$result = Server::get(\OCP\Contacts\IManager::class)->search($query, [$property], [
339
-				'limit' => 1,
340
-				'enumeration' => false,
341
-				'strict_search' => true,
342
-			]);
343
-		} catch (Exception $e) {
344
-			$this->logger->error(
345
-				$e->getMessage(),
346
-				['exception' => $e]
347
-			);
348
-			return $query;
349
-		}
350
-
351
-		foreach ($result as $r) {
352
-			foreach ($r[$property] as $value) {
353
-				if ($value === $query && $r['FN']) {
354
-					return $r['FN'];
355
-				}
356
-			}
357
-		}
358
-
359
-		return $query;
360
-	}
361
-
362
-
363
-	/**
364
-	 * @param list<Files_SharingShare> $shares
365
-	 * @param array<string, string>|null $updatedDisplayName
366
-	 *
367
-	 * @return list<Files_SharingShare>
368
-	 */
369
-	private function fixMissingDisplayName(array $shares, ?array $updatedDisplayName = null): array {
370
-		$userIds = $updated = [];
371
-		foreach ($shares as $share) {
372
-			// share is federated and share have no display name yet
373
-			if ($share['share_type'] === IShare::TYPE_REMOTE
374
-				&& ($share['share_with'] ?? '') !== ''
375
-				&& ($share['share_with_displayname'] ?? '') === '') {
376
-				$userIds[] = $userId = $share['share_with'];
377
-
378
-				if ($updatedDisplayName !== null && array_key_exists($userId, $updatedDisplayName)) {
379
-					$share['share_with_displayname'] = $updatedDisplayName[$userId];
380
-				}
381
-			}
382
-
383
-			// prepping userIds with displayName to be updated
384
-			$updated[] = $share;
385
-		}
386
-
387
-		// if $updatedDisplayName is not null, it means we should have already fixed displayNames of the shares
388
-		if ($updatedDisplayName !== null) {
389
-			return $updated;
390
-		}
391
-
392
-		// get displayName for the generated list of userId with no displayName
393
-		$displayNames = $this->retrieveFederatedDisplayName($userIds);
394
-
395
-		// if no displayName are updated, we exit
396
-		if (empty($displayNames)) {
397
-			return $updated;
398
-		}
399
-
400
-		// let's fix missing display name and returns all shares
401
-		return $this->fixMissingDisplayName($shares, $displayNames);
402
-	}
403
-
404
-
405
-	/**
406
-	 * get displayName of a list of userIds from the lookup-server; through the globalsiteselector app.
407
-	 * returns an array with userIds as keys and displayName as values.
408
-	 *
409
-	 * @param array $userIds
410
-	 * @param bool $cacheOnly - do not reach LUS, get data from cache.
411
-	 *
412
-	 * @return array
413
-	 * @throws ContainerExceptionInterface
414
-	 */
415
-	private function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = false): array {
416
-		// check if gss is enabled and available
417
-		if (count($userIds) === 0
418
-			|| !$this->appManager->isEnabledForAnyone('globalsiteselector')
419
-			|| !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
420
-			return [];
421
-		}
422
-
423
-		try {
424
-			$slaveService = Server::get(SlaveService::class);
425
-		} catch (\Throwable $e) {
426
-			$this->logger->error(
427
-				$e->getMessage(),
428
-				['exception' => $e]
429
-			);
430
-			return [];
431
-		}
432
-
433
-		return $slaveService->getUsersDisplayName($userIds, $cacheOnly);
434
-	}
435
-
436
-
437
-	/**
438
-	 * retrieve displayName from cache if available (should be used on federated shares)
439
-	 * if not available in cache/lus, try for get from address-book, else returns empty string.
440
-	 *
441
-	 * @param string $userId
442
-	 * @param bool $cacheOnly if true will not reach the lus but will only get data from cache
443
-	 *
444
-	 * @return string
445
-	 */
446
-	private function getCachedFederatedDisplayName(string $userId, bool $cacheOnly = true): string {
447
-		$details = $this->retrieveFederatedDisplayName([$userId], $cacheOnly);
448
-		if (array_key_exists($userId, $details)) {
449
-			return $details[$userId];
450
-		}
451
-
452
-		$displayName = $this->getDisplayNameFromAddressBook($userId, 'CLOUD');
453
-		return ($displayName === $userId) ? '' : $displayName;
454
-	}
455
-
456
-
457
-
458
-	/**
459
-	 * Get a specific share by id
460
-	 *
461
-	 * @param string $id ID of the share
462
-	 * @param bool $include_tags Include tags in the share
463
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
464
-	 * @throws OCSNotFoundException Share not found
465
-	 *
466
-	 * 200: Share returned
467
-	 */
468
-	#[NoAdminRequired]
469
-	public function getShare(string $id, bool $include_tags = false): DataResponse {
470
-		try {
471
-			$share = $this->getShareById($id);
472
-		} catch (ShareNotFound $e) {
473
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
474
-		}
475
-
476
-		try {
477
-			if ($this->canAccessShare($share)) {
478
-				$share = $this->formatShare($share);
479
-
480
-				if ($include_tags) {
481
-					$share = $this->populateTags([$share]);
482
-				} else {
483
-					$share = [$share];
484
-				}
485
-
486
-				return new DataResponse($share);
487
-			}
488
-		} catch (NotFoundException $e) {
489
-			// Fall through
490
-		}
491
-
492
-		throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
493
-	}
494
-
495
-	/**
496
-	 * Delete a share
497
-	 *
498
-	 * @param string $id ID of the share
499
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
500
-	 * @throws OCSNotFoundException Share not found
501
-	 * @throws OCSForbiddenException Missing permissions to delete the share
502
-	 *
503
-	 * 200: Share deleted successfully
504
-	 */
505
-	#[NoAdminRequired]
506
-	public function deleteShare(string $id): DataResponse {
507
-		try {
508
-			$share = $this->getShareById($id);
509
-		} catch (ShareNotFound $e) {
510
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
511
-		}
512
-
513
-		try {
514
-			$this->lock($share->getNode());
515
-		} catch (LockedException $e) {
516
-			throw new OCSNotFoundException($this->l->t('Could not delete share'));
517
-		}
518
-
519
-		if (!$this->canAccessShare($share)) {
520
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
521
-		}
522
-
523
-		// if it's a group share or a room share
524
-		// we don't delete the share, but only the
525
-		// mount point. Allowing it to be restored
526
-		// from the deleted shares
527
-		if ($this->canDeleteShareFromSelf($share)) {
528
-			$this->shareManager->deleteFromSelf($share, $this->userId);
529
-		} else {
530
-			if (!$this->canDeleteShare($share)) {
531
-				throw new OCSForbiddenException($this->l->t('Could not delete share'));
532
-			}
533
-
534
-			$this->shareManager->deleteShare($share);
535
-		}
536
-
537
-		return new DataResponse();
538
-	}
539
-
540
-	/**
541
-	 * Create a share
542
-	 *
543
-	 * @param string|null $path Path of the share
544
-	 * @param int|null $permissions Permissions for the share
545
-	 * @param int $shareType Type of the share
546
-	 * @param ?string $shareWith The entity this should be shared with
547
-	 * @param 'true'|'false'|null $publicUpload If public uploading is allowed (deprecated)
548
-	 * @param string $password Password for the share
549
-	 * @param string|null $sendPasswordByTalk Send the password for the share over Talk
550
-	 * @param ?string $expireDate The expiry date of the share in the user's timezone at 00:00.
551
-	 *                            If $expireDate is not supplied or set to `null`, the system default will be used.
552
-	 * @param string $note Note for the share
553
-	 * @param string $label Label for the share (only used in link and email)
554
-	 * @param string|null $attributes Additional attributes for the share
555
-	 * @param 'false'|'true'|null $sendMail Send a mail to the recipient
556
-	 *
557
-	 * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
558
-	 * @throws OCSBadRequestException Unknown share type
559
-	 * @throws OCSException
560
-	 * @throws OCSForbiddenException Creating the share is not allowed
561
-	 * @throws OCSNotFoundException Creating the share failed
562
-	 * @suppress PhanUndeclaredClassMethod
563
-	 *
564
-	 * 200: Share created
565
-	 */
566
-	#[NoAdminRequired]
567
-	#[UserRateLimit(limit: 20, period: 600)]
568
-	public function createShare(
569
-		?string $path = null,
570
-		?int $permissions = null,
571
-		int $shareType = -1,
572
-		?string $shareWith = null,
573
-		?string $publicUpload = null,
574
-		string $password = '',
575
-		?string $sendPasswordByTalk = null,
576
-		?string $expireDate = null,
577
-		string $note = '',
578
-		string $label = '',
579
-		?string $attributes = null,
580
-		?string $sendMail = null,
581
-	): DataResponse {
582
-		assert($this->userId !== null);
583
-
584
-		$share = $this->shareManager->newShare();
585
-		$hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
586
-
587
-		// Verify path
588
-		if ($path === null) {
589
-			throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
590
-		}
591
-
592
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
593
-		try {
594
-			/** @var \OC\Files\Node\Node $node */
595
-			$node = $userFolder->get($path);
596
-		} catch (NotFoundException $e) {
597
-			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
598
-		}
599
-
600
-		// a user can have access to a file through different paths, with differing permissions
601
-		// combine all permissions to determine if the user can share this file
602
-		$nodes = $userFolder->getById($node->getId());
603
-		foreach ($nodes as $nodeById) {
604
-			/** @var FileInfo $fileInfo */
605
-			$fileInfo = $node->getFileInfo();
606
-			$fileInfo['permissions'] |= $nodeById->getPermissions();
607
-		}
608
-
609
-		$share->setNode($node);
610
-
611
-		try {
612
-			$this->lock($share->getNode());
613
-		} catch (LockedException $e) {
614
-			throw new OCSNotFoundException($this->l->t('Could not create share'));
615
-		}
616
-
617
-		// Set permissions
618
-		if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_EMAIL) {
619
-			$permissions = $this->getLinkSharePermissions($permissions, $hasPublicUpload);
620
-			$this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
621
-		} else {
622
-			// Use default permissions only for non-link shares to keep legacy behavior
623
-			if ($permissions === null) {
624
-				$permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
625
-			}
626
-			// Non-link shares always require read permissions (link shares could be file drop)
627
-			$permissions |= Constants::PERMISSION_READ;
628
-		}
629
-
630
-		// For legacy reasons the API allows to pass PERMISSIONS_ALL even for single file shares (I look at you Talk)
631
-		if ($node instanceof File) {
632
-			// if this is a single file share we remove the DELETE and CREATE permissions
633
-			$permissions = $permissions & ~(Constants::PERMISSION_DELETE | Constants::PERMISSION_CREATE);
634
-		}
635
-
636
-		/**
637
-		 * Hack for https://github.com/owncloud/core/issues/22587
638
-		 * We check the permissions via webdav. But the permissions of the mount point
639
-		 * do not equal the share permissions. Here we fix that for federated mounts.
640
-		 */
641
-		if ($node->getStorage()->instanceOfStorage(Storage::class)) {
642
-			$permissions &= ~($permissions & ~$node->getPermissions());
643
-		}
644
-
645
-		if ($attributes !== null) {
646
-			$share = $this->setShareAttributes($share, $attributes);
647
-		}
648
-
649
-		// Expire date checks
650
-		// Normally, null means no expiration date but we still set the default for backwards compatibility
651
-		// If the client sends an empty string, we set noExpirationDate to true
652
-		if ($expireDate !== null) {
653
-			if ($expireDate !== '') {
654
-				try {
655
-					$expireDateTime = $this->parseDate($expireDate);
656
-					$share->setExpirationDate($expireDateTime);
657
-				} catch (\Exception $e) {
658
-					throw new OCSNotFoundException($e->getMessage(), $e);
659
-				}
660
-			} else {
661
-				// Client sent empty string for expire date.
662
-				// Set noExpirationDate to true so overwrite is prevented.
663
-				$share->setNoExpirationDate(true);
664
-			}
665
-		}
666
-
667
-		$share->setSharedBy($this->userId);
668
-
669
-		// Handle mail send
670
-		if (is_null($sendMail)) {
671
-			$allowSendMail = $this->config->getSystemValueBool('sharing.enable_share_mail', true);
672
-			if ($allowSendMail !== true || $shareType === IShare::TYPE_EMAIL) {
673
-				// Define a default behavior when sendMail is not provided
674
-				// For email shares with a valid recipient, the default is to send the mail
675
-				// For all other share types, the default is to not send the mail
676
-				$allowSendMail = ($shareType === IShare::TYPE_EMAIL && $shareWith !== null && $shareWith !== '');
677
-			}
678
-			$share->setMailSend($allowSendMail);
679
-		} else {
680
-			$share->setMailSend($sendMail === 'true');
681
-		}
682
-
683
-		if ($shareType === IShare::TYPE_USER) {
684
-			// Valid user is required to share
685
-			if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
686
-				throw new OCSNotFoundException($this->l->t('Please specify a valid account to share with'));
687
-			}
688
-			$share->setSharedWith($shareWith);
689
-			$share->setPermissions($permissions);
690
-		} elseif ($shareType === IShare::TYPE_GROUP) {
691
-			if (!$this->shareManager->allowGroupSharing()) {
692
-				throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator'));
693
-			}
694
-
695
-			// Valid group is required to share
696
-			if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) {
697
-				throw new OCSNotFoundException($this->l->t('Please specify a valid group'));
698
-			}
699
-			$share->setSharedWith($shareWith);
700
-			$share->setPermissions($permissions);
701
-		} elseif ($shareType === IShare::TYPE_LINK
702
-			|| $shareType === IShare::TYPE_EMAIL) {
703
-
704
-			// Can we even share links?
705
-			if (!$this->shareManager->shareApiAllowLinks()) {
706
-				throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
707
-			}
708
-
709
-			$this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
710
-			$share->setPermissions($permissions);
711
-
712
-			// Set password
713
-			if ($password !== '') {
714
-				$share->setPassword($password);
715
-			}
716
-
717
-			// Only share by mail have a recipient
718
-			if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) {
719
-				// If sending a mail have been requested, validate the mail address
720
-				if ($share->getMailSend() && !$this->mailer->validateMailAddress($shareWith)) {
721
-					throw new OCSNotFoundException($this->l->t('Please specify a valid email address'));
722
-				}
723
-				$share->setSharedWith($shareWith);
724
-			}
725
-
726
-			// If we have a label, use it
727
-			if ($label !== '') {
728
-				if (strlen($label) > 255) {
729
-					throw new OCSBadRequestException('Maximum label length is 255');
730
-				}
731
-				$share->setLabel($label);
732
-			}
733
-
734
-			if ($sendPasswordByTalk === 'true') {
735
-				if (!$this->appManager->isEnabledForUser('spreed')) {
736
-					throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$node->getPath()]));
737
-				}
738
-
739
-				$share->setSendPasswordByTalk(true);
740
-			}
741
-		} elseif ($shareType === IShare::TYPE_REMOTE) {
742
-			if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
743
-				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]));
744
-			}
745
-
746
-			if ($shareWith === null) {
747
-				throw new OCSNotFoundException($this->l->t('Please specify a valid federated account ID'));
748
-			}
749
-
750
-			$share->setSharedWith($shareWith);
751
-			$share->setPermissions($permissions);
752
-			$share->setSharedWithDisplayName($this->getCachedFederatedDisplayName($shareWith, false));
753
-		} elseif ($shareType === IShare::TYPE_REMOTE_GROUP) {
754
-			if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
755
-				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]));
756
-			}
757
-
758
-			if ($shareWith === null) {
759
-				throw new OCSNotFoundException($this->l->t('Please specify a valid federated group ID'));
760
-			}
761
-
762
-			$share->setSharedWith($shareWith);
763
-			$share->setPermissions($permissions);
764
-		} elseif ($shareType === IShare::TYPE_CIRCLE) {
765
-			if (!Server::get(IAppManager::class)->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
766
-				throw new OCSNotFoundException($this->l->t('You cannot share to a Team if the app is not enabled'));
767
-			}
768
-
769
-			$circle = Circles::detailsCircle($shareWith);
770
-
771
-			// Valid team is required to share
772
-			if ($circle === null) {
773
-				throw new OCSNotFoundException($this->l->t('Please specify a valid team'));
774
-			}
775
-			$share->setSharedWith($shareWith);
776
-			$share->setPermissions($permissions);
777
-		} elseif ($shareType === IShare::TYPE_ROOM) {
778
-			try {
779
-				$this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
780
-			} catch (ContainerExceptionInterface $e) {
781
-				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
782
-			}
783
-		} elseif ($shareType === IShare::TYPE_DECK) {
784
-			try {
785
-				$this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
786
-			} catch (ContainerExceptionInterface $e) {
787
-				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
788
-			}
789
-		} elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
790
-			try {
791
-				$this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
792
-			} catch (ContainerExceptionInterface $e) {
793
-				throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support ScienceMesh shares', [$node->getPath()]));
794
-			}
795
-		} else {
796
-			throw new OCSBadRequestException($this->l->t('Unknown share type'));
797
-		}
798
-
799
-		$share->setShareType($shareType);
800
-		$this->checkInheritedAttributes($share);
801
-
802
-		if ($note !== '') {
803
-			$share->setNote($note);
804
-		}
805
-
806
-		try {
807
-			$share = $this->shareManager->createShare($share);
808
-		} catch (HintException $e) {
809
-			$code = $e->getCode() === 0 ? 403 : $e->getCode();
810
-			throw new OCSException($e->getHint(), $code);
811
-		} catch (GenericShareException|\InvalidArgumentException $e) {
812
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
813
-			throw new OCSForbiddenException($e->getMessage(), $e);
814
-		} catch (\Exception $e) {
815
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
816
-			throw new OCSForbiddenException('Failed to create share.', $e);
817
-		}
818
-
819
-		$output = $this->formatShare($share);
820
-
821
-		return new DataResponse($output);
822
-	}
823
-
824
-	/**
825
-	 * @param null|Node $node
826
-	 * @param boolean $includeTags
827
-	 *
828
-	 * @return list<Files_SharingShare>
829
-	 */
830
-	private function getSharedWithMe($node, bool $includeTags): array {
831
-		$userShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_USER, $node, -1, 0);
832
-		$groupShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_GROUP, $node, -1, 0);
833
-		$circleShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_CIRCLE, $node, -1, 0);
834
-		$roomShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_ROOM, $node, -1, 0);
835
-		$deckShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_DECK, $node, -1, 0);
836
-		$sciencemeshShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_SCIENCEMESH, $node, -1, 0);
837
-
838
-		$shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares);
839
-
840
-		$filteredShares = array_filter($shares, function (IShare $share) {
841
-			return $share->getShareOwner() !== $this->userId;
842
-		});
843
-
844
-		$formatted = [];
845
-		foreach ($filteredShares as $share) {
846
-			if ($this->canAccessShare($share)) {
847
-				try {
848
-					$formatted[] = $this->formatShare($share);
849
-				} catch (NotFoundException $e) {
850
-					// Ignore this share
851
-				}
852
-			}
853
-		}
854
-
855
-		if ($includeTags) {
856
-			$formatted = $this->populateTags($formatted);
857
-		}
858
-
859
-		return $formatted;
860
-	}
861
-
862
-	/**
863
-	 * @param Node $folder
864
-	 *
865
-	 * @return list<Files_SharingShare>
866
-	 * @throws OCSBadRequestException
867
-	 * @throws NotFoundException
868
-	 */
869
-	private function getSharesInDir(Node $folder): array {
870
-		if (!($folder instanceof Folder)) {
871
-			throw new OCSBadRequestException($this->l->t('Not a directory'));
872
-		}
873
-
874
-		$nodes = $folder->getDirectoryListing();
875
-
876
-		/** @var IShare[] $shares */
877
-		$shares = array_reduce($nodes, function ($carry, $node) {
878
-			$carry = array_merge($carry, $this->getAllShares($node, true));
879
-			return $carry;
880
-		}, []);
881
-
882
-		// filter out duplicate shares
883
-		$known = [];
884
-
885
-		$formatted = $miniFormatted = [];
886
-		$resharingRight = false;
887
-		$known = [];
888
-		foreach ($shares as $share) {
889
-			if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->userId) {
890
-				continue;
891
-			}
892
-
893
-			try {
894
-				$format = $this->formatShare($share);
895
-
896
-				$known[] = $share->getId();
897
-				$formatted[] = $format;
898
-				if ($share->getSharedBy() === $this->userId) {
899
-					$miniFormatted[] = $format;
900
-				}
901
-				if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $folder)) {
902
-					$resharingRight = true;
903
-				}
904
-			} catch (\Exception $e) {
905
-				//Ignore this share
906
-			}
907
-		}
908
-
909
-		if (!$resharingRight) {
910
-			$formatted = $miniFormatted;
911
-		}
912
-
913
-		return $formatted;
914
-	}
915
-
916
-	/**
917
-	 * Get shares of the current user
918
-	 *
919
-	 * @param string $shared_with_me Only get shares with the current user
920
-	 * @param string $reshares Only get shares by the current user and reshares
921
-	 * @param string $subfiles Only get all shares in a folder
922
-	 * @param string $path Get shares for a specific path
923
-	 * @param string $include_tags Include tags in the share
924
-	 *
925
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
926
-	 * @throws OCSNotFoundException The folder was not found or is inaccessible
927
-	 *
928
-	 * 200: Shares returned
929
-	 */
930
-	#[NoAdminRequired]
931
-	public function getShares(
932
-		string $shared_with_me = 'false',
933
-		string $reshares = 'false',
934
-		string $subfiles = 'false',
935
-		string $path = '',
936
-		string $include_tags = 'false',
937
-	): DataResponse {
938
-		$node = null;
939
-		if ($path !== '') {
940
-			$userFolder = $this->rootFolder->getUserFolder($this->userId);
941
-			try {
942
-				$node = $userFolder->get($path);
943
-				$this->lock($node);
944
-			} catch (NotFoundException $e) {
945
-				throw new OCSNotFoundException(
946
-					$this->l->t('Wrong path, file/folder does not exist')
947
-				);
948
-			} catch (LockedException $e) {
949
-				throw new OCSNotFoundException($this->l->t('Could not lock node'));
950
-			}
951
-		}
952
-
953
-		$shares = $this->getFormattedShares(
954
-			$this->userId,
955
-			$node,
956
-			($shared_with_me === 'true'),
957
-			($reshares === 'true'),
958
-			($subfiles === 'true'),
959
-			($include_tags === 'true')
960
-		);
961
-
962
-		return new DataResponse($shares);
963
-	}
964
-
965
-	private function getLinkSharePermissions(?int $permissions, ?bool $legacyPublicUpload): int {
966
-		$permissions = $permissions ?? Constants::PERMISSION_READ;
967
-
968
-		// Legacy option handling
969
-		if ($legacyPublicUpload !== null) {
970
-			$permissions = $legacyPublicUpload
971
-				? (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
972
-				: Constants::PERMISSION_READ;
973
-		}
974
-
975
-		if ($this->hasPermission($permissions, Constants::PERMISSION_READ)
976
-			&& $this->shareManager->outgoingServer2ServerSharesAllowed()
977
-			&& $this->appConfig->getValueBool('core', ConfigLexicon::SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES)) {
978
-			$permissions |= Constants::PERMISSION_SHARE;
979
-		}
980
-
981
-		return $permissions;
982
-	}
983
-
984
-	/**
985
-	 * Helper to check for legacy "publicUpload" handling.
986
-	 * If the value is set to `true` or `false` then true or false are returned.
987
-	 * Otherwise null is returned to indicate that the option was not (or wrong) set.
988
-	 *
989
-	 * @param null|string $legacyPublicUpload The value of `publicUpload`
990
-	 */
991
-	private function getLegacyPublicUpload(?string $legacyPublicUpload): ?bool {
992
-		if ($legacyPublicUpload === 'true') {
993
-			return true;
994
-		} elseif ($legacyPublicUpload === 'false') {
995
-			return false;
996
-		}
997
-		// Not set at all
998
-		return null;
999
-	}
1000
-
1001
-	/**
1002
-	 * For link and email shares validate that only allowed combinations are set.
1003
-	 *
1004
-	 * @throw OCSBadRequestException If permission combination is invalid.
1005
-	 * @throw OCSForbiddenException If public upload was forbidden by the administrator.
1006
-	 */
1007
-	private function validateLinkSharePermissions(Node $node, int $permissions, ?bool $legacyPublicUpload): void {
1008
-		if ($legacyPublicUpload && ($node instanceof File)) {
1009
-			throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
1010
-		}
1011
-
1012
-		// We need at least READ or CREATE (file drop)
1013
-		if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1014
-			&& !$this->hasPermission($permissions, Constants::PERMISSION_CREATE)) {
1015
-			throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
1016
-		}
1017
-
1018
-		// UPDATE and DELETE require a READ permission
1019
-		if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1020
-			&& ($this->hasPermission($permissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($permissions, Constants::PERMISSION_DELETE))) {
1021
-			throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
1022
-		}
1023
-
1024
-		// Check if public uploading was disabled
1025
-		if ($this->hasPermission($permissions, Constants::PERMISSION_CREATE)
1026
-			&& !$this->shareManager->shareApiLinkAllowPublicUpload()) {
1027
-			throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
1028
-		}
1029
-	}
1030
-
1031
-	/**
1032
-	 * @param string $viewer
1033
-	 * @param Node $node
1034
-	 * @param bool $sharedWithMe
1035
-	 * @param bool $reShares
1036
-	 * @param bool $subFiles
1037
-	 * @param bool $includeTags
1038
-	 *
1039
-	 * @return list<Files_SharingShare>
1040
-	 * @throws NotFoundException
1041
-	 * @throws OCSBadRequestException
1042
-	 */
1043
-	private function getFormattedShares(
1044
-		string $viewer,
1045
-		$node = null,
1046
-		bool $sharedWithMe = false,
1047
-		bool $reShares = false,
1048
-		bool $subFiles = false,
1049
-		bool $includeTags = false,
1050
-	): array {
1051
-		if ($sharedWithMe) {
1052
-			return $this->getSharedWithMe($node, $includeTags);
1053
-		}
1054
-
1055
-		if ($subFiles) {
1056
-			return $this->getSharesInDir($node);
1057
-		}
1058
-
1059
-		$shares = $this->getSharesFromNode($viewer, $node, $reShares);
1060
-
1061
-		$known = $formatted = $miniFormatted = [];
1062
-		$resharingRight = false;
1063
-		foreach ($shares as $share) {
1064
-			try {
1065
-				$share->getNode();
1066
-			} catch (NotFoundException $e) {
1067
-				/*
78
+    private ?Node $lockedNode = null;
79
+
80
+    /**
81
+     * Share20OCS constructor.
82
+     */
83
+    public function __construct(
84
+        string $appName,
85
+        IRequest $request,
86
+        private IManager $shareManager,
87
+        private IGroupManager $groupManager,
88
+        private IUserManager $userManager,
89
+        private IRootFolder $rootFolder,
90
+        private IURLGenerator $urlGenerator,
91
+        private IL10N $l,
92
+        private IConfig $config,
93
+        private IAppConfig $appConfig,
94
+        private IAppManager $appManager,
95
+        private ContainerInterface $serverContainer,
96
+        private IUserStatusManager $userStatusManager,
97
+        private IPreview $previewManager,
98
+        private IDateTimeZone $dateTimeZone,
99
+        private LoggerInterface $logger,
100
+        private IProviderFactory $factory,
101
+        private IMailer $mailer,
102
+        private ITagManager $tagManager,
103
+        private ?string $userId = null,
104
+    ) {
105
+        parent::__construct($appName, $request);
106
+    }
107
+
108
+    /**
109
+     * Convert an IShare to an array for OCS output
110
+     *
111
+     * @param IShare $share
112
+     * @param Node|null $recipientNode
113
+     * @return Files_SharingShare
114
+     * @throws NotFoundException In case the node can't be resolved.
115
+     *
116
+     * @suppress PhanUndeclaredClassMethod
117
+     */
118
+    protected function formatShare(IShare $share, ?Node $recipientNode = null): array {
119
+        $sharedBy = $this->userManager->get($share->getSharedBy());
120
+        $shareOwner = $this->userManager->get($share->getShareOwner());
121
+
122
+        $isOwnShare = false;
123
+        if ($shareOwner !== null) {
124
+            $isOwnShare = $shareOwner->getUID() === $this->userId;
125
+        }
126
+
127
+        $result = [
128
+            'id' => $share->getId(),
129
+            'share_type' => $share->getShareType(),
130
+            'uid_owner' => $share->getSharedBy(),
131
+            'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
132
+            // recipient permissions
133
+            'permissions' => $share->getPermissions(),
134
+            // current user permissions on this share
135
+            'can_edit' => $this->canEditShare($share),
136
+            'can_delete' => $this->canDeleteShare($share),
137
+            'stime' => $share->getShareTime()->getTimestamp(),
138
+            'parent' => null,
139
+            'expiration' => null,
140
+            'token' => null,
141
+            'uid_file_owner' => $share->getShareOwner(),
142
+            'note' => $share->getNote(),
143
+            'label' => $share->getLabel(),
144
+            'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
145
+        ];
146
+
147
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
148
+        if ($recipientNode) {
149
+            $node = $recipientNode;
150
+        } else {
151
+            $node = $userFolder->getFirstNodeById($share->getNodeId());
152
+            if (!$node) {
153
+                // fallback to guessing the path
154
+                $node = $userFolder->get($share->getTarget());
155
+                if ($node === null || $share->getTarget() === '') {
156
+                    throw new NotFoundException();
157
+                }
158
+            }
159
+        }
160
+
161
+        $result['path'] = $userFolder->getRelativePath($node->getPath());
162
+        if ($node instanceof Folder) {
163
+            $result['item_type'] = 'folder';
164
+        } else {
165
+            $result['item_type'] = 'file';
166
+        }
167
+
168
+        // Get the original node permission if the share owner is the current user
169
+        if ($isOwnShare) {
170
+            $result['item_permissions'] = $node->getPermissions();
171
+        }
172
+
173
+        // If we're on the recipient side, the node permissions
174
+        // are bound to the share permissions. So we need to
175
+        // adjust the permissions to the share permissions if necessary.
176
+        if (!$isOwnShare) {
177
+            $result['item_permissions'] = $share->getPermissions();
178
+
179
+            // For some reason, single files share are forbidden to have the delete permission
180
+            // since we have custom methods to check those, let's adjust straight away.
181
+            // DAV permissions does not have that issue though.
182
+            if ($this->canDeleteShare($share) || $this->canDeleteShareFromSelf($share)) {
183
+                $result['item_permissions'] |= Constants::PERMISSION_DELETE;
184
+            }
185
+            if ($this->canEditShare($share)) {
186
+                $result['item_permissions'] |= Constants::PERMISSION_UPDATE;
187
+            }
188
+        }
189
+
190
+        // See MOUNT_ROOT_PROPERTYNAME dav property
191
+        $result['is-mount-root'] = $node->getInternalPath() === '';
192
+        $result['mount-type'] = $node->getMountPoint()->getMountType();
193
+
194
+        $result['mimetype'] = $node->getMimetype();
195
+        $result['has_preview'] = $this->previewManager->isAvailable($node);
196
+        $result['storage_id'] = $node->getStorage()->getId();
197
+        $result['storage'] = $node->getStorage()->getCache()->getNumericStorageId();
198
+        $result['item_source'] = $node->getId();
199
+        $result['file_source'] = $node->getId();
200
+        $result['file_parent'] = $node->getParent()->getId();
201
+        $result['file_target'] = $share->getTarget();
202
+        $result['item_size'] = $node->getSize();
203
+        $result['item_mtime'] = $node->getMTime();
204
+
205
+        $expiration = $share->getExpirationDate();
206
+        if ($expiration !== null) {
207
+            $expiration->setTimezone($this->dateTimeZone->getTimeZone());
208
+            $result['expiration'] = $expiration->format('Y-m-d 00:00:00');
209
+        }
210
+
211
+        if ($share->getShareType() === IShare::TYPE_USER) {
212
+            $sharedWith = $this->userManager->get($share->getSharedWith());
213
+            $result['share_with'] = $share->getSharedWith();
214
+            $result['share_with_displayname'] = $sharedWith !== null ? $sharedWith->getDisplayName() : $share->getSharedWith();
215
+            $result['share_with_displayname_unique'] = $sharedWith !== null ? (
216
+                !empty($sharedWith->getSystemEMailAddress()) ? $sharedWith->getSystemEMailAddress() : $sharedWith->getUID()
217
+            ) : $share->getSharedWith();
218
+
219
+            $userStatuses = $this->userStatusManager->getUserStatuses([$share->getSharedWith()]);
220
+            $userStatus = array_shift($userStatuses);
221
+            if ($userStatus) {
222
+                $result['status'] = [
223
+                    'status' => $userStatus->getStatus(),
224
+                    'message' => $userStatus->getMessage(),
225
+                    'icon' => $userStatus->getIcon(),
226
+                    'clearAt' => $userStatus->getClearAt()
227
+                        ? (int)$userStatus->getClearAt()->format('U')
228
+                        : null,
229
+                ];
230
+            }
231
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
232
+            $group = $this->groupManager->get($share->getSharedWith());
233
+            $result['share_with'] = $share->getSharedWith();
234
+            $result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith();
235
+        } elseif ($share->getShareType() === IShare::TYPE_LINK) {
236
+
237
+            // "share_with" and "share_with_displayname" for passwords of link
238
+            // shares was deprecated in Nextcloud 15, use "password" instead.
239
+            $result['share_with'] = $share->getPassword();
240
+            $result['share_with_displayname'] = '(' . $this->l->t('Shared link') . ')';
241
+
242
+            $result['password'] = $share->getPassword();
243
+
244
+            $result['send_password_by_talk'] = $share->getSendPasswordByTalk();
245
+
246
+            $result['token'] = $share->getToken();
247
+            $result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]);
248
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
249
+            $result['share_with'] = $share->getSharedWith();
250
+            $result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith());
251
+            $result['token'] = $share->getToken();
252
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
253
+            $result['share_with'] = $share->getSharedWith();
254
+            $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD');
255
+            $result['token'] = $share->getToken();
256
+        } elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
257
+            $result['share_with'] = $share->getSharedWith();
258
+            $result['password'] = $share->getPassword();
259
+            $result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null;
260
+            $result['send_password_by_talk'] = $share->getSendPasswordByTalk();
261
+            $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL');
262
+            $result['token'] = $share->getToken();
263
+        } elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
264
+            // getSharedWith() returns either "name (type, owner)" or
265
+            // "name (type, owner) [id]", depending on the Teams app version.
266
+            $hasCircleId = (substr($share->getSharedWith(), -1) === ']');
267
+
268
+            $result['share_with_displayname'] = $share->getSharedWithDisplayName();
269
+            if (empty($result['share_with_displayname'])) {
270
+                $displayNameLength = ($hasCircleId ? strrpos($share->getSharedWith(), ' ') : strlen($share->getSharedWith()));
271
+                $result['share_with_displayname'] = substr($share->getSharedWith(), 0, $displayNameLength);
272
+            }
273
+
274
+            $result['share_with_avatar'] = $share->getSharedWithAvatar();
275
+
276
+            $shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
277
+            $shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
278
+            if ($shareWithLength === false) {
279
+                $result['share_with'] = substr($share->getSharedWith(), $shareWithStart);
280
+            } else {
281
+                $result['share_with'] = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
282
+            }
283
+        } elseif ($share->getShareType() === IShare::TYPE_ROOM) {
284
+            $result['share_with'] = $share->getSharedWith();
285
+            $result['share_with_displayname'] = '';
286
+
287
+            try {
288
+                /** @var array{share_with_displayname: string, share_with_link: string, share_with?: string, token?: string} $roomShare */
289
+                $roomShare = $this->getRoomShareHelper()->formatShare($share);
290
+                $result = array_merge($result, $roomShare);
291
+            } catch (ContainerExceptionInterface $e) {
292
+            }
293
+        } elseif ($share->getShareType() === IShare::TYPE_DECK) {
294
+            $result['share_with'] = $share->getSharedWith();
295
+            $result['share_with_displayname'] = '';
296
+
297
+            try {
298
+                /** @var array{share_with: string, share_with_displayname: string, share_with_link: string} $deckShare */
299
+                $deckShare = $this->getDeckShareHelper()->formatShare($share);
300
+                $result = array_merge($result, $deckShare);
301
+            } catch (ContainerExceptionInterface $e) {
302
+            }
303
+        } elseif ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
304
+            $result['share_with'] = $share->getSharedWith();
305
+            $result['share_with_displayname'] = '';
306
+
307
+            try {
308
+                /** @var array{share_with: string, share_with_displayname: string, token: string} $scienceMeshShare */
309
+                $scienceMeshShare = $this->getSciencemeshShareHelper()->formatShare($share);
310
+                $result = array_merge($result, $scienceMeshShare);
311
+            } catch (ContainerExceptionInterface $e) {
312
+            }
313
+        }
314
+
315
+
316
+        $result['mail_send'] = $share->getMailSend() ? 1 : 0;
317
+        $result['hide_download'] = $share->getHideDownload() ? 1 : 0;
318
+
319
+        $result['attributes'] = null;
320
+        if ($attributes = $share->getAttributes()) {
321
+            $result['attributes'] = (string)\json_encode($attributes->toArray());
322
+        }
323
+
324
+        return $result;
325
+    }
326
+
327
+    /**
328
+     * Check if one of the users address books knows the exact property, if
329
+     * not we return the full name.
330
+     *
331
+     * @param string $query
332
+     * @param string $property
333
+     * @return string
334
+     */
335
+    private function getDisplayNameFromAddressBook(string $query, string $property): string {
336
+        // FIXME: If we inject the contacts manager it gets initialized before any address books are registered
337
+        try {
338
+            $result = Server::get(\OCP\Contacts\IManager::class)->search($query, [$property], [
339
+                'limit' => 1,
340
+                'enumeration' => false,
341
+                'strict_search' => true,
342
+            ]);
343
+        } catch (Exception $e) {
344
+            $this->logger->error(
345
+                $e->getMessage(),
346
+                ['exception' => $e]
347
+            );
348
+            return $query;
349
+        }
350
+
351
+        foreach ($result as $r) {
352
+            foreach ($r[$property] as $value) {
353
+                if ($value === $query && $r['FN']) {
354
+                    return $r['FN'];
355
+                }
356
+            }
357
+        }
358
+
359
+        return $query;
360
+    }
361
+
362
+
363
+    /**
364
+     * @param list<Files_SharingShare> $shares
365
+     * @param array<string, string>|null $updatedDisplayName
366
+     *
367
+     * @return list<Files_SharingShare>
368
+     */
369
+    private function fixMissingDisplayName(array $shares, ?array $updatedDisplayName = null): array {
370
+        $userIds = $updated = [];
371
+        foreach ($shares as $share) {
372
+            // share is federated and share have no display name yet
373
+            if ($share['share_type'] === IShare::TYPE_REMOTE
374
+                && ($share['share_with'] ?? '') !== ''
375
+                && ($share['share_with_displayname'] ?? '') === '') {
376
+                $userIds[] = $userId = $share['share_with'];
377
+
378
+                if ($updatedDisplayName !== null && array_key_exists($userId, $updatedDisplayName)) {
379
+                    $share['share_with_displayname'] = $updatedDisplayName[$userId];
380
+                }
381
+            }
382
+
383
+            // prepping userIds with displayName to be updated
384
+            $updated[] = $share;
385
+        }
386
+
387
+        // if $updatedDisplayName is not null, it means we should have already fixed displayNames of the shares
388
+        if ($updatedDisplayName !== null) {
389
+            return $updated;
390
+        }
391
+
392
+        // get displayName for the generated list of userId with no displayName
393
+        $displayNames = $this->retrieveFederatedDisplayName($userIds);
394
+
395
+        // if no displayName are updated, we exit
396
+        if (empty($displayNames)) {
397
+            return $updated;
398
+        }
399
+
400
+        // let's fix missing display name and returns all shares
401
+        return $this->fixMissingDisplayName($shares, $displayNames);
402
+    }
403
+
404
+
405
+    /**
406
+     * get displayName of a list of userIds from the lookup-server; through the globalsiteselector app.
407
+     * returns an array with userIds as keys and displayName as values.
408
+     *
409
+     * @param array $userIds
410
+     * @param bool $cacheOnly - do not reach LUS, get data from cache.
411
+     *
412
+     * @return array
413
+     * @throws ContainerExceptionInterface
414
+     */
415
+    private function retrieveFederatedDisplayName(array $userIds, bool $cacheOnly = false): array {
416
+        // check if gss is enabled and available
417
+        if (count($userIds) === 0
418
+            || !$this->appManager->isEnabledForAnyone('globalsiteselector')
419
+            || !class_exists('\OCA\GlobalSiteSelector\Service\SlaveService')) {
420
+            return [];
421
+        }
422
+
423
+        try {
424
+            $slaveService = Server::get(SlaveService::class);
425
+        } catch (\Throwable $e) {
426
+            $this->logger->error(
427
+                $e->getMessage(),
428
+                ['exception' => $e]
429
+            );
430
+            return [];
431
+        }
432
+
433
+        return $slaveService->getUsersDisplayName($userIds, $cacheOnly);
434
+    }
435
+
436
+
437
+    /**
438
+     * retrieve displayName from cache if available (should be used on federated shares)
439
+     * if not available in cache/lus, try for get from address-book, else returns empty string.
440
+     *
441
+     * @param string $userId
442
+     * @param bool $cacheOnly if true will not reach the lus but will only get data from cache
443
+     *
444
+     * @return string
445
+     */
446
+    private function getCachedFederatedDisplayName(string $userId, bool $cacheOnly = true): string {
447
+        $details = $this->retrieveFederatedDisplayName([$userId], $cacheOnly);
448
+        if (array_key_exists($userId, $details)) {
449
+            return $details[$userId];
450
+        }
451
+
452
+        $displayName = $this->getDisplayNameFromAddressBook($userId, 'CLOUD');
453
+        return ($displayName === $userId) ? '' : $displayName;
454
+    }
455
+
456
+
457
+
458
+    /**
459
+     * Get a specific share by id
460
+     *
461
+     * @param string $id ID of the share
462
+     * @param bool $include_tags Include tags in the share
463
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
464
+     * @throws OCSNotFoundException Share not found
465
+     *
466
+     * 200: Share returned
467
+     */
468
+    #[NoAdminRequired]
469
+    public function getShare(string $id, bool $include_tags = false): DataResponse {
470
+        try {
471
+            $share = $this->getShareById($id);
472
+        } catch (ShareNotFound $e) {
473
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
474
+        }
475
+
476
+        try {
477
+            if ($this->canAccessShare($share)) {
478
+                $share = $this->formatShare($share);
479
+
480
+                if ($include_tags) {
481
+                    $share = $this->populateTags([$share]);
482
+                } else {
483
+                    $share = [$share];
484
+                }
485
+
486
+                return new DataResponse($share);
487
+            }
488
+        } catch (NotFoundException $e) {
489
+            // Fall through
490
+        }
491
+
492
+        throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
493
+    }
494
+
495
+    /**
496
+     * Delete a share
497
+     *
498
+     * @param string $id ID of the share
499
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
500
+     * @throws OCSNotFoundException Share not found
501
+     * @throws OCSForbiddenException Missing permissions to delete the share
502
+     *
503
+     * 200: Share deleted successfully
504
+     */
505
+    #[NoAdminRequired]
506
+    public function deleteShare(string $id): DataResponse {
507
+        try {
508
+            $share = $this->getShareById($id);
509
+        } catch (ShareNotFound $e) {
510
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
511
+        }
512
+
513
+        try {
514
+            $this->lock($share->getNode());
515
+        } catch (LockedException $e) {
516
+            throw new OCSNotFoundException($this->l->t('Could not delete share'));
517
+        }
518
+
519
+        if (!$this->canAccessShare($share)) {
520
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
521
+        }
522
+
523
+        // if it's a group share or a room share
524
+        // we don't delete the share, but only the
525
+        // mount point. Allowing it to be restored
526
+        // from the deleted shares
527
+        if ($this->canDeleteShareFromSelf($share)) {
528
+            $this->shareManager->deleteFromSelf($share, $this->userId);
529
+        } else {
530
+            if (!$this->canDeleteShare($share)) {
531
+                throw new OCSForbiddenException($this->l->t('Could not delete share'));
532
+            }
533
+
534
+            $this->shareManager->deleteShare($share);
535
+        }
536
+
537
+        return new DataResponse();
538
+    }
539
+
540
+    /**
541
+     * Create a share
542
+     *
543
+     * @param string|null $path Path of the share
544
+     * @param int|null $permissions Permissions for the share
545
+     * @param int $shareType Type of the share
546
+     * @param ?string $shareWith The entity this should be shared with
547
+     * @param 'true'|'false'|null $publicUpload If public uploading is allowed (deprecated)
548
+     * @param string $password Password for the share
549
+     * @param string|null $sendPasswordByTalk Send the password for the share over Talk
550
+     * @param ?string $expireDate The expiry date of the share in the user's timezone at 00:00.
551
+     *                            If $expireDate is not supplied or set to `null`, the system default will be used.
552
+     * @param string $note Note for the share
553
+     * @param string $label Label for the share (only used in link and email)
554
+     * @param string|null $attributes Additional attributes for the share
555
+     * @param 'false'|'true'|null $sendMail Send a mail to the recipient
556
+     *
557
+     * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
558
+     * @throws OCSBadRequestException Unknown share type
559
+     * @throws OCSException
560
+     * @throws OCSForbiddenException Creating the share is not allowed
561
+     * @throws OCSNotFoundException Creating the share failed
562
+     * @suppress PhanUndeclaredClassMethod
563
+     *
564
+     * 200: Share created
565
+     */
566
+    #[NoAdminRequired]
567
+    #[UserRateLimit(limit: 20, period: 600)]
568
+    public function createShare(
569
+        ?string $path = null,
570
+        ?int $permissions = null,
571
+        int $shareType = -1,
572
+        ?string $shareWith = null,
573
+        ?string $publicUpload = null,
574
+        string $password = '',
575
+        ?string $sendPasswordByTalk = null,
576
+        ?string $expireDate = null,
577
+        string $note = '',
578
+        string $label = '',
579
+        ?string $attributes = null,
580
+        ?string $sendMail = null,
581
+    ): DataResponse {
582
+        assert($this->userId !== null);
583
+
584
+        $share = $this->shareManager->newShare();
585
+        $hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
586
+
587
+        // Verify path
588
+        if ($path === null) {
589
+            throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
590
+        }
591
+
592
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
593
+        try {
594
+            /** @var \OC\Files\Node\Node $node */
595
+            $node = $userFolder->get($path);
596
+        } catch (NotFoundException $e) {
597
+            throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
598
+        }
599
+
600
+        // a user can have access to a file through different paths, with differing permissions
601
+        // combine all permissions to determine if the user can share this file
602
+        $nodes = $userFolder->getById($node->getId());
603
+        foreach ($nodes as $nodeById) {
604
+            /** @var FileInfo $fileInfo */
605
+            $fileInfo = $node->getFileInfo();
606
+            $fileInfo['permissions'] |= $nodeById->getPermissions();
607
+        }
608
+
609
+        $share->setNode($node);
610
+
611
+        try {
612
+            $this->lock($share->getNode());
613
+        } catch (LockedException $e) {
614
+            throw new OCSNotFoundException($this->l->t('Could not create share'));
615
+        }
616
+
617
+        // Set permissions
618
+        if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_EMAIL) {
619
+            $permissions = $this->getLinkSharePermissions($permissions, $hasPublicUpload);
620
+            $this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
621
+        } else {
622
+            // Use default permissions only for non-link shares to keep legacy behavior
623
+            if ($permissions === null) {
624
+                $permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
625
+            }
626
+            // Non-link shares always require read permissions (link shares could be file drop)
627
+            $permissions |= Constants::PERMISSION_READ;
628
+        }
629
+
630
+        // For legacy reasons the API allows to pass PERMISSIONS_ALL even for single file shares (I look at you Talk)
631
+        if ($node instanceof File) {
632
+            // if this is a single file share we remove the DELETE and CREATE permissions
633
+            $permissions = $permissions & ~(Constants::PERMISSION_DELETE | Constants::PERMISSION_CREATE);
634
+        }
635
+
636
+        /**
637
+         * Hack for https://github.com/owncloud/core/issues/22587
638
+         * We check the permissions via webdav. But the permissions of the mount point
639
+         * do not equal the share permissions. Here we fix that for federated mounts.
640
+         */
641
+        if ($node->getStorage()->instanceOfStorage(Storage::class)) {
642
+            $permissions &= ~($permissions & ~$node->getPermissions());
643
+        }
644
+
645
+        if ($attributes !== null) {
646
+            $share = $this->setShareAttributes($share, $attributes);
647
+        }
648
+
649
+        // Expire date checks
650
+        // Normally, null means no expiration date but we still set the default for backwards compatibility
651
+        // If the client sends an empty string, we set noExpirationDate to true
652
+        if ($expireDate !== null) {
653
+            if ($expireDate !== '') {
654
+                try {
655
+                    $expireDateTime = $this->parseDate($expireDate);
656
+                    $share->setExpirationDate($expireDateTime);
657
+                } catch (\Exception $e) {
658
+                    throw new OCSNotFoundException($e->getMessage(), $e);
659
+                }
660
+            } else {
661
+                // Client sent empty string for expire date.
662
+                // Set noExpirationDate to true so overwrite is prevented.
663
+                $share->setNoExpirationDate(true);
664
+            }
665
+        }
666
+
667
+        $share->setSharedBy($this->userId);
668
+
669
+        // Handle mail send
670
+        if (is_null($sendMail)) {
671
+            $allowSendMail = $this->config->getSystemValueBool('sharing.enable_share_mail', true);
672
+            if ($allowSendMail !== true || $shareType === IShare::TYPE_EMAIL) {
673
+                // Define a default behavior when sendMail is not provided
674
+                // For email shares with a valid recipient, the default is to send the mail
675
+                // For all other share types, the default is to not send the mail
676
+                $allowSendMail = ($shareType === IShare::TYPE_EMAIL && $shareWith !== null && $shareWith !== '');
677
+            }
678
+            $share->setMailSend($allowSendMail);
679
+        } else {
680
+            $share->setMailSend($sendMail === 'true');
681
+        }
682
+
683
+        if ($shareType === IShare::TYPE_USER) {
684
+            // Valid user is required to share
685
+            if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
686
+                throw new OCSNotFoundException($this->l->t('Please specify a valid account to share with'));
687
+            }
688
+            $share->setSharedWith($shareWith);
689
+            $share->setPermissions($permissions);
690
+        } elseif ($shareType === IShare::TYPE_GROUP) {
691
+            if (!$this->shareManager->allowGroupSharing()) {
692
+                throw new OCSNotFoundException($this->l->t('Group sharing is disabled by the administrator'));
693
+            }
694
+
695
+            // Valid group is required to share
696
+            if ($shareWith === null || !$this->groupManager->groupExists($shareWith)) {
697
+                throw new OCSNotFoundException($this->l->t('Please specify a valid group'));
698
+            }
699
+            $share->setSharedWith($shareWith);
700
+            $share->setPermissions($permissions);
701
+        } elseif ($shareType === IShare::TYPE_LINK
702
+            || $shareType === IShare::TYPE_EMAIL) {
703
+
704
+            // Can we even share links?
705
+            if (!$this->shareManager->shareApiAllowLinks()) {
706
+                throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
707
+            }
708
+
709
+            $this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
710
+            $share->setPermissions($permissions);
711
+
712
+            // Set password
713
+            if ($password !== '') {
714
+                $share->setPassword($password);
715
+            }
716
+
717
+            // Only share by mail have a recipient
718
+            if (is_string($shareWith) && $shareType === IShare::TYPE_EMAIL) {
719
+                // If sending a mail have been requested, validate the mail address
720
+                if ($share->getMailSend() && !$this->mailer->validateMailAddress($shareWith)) {
721
+                    throw new OCSNotFoundException($this->l->t('Please specify a valid email address'));
722
+                }
723
+                $share->setSharedWith($shareWith);
724
+            }
725
+
726
+            // If we have a label, use it
727
+            if ($label !== '') {
728
+                if (strlen($label) > 255) {
729
+                    throw new OCSBadRequestException('Maximum label length is 255');
730
+                }
731
+                $share->setLabel($label);
732
+            }
733
+
734
+            if ($sendPasswordByTalk === 'true') {
735
+                if (!$this->appManager->isEnabledForUser('spreed')) {
736
+                    throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$node->getPath()]));
737
+                }
738
+
739
+                $share->setSendPasswordByTalk(true);
740
+            }
741
+        } elseif ($shareType === IShare::TYPE_REMOTE) {
742
+            if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
743
+                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]));
744
+            }
745
+
746
+            if ($shareWith === null) {
747
+                throw new OCSNotFoundException($this->l->t('Please specify a valid federated account ID'));
748
+            }
749
+
750
+            $share->setSharedWith($shareWith);
751
+            $share->setPermissions($permissions);
752
+            $share->setSharedWithDisplayName($this->getCachedFederatedDisplayName($shareWith, false));
753
+        } elseif ($shareType === IShare::TYPE_REMOTE_GROUP) {
754
+            if (!$this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
755
+                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]));
756
+            }
757
+
758
+            if ($shareWith === null) {
759
+                throw new OCSNotFoundException($this->l->t('Please specify a valid federated group ID'));
760
+            }
761
+
762
+            $share->setSharedWith($shareWith);
763
+            $share->setPermissions($permissions);
764
+        } elseif ($shareType === IShare::TYPE_CIRCLE) {
765
+            if (!Server::get(IAppManager::class)->isEnabledForUser('circles') || !class_exists('\OCA\Circles\ShareByCircleProvider')) {
766
+                throw new OCSNotFoundException($this->l->t('You cannot share to a Team if the app is not enabled'));
767
+            }
768
+
769
+            $circle = Circles::detailsCircle($shareWith);
770
+
771
+            // Valid team is required to share
772
+            if ($circle === null) {
773
+                throw new OCSNotFoundException($this->l->t('Please specify a valid team'));
774
+            }
775
+            $share->setSharedWith($shareWith);
776
+            $share->setPermissions($permissions);
777
+        } elseif ($shareType === IShare::TYPE_ROOM) {
778
+            try {
779
+                $this->getRoomShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
780
+            } catch (ContainerExceptionInterface $e) {
781
+                throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
782
+            }
783
+        } elseif ($shareType === IShare::TYPE_DECK) {
784
+            try {
785
+                $this->getDeckShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
786
+            } catch (ContainerExceptionInterface $e) {
787
+                throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support room shares', [$node->getPath()]));
788
+            }
789
+        } elseif ($shareType === IShare::TYPE_SCIENCEMESH) {
790
+            try {
791
+                $this->getSciencemeshShareHelper()->createShare($share, $shareWith, $permissions, $expireDate ?? '');
792
+            } catch (ContainerExceptionInterface $e) {
793
+                throw new OCSForbiddenException($this->l->t('Sharing %s failed because the back end does not support ScienceMesh shares', [$node->getPath()]));
794
+            }
795
+        } else {
796
+            throw new OCSBadRequestException($this->l->t('Unknown share type'));
797
+        }
798
+
799
+        $share->setShareType($shareType);
800
+        $this->checkInheritedAttributes($share);
801
+
802
+        if ($note !== '') {
803
+            $share->setNote($note);
804
+        }
805
+
806
+        try {
807
+            $share = $this->shareManager->createShare($share);
808
+        } catch (HintException $e) {
809
+            $code = $e->getCode() === 0 ? 403 : $e->getCode();
810
+            throw new OCSException($e->getHint(), $code);
811
+        } catch (GenericShareException|\InvalidArgumentException $e) {
812
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
813
+            throw new OCSForbiddenException($e->getMessage(), $e);
814
+        } catch (\Exception $e) {
815
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
816
+            throw new OCSForbiddenException('Failed to create share.', $e);
817
+        }
818
+
819
+        $output = $this->formatShare($share);
820
+
821
+        return new DataResponse($output);
822
+    }
823
+
824
+    /**
825
+     * @param null|Node $node
826
+     * @param boolean $includeTags
827
+     *
828
+     * @return list<Files_SharingShare>
829
+     */
830
+    private function getSharedWithMe($node, bool $includeTags): array {
831
+        $userShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_USER, $node, -1, 0);
832
+        $groupShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_GROUP, $node, -1, 0);
833
+        $circleShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_CIRCLE, $node, -1, 0);
834
+        $roomShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_ROOM, $node, -1, 0);
835
+        $deckShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_DECK, $node, -1, 0);
836
+        $sciencemeshShares = $this->shareManager->getSharedWith($this->userId, IShare::TYPE_SCIENCEMESH, $node, -1, 0);
837
+
838
+        $shares = array_merge($userShares, $groupShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares);
839
+
840
+        $filteredShares = array_filter($shares, function (IShare $share) {
841
+            return $share->getShareOwner() !== $this->userId;
842
+        });
843
+
844
+        $formatted = [];
845
+        foreach ($filteredShares as $share) {
846
+            if ($this->canAccessShare($share)) {
847
+                try {
848
+                    $formatted[] = $this->formatShare($share);
849
+                } catch (NotFoundException $e) {
850
+                    // Ignore this share
851
+                }
852
+            }
853
+        }
854
+
855
+        if ($includeTags) {
856
+            $formatted = $this->populateTags($formatted);
857
+        }
858
+
859
+        return $formatted;
860
+    }
861
+
862
+    /**
863
+     * @param Node $folder
864
+     *
865
+     * @return list<Files_SharingShare>
866
+     * @throws OCSBadRequestException
867
+     * @throws NotFoundException
868
+     */
869
+    private function getSharesInDir(Node $folder): array {
870
+        if (!($folder instanceof Folder)) {
871
+            throw new OCSBadRequestException($this->l->t('Not a directory'));
872
+        }
873
+
874
+        $nodes = $folder->getDirectoryListing();
875
+
876
+        /** @var IShare[] $shares */
877
+        $shares = array_reduce($nodes, function ($carry, $node) {
878
+            $carry = array_merge($carry, $this->getAllShares($node, true));
879
+            return $carry;
880
+        }, []);
881
+
882
+        // filter out duplicate shares
883
+        $known = [];
884
+
885
+        $formatted = $miniFormatted = [];
886
+        $resharingRight = false;
887
+        $known = [];
888
+        foreach ($shares as $share) {
889
+            if (in_array($share->getId(), $known) || $share->getSharedWith() === $this->userId) {
890
+                continue;
891
+            }
892
+
893
+            try {
894
+                $format = $this->formatShare($share);
895
+
896
+                $known[] = $share->getId();
897
+                $formatted[] = $format;
898
+                if ($share->getSharedBy() === $this->userId) {
899
+                    $miniFormatted[] = $format;
900
+                }
901
+                if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $folder)) {
902
+                    $resharingRight = true;
903
+                }
904
+            } catch (\Exception $e) {
905
+                //Ignore this share
906
+            }
907
+        }
908
+
909
+        if (!$resharingRight) {
910
+            $formatted = $miniFormatted;
911
+        }
912
+
913
+        return $formatted;
914
+    }
915
+
916
+    /**
917
+     * Get shares of the current user
918
+     *
919
+     * @param string $shared_with_me Only get shares with the current user
920
+     * @param string $reshares Only get shares by the current user and reshares
921
+     * @param string $subfiles Only get all shares in a folder
922
+     * @param string $path Get shares for a specific path
923
+     * @param string $include_tags Include tags in the share
924
+     *
925
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
926
+     * @throws OCSNotFoundException The folder was not found or is inaccessible
927
+     *
928
+     * 200: Shares returned
929
+     */
930
+    #[NoAdminRequired]
931
+    public function getShares(
932
+        string $shared_with_me = 'false',
933
+        string $reshares = 'false',
934
+        string $subfiles = 'false',
935
+        string $path = '',
936
+        string $include_tags = 'false',
937
+    ): DataResponse {
938
+        $node = null;
939
+        if ($path !== '') {
940
+            $userFolder = $this->rootFolder->getUserFolder($this->userId);
941
+            try {
942
+                $node = $userFolder->get($path);
943
+                $this->lock($node);
944
+            } catch (NotFoundException $e) {
945
+                throw new OCSNotFoundException(
946
+                    $this->l->t('Wrong path, file/folder does not exist')
947
+                );
948
+            } catch (LockedException $e) {
949
+                throw new OCSNotFoundException($this->l->t('Could not lock node'));
950
+            }
951
+        }
952
+
953
+        $shares = $this->getFormattedShares(
954
+            $this->userId,
955
+            $node,
956
+            ($shared_with_me === 'true'),
957
+            ($reshares === 'true'),
958
+            ($subfiles === 'true'),
959
+            ($include_tags === 'true')
960
+        );
961
+
962
+        return new DataResponse($shares);
963
+    }
964
+
965
+    private function getLinkSharePermissions(?int $permissions, ?bool $legacyPublicUpload): int {
966
+        $permissions = $permissions ?? Constants::PERMISSION_READ;
967
+
968
+        // Legacy option handling
969
+        if ($legacyPublicUpload !== null) {
970
+            $permissions = $legacyPublicUpload
971
+                ? (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
972
+                : Constants::PERMISSION_READ;
973
+        }
974
+
975
+        if ($this->hasPermission($permissions, Constants::PERMISSION_READ)
976
+            && $this->shareManager->outgoingServer2ServerSharesAllowed()
977
+            && $this->appConfig->getValueBool('core', ConfigLexicon::SHAREAPI_ALLOW_FEDERATION_ON_PUBLIC_SHARES)) {
978
+            $permissions |= Constants::PERMISSION_SHARE;
979
+        }
980
+
981
+        return $permissions;
982
+    }
983
+
984
+    /**
985
+     * Helper to check for legacy "publicUpload" handling.
986
+     * If the value is set to `true` or `false` then true or false are returned.
987
+     * Otherwise null is returned to indicate that the option was not (or wrong) set.
988
+     *
989
+     * @param null|string $legacyPublicUpload The value of `publicUpload`
990
+     */
991
+    private function getLegacyPublicUpload(?string $legacyPublicUpload): ?bool {
992
+        if ($legacyPublicUpload === 'true') {
993
+            return true;
994
+        } elseif ($legacyPublicUpload === 'false') {
995
+            return false;
996
+        }
997
+        // Not set at all
998
+        return null;
999
+    }
1000
+
1001
+    /**
1002
+     * For link and email shares validate that only allowed combinations are set.
1003
+     *
1004
+     * @throw OCSBadRequestException If permission combination is invalid.
1005
+     * @throw OCSForbiddenException If public upload was forbidden by the administrator.
1006
+     */
1007
+    private function validateLinkSharePermissions(Node $node, int $permissions, ?bool $legacyPublicUpload): void {
1008
+        if ($legacyPublicUpload && ($node instanceof File)) {
1009
+            throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
1010
+        }
1011
+
1012
+        // We need at least READ or CREATE (file drop)
1013
+        if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1014
+            && !$this->hasPermission($permissions, Constants::PERMISSION_CREATE)) {
1015
+            throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
1016
+        }
1017
+
1018
+        // UPDATE and DELETE require a READ permission
1019
+        if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1020
+            && ($this->hasPermission($permissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($permissions, Constants::PERMISSION_DELETE))) {
1021
+            throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
1022
+        }
1023
+
1024
+        // Check if public uploading was disabled
1025
+        if ($this->hasPermission($permissions, Constants::PERMISSION_CREATE)
1026
+            && !$this->shareManager->shareApiLinkAllowPublicUpload()) {
1027
+            throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
1028
+        }
1029
+    }
1030
+
1031
+    /**
1032
+     * @param string $viewer
1033
+     * @param Node $node
1034
+     * @param bool $sharedWithMe
1035
+     * @param bool $reShares
1036
+     * @param bool $subFiles
1037
+     * @param bool $includeTags
1038
+     *
1039
+     * @return list<Files_SharingShare>
1040
+     * @throws NotFoundException
1041
+     * @throws OCSBadRequestException
1042
+     */
1043
+    private function getFormattedShares(
1044
+        string $viewer,
1045
+        $node = null,
1046
+        bool $sharedWithMe = false,
1047
+        bool $reShares = false,
1048
+        bool $subFiles = false,
1049
+        bool $includeTags = false,
1050
+    ): array {
1051
+        if ($sharedWithMe) {
1052
+            return $this->getSharedWithMe($node, $includeTags);
1053
+        }
1054
+
1055
+        if ($subFiles) {
1056
+            return $this->getSharesInDir($node);
1057
+        }
1058
+
1059
+        $shares = $this->getSharesFromNode($viewer, $node, $reShares);
1060
+
1061
+        $known = $formatted = $miniFormatted = [];
1062
+        $resharingRight = false;
1063
+        foreach ($shares as $share) {
1064
+            try {
1065
+                $share->getNode();
1066
+            } catch (NotFoundException $e) {
1067
+                /*
1068 1068
 				 * Ignore shares where we can't get the node
1069 1069
 				 * For example deleted shares
1070 1070
 				 */
1071
-				continue;
1072
-			}
1073
-
1074
-			if (in_array($share->getId(), $known)
1075
-				|| ($share->getSharedWith() === $this->userId && $share->getShareType() === IShare::TYPE_USER)) {
1076
-				continue;
1077
-			}
1078
-
1079
-			$known[] = $share->getId();
1080
-			try {
1081
-				/** @var IShare $share */
1082
-				$format = $this->formatShare($share, $node);
1083
-				$formatted[] = $format;
1084
-
1085
-				// let's also build a list of shares created
1086
-				// by the current user only, in case
1087
-				// there is no resharing rights
1088
-				if ($share->getSharedBy() === $this->userId) {
1089
-					$miniFormatted[] = $format;
1090
-				}
1091
-
1092
-				// check if one of those share is shared with me
1093
-				// and if I have resharing rights on it
1094
-				if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $node)) {
1095
-					$resharingRight = true;
1096
-				}
1097
-			} catch (InvalidPathException|NotFoundException $e) {
1098
-			}
1099
-		}
1100
-
1101
-		if (!$resharingRight) {
1102
-			$formatted = $miniFormatted;
1103
-		}
1104
-
1105
-		// fix eventual missing display name from federated shares
1106
-		$formatted = $this->fixMissingDisplayName($formatted);
1107
-
1108
-		if ($includeTags) {
1109
-			$formatted = $this->populateTags($formatted);
1110
-		}
1111
-
1112
-		return $formatted;
1113
-	}
1114
-
1115
-
1116
-	/**
1117
-	 * Get all shares relative to a file, including parent folders shares rights
1118
-	 *
1119
-	 * @param string $path Path all shares will be relative to
1120
-	 *
1121
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1122
-	 * @throws InvalidPathException
1123
-	 * @throws NotFoundException
1124
-	 * @throws OCSNotFoundException The given path is invalid
1125
-	 * @throws SharingRightsException
1126
-	 *
1127
-	 * 200: Shares returned
1128
-	 */
1129
-	#[NoAdminRequired]
1130
-	public function getInheritedShares(string $path): DataResponse {
1131
-		// get Node from (string) path.
1132
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1133
-		try {
1134
-			$node = $userFolder->get($path);
1135
-			$this->lock($node);
1136
-		} catch (NotFoundException $e) {
1137
-			throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
1138
-		} catch (LockedException $e) {
1139
-			throw new OCSNotFoundException($this->l->t('Could not lock path'));
1140
-		}
1141
-
1142
-		if (!($node->getPermissions() & Constants::PERMISSION_SHARE)) {
1143
-			throw new SharingRightsException($this->l->t('no sharing rights on this item'));
1144
-		}
1145
-
1146
-		// The current top parent we have access to
1147
-		$parent = $node;
1148
-
1149
-		// initiate real owner.
1150
-		$owner = $node->getOwner()
1151
-			->getUID();
1152
-		if (!$this->userManager->userExists($owner)) {
1153
-			return new DataResponse([]);
1154
-		}
1155
-
1156
-		// get node based on the owner, fix owner in case of external storage
1157
-		$userFolder = $this->rootFolder->getUserFolder($owner);
1158
-		if ($node->getId() !== $userFolder->getId() && !$userFolder->isSubNode($node)) {
1159
-			$owner = $node->getOwner()
1160
-				->getUID();
1161
-			$userFolder = $this->rootFolder->getUserFolder($owner);
1162
-			$node = $userFolder->getFirstNodeById($node->getId());
1163
-		}
1164
-		$basePath = $userFolder->getPath();
1165
-
1166
-		// generate node list for each parent folders
1167
-		/** @var Node[] $nodes */
1168
-		$nodes = [];
1169
-		while (true) {
1170
-			$node = $node->getParent();
1171
-			if ($node->getPath() === $basePath) {
1172
-				break;
1173
-			}
1174
-			$nodes[] = $node;
1175
-		}
1176
-
1177
-		// The user that is requesting this list
1178
-		$currentUserFolder = $this->rootFolder->getUserFolder($this->userId);
1179
-
1180
-		// for each nodes, retrieve shares.
1181
-		$shares = [];
1182
-
1183
-		foreach ($nodes as $node) {
1184
-			$getShares = $this->getFormattedShares($owner, $node, false, true);
1185
-
1186
-			$currentUserNode = $currentUserFolder->getFirstNodeById($node->getId());
1187
-			if ($currentUserNode) {
1188
-				$parent = $currentUserNode;
1189
-			}
1190
-
1191
-			$subPath = $currentUserFolder->getRelativePath($parent->getPath());
1192
-			foreach ($getShares as &$share) {
1193
-				$share['via_fileid'] = $parent->getId();
1194
-				$share['via_path'] = $subPath;
1195
-			}
1196
-			$this->mergeFormattedShares($shares, $getShares);
1197
-		}
1198
-
1199
-		return new DataResponse(array_values($shares));
1200
-	}
1201
-
1202
-	/**
1203
-	 * Check whether a set of permissions contains the permissions to check.
1204
-	 */
1205
-	private function hasPermission(int $permissionsSet, int $permissionsToCheck): bool {
1206
-		return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
1207
-	}
1208
-
1209
-	/**
1210
-	 * Update a share
1211
-	 *
1212
-	 * @param string $id ID of the share
1213
-	 * @param int|null $permissions New permissions
1214
-	 * @param string|null $password New password
1215
-	 * @param string|null $sendPasswordByTalk New condition if the password should be send over Talk
1216
-	 * @param string|null $publicUpload New condition if public uploading is allowed
1217
-	 * @param string|null $expireDate New expiry date
1218
-	 * @param string|null $note New note
1219
-	 * @param string|null $label New label
1220
-	 * @param string|null $hideDownload New condition if the download should be hidden
1221
-	 * @param string|null $attributes New additional attributes
1222
-	 * @param string|null $sendMail if the share should be send by mail.
1223
-	 *                              Considering the share already exists, no mail will be send after the share is updated.
1224
-	 *                              You will have to use the sendMail action to send the mail.
1225
-	 * @param string|null $shareWith New recipient for email shares
1226
-	 * @param string|null $token New token
1227
-	 * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
1228
-	 * @throws OCSBadRequestException Share could not be updated because the requested changes are invalid
1229
-	 * @throws OCSForbiddenException Missing permissions to update the share
1230
-	 * @throws OCSNotFoundException Share not found
1231
-	 *
1232
-	 * 200: Share updated successfully
1233
-	 */
1234
-	#[NoAdminRequired]
1235
-	public function updateShare(
1236
-		string $id,
1237
-		?int $permissions = null,
1238
-		?string $password = null,
1239
-		?string $sendPasswordByTalk = null,
1240
-		?string $publicUpload = null,
1241
-		?string $expireDate = null,
1242
-		?string $note = null,
1243
-		?string $label = null,
1244
-		?string $hideDownload = null,
1245
-		?string $attributes = null,
1246
-		?string $sendMail = null,
1247
-		?string $token = null,
1248
-	): DataResponse {
1249
-		try {
1250
-			$share = $this->getShareById($id);
1251
-		} catch (ShareNotFound $e) {
1252
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1253
-		}
1254
-
1255
-		$this->lock($share->getNode());
1256
-
1257
-		if (!$this->canAccessShare($share, false)) {
1258
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1259
-		}
1260
-
1261
-		if (!$this->canEditShare($share)) {
1262
-			throw new OCSForbiddenException($this->l->t('You are not allowed to edit incoming shares'));
1263
-		}
1264
-
1265
-		if (
1266
-			$permissions === null
1267
-			&& $password === null
1268
-			&& $sendPasswordByTalk === null
1269
-			&& $publicUpload === null
1270
-			&& $expireDate === null
1271
-			&& $note === null
1272
-			&& $label === null
1273
-			&& $hideDownload === null
1274
-			&& $attributes === null
1275
-			&& $sendMail === null
1276
-			&& $token === null
1277
-		) {
1278
-			throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
1279
-		}
1280
-
1281
-		if ($note !== null) {
1282
-			$share->setNote($note);
1283
-		}
1284
-
1285
-		if ($attributes !== null) {
1286
-			$share = $this->setShareAttributes($share, $attributes);
1287
-		}
1288
-
1289
-		// Handle mail send
1290
-		if ($sendMail === 'true' || $sendMail === 'false') {
1291
-			$share->setMailSend($sendMail === 'true');
1292
-		}
1293
-
1294
-		/**
1295
-		 * expiration date, password and publicUpload only make sense for link shares
1296
-		 */
1297
-		if ($share->getShareType() === IShare::TYPE_LINK
1298
-			|| $share->getShareType() === IShare::TYPE_EMAIL) {
1299
-
1300
-			// Update hide download state
1301
-			if ($hideDownload === 'true') {
1302
-				$share->setHideDownload(true);
1303
-			} elseif ($hideDownload === 'false') {
1304
-				$share->setHideDownload(false);
1305
-			}
1306
-
1307
-			// If either manual permissions are specified or publicUpload
1308
-			// then we need to also update the permissions of the share
1309
-			if ($permissions !== null || $publicUpload !== null) {
1310
-				$hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
1311
-				$permissions = $this->getLinkSharePermissions($permissions ?? Constants::PERMISSION_READ, $hasPublicUpload);
1312
-				$this->validateLinkSharePermissions($share->getNode(), $permissions, $hasPublicUpload);
1313
-				$share->setPermissions($permissions);
1314
-			}
1315
-
1316
-			if ($password === '') {
1317
-				$share->setPassword(null);
1318
-			} elseif ($password !== null) {
1319
-				$share->setPassword($password);
1320
-			}
1321
-
1322
-			if ($label !== null) {
1323
-				if (strlen($label) > 255) {
1324
-					throw new OCSBadRequestException('Maximum label length is 255');
1325
-				}
1326
-				$share->setLabel($label);
1327
-			}
1328
-
1329
-			if ($sendPasswordByTalk === 'true') {
1330
-				if (!$this->appManager->isEnabledForUser('spreed')) {
1331
-					throw new OCSForbiddenException($this->l->t('"Sending the password by Nextcloud Talk" for sharing a file or folder failed because Nextcloud Talk is not enabled.'));
1332
-				}
1333
-
1334
-				$share->setSendPasswordByTalk(true);
1335
-			} elseif ($sendPasswordByTalk !== null) {
1336
-				$share->setSendPasswordByTalk(false);
1337
-			}
1338
-
1339
-			if ($token !== null) {
1340
-				if (!$this->shareManager->allowCustomTokens()) {
1341
-					throw new OCSForbiddenException($this->l->t('Custom share link tokens have been disabled by the administrator'));
1342
-				}
1343
-				if (!$this->validateToken($token)) {
1344
-					throw new OCSBadRequestException($this->l->t('Tokens must contain at least 1 character and may only contain letters, numbers, or a hyphen'));
1345
-				}
1346
-				$share->setToken($token);
1347
-			}
1348
-		}
1349
-
1350
-		// NOT A LINK SHARE
1351
-		else {
1352
-			if ($permissions !== null) {
1353
-				$share->setPermissions($permissions);
1354
-			}
1355
-		}
1356
-
1357
-		if ($expireDate === '') {
1358
-			$share->setExpirationDate(null);
1359
-		} elseif ($expireDate !== null) {
1360
-			try {
1361
-				$expireDateTime = $this->parseDate($expireDate);
1362
-				$share->setExpirationDate($expireDateTime);
1363
-			} catch (\Exception $e) {
1364
-				throw new OCSBadRequestException($e->getMessage(), $e);
1365
-			}
1366
-		}
1367
-
1368
-		try {
1369
-			$this->checkInheritedAttributes($share);
1370
-			$share = $this->shareManager->updateShare($share);
1371
-		} catch (HintException $e) {
1372
-			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1373
-			throw new OCSException($e->getHint(), (int)$code);
1374
-		} catch (\Exception $e) {
1375
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
1376
-			throw new OCSBadRequestException('Failed to update share.', $e);
1377
-		}
1378
-
1379
-		return new DataResponse($this->formatShare($share));
1380
-	}
1381
-
1382
-	private function validateToken(string $token): bool {
1383
-		if (mb_strlen($token) === 0) {
1384
-			return false;
1385
-		}
1386
-		if (!preg_match('/^[a-z0-9-]+$/i', $token)) {
1387
-			return false;
1388
-		}
1389
-		return true;
1390
-	}
1391
-
1392
-	/**
1393
-	 * Get all shares that are still pending
1394
-	 *
1395
-	 * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1396
-	 *
1397
-	 * 200: Pending shares returned
1398
-	 */
1399
-	#[NoAdminRequired]
1400
-	public function pendingShares(): DataResponse {
1401
-		$pendingShares = [];
1402
-
1403
-		$shareTypes = [
1404
-			IShare::TYPE_USER,
1405
-			IShare::TYPE_GROUP
1406
-		];
1407
-
1408
-		foreach ($shareTypes as $shareType) {
1409
-			$shares = $this->shareManager->getSharedWith($this->userId, $shareType, null, -1, 0);
1410
-
1411
-			foreach ($shares as $share) {
1412
-				if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
1413
-					$pendingShares[] = $share;
1414
-				}
1415
-			}
1416
-		}
1417
-
1418
-		$result = array_values(array_filter(array_map(function (IShare $share) {
1419
-			$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1420
-			$node = $userFolder->getFirstNodeById($share->getNodeId());
1421
-			if (!$node) {
1422
-				// fallback to guessing the path
1423
-				$node = $userFolder->get($share->getTarget());
1424
-				if ($node === null || $share->getTarget() === '') {
1425
-					return null;
1426
-				}
1427
-			}
1428
-
1429
-			try {
1430
-				$formattedShare = $this->formatShare($share, $node);
1431
-				$formattedShare['path'] = '/' . $share->getNode()->getName();
1432
-				$formattedShare['permissions'] = 0;
1433
-				return $formattedShare;
1434
-			} catch (NotFoundException $e) {
1435
-				return null;
1436
-			}
1437
-		}, $pendingShares), function ($entry) {
1438
-			return $entry !== null;
1439
-		}));
1440
-
1441
-		return new DataResponse($result);
1442
-	}
1443
-
1444
-	/**
1445
-	 * Accept a share
1446
-	 *
1447
-	 * @param string $id ID of the share
1448
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
1449
-	 * @throws OCSNotFoundException Share not found
1450
-	 * @throws OCSException
1451
-	 * @throws OCSBadRequestException Share could not be accepted
1452
-	 *
1453
-	 * 200: Share accepted successfully
1454
-	 */
1455
-	#[NoAdminRequired]
1456
-	public function acceptShare(string $id): DataResponse {
1457
-		try {
1458
-			$share = $this->getShareById($id);
1459
-		} catch (ShareNotFound $e) {
1460
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1461
-		}
1462
-
1463
-		if (!$this->canAccessShare($share)) {
1464
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1465
-		}
1466
-
1467
-		try {
1468
-			$this->shareManager->acceptShare($share, $this->userId);
1469
-		} catch (HintException $e) {
1470
-			$code = $e->getCode() === 0 ? 403 : $e->getCode();
1471
-			throw new OCSException($e->getHint(), (int)$code);
1472
-		} catch (\Exception $e) {
1473
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
1474
-			throw new OCSBadRequestException('Failed to accept share.', $e);
1475
-		}
1476
-
1477
-		return new DataResponse();
1478
-	}
1479
-
1480
-	/**
1481
-	 * Does the user have read permission on the share
1482
-	 *
1483
-	 * @param IShare $share the share to check
1484
-	 * @param boolean $checkGroups check groups as well?
1485
-	 * @return boolean
1486
-	 * @throws NotFoundException
1487
-	 *
1488
-	 * @suppress PhanUndeclaredClassMethod
1489
-	 */
1490
-	protected function canAccessShare(IShare $share, bool $checkGroups = true): bool {
1491
-		// A file with permissions 0 can't be accessed by us. So Don't show it
1492
-		if ($share->getPermissions() === 0) {
1493
-			return false;
1494
-		}
1495
-
1496
-		// Owner of the file and the sharer of the file can always get share
1497
-		if ($share->getShareOwner() === $this->userId
1498
-			|| $share->getSharedBy() === $this->userId) {
1499
-			return true;
1500
-		}
1501
-
1502
-		// If the share is shared with you, you can access it!
1503
-		if ($share->getShareType() === IShare::TYPE_USER
1504
-			&& $share->getSharedWith() === $this->userId) {
1505
-			return true;
1506
-		}
1507
-
1508
-		// Have reshare rights on the shared file/folder ?
1509
-		// Does the currentUser have access to the shared file?
1510
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1511
-		$file = $userFolder->getFirstNodeById($share->getNodeId());
1512
-		if ($file && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1513
-			return true;
1514
-		}
1515
-
1516
-		// If in the recipient group, you can see the share
1517
-		if ($checkGroups && $share->getShareType() === IShare::TYPE_GROUP) {
1518
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
1519
-			$user = $this->userManager->get($this->userId);
1520
-			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1521
-				return true;
1522
-			}
1523
-		}
1524
-
1525
-		if ($share->getShareType() === IShare::TYPE_CIRCLE) {
1526
-			// TODO: have a sanity check like above?
1527
-			return true;
1528
-		}
1529
-
1530
-		if ($share->getShareType() === IShare::TYPE_ROOM) {
1531
-			try {
1532
-				return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1533
-			} catch (ContainerExceptionInterface $e) {
1534
-				return false;
1535
-			}
1536
-		}
1537
-
1538
-		if ($share->getShareType() === IShare::TYPE_DECK) {
1539
-			try {
1540
-				return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1541
-			} catch (ContainerExceptionInterface $e) {
1542
-				return false;
1543
-			}
1544
-		}
1545
-
1546
-		if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1547
-			try {
1548
-				return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1549
-			} catch (ContainerExceptionInterface $e) {
1550
-				return false;
1551
-			}
1552
-		}
1553
-
1554
-		return false;
1555
-	}
1556
-
1557
-	/**
1558
-	 * Does the user have edit permission on the share
1559
-	 *
1560
-	 * @param IShare $share the share to check
1561
-	 * @return boolean
1562
-	 */
1563
-	protected function canEditShare(IShare $share): bool {
1564
-		// A file with permissions 0 can't be accessed by us. So Don't show it
1565
-		if ($share->getPermissions() === 0) {
1566
-			return false;
1567
-		}
1568
-
1569
-		// The owner of the file and the creator of the share
1570
-		// can always edit the share
1571
-		if ($share->getShareOwner() === $this->userId
1572
-			|| $share->getSharedBy() === $this->userId
1573
-		) {
1574
-			return true;
1575
-		}
1576
-
1577
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1578
-		$file = $userFolder->getFirstNodeById($share->getNodeId());
1579
-		if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1580
-			return true;
1581
-		}
1582
-
1583
-		//! we do NOT support some kind of `admin` in groups.
1584
-		//! You cannot edit shares shared to a group you're
1585
-		//! a member of if you're not the share owner or the file owner!
1586
-
1587
-		return false;
1588
-	}
1589
-
1590
-	/**
1591
-	 * Does the user have delete permission on the share
1592
-	 *
1593
-	 * @param IShare $share the share to check
1594
-	 * @return boolean
1595
-	 */
1596
-	protected function canDeleteShare(IShare $share): bool {
1597
-		// A file with permissions 0 can't be accessed by us. So Don't show it
1598
-		if ($share->getPermissions() === 0) {
1599
-			return false;
1600
-		}
1601
-
1602
-		// if the user is the recipient, i can unshare
1603
-		// the share with self
1604
-		if ($share->getShareType() === IShare::TYPE_USER
1605
-			&& $share->getSharedWith() === $this->userId
1606
-		) {
1607
-			return true;
1608
-		}
1609
-
1610
-		// The owner of the file and the creator of the share
1611
-		// can always delete the share
1612
-		if ($share->getShareOwner() === $this->userId
1613
-			|| $share->getSharedBy() === $this->userId
1614
-		) {
1615
-			return true;
1616
-		}
1617
-
1618
-		$userFolder = $this->rootFolder->getUserFolder($this->userId);
1619
-		$file = $userFolder->getFirstNodeById($share->getNodeId());
1620
-		if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1621
-			return true;
1622
-		}
1623
-
1624
-		return false;
1625
-	}
1626
-
1627
-	/**
1628
-	 * Does the user have delete permission on the share
1629
-	 * This differs from the canDeleteShare function as it only
1630
-	 * remove the share for the current user. It does NOT
1631
-	 * completely delete the share but only the mount point.
1632
-	 * It can then be restored from the deleted shares section.
1633
-	 *
1634
-	 * @param IShare $share the share to check
1635
-	 * @return boolean
1636
-	 *
1637
-	 * @suppress PhanUndeclaredClassMethod
1638
-	 */
1639
-	protected function canDeleteShareFromSelf(IShare $share): bool {
1640
-		if ($share->getShareType() !== IShare::TYPE_GROUP
1641
-			&& $share->getShareType() !== IShare::TYPE_ROOM
1642
-			&& $share->getShareType() !== IShare::TYPE_DECK
1643
-			&& $share->getShareType() !== IShare::TYPE_SCIENCEMESH
1644
-		) {
1645
-			return false;
1646
-		}
1647
-
1648
-		if ($share->getShareOwner() === $this->userId
1649
-			|| $share->getSharedBy() === $this->userId
1650
-		) {
1651
-			// Delete the whole share, not just for self
1652
-			return false;
1653
-		}
1654
-
1655
-		// If in the recipient group, you can delete the share from self
1656
-		if ($share->getShareType() === IShare::TYPE_GROUP) {
1657
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
1658
-			$user = $this->userManager->get($this->userId);
1659
-			if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1660
-				return true;
1661
-			}
1662
-		}
1663
-
1664
-		if ($share->getShareType() === IShare::TYPE_ROOM) {
1665
-			try {
1666
-				return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1667
-			} catch (ContainerExceptionInterface $e) {
1668
-				return false;
1669
-			}
1670
-		}
1671
-
1672
-		if ($share->getShareType() === IShare::TYPE_DECK) {
1673
-			try {
1674
-				return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1675
-			} catch (ContainerExceptionInterface $e) {
1676
-				return false;
1677
-			}
1678
-		}
1679
-
1680
-		if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1681
-			try {
1682
-				return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1683
-			} catch (ContainerExceptionInterface $e) {
1684
-				return false;
1685
-			}
1686
-		}
1687
-
1688
-		return false;
1689
-	}
1690
-
1691
-	/**
1692
-	 * Make sure that the passed date is valid ISO 8601
1693
-	 * So YYYY-MM-DD
1694
-	 * If not throw an exception
1695
-	 *
1696
-	 * @param string $expireDate
1697
-	 *
1698
-	 * @throws \Exception
1699
-	 * @return \DateTime
1700
-	 */
1701
-	private function parseDate(string $expireDate): \DateTime {
1702
-		try {
1703
-			$date = new \DateTime(trim($expireDate, '"'), $this->dateTimeZone->getTimeZone());
1704
-			// Make sure it expires at midnight in owner timezone
1705
-			$date->setTime(0, 0, 0);
1706
-		} catch (\Exception $e) {
1707
-			throw new \Exception($this->l->t('Invalid date. Format must be YYYY-MM-DD'));
1708
-		}
1709
-
1710
-		return $date;
1711
-	}
1712
-
1713
-	/**
1714
-	 * Since we have multiple providers but the OCS Share API v1 does
1715
-	 * not support this we need to check all backends.
1716
-	 *
1717
-	 * @param string $id
1718
-	 * @return IShare
1719
-	 * @throws ShareNotFound
1720
-	 */
1721
-	private function getShareById(string $id): IShare {
1722
-		$share = null;
1723
-
1724
-		// First check if it is an internal share.
1725
-		try {
1726
-			$share = $this->shareManager->getShareById('ocinternal:' . $id, $this->userId);
1727
-			return $share;
1728
-		} catch (ShareNotFound $e) {
1729
-			// Do nothing, just try the other share type
1730
-		}
1731
-
1732
-
1733
-		try {
1734
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
1735
-				$share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->userId);
1736
-				return $share;
1737
-			}
1738
-		} catch (ShareNotFound $e) {
1739
-			// Do nothing, just try the other share type
1740
-		}
1741
-
1742
-		try {
1743
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
1744
-				$share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->userId);
1745
-				return $share;
1746
-			}
1747
-		} catch (ShareNotFound $e) {
1748
-			// Do nothing, just try the other share type
1749
-		}
1750
-
1751
-		try {
1752
-			$share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->userId);
1753
-			return $share;
1754
-		} catch (ShareNotFound $e) {
1755
-			// Do nothing, just try the other share type
1756
-		}
1757
-
1758
-		try {
1759
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
1760
-				$share = $this->shareManager->getShareById('deck:' . $id, $this->userId);
1761
-				return $share;
1762
-			}
1763
-		} catch (ShareNotFound $e) {
1764
-			// Do nothing, just try the other share type
1765
-		}
1766
-
1767
-		try {
1768
-			if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
1769
-				$share = $this->shareManager->getShareById('sciencemesh:' . $id, $this->userId);
1770
-				return $share;
1771
-			}
1772
-		} catch (ShareNotFound $e) {
1773
-			// Do nothing, just try the other share type
1774
-		}
1775
-
1776
-		if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1777
-			throw new ShareNotFound();
1778
-		}
1779
-		$share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->userId);
1780
-
1781
-		return $share;
1782
-	}
1783
-
1784
-	/**
1785
-	 * Lock a Node
1786
-	 *
1787
-	 * @param Node $node
1788
-	 * @throws LockedException
1789
-	 */
1790
-	private function lock(Node $node) {
1791
-		$node->lock(ILockingProvider::LOCK_SHARED);
1792
-		$this->lockedNode = $node;
1793
-	}
1794
-
1795
-	/**
1796
-	 * Cleanup the remaining locks
1797
-	 * @throws LockedException
1798
-	 */
1799
-	public function cleanup() {
1800
-		if ($this->lockedNode !== null) {
1801
-			$this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
1802
-		}
1803
-	}
1804
-
1805
-	/**
1806
-	 * Returns the helper of ShareAPIController for room shares.
1807
-	 *
1808
-	 * If the Talk application is not enabled or the helper is not available
1809
-	 * a ContainerExceptionInterface is thrown instead.
1810
-	 *
1811
-	 * @return \OCA\Talk\Share\Helper\ShareAPIController
1812
-	 * @throws ContainerExceptionInterface
1813
-	 */
1814
-	private function getRoomShareHelper() {
1815
-		if (!$this->appManager->isEnabledForUser('spreed')) {
1816
-			throw new QueryException();
1817
-		}
1818
-
1819
-		return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
1820
-	}
1821
-
1822
-	/**
1823
-	 * Returns the helper of ShareAPIHelper for deck shares.
1824
-	 *
1825
-	 * If the Deck application is not enabled or the helper is not available
1826
-	 * a ContainerExceptionInterface is thrown instead.
1827
-	 *
1828
-	 * @return ShareAPIHelper
1829
-	 * @throws ContainerExceptionInterface
1830
-	 */
1831
-	private function getDeckShareHelper() {
1832
-		if (!$this->appManager->isEnabledForUser('deck')) {
1833
-			throw new QueryException();
1834
-		}
1835
-
1836
-		return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
1837
-	}
1838
-
1839
-	/**
1840
-	 * Returns the helper of ShareAPIHelper for sciencemesh shares.
1841
-	 *
1842
-	 * If the sciencemesh application is not enabled or the helper is not available
1843
-	 * a ContainerExceptionInterface is thrown instead.
1844
-	 *
1845
-	 * @return ShareAPIHelper
1846
-	 * @throws ContainerExceptionInterface
1847
-	 */
1848
-	private function getSciencemeshShareHelper() {
1849
-		if (!$this->appManager->isEnabledForUser('sciencemesh')) {
1850
-			throw new QueryException();
1851
-		}
1852
-
1853
-		return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
1854
-	}
1855
-
1856
-	/**
1857
-	 * @param string $viewer
1858
-	 * @param Node $node
1859
-	 * @param bool $reShares
1860
-	 *
1861
-	 * @return IShare[]
1862
-	 */
1863
-	private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
1864
-		$providers = [
1865
-			IShare::TYPE_USER,
1866
-			IShare::TYPE_GROUP,
1867
-			IShare::TYPE_LINK,
1868
-			IShare::TYPE_EMAIL,
1869
-			IShare::TYPE_CIRCLE,
1870
-			IShare::TYPE_ROOM,
1871
-			IShare::TYPE_DECK,
1872
-			IShare::TYPE_SCIENCEMESH
1873
-		];
1874
-
1875
-		// Should we assume that the (currentUser) viewer is the owner of the node !?
1876
-		$shares = [];
1877
-		foreach ($providers as $provider) {
1878
-			if (!$this->shareManager->shareProviderExists($provider)) {
1879
-				continue;
1880
-			}
1881
-
1882
-			$providerShares
1883
-				= $this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
1884
-			$shares = array_merge($shares, $providerShares);
1885
-		}
1886
-
1887
-		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
1888
-			$federatedShares = $this->shareManager->getSharesBy(
1889
-				$this->userId, IShare::TYPE_REMOTE, $node, $reShares, -1, 0
1890
-			);
1891
-			$shares = array_merge($shares, $federatedShares);
1892
-		}
1893
-
1894
-		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
1895
-			$federatedShares = $this->shareManager->getSharesBy(
1896
-				$this->userId, IShare::TYPE_REMOTE_GROUP, $node, $reShares, -1, 0
1897
-			);
1898
-			$shares = array_merge($shares, $federatedShares);
1899
-		}
1900
-
1901
-		return $shares;
1902
-	}
1903
-
1904
-
1905
-	/**
1906
-	 * @param Node $node
1907
-	 *
1908
-	 * @throws SharingRightsException
1909
-	 */
1910
-	private function confirmSharingRights(Node $node): void {
1911
-		if (!$this->hasResharingRights($this->userId, $node)) {
1912
-			throw new SharingRightsException($this->l->t('No sharing rights on this item'));
1913
-		}
1914
-	}
1915
-
1916
-
1917
-	/**
1918
-	 * @param string $viewer
1919
-	 * @param Node $node
1920
-	 *
1921
-	 * @return bool
1922
-	 */
1923
-	private function hasResharingRights($viewer, $node): bool {
1924
-		if ($viewer === $node->getOwner()->getUID()) {
1925
-			return true;
1926
-		}
1927
-
1928
-		foreach ([$node, $node->getParent()] as $node) {
1929
-			$shares = $this->getSharesFromNode($viewer, $node, true);
1930
-			foreach ($shares as $share) {
1931
-				try {
1932
-					if ($this->shareProviderResharingRights($viewer, $share, $node)) {
1933
-						return true;
1934
-					}
1935
-				} catch (InvalidPathException|NotFoundException $e) {
1936
-				}
1937
-			}
1938
-		}
1939
-
1940
-		return false;
1941
-	}
1942
-
1943
-
1944
-	/**
1945
-	 * Returns if we can find resharing rights in an IShare object for a specific user.
1946
-	 *
1947
-	 * @suppress PhanUndeclaredClassMethod
1948
-	 *
1949
-	 * @param string $userId
1950
-	 * @param IShare $share
1951
-	 * @param Node $node
1952
-	 *
1953
-	 * @return bool
1954
-	 * @throws NotFoundException
1955
-	 * @throws InvalidPathException
1956
-	 */
1957
-	private function shareProviderResharingRights(string $userId, IShare $share, $node): bool {
1958
-		if ($share->getShareOwner() === $userId) {
1959
-			return true;
1960
-		}
1961
-
1962
-		// we check that current user have parent resharing rights on the current file
1963
-		if ($node !== null && ($node->getPermissions() & Constants::PERMISSION_SHARE) !== 0) {
1964
-			return true;
1965
-		}
1966
-
1967
-		if ((Constants::PERMISSION_SHARE & $share->getPermissions()) === 0) {
1968
-			return false;
1969
-		}
1970
-
1971
-		if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() === $userId) {
1972
-			return true;
1973
-		}
1974
-
1975
-		if ($share->getShareType() === IShare::TYPE_GROUP && $this->groupManager->isInGroup($userId, $share->getSharedWith())) {
1976
-			return true;
1977
-		}
1978
-
1979
-		if ($share->getShareType() === IShare::TYPE_CIRCLE && Server::get(IAppManager::class)->isEnabledForUser('circles')
1980
-			&& class_exists('\OCA\Circles\Api\v1\Circles')) {
1981
-			$hasCircleId = (str_ends_with($share->getSharedWith(), ']'));
1982
-			$shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
1983
-			$shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
1984
-			if ($shareWithLength === false) {
1985
-				$sharedWith = substr($share->getSharedWith(), $shareWithStart);
1986
-			} else {
1987
-				$sharedWith = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
1988
-			}
1989
-			try {
1990
-				$member = Circles::getMember($sharedWith, $userId, 1);
1991
-				if ($member->getLevel() >= 4) {
1992
-					return true;
1993
-				}
1994
-				return false;
1995
-			} catch (ContainerExceptionInterface $e) {
1996
-				return false;
1997
-			}
1998
-		}
1999
-
2000
-		return false;
2001
-	}
2002
-
2003
-	/**
2004
-	 * Get all the shares for the current user
2005
-	 *
2006
-	 * @param Node|null $path
2007
-	 * @param boolean $reshares
2008
-	 * @return IShare[]
2009
-	 */
2010
-	private function getAllShares(?Node $path = null, bool $reshares = false) {
2011
-		// Get all shares
2012
-		$userShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_USER, $path, $reshares, -1, 0);
2013
-		$groupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
2014
-		$linkShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_LINK, $path, $reshares, -1, 0);
2015
-
2016
-		// EMAIL SHARES
2017
-		$mailShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);
2018
-
2019
-		// TEAM SHARES
2020
-		$circleShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);
2021
-
2022
-		// TALK SHARES
2023
-		$roomShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_ROOM, $path, $reshares, -1, 0);
2024
-
2025
-		// DECK SHARES
2026
-		$deckShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_DECK, $path, $reshares, -1, 0);
2027
-
2028
-		// SCIENCEMESH SHARES
2029
-		$sciencemeshShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_SCIENCEMESH, $path, $reshares, -1, 0);
2030
-
2031
-		// FEDERATION
2032
-		if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
2033
-			$federatedShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE, $path, $reshares, -1, 0);
2034
-		} else {
2035
-			$federatedShares = [];
2036
-		}
2037
-		if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
2038
-			$federatedGroupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
2039
-		} else {
2040
-			$federatedGroupShares = [];
2041
-		}
2042
-
2043
-		return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares, $federatedShares, $federatedGroupShares);
2044
-	}
2045
-
2046
-
2047
-	/**
2048
-	 * merging already formatted shares.
2049
-	 * We'll make an associative array to easily detect duplicate Ids.
2050
-	 * Keys _needs_ to be removed after all shares are retrieved and merged.
2051
-	 *
2052
-	 * @param array $shares
2053
-	 * @param array $newShares
2054
-	 */
2055
-	private function mergeFormattedShares(array &$shares, array $newShares) {
2056
-		foreach ($newShares as $newShare) {
2057
-			if (!array_key_exists($newShare['id'], $shares)) {
2058
-				$shares[$newShare['id']] = $newShare;
2059
-			}
2060
-		}
2061
-	}
2062
-
2063
-	/**
2064
-	 * @param IShare $share
2065
-	 * @param string|null $attributesString
2066
-	 * @return IShare modified share
2067
-	 */
2068
-	private function setShareAttributes(IShare $share, ?string $attributesString) {
2069
-		$newShareAttributes = null;
2070
-		if ($attributesString !== null) {
2071
-			$newShareAttributes = $this->shareManager->newShare()->newAttributes();
2072
-			$formattedShareAttributes = \json_decode($attributesString, true);
2073
-			if (is_array($formattedShareAttributes)) {
2074
-				foreach ($formattedShareAttributes as $formattedAttr) {
2075
-					$newShareAttributes->setAttribute(
2076
-						$formattedAttr['scope'],
2077
-						$formattedAttr['key'],
2078
-						$formattedAttr['value'],
2079
-					);
2080
-				}
2081
-			} else {
2082
-				throw new OCSBadRequestException($this->l->t('Invalid share attributes provided: "%s"', [$attributesString]));
2083
-			}
2084
-		}
2085
-		$share->setAttributes($newShareAttributes);
2086
-
2087
-		return $share;
2088
-	}
2089
-
2090
-	private function checkInheritedAttributes(IShare $share): void {
2091
-		if (!$share->getSharedBy()) {
2092
-			return; // Probably in a test
2093
-		}
2094
-
2095
-		$canDownload = false;
2096
-		$hideDownload = true;
2097
-
2098
-		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
2099
-		$nodes = $userFolder->getById($share->getNodeId());
2100
-		foreach ($nodes as $node) {
2101
-			// Owner always can download it - so allow it and break
2102
-			if ($node->getOwner()?->getUID() === $share->getSharedBy()) {
2103
-				$canDownload = true;
2104
-				$hideDownload = false;
2105
-				break;
2106
-			}
2107
-
2108
-			if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
2109
-				$storage = $node->getStorage();
2110
-				if ($storage instanceof Wrapper) {
2111
-					$storage = $storage->getInstanceOfStorage(SharedStorage::class);
2112
-					if ($storage === null) {
2113
-						throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
2114
-					}
2115
-				} else {
2116
-					throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
2117
-				}
2118
-
2119
-				/** @var SharedStorage $storage */
2120
-				$originalShare = $storage->getShare();
2121
-				$inheritedAttributes = $originalShare->getAttributes();
2122
-				// hide if hidden and also the current share enforces hide (can only be false if one share is false or user is owner)
2123
-				$hideDownload = $hideDownload && $originalShare->getHideDownload();
2124
-				// allow download if already allowed by previous share or when the current share allows downloading
2125
-				$canDownload = $canDownload || $inheritedAttributes === null || $inheritedAttributes->getAttribute('permissions', 'download') !== false;
2126
-			} elseif ($node->getStorage()->instanceOfStorage(Storage::class)) {
2127
-				$canDownload = true; // in case of federation storage, we can expect the download to be activated by default
2128
-			}
2129
-		}
2130
-
2131
-		if ($hideDownload || !$canDownload) {
2132
-			$share->setHideDownload(true);
2133
-
2134
-			if (!$canDownload) {
2135
-				$attributes = $share->getAttributes() ?? $share->newAttributes();
2136
-				$attributes->setAttribute('permissions', 'download', false);
2137
-				$share->setAttributes($attributes);
2138
-			}
2139
-		}
2140
-	}
2141
-
2142
-	/**
2143
-	 * Send a mail notification again for a share.
2144
-	 * The mail_send option must be enabled for the given share.
2145
-	 * @param string $id the share ID
2146
-	 * @param string $password the password to check against. Necessary for password protected shares.
2147
-	 * @throws OCSNotFoundException Share not found
2148
-	 * @throws OCSForbiddenException You are not allowed to send mail notifications
2149
-	 * @throws OCSBadRequestException Invalid request or wrong password
2150
-	 * @throws OCSException Error while sending mail notification
2151
-	 * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
2152
-	 *
2153
-	 * 200: The email notification was sent successfully
2154
-	 */
2155
-	#[NoAdminRequired]
2156
-	#[UserRateLimit(limit: 10, period: 600)]
2157
-	public function sendShareEmail(string $id, $password = ''): DataResponse {
2158
-		try {
2159
-			$share = $this->getShareById($id);
2160
-
2161
-			if (!$this->canAccessShare($share, false)) {
2162
-				throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2163
-			}
2164
-
2165
-			if (!$this->canEditShare($share)) {
2166
-				throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2167
-			}
2168
-
2169
-			// For mail and link shares, the user must be
2170
-			// the owner of the share, not only the file owner.
2171
-			if ($share->getShareType() === IShare::TYPE_EMAIL
2172
-				|| $share->getShareType() === IShare::TYPE_LINK) {
2173
-				if ($share->getSharedBy() !== $this->userId) {
2174
-					throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2175
-				}
2176
-			}
2177
-
2178
-			try {
2179
-				$provider = $this->factory->getProviderForType($share->getShareType());
2180
-				if (!($provider instanceof IShareProviderWithNotification)) {
2181
-					throw new OCSBadRequestException($this->l->t('No mail notification configured for this share type'));
2182
-				}
2183
-
2184
-				// Circumvent the password encrypted data by
2185
-				// setting the password clear. We're not storing
2186
-				// the password clear, it is just a temporary
2187
-				// object manipulation. The password will stay
2188
-				// encrypted in the database.
2189
-				if ($share->getPassword() !== null && $share->getPassword() !== $password) {
2190
-					if (!$this->shareManager->checkPassword($share, $password)) {
2191
-						throw new OCSBadRequestException($this->l->t('Wrong password'));
2192
-					}
2193
-					$share = $share->setPassword($password);
2194
-				}
2195
-
2196
-				$provider->sendMailNotification($share);
2197
-				return new DataResponse();
2198
-			} catch (Exception $e) {
2199
-				$this->logger->error($e->getMessage(), ['exception' => $e]);
2200
-				throw new OCSException($this->l->t('Error while sending mail notification'));
2201
-			}
2202
-
2203
-		} catch (ShareNotFound $e) {
2204
-			throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2205
-		}
2206
-	}
2207
-
2208
-	/**
2209
-	 * Get a unique share token
2210
-	 *
2211
-	 * @throws OCSException Failed to generate a unique token
2212
-	 *
2213
-	 * @return DataResponse<Http::STATUS_OK, array{token: string}, array{}>
2214
-	 *
2215
-	 * 200: Token generated successfully
2216
-	 */
2217
-	#[ApiRoute(verb: 'GET', url: '/api/v1/token')]
2218
-	#[NoAdminRequired]
2219
-	public function generateToken(): DataResponse {
2220
-		try {
2221
-			$token = $this->shareManager->generateToken();
2222
-			return new DataResponse([
2223
-				'token' => $token,
2224
-			]);
2225
-		} catch (ShareTokenException $e) {
2226
-			throw new OCSException($this->l->t('Failed to generate a unique token'));
2227
-		}
2228
-	}
2229
-
2230
-	/**
2231
-	 * Populate the result set with file tags
2232
-	 *
2233
-	 * @psalm-template T of array{tags?: list<string>, file_source: int, ...array<string, mixed>}
2234
-	 * @param list<T> $fileList
2235
-	 * @return list<T> file list populated with tags
2236
-	 */
2237
-	private function populateTags(array $fileList): array {
2238
-		$tagger = $this->tagManager->load('files');
2239
-		$tags = $tagger->getTagsForObjects(array_map(static fn (array $fileData) => $fileData['file_source'], $fileList));
2240
-
2241
-		if (!is_array($tags)) {
2242
-			throw new \UnexpectedValueException('$tags must be an array');
2243
-		}
2244
-
2245
-		// Set empty tag array
2246
-		foreach ($fileList as &$fileData) {
2247
-			$fileData['tags'] = [];
2248
-		}
2249
-		unset($fileData);
2250
-
2251
-		if (!empty($tags)) {
2252
-			foreach ($tags as $fileId => $fileTags) {
2253
-				foreach ($fileList as &$fileData) {
2254
-					if ($fileId !== $fileData['file_source']) {
2255
-						continue;
2256
-					}
2257
-
2258
-					$fileData['tags'] = $fileTags;
2259
-				}
2260
-				unset($fileData);
2261
-			}
2262
-		}
2263
-
2264
-		return $fileList;
2265
-	}
1071
+                continue;
1072
+            }
1073
+
1074
+            if (in_array($share->getId(), $known)
1075
+                || ($share->getSharedWith() === $this->userId && $share->getShareType() === IShare::TYPE_USER)) {
1076
+                continue;
1077
+            }
1078
+
1079
+            $known[] = $share->getId();
1080
+            try {
1081
+                /** @var IShare $share */
1082
+                $format = $this->formatShare($share, $node);
1083
+                $formatted[] = $format;
1084
+
1085
+                // let's also build a list of shares created
1086
+                // by the current user only, in case
1087
+                // there is no resharing rights
1088
+                if ($share->getSharedBy() === $this->userId) {
1089
+                    $miniFormatted[] = $format;
1090
+                }
1091
+
1092
+                // check if one of those share is shared with me
1093
+                // and if I have resharing rights on it
1094
+                if (!$resharingRight && $this->shareProviderResharingRights($this->userId, $share, $node)) {
1095
+                    $resharingRight = true;
1096
+                }
1097
+            } catch (InvalidPathException|NotFoundException $e) {
1098
+            }
1099
+        }
1100
+
1101
+        if (!$resharingRight) {
1102
+            $formatted = $miniFormatted;
1103
+        }
1104
+
1105
+        // fix eventual missing display name from federated shares
1106
+        $formatted = $this->fixMissingDisplayName($formatted);
1107
+
1108
+        if ($includeTags) {
1109
+            $formatted = $this->populateTags($formatted);
1110
+        }
1111
+
1112
+        return $formatted;
1113
+    }
1114
+
1115
+
1116
+    /**
1117
+     * Get all shares relative to a file, including parent folders shares rights
1118
+     *
1119
+     * @param string $path Path all shares will be relative to
1120
+     *
1121
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1122
+     * @throws InvalidPathException
1123
+     * @throws NotFoundException
1124
+     * @throws OCSNotFoundException The given path is invalid
1125
+     * @throws SharingRightsException
1126
+     *
1127
+     * 200: Shares returned
1128
+     */
1129
+    #[NoAdminRequired]
1130
+    public function getInheritedShares(string $path): DataResponse {
1131
+        // get Node from (string) path.
1132
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1133
+        try {
1134
+            $node = $userFolder->get($path);
1135
+            $this->lock($node);
1136
+        } catch (NotFoundException $e) {
1137
+            throw new OCSNotFoundException($this->l->t('Wrong path, file/folder does not exist'));
1138
+        } catch (LockedException $e) {
1139
+            throw new OCSNotFoundException($this->l->t('Could not lock path'));
1140
+        }
1141
+
1142
+        if (!($node->getPermissions() & Constants::PERMISSION_SHARE)) {
1143
+            throw new SharingRightsException($this->l->t('no sharing rights on this item'));
1144
+        }
1145
+
1146
+        // The current top parent we have access to
1147
+        $parent = $node;
1148
+
1149
+        // initiate real owner.
1150
+        $owner = $node->getOwner()
1151
+            ->getUID();
1152
+        if (!$this->userManager->userExists($owner)) {
1153
+            return new DataResponse([]);
1154
+        }
1155
+
1156
+        // get node based on the owner, fix owner in case of external storage
1157
+        $userFolder = $this->rootFolder->getUserFolder($owner);
1158
+        if ($node->getId() !== $userFolder->getId() && !$userFolder->isSubNode($node)) {
1159
+            $owner = $node->getOwner()
1160
+                ->getUID();
1161
+            $userFolder = $this->rootFolder->getUserFolder($owner);
1162
+            $node = $userFolder->getFirstNodeById($node->getId());
1163
+        }
1164
+        $basePath = $userFolder->getPath();
1165
+
1166
+        // generate node list for each parent folders
1167
+        /** @var Node[] $nodes */
1168
+        $nodes = [];
1169
+        while (true) {
1170
+            $node = $node->getParent();
1171
+            if ($node->getPath() === $basePath) {
1172
+                break;
1173
+            }
1174
+            $nodes[] = $node;
1175
+        }
1176
+
1177
+        // The user that is requesting this list
1178
+        $currentUserFolder = $this->rootFolder->getUserFolder($this->userId);
1179
+
1180
+        // for each nodes, retrieve shares.
1181
+        $shares = [];
1182
+
1183
+        foreach ($nodes as $node) {
1184
+            $getShares = $this->getFormattedShares($owner, $node, false, true);
1185
+
1186
+            $currentUserNode = $currentUserFolder->getFirstNodeById($node->getId());
1187
+            if ($currentUserNode) {
1188
+                $parent = $currentUserNode;
1189
+            }
1190
+
1191
+            $subPath = $currentUserFolder->getRelativePath($parent->getPath());
1192
+            foreach ($getShares as &$share) {
1193
+                $share['via_fileid'] = $parent->getId();
1194
+                $share['via_path'] = $subPath;
1195
+            }
1196
+            $this->mergeFormattedShares($shares, $getShares);
1197
+        }
1198
+
1199
+        return new DataResponse(array_values($shares));
1200
+    }
1201
+
1202
+    /**
1203
+     * Check whether a set of permissions contains the permissions to check.
1204
+     */
1205
+    private function hasPermission(int $permissionsSet, int $permissionsToCheck): bool {
1206
+        return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
1207
+    }
1208
+
1209
+    /**
1210
+     * Update a share
1211
+     *
1212
+     * @param string $id ID of the share
1213
+     * @param int|null $permissions New permissions
1214
+     * @param string|null $password New password
1215
+     * @param string|null $sendPasswordByTalk New condition if the password should be send over Talk
1216
+     * @param string|null $publicUpload New condition if public uploading is allowed
1217
+     * @param string|null $expireDate New expiry date
1218
+     * @param string|null $note New note
1219
+     * @param string|null $label New label
1220
+     * @param string|null $hideDownload New condition if the download should be hidden
1221
+     * @param string|null $attributes New additional attributes
1222
+     * @param string|null $sendMail if the share should be send by mail.
1223
+     *                              Considering the share already exists, no mail will be send after the share is updated.
1224
+     *                              You will have to use the sendMail action to send the mail.
1225
+     * @param string|null $shareWith New recipient for email shares
1226
+     * @param string|null $token New token
1227
+     * @return DataResponse<Http::STATUS_OK, Files_SharingShare, array{}>
1228
+     * @throws OCSBadRequestException Share could not be updated because the requested changes are invalid
1229
+     * @throws OCSForbiddenException Missing permissions to update the share
1230
+     * @throws OCSNotFoundException Share not found
1231
+     *
1232
+     * 200: Share updated successfully
1233
+     */
1234
+    #[NoAdminRequired]
1235
+    public function updateShare(
1236
+        string $id,
1237
+        ?int $permissions = null,
1238
+        ?string $password = null,
1239
+        ?string $sendPasswordByTalk = null,
1240
+        ?string $publicUpload = null,
1241
+        ?string $expireDate = null,
1242
+        ?string $note = null,
1243
+        ?string $label = null,
1244
+        ?string $hideDownload = null,
1245
+        ?string $attributes = null,
1246
+        ?string $sendMail = null,
1247
+        ?string $token = null,
1248
+    ): DataResponse {
1249
+        try {
1250
+            $share = $this->getShareById($id);
1251
+        } catch (ShareNotFound $e) {
1252
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1253
+        }
1254
+
1255
+        $this->lock($share->getNode());
1256
+
1257
+        if (!$this->canAccessShare($share, false)) {
1258
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1259
+        }
1260
+
1261
+        if (!$this->canEditShare($share)) {
1262
+            throw new OCSForbiddenException($this->l->t('You are not allowed to edit incoming shares'));
1263
+        }
1264
+
1265
+        if (
1266
+            $permissions === null
1267
+            && $password === null
1268
+            && $sendPasswordByTalk === null
1269
+            && $publicUpload === null
1270
+            && $expireDate === null
1271
+            && $note === null
1272
+            && $label === null
1273
+            && $hideDownload === null
1274
+            && $attributes === null
1275
+            && $sendMail === null
1276
+            && $token === null
1277
+        ) {
1278
+            throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
1279
+        }
1280
+
1281
+        if ($note !== null) {
1282
+            $share->setNote($note);
1283
+        }
1284
+
1285
+        if ($attributes !== null) {
1286
+            $share = $this->setShareAttributes($share, $attributes);
1287
+        }
1288
+
1289
+        // Handle mail send
1290
+        if ($sendMail === 'true' || $sendMail === 'false') {
1291
+            $share->setMailSend($sendMail === 'true');
1292
+        }
1293
+
1294
+        /**
1295
+         * expiration date, password and publicUpload only make sense for link shares
1296
+         */
1297
+        if ($share->getShareType() === IShare::TYPE_LINK
1298
+            || $share->getShareType() === IShare::TYPE_EMAIL) {
1299
+
1300
+            // Update hide download state
1301
+            if ($hideDownload === 'true') {
1302
+                $share->setHideDownload(true);
1303
+            } elseif ($hideDownload === 'false') {
1304
+                $share->setHideDownload(false);
1305
+            }
1306
+
1307
+            // If either manual permissions are specified or publicUpload
1308
+            // then we need to also update the permissions of the share
1309
+            if ($permissions !== null || $publicUpload !== null) {
1310
+                $hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
1311
+                $permissions = $this->getLinkSharePermissions($permissions ?? Constants::PERMISSION_READ, $hasPublicUpload);
1312
+                $this->validateLinkSharePermissions($share->getNode(), $permissions, $hasPublicUpload);
1313
+                $share->setPermissions($permissions);
1314
+            }
1315
+
1316
+            if ($password === '') {
1317
+                $share->setPassword(null);
1318
+            } elseif ($password !== null) {
1319
+                $share->setPassword($password);
1320
+            }
1321
+
1322
+            if ($label !== null) {
1323
+                if (strlen($label) > 255) {
1324
+                    throw new OCSBadRequestException('Maximum label length is 255');
1325
+                }
1326
+                $share->setLabel($label);
1327
+            }
1328
+
1329
+            if ($sendPasswordByTalk === 'true') {
1330
+                if (!$this->appManager->isEnabledForUser('spreed')) {
1331
+                    throw new OCSForbiddenException($this->l->t('"Sending the password by Nextcloud Talk" for sharing a file or folder failed because Nextcloud Talk is not enabled.'));
1332
+                }
1333
+
1334
+                $share->setSendPasswordByTalk(true);
1335
+            } elseif ($sendPasswordByTalk !== null) {
1336
+                $share->setSendPasswordByTalk(false);
1337
+            }
1338
+
1339
+            if ($token !== null) {
1340
+                if (!$this->shareManager->allowCustomTokens()) {
1341
+                    throw new OCSForbiddenException($this->l->t('Custom share link tokens have been disabled by the administrator'));
1342
+                }
1343
+                if (!$this->validateToken($token)) {
1344
+                    throw new OCSBadRequestException($this->l->t('Tokens must contain at least 1 character and may only contain letters, numbers, or a hyphen'));
1345
+                }
1346
+                $share->setToken($token);
1347
+            }
1348
+        }
1349
+
1350
+        // NOT A LINK SHARE
1351
+        else {
1352
+            if ($permissions !== null) {
1353
+                $share->setPermissions($permissions);
1354
+            }
1355
+        }
1356
+
1357
+        if ($expireDate === '') {
1358
+            $share->setExpirationDate(null);
1359
+        } elseif ($expireDate !== null) {
1360
+            try {
1361
+                $expireDateTime = $this->parseDate($expireDate);
1362
+                $share->setExpirationDate($expireDateTime);
1363
+            } catch (\Exception $e) {
1364
+                throw new OCSBadRequestException($e->getMessage(), $e);
1365
+            }
1366
+        }
1367
+
1368
+        try {
1369
+            $this->checkInheritedAttributes($share);
1370
+            $share = $this->shareManager->updateShare($share);
1371
+        } catch (HintException $e) {
1372
+            $code = $e->getCode() === 0 ? 403 : $e->getCode();
1373
+            throw new OCSException($e->getHint(), (int)$code);
1374
+        } catch (\Exception $e) {
1375
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
1376
+            throw new OCSBadRequestException('Failed to update share.', $e);
1377
+        }
1378
+
1379
+        return new DataResponse($this->formatShare($share));
1380
+    }
1381
+
1382
+    private function validateToken(string $token): bool {
1383
+        if (mb_strlen($token) === 0) {
1384
+            return false;
1385
+        }
1386
+        if (!preg_match('/^[a-z0-9-]+$/i', $token)) {
1387
+            return false;
1388
+        }
1389
+        return true;
1390
+    }
1391
+
1392
+    /**
1393
+     * Get all shares that are still pending
1394
+     *
1395
+     * @return DataResponse<Http::STATUS_OK, list<Files_SharingShare>, array{}>
1396
+     *
1397
+     * 200: Pending shares returned
1398
+     */
1399
+    #[NoAdminRequired]
1400
+    public function pendingShares(): DataResponse {
1401
+        $pendingShares = [];
1402
+
1403
+        $shareTypes = [
1404
+            IShare::TYPE_USER,
1405
+            IShare::TYPE_GROUP
1406
+        ];
1407
+
1408
+        foreach ($shareTypes as $shareType) {
1409
+            $shares = $this->shareManager->getSharedWith($this->userId, $shareType, null, -1, 0);
1410
+
1411
+            foreach ($shares as $share) {
1412
+                if ($share->getStatus() === IShare::STATUS_PENDING || $share->getStatus() === IShare::STATUS_REJECTED) {
1413
+                    $pendingShares[] = $share;
1414
+                }
1415
+            }
1416
+        }
1417
+
1418
+        $result = array_values(array_filter(array_map(function (IShare $share) {
1419
+            $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
1420
+            $node = $userFolder->getFirstNodeById($share->getNodeId());
1421
+            if (!$node) {
1422
+                // fallback to guessing the path
1423
+                $node = $userFolder->get($share->getTarget());
1424
+                if ($node === null || $share->getTarget() === '') {
1425
+                    return null;
1426
+                }
1427
+            }
1428
+
1429
+            try {
1430
+                $formattedShare = $this->formatShare($share, $node);
1431
+                $formattedShare['path'] = '/' . $share->getNode()->getName();
1432
+                $formattedShare['permissions'] = 0;
1433
+                return $formattedShare;
1434
+            } catch (NotFoundException $e) {
1435
+                return null;
1436
+            }
1437
+        }, $pendingShares), function ($entry) {
1438
+            return $entry !== null;
1439
+        }));
1440
+
1441
+        return new DataResponse($result);
1442
+    }
1443
+
1444
+    /**
1445
+     * Accept a share
1446
+     *
1447
+     * @param string $id ID of the share
1448
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
1449
+     * @throws OCSNotFoundException Share not found
1450
+     * @throws OCSException
1451
+     * @throws OCSBadRequestException Share could not be accepted
1452
+     *
1453
+     * 200: Share accepted successfully
1454
+     */
1455
+    #[NoAdminRequired]
1456
+    public function acceptShare(string $id): DataResponse {
1457
+        try {
1458
+            $share = $this->getShareById($id);
1459
+        } catch (ShareNotFound $e) {
1460
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1461
+        }
1462
+
1463
+        if (!$this->canAccessShare($share)) {
1464
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
1465
+        }
1466
+
1467
+        try {
1468
+            $this->shareManager->acceptShare($share, $this->userId);
1469
+        } catch (HintException $e) {
1470
+            $code = $e->getCode() === 0 ? 403 : $e->getCode();
1471
+            throw new OCSException($e->getHint(), (int)$code);
1472
+        } catch (\Exception $e) {
1473
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
1474
+            throw new OCSBadRequestException('Failed to accept share.', $e);
1475
+        }
1476
+
1477
+        return new DataResponse();
1478
+    }
1479
+
1480
+    /**
1481
+     * Does the user have read permission on the share
1482
+     *
1483
+     * @param IShare $share the share to check
1484
+     * @param boolean $checkGroups check groups as well?
1485
+     * @return boolean
1486
+     * @throws NotFoundException
1487
+     *
1488
+     * @suppress PhanUndeclaredClassMethod
1489
+     */
1490
+    protected function canAccessShare(IShare $share, bool $checkGroups = true): bool {
1491
+        // A file with permissions 0 can't be accessed by us. So Don't show it
1492
+        if ($share->getPermissions() === 0) {
1493
+            return false;
1494
+        }
1495
+
1496
+        // Owner of the file and the sharer of the file can always get share
1497
+        if ($share->getShareOwner() === $this->userId
1498
+            || $share->getSharedBy() === $this->userId) {
1499
+            return true;
1500
+        }
1501
+
1502
+        // If the share is shared with you, you can access it!
1503
+        if ($share->getShareType() === IShare::TYPE_USER
1504
+            && $share->getSharedWith() === $this->userId) {
1505
+            return true;
1506
+        }
1507
+
1508
+        // Have reshare rights on the shared file/folder ?
1509
+        // Does the currentUser have access to the shared file?
1510
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1511
+        $file = $userFolder->getFirstNodeById($share->getNodeId());
1512
+        if ($file && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1513
+            return true;
1514
+        }
1515
+
1516
+        // If in the recipient group, you can see the share
1517
+        if ($checkGroups && $share->getShareType() === IShare::TYPE_GROUP) {
1518
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
1519
+            $user = $this->userManager->get($this->userId);
1520
+            if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1521
+                return true;
1522
+            }
1523
+        }
1524
+
1525
+        if ($share->getShareType() === IShare::TYPE_CIRCLE) {
1526
+            // TODO: have a sanity check like above?
1527
+            return true;
1528
+        }
1529
+
1530
+        if ($share->getShareType() === IShare::TYPE_ROOM) {
1531
+            try {
1532
+                return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1533
+            } catch (ContainerExceptionInterface $e) {
1534
+                return false;
1535
+            }
1536
+        }
1537
+
1538
+        if ($share->getShareType() === IShare::TYPE_DECK) {
1539
+            try {
1540
+                return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1541
+            } catch (ContainerExceptionInterface $e) {
1542
+                return false;
1543
+            }
1544
+        }
1545
+
1546
+        if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1547
+            try {
1548
+                return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1549
+            } catch (ContainerExceptionInterface $e) {
1550
+                return false;
1551
+            }
1552
+        }
1553
+
1554
+        return false;
1555
+    }
1556
+
1557
+    /**
1558
+     * Does the user have edit permission on the share
1559
+     *
1560
+     * @param IShare $share the share to check
1561
+     * @return boolean
1562
+     */
1563
+    protected function canEditShare(IShare $share): bool {
1564
+        // A file with permissions 0 can't be accessed by us. So Don't show it
1565
+        if ($share->getPermissions() === 0) {
1566
+            return false;
1567
+        }
1568
+
1569
+        // The owner of the file and the creator of the share
1570
+        // can always edit the share
1571
+        if ($share->getShareOwner() === $this->userId
1572
+            || $share->getSharedBy() === $this->userId
1573
+        ) {
1574
+            return true;
1575
+        }
1576
+
1577
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1578
+        $file = $userFolder->getFirstNodeById($share->getNodeId());
1579
+        if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1580
+            return true;
1581
+        }
1582
+
1583
+        //! we do NOT support some kind of `admin` in groups.
1584
+        //! You cannot edit shares shared to a group you're
1585
+        //! a member of if you're not the share owner or the file owner!
1586
+
1587
+        return false;
1588
+    }
1589
+
1590
+    /**
1591
+     * Does the user have delete permission on the share
1592
+     *
1593
+     * @param IShare $share the share to check
1594
+     * @return boolean
1595
+     */
1596
+    protected function canDeleteShare(IShare $share): bool {
1597
+        // A file with permissions 0 can't be accessed by us. So Don't show it
1598
+        if ($share->getPermissions() === 0) {
1599
+            return false;
1600
+        }
1601
+
1602
+        // if the user is the recipient, i can unshare
1603
+        // the share with self
1604
+        if ($share->getShareType() === IShare::TYPE_USER
1605
+            && $share->getSharedWith() === $this->userId
1606
+        ) {
1607
+            return true;
1608
+        }
1609
+
1610
+        // The owner of the file and the creator of the share
1611
+        // can always delete the share
1612
+        if ($share->getShareOwner() === $this->userId
1613
+            || $share->getSharedBy() === $this->userId
1614
+        ) {
1615
+            return true;
1616
+        }
1617
+
1618
+        $userFolder = $this->rootFolder->getUserFolder($this->userId);
1619
+        $file = $userFolder->getFirstNodeById($share->getNodeId());
1620
+        if ($file?->getMountPoint() instanceof IShareOwnerlessMount && $this->shareProviderResharingRights($this->userId, $share, $file)) {
1621
+            return true;
1622
+        }
1623
+
1624
+        return false;
1625
+    }
1626
+
1627
+    /**
1628
+     * Does the user have delete permission on the share
1629
+     * This differs from the canDeleteShare function as it only
1630
+     * remove the share for the current user. It does NOT
1631
+     * completely delete the share but only the mount point.
1632
+     * It can then be restored from the deleted shares section.
1633
+     *
1634
+     * @param IShare $share the share to check
1635
+     * @return boolean
1636
+     *
1637
+     * @suppress PhanUndeclaredClassMethod
1638
+     */
1639
+    protected function canDeleteShareFromSelf(IShare $share): bool {
1640
+        if ($share->getShareType() !== IShare::TYPE_GROUP
1641
+            && $share->getShareType() !== IShare::TYPE_ROOM
1642
+            && $share->getShareType() !== IShare::TYPE_DECK
1643
+            && $share->getShareType() !== IShare::TYPE_SCIENCEMESH
1644
+        ) {
1645
+            return false;
1646
+        }
1647
+
1648
+        if ($share->getShareOwner() === $this->userId
1649
+            || $share->getSharedBy() === $this->userId
1650
+        ) {
1651
+            // Delete the whole share, not just for self
1652
+            return false;
1653
+        }
1654
+
1655
+        // If in the recipient group, you can delete the share from self
1656
+        if ($share->getShareType() === IShare::TYPE_GROUP) {
1657
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
1658
+            $user = $this->userManager->get($this->userId);
1659
+            if ($user !== null && $sharedWith !== null && $sharedWith->inGroup($user)) {
1660
+                return true;
1661
+            }
1662
+        }
1663
+
1664
+        if ($share->getShareType() === IShare::TYPE_ROOM) {
1665
+            try {
1666
+                return $this->getRoomShareHelper()->canAccessShare($share, $this->userId);
1667
+            } catch (ContainerExceptionInterface $e) {
1668
+                return false;
1669
+            }
1670
+        }
1671
+
1672
+        if ($share->getShareType() === IShare::TYPE_DECK) {
1673
+            try {
1674
+                return $this->getDeckShareHelper()->canAccessShare($share, $this->userId);
1675
+            } catch (ContainerExceptionInterface $e) {
1676
+                return false;
1677
+            }
1678
+        }
1679
+
1680
+        if ($share->getShareType() === IShare::TYPE_SCIENCEMESH) {
1681
+            try {
1682
+                return $this->getSciencemeshShareHelper()->canAccessShare($share, $this->userId);
1683
+            } catch (ContainerExceptionInterface $e) {
1684
+                return false;
1685
+            }
1686
+        }
1687
+
1688
+        return false;
1689
+    }
1690
+
1691
+    /**
1692
+     * Make sure that the passed date is valid ISO 8601
1693
+     * So YYYY-MM-DD
1694
+     * If not throw an exception
1695
+     *
1696
+     * @param string $expireDate
1697
+     *
1698
+     * @throws \Exception
1699
+     * @return \DateTime
1700
+     */
1701
+    private function parseDate(string $expireDate): \DateTime {
1702
+        try {
1703
+            $date = new \DateTime(trim($expireDate, '"'), $this->dateTimeZone->getTimeZone());
1704
+            // Make sure it expires at midnight in owner timezone
1705
+            $date->setTime(0, 0, 0);
1706
+        } catch (\Exception $e) {
1707
+            throw new \Exception($this->l->t('Invalid date. Format must be YYYY-MM-DD'));
1708
+        }
1709
+
1710
+        return $date;
1711
+    }
1712
+
1713
+    /**
1714
+     * Since we have multiple providers but the OCS Share API v1 does
1715
+     * not support this we need to check all backends.
1716
+     *
1717
+     * @param string $id
1718
+     * @return IShare
1719
+     * @throws ShareNotFound
1720
+     */
1721
+    private function getShareById(string $id): IShare {
1722
+        $share = null;
1723
+
1724
+        // First check if it is an internal share.
1725
+        try {
1726
+            $share = $this->shareManager->getShareById('ocinternal:' . $id, $this->userId);
1727
+            return $share;
1728
+        } catch (ShareNotFound $e) {
1729
+            // Do nothing, just try the other share type
1730
+        }
1731
+
1732
+
1733
+        try {
1734
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_CIRCLE)) {
1735
+                $share = $this->shareManager->getShareById('ocCircleShare:' . $id, $this->userId);
1736
+                return $share;
1737
+            }
1738
+        } catch (ShareNotFound $e) {
1739
+            // Do nothing, just try the other share type
1740
+        }
1741
+
1742
+        try {
1743
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_EMAIL)) {
1744
+                $share = $this->shareManager->getShareById('ocMailShare:' . $id, $this->userId);
1745
+                return $share;
1746
+            }
1747
+        } catch (ShareNotFound $e) {
1748
+            // Do nothing, just try the other share type
1749
+        }
1750
+
1751
+        try {
1752
+            $share = $this->shareManager->getShareById('ocRoomShare:' . $id, $this->userId);
1753
+            return $share;
1754
+        } catch (ShareNotFound $e) {
1755
+            // Do nothing, just try the other share type
1756
+        }
1757
+
1758
+        try {
1759
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_DECK)) {
1760
+                $share = $this->shareManager->getShareById('deck:' . $id, $this->userId);
1761
+                return $share;
1762
+            }
1763
+        } catch (ShareNotFound $e) {
1764
+            // Do nothing, just try the other share type
1765
+        }
1766
+
1767
+        try {
1768
+            if ($this->shareManager->shareProviderExists(IShare::TYPE_SCIENCEMESH)) {
1769
+                $share = $this->shareManager->getShareById('sciencemesh:' . $id, $this->userId);
1770
+                return $share;
1771
+            }
1772
+        } catch (ShareNotFound $e) {
1773
+            // Do nothing, just try the other share type
1774
+        }
1775
+
1776
+        if (!$this->shareManager->outgoingServer2ServerSharesAllowed()) {
1777
+            throw new ShareNotFound();
1778
+        }
1779
+        $share = $this->shareManager->getShareById('ocFederatedSharing:' . $id, $this->userId);
1780
+
1781
+        return $share;
1782
+    }
1783
+
1784
+    /**
1785
+     * Lock a Node
1786
+     *
1787
+     * @param Node $node
1788
+     * @throws LockedException
1789
+     */
1790
+    private function lock(Node $node) {
1791
+        $node->lock(ILockingProvider::LOCK_SHARED);
1792
+        $this->lockedNode = $node;
1793
+    }
1794
+
1795
+    /**
1796
+     * Cleanup the remaining locks
1797
+     * @throws LockedException
1798
+     */
1799
+    public function cleanup() {
1800
+        if ($this->lockedNode !== null) {
1801
+            $this->lockedNode->unlock(ILockingProvider::LOCK_SHARED);
1802
+        }
1803
+    }
1804
+
1805
+    /**
1806
+     * Returns the helper of ShareAPIController for room shares.
1807
+     *
1808
+     * If the Talk application is not enabled or the helper is not available
1809
+     * a ContainerExceptionInterface is thrown instead.
1810
+     *
1811
+     * @return \OCA\Talk\Share\Helper\ShareAPIController
1812
+     * @throws ContainerExceptionInterface
1813
+     */
1814
+    private function getRoomShareHelper() {
1815
+        if (!$this->appManager->isEnabledForUser('spreed')) {
1816
+            throw new QueryException();
1817
+        }
1818
+
1819
+        return $this->serverContainer->get('\OCA\Talk\Share\Helper\ShareAPIController');
1820
+    }
1821
+
1822
+    /**
1823
+     * Returns the helper of ShareAPIHelper for deck shares.
1824
+     *
1825
+     * If the Deck application is not enabled or the helper is not available
1826
+     * a ContainerExceptionInterface is thrown instead.
1827
+     *
1828
+     * @return ShareAPIHelper
1829
+     * @throws ContainerExceptionInterface
1830
+     */
1831
+    private function getDeckShareHelper() {
1832
+        if (!$this->appManager->isEnabledForUser('deck')) {
1833
+            throw new QueryException();
1834
+        }
1835
+
1836
+        return $this->serverContainer->get('\OCA\Deck\Sharing\ShareAPIHelper');
1837
+    }
1838
+
1839
+    /**
1840
+     * Returns the helper of ShareAPIHelper for sciencemesh shares.
1841
+     *
1842
+     * If the sciencemesh application is not enabled or the helper is not available
1843
+     * a ContainerExceptionInterface is thrown instead.
1844
+     *
1845
+     * @return ShareAPIHelper
1846
+     * @throws ContainerExceptionInterface
1847
+     */
1848
+    private function getSciencemeshShareHelper() {
1849
+        if (!$this->appManager->isEnabledForUser('sciencemesh')) {
1850
+            throw new QueryException();
1851
+        }
1852
+
1853
+        return $this->serverContainer->get('\OCA\ScienceMesh\Sharing\ShareAPIHelper');
1854
+    }
1855
+
1856
+    /**
1857
+     * @param string $viewer
1858
+     * @param Node $node
1859
+     * @param bool $reShares
1860
+     *
1861
+     * @return IShare[]
1862
+     */
1863
+    private function getSharesFromNode(string $viewer, $node, bool $reShares): array {
1864
+        $providers = [
1865
+            IShare::TYPE_USER,
1866
+            IShare::TYPE_GROUP,
1867
+            IShare::TYPE_LINK,
1868
+            IShare::TYPE_EMAIL,
1869
+            IShare::TYPE_CIRCLE,
1870
+            IShare::TYPE_ROOM,
1871
+            IShare::TYPE_DECK,
1872
+            IShare::TYPE_SCIENCEMESH
1873
+        ];
1874
+
1875
+        // Should we assume that the (currentUser) viewer is the owner of the node !?
1876
+        $shares = [];
1877
+        foreach ($providers as $provider) {
1878
+            if (!$this->shareManager->shareProviderExists($provider)) {
1879
+                continue;
1880
+            }
1881
+
1882
+            $providerShares
1883
+                = $this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0);
1884
+            $shares = array_merge($shares, $providerShares);
1885
+        }
1886
+
1887
+        if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
1888
+            $federatedShares = $this->shareManager->getSharesBy(
1889
+                $this->userId, IShare::TYPE_REMOTE, $node, $reShares, -1, 0
1890
+            );
1891
+            $shares = array_merge($shares, $federatedShares);
1892
+        }
1893
+
1894
+        if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
1895
+            $federatedShares = $this->shareManager->getSharesBy(
1896
+                $this->userId, IShare::TYPE_REMOTE_GROUP, $node, $reShares, -1, 0
1897
+            );
1898
+            $shares = array_merge($shares, $federatedShares);
1899
+        }
1900
+
1901
+        return $shares;
1902
+    }
1903
+
1904
+
1905
+    /**
1906
+     * @param Node $node
1907
+     *
1908
+     * @throws SharingRightsException
1909
+     */
1910
+    private function confirmSharingRights(Node $node): void {
1911
+        if (!$this->hasResharingRights($this->userId, $node)) {
1912
+            throw new SharingRightsException($this->l->t('No sharing rights on this item'));
1913
+        }
1914
+    }
1915
+
1916
+
1917
+    /**
1918
+     * @param string $viewer
1919
+     * @param Node $node
1920
+     *
1921
+     * @return bool
1922
+     */
1923
+    private function hasResharingRights($viewer, $node): bool {
1924
+        if ($viewer === $node->getOwner()->getUID()) {
1925
+            return true;
1926
+        }
1927
+
1928
+        foreach ([$node, $node->getParent()] as $node) {
1929
+            $shares = $this->getSharesFromNode($viewer, $node, true);
1930
+            foreach ($shares as $share) {
1931
+                try {
1932
+                    if ($this->shareProviderResharingRights($viewer, $share, $node)) {
1933
+                        return true;
1934
+                    }
1935
+                } catch (InvalidPathException|NotFoundException $e) {
1936
+                }
1937
+            }
1938
+        }
1939
+
1940
+        return false;
1941
+    }
1942
+
1943
+
1944
+    /**
1945
+     * Returns if we can find resharing rights in an IShare object for a specific user.
1946
+     *
1947
+     * @suppress PhanUndeclaredClassMethod
1948
+     *
1949
+     * @param string $userId
1950
+     * @param IShare $share
1951
+     * @param Node $node
1952
+     *
1953
+     * @return bool
1954
+     * @throws NotFoundException
1955
+     * @throws InvalidPathException
1956
+     */
1957
+    private function shareProviderResharingRights(string $userId, IShare $share, $node): bool {
1958
+        if ($share->getShareOwner() === $userId) {
1959
+            return true;
1960
+        }
1961
+
1962
+        // we check that current user have parent resharing rights on the current file
1963
+        if ($node !== null && ($node->getPermissions() & Constants::PERMISSION_SHARE) !== 0) {
1964
+            return true;
1965
+        }
1966
+
1967
+        if ((Constants::PERMISSION_SHARE & $share->getPermissions()) === 0) {
1968
+            return false;
1969
+        }
1970
+
1971
+        if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() === $userId) {
1972
+            return true;
1973
+        }
1974
+
1975
+        if ($share->getShareType() === IShare::TYPE_GROUP && $this->groupManager->isInGroup($userId, $share->getSharedWith())) {
1976
+            return true;
1977
+        }
1978
+
1979
+        if ($share->getShareType() === IShare::TYPE_CIRCLE && Server::get(IAppManager::class)->isEnabledForUser('circles')
1980
+            && class_exists('\OCA\Circles\Api\v1\Circles')) {
1981
+            $hasCircleId = (str_ends_with($share->getSharedWith(), ']'));
1982
+            $shareWithStart = ($hasCircleId ? strrpos($share->getSharedWith(), '[') + 1 : 0);
1983
+            $shareWithLength = ($hasCircleId ? -1 : strpos($share->getSharedWith(), ' '));
1984
+            if ($shareWithLength === false) {
1985
+                $sharedWith = substr($share->getSharedWith(), $shareWithStart);
1986
+            } else {
1987
+                $sharedWith = substr($share->getSharedWith(), $shareWithStart, $shareWithLength);
1988
+            }
1989
+            try {
1990
+                $member = Circles::getMember($sharedWith, $userId, 1);
1991
+                if ($member->getLevel() >= 4) {
1992
+                    return true;
1993
+                }
1994
+                return false;
1995
+            } catch (ContainerExceptionInterface $e) {
1996
+                return false;
1997
+            }
1998
+        }
1999
+
2000
+        return false;
2001
+    }
2002
+
2003
+    /**
2004
+     * Get all the shares for the current user
2005
+     *
2006
+     * @param Node|null $path
2007
+     * @param boolean $reshares
2008
+     * @return IShare[]
2009
+     */
2010
+    private function getAllShares(?Node $path = null, bool $reshares = false) {
2011
+        // Get all shares
2012
+        $userShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_USER, $path, $reshares, -1, 0);
2013
+        $groupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_GROUP, $path, $reshares, -1, 0);
2014
+        $linkShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_LINK, $path, $reshares, -1, 0);
2015
+
2016
+        // EMAIL SHARES
2017
+        $mailShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_EMAIL, $path, $reshares, -1, 0);
2018
+
2019
+        // TEAM SHARES
2020
+        $circleShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_CIRCLE, $path, $reshares, -1, 0);
2021
+
2022
+        // TALK SHARES
2023
+        $roomShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_ROOM, $path, $reshares, -1, 0);
2024
+
2025
+        // DECK SHARES
2026
+        $deckShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_DECK, $path, $reshares, -1, 0);
2027
+
2028
+        // SCIENCEMESH SHARES
2029
+        $sciencemeshShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_SCIENCEMESH, $path, $reshares, -1, 0);
2030
+
2031
+        // FEDERATION
2032
+        if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
2033
+            $federatedShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE, $path, $reshares, -1, 0);
2034
+        } else {
2035
+            $federatedShares = [];
2036
+        }
2037
+        if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) {
2038
+            $federatedGroupShares = $this->shareManager->getSharesBy($this->userId, IShare::TYPE_REMOTE_GROUP, $path, $reshares, -1, 0);
2039
+        } else {
2040
+            $federatedGroupShares = [];
2041
+        }
2042
+
2043
+        return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $deckShares, $sciencemeshShares, $federatedShares, $federatedGroupShares);
2044
+    }
2045
+
2046
+
2047
+    /**
2048
+     * merging already formatted shares.
2049
+     * We'll make an associative array to easily detect duplicate Ids.
2050
+     * Keys _needs_ to be removed after all shares are retrieved and merged.
2051
+     *
2052
+     * @param array $shares
2053
+     * @param array $newShares
2054
+     */
2055
+    private function mergeFormattedShares(array &$shares, array $newShares) {
2056
+        foreach ($newShares as $newShare) {
2057
+            if (!array_key_exists($newShare['id'], $shares)) {
2058
+                $shares[$newShare['id']] = $newShare;
2059
+            }
2060
+        }
2061
+    }
2062
+
2063
+    /**
2064
+     * @param IShare $share
2065
+     * @param string|null $attributesString
2066
+     * @return IShare modified share
2067
+     */
2068
+    private function setShareAttributes(IShare $share, ?string $attributesString) {
2069
+        $newShareAttributes = null;
2070
+        if ($attributesString !== null) {
2071
+            $newShareAttributes = $this->shareManager->newShare()->newAttributes();
2072
+            $formattedShareAttributes = \json_decode($attributesString, true);
2073
+            if (is_array($formattedShareAttributes)) {
2074
+                foreach ($formattedShareAttributes as $formattedAttr) {
2075
+                    $newShareAttributes->setAttribute(
2076
+                        $formattedAttr['scope'],
2077
+                        $formattedAttr['key'],
2078
+                        $formattedAttr['value'],
2079
+                    );
2080
+                }
2081
+            } else {
2082
+                throw new OCSBadRequestException($this->l->t('Invalid share attributes provided: "%s"', [$attributesString]));
2083
+            }
2084
+        }
2085
+        $share->setAttributes($newShareAttributes);
2086
+
2087
+        return $share;
2088
+    }
2089
+
2090
+    private function checkInheritedAttributes(IShare $share): void {
2091
+        if (!$share->getSharedBy()) {
2092
+            return; // Probably in a test
2093
+        }
2094
+
2095
+        $canDownload = false;
2096
+        $hideDownload = true;
2097
+
2098
+        $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
2099
+        $nodes = $userFolder->getById($share->getNodeId());
2100
+        foreach ($nodes as $node) {
2101
+            // Owner always can download it - so allow it and break
2102
+            if ($node->getOwner()?->getUID() === $share->getSharedBy()) {
2103
+                $canDownload = true;
2104
+                $hideDownload = false;
2105
+                break;
2106
+            }
2107
+
2108
+            if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) {
2109
+                $storage = $node->getStorage();
2110
+                if ($storage instanceof Wrapper) {
2111
+                    $storage = $storage->getInstanceOfStorage(SharedStorage::class);
2112
+                    if ($storage === null) {
2113
+                        throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
2114
+                    }
2115
+                } else {
2116
+                    throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
2117
+                }
2118
+
2119
+                /** @var SharedStorage $storage */
2120
+                $originalShare = $storage->getShare();
2121
+                $inheritedAttributes = $originalShare->getAttributes();
2122
+                // hide if hidden and also the current share enforces hide (can only be false if one share is false or user is owner)
2123
+                $hideDownload = $hideDownload && $originalShare->getHideDownload();
2124
+                // allow download if already allowed by previous share or when the current share allows downloading
2125
+                $canDownload = $canDownload || $inheritedAttributes === null || $inheritedAttributes->getAttribute('permissions', 'download') !== false;
2126
+            } elseif ($node->getStorage()->instanceOfStorage(Storage::class)) {
2127
+                $canDownload = true; // in case of federation storage, we can expect the download to be activated by default
2128
+            }
2129
+        }
2130
+
2131
+        if ($hideDownload || !$canDownload) {
2132
+            $share->setHideDownload(true);
2133
+
2134
+            if (!$canDownload) {
2135
+                $attributes = $share->getAttributes() ?? $share->newAttributes();
2136
+                $attributes->setAttribute('permissions', 'download', false);
2137
+                $share->setAttributes($attributes);
2138
+            }
2139
+        }
2140
+    }
2141
+
2142
+    /**
2143
+     * Send a mail notification again for a share.
2144
+     * The mail_send option must be enabled for the given share.
2145
+     * @param string $id the share ID
2146
+     * @param string $password the password to check against. Necessary for password protected shares.
2147
+     * @throws OCSNotFoundException Share not found
2148
+     * @throws OCSForbiddenException You are not allowed to send mail notifications
2149
+     * @throws OCSBadRequestException Invalid request or wrong password
2150
+     * @throws OCSException Error while sending mail notification
2151
+     * @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
2152
+     *
2153
+     * 200: The email notification was sent successfully
2154
+     */
2155
+    #[NoAdminRequired]
2156
+    #[UserRateLimit(limit: 10, period: 600)]
2157
+    public function sendShareEmail(string $id, $password = ''): DataResponse {
2158
+        try {
2159
+            $share = $this->getShareById($id);
2160
+
2161
+            if (!$this->canAccessShare($share, false)) {
2162
+                throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2163
+            }
2164
+
2165
+            if (!$this->canEditShare($share)) {
2166
+                throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2167
+            }
2168
+
2169
+            // For mail and link shares, the user must be
2170
+            // the owner of the share, not only the file owner.
2171
+            if ($share->getShareType() === IShare::TYPE_EMAIL
2172
+                || $share->getShareType() === IShare::TYPE_LINK) {
2173
+                if ($share->getSharedBy() !== $this->userId) {
2174
+                    throw new OCSForbiddenException($this->l->t('You are not allowed to send mail notifications'));
2175
+                }
2176
+            }
2177
+
2178
+            try {
2179
+                $provider = $this->factory->getProviderForType($share->getShareType());
2180
+                if (!($provider instanceof IShareProviderWithNotification)) {
2181
+                    throw new OCSBadRequestException($this->l->t('No mail notification configured for this share type'));
2182
+                }
2183
+
2184
+                // Circumvent the password encrypted data by
2185
+                // setting the password clear. We're not storing
2186
+                // the password clear, it is just a temporary
2187
+                // object manipulation. The password will stay
2188
+                // encrypted in the database.
2189
+                if ($share->getPassword() !== null && $share->getPassword() !== $password) {
2190
+                    if (!$this->shareManager->checkPassword($share, $password)) {
2191
+                        throw new OCSBadRequestException($this->l->t('Wrong password'));
2192
+                    }
2193
+                    $share = $share->setPassword($password);
2194
+                }
2195
+
2196
+                $provider->sendMailNotification($share);
2197
+                return new DataResponse();
2198
+            } catch (Exception $e) {
2199
+                $this->logger->error($e->getMessage(), ['exception' => $e]);
2200
+                throw new OCSException($this->l->t('Error while sending mail notification'));
2201
+            }
2202
+
2203
+        } catch (ShareNotFound $e) {
2204
+            throw new OCSNotFoundException($this->l->t('Wrong share ID, share does not exist'));
2205
+        }
2206
+    }
2207
+
2208
+    /**
2209
+     * Get a unique share token
2210
+     *
2211
+     * @throws OCSException Failed to generate a unique token
2212
+     *
2213
+     * @return DataResponse<Http::STATUS_OK, array{token: string}, array{}>
2214
+     *
2215
+     * 200: Token generated successfully
2216
+     */
2217
+    #[ApiRoute(verb: 'GET', url: '/api/v1/token')]
2218
+    #[NoAdminRequired]
2219
+    public function generateToken(): DataResponse {
2220
+        try {
2221
+            $token = $this->shareManager->generateToken();
2222
+            return new DataResponse([
2223
+                'token' => $token,
2224
+            ]);
2225
+        } catch (ShareTokenException $e) {
2226
+            throw new OCSException($this->l->t('Failed to generate a unique token'));
2227
+        }
2228
+    }
2229
+
2230
+    /**
2231
+     * Populate the result set with file tags
2232
+     *
2233
+     * @psalm-template T of array{tags?: list<string>, file_source: int, ...array<string, mixed>}
2234
+     * @param list<T> $fileList
2235
+     * @return list<T> file list populated with tags
2236
+     */
2237
+    private function populateTags(array $fileList): array {
2238
+        $tagger = $this->tagManager->load('files');
2239
+        $tags = $tagger->getTagsForObjects(array_map(static fn (array $fileData) => $fileData['file_source'], $fileList));
2240
+
2241
+        if (!is_array($tags)) {
2242
+            throw new \UnexpectedValueException('$tags must be an array');
2243
+        }
2244
+
2245
+        // Set empty tag array
2246
+        foreach ($fileList as &$fileData) {
2247
+            $fileData['tags'] = [];
2248
+        }
2249
+        unset($fileData);
2250
+
2251
+        if (!empty($tags)) {
2252
+            foreach ($tags as $fileId => $fileTags) {
2253
+                foreach ($fileList as &$fileData) {
2254
+                    if ($fileId !== $fileData['file_source']) {
2255
+                        continue;
2256
+                    }
2257
+
2258
+                    $fileData['tags'] = $fileTags;
2259
+                }
2260
+                unset($fileData);
2261
+            }
2262
+        }
2263
+
2264
+        return $fileList;
2265
+    }
2266 2266
 }
Please login to merge, or discard this patch.