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