Completed
Push — master ( 282f78...6dce89 )
by Sam
05:20
created

AbstractResolver::extractRdata()   C

Complexity

Conditions 12
Paths 12

Size

Total Lines 40
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 35
nc 12
nop 3
dl 0
loc 40
rs 6.9666
c 0
b 0
f 0

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
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
    public function getAnswer(array $queries): array
40
    {
41
        $answers = [];
42
        foreach ($queries as $query) {
43
            $answer = $this->resourceRecords[$query->getName()][$query->getType()][$query->getClass()] ?? [];
44
            if (empty($answer)) {
45
                $answer = $this->findWildcardEntry($query);
46
            }
47
48
            $answers = array_merge($answers, $answer);
49
        }
50
51
        return $answers;
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57
    public function allowsRecursion(): bool
58
    {
59
        return $this->allowRecursion;
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65
    public function isAuthority($domain): bool
66
    {
67
        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
    public function isWildcardDomain(string $domain): bool
78
    {
79
        $domain = rtrim($domain, '.').'.';
80
        $pattern = '/^\*\.(?:[a-zA-Z0-9\-\_]+\.)*$/';
81
82
        return preg_match($pattern, $domain);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match($pattern, $domain) returns the type integer which is incompatible with the type-hinted return boolean.
Loading history...
83
    }
84
85
    /**
86
     * @param ResourceRecord[] $resourceRecords
87
     */
88
    protected function addZone(array $resourceRecords): void
89
    {
90
        foreach ($resourceRecords as $resourceRecord) {
91
            if ($this->isWildcardDomain($resourceRecord->getName())) {
92
                $this->addWildcardRecord($resourceRecord);
93
                continue;
94
            }
95
            $this->resourceRecords[$resourceRecord->getName()][$resourceRecord->getType()][$resourceRecord->getClass()][] = $resourceRecord;
96
        }
97
    }
98
99
    /**
100
     * Add a wildcard ResourceRecord.
101
     *
102
     * @param ResourceRecord $resourceRecord
103
     */
104
    protected function addWildcardRecord(ResourceRecord $resourceRecord): void
105
    {
106
        $labels = explode('.', rtrim($resourceRecord->getName(), '.'));
107
        $labels = array_reverse($labels);
108
109
        $array = &$this->wildcardRecords;
110
        foreach ($labels as $label) {
111
            if ('*' === $label) {
112
                $array[$label][$resourceRecord->getClass()][$resourceRecord->getType()][] = $resourceRecord;
113
                break;
114
            }
115
116
            $array = &$array[$label];
117
        }
118
    }
119
120
    /**
121
     * @param ResourceRecord $query
122
     *
123
     * @return array
124
     */
125
    protected function findWildcardEntry(ResourceRecord $query): array
126
    {
127
        $labels = explode('.', rtrim($query->getName(), '.'));
128
        $labels = array_reverse($labels);
129
130
        /** @var ResourceRecord[] $wildcards */
131
        $wildcards = [];
132
        $array = &$this->wildcardRecords;
133
        foreach ($labels as $label) {
134
            if (array_key_exists($label, $array)) {
135
                $array = &$array[$label];
136
                continue;
137
            }
138
139
            if (array_key_exists('*', $array)) {
140
                $wildcards = $array['*'][$query->getClass()][$query->getType()] ?? null;
141
            }
142
        }
143
144
        $answers = [];
145
        foreach ($wildcards as $wildcard) {
146
            $rr = clone $wildcard;
147
            $rr->setName($query->getName());
148
            $answers[] = $rr;
149
        }
150
151
        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
    protected function handleName(string $name, string $parent)
167
    {
168
        if ('@' === $name || '' === $name) {
169
            return $parent;
170
        }
171
172
        if ('.' !== substr($name, -1, 1)) {
173
            return $name.'.'.$parent;
174
        }
175
176
        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
    protected function extractRdata(array $resourceRecord, int $type, string $parent)
189
    {
190
        switch ($type) {
191
            case RecordTypeEnum::TYPE_A:
192
            case RecordTypeEnum::TYPE_AAAA:
193
                return $resourceRecord['address'];
194
            case RecordTypeEnum::TYPE_NS:
195
            case RecordTypeEnum::TYPE_CNAME:
196
            case RecordTypeEnum::TYPE_PTR:
197
                return $this->handleName($resourceRecord['target'], $parent);
198
            case RecordTypeEnum::TYPE_SOA:
199
                return [
200
                    'mname' => $this->handleName($resourceRecord['mname'], $parent),
201
                    'rname' => $this->handleName($resourceRecord['rname'], $parent),
202
                    'serial' => $resourceRecord['serial'],
203
                    'refresh' => $resourceRecord['refresh'],
204
                    'retry' => $resourceRecord['retry'],
205
                    'expire' => $resourceRecord['expire'],
206
                    'minimum' => $resourceRecord['minimum'],
207
                ];
208
            case RecordTypeEnum::TYPE_MX:
209
                return [
210
                    'preference' => $resourceRecord['preference'],
211
                    'exchange' => $this->handleName($resourceRecord['exchange'], $parent),
212
                ];
213
            case RecordTypeEnum::TYPE_TXT:
214
                return $resourceRecord['text'];
215
            case RecordTypeEnum::TYPE_SRV:
216
                return [
217
                    'priority' => (int) $resourceRecord['priority'],
218
                    'weight' => (int) $resourceRecord['weight'],
219
                    'port' => (int) $resourceRecord['port'],
220
                    '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