Completed
Push — master ( 44be41...59ae73 )
by
unknown
19:47 queued 23s
created
lib/public/Http/Client/IResponse.php 1 patch
Indentation   +22 added lines, -22 removed lines patch added patch discarded remove patch
@@ -14,29 +14,29 @@
 block discarded – undo
14 14
  * @since 8.1.0
15 15
  */
16 16
 interface IResponse {
17
-	/**
18
-	 * @return null|resource|string
19
-	 * @since 8.1.0
20
-	 * @sicne 8.2.0 with stream enabled, the function returns null or a resource
21
-	 */
22
-	public function getBody();
17
+    /**
18
+     * @return null|resource|string
19
+     * @since 8.1.0
20
+     * @sicne 8.2.0 with stream enabled, the function returns null or a resource
21
+     */
22
+    public function getBody();
23 23
 
24
-	/**
25
-	 * @return int
26
-	 * @since 8.1.0
27
-	 */
28
-	public function getStatusCode(): int;
24
+    /**
25
+     * @return int
26
+     * @since 8.1.0
27
+     */
28
+    public function getStatusCode(): int;
29 29
 
30
-	/**
31
-	 * @param string $key
32
-	 * @return string
33
-	 * @since 8.1.0
34
-	 */
35
-	public function getHeader(string $key): string;
30
+    /**
31
+     * @param string $key
32
+     * @return string
33
+     * @since 8.1.0
34
+     */
35
+    public function getHeader(string $key): string;
36 36
 
37
-	/**
38
-	 * @return array
39
-	 * @since 8.1.0
40
-	 */
41
-	public function getHeaders(): array;
37
+    /**
38
+     * @return array
39
+     * @since 8.1.0
40
+     */
41
+    public function getHeaders(): array;
42 42
 }
Please login to merge, or discard this patch.
lib/private/Http/Client/Response.php 2 patches
Indentation   +24 added lines, -24 removed lines patch added patch discarded remove patch
@@ -12,35 +12,35 @@
 block discarded – undo
12 12
 use Psr\Http\Message\ResponseInterface;
13 13
 
