Passed
Push — master ( 5e7206...9f04a7 )
by Daniel
32:50 queued 17:09
created

DnsPinMiddleware::dnsResolve()   B

Complexity

Conditions 11
Paths 6

Size

Total Lines 38
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 26
c 1
b 0
f 0
nc 6
nop 2
dl 0
loc 38
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2021, Lukas Reschke <[email protected]>
7
 *
8
 * @author Lukas Reschke <[email protected]>
9
 *
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
namespace OC\Http\Client;
27
28
use Psr\Http\Message\RequestInterface;
29
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 = $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 (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($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
}
151