00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010 define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])');
00011 define( 'RE_IP_ADD' , RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
00012
00013 define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)');
00014 define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX);
00015
00016 define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
00017 define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
00018 define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
00019
00020 define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)');
00021
00022
00023 define( 'RE_IPV6_ADD',
00024 '(' .
00025 ':(:' . RE_IPV6_WORD . '){1,7}' .
00026 '|' .
00027 RE_IPV6_WORD . '(:{1,2}' . RE_IPV6_WORD . '|::$){1,7}' .
00028 ')'
00029 );
00030 define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
00031
00032 define( 'IP_ADDRESS_STRING',
00033 '(?:' .
00034 RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)' .
00035 '|' .
00036 RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)' .
00037 ')'
00038 );
00039
00044 class IP {
00051 public static function isIPAddress( $ip ) {
00052 if ( !$ip ) return false;
00053 if ( is_array( $ip ) ) {
00054 throw new MWException( "invalid value passed to " . __METHOD__ );
00055 }
00056
00057 return preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip) && ( substr_count($ip, '::') < 2 );
00058 }
00059
00060 public static function isIPv6( $ip ) {
00061 if ( !$ip ) return false;
00062 if( is_array( $ip ) ) {
00063 throw new MWException( "invalid value passed to " . __METHOD__ );
00064 }
00065 $doubleColons = substr_count($ip, '::');
00066
00067 return preg_match( '/^' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)$/', $ip)
00068 && ( $doubleColons == 1 || substr_count($ip,':') == 7 );
00069 }
00070
00071 public static function isIPv4( $ip ) {
00072 if ( !$ip ) return false;
00073 return preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip);
00074 }
00075
00083 public static function IPv4toIPv6( $ip ) {
00084 if ( !$ip ) return null;
00085
00086 if ( self::isIPv6( $ip ) ) return $ip;
00087
00088 if ( strpos( $ip, '/' ) !== false ) {
00089 $parts = explode( '/', $ip, 2 );
00090 if ( count( $parts ) != 2 ) {
00091 return false;
00092 }
00093 $network = self::toUnsigned( $parts[0] );
00094 if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
00095 $bits = $parts[1] + 96;
00096 return self::toOctet( $network ) . "/$bits";
00097 } else {
00098 return false;
00099 }
00100 }
00101 return self::toOctet( self::toUnsigned( $ip ) );
00102 }
00103
00109 public static function toUnsigned6( $ip ) {
00110 if ( !$ip ) return null;
00111 $ip = explode(':', self::sanitizeIP( $ip ) );
00112 $r_ip = '';
00113 foreach ($ip as $v) {
00114 $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
00115 }
00116 $r_ip = wfBaseConvert( $r_ip, 16, 10 );
00117 return $r_ip;
00118 }
00119
00126 public static function sanitizeIP( $ip ) {
00127 $ip = trim( $ip );
00128 if ( $ip === '' ) return null;
00129
00130 if ( self::isIPv4($ip) ) return $ip;
00131
00132 if ( !self::isIPv6($ip) ) return $ip;
00133
00134 $ip = strtoupper( $ip );
00135
00136 $abbrevPos = strpos( $ip, '::' );
00137 if ( $abbrevPos !== false ) {
00138
00139 if( $abbrevPos == 0 ) {
00140 $repeat = '0:'; $extra = ''; $pad = 9;
00141
00142 } else if( $abbrevPos == (strlen($ip)-2) ) {
00143 $repeat = ':0'; $extra = ''; $pad = 9;
00144
00145 } else {
00146 $repeat = ':0'; $extra = ':'; $pad = 8;
00147 }
00148 $ip = str_replace('::', str_repeat($repeat, $pad-substr_count($ip,':')).$extra, $ip);
00149 }
00150
00151 $ip = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip );
00152 return $ip;
00153 }
00154
00160 public static function toOctet( $ip_int ) {
00161
00162 $ip_hex = wfBaseConvert($ip_int, 10, 16, 32, false);
00163
00164 $ip_oct = substr( $ip_hex, 0, 4 );
00165 for ($n=1; $n < 8; $n++) {
00166 $ip_oct .= ':' . substr($ip_hex, 4*$n, 4);
00167 }
00168
00169 $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct );
00170 return $ip_oct;
00171 }
00172
00176 public static function formatHex( $hex ) {
00177 if ( substr( $hex, 0, 3 ) == 'v6-' ) {
00178 return self::hexToOctet( $hex );
00179 } else {
00180 return self::hexToQuad( $hex );
00181 }
00182 }
00183
00189 public static function hextoOctet( $ip_hex ) {
00190
00191 $ip_hex = str_pad( strtoupper($ip_hex), 32, '0');
00192
00193 $ip_oct = substr( $ip_hex, 0, 4 );
00194 for ($n=1; $n < 8; $n++) {
00195 $ip_oct .= ':' . substr($ip_hex, 4*$n, 4);
00196 }
00197
00198 $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct );
00199 return $ip_oct;
00200 }
00201
00207 public static function hexToQuad( $ip ) {
00208
00209 $s = '';
00210 for ( $i = 0; $i < 4; $i++ ) {
00211 if ( $s !== '' ) {
00212 $s .= '.';
00213 }
00214 $s .= base_convert( substr( $ip, $i * 2, 2 ), 16, 10 );
00215 }
00216 return $s;
00217 }
00218
00223 public static function parseCIDR6( $range ) {
00224 # Expand any IPv6 IP
00225 $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
00226 if ( count( $parts ) != 2 ) {
00227 return array( false, false );
00228 }
00229 $network = self::toUnsigned6( $parts[0] );
00230 if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 128 ) {
00231 $bits = $parts[1];
00232 if ( $bits == 0 ) {
00233 $network = 0;
00234 } else {
00235 # Native 32 bit functions WONT work here!!!
00236 # Convert to a padded binary number
00237 $network = wfBaseConvert( $network, 10, 2, 128 );
00238 # Truncate the last (128-$bits) bits and replace them with zeros
00239 $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
00240 # Convert back to an integer
00241 $network = wfBaseConvert( $network, 2, 10 );
00242 }
00243 } else {
00244 $network = false;
00245 $bits = false;
00246 }
00247 return array( $network, $bits );
00248 }
00249
00260 public static function parseRange6( $range ) {
00261 # Expand any IPv6 IP
00262 $range = IP::sanitizeIP( $range );
00263 if ( strpos( $range, '/' ) !== false ) {
00264 # CIDR
00265 list( $network, $bits ) = self::parseCIDR6( $range );
00266 if ( $network === false ) {
00267 $start = $end = false;
00268 } else {
00269 $start = wfBaseConvert( $network, 10, 16, 32, false );
00270 # Turn network to binary (again)
00271 $end = wfBaseConvert( $network, 10, 2, 128 );
00272 # Truncate the last (128-$bits) bits and replace them with ones
00273 $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
00274 # Convert to hex
00275 $end = wfBaseConvert( $end, 2, 16, 32, false );
00276 # see toHex() comment
00277 $start = "v6-$start"; $end = "v6-$end";
00278 }
00279 } elseif ( strpos( $range, '-' ) !== false ) {
00280 # Explicit range
00281 list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
00282 $start = self::toUnsigned6( $start ); $end = self::toUnsigned6( $end );
00283 if ( $start > $end ) {
00284 $start = $end = false;
00285 } else {
00286 $start = wfBaseConvert( $start, 10, 16, 32, false );
00287 $end = wfBaseConvert( $end, 10, 16, 32, false );
00288 }
00289 # see toHex() comment
00290 $start = "v6-$start"; $end = "v6-$end";
00291 } else {
00292 # Single IP
00293 $start = $end = self::toHex( $range );
00294 }
00295 if ( $start === false || $end === false ) {
00296 return array( false, false );
00297 } else {
00298 return array( $start, $end );
00299 }
00300 }
00301
00306 public static function isValid( $ip ) {
00307 return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip) || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip) );
00308 }
00309
00314 public static function isValidBlock( $ipblock ) {
00315 return ( count(self::toArray($ipblock)) == 1 + 5 );
00316 }
00317
00323 public static function isPublic( $ip ) {
00324 $n = self::toUnsigned( $ip );
00325 if ( !$n ) {
00326 return false;
00327 }
00328
00329
00330
00331 if( $ip != long2ip( $n ) ) {
00332 return false;
00333 }
00334
00335 static $privateRanges = false;
00336 if ( !$privateRanges ) {
00337 $privateRanges = array(
00338 array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
00339 array( '172.16.0.0', '172.31.255.255' ), # "
00340 array( '192.168.0.0', '192.168.255.255' ), # "
00341 array( '0.0.0.0', '0.255.255.255' ), # this network
00342 array( '127.0.0.0', '127.255.255.255' ), # loopback
00343 );
00344 }
00345
00346 foreach ( $privateRanges as $r ) {
00347 $start = self::toUnsigned( $r[0] );
00348 $end = self::toUnsigned( $r[1] );
00349 if ( $n >= $start && $n <= $end ) {
00350 return false;
00351 }
00352 }
00353 return true;
00354 }
00355
00363 public static function toArray( $ipblock ) {
00364 $matches = array();
00365 if( preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
00366 return $matches;
00367 } else if ( preg_match( '/^' . RE_IPV6_ADD . '(?:\/(?:'.RE_IPV6_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
00368 return $matches;
00369 } else {
00370 return false;
00371 }
00372 }
00373
00385 public static function toHex( $ip ) {
00386 $n = self::toUnsigned( $ip );
00387 if ( $n !== false ) {
00388 $n = self::isIPv6($ip) ? "v6-" . wfBaseConvert( $n, 10, 16, 32, false ) : wfBaseConvert( $n, 10, 16, 8, false );
00389 }
00390 return $n;
00391 }
00392
00400 public static function toUnsigned( $ip ) {
00401
00402 if ( self::isIPv6( $ip ) ) {
00403 return self::toUnsigned6( $ip );
00404 }
00405 if ( $ip == '255.255.255.255' ) {
00406 $n = -1;
00407 } else {
00408 $n = ip2long( $ip );
00409 if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version
00410 $n = false;
00411 }
00412 }
00413 if ( $n < 0 ) {
00414 $n += pow( 2, 32 );
00415 }
00416 return $n;
00417 }
00418
00423 public static function toSigned( $ip ) {
00424 if ( $ip == '255.255.255.255' ) {
00425 $n = -1;
00426 } else {
00427 $n = ip2long( $ip );
00428 if ( $n == -1 ) {
00429 $n = false;
00430 }
00431 }
00432 return $n;
00433 }
00434
00439 public static function parseCIDR( $range ) {
00440 $parts = explode( '/', $range, 2 );
00441 if ( count( $parts ) != 2 ) {
00442 return array( false, false );
00443 }
00444 $network = self::toSigned( $parts[0] );
00445 if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
00446 $bits = $parts[1];
00447 if ( $bits == 0 ) {
00448 $network = 0;
00449 } else {
00450 $network &= ~((1 << (32 - $bits)) - 1);
00451 }
00452 # Convert to unsigned
00453 if ( $network < 0 ) {
00454 $network += pow( 2, 32 );
00455 }
00456 } else {
00457 $network = false;
00458 $bits = false;
00459 }
00460 return array( $network, $bits );
00461 }
00462
00477 public static function parseRange( $range ) {
00478
00479 if ( self::isIPv6( $range ) ) {
00480 return self::parseRange6( $range );
00481 }
00482 if ( strpos( $range, '/' ) !== false ) {
00483 # CIDR
00484 list( $network, $bits ) = self::parseCIDR( $range );
00485 if ( $network === false ) {
00486 $start = $end = false;
00487 } else {
00488 $start = sprintf( '%08X', $network );
00489 $end = sprintf( '%08X', $network + pow( 2, (32 - $bits) ) - 1 );
00490 }
00491 } elseif ( strpos( $range, '-' ) !== false ) {
00492 # Explicit range
00493 list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
00494 if( self::isIPAddress( $start ) && self::isIPAddress( $end ) ) {
00495 $start = self::toUnsigned( $start ); $end = self::toUnsigned( $end );
00496 if ( $start > $end ) {
00497 $start = $end = false;
00498 } else {
00499 $start = sprintf( '%08X', $start );
00500 $end = sprintf( '%08X', $end );
00501 }
00502 } else {
00503 $start = $end = false;
00504 }
00505 } else {
00506 # Single IP
00507 $start = $end = self::toHex( $range );
00508 }
00509 if ( $start === false || $end === false ) {
00510 return array( false, false );
00511 } else {
00512 return array( $start, $end );
00513 }
00514 }
00515
00522 public static function isInRange( $addr, $range ) {
00523
00524 $hexIP = self::toHex( $addr );
00525 list( $start, $end ) = self::parseRange( $range );
00526 return (strcmp($hexIP, $start) >= 0 &&
00527 strcmp($hexIP, $end) <= 0);
00528 }
00529
00540 public static function canonicalize( $addr ) {
00541 if ( self::isValid( $addr ) )
00542 return $addr;
00543
00544
00545 if ( strpos($addr,':') !==false && strpos($addr,'.') !==false ) {
00546 $addr = substr( $addr, strrpos($addr,':')+1 );
00547 if( self::isIPv4($addr) ) return $addr;
00548 }
00549
00550
00551 $m = array();
00552 if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) )
00553 return '127.0.0.1';
00554
00555
00556 if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) )
00557 return $m[1];
00558 if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD . ':' . RE_IPV6_WORD . '$/i', $addr, $m ) )
00559 return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
00560
00561 return null;
00562 }
00563 }