14 14
 class Response implements IResponse {
15
-	private ResponseInterface $response;
16
-	private bool $stream;
15
+    private ResponseInterface $response;
16
+    private bool $stream;
17 17
 
18
-	public function __construct(ResponseInterface $response, bool $stream = false) {
19
-		$this->response = $response;
20
-		$this->stream = $stream;
21
-	}
18
+    public function __construct(ResponseInterface $response, bool $stream = false) {
19
+        $this->response = $response;
20
+        $this->stream = $stream;
21
+    }
22 22
 
23
-	public function getBody() {
24
-		return $this->stream ?
25
-			$this->response->getBody()->detach():
26
-			$this->response->getBody()->getContents();
27
-	}
23
+    public function getBody() {
24
+        return $this->stream ?
25
+            $this->response->getBody()->detach():
26
+            $this->response->getBody()->getContents();
27
+    }
28 28
 
29
-	public function getStatusCode(): int {
30
-		return $this->response->getStatusCode();
31
-	}
29
+    public function getStatusCode(): int {
30
+        return $this->response->getStatusCode();
31
+    }
32 32
 
33
-	public function getHeader(string $key): string {
34
-		$headers = $this->response->getHeader($key);
33
+    public function getHeader(string $key): string {
34
+        $headers = $this->response->getHeader($key);
35 35
 
36
-		if (count($headers) === 0) {
37
-			return '';
38
-		}
36
+        if (count($headers) === 0) {
37
+            return '';
38
+        }
39 39
 
40
-		return $headers[0];
41
-	}
40
+        return $headers[0];
41
+    }
42 42
 
43
-	public function getHeaders(): array {
44
-		return $this->response->getHeaders();
45
-	}
43
+    public function getHeaders(): array {
44
+        return $this->response->getHeaders();
45
+    }
46 46
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -2 removed lines patch added patch discarded remove patch
@@ -22,8 +22,7 @@
 block discarded – undo
22 22
 
23 23
 	public function getBody() {
24 24
 		return $this->stream ?
25
-			$this->response->getBody()->detach():
26
-			$this->response->getBody()->getContents();
25
+			$this->response->getBody()->detach() : $this->response->getBody()->getContents();
27 26
 	}
28 27
 
29 28
 	public function getStatusCode(): int {
Please login to merge, or discard this patch.
lib/private/Files/Storage/DAV.php 1 patch
Indentation   +798 added lines, -798 removed lines patch added patch discarded remove patch
@@ -40,802 +40,802 @@
 block discarded – undo
40 40
  * @package OC\Files\Storage
41 41
  */
42 42
 class DAV extends Common {
43
-	/** @var string */
44
-	protected $password;
45
-	/** @var string */
46
-	protected $user;
47
-	/** @var string|null */
48
-	protected $authType;
49
-	/** @var string */
50
-	protected $host;
51
-	/** @var bool */
52
-	protected $secure;
53
-	/** @var string */
54
-	protected $root;
55
-	/** @var string */
56
-	protected $certPath;
57
-	/** @var bool */
58
-	protected $ready;
59
-	/** @var Client */
60
-	protected $client;
61
-	/** @var ArrayCache */
62
-	protected $statCache;
63
-	/** @var IClientService */
64
-	protected $httpClientService;
65
-	/** @var ICertificateManager */
66
-	protected $certManager;
67
-	protected LoggerInterface $logger;
68
-	protected IEventLogger $eventLogger;
69
-	protected IMimeTypeDetector $mimeTypeDetector;
70
-
71
-	/** @var int */
72
-	private $timeout;
73
-
74
-	protected const PROPFIND_PROPS = [
75
-		'{DAV:}getlastmodified',
76
-		'{DAV:}getcontentlength',
77
-		'{DAV:}getcontenttype',
78
-		'{http://owncloud.org/ns}permissions',
79
-		'{http://open-collaboration-services.org/ns}share-permissions',
80
-		'{DAV:}resourcetype',
81
-		'{DAV:}getetag',
82
-		'{DAV:}quota-available-bytes',
83
-	];
84
-
85
-	/**
86
-	 * @param array $parameters
87
-	 * @throws \Exception
88
-	 */
89
-	public function __construct(array $parameters) {
90
-		$this->statCache = new ArrayCache();
91
-		$this->httpClientService = Server::get(IClientService::class);
92
-		if (isset($parameters['host']) && isset($parameters['user']) && isset($parameters['password'])) {
93
-			$host = $parameters['host'];
94
-			//remove leading http[s], will be generated in createBaseUri()
95
-			if (str_starts_with($host, 'https://')) {
96
-				$host = substr($host, 8);
97
-			} elseif (str_starts_with($host, 'http://')) {
98
-				$host = substr($host, 7);
99
-			}
100
-			$this->host = $host;
101
-			$this->user = $parameters['user'];
102
-			$this->password = $parameters['password'];
103
-			if (isset($parameters['authType'])) {
104
-				$this->authType = $parameters['authType'];
105
-			}
106
-			if (isset($parameters['secure'])) {
107
-				if (is_string($parameters['secure'])) {
108
-					$this->secure = ($parameters['secure'] === 'true');
109
-				} else {
110
-					$this->secure = (bool)$parameters['secure'];
111
-				}
112
-			} else {
113
-				$this->secure = false;
114
-			}
115
-			if ($this->secure === true) {
116
-				// inject mock for testing
117
-				$this->certManager = \OC::$server->getCertificateManager();
118
-			}
119
-			$this->root = $parameters['root'] ?? '/';
120
-			$this->root = '/' . ltrim($this->root, '/');
121
-			$this->root = rtrim($this->root, '/') . '/';
122
-		} else {
123
-			throw new \Exception('Invalid webdav storage configuration');
124
-		}
125
-		$this->logger = Server::get(LoggerInterface::class);
126
-		$this->eventLogger = Server::get(IEventLogger::class);
127
-		// This timeout value will be used for the download and upload of files
128
-		$this->timeout = Server::get(IConfig::class)->getSystemValueInt('davstorage.request_timeout', IClient::DEFAULT_REQUEST_TIMEOUT);
129
-		$this->mimeTypeDetector = \OC::$server->getMimeTypeDetector();
130
-	}
131
-
132
-	protected function init(): void {
133
-		if ($this->ready) {
134
-			return;
135
-		}
136
-		$this->ready = true;
137
-
138
-		$settings = [
139
-			'baseUri' => $this->createBaseUri(),
140
-			'userName' => $this->user,
141
-			'password' => $this->password,
142
-		];
143
-		if ($this->authType !== null) {
144
-			$settings['authType'] = $this->authType;
145
-		}
146
-
147
-		$proxy = Server::get(IConfig::class)->getSystemValueString('proxy', '');
148
-		if ($proxy !== '') {
149
-			$settings['proxy'] = $proxy;
150
-		}
151
-
152
-		$this->client = new Client($settings);
153
-		$this->client->setThrowExceptions(true);
154
-
155
-		if ($this->secure === true) {
156
-			$certPath = $this->certManager->getAbsoluteBundlePath();
157
-			if (file_exists($certPath)) {
158
-				$this->certPath = $certPath;
159
-			}
160
-			if ($this->certPath) {
161
-				$this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
162
-			}
163
-		}
164
-
165
-		$lastRequestStart = 0;
166
-		$this->client->on('beforeRequest', function (RequestInterface $request) use (&$lastRequestStart) {
167
-			$this->logger->debug('sending dav ' . $request->getMethod() . ' request to external storage: ' . $request->getAbsoluteUrl(), ['app' => 'dav']);
168
-			$lastRequestStart = microtime(true);
169
-			$this->eventLogger->start('fs:storage:dav:request', 'Sending dav request to external storage');
170
-		});
171
-		$this->client->on('afterRequest', function (RequestInterface $request) use (&$lastRequestStart) {
172
-			$elapsed = microtime(true) - $lastRequestStart;
173
-			$this->logger->debug('dav ' . $request->getMethod() . ' request to external storage: ' . $request->getAbsoluteUrl() . ' took ' . round($elapsed * 1000, 1) . 'ms', ['app' => 'dav']);
174
-			$this->eventLogger->end('fs:storage:dav:request');
175
-		});
176
-	}
177
-
178
-	/**
179
-	 * Clear the stat cache
180
-	 */
181
-	public function clearStatCache(): void {
182
-		$this->statCache->clear();
183
-	}
184
-
185
-	public function getId(): string {
186
-		return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
187
-	}
188
-
189
-	public function createBaseUri(): string {
190
-		$baseUri = 'http';
191
-		if ($this->secure) {
192
-			$baseUri .= 's';
193
-		}
194
-		$baseUri .= '://' . $this->host . $this->root;
195
-		return $baseUri;
196
-	}
197
-
198
-	public function mkdir(string $path): bool {
199
-		$this->init();
200
-		$path = $this->cleanPath($path);
201
-		$result = $this->simpleResponse('MKCOL', $path, null, 201);
202
-		if ($result) {
203
-			$this->statCache->set($path, true);
204
-		}
205
-		return $result;
206
-	}
207
-
208
-	public function rmdir(string $path): bool {
209
-		$this->init();
210
-		$path = $this->cleanPath($path);
211
-		// FIXME: some WebDAV impl return 403 when trying to DELETE
212
-		// a non-empty folder
213
-		$result = $this->simpleResponse('DELETE', $path . '/', null, 204);
214
-		$this->statCache->clear($path . '/');
215
-		$this->statCache->remove($path);
216
-		return $result;
217
-	}
218
-
219
-	public function opendir(string $path) {
220
-		$this->init();
221
-		$path = $this->cleanPath($path);
222
-		try {
223
-			$content = $this->getDirectoryContent($path);
224
-			$files = [];
225
-			foreach ($content as $child) {
226
-				$files[] = $child['name'];
227
-			}
228
-			return IteratorDirectory::wrap($files);
229
-		} catch (\Exception $e) {
230
-			$this->convertException($e, $path);
231
-		}
232
-		return false;
233
-	}
234
-
235
-	/**
236
-	 * Propfind call with cache handling.
237
-	 *
238
-	 * First checks if information is cached.
239
-	 * If not, request it from the server then store to cache.
240
-	 *
241
-	 * @param string $path path to propfind
242
-	 *
243
-	 * @return array|false propfind response or false if the entry was not found
244
-	 *
245
-	 * @throws ClientHttpException
246
-	 */
247
-	protected function propfind(string $path): array|false {
248
-		$path = $this->cleanPath($path);
249
-		$cachedResponse = $this->statCache->get($path);
250
-		// we either don't know it, or we know it exists but need more details
251
-		if (is_null($cachedResponse) || $cachedResponse === true) {
252
-			$this->init();
253
-			$response = false;
254
-			try {
255
-				$response = $this->client->propFind(
256
-					$this->encodePath($path),
257
-					self::PROPFIND_PROPS
258
-				);
259
-				$this->statCache->set($path, $response);
260
-			} catch (ClientHttpException $e) {
261
-				if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) {
262
-					$this->statCache->clear($path . '/');
263
-					$this->statCache->set($path, false);
264
-				} else {
265
-					$this->convertException($e, $path);
266
-				}
267
-			} catch (\Exception $e) {
268
-				$this->convertException($e, $path);
269
-			}
270
-		} else {
271
-			$response = $cachedResponse;
272
-		}
273
-		return $response;
274
-	}
275
-
276
-	public function filetype(string $path): string|false {
277
-		try {
278
-			$response = $this->propfind($path);
279
-			if ($response === false) {
280
-				return false;
281
-			}
282
-			$responseType = [];
283
-			if (isset($response['{DAV:}resourcetype'])) {
284
-				/** @var ResourceType[] $response */
285
-				$responseType = $response['{DAV:}resourcetype']->getValue();
286
-			}
287
-			return (count($responseType) > 0 && $responseType[0] == '{DAV:}collection') ? 'dir' : 'file';
288
-		} catch (\Exception $e) {
289
-			$this->convertException($e, $path);
290
-		}
291
-		return false;
292
-	}
293
-
294
-	public function file_exists(string $path): bool {
295
-		try {
296
-			$path = $this->cleanPath($path);
297
-			$cachedState = $this->statCache->get($path);
298
-			if ($cachedState === false) {
299
-				// we know the file doesn't exist
300
-				return false;
301
-			} elseif (!is_null($cachedState)) {
302
-				return true;
303
-			}
304
-			// need to get from server
305
-			return ($this->propfind($path) !== false);
306
-		} catch (\Exception $e) {
307
-			$this->convertException($e, $path);
308
-		}
309
-		return false;
310
-	}
311
-
312
-	public function unlink(string $path): bool {
313
-		$this->init();
314
-		$path = $this->cleanPath($path);
315
-		$result = $this->simpleResponse('DELETE', $path, null, 204);
316
-		$this->statCache->clear($path . '/');
317
-		$this->statCache->remove($path);
318
-		return $result;
319
-	}
320
-
321
-	public function fopen(string $path, string $mode) {
322
-		$this->init();
323
-		$path = $this->cleanPath($path);
324
-		switch ($mode) {
325
-			case 'r':
326
-			case 'rb':
327
-				try {
328
-					$response = $this->httpClientService
329
-						->newClient()
330
-						->get($this->createBaseUri() . $this->encodePath($path), [
331
-							'auth' => [$this->user, $this->password],
332
-							'stream' => true,
333
-							// set download timeout for users with slow connections or large files
334
-							'timeout' => $this->timeout
335
-						]);
336
-				} catch (\GuzzleHttp\Exception\ClientException $e) {
337
-					if ($e->getResponse() instanceof ResponseInterface
338
-						&& $e->getResponse()->getStatusCode() === 404) {
339
-						return false;
340
-					} else {
341
-						throw $e;
342
-					}
343
-				}
344
-
345
-				if ($response->getStatusCode() !== Http::STATUS_OK) {
346
-					if ($response->getStatusCode() === Http::STATUS_LOCKED) {
347
-						throw new \OCP\Lock\LockedException($path);
348
-					} else {
349
-						$this->logger->error('Guzzle get returned status code ' . $response->getStatusCode(), ['app' => 'webdav client']);
350
-					}
351
-				}
352
-
353
-				$content = $response->getBody();
354
-
355
-				if ($content === null || is_string($content)) {
356
-					return false;
357
-				}
358
-
359
-				return $content;
360
-			case 'w':
361
-			case 'wb':
362
-			case 'a':
363
-			case 'ab':
364
-			case 'r+':
365
-			case 'w+':
366
-			case 'wb+':
367
-			case 'a+':
368
-			case 'x':
369
-			case 'x+':
370
-			case 'c':
371
-			case 'c+':
372
-				//emulate these
373
-				$tempManager = \OC::$server->getTempManager();
374
-				if (strrpos($path, '.') !== false) {
375
-					$ext = substr($path, strrpos($path, '.'));
376
-				} else {
377
-					$ext = '';
378
-				}
379
-				if ($this->file_exists($path)) {
380
-					if (!$this->isUpdatable($path)) {
381
-						return false;
382
-					}
383
-					if ($mode === 'w' || $mode === 'w+') {
384
-						$tmpFile = $tempManager->getTemporaryFile($ext);
385
-					} else {
386
-						$tmpFile = $this->getCachedFile($path);
387
-					}
388
-				} else {
389
-					if (!$this->isCreatable(dirname($path))) {
390
-						return false;
391
-					}
392
-					$tmpFile = $tempManager->getTemporaryFile($ext);
393
-				}
394
-				$handle = fopen($tmpFile, $mode);
395
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
396
-					$this->writeBack($tmpFile, $path);
397
-				});
398
-		}
399
-
400
-		return false;
401
-	}
402
-
403
-	public function writeBack(string $tmpFile, string $path): void {
404
-		$this->uploadFile($tmpFile, $path);
405
-		unlink($tmpFile);
406
-	}
407
-
408
-	public function free_space(string $path): int|float|false {
409
-		$this->init();
410
-		$path = $this->cleanPath($path);
411
-		try {
412
-			$response = $this->propfind($path);
413
-			if ($response === false) {
414
-				return FileInfo::SPACE_UNKNOWN;
415
-			}
416
-			if (isset($response['{DAV:}quota-available-bytes'])) {
417
-				return Util::numericToNumber($response['{DAV:}quota-available-bytes']);
418
-			} else {
419
-				return FileInfo::SPACE_UNKNOWN;
420
-			}
421
-		} catch (\Exception $e) {
422
-			return FileInfo::SPACE_UNKNOWN;
423
-		}
424
-	}
425
-
426
-	public function touch(string $path, ?int $mtime = null): bool {
427
-		$this->init();
428
-		if (is_null($mtime)) {
429
-			$mtime = time();
430
-		}
431
-		$path = $this->cleanPath($path);
432
-
433
-		// if file exists, update the mtime, else create a new empty file
434
-		if ($this->file_exists($path)) {
435
-			try {
436
-				$this->statCache->remove($path);
437
-				$this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]);
438
-				// non-owncloud clients might not have accepted the property, need to recheck it
439
-				$response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0);
440
-				if (isset($response['{DAV:}getlastmodified'])) {
441
-					$remoteMtime = strtotime($response['{DAV:}getlastmodified']);
442
-					if ($remoteMtime !== $mtime) {
443
-						// server has not accepted the mtime
444
-						return false;
445
-					}
446
-				}
447
-			} catch (ClientHttpException $e) {
448
-				if ($e->getHttpStatus() === 501) {
449
-					return false;
450
-				}
451
-				$this->convertException($e, $path);
452
-				return false;
453
-			} catch (\Exception $e) {
454
-				$this->convertException($e, $path);
455
-				return false;
456
-			}
457
-		} else {
458
-			$this->file_put_contents($path, '');
459
-		}
460
-		return true;
461
-	}
462
-
463
-	public function file_put_contents(string $path, mixed $data): int|float|false {
464
-		$path = $this->cleanPath($path);
465
-		$result = parent::file_put_contents($path, $data);
466
-		$this->statCache->remove($path);
467
-		return $result;
468
-	}
469
-
470
-	protected function uploadFile(string $path, string $target): void {
471
-		$this->init();
472
-
473
-		// invalidate
474
-		$target = $this->cleanPath($target);
475
-		$this->statCache->remove($target);
476
-		$source = fopen($path, 'r');
477
-
478
-		$this->httpClientService
479
-			->newClient()
480
-			->put($this->createBaseUri() . $this->encodePath($target), [
481
-				'body' => $source,
482
-				'auth' => [$this->user, $this->password],
483
-				// set upload timeout for users with slow connections or large files
484
-				'timeout' => $this->timeout
485
-			]);
486
-
487
-		$this->removeCachedFile($target);
488
-	}
489
-
490
-	public function rename(string $source, string $target): bool {
491
-		$this->init();
492
-		$source = $this->cleanPath($source);
493
-		$target = $this->cleanPath($target);
494
-		try {
495
-			// overwrite directory ?
496
-			if ($this->is_dir($target)) {
497
-				// needs trailing slash in destination
498
-				$target = rtrim($target, '/') . '/';
499
-			}
500
-			$this->client->request(
501
-				'MOVE',
502
-				$this->encodePath($source),
503
-				null,
504
-				[
505
-					'Destination' => $this->createBaseUri() . $this->encodePath($target),
506
-				]
507
-			);
508
-			$this->statCache->clear($source . '/');
509
-			$this->statCache->clear($target . '/');
510
-			$this->statCache->set($source, false);
511
-			$this->statCache->set($target, true);
512
-			$this->removeCachedFile($source);
513
-			$this->removeCachedFile($target);
514
-			return true;
515
-		} catch (\Exception $e) {
516
-			$this->convertException($e);
517
-		}
518
-		return false;
519
-	}
520
-
521
-	public function copy(string $source, string $target): bool {
522
-		$this->init();
523
-		$source = $this->cleanPath($source);
524
-		$target = $this->cleanPath($target);
525
-		try {
526
-			// overwrite directory ?
527
-			if ($this->is_dir($target)) {
528
-				// needs trailing slash in destination
529
-				$target = rtrim($target, '/') . '/';
530
-			}
531
-			$this->client->request(
532
-				'COPY',
533
-				$this->encodePath($source),
534
-				null,
535
-				[
536
-					'Destination' => $this->createBaseUri() . $this->encodePath($target),
537
-				]
538
-			);
539
-			$this->statCache->clear($target . '/');
540
-			$this->statCache->set($target, true);
541
-			$this->removeCachedFile($target);
542
-			return true;
543
-		} catch (\Exception $e) {
544
-			$this->convertException($e);
545
-		}
546
-		return false;
547
-	}
548
-
549
-	public function getMetaData(string $path): ?array {
550
-		if (Filesystem::isFileBlacklisted($path)) {
551
-			throw new ForbiddenException('Invalid path: ' . $path, false);
552
-		}
553
-		$response = $this->propfind($path);
554
-		if (!$response) {
555
-			return null;
556
-		} else {
557
-			return $this->getMetaFromPropfind($path, $response);
558
-		}
559
-	}
560
-	private function getMetaFromPropfind(string $path, array $response): array {
561
-		if (isset($response['{DAV:}getetag'])) {
562
-			$etag = trim($response['{DAV:}getetag'], '"');
563
-			if (strlen($etag) > 40) {
564
-				$etag = md5($etag);
565
-			}
566
-		} else {
567
-			$etag = parent::getETag($path);
568
-		}
569
-
570
-		$responseType = [];
571
-		if (isset($response['{DAV:}resourcetype'])) {
572
-			/** @var ResourceType[] $response */
573
-			$responseType = $response['{DAV:}resourcetype']->getValue();
574
-		}
575
-		$type = (count($responseType) > 0 && $responseType[0] == '{DAV:}collection') ? 'dir' : 'file';
576
-		if ($type === 'dir') {
577
-			$mimeType = 'httpd/unix-directory';
578
-		} elseif (isset($response['{DAV:}getcontenttype'])) {
579
-			$mimeType = $response['{DAV:}getcontenttype'];
580
-		} else {
581
-			$mimeType = $this->mimeTypeDetector->detectPath($path);
582
-		}
583
-
584
-		if (isset($response['{http://owncloud.org/ns}permissions'])) {
585
-			$permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
586
-		} elseif ($type === 'dir') {
587
-			$permissions = Constants::PERMISSION_ALL;
588
-		} else {
589
-			$permissions = Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
590
-		}
591
-
592
-		$mtime = isset($response['{DAV:}getlastmodified']) ? strtotime($response['{DAV:}getlastmodified']) : null;
593
-
594
-		if ($type === 'dir') {
595
-			$size = -1;
596
-		} else {
597
-			$size = Util::numericToNumber($response['{DAV:}getcontentlength'] ?? 0);
598
-		}
599
-
600
-		return [
601
-			'name' => basename($path),
602
-			'mtime' => $mtime,
603
-			'storage_mtime' => $mtime,
604
-			'size' => $size,
605
-			'permissions' => $permissions,
606
-			'etag' => $etag,
607
-			'mimetype' => $mimeType,
608
-		];
609
-	}
610
-
611
-	public function stat(string $path): array|false {
612
-		$meta = $this->getMetaData($path);
613
-		return $meta ?: false;
614
-
615
-	}
616
-
617
-	public function getMimeType(string $path): string|false {
618
-		$meta = $this->getMetaData($path);
619
-		return $meta ? $meta['mimetype'] : false;
620
-	}
621
-
622
-	public function cleanPath(string $path): string {
623
-		if ($path === '') {
624
-			return $path;
625
-		}
626
-		$path = Filesystem::normalizePath($path);
627
-		// remove leading slash
628
-		return substr($path, 1);
629
-	}
630
-
631
-	/**
632
-	 * URL encodes the given path but keeps the slashes
633
-	 *
634
-	 * @param string $path to encode
635
-	 * @return string encoded path
636
-	 */
637
-	protected function encodePath(string $path): string {
638
-		// slashes need to stay
639
-		return str_replace('%2F', '/', rawurlencode($path));
640
-	}
641
-
642
-	/**
643
-	 * @return bool
644
-	 * @throws StorageInvalidException
645
-	 * @throws StorageNotAvailableException
646
-	 */
647
-	protected function simpleResponse(string $method, string $path, ?string $body, int $expected): bool {
648
-		$path = $this->cleanPath($path);
649
-		try {
650
-			$response = $this->client->request($method, $this->encodePath($path), $body);
651
-			return $response['statusCode'] == $expected;
652
-		} catch (ClientHttpException $e) {
653
-			if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
654
-				$this->statCache->clear($path . '/');
655
-				$this->statCache->set($path, false);
656
-				return false;
657
-			}
658
-
659
-			$this->convertException($e, $path);
660
-		} catch (\Exception $e) {
661
-			$this->convertException($e, $path);
662
-		}
663
-		return false;
664
-	}
665
-
666
-	/**
667
-	 * check if curl is installed
668
-	 */
669
-	public static function checkDependencies(): bool {
670
-		return true;
671
-	}
672
-
673
-	public function isUpdatable(string $path): bool {
674
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
675
-	}
676
-
677
-	public function isCreatable(string $path): bool {
678
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
679
-	}
680
-
681
-	public function isSharable(string $path): bool {
682
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
683
-	}
684
-
685
-	public function isDeletable(string $path): bool {
686
-		return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
687
-	}
688
-
689
-	public function getPermissions(string $path): int {
690
-		$stat = $this->getMetaData($path);
691
-		return $stat ? $stat['permissions'] : 0;
692
-	}
693
-
694
-	public function getETag(string $path): string|false {
695
-		$meta = $this->getMetaData($path);
696
-		return $meta ? $meta['etag'] : false;
697
-	}
698
-
699
-	protected function parsePermissions(string $permissionsString): int {
700
-		$permissions = Constants::PERMISSION_READ;
701
-		if (str_contains($permissionsString, 'R')) {
702
-			$permissions |= Constants::PERMISSION_SHARE;
703
-		}
704
-		if (str_contains($permissionsString, 'D')) {
705
-			$permissions |= Constants::PERMISSION_DELETE;
706
-		}
707
-		if (str_contains($permissionsString, 'W')) {
708
-			$permissions |= Constants::PERMISSION_UPDATE;
709
-		}
710
-		if (str_contains($permissionsString, 'CK')) {
711
-			$permissions |= Constants::PERMISSION_CREATE;
712
-			$permissions |= Constants::PERMISSION_UPDATE;
713
-		}
714
-		return $permissions;
715
-	}
716
-
717
-	public function hasUpdated(string $path, int $time): bool {
718
-		$this->init();
719
-		$path = $this->cleanPath($path);
720
-		try {
721
-			// force refresh for $path
722
-			$this->statCache->remove($path);
723
-			$response = $this->propfind($path);
724
-			if ($response === false) {
725
-				if ($path === '') {
726
-					// if root is gone it means the storage is not available
727
-					throw new StorageNotAvailableException('root is gone');
728
-				}
729
-				return false;
730
-			}
731
-			if (isset($response['{DAV:}getetag'])) {
732
-				$cachedData = $this->getCache()->get($path);
733
-				$etag = trim($response['{DAV:}getetag'], '"');
734
-				if (($cachedData === false) || (!empty($etag) && ($cachedData['etag'] !== $etag))) {
735
-					return true;
736
-				} elseif (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
737
-					$sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
738
-					return $sharePermissions !== $cachedData['permissions'];
739
-				} elseif (isset($response['{http://owncloud.org/ns}permissions'])) {
740
-					$permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
741
-					return $permissions !== $cachedData['permissions'];
742
-				} else {
743
-					return false;
744
-				}
745
-			} elseif (isset($response['{DAV:}getlastmodified'])) {
746
-				$remoteMtime = strtotime($response['{DAV:}getlastmodified']);
747
-				return $remoteMtime > $time;
748
-			} else {
749
-				// neither `getetag` nor `getlastmodified` is set
750
-				return false;
751
-			}
752
-		} catch (ClientHttpException $e) {
753
-			if ($e->getHttpStatus() === 405) {
754
-				if ($path === '') {
755
-					// if root is gone it means the storage is not available
756
-					throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
757
-				}
758
-				return false;
759
-			}
760
-			$this->convertException($e, $path);
761
-			return false;
762
-		} catch (\Exception $e) {
763
-			$this->convertException($e, $path);
764
-			return false;
765
-		}
766
-	}
767
-
768
-	/**
769
-	 * Interpret the given exception and decide whether it is due to an
770
-	 * unavailable storage, invalid storage or other.
771
-	 * This will either throw StorageInvalidException, StorageNotAvailableException
772
-	 * or do nothing.
773
-	 *
774
-	 * @param Exception $e sabre exception
775
-	 * @param string $path optional path from the operation
776
-	 *
777
-	 * @throws StorageInvalidException if the storage is invalid, for example
778
-	 *                                 when the authentication expired or is invalid
779
-	 * @throws StorageNotAvailableException if the storage is not available,
780
-	 *                                      which might be temporary
781
-	 * @throws ForbiddenException if the action is not allowed
782
-	 */
783
-	protected function convertException(Exception $e, string $path = ''): void {
784
-		$this->logger->debug($e->getMessage(), ['app' => 'files_external', 'exception' => $e]);
785
-		if ($e instanceof ClientHttpException) {
786
-			if ($e->getHttpStatus() === Http::STATUS_LOCKED) {
787
-				throw new \OCP\Lock\LockedException($path);
788
-			}
789
-			if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
790
-				// either password was changed or was invalid all along
791
-				throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
792
-			} elseif ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
793
-				// ignore exception for MethodNotAllowed, false will be returned
794
-				return;
795
-			} elseif ($e->getHttpStatus() === Http::STATUS_FORBIDDEN) {
796
-				// The operation is forbidden. Fail somewhat gracefully
797
-				throw new ForbiddenException(get_class($e) . ':' . $e->getMessage(), false);
798
-			}
799
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
800
-		} elseif ($e instanceof ClientException) {
801
-			// connection timeout or refused, server could be temporarily down
802
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
803
-		} elseif ($e instanceof \InvalidArgumentException) {
804
-			// parse error because the server returned HTML instead of XML,
805
-			// possibly temporarily down
806
-			throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
807
-		} elseif (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
808
-			// rethrow
809
-			throw $e;
810
-		}
811
-
812
-		// TODO: only log for now, but in the future need to wrap/rethrow exception
813
-	}
814
-
815
-	public function getDirectoryContent(string $directory): \Traversable {
816
-		$this->init();
817
-		$directory = $this->cleanPath($directory);
818
-		try {
819
-			$responses = $this->client->propFind(
820
-				$this->encodePath($directory),
821
-				self::PROPFIND_PROPS,
822
-				1
823
-			);
824
-
825
-			array_shift($responses); //the first entry is the current directory
826
-			if (!$this->statCache->hasKey($directory)) {
827
-				$this->statCache->set($directory, true);
828
-			}
829
-
830
-			foreach ($responses as $file => $response) {
831
-				$file = rawurldecode($file);
832
-				$file = substr($file, strlen($this->root));
833
-				$file = $this->cleanPath($file);
834
-				$this->statCache->set($file, $response);
835
-				yield $this->getMetaFromPropfind($file, $response);
836
-			}
837
-		} catch (\Exception $e) {
838
-			$this->convertException($e, $directory);
839
-		}
840
-	}
43
+    /** @var string */
44
+    protected $password;
45
+    /** @var string */
46
+    protected $user;
47
+    /** @var string|null */
48
+    protected $authType;
49
+    /** @var string */
50
+    protected $host;
51
+    /** @var bool */
52
+    protected $secure;
53
+    /** @var string */
54
+    protected $root;
55
+    /** @var string */
56
+    protected $certPath;
57
+    /** @var bool */
58
+    protected $ready;
59
+    /** @var Client */
60
+    protected $client;
61
+    /** @var ArrayCache */
62
+    protected $statCache;
63
+    /** @var IClientService */
64
+    protected $httpClientService;
65
+    /** @var ICertificateManager */
66
+    protected $certManager;
67
+    protected LoggerInterface $logger;
68
+    protected IEventLogger $eventLogger;
69
+    protected IMimeTypeDetector $mimeTypeDetector;
70
+
71
+    /** @var int */
72
+    private $timeout;
73
+
74
+    protected const PROPFIND_PROPS = [
75
+        '{DAV:}getlastmodified',
76
+        '{DAV:}getcontentlength',
77
+        '{DAV:}getcontenttype',
78
+        '{http://owncloud.org/ns}permissions',
79
+        '{http://open-collaboration-services.org/ns}share-permissions',
80
+        '{DAV:}resourcetype',
81
+        '{DAV:}getetag',
82
+        '{DAV:}quota-available-bytes',
83
+    ];
84
+
85
+    /**
86
+     * @param array $parameters
87
+     * @throws \Exception
88
+     */
89
+    public function __construct(array $parameters) {
90
+        $this->statCache = new ArrayCache();
91
+        $this->httpClientService = Server::get(IClientService::class);
92
+        if (isset($parameters['host']) && isset($parameters['user']) && isset($parameters['password'])) {
93
+            $host = $parameters['host'];
94
+            //remove leading http[s], will be generated in createBaseUri()
95
+            if (str_starts_with($host, 'https://')) {
96
+                $host = substr($host, 8);
97
+            } elseif (str_starts_with($host, 'http://')) {
98
+                $host = substr($host, 7);
99
+            }
100
+            $this->host = $host;
101
+            $this->user = $parameters['user'];
102
+            $this->password = $parameters['password'];
103
+            if (isset($parameters['authType'])) {
104
+                $this->authType = $parameters['authType'];
105
+            }
106
+            if (isset($parameters['secure'])) {
107
+                if (is_string($parameters['secure'])) {
108
+                    $this->secure = ($parameters['secure'] === 'true');
109
+                } else {
110
+                    $this->secure = (bool)$parameters['secure'];
111
+                }
112
+            } else {
113
+                $this->secure = false;
114
+            }
115
+            if ($this->secure === true) {
116
+                // inject mock for testing
117
+                $this->certManager = \OC::$server->getCertificateManager();
118
+            }
119
+            $this->root = $parameters['root'] ?? '/';
120
+            $this->root = '/' . ltrim($this->root, '/');
121
+            $this->root = rtrim($this->root, '/') . '/';
122
+        } else {
123
+            throw new \Exception('Invalid webdav storage configuration');
124
+        }
125
+        $this->logger = Server::get(LoggerInterface::class);
126
+        $this->eventLogger = Server::get(IEventLogger::class);
127
+        // This timeout value will be used for the download and upload of files
128
+        $this->timeout = Server::get(IConfig::class)->getSystemValueInt('davstorage.request_timeout', IClient::DEFAULT_REQUEST_TIMEOUT);
129
+        $this->mimeTypeDetector = \OC::$server->getMimeTypeDetector();
130
+    }
131
+
132
+    protected function init(): void {
133
+        if ($this->ready) {
134
+            return;
135
+        }
136
+        $this->ready = true;
137
+
138
+        $settings = [
139
+            'baseUri' => $this->createBaseUri(),
140
+            'userName' => $this->user,
141
+            'password' => $this->password,
142
+        ];
143
+        if ($this->authType !== null) {
144
+            $settings['authType'] = $this->authType;
145
+        }
146
+
147
+        $proxy = Server::get(IConfig::class)->getSystemValueString('proxy', '');
148
+        if ($proxy !== '') {
149
+            $settings['proxy'] = $proxy;
150
+        }
151
+
152
+        $this->client = new Client($settings);
153
+        $this->client->setThrowExceptions(true);
154
+
155
+        if ($this->secure === true) {
156
+            $certPath = $this->certManager->getAbsoluteBundlePath();
157
+            if (file_exists($certPath)) {
158
+                $this->certPath = $certPath;
159
+            }
160
+            if ($this->certPath) {
161
+                $this->client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
162
+            }
163
+        }
164
+
165
+        $lastRequestStart = 0;
166
+        $this->client->on('beforeRequest', function (RequestInterface $request) use (&$lastRequestStart) {
167
+            $this->logger->debug('sending dav ' . $request->getMethod() . ' request to external storage: ' . $request->getAbsoluteUrl(), ['app' => 'dav']);
168
+            $lastRequestStart = microtime(true);
169
+            $this->eventLogger->start('fs:storage:dav:request', 'Sending dav request to external storage');
170
+        });
171
+        $this->client->on('afterRequest', function (RequestInterface $request) use (&$lastRequestStart) {
172
+            $elapsed = microtime(true) - $lastRequestStart;
173
+            $this->logger->debug('dav ' . $request->getMethod() . ' request to external storage: ' . $request->getAbsoluteUrl() . ' took ' . round($elapsed * 1000, 1) . 'ms', ['app' => 'dav']);
174
+            $this->eventLogger->end('fs:storage:dav:request');
175
+        });
176
+    }
177
+
178
+    /**
179
+     * Clear the stat cache
180
+     */
181
+    public function clearStatCache(): void {
182
+        $this->statCache->clear();
183
+    }
184
+
185
+    public function getId(): string {
186
+        return 'webdav::' . $this->user . '@' . $this->host . '/' . $this->root;
187
+    }
188
+
189
+    public function createBaseUri(): string {
190
+        $baseUri = 'http';
191
+        if ($this->secure) {
192
+            $baseUri .= 's';
193
+        }
194
+        $baseUri .= '://' . $this->host . $this->root;
195
+        return $baseUri;
196
+    }
197
+
198
+    public function mkdir(string $path): bool {
199
+        $this->init();
200
+        $path = $this->cleanPath($path);
201
+        $result = $this->simpleResponse('MKCOL', $path, null, 201);
202
+        if ($result) {
203
+            $this->statCache->set($path, true);
204
+        }
205
+        return $result;
206
+    }
207
+
208
+    public function rmdir(string $path): bool {
209
+        $this->init();
210
+        $path = $this->cleanPath($path);
211
+        // FIXME: some WebDAV impl return 403 when trying to DELETE
212
+        // a non-empty folder
213
+        $result = $this->simpleResponse('DELETE', $path . '/', null, 204);
214
+        $this->statCache->clear($path . '/');
215
+        $this->statCache->remove($path);
216
+        return $result;
217
+    }
218
+
219
+    public function opendir(string $path) {
220
+        $this->init();
221
+        $path = $this->cleanPath($path);
222
+        try {
223
+            $content = $this->getDirectoryContent($path);
224
+            $files = [];
225
+            foreach ($content as $child) {
226
+                $files[] = $child['name'];
227
+            }
228
+            return IteratorDirectory::wrap($files);
229
+        } catch (\Exception $e) {
230
+            $this->convertException($e, $path);
231
+        }
232
+        return false;
233
+    }
234
+
235
+    /**
236
+     * Propfind call with cache handling.
237
+     *
238
+     * First checks if information is cached.
239
+     * If not, request it from the server then store to cache.
240
+     *
241
+     * @param string $path path to propfind
242
+     *
243
+     * @return array|false propfind response or false if the entry was not found
244
+     *
245
+     * @throws ClientHttpException
246
+     */
247
+    protected function propfind(string $path): array|false {
248
+        $path = $this->cleanPath($path);
249
+        $cachedResponse = $this->statCache->get($path);
250
+        // we either don't know it, or we know it exists but need more details
251
+        if (is_null($cachedResponse) || $cachedResponse === true) {
252
+            $this->init();
253
+            $response = false;
254
+            try {
255
+                $response = $this->client->propFind(
256
+                    $this->encodePath($path),
257
+                    self::PROPFIND_PROPS
258
+                );
259
+                $this->statCache->set($path, $response);
260
+            } catch (ClientHttpException $e) {
261
+                if ($e->getHttpStatus() === 404 || $e->getHttpStatus() === 405) {
262
+                    $this->statCache->clear($path . '/');
263
+                    $this->statCache->set($path, false);
264
+                } else {
265
+                    $this->convertException($e, $path);
266
+                }
267
+            } catch (\Exception $e) {
268
+                $this->convertException($e, $path);
269
+            }
270
+        } else {
271
+            $response = $cachedResponse;
272
+        }
273
+        return $response;
274
+    }
275
+
276
+    public function filetype(string $path): string|false {
277
+        try {
278
+            $response = $this->propfind($path);
279
+            if ($response === false) {
280
+                return false;
281
+            }
282
+            $responseType = [];
283
+            if (isset($response['{DAV:}resourcetype'])) {
284
+                /** @var ResourceType[] $response */
285
+                $responseType = $response['{DAV:}resourcetype']->getValue();
286
+            }
287
+            return (count($responseType) > 0 && $responseType[0] == '{DAV:}collection') ? 'dir' : 'file';
288
+        } catch (\Exception $e) {
289
+            $this->convertException($e, $path);
290
+        }
291
+        return false;
292
+    }
293
+
294
+    public function file_exists(string $path): bool {
295
+        try {
296
+            $path = $this->cleanPath($path);
297
+            $cachedState = $this->statCache->get($path);
298
+            if ($cachedState === false) {
299
+                // we know the file doesn't exist
300
+                return false;
301
+            } elseif (!is_null($cachedState)) {
302
+                return true;
303
+            }
304
+            // need to get from server
305
+            return ($this->propfind($path) !== false);
306
+        } catch (\Exception $e) {
307
+            $this->convertException($e, $path);
308
+        }
309
+        return false;
310
+    }
311
+
312
+    public function unlink(string $path): bool {
313
+        $this->init();
314
+        $path = $this->cleanPath($path);
315
+        $result = $this->simpleResponse('DELETE', $path, null, 204);
316
+        $this->statCache->clear($path . '/');
317
+        $this->statCache->remove($path);
318
+        return $result;
319
+    }
320
+
321
+    public function fopen(string $path, string $mode) {
322
+        $this->init();
323
+        $path = $this->cleanPath($path);
324
+        switch ($mode) {
325
+            case 'r':
326
+            case 'rb':
327
+                try {
328
+                    $response = $this->httpClientService
329
+                        ->newClient()
330
+                        ->get($this->createBaseUri() . $this->encodePath($path), [
331
+                            'auth' => [$this->user, $this->password],
332
+                            'stream' => true,
333
+                            // set download timeout for users with slow connections or large files
334
+                            'timeout' => $this->timeout
335
+                        ]);
336
+                } catch (\GuzzleHttp\Exception\ClientException $e) {
337
+                    if ($e->getResponse() instanceof ResponseInterface
338
+                        && $e->getResponse()->getStatusCode() === 404) {
339
+                        return false;
340
+                    } else {
341
+                        throw $e;
342
+                    }
343
+                }
344
+
345
+                if ($response->getStatusCode() !== Http::STATUS_OK) {
346
+                    if ($response->getStatusCode() === Http::STATUS_LOCKED) {
347
+                        throw new \OCP\Lock\LockedException($path);
348
+                    } else {
349
+                        $this->logger->error('Guzzle get returned status code ' . $response->getStatusCode(), ['app' => 'webdav client']);
350
+                    }
351
+                }
352
+
353
+                $content = $response->getBody();
354
+
355
+                if ($content === null || is_string($content)) {
356
+                    return false;
357
+                }
358
+
359
+                return $content;
360
+            case 'w':
361
+            case 'wb':
362
+            case 'a':
363
+            case 'ab':
364
+            case 'r+':
365
+            case 'w+':
366
+            case 'wb+':
367
+            case 'a+':
368
+            case 'x':
369
+            case 'x+':
370
+            case 'c':
371
+            case 'c+':
372
+                //emulate these
373
+                $tempManager = \OC::$server->getTempManager();
374
+                if (strrpos($path, '.') !== false) {
375
+                    $ext = substr($path, strrpos($path, '.'));
376
+                } else {
377
+                    $ext = '';
378
+                }
379
+                if ($this->file_exists($path)) {
380
+                    if (!$this->isUpdatable($path)) {
381
+                        return false;
382
+                    }
383
+                    if ($mode === 'w' || $mode === 'w+') {
384
+                        $tmpFile = $tempManager->getTemporaryFile($ext);
385
+                    } else {
386
+                        $tmpFile = $this->getCachedFile($path);
387
+                    }
388
+                } else {
389
+                    if (!$this->isCreatable(dirname($path))) {
390
+                        return false;
391
+                    }
392
+                    $tmpFile = $tempManager->getTemporaryFile($ext);
393
+                }
394
+                $handle = fopen($tmpFile, $mode);
395
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
396
+                    $this->writeBack($tmpFile, $path);
397
+                });
398
+        }
399
+
400
+        return false;
401
+    }
402
+
403
+    public function writeBack(string $tmpFile, string $path): void {
404
+        $this->uploadFile($tmpFile, $path);
405
+        unlink($tmpFile);
406
+    }
407
+
408
+    public function free_space(string $path): int|float|false {
409
+        $this->init();
410
+        $path = $this->cleanPath($path);
411
+        try {
412
+            $response = $this->propfind($path);
413
+            if ($response === false) {
414
+                return FileInfo::SPACE_UNKNOWN;
415
+            }
416
+            if (isset($response['{DAV:}quota-available-bytes'])) {
417
+                return Util::numericToNumber($response['{DAV:}quota-available-bytes']);
418
+            } else {
419
+                return FileInfo::SPACE_UNKNOWN;
420
+            }
421
+        } catch (\Exception $e) {
422
+            return FileInfo::SPACE_UNKNOWN;
423
+        }
424
+    }
425
+
426
+    public function touch(string $path, ?int $mtime = null): bool {
427
+        $this->init();
428
+        if (is_null($mtime)) {
429
+            $mtime = time();
430
+        }
431
+        $path = $this->cleanPath($path);
432
+
433
+        // if file exists, update the mtime, else create a new empty file
434
+        if ($this->file_exists($path)) {
435
+            try {
436
+                $this->statCache->remove($path);
437
+                $this->client->proppatch($this->encodePath($path), ['{DAV:}lastmodified' => $mtime]);
438
+                // non-owncloud clients might not have accepted the property, need to recheck it
439
+                $response = $this->client->propfind($this->encodePath($path), ['{DAV:}getlastmodified'], 0);
440
+                if (isset($response['{DAV:}getlastmodified'])) {
441
+                    $remoteMtime = strtotime($response['{DAV:}getlastmodified']);
442
+                    if ($remoteMtime !== $mtime) {
443
+                        // server has not accepted the mtime
444
+                        return false;
445
+                    }
446
+                }
447
+            } catch (ClientHttpException $e) {
448
+                if ($e->getHttpStatus() === 501) {
449
+                    return false;
450
+                }
451
+                $this->convertException($e, $path);
452
+                return false;
453
+            } catch (\Exception $e) {
454
+                $this->convertException($e, $path);
455
+                return false;
456
+            }
457
+        } else {
458
+            $this->file_put_contents($path, '');
459
+        }
460
+        return true;
461
+    }
462
+
463
+    public function file_put_contents(string $path, mixed $data): int|float|false {
464
+        $path = $this->cleanPath($path);
465
+        $result = parent::file_put_contents($path, $data);
466
+        $this->statCache->remove($path);
467
+        return $result;
468
+    }
469
+
470
+    protected function uploadFile(string $path, string $target): void {
471
+        $this->init();
472
+
473
+        // invalidate
474
+        $target = $this->cleanPath($target);
475
+        $this->statCache->remove($target);
476
+        $source = fopen($path, 'r');
477
+
478
+        $this->httpClientService
479
+            ->newClient()
480
+            ->put($this->createBaseUri() . $this->encodePath($target), [
481
+                'body' => $source,
482
+                'auth' => [$this->user, $this->password],
483
+                // set upload timeout for users with slow connections or large files
484
+                'timeout' => $this->timeout
485
+            ]);
486
+
487
+        $this->removeCachedFile($target);
488
+    }
489
+
490
+    public function rename(string $source, string $target): bool {
491
+        $this->init();
492
+        $source = $this->cleanPath($source);
493
+        $target = $this->cleanPath($target);
494
+        try {
495
+            // overwrite directory ?
496
+            if ($this->is_dir($target)) {
497
+                // needs trailing slash in destination
498
+                $target = rtrim($target, '/') . '/';
499
+            }
500
+            $this->client->request(
501
+                'MOVE',
502
+                $this->encodePath($source),
503
+                null,
504
+                [
505
+                    'Destination' => $this->createBaseUri() . $this->encodePath($target),
506
+                ]
507
+            );
508
+            $this->statCache->clear($source . '/');
509
+            $this->statCache->clear($target . '/');
510
+            $this->statCache->set($source, false);
511
+            $this->statCache->set($target, true);
512
+            $this->removeCachedFile($source);
513
+            $this->removeCachedFile($target);
514
+            return true;
515
+        } catch (\Exception $e) {
516
+            $this->convertException($e);
517
+        }
518
+        return false;
519
+    }
520
+
521
+    public function copy(string $source, string $target): bool {
522
+        $this->init();
523
+        $source = $this->cleanPath($source);
524
+        $target = $this->cleanPath($target);
525
+        try {
526
+            // overwrite directory ?
527
+            if ($this->is_dir($target)) {
528
+                // needs trailing slash in destination
529
+                $target = rtrim($target, '/') . '/';
530
+            }
531
+            $this->client->request(
532
+                'COPY',
533
+                $this->encodePath($source),
534
+                null,
535
+                [
536
+                    'Destination' => $this->createBaseUri() . $this->encodePath($target),
537
+                ]
538
+            );
539
+            $this->statCache->clear($target . '/');
540
+            $this->statCache->set($target, true);
541
+            $this->removeCachedFile($target);
542
+            return true;
543
+        } catch (\Exception $e) {
544
+            $this->convertException($e);
545
+        }
546
+        return false;
547
+    }
548
+
549
+    public function getMetaData(string $path): ?array {
550
+        if (Filesystem::isFileBlacklisted($path)) {
551
+            throw new ForbiddenException('Invalid path: ' . $path, false);
552
+        }
553
+        $response = $this->propfind($path);
554
+        if (!$response) {
555
+            return null;
556
+        } else {
557
+            return $this->getMetaFromPropfind($path, $response);
558
+        }
559
+    }
560
+    private function getMetaFromPropfind(string $path, array $response): array {
561
+        if (isset($response['{DAV:}getetag'])) {
562
+            $etag = trim($response['{DAV:}getetag'], '"');
563
+            if (strlen($etag) > 40) {
564
+                $etag = md5($etag);
565
+            }
566
+        } else {
567
+            $etag = parent::getETag($path);
568
+        }
569
+
570
+        $responseType = [];
571
+        if (isset($response['{DAV:}resourcetype'])) {
572
+            /** @var ResourceType[] $response */
573
+            $responseType = $response['{DAV:}resourcetype']->getValue();
574
+        }
575
+        $type = (count($responseType) > 0 && $responseType[0] == '{DAV:}collection') ? 'dir' : 'file';
576
+        if ($type === 'dir') {
577
+            $mimeType = 'httpd/unix-directory';
578
+        } elseif (isset($response['{DAV:}getcontenttype'])) {
579
+            $mimeType = $response['{DAV:}getcontenttype'];
580
+        } else {
581
+            $mimeType = $this->mimeTypeDetector->detectPath($path);
582
+        }
583
+
584
+        if (isset($response['{http://owncloud.org/ns}permissions'])) {
585
+            $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
586
+        } elseif ($type === 'dir') {
587
+            $permissions = Constants::PERMISSION_ALL;
588
+        } else {
589
+            $permissions = Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE;
590
+        }
591
+
592
+        $mtime = isset($response['{DAV:}getlastmodified']) ? strtotime($response['{DAV:}getlastmodified']) : null;
593
+
594
+        if ($type === 'dir') {
595
+            $size = -1;
596
+        } else {
597
+            $size = Util::numericToNumber($response['{DAV:}getcontentlength'] ?? 0);
598
+        }
599
+
600
+        return [
601
+            'name' => basename($path),
602
+            'mtime' => $mtime,
603
+            'storage_mtime' => $mtime,
604
+            'size' => $size,
605
+            'permissions' => $permissions,
606
+            'etag' => $etag,
607
+            'mimetype' => $mimeType,
608
+        ];
609
+    }
610
+
611
+    public function stat(string $path): array|false {
612
+        $meta = $this->getMetaData($path);
613
+        return $meta ?: false;
614
+
615
+    }
616
+
617
+    public function getMimeType(string $path): string|false {
618
+        $meta = $this->getMetaData($path);
619
+        return $meta ? $meta['mimetype'] : false;
620
+    }
621
+
622
+    public function cleanPath(string $path): string {
623
+        if ($path === '') {
624
+            return $path;
625
+        }
626
+        $path = Filesystem::normalizePath($path);
627
+        // remove leading slash
628
+        return substr($path, 1);
629
+    }
630
+
631
+    /**
632
+     * URL encodes the given path but keeps the slashes
633
+     *
634
+     * @param string $path to encode
635
+     * @return string encoded path
636
+     */
637
+    protected function encodePath(string $path): string {
638
+        // slashes need to stay
639
+        return str_replace('%2F', '/', rawurlencode($path));
640
+    }
641
+
642
+    /**
643
+     * @return bool
644
+     * @throws StorageInvalidException
645
+     * @throws StorageNotAvailableException
646
+     */
647
+    protected function simpleResponse(string $method, string $path, ?string $body, int $expected): bool {
648
+        $path = $this->cleanPath($path);
649
+        try {
650
+            $response = $this->client->request($method, $this->encodePath($path), $body);
651
+            return $response['statusCode'] == $expected;
652
+        } catch (ClientHttpException $e) {
653
+            if ($e->getHttpStatus() === 404 && $method === 'DELETE') {
654
+                $this->statCache->clear($path . '/');
655
+                $this->statCache->set($path, false);
656
+                return false;
657
+            }
658
+
659
+            $this->convertException($e, $path);
660
+        } catch (\Exception $e) {
661
+            $this->convertException($e, $path);
662
+        }
663
+        return false;
664
+    }
665
+
666
+    /**
667
+     * check if curl is installed
668
+     */
669
+    public static function checkDependencies(): bool {
670
+        return true;
671
+    }
672
+
673
+    public function isUpdatable(string $path): bool {
674
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE);
675
+    }
676
+
677
+    public function isCreatable(string $path): bool {
678
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE);
679
+    }
680
+
681
+    public function isSharable(string $path): bool {
682
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE);
683
+    }
684
+
685
+    public function isDeletable(string $path): bool {
686
+        return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE);
687
+    }
688
+
689
+    public function getPermissions(string $path): int {
690
+        $stat = $this->getMetaData($path);
691
+        return $stat ? $stat['permissions'] : 0;
692
+    }
693
+
694
+    public function getETag(string $path): string|false {
695
+        $meta = $this->getMetaData($path);
696
+        return $meta ? $meta['etag'] : false;
697
+    }
698
+
699
+    protected function parsePermissions(string $permissionsString): int {
700
+        $permissions = Constants::PERMISSION_READ;
701
+        if (str_contains($permissionsString, 'R')) {
702
+            $permissions |= Constants::PERMISSION_SHARE;
703
+        }
704
+        if (str_contains($permissionsString, 'D')) {
705
+            $permissions |= Constants::PERMISSION_DELETE;
706
+        }
707
+        if (str_contains($permissionsString, 'W')) {
708
+            $permissions |= Constants::PERMISSION_UPDATE;
709
+        }
710
+        if (str_contains($permissionsString, 'CK')) {
711
+            $permissions |= Constants::PERMISSION_CREATE;
712
+            $permissions |= Constants::PERMISSION_UPDATE;
713
+        }
714
+        return $permissions;
715
+    }
716
+
717
+    public function hasUpdated(string $path, int $time): bool {
718
+        $this->init();
719
+        $path = $this->cleanPath($path);
720
+        try {
721
+            // force refresh for $path
722
+            $this->statCache->remove($path);
723
+            $response = $this->propfind($path);
724
+            if ($response === false) {
725
+                if ($path === '') {
726
+                    // if root is gone it means the storage is not available
727
+                    throw new StorageNotAvailableException('root is gone');
728
+                }
729
+                return false;
730
+            }
731
+            if (isset($response['{DAV:}getetag'])) {
732
+                $cachedData = $this->getCache()->get($path);
733
+                $etag = trim($response['{DAV:}getetag'], '"');
734
+                if (($cachedData === false) || (!empty($etag) && ($cachedData['etag'] !== $etag))) {
735
+                    return true;
736
+                } elseif (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) {
737
+                    $sharePermissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions'];
738
+                    return $sharePermissions !== $cachedData['permissions'];
739
+                } elseif (isset($response['{http://owncloud.org/ns}permissions'])) {
740
+                    $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']);
741
+                    return $permissions !== $cachedData['permissions'];
742
+                } else {
743
+                    return false;
744
+                }
745
+            } elseif (isset($response['{DAV:}getlastmodified'])) {
746
+                $remoteMtime = strtotime($response['{DAV:}getlastmodified']);
747
+                return $remoteMtime > $time;
748
+            } else {
749
+                // neither `getetag` nor `getlastmodified` is set
750
+                return false;
751
+            }
752
+        } catch (ClientHttpException $e) {
753
+            if ($e->getHttpStatus() === 405) {
754
+                if ($path === '') {
755
+                    // if root is gone it means the storage is not available
756
+                    throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
757
+                }
758
+                return false;
759
+            }
760
+            $this->convertException($e, $path);
761
+            return false;
762
+        } catch (\Exception $e) {
763
+            $this->convertException($e, $path);
764
+            return false;
765
+        }
766
+    }
767
+
768
+    /**
769
+     * Interpret the given exception and decide whether it is due to an
770
+     * unavailable storage, invalid storage or other.
771
+     * This will either throw StorageInvalidException, StorageNotAvailableException
772
+     * or do nothing.
773
+     *
774
+     * @param Exception $e sabre exception
775
+     * @param string $path optional path from the operation
776
+     *
777
+     * @throws StorageInvalidException if the storage is invalid, for example
778
+     *                                 when the authentication expired or is invalid
779
+     * @throws StorageNotAvailableException if the storage is not available,
780
+     *                                      which might be temporary
781
+     * @throws ForbiddenException if the action is not allowed
782
+     */
783
+    protected function convertException(Exception $e, string $path = ''): void {
784
+        $this->logger->debug($e->getMessage(), ['app' => 'files_external', 'exception' => $e]);
785
+        if ($e instanceof ClientHttpException) {
786
+            if ($e->getHttpStatus() === Http::STATUS_LOCKED) {
787
+                throw new \OCP\Lock\LockedException($path);
788
+            }
789
+            if ($e->getHttpStatus() === Http::STATUS_UNAUTHORIZED) {
790
+                // either password was changed or was invalid all along
791
+                throw new StorageInvalidException(get_class($e) . ': ' . $e->getMessage());
792
+            } elseif ($e->getHttpStatus() === Http::STATUS_METHOD_NOT_ALLOWED) {
793
+                // ignore exception for MethodNotAllowed, false will be returned
794
+                return;
795
+            } elseif ($e->getHttpStatus() === Http::STATUS_FORBIDDEN) {
796
+                // The operation is forbidden. Fail somewhat gracefully
797
+                throw new ForbiddenException(get_class($e) . ':' . $e->getMessage(), false);
798
+            }
799
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
800
+        } elseif ($e instanceof ClientException) {
801
+            // connection timeout or refused, server could be temporarily down
802
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
803
+        } elseif ($e instanceof \InvalidArgumentException) {
804
+            // parse error because the server returned HTML instead of XML,
805
+            // possibly temporarily down
806
+            throw new StorageNotAvailableException(get_class($e) . ': ' . $e->getMessage());
807
+        } elseif (($e instanceof StorageNotAvailableException) || ($e instanceof StorageInvalidException)) {
808
+            // rethrow
809
+            throw $e;
810
+        }
811
+
812
+        // TODO: only log for now, but in the future need to wrap/rethrow exception
813
+    }
814
+
815
+    public function getDirectoryContent(string $directory): \Traversable {
816
+        $this->init();
817
+        $directory = $this->cleanPath($directory);
818
+        try {
819
+            $responses = $this->client->propFind(
820
+                $this->encodePath($directory),
821
+                self::PROPFIND_PROPS,
822
+                1
823
+            );
824
+
825
+            array_shift($responses); //the first entry is the current directory
826
+            if (!$this->statCache->hasKey($directory)) {
827
+                $this->statCache->set($directory, true);
828
+            }
829
+
830
+            foreach ($responses as $file => $response) {
831
+                $file = rawurldecode($file);
832
+                $file = substr($file, strlen($this->root));
833
+                $file = $this->cleanPath($file);
834
+                $this->statCache->set($file, $response);
835
+                yield $this->getMetaFromPropfind($file, $response);
836
+            }
837
+        } catch (\Exception $e) {
838
+            $this->convertException($e, $directory);
839
+        }
840
+    }
841 841
 }
