Passed
Push — master ( 5b7767...d00708 )
by Joas
15:14 queued 12s
created
lib/private/Http/Client/LocalAddressChecker.php 1 patch
Indentation   +58 added lines, -58 removed lines patch added patch discarded remove patch
@@ -33,70 +33,70 @@
 block discarded – undo
33 33
 use Symfony\Component\HttpFoundation\IpUtils;
34 34
 
35 35
 class LocalAddressChecker {
36
-	private LoggerInterface $logger;
36
+    private LoggerInterface $logger;
37 37
 
38
-	public function __construct(LoggerInterface $logger) {
39
-		$this->logger = $logger;
40
-	}
38
+    public function __construct(LoggerInterface $logger) {
39
+        $this->logger = $logger;
40
+    }
41 41
 
42
-	public function throwIfLocalIp(string $ip) : void {
43
-		$parsedIp = Factory::parseAddressString(
44
-			$ip,
45
-			ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED
46
-		);
47
-		if ($parsedIp === null) {
48
-			/* Not an IP */
49
-			return;
50
-		}
51
-		/* Replace by normalized form */
52
-		if ($parsedIp instanceof IPv6) {
53
-			$ip = (string)($parsedIp->toIPv4() ?? $parsedIp);
54
-		} else {
55
-			$ip = (string)$parsedIp;
56
-		}
42
+    public function throwIfLocalIp(string $ip) : void {
43
+        $parsedIp = Factory::parseAddressString(
44
+            $ip,
45
+            ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED
46
+        );
47
+        if ($parsedIp === null) {
48
+            /* Not an IP */
49
+            return;
50
+        }
51
+        /* Replace by normalized form */
52
+        if ($parsedIp instanceof IPv6) {
53
+            $ip = (string)($parsedIp->toIPv4() ?? $parsedIp);
54
+        } else {
55
+            $ip = (string)$parsedIp;
56
+        }
57 57
 
58
-		$localRanges = [
59
-			'100.64.0.0/10', // See RFC 6598
60
-			'192.0.0.0/24', // See RFC 6890
61
-		];
62
-		if (
63
-			(bool)filter_var($ip, FILTER_VALIDATE_IP) &&
64
-			(
65
-				!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) ||
66
-				IpUtils::checkIp($ip, $localRanges)
67
-			)) {
68
-			$this->logger->warning("Host $ip was not connected to because it violates local access rules");
69
-			throw new LocalServerException('Host violates local access rules');
70
-		}
71
-	}
58
+        $localRanges = [
59
+            '100.64.0.0/10', // See RFC 6598
60
+            '192.0.0.0/24', // See RFC 6890
61
+        ];
62
+        if (
63
+            (bool)filter_var($ip, FILTER_VALIDATE_IP) &&
64
+            (
65
+                !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) ||
66
+                IpUtils::checkIp($ip, $localRanges)
67
+            )) {
68
+            $this->logger->warning("Host $ip was not connected to because it violates local access rules");
69
+            throw new LocalServerException('Host violates local access rules');
70
+        }
71
+    }
72 72
 
73
-	public function throwIfLocalAddress(string $uri) : void {
74
-		$host = parse_url($uri, PHP_URL_HOST);
75
-		if ($host === false || $host === null) {
76
-			$this->logger->warning("Could not detect any host in $uri");
77
-			throw new LocalServerException('Could not detect any host');
78
-		}
73
+    public function throwIfLocalAddress(string $uri) : void {
74
+        $host = parse_url($uri, PHP_URL_HOST);
75
+        if ($host === false || $host === null) {
76
+            $this->logger->warning("Could not detect any host in $uri");
77
+            throw new LocalServerException('Could not detect any host');
78
+        }
79 79
 
80
-		$host = idn_to_utf8(strtolower(urldecode($host)));
81
-		// Remove brackets from IPv6 addresses
82
-		if (strpos($host, '[') === 0 && substr($host, -1) === ']') {
83
-			$host = substr($host, 1, -1);
84
-		}
80
+        $host = idn_to_utf8(strtolower(urldecode($host)));
81
+        // Remove brackets from IPv6 addresses
82
+        if (strpos($host, '[') === 0 && substr($host, -1) === ']') {
83
+            $host = substr($host, 1, -1);
84
+        }
85 85
 
86
-		// Disallow local network top-level domains from RFC 6762
87
-		$localTopLevelDomains = ['local','localhost','intranet','internal','private','corp','home','lan'];
88
-		$topLevelDomain = substr((strrchr($host, '.') ?: ''), 1);
89
-		if (in_array($topLevelDomain, $localTopLevelDomains)) {
90
-			$this->logger->warning("Host $host was not connected to because it violates local access rules");
91
-			throw new LocalServerException('Host violates local access rules');
92
-		}
86
+        // Disallow local network top-level domains from RFC 6762
87
+        $localTopLevelDomains = ['local','localhost','intranet','internal','private','corp','home','lan'];
88
+        $topLevelDomain = substr((strrchr($host, '.') ?: ''), 1);
89
+        if (in_array($topLevelDomain, $localTopLevelDomains)) {
90
+            $this->logger->warning("Host $host was not connected to because it violates local access rules");
91
+            throw new LocalServerException('Host violates local access rules');
92
+        }
93 93
 
94
-		// Disallow hostname only
95
-		if (substr_count($host, '.') === 0 && !(bool)filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
96
-			$this->logger->warning("Host $host was not connected to because it violates local access rules");
97
-			throw new LocalServerException('Host violates local access rules');
98
-		}
94
+        // Disallow hostname only
95
+        if (substr_count($host, '.') === 0 && !(bool)filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
96
+            $this->logger->warning("Host $host was not connected to because it violates local access rules");
97
+            throw new LocalServerException('Host violates local access rules');
98
+        }
99 99
 
100
-		$this->throwIfLocalIp($host);
101
-	}
100
+        $this->throwIfLocalIp($host);
101
+    }
102 102
 }
