Completed
Pull Request — master (#57)
by Michele
07:38
created

IPv4::toOctal()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 13
Ratio 100 %

Code Coverage

Tests 1
CRAP Score 3

Importance

Changes 0
Metric Value
dl 13
loc 13
c 0
b 0
f 0
ccs 1
cts 1
cp 1
rs 9.8333
cc 3
nc 3
nop 1
crap 3
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 715
    protected function __construct($address)
57
    {
58 715
        $this->address = $address;
59 715
        $this->bytes = null;
60 715
        $this->rangeType = null;
61 715
        $this->comparableString = null;
62 715
    }
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
     * {@inheritdoc}
76
     *
77
     * @see \IPLib\Address\AddressInterface::getNumberOfBits()
78
     */
79
    public static function getNumberOfBits()
80
    {
81
        return 32;
82
    }
83 1187
84
    /**
85 1187
     * Parse a string and returns an IPv4 instance if the string is valid, or null otherwise.
86 469
     *
87
     * @param string|mixed $address the address to parse
88 727
     * @param bool $mayIncludePort set to false to avoid parsing addresses with ports
89 727
     * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
90 32
     *
91
     * @return static|null
92 727
     */
93 727
    public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false)
94 727
    {
95
        if (!is_string($address) || !strpos($address, '.')) {
96 727
            return null;
97 727
        }
98 27
        $rxChunk = '0?[0-9]{1,3}';
99
        if ($supportNonDecimalIPv4) {
100 709
            $rxChunk = "(?:0[Xx]0*[0-9A-Fa-f]{1,2})|(?:{$rxChunk})";
101 709
        }
102 709
        $rx = "0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})";
103 709
        if ($mayIncludePort) {
104 32
            $rx .= '(?::\d+)?';
105 21
        }
106 32
        $matches = null;
107 32
        if (!preg_match('/^' . $rx . '$/', $address, $matches)) {
108 3
            return null;
109
        }
110 29
        $nums = array();
111
        for ($i = 1; $i <= 4; $i++) {
112 29
            $s = $matches[$i];
113
            if ($supportNonDecimalIPv4) {
114
                if (stripos($s, '0x') === 0) {
115 689
                    $n = hexdec(substr($s, 2));
116
                } elseif ($s[0] === '0') {
117 706
                    if (!preg_match('/^[0-7]+$/', $s)) {
118 6
                        return null;
119
                    }
120 706
                    $n = octdec(substr($s, 1));
121
                } else {
122
                    $n = (int) $s;
123 700
                }
124
            } else {
125
                $n = (int) $s;
126
            }
127
            if ($n < 0 || $n > 255) {
128
                return null;
129
            }
130
            $nums[] = (string) $n;
131
        }
132
133 625
        return new static(implode('.', $nums));
134
    }
135 625
136 625
    /**
137 353
     * Parse an array of bytes and returns an IPv4 instance if the array is valid, or null otherwise.
138 353
     *
139 353
     * @param int[]|array $bytes
140 353
     *
141 353
     * @return static|null
142
     */
143 353
    public static function fromBytes(array $bytes)
144 353
    {
145
        $result = null;
146
        if (count($bytes) === 4) {
147
            $chunks = array_map(
148 625
                function ($byte) {
149
                    return (is_int($byte) && $byte >= 0 && $byte <= 255) ? (string) $byte : false;
150
                },
151
                $bytes
152
            );
153
            if (in_array(false, $chunks, true) === false) {
154
                $result = new static(implode('.', $chunks));
155
            }
156 594
        }
157
158 594
        return $result;
159 37
    }
160
161
    /**
162 594
     * {@inheritdoc}
163
     *
164
     * @see \IPLib\Address\AddressInterface::toString()
165
     */
166
    public function toString($long = false)
167
    {
168
        if ($long) {
169
            return $this->getComparableString();
170
        }
171
172
        return $this->address;
173
    }
174
175 12
    /**
176
     * Get the octal representation of this IP address.
177 12
     *
178 12
     * @param bool $long
179 12
     *
180 12
     * @return string
181
     *
182 12
     * @example if $long == false: if the decimal representation is '0.7.8.255': '0.7.010.0377'
183
     * @example if $long == true: if the decimal representation is '0.7.8.255': '0000.0007.0010.0377'
184
     */
