Completed
Push — master ( cd1c45...d14cf6 )
by Joas
26:38
created
lib/private/Http/Client/Client.php 2 patches
Indentation   +657 added lines, -657 removed lines patch added patch discarded remove patch
@@ -28,661 +28,661 @@
 block discarded – undo
28 28
  * @package OC\Http
29 29
  */
30 30
 class Client implements IClient {
31
-	/** @var GuzzleClient */
32
-	private $client;
33
-	/** @var IConfig */
34
-	private $config;
35
-	/** @var ICertificateManager */
36
-	private $certificateManager;
37
-	private IRemoteHostValidator $remoteHostValidator;
38
-
39
-	public function __construct(
40
-		IConfig $config,
41
-		ICertificateManager $certificateManager,
42
-		GuzzleClient $client,
43
-		IRemoteHostValidator $remoteHostValidator,
44
-		protected LoggerInterface $logger,
45
-		protected ServerVersion $serverVersion,
46
-	) {
47
-		$this->config = $config;
48
-		$this->client = $client;
49
-		$this->certificateManager = $certificateManager;
50
-		$this->remoteHostValidator = $remoteHostValidator;
51
-	}
52
-
53
-	private function buildRequestOptions(array $options): array {
54
-		$proxy = $this->getProxyUri();
55
-
56
-		$defaults = [
57
-			RequestOptions::VERIFY => $this->getCertBundle(),
58
-			RequestOptions::TIMEOUT => IClient::DEFAULT_REQUEST_TIMEOUT,
59
-		];
60
-
61
-		$options['nextcloud']['allow_local_address'] = $this->isLocalAddressAllowed($options);
62
-		if ($options['nextcloud']['allow_local_address'] === false) {
63
-			$onRedirectFunction = function (
64
-				\Psr\Http\Message\RequestInterface $request,
65
-				\Psr\Http\Message\ResponseInterface $response,
66
-				\Psr\Http\Message\UriInterface $uri,
67
-			) use ($options) {
68
-				$this->preventLocalAddress($uri->__toString(), $options);
69
-			};
70
-
71
-			$defaults[RequestOptions::ALLOW_REDIRECTS] = [
72
-				'on_redirect' => $onRedirectFunction
73
-			];
74
-		}
75
-
76
-		// Only add RequestOptions::PROXY if Nextcloud is explicitly
77
-		// configured to use a proxy. This is needed in order not to override
78
-		// Guzzle default values.
79
-		if ($proxy !== null) {
80
-			$defaults[RequestOptions::PROXY] = $proxy;
81
-		}
82
-
83
-		$options = array_merge($defaults, $options);
84
-
85
-		if (!isset($options[RequestOptions::HEADERS]['User-Agent'])) {
86
-			$userAgent = 'Nextcloud-Server-Crawler/' . $this->serverVersion->getVersionString();
87
-			$options[RequestOptions::HEADERS]['User-Agent'] = $userAgent;
88
-		}
89
-
90
-		if (!isset($options[RequestOptions::HEADERS]['Accept-Encoding'])) {
91
-			$options[RequestOptions::HEADERS]['Accept-Encoding'] = 'gzip';
92
-		}
93
-
94
-		// Fallback for save_to
95
-		if (isset($options['save_to'])) {
96
-			$options['sink'] = $options['save_to'];
97
-			unset($options['save_to']);
98
-		}
99
-
100
-		return $options;
101
-	}
102
-
103
-	private function getCertBundle(): string {
104
-		// If the instance is not yet setup we need to use the static path as
105
-		// $this->certificateManager->getAbsoluteBundlePath() tries to instantiate
106
-		// a view
107
-		if (!$this->config->getSystemValueBool('installed', false)) {
108
-			return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt';
109
-		}
110
-
111
-		return $this->certificateManager->getAbsoluteBundlePath();
112
-	}
113
-
114
-	/**
115
-	 * Returns a null or an associative array specifying the proxy URI for
116
-	 * 'http' and 'https' schemes, in addition to a 'no' key value pair
117
-	 * providing a list of host names that should not be proxied to.
118
-	 *
119
-	 * @return array|null
120
-	 *
121
-	 * The return array looks like:
122
-	 * [
123
-	 *   'http' => 'username:[email protected]',
124
-	 *   'https' => 'username:[email protected]',
125
-	 *   'no' => ['foo.com', 'bar.com']
126
-	 * ]
127
-	 *
128
-	 */
129
-	private function getProxyUri(): ?array {
130
-		$proxyHost = $this->config->getSystemValueString('proxy', '');
131
-
132
-		if ($proxyHost === '') {
133
-			return null;
134
-		}
135
-
136
-		$proxyUserPwd = $this->config->getSystemValueString('proxyuserpwd', '');
137
-		if ($proxyUserPwd !== '') {
138
-			$proxyHost = $proxyUserPwd . '@' . $proxyHost;
139
-		}
140
-
141
-		$proxy = [
142
-			'http' => $proxyHost,
143
-			'https' => $proxyHost,
144
-		];
145
-
146
-		$proxyExclude = $this->config->getSystemValue('proxyexclude', []);
147
-		if ($proxyExclude !== [] && $proxyExclude !== null) {
148
-			$proxy['no'] = $proxyExclude;
149
-		}
150
-
151
-		return $proxy;
152
-	}
153
-
154
-	private function isLocalAddressAllowed(array $options) : bool {
155
-		if (($options['nextcloud']['allow_local_address'] ?? false)
156
-			|| $this->config->getSystemValueBool('allow_local_remote_servers', false)) {
157
-			return true;
158
-		}
159
-
160
-		return false;
161
-	}
162
-
163
-	protected function preventLocalAddress(string $uri, array $options): void {
164
-		$host = parse_url($uri, PHP_URL_HOST);
165
-		if ($host === false || $host === null) {
166
-			throw new LocalServerException('Could not detect any host');
167
-		}
168
-
169
-		if ($this->isLocalAddressAllowed($options)) {
170
-			return;
171
-		}
172
-
173
-		if (!$this->remoteHostValidator->isValid($host)) {
174
-			throw new LocalServerException('Host "' . $host . '" violates local access rules');
175
-		}
176
-	}
177
-
178
-	/**
179
-	 * Sends a GET request
180
-	 *
181
-	 * @param string $uri
182
-	 * @param array $options Array such as
183
-	 *                       'query' => [
184
-	 *                       'field' => 'abc',
185
-	 *                       'other_field' => '123',
186
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
187
-	 *                       ],
188
-	 *                       'headers' => [
189
-	 *                       'foo' => 'bar',
190
-	 *                       ],
191
-	 *                       'cookies' => [
192
-	 *                       'foo' => 'bar',
193
-	 *                       ],
194
-	 *                       'allow_redirects' => [
195
-	 *                       'max'       => 10,  // allow at most 10 redirects.
196
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
197
-	 *                       'referer'   => true,     // add a Referer header
198
-	 *                       'protocols' => ['https'] // only allow https URLs
199
-	 *                       ],
200
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
201
-	 *                       'verify' => true, // bool or string to CA file
202
-	 *                       'debug' => true,
203
-	 *                       'timeout' => 5,
204
-	 * @return IResponse
205
-	 * @throws \Exception If the request could not get completed
206
-	 */
207
-	public function get(string $uri, array $options = []): IResponse {
208
-		$this->preventLocalAddress($uri, $options);
209
-		$response = $this->client->request('get', $uri, $this->buildRequestOptions($options));
210
-		$isStream = isset($options['stream']) && $options['stream'];
211
-		return new Response($response, $isStream);
212
-	}
213
-
214
-	/**
215
-	 * Sends a HEAD request
216
-	 *
217
-	 * @param string $uri
218
-	 * @param array $options Array such as
219
-	 *                       'headers' => [
220
-	 *                       'foo' => 'bar',
221
-	 *                       ],
222
-	 *                       'cookies' => [
223
-	 *                       'foo' => 'bar',
224
-	 *                       ],
225
-	 *                       'allow_redirects' => [
226
-	 *                       'max'       => 10,  // allow at most 10 redirects.
227
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
228
-	 *                       'referer'   => true,     // add a Referer header
229
-	 *                       'protocols' => ['https'] // only allow https URLs
230
-	 *                       ],
231
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
232
-	 *                       'verify' => true, // bool or string to CA file
233
-	 *                       'debug' => true,
234
-	 *                       'timeout' => 5,
235
-	 * @return IResponse
236
-	 * @throws \Exception If the request could not get completed
237
-	 */
238
-	public function head(string $uri, array $options = []): IResponse {
239
-		$this->preventLocalAddress($uri, $options);
240
-		$response = $this->client->request('head', $uri, $this->buildRequestOptions($options));
241
-		return new Response($response);
242
-	}
243
-
244
-	/**
245
-	 * Sends a POST request
246
-	 *
247
-	 * @param string $uri
248
-	 * @param array $options Array such as
249
-	 *                       'body' => [
250
-	 *                       'field' => 'abc',
251
-	 *                       'other_field' => '123',
252
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
253
-	 *                       ],
254
-	 *                       'headers' => [
255
-	 *                       'foo' => 'bar',
256
-	 *                       ],
257
-	 *                       'cookies' => [
258
-	 *                       'foo' => 'bar',
259
-	 *                       ],
260
-	 *                       'allow_redirects' => [
261
-	 *                       'max'       => 10,  // allow at most 10 redirects.
262
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
263
-	 *                       'referer'   => true,     // add a Referer header
264
-	 *                       'protocols' => ['https'] // only allow https URLs
265
-	 *                       ],
266
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
267
-	 *                       'verify' => true, // bool or string to CA file
268
-	 *                       'debug' => true,
269
-	 *                       'timeout' => 5,
270
-	 * @return IResponse
271
-	 * @throws \Exception If the request could not get completed
272
-	 */
273
-	public function post(string $uri, array $options = []): IResponse {
274
-		$this->preventLocalAddress($uri, $options);
275
-
276
-		if (isset($options['body']) && is_array($options['body'])) {
277
-			$options['form_params'] = $options['body'];
278
-			unset($options['body']);
279
-		}
280
-		$response = $this->client->request('post', $uri, $this->buildRequestOptions($options));
281
-		$isStream = isset($options['stream']) && $options['stream'];
282
-		return new Response($response, $isStream);
283
-	}
284
-
285
-	/**
286
-	 * Sends a PUT request
287
-	 *
288
-	 * @param string $uri
289
-	 * @param array $options Array such as
290
-	 *                       'body' => [
291
-	 *                       'field' => 'abc',
292
-	 *                       'other_field' => '123',
293
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
294
-	 *                       ],
295
-	 *                       'headers' => [
296
-	 *                       'foo' => 'bar',
297
-	 *                       ],
298
-	 *                       'cookies' => [
299
-	 *                       'foo' => 'bar',
300
-	 *                       ],
301
-	 *                       'allow_redirects' => [
302
-	 *                       'max'       => 10,  // allow at most 10 redirects.
303
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
304
-	 *                       'referer'   => true,     // add a Referer header
305
-	 *                       'protocols' => ['https'] // only allow https URLs
306
-	 *                       ],
307
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
308
-	 *                       'verify' => true, // bool or string to CA file
309
-	 *                       'debug' => true,
310
-	 *                       'timeout' => 5,
311
-	 * @return IResponse
312
-	 * @throws \Exception If the request could not get completed
313
-	 */
314
-	public function put(string $uri, array $options = []): IResponse {
315
-		$this->preventLocalAddress($uri, $options);
316
-		$response = $this->client->request('put', $uri, $this->buildRequestOptions($options));
317
-		return new Response($response);
318
-	}
319
-
320
-	/**
321
-	 * Sends a PATCH request
322
-	 *
323
-	 * @param string $uri
324
-	 * @param array $options Array such as
325
-	 *                       'body' => [
326
-	 *                       'field' => 'abc',
327
-	 *                       'other_field' => '123',
328
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
329
-	 *                       ],
330
-	 *                       'headers' => [
331
-	 *                       'foo' => 'bar',
332
-	 *                       ],
333
-	 *                       'cookies' => [
334
-	 *                       'foo' => 'bar',
335
-	 *                       ],
336
-	 *                       'allow_redirects' => [
337
-	 *                       'max'       => 10,  // allow at most 10 redirects.
338
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
339
-	 *                       'referer'   => true,     // add a Referer header
340
-	 *                       'protocols' => ['https'] // only allow https URLs
341
-	 *                       ],
342
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
343
-	 *                       'verify' => true, // bool or string to CA file
344
-	 *                       'debug' => true,
345
-	 *                       'timeout' => 5,
346
-	 * @return IResponse
347
-	 * @throws \Exception If the request could not get completed
348
-	 */
349
-	public function patch(string $uri, array $options = []): IResponse {
350
-		$this->preventLocalAddress($uri, $options);
351
-		$response = $this->client->request('patch', $uri, $this->buildRequestOptions($options));
352
-		return new Response($response);
353
-	}
354
-
355
-	/**
356
-	 * Sends a DELETE request
357
-	 *
358
-	 * @param string $uri
359
-	 * @param array $options Array such as
360
-	 *                       'body' => [
361
-	 *                       'field' => 'abc',
362
-	 *                       'other_field' => '123',
363
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
364
-	 *                       ],
365
-	 *                       'headers' => [
366
-	 *                       'foo' => 'bar',
367
-	 *                       ],
368
-	 *                       'cookies' => [
369
-	 *                       'foo' => 'bar',
370
-	 *                       ],
371
-	 *                       'allow_redirects' => [
372
-	 *                       'max'       => 10,  // allow at most 10 redirects.
373
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
374
-	 *                       'referer'   => true,     // add a Referer header
375
-	 *                       'protocols' => ['https'] // only allow https URLs
376
-	 *                       ],
377
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
378
-	 *                       'verify' => true, // bool or string to CA file
379
-	 *                       'debug' => true,
380
-	 *                       'timeout' => 5,
381
-	 * @return IResponse
382
-	 * @throws \Exception If the request could not get completed
383
-	 */
384
-	public function delete(string $uri, array $options = []): IResponse {
385
-		$this->preventLocalAddress($uri, $options);
386
-		$response = $this->client->request('delete', $uri, $this->buildRequestOptions($options));
387
-		return new Response($response);
388
-	}
389
-
390
-	/**
391
-	 * Sends an OPTIONS request
392
-	 *
393
-	 * @param string $uri
394
-	 * @param array $options Array such as
395
-	 *                       'body' => [
396
-	 *                       'field' => 'abc',
397
-	 *                       'other_field' => '123',
398
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
399
-	 *                       ],
400
-	 *                       'headers' => [
401
-	 *                       'foo' => 'bar',
402
-	 *                       ],
403
-	 *                       'cookies' => [
404
-	 *                       'foo' => 'bar',
405
-	 *                       ],
406
-	 *                       'allow_redirects' => [
407
-	 *                       'max'       => 10,  // allow at most 10 redirects.
408
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
409
-	 *                       'referer'   => true,     // add a Referer header
410
-	 *                       'protocols' => ['https'] // only allow https URLs
411
-	 *                       ],
412
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
413
-	 *                       'verify' => true, // bool or string to CA file
414
-	 *                       'debug' => true,
415
-	 *                       'timeout' => 5,
416
-	 * @return IResponse
417
-	 * @throws \Exception If the request could not get completed
418
-	 */
419
-	public function options(string $uri, array $options = []): IResponse {
420
-		$this->preventLocalAddress($uri, $options);
421
-		$response = $this->client->request('options', $uri, $this->buildRequestOptions($options));
422
-		return new Response($response);
423
-	}
424
-
425
-	/**
426
-	 * Get the response of a Throwable thrown by the request methods when possible
427
-	 *
428
-	 * @param \Throwable $e
429
-	 * @return IResponse
430
-	 * @throws \Throwable When $e did not have a response
431
-	 * @since 29.0.0
432
-	 */
433
-	public function getResponseFromThrowable(\Throwable $e): IResponse {
434
-		if (method_exists($e, 'hasResponse') && method_exists($e, 'getResponse') && $e->hasResponse()) {
435
-			return new Response($e->getResponse());
436
-		}
437
-
438
-		throw $e;
439
-	}
440
-
441
-	/**
442
-	 * Sends a HTTP request
443
-	 *
444
-	 * @param string $method The HTTP method to use
445
-	 * @param string $uri
446
-	 * @param array $options Array such as
447
-	 *                       'query' => [
448
-	 *                       'field' => 'abc',
449
-	 *                       'other_field' => '123',
450
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
451
-	 *                       ],
452
-	 *                       'headers' => [
453
-	 *                       'foo' => 'bar',
454
-	 *                       ],
455
-	 *                       'cookies' => [
456
-	 *                       'foo' => 'bar',
457
-	 *                       ],
458
-	 *                       'allow_redirects' => [
459
-	 *                       'max'       => 10,  // allow at most 10 redirects.
460
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
461
-	 *                       'referer'   => true,     // add a Referer header
462
-	 *                       'protocols' => ['https'] // only allow https URLs
463
-	 *                       ],
464
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
465
-	 *                       'verify' => true, // bool or string to CA file
466
-	 *                       'debug' => true,
467
-	 *                       'timeout' => 5,
468
-	 * @return IResponse
469
-	 * @throws \Exception If the request could not get completed
470
-	 */
471
-	public function request(string $method, string $uri, array $options = []): IResponse {
472
-		$this->preventLocalAddress($uri, $options);
473
-		$response = $this->client->request($method, $uri, $this->buildRequestOptions($options));
474
-		$isStream = isset($options['stream']) && $options['stream'];
475
-		return new Response($response, $isStream);
476
-	}
477
-
478
-	protected function wrapGuzzlePromise(PromiseInterface $promise): IPromise {
479
-		return new GuzzlePromiseAdapter(
480
-			$promise,
481
-			$this->logger
482
-		);
483
-	}
484
-
485
-	/**
486
-	 * Sends an asynchronous GET request
487
-	 *
488
-	 * @param string $uri
489
-	 * @param array $options Array such as
490
-	 *                       'query' => [
491
-	 *                       'field' => 'abc',
492
-	 *                       'other_field' => '123',
493
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
494
-	 *                       ],
495
-	 *                       'headers' => [
496
-	 *                       'foo' => 'bar',
497
-	 *                       ],
498
-	 *                       'cookies' => [
499
-	 *                       'foo' => 'bar',
500
-	 *                       ],
501
-	 *                       'allow_redirects' => [
502
-	 *                       'max'       => 10,  // allow at most 10 redirects.
503
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
504
-	 *                       'referer'   => true,     // add a Referer header
505
-	 *                       'protocols' => ['https'] // only allow https URLs
506
-	 *                       ],
507
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
508
-	 *                       'verify' => true, // bool or string to CA file
509
-	 *                       'debug' => true,
510
-	 *                       'timeout' => 5,
511
-	 * @return IPromise
512
-	 */
513
-	public function getAsync(string $uri, array $options = []): IPromise {
514
-		$this->preventLocalAddress($uri, $options);
515
-		$response = $this->client->requestAsync('get', $uri, $this->buildRequestOptions($options));
516
-		return $this->wrapGuzzlePromise($response);
517
-	}
518
-
519
-	/**
520
-	 * Sends an asynchronous HEAD request
521
-	 *
522
-	 * @param string $uri
523
-	 * @param array $options Array such as
524
-	 *                       'headers' => [
525
-	 *                       'foo' => 'bar',
526
-	 *                       ],
527
-	 *                       'cookies' => [
528
-	 *                       'foo' => 'bar',
529
-	 *                       ],
530
-	 *                       'allow_redirects' => [
531
-	 *                       'max'       => 10,  // allow at most 10 redirects.
532
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
533
-	 *                       'referer'   => true,     // add a Referer header
534
-	 *                       'protocols' => ['https'] // only allow https URLs
535
-	 *                       ],
536
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
537
-	 *                       'verify' => true, // bool or string to CA file
538
-	 *                       'debug' => true,
539
-	 *                       'timeout' => 5,
540
-	 * @return IPromise
541
-	 */
542
-	public function headAsync(string $uri, array $options = []): IPromise {
543
-		$this->preventLocalAddress($uri, $options);
544
-		$response = $this->client->requestAsync('head', $uri, $this->buildRequestOptions($options));
545
-		return $this->wrapGuzzlePromise($response);
546
-	}
547
-
548
-	/**
549
-	 * Sends an asynchronous POST request
550
-	 *
551
-	 * @param string $uri
552
-	 * @param array $options Array such as
553
-	 *                       'body' => [
554
-	 *                       'field' => 'abc',
555
-	 *                       'other_field' => '123',
556
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
557
-	 *                       ],
558
-	 *                       'headers' => [
559
-	 *                       'foo' => 'bar',
560
-	 *                       ],
561
-	 *                       'cookies' => [
562
-	 *                       'foo' => 'bar',
563
-	 *                       ],
564
-	 *                       'allow_redirects' => [
565
-	 *                       'max'       => 10,  // allow at most 10 redirects.
566
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
567
-	 *                       'referer'   => true,     // add a Referer header
568
-	 *                       'protocols' => ['https'] // only allow https URLs
569
-	 *                       ],
570
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
571
-	 *                       'verify' => true, // bool or string to CA file
572
-	 *                       'debug' => true,
573
-	 *                       'timeout' => 5,
574
-	 * @return IPromise
575
-	 */
576
-	public function postAsync(string $uri, array $options = []): IPromise {
577
-		$this->preventLocalAddress($uri, $options);
578
-
579
-		if (isset($options['body']) && is_array($options['body'])) {
580
-			$options['form_params'] = $options['body'];
581
-			unset($options['body']);
582
-		}
583
-
584
-		return $this->wrapGuzzlePromise($this->client->requestAsync('post', $uri, $this->buildRequestOptions($options)));
585
-	}
586
-
587
-	/**
588
-	 * Sends an asynchronous PUT request
589
-	 *
590
-	 * @param string $uri
591
-	 * @param array $options Array such as
592
-	 *                       'body' => [
593
-	 *                       'field' => 'abc',
594
-	 *                       'other_field' => '123',
595
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
596
-	 *                       ],
597
-	 *                       'headers' => [
598
-	 *                       'foo' => 'bar',
599
-	 *                       ],
600
-	 *                       'cookies' => [
601
-	 *                       'foo' => 'bar',
602
-	 *                       ],
603
-	 *                       'allow_redirects' => [
604
-	 *                       'max'       => 10,  // allow at most 10 redirects.
605
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
606
-	 *                       'referer'   => true,     // add a Referer header
607
-	 *                       'protocols' => ['https'] // only allow https URLs
608
-	 *                       ],
609
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
610
-	 *                       'verify' => true, // bool or string to CA file
611
-	 *                       'debug' => true,
612
-	 *                       'timeout' => 5,
613
-	 * @return IPromise
614
-	 */
615
-	public function putAsync(string $uri, array $options = []): IPromise {
616
-		$this->preventLocalAddress($uri, $options);
617
-		$response = $this->client->requestAsync('put', $uri, $this->buildRequestOptions($options));
618
-		return $this->wrapGuzzlePromise($response);
619
-	}
620
-
621
-	/**
622
-	 * Sends an asynchronous DELETE request
623
-	 *
624
-	 * @param string $uri
625
-	 * @param array $options Array such as
626
-	 *                       'body' => [
627
-	 *                       'field' => 'abc',
628
-	 *                       'other_field' => '123',
629
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
630
-	 *                       ],
631
-	 *                       'headers' => [
632
-	 *                       'foo' => 'bar',
633
-	 *                       ],
634
-	 *                       'cookies' => [
635
-	 *                       'foo' => 'bar',
636
-	 *                       ],
637
-	 *                       'allow_redirects' => [
638
-	 *                       'max'       => 10,  // allow at most 10 redirects.
639
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
640
-	 *                       'referer'   => true,     // add a Referer header
641
-	 *                       'protocols' => ['https'] // only allow https URLs
642
-	 *                       ],
643
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
644
-	 *                       'verify' => true, // bool or string to CA file
645
-	 *                       'debug' => true,
646
-	 *                       'timeout' => 5,
647
-	 * @return IPromise
648
-	 */
649
-	public function deleteAsync(string $uri, array $options = []): IPromise {
650
-		$this->preventLocalAddress($uri, $options);
651
-		$response = $this->client->requestAsync('delete', $uri, $this->buildRequestOptions($options));
652
-		return $this->wrapGuzzlePromise($response);
653
-	}
654
-
655
-	/**
656
-	 * Sends an asynchronous OPTIONS request
657
-	 *
658
-	 * @param string $uri
659
-	 * @param array $options Array such as
660
-	 *                       'body' => [
661
-	 *                       'field' => 'abc',
662
-	 *                       'other_field' => '123',
663
-	 *                       'file_name' => fopen('/path/to/file', 'r'),
664
-	 *                       ],
665
-	 *                       'headers' => [
666
-	 *                       'foo' => 'bar',
667
-	 *                       ],
668
-	 *                       'cookies' => [
669
-	 *                       'foo' => 'bar',
670
-	 *                       ],
671
-	 *                       'allow_redirects' => [
672
-	 *                       'max'       => 10,  // allow at most 10 redirects.
673
-	 *                       'strict'    => true,     // use "strict" RFC compliant redirects.
674
-	 *                       'referer'   => true,     // add a Referer header
675
-	 *                       'protocols' => ['https'] // only allow https URLs
676
-	 *                       ],
677
-	 *                       'sink' => '/path/to/file', // save to a file or a stream
678
-	 *                       'verify' => true, // bool or string to CA file
679
-	 *                       'debug' => true,
680
-	 *                       'timeout' => 5,
681
-	 * @return IPromise
682
-	 */
683
-	public function optionsAsync(string $uri, array $options = []): IPromise {
684
-		$this->preventLocalAddress($uri, $options);
685
-		$response = $this->client->requestAsync('options', $uri, $this->buildRequestOptions($options));
686
-		return $this->wrapGuzzlePromise($response);
687
-	}
31
+    /** @var GuzzleClient */
32
+    private $client;
33
+    /** @var IConfig */
34
+    private $config;
35
+    /** @var ICertificateManager */
36
+    private $certificateManager;
37
+    private IRemoteHostValidator $remoteHostValidator;
38
+
39
+    public function __construct(
40
+        IConfig $config,
41
+        ICertificateManager $certificateManager,
42
+        GuzzleClient $client,
43
+        IRemoteHostValidator $remoteHostValidator,
44
+        protected LoggerInterface $logger,
45
+        protected ServerVersion $serverVersion,
46
+    ) {
47
+        $this->config = $config;
48
+        $this->client = $client;
49
+        $this->certificateManager = $certificateManager;
50
+        $this->remoteHostValidator = $remoteHostValidator;
51
+    }
52
+
53
+    private function buildRequestOptions(array $options): array {
54
+        $proxy = $this->getProxyUri();
55
+
56
+        $defaults = [
57
+            RequestOptions::VERIFY => $this->getCertBundle(),
58
+            RequestOptions::TIMEOUT => IClient::DEFAULT_REQUEST_TIMEOUT,
59
+        ];
60
+
61
+        $options['nextcloud']['allow_local_address'] = $this->isLocalAddressAllowed($options);
62
+        if ($options['nextcloud']['allow_local_address'] === false) {
63
+            $onRedirectFunction = function (
64
+                \Psr\Http\Message\RequestInterface $request,
65
+                \Psr\Http\Message\ResponseInterface $response,
66
+                \Psr\Http\Message\UriInterface $uri,
67
+            ) use ($options) {
68
+                $this->preventLocalAddress($uri->__toString(), $options);
69
+            };
70
+
71
+            $defaults[RequestOptions::ALLOW_REDIRECTS] = [
72
+                'on_redirect' => $onRedirectFunction
73
+            ];
74
+        }
75
+
76
+        // Only add RequestOptions::PROXY if Nextcloud is explicitly
77
+        // configured to use a proxy. This is needed in order not to override
78
+        // Guzzle default values.
79
+        if ($proxy !== null) {
80
+            $defaults[RequestOptions::PROXY] = $proxy;
81
+        }
82
+
83
+        $options = array_merge($defaults, $options);
84
+
85
+        if (!isset($options[RequestOptions::HEADERS]['User-Agent'])) {
86
+            $userAgent = 'Nextcloud-Server-Crawler/' . $this->serverVersion->getVersionString();
87
+            $options[RequestOptions::HEADERS]['User-Agent'] = $userAgent;
88
+        }
89
+
90
+        if (!isset($options[RequestOptions::HEADERS]['Accept-Encoding'])) {
91
+            $options[RequestOptions::HEADERS]['Accept-Encoding'] = 'gzip';
92
+        }
93
+
94
+        // Fallback for save_to
95
+        if (isset($options['save_to'])) {
96
+            $options['sink'] = $options['save_to'];
97
+            unset($options['save_to']);
98
+        }
99
+
100
+        return $options;
101
+    }
102
+
103
+    private function getCertBundle(): string {
104
+        // If the instance is not yet setup we need to use the static path as
105
+        // $this->certificateManager->getAbsoluteBundlePath() tries to instantiate
106
+        // a view
107
+        if (!$this->config->getSystemValueBool('installed', false)) {
108
+            return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt';
109
+        }
110
+
111
+        return $this->certificateManager->getAbsoluteBundlePath();
112
+    }
113
+
114
+    /**
115
+     * Returns a null or an associative array specifying the proxy URI for
116
+     * 'http' and 'https' schemes, in addition to a 'no' key value pair
117
+     * providing a list of host names that should not be proxied to.
118
+     *
119
+     * @return array|null
120
+     *
121
+     * The return array looks like:
122
+     * [
123
+     *   'http' => 'username:[email protected]',
124
+     *   'https' => 'username:[email protected]',
125
+     *   'no' => ['foo.com', 'bar.com']
126
+     * ]
127
+     *
128
+     */
129
+    private function getProxyUri(): ?array {
130
+        $proxyHost = $this->config->getSystemValueString('proxy', '');
131
+
132
+        if ($proxyHost === '') {
133
+            return null;
134
+        }
135
+
136
+        $proxyUserPwd = $this->config->getSystemValueString('proxyuserpwd', '');
137
+        if ($proxyUserPwd !== '') {
138
+            $proxyHost = $proxyUserPwd . '@' . $proxyHost;
139
+        }
140
+
141
+        $proxy = [
142
+            'http' => $proxyHost,
143
+            'https' => $proxyHost,
144
+        ];
145
+
146
+        $proxyExclude = $this->config->getSystemValue('proxyexclude', []);
147
+        if ($proxyExclude !== [] && $proxyExclude !== null) {
148
+            $proxy['no'] = $proxyExclude;
149
+        }
150
+
151
+        return $proxy;
152
+    }
153
+
154
+    private function isLocalAddressAllowed(array $options) : bool {
155
+        if (($options['nextcloud']['allow_local_address'] ?? false)
156
+            || $this->config->getSystemValueBool('allow_local_remote_servers', false)) {
157
+            return true;
158
+        }
159
+
160
+        return false;
161
+    }
162
+
163
+    protected function preventLocalAddress(string $uri, array $options): void {
164
+        $host = parse_url($uri, PHP_URL_HOST);
165
+        if ($host === false || $host === null) {
166
+            throw new LocalServerException('Could not detect any host');
167
+        }
168
+
169
+        if ($this->isLocalAddressAllowed($options)) {
170
+            return;
171
+        }
172
+
173
+        if (!$this->remoteHostValidator->isValid($host)) {
174
+            throw new LocalServerException('Host "' . $host . '" violates local access rules');
175
+        }
176
+    }
177
+
178
+    /**
179
+     * Sends a GET request
180
+     *
181
+     * @param string $uri
182
+     * @param array $options Array such as
183
+     *                       'query' => [
184
+     *                       'field' => 'abc',
185
+     *                       'other_field' => '123',
186
+     *                       'file_name' => fopen('/path/to/file', 'r'),
187
+     *                       ],
188
+     *                       'headers' => [
189
+     *                       'foo' => 'bar',
190
+     *                       ],
191
+     *                       'cookies' => [
192
+     *                       'foo' => 'bar',
193
+     *                       ],
194
+     *                       'allow_redirects' => [
195
+     *                       'max'       => 10,  // allow at most 10 redirects.
196
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
197
+     *                       'referer'   => true,     // add a Referer header
198
+     *                       'protocols' => ['https'] // only allow https URLs
199
+     *                       ],
200
+     *                       'sink' => '/path/to/file', // save to a file or a stream
201
+     *                       'verify' => true, // bool or string to CA file
202
+     *                       'debug' => true,
203
+     *                       'timeout' => 5,
204
+     * @return IResponse
205
+     * @throws \Exception If the request could not get completed
206
+     */
207
+    public function get(string $uri, array $options = []): IResponse {
208
+        $this->preventLocalAddress($uri, $options);
209
+        $response = $this->client->request('get', $uri, $this->buildRequestOptions($options));
210
+        $isStream = isset($options['stream']) && $options['stream'];
211
+        return new Response($response, $isStream);
212
+    }
213
+
214
+    /**
215
+     * Sends a HEAD request
216
+     *
217
+     * @param string $uri
218
+     * @param array $options Array such as
219
+     *                       'headers' => [
220
+     *                       'foo' => 'bar',
221
+     *                       ],
222
+     *                       'cookies' => [
223
+     *                       'foo' => 'bar',
224
+     *                       ],
225
+     *                       'allow_redirects' => [
226
+     *                       'max'       => 10,  // allow at most 10 redirects.
227
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
228
+     *                       'referer'   => true,     // add a Referer header
229
+     *                       'protocols' => ['https'] // only allow https URLs
230
+     *                       ],
231
+     *                       'sink' => '/path/to/file', // save to a file or a stream
232
+     *                       'verify' => true, // bool or string to CA file
233
+     *                       'debug' => true,
234
+     *                       'timeout' => 5,
235
+     * @return IResponse
236
+     * @throws \Exception If the request could not get completed
237
+     */
238
+    public function head(string $uri, array $options = []): IResponse {
239
+        $this->preventLocalAddress($uri, $options);
240
+        $response = $this->client->request('head', $uri, $this->buildRequestOptions($options));
241
+        return new Response($response);
242
+    }
243
+
244
+    /**
245
+     * Sends a POST request
246
+     *
247
+     * @param string $uri
248
+     * @param array $options Array such as
249
+     *                       'body' => [
250
+     *                       'field' => 'abc',
251
+     *                       'other_field' => '123',
252
+     *                       'file_name' => fopen('/path/to/file', 'r'),
253
+     *                       ],
254
+     *                       'headers' => [
255
+     *                       'foo' => 'bar',
256
+     *                       ],
257
+     *                       'cookies' => [
258
+     *                       'foo' => 'bar',
259
+     *                       ],
260
+     *                       'allow_redirects' => [
261
+     *                       'max'       => 10,  // allow at most 10 redirects.
262
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
263
+     *                       'referer'   => true,     // add a Referer header
264
+     *                       'protocols' => ['https'] // only allow https URLs
265
+     *                       ],
266
+     *                       'sink' => '/path/to/file', // save to a file or a stream
267
+     *                       'verify' => true, // bool or string to CA file
268
+     *                       'debug' => true,
269
+     *                       'timeout' => 5,
270
+     * @return IResponse
271
+     * @throws \Exception If the request could not get completed
272
+     */
273
+    public function post(string $uri, array $options = []): IResponse {
274
+        $this->preventLocalAddress($uri, $options);
275
+
276
+        if (isset($options['body']) && is_array($options['body'])) {
277
+            $options['form_params'] = $options['body'];
278
+            unset($options['body']);
279
+        }
280
+        $response = $this->client->request('post', $uri, $this->buildRequestOptions($options));
281
+        $isStream = isset($options['stream']) && $options['stream'];
282
+        return new Response($response, $isStream);
283
+    }
284
+
285
+    /**
286
+     * Sends a PUT request
287
+     *
288
+     * @param string $uri
289
+     * @param array $options Array such as
290
+     *                       'body' => [
291
+     *                       'field' => 'abc',
292
+     *                       'other_field' => '123',
293
+     *                       'file_name' => fopen('/path/to/file', 'r'),
294
+     *                       ],
295
+     *                       'headers' => [
296
+     *                       'foo' => 'bar',
297
+     *                       ],
298
+     *                       'cookies' => [
299
+     *                       'foo' => 'bar',
300
+     *                       ],
301
+     *                       'allow_redirects' => [
302
+     *                       'max'       => 10,  // allow at most 10 redirects.
303
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
304
+     *                       'referer'   => true,     // add a Referer header
305
+     *                       'protocols' => ['https'] // only allow https URLs
306
+     *                       ],
307
+     *                       'sink' => '/path/to/file', // save to a file or a stream
308
+     *                       'verify' => true, // bool or string to CA file
309
+     *                       'debug' => true,
310
+     *                       'timeout' => 5,
311
+     * @return IResponse
312
+     * @throws \Exception If the request could not get completed
313
+     */
314
+    public function put(string $uri, array $options = []): IResponse {
315
+        $this->preventLocalAddress($uri, $options);
316
+        $response = $this->client->request('put', $uri, $this->buildRequestOptions($options));
317
+        return new Response($response);
318
+    }
319
+
320
+    /**
321
+     * Sends a PATCH request
322
+     *
323
+     * @param string $uri
324
+     * @param array $options Array such as
325
+     *                       'body' => [
326
+     *                       'field' => 'abc',
327
+     *                       'other_field' => '123',
328
+     *                       'file_name' => fopen('/path/to/file', 'r'),
329
+     *                       ],
330
+     *                       'headers' => [
331
+     *                       'foo' => 'bar',
332
+     *                       ],
333
+     *                       'cookies' => [
334
+     *                       'foo' => 'bar',
335
+     *                       ],
336
+     *                       'allow_redirects' => [
337
+     *                       'max'       => 10,  // allow at most 10 redirects.
338
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
339
+     *                       'referer'   => true,     // add a Referer header
340
+     *                       'protocols' => ['https'] // only allow https URLs
341
+     *                       ],
342
+     *                       'sink' => '/path/to/file', // save to a file or a stream
343
+     *                       'verify' => true, // bool or string to CA file
344
+     *                       'debug' => true,
345
+     *                       'timeout' => 5,
346
+     * @return IResponse
347
+     * @throws \Exception If the request could not get completed
348
+     */
349
+    public function patch(string $uri, array $options = []): IResponse {
350
+        $this->preventLocalAddress($uri, $options);
351
+        $response = $this->client->request('patch', $uri, $this->buildRequestOptions($options));
352
+        return new Response($response);
353
+    }
354
+
355
+    /**
356
+     * Sends a DELETE request
357
+     *
358
+     * @param string $uri
359
+     * @param array $options Array such as
360
+     *                       'body' => [
361
+     *                       'field' => 'abc',
362
+     *                       'other_field' => '123',
363
+     *                       'file_name' => fopen('/path/to/file', 'r'),
364
+     *                       ],
365
+     *                       'headers' => [
366
+     *                       'foo' => 'bar',
367
+     *                       ],
368
+     *                       'cookies' => [
369
+     *                       'foo' => 'bar',
370
+     *                       ],
371
+     *                       'allow_redirects' => [
372
+     *                       'max'       => 10,  // allow at most 10 redirects.
373
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
374
+     *                       'referer'   => true,     // add a Referer header
375
+     *                       'protocols' => ['https'] // only allow https URLs
376
+     *                       ],
377
+     *                       'sink' => '/path/to/file', // save to a file or a stream
378
+     *                       'verify' => true, // bool or string to CA file
379
+     *                       'debug' => true,
380
+     *                       'timeout' => 5,
381
+     * @return IResponse
382
+     * @throws \Exception If the request could not get completed
383
+     */
384
+    public function delete(string $uri, array $options = []): IResponse {
385
+        $this->preventLocalAddress($uri, $options);
386
+        $response = $this->client->request('delete', $uri, $this->buildRequestOptions($options));
387
+        return new Response($response);
388
+    }
389
+
390
+    /**
391
+     * Sends an OPTIONS request
392
+     *
393
+     * @param string $uri
394
+     * @param array $options Array such as
395
+     *                       'body' => [
396
+     *                       'field' => 'abc',
397
+     *                       'other_field' => '123',
398
+     *                       'file_name' => fopen('/path/to/file', 'r'),
399
+     *                       ],
400
+     *                       'headers' => [
401
+     *                       'foo' => 'bar',
402
+     *                       ],
403
+     *                       'cookies' => [
404
+     *                       'foo' => 'bar',
405
+     *                       ],
406
+     *                       'allow_redirects' => [
407
+     *                       'max'       => 10,  // allow at most 10 redirects.
408
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
409
+     *                       'referer'   => true,     // add a Referer header
410
+     *                       'protocols' => ['https'] // only allow https URLs
411
+     *                       ],
412
+     *                       'sink' => '/path/to/file', // save to a file or a stream
413
+     *                       'verify' => true, // bool or string to CA file
414
+     *                       'debug' => true,
415
+     *                       'timeout' => 5,
416
+     * @return IResponse
417
+     * @throws \Exception If the request could not get completed
418
+     */
419
+    public function options(string $uri, array $options = []): IResponse {
420
+        $this->preventLocalAddress($uri, $options);
421
+        $response = $this->client->request('options', $uri, $this->buildRequestOptions($options));
422
+        return new Response($response);
423
+    }
424
+
425
+    /**
426
+     * Get the response of a Throwable thrown by the request methods when possible
427
+     *
428
+     * @param \Throwable $e
429
+     * @return IResponse
430
+     * @throws \Throwable When $e did not have a response
431
+     * @since 29.0.0
432
+     */
433
+    public function getResponseFromThrowable(\Throwable $e): IResponse {
434
+        if (method_exists($e, 'hasResponse') && method_exists($e, 'getResponse') && $e->hasResponse()) {
435
+            return new Response($e->getResponse());
436
+        }
437
+
438
+        throw $e;
439
+    }
440
+
441
+    /**
442
+     * Sends a HTTP request
443
+     *
444
+     * @param string $method The HTTP method to use
445
+     * @param string $uri
446
+     * @param array $options Array such as
447
+     *                       'query' => [
448
+     *                       'field' => 'abc',
449
+     *                       'other_field' => '123',
450
+     *                       'file_name' => fopen('/path/to/file', 'r'),
451
+     *                       ],
452
+     *                       'headers' => [
453
+     *                       'foo' => 'bar',
454
+     *                       ],
455
+     *                       'cookies' => [
456
+     *                       'foo' => 'bar',
457
+     *                       ],
458
+     *                       'allow_redirects' => [
459
+     *                       'max'       => 10,  // allow at most 10 redirects.
460
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
461
+     *                       'referer'   => true,     // add a Referer header
462
+     *                       'protocols' => ['https'] // only allow https URLs
463
+     *                       ],
464
+     *                       'sink' => '/path/to/file', // save to a file or a stream
465
+     *                       'verify' => true, // bool or string to CA file
466
+     *                       'debug' => true,
467
+     *                       'timeout' => 5,
468
+     * @return IResponse
469
+     * @throws \Exception If the request could not get completed
470
+     */
471
+    public function request(string $method, string $uri, array $options = []): IResponse {
472
+        $this->preventLocalAddress($uri, $options);
473
+        $response = $this->client->request($method, $uri, $this->buildRequestOptions($options));
474
+        $isStream = isset($options['stream']) && $options['stream'];
475
+        return new Response($response, $isStream);
476
+    }
477
+
478
+    protected function wrapGuzzlePromise(PromiseInterface $promise): IPromise {
479
+        return new GuzzlePromiseAdapter(
480
+            $promise,
481
+            $this->logger
482
+        );
483
+    }
484
+
485
+    /**
486
+     * Sends an asynchronous GET request
487
+     *
488
+     * @param string $uri
489
+     * @param array $options Array such as
490
+     *                       'query' => [
491
+     *                       'field' => 'abc',
492
+     *                       'other_field' => '123',
493
+     *                       'file_name' => fopen('/path/to/file', 'r'),
494
+     *                       ],
495
+     *                       'headers' => [
496
+     *                       'foo' => 'bar',
497
+     *                       ],
498
+     *                       'cookies' => [
499
+     *                       'foo' => 'bar',
500
+     *                       ],
501
+     *                       'allow_redirects' => [
502
+     *                       'max'       => 10,  // allow at most 10 redirects.
503
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
504
+     *                       'referer'   => true,     // add a Referer header
505
+     *                       'protocols' => ['https'] // only allow https URLs
506
+     *                       ],
507
+     *                       'sink' => '/path/to/file', // save to a file or a stream
508
+     *                       'verify' => true, // bool or string to CA file
509
+     *                       'debug' => true,
510
+     *                       'timeout' => 5,
511
+     * @return IPromise
512
+     */
513
+    public function getAsync(string $uri, array $options = []): IPromise {
514
+        $this->preventLocalAddress($uri, $options);
515
+        $response = $this->client->requestAsync('get', $uri, $this->buildRequestOptions($options));
516
+        return $this->wrapGuzzlePromise($response);
517
+    }
518
+
519
+    /**
520
+     * Sends an asynchronous HEAD request
521
+     *
522
+     * @param string $uri
523
+     * @param array $options Array such as
524
+     *                       'headers' => [
525
+     *                       'foo' => 'bar',
526
+     *                       ],
527
+     *                       'cookies' => [
528
+     *                       'foo' => 'bar',
529
+     *                       ],
530
+     *                       'allow_redirects' => [
531
+     *                       'max'       => 10,  // allow at most 10 redirects.
532
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
533
+     *                       'referer'   => true,     // add a Referer header
534
+     *                       'protocols' => ['https'] // only allow https URLs
535
+     *                       ],
536
+     *                       'sink' => '/path/to/file', // save to a file or a stream
537
+     *                       'verify' => true, // bool or string to CA file
538
+     *                       'debug' => true,
539
+     *                       'timeout' => 5,
540
+     * @return IPromise
541
+     */
542
+    public function headAsync(string $uri, array $options = []): IPromise {
543
+        $this->preventLocalAddress($uri, $options);
544
+        $response = $this->client->requestAsync('head', $uri, $this->buildRequestOptions($options));
545
+        return $this->wrapGuzzlePromise($response);
546
+    }
547
+
548
+    /**
549
+     * Sends an asynchronous POST request
550
+     *
551
+     * @param string $uri
552
+     * @param array $options Array such as
553
+     *                       'body' => [
554
+     *                       'field' => 'abc',
555
+     *                       'other_field' => '123',
556
+     *                       'file_name' => fopen('/path/to/file', 'r'),
557
+     *                       ],
558
+     *                       'headers' => [
559
+     *                       'foo' => 'bar',
560
+     *                       ],
561
+     *                       'cookies' => [
562
+     *                       'foo' => 'bar',
563
+     *                       ],
564
+     *                       'allow_redirects' => [
565
+     *                       'max'       => 10,  // allow at most 10 redirects.
566
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
567
+     *                       'referer'   => true,     // add a Referer header
568
+     *                       'protocols' => ['https'] // only allow https URLs
569
+     *                       ],
570
+     *                       'sink' => '/path/to/file', // save to a file or a stream
571
+     *                       'verify' => true, // bool or string to CA file
572
+     *                       'debug' => true,
573
+     *                       'timeout' => 5,
574
+     * @return IPromise
575
+     */
576
+    public function postAsync(string $uri, array $options = []): IPromise {
577
+        $this->preventLocalAddress($uri, $options);
578
+
579
+        if (isset($options['body']) && is_array($options['body'])) {
580
+            $options['form_params'] = $options['body'];
581
+            unset($options['body']);
582
+        }
583
+
584
+        return $this->wrapGuzzlePromise($this->client->requestAsync('post', $uri, $this->buildRequestOptions($options)));
585
+    }
586
+
587
+    /**
588
+     * Sends an asynchronous PUT request
589
+     *
590
+     * @param string $uri
591
+     * @param array $options Array such as
592
+     *                       'body' => [
593
+     *                       'field' => 'abc',
594
+     *                       'other_field' => '123',
595
+     *                       'file_name' => fopen('/path/to/file', 'r'),
596
+     *                       ],
597
+     *                       'headers' => [
598
+     *                       'foo' => 'bar',
599
+     *                       ],
600
+     *                       'cookies' => [
601
+     *                       'foo' => 'bar',
602
+     *                       ],
603
+     *                       'allow_redirects' => [
604
+     *                       'max'       => 10,  // allow at most 10 redirects.
605
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
606
+     *                       'referer'   => true,     // add a Referer header
607
+     *                       'protocols' => ['https'] // only allow https URLs
608
+     *                       ],
609
+     *                       'sink' => '/path/to/file', // save to a file or a stream
610
+     *                       'verify' => true, // bool or string to CA file
611
+     *                       'debug' => true,
612
+     *                       'timeout' => 5,
613
+     * @return IPromise
614
+     */
615
+    public function putAsync(string $uri, array $options = []): IPromise {
616
+        $this->preventLocalAddress($uri, $options);
617
+        $response = $this->client->requestAsync('put', $uri, $this->buildRequestOptions($options));
618
+        return $this->wrapGuzzlePromise($response);
619
+    }
620
+
621
+    /**
622
+     * Sends an asynchronous DELETE request
623
+     *
624
+     * @param string $uri
625
+     * @param array $options Array such as
626
+     *                       'body' => [
627
+     *                       'field' => 'abc',
628
+     *                       'other_field' => '123',
629
+     *                       'file_name' => fopen('/path/to/file', 'r'),
630
+     *                       ],
631
+     *                       'headers' => [
632
+     *                       'foo' => 'bar',
633
+     *                       ],
634
+     *                       'cookies' => [
635
+     *                       'foo' => 'bar',
636
+     *                       ],
637
+     *                       'allow_redirects' => [
638
+     *                       'max'       => 10,  // allow at most 10 redirects.
639
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
640
+     *                       'referer'   => true,     // add a Referer header
641
+     *                       'protocols' => ['https'] // only allow https URLs
642
+     *                       ],
643
+     *                       'sink' => '/path/to/file', // save to a file or a stream
644
+     *                       'verify' => true, // bool or string to CA file
645
+     *                       'debug' => true,
646
+     *                       'timeout' => 5,
647
+     * @return IPromise
648
+     */
649
+    public function deleteAsync(string $uri, array $options = []): IPromise {
650
+        $this->preventLocalAddress($uri, $options);
651
+        $response = $this->client->requestAsync('delete', $uri, $this->buildRequestOptions($options));
652
+        return $this->wrapGuzzlePromise($response);
653
+    }
654
+
655
+    /**
656
+     * Sends an asynchronous OPTIONS request
657
+     *
658
+     * @param string $uri
659
+     * @param array $options Array such as
660
+     *                       'body' => [
661
+     *                       'field' => 'abc',
662
+     *                       'other_field' => '123',
663
+     *                       'file_name' => fopen('/path/to/file', 'r'),
664
+     *                       ],
665
+     *                       'headers' => [
666
+     *                       'foo' => 'bar',
667
+     *                       ],
668
+     *                       'cookies' => [
669
+     *                       'foo' => 'bar',
670
+     *                       ],
671
+     *                       'allow_redirects' => [
672
+     *                       'max'       => 10,  // allow at most 10 redirects.
673
+     *                       'strict'    => true,     // use "strict" RFC compliant redirects.
674
+     *                       'referer'   => true,     // add a Referer header
675
+     *                       'protocols' => ['https'] // only allow https URLs
676
+     *                       ],
677
+     *                       'sink' => '/path/to/file', // save to a file or a stream
678
+     *                       'verify' => true, // bool or string to CA file
679
+     *                       'debug' => true,
680
+     *                       'timeout' => 5,
681
+     * @return IPromise
682
+     */
683
+    public function optionsAsync(string $uri, array $options = []): IPromise {
684
+        $this->preventLocalAddress($uri, $options);
685
+        $response = $this->client->requestAsync('options', $uri, $this->buildRequestOptions($options));
686
+        return $this->wrapGuzzlePromise($response);
687
+    }
688 688
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -60,7 +60,7 @@  discard block
 block discarded – undo
60 60
 
61 61
 		$options['nextcloud']['allow_local_address'] = $this->isLocalAddressAllowed($options);
62 62
 		if ($options['nextcloud']['allow_local_address'] === false) {
63
-			$onRedirectFunction = function (
63
+			$onRedirectFunction = function(
64 64
 				\Psr\Http\Message\RequestInterface $request,
65 65
 				\Psr\Http\Message\ResponseInterface $response,
66 66
 				\Psr\Http\Message\UriInterface $uri,
@@ -83,7 +83,7 @@  discard block
 block discarded – undo
83 83
 		$options = array_merge($defaults, $options);
84 84
 
85 85
 		if (!isset($options[RequestOptions::HEADERS]['User-Agent'])) {
86
-			$userAgent = 'Nextcloud-Server-Crawler/' . $this->serverVersion->getVersionString();
86
+			$userAgent = 'Nextcloud-Server-Crawler/'.$this->serverVersion->getVersionString();
87 87
 			$options[RequestOptions::HEADERS]['User-Agent'] = $userAgent;
88 88
 		}
89 89
 
@@ -105,7 +105,7 @@  discard block
 block discarded – undo
105 105
 		// $this->certificateManager->getAbsoluteBundlePath() tries to instantiate
106 106
 		// a view
107 107
 		if (!$this->config->getSystemValueBool('installed', false)) {
108
-			return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt';
108
+			return \OC::$SERVERROOT.'/resources/config/ca-bundle.crt';
109 109
 		}
110 110
 
111 111
 		return $this->certificateManager->getAbsoluteBundlePath();
@@ -135,7 +135,7 @@  discard block
 block discarded – undo
135 135
 
136 136
 		$proxyUserPwd = $this->config->getSystemValueString('proxyuserpwd', '');
137 137
 		if ($proxyUserPwd !== '') {
138
-			$proxyHost = $proxyUserPwd . '@' . $proxyHost;
138
+			$proxyHost = $proxyUserPwd.'@'.$proxyHost;
139 139
 		}
140 140
 
141 141
 		$proxy = [
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
 		}
172 172
 
173 173
 		if (!$this->remoteHostValidator->isValid($host)) {
174
-			throw new LocalServerException('Host "' . $host . '" violates local access rules');
174
+			throw new LocalServerException('Host "'.$host.'" violates local access rules');
175 175
 		}
176 176
 	}
177 177
 
Please login to merge, or discard this patch.
lib/private/Http/Client/ClientService.php 1 patch
Indentation   +47 added lines, -47 removed lines patch added patch discarded remove patch
@@ -28,55 +28,55 @@
 block discarded – undo
28 28
  * @package OC\Http
29 29
  */
30 30
 class ClientService implements IClientService {
31
-	/** @var IConfig */
32
-	private $config;
33
-	/** @var ICertificateManager */
34
-	private $certificateManager;
35
-	/** @var DnsPinMiddleware */
36
-	private $dnsPinMiddleware;
37
-	private IRemoteHostValidator $remoteHostValidator;
38
-	private IEventLogger $eventLogger;
31
+    /** @var IConfig */
32
+    private $config;
33
+    /** @var ICertificateManager */
34
+    private $certificateManager;
35
+    /** @var DnsPinMiddleware */
36
+    private $dnsPinMiddleware;
37
+    private IRemoteHostValidator $remoteHostValidator;
38
+    private IEventLogger $eventLogger;
39 39
 
40
-	public function __construct(
41
-		IConfig $config,
42
-		ICertificateManager $certificateManager,
43
-		DnsPinMiddleware $dnsPinMiddleware,
44
-		IRemoteHostValidator $remoteHostValidator,
45
-		IEventLogger $eventLogger,
46
-		protected LoggerInterface $logger,
47
-		protected ServerVersion $serverVersion,
48
-	) {
49
-		$this->config = $config;
50
-		$this->certificateManager = $certificateManager;
51
-		$this->dnsPinMiddleware = $dnsPinMiddleware;
52
-		$this->remoteHostValidator = $remoteHostValidator;
53
-		$this->eventLogger = $eventLogger;
54
-	}
40
+    public function __construct(
41
+        IConfig $config,
42
+        ICertificateManager $certificateManager,
43
+        DnsPinMiddleware $dnsPinMiddleware,
44
+        IRemoteHostValidator $remoteHostValidator,
45
+        IEventLogger $eventLogger,
46
+        protected LoggerInterface $logger,
47
+        protected ServerVersion $serverVersion,
48
+    ) {
49
+        $this->config = $config;
50
+        $this->certificateManager = $certificateManager;
51
+        $this->dnsPinMiddleware = $dnsPinMiddleware;
52
+        $this->remoteHostValidator = $remoteHostValidator;
53
+        $this->eventLogger = $eventLogger;
54
+    }
55 55
 
56
-	/**
57
-	 * @return Client
58
-	 */
59
-	public function newClient(): IClient {
60
-		$handler = new CurlHandler();
61
-		$stack = HandlerStack::create($handler);
62
-		if ($this->config->getSystemValueBool('dns_pinning', true)) {
63
-			$stack->push($this->dnsPinMiddleware->addDnsPinning());
64
-		}
65
-		$stack->push(Middleware::tap(function (RequestInterface $request) {
66
-			$this->eventLogger->start('http:request', $request->getMethod() . ' request to ' . $request->getRequestTarget());
67
-		}, function () {
68
-			$this->eventLogger->end('http:request');
69
-		}), 'event logger');
56
+    /**
57
+     * @return Client
58
+     */
59
+    public function newClient(): IClient {
60
+        $handler = new CurlHandler();
61
+        $stack = HandlerStack::create($handler);
62
+        if ($this->config->getSystemValueBool('dns_pinning', true)) {
63
+            $stack->push($this->dnsPinMiddleware->addDnsPinning());
64
+        }
65
+        $stack->push(Middleware::tap(function (RequestInterface $request) {
66
+            $this->eventLogger->start('http:request', $request->getMethod() . ' request to ' . $request->getRequestTarget());
67
+        }, function () {
68
+            $this->eventLogger->end('http:request');
69
+        }), 'event logger');
70 70
 
71
-		$client = new GuzzleClient(['handler' => $stack]);
71
+        $client = new GuzzleClient(['handler' => $stack]);
72 72
 
73
-		return new Client(
74
-			$this->config,
75
-			$this->certificateManager,
76
-			$client,
77
-			$this->remoteHostValidator,
78
-			$this->logger,
79
-			$this->serverVersion,
80
-		);
81
-	}
73
+        return new Client(
74
+            $this->config,
75
+            $this->certificateManager,
76
+            $client,
77
+            $this->remoteHostValidator,
78
+            $this->logger,
79
+            $this->serverVersion,
80
+        );
81
+    }
82 82
 }
Please login to merge, or discard this patch.
tests/lib/Http/Client/ClientTest.php 2 patches
Indentation   +580 added lines, -580 removed lines patch added patch discarded remove patch
@@ -26,584 +26,584 @@
 block discarded – undo
26 26
  * Class ClientTest
27 27
  */
28 28
 class ClientTest extends \Test\TestCase {
29
-	/** @var \GuzzleHttp\Client|MockObject */
30
-	private $guzzleClient;
31
-	/** @var CertificateManager|MockObject */
32
-	private $certificateManager;
33
-	/** @var Client */
34
-	private $client;
35
-	/** @var IConfig|MockObject */
36
-	private $config;
37
-	/** @var IRemoteHostValidator|MockObject */
38
-	private IRemoteHostValidator $remoteHostValidator;
39
-	private LoggerInterface $logger;
40
-	private ServerVersion $serverVersion;
41
-	/** @var array */
42
-	private $defaultRequestOptions;
43
-
44
-	protected function setUp(): void {
45
-		parent::setUp();
46
-		$this->config = $this->createMock(IConfig::class);
47
-		$this->guzzleClient = $this->createMock(\GuzzleHttp\Client::class);
48
-		$this->certificateManager = $this->createMock(ICertificateManager::class);
49
-		$this->remoteHostValidator = $this->createMock(IRemoteHostValidator::class);
50
-		$this->logger = $this->createMock(LoggerInterface::class);
51
-		$this->serverVersion = $this->createMock(ServerVersion::class);
52
-
53
-		$this->client = new Client(
54
-			$this->config,
55
-			$this->certificateManager,
56
-			$this->guzzleClient,
57
-			$this->remoteHostValidator,
58
-			$this->logger,
59
-			$this->serverVersion,
60
-		);
61
-	}
62
-
63
-	public function testGetProxyUri(): void {
64
-		$this->config
65
-			->method('getSystemValueString')
66
-			->with('proxy', '')
67
-			->willReturn('');
68
-		$this->assertNull(self::invokePrivate($this->client, 'getProxyUri'));
69
-	}
70
-
71
-	public function testGetProxyUriProxyHostEmptyPassword(): void {
72
-		$this->config
73
-			->method('getSystemValue')
74
-			->willReturnMap([
75
-				['proxyexclude', [], []],
76
-			]);
77
-
78
-		$this->config
79
-			->method('getSystemValueString')
80
-			->willReturnMap([
81
-				['proxy', '', 'foo'],
82
-				['proxyuserpwd', '', ''],
83
-			]);
84
-
85
-		$this->assertEquals([
86
-			'http' => 'foo',
87
-			'https' => 'foo'
88
-		], self::invokePrivate($this->client, 'getProxyUri'));
89
-	}
90
-
91
-	public function testGetProxyUriProxyHostWithPassword(): void {
92
-		$this->config
93
-			->expects($this->once())
94
-			->method('getSystemValue')
95
-			->with('proxyexclude', [])
96
-			->willReturn([]);
97
-		$this->config
98
-			->expects($this->exactly(2))
99
-			->method('getSystemValueString')
100
-			->willReturnMap([
101
-				['proxy', '', 'foo'],
102
-				['proxyuserpwd', '', 'username:password'],
103
-			]);
104
-		$this->assertEquals([
105
-			'http' => 'username:password@foo',
106
-			'https' => 'username:password@foo'
107
-		], self::invokePrivate($this->client, 'getProxyUri'));
108
-	}
109
-
110
-	public function testGetProxyUriProxyHostWithPasswordAndExclude(): void {
111
-		$this->config
112
-			->expects($this->once())
113
-			->method('getSystemValue')
114
-			->with('proxyexclude', [])
115
-			->willReturn(['bar']);
116
-		$this->config
117
-			->expects($this->exactly(2))
118
-			->method('getSystemValueString')
119
-			->willReturnMap([
120
-				['proxy', '', 'foo'],
121
-				['proxyuserpwd', '', 'username:password'],
122
-			]);
123
-		$this->assertEquals([
124
-			'http' => 'username:password@foo',
125
-			'https' => 'username:password@foo',
126
-			'no' => ['bar']
127
-		], self::invokePrivate($this->client, 'getProxyUri'));
128
-	}
129
-
130
-	public function testPreventLocalAddressThrowOnInvalidUri(): void {
131
-		$this->expectException(LocalServerException::class);
132
-		$this->expectExceptionMessage('Could not detect any host');
133
-
134
-		self::invokePrivate($this->client, 'preventLocalAddress', ['!@#$', []]);
135
-	}
136
-
137
-	public static function dataPreventLocalAddress(): array {
138
-		return [
139
-			['https://localhost/foo.bar'],
140
-			['https://localHost/foo.bar'],
141
-			['https://random-host/foo.bar'],
142
-			['https://[::1]/bla.blub'],
143
-			['https://[::]/bla.blub'],
144
-			['https://192.168.0.1'],
145
-			['https://172.16.42.1'],
146
-			['https://[fdf8:f53b:82e4::53]/secret.ics'],
147
-			['https://[fe80::200:5aee:feaa:20a2]/secret.ics'],
148
-			['https://[0:0:0:0:0:0:10.0.0.1]/secret.ics'],
149
-			['https://[0:0:0:0:0:ffff:127.0.0.0]/secret.ics'],
150
-			['https://10.0.0.1'],
151
-			['https://another-host.local'],
152
-			['https://service.localhost'],
153
-			['https://normal.host.com'],
154
-			['https://com.one-.nextcloud-one.com'],
155
-		];
156
-	}
157
-
158
-	/**
159
-	 * @param string $uri
160
-	 */
161
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
162
-	public function testPreventLocalAddressDisabledByGlobalConfig(string $uri): void {
163
-		$this->config->expects($this->once())
164
-			->method('getSystemValueBool')
165
-			->with('allow_local_remote_servers', false)
166
-			->willReturn(true);
167
-
168
-		self::invokePrivate($this->client, 'preventLocalAddress', [$uri, []]);
169
-	}
170
-
171
-	/**
172
-	 * @param string $uri
173
-	 */
174
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
175
-	public function testPreventLocalAddressDisabledByOption(string $uri): void {
176
-		$this->config->expects($this->never())
177
-			->method('getSystemValueBool');
178
-
179
-		self::invokePrivate($this->client, 'preventLocalAddress', [$uri, [
180
-			'nextcloud' => ['allow_local_address' => true],
181
-		]]);
182
-	}
183
-
184
-	/**
185
-	 * @param string $uri
186
-	 */
187
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
188
-	public function testPreventLocalAddressOnGet(string $uri): void {
189
-		$host = parse_url($uri, PHP_URL_HOST);
190
-		$this->expectException(LocalServerException::class);
191
-		$this->remoteHostValidator
192
-			->method('isValid')
193
-			->with($host)
194
-			->willReturn(false);
195
-
196
-		$this->client->get($uri);
197
-	}
198
-
199
-	/**
200
-	 * @param string $uri
201
-	 */
202
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
203
-	public function testPreventLocalAddressOnHead(string $uri): void {
204
-		$host = parse_url($uri, PHP_URL_HOST);
205
-		$this->expectException(LocalServerException::class);
206
-		$this->remoteHostValidator
207
-			->method('isValid')
208
-			->with($host)
209
-			->willReturn(false);
210
-
211
-		$this->client->head($uri);
212
-	}
213
-
214
-	/**
215
-	 * @param string $uri
216
-	 */
217
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
218
-	public function testPreventLocalAddressOnPost(string $uri): void {
219
-		$host = parse_url($uri, PHP_URL_HOST);
220
-		$this->expectException(LocalServerException::class);
221
-		$this->remoteHostValidator
222
-			->method('isValid')
223
-			->with($host)
224
-			->willReturn(false);
225
-
226
-		$this->client->post($uri);
227
-	}
228
-
229
-	/**
230
-	 * @param string $uri
231
-	 */
232
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
233
-	public function testPreventLocalAddressOnPut(string $uri): void {
234
-		$host = parse_url($uri, PHP_URL_HOST);
235
-		$this->expectException(LocalServerException::class);
236
-		$this->remoteHostValidator
237
-			->method('isValid')
238
-			->with($host)
239
-			->willReturn(false);
240
-
241
-		$this->client->put($uri);
242
-	}
243
-
244
-	/**
245
-	 * @param string $uri
246
-	 */
247
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
248
-	public function testPreventLocalAddressOnDelete(string $uri): void {
249
-		$host = parse_url($uri, PHP_URL_HOST);
250
-		$this->expectException(LocalServerException::class);
251
-		$this->remoteHostValidator
252
-			->method('isValid')
253
-			->with($host)
254
-			->willReturn(false);
255
-
256
-		$this->client->delete($uri);
257
-	}
258
-
259
-	private function setUpDefaultRequestOptions(): void {
260
-		$this->config
261
-			->method('getSystemValue')
262
-			->willReturnMap([
263
-				['proxyexclude', [], []],
264
-			]);
265
-		$this->config
266
-			->method('getSystemValueString')
267
-			->willReturnMap([
268
-				['proxy', '', 'foo'],
269
-				['proxyuserpwd', '', ''],
270
-			]);
271
-		$this->config
272
-			->method('getSystemValueBool')
273
-			->willReturnMap([
274
-				['installed', false, true],
275
-				['allow_local_remote_servers', false, true],
276
-			]);
277
-
278
-		$this->certificateManager
279
-			->expects($this->once())
280
-			->method('getAbsoluteBundlePath')
281
-			->with()
282
-			->willReturn('/my/path.crt');
283
-
284
-		$this->serverVersion->method('getVersionString')
285
-			->willReturn('123.45.6');
286
-
287
-		$this->defaultRequestOptions = [
288
-			'verify' => '/my/path.crt',
289
-			'proxy' => [
290
-				'http' => 'foo',
291
-				'https' => 'foo'
292
-			],
293
-			'headers' => [
294
-				'User-Agent' => 'Nextcloud-Server-Crawler/123.45.6',
295
-				'Accept-Encoding' => 'gzip',
296
-			],
297
-			'timeout' => 30,
298
-			'nextcloud' => [
299
-				'allow_local_address' => true,
300
-			],
301
-		];
302
-	}
303
-
304
-	public function testGet(): void {
305
-		$this->setUpDefaultRequestOptions();
306
-
307
-		$this->guzzleClient->method('request')
308
-			->with('get', 'http://localhost/', $this->defaultRequestOptions)
309
-			->willReturn(new Response(418));
310
-		$this->assertEquals(418, $this->client->get('http://localhost/', [])->getStatusCode());
311
-	}
312
-
313
-	public function testGetWithOptions(): void {
314
-		$this->setUpDefaultRequestOptions();
315
-
316
-		$options = array_merge($this->defaultRequestOptions, [
317
-			'verify' => false,
318
-			'proxy' => [
319
-				'http' => 'bar',
320
-				'https' => 'bar'
321
-			],
322
-		]);
323
-
324
-		$this->guzzleClient->method('request')
325
-			->with('get', 'http://localhost/', $options)
326
-			->willReturn(new Response(418));
327
-		$this->assertEquals(418, $this->client->get('http://localhost/', $options)->getStatusCode());
328
-	}
329
-
330
-	public function testPost(): void {
331
-		$this->setUpDefaultRequestOptions();
332
-
333
-		$this->guzzleClient->method('request')
334
-			->with('post', 'http://localhost/', $this->defaultRequestOptions)
335
-			->willReturn(new Response(418));
336
-		$this->assertEquals(418, $this->client->post('http://localhost/', [])->getStatusCode());
337
-	}
338
-
339
-	public function testPostWithOptions(): void {
340
-		$this->setUpDefaultRequestOptions();
341
-
342
-		$options = array_merge($this->defaultRequestOptions, [
343
-			'verify' => false,
344
-			'proxy' => [
345
-				'http' => 'bar',
346
-				'https' => 'bar'
347
-			],
348
-		]);
349
-
350
-		$this->guzzleClient->method('request')
351
-			->with('post', 'http://localhost/', $options)
352
-			->willReturn(new Response(418));
353
-		$this->assertEquals(418, $this->client->post('http://localhost/', $options)->getStatusCode());
354
-	}
355
-
356
-	public function testPut(): void {
357
-		$this->setUpDefaultRequestOptions();
358
-
359
-		$this->guzzleClient->method('request')
360
-			->with('put', 'http://localhost/', $this->defaultRequestOptions)
361
-			->willReturn(new Response(418));
362
-		$this->assertEquals(418, $this->client->put('http://localhost/', [])->getStatusCode());
363
-	}
364
-
365
-	public function testPutWithOptions(): void {
366
-		$this->setUpDefaultRequestOptions();
367
-
368
-		$options = array_merge($this->defaultRequestOptions, [
369
-			'verify' => false,
370
-			'proxy' => [
371
-				'http' => 'bar',
372
-				'https' => 'bar'
373
-			],
374
-		]);
375
-
376
-		$this->guzzleClient->method('request')
377
-			->with('put', 'http://localhost/', $options)
378
-			->willReturn(new Response(418));
379
-		$this->assertEquals(418, $this->client->put('http://localhost/', $options)->getStatusCode());
380
-	}
381
-
382
-	public function testDelete(): void {
383
-		$this->setUpDefaultRequestOptions();
384
-
385
-		$this->guzzleClient->method('request')
386
-			->with('delete', 'http://localhost/', $this->defaultRequestOptions)
387
-			->willReturn(new Response(418));
388
-		$this->assertEquals(418, $this->client->delete('http://localhost/', [])->getStatusCode());
389
-	}
390
-
391
-	public function testDeleteWithOptions(): void {
392
-		$this->setUpDefaultRequestOptions();
393
-
394
-		$options = array_merge($this->defaultRequestOptions, [
395
-			'verify' => false,
396
-			'proxy' => [
397
-				'http' => 'bar',
398
-				'https' => 'bar'
399
-			],
400
-		]);
401
-
402
-		$this->guzzleClient->method('request')
403
-			->with('delete', 'http://localhost/', $options)
404
-			->willReturn(new Response(418));
405
-		$this->assertEquals(418, $this->client->delete('http://localhost/', $options)->getStatusCode());
406
-	}
407
-
408
-	public function testOptions(): void {
409
-		$this->setUpDefaultRequestOptions();
410
-
411
-		$this->guzzleClient->method('request')
412
-			->with('options', 'http://localhost/', $this->defaultRequestOptions)
413
-			->willReturn(new Response(418));
414
-		$this->assertEquals(418, $this->client->options('http://localhost/', [])->getStatusCode());
415
-	}
416
-
417
-	public function testOptionsWithOptions(): void {
418
-		$this->setUpDefaultRequestOptions();
419
-
420
-		$options = array_merge($this->defaultRequestOptions, [
421
-			'verify' => false,
422
-			'proxy' => [
423
-				'http' => 'bar',
424
-				'https' => 'bar'
425
-			],
426
-		]);
427
-
428
-		$this->guzzleClient->method('request')
429
-			->with('options', 'http://localhost/', $options)
430
-			->willReturn(new Response(418));
431
-		$this->assertEquals(418, $this->client->options('http://localhost/', $options)->getStatusCode());
432
-	}
433
-
434
-	public function testHead(): void {
435
-		$this->setUpDefaultRequestOptions();
436
-
437
-		$this->guzzleClient->method('request')
438
-			->with('head', 'http://localhost/', $this->defaultRequestOptions)
439
-			->willReturn(new Response(418));
440
-		$this->assertEquals(418, $this->client->head('http://localhost/', [])->getStatusCode());
441
-	}
442
-
443
-	public function testHeadWithOptions(): void {
444
-		$this->setUpDefaultRequestOptions();
445
-
446
-		$options = array_merge($this->defaultRequestOptions, [
447
-			'verify' => false,
448
-			'proxy' => [
449
-				'http' => 'bar',
450
-				'https' => 'bar'
451
-			],
452
-		]);
453
-
454
-		$this->guzzleClient->method('request')
455
-			->with('head', 'http://localhost/', $options)
456
-			->willReturn(new Response(418));
457
-		$this->assertEquals(418, $this->client->head('http://localhost/', $options)->getStatusCode());
458
-	}
459
-
460
-	public function testSetDefaultOptionsWithNotInstalled(): void {
461
-		$this->config
462
-			->expects($this->exactly(2))
463
-			->method('getSystemValueBool')
464
-			->willReturnMap([
465
-				['installed', false, false],
466
-				['allow_local_remote_servers', false, false],
467
-			]);
468
-		$this->config
469
-			->expects($this->once())
470
-			->method('getSystemValueString')
471
-			->with('proxy', '')
472
-			->willReturn('');
473
-		$this->certificateManager
474
-			->expects($this->never())
475
-			->method('listCertificates');
476
-
477
-		$this->serverVersion->method('getVersionString')
478
-			->willReturn('123.45.6');
479
-
480
-		$this->assertEquals([
481
-			'verify' => \OC::$SERVERROOT . '/resources/config/ca-bundle.crt',
482
-			'headers' => [
483
-				'User-Agent' => 'Nextcloud-Server-Crawler/123.45.6',
484
-				'Accept-Encoding' => 'gzip',
485
-			],
486
-			'timeout' => 30,
487
-			'nextcloud' => [
488
-				'allow_local_address' => false,
489
-			],
490
-			'allow_redirects' => [
491
-				'on_redirect' => function (
492
-					\Psr\Http\Message\RequestInterface $request,
493
-					\Psr\Http\Message\ResponseInterface $response,
494
-					\Psr\Http\Message\UriInterface $uri,
495
-				): void {
496
-				},
497
-			],
498
-		], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
499
-	}
500
-
501
-	public function testSetDefaultOptionsWithProxy(): void {
502
-		$this->config
503
-			->expects($this->exactly(2))
504
-			->method('getSystemValueBool')
505
-			->willReturnMap([
506
-				['installed', false, true],
507
-				['allow_local_remote_servers', false, false],
508
-			]);
509
-		$this->config
510
-			->expects($this->once())
511
-			->method('getSystemValue')
512
-			->with('proxyexclude', [])
513
-			->willReturn([]);
514
-		$this->config
515
-			->expects($this->exactly(2))
516
-			->method('getSystemValueString')
517
-			->willReturnMap([
518
-				['proxy', '', 'foo'],
519
-				['proxyuserpwd', '', ''],
520
-			]);
521
-		$this->certificateManager
522
-			->expects($this->once())
523
-			->method('getAbsoluteBundlePath')
524
-			->with()
525
-			->willReturn('/my/path.crt');
526
-
527
-		$this->serverVersion->method('getVersionString')
528
-			->willReturn('123.45.6');
529
-
530
-		$this->assertEquals([
531
-			'verify' => '/my/path.crt',
532
-			'proxy' => [
533
-				'http' => 'foo',
534
-				'https' => 'foo'
535
-			],
536
-			'headers' => [
537
-				'User-Agent' => 'Nextcloud-Server-Crawler/123.45.6',
538
-				'Accept-Encoding' => 'gzip',
539
-			],
540
-			'timeout' => 30,
541
-			'nextcloud' => [
542
-				'allow_local_address' => false,
543
-			],
544
-			'allow_redirects' => [
545
-				'on_redirect' => function (
546
-					\Psr\Http\Message\RequestInterface $request,
547
-					\Psr\Http\Message\ResponseInterface $response,
548
-					\Psr\Http\Message\UriInterface $uri,
549
-				): void {
550
-				},
551
-			],
552
-		], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
553
-	}
554
-
555
-	public function testSetDefaultOptionsWithProxyAndExclude(): void {
556
-		$this->config
557
-			->expects($this->exactly(2))
558
-			->method('getSystemValueBool')
559
-			->willReturnMap([
560
-				['installed', false, true],
561
-				['allow_local_remote_servers', false, false],
562
-			]);
563
-		$this->config
564
-			->expects($this->once())
565
-			->method('getSystemValue')
566
-			->with('proxyexclude', [])
567
-			->willReturn(['bar']);
568
-		$this->config
569
-			->expects($this->exactly(2))
570
-			->method('getSystemValueString')
571
-			->willReturnMap([
572
-				['proxy', '', 'foo'],
573
-				['proxyuserpwd', '', ''],
574
-			]);
575
-		$this->certificateManager
576
-			->expects($this->once())
577
-			->method('getAbsoluteBundlePath')
578
-			->with()
579
-			->willReturn('/my/path.crt');
580
-
581
-		$this->serverVersion->method('getVersionString')
582
-			->willReturn('123.45.6');
583
-
584
-		$this->assertEquals([
585
-			'verify' => '/my/path.crt',
586
-			'proxy' => [
587
-				'http' => 'foo',
588
-				'https' => 'foo',
589
-				'no' => ['bar']
590
-			],
591
-			'headers' => [
592
-				'User-Agent' => 'Nextcloud-Server-Crawler/123.45.6',
593
-				'Accept-Encoding' => 'gzip',
594
-			],
595
-			'timeout' => 30,
596
-			'nextcloud' => [
597
-				'allow_local_address' => false,
598
-			],
599
-			'allow_redirects' => [
600
-				'on_redirect' => function (
601
-					\Psr\Http\Message\RequestInterface $request,
602
-					\Psr\Http\Message\ResponseInterface $response,
603
-					\Psr\Http\Message\UriInterface $uri,
604
-				): void {
605
-				},
606
-			],
607
-		], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
608
-	}
29
+    /** @var \GuzzleHttp\Client|MockObject */
30
+    private $guzzleClient;
31
+    /** @var CertificateManager|MockObject */
32
+    private $certificateManager;
33
+    /** @var Client */
34
+    private $client;
35
+    /** @var IConfig|MockObject */
36
+    private $config;
37
+    /** @var IRemoteHostValidator|MockObject */
38
+    private IRemoteHostValidator $remoteHostValidator;
39
+    private LoggerInterface $logger;
40
+    private ServerVersion $serverVersion;
41
+    /** @var array */
42
+    private $defaultRequestOptions;
43
+
44
+    protected function setUp(): void {
45
+        parent::setUp();
46
+        $this->config = $this->createMock(IConfig::class);
47
+        $this->guzzleClient = $this->createMock(\GuzzleHttp\Client::class);
48
+        $this->certificateManager = $this->createMock(ICertificateManager::class);
49
+        $this->remoteHostValidator = $this->createMock(IRemoteHostValidator::class);
50
+        $this->logger = $this->createMock(LoggerInterface::class);
51
+        $this->serverVersion = $this->createMock(ServerVersion::class);
52
+
53
+        $this->client = new Client(
54
+            $this->config,
55
+            $this->certificateManager,
56
+            $this->guzzleClient,
57
+            $this->remoteHostValidator,
58
+            $this->logger,
59
+            $this->serverVersion,
60
+        );
61
+    }
62
+
63
+    public function testGetProxyUri(): void {
64
+        $this->config
65
+            ->method('getSystemValueString')
66
+            ->with('proxy', '')
67
+            ->willReturn('');
68
+        $this->assertNull(self::invokePrivate($this->client, 'getProxyUri'));
69
+    }
70
+
71
+    public function testGetProxyUriProxyHostEmptyPassword(): void {
72
+        $this->config
73
+            ->method('getSystemValue')
74
+            ->willReturnMap([
75
+                ['proxyexclude', [], []],
76
+            ]);
77
+
78
+        $this->config
79
+            ->method('getSystemValueString')
80
+            ->willReturnMap([
81
+                ['proxy', '', 'foo'],
82
+                ['proxyuserpwd', '', ''],
83
+            ]);
84
+
85
+        $this->assertEquals([
86
+            'http' => 'foo',
87
+            'https' => 'foo'
88
+        ], self::invokePrivate($this->client, 'getProxyUri'));
89
+    }
90
+
91
+    public function testGetProxyUriProxyHostWithPassword(): void {
92
+        $this->config
93
+            ->expects($this->once())
94
+            ->method('getSystemValue')
95
+            ->with('proxyexclude', [])
96
+            ->willReturn([]);
97
+        $this->config
98
+            ->expects($this->exactly(2))
99
+            ->method('getSystemValueString')
100
+            ->willReturnMap([
101
+                ['proxy', '', 'foo'],
102
+                ['proxyuserpwd', '', 'username:password'],
103
+            ]);
104
+        $this->assertEquals([
105
+            'http' => 'username:password@foo',
106
+            'https' => 'username:password@foo'
107
+        ], self::invokePrivate($this->client, 'getProxyUri'));
108
+    }
109
+
110
+    public function testGetProxyUriProxyHostWithPasswordAndExclude(): void {
111
+        $this->config
112
+            ->expects($this->once())
113
+            ->method('getSystemValue')
114
+            ->with('proxyexclude', [])
115
+            ->willReturn(['bar']);
116
+        $this->config
117
+            ->expects($this->exactly(2))
118
+            ->method('getSystemValueString')
119
+            ->willReturnMap([
120
+                ['proxy', '', 'foo'],
121
+                ['proxyuserpwd', '', 'username:password'],
122
+            ]);
123
+        $this->assertEquals([
124
+            'http' => 'username:password@foo',
125
+            'https' => 'username:password@foo',
126
+            'no' => ['bar']
127
+        ], self::invokePrivate($this->client, 'getProxyUri'));
128
+    }
129
+
130
+    public function testPreventLocalAddressThrowOnInvalidUri(): void {
131
+        $this->expectException(LocalServerException::class);
132
+        $this->expectExceptionMessage('Could not detect any host');
133
+
134
+        self::invokePrivate($this->client, 'preventLocalAddress', ['!@#$', []]);
135
+    }
136
+
137
+    public static function dataPreventLocalAddress(): array {
138
+        return [
139
+            ['https://localhost/foo.bar'],
140
+            ['https://localHost/foo.bar'],
141
+            ['https://random-host/foo.bar'],
142
+            ['https://[::1]/bla.blub'],
143
+            ['https://[::]/bla.blub'],
144
+            ['https://192.168.0.1'],
145
+            ['https://172.16.42.1'],
146
+            ['https://[fdf8:f53b:82e4::53]/secret.ics'],
147
+            ['https://[fe80::200:5aee:feaa:20a2]/secret.ics'],
148
+            ['https://[0:0:0:0:0:0:10.0.0.1]/secret.ics'],
149
+            ['https://[0:0:0:0:0:ffff:127.0.0.0]/secret.ics'],
150
+            ['https://10.0.0.1'],
151
+            ['https://another-host.local'],
152
+            ['https://service.localhost'],
153
+            ['https://normal.host.com'],
154
+            ['https://com.one-.nextcloud-one.com'],
155
+        ];
156
+    }
157
+
158
+    /**
159
+     * @param string $uri
160
+     */
161
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
162
+    public function testPreventLocalAddressDisabledByGlobalConfig(string $uri): void {
163
+        $this->config->expects($this->once())
164
+            ->method('getSystemValueBool')
165
+            ->with('allow_local_remote_servers', false)
166
+            ->willReturn(true);
167
+
168
+        self::invokePrivate($this->client, 'preventLocalAddress', [$uri, []]);
169
+    }
170
+
171
+    /**
172
+     * @param string $uri
173
+     */
174
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
175
+    public function testPreventLocalAddressDisabledByOption(string $uri): void {
176
+        $this->config->expects($this->never())
177
+            ->method('getSystemValueBool');
178
+
179
+        self::invokePrivate($this->client, 'preventLocalAddress', [$uri, [
180
+            'nextcloud' => ['allow_local_address' => true],
181
+        ]]);
182
+    }
183
+
184
+    /**
185
+     * @param string $uri
186
+     */
187
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
188
+    public function testPreventLocalAddressOnGet(string $uri): void {
189
+        $host = parse_url($uri, PHP_URL_HOST);
190
+        $this->expectException(LocalServerException::class);
191
+        $this->remoteHostValidator
192
+            ->method('isValid')
193
+            ->with($host)
194
+            ->willReturn(false);
195
+
196
+        $this->client->get($uri);
197
+    }
198
+
199
+    /**
200
+     * @param string $uri
201
+     */
202
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
203
+    public function testPreventLocalAddressOnHead(string $uri): void {
204
+        $host = parse_url($uri, PHP_URL_HOST);
205
+        $this->expectException(LocalServerException::class);
206
+        $this->remoteHostValidator
207
+            ->method('isValid')
208
+            ->with($host)
209
+            ->willReturn(false);
210
+
211
+        $this->client->head($uri);
212
+    }
213
+
214
+    /**
215
+     * @param string $uri
216
+     */
217
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
218
+    public function testPreventLocalAddressOnPost(string $uri): void {
219
+        $host = parse_url($uri, PHP_URL_HOST);
220
+        $this->expectException(LocalServerException::class);
221
+        $this->remoteHostValidator
222
+            ->method('isValid')
223
+            ->with($host)
224
+            ->willReturn(false);
225
+
226
+        $this->client->post($uri);
227
+    }
228
+
229
+    /**
230
+     * @param string $uri
231
+     */
232
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
233
+    public function testPreventLocalAddressOnPut(string $uri): void {
234
+        $host = parse_url($uri, PHP_URL_HOST);
235
+        $this->expectException(LocalServerException::class);
236
+        $this->remoteHostValidator
237
+            ->method('isValid')
238
+            ->with($host)
239
+            ->willReturn(false);
240
+
241
+        $this->client->put($uri);
242
+    }
243
+
244
+    /**
245
+     * @param string $uri
246
+     */
247
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
248
+    public function testPreventLocalAddressOnDelete(string $uri): void {
249
+        $host = parse_url($uri, PHP_URL_HOST);
250
+        $this->expectException(LocalServerException::class);
251
+        $this->remoteHostValidator
252
+            ->method('isValid')
253
+            ->with($host)
254
+            ->willReturn(false);
255
+
256
+        $this->client->delete($uri);
257
+    }
258
+
259
+    private function setUpDefaultRequestOptions(): void {
260
+        $this->config
261
+            ->method('getSystemValue')
262
+            ->willReturnMap([
263
+                ['proxyexclude', [], []],
264
+            ]);
265
+        $this->config
266
+            ->method('getSystemValueString')
267
+            ->willReturnMap([
268
+                ['proxy', '', 'foo'],
269
+                ['proxyuserpwd', '', ''],
270
+            ]);
271
+        $this->config
272
+            ->method('getSystemValueBool')
273
+            ->willReturnMap([
274
+                ['installed', false, true],
275
+                ['allow_local_remote_servers', false, true],
276
+            ]);
277
+
278
+        $this->certificateManager
279
+            ->expects($this->once())
280
+            ->method('getAbsoluteBundlePath')
281
+            ->with()
282
+            ->willReturn('/my/path.crt');
283
+
284
+        $this->serverVersion->method('getVersionString')
285
+            ->willReturn('123.45.6');
286
+
287
+        $this->defaultRequestOptions = [
288
+            'verify' => '/my/path.crt',
289
+            'proxy' => [
290
+                'http' => 'foo',
291
+                'https' => 'foo'
292
+            ],
293
+            'headers' => [
294
+                'User-Agent' => 'Nextcloud-Server-Crawler/123.45.6',
295
+                'Accept-Encoding' => 'gzip',
296
+            ],
297
+            'timeout' => 30,
298
+            'nextcloud' => [
299
+                'allow_local_address' => true,
300
+            ],
301
+        ];
302
+    }
303
+
304
+    public function testGet(): void {
305
+        $this->setUpDefaultRequestOptions();
306
+
307
+        $this->guzzleClient->method('request')
308
+            ->with('get', 'http://localhost/', $this->defaultRequestOptions)
309
+            ->willReturn(new Response(418));
310
+        $this->assertEquals(418, $this->client->get('http://localhost/', [])->getStatusCode());
311
+    }
312
+
313
+    public function testGetWithOptions(): void {
314
+        $this->setUpDefaultRequestOptions();
315
+
316
+        $options = array_merge($this->defaultRequestOptions, [
317
+            'verify' => false,
318
+            'proxy' => [
319
+                'http' => 'bar',
320
+                'https' => 'bar'
321
+            ],
322
+        ]);
323
+
324
+        $this->guzzleClient->method('request')
325
+            ->with('get', 'http://localhost/', $options)
326
+            ->willReturn(new Response(418));
327
+        $this->assertEquals(418, $this->client->get('http://localhost/', $options)->getStatusCode());
328
+    }
329
+
330
+    public function testPost(): void {
331
+        $this->setUpDefaultRequestOptions();
332
+
333
+        $this->guzzleClient->method('request')
334
+            ->with('post', 'http://localhost/', $this->defaultRequestOptions)
335
+            ->willReturn(new Response(418));
336
+        $this->assertEquals(418, $this->client->post('http://localhost/', [])->getStatusCode());
337
+    }
338
+
339
+    public function testPostWithOptions(): void {
340
+        $this->setUpDefaultRequestOptions();
341
+
342
+        $options = array_merge($this->defaultRequestOptions, [
343
+            'verify' => false,
344
+            'proxy' => [
345
+                'http' => 'bar',
346
+                'https' => 'bar'
347
+            ],
348
+        ]);
349
+
350
+        $this->guzzleClient->method('request')
351
+            ->with('post', 'http://localhost/', $options)
352
+            ->willReturn(new Response(418));
353
+        $this->assertEquals(418, $this->client->post('http://localhost/', $options)->getStatusCode());
354
+    }
355
+
356
+    public function testPut(): void {
357
+        $this->setUpDefaultRequestOptions();
358
+
359
+        $this->guzzleClient->method('request')
360
+            ->with('put', 'http://localhost/', $this->defaultRequestOptions)
361
+            ->willReturn(new Response(418));
362
+        $this->assertEquals(418, $this->client->put('http://localhost/', [])->getStatusCode());
363
+    }
364
+
365
+    public function testPutWithOptions(): void {
366
+        $this->setUpDefaultRequestOptions();
367
+
368
+        $options = array_merge($this->defaultRequestOptions, [
369
+            'verify' => false,
370
+            'proxy' => [
371
+                'http' => 'bar',
372
+                'https' => 'bar'
373
+            ],
374
+        ]);
375
+
376
+        $this->guzzleClient->method('request')
377
+            ->with('put', 'http://localhost/', $options)
378
+            ->willReturn(new Response(418));
379
+        $this->assertEquals(418, $this->client->put('http://localhost/', $options)->getStatusCode());
380
+    }
381
+
382
+    public function testDelete(): void {
383
+        $this->setUpDefaultRequestOptions();
384
+
385
+        $this->guzzleClient->method('request')
386
+            ->with('delete', 'http://localhost/', $this->defaultRequestOptions)
387
+            ->willReturn(new Response(418));
388
+        $this->assertEquals(418, $this->client->delete('http://localhost/', [])->getStatusCode());
389
+    }
390
+
391
+    public function testDeleteWithOptions(): void {
392
+        $this->setUpDefaultRequestOptions();
393
+
394
+        $options = array_merge($this->defaultRequestOptions, [
395
+            'verify' => false,
396
+            'proxy' => [
397
+                'http' => 'bar',
398
+                'https' => 'bar'
399
+            ],
400
+        ]);
401
+
402
+        $this->guzzleClient->method('request')
403
+            ->with('delete', 'http://localhost/', $options)
404
+            ->willReturn(new Response(418));
405
+        $this->assertEquals(418, $this->client->delete('http://localhost/', $options)->getStatusCode());
406
+    }
407
+
408
+    public function testOptions(): void {
409
+        $this->setUpDefaultRequestOptions();
410
+
411
+        $this->guzzleClient->method('request')
412
+            ->with('options', 'http://localhost/', $this->defaultRequestOptions)
413
+            ->willReturn(new Response(418));
414
+        $this->assertEquals(418, $this->client->options('http://localhost/', [])->getStatusCode());
415
+    }
416
+
417
+    public function testOptionsWithOptions(): void {
418
+        $this->setUpDefaultRequestOptions();
419
+
420
+        $options = array_merge($this->defaultRequestOptions, [
421
+            'verify' => false,
422
+            'proxy' => [
423
+                'http' => 'bar',
424
+                'https' => 'bar'
425
+            ],
426
+        ]);
427
+
428
+        $this->guzzleClient->method('request')
429
+            ->with('options', 'http://localhost/', $options)
430
+            ->willReturn(new Response(418));
431
+        $this->assertEquals(418, $this->client->options('http://localhost/', $options)->getStatusCode());
432
+    }
433
+
434
+    public function testHead(): void {
435
+        $this->setUpDefaultRequestOptions();
436
+
437
+        $this->guzzleClient->method('request')
438
+            ->with('head', 'http://localhost/', $this->defaultRequestOptions)
439
+            ->willReturn(new Response(418));
440
+        $this->assertEquals(418, $this->client->head('http://localhost/', [])->getStatusCode());
441
+    }
442
+
443
+    public function testHeadWithOptions(): void {
444
+        $this->setUpDefaultRequestOptions();
445
+
446
+        $options = array_merge($this->defaultRequestOptions, [
447
+            'verify' => false,
448
+            'proxy' => [
449
+                'http' => 'bar',
450
+                'https' => 'bar'
451
+            ],
452
+        ]);
453
+
454
+        $this->guzzleClient->method('request')
455
+            ->with('head', 'http://localhost/', $options)
456
+            ->willReturn(new Response(418));
457
+        $this->assertEquals(418, $this->client->head('http://localhost/', $options)->getStatusCode());
458
+    }
459
+
460
+    public function testSetDefaultOptionsWithNotInstalled(): void {
461
+        $this->config
462
+            ->expects($this->exactly(2))
463
+            ->method('getSystemValueBool')
464
+            ->willReturnMap([
465
+                ['installed', false, false],
466
+                ['allow_local_remote_servers', false, false],
467
+            ]);
468
+        $this->config
469
+            ->expects($this->once())
470
+            ->method('getSystemValueString')
471
+            ->with('proxy', '')
472
+            ->willReturn('');
473
+        $this->certificateManager
474
+            ->expects($this->never())
475
+            ->method('listCertificates');
476
+
477
+        $this->serverVersion->method('getVersionString')
478
+            ->willReturn('123.45.6');
479
+
480
+        $this->assertEquals([
481
+            'verify' => \OC::$SERVERROOT . '/resources/config/ca-bundle.crt',
482
+            'headers' => [
483
+                'User-Agent' => 'Nextcloud-Server-Crawler/123.45.6',
484
+                'Accept-Encoding' => 'gzip',
485
+            ],
486
+            'timeout' => 30,
487
+            'nextcloud' => [
488
+                'allow_local_address' => false,
489
+            ],
490
+            'allow_redirects' => [
491
+                'on_redirect' => function (
492
+                    \Psr\Http\Message\RequestInterface $request,
493
+                    \Psr\Http\Message\ResponseInterface $response,
494
+                    \Psr\Http\Message\UriInterface $uri,
495
+                ): void {
496
+                },
497
+            ],
498
+        ], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
499
+    }
500
+
501
+    public function testSetDefaultOptionsWithProxy(): void {
502
+        $this->config
503
+            ->expects($this->exactly(2))
504
+            ->method('getSystemValueBool')
505
+            ->willReturnMap([
506
+                ['installed', false, true],
507
+                ['allow_local_remote_servers', false, false],
508
+            ]);
509
+        $this->config
510
+            ->expects($this->once())
511
+            ->method('getSystemValue')
512
+            ->with('proxyexclude', [])
513
+            ->willReturn([]);
514
+        $this->config
515
+            ->expects($this->exactly(2))
516
+            ->method('getSystemValueString')
517
+            ->willReturnMap([
518
+                ['proxy', '', 'foo'],
519
+                ['proxyuserpwd', '', ''],
520
+            ]);
521
+        $this->certificateManager
522
+            ->expects($this->once())
523
+            ->method('getAbsoluteBundlePath')
524
+            ->with()
525
+            ->willReturn('/my/path.crt');
526
+
527
+        $this->serverVersion->method('getVersionString')
528
+            ->willReturn('123.45.6');
529
+
530
+        $this->assertEquals([
531
+            'verify' => '/my/path.crt',
532
+            'proxy' => [
533
+                'http' => 'foo',
534
+                'https' => 'foo'
535
+            ],
536
+            'headers' => [
537
+                'User-Agent' => 'Nextcloud-Server-Crawler/123.45.6',
538
+                'Accept-Encoding' => 'gzip',
539
+            ],
540
+            'timeout' => 30,
541
+            'nextcloud' => [
542
+                'allow_local_address' => false,
543
+            ],
544
+            'allow_redirects' => [
545
+                'on_redirect' => function (
546
+                    \Psr\Http\Message\RequestInterface $request,
547
+                    \Psr\Http\Message\ResponseInterface $response,
548
+                    \Psr\Http\Message\UriInterface $uri,
549
+                ): void {
550
+                },
551
+            ],
552
+        ], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
553
+    }
554
+
555
+    public function testSetDefaultOptionsWithProxyAndExclude(): void {
556
+        $this->config
557
+            ->expects($this->exactly(2))
558
+            ->method('getSystemValueBool')
559
+            ->willReturnMap([
560
+                ['installed', false, true],
561
+                ['allow_local_remote_servers', false, false],
562
+            ]);
563
+        $this->config
564
+            ->expects($this->once())
565
+            ->method('getSystemValue')
566
+            ->with('proxyexclude', [])
567
+            ->willReturn(['bar']);
568
+        $this->config
569
+            ->expects($this->exactly(2))
570
+            ->method('getSystemValueString')
571
+            ->willReturnMap([
572
+                ['proxy', '', 'foo'],
573
+                ['proxyuserpwd', '', ''],
574
+            ]);
575
+        $this->certificateManager
576
+            ->expects($this->once())
577
+            ->method('getAbsoluteBundlePath')
578
+            ->with()
579
+            ->willReturn('/my/path.crt');
580
+
581
+        $this->serverVersion->method('getVersionString')
582
+            ->willReturn('123.45.6');
583
+
584
+        $this->assertEquals([
585
+            'verify' => '/my/path.crt',
586
+            'proxy' => [
587
+                'http' => 'foo',
588
+                'https' => 'foo',
589
+                'no' => ['bar']
590
+            ],
591
+            'headers' => [
592
+                'User-Agent' => 'Nextcloud-Server-Crawler/123.45.6',
593
+                'Accept-Encoding' => 'gzip',
594
+            ],
595
+            'timeout' => 30,
596
+            'nextcloud' => [
597
+                'allow_local_address' => false,
598
+            ],
599
+            'allow_redirects' => [
600
+                'on_redirect' => function (
601
+                    \Psr\Http\Message\RequestInterface $request,
602
+                    \Psr\Http\Message\ResponseInterface $response,
603
+                    \Psr\Http\Message\UriInterface $uri,
604
+                ): void {
605
+                },
606
+            ],
607
+        ], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
608
+    }
609 609
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -478,7 +478,7 @@  discard block
 block discarded – undo
478 478
 			->willReturn('123.45.6');
479 479
 
480 480
 		$this->assertEquals([
481
-			'verify' => \OC::$SERVERROOT . '/resources/config/ca-bundle.crt',
481
+			'verify' => \OC::$SERVERROOT.'/resources/config/ca-bundle.crt',
482 482
 			'headers' => [
483 483
 				'User-Agent' => 'Nextcloud-Server-Crawler/123.45.6',
484 484
 				'Accept-Encoding' => 'gzip',
@@ -488,7 +488,7 @@  discard block
 block discarded – undo
488 488
 				'allow_local_address' => false,
489 489
 			],
490 490
 			'allow_redirects' => [
491
-				'on_redirect' => function (
491
+				'on_redirect' => function(
492 492
 					\Psr\Http\Message\RequestInterface $request,
493 493
 					\Psr\Http\Message\ResponseInterface $response,
494 494
 					\Psr\Http\Message\UriInterface $uri,
@@ -542,7 +542,7 @@  discard block
 block discarded – undo
542 542
 				'allow_local_address' => false,
543 543
 			],
544 544
 			'allow_redirects' => [
545
-				'on_redirect' => function (
545
+				'on_redirect' => function(
546 546
 					\Psr\Http\Message\RequestInterface $request,
547 547
 					\Psr\Http\Message\ResponseInterface $response,
548 548
 					\Psr\Http\Message\UriInterface $uri,
@@ -597,7 +597,7 @@  discard block
 block discarded – undo
597 597
 				'allow_local_address' => false,
598 598
 			],
599 599
 			'allow_redirects' => [
600
-				'on_redirect' => function (
600
+				'on_redirect' => function(
601 601
 					\Psr\Http\Message\RequestInterface $request,
602 602
 					\Psr\Http\Message\ResponseInterface $response,
603 603
 					\Psr\Http\Message\UriInterface $uri,
Please login to merge, or discard this patch.
tests/lib/Http/Client/ClientServiceTest.php 1 patch
Indentation   +95 added lines, -95 removed lines patch added patch discarded remove patch
@@ -29,106 +29,106 @@
 block discarded – undo
29 29
  * Class ClientServiceTest
30 30
  */
31 31
 class ClientServiceTest extends \Test\TestCase {
32
-	public function testNewClient(): void {
33
-		/** @var IConfig $config */
34
-		$config = $this->createMock(IConfig::class);
35
-		$config->method('getSystemValueBool')
36
-			->with('dns_pinning', true)
37
-			->willReturn(true);
38
-		/** @var ICertificateManager $certificateManager */
39
-		$certificateManager = $this->createMock(ICertificateManager::class);
40
-		$dnsPinMiddleware = $this->createMock(DnsPinMiddleware::class);
41
-		$dnsPinMiddleware
42
-			->expects($this->atLeastOnce())
43
-			->method('addDnsPinning')
44
-			->willReturn(function (): void {
45
-			});
46
-		$remoteHostValidator = $this->createMock(IRemoteHostValidator::class);
47
-		$eventLogger = $this->createMock(IEventLogger::class);
48
-		$logger = $this->createMock(LoggerInterface::class);
49
-		$serverVersion = $this->createMock(ServerVersion::class);
32
+    public function testNewClient(): void {
33
+        /** @var IConfig $config */
34
+        $config = $this->createMock(IConfig::class);
35
+        $config->method('getSystemValueBool')
36
+            ->with('dns_pinning', true)
37
+            ->willReturn(true);
38
+        /** @var ICertificateManager $certificateManager */
39
+        $certificateManager = $this->createMock(ICertificateManager::class);
40
+        $dnsPinMiddleware = $this->createMock(DnsPinMiddleware::class);
41
+        $dnsPinMiddleware
42
+            ->expects($this->atLeastOnce())
43
+            ->method('addDnsPinning')
44
+            ->willReturn(function (): void {
45
+            });
46
+        $remoteHostValidator = $this->createMock(IRemoteHostValidator::class);
47
+        $eventLogger = $this->createMock(IEventLogger::class);
48
+        $logger = $this->createMock(LoggerInterface::class);
49
+        $serverVersion = $this->createMock(ServerVersion::class);
50 50
 
51
-		$clientService = new ClientService(
52
-			$config,
53
-			$certificateManager,
54
-			$dnsPinMiddleware,
55
-			$remoteHostValidator,
56
-			$eventLogger,
57
-			$logger,
58
-			$serverVersion,
59
-		);
51
+        $clientService = new ClientService(
52
+            $config,
53
+            $certificateManager,
54
+            $dnsPinMiddleware,
55
+            $remoteHostValidator,
56
+            $eventLogger,
57
+            $logger,
58
+            $serverVersion,
59
+        );
60 60
 
61
-		$handler = new CurlHandler();
62
-		$stack = HandlerStack::create($handler);
63
-		$stack->push($dnsPinMiddleware->addDnsPinning());
64
-		$stack->push(Middleware::tap(function (RequestInterface $request) use ($eventLogger): void {
65
-			$eventLogger->start('http:request', $request->getMethod() . ' request to ' . $request->getRequestTarget());
66
-		}, function () use ($eventLogger): void {
67
-			$eventLogger->end('http:request');
68
-		}), 'event logger');
69
-		$guzzleClient = new GuzzleClient(['handler' => $stack]);
61
+        $handler = new CurlHandler();
62
+        $stack = HandlerStack::create($handler);
63
+        $stack->push($dnsPinMiddleware->addDnsPinning());
64
+        $stack->push(Middleware::tap(function (RequestInterface $request) use ($eventLogger): void {
65
+            $eventLogger->start('http:request', $request->getMethod() . ' request to ' . $request->getRequestTarget());
66
+        }, function () use ($eventLogger): void {
67
+            $eventLogger->end('http:request');
68
+        }), 'event logger');
69
+        $guzzleClient = new GuzzleClient(['handler' => $stack]);
70 70
 
71
-		$this->assertEquals(
72
-			new Client(
73
-				$config,
74
-				$certificateManager,
75
-				$guzzleClient,
76
-				$remoteHostValidator,
77
-				$logger,
78
-				$serverVersion,
79
-			),
80
-			$clientService->newClient()
81
-		);
82
-	}
71
+        $this->assertEquals(
72
+            new Client(
73
+                $config,
74
+                $certificateManager,
75
+                $guzzleClient,
76
+                $remoteHostValidator,
77
+                $logger,
78
+                $serverVersion,
79
+            ),
80
+            $clientService->newClient()
81
+        );
82
+    }
83 83
 
84
-	public function testDisableDnsPinning(): void {
85
-		/** @var IConfig $config */
86
-		$config = $this->createMock(IConfig::class);
87
-		$config->method('getSystemValueBool')
88
-			->with('dns_pinning', true)
89
-			->willReturn(false);
90
-		/** @var ICertificateManager $certificateManager */
91
-		$certificateManager = $this->createMock(ICertificateManager::class);
92
-		$dnsPinMiddleware = $this->createMock(DnsPinMiddleware::class);
93
-		$dnsPinMiddleware
94
-			->expects($this->never())
95
-			->method('addDnsPinning')
96
-			->willReturn(function (): void {
97
-			});
98
-		$remoteHostValidator = $this->createMock(IRemoteHostValidator::class);
99
-		$eventLogger = $this->createMock(IEventLogger::class);
100
-		$logger = $this->createMock(LoggerInterface::class);
101
-		$serverVersion = $this->createMock(ServerVersion::class);
84
+    public function testDisableDnsPinning(): void {
85
+        /** @var IConfig $config */
86
+        $config = $this->createMock(IConfig::class);
87
+        $config->method('getSystemValueBool')
88
+            ->with('dns_pinning', true)
89
+            ->willReturn(false);
90
+        /** @var ICertificateManager $certificateManager */
91
+        $certificateManager = $this->createMock(ICertificateManager::class);
92
+        $dnsPinMiddleware = $this->createMock(DnsPinMiddleware::class);
93
+        $dnsPinMiddleware
94
+            ->expects($this->never())
95
+            ->method('addDnsPinning')
96
+            ->willReturn(function (): void {
97
+            });
98
+        $remoteHostValidator = $this->createMock(IRemoteHostValidator::class);
99
+        $eventLogger = $this->createMock(IEventLogger::class);
100
+        $logger = $this->createMock(LoggerInterface::class);
101
+        $serverVersion = $this->createMock(ServerVersion::class);
102 102
 
103
-		$clientService = new ClientService(
104
-			$config,
105
-			$certificateManager,
106
-			$dnsPinMiddleware,
107
-			$remoteHostValidator,
108
-			$eventLogger,
109
-			$logger,
110
-			$serverVersion,
111
-		);
103
+        $clientService = new ClientService(
104
+            $config,
105
+            $certificateManager,
106
+            $dnsPinMiddleware,
107
+            $remoteHostValidator,
108
+            $eventLogger,
109
+            $logger,
110
+            $serverVersion,
111
+        );
112 112
 
113
-		$handler = new CurlHandler();
114
-		$stack = HandlerStack::create($handler);
115
-		$stack->push(Middleware::tap(function (RequestInterface $request) use ($eventLogger): void {
116
-			$eventLogger->start('http:request', $request->getMethod() . ' request to ' . $request->getRequestTarget());
117
-		}, function () use ($eventLogger): void {
118
-			$eventLogger->end('http:request');
119
-		}), 'event logger');
120
-		$guzzleClient = new GuzzleClient(['handler' => $stack]);
113
+        $handler = new CurlHandler();
114
+        $stack = HandlerStack::create($handler);
115
+        $stack->push(Middleware::tap(function (RequestInterface $request) use ($eventLogger): void {
116
+            $eventLogger->start('http:request', $request->getMethod() . ' request to ' . $request->getRequestTarget());
117
+        }, function () use ($eventLogger): void {
118
+            $eventLogger->end('http:request');
119
+        }), 'event logger');
120
+        $guzzleClient = new GuzzleClient(['handler' => $stack]);
121 121
 
122
-		$this->assertEquals(
123
-			new Client(
124
-				$config,
125
-				$certificateManager,
126
-				$guzzleClient,
127
-				$remoteHostValidator,
128
-				$logger,
129
-				$serverVersion,
130
-			),
131
-			$clientService->newClient()
132
-		);
133
-	}
122
+        $this->assertEquals(
123
+            new Client(
124
+                $config,
125
+                $certificateManager,
126
+                $guzzleClient,
127
+                $remoteHostValidator,
128
+                $logger,
129
+                $serverVersion,
130
+            ),
131
+            $clientService->newClient()
132
+        );
133
+    }
134 134
 }
Please login to merge, or discard this patch.