Completed
Push — master ( 85a727...790e23 )
by Michele
03:16
created

IPv4::getReverseDNSLookupName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
c 0
b 0
f 0
ccs 5
cts 5
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace IPLib\Address;
4
5
use IPLib\Range\RangeInterface;
6
use IPLib\Range\Subnet;
7
use IPLib\Range\Type as RangeType;
8
9
/**
10
 * An IPv4 address.
11
 */
12
class IPv4 implements AddressInterface
13
{
14
    /**
15
     * The string representation of the address.
16
     *
17
     * @var string
18
     *
19
     * @example '127.0.0.1'
20
     */
21
    protected $address;
22
23
    /**
24
     * The byte list of the IP address.
25
     *
26
     * @var int[]|null
27
     */
28
    protected $bytes;
29
30
    /**
31
     * The type of the range of this IP address.
32
     *
33
     * @var int|null
34
     */
35
    protected $rangeType;
36
37
    /**
38
     * A string representation of this address than can be used when comparing addresses and ranges.
39
     *
40
     * @var string
41
     */
42
    protected $comparableString;
43
44
    /**
45
     * An array containing RFC designated address ranges.
46
     *
47
     * @var array|null
48
     */
49
    private static $reservedRanges = null;
50
51
    /**
52
     * Initializes the instance.
53
     *
54
     * @param string $address
55
     */
56 700
    protected function __construct($address)
57
    {
58 700
        $this->address = $address;
59 700
        $this->bytes = null;
60 700
        $this->rangeType = null;
61 700
        $this->comparableString = null;
62 700
    }
63
64
    /**
65
     * {@inheritdoc}
66
     *
67
     * @see \IPLib\Address\AddressInterface::__toString()
68
     */
69 58
    public function __toString()
70
    {
71 58
        return $this->address;
72
    }
73
74
    /**
75
     * Parse a string and returns an IPv4 instance if the string is valid, or null otherwise.
76
     *
77
     * @param string|mixed $address the address to parse
78
     * @param bool $mayIncludePort set to false to avoid parsing addresses with ports
79
     * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
80
     *
81
     * @return static|null
82
     */
83 1147
    public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false)
84
    {
85 1147
        if (!is_string($address) || !strpos($address, '.')) {
86 444
            return null;
87
        }
88 712
        $rxChunk = '0?[0-9]{1,3}';
89 712
        if ($supportNonDecimalIPv4) {
90 32
            $rxChunk = "(?:0[Xx]0*[0-9A-Fa-f]{1,2})|(?:{$rxChunk})";
91
        }
92 712
        $rx = "0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})";
93 712
        if ($mayIncludePort) {
94 712
            $rx .= '(?::\d+)?';
95
        }
96 712
        $matches = null;
97 712
        if (!preg_match('/^' . $rx . '$/', $address, $matches)) {
98 27
            return null;
99
        }
100 694
        $nums = array();
101 694
        for ($i = 1; $i <= 4; $i++) {
102 694
            $s = $matches[$i];
103 694
            if ($supportNonDecimalIPv4) {
104 32
                if (stripos($s, '0x') === 0) {
105 21
                    $n = hexdec(substr($s, 2));
106 32
                } elseif ($s[0] === '0') {
107 32
                    if (!preg_match('/^[0-7]+$/', $s)) {
108 3
                        return null;
109
                    }
110 29
                    $n = octdec(substr($s, 1));
111
                } else {
112 29
                    $n = (int) $s;
113
                }
114
            } else {
115 674
                $n = (int) $s;
116
            }
117 691
            if ($n < 0 || $n > 255) {
118 6
                return null;
119
            }
120 691
            $nums[] = (string) $n;
121
        }
122
123 685
        return new static(implode('.', $nums));
124
    }
125
126
    /**
127
     * Parse an array of bytes and returns an IPv4 instance if the array is valid, or null otherwise.
128
     *
129
     * @param int[]|array $bytes
130
     *
131
     * @return static|null
132
     */
133 610
    public static function fromBytes(array $bytes)
