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