Please login to merge, or discard this patch.
lib/private/Updater/VersionCheck.php 1 patch
Indentation   +124 added lines, -124 removed lines patch added patch discarded remove patch
@@ -17,128 +17,128 @@
 block discarded – undo
17 17
 use Psr\Log\LoggerInterface;
18 18
 
19 19
 class VersionCheck {
20
-	public function __construct(
21
-		private ServerVersion $serverVersion,
22
-		private IClientService $clientService,
23
-		private IConfig $config,
24
-		private IAppConfig $appConfig,
25
-		private IUserManager $userManager,
26
-		private IRegistry $registry,
27
-		private LoggerInterface $logger,
28
-	) {
29
-	}
30
-
31
-
32
-	/**
33
-	 * Check if a new version is available
34
-	 *
35
-	 * @return array|bool
36
-	 */
37
-	public function check() {
38
-		// If this server is set to have no internet connection this is all not needed
39
-		if (!$this->config->getSystemValueBool('has_internet_connection', true)) {
40
-			return false;
41
-		}
42
-
43
-		// Look up the cache - it is invalidated all 30 minutes
44
-		if (($this->appConfig->getValueInt('core', 'lastupdatedat') + 1800) > time()) {
45
-			return json_decode($this->config->getAppValue('core', 'lastupdateResult'), true);
46
-		}
47
-
48
-		$updaterUrl = $this->config->getSystemValueString('updater.server.url', 'https://updates.nextcloud.com/updater_server/');
49
-
50
-		$this->appConfig->setValueInt('core', 'lastupdatedat', time());
51
-
52
-		if ($this->config->getAppValue('core', 'installedat', '') === '') {
53
-			$this->config->setAppValue('core', 'installedat', (string)microtime(true));
54
-		}
55
-
56
-		$version = Util::getVersion();
57
-		$version['installed'] = $this->config->getAppValue('core', 'installedat');
58
-		$version['updated'] = $this->appConfig->getValueInt('core', 'lastupdatedat');
59
-		$version['updatechannel'] = $this->serverVersion->getChannel();
60
-		$version['edition'] = '';
61
-		$version['build'] = $this->serverVersion->getBuild();
62
-		$version['php_major'] = PHP_MAJOR_VERSION;
63
-		$version['php_minor'] = PHP_MINOR_VERSION;
64
-		$version['php_release'] = PHP_RELEASE_VERSION;
65
-		$version['category'] = $this->computeCategory();
66
-		$version['isSubscriber'] = (int)$this->registry->delegateHasValidSubscription();
67
-		$versionString = implode('x', $version);
68
-
69
-		//fetch xml data from updater
70
-		$url = $updaterUrl . '?version=' . $versionString;
71
-
72
-		$tmp = [];
73
-		try {
74
-			$xml = $this->getUrlContent($url);
75
-		} catch (\Exception $e) {
76
-			$this->logger->info('Version could not be fetched from updater server: ' . $url, ['exception' => $e]);
77
-
78
-			return false;
79
-		}
80
-
81
-		if ($xml) {
82
-			if (\LIBXML_VERSION < 20900) {
83
-				$loadEntities = libxml_disable_entity_loader(true);
84
-				$data = @simplexml_load_string($xml);
85
-				libxml_disable_entity_loader($loadEntities);
86
-			} else {
87
-				$data = @simplexml_load_string($xml);
88
-			}
89
-			if ($data !== false) {
90
-				$tmp['version'] = (string)$data->version;
91
-				$tmp['versionstring'] = (string)$data->versionstring;
92
-				$tmp['url'] = (string)$data->url;
93
-				$tmp['web'] = (string)$data->web;
94
-				$tmp['changes'] = isset($data->changes) ? (string)$data->changes : '';
95
-				$tmp['autoupdater'] = (string)$data->autoupdater;
96
-				$tmp['eol'] = isset($data->eol) ? (string)$data->eol : '0';
97
-			} else {
98
-				libxml_clear_errors();
99
-			}
100
-		}
101
-
102
-		// Cache the result
103
-		$this->config->setAppValue('core', 'lastupdateResult', json_encode($tmp));
104
-		return $tmp;
105
-	}
106
-
107
-	/**
108
-	 * @throws \Exception
109
-	 */
110
-	protected function getUrlContent(string $url): string {
111
-		$response = $this->clientService->newClient()->get($url, [
112
-			'timeout' => 5,
113
-		]);
114
-
115
-		$content = $response->getBody();
116
-
117
-		// IResponse.getBody responds with null|resource if returning a stream response was requested.
118
-		// As that's not the case here, we can just ignore the psalm warning by adding an assertion.
119
-		assert(is_string($content));
120
-
121
-		return $content;
122
-	}
123
-
124
-	private function computeCategory(): int {
125
-		$categoryBoundaries = [
126
-			100,
127
-			500,
128
-			1000,
129
-			5000,
130
-			10000,
131
-			100000,
132
-			1000000,
133
-		];
134
-
135
-		$nbUsers = $this->userManager->countSeenUsers();
136
-		foreach ($categoryBoundaries as $categoryId => $boundary) {
137
-			if ($nbUsers <= $boundary) {
138
-				return $categoryId;
139
-			}
140
-		}
141
-
142
-		return count($categoryBoundaries);
143
-	}
20
+    public function __construct(
21
+        private ServerVersion $serverVersion,
22
+        private IClientService $clientService,
23
+        private IConfig $config,
24
+        private IAppConfig $appConfig,
25
+        private IUserManager $userManager,
26
+        private IRegistry $registry,
27
+        private LoggerInterface $logger,
28
+    ) {
29
+    }
30
+
31
+
32
+    /**
33
+     * Check if a new version is available
34
+     *
35
+     * @return array|bool
36
+     */
37
+    public function check() {
38
+        // If this server is set to have no internet connection this is all not needed
39
+        if (!$this->config->getSystemValueBool('has_internet_connection', true)) {
40
+            return false;
41
+        }
42
+
43
+        // Look up the cache - it is invalidated all 30 minutes
44
+        if (($this->appConfig->getValueInt('core', 'lastupdatedat') + 1800) > time()) {
45
+            return json_decode($this->config->getAppValue('core', 'lastupdateResult'), true);
46
+        }
47
+
48
+        $updaterUrl = $this->config->getSystemValueString('updater.server.url', 'https://updates.nextcloud.com/updater_server/');
49
+
50
+        $this->appConfig->setValueInt('core', 'lastupdatedat', time());
51
+
52
+        if ($this->config->getAppValue('core', 'installedat', '') === '') {
53
+            $this->config->setAppValue('core', 'installedat', (string)microtime(true));
54
+        }
55
+
56
+        $version = Util::getVersion();
57
+        $version['installed'] = $this->config->getAppValue('core', 'installedat');
58
+        $version['updated'] = $this->appConfig->getValueInt('core', 'lastupdatedat');
59
+        $version['updatechannel'] = $this->serverVersion->getChannel();
60
+        $version['edition'] = '';
61
+        $version['build'] = $this->serverVersion->getBuild();
62
+        $version['php_major'] = PHP_MAJOR_VERSION;
63
+        $version['php_minor'] = PHP_MINOR_VERSION;
64
+        $version['php_release'] = PHP_RELEASE_VERSION;
65
+        $version['category'] = $this->computeCategory();
66
+        $version['isSubscriber'] = (int)$this->registry->delegateHasValidSubscription();
67
+        $versionString = implode('x', $version);
68
+
69
+        //fetch xml data from updater
70
+        $url = $updaterUrl . '?version=' . $versionString;
71
+
72
+        $tmp = [];
73
+        try {
74
+            $xml = $this->getUrlContent($url);
75
+        } catch (\Exception $e) {
76
+            $this->logger->info('Version could not be fetched from updater server: ' . $url, ['exception' => $e]);
77
+
78
+            return false;
79
+        }
80
+
81
+        if ($xml) {
82
+            if (\LIBXML_VERSION < 20900) {
83
+                $loadEntities = libxml_disable_entity_loader(true);
84
+                $data = @simplexml_load_string($xml);
85
+                libxml_disable_entity_loader($loadEntities);
86
+            } else {
87
+                $data = @simplexml_load_string($xml);
88
+            }
89
+            if ($data !== false) {
90
+                $tmp['version'] = (string)$data->version;
91
+                $tmp['versionstring'] = (string)$data->versionstring;
92
+                $tmp['url'] = (string)$data->url;
93
+                $tmp['web'] = (string)$data->web;
94
+                $tmp['changes'] = isset($data->changes) ? (string)$data->changes : '';
95
+                $tmp['autoupdater'] = (string)$data->autoupdater;
96
+                $tmp['eol'] = isset($data->eol) ? (string)$data->eol : '0';
97
+            } else {
98
+                libxml_clear_errors();
99
+            }
100
+        }
101
+
102
+        // Cache the result
103
+        $this->config->setAppValue('core', 'lastupdateResult', json_encode($tmp));
104
+        return $tmp;
105
+    }
106
+
107
+    /**
108
+     * @throws \Exception
109
+     */
110
+    protected function getUrlContent(string $url): string {
111
+        $response = $this->clientService->newClient()->get($url, [
112
+            'timeout' => 5,
113
+        ]);
114
+
115
+        $content = $response->getBody();
116
+
117
+        // IResponse.getBody responds with null|resource if returning a stream response was requested.
118
+        // As that's not the case here, we can just ignore the psalm warning by adding an assertion.
119
+        assert(is_string($content));
120
+
121
+        return $content;
122
+    }
123
+
124
+    private function computeCategory(): int {
125
+        $categoryBoundaries = [
126
+            100,
127
+            500,
128
+            1000,
129
+            5000,
130
+            10000,
131
+            100000,
132
+            1000000,
133
+        ];
134
+
135
+        $nbUsers = $this->userManager->countSeenUsers();
136
+        foreach ($categoryBoundaries as $categoryId => $boundary) {
137
+            if ($nbUsers <= $boundary) {
138
+                return $categoryId;
139
+            }
140
+        }
141
+
142
+        return count($categoryBoundaries);
143
+    }
144 144
 }
