Completed
Push — master ( 169aa5...85a727 )
by Michele
13s queued 12s
created

IPv4::getReverseDNSLookupName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
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 696
    protected function __construct($address)
57
    {
58 696
        $this->address = $address;
59 696
        $this->bytes = null;
60 696
        $this->rangeType = null;
61 696
        $this->comparableString = null;
62 696
    }
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 1138
    public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false)
84
    {
85 1138
        if (!is_string($address) || !strpos($address, '.')) {
86 439
            return null;
87
        }
88 708
        $rxChunk = '0?[0-9]{1,3}';
89 708
        if ($supportNonDecimalIPv4) {
90 32
            $rxChunk = "(?:0[Xx]0*[0-9A-Fa-f]{1,2})|(?:{$rxChunk})";
91
        }
92 708
        $rx = "0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})";
93 708
        if ($mayIncludePort) {
94 708
            $rx .= '(?::\d+)?';
95
        }
96 708
        $matches = null;
97 708
        if (!preg_match('/^' . $rx . '$/', $address, $matches)) {
98 27
            return null;
99
        }
100 690
        $nums = array();
101 690
        for ($i = 1; $i <= 4; $i++) {
102 690
            $s = $matches[$i];
103 690
            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 670
                $n = (int) $s;
116
            }
117 687
            if ($n < 0 || $n > 255) {
118 6
                return null;
119
            }
120 687
            $nums[] = (string) $n;
121
        }
122
123 681
        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 517
    public function getBytes()
219
    {
220 517
        if ($this->bytes === null) {
221 517
            $this->bytes = array_map(
222 517
                function ($chunk) {
223 517
                    return (int) $chunk;
224 517
                },
225 517
                explode('.', $this->address)
226
            );
227
        }
228
229 517
        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
    public function getReverseDNSLookupName()
434
    {
435
        return implode(
436
            '.',
437
            array_reverse($this->getBytes())
438
        ) . '.in-addr.arpa';
439
    }
440
}
441