diff options
author | robocoder <anthon.pang@gmail.com> | 2011-04-26 22:58:36 +0400 |
---|---|---|
committer | robocoder <anthon.pang@gmail.com> | 2011-04-26 22:58:36 +0400 |
commit | cc0bd56e815bb77bd99250e78caeb0b8e8138f93 (patch) | |
tree | 188dedbf15f6ba0831e5c2c48fdb321d423519b3 | |
parent | 298988092d46f683c34832b04b24a433fcdcfa71 (diff) |
fixes #2359 - note: on Windows with php 5.1.x/5.2.x, gethostbyaddr() doesn't work with ipv6 addresses
git-svn-id: http://dev.piwik.org/svn/trunk@4564 59fd770c-687e-43c8-a1e3-f5a4ff64c105
-rw-r--r-- | LEGALNOTICE | 4 | ||||
-rw-r--r-- | core/IP.php | 243 | ||||
-rw-r--r-- | tests/core/IP.test.php | 57 |
3 files changed, 219 insertions, 85 deletions
diff --git a/LEGALNOTICE b/LEGALNOTICE index f40d5c0320..d7b2e927a2 100644 --- a/LEGALNOTICE +++ b/LEGALNOTICE @@ -200,10 +200,6 @@ THIRD-PARTY COMPONENTS AND LIBRARIES Link: http://pear.php.net/package/PEAR License: New BSD - Name: PHP_Compat (inet_pton() and inet_ntop() functions) - Link: http://pear.php.net/package/PHP_Compat/ - License: LGPL - Name: PhpSecInfo Link: http://phpsec.org/projects/phpsecinfo/ License: New BSD diff --git a/core/IP.php b/core/IP.php index b2d94d4b14..0cc98dfdb2 100644 --- a/core/IP.php +++ b/core/IP.php @@ -10,6 +10,22 @@ * @package Piwik */ +if(Piwik_Common::isWindows()) { + function _inet_ntop($in_addr) { + return php_compat_inet_ntop($in_addr); + } + function _inet_pton($address) { + return php_compat_inet_pton($address); + } +} else { + function _inet_ntop($in_addr) { + return inet_ntop($in_addr); + } + function _inet_pton($address) { + return inet_pton($address); + } +} + /** * Handling IP addresses (both IPv4 and IPv6). * @@ -21,6 +37,10 @@ * As a matter of naming convention, we use $ip for the network address format * and $ipString for the presentation format (i.e., human-readable form). * + * We're not using the network address format (in_addr) for socket functions, + * so we don't have to worry about incompatibility with Windows UNICODE + * and inetPtonW(). + * * @package Piwik */ class Piwik_IP @@ -113,7 +133,7 @@ class Piwik_IP } // single IP - if(($ip = @inet_pton($ipRangeString)) === false) + if(($ip = @_inet_pton($ipRangeString)) === false) return false; $maxbits = $strlen($ip) * 8; @@ -137,7 +157,7 @@ class Piwik_IP static public function P2N($ipString) { // use @inet_pton() because it throws an exception and E_WARNING on invalid input - $ip = @inet_pton($ipString); + $ip = @_inet_pton($ipString); return $ip === false ? "\x00\x00\x00\x00" : $ip; } @@ -152,7 +172,7 @@ class Piwik_IP static public function N2P($ip) { // use @inet_ntop() because it throws an exception and E_WARNING on invalid input - $ipStr = @inet_ntop($ip); + $ipStr = @_inet_ntop($ip); return $ipStr === false ? '0.0.0.0' : $ipStr; } @@ -186,7 +206,7 @@ class Piwik_IP $bits = substr($ipRange, $pos + 1); $range = substr($ipRange, 0, $pos); - $high = $low = @inet_pton($range); + $high = $low = @_inet_pton($range); if($low === false) { return false; @@ -381,95 +401,166 @@ class Piwik_IP static public function getHostByAddr($ipStr) { // PHP's reverse lookup supports ipv4 and ipv6 - return gethostbyaddr($ipStr); + // except on Windows before PHP 5.3 + return @gethostbyaddr($ipStr); } } /** - * Replace inet_ntop() + * Converts a packed internet address to a human readable representation * - * @category PHP - * @package PHP_Compat - * @license LGPL - http://www.gnu.org/licenses/lgpl.html - * @copyright 2004-2007 Aidan Lister <aidan@php.net>, Arpad Ray <arpad@php.net> - * @link http://php.net/inet_ntop - * @author Arpad Ray <arpad@php.net> - * @version $Revision: 269597 $ - * @since PHP 5.1.0 - * @require PHP 4.0.0 (long2ip) + * @link http://php.net/inet_ntop + * + * @param string $in_addr 32-bit IPv4 or 128-bit IPv6 address + * @return string|false string representation of address or false on failure */ function php_compat_inet_ntop($in_addr) { - switch (strlen($in_addr)) { - case 4: - list(,$r) = unpack('N', $in_addr); - return long2ip($r); - - case 16: - $r = substr(chunk_split(bin2hex($in_addr), 4, ':'), 0, -1); - $r = preg_replace( - array('/(?::?\b0+\b:?){2,}/', '/\b0+([^0])/e'), - array('::', '(int)"$1"?"$1":"0$1"'), - $r); - return $r; - } - - return false; -} + switch (strlen($in_addr)) { + case 4: + $r = str_split(bin2hex($in_addr), 2); + $r = array_map('hexdec', $r); + $r = implode('.', $r); + return $r; + + case 16: + $r = bin2hex($in_addr); + + // IPv4-mapped address + if(substr_compare($r, '00000000000000000000ffff', 0, 24) === 0) + { + $r = str_split(substr($r, 24), 2); + $r = array_map('hexdec', $r); + $r = implode('.', $r); + return '::ffff:' . $r; + } + + $r = str_split($r, 4); + $r = implode(':', $r); + + // compress leading zeros + $r = preg_replace( + '/(^|:)0{1,3}/', + '$1', + $r + ); + + // compress groups of zeros + if(preg_match_all('/(?:^|:)(0(:|$))+/', $r, $matches)) + { + $longestMatch = 0; + foreach($matches[0] as $aMatch) + { + if(strlen($aMatch) > strlen($longestMatch)) + { + $longestMatch = $aMatch; + } + } + $r = substr_replace($r, '::', strpos($r, $longestMatch), strlen($longestMatch)); + } + return $r; + } -// Define -if (!function_exists('inet_ntop')) { - function inet_ntop($in_addr) - { - return php_compat_inet_ntop($in_addr); - } + return false; } /** - * Replace inet_pton() + * Converts a human readable IP address to its packed in_addr representation + * + * @link http://php.net/inet_pton * - * @category PHP - * @package PHP_Compat - * @license LGPL - http://www.gnu.org/licenses/lgpl.html - * @copyright 2004-2007 Aidan Lister <aidan@php.net>, Arpad Ray <arpad@php.net> - * @link http://php.net/inet_pton - * @author Arpad Ray <arpad@php.net> - * @version $Revision: 269597 $ - * @since PHP 5.1.0 - * @require PHP 4.2.0 (array_fill) + * @param string $address a human readable IPv4 or IPv6 address + * @return string in_addr representation or false on failure */ function php_compat_inet_pton($address) { - $r = ip2long($address); - if ($r !== false && $r != -1) { - return pack('N', $r); - } - - $delim_count = substr_count($address, ':'); - if ($delim_count < 1 || $delim_count > 7) { - return false; - } - - $r = explode(':', $address); - $rcount = count($r); - if (($doub = array_search('', $r, 1)) !== false) { - $length = (!$doub || $doub == $rcount - 1 ? 2 : 1); - array_splice($r, $doub, $length, array_fill(0, 8 + $length - $rcount, 0)); - } - - $r = array_map('hexdec', $r); - array_unshift($r, 'n*'); - $r = call_user_func_array('pack', $r); - - return $r; -} + if(empty($address) || strspn($address, '01234567890abcdefABCDEF:.') !== strlen($address)) + { + return false; + } + + // IPv4 + if(preg_match('/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/i', $address, $matches)) + { + for($i = count($matches); $i-- > 1; ) + { + if($matches[$i] > 255 || + ($matches[$i][0] == '0' && strlen($matches[$i]) > 1)) + { + return false; + } + } + + $r = ip2long($address); + if ($r === false) + { + return false; + } + return pack('N', $r); + } -// Define -if (!function_exists('inet_pton')) { - function inet_pton($address) - { - return php_compat_inet_pton($address); - } + // IPv6 + if($address[0] == ':') + { + $address = '0'.$address; + } + if($address[strlen($address) - 1] == ':') + { + $address .= '0'; + } + + $looksLikeIpv4Mapped = false; + if(preg_match('/:ffff:([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/i', $address, $matches)) + { + for($i = count($matches); $i-- > 1; ) + { + if($matches[$i] > 255 || + ($matches[$i][0] == '0' && strlen($matches[$i]) > 1)) + { + return false; + } + } + + $looksLikeIpv4Mapped = true; + $address = substr_replace($address, ':ffff:' . dechex($matches[1]) . sprintf("%02x", $matches[2]) . ':' . dechex($matches[3]) . sprintf("%02x", $matches[4]), strrpos($address, $matches[0])); + } + + $r = explode(':', $address); + $count = count($r); + if($count > 8) + { + return false; + } + if($count < 8) + { + // grouped zeros + $zeroGroup = array_search('', $r, 1); + if($zeroGroup === false) + { + return false; + } + + array_splice($r, $zeroGroup, 1, array_fill(0, 8 - $count + 1, '0')); + } + + // leading zeros + foreach($r as $k => $v) + { + if(strlen($v) > 4) + { + return false; + } + $r[$k] = str_pad($v, 4, '0', STR_PAD_LEFT); + } + + $r = implode(array_map(create_function('$v', 'return pack("H*", $v);'), $r)); + + if($looksLikeIpv4Mapped && @substr_compare($r, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 0, 10) !== 0) + { + return false; + } + + return $r; } diff --git a/tests/core/IP.test.php b/tests/core/IP.test.php index 0a9d4ef9a5..7edc8f877d 100644 --- a/tests/core/IP.test.php +++ b/tests/core/IP.test.php @@ -11,13 +11,13 @@ class Test_Piwik_IP extends UnitTestCase { parent::__construct( $title ); } - + public function setUp() { parent::setUp(); $_GET = $_POST = array(); } - + public function tearDown() { parent::tearDown(); @@ -108,7 +108,7 @@ class Test_Piwik_IP extends UnitTestCase '::' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", '::1' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", '::fffe:7f00:1' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x7f\x00\x00\x01", - '::ffff:127.0.0.1' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x7f\x00\x00\x01", + '::ffff:127.0.0.1' => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x7f\x00\x00\x01", '2001:5c0:1000:b::90f8' => "\x20\x01\x05\xc0\x10\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x90\xf8", ); } @@ -493,7 +493,54 @@ class Test_Piwik_IP extends UnitTestCase function test_getHostByAddr() { - $this->assertEqual( Piwik_IP::getHostByAddr('127.0.0.1'), 'localhost', '127.0.0.1 -> localhost' ); - $this->assertEqual( Piwik_IP::getHostByAddr('::1'), 'ip6-localhost', '::1 -> ip6-localhost' ); + $hosts = array( 'localhost', @php_uname('n') ); + $this->assertTrue( in_array(Piwik_IP::getHostByAddr('127.0.0.1'), $hosts), '127.0.0.1 -> localhost' ); + + if (!Piwik_Common::isWindows() || PHP_VERSION >= '5.3') + { + $hosts = array( 'ip6-localhost', @php_uname('n') ); + $this->assertTrue( in_array(Piwik_IP::getHostByAddr('::1'), $hosts), '::1 -> ip6-localhost' ); + } + } + + + function test_php_compat_inet_pton() + { + $adds = array( + '127.0.0.1' => '7f000001', + '192.232.131.222' => 'c0e883de', + '255.0.0.0' => 'ff000000', + '255.255.255.255' => 'ffffffff', + '::1' => '00000000000000000000000000000001', + '2001:260:0:10::1' => '20010260000000100000000000000001', + '2001:5c0:1000:b::90f8' => '200105c01000000b00000000000090f8', + 'fe80::200:4cff:fe43:172f' => 'fe8000000000000002004cfffe43172f', + '::ffff:127.0.0.1' => '00000000000000000000ffff7f000001', + ); + + foreach ($adds as $k => $v) { + $this->assertEqual( bin2hex(php_compat_inet_pton($k)), $v, $k ); + } + } + + function test_php_compat_inet_ntop() + { + $adds = array( + '127.0.0.1' => '7f000001', + '192.232.131.222' => 'c0e883de', + '255.0.0.0' => 'ff000000', + '255.255.255.255' => 'ffffffff', + '::1' => '00000000000000000000000000000001', + '2001:260:0:10::1' => '20010260000000100000000000000001', + '2001:0:0:260::1' => '20010000000002600000000000000001', + '2001::260:0:0:10:1' => '20010000000002600000000000100001', + '2001:5c0:1000:b::90f8' => '200105c01000000b00000000000090f8', + 'fe80::200:4cff:fe43:172f' => 'fe8000000000000002004cfffe43172f', + '::ffff:127.0.0.1' => '00000000000000000000ffff7f000001', + ); + + foreach ($adds as $k => $v) { + $this->assertEqual( php_compat_inet_ntop(pack('H*', $v)), $k, $k ); + } } } |