[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Functions and constants to play with IP addresses and ranges 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @author Antoine Musso "<hashar at free dot fr>", Aaron Schulz 22 */ 23 24 // Some regex definition to "play" with IP address and IP address blocks 25 26 // An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255 27 define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' ); 28 define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE ); 29 // An IPv4 block is an IP address and a prefix (d1 to d32) 30 define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' ); 31 define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX ); 32 33 // An IPv6 address is made up of 8 words (each x0000 to xFFFF). 34 // However, the "::" abbreviation can be used on consecutive x0000 words. 35 define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' ); 36 define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)' ); 37 define( 'RE_IPV6_ADD', 38 '(?:' . // starts with "::" (including "::") 39 ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' . 40 '|' . // ends with "::" (except "::") 41 RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' . 42 '|' . // contains one "::" in the middle (the ^ makes the test fail if none found) 43 RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' . 44 '|' . // contains no "::" 45 RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' . 46 ')' 47 ); 48 // An IPv6 block is an IP address and a prefix (d1 to d128) 49 define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX ); 50 // For IPv6 canonicalization (NOT for strict validation; these are quite lax!) 51 define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' ); 52 define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' ); 53 54 // This might be useful for regexps used elsewhere, matches any IPv6 or IPv6 address or network 55 define( 'IP_ADDRESS_STRING', 56 '(?:' . 57 RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4 58 '|' . 59 RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6 60 ')' 61 ); 62 63 /** 64 * A collection of public static functions to play with IP address 65 * and IP blocks. 66 */ 67 class IP { 68 /** @var IPSet */ 69 private static $proxyIpSet = null; 70 71 /** 72 * Determine if a string is as valid IP address or network (CIDR prefix). 73 * SIIT IPv4-translated addresses are rejected. 74 * Note: canonicalize() tries to convert translated addresses to IPv4. 75 * 76 * @param string $ip Possible IP address 77 * @return bool 78 */ 79 public static function isIPAddress( $ip ) { 80 return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip ); 81 } 82 83 /** 84 * Given a string, determine if it as valid IP in IPv6 only. 85 * Note: Unlike isValid(), this looks for networks too. 86 * 87 * @param string $ip Possible IP address 88 * @return bool 89 */ 90 public static function isIPv6( $ip ) { 91 return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip ); 92 } 93 94 /** 95 * Given a string, determine if it as valid IP in IPv4 only. 96 * Note: Unlike isValid(), this looks for networks too. 97 * 98 * @param string $ip Possible IP address 99 * @return bool 100 */ 101 public static function isIPv4( $ip ) { 102 return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip ); 103 } 104 105 /** 106 * Validate an IP address. Ranges are NOT considered valid. 107 * SIIT IPv4-translated addresses are rejected. 108 * Note: canonicalize() tries to convert translated addresses to IPv4. 109 * 110 * @param string $ip 111 * @return bool True if it is valid 112 */ 113 public static function isValid( $ip ) { 114 return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip ) 115 || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) ); 116 } 117 118 /** 119 * Validate an IP Block (valid address WITH a valid prefix). 120 * SIIT IPv4-translated addresses are rejected. 121 * Note: canonicalize() tries to convert translated addresses to IPv4. 122 * 123 * @param string $ipblock 124 * @return bool True if it is valid 125 */ 126 public static function isValidBlock( $ipblock ) { 127 return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock ) 128 || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) ); 129 } 130 131 /** 132 * Convert an IP into a verbose, uppercase, normalized form. 133 * IPv6 addresses in octet notation are expanded to 8 words. 134 * IPv4 addresses are just trimmed. 135 * 136 * @param string $ip IP address in quad or octet form (CIDR or not). 137 * @return string 138 */ 139 public static function sanitizeIP( $ip ) { 140 $ip = trim( $ip ); 141 if ( $ip === '' ) { 142 return null; 143 } 144 if ( self::isIPv4( $ip ) || !self::isIPv6( $ip ) ) { 145 return $ip; // nothing else to do for IPv4 addresses or invalid ones 146 } 147 // Remove any whitespaces, convert to upper case 148 $ip = strtoupper( $ip ); 149 // Expand zero abbreviations 150 $abbrevPos = strpos( $ip, '::' ); 151 if ( $abbrevPos !== false ) { 152 // We know this is valid IPv6. Find the last index of the 153 // address before any CIDR number (e.g. "a:b:c::/24"). 154 $CIDRStart = strpos( $ip, "/" ); 155 $addressEnd = ( $CIDRStart !== false ) 156 ? $CIDRStart - 1 157 : strlen( $ip ) - 1; 158 // If the '::' is at the beginning... 159 if ( $abbrevPos == 0 ) { 160 $repeat = '0:'; 161 $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::' 162 $pad = 9; // 7+2 (due to '::') 163 // If the '::' is at the end... 164 } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) { 165 $repeat = ':0'; 166 $extra = ''; 167 $pad = 9; // 7+2 (due to '::') 168 // If the '::' is in the middle... 169 } else { 170 $repeat = ':0'; 171 $extra = ':'; 172 $pad = 8; // 6+2 (due to '::') 173 } 174 $ip = str_replace( '::', 175 str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra, 176 $ip 177 ); 178 } 179 // Remove leading zeros from each bloc as needed 180 $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip ); 181 182 return $ip; 183 } 184 185 /** 186 * Prettify an IP for display to end users. 187 * This will make it more compact and lower-case. 188 * 189 * @param string $ip 190 * @return string 191 */ 192 public static function prettifyIP( $ip ) { 193 $ip = self::sanitizeIP( $ip ); // normalize (removes '::') 194 if ( self::isIPv6( $ip ) ) { 195 // Split IP into an address and a CIDR 196 if ( strpos( $ip, '/' ) !== false ) { 197 list( $ip, $cidr ) = explode( '/', $ip, 2 ); 198 } else { 199 list( $ip, $cidr ) = array( $ip, '' ); 200 } 201 // Get the largest slice of words with multiple zeros 202 $offset = 0; 203 $longest = $longestPos = false; 204 while ( preg_match( 205 '!(?:^|:)0(?::0)+(?:$|:)!', $ip, $m, PREG_OFFSET_CAPTURE, $offset 206 ) ) { 207 list( $match, $pos ) = $m[0]; // full match 208 if ( strlen( $match ) > strlen( $longest ) ) { 209 $longest = $match; 210 $longestPos = $pos; 211 } 212 $offset = ( $pos + strlen( $match ) ); // advance 213 } 214 if ( $longest !== false ) { 215 // Replace this portion of the string with the '::' abbreviation 216 $ip = substr_replace( $ip, '::', $longestPos, strlen( $longest ) ); 217 } 218 // Add any CIDR back on 219 if ( $cidr !== '' ) { 220 $ip = "{$ip}/{$cidr}"; 221 } 222 // Convert to lower case to make it more readable 223 $ip = strtolower( $ip ); 224 } 225 226 return $ip; 227 } 228 229 /** 230 * Given a host/port string, like one might find in the host part of a URL 231 * per RFC 2732, split the hostname part and the port part and return an 232 * array with an element for each. If there is no port part, the array will 233 * have false in place of the port. If the string was invalid in some way, 234 * false is returned. 235 * 236 * This was easy with IPv4 and was generally done in an ad-hoc way, but 237 * with IPv6 it's somewhat more complicated due to the need to parse the 238 * square brackets and colons. 239 * 240 * A bare IPv6 address is accepted despite the lack of square brackets. 241 * 242 * @param string $both The string with the host and port 243 * @return array 244 */ 245 public static function splitHostAndPort( $both ) { 246 if ( substr( $both, 0, 1 ) === '[' ) { 247 if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P<port>\d+))?$/', $both, $m ) ) { 248 if ( isset( $m['port'] ) ) { 249 return array( $m[1], intval( $m['port'] ) ); 250 } else { 251 return array( $m[1], false ); 252 } 253 } else { 254 // Square bracket found but no IPv6 255 return false; 256 } 257 } 258 $numColons = substr_count( $both, ':' ); 259 if ( $numColons >= 2 ) { 260 // Is it a bare IPv6 address? 261 if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) { 262 return array( $both, false ); 263 } else { 264 // Not valid IPv6, but too many colons for anything else 265 return false; 266 } 267 } 268 if ( $numColons >= 1 ) { 269 // Host:port? 270 $bits = explode( ':', $both ); 271 if ( preg_match( '/^\d+/', $bits[1] ) ) { 272 return array( $bits[0], intval( $bits[1] ) ); 273 } else { 274 // Not a valid port 275 return false; 276 } 277 } 278 279 // Plain hostname 280 return array( $both, false ); 281 } 282 283 /** 284 * Given a host name and a port, combine them into host/port string like 285 * you might find in a URL. If the host contains a colon, wrap it in square 286 * brackets like in RFC 2732. If the port matches the default port, omit 287 * the port specification 288 * 289 * @param string $host 290 * @param int $port 291 * @param bool|int $defaultPort 292 * @return string 293 */ 294 public static function combineHostAndPort( $host, $port, $defaultPort = false ) { 295 if ( strpos( $host, ':' ) !== false ) { 296 $host = "[$host]"; 297 } 298 if ( $defaultPort !== false && $port == $defaultPort ) { 299 return $host; 300 } else { 301 return "$host:$port"; 302 } 303 } 304 305 /** 306 * Convert an IPv4 or IPv6 hexadecimal representation back to readable format 307 * 308 * @param string $hex Number, with "v6-" prefix if it is IPv6 309 * @return string Quad-dotted (IPv4) or octet notation (IPv6) 310 */ 311 public static function formatHex( $hex ) { 312 if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6 313 return self::hexToOctet( substr( $hex, 3 ) ); 314 } else { // IPv4 315 return self::hexToQuad( $hex ); 316 } 317 } 318 319 /** 320 * Converts a hexadecimal number to an IPv6 address in octet notation 321 * 322 * @param string $ip_hex Pure hex (no v6- prefix) 323 * @return string (of format a:b:c:d:e:f:g:h) 324 */ 325 public static function hexToOctet( $ip_hex ) { 326 // Pad hex to 32 chars (128 bits) 327 $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT ); 328 // Separate into 8 words 329 $ip_oct = substr( $ip_hex, 0, 4 ); 330 for ( $n = 1; $n < 8; $n++ ) { 331 $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 ); 332 } 333 // NO leading zeroes 334 $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct ); 335 336 return $ip_oct; 337 } 338 339 /** 340 * Converts a hexadecimal number to an IPv4 address in quad-dotted notation 341 * 342 * @param string $ip_hex Pure hex 343 * @return string (of format a.b.c.d) 344 */ 345 public static function hexToQuad( $ip_hex ) { 346 // Pad hex to 8 chars (32 bits) 347 $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT ); 348 // Separate into four quads 349 $s = ''; 350 for ( $i = 0; $i < 4; $i++ ) { 351 if ( $s !== '' ) { 352 $s .= '.'; 353 } 354 $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 ); 355 } 356 357 return $s; 358 } 359 360 /** 361 * Determine if an IP address really is an IP address, and if it is public, 362 * i.e. not RFC 1918 or similar 363 * 364 * @param string $ip 365 * @return bool 366 */ 367 public static function isPublic( $ip ) { 368 static $privateSet = null; 369 if ( !$privateSet ) { 370 $privateSet = new IPSet( array( 371 '10.0.0.0/8', # RFC 1918 (private) 372 '172.16.0.0/12', # RFC 1918 (private) 373 '192.168.0.0/16', # RFC 1918 (private) 374 '0.0.0.0/8', # this network 375 '127.0.0.0/8', # loopback 376 'fc00::/7', # RFC 4193 (local) 377 '0:0:0:0:0:0:0:1', # loopback 378 ) ); 379 } 380 return !$privateSet->match( $ip ); 381 } 382 383 /** 384 * Return a zero-padded upper case hexadecimal representation of an IP address. 385 * 386 * Hexadecimal addresses are used because they can easily be extended to 387 * IPv6 support. To separate the ranges, the return value from this 388 * function for an IPv6 address will be prefixed with "v6-", a non- 389 * hexadecimal string which sorts after the IPv4 addresses. 390 * 391 * @param string $ip Quad dotted/octet IP address. 392 * @return string|bool False on failure 393 */ 394 public static function toHex( $ip ) { 395 if ( self::isIPv6( $ip ) ) { 396 $n = 'v6-' . self::IPv6ToRawHex( $ip ); 397 } elseif ( self::isIPv4( $ip ) ) { 398 // Bug 60035: an IP with leading 0's fails in ip2long sometimes (e.g. *.08) 399 $ip = preg_replace( '/(?<=\.)0+(?=[1-9])/', '', $ip ); 400 $n = ip2long( $ip ); 401 if ( $n < 0 ) { 402 $n += pow( 2, 32 ); 403 # On 32-bit platforms (and on Windows), 2^32 does not fit into an int, 404 # so $n becomes a float. We convert it to string instead. 405 if ( is_float( $n ) ) { 406 $n = (string)$n; 407 } 408 } 409 if ( $n !== false ) { 410 # Floating points can handle the conversion; faster than wfBaseConvert() 411 $n = strtoupper( str_pad( base_convert( $n, 10, 16 ), 8, '0', STR_PAD_LEFT ) ); 412 } 413 } else { 414 $n = false; 415 } 416 417 return $n; 418 } 419 420 /** 421 * Given an IPv6 address in octet notation, returns a pure hex string. 422 * 423 * @param string $ip Octet ipv6 IP address. 424 * @return string|bool Pure hex (uppercase); false on failure 425 */ 426 private static function IPv6ToRawHex( $ip ) { 427 $ip = self::sanitizeIP( $ip ); 428 if ( !$ip ) { 429 return false; 430 } 431 $r_ip = ''; 432 foreach ( explode( ':', $ip ) as $v ) { 433 $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT ); 434 } 435 436 return $r_ip; 437 } 438 439 /** 440 * Convert a network specification in CIDR notation 441 * to an integer network and a number of bits 442 * 443 * @param string $range IP with CIDR prefix 444 * @return array(int or string, int) 445 */ 446 public static function parseCIDR( $range ) { 447 if ( self::isIPv6( $range ) ) { 448 return self::parseCIDR6( $range ); 449 } 450 $parts = explode( '/', $range, 2 ); 451 if ( count( $parts ) != 2 ) { 452 return array( false, false ); 453 } 454 list( $network, $bits ) = $parts; 455 $network = ip2long( $network ); 456 if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) { 457 if ( $bits == 0 ) { 458 $network = 0; 459 } else { 460 $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 ); 461 } 462 # Convert to unsigned 463 if ( $network < 0 ) { 464 $network += pow( 2, 32 ); 465 } 466 } else { 467 $network = false; 468 $bits = false; 469 } 470 471 return array( $network, $bits ); 472 } 473 474 /** 475 * Given a string range in a number of formats, 476 * return the start and end of the range in hexadecimal. 477 * 478 * Formats are: 479 * 1.2.3.4/24 CIDR 480 * 1.2.3.4 - 1.2.3.5 Explicit range 481 * 1.2.3.4 Single IP 482 * 483 * 2001:0db8:85a3::7344/96 CIDR 484 * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range 485 * 2001:0db8:85a3::7344 Single IP 486 * @param string $range IP range 487 * @return array(string, string) 488 */ 489 public static function parseRange( $range ) { 490 // CIDR notation 491 if ( strpos( $range, '/' ) !== false ) { 492 if ( self::isIPv6( $range ) ) { 493 return self::parseRange6( $range ); 494 } 495 list( $network, $bits ) = self::parseCIDR( $range ); 496 if ( $network === false ) { 497 $start = $end = false; 498 } else { 499 $start = sprintf( '%08X', $network ); 500 $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 ); 501 } 502 // Explicit range 503 } elseif ( strpos( $range, '-' ) !== false ) { 504 list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) ); 505 if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) { 506 return self::parseRange6( $range ); 507 } 508 if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) { 509 $start = self::toHex( $start ); 510 $end = self::toHex( $end ); 511 if ( $start > $end ) { 512 $start = $end = false; 513 } 514 } else { 515 $start = $end = false; 516 } 517 } else { 518 # Single IP 519 $start = $end = self::toHex( $range ); 520 } 521 if ( $start === false || $end === false ) { 522 return array( false, false ); 523 } else { 524 return array( $start, $end ); 525 } 526 } 527 528 /** 529 * Convert a network specification in IPv6 CIDR notation to an 530 * integer network and a number of bits 531 * 532 * @param string $range 533 * 534 * @return array(string, int) 535 */ 536 private static function parseCIDR6( $range ) { 537 # Explode into <expanded IP,range> 538 $parts = explode( '/', IP::sanitizeIP( $range ), 2 ); 539 if ( count( $parts ) != 2 ) { 540 return array( false, false ); 541 } 542 list( $network, $bits ) = $parts; 543 $network = self::IPv6ToRawHex( $network ); 544 if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) { 545 if ( $bits == 0 ) { 546 $network = "0"; 547 } else { 548 # Native 32 bit functions WONT work here!!! 549 # Convert to a padded binary number 550 $network = wfBaseConvert( $network, 16, 2, 128 ); 551 # Truncate the last (128-$bits) bits and replace them with zeros 552 $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT ); 553 # Convert back to an integer 554 $network = wfBaseConvert( $network, 2, 10 ); 555 } 556 } else { 557 $network = false; 558 $bits = false; 559 } 560 561 return array( $network, (int)$bits ); 562 } 563 564 /** 565 * Given a string range in a number of formats, return the 566 * start and end of the range in hexadecimal. For IPv6. 567 * 568 * Formats are: 569 * 2001:0db8:85a3::7344/96 CIDR 570 * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range 571 * 2001:0db8:85a3::7344/96 Single IP 572 * 573 * @param string $range 574 * 575 * @return array(string, string) 576 */ 577 private static function parseRange6( $range ) { 578 # Expand any IPv6 IP 579 $range = IP::sanitizeIP( $range ); 580 // CIDR notation... 581 if ( strpos( $range, '/' ) !== false ) { 582 list( $network, $bits ) = self::parseCIDR6( $range ); 583 if ( $network === false ) { 584 $start = $end = false; 585 } else { 586 $start = wfBaseConvert( $network, 10, 16, 32, false ); 587 # Turn network to binary (again) 588 $end = wfBaseConvert( $network, 10, 2, 128 ); 589 # Truncate the last (128-$bits) bits and replace them with ones 590 $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT ); 591 # Convert to hex 592 $end = wfBaseConvert( $end, 2, 16, 32, false ); 593 # see toHex() comment 594 $start = "v6-$start"; 595 $end = "v6-$end"; 596 } 597 // Explicit range notation... 598 } elseif ( strpos( $range, '-' ) !== false ) { 599 list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) ); 600 $start = self::toHex( $start ); 601 $end = self::toHex( $end ); 602 if ( $start > $end ) { 603 $start = $end = false; 604 } 605 } else { 606 # Single IP 607 $start = $end = self::toHex( $range ); 608 } 609 if ( $start === false || $end === false ) { 610 return array( false, false ); 611 } else { 612 return array( $start, $end ); 613 } 614 } 615 616 /** 617 * Determine if a given IPv4/IPv6 address is in a given CIDR network 618 * 619 * @param string $addr The address to check against the given range. 620 * @param string $range The range to check the given address against. 621 * @return bool Whether or not the given address is in the given range. 622 */ 623 public static function isInRange( $addr, $range ) { 624 $hexIP = self::toHex( $addr ); 625 list( $start, $end ) = self::parseRange( $range ); 626 627 return ( strcmp( $hexIP, $start ) >= 0 && 628 strcmp( $hexIP, $end ) <= 0 ); 629 } 630 631 /** 632 * Convert some unusual representations of IPv4 addresses to their 633 * canonical dotted quad representation. 634 * 635 * This currently only checks a few IPV4-to-IPv6 related cases. More 636 * unusual representations may be added later. 637 * 638 * @param string $addr Something that might be an IP address 639 * @return string Valid dotted quad IPv4 address or null 640 */ 641 public static function canonicalize( $addr ) { 642 // remove zone info (bug 35738) 643 $addr = preg_replace( '/\%.*/', '', $addr ); 644 645 if ( self::isValid( $addr ) ) { 646 return $addr; 647 } 648 // Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4 649 if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) { 650 $addr = substr( $addr, strrpos( $addr, ':' ) + 1 ); 651 if ( self::isIPv4( $addr ) ) { 652 return $addr; 653 } 654 } 655 // IPv6 loopback address 656 $m = array(); 657 if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) { 658 return '127.0.0.1'; 659 } 660 // IPv4-mapped and IPv4-compatible IPv6 addresses 661 if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) { 662 return $m[1]; 663 } 664 if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD . 665 ':' . RE_IPV6_WORD . '$/i', $addr, $m ) 666 ) { 667 return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) ); 668 } 669 670 return null; // give up 671 } 672 673 /** 674 * Gets rid of unneeded numbers in quad-dotted/octet IP strings 675 * For example, 127.111.113.151/24 -> 127.111.113.0/24 676 * @param string $range IP address to normalize 677 * @return string 678 */ 679 public static function sanitizeRange( $range ) { 680 list( /*...*/, $bits ) = self::parseCIDR( $range ); 681 list( $start, /*...*/ ) = self::parseRange( $range ); 682 $start = self::formatHex( $start ); 683 if ( $bits === false ) { 684 return $start; // wasn't actually a range 685 } 686 687 return "$start/$bits"; 688 } 689 690 /** 691 * Checks if an IP is a trusted proxy provider. 692 * Useful to tell if X-Forwarded-For data is possibly bogus. 693 * Squid cache servers for the site are whitelisted. 694 * @since 1.24 695 * 696 * @param string $ip 697 * @return bool 698 */ 699 public static function isTrustedProxy( $ip ) { 700 $trusted = self::isConfiguredProxy( $ip ); 701 wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) ); 702 return $trusted; 703 } 704 705 /** 706 * Checks if an IP matches a proxy we've configured 707 * @since 1.24 708 * 709 * @param string $ip 710 * @return bool 711 */ 712 public static function isConfiguredProxy( $ip ) { 713 global $wgSquidServers, $wgSquidServersNoPurge; 714 715 wfProfileIn( __METHOD__ ); 716 // Quick check of known singular proxy servers 717 $trusted = in_array( $ip, $wgSquidServers ); 718 719 // Check against addresses and CIDR nets in the NoPurge list 720 if ( !$trusted ) { 721 if ( !self::$proxyIpSet ) { 722 self::$proxyIpSet = new IPSet( $wgSquidServersNoPurge ); 723 } 724 $trusted = self::$proxyIpSet->match( $ip ); 725 } 726 wfProfileOut( __METHOD__ ); 727 728 return $trusted; 729 } 730 731 /** 732 * Clears precomputed data used for proxy support. 733 * Use this only for unit tests. 734 */ 735 public static function clearCaches() { 736 self::$proxyIpSet = null; 737 } 738 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |