Completed
Push — master ( 945669...6fba83 )
by Sam
01:50
created

Parser::handleLocRdata()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 12
ccs 10
cts 10
cp 1
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Badcow\DNS\Parser;
4
5
use Badcow\DNS\Classes;
6
use Badcow\DNS\ResourceRecord;
7
use Badcow\DNS\Zone;
8
use Badcow\DNS\Rdata;
9
use Badcow\DNS\ZoneInterface;
10
use LTDBeget\ascii\AsciiChar;
11
use Badcow\DNS\Rdata\UnsupportedTypeException;
12
13
class Parser
14
{
15
    /**
16
     * @var string
17
     */
18
    private $string;
19
20
    /**
21
     * @var string
22
     */
23
    private $previousName;
24
25
    /**
26
     * @var ZoneInterface
27
     */
28
    private $zone;
29
30
    /**
31
     * @param string $name
32
     * @param string $zone
33
     *
34
     * @return ZoneInterface
35
     *
36
     * @throws ParseException
37
     * @throws UnsupportedTypeException
38
     */
39 4
    public static function parse(string $name, string $zone): ZoneInterface
40
    {
41 4
        $parser = new self();
42
43 4
        return $parser->makeZone($name, $zone);
44
    }
45
46
    /**
47
     * @param $name
48
     * @param $string
49
     *
50
     * @return ZoneInterface
51
     *
52
     * @throws ParseException
53
     * @throws UnsupportedTypeException
54
     */
55 4
    public function makeZone($name, $string): ZoneInterface
56
    {
57 4
        $this->zone = new Zone($name);
58 4
        $this->string = Normaliser::normalise($string);
59
60 4
        foreach (explode("\n", $this->string) as $line) {
61 4
            $this->processLine($line);
62
        }
63
64 3
        return $this->zone;
65
    }
66
67
    /**
68
     * @param string $line
69
     *
70
     * @throws UnsupportedTypeException
71
     * @throws ParseException
72
     */
73 4
    private function processLine(string $line)
74
    {
75 4
        $iterator = new \ArrayIterator(explode(' ', $line));
76
77 4
        if (1 === preg_match('/^\$[A-Z0-9]+/i', $iterator->current())) {
78 3
            $this->processControlEntry($iterator);
79 3
            return;
80
        }
81
82 4
        $resourceRecord = new ResourceRecord();
83
84
        if (
85 4
            1 === preg_match('/^\d+$/', $iterator->current()) ||
86 4
            Classes::isValid(strtoupper($iterator->current())) ||
87 4
            RDataTypes::isValid(strtoupper($iterator->current()))
88
        ) {
89 1
            $resourceRecord->setName($this->previousName);
90
        } else {
91 4
            $resourceRecord->setName($iterator->current());
92 4
            $this->previousName = $iterator->current();
93 4
            $iterator->next();
94
        }
95
96 4
        $this->processTtl($iterator, $resourceRecord);
97 4
        $this->processClass($iterator, $resourceRecord);
98 4
        $resourceRecord->setRdata($this->extractRdata($iterator));
99
100 3
        $this->zone->addResourceRecord($resourceRecord);
101 3
    }
102
103
    /**
104
     * Processes control entries at the top of a BIND record, i.e. $ORIGIN, $TTL, $INCLUDE, etc.
105
     *
106
     * @param \ArrayIterator $iterator
107
     */
108 3
    private function processControlEntry(\ArrayIterator $iterator): void
109
    {
110 3
        if ('$TTL' === strtoupper($iterator->current())) {
111 3
            $iterator->next();
112 3
            $this->zone->setDefaultTtl((int) $iterator->current());
113
        }
114 3
    }
115
116
    /**
117
     * Set RR's TTL if there is one.
118
     *
119
     * @param \ArrayIterator $iterator
120
     * @param ResourceRecord $resourceRecord
121
     */
122 4
    private function processTtl(\ArrayIterator $iterator, ResourceRecord $resourceRecord)
123
    {
124 4
        if (1 === preg_match('/^\d+$/', $iterator->current())) {
125 2
            $resourceRecord->setTtl($iterator->current());
126 2
            $iterator->next();
127
        }
128 4
    }
129
130
    /**
131
     * Set RR's class if there is one.
132
     *
133
     * @param \ArrayIterator $iterator
134
     * @param ResourceRecord $resourceRecord
135
     */
136 4
    private function processClass(\ArrayIterator $iterator, ResourceRecord $resourceRecord)
137
    {
138 4
        if (Classes::isValid(strtoupper($iterator->current()))) {
139 3
            $resourceRecord->setClass(strtoupper($iterator->current()));
140 3
            $iterator->next();
141
        }
142 4
    }
143
144
    /**
145
     * @param \ArrayIterator $iterator
146
     *
147
     * @return RData\RDataInterface
148
     *
149
     * @throws UnsupportedTypeException
150
     * @throws ParseException
151
     */
152 4
    private function extractRdata(\ArrayIterator $iterator): Rdata\RdataInterface
153
    {
154 4
        $type = strtoupper($iterator->current());
155 4
        $iterator->next();
156
157 4
        if (!Rdata\Factory::isTypeImplemented($type)) {
158 1
            throw new UnsupportedTypeException($type);
159
        }
160
161 3
        if (Rdata\LOC::TYPE === $type) {
162 1
            return $this->handleLocRdata($iterator);
163
        }
164
165 3
        if (Rdata\TXT::TYPE === $type) {
166 2
            return $this->handleTxtRdata($iterator);
167
        }
168
169 3
        $parameters = [];
170
171 3
        while ($iterator->valid()) {
172 3
            $parameters[] = $this->pop($iterator);
173
        }
174
175 3
        return call_user_func_array(['\\Badcow\\DNS\\Rdata\\Factory', $type], $parameters);
176
    }
177
178
    /**
179
     * @param \ArrayIterator $iterator
180
     *
181
     * @return Rdata\TXT
182
     *
183
     * @throws ParseException
184
     */
185 2
    private function handleTxtRdata(\ArrayIterator $iterator): Rdata\TXT
186
    {
187 2
        $txt = '';
188 2
        while ($iterator->valid()) {
189 2
            $txt .= $this->pop($iterator).' ';
190
        }
191 2
        $txt = substr($txt, 0, -1);
192
193 2
        $string = new StringIterator($txt);
194 2
        $txt = '';
195 2
        $doubleQuotesOpen = false;
196
197 2
        while ($string->valid()) {
198 2
            switch ($string->ord()) {
199 2
                case AsciiChar::BACKSLASH:
200 2
                    $string->next();
201 2
                    $txt .= $string->current();
202 2
                    $string->next();
203 2
                    break;
204 2
                case AsciiChar::DOUBLE_QUOTES:
205 2
                    $doubleQuotesOpen = !$doubleQuotesOpen;
0 ignored issues
show
introduced by
The condition $doubleQuotesOpen is always false.
Loading history...
206 2
                    $string->next();
207 2
                    break;
208
                default:
209 2
                    if ($doubleQuotesOpen) {
210 2
                        $txt .= $string->current();
211
                    }
212 2
                    $string->next();
213 2
                    break;
214
            }
215
        }
216
217 2
        if ($doubleQuotesOpen) {
218
            throw new ParseException('Unbalanced double quotation marks.');
219
        }
220
221 2
        return Rdata\Factory::txt($txt);
222
    }
223
224
    /**
225
     * Return current entry and moves the iterator to the next entry.
226
     *
227
     * @param \ArrayIterator $arrayIterator
228
     *
229
     * @return mixed
230
     */
231 3
    private function pop(\ArrayIterator $arrayIterator)
232
    {
233 3
        $current = $arrayIterator->current();
234 3
        $arrayIterator->next();
235
236 3
        return $current;
237
    }
238
239
    /**
240
     * Transform a DMS string to a decimal representation. Used for LOC records.
241
     *
242
     * @param int    $deg
243
     * @param int    $m
244
     * @param float  $s
245
     * @param string $hemi
246
     *
247
     * @return float
248
     */
249 1
    private function dmsToDecimal(int $deg, int $m, float $s, string $hemi): float
250
    {
251 1
        $multiplier = ('S' === $hemi || 'W' === $hemi) ? -1 : 1;
252
253 1
        return $multiplier * ($deg + ($m / 60) + ($s / 3600));
254
    }
255
256
    /**
257
     * @param \ArrayIterator $a
258
     * @return Rdata\LOC
259
     */
260 1
    private function handleLocRdata(\ArrayIterator $a): Rdata\LOC
261
    {
262 1
        $lat = $this->dmsToDecimal($this->pop($a), $this->pop($a), $this->pop($a), $this->pop($a));
263 1
        $lon = $this->dmsToDecimal($this->pop($a), $this->pop($a), $this->pop($a), $this->pop($a));
264
265 1
        return Rdata\Factory::Loc(
266 1
            $lat,
267 1
            $lon,
268 1
            (float) $this->pop($a),
269 1
            (float) $this->pop($a),
270 1
            (float) $this->pop($a),
271 1
            (float) $this->pop($a)
272
        );
273
    }
274
}
275