X-Git-Url: https://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2FIP.php;h=e1c1f6d0a6db79a94482451e9e2839b83b18bb3a;hb=a682b7335a179cd0693d838c9bbd62485fe7cbad;hp=02a1babb827e0a8bc4847153626b02aacba52a4a;hpb=386b748ca294ba105c45083a16635edc2ef09d6d;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/IP.php b/includes/IP.php index 02a1babb82..e1c1f6d0a6 100644 --- a/includes/IP.php +++ b/includes/IP.php @@ -18,14 +18,14 @@ * http://www.gnu.org/copyleft/gpl.html * * @file - * @author Ashar Voultoiz , Aaron Schulz + * @author Antoine Musso , Aaron Schulz */ // Some regex definition to "play" with IP address and IP address blocks // An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255 define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' ); -define( 'RE_IP_ADD' , RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE ); +define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE ); // An IPv4 block is an IP address and a prefix (d1 to d32) define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' ); define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX ); @@ -35,15 +35,22 @@ define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX ); define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' ); define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)'); define( 'RE_IPV6_ADD', - '(' . // starts with "::" (includes the address "::") - '(::|:(:' . RE_IPV6_WORD . '){1,7})' . - '|' . // ends with "::" (not including the address "::") - RE_IPV6_WORD . '(:' . RE_IPV6_WORD . '){0,6}::' . - '|' . // has no "::" - RE_IPV6_WORD . '(:' . RE_IPV6_WORD . '){7}' . - '|' . // contains one "::" in the middle ("^" check always fails if no "::" found) - RE_IPV6_WORD . '(:(?P(?(abbr)|:))?' . RE_IPV6_WORD . '){1,6}(?(abbr)|^)' . + '(?:' . // starts with "::" (including "::") + ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' . + '|' . // ends with "::" (except "::") + RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' . + '|' . // contains one "::" in the middle, ending in "::WORD" + RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,5}' . '::' . RE_IPV6_WORD . + '|' . // contains one "::" in the middle, not ending in "::WORD" (regex for PCRE 4.0+) + RE_IPV6_WORD . '(?::(?P:(?P))?' . RE_IPV6_WORD . '(?!:(?P=abn))){1,5}' . + ':' . RE_IPV6_WORD . '(?P=iabn)' . + // NOTE: (?!(?P=abn)) fails iff "::" used twice; (?P=iabn) passes iff a "::" was found. + '|' . // contains no "::" + RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' . ')' + // NOTE: With PCRE 7.2+, we can combine the two '"::" in the middle' cases into: + // RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' + // This also improves regex concatenation by using relative references. ); // An IPv6 block is an IP address and a prefix (d1 to d128) define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX ); @@ -54,9 +61,9 @@ define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' ); // This might be useful for regexps used elsewhere, matches any IPv6 or IPv6 address or network define( 'IP_ADDRESS_STRING', '(?:' . - RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)' . // IPv4 + RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4 '|' . - RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)' . // IPv6 + RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6 ')' ); @@ -85,7 +92,7 @@ class IP { * @return Boolean */ public static function isIPv6( $ip ) { - return (bool)preg_match( '/^' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)$/', $ip ); + return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip ); } /** @@ -96,7 +103,7 @@ class IP { * @return Boolean */ public static function isIPv4( $ip ) { - return (bool)preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip ); + return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip ); } /** @@ -174,10 +181,85 @@ class IP { ); } // Remove leading zereos from each bloc as needed - $ip = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip ); + $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip ); return $ip; } + /** + * Given a host/port string, like one might find in the host part of a URL + * per RFC 2732, split the hostname part and the port part and return an + * array with an element for each. If there is no port part, the array will + * have false in place of the port. If the string was invalid in some way, + * false is returned. + * + * This was easy with IPv4 and was generally done in an ad-hoc way, but + * with IPv6 it's somewhat more complicated due to the need to parse the + * square brackets and colons. + * + * A bare IPv6 address is accepted despite the lack of square brackets. + * + * @param $both string The string with the host and port + * @return array + */ + public static function splitHostAndPort( $both ) { + if ( substr( $both, 0, 1 ) === '[' ) { + if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P\d+))?$/', $both, $m ) ) { + if ( isset( $m['port'] ) ) { + return array( $m[1], intval( $m['port'] ) ); + } else { + return array( $m[1], false ); + } + } else { + // Square bracket found but no IPv6 + return false; + } + } + $numColons = substr_count( $both, ':' ); + if ( $numColons >= 2 ) { + // Is it a bare IPv6 address? + if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) { + return array( $both, false ); + } else { + // Not valid IPv6, but too many colons for anything else + return false; + } + } + if ( $numColons >= 1 ) { + // Host:port? + $bits = explode( ':', $both ); + if ( preg_match( '/^\d+/', $bits[1] ) ) { + return array( $bits[0], intval( $bits[1] ) ); + } else { + // Not a valid port + return false; + } + } + // Plain hostname + return array( $both, false ); + } + + /** + * Given a host name and a port, combine them into host/port string like + * you might find in a URL. If the host contains a colon, wrap it in square + * brackets like in RFC 2732. If the port matches the default port, omit + * the port specification + * + * @param $host string + * @param $port int + * @param $defaultPort bool|int + * @return string + */ + public static function combineHostAndPort( $host, $port, $defaultPort = false ) { + if ( strpos( $host, ':' ) !== false ) { + $host = "[$host]"; + } + if ( $defaultPort !== false && $port == $defaultPort ) { + return $host; + } else { + return "$host:$port"; + } + } + /** * Given an unsigned integer, returns an IPv6 address in octet notation * @@ -217,7 +299,7 @@ class IP { $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 ); } // NO leading zeroes - $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct ); + $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct ); return $ip_oct; } @@ -296,7 +378,7 @@ class IP { static $privateRanges = false; if ( !$privateRanges ) { $privateRanges = array( - array( 'fc::', 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ), # RFC 4193 (local) + array( 'fc00::', '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 ); } @@ -372,6 +454,10 @@ class IP { return $n; } + /** + * @param $ip + * @return String + */ private static function toUnsigned6( $ip ) { return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 ); } @@ -471,6 +557,8 @@ class IP { * Convert a network specification in IPv6 CIDR notation to an * integer network and a number of bits * + * @param $range + * * @return array(string, int) */ private static function parseCIDR6( $range ) { @@ -508,6 +596,9 @@ class IP { * 2001:0db8:85a3::7344/96 CIDR * 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range * 2001:0db8:85a3::7344/96 Single IP + * + * @param $range + * * @return array(string, string) */ private static function parseRange6( $range ) { @@ -593,7 +684,7 @@ class IP { // IPv6 loopback address $m = array(); if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) { - return '127.0.0.1'; + return '127.0.0.1'; } // IPv4-mapped and IPv4-compatible IPv6 addresses if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) { @@ -607,4 +698,20 @@ class IP { return null; // give up } + + /** + * Gets rid of uneeded numbers in quad-dotted/octet IP strings + * For example, 127.111.113.151/24 -> 127.111.113.0/24 + * @param $range String: IP address to normalize + * @return string + */ + public static function sanitizeRange( $range ) { + list( /*...*/, $bits ) = self::parseCIDR( $range ); + list( $start, /*...*/ ) = self::parseRange( $range ); + $start = self::formatHex( $start ); + if ( $bits === false ) { + return $start; // wasn't actually a range + } + return "$start/$bits"; + } }