Skip to content

Commit e6b4f35

Browse files
authored
Merge pull request #18 from xp-framework/fix/octal-and-hex-ipv4
Fix IPv4 addresses using octal and hexadecimal notation
2 parents 46d569a + 0341856 commit e6b4f35

File tree

2 files changed

+61
-22
lines changed

2 files changed

+61
-22
lines changed

src/main/php/peer/net/Inet4Address.class.php

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php namespace peer\net;
22

3+
use lang\FormatException;
4+
35
/**
46
* IPv4 address
57
*
@@ -9,30 +11,47 @@
911
class Inet4Address implements InetAddress {
1012

1113
/**
12-
* Convert IPv4 address from dotted form into a long
14+
* Convert IPv4 address from dotted form into a long. Supports hexadecimal and
15+
* octal notations. Yes, 0177.0.0.1 and 0x7F.0.0.1 are both equivalent with
16+
* 127.0.0.1 - localhost!
1317
*
14-
* @param string ip
15-
* @return int
18+
* @param string $ip
19+
* @return int
20+
* @throws lang.FormatException
1621
*/
1722
protected static function ip2long($ip) {
1823
$i= 0; $addr= 0; $count= 0;
1924
foreach (explode('.', $ip) as $byte) {
20-
if (++$count > 4)
21-
throw new \lang\FormatException('Given IP string has more than 4 blocks: ['.$ip.']');
25+
if (++$count > 4) {
26+
throw new FormatException('Given IP string has more than 4 blocks: ['.$ip.']');
27+
}
28+
29+
$l= strlen($byte);
30+
$n= -1;
31+
if ($l > 1 && '0' === $byte[0]) {
32+
if (('x' === $byte[1] || 'X' === $byte[1]) && $l === strspn($byte, '0123456789aAbBcCdDeEfF', 2) + 2) {
33+
$n= hexdec($byte);
34+
} else if ($l === strspn($byte, '0123456789')) {
35+
$n= octdec($byte);
36+
}
37+
} else if ($l > 0 && $l === strspn($byte, '0123456789')) {
38+
$n= (int)$byte;
39+
}
2240

23-
if (!is_numeric($byte) || $byte < 0 || $byte > 255)
24-
throw new \lang\FormatException('Invalid format of ip address: ['.$ip.']');
41+
if ($n < 0 || $n > 255) {
42+
throw new FormatException('Invalid format of IP address: ['.$ip.']');
43+
}
2544

26-
$addr|= ($byte << (8 * (3 - $i++)));
45+
$addr|= ($n << (8 * (3 - $i++)));
2746
}
2847
return $addr;
2948
}
3049

3150
/**
3251
* Constructor
3352
*
34-
* @param string address
35-
* @throws lang.FormatException in case address is illegal
53+
* @param string $address
54+
* @throws lang.FormatException in case address is illegal
3655
*/
3756
public function __construct($address) {
3857
$this->addr= self::ip2long($address);
@@ -71,7 +90,7 @@ public function asString() {
7190
* @return bool
7291
*/
7392
public function isLoopback() {
74-
return $this->addr >> 8 == 0x7F0000;
93+
return $this->addr >> 8 === 0x7F0000;
7594
}
7695

7796
/**
@@ -86,18 +105,18 @@ public function inSubnet(Network $net) {
86105

87106
$addrn= $net->getAddress()->addr;
88107
$mask= $net->getNetmask();
89-
return $this->addr >> (32 - $mask) == $addrn >> (32 - $mask);
108+
return $this->addr >> (32 - $mask) === $addrn >> (32 - $mask);
90109
}
91110

92111
/**
93112
* Create a subnet of this address, with the specified size.
94113
*
95114
* @param int subnetSize
96-
* @return Network
115+
* @return peer.net.Network
97116
* @throws lang.IllegalArgumentException in case the subnetSize is not correct
98117
*/
99118
public function createSubnet($subnetSize) {
100-
$addr= $this->addr & (0xFFFFFFFF << (32-$subnetSize));
119+
$addr= $this->addr & (0xFFFFFFFF << (32 - $subnetSize));
101120
return new Network(new Inet4Address(long2ip($addr)), $subnetSize);
102121
}
103122

src/test/php/peer/unittest/net/Inet4AddressTest.class.php

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,30 @@
22

33
use lang\FormatException;
44
use peer\net\{Inet4Address, Network};
5-
use unittest\{Expect, Test};
5+
use unittest\{Expect, Test, Values, TestCase};
66

7-
class Inet4AddressTest extends \unittest\TestCase {
7+
class Inet4AddressTest extends TestCase {
88

9-
#[Test]
10-
public function createAddress() {
11-
$this->assertEquals('127.0.0.1', (new Inet4Address('127.0.0.1'))->asString());
9+
/** @return iterable */
10+
private function localhost() {
11+
return [['127.0.0.1'], ['0177.0000.000.01'], ['0177.0.0.1'], ['0x7F.0.0.1'], ['0x7f.0.0.1'], ['0X7F.0.0.1']];
12+
}
13+
14+
#[Test, Values('localhost')]
15+
public function createAddress($addr) {
16+
$this->assertEquals('127.0.0.1', (new Inet4Address($addr))->asString());
1217
}
1318

1419
#[Test, Expect(FormatException::class)]
1520
public function createInvalidAddressRaisesException() {
1621
new Inet4Address('Who am I');
1722
}
1823

24+
#[Test, Expect(FormatException::class)]
25+
public function emptySegmentRaisesException() {
26+
new Inet4Address('127.0..1');
27+
}
28+
1929
#[Test, Expect(FormatException::class)]
2030
public function createInvalidAddressThatLooksLikeAddressRaisesException() {
2131
new Inet4Address('10.0.0.355');
@@ -26,9 +36,19 @@ public function createInvalidAddressWithTooManyBlocksRaisesException() {
2636
new Inet4Address('10.0.0.255.5');
2737
}
2838

29-
#[Test]
30-
public function loopbackAddress() {
31-
$this->assertTrue((new Inet4Address('127.0.0.1'))->isLoopback());
39+
#[Test, Expect(FormatException::class)]
40+
public function invalidHexRaisesException() {
41+
new Inet4Address('0xZZ.0.0.1');
42+
}
43+
44+
#[Test, Expect(FormatException::class)]
45+
public function invalidOctalRaisesException() {
46+
new Inet4Address('0ABC.0.0.1');
47+
}
48+
49+
#[Test, Values('localhost')]
50+
public function loopbackAddress($addr) {
51+
$this->assertTrue((new Inet4Address($addr))->isLoopback());
3252
}
3353

3454
#[Test]

0 commit comments

Comments
 (0)