* (bug 32786) Fix backwards compatibility for extensions using 1.17's Database::newFr...
[lhc/web/wiklou.git] / includes / IP.php
index e64c0ca..e3f6121 100644 (file)
@@ -18,7 +18,7 @@
  * http://www.gnu.org/copyleft/gpl.html
  *
  * @file
- * @author Ashar Voultoiz <hashar at free dot fr>, Aaron Schulz
+ * @author Antoine Musso <hashar at free dot fr>, Aaron Schulz
  */
 
 // Some regex definition to "play" with IP address and IP address blocks
@@ -48,7 +48,7 @@ define( 'RE_IPV6_ADD',
        '|' . // contains no "::"
                RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
        ')'
-       // NOTE: With PCRE 7.2+, we can combine the last two cases into one regex:
+       // 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.
 );
@@ -185,6 +185,81 @@ class 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 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<port>\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
         *
@@ -303,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
                        );
                }
@@ -379,6 +454,10 @@ class IP {
                return $n;
        }
 
+       /**
+        * @param $ip
+        * @return String
+        */
        private static function toUnsigned6( $ip ) {
                return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
        }
@@ -478,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 ) {
@@ -515,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 ) {
@@ -614,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";
+       }
 }