Completed
Push — master ( a836c8...d91908 )
by
unknown
26:38
created
apps/files_sharing/lib/External/Storage.php 2 patches
Indentation   +387 added lines, -387 removed lines patch added patch discarded remove patch
@@ -40,391 +40,391 @@
 block discarded – undo
40 40
 use Psr\Log\LoggerInterface;
41 41
 
42 42
 class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, IReliableEtagStorage {
43
-	private ICloudId $cloudId;
44
-	private string $mountPoint;
45
-	private string $token;
46
-	private ICacheFactory $memcacheFactory;
47
-	private IClientService $httpClient;
48
-	private bool $updateChecked = false;
49
-	private ExternalShareManager $manager;
50
-	private IConfig $config;
51
-
52
-	/**
53
-	 * @param array{HttpClientService: IClientService, manager: ExternalShareManager, cloudId: ICloudId, mountpoint: string, token: string, password: ?string}|array $options
54
-	 */
55
-	public function __construct($options) {
56
-		$this->memcacheFactory = Server::get(ICacheFactory::class);
57
-		$this->httpClient = $options['HttpClientService'];
58
-		$this->manager = $options['manager'];
59
-		$this->cloudId = $options['cloudId'];
60
-		$this->logger = Server::get(LoggerInterface::class);
61
-		$discoveryService = Server::get(IOCMDiscoveryService::class);
62
-		$this->config = Server::get(IConfig::class);
63
-
64
-		// use default path to webdav if not found on discovery
65
-		try {
66
-			$ocmProvider = $discoveryService->discover($this->cloudId->getRemote());
67
-			$webDavEndpoint = $ocmProvider->extractProtocolEntry('file', 'webdav');
68
-			$remote = $ocmProvider->getEndPoint();
69
-		} catch (OCMProviderException|OCMArgumentException $e) {
70
-			$this->logger->notice('exception while retrieving webdav endpoint', ['exception' => $e]);
71
-			$webDavEndpoint = '/public.php/webdav';
72
-			$remote = $this->cloudId->getRemote();
73
-		}
74
-
75
-		$host = parse_url($remote, PHP_URL_HOST);
76
-		$port = parse_url($remote, PHP_URL_PORT);
77
-		$host .= ($port === null) ? '' : ':' . $port; // we add port if available
78
-
79
-		// in case remote NC is on a sub folder and using deprecated ocm provider
80
-		$tmpPath = rtrim(parse_url($this->cloudId->getRemote(), PHP_URL_PATH) ?? '', '/');
81
-		if (!str_starts_with($webDavEndpoint, $tmpPath)) {
82
-			$webDavEndpoint = $tmpPath . $webDavEndpoint;
83
-		}
84
-
85
-		$this->mountPoint = $options['mountpoint'];
86
-		$this->token = $options['token'];
87
-
88
-		parent::__construct(
89
-			[
90
-				'secure' => ((parse_url($remote, PHP_URL_SCHEME) ?? 'https') === 'https'),
91
-				'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
92
-				'host' => $host,
93
-				'root' => $webDavEndpoint,
94
-				'user' => $options['token'],
95
-				'authType' => \Sabre\DAV\Client::AUTH_BASIC,
96
-				'password' => (string)$options['password']
97
-			]
98
-		);
99
-	}
100
-
101
-	public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher {
102
-		if (!$storage) {
103
-			$storage = $this;
104
-		}
105
-		if (!isset($this->watcher)) {
106
-			$this->watcher = new Watcher($storage);
107
-			$this->watcher->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE);
108
-		}
109
-		return $this->watcher;
110
-	}
111
-
112
-	public function getRemoteUser(): string {
113
-		return $this->cloudId->getUser();
114
-	}
115
-
116
-	public function getRemote(): string {
117
-		return $this->cloudId->getRemote();
118
-	}
119
-
120
-	public function getMountPoint(): string {
121
-		return $this->mountPoint;
122
-	}
123
-
124
-	public function getToken(): string {
125
-		return $this->token;
126
-	}
127
-
128
-	public function getPassword(): ?string {
129
-		return $this->password;
130
-	}
131
-
132
-	public function getId(): string {
133
-		return 'shared::' . md5($this->token . '@' . $this->getRemote());
134
-	}
135
-
136
-	public function getCache(string $path = '', ?IStorage $storage = null): ICache {
137
-		if (is_null($this->cache)) {
138
-			$this->cache = new Cache($this, $this->cloudId);
139
-		}
140
-		return $this->cache;
141
-	}
142
-
143
-	public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
144
-		if (!$storage) {
145
-			$storage = $this;
146
-		}
147
-		if (!isset($this->scanner)) {
148
-			$this->scanner = new Scanner($storage);
149
-		}
150
-		/** @var Scanner */
151
-		return $this->scanner;
152
-	}
153
-
154
-	public function hasUpdated(string $path, int $time): bool {
155
-		// since for owncloud webdav servers we can rely on etag propagation we only need to check the root of the storage
156
-		// because of that we only do one check for the entire storage per request
157
-		if ($this->updateChecked) {
158
-			return false;
159
-		}
160
-		$this->updateChecked = true;
161
-		try {
162
-			return parent::hasUpdated('', $time);
163
-		} catch (StorageInvalidException $e) {
164
-			// check if it needs to be removed
165
-			$this->checkStorageAvailability();
166
-			throw $e;
167
-		} catch (StorageNotAvailableException $e) {
168
-			// check if it needs to be removed or just temp unavailable
169
-			$this->checkStorageAvailability();
170
-			throw $e;
171
-		}
172
-	}
173
-
174
-	public function test(): bool {
175
-		try {
176
-			return parent::test();
177
-		} catch (StorageInvalidException $e) {
178
-			// check if it needs to be removed
179
-			$this->checkStorageAvailability();
180
-			throw $e;
181
-		} catch (StorageNotAvailableException $e) {
182
-			// check if it needs to be removed or just temp unavailable
183
-			$this->checkStorageAvailability();
184
-			throw $e;
185
-		}
186
-	}
187
-
188
-	/**
189
-	 * Check whether this storage is permanently or temporarily
190
-	 * unavailable
191
-	 *
192
-	 * @throws StorageNotAvailableException
193
-	 * @throws StorageInvalidException
194
-	 */
195
-	public function checkStorageAvailability() {
196
-		// see if we can find out why the share is unavailable
197
-		try {
198
-			$this->getShareInfo(0);
199
-		} catch (NotFoundException $e) {
200
-			// a 404 can either mean that the share no longer exists or there is no Nextcloud on the remote
201
-			if ($this->testRemote()) {
202
-				// valid Nextcloud instance means that the public share no longer exists
203
-				// since this is permanent (re-sharing the file will create a new token)
204
-				// we remove the invalid storage
205
-				$this->manager->removeShare($this->mountPoint);
206
-				$this->manager->getMountManager()->removeMount($this->mountPoint);
207
-				throw new StorageInvalidException('Remote share not found', 0, $e);
208
-			} else {
209
-				// Nextcloud instance is gone, likely to be a temporary server configuration error
210
-				throw new StorageNotAvailableException('No nextcloud instance found at remote', 0, $e);
211
-			}
212
-		} catch (ForbiddenException $e) {
213
-			// auth error, remove share for now (provide a dialog in the future)
214
-			$this->manager->removeShare($this->mountPoint);
215
-			$this->manager->getMountManager()->removeMount($this->mountPoint);
216
-			throw new StorageInvalidException('Auth error when getting remote share');
217
-		} catch (\GuzzleHttp\Exception\ConnectException $e) {
218
-			throw new StorageNotAvailableException('Failed to connect to remote instance', 0, $e);
219
-		} catch (\GuzzleHttp\Exception\RequestException $e) {
220
-			throw new StorageNotAvailableException('Error while sending request to remote instance', 0, $e);
221
-		}
222
-	}
223
-
224
-	public function file_exists(string $path): bool {
225
-		if ($path === '') {
226
-			return true;
227
-		} else {
228
-			return parent::file_exists($path);
229
-		}
230
-	}
231
-
232
-	/**
233
-	 * Check if the configured remote is a valid federated share provider
234
-	 *
235
-	 * @return bool
236
-	 */
237
-	protected function testRemote(): bool {
238
-		try {
239
-			return $this->testRemoteUrl($this->getRemote() . '/ocm-provider/index.php')
240
-				   || $this->testRemoteUrl($this->getRemote() . '/ocm-provider/')
241
-				   || $this->testRemoteUrl($this->getRemote() . '/.well-known/ocm')
242
-				   || $this->testRemoteUrl($this->getRemote() . '/status.php');
243
-		} catch (\Exception $e) {
244
-			return false;
245
-		}
246
-	}
247
-
248
-	private function testRemoteUrl(string $url): bool {
249
-		$cache = $this->memcacheFactory->createDistributed('files_sharing_remote_url');
250
-		$cached = $cache->get($url);
251
-		if ($cached !== null) {
252
-			return (bool)$cached;
253
-		}
254
-
255
-		$client = $this->httpClient->newClient();
256
-		try {
257
-			$result = $client->get($url, $this->getDefaultRequestOptions())->getBody();
258
-			$data = json_decode($result);
259
-			$returnValue = (is_object($data) && !empty($data->version));
260
-		} catch (ConnectException|ClientException|RequestException $e) {
261
-			$returnValue = false;
262
-			$this->logger->warning('Failed to test remote URL', ['exception' => $e]);
263
-		}
264
-
265
-		$cache->set($url, $returnValue, 60 * 60 * 24);
266
-		return $returnValue;
267
-	}
268
-
269
-	/**
270
-	 * Check whether the remote is an ownCloud/Nextcloud. This is needed since some sharing
271
-	 * features are not standardized.
272
-	 *
273
-	 * @throws LocalServerException
274
-	 */
275
-	public function remoteIsOwnCloud(): bool {
276
-		if (defined('PHPUNIT_RUN') || !$this->testRemoteUrl($this->getRemote() . '/status.php')) {
277
-			return false;
278
-		}
279
-		return true;
280
-	}
281
-
282
-	/**
283
-	 * @return mixed
284
-	 * @throws ForbiddenException
285
-	 * @throws NotFoundException
286
-	 * @throws \Exception
287
-	 */
288
-	public function getShareInfo(int $depth = -1) {
289
-		$remote = $this->getRemote();
290
-		$token = $this->getToken();
291
-		$password = $this->getPassword();
292
-
293
-		try {
294
-			// If remote is not an ownCloud do not try to get any share info
295
-			if (!$this->remoteIsOwnCloud()) {
296
-				return ['status' => 'unsupported'];
297
-			}
298
-		} catch (LocalServerException $e) {
299
-			// throw this to be on the safe side: the share will still be visible
300
-			// in the UI in case the failure is intermittent, and the user will
301
-			// be able to decide whether to remove it if it's really gone
302
-			throw new StorageNotAvailableException();
303
-		}
304
-
305
-		$url = rtrim($remote, '/') . '/index.php/apps/files_sharing/shareinfo?t=' . $token;
306
-
307
-		// TODO: DI
308
-		$client = Server::get(IClientService::class)->newClient();
309
-		try {
310
-			$response = $client->post($url, array_merge($this->getDefaultRequestOptions(), [
311
-				'body' => ['password' => $password, 'depth' => $depth],
312
-			]));
313
-		} catch (\GuzzleHttp\Exception\RequestException $e) {
314
-			$this->logger->warning('Failed to fetch share info', ['exception' => $e]);
315
-			if ($e->getCode() === Http::STATUS_UNAUTHORIZED || $e->getCode() === Http::STATUS_FORBIDDEN) {
316
-				throw new ForbiddenException();
317
-			}
318
-			if ($e->getCode() === Http::STATUS_NOT_FOUND) {
319
-				throw new NotFoundException();
320
-			}
321
-			// throw this to be on the safe side: the share will still be visible
322
-			// in the UI in case the failure is intermittent, and the user will
323
-			// be able to decide whether to remove it if it's really gone
324
-			throw new StorageNotAvailableException();
325
-		}
326
-
327
-		return json_decode($response->getBody(), true);
328
-	}
329
-
330
-	public function getOwner(string $path): string|false {
331
-		return $this->cloudId->getDisplayId();
332
-	}
333
-
334
-	public function isSharable(string $path): bool {
335
-		if (Util::isSharingDisabledForUser() || !Share::isResharingAllowed()) {
336
-			return false;
337
-		}
338
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
339
-	}
340
-
341
-	public function getPermissions(string $path): int {
342
-		$response = $this->propfind($path);
343
-		if ($response === false) {
344
-			return 0;
345
-		}
346
-
347
-		$ocsPermissions = $response['{http://open-collaboration-services.org/ns}share-permissions'] ?? null;
348
-		$ocmPermissions = $response['{http://open-cloud-mesh.org/ns}share-permissions'] ?? null;
349
-		$ocPermissions = $response['{http://owncloud.org/ns}permissions'] ?? null;
350
-		// old federated sharing permissions
351
-		if ($ocsPermissions !== null) {
352
-			$permissions = (int)$ocsPermissions;
353
-		} elseif ($ocmPermissions !== null) {
354
-			// permissions provided by the OCM API
355
-			$permissions = $this->ocmPermissions2ncPermissions($ocmPermissions, $path);
356
-		} elseif ($ocPermissions !== null) {
357
-			return $this->parsePermissions($ocPermissions);
358
-		} else {
359
-			// use default permission if remote server doesn't provide the share permissions
360
-			$permissions = $this->getDefaultPermissions($path);
361
-		}
362
-
363
-		return $permissions;
364
-	}
365
-
366
-	public function needsPartFile(): bool {
367
-		return false;
368
-	}
369
-
370
-	/**
371
-	 * Translate OCM Permissions to Nextcloud permissions
372
-	 *
373
-	 * @param string $ocmPermissions json encoded OCM permissions
374
-	 * @param string $path path to file
375
-	 * @return int
376
-	 */
377
-	protected function ocmPermissions2ncPermissions(string $ocmPermissions, string $path): int {
378
-		try {
379
-			$ocmPermissions = json_decode($ocmPermissions);
380
-			$ncPermissions = 0;
381
-			foreach ($ocmPermissions as $permission) {
382
-				switch (strtolower($permission)) {
383
-					case 'read':
384
-						$ncPermissions += Constants::PERMISSION_READ;
385
-						break;
386
-					case 'write':
387
-						$ncPermissions += Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE;
388
-						break;
389
-					case 'share':
390
-						$ncPermissions += Constants::PERMISSION_SHARE;
391
-						break;
392
-					default:
393
-						throw new \Exception();
394
-				}
395
-			}
396
-		} catch (\Exception $e) {
397
-			$ncPermissions = $this->getDefaultPermissions($path);
398
-		}
399
-
400
-		return $ncPermissions;
401
-	}
402
-
403
-	/**
404
-	 * Calculate the default permissions in case no permissions are provided
405
-	 */
406
-	protected function getDefaultPermissions(string $path): int {
407
-		if ($this->is_dir($path)) {
408
-			$permissions = Constants::PERMISSION_ALL;
409
-		} else {
410
-			$permissions = Constants::PERMISSION_ALL & ~Constants::PERMISSION_CREATE;
411
-		}
412
-
413
-		return $permissions;
414
-	}
415
-
416
-	public function free_space(string $path): int|float|false {
417
-		return parent::free_space('');
418
-	}
419
-
420
-	private function getDefaultRequestOptions(): array {
421
-		$options = [
422
-			'timeout' => 10,
423
-			'connect_timeout' => 10,
424
-		];
425
-		if ($this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates')) {
426
-			$options['verify'] = false;
427
-		}
428
-		return $options;
429
-	}
43
+    private ICloudId $cloudId;
44
+    private string $mountPoint;
45
+    private string $token;
46
+    private ICacheFactory $memcacheFactory;
47
+    private IClientService $httpClient;
48
+    private bool $updateChecked = false;
49
+    private ExternalShareManager $manager;
50
+    private IConfig $config;
51
+
52
+    /**
53
+     * @param array{HttpClientService: IClientService, manager: ExternalShareManager, cloudId: ICloudId, mountpoint: string, token: string, password: ?string}|array $options
54
+     */
55
+    public function __construct($options) {
56
+        $this->memcacheFactory = Server::get(ICacheFactory::class);
57
+        $this->httpClient = $options['HttpClientService'];
58
+        $this->manager = $options['manager'];
59
+        $this->cloudId = $options['cloudId'];
60
+        $this->logger = Server::get(LoggerInterface::class);
61
+        $discoveryService = Server::get(IOCMDiscoveryService::class);
62
+        $this->config = Server::get(IConfig::class);
63
+
64
+        // use default path to webdav if not found on discovery
65
+        try {
66
+            $ocmProvider = $discoveryService->discover($this->cloudId->getRemote());
67
+            $webDavEndpoint = $ocmProvider->extractProtocolEntry('file', 'webdav');
68
+            $remote = $ocmProvider->getEndPoint();
69
+        } catch (OCMProviderException|OCMArgumentException $e) {
70
+            $this->logger->notice('exception while retrieving webdav endpoint', ['exception' => $e]);
71
+            $webDavEndpoint = '/public.php/webdav';
72
+            $remote = $this->cloudId->getRemote();
73
+        }
74
+
75
+        $host = parse_url($remote, PHP_URL_HOST);
76
+        $port = parse_url($remote, PHP_URL_PORT);
77
+        $host .= ($port === null) ? '' : ':' . $port; // we add port if available
78
+
79
+        // in case remote NC is on a sub folder and using deprecated ocm provider
80
+        $tmpPath = rtrim(parse_url($this->cloudId->getRemote(), PHP_URL_PATH) ?? '', '/');
81
+        if (!str_starts_with($webDavEndpoint, $tmpPath)) {
82
+            $webDavEndpoint = $tmpPath . $webDavEndpoint;
83
+        }
84
+
85
+        $this->mountPoint = $options['mountpoint'];
86
+        $this->token = $options['token'];
87
+
88
+        parent::__construct(
89
+            [
90
+                'secure' => ((parse_url($remote, PHP_URL_SCHEME) ?? 'https') === 'https'),
91
+                'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
92
+                'host' => $host,
93
+                'root' => $webDavEndpoint,
94
+                'user' => $options['token'],
95
+                'authType' => \Sabre\DAV\Client::AUTH_BASIC,
96
+                'password' => (string)$options['password']
97
+            ]
98
+        );
99
+    }
100
+
101
+    public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher {
102
+        if (!$storage) {
103
+            $storage = $this;
104
+        }
105
+        if (!isset($this->watcher)) {
106
+            $this->watcher = new Watcher($storage);
107
+            $this->watcher->setPolicy(\OC\Files\Cache\Watcher::CHECK_ONCE);
108
+        }
109
+        return $this->watcher;
110
+    }
111
+
112
+    public function getRemoteUser(): string {
113
+        return $this->cloudId->getUser();
114
+    }
115
+
116
+    public function getRemote(): string {
117
+        return $this->cloudId->getRemote();
118
+    }
119
+
120
+    public function getMountPoint(): string {
121
+        return $this->mountPoint;
122
+    }
123
+
124
+    public function getToken(): string {
125
+        return $this->token;
126
+    }
127
+
128
+    public function getPassword(): ?string {
129
+        return $this->password;
130
+    }
131
+
132
+    public function getId(): string {
133
+        return 'shared::' . md5($this->token . '@' . $this->getRemote());
134
+    }
135
+
136
+    public function getCache(string $path = '', ?IStorage $storage = null): ICache {
137
+        if (is_null($this->cache)) {
138
+            $this->cache = new Cache($this, $this->cloudId);
139
+        }
140
+        return $this->cache;
141
+    }
142
+
143
+    public function getScanner(string $path = '', ?IStorage $storage = null): IScanner {
144
+        if (!$storage) {
145
+            $storage = $this;
146
+        }
147
+        if (!isset($this->scanner)) {
148
+            $this->scanner = new Scanner($storage);
149
+        }
150
+        /** @var Scanner */
151
+        return $this->scanner;
152
+    }
153
+
154
+    public function hasUpdated(string $path, int $time): bool {
155
+        // since for owncloud webdav servers we can rely on etag propagation we only need to check the root of the storage
156
+        // because of that we only do one check for the entire storage per request
157
+        if ($this->updateChecked) {
158
+            return false;
159
+        }
160
+        $this->updateChecked = true;
161
+        try {
162
+            return parent::hasUpdated('', $time);
163
+        } catch (StorageInvalidException $e) {
164
+            // check if it needs to be removed
165
+            $this->checkStorageAvailability();
166
+            throw $e;
167
+        } catch (StorageNotAvailableException $e) {
168
+            // check if it needs to be removed or just temp unavailable
169
+            $this->checkStorageAvailability();
170
+            throw $e;
171
+        }
172
+    }
173
+
174
+    public function test(): bool {
175
+        try {
176
+            return parent::test();
177
+        } catch (StorageInvalidException $e) {
178
+            // check if it needs to be removed
179
+            $this->checkStorageAvailability();
180
+            throw $e;
181
+        } catch (StorageNotAvailableException $e) {
182
+            // check if it needs to be removed or just temp unavailable
183
+            $this->checkStorageAvailability();
184
+            throw $e;
185
+        }
186
+    }
187
+
188
+    /**
189
+     * Check whether this storage is permanently or temporarily
190
+     * unavailable
191
+     *
192
+     * @throws StorageNotAvailableException
193
+     * @throws StorageInvalidException
194
+     */
195
+    public function checkStorageAvailability() {
196
+        // see if we can find out why the share is unavailable
197
+        try {
198
+            $this->getShareInfo(0);
199
+        } catch (NotFoundException $e) {
200
+            // a 404 can either mean that the share no longer exists or there is no Nextcloud on the remote
201
+            if ($this->testRemote()) {
202
+                // valid Nextcloud instance means that the public share no longer exists
203
+                // since this is permanent (re-sharing the file will create a new token)
204
+                // we remove the invalid storage
205
+                $this->manager->removeShare($this->mountPoint);
206
+                $this->manager->getMountManager()->removeMount($this->mountPoint);
207
+                throw new StorageInvalidException('Remote share not found', 0, $e);
208
+            } else {
209
+                // Nextcloud instance is gone, likely to be a temporary server configuration error
210
+                throw new StorageNotAvailableException('No nextcloud instance found at remote', 0, $e);
211
+            }
212
+        } catch (ForbiddenException $e) {
213
+            // auth error, remove share for now (provide a dialog in the future)
214
+            $this->manager->removeShare($this->mountPoint);
215
+            $this->manager->getMountManager()->removeMount($this->mountPoint);
216
+            throw new StorageInvalidException('Auth error when getting remote share');
217
+        } catch (\GuzzleHttp\Exception\ConnectException $e) {
218
+            throw new StorageNotAvailableException('Failed to connect to remote instance', 0, $e);
219
+        } catch (\GuzzleHttp\Exception\RequestException $e) {
220
+            throw new StorageNotAvailableException('Error while sending request to remote instance', 0, $e);
221
+        }
222
+    }
223
+
224
+    public function file_exists(string $path): bool {
225
+        if ($path === '') {
226
+            return true;
227
+        } else {
228
+            return parent::file_exists($path);
229
+        }
230
+    }
231
+
232
+    /**
233
+     * Check if the configured remote is a valid federated share provider
234
+     *
235
+     * @return bool
236
+     */
237
+    protected function testRemote(): bool {
238
+        try {
239
+            return $this->testRemoteUrl($this->getRemote() . '/ocm-provider/index.php')
240
+                   || $this->testRemoteUrl($this->getRemote() . '/ocm-provider/')
241
+                   || $this->testRemoteUrl($this->getRemote() . '/.well-known/ocm')
242
+                   || $this->testRemoteUrl($this->getRemote() . '/status.php');
243
+        } catch (\Exception $e) {
244
+            return false;
245
+        }
246
+    }
247
+
248
+    private function testRemoteUrl(string $url): bool {
249
+        $cache = $this->memcacheFactory->createDistributed('files_sharing_remote_url');
250
+        $cached = $cache->get($url);
251
+        if ($cached !== null) {
252
+            return (bool)$cached;
253
+        }
254
+
255
+        $client = $this->httpClient->newClient();
256
+        try {
257
+            $result = $client->get($url, $this->getDefaultRequestOptions())->getBody();
258
+            $data = json_decode($result);
259
+            $returnValue = (is_object($data) && !empty($data->version));
260
+        } catch (ConnectException|ClientException|RequestException $e) {
261
+            $returnValue = false;
262
+            $this->logger->warning('Failed to test remote URL', ['exception' => $e]);
263
+        }
264
+
265
+        $cache->set($url, $returnValue, 60 * 60 * 24);
266
+        return $returnValue;
267
+    }
268
+
269
+    /**
270
+     * Check whether the remote is an ownCloud/Nextcloud. This is needed since some sharing
271
+     * features are not standardized.
272
+     *
273
+     * @throws LocalServerException
274
+     */
275
+    public function remoteIsOwnCloud(): bool {
276
+        if (defined('PHPUNIT_RUN') || !$this->testRemoteUrl($this->getRemote() . '/status.php')) {
277
+            return false;
278
+        }
279
+        return true;
280
+    }
281
+
282
+    /**
283
+     * @return mixed
284
+     * @throws ForbiddenException
285
+     * @throws NotFoundException
286
+     * @throws \Exception
287
+     */
288
+    public function getShareInfo(int $depth = -1) {
289
+        $remote = $this->getRemote();
290
+        $token = $this->getToken();
291
+        $password = $this->getPassword();
292
+
293
+        try {
294
+            // If remote is not an ownCloud do not try to get any share info
295
+            if (!$this->remoteIsOwnCloud()) {
296
+                return ['status' => 'unsupported'];
297
+            }
298
+        } catch (LocalServerException $e) {
299
+            // throw this to be on the safe side: the share will still be visible
300
+            // in the UI in case the failure is intermittent, and the user will
301
+            // be able to decide whether to remove it if it's really gone
302
+            throw new StorageNotAvailableException();
303
+        }
304
+
305
+        $url = rtrim($remote, '/') . '/index.php/apps/files_sharing/shareinfo?t=' . $token;
306
+
307
+        // TODO: DI
308
+        $client = Server::get(IClientService::class)->newClient();
309
+        try {
310
+            $response = $client->post($url, array_merge($this->getDefaultRequestOptions(), [
311
+                'body' => ['password' => $password, 'depth' => $depth],
312
+            ]));
313
+        } catch (\GuzzleHttp\Exception\RequestException $e) {
314
+            $this->logger->warning('Failed to fetch share info', ['exception' => $e]);
315
+            if ($e->getCode() === Http::STATUS_UNAUTHORIZED || $e->getCode() === Http::STATUS_FORBIDDEN) {
316
+                throw new ForbiddenException();
317
+            }
318
+            if ($e->getCode() === Http::STATUS_NOT_FOUND) {
319
+                throw new NotFoundException();
320
+            }
321
+            // throw this to be on the safe side: the share will still be visible
322
+            // in the UI in case the failure is intermittent, and the user will
323
+            // be able to decide whether to remove it if it's really gone
324
+            throw new StorageNotAvailableException();
325
+        }
326
+
327
+        return json_decode($response->getBody(), true);
328
+    }
329
+
330
+    public function getOwner(string $path): string|false {
331
+        return $this->cloudId->getDisplayId();
332
+    }
333
+
334
+    public function isSharable(string $path): bool {
335
+        if (Util::isSharingDisabledForUser() || !Share::isResharingAllowed()) {
336
+            return false;
337
+        }
338
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
339
+    }
340
+
341
+    public function getPermissions(string $path): int {
342
+        $response = $this->propfind($path);
343
+        if ($response === false) {
344
+            return 0;
345
+        }
346
+
347
+        $ocsPermissions = $response['{http://open-collaboration-services.org/ns}share-permissions'] ?? null;
348
+        $ocmPermissions = $response['{http://open-cloud-mesh.org/ns}share-permissions'] ?? null;
349
+        $ocPermissions = $response['{http://owncloud.org/ns}permissions'] ?? null;
350
+        // old federated sharing permissions
351
+        if ($ocsPermissions !== null) {
352
+            $permissions = (int)$ocsPermissions;
353
+        } elseif ($ocmPermissions !== null) {
354
+            // permissions provided by the OCM API
355
+            $permissions = $this->ocmPermissions2ncPermissions($ocmPermissions, $path);
356
+        } elseif ($ocPermissions !== null) {
357
+            return $this->parsePermissions($ocPermissions);
358
+        } else {
359
+            // use default permission if remote server doesn't provide the share permissions
360
+            $permissions = $this->getDefaultPermissions($path);
361
+        }
362
+
363
+        return $permissions;
364
+    }
365
+
366
+    public function needsPartFile(): bool {
367
+        return false;
368
+    }
369
+
370
+    /**
371
+     * Translate OCM Permissions to Nextcloud permissions
372
+     *
373
+     * @param string $ocmPermissions json encoded OCM permissions
374
+     * @param string $path path to file
375
+     * @return int
376
+     */
377
+    protected function ocmPermissions2ncPermissions(string $ocmPermissions, string $path): int {
378
+        try {
379
+            $ocmPermissions = json_decode($ocmPermissions);
380
+            $ncPermissions = 0;
381
+            foreach ($ocmPermissions as $permission) {
382
+                switch (strtolower($permission)) {
383
+                    case 'read':
384
+                        $ncPermissions += Constants::PERMISSION_READ;
385
+                        break;
386
+                    case 'write':
387
+                        $ncPermissions += Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE;
388
+                        break;
389
+                    case 'share':
390
+                        $ncPermissions += Constants::PERMISSION_SHARE;
391
+                        break;
392
+                    default:
393
+                        throw new \Exception();
394
+                }
395
+            }
396
+        } catch (\Exception $e) {
397
+            $ncPermissions = $this->getDefaultPermissions($path);
398
+        }
399
+
400
+        return $ncPermissions;
401
+    }
402
+
403
+    /**
404
+     * Calculate the default permissions in case no permissions are provided
405
+     */
406
+    protected function getDefaultPermissions(string $path): int {
407
+        if ($this->is_dir($path)) {
408
+            $permissions = Constants::PERMISSION_ALL;
409
+        } else {
410
+            $permissions = Constants::PERMISSION_ALL & ~Constants::PERMISSION_CREATE;
411
+        }
412
+
413
+        return $permissions;
414
+    }
415
+
416
+    public function free_space(string $path): int|float|false {
417
+        return parent::free_space('');
418
+    }
419
+
420
+    private function getDefaultRequestOptions(): array {
421
+        $options = [
422
+            'timeout' => 10,
423
+            'connect_timeout' => 10,
424
+        ];
425
+        if ($this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates')) {
426
+            $options['verify'] = false;
427
+        }
428
+        return $options;
429
+    }
430 430
 }
