* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @author Ashar Voultoiz <hashar at free dot fr>
+ * @author Ashar Voultoiz <hashar at free dot fr>, Aaron Schulz
*/
// Some regex definition to "play" with IP address and IP address blocks
* @return bool
*/
public static function isIPAddress( $ip ) {
- if ( !$ip ) {
- return false;
- }
return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
}
* @return bool
*/
public static function isIPv6( $ip ) {
- if ( !$ip ) {
- return false;
- }
return (bool)preg_match( '/^' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)$/', $ip );
}
* @return bool
*/
public static function isIPv4( $ip ) {
- if ( !$ip ) {
- return false;
- }
return (bool)preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip );
}
if ( self::isIPv6( $ip ) ) {
return $ip;
}
- // IPv4 CIDRs
+ // IPv4 address with CIDR
if ( strpos( $ip, '/' ) !== false ) {
$parts = explode( '/', $ip, 2 );
if ( count( $parts ) != 2 ) {
return self::toOctet( self::toUnsigned( $ip ) );
}
- /**
- * Given an IPv6 address in octet notation, returns an unsigned integer.
- * @param string $ip octet ipv6 IP address.
- * @return string
- */
- public static function toUnsigned6( $ip ) {
- if ( !$ip ) {
- return null;
- }
- $ip = explode( ':', self::sanitizeIP( $ip ) );
- $r_ip = '';
- foreach ( $ip as $v ) {
- $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
+ private static function toUnsigned6( $ip ) {
+ if ( self::isIPv6( $ip ) ) {
+ return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
}
- $r_ip = wfBaseConvert( $r_ip, 16, 10 );
- return $r_ip;
+ return false;
}
/**
- * Given an IPv6 address in octet notation, returns the expanded octet.
- * IPv4 IPs will be trimmed, thats it...
+ * Convert an IP into a nice standard form.
+ * IPv6 addresses in octet notation are expanded to 8 octets.
+ * IPv4 addresses are just trimmed.
* @param string $ip IP address in quad or octet form (CIDR or not).
* @return string
*/
/**
* Given an unsigned integer, returns an IPv6 address in octet notation
- * @param int $ip_int IP address.
+ * @param string $ip_int IP address.
* @return string
*/
public static function toOctet( $ip_int ) {
- // Convert to padded uppercase hex
- $ip_hex = wfBaseConvert( $ip_int, 10, 16, 32, false );
- // Separate into 8 octets
- $ip_oct = substr( $ip_hex, 0, 4 );
- for ( $n = 1; $n < 8; $n++ ) {
- $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
- }
- // NO leading zeroes
- $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct );
- return $ip_oct;
+ $ip_hex = wfBaseConvert( $ip_int, 10, 16, 32, false ); // uppercase hex
+ return self::hexToOctet( $ip_hex );
}
/**
/**
* Converts a hexadecimal number to an IPv6 address in octet notation
- * @param string $ip_hex hex IP
+ * @param string $ip_hex pure hex (no v6- prefix)
* @return string (of format a:b:c:d:e:f:g:h)
*/
public static function hexToOctet( $ip_hex ) {
// Convert to padded uppercase hex
- $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0' );
+ $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
// Separate into 8 octets
$ip_oct = substr( $ip_hex, 0, 4 );
for ( $n = 1; $n < 8; $n++ ) {
/**
* Converts a hexadecimal number to an IPv4 address in quad-dotted notation
- * @param string $ip Hex IP
+ * @param string $ip_hex pure hex
* @return string (of format a.b.c.d)
*/
- public static function hexToQuad( $ip ) {
+ public static function hexToQuad( $ip_hex ) {
+ // Convert to padded uppercase hex
+ $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
+ // Separate into four quads
$s = '';
for ( $i = 0; $i < 4; $i++ ) {
if ( $s !== '' ) {
$s .= '.';
}
- $s .= base_convert( substr( $ip, $i * 2, 2 ), 16, 10 );
+ $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
}
return $s;
}
* integer network and a number of bits
* @return array(string, int)
*/
- public static function parseCIDR6( $range ) {
+ private static function parseCIDR6( $range ) {
# Explode into <expanded IP,range>
$parts = explode( '/', IP::sanitizeIP( $range ), 2 );
if ( count( $parts ) != 2 ) {
return array( false, false );
}
list( $network, $bits ) = $parts;
- $network = self::toUnsigned6( $network );
+ $network = self::IPv6ToRawHex( $network );
if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
if ( $bits == 0 ) {
$network = "0";
} else {
# Native 32 bit functions WONT work here!!!
# Convert to a padded binary number
- $network = wfBaseConvert( $network, 10, 2, 128 );
+ $network = wfBaseConvert( $network, 16, 2, 128 );
# Truncate the last (128-$bits) bits and replace them with zeros
$network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
# Convert back to an integer
}
/**
- * Given a string range in a number of formats, return the start and end of
- * the range in hexadecimal. For IPv6.
+ * Given a string range in a number of formats, return the
+ * start and end of the range in hexadecimal. For IPv6.
*
* Formats are:
* 2001:0db8:85a3::7344/96 CIDR
* 2001:0db8:85a3::7344/96 Single IP
* @return array(string, int)
*/
- public static function parseRange6( $range ) {
+ private static function parseRange6( $range ) {
# Expand any IPv6 IP
$range = IP::sanitizeIP( $range );
// CIDR notation...
}
/**
- * Validate an IP address.
+ * Validate an IP address. Ranges are NOT considered valid.
* @param string $ip
* @return boolean True if it is valid.
*/
public static function isValid( $ip ) {
- return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip ) || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
+ return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
+ || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
}
/**
- * Validate an IP Block.
+ * Validate an IP Block (valid address WITH a valid prefix).
* @param string $ipblock
* @return boolean True if it is valid.
*/
public static function isValidBlock( $ipblock ) {
- return ( count( self::toArray( $ipblock ) ) == 1 + 5 );
+ return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
+ || preg_match( '/^' . RE_IPV4_BLOCK . '$/', $ipblock ) );
}
/**
* @return bool
*/
public static function isPublic( $ip ) {
+ if ( self::isIPv6( $ip ) ) {
+ return self::isPublic6( $ip );
+ }
$n = self::toUnsigned( $ip );
if ( !$n ) {
return false;
}
/**
- * Split out an IP block as an array of 4 bytes and a mask,
- * return false if it can't be determined
- *
- * @param string $ipblock A quad dotted/octet IP address
- * @return array
+ * Determine if an IPv6 address really is an IP address, and if it is public,
+ * i.e. not RFC 4193 or similar
+ * @param string $ip
+ * @return bool
*/
- public static function toArray( $ipblock ) {
- $matches = array();
- if( preg_match( '/^' . RE_IP_ADD . '(?:\/(?:' . RE_IP_PREFIX . '))?' . '$/', $ipblock, $matches ) ) {
- return $matches;
- } elseif ( preg_match( '/^' . RE_IPV6_ADD . '(?:\/(?:' . RE_IPV6_PREFIX . '))?' . '$/', $ipblock, $matches ) ) {
- return $matches;
- } else {
- return false;
+ private static function isPublic6( $ip ) {
+ static $privateRanges = false;
+ if ( !$privateRanges ) {
+ $privateRanges = array(
+ array( 'fc::', 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ), # RFC 4193 (local)
+ array( '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1' ), # loopback
+ );
+ }
+ $n = self::toHex( $ip );
+ foreach ( $privateRanges as $r ) {
+ $start = self::toHex( $r[0] );
+ $end = self::toHex( $r[1] );
+ if ( $n >= $start && $n <= $end ) {
+ return false;
+ }
}
+ return true;
}
/**
- * Return a zero-padded hexadecimal representation of an IP address.
+ * Return a zero-padded upper case hexadecimal representation of an IP address.
*
* Hexadecimal addresses are used because they can easily be extended to
* IPv6 support. To separate the ranges, the return value from this
* @return string
*/
public static function toHex( $ip ) {
- $n = self::toUnsigned( $ip );
- if ( $n !== false ) {
- $n = self::isIPv6( $ip )
- ? 'v6-' . wfBaseConvert( $n, 10, 16, 32, false )
- : wfBaseConvert( $n, 10, 16, 8, false );
+ if ( self::isIPv6( $ip ) ) {
+ $n = 'v6-' . self::IPv6ToRawHex( $ip );
+ } else {
+ $n = self::toUnsigned( $ip );
+ if ( $n !== false ) {
+ $n = wfBaseConvert( $n, 10, 16, 8, false );
+ }
}
return $n;
}
* Like ip2long() except that it actually works and has a consistent error return value.
* Comes from ProxyTools.php
* @param string $ip Quad dotted IP address.
- * @return integer
+ * @return mixed (string/int/false)
*/
public static function toUnsigned( $ip ) {
- // Use IPv6 functions if needed
if ( self::isIPv6( $ip ) ) {
- return self::toUnsigned6( $ip );
- }
- if ( $ip == '255.255.255.255' ) {
- $n = -1;
+ $n = wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
} else {
- $n = ip2long( $ip );
- if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version
- $n = false;
+ if ( $ip == '255.255.255.255' ) {
+ $n = -1;
+ } else {
+ $n = ip2long( $ip );
+ if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version
+ $n = false;
+ }
+ }
+ if ( $n < 0 ) {
+ $n += pow( 2, 32 );
}
- }
- if ( $n < 0 ) {
- $n += pow( 2, 32 );
}
return $n;
}
+ /**
+ * Given an IPv6 address in octet notation, returns a pure hex string.
+ * @param string $ip octet ipv6 IP address.
+ * @return string hex (uppercase)
+ */
+ private static function IPv6ToRawHex( $ip ) {
+ $ip = self::sanitizeIP( $ip );
+ if ( !$ip ) {
+ return null;
+ }
+ $r_ip = '';
+ foreach ( explode( ':', $ip ) as $v ) {
+ $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
+ }
+ return $r_ip;
+ }
+
/**
* Convert a dotted-quad IP to a signed integer
* @param string $ip
* @return bool Whether or not the given address is in the given range.
*/
public static function isInRange( $addr, $range ) {
- // Convert to IPv6 if needed
$hexIP = self::toHex( $addr );
list( $start, $end ) = self::parseRange( $range );
return ( strcmp( $hexIP, $start ) >= 0 &&
// not sure it should be tested with boolean false. hashar 20100924
public function testisIPAddress() {
$this->assertFalse( IP::isIPAddress( false ) );
+ $this->assertFalse( IP::isIPAddress( "" ) );
+ $this->assertFalse( IP::isIPAddress( 'abc' ) );
+ $this->assertFalse( IP::isIPAddress( ':' ) );
$this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1'), 'IPv6 with a double :: occurence' );
$this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::'), 'IPv6 with a double :: occurence, last at end' );
$this->assertFalse( IP::isIPAddress( '::2001:0DB8::5:1'), 'IPv6 with a double :: occurence, firt at beginning' );
- }
+ $this->assertFalse( IP::isIPAddress( '124.24.52' ), 'IPv4 not enough quads' );
+ $this->assertFalse( IP::isIPAddress( '24.324.52.13' ), 'IPv4 out of range' );
+ $this->assertFalse( IP::isIPAddress( '.24.52.13' ), 'IPv4 starts with period' );
- /**
- * @expectedException MWException
- */
- public function testArrayIsNotIPAddress() {
- IP::isIPAddress( array('') );
+ $this->assertTrue( IP::isIPAddress( 'fc:100::' ) );
+ $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac::' ) );
+ $this->assertTrue( IP::isIPAddress( '::' ), 'IPv6 zero address' );
+ $this->assertTrue( IP::isIPAddress( '::fc' ) );
+ $this->assertTrue( IP::isIPAddress( '::fc:100:a:d:1:e:ac' ) );
+ $this->assertTrue( IP::isIPAddress( 'fc::100' ) );
+ $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac' ) );
+ $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96', 'IPv6 range with "::"' ) );
+ $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0' ) );
+ $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24', 'IPv6 range' ) );
+ $this->assertTrue( IP::isIPAddress( '124.24.52.13' ) );
+ $this->assertTrue( IP::isIPAddress( '1.24.52.13' ) );
+ $this->assertTrue( IP::isIPAddress( '74.24.52.13/20', 'IPv4 range' ) );
}
- /**
- * @expectedException MWException
- */
- public function testArrayIsNotIPv6() {
- IP::isIPv6( array('') );
+
+ public function testisIPv6() {
+ $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' );
+ $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
+ $this->assertTrue( IP::isIPv6( 'fc:100::' ) );
+ $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
+ $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
+ $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
+ $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
+ $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
+ $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 ending it "::" with 8 words' );
+ $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ), 'IPv6 with 9 words' );
+
+ $this->assertFalse( IP::isIPv6( ':::' ) );
+ $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
+ $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
+ $this->assertTrue( IP::isIPv6( '::0' ) );
+ $this->assertTrue( IP::isIPv6( '::fc' ) );
+ $this->assertTrue( IP::isIPv6( '::fc:100' ) );
+ $this->assertTrue( IP::isIPv6( '::fc:100:a' ) );
+ $this->assertTrue( IP::isIPv6( '::fc:100:a:d' ) );
+ $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
+ $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
+ $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
+ $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 octets' );
+ $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 octets' );
+
+ $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
+ $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending in lone ":"' );
+ $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
+ $this->assertTrue( IP::isIPv6( 'fc::100' ) );
+ $this->assertTrue( IP::isIPv6( 'fc::100:a' ) );
+ $this->assertTrue( IP::isIPv6( 'fc::100:a:d' ) );
+ $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ) );
+ $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ) );
+ $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ) );
+ $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 octets' );
+ $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 octets' );
+
+ $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) );
}
public function testValidIPs() {
$this->assertTrue( IP::isValid( $ip ) , "$ip is a valid IPv4 address" );
}
}
+ foreach ( range( 0, 65535 ) as $i ) {
+ $a = sprintf( "%04x", $i );
+ $b = sprintf( "%03x", $i );
+ $c = sprintf( "%02x", $i );
+ foreach ( array_unique( array( $a, $b, $c ) ) as $f ) {
+ $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
+ $this->assertTrue( IP::isValid( $ip ) , "$ip is a valid IPv6 address" );
+ }
+ }
}
public function testInvalidIPs() {
$this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" );
}
}
+ foreach ( range( 'g', 'z' ) as $i ) {
+ $a = sprintf( "%04", $i );
+ $b = sprintf( "%03", $i );
+ $c = sprintf( "%02", $i );
+ foreach ( array_unique( array( $a, $b, $c ) ) as $f ) {
+ $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
+ $this->assertFalse( IP::isValid( $ip ) , "$ip is not a valid IPv6 address" );
+ }
+ }
}
public function testBogusIPs() {
$this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" );
}
-
public function testHexToQuad() {
- $this->assertEquals( '0.0.0.0' , IP::hexToQuad( '0' ) );
$this->assertEquals( '0.0.0.1' , IP::hexToQuad( '00000001' ) );
$this->assertEquals( '255.0.0.0' , IP::hexToQuad( 'FF000000' ) );
$this->assertEquals( '255.255.255.255', IP::hexToQuad( 'FFFFFFFF' ) );
$this->assertEquals( '10.188.222.255' , IP::hexToQuad( '0ABCDEFF' ) );
-
- $this->assertNotEquals( '0.0.0.1' , IP::hexToQuad( '1' ) );
- $this->assertNotEquals( '0.0.0.255' , IP::hexToQuad( 'FF' ) );
- $this->assertNotEquals( '0.0.255.0' , IP::hexToQuad( 'FF00' ) );
+ // hex not left-padded...
+ $this->assertEquals( '0.0.0.0' , IP::hexToQuad( '0' ) );
+ $this->assertEquals( '0.0.0.1' , IP::hexToQuad( '1' ) );
+ $this->assertEquals( '0.0.0.255' , IP::hexToQuad( 'FF' ) );
+ $this->assertEquals( '0.0.255.0' , IP::hexToQuad( 'FF00' ) );
+ }
+
+ public function testHexToOctet() {
+ $this->assertEquals( '0:0:0:0:0:0:0:1',
+ IP::hexToOctet( '00000000000000000000000000000001' ) );
+ $this->assertEquals( '0:0:0:0:0:0:FF:3',
+ IP::hexToOctet( '00000000000000000000000000FF0003' ) );
+ $this->assertEquals( '0:0:0:0:0:0:FF00:6',
+ IP::hexToOctet( '000000000000000000000000FF000006' ) );
+ $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF',
+ IP::hexToOctet( '000000000000000000000000FCCFFAFF' ) );
+ $this->assertEquals( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF',
+ IP::hexToOctet( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ) );
+ // hex not left-padded...
+ $this->assertEquals( '0:0:0:0:0:0:0:0' , IP::hexToOctet( '0' ) );
+ $this->assertEquals( '0:0:0:0:0:0:0:1' , IP::hexToOctet( '1' ) );
+ $this->assertEquals( '0:0:0:0:0:0:0:FF' , IP::hexToOctet( 'FF' ) );
+ $this->assertEquals( '0:0:0:0:0:0:0:FFD0' , IP::hexToOctet( 'FFD0' ) );
+ $this->assertEquals( '0:0:0:0:0:0:FA00:0' , IP::hexToOctet( 'FA000000' ) );
+ $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) );
}
/*