185 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...
186 12
    {
187
        $chunks = array();
188
        foreach ($this->getBytes() as $byte) {
189
            if ($long) {
190
                $chunks[] = sprintf('%04o', $byte);
191
            } else {
192
                $chunks[] = '0' . decoct($byte);
193
            }
194
        }
195
196
        return implode('.', $chunks);
197
    }
198
199 12
    /**
200
     * Get the hexadecimal representation of this IP address.
201 12
     *
202 12
     * @param bool $long
203 12
     *
204 12
     * @return string
205
     *
206 12
     * @example if $long == false: if the decimal representation is '0.9.10.255': '0.9.0xa.0xff'
207
     * @example if $long == true: if the decimal representation is '0.9.10.255': '0x00.0x09.0x0a.0xff'
208
     */
209 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...
210 12
    {
211
        $chunks = array();
212
        foreach ($this->getBytes() as $byte) {
213
            if ($long) {
214
                $chunks[] = sprintf('0x%02x', $byte);
215
            } else {
216
                $chunks[] = '0x' . dechex($byte);
217
            }
218 536
        }
219
220 536
        return implode('.', $chunks);
221 536
    }
222 536
223 536
    /**
224 536
     * {@inheritdoc}
225 536
     *
226
     * @see \IPLib\Address\AddressInterface::getBytes()
227
     */
228
    public function getBytes()
229 536
    {
230
        if ($this->bytes === null) {
231
            $this->bytes = array_map(
232
                function ($chunk) {
233
                    return (int) $chunk;
234
                },
235
                explode('.', $this->address)
236
            );
237 5
        }
238
239 5
        return $this->bytes;
240 5
    }
241 5
242
    /**
243
     * {@inheritdoc}
244 5
     *
245
     * @see \IPLib\Address\AddressInterface::getBits()
246
     */
247 View Code Duplication
    public function getBits()
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...
248
    {
249
        $parts = array();
250
        foreach ($this->getBytes() as $byte) {
251
            $parts[] = sprintf('%08b', $byte);
252 579
        }
253
254 579
        return implode('', $parts);
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     *
260
     * @see \IPLib\Address\AddressInterface::getAddressType()
261
     */
262 173
    public function getAddressType()
263
    {
264 173
        return Type::T_IPv4;
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     *
270
     * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
271
     */
272 341
    public static function getDefaultReservedRangeType()
273
    {
274 341
        return RangeType::T_PUBLIC;
275 1
    }
276
277
    /**
278 1
     * {@inheritdoc}
279
     *
280
     * @see \IPLib\Address\AddressInterface::getReservedRanges()
281
     */
282
    public static function getReservedRanges()
283
    {
284
        if (self::$reservedRanges === null) {
285
            $reservedRanges = array();
286
            foreach (array(
287
                // RFC 5735
288
                '0.0.0.0/8' => array(RangeType::T_THISNETWORK, array('0.0.0.0/32' => RangeType::T_UNSPECIFIED)),
289
                // RFC 5735
290
                '10.0.0.0/8' => array(RangeType::T_PRIVATENETWORK),
291
                // RFC 6598
292
                '100.64.0.0/10' => array(RangeType::T_CGNAT),
293
                // RFC 5735
294
                '127.0.0.0/8' => array(RangeType::T_LOOPBACK),
295
                // RFC 5735
296
                '169.254.0.0/16' => array(RangeType::T_LINKLOCAL),
297
                // RFC 5735
298
                '172.16.0.0/12' => array(RangeType::T_PRIVATENETWORK),
299
                // RFC 5735
300
                '192.0.0.0/24' => array(RangeType::T_RESERVED),
301
                // RFC 5735
302
                '192.0.2.0/24' => array(RangeType::T_RESERVED),
303
                // RFC 5735
304
                '192.88.99.0/24' => array(RangeType::T_ANYCASTRELAY),
305
                // RFC 5735
306
                '192.168.0.0/16' => array(RangeType::T_PRIVATENETWORK),
307
                // RFC 5735
308 1
                '198.18.0.0/15' => array(RangeType::T_RESERVED),
309 1
                // RFC 5735
310 1
                '198.51.100.0/24' => array(RangeType::T_RESERVED),
311 1
                // RFC 5735
312
                '203.0.113.0/24' => array(RangeType::T_RESERVED),
313
                // RFC 5735
314 1
                '224.0.0.0/4' => array(RangeType::T_MULTICAST),
315
                // RFC 5735
316 1
                '240.0.0.0/4' => array(RangeType::T_RESERVED, array('255.255.255.255/32' => RangeType::T_LIMITEDBROADCAST)),
317
            ) as $range => $data) {
318
                $exceptions = array();
319 341 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...
320
                    foreach ($data[1] as $exceptionRange => $exceptionType) {
321
                        $exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType);
322
                    }
323
                }
324
                $reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions);
325
            }
326
            self::$reservedRanges = $reservedRanges;
327 171
        }
