Passed
Push — master ( 5a1acc...523cb4 )
by Sam
01:57
created

Parser::makeZone()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
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 2
    public static function parse(string $name, string $zone): ZoneInterface
40
    {
41 2
        $parser = new self();
42
43 2
        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 2
    public function makeZone($name, $string): ZoneInterface
56
    {
57 2
        $this->zone = new Zone($name);
58 2
        $this->string = Normaliser::normalise($string);
59 2
        $lines = explode("\n", $this->string);
60
61 2
        foreach ($lines as $line) {
62 2
            $this->processLine($line);
63
        }
64
65 2
        return $this->zone;
66
    }
67
68
    /**
69
     * @param string $line
70
     *
71
     * @throws UnsupportedTypeException
72
     */
73 2
    private function processLine(string $line)
74
    {
75 2
        if (1 === preg_match('/^\$ORIGIN/i', strtoupper($line))) {
76 2
            return;
77
        }
78
79 2
        $matches = [];
80 2
        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 2
        $parts = explode(' ', $line);
88 2
        $iterator = new \ArrayIterator($parts);
89 2
        $record = new ResourceRecord();
90
91
        // Is it a TTL?
92 2
        if (1 === preg_match('/^\d+$/', $iterator->current())) {
93
            $record->setName($this->previousName);
94
            goto ttl;
95
        }
96
97
        // Is it a valid class?
98 2
        if (Classes::isValid(strtoupper($iterator->current()))) {
99
            $record->setName($this->previousName);
100
            goto _class;
101
        }
102
103
        // Is it a valid RDATA type?
104 2
        if (RDataEnum::isValid(strtoupper($iterator->current()))) {
105
            $record->setName($this->previousName);
106
            goto type;
107
        }
108
109 2
        $record->setName($iterator->current());
110 2
        $this->previousName = $iterator->current();
111 2
        $iterator->next();
112
113
        ttl:
114 2
        $matches = [];
115 2
        if (1 === preg_match('/^(\d+)$/', $iterator->current(), $matches)) {
116 1
            $ttl = (int) $matches[1];
117 1
            $record->setTtl($ttl);
118 1
            $iterator->next();
119
        }
120
121
        _class:
122 2
        if (Classes::isValid(strtoupper($iterator->current()))) {
123 1
            $record->setClass(strtoupper($iterator->current()));
124 1
            $iterator->next();
125
        }
126
127
        type:
128 2
        if (!RDataEnum::isValid(strtoupper($iterator->current()))) {
129
            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
                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
     * @return Rdata\TXT
200
     * @throws ParseException
201
     */
202 1
    private function extractTxtRecord(\ArrayIterator $a): Rdata\TXT
203
    {
204 1
        $txt = '';
205 1
        while ($a->valid()) {
206 1
            $txt .= $this->pop($a).' ';
207
        }
208 1
        $txt = substr($txt, 0, -1);
209
210 1
        $string = new StringIterator($txt);
211 1
        $txt = '';
212 1
        $doubleQuotesOpen = false;
213
214 1
        while ($string->valid()) {
215 1
            switch ($string->ord()) {
216 1
                case AsciiChar::BACKSLASH:
217 1
                    $string->next();
218 1
                    $txt .= $string->current();
219 1
                    $string->next();
220 1
                    break;
221 1
                case AsciiChar::DOUBLE_QUOTES:
222 1
                    $doubleQuotesOpen = !$doubleQuotesOpen;
0 ignored issues
show
introduced by
The condition $doubleQuotesOpen is always false.
Loading history...
223 1
                    $string->next();
224 1
                    break;
225
                default:
226 1
                    if ($doubleQuotesOpen) {
227 1
                        $txt .= $string->current();
228
                    }
229 1
                    $string->next();
230 1
                    break;
231
            }
232
        }
233
234 1
        if ($doubleQuotesOpen) {
235
            throw new ParseException('Unbalanced double quotation marks.');
236
        }
237
238 1
        return Rdata\Factory::txt($txt);
239
    }
240
241
    /**
242
     * Return current entry and moves the iterator to the next entry.
243
     *
244
     * @param \ArrayIterator $arrayIterator
245
     *
246
     * @return mixed
247
     */
248 2
    private function pop(\ArrayIterator $arrayIterator)
249
    {
250 2
        $current = $arrayIterator->current();
251 2
        $arrayIterator->next();
252
253 2
        return $current;
254
    }
255
256
    /**
257
     * Transform a DMS string to a decimal representation. Used for LOC records.
258
     *
259
     * @param int    $deg
260
     * @param int    $m
261
     * @param float  $s
262
     * @param string $hemi
263
     *
264
     * @return float
265
     */
266 1
    private function dmsToDecimal(int $deg, int $m, float $s, string $hemi): float
267
    {
268 1
        $multiplier = ('S' === $hemi || 'W' === $hemi) ? -1 : 1;
269
270 1
        return $multiplier * ($deg + ($m / 60) + ($s / 3600));
271
    }
272
}
273