Passed
Push — master ( 302393...f8e643 )
by Sam
02:04
created

Parser::parse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
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\Parser\RData as RDataEnum;
9
use Badcow\DNS\Rdata;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Badcow\DNS\Parser\Rdata. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use Badcow\DNS\ZoneInterface;
11
use LTDBeget\ascii\AsciiChar;
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 3
    public static function parse(string $name, string $zone): ZoneInterface
40
    {
41 3
        $parser = new self();
42
43 3
        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 3
    public function makeZone($name, $string): ZoneInterface
56
    {
57 3
        $this->zone = new Zone($name);
58 3
        $this->string = Normaliser::normalise($string);
59 3
        $lines = explode("\n", $this->string);
60
61 3
        foreach ($lines as $line) {
62 3
            $this->processLine($line);
63
        }
64
65 2
        return $this->zone;
66
    }
67
68
    /**
69
     * @param string $line
70
     *
71
     * @throws UnsupportedTypeException
72
     * @throws ParseException
73
     */
74 3
    private function processLine(string $line)
75
    {
76 3
        if (1 === preg_match('/^\$ORIGIN/i', strtoupper($line))) {
77 2
            return;
78
        }
79
80 3
        $matches = [];
81 3
        if (1 === preg_match('/^\$TTL (\d+)/i', strtoupper($line), $matches)) {
82 2
            $ttl = (int) $matches[1];
83 2
            $this->zone->setDefaultTtl($ttl);
84
85 2
            return;
86
        }
87
88 3
        $parts = explode(' ', $line);
89 3
        $iterator = new \ArrayIterator($parts);
90 3
        $record = new ResourceRecord();
91
92
        // Is it a TTL?
93 3
        if (1 === preg_match('/^\d+$/', $iterator->current())) {
94 1
            $record->setName($this->previousName);
95 1
            goto ttl;
96
        }
97
98
        // Is it a valid class?
99 3
        if (Classes::isValid(strtoupper($iterator->current()))) {
100 1
            $record->setName($this->previousName);
101 1
            goto _class;
102
        }
103
104
        // Is it a valid RDATA type?
105 3
        if (RDataEnum::isValid(strtoupper($iterator->current()))) {
106 1
            $record->setName($this->previousName);
107 1
            goto type;
108
        }
109
110 3
        $record->setName($iterator->current());
111 3
        $this->previousName = $iterator->current();
112 3
        $iterator->next();
113
114
        ttl:
115 3
        $matches = [];
116 3
        if (1 === preg_match('/^(\d+)$/', $iterator->current(), $matches)) {
117 2
            $ttl = (int) $matches[1];
118 2
            $record->setTtl($ttl);
119 2
            $iterator->next();
120
        }
121
122
        _class:
123 3
        if (Classes::isValid(strtoupper($iterator->current()))) {
124 2
            $record->setClass(strtoupper($iterator->current()));
125 2
            $iterator->next();
126
        }
127
128
        type:
129 3
        if (!RDataEnum::isValid(strtoupper($iterator->current()))) {
130 1
            throw new UnsupportedTypeException($iterator->current());
131
        }
132
133 2
        $rdata = $this->extractRdata($iterator);
134 2
        $record->setRdata($rdata);
135
136 2
        $this->zone->addResourceRecord($record);
137 2
    }
138
139
    /**
140
     * @param \ArrayIterator $a
141
     *
142
     * @return RData\RDataInterface
143
     *
144
     * @throws UnsupportedTypeException
145
     * @throws ParseException
146
     */
147 2
    private function extractRdata(\ArrayIterator $a): Rdata\RdataInterface
148
    {
149 2
        $type = strtoupper($a->current());
150 2
        $a->next();
151 2
        switch ($type) {
152
            case Rdata\A::TYPE:
153 2
                return Rdata\Factory::A($this->pop($a));
154
            case Rdata\AAAA::TYPE:
155 2
                return Rdata\Factory::Aaaa($this->pop($a));
156
            case Rdata\CNAME::TYPE:
157 1
                return Rdata\Factory::Cname($this->pop($a));
158
            case Rdata\DNAME::TYPE:
159 1
                return Rdata\Factory::Dname($this->pop($a));
160
            case Rdata\HINFO::TYPE:
161
                return Rdata\Factory::Hinfo($this->pop($a), $this->pop($a));
162
            case Rdata\LOC::TYPE:
163 1
                $lat = $this->dmsToDecimal($this->pop($a), $this->pop($a), $this->pop($a), $this->pop($a));
164 1
                $lon = $this->dmsToDecimal($this->pop($a), $this->pop($a), $this->pop($a), $this->pop($a));
165
166 1
                return Rdata\Factory::Loc(
167 1
                    $lat,
168 1
                    $lon,
169 1
                    (float) $this->pop($a),
170 1
                    (float) $this->pop($a),
171 1
                    (float) $this->pop($a),
172 1
                    (float) $this->pop($a)
173
                );
174
            case Rdata\MX::TYPE:
175 2
                return Rdata\Factory::Mx($this->pop($a), $this->pop($a));
176
            case Rdata\NS::TYPE:
177 2
                return Rdata\Factory::Ns($this->pop($a));
178
            case Rdata\PTR::TYPE:
179 1
                return Rdata\Factory::Ptr($this->pop($a));
180
            case Rdata\SOA::TYPE:
181 2
                return Rdata\Factory::Soa(
182 2
                    $this->pop($a),
183 2
                    $this->pop($a),
184 2
                    $this->pop($a),
185 2
                    $this->pop($a),
186 2
                    $this->pop($a),
187 2
                    $this->pop($a),
188 2
                    $this->pop($a)
189
                );
190
            case Rdata\SRV::TYPE:
191 1
                return Rdata\Factory::Srv($this->pop($a), $this->pop($a), $this->pop($a), $this->pop($a));
192
            case Rdata\TXT::TYPE:
193 1
                return $this->extractTxtRecord($a);
194
            default:
195
                throw new UnsupportedTypeException($type);
196
        }
197
    }
198
199
    /**
200
     * @param \ArrayIterator $a
201
     *
202
     * @return Rdata\TXT
203
     *
204
     * @throws ParseException
205
     */
206 1
    private function extractTxtRecord(\ArrayIterator $a): Rdata\TXT
207
    {
208 1
        $txt = '';
209 1
        while ($a->valid()) {
210 1
            $txt .= $this->pop($a).' ';
211
        }
212 1
        $txt = substr($txt, 0, -1);
213
214 1
        $string = new StringIterator($txt);
215 1
        $txt = '';
216 1
        $doubleQuotesOpen = false;
217
218 1
        while ($string->valid()) {
219 1
            switch ($string->ord()) {
220
                case AsciiChar::BACKSLASH:
221 1
                    $string->next();
222 1
                    $txt .= $string->current();
223 1
                    $string->next();
224 1
                    break;
225
                case AsciiChar::DOUBLE_QUOTES:
226 1
                    $doubleQuotesOpen = !$doubleQuotesOpen;
0 ignored issues
show
introduced by
The condition $doubleQuotesOpen is always false.
Loading history...
227 1
                    $string->next();
228 1
                    break;
229
                default:
230 1
                    if ($doubleQuotesOpen) {
231 1
                        $txt .= $string->current();
232
                    }
233 1
                    $string->next();
234 1
                    break;
235
            }
236
        }
237
238 1
        if ($doubleQuotesOpen) {
239
            throw new ParseException('Unbalanced double quotation marks.');
240
        }
241
242 1
        return Rdata\Factory::txt($txt);
243
    }
244
245
    /**
246
     * Return current entry and moves the iterator to the next entry.
247
     *
248
     * @param \ArrayIterator $arrayIterator
249
     *
250
     * @return mixed
251
     */
252 2
    private function pop(\ArrayIterator $arrayIterator)
253
    {
254 2
        $current = $arrayIterator->current();
255 2
        $arrayIterator->next();
256
257 2
        return $current;
258
    }
259
260
    /**
261
     * Transform a DMS string to a decimal representation. Used for LOC records.
262
     *
263
     * @param int    $deg
264
     * @param int    $m
265
     * @param float  $s
266
     * @param string $hemi
267
     *
268
     * @return float
269
     */
270 1
    private function dmsToDecimal(int $deg, int $m, float $s, string $hemi): float
271
    {
272 1
        $multiplier = ('S' === $hemi || 'W' === $hemi) ? -1 : 1;
273
274 1
        return $multiplier * ($deg + ($m / 60) + ($s / 3600));
275
    }
276
}
277