328
329 171
        return self::$reservedRanges;
330 171
    }
331 171
332 171
    /**
333 171
     * {@inheritdoc}
334 171
     *
335
     * @see \IPLib\Address\AddressInterface::getRangeType()
336
     */
337 171
    public function getRangeType()
338
    {
339 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...
340 171
            $rangeType = null;
341
            foreach (static::getReservedRanges() as $reservedRange) {
342
                $rangeType = $reservedRange->getAddressType($this);
343
                if ($rangeType !== null) {
344
                    break;
345
                }
346
            }
347
            $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
348 4
        }
349
350 4
        return $this->rangeType;
351
    }
352 4
353
    /**
354
     * Create an IPv6 representation of this address (in 6to4 notation).
355
     *
356
     * @return \IPLib\Address\IPv6
357
     */
358
    public function toIPv6()
359
    {
360 4
        $myBytes = $this->getBytes();
361
362 4
        return IPv6::fromString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::');
363
    }
364
365
    /**
366
     * Create an IPv6 representation of this address (in IPv6 IPv4-mapped notation).
367
     *
368
     * @return \IPLib\Address\IPv6
369
     */
370 391
    public function toIPv6IPv4Mapped()
371
    {
372 391
        return IPv6::fromBytes(array_merge(array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff), $this->getBytes()));
373 391
    }
374 391
375 391
    /**
376
     * {@inheritdoc}
377 391
     *
378
     * @see \IPLib\Address\AddressInterface::getComparableString()
379
     */
380 391
    public function getComparableString()
381
    {
382
        if ($this->comparableString === null) {
383
            $chunks = array();
384
            foreach ($this->getBytes() as $byte) {
385
                $chunks[] = sprintf('%03d', $byte);
386
            }
387
            $this->comparableString = implode('.', $chunks);
388 11
        }
389
390 11
        return $this->comparableString;
391
    }
392
393
    /**
394
     * {@inheritdoc}
395
     *
396
     * @see \IPLib\Address\AddressInterface::matches()
397
     */
398 9
    public function matches(RangeInterface $range)
399
    {
400 9
        return $range->contains($this);
401 9
    }
402 9
403 9
    /**
404 6
     * {@inheritdoc}
405 1
     *
406 1
     * @see \IPLib\Address\AddressInterface::getNextAddress()
407
     */
408 6 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...
409
    {
410 9
        $overflow = false;
411 9
        $bytes = $this->getBytes();
412
        for ($i = count($bytes) - 1; $i >= 0; $i--) {
413
            if ($bytes[$i] === 255) {
414
                if ($i === 0) {
415 9
                    $overflow = true;
416
                    break;
417
                }
418
                $bytes[$i] = 0;
419
            } else {
420
                $bytes[$i]++;
421
                break;
422
            }
423 9
        }
424
425 9
        return $overflow ? null : static::fromBytes($bytes);
426 9
    }
427 9
428 9
    /**
429 6
     * {@inheritdoc}
430 1
     *
431 1
     * @see \IPLib\Address\AddressInterface::getPreviousAddress()
432
     */
433 6 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...
434
    {
435 9
        $overflow = false;
436 9
        $bytes = $this->getBytes();
437
        for ($i = count($bytes) - 1; $i >= 0; $i--) {
438
            if ($bytes[$i] === 0) {
439
                if ($i === 0) {
440 9
                    $overflow = true;
441
                    break;
442
                }
443
                $bytes[$i] = 255;
444
            } else {
445
                $bytes[$i]--;
446
                break;
447
            }
448 14
        }
449
450 14
        return $overflow ? null : static::fromBytes($bytes);
451 14
    }
452 14
453 14
    /**
454
     * {@inheritdoc}
455
     *
456
     * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
457
     */
458
    public function getReverseDNSLookupName()
459
    {
460
        return implode(
461
            '.',
462
            array_reverse($this->getBytes())
463
        ) . '.in-addr.arpa';
464
    }
465
}
466