Test Setup Failed
Push — master ( 46ef65...ecf71f )
by Sam
05:21
created

AbstractResolver::supportsSaving()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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