Please login to merge, or discard this patch.
lib/private/Remote/Instance.php 1 patch
Indentation   +107 added lines, -107 removed lines patch added patch discarded remove patch
@@ -14,124 +14,124 @@
 block discarded – undo
14 14
  * Provides some basic info about a remote Nextcloud instance
15 15
  */
16 16
 class Instance implements IInstance {
17
-	/** @var string */
18
-	private $url;
17
+    /** @var string */
18
+    private $url;
19 19
 
20
-	/** @var ICache */
21
-	private $cache;
20
+    /** @var ICache */
21
+    private $cache;
22 22
 
23
-	/** @var IClientService */
24
-	private $clientService;
23
+    /** @var IClientService */
24
+    private $clientService;
25 25
 
26
-	/** @var array|null */
27
-	private $status;
26
+    /** @var array|null */
27
+    private $status;
28 28
 
29
-	/**
30
-	 * @param string $url
31
-	 * @param ICache $cache
32
-	 * @param IClientService $clientService
33
-	 */
34
-	public function __construct($url, ICache $cache, IClientService $clientService) {
35
-		$url = str_replace('https://', '', $url);
36
-		$this->url = str_replace('http://', '', $url);
37
-		$this->cache = $cache;
38
-		$this->clientService = $clientService;
39
-	}
29
+    /**
30
+     * @param string $url
31
+     * @param ICache $cache
32
+     * @param IClientService $clientService
33
+     */
34
+    public function __construct($url, ICache $cache, IClientService $clientService) {
35
+        $url = str_replace('https://', '', $url);
36
+        $this->url = str_replace('http://', '', $url);
37
+        $this->cache = $cache;
38
+        $this->clientService = $clientService;
39
+    }
40 40
 
41
-	/**
42
-	 * @return string The url of the remote server without protocol
43
-	 */
44
-	public function getUrl() {
45
-		return $this->url;
46
-	}
41
+    /**
42
+     * @return string The url of the remote server without protocol
43
+     */
44
+    public function getUrl() {
45
+        return $this->url;
46
+    }
47 47
 
48
-	/**
49
-	 * @return string The of the remote server with protocol
50
-	 */
51
-	public function getFullUrl() {
52
-		return $this->getProtocol() . '://' . $this->getUrl();
53
-	}
48
+    /**
49
+     * @return string The of the remote server with protocol
50
+     */
51
+    public function getFullUrl() {
52
+        return $this->getProtocol() . '://' . $this->getUrl();
53
+    }
54 54
 
55
-	/**
56
-	 * @return string The full version string in '13.1.2.3' format
57
-	 */
58
-	public function getVersion() {
59
-		$status = $this->getStatus();
60
-		return $status['version'];
61
-	}
55
+    /**
56
+     * @return string The full version string in '13.1.2.3' format
57
+     */
58
+    public function getVersion() {
59
+        $status = $this->getStatus();
60
+        return $status['version'];
61
+    }
62 62
 
63
-	/**
64
-	 * @return string 'http' or 'https'
65
-	 */
66
-	public function getProtocol() {
67
-		$status = $this->getStatus();
68
-		return $status['protocol'];
69
-	}
63
+    /**
64
+     * @return string 'http' or 'https'
65
+     */
66
+    public function getProtocol() {
67
+        $status = $this->getStatus();
68
+        return $status['protocol'];
69
+    }
70 70
 
71
-	/**
72
-	 * Check that the remote server is installed and not in maintenance mode
73
-	 *
74
-	 * @return bool
75
-	 */
76
-	public function isActive() {
77
-		$status = $this->getStatus();
78
-		return $status['installed'] && !$status['maintenance'];
79
-	}
71
+    /**
72
+     * Check that the remote server is installed and not in maintenance mode
73
+     *
74
+     * @return bool
75
+     */
76
+    public function isActive() {
77
+        $status = $this->getStatus();
78
+        return $status['installed'] && !$status['maintenance'];
79
+    }
80 80
 
81
-	/**
82
-	 * @return array
83
-	 * @throws NotFoundException
84
-	 * @throws \Exception
85
-	 */
86
-	private function getStatus() {
87
-		if ($this->status) {
88
-			return $this->status;
89
-		}
90
-		$key = 'remote/' . $this->url . '/status';
91
-		$httpsKey = 'remote/' . $this->url . '/https';
92
-		$status = $this->cache->get($key);
93
-		if (!$status) {
94
-			$response = $this->downloadStatus('https://' . $this->getUrl() . '/status.php');
95
-			$protocol = 'https';
96
-			if (!$response) {
97
-				if ($status = $this->cache->get($httpsKey)) {
98
-					throw new \Exception('refusing to connect to remote instance(' . $this->url . ') over http that was previously accessible over https');
99
-				}
100
-				$response = $this->downloadStatus('http://' . $this->getUrl() . '/status.php');
101
-				$protocol = 'http';
102
-			} else {
103
-				$this->cache->set($httpsKey, true, 60 * 60 * 24 * 365);
104
-			}
105
-			$status = json_decode($response, true);
106
-			if ($status) {
107
-				$status['protocol'] = $protocol;
108
-			}
109
-			if ($status) {
110
-				$this->cache->set($key, $status, 5 * 60);
111
-				$this->status = $status;
112
-			} else {
113
-				throw new NotFoundException('Remote server not found at address ' . $this->url);
114
-			}
115
-		}
116
-		return $status;
117
-	}
81
+    /**
82
+     * @return array
83
+     * @throws NotFoundException
84
+     * @throws \Exception
85
+     */
86
+    private function getStatus() {
87
+        if ($this->status) {
88
+            return $this->status;
89
+        }
90
+        $key = 'remote/' . $this->url . '/status';
91
+        $httpsKey = 'remote/' . $this->url . '/https';
92
+        $status = $this->cache->get($key);
93
+        if (!$status) {
94
+            $response = $this->downloadStatus('https://' . $this->getUrl() . '/status.php');
95
+            $protocol = 'https';
96
+            if (!$response) {
97
+                if ($status = $this->cache->get($httpsKey)) {
98
+                    throw new \Exception('refusing to connect to remote instance(' . $this->url . ') over http that was previously accessible over https');
99
+                }
100
+                $response = $this->downloadStatus('http://' . $this->getUrl() . '/status.php');
101
+                $protocol = 'http';
102
+            } else {
103
+                $this->cache->set($httpsKey, true, 60 * 60 * 24 * 365);
104
+            }
105
+            $status = json_decode($response, true);
106
+            if ($status) {
107
+                $status['protocol'] = $protocol;
108
+            }
109
+            if ($status) {
110
+                $this->cache->set($key, $status, 5 * 60);
111
+                $this->status = $status;
112
+            } else {
113
+                throw new NotFoundException('Remote server not found at address ' . $this->url);
114
+            }
115
+        }
116
+        return $status;
117
+    }
118 118
 
119
-	/**
120
-	 * @param string $url
121
-	 * @return bool|string
122
-	 */
123
-	private function downloadStatus($url) {
124
-		try {
125
-			$request = $this->clientService->newClient()->get($url);
126
-			$content = $request->getBody();
119
+    /**
120
+     * @param string $url
121
+     * @return bool|string
122
+     */
123
+    private function downloadStatus($url) {
124
+        try {
125
+            $request = $this->clientService->newClient()->get($url);
126
+            $content = $request->getBody();
127 127
 
128
-			// IResponse.getBody responds with null|resource if returning a stream response was requested.
129
-			// As that's not the case here, we can just ignore the psalm warning by adding an assertion.
130
-			assert(is_string($content));
128
+            // IResponse.getBody responds with null|resource if returning a stream response was requested.
129
+            // As that's not the case here, we can just ignore the psalm warning by adding an assertion.
130
+            assert(is_string($content));
131 131
 
132
-			return $content;
133
-		} catch (\Exception $e) {
134
-			return false;
135
-		}
136
-	}
132
+            return $content;
133
+        } catch (\Exception $e) {
134
+            return false;
135
+        }
136
+    }
137 137
 }
Please login to merge, or discard this patch.