134
    {
135 610
        $result = null;
136 610
        if (count($bytes) === 4) {
137 345
            $chunks = array_map(
138 345
                function ($byte) {
139 345
                    return (is_int($byte) && $byte >= 0 && $byte <= 255) ? (string) $byte : false;
140 345
                },
141 345
                $bytes
142
            );
143 345
            if (in_array(false, $chunks, true) === false) {
144 345
                $result = new static(implode('.', $chunks));
145
            }
146
        }
147
148 610
        return $result;
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     *
154
     * @see \IPLib\Address\AddressInterface::toString()
155
     */
156 581
    public function toString($long = false)
157
    {
158 581
        if ($long) {
159 37
            return $this->getComparableString();
160
        }
161
162 581
        return $this->address;
163
    }
164
165
    /**
166
     * Get the octal representation of this IP address.
167
     *
168
     * @param bool $long
169
     *
170
     * @return string
171
     *
172
     * @example if $long == false: if the decimal representation is '0.7.8.255': '0.7.010.0377'
173
     * @example if $long == true: if the decimal representation is '0.7.8.255': '0000.0007.0010.0377'
174
     */
175 12 View Code Duplication
    public function toOctal($long = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
    {
177 12
        $chunks = array();
178 12
        foreach ($this->getBytes() as $byte) {
179 12
            if ($long) {
180 12
                $chunks[] = sprintf('%04o', $byte);
181
            } else {
182 12
                $chunks[] = '0' . decoct($byte);
183
            }
184
        }
185
186 12
        return implode('.', $chunks);
187
    }
188
189
    /**
190
     * Get the hexadecimal representation of this IP address.
191
     *
192
     * @param bool $long
193
     *
194
     * @return string
195
     *
196
     * @example if $long == false: if the decimal representation is '0.9.10.255': '0.9.0xa.0xff'
197
     * @example if $long == true: if the decimal representation is '0.9.10.255': '0x00.0x09.0x0a.0xff'
198
     */
199 12 View Code Duplication
    public function toHexadecimal($long = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
    {
201 12
        $chunks = array();
202 12
        foreach ($this->getBytes() as $byte) {
203 12
            if ($long) {
204 12
                $chunks[] = sprintf('0x%02x', $byte);
205
            } else {
206 12
                $chunks[] = '0x' . dechex($byte);
207
            }
208
        }
209
210 12
        return implode('.', $chunks);
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     *
216
     * @see \IPLib\Address\AddressInterface::getBytes()
217
     */
218 521
    public function getBytes()
219
    {
220 521
        if ($this->bytes === null) {
221 521
            $this->bytes = array_map(
222 521
                function ($chunk) {
223 521
                    return (int) $chunk;
224 521
                },
225 521
                explode('.', $this->address)
226
            );
227
        }
228
229 521
        return $this->bytes;
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     *
235
     * @see \IPLib\Address\AddressInterface::getAddressType()
236
     */
237 570
    public function getAddressType()
238
    {
239 570
        return Type::T_IPv4;
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     *
245
     * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
246
     */
247 173
    public static function getDefaultReservedRangeType()
248
    {
249 173
        return RangeType::T_PUBLIC;
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     *
255
     * @see \IPLib\Address\AddressInterface::getReservedRanges()
256
     */
257 341
    public static function getReservedRanges()
258
    {
259 341
        if (self::$reservedRanges === null) {
260 1
            $reservedRanges = array();
261
            foreach (array(
262
                // RFC 5735
263 1
                '0.0.0.0/8' => array(RangeType::T_THISNETWORK, array('0.0.0.0/32' => RangeType::T_UNSPECIFIED)),
264
                // RFC 5735
265
                '10.0.0.0/8' => array(RangeType::T_PRIVATENETWORK),
266
                // RFC 6598
267
                '100.64.0.0/10' => array(RangeType::T_CGNAT),
268
                // RFC 5735
269
                '127.0.0.0/8' => array(RangeType::T_LOOPBACK),
270
                // RFC 5735
271
                '169.254.0.0/16' => array(RangeType::T_LINKLOCAL),
272
                // RFC 5735
273
                '172.16.0.0/12' => array(RangeType::T_PRIVATENETWORK),
274
                // RFC 5735
275
                '192.0.0.0/24' => array(RangeType::T_RESERVED),
276
                // RFC 5735
277
                '192.0.2.0/24' => array(RangeType::T_RESERVED),
278
                // RFC 5735
279
                '192.88.99.0/24' => array(RangeType::T_ANYCASTRELAY),
280
                // RFC 5735
281
                '192.168.0.0/16' => array(RangeType::T_PRIVATENETWORK),
282
                // RFC 5735
283
                '198.18.0.0/15' => array(RangeType::T_RESERVED),
284
                // RFC 5735
285
                '198.51.100.0/24' => array(RangeType::T_RESERVED),
286
                // RFC 5735
287
                '203.0.113.0/24' => array(RangeType::T_RESERVED),
288
                // RFC 5735
289
                '224.0.0.0/4' => array(RangeType::T_MULTICAST),
290
                // RFC 5735
291
                '240.0.0.0/4' => array(RangeType::T_RESERVED, array('255.255.255.255/32' => RangeType::T_LIMITEDBROADCAST)),
292
            ) as $range => $data) {
293 1
                $exceptions = array();
294 1 View Code Duplication
                if (isset($data[1])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
295 1
                    foreach ($data[1] as $exceptionRange => $exceptionType) {
296 1
                        $exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType);
1 ignored issue
show
Bug introduced by
It seems like \IPLib\Range\Subnet::fromString($exceptionRange) can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
297
                    }
298
                }
299 1
                $reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions);
1 ignored issue
show
Bug introduced by
It seems like \IPLib\Range\Subnet::fromString($range) can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
300
            }
301 1
            self::$reservedRanges = $reservedRanges;
302
        }
303
304 341
        return self::$reservedRanges;
305
    }
306
307
    /**
308
     * {@inheritdoc}
309
     *
310
     * @see \IPLib\Address\AddressInterface::getRangeType()
311
     */
312 171
    public function getRangeType()
313
    {
314 171 View Code Duplication
        if ($this->rangeType === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
315 171
            $rangeType = null;
316 171
            foreach (static::getReservedRanges() as $reservedRange) {
317 171
                $rangeType = $reservedRange->getAddressType($this);
318 171
                if ($rangeType !== null) {
319 171
                    break;
320
                }
321
            }
322 171
            $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
323
        }
324
325 171
        return $this->rangeType;
326
    }
327
328
    /**
329
     * Create an IPv6 representation of this address (in 6to4 notation).
330
     *
331
     * @return \IPLib\Address\IPv6
332
     */
333 4
    public function toIPv6()
334
    {
335 4
        $myBytes = $this->getBytes();
336
337 4
        return IPv6::fromString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::');
338
    }
339
340
    /**
341
     * Create an IPv6 representation of this address (in IPv6 IPv4-mapped notation).
342
     *
343
     * @return \IPLib\Address\IPv6
344
     */
345 4
    public function toIPv6IPv4Mapped()
346
    {
347 4
        return IPv6::fromBytes(array_merge(array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff), $this->getBytes()));
348
    }
349
350
    /**
351
     * {@inheritdoc}
352
     *
353
     * @see \IPLib\Address\AddressInterface::getComparableString()
354
     */
355 391
    public function getComparableString()
356
    {
357 391
        if ($this->comparableString === null) {
358 391
            $chunks = array();
359 391
            foreach ($this->getBytes() as $byte) {
360 391
                $chunks[] = sprintf('%03d', $byte);
361
            }
362 391
            $this->comparableString = implode('.', $chunks);
363
        }
364
365 391
        return $this->comparableString;
366
    }
367
368
    /**
369
     * {@inheritdoc}
370
     *
371
     * @see \IPLib\Address\AddressInterface::matches()
372
     */
373 11
    public function matches(RangeInterface $range)
374
    {
375 11
        return $range->contains($this);
376
    }
377
378
    /**
379
     * {@inheritdoc}
380
     *
381
     * @see \IPLib\Address\AddressInterface::getNextAddress()
382
     */
383 9 View Code Duplication
    public function getNextAddress()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
    {
385 9
        $overflow = false;
386 9
        $bytes = $this->getBytes();
387 9
        for ($i = count($bytes) - 1; $i >= 0; $i--) {
388 9
            if ($bytes[$i] === 255) {
389 6
                if ($i === 0) {
390 1
                    $overflow = true;
391 1
                    break;
392
                }
393 6
                $bytes[$i] = 0;
394
            } else {
395 9
                $bytes[$i]++;
396 9
                break;
397
            }
398
        }
399
400 9
        return $overflow ? null : static::fromBytes($bytes);
401
    }
402
403
    /**
404
     * {@inheritdoc}
405
     *
406
     * @see \IPLib\Address\AddressInterface::getPreviousAddress()
407
     */
408 9 View Code Duplication
    public function getPreviousAddress()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
409
    {
410 9
        $overflow = false;
411 9
        $bytes = $this->getBytes();
412 9
        for ($i = count($bytes) - 1; $i >= 0; $i--) {
413 9
            if ($bytes[$i] === 0) {
414 6
                if ($i === 0) {
415 1
                    $overflow = true;
416 1
                    break;
417
                }
418 6
                $bytes[$i] = 255;
419
            } else {
420 9
                $bytes[$i]--;
421 9
                break;
422
            }
423
        }
424
425 9
        return $overflow ? null : static::fromBytes($bytes);
426
    }
427
428
    /**
429
     * {@inheritdoc}
430
     *
431
     * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
432
     */
433 4
    public function getReverseDNSLookupName()
434
    {
435 4
        return implode(
436 4
            '.',
437 4
            array_reverse($this->getBytes())
438 4
        ) . '.in-addr.arpa';
439
    }
440
}
441