Completed
Push — master ( ecf71f...65af6f )
by Sam
09:47
created

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