Completed
Push — master ( 484f58...46ef65 )
by Sam
03:14
created

AbstractResolver   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 219
Duplicated Lines 0 %

Test Coverage

Coverage 96.1%

Importance

Changes 0
Metric Value
eloc 86
dl 0
loc 219
ccs 74
cts 77
cp 0.961
rs 9.76
c 0
b 0
f 0
wmc 33

9 Methods

Rating   Name   Duplication   Size   Complexity  
A findWildcardEntry() 0 27 5
A isWildcardDomain() 0 6 1
A addWildcardRecord() 0 13 3
A isAuthority() 0 3 1
A getAnswer() 0 13 3
A handleName() 0 11 4
A allowsRecursion() 0 3 1
A addZone() 0 8 3
C extractRdata() 0 40 12
1
<?php
2
3
/*
4
 * This file is part of PHP DNS Server.
5
 *
6
 * (c) Yif Swery <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace yswery\DNS\Resolver;
13
14
use yswery\DNS\ResourceRecord;
15
use yswery\DNS\RecordTypeEnum;
16
use yswery\DNS\UnsupportedTypeException;
17
18
abstract class AbstractResolver implements ResolverInterface
19
{
20
    /**
21
     * @var bool
22
     */
23
    protected $allowRecursion;
24
25
    /**
26
     * @var bool
27
     */
28
    protected $isAuthoritative;
29
30
    /**
31
     * @var ResourceRecord[]
32
     */
33
    protected $resourceRecords = [];
34
35
    /**
36
     * Wildcard records are stored as an associative array of labels in reverse. E.g.
37
     * ResourceRecord for "*.example.com." is stored as ['com']['example']['*'][<CLASS>][<TYPE>][].
38
     *
39
     * @var ResourceRecord[]
40
     */
41
    protected $wildcardRecords = [];
42
43
    /**
44
     * @param ResourceRecord[] $queries
45
     *
46
     * @return array
47
     */
48 28
    public function getAnswer(array $queries): array
