Completed
Pull Request — master (#31)
by
unknown
02:03
created

Validator::ipAddress()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 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;
13
14
use Badcow\DNS\Rdata\CNAME;
15
use Badcow\DNS\Rdata\NS;
16
use Badcow\DNS\Rdata\SOA;
17
18
class Validator
19
{
20
    const ZONE_OKAY = 0;
21
22
    const ZONE_NO_SOA = 1;
23
24
    const ZONE_TOO_MANY_SOA = 2;
25
26
    const ZONE_NO_NS = 4;
27
28
    const ZONE_NO_CLASS = 8;
29
30
    const ZONE_TOO_MANY_CLASSES = 16;
31
32
    /**
33
     * Validate the string as a valid hostname in accordance with RFC 952 {@link https://tools.ietf.org/html/rfc952}
34
     * and RFC 1123 {@link https://tools.ietf.org/html/rfc1123}.
35
     *
36
     * @param string $name
37
     *
38
     * @return bool
39
     */
40 1
    public static function hostName(string $name): bool
41
    {
42 1
        return self::fullyQualifiedDomainName(rtrim($name, '.').'.');
43
    }
44
45
    /**
46
     * Validate the string is a Fully Qualified Domain Name.
47
     *
48
     * @param string $name
49
     *
50
     * @return bool
51
     */
52 4
    public static function fullyQualifiedDomainName(string $name): bool
53
    {
54 4
        $isValid = strlen($name) < 254;
55 4
        $isValid &= 1 === preg_match('/^(?:(?!-)[a-z0-9\-]{1,63}(?<!-)\.){1,127}$/i', $name);
56
57 4
        return $isValid;
58
    }
59
60
    /**
61
     * Validate the name for a Resource Record. This is distinct from validating a hostname in that this function
62
     * will permit '@' and wildcards as well as underscores used in SRV records.
63
     *
64
     * @param string $name
65
     *
66
     * @return bool
67
     */
68 3
    public static function resourceRecordName(string $name): bool
69
    {
70 3
        $isValid = strlen($name) < 254;
71 3
        $isValid &= 1 === preg_match('/(?:^(?:\*\.)?((?!-)[a-z0-9_\-]{1,63}(?<!-)\.?){1,127}$)|^@$|^\*$/i', $name);
72
73 3
        return $isValid;
74
    }
75
76
    /**
77
     * Validates an IPv4 Address.
78
     *
79
     * @static
80
     *
81
     * @param string $address
82
     *
83
     * @return bool
84
     */
85 1
    public static function ipv4(string $address): bool
86
    {
87 1
        return (bool) filter_var($address, FILTER_VALIDATE_IP, [
88 1
            'flags' => FILTER_FLAG_IPV4,
89
        ]);
90
    }
91
92
    /**
93
     * Validates an IPv6 Address.
94
     *
95
     * @static
96
     *
97
     * @param string $address
98
     *
99
     * @return bool
100
     */
101 22
    public static function ipv6(string $address): bool
102
    {
103 22
        return (bool) filter_var($address, FILTER_VALIDATE_IP, [
104 22
            'flags' => FILTER_FLAG_IPV6,
105
        ]);
106
    }
107
108
    /**
109
     * Validates an IPv4 or IPv6 address.
110
     *
111
     * @static
112
     *
113
     * @param $address
114
     *
115
     * @return bool
116
     */
117 1
    public static function ipAddress(string $address): bool
118
    {
119 1
        return (bool) filter_var($address, FILTER_VALIDATE_IP);
120
    }
121
122
    /**
123
     * Validates that the zone meets
124
     * RFC-1035 especially that:
125
     *   1) 5.2.1 All RRs in the file should be of the same class.
126
     *   2) 5.2.2 Exactly one SOA RR should be present at the top of the zone.
127
     *   3) There is at least one NS record.
128
     *
129
     * Return values are:
130
     *   - ZONE_NO_SOA
131
     *   - ZONE_TOO_MANY_SOA
132
     *   - ZONE_NO_NS
133
     *   - ZONE_NO_CLASS
134
     *   - ZONE_TOO_MANY_CLASSES
135
     *   - ZONE_OKAY
136
     *
137
     * You SHOULD compare these return values to the defined constants of this
138
     * class rather than against integers directly.
139
     *
140
     * @param Zone $zone
141
     *
142
     * @return int
143
     */
144 4
    public static function zone(Zone $zone): int
145
    {
146 4
        $n_soa = self::countResourceRecords($zone, SOA::TYPE);
147 4
        $n_ns = self::countResourceRecords($zone, NS::TYPE);
148 4
        $n_class = self::countClasses($zone);
149
150 4
        $totalError = 0;
151
152
        $incrementError = function (bool $errorCondition, int $errorOrdinal) use (&$totalError) {
153 4
            $totalError += $errorCondition ? $errorOrdinal : 0;
154 4
        };
155
156 4
        $incrementError($n_soa < 1, self::ZONE_NO_SOA);
157 4
        $incrementError($n_soa > 1, self::ZONE_TOO_MANY_SOA);
158 4
        $incrementError($n_ns < 1, self::ZONE_NO_NS);
159 4
        $incrementError($n_class < 1, self::ZONE_NO_CLASS);
160 4
        $incrementError($n_class > 1, self::ZONE_TOO_MANY_CLASSES);
161
162 4
        return $totalError;
163
    }
164
165
    /**
166
     * Counts the number of Resource Records of a particular type ($type) in a Zone.
167
     *
168
     * @param Zone   $zone
169
     * @param string $type The ResourceRecord type to be counted. If NULL, then the method will return
170
     *                     the number of records without RData.
171
     *
172
     * @return int the number of records to be counted
173
     */
174 4
    public static function countResourceRecords(Zone $zone, ?string $type = null): int
175
    {
176 4
        $n = 0;
177 4
        foreach ($zone as $rr) {
178 4
            $n += (int) ($type === $rr->getType());
179
        }
180
181 4
        return $n;
182
    }
183
184
    /**
185
     * Validates a reverse IPv4 address. Ensures that all octets are in the range [0-255].
186
     *
187
     * @param string $address
188
     *
189
     * @return bool
190
     */
191 1
    public static function reverseIpv4(string $address): bool
192
    {
193 1
        $pattern = '/^((?:[0-9]+\.){1,4})in\-addr\.arpa\.$/i';
194
195 1
        if (1 !== preg_match($pattern, $address, $matches)) {
196 1
            return false;
197
        }
198
199 1
        $octets = explode('.', $matches[1]);
200 1
        array_pop($octets); //Remove the last decimal from the array
201
202 1
        foreach ($octets as $octet) {
203 1
            if ((int) $octet > 255) {
204 1
                return false;
205
            }
206
        }
207
208 1
        return true;
209
    }
210
211
    /**
212
     * Validates a reverse IPv6 address.
213
     *
214
     * @param string $address
215
     *
216
     * @return bool
217
     */
218 1
    public static function reverseIpv6(string $address): bool
219
    {
220 1
        $pattern = '/^(?:[0-9a-f]\.){1,32}ip6\.arpa\.$/i';
221
222 1
        return 1 === preg_match($pattern, $address);
223
    }
224
225
    /**
226
     * Determine the number of unique non-null classes in a Zone. In a valid zone this MUST be 1.
227
     *
228
     * @param Zone $zone
229
     *
230
     * @return int
231
     */
232 4
    private static function countClasses(Zone $zone): int
233
    {
234 4
        $classes = [];
235
236 4
        foreach ($zone as $rr) {
237 4
            if (null !== $rr->getClass()) {
238 4
                $classes[$rr->getClass()] = null;
239
            }
240
        }
241
242 4
        return count($classes);
243
    }
244
245
    /**
246
     * Check if the record could be inserted in the zone by checking the CNAME aliases.
247
     *
248
     * @see https://tools.ietf.org/html/rfc1034#section-3.6.2
249
     *
250
     * @param Zone           $zone
251
     * @param ResourceRecord $newRecord
252
     *
253
     * @return bool
254
     */
255
    public static function record(Zone $zone, ResourceRecord $newRecord): bool
256
    {
257
        foreach ($zone as $rr) {
258
            if (CNAME::TYPE === $rr->getType()
259
                && $newRecord->getName() === $rr->getName()) {
260
                return false;
261
            }
262
        }
263
264
        return true;
265
    }
266
}
267