Please login to merge, or discard this patch.
Spacing   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -66,7 +66,7 @@  discard block
 block discarded – undo
66 66
 			$ocmProvider = $discoveryService->discover($this->cloudId->getRemote());
67 67
 			$webDavEndpoint = $ocmProvider->extractProtocolEntry('file', 'webdav');
68 68
 			$remote = $ocmProvider->getEndPoint();
69
-		} catch (OCMProviderException|OCMArgumentException $e) {
69
+		} catch (OCMProviderException | OCMArgumentException $e) {
70 70
 			$this->logger->notice('exception while retrieving webdav endpoint', ['exception' => $e]);
71 71
 			$webDavEndpoint = '/public.php/webdav';
72 72
 			$remote = $this->cloudId->getRemote();
@@ -74,12 +74,12 @@  discard block
 block discarded – undo
74 74
 
75 75
 		$host = parse_url($remote, PHP_URL_HOST);
76 76
 		$port = parse_url($remote, PHP_URL_PORT);
77
-		$host .= ($port === null) ? '' : ':' . $port; // we add port if available
77
+		$host .= ($port === null) ? '' : ':'.$port; // we add port if available
78 78
 
79 79
 		// in case remote NC is on a sub folder and using deprecated ocm provider
80 80
 		$tmpPath = rtrim(parse_url($this->cloudId->getRemote(), PHP_URL_PATH) ?? '', '/');
81 81
 		if (!str_starts_with($webDavEndpoint, $tmpPath)) {
82
-			$webDavEndpoint = $tmpPath . $webDavEndpoint;
82
+			$webDavEndpoint = $tmpPath.$webDavEndpoint;
83 83
 		}
84 84
 
85 85
 		$this->mountPoint = $options['mountpoint'];
@@ -93,7 +93,7 @@  discard block
 block discarded – undo
93 93
 				'root' => $webDavEndpoint,
94 94
 				'user' => $options['token'],
95 95
 				'authType' => \Sabre\DAV\Client::AUTH_BASIC,
96
-				'password' => (string)$options['password']
96
+				'password' => (string) $options['password']
97 97
 			]