Please login to merge, or discard this patch.
lib/private/Http/Client/Client.php 1 patch
Indentation   +349 added lines, -349 removed lines patch added patch discarded remove patch
@@ -46,353 +46,353 @@
 block discarded – undo
46 46
  * @package OC\Http
47 47
  */
48 48
 class Client implements IClient {
49
-	/** @var GuzzleClient */
50
-	private $client;
51
-	/** @var IConfig */
52
-	private $config;
53
-	/** @var ICertificateManager */
54
-	private $certificateManager;
55
-	/** @var LocalAddressChecker */
56
-	private $localAddressChecker;
57
-
58
-	public function __construct(
59
-		IConfig $config,
60
-		ICertificateManager $certificateManager,
61
-		GuzzleClient $client,
62
-		LocalAddressChecker $localAddressChecker
63
-	) {
64
-		$this->config = $config;
65
-		$this->client = $client;
66
-		$this->certificateManager = $certificateManager;
67
-		$this->localAddressChecker = $localAddressChecker;
68
-	}
69
-
70
-	private function buildRequestOptions(array $options): array {
71
-		$proxy = $this->getProxyUri();
72
-
73
-		$defaults = [
74
-			RequestOptions::VERIFY => $this->getCertBundle(),
75
-			RequestOptions::TIMEOUT => 30,
76
-		];
77
-
78
-		$options['nextcloud']['allow_local_address'] = $this->isLocalAddressAllowed($options);
79
-		if ($options['nextcloud']['allow_local_address'] === false) {
80
-			$onRedirectFunction = function (
81
-				\Psr\Http\Message\RequestInterface $request,
82
-				\Psr\Http\Message\ResponseInterface $response,
83
-				\Psr\Http\Message\UriInterface $uri
84
-			) use ($options) {
85
-				$this->preventLocalAddress($uri->__toString(), $options);
86
-			};
87
-
88
-			$defaults[RequestOptions::ALLOW_REDIRECTS] = [
89
-				'on_redirect' => $onRedirectFunction
90
-			];
91
-		}
92
-
93
-		// Only add RequestOptions::PROXY if Nextcloud is explicitly
94
-		// configured to use a proxy. This is needed in order not to override
95
-		// Guzzle default values.
96
-		if ($proxy !== null) {
97
-			$defaults[RequestOptions::PROXY] = $proxy;
98
-		}
99
-
100
-		$options = array_merge($defaults, $options);
101
-
102
-		if (!isset($options[RequestOptions::HEADERS]['User-Agent'])) {
103
-			$options[RequestOptions::HEADERS]['User-Agent'] = 'Nextcloud Server Crawler';
104
-		}
105
-
106
-		if (!isset($options[RequestOptions::HEADERS]['Accept-Encoding'])) {
107
-			$options[RequestOptions::HEADERS]['Accept-Encoding'] = 'gzip';
108
-		}
109
-
110
-		// Fallback for save_to
111
-		if (isset($options['save_to'])) {
112
-			$options['sink'] = $options['save_to'];
113
-			unset($options['save_to']);
114
-		}
115
-
116
-		return $options;
117
-	}
118
-
119
-	private function getCertBundle(): string {
120
-		// If the instance is not yet setup we need to use the static path as
121
-		// $this->certificateManager->getAbsoluteBundlePath() tries to instantiate
122
-		// a view
123
-		if ($this->config->getSystemValue('installed', false) === false) {
124
-			return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt';
125
-		}
126
-
127
-		return $this->certificateManager->getAbsoluteBundlePath();
128
-	}
129
-
130
-	/**
131
-	 * Returns a null or an associative array specifying the proxy URI for
132
-	 * 'http' and 'https' schemes, in addition to a 'no' key value pair
133
-	 * providing a list of host names that should not be proxied to.
134
-	 *
135
-	 * @return array|null
136
-	 *
137
-	 * The return array looks like:
138
-	 * [
139
-	 *   'http' => 'username:[email protected]',
140
-	 *   'https' => 'username:[email protected]',
141
-	 *   'no' => ['foo.com', 'bar.com']
142
-	 * ]
143
-	 *
144
-	 */
145
-	private function getProxyUri(): ?array {
146
-		$proxyHost = $this->config->getSystemValue('proxy', '');
147
-
148
-		if ($proxyHost === '' || $proxyHost === null) {
149
-			return null;
150
-		}
151
-
152
-		$proxyUserPwd = $this->config->getSystemValue('proxyuserpwd', '');
153
-		if ($proxyUserPwd !== '' && $proxyUserPwd !== null) {
154
-			$proxyHost = $proxyUserPwd . '@' . $proxyHost;
155
-		}
156
-
157
-		$proxy = [
158
-			'http' => $proxyHost,
159
-			'https' => $proxyHost,
160
-		];
161
-
162
-		$proxyExclude = $this->config->getSystemValue('proxyexclude', []);
163
-		if ($proxyExclude !== [] && $proxyExclude !== null) {
164
-			$proxy['no'] = $proxyExclude;
165
-		}
166
-
167
-		return $proxy;
168
-	}
169
-
170
-	private function isLocalAddressAllowed(array $options) : bool {
171
-		if (($options['nextcloud']['allow_local_address'] ?? false) ||
172
-			$this->config->getSystemValueBool('allow_local_remote_servers', false)) {
173
-			return true;
174
-		}
175
-
176
-		return false;
177
-	}
178
-
179
-	protected function preventLocalAddress(string $uri, array $options): void {
180
-		if ($this->isLocalAddressAllowed($options)) {
181
-			return;
182
-		}
183
-
184
-		$this->localAddressChecker->throwIfLocalAddress($uri);
185
-	}
186
-
187
-	/**
188
-	 * Sends a GET request
189
-	 *
190
-	 * @param string $uri
191
-	 * @param array $options Array such as
192
-	 *              'query' => [
193
-	 *                  'field' => 'abc',
194
-	 *                  'other_field' => '123',
195
-	 *                  'file_name' => fopen('/path/to/file', 'r'),
196
-	 *              ],
197
-	 *              'headers' => [
198
-	 *                  'foo' => 'bar',
199
-	 *              ],
200
-	 *              'cookies' => ['
201
-	 *                  'foo' => 'bar',
202
-	 *              ],
203
-	 *              'allow_redirects' => [
204
-	 *                   'max'       => 10,  // allow at most 10 redirects.
205
-	 *                   'strict'    => true,     // use "strict" RFC compliant redirects.
206
-	 *                   'referer'   => true,     // add a Referer header
207
-	 *                   'protocols' => ['https'] // only allow https URLs
208
-	 *              ],
209
-	 *              'sink' => '/path/to/file', // save to a file or a stream
210
-	 *              'verify' => true, // bool or string to CA file
211
-	 *              'debug' => true,
212
-	 *              'timeout' => 5,
213
-	 * @return IResponse
214
-	 * @throws \Exception If the request could not get completed
215
-	 */
216
-	public function get(string $uri, array $options = []): IResponse {
217
-		$this->preventLocalAddress($uri, $options);
218
-		$response = $this->client->request('get', $uri, $this->buildRequestOptions($options));
219
-		$isStream = isset($options['stream']) && $options['stream'];
220
-		return new Response($response, $isStream);
221
-	}
222
-
223
-	/**
224
-	 * Sends a HEAD request
225
-	 *
226
-	 * @param string $uri
227
-	 * @param array $options Array such as
228
-	 *              'headers' => [
229
-	 *                  'foo' => 'bar',
230
-	 *              ],
231
-	 *              'cookies' => ['
232
-	 *                  'foo' => 'bar',
233
-	 *              ],
234
-	 *              'allow_redirects' => [
235
-	 *                   'max'       => 10,  // allow at most 10 redirects.
236
-	 *                   'strict'    => true,     // use "strict" RFC compliant redirects.
237
-	 *                   'referer'   => true,     // add a Referer header
238
-	 *                   'protocols' => ['https'] // only allow https URLs
239
-	 *              ],
240
-	 *              'sink' => '/path/to/file', // save to a file or a stream
241
-	 *              'verify' => true, // bool or string to CA file
242
-	 *              'debug' => true,
243
-	 *              'timeout' => 5,
244
-	 * @return IResponse
245
-	 * @throws \Exception If the request could not get completed
246
-	 */
247
-	public function head(string $uri, array $options = []): IResponse {
248
-		$this->preventLocalAddress($uri, $options);
249
-		$response = $this->client->request('head', $uri, $this->buildRequestOptions($options));
250
-		return new Response($response);
251
-	}
252
-
253
-	/**
254
-	 * Sends a POST request
255
-	 *
256
-	 * @param string $uri
257
-	 * @param array $options Array such as
258
-	 *              'body' => [
259
-	 *                  'field' => 'abc',
260
-	 *                  'other_field' => '123',
261
-	 *                  'file_name' => fopen('/path/to/file', 'r'),
262
-	 *              ],
263
-	 *              'headers' => [
264
-	 *                  'foo' => 'bar',
265
-	 *              ],
266
-	 *              'cookies' => ['
267
-	 *                  'foo' => 'bar',
268
-	 *              ],
269
-	 *              'allow_redirects' => [
270
-	 *                   'max'       => 10,  // allow at most 10 redirects.
271
-	 *                   'strict'    => true,     // use "strict" RFC compliant redirects.
272
-	 *                   'referer'   => true,     // add a Referer header
273
-	 *                   'protocols' => ['https'] // only allow https URLs
274
-	 *              ],
275
-	 *              'sink' => '/path/to/file', // save to a file or a stream
276
-	 *              'verify' => true, // bool or string to CA file
277
-	 *              'debug' => true,
278
-	 *              'timeout' => 5,
279
-	 * @return IResponse
280
-	 * @throws \Exception If the request could not get completed
281
-	 */
282
-	public function post(string $uri, array $options = []): IResponse {
283
-		$this->preventLocalAddress($uri, $options);
284
-
285
-		if (isset($options['body']) && is_array($options['body'])) {
286
-			$options['form_params'] = $options['body'];
287
-			unset($options['body']);
288
-		}
289
-		$response = $this->client->request('post', $uri, $this->buildRequestOptions($options));
290
-		$isStream = isset($options['stream']) && $options['stream'];
291
-		return new Response($response, $isStream);
292
-	}
293
-
294
-	/**
295
-	 * Sends a PUT request
296
-	 *
297
-	 * @param string $uri
298
-	 * @param array $options Array such as
299
-	 *              'body' => [
300
-	 *                  'field' => 'abc',
301
-	 *                  'other_field' => '123',
302
-	 *                  'file_name' => fopen('/path/to/file', 'r'),
303
-	 *              ],
304
-	 *              'headers' => [
305
-	 *                  'foo' => 'bar',
306
-	 *              ],
307
-	 *              'cookies' => ['
308
-	 *                  'foo' => 'bar',
309
-	 *              ],
310
-	 *              'allow_redirects' => [
311
-	 *                   'max'       => 10,  // allow at most 10 redirects.
312
-	 *                   'strict'    => true,     // use "strict" RFC compliant redirects.
313
-	 *                   'referer'   => true,     // add a Referer header
314
-	 *                   'protocols' => ['https'] // only allow https URLs
315
-	 *              ],
316
-	 *              'sink' => '/path/to/file', // save to a file or a stream
317
-	 *              'verify' => true, // bool or string to CA file
318
-	 *              'debug' => true,
319
-	 *              'timeout' => 5,
320
-	 * @return IResponse
321
-	 * @throws \Exception If the request could not get completed
322
-	 */
323
-	public function put(string $uri, array $options = []): IResponse {
324
-		$this->preventLocalAddress($uri, $options);
325
-		$response = $this->client->request('put', $uri, $this->buildRequestOptions($options));
326
-		return new Response($response);
327
-	}
328
-
329
-	/**
330
-	 * Sends a DELETE request
331
-	 *
332
-	 * @param string $uri
333
-	 * @param array $options Array such as
334
-	 *              'body' => [
335
-	 *                  'field' => 'abc',
336
-	 *                  'other_field' => '123',
337
-	 *                  'file_name' => fopen('/path/to/file', 'r'),
338
-	 *              ],
339
-	 *              'headers' => [
340
-	 *                  'foo' => 'bar',
341
-	 *              ],
342
-	 *              'cookies' => ['
343
-	 *                  'foo' => 'bar',
344
-	 *              ],
345
-	 *              'allow_redirects' => [
346
-	 *                   'max'       => 10,  // allow at most 10 redirects.
347
-	 *                   'strict'    => true,     // use "strict" RFC compliant redirects.
348
-	 *                   'referer'   => true,     // add a Referer header
349
-	 *                   'protocols' => ['https'] // only allow https URLs
350
-	 *              ],
351
-	 *              'sink' => '/path/to/file', // save to a file or a stream
352
-	 *              'verify' => true, // bool or string to CA file
353
-	 *              'debug' => true,
354
-	 *              'timeout' => 5,
355
-	 * @return IResponse
356
-	 * @throws \Exception If the request could not get completed
357
-	 */
358
-	public function delete(string $uri, array $options = []): IResponse {
359
-		$this->preventLocalAddress($uri, $options);
360
-		$response = $this->client->request('delete', $uri, $this->buildRequestOptions($options));
361
-		return new Response($response);
362
-	}
363
-
364
-	/**
365
-	 * Sends a options request
366
-	 *
367
-	 * @param string $uri
368
-	 * @param array $options Array such as
369
-	 *              'body' => [
370
-	 *                  'field' => 'abc',
371
-	 *                  'other_field' => '123',
372
-	 *                  'file_name' => fopen('/path/to/file', 'r'),
373
-	 *              ],
374
-	 *              'headers' => [
375
-	 *                  'foo' => 'bar',
376
-	 *              ],
377
-	 *              'cookies' => ['
378
-	 *                  'foo' => 'bar',
379
-	 *              ],
380
-	 *              'allow_redirects' => [
381
-	 *                   'max'       => 10,  // allow at most 10 redirects.
382
-	 *                   'strict'    => true,     // use "strict" RFC compliant redirects.
383
-	 *                   'referer'   => true,     // add a Referer header
384
-	 *                   'protocols' => ['https'] // only allow https URLs
385
-	 *              ],
386
-	 *              'sink' => '/path/to/file', // save to a file or a stream
387
-	 *              'verify' => true, // bool or string to CA file
388
-	 *              'debug' => true,
389
-	 *              'timeout' => 5,
390
-	 * @return IResponse
391
-	 * @throws \Exception If the request could not get completed
392
-	 */
393
-	public function options(string $uri, array $options = []): IResponse {
394
-		$this->preventLocalAddress($uri, $options);
395
-		$response = $this->client->request('options', $uri, $this->buildRequestOptions($options));
396
-		return new Response($response);
397
-	}
49
+    /** @var GuzzleClient */
50
+    private $client;
51
+    /** @var IConfig */
52
+    private $config;
53
+    /** @var ICertificateManager */
54
+    private $certificateManager;
55
+    /** @var LocalAddressChecker */
56
+    private $localAddressChecker;
57
+
58
+    public function __construct(
59
+        IConfig $config,
60
+        ICertificateManager $certificateManager,
61
+        GuzzleClient $client,
62
+        LocalAddressChecker $localAddressChecker
63
+    ) {
64
+        $this->config = $config;
65
+        $this->client = $client;
66
+        $this->certificateManager = $certificateManager;
67
+        $this->localAddressChecker = $localAddressChecker;
68
+    }
69
+
70
+    private function buildRequestOptions(array $options): array {
71
+        $proxy = $this->getProxyUri();
72
+
73
+        $defaults = [
74
+            RequestOptions::VERIFY => $this->getCertBundle(),
75
+            RequestOptions::TIMEOUT => 30,
76
+        ];
77
+
78
+        $options['nextcloud']['allow_local_address'] = $this->isLocalAddressAllowed($options);
79
+        if ($options['nextcloud']['allow_local_address'] === false) {
80
+            $onRedirectFunction = function (
81
+                \Psr\Http\Message\RequestInterface $request,
82
+                \Psr\Http\Message\ResponseInterface $response,
83
+                \Psr\Http\Message\UriInterface $uri
84
+            ) use ($options) {
85
+                $this->preventLocalAddress($uri->__toString(), $options);
86
+            };
87
+
88
+            $defaults[RequestOptions::ALLOW_REDIRECTS] = [
89
+                'on_redirect' => $onRedirectFunction
90
+            ];
91
+        }
92
+
93
+        // Only add RequestOptions::PROXY if Nextcloud is explicitly
94
+        // configured to use a proxy. This is needed in order not to override
95
+        // Guzzle default values.
96
+        if ($proxy !== null) {
97
+            $defaults[RequestOptions::PROXY] = $proxy;
98
+        }
99
+
100
+        $options = array_merge($defaults, $options);
101
+
102
+        if (!isset($options[RequestOptions::HEADERS]['User-Agent'])) {
103
+            $options[RequestOptions::HEADERS]['User-Agent'] = 'Nextcloud Server Crawler';
104
+        }
105
+
106
+        if (!isset($options[RequestOptions::HEADERS]['Accept-Encoding'])) {
107
+            $options[RequestOptions::HEADERS]['Accept-Encoding'] = 'gzip';
108
+        }
109
+
110
+        // Fallback for save_to
111
+        if (isset($options['save_to'])) {
112
+            $options['sink'] = $options['save_to'];
113
+            unset($options['save_to']);
114
+        }
115
+
116
+        return $options;
117
+    }
118
+
119
+    private function getCertBundle(): string {
120
+        // If the instance is not yet setup we need to use the static path as
121
+        // $this->certificateManager->getAbsoluteBundlePath() tries to instantiate
122
+        // a view
123
+        if ($this->config->getSystemValue('installed', false) === false) {
124
+            return \OC::$SERVERROOT . '/resources/config/ca-bundle.crt';
125
+        }
126
+
127
+        return $this->certificateManager->getAbsoluteBundlePath();
128
+    }
129
+
130
+    /**
131
+     * Returns a null or an associative array specifying the proxy URI for
132
+     * 'http' and 'https' schemes, in addition to a 'no' key value pair
133
+     * providing a list of host names that should not be proxied to.
134
+     *
135
+     * @return array|null
136
+     *
137
+     * The return array looks like:
138
+     * [
139
+     *   'http' => 'username:[email protected]',
140
+     *   'https' => 'username:[email protected]',
141
+     *   'no' => ['foo.com', 'bar.com']
142
+     * ]
143
+     *
144
+     */
145
+    private function getProxyUri(): ?array {
146
+        $proxyHost = $this->config->getSystemValue('proxy', '');
147
+
148
+        if ($proxyHost === '' || $proxyHost === null) {
149
+            return null;
150
+        }
151
+
152
+        $proxyUserPwd = $this->config->getSystemValue('proxyuserpwd', '');
153
+        if ($proxyUserPwd !== '' && $proxyUserPwd !== null) {
154
+            $proxyHost = $proxyUserPwd . '@' . $proxyHost;
155
+        }
156
+
157
+        $proxy = [
158
+            'http' => $proxyHost,
159
+            'https' => $proxyHost,
160
+        ];
161
+
162
+        $proxyExclude = $this->config->getSystemValue('proxyexclude', []);
163
+        if ($proxyExclude !== [] && $proxyExclude !== null) {
164
+            $proxy['no'] = $proxyExclude;
165
+        }
166
+
167
+        return $proxy;
168
+    }
169
+
170
+    private function isLocalAddressAllowed(array $options) : bool {
171
+        if (($options['nextcloud']['allow_local_address'] ?? false) ||
172
+            $this->config->getSystemValueBool('allow_local_remote_servers', false)) {
173
+            return true;
174
+        }
175
+
176
+        return false;
177
+    }
178
+
179
+    protected function preventLocalAddress(string $uri, array $options): void {
180
+        if ($this->isLocalAddressAllowed($options)) {
181
+            return;
182
+        }
183
+
184
+        $this->localAddressChecker->throwIfLocalAddress($uri);
185
+    }
186
+
187
+    /**
188
+     * Sends a GET request
189
+     *
190
+     * @param string $uri
191
+     * @param array $options Array such as
192
+     *              'query' => [
193
+     *                  'field' => 'abc',
194
+     *                  'other_field' => '123',
195
+     *                  'file_name' => fopen('/path/to/file', 'r'),
196
+     *              ],
197
+     *              'headers' => [
198
+     *                  'foo' => 'bar',
199
+     *              ],
200
+     *              'cookies' => ['
201
+     *                  'foo' => 'bar',
202
+     *              ],
203
+     *              'allow_redirects' => [
204
+     *                   'max'       => 10,  // allow at most 10 redirects.
205
+     *                   'strict'    => true,     // use "strict" RFC compliant redirects.
206
+     *                   'referer'   => true,     // add a Referer header
207
+     *                   'protocols' => ['https'] // only allow https URLs
208
+     *              ],
209
+     *              'sink' => '/path/to/file', // save to a file or a stream
210
+     *              'verify' => true, // bool or string to CA file
211
+     *              'debug' => true,
212
+     *              'timeout' => 5,
213
+     * @return IResponse
214
+     * @throws \Exception If the request could not get completed
215
+     */
216
+    public function get(string $uri, array $options = []): IResponse {
217
+        $this->preventLocalAddress($uri, $options);
218
+        $response = $this->client->request('get', $uri, $this->buildRequestOptions($options));
219
+        $isStream = isset($options['stream']) && $options['stream'];
220
+        return new Response($response, $isStream);
221
+    }
222
+
223
+    /**
224
+     * Sends a HEAD request
225
+     *
226
+     * @param string $uri
227
+     * @param array $options Array such as
228
+     *              'headers' => [
229
+     *                  'foo' => 'bar',
230
+     *              ],
231
+     *              'cookies' => ['
232
+     *                  'foo' => 'bar',
233
+     *              ],
234
+     *              'allow_redirects' => [
235
+     *                   'max'       => 10,  // allow at most 10 redirects.
236
+     *                   'strict'    => true,     // use "strict" RFC compliant redirects.
237
+     *                   'referer'   => true,     // add a Referer header
238
+     *                   'protocols' => ['https'] // only allow https URLs
239
+     *              ],
240
+     *              'sink' => '/path/to/file', // save to a file or a stream
241
+     *              'verify' => true, // bool or string to CA file
242
+     *              'debug' => true,
243
+     *              'timeout' => 5,
244
+     * @return IResponse
245
+     * @throws \Exception If the request could not get completed
246
+     */
247
+    public function head(string $uri, array $options = []): IResponse {
248
+        $this->preventLocalAddress($uri, $options);
249
+        $response = $this->client->request('head', $uri, $this->buildRequestOptions($options));
250
+        return new Response($response);
251
+    }
252
+
253
+    /**
254
+     * Sends a POST request
255
+     *
256
+     * @param string $uri
257
+     * @param array $options Array such as
258
+     *              'body' => [
259
+     *                  'field' => 'abc',
260
+     *                  'other_field' => '123',
261
+     *                  'file_name' => fopen('/path/to/file', 'r'),
262
+     *              ],
263
+     *              'headers' => [
264
+     *                  'foo' => 'bar',
265
+     *              ],
266
+     *              'cookies' => ['
267
+     *                  'foo' => 'bar',
268
+     *              ],
269
+     *              'allow_redirects' => [
270
+     *                   'max'       => 10,  // allow at most 10 redirects.
271
+     *                   'strict'    => true,     // use "strict" RFC compliant redirects.
272
+     *                   'referer'   => true,     // add a Referer header
273
+     *                   'protocols' => ['https'] // only allow https URLs
274
+     *              ],
275
+     *              'sink' => '/path/to/file', // save to a file or a stream
276
+     *              'verify' => true, // bool or string to CA file
277
+     *              'debug' => true,
278
+     *              'timeout' => 5,
279
+     * @return IResponse
280
+     * @throws \Exception If the request could not get completed
281
+     */
282
+    public function post(string $uri, array $options = []): IResponse {
283
+        $this->preventLocalAddress($uri, $options);
284
+
285
+        if (isset($options['body']) && is_array($options['body'])) {
286
+            $options['form_params'] = $options['body'];
287
+            unset($options['body']);
288
+        }
289
+        $response = $this->client->request('post', $uri, $this->buildRequestOptions($options));
290
+        $isStream = isset($options['stream']) && $options['stream'];
291
+        return new Response($response, $isStream);
292
+    }
293
+
294
+    /**
295
+     * Sends a PUT request
296
+     *
297
+     * @param string $uri
298
+     * @param array $options Array such as
299
+     *              'body' => [
300
+     *                  'field' => 'abc',
301
+     *                  'other_field' => '123',
302
+     *                  'file_name' => fopen('/path/to/file', 'r'),
303
+     *              ],
304
+     *              'headers' => [
305
+     *                  'foo' => 'bar',
306
+     *              ],
307
+     *              'cookies' => ['
308
+     *                  'foo' => 'bar',
309
+     *              ],
310
+     *              'allow_redirects' => [
311
+     *                   'max'       => 10,  // allow at most 10 redirects.
312
+     *                   'strict'    => true,     // use "strict" RFC compliant redirects.
313
+     *                   'referer'   => true,     // add a Referer header
314
+     *                   'protocols' => ['https'] // only allow https URLs
315
+     *              ],
316
+     *              'sink' => '/path/to/file', // save to a file or a stream
317
+     *              'verify' => true, // bool or string to CA file
318
+     *              'debug' => true,
319
+     *              'timeout' => 5,
320
+     * @return IResponse
321
+     * @throws \Exception If the request could not get completed
322
+     */
323
+    public function put(string $uri, array $options = []): IResponse {
324
+        $this->preventLocalAddress($uri, $options);
325
+        $response = $this->client->request('put', $uri, $this->buildRequestOptions($options));
326
+        return new Response($response);
327
+    }
328
+
329
+    /**
330
+     * Sends a DELETE request
331
+     *
332
+     * @param string $uri
333
+     * @param array $options Array such as
334
+     *              'body' => [
335
+     *                  'field' => 'abc',
336
+     *                  'other_field' => '123',
337
+     *                  'file_name' => fopen('/path/to/file', 'r'),
338
+     *              ],
339
+     *              'headers' => [
340
+     *                  'foo' => 'bar',
341
+     *              ],
342
+     *              'cookies' => ['
343
+     *                  'foo' => 'bar',
344
+     *              ],
345
+     *              'allow_redirects' => [
346
+     *                   'max'       => 10,  // allow at most 10 redirects.
347
+     *                   'strict'    => true,     // use "strict" RFC compliant redirects.
348
+     *                   'referer'   => true,     // add a Referer header
349
+     *                   'protocols' => ['https'] // only allow https URLs
350
+     *              ],
351
+     *              'sink' => '/path/to/file', // save to a file or a stream
352
+     *              'verify' => true, // bool or string to CA file
353
+     *              'debug' => true,
354
+     *              'timeout' => 5,
355
+     * @return IResponse
356
+     * @throws \Exception If the request could not get completed
357
+     */
358
+    public function delete(string $uri, array $options = []): IResponse {
359
+        $this->preventLocalAddress($uri, $options);
360
+        $response = $this->client->request('delete', $uri, $this->buildRequestOptions($options));
361
+        return new Response($response);
362
+    }
363
+
364
+    /**
365
+     * Sends a options request
366
+     *
367
+     * @param string $uri
368
+     * @param array $options Array such as
369
+     *              'body' => [
370
+     *                  'field' => 'abc',
371
+     *                  'other_field' => '123',
372
+     *                  'file_name' => fopen('/path/to/file', 'r'),
373
+     *              ],
374
+     *              'headers' => [
375
+     *                  'foo' => 'bar',
376
+     *              ],
377
+     *              'cookies' => ['
378
+     *                  'foo' => 'bar',
379
+     *              ],
380
+     *              'allow_redirects' => [
381
+     *                   'max'       => 10,  // allow at most 10 redirects.
382
+     *                   'strict'    => true,     // use "strict" RFC compliant redirects.
383
+     *                   'referer'   => true,     // add a Referer header
384
+     *                   'protocols' => ['https'] // only allow https URLs
385
+     *              ],
386
+     *              'sink' => '/path/to/file', // save to a file or a stream
387
+     *              'verify' => true, // bool or string to CA file
388
+     *              'debug' => true,
389
+     *              'timeout' => 5,
390
+     * @return IResponse
391
+     * @throws \Exception If the request could not get completed
392
+     */
393
+    public function options(string $uri, array $options = []): IResponse {
394
+        $this->preventLocalAddress($uri, $options);
395
+        $response = $this->client->request('options', $uri, $this->buildRequestOptions($options));
396
+        return new Response($response);
397
+    }
398 398
 }
