Parser   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 21
eloc 46
dl 0
loc 206
ccs 58
cts 58
cp 1
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A parse() 0 3 1
A isTTL() 0 3 1
A processClass() 0 5 2
A isControlEntry() 0 3 1
A processResourceName() 0 8 2
A processTtl() 0 5 2
A extractRdata() 0 10 2
A isResourceName() 0 6 3
A processControlEntry() 0 5 2
A processLine() 0 18 2
A makeZone() 0 10 2
A __construct() 0 3 1
1
<?php
2
3
/*
4
 * This file is part of Badcow DNS Library.
5
 *
6
 * (c) Samuel Williams <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Badcow\DNS\Parser;
13
14
use Badcow\DNS\Classes;
15
use Badcow\DNS\ResourceRecord;
16
use Badcow\DNS\Zone;
17
use Badcow\DNS\Rdata;
18
19
class Parser
20
{
21
    /**
22
     * @var string
23
     */
24
    private $string;
25
26
    /**
27
     * @var string
28
     */
29
    private $previousName;
30
31
    /**
32
     * @var Zone
33
     */
34
    private $zone;
35
36
    /**
37
     * Array of methods that take an ArrayIterator and return an Rdata object. The array key is the Rdata type.
38
     *
39
     * @var array
40
     */
41
    private $rdataHandlers = [];
42
43
    /**
44
     * Parser constructor.
45
     *
46
     * @param array $rdataHandlers
47
     */
48 7
    public function __construct(array $rdataHandlers = [])
49
    {
50 7
        $this->rdataHandlers = array_merge(RdataHandlers::getHandlers(), $rdataHandlers);
51 7
    }
52
53
    /**
54
     * @param string $name
55
     * @param string $zone
56
     *
57
     * @return Zone
58
     *
59
     * @throws ParseException
60
     */
61 7
    public static function parse(string $name, string $zone): Zone
62
    {
63 7
        return (new self())->makeZone($name, $zone);
64
    }
65
66
    /**
67
     * @param $name
68
     * @param $string
69
     *
70
     * @return Zone
71
     *
72
     * @throws ParseException
73
     */
74 7
    public function makeZone($name, $string): Zone
75
    {
76 7
        $this->zone = new Zone($name);
77 7
        $this->string = Normaliser::normalise($string);
78
79 7
        foreach (explode(Tokens::LINE_FEED, $this->string) as $line) {
80 7
            $this->processLine($line);
81
        }
82
83 5
        return $this->zone;
84
    }
85
86
    /**
87
     * @param string $line
88
     *
89
     * @throws ParseException
90
     */
91 7
    private function processLine(string $line): void
92
    {
93 7
        $iterator = new \ArrayIterator(explode(Tokens::SPACE, $line));
94
95 7
        if ($this->isControlEntry($iterator)) {
96 4
            $this->processControlEntry($iterator);
97
98 4
            return;
99
        }
100
101 7
        $resourceRecord = new ResourceRecord();
102
103 7
        $this->processResourceName($iterator, $resourceRecord);
104 7
        $this->processTtl($iterator, $resourceRecord);
105 7
        $this->processClass($iterator, $resourceRecord);
106 7
        $resourceRecord->setRdata($this->extractRdata($iterator));
107
108 5
        $this->zone->addResourceRecord($resourceRecord);
109 5
    }
110
111
    /**
112
     * Processes control entries at the top of a BIND record, i.e. $ORIGIN, $TTL, $INCLUDE, etc.
113
     *
114
     * @param \ArrayIterator $iterator
115
     */
116 4
    private function processControlEntry(\ArrayIterator $iterator): void
117
    {
118 4
        if ('$TTL' === strtoupper($iterator->current())) {
119 4
            $iterator->next();
120 4
            $this->zone->setDefaultTtl((int) $iterator->current());
121
        }
122 4
    }
123
124
    /**
125
     * Processes a ResourceRecord name.
126
     *
127
     * @param \ArrayIterator $iterator
128
     * @param ResourceRecord $resourceRecord
129
     */
130 7
    private function processResourceName(\ArrayIterator $iterator, ResourceRecord $resourceRecord): void
131
    {
132 7
        if ($this->isResourceName($iterator)) {
133 7
            $this->previousName = $iterator->current();
134 7
            $iterator->next();
135
        }
136
137 7
        $resourceRecord->setName($this->previousName);
138 7
    }
139
140
    /**
141
     * Set RR's TTL if there is one.
142
     *
143
     * @param \ArrayIterator $iterator
144
     * @param ResourceRecord $resourceRecord
145
     */
146 7
    private function processTtl(\ArrayIterator $iterator, ResourceRecord $resourceRecord): void
147
    {
148 7
        if ($this->isTTL($iterator)) {
149 4
            $resourceRecord->setTtl($iterator->current());
150 4
            $iterator->next();
151
        }
152 7
    }
153
154
    /**
155
     * Set RR's class if there is one.
156
     *
157
     * @param \ArrayIterator $iterator
158
     * @param ResourceRecord $resourceRecord
159
     */
160 7
    private function processClass(\ArrayIterator $iterator, ResourceRecord $resourceRecord): void
161
    {
162 7
        if (Classes::isValid(strtoupper($iterator->current()))) {
163 6
            $resourceRecord->setClass(strtoupper($iterator->current()));
164 6
            $iterator->next();
165
        }
166 7
    }
167
168
    /**
169
     * Determine if iterant is a resource name.
170
     *
171
     * @param \ArrayIterator $iterator
172
     *
173
     * @return bool
174
     */
175 7
    private function isResourceName(\ArrayIterator $iterator): bool
176
    {
177
        return !(
178 7
            $this->isTTL($iterator) ||
179 7
            Classes::isValid(strtoupper($iterator->current())) ||
180 7
            RDataTypes::isValid(strtoupper($iterator->current()))
181
        );
182
    }
183
184
    /**
185
     * Determine if iterant is a control entry such as $TTL, $ORIGIN, $INCLUDE, etcetera.
186
     *
187
     * @param \ArrayIterator $iterator
188
     *
189
     * @return bool
190
     */
191 7
    private function isControlEntry(\ArrayIterator $iterator): bool
192
    {
193 7
        return 1 === preg_match('/^\$[A-Z0-9]+/i', $iterator->current());
194
    }
195
196
    /**
197
     * Determine if the iterant is a TTL (i.e. it is an integer).
198
     *
199
     * @param \ArrayIterator $iterator
200
     *
201
     * @return bool
202
     */
203 7
    private function isTTL(\ArrayIterator $iterator): bool
204
    {
205 7
        return 1 === preg_match('/^\d+$/', $iterator->current());
206
    }
207
208
    /**
209
     * @param \ArrayIterator $iterator
210
     *
211
     * @return RData\RDataInterface
212
     *
213
     * @throws ParseException
214
     */
215 7
    private function extractRdata(\ArrayIterator $iterator): Rdata\RdataInterface
216
    {
217 7
        $type = strtoupper($iterator->current());
218 7
        $iterator->next();
219
220 7
        if (array_key_exists($type, $this->rdataHandlers)) {
221 6
            return call_user_func($this->rdataHandlers[$type], $iterator);
222
        }
223
224 5
        return RdataHandlers::catchAll($type, $iterator);
225
    }
226
}
227