98 98
 		);
99 99
 	}
@@ -130,7 +130,7 @@  discard block
 block discarded – undo
130 130
 	}
131 131
 
132 132
 	public function getId(): string {
133
-		return 'shared::' . md5($this->token . '@' . $this->getRemote());
133
+		return 'shared::'.md5($this->token.'@'.$this->getRemote());
134 134
 	}
135 135
 
136 136
 	public function getCache(string $path = '', ?IStorage $storage = null): ICache {
@@ -236,10 +236,10 @@  discard block
 block discarded – undo
236 236
 	 */
237 237
 	protected function testRemote(): bool {
238 238
 		try {
239
-			return $this->testRemoteUrl($this->getRemote() . '/ocm-provider/index.php')
240
-				   || $this->testRemoteUrl($this->getRemote() . '/ocm-provider/')
241
-				   || $this->testRemoteUrl($this->getRemote() . '/.well-known/ocm')
242
-				   || $this->testRemoteUrl($this->getRemote() . '/status.php');
239
+			return $this->testRemoteUrl($this->getRemote().'/ocm-provider/index.php')
240
+				   || $this->testRemoteUrl($this->getRemote().'/ocm-provider/')
241
+				   || $this->testRemoteUrl($this->getRemote().'/.well-known/ocm')
242
+				   || $this->testRemoteUrl($this->getRemote().'/status.php');
243 243
 		} catch (\Exception $e) {
244 244
 			return false;
245 245
 		}
@@ -249,7 +249,7 @@  discard block
 block discarded – undo
249 249
 		$cache = $this->memcacheFactory->createDistributed('files_sharing_remote_url');
250 250
 		$cached = $cache->get($url);
251 251
 		if ($cached !== null) {
252
-			return (bool)$cached;
252
+			return (bool) $cached;
253 253
 		}
254 254
 
255 255
 		$client = $this->httpClient->newClient();
@@ -257,7 +257,7 @@  discard block
 block discarded – undo
257 257
 			$result = $client->get($url, $this->getDefaultRequestOptions())->getBody();
258 258
 			$data = json_decode($result);
259 259
 			$returnValue = (is_object($data) && !empty($data->version));
260
-		} catch (ConnectException|ClientException|RequestException $e) {
260
+		} catch (ConnectException | ClientException | RequestException $e) {
261 261
 			$returnValue = false;
262 262
 			$this->logger->warning('Failed to test remote URL', ['exception' => $e]);
263 263
 		}
@@ -273,7 +273,7 @@  discard block
 block discarded – undo
273 273
 	 * @throws LocalServerException
274 274
 	 */
275 275
 	public function remoteIsOwnCloud(): bool {
276
-		if (defined('PHPUNIT_RUN') || !$this->testRemoteUrl($this->getRemote() . '/status.php')) {
276
+		if (defined('PHPUNIT_RUN') || !$this->testRemoteUrl($this->getRemote().'/status.php')) {
277 277
 			return false;
278 278
 		}
279 279
 		return true;
@@ -302,7 +302,7 @@  discard block
 block discarded – undo
302 302
 			throw new StorageNotAvailableException();
303 303
 		}
304 304
 
305
-		$url = rtrim($remote, '/') . '/index.php/apps/files_sharing/shareinfo?t=' . $token;
305
+		$url = rtrim($remote, '/').'/index.php/apps/files_sharing/shareinfo?t='.$token;
306 306
 
307 307
 		// TODO: DI
308 308
 		$client = Server::get(IClientService::class)->newClient();
@@ -327,7 +327,7 @@  discard block
 block discarded – undo
327 327
 		return json_decode($response->getBody(), true);
328 328
 	}
329 329
 
330
-	public function getOwner(string $path): string|false {
330
+	public function getOwner(string $path): string | false {
331 331
 		return $this->cloudId->getDisplayId();
332 332
 	}
333 333
 
@@ -335,7 +335,7 @@  discard block
 block discarded – undo
335 335
 		if (Util::isSharingDisabledForUser() || !Share::isResharingAllowed()) {
336 336
 			return false;
337 337
 		}
338
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
338
+		return (bool) ($this->getPermissions($path) & Constants::PERMISSION_SHARE);
339 339
 	}
340 340
 
341 341
 	public function getPermissions(string $path): int {
@@ -349,7 +349,7 @@  discard block
 block discarded – undo
349 349
 		$ocPermissions = $response['{http://owncloud.org/ns}permissions'] ?? null;
350 350
 		// old federated sharing permissions
351 351
 		if ($ocsPermissions !== null) {
352
-			$permissions = (int)$ocsPermissions;
352
+			$permissions = (int) $ocsPermissions;
353 353
 		} elseif ($ocmPermissions !== null) {
354 354
 			// permissions provided by the OCM API
355 355
 			$permissions = $this->ocmPermissions2ncPermissions($ocmPermissions, $path);
@@ -413,7 +413,7 @@  discard block
 block discarded – undo
413 413
 		return $permissions;
414 414
 	}
415 415
 
416
-	public function free_space(string $path): int|float|false {
416
+	public function free_space(string $path): int | float | false {
417 417
 		return parent::free_space('');
418 418
 	}
419 419
 
Please login to merge, or discard this patch.