Please login to merge, or discard this patch.
lib/private/Http/Client/DnsPinMiddleware.php 1 patch
Indentation   +119 added lines, -119 removed lines patch added patch discarded remove patch
@@ -28,123 +28,123 @@
 block discarded – undo
28 28
 use Psr\Http\Message\RequestInterface;
29 29
 
30 30
 class DnsPinMiddleware {
31
-	/** @var NegativeDnsCache */
32
-	private $negativeDnsCache;
33
-	/** @var LocalAddressChecker */
34
-	private $localAddressChecker;
35
-
36
-	public function __construct(
37
-		NegativeDnsCache $negativeDnsCache,
38
-		LocalAddressChecker $localAddressChecker
39
-	) {
40
-		$this->negativeDnsCache = $negativeDnsCache;
41
-		$this->localAddressChecker = $localAddressChecker;
42
-	}
43
-
44
-	/**
45
-	 * Fetch soa record for a target
46
-	 *
47
-	 * @param string $target
48
-	 * @return array|null
49
-	 */
50
-	private function soaRecord(string $target): ?array {
51
-		$labels = explode('.', $target);
52
-
53
-		$top = count($labels) >= 2 ? array_pop($labels) : '';
54
-		$second = array_pop($labels);
55
-
56
-		$hostname = $second . '.' . $top;
57
-		$responses = dns_get_record($hostname, DNS_SOA);
58
-
59
-		if ($responses === false || count($responses) === 0) {
60
-			return null;
61
-		}
62
-
63
-		return reset($responses);
64
-	}
65
-
66
-	private function dnsResolve(string $target, int $recursionCount) : array {
67
-		if ($recursionCount >= 10) {
68
-			return [];
69
-		}
70
-
71
-		$recursionCount++;
72
-		$targetIps = [];
73
-
74
-		$soaDnsEntry = $this->soaRecord($target);
75
-		$dnsNegativeTtl = $soaDnsEntry['minimum-ttl'] ?? null;
76
-
77
-		$dnsTypes = [DNS_A, DNS_AAAA, DNS_CNAME];
78
-		foreach ($dnsTypes as $dnsType) {
79
-			if ($this->negativeDnsCache->isNegativeCached($target, $dnsType)) {
80
-				continue;
81
-			}
82
-
83
-			$dnsResponses = dns_get_record($target, $dnsType);
84
-			$canHaveCnameRecord = true;
85
-			if ($dnsResponses !== false && count($dnsResponses) > 0) {
86
-				foreach ($dnsResponses as $dnsResponse) {
87
-					if (isset($dnsResponse['ip'])) {
88
-						$targetIps[] = $dnsResponse['ip'];
89
-						$canHaveCnameRecord = false;
90
-					} elseif (isset($dnsResponse['ipv6'])) {
91
-						$targetIps[] = $dnsResponse['ipv6'];
92
-						$canHaveCnameRecord = false;
93
-					} elseif (isset($dnsResponse['target']) && $canHaveCnameRecord) {
94
-						$targetIps = array_merge($targetIps, $this->dnsResolve($dnsResponse['target'], $recursionCount));
95
-						$canHaveCnameRecord = true;
96
-					}
97
-				}
98
-			} elseif ($dnsNegativeTtl !== null) {
99
-				$this->negativeDnsCache->setNegativeCacheForDnsType($target, $dnsType, $dnsNegativeTtl);
100
-			}
101
-		}
102
-
103
-		return $targetIps;
104
-	}
105
-
106
-	public function addDnsPinning() {
107
-		return function (callable $handler) {
108
-			return function (
109
-				RequestInterface $request,
110
-				array $options
111
-			) use ($handler) {
112
-				if ($options['nextcloud']['allow_local_address'] === true) {
113
-					return $handler($request, $options);
114
-				}
115
-
116
-				$hostName = (string)$request->getUri()->getHost();
117
-				$port = $request->getUri()->getPort();
118
-
119
-				$ports = [
120
-					'80',
121
-					'443',
122
-				];
123
-
124
-				if ($port !== null) {
125
-					$ports[] = (string)$port;
126
-				}
127
-
128
-				$targetIps = $this->dnsResolve(idn_to_utf8($hostName), 0);
129
-
130
-				$curlResolves = [];
131
-
132
-				foreach ($ports as $port) {
133
-					$curlResolves["$hostName:$port"] = [];
134
-
135
-					foreach ($targetIps as $ip) {
136
-						$this->localAddressChecker->throwIfLocalIp($ip);
137
-						$curlResolves["$hostName:$port"][] = $ip;
138
-					}
139
-				}
140
-
141
-				// Coalesce the per-host:port ips back into a comma separated list
142
-				foreach ($curlResolves as $hostport => $ips) {
143
-					$options['curl'][CURLOPT_RESOLVE][] = "$hostport:" . implode(',', $ips);
144
-				}
145
-
146
-				return $handler($request, $options);
147
-			};
148
-		};
149
-	}
31
+    /** @var NegativeDnsCache */
32
+    private $negativeDnsCache;
33
+    /** @var LocalAddressChecker */
34
+    private $localAddressChecker;
35
+
36
+    public function __construct(
37
+        NegativeDnsCache $negativeDnsCache,
38
+        LocalAddressChecker $localAddressChecker
39
+    ) {
40
+        $this->negativeDnsCache = $negativeDnsCache;
41
+        $this->localAddressChecker = $localAddressChecker;
42
+    }
43
+
44
+    /**
45
+     * Fetch soa record for a target
46
+     *
47
+     * @param string $target
48
+     * @return array|null
49
+     */
50
+    private function soaRecord(string $target): ?array {
51
+        $labels = explode('.', $target);
52
+
53
+        $top = count($labels) >= 2 ? array_pop($labels) : '';
54
+        $second = array_pop($labels);
55
+
56
+        $hostname = $second . '.' . $top;
57
+        $responses = dns_get_record($hostname, DNS_SOA);
58
+
59
+        if ($responses === false || count($responses) === 0) {
60
+            return null;
61
+        }
62
+
63
+        return reset($responses);
64
+    }
65
+
66
+    private function dnsResolve(string $target, int $recursionCount) : array {
67
+        if ($recursionCount >= 10) {
68
+            return [];
69
+        }
70
+
71
+        $recursionCount++;
72
+        $targetIps = [];
73
+
74
+        $soaDnsEntry = $this->soaRecord($target);
75
+        $dnsNegativeTtl = $soaDnsEntry['minimum-ttl'] ?? null;
76
+
77
+        $dnsTypes = [DNS_A, DNS_AAAA, DNS_CNAME];
78
+        foreach ($dnsTypes as $dnsType) {
79
+            if ($this->negativeDnsCache->isNegativeCached($target, $dnsType)) {
80
+                continue;
81
+            }
82
+
83
+            $dnsResponses = dns_get_record($target, $dnsType);
84
+            $canHaveCnameRecord = true;
85
+            if ($dnsResponses !== false && count($dnsResponses) > 0) {
86
+                foreach ($dnsResponses as $dnsResponse) {
87
+                    if (isset($dnsResponse['ip'])) {
88
+                        $targetIps[] = $dnsResponse['ip'];
89
+                        $canHaveCnameRecord = false;
90
+                    } elseif (isset($dnsResponse['ipv6'])) {
91
+                        $targetIps[] = $dnsResponse['ipv6'];
92
+                        $canHaveCnameRecord = false;
93
+                    } elseif (isset($dnsResponse['target']) && $canHaveCnameRecord) {
94
+                        $targetIps = array_merge($targetIps, $this->dnsResolve($dnsResponse['target'], $recursionCount));
95
+                        $canHaveCnameRecord = true;
96
+                    }
97
+                }
98
+            } elseif ($dnsNegativeTtl !== null) {
99
+                $this->negativeDnsCache->setNegativeCacheForDnsType($target, $dnsType, $dnsNegativeTtl);
100
+            }
101
+        }
102
+
103
+        return $targetIps;
104
+    }
105
+
106
+    public function addDnsPinning() {
107
+        return function (callable $handler) {
108
+            return function (
109
+                RequestInterface $request,
110
+                array $options
111
+            ) use ($handler) {
112
+                if ($options['nextcloud']['allow_local_address'] === true) {
113
+                    return $handler($request, $options);
114
+                }
115
+
116
+                $hostName = (string)$request->getUri()->getHost();
117
+                $port = $request->getUri()->getPort();
118
+
119
+                $ports = [
120
+                    '80',
121
+                    '443',
122
+                ];
123
+
124
+                if ($port !== null) {
125
+                    $ports[] = (string)$port;
126
+                }
127
+
128
+                $targetIps = $this->dnsResolve(idn_to_utf8($hostName), 0);
129
+
130
+                $curlResolves = [];
131
+
132
+                foreach ($ports as $port) {
133
+                    $curlResolves["$hostName:$port"] = [];
134
+
135
+                    foreach ($targetIps as $ip) {
136
+                        $this->localAddressChecker->throwIfLocalIp($ip);
137
+                        $curlResolves["$hostName:$port"][] = $ip;
138
+                    }
139
+                }
140
+
141
+                // Coalesce the per-host:port ips back into a comma separated list
142
+                foreach ($curlResolves as $hostport => $ips) {
143
+                    $options['curl'][CURLOPT_RESOLVE][] = "$hostport:" . implode(',', $ips);
144
+                }
145
+
146
+                return $handler($request, $options);
147
+            };
148
+        };
149
+    }
150 150
 }
Please login to merge, or discard this patch.