49
    {
50 28
        $answers = [];
51 28
        foreach ($queries as $query) {
52 27
            $answer = $this->resourceRecords[$query->getName()][$query->getType()][$query->getClass()] ?? [];
53 27
            if (empty($answer)) {
54 11
                $answer = $this->findWildcardEntry($query);
55
            }
56
57 27
            $answers = array_merge($answers, $answer);
58
        }
59
60 28
        return $answers;
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 10
    public function allowsRecursion(): bool
67
    {
68 10
        return $this->allowRecursion;
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74 9
    public function isAuthority($domain): bool
75
    {
76 9
        return $this->isAuthoritative;
77
    }
78
79
    /**
80
     * Determine if a domain is a wildcard domain.
81
     *
82
     * @param string $domain
83
     *
84
     * @return bool
85
     */
86 42
    public function isWildcardDomain(string $domain): bool
87
    {
88 42
        $domain = rtrim($domain, '.').'.';
89 42
        $pattern = '/^\*\.(?:[a-zA-Z0-9\-\_]+\.)*$/';
90
91 42
        return (bool) preg_match($pattern, $domain);
92
    }
93
94
    /**
95
     * @param ResourceRecord[] $resourceRecords
96
     */
97 42
    protected function addZone(array $resourceRecords): void
98
    {
99 42
        foreach ($resourceRecords as $resourceRecord) {
100 42
            if ($this->isWildcardDomain($resourceRecord->getName())) {
101 42
                $this->addWildcardRecord($resourceRecord);
102 42
                continue;
103
            }
104 42
            $this->resourceRecords[$resourceRecord->getName()][$resourceRecord->getType()][$resourceRecord->getClass()][] = $resourceRecord;
105
        }
106 42
    }
107
108
    /**
109
     * Add a wildcard ResourceRecord.
110
     *
111
     * @param ResourceRecord $resourceRecord
112
     */
113 42
    protected function addWildcardRecord(ResourceRecord $resourceRecord): void
114
    {
115 42
        $labels = explode('.', rtrim($resourceRecord->getName(), '.'));
116 42
        $labels = array_reverse($labels);
117
118 42
        $array = &$this->wildcardRecords;
119 42
        foreach ($labels as $label) {
120 42
            if ('*' === $label) {
121 42
                $array[$label][$resourceRecord->getClass()][$resourceRecord->getType()][] = $resourceRecord;
122 42
                break;
123
            }
124
125 42
            $array = &$array[$label];
126
        }
127 42
    }
128
129
    /**
130
     * @param ResourceRecord $query
131
     *
132
     * @return array
133
     */
134 11
    protected function findWildcardEntry(ResourceRecord $query): array
135
    {
136 11
        $labels = explode('.', rtrim($query->getName(), '.'));
137 11
        $labels = array_reverse($labels);
138
139
        /** @var ResourceRecord[] $wildcards */
140 11
        $wildcards = [];
141 11
        $array = &$this->wildcardRecords;
142 11
        foreach ($labels as $label) {
143 11
            if (array_key_exists($label, $array)) {
144 11
                $array = &$array[$label];
145 11
                continue;
146
            }
147
148 11
            if (array_key_exists('*', $array)) {
149 4
                $wildcards = $array['*'][$query->getClass()][$query->getType()] ?? [];
150
            }
151
        }
152
153 11
        $answers = [];
154 11
        foreach ($wildcards as $wildcard) {
155 4
            $rr = clone $wildcard;
156 4
            $rr->setName($query->getName());
157 4
            $answers[] = $rr;
158
        }
159
160 11
        return $answers;
161
    }
162
163
    /**
164
     * Add the parent domain to names that are not fully qualified.
165
     *
166
     * AbstractResolver::handleName('www', 'example.com.') //Outputs 'www.example.com.'
167
     * AbstractResolver::handleName('@', 'example.com.') //Outputs 'example.com.'
168
     * AbstractResolver::handleName('ns1.example.com.', 'example.com.') //Outputs 'ns1.example.com.'
169
     *
170
     * @param $name
171
     * @param $parent
172
     *
173
     * @return string
174
     */
175 42
    protected function handleName(string $name, string $parent)
176
    {
177 42
        if ('@' === $name || '' === $name) {
178 42
            return $parent;
179
        }
180
181 42
        if ('.' !== substr($name, -1, 1)) {
182 42
            return $name.'.'.$parent;
183
        }
184
185 42
        return $name;
186
    }
187
188
    /**
189
     * @param array  $resourceRecord
190
     * @param int    $type
191
     * @param string $parent
192
     *
193
     * @return mixed
194
     *
195
     * @throws UnsupportedTypeException
196
     */
197 42
    protected function extractRdata(array $resourceRecord, int $type, string $parent)
198
    {
199 42
        switch ($type) {
200
            case RecordTypeEnum::TYPE_A:
201
            case RecordTypeEnum::TYPE_AAAA:
202 42
                return $resourceRecord['address'];
203
            case RecordTypeEnum::TYPE_NS:
204
            case RecordTypeEnum::TYPE_CNAME:
205
            case RecordTypeEnum::TYPE_PTR:
206 42
                return $this->handleName($resourceRecord['target'], $parent);
207
            case RecordTypeEnum::TYPE_SOA:
208
                return [
209 42
                    'mname' => $this->handleName($resourceRecord['mname'], $parent),
210 42
                    'rname' => $this->handleName($resourceRecord['rname'], $parent),
211 42
                    'serial' => $resourceRecord['serial'],
212 42
                    'refresh' => $resourceRecord['refresh'],
213 42
                    'retry' => $resourceRecord['retry'],
214 42
                    'expire' => $resourceRecord['expire'],
215 42
                    'minimum' => $resourceRecord['minimum'],
216
                ];
217
            case RecordTypeEnum::TYPE_MX:
218
                return [
219 42
                    'preference' => $resourceRecord['preference'],
220 42
                    'exchange' => $this->handleName($resourceRecord['exchange'], $parent),
221
                ];
222
            case RecordTypeEnum::TYPE_TXT:
223 23
                return $resourceRecord['text'];
224
            case RecordTypeEnum::TYPE_SRV:
225
                return [
226 42
                    'priority' => (int) $resourceRecord['priority'],
227 42
                    'weight' => (int) $resourceRecord['weight'],
228 42
                    'port' => (int) $resourceRecord['port'],
229 42
                    'target' => $this->handleName($resourceRecord['target'], $parent),
230
                ];
231
            case RecordTypeEnum::TYPE_AXFR:
232
            case RecordTypeEnum::TYPE_ANY:
233
                return '';
234
            default:
235
                throw new UnsupportedTypeException(
236
                    sprintf('Resource Record type "%s" is not a supported type.', RecordTypeEnum::getName($type))
237
                );
238
        }
239
    }
240
}
241