Move IP class to libs/
authorKunal Mehta <legoktm@member.fsf.org>
Thu, 22 Sep 2016 03:18:08 +0000 (20:18 -0700)
committerKunal Mehta <legoktm@member.fsf.org>
Thu, 22 Sep 2016 03:18:08 +0000 (20:18 -0700)
Also fix some misplaced parenthesis in IPTest.

Change-Id: I84d6120c49f733ec45e7e0005259871808b7568b

autoload.php
includes/libs/IP.php [new file with mode: 0644]
includes/utils/IP.php [deleted file]
tests/phpunit/includes/libs/IPTest.php [new file with mode: 0644]
tests/phpunit/includes/utils/IPTest.php [deleted file]

index aa0f561..9e5bf04 100644 (file)
@@ -586,7 +586,7 @@ $wgAutoloadLocalClasses = [
        'IJobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php',
        'ILoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/ILoadBalancer.php',
        'ILoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/ILoadMonitor.php',
-       'IP' => __DIR__ . '/includes/utils/IP.php',
+       'IP' => __DIR__ . '/includes/libs/IP.php',
        'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php',
        'IPTC' => __DIR__ . '/includes/media/IPTC.php',
        'IRCColourfulRCFeedFormatter' => __DIR__ . '/includes/rcfeed/IRCColourfulRCFeedFormatter.php',
diff --git a/includes/libs/IP.php b/includes/libs/IP.php
new file mode 100644 (file)
index 0000000..21203a4
--- /dev/null
@@ -0,0 +1,744 @@
+<?php
+/**
+ * Functions and constants to play with IP addresses and ranges
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Antoine Musso "<hashar at free dot fr>", Aaron Schulz
+ */
+
+use IPSet\IPSet;
+
+// 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 );
+// 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 );
+
+// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
+// However, the "::" abbreviation can be used on consecutive x0000 words.
+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 "::" (including "::")
+               ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
+       '|' . // ends with "::" (except "::")
+               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
+       '|' . // contains one "::" in the middle (the ^ makes the test fail if none found)
+               RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' .
+       '|' . // contains no "::"
+               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
+       ')'
+);
+// An IPv6 block is an IP address and a prefix (d1 to d128)
+define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
+// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
+define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
+define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
+
+// This might be useful for regexps used elsewhere, matches any IPv4 or IPv6 address or network
+define( 'IP_ADDRESS_STRING',
+       '(?:' .
+               RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4
+       '|' .
+               RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6
+       ')'
+);
+
+/**
+ * A collection of public static functions to play with IP address
+ * and IP blocks.
+ */
+class IP {
+       /** @var IPSet */
+       private static $proxyIpSet = null;
+
+       /**
+        * Determine if a string is as valid IP address or network (CIDR prefix).
+        * SIIT IPv4-translated addresses are rejected.
+        * @note canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ip Possible IP address
+        * @return bool
+        */
+       public static function isIPAddress( $ip ) {
+               return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
+       }
+
+       /**
+        * Given a string, determine if it as valid IP in IPv6 only.
+        * @note Unlike isValid(), this looks for networks too.
+        *
+        * @param string $ip Possible IP address
+        * @return bool
+        */
+       public static function isIPv6( $ip ) {
+               return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
+       }
+
+       /**
+        * Given a string, determine if it as valid IP in IPv4 only.
+        * @note Unlike isValid(), this looks for networks too.
+        *
+        * @param string $ip Possible IP address
+        * @return bool
+        */
+       public static function isIPv4( $ip ) {
+               return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
+       }
+
+       /**
+        * Validate an IP address. Ranges are NOT considered valid.
+        * SIIT IPv4-translated addresses are rejected.
+        * @note canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ip
+        * @return bool True if it is valid
+        */
+       public static function isValid( $ip ) {
+               return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
+                       || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
+       }
+
+       /**
+        * Validate an IP Block (valid address WITH a valid prefix).
+        * SIIT IPv4-translated addresses are rejected.
+        * @note canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ipblock
+        * @return bool True if it is valid
+        */
+       public static function isValidBlock( $ipblock ) {
+               return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
+                       || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) );
+       }
+
+       /**
+        * Convert an IP into a verbose, uppercase, normalized form.
+        * Both IPv4 and IPv6 addresses are trimmed. Additionally,
+        * IPv6 addresses in octet notation are expanded to 8 words;
+        * IPv4 addresses have leading zeros, in each octet, removed.
+        *
+        * @param string $ip IP address in quad or octet form (CIDR or not).
+        * @return string
+        */
+       public static function sanitizeIP( $ip ) {
+               $ip = trim( $ip );
+               if ( $ip === '' ) {
+                       return null;
+               }
+               /* If not an IP, just return trimmed value, since sanitizeIP() is called
+                * in a number of contexts where usernames are supplied as input.
+                */
+               if ( !self::isIPAddress( $ip ) ) {
+                       return $ip;
+               }
+               if ( self::isIPv4( $ip ) ) {
+                       // Remove leading 0's from octet representation of IPv4 address
+                       $ip = preg_replace( '/(?:^|(?<=\.))0+(?=[1-9]|0\.|0$)/', '', $ip );
+                       return $ip;
+               }
+               // Remove any whitespaces, convert to upper case
+               $ip = strtoupper( $ip );
+               // Expand zero abbreviations
+               $abbrevPos = strpos( $ip, '::' );
+               if ( $abbrevPos !== false ) {
+                       // We know this is valid IPv6. Find the last index of the
+                       // address before any CIDR number (e.g. "a:b:c::/24").
+                       $CIDRStart = strpos( $ip, "/" );
+                       $addressEnd = ( $CIDRStart !== false )
+                               ? $CIDRStart - 1
+                               : strlen( $ip ) - 1;
+                       // If the '::' is at the beginning...
+                       if ( $abbrevPos == 0 ) {
+                               $repeat = '0:';
+                               $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::'
+                               $pad = 9; // 7+2 (due to '::')
+                       // If the '::' is at the end...
+                       } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) {
+                               $repeat = ':0';
+                               $extra = '';
+                               $pad = 9; // 7+2 (due to '::')
+                       // If the '::' is in the middle...
+                       } else {
+                               $repeat = ':0';
+                               $extra = ':';
+                               $pad = 8; // 6+2 (due to '::')
+                       }
+                       $ip = str_replace( '::',
+                               str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra,
+                               $ip
+                       );
+               }
+               // Remove leading zeros from each bloc as needed
+               $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
+
+               return $ip;
+       }
+
+       /**
+        * Prettify an IP for display to end users.
+        * This will make it more compact and lower-case.
+        *
+        * @param string $ip
+        * @return string
+        */
+       public static function prettifyIP( $ip ) {
+               $ip = self::sanitizeIP( $ip ); // normalize (removes '::')
+               if ( self::isIPv6( $ip ) ) {
+                       // Split IP into an address and a CIDR
+                       if ( strpos( $ip, '/' ) !== false ) {
+                               list( $ip, $cidr ) = explode( '/', $ip, 2 );
+                       } else {
+                               list( $ip, $cidr ) = [ $ip, '' ];
+                       }
+                       // Get the largest slice of words with multiple zeros
+                       $offset = 0;
+                       $longest = $longestPos = false;
+                       while ( preg_match(
+                               '!(?:^|:)0(?::0)+(?:$|:)!', $ip, $m, PREG_OFFSET_CAPTURE, $offset
+                       ) ) {
+                               list( $match, $pos ) = $m[0]; // full match
+                               if ( strlen( $match ) > strlen( $longest ) ) {
+                                       $longest = $match;
+                                       $longestPos = $pos;
+                               }
+                               $offset = ( $pos + strlen( $match ) ); // advance
+                       }
+                       if ( $longest !== false ) {
+                               // Replace this portion of the string with the '::' abbreviation
+                               $ip = substr_replace( $ip, '::', $longestPos, strlen( $longest ) );
+                       }
+                       // Add any CIDR back on
+                       if ( $cidr !== '' ) {
+                               $ip = "{$ip}/{$cidr}";
+                       }
+                       // Convert to lower case to make it more readable
+                       $ip = strtolower( $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 string $both The string with the host and port
+        * @return array|false Array normally, false on certain failures
+        */
+       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 [ $m[1], intval( $m['port'] ) ];
+                               } else {
+                                       return [ $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 [ $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 [ $bits[0], intval( $bits[1] ) ];
+                       } else {
+                               // Not a valid port
+                               return false;
+                       }
+               }
+
+               // Plain hostname
+               return [ $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 string $host
+        * @param int $port
+        * @param bool|int $defaultPort
+        * @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";
+               }
+       }
+
+       /**
+        * Convert an IPv4 or IPv6 hexadecimal representation back to readable format
+        *
+        * @param string $hex Number, with "v6-" prefix if it is IPv6
+        * @return string Quad-dotted (IPv4) or octet notation (IPv6)
+        */
+       public static function formatHex( $hex ) {
+               if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
+                       return self::hexToOctet( substr( $hex, 3 ) );
+               } else { // IPv4
+                       return self::hexToQuad( $hex );
+               }
+       }
+
+       /**
+        * Converts a hexadecimal number to an IPv6 address in octet notation
+        *
+        * @param string $ip_hex Pure hex (no v6- prefix)
+        * @return string (of format a:b:c:d:e:f:g:h)
+        */
+       public static function hexToOctet( $ip_hex ) {
+               // Pad hex to 32 chars (128 bits)
+               $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
+               // Separate into 8 words
+               $ip_oct = substr( $ip_hex, 0, 4 );
+               for ( $n = 1; $n < 8; $n++ ) {
+                       $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
+               }
+               // NO leading zeroes
+               $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
+
+               return $ip_oct;
+       }
+
+       /**
+        * Converts a hexadecimal number to an IPv4 address in quad-dotted notation
+        *
+        * @param string $ip_hex Pure hex
+        * @return string (of format a.b.c.d)
+        */
+       public static function hexToQuad( $ip_hex ) {
+               // Pad hex to 8 chars (32 bits)
+               $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
+               // Separate into four quads
+               $s = '';
+               for ( $i = 0; $i < 4; $i++ ) {
+                       if ( $s !== '' ) {
+                               $s .= '.';
+                       }
+                       $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
+               }
+
+               return $s;
+       }
+
+       /**
+        * Determine if an IP address really is an IP address, and if it is public,
+        * i.e. not RFC 1918 or similar
+        *
+        * @param string $ip
+        * @return bool
+        */
+       public static function isPublic( $ip ) {
+               static $privateSet = null;
+               if ( !$privateSet ) {
+                       $privateSet = new IPSet( [
+                               '10.0.0.0/8', # RFC 1918 (private)
+                               '172.16.0.0/12', # RFC 1918 (private)
+                               '192.168.0.0/16', # RFC 1918 (private)
+                               '0.0.0.0/8', # this network
+                               '127.0.0.0/8', # loopback
+                               'fc00::/7', # RFC 4193 (local)
+                               '0:0:0:0:0:0:0:1', # loopback
+                               '169.254.0.0/16', # link-local
+                               'fe80::/10', # link-local
+                       ] );
+               }
+               return !$privateSet->match( $ip );
+       }
+
+       /**
+        * Return a zero-padded upper case hexadecimal representation of an IP address.
+        *
+        * Hexadecimal addresses are used because they can easily be extended to
+        * IPv6 support. To separate the ranges, the return value from this
+        * function for an IPv6 address will be prefixed with "v6-", a non-
+        * hexadecimal string which sorts after the IPv4 addresses.
+        *
+        * @param string $ip Quad dotted/octet IP address.
+        * @return string|bool False on failure
+        */
+       public static function toHex( $ip ) {
+               if ( self::isIPv6( $ip ) ) {
+                       $n = 'v6-' . self::IPv6ToRawHex( $ip );
+               } elseif ( self::isIPv4( $ip ) ) {
+                       // T62035/T97897: An IP with leading 0's fails in ip2long sometimes (e.g. *.08),
+                       // also double/triple 0 needs to be changed to just a single 0 for ip2long.
+                       $ip = self::sanitizeIP( $ip );
+                       $n = ip2long( $ip );
+                       if ( $n < 0 ) {
+                               $n += pow( 2, 32 );
+                               # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
+                               # so $n becomes a float. We convert it to string instead.
+                               if ( is_float( $n ) ) {
+                                       $n = (string)$n;
+                               }
+                       }
+                       if ( $n !== false ) {
+                               # Floating points can handle the conversion; faster than Wikimedia\base_convert()
+                               $n = strtoupper( str_pad( base_convert( $n, 10, 16 ), 8, '0', STR_PAD_LEFT ) );
+                       }
+               } else {
+                       $n = false;
+               }
+
+               return $n;
+       }
+
+       /**
+        * Given an IPv6 address in octet notation, returns a pure hex string.
+        *
+        * @param string $ip Octet ipv6 IP address.
+        * @return string|bool Pure hex (uppercase); false on failure
+        */
+       private static function IPv6ToRawHex( $ip ) {
+               $ip = self::sanitizeIP( $ip );
+               if ( !$ip ) {
+                       return false;
+               }
+               $r_ip = '';
+               foreach ( explode( ':', $ip ) as $v ) {
+                       $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
+               }
+
+               return $r_ip;
+       }
+
+       /**
+        * Convert a network specification in CIDR notation
+        * to an integer network and a number of bits
+        *
+        * @param string $range IP with CIDR prefix
+        * @return array(int or string, int)
+        */
+       public static function parseCIDR( $range ) {
+               if ( self::isIPv6( $range ) ) {
+                       return self::parseCIDR6( $range );
+               }
+               $parts = explode( '/', $range, 2 );
+               if ( count( $parts ) != 2 ) {
+                       return [ false, false ];
+               }
+               list( $network, $bits ) = $parts;
+               $network = ip2long( $network );
+               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
+                       if ( $bits == 0 ) {
+                               $network = 0;
+                       } else {
+                               $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 );
+                       }
+                       # Convert to unsigned
+                       if ( $network < 0 ) {
+                               $network += pow( 2, 32 );
+                       }
+               } else {
+                       $network = false;
+                       $bits = false;
+               }
+
+               return [ $network, $bits ];
+       }
+
+       /**
+        * Given a string range in a number of formats,
+        * return the start and end of the range in hexadecimal.
+        *
+        * Formats are:
+        *     1.2.3.4/24          CIDR
+        *     1.2.3.4 - 1.2.3.5   Explicit range
+        *     1.2.3.4             Single IP
+        *
+        *     2001:0db8:85a3::7344/96                       CIDR
+        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
+        *     2001:0db8:85a3::7344                          Single IP
+        * @param string $range IP range
+        * @return array(string, string)
+        */
+       public static function parseRange( $range ) {
+               // CIDR notation
+               if ( strpos( $range, '/' ) !== false ) {
+                       if ( self::isIPv6( $range ) ) {
+                               return self::parseRange6( $range );
+                       }
+                       list( $network, $bits ) = self::parseCIDR( $range );
+                       if ( $network === false ) {
+                               $start = $end = false;
+                       } else {
+                               $start = sprintf( '%08X', $network );
+                               $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 );
+                       }
+               // Explicit range
+               } elseif ( strpos( $range, '-' ) !== false ) {
+                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+                       if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) {
+                               return self::parseRange6( $range );
+                       }
+                       if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
+                               $start = self::toHex( $start );
+                               $end = self::toHex( $end );
+                               if ( $start > $end ) {
+                                       $start = $end = false;
+                               }
+                       } else {
+                               $start = $end = false;
+                       }
+               } else {
+                       # Single IP
+                       $start = $end = self::toHex( $range );
+               }
+               if ( $start === false || $end === false ) {
+                       return [ false, false ];
+               } else {
+                       return [ $start, $end ];
+               }
+       }
+
+       /**
+        * Convert a network specification in IPv6 CIDR notation to an
+        * integer network and a number of bits
+        *
+        * @param string $range
+        *
+        * @return array(string, int)
+        */
+       private static function parseCIDR6( $range ) {
+               # Explode into <expanded IP,range>
+               $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
+               if ( count( $parts ) != 2 ) {
+                       return [ false, false ];
+               }
+               list( $network, $bits ) = $parts;
+               $network = self::IPv6ToRawHex( $network );
+               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
+                       if ( $bits == 0 ) {
+                               $network = "0";
+                       } else {
+                               # Native 32 bit functions WONT work here!!!
+                               # Convert to a padded binary number
+                               $network = Wikimedia\base_convert( $network, 16, 2, 128 );
+                               # Truncate the last (128-$bits) bits and replace them with zeros
+                               $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
+                               # Convert back to an integer
+                               $network = Wikimedia\base_convert( $network, 2, 10 );
+                       }
+               } else {
+                       $network = false;
+                       $bits = false;
+               }
+
+               return [ $network, (int)$bits ];
+       }
+
+       /**
+        * Given a string range in a number of formats, return the
+        * start and end of the range in hexadecimal. For IPv6.
+        *
+        * Formats are:
+        *     2001:0db8:85a3::7344/96                       CIDR
+        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
+        *     2001:0db8:85a3::7344/96                       Single IP
+        *
+        * @param string $range
+        *
+        * @return array(string, string)
+        */
+       private static function parseRange6( $range ) {
+               # Expand any IPv6 IP
+               $range = IP::sanitizeIP( $range );
+               // CIDR notation...
+               if ( strpos( $range, '/' ) !== false ) {
+                       list( $network, $bits ) = self::parseCIDR6( $range );
+                       if ( $network === false ) {
+                               $start = $end = false;
+                       } else {
+                               $start = Wikimedia\base_convert( $network, 10, 16, 32, false );
+                               # Turn network to binary (again)
+                               $end = Wikimedia\base_convert( $network, 10, 2, 128 );
+                               # Truncate the last (128-$bits) bits and replace them with ones
+                               $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
+                               # Convert to hex
+                               $end = Wikimedia\base_convert( $end, 2, 16, 32, false );
+                               # see toHex() comment
+                               $start = "v6-$start";
+                               $end = "v6-$end";
+                       }
+               // Explicit range notation...
+               } elseif ( strpos( $range, '-' ) !== false ) {
+                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+                       $start = self::toHex( $start );
+                       $end = self::toHex( $end );
+                       if ( $start > $end ) {
+                               $start = $end = false;
+                       }
+               } else {
+                       # Single IP
+                       $start = $end = self::toHex( $range );
+               }
+               if ( $start === false || $end === false ) {
+                       return [ false, false ];
+               } else {
+                       return [ $start, $end ];
+               }
+       }
+
+       /**
+        * Determine if a given IPv4/IPv6 address is in a given CIDR network
+        *
+        * @param string $addr The address to check against the given range.
+        * @param string $range The range to check the given address against.
+        * @return bool Whether or not the given address is in the given range.
+        *
+        * @note This can return unexpected results for invalid arguments!
+        *       Make sure you pass a valid IP address and IP range.
+        */
+       public static function isInRange( $addr, $range ) {
+               $hexIP = self::toHex( $addr );
+               list( $start, $end ) = self::parseRange( $range );
+
+               return ( strcmp( $hexIP, $start ) >= 0 &&
+                       strcmp( $hexIP, $end ) <= 0 );
+       }
+
+       /**
+        * Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
+        *
+        * @since 1.25
+        *
+        * @param string $ip the IP to check
+        * @param array $ranges the IP ranges, each element a range
+        *
+        * @return bool true if the specified adress belongs to the specified range; otherwise, false.
+        */
+       public static function isInRanges( $ip, $ranges ) {
+               foreach ( $ranges as $range ) {
+                       if ( self::isInRange( $ip, $range ) ) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Convert some unusual representations of IPv4 addresses to their
+        * canonical dotted quad representation.
+        *
+        * This currently only checks a few IPV4-to-IPv6 related cases.  More
+        * unusual representations may be added later.
+        *
+        * @param string $addr Something that might be an IP address
+        * @return string|null Valid dotted quad IPv4 address or null
+        */
+       public static function canonicalize( $addr ) {
+               // remove zone info (bug 35738)
+               $addr = preg_replace( '/\%.*/', '', $addr );
+
+               if ( self::isValid( $addr ) ) {
+                       return $addr;
+               }
+               // Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4
+               if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) {
+                       $addr = substr( $addr, strrpos( $addr, ':' ) + 1 );
+                       if ( self::isIPv4( $addr ) ) {
+                               return $addr;
+                       }
+               }
+               // IPv6 loopback address
+               $m = [];
+               if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) {
+                       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 ) ) {
+                       return $m[1];
+               }
+               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
+                       ':' . RE_IPV6_WORD . '$/i', $addr, $m )
+               ) {
+                       return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
+               }
+
+               return null; // give up
+       }
+
+       /**
+        * Gets rid of unneeded numbers in quad-dotted/octet IP strings
+        * For example, 127.111.113.151/24 -> 127.111.113.0/24
+        * @param string $range 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";
+       }
+
+       /**
+        * Returns the subnet of a given IP
+        *
+        * @param string $ip
+        * @return string|false
+        */
+       public static function getSubnet( $ip ) {
+               $matches = [];
+               $subnet = false;
+               if ( IP::isIPv6( $ip ) ) {
+                       $parts = IP::parseRange( "$ip/64" );
+                       $subnet = $parts[0];
+               } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
+                       // IPv4
+                       $subnet = $matches[1];
+               }
+               return $subnet;
+       }
+}
diff --git a/includes/utils/IP.php b/includes/utils/IP.php
deleted file mode 100644 (file)
index 21203a4..0000000
+++ /dev/null
@@ -1,744 +0,0 @@
-<?php
-/**
- * Functions and constants to play with IP addresses and ranges
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @author Antoine Musso "<hashar at free dot fr>", Aaron Schulz
- */
-
-use IPSet\IPSet;
-
-// 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 );
-// 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 );
-
-// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
-// However, the "::" abbreviation can be used on consecutive x0000 words.
-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 "::" (including "::")
-               ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
-       '|' . // ends with "::" (except "::")
-               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
-       '|' . // contains one "::" in the middle (the ^ makes the test fail if none found)
-               RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' .
-       '|' . // contains no "::"
-               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
-       ')'
-);
-// An IPv6 block is an IP address and a prefix (d1 to d128)
-define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
-// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
-define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
-define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
-
-// This might be useful for regexps used elsewhere, matches any IPv4 or IPv6 address or network
-define( 'IP_ADDRESS_STRING',
-       '(?:' .
-               RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4
-       '|' .
-               RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6
-       ')'
-);
-
-/**
- * A collection of public static functions to play with IP address
- * and IP blocks.
- */
-class IP {
-       /** @var IPSet */
-       private static $proxyIpSet = null;
-
-       /**
-        * Determine if a string is as valid IP address or network (CIDR prefix).
-        * SIIT IPv4-translated addresses are rejected.
-        * @note canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param string $ip Possible IP address
-        * @return bool
-        */
-       public static function isIPAddress( $ip ) {
-               return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
-       }
-
-       /**
-        * Given a string, determine if it as valid IP in IPv6 only.
-        * @note Unlike isValid(), this looks for networks too.
-        *
-        * @param string $ip Possible IP address
-        * @return bool
-        */
-       public static function isIPv6( $ip ) {
-               return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
-       }
-
-       /**
-        * Given a string, determine if it as valid IP in IPv4 only.
-        * @note Unlike isValid(), this looks for networks too.
-        *
-        * @param string $ip Possible IP address
-        * @return bool
-        */
-       public static function isIPv4( $ip ) {
-               return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
-       }
-
-       /**
-        * Validate an IP address. Ranges are NOT considered valid.
-        * SIIT IPv4-translated addresses are rejected.
-        * @note canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param string $ip
-        * @return bool True if it is valid
-        */
-       public static function isValid( $ip ) {
-               return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
-                       || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
-       }
-
-       /**
-        * Validate an IP Block (valid address WITH a valid prefix).
-        * SIIT IPv4-translated addresses are rejected.
-        * @note canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param string $ipblock
-        * @return bool True if it is valid
-        */
-       public static function isValidBlock( $ipblock ) {
-               return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
-                       || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) );
-       }
-
-       /**
-        * Convert an IP into a verbose, uppercase, normalized form.
-        * Both IPv4 and IPv6 addresses are trimmed. Additionally,
-        * IPv6 addresses in octet notation are expanded to 8 words;
-        * IPv4 addresses have leading zeros, in each octet, removed.
-        *
-        * @param string $ip IP address in quad or octet form (CIDR or not).
-        * @return string
-        */
-       public static function sanitizeIP( $ip ) {
-               $ip = trim( $ip );
-               if ( $ip === '' ) {
-                       return null;
-               }
-               /* If not an IP, just return trimmed value, since sanitizeIP() is called
-                * in a number of contexts where usernames are supplied as input.
-                */
-               if ( !self::isIPAddress( $ip ) ) {
-                       return $ip;
-               }
-               if ( self::isIPv4( $ip ) ) {
-                       // Remove leading 0's from octet representation of IPv4 address
-                       $ip = preg_replace( '/(?:^|(?<=\.))0+(?=[1-9]|0\.|0$)/', '', $ip );
-                       return $ip;
-               }
-               // Remove any whitespaces, convert to upper case
-               $ip = strtoupper( $ip );
-               // Expand zero abbreviations
-               $abbrevPos = strpos( $ip, '::' );
-               if ( $abbrevPos !== false ) {
-                       // We know this is valid IPv6. Find the last index of the
-                       // address before any CIDR number (e.g. "a:b:c::/24").
-                       $CIDRStart = strpos( $ip, "/" );
-                       $addressEnd = ( $CIDRStart !== false )
-                               ? $CIDRStart - 1
-                               : strlen( $ip ) - 1;
-                       // If the '::' is at the beginning...
-                       if ( $abbrevPos == 0 ) {
-                               $repeat = '0:';
-                               $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::'
-                               $pad = 9; // 7+2 (due to '::')
-                       // If the '::' is at the end...
-                       } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) {
-                               $repeat = ':0';
-                               $extra = '';
-                               $pad = 9; // 7+2 (due to '::')
-                       // If the '::' is in the middle...
-                       } else {
-                               $repeat = ':0';
-                               $extra = ':';
-                               $pad = 8; // 6+2 (due to '::')
-                       }
-                       $ip = str_replace( '::',
-                               str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra,
-                               $ip
-                       );
-               }
-               // Remove leading zeros from each bloc as needed
-               $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
-
-               return $ip;
-       }
-
-       /**
-        * Prettify an IP for display to end users.
-        * This will make it more compact and lower-case.
-        *
-        * @param string $ip
-        * @return string
-        */
-       public static function prettifyIP( $ip ) {
-               $ip = self::sanitizeIP( $ip ); // normalize (removes '::')
-               if ( self::isIPv6( $ip ) ) {
-                       // Split IP into an address and a CIDR
-                       if ( strpos( $ip, '/' ) !== false ) {
-                               list( $ip, $cidr ) = explode( '/', $ip, 2 );
-                       } else {
-                               list( $ip, $cidr ) = [ $ip, '' ];
-                       }
-                       // Get the largest slice of words with multiple zeros
-                       $offset = 0;
-                       $longest = $longestPos = false;
-                       while ( preg_match(
-                               '!(?:^|:)0(?::0)+(?:$|:)!', $ip, $m, PREG_OFFSET_CAPTURE, $offset
-                       ) ) {
-                               list( $match, $pos ) = $m[0]; // full match
-                               if ( strlen( $match ) > strlen( $longest ) ) {
-                                       $longest = $match;
-                                       $longestPos = $pos;
-                               }
-                               $offset = ( $pos + strlen( $match ) ); // advance
-                       }
-                       if ( $longest !== false ) {
-                               // Replace this portion of the string with the '::' abbreviation
-                               $ip = substr_replace( $ip, '::', $longestPos, strlen( $longest ) );
-                       }
-                       // Add any CIDR back on
-                       if ( $cidr !== '' ) {
-                               $ip = "{$ip}/{$cidr}";
-                       }
-                       // Convert to lower case to make it more readable
-                       $ip = strtolower( $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 string $both The string with the host and port
-        * @return array|false Array normally, false on certain failures
-        */
-       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 [ $m[1], intval( $m['port'] ) ];
-                               } else {
-                                       return [ $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 [ $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 [ $bits[0], intval( $bits[1] ) ];
-                       } else {
-                               // Not a valid port
-                               return false;
-                       }
-               }
-
-               // Plain hostname
-               return [ $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 string $host
-        * @param int $port
-        * @param bool|int $defaultPort
-        * @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";
-               }
-       }
-
-       /**
-        * Convert an IPv4 or IPv6 hexadecimal representation back to readable format
-        *
-        * @param string $hex Number, with "v6-" prefix if it is IPv6
-        * @return string Quad-dotted (IPv4) or octet notation (IPv6)
-        */
-       public static function formatHex( $hex ) {
-               if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
-                       return self::hexToOctet( substr( $hex, 3 ) );
-               } else { // IPv4
-                       return self::hexToQuad( $hex );
-               }
-       }
-
-       /**
-        * Converts a hexadecimal number to an IPv6 address in octet notation
-        *
-        * @param string $ip_hex Pure hex (no v6- prefix)
-        * @return string (of format a:b:c:d:e:f:g:h)
-        */
-       public static function hexToOctet( $ip_hex ) {
-               // Pad hex to 32 chars (128 bits)
-               $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
-               // Separate into 8 words
-               $ip_oct = substr( $ip_hex, 0, 4 );
-               for ( $n = 1; $n < 8; $n++ ) {
-                       $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
-               }
-               // NO leading zeroes
-               $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
-
-               return $ip_oct;
-       }
-
-       /**
-        * Converts a hexadecimal number to an IPv4 address in quad-dotted notation
-        *
-        * @param string $ip_hex Pure hex
-        * @return string (of format a.b.c.d)
-        */
-       public static function hexToQuad( $ip_hex ) {
-               // Pad hex to 8 chars (32 bits)
-               $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
-               // Separate into four quads
-               $s = '';
-               for ( $i = 0; $i < 4; $i++ ) {
-                       if ( $s !== '' ) {
-                               $s .= '.';
-                       }
-                       $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
-               }
-
-               return $s;
-       }
-
-       /**
-        * Determine if an IP address really is an IP address, and if it is public,
-        * i.e. not RFC 1918 or similar
-        *
-        * @param string $ip
-        * @return bool
-        */
-       public static function isPublic( $ip ) {
-               static $privateSet = null;
-               if ( !$privateSet ) {
-                       $privateSet = new IPSet( [
-                               '10.0.0.0/8', # RFC 1918 (private)
-                               '172.16.0.0/12', # RFC 1918 (private)
-                               '192.168.0.0/16', # RFC 1918 (private)
-                               '0.0.0.0/8', # this network
-                               '127.0.0.0/8', # loopback
-                               'fc00::/7', # RFC 4193 (local)
-                               '0:0:0:0:0:0:0:1', # loopback
-                               '169.254.0.0/16', # link-local
-                               'fe80::/10', # link-local
-                       ] );
-               }
-               return !$privateSet->match( $ip );
-       }
-
-       /**
-        * Return a zero-padded upper case hexadecimal representation of an IP address.
-        *
-        * Hexadecimal addresses are used because they can easily be extended to
-        * IPv6 support. To separate the ranges, the return value from this
-        * function for an IPv6 address will be prefixed with "v6-", a non-
-        * hexadecimal string which sorts after the IPv4 addresses.
-        *
-        * @param string $ip Quad dotted/octet IP address.
-        * @return string|bool False on failure
-        */
-       public static function toHex( $ip ) {
-               if ( self::isIPv6( $ip ) ) {
-                       $n = 'v6-' . self::IPv6ToRawHex( $ip );
-               } elseif ( self::isIPv4( $ip ) ) {
-                       // T62035/T97897: An IP with leading 0's fails in ip2long sometimes (e.g. *.08),
-                       // also double/triple 0 needs to be changed to just a single 0 for ip2long.
-                       $ip = self::sanitizeIP( $ip );
-                       $n = ip2long( $ip );
-                       if ( $n < 0 ) {
-                               $n += pow( 2, 32 );
-                               # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
-                               # so $n becomes a float. We convert it to string instead.
-                               if ( is_float( $n ) ) {
-                                       $n = (string)$n;
-                               }
-                       }
-                       if ( $n !== false ) {
-                               # Floating points can handle the conversion; faster than Wikimedia\base_convert()
-                               $n = strtoupper( str_pad( base_convert( $n, 10, 16 ), 8, '0', STR_PAD_LEFT ) );
-                       }
-               } else {
-                       $n = false;
-               }
-
-               return $n;
-       }
-
-       /**
-        * Given an IPv6 address in octet notation, returns a pure hex string.
-        *
-        * @param string $ip Octet ipv6 IP address.
-        * @return string|bool Pure hex (uppercase); false on failure
-        */
-       private static function IPv6ToRawHex( $ip ) {
-               $ip = self::sanitizeIP( $ip );
-               if ( !$ip ) {
-                       return false;
-               }
-               $r_ip = '';
-               foreach ( explode( ':', $ip ) as $v ) {
-                       $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
-               }
-
-               return $r_ip;
-       }
-
-       /**
-        * Convert a network specification in CIDR notation
-        * to an integer network and a number of bits
-        *
-        * @param string $range IP with CIDR prefix
-        * @return array(int or string, int)
-        */
-       public static function parseCIDR( $range ) {
-               if ( self::isIPv6( $range ) ) {
-                       return self::parseCIDR6( $range );
-               }
-               $parts = explode( '/', $range, 2 );
-               if ( count( $parts ) != 2 ) {
-                       return [ false, false ];
-               }
-               list( $network, $bits ) = $parts;
-               $network = ip2long( $network );
-               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
-                       if ( $bits == 0 ) {
-                               $network = 0;
-                       } else {
-                               $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 );
-                       }
-                       # Convert to unsigned
-                       if ( $network < 0 ) {
-                               $network += pow( 2, 32 );
-                       }
-               } else {
-                       $network = false;
-                       $bits = false;
-               }
-
-               return [ $network, $bits ];
-       }
-
-       /**
-        * Given a string range in a number of formats,
-        * return the start and end of the range in hexadecimal.
-        *
-        * Formats are:
-        *     1.2.3.4/24          CIDR
-        *     1.2.3.4 - 1.2.3.5   Explicit range
-        *     1.2.3.4             Single IP
-        *
-        *     2001:0db8:85a3::7344/96                       CIDR
-        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
-        *     2001:0db8:85a3::7344                          Single IP
-        * @param string $range IP range
-        * @return array(string, string)
-        */
-       public static function parseRange( $range ) {
-               // CIDR notation
-               if ( strpos( $range, '/' ) !== false ) {
-                       if ( self::isIPv6( $range ) ) {
-                               return self::parseRange6( $range );
-                       }
-                       list( $network, $bits ) = self::parseCIDR( $range );
-                       if ( $network === false ) {
-                               $start = $end = false;
-                       } else {
-                               $start = sprintf( '%08X', $network );
-                               $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 );
-                       }
-               // Explicit range
-               } elseif ( strpos( $range, '-' ) !== false ) {
-                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
-                       if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) {
-                               return self::parseRange6( $range );
-                       }
-                       if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
-                               $start = self::toHex( $start );
-                               $end = self::toHex( $end );
-                               if ( $start > $end ) {
-                                       $start = $end = false;
-                               }
-                       } else {
-                               $start = $end = false;
-                       }
-               } else {
-                       # Single IP
-                       $start = $end = self::toHex( $range );
-               }
-               if ( $start === false || $end === false ) {
-                       return [ false, false ];
-               } else {
-                       return [ $start, $end ];
-               }
-       }
-
-       /**
-        * Convert a network specification in IPv6 CIDR notation to an
-        * integer network and a number of bits
-        *
-        * @param string $range
-        *
-        * @return array(string, int)
-        */
-       private static function parseCIDR6( $range ) {
-               # Explode into <expanded IP,range>
-               $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
-               if ( count( $parts ) != 2 ) {
-                       return [ false, false ];
-               }
-               list( $network, $bits ) = $parts;
-               $network = self::IPv6ToRawHex( $network );
-               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
-                       if ( $bits == 0 ) {
-                               $network = "0";
-                       } else {
-                               # Native 32 bit functions WONT work here!!!
-                               # Convert to a padded binary number
-                               $network = Wikimedia\base_convert( $network, 16, 2, 128 );
-                               # Truncate the last (128-$bits) bits and replace them with zeros
-                               $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
-                               # Convert back to an integer
-                               $network = Wikimedia\base_convert( $network, 2, 10 );
-                       }
-               } else {
-                       $network = false;
-                       $bits = false;
-               }
-
-               return [ $network, (int)$bits ];
-       }
-
-       /**
-        * Given a string range in a number of formats, return the
-        * start and end of the range in hexadecimal. For IPv6.
-        *
-        * Formats are:
-        *     2001:0db8:85a3::7344/96                       CIDR
-        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
-        *     2001:0db8:85a3::7344/96                       Single IP
-        *
-        * @param string $range
-        *
-        * @return array(string, string)
-        */
-       private static function parseRange6( $range ) {
-               # Expand any IPv6 IP
-               $range = IP::sanitizeIP( $range );
-               // CIDR notation...
-               if ( strpos( $range, '/' ) !== false ) {
-                       list( $network, $bits ) = self::parseCIDR6( $range );
-                       if ( $network === false ) {
-                               $start = $end = false;
-                       } else {
-                               $start = Wikimedia\base_convert( $network, 10, 16, 32, false );
-                               # Turn network to binary (again)
-                               $end = Wikimedia\base_convert( $network, 10, 2, 128 );
-                               # Truncate the last (128-$bits) bits and replace them with ones
-                               $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
-                               # Convert to hex
-                               $end = Wikimedia\base_convert( $end, 2, 16, 32, false );
-                               # see toHex() comment
-                               $start = "v6-$start";
-                               $end = "v6-$end";
-                       }
-               // Explicit range notation...
-               } elseif ( strpos( $range, '-' ) !== false ) {
-                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
-                       $start = self::toHex( $start );
-                       $end = self::toHex( $end );
-                       if ( $start > $end ) {
-                               $start = $end = false;
-                       }
-               } else {
-                       # Single IP
-                       $start = $end = self::toHex( $range );
-               }
-               if ( $start === false || $end === false ) {
-                       return [ false, false ];
-               } else {
-                       return [ $start, $end ];
-               }
-       }
-
-       /**
-        * Determine if a given IPv4/IPv6 address is in a given CIDR network
-        *
-        * @param string $addr The address to check against the given range.
-        * @param string $range The range to check the given address against.
-        * @return bool Whether or not the given address is in the given range.
-        *
-        * @note This can return unexpected results for invalid arguments!
-        *       Make sure you pass a valid IP address and IP range.
-        */
-       public static function isInRange( $addr, $range ) {
-               $hexIP = self::toHex( $addr );
-               list( $start, $end ) = self::parseRange( $range );
-
-               return ( strcmp( $hexIP, $start ) >= 0 &&
-                       strcmp( $hexIP, $end ) <= 0 );
-       }
-
-       /**
-        * Determines if an IP address is a list of CIDR a.b.c.d/n ranges.
-        *
-        * @since 1.25
-        *
-        * @param string $ip the IP to check
-        * @param array $ranges the IP ranges, each element a range
-        *
-        * @return bool true if the specified adress belongs to the specified range; otherwise, false.
-        */
-       public static function isInRanges( $ip, $ranges ) {
-               foreach ( $ranges as $range ) {
-                       if ( self::isInRange( $ip, $range ) ) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Convert some unusual representations of IPv4 addresses to their
-        * canonical dotted quad representation.
-        *
-        * This currently only checks a few IPV4-to-IPv6 related cases.  More
-        * unusual representations may be added later.
-        *
-        * @param string $addr Something that might be an IP address
-        * @return string|null Valid dotted quad IPv4 address or null
-        */
-       public static function canonicalize( $addr ) {
-               // remove zone info (bug 35738)
-               $addr = preg_replace( '/\%.*/', '', $addr );
-
-               if ( self::isValid( $addr ) ) {
-                       return $addr;
-               }
-               // Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4
-               if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) {
-                       $addr = substr( $addr, strrpos( $addr, ':' ) + 1 );
-                       if ( self::isIPv4( $addr ) ) {
-                               return $addr;
-                       }
-               }
-               // IPv6 loopback address
-               $m = [];
-               if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) {
-                       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 ) ) {
-                       return $m[1];
-               }
-               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
-                       ':' . RE_IPV6_WORD . '$/i', $addr, $m )
-               ) {
-                       return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
-               }
-
-               return null; // give up
-       }
-
-       /**
-        * Gets rid of unneeded numbers in quad-dotted/octet IP strings
-        * For example, 127.111.113.151/24 -> 127.111.113.0/24
-        * @param string $range 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";
-       }
-
-       /**
-        * Returns the subnet of a given IP
-        *
-        * @param string $ip
-        * @return string|false
-        */
-       public static function getSubnet( $ip ) {
-               $matches = [];
-               $subnet = false;
-               if ( IP::isIPv6( $ip ) ) {
-                       $parts = IP::parseRange( "$ip/64" );
-                       $subnet = $parts[0];
-               } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
-                       // IPv4
-                       $subnet = $matches[1];
-               }
-               return $subnet;
-       }
-}
diff --git a/tests/phpunit/includes/libs/IPTest.php b/tests/phpunit/includes/libs/IPTest.php
new file mode 100644 (file)
index 0000000..307652d
--- /dev/null
@@ -0,0 +1,670 @@
+<?php
+/**
+ * Tests for IP validity functions.
+ *
+ * Ported from /t/inc/IP.t by avar.
+ *
+ * @group IP
+ * @todo Test methods in this call should be split into a method and a
+ * dataprovider.
+ */
+
+class IPTest extends PHPUnit_Framework_TestCase {
+       /**
+        * @covers IP::isIPAddress
+        * @dataProvider provideInvalidIPs
+        */
+       public function isNotIPAddress( $val, $desc ) {
+               $this->assertFalse( IP::isIPAddress( $val ), $desc );
+       }
+
+       /**
+        * Provide a list of things that aren't IP addresses
+        */
+       public function provideInvalidIPs() {
+               return [
+                       [ false, 'Boolean false is not an IP' ],
+                       [ true, 'Boolean true is not an IP' ],
+                       [ '', 'Empty string is not an IP' ],
+                       [ 'abc', 'Garbage IP string' ],
+                       [ ':', 'Single ":" is not an IP' ],
+                       [ '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ],
+                       [ '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ],
+                       [ '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ],
+                       [ '124.24.52', 'IPv4 not enough quads' ],
+                       [ '24.324.52.13', 'IPv4 out of range' ],
+                       [ '.24.52.13', 'IPv4 starts with period' ],
+                       [ 'fc:100:300', 'IPv6 with only 3 words' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isIPAddress
+        */
+       public function testisIPAddress() {
+               $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' );
+               $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' );
+               $this->assertTrue( IP::isIPAddress( '74.24.52.13/20' ), 'IPv4 range' );
+               $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24' ), 'IPv6 range' );
+               $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96' ), 'IPv6 range with "::"' );
+
+               $validIPs = [ 'fc:100::', 'fc:100:a:d:1:e:ac::', 'fc::100', '::fc:100:a:d:1:e:ac',
+                       '::fc', 'fc::100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0', '124.24.52.13', '1.24.52.13' ];
+               foreach ( $validIPs as $ip ) {
+                       $this->assertTrue( IP::isIPAddress( $ip ), "$ip is a valid IP address" );
+               }
+       }
+
+       /**
+        * @covers IP::isIPv6
+        */
+       public function testisIPv6() {
+               $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' );
+               $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
+               $this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' );
+               $this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' );
+
+               $this->assertTrue( IP::isIPv6( 'fc:100::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
+
+               $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
+               $this->assertFalse(
+                       IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ),
+                       'IPv6 with 9 words ending with "::"'
+               );
+
+               $this->assertFalse( IP::isIPv6( ':::' ) );
+               $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
+
+               $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
+               $this->assertTrue( IP::isIPv6( '::0' ) );
+               $this->assertTrue( IP::isIPv6( '::fc' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
+               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
+
+               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
+               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
+
+               $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
+               $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' );
+               $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
+
+               $this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d' ), 'IPv6 with "::" and 4 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' );
+               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
+               $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
+
+               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
+               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
+
+               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) );
+       }
+
+       /**
+        * @covers IP::isIPv4
+        * @dataProvider provideInvalidIPv4Addresses
+        */
+       public function testisNotIPv4( $bogusIP, $desc ) {
+               $this->assertFalse( IP::isIPv4( $bogusIP ), $desc );
+       }
+
+       public function provideInvalidIPv4Addresses() {
+               return [
+                       [ false, 'Boolean false is not an IP' ],
+                       [ true, 'Boolean true is not an IP' ],
+                       [ '', 'Empty string is not an IP' ],
+                       [ 'abc', 'Letters are not an IP' ],
+                       [ ':', 'A colon is not an IP' ],
+                       [ '124.24.52', 'IPv4 not enough quads' ],
+                       [ '24.324.52.13', 'IPv4 out of range' ],
+                       [ '.24.52.13', 'IPv4 starts with period' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isIPv4
+        * @dataProvider provideValidIPv4Address
+        */
+       public function testIsIPv4( $ip, $desc ) {
+               $this->assertTrue( IP::isIPv4( $ip ), $desc );
+       }
+
+       /**
+        * Provide some IPv4 addresses and ranges
+        */
+       public function provideValidIPv4Address() {
+               return [
+                       [ '124.24.52.13', 'Valid IPv4 address' ],
+                       [ '1.24.52.13', 'Another valid IPv4 address' ],
+                       [ '74.24.52.13/20', 'An IPv4 range' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isValid
+        */
+       public function testValidIPs() {
+               foreach ( range( 0, 255 ) as $i ) {
+                       $a = sprintf( "%03d", $i );
+                       $b = sprintf( "%02d", $i );
+                       $c = sprintf( "%01d", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f.$f.$f.$f";
+                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" );
+                       }
+               }
+               foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) {
+                       $a = sprintf( "%04x", $i );
+                       $b = sprintf( "%03x", $i );
+                       $c = sprintf( "%02x", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
+                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" );
+                       }
+               }
+               // test with some abbreviations
+               $this->assertFalse( IP::isValid( ':fc:100::' ), 'IPv6 starting with lone ":"' );
+               $this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
+               $this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' );
+               $this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' );
+
+               $this->assertTrue( IP::isValid( 'fc:100::' ) );
+               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) );
+               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) );
+
+               $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
+               $this->assertTrue( IP::isValid( '2001::df' ), 'IPv6 with "::" and 2 words' );
+               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
+               $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
+               $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
+
+               $this->assertFalse(
+                       IP::isValid( 'fc:100:a:d:1:e:ac:0::' ),
+                       'IPv6 with 8 words ending with "::"'
+               );
+               $this->assertFalse(
+                       IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ),
+                       'IPv6 with 9 words ending with "::"'
+               );
+       }
+
+       /**
+        * @covers IP::isValid
+        */
+       public function testInvalidIPs() {
+               // Out of range...
+               foreach ( range( 256, 999 ) as $i ) {
+                       $a = sprintf( "%03d", $i );
+                       $b = sprintf( "%02d", $i );
+                       $c = sprintf( "%01d", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f.$f.$f.$f";
+                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" );
+                       }
+               }
+               foreach ( range( 'g', 'z' ) as $i ) {
+                       $a = sprintf( "%04s", $i );
+                       $b = sprintf( "%03s", $i );
+                       $c = sprintf( "%02s", $i );
+                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
+                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
+                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" );
+                       }
+               }
+               // Have CIDR
+               $ipCIDRs = [
+                       '212.35.31.121/32',
+                       '212.35.31.121/18',
+                       '212.35.31.121/24',
+                       '::ff:d:321:5/96',
+                       'ff::d3:321:5/116',
+                       'c:ff:12:1:ea:d:321:5/120',
+               ];
+               foreach ( $ipCIDRs as $i ) {
+                       $this->assertFalse( IP::isValid( $i ),
+                               "$i is an invalid IP address because it is a block" );
+               }
+               // Incomplete/garbage
+               $invalid = [
+                       'www.xn--var-xla.net',
+                       '216.17.184.G',
+                       '216.17.184.1.',
+                       '216.17.184',
+                       '216.17.184.',
+                       '256.17.184.1'
+               ];
+               foreach ( $invalid as $i ) {
+                       $this->assertFalse( IP::isValid( $i ), "$i is an invalid IP address" );
+               }
+       }
+
+       /**
+        * Provide some valid IP blocks
+        */
+       public function provideValidBlocks() {
+               return [
+                       [ '116.17.184.5/32' ],
+                       [ '0.17.184.5/30' ],
+                       [ '16.17.184.1/24' ],
+                       [ '30.242.52.14/1' ],
+                       [ '10.232.52.13/8' ],
+                       [ '30.242.52.14/0' ],
+                       [ '::e:f:2001/96' ],
+                       [ '::c:f:2001/128' ],
+                       [ '::10:f:2001/70' ],
+                       [ '::fe:f:2001/1' ],
+                       [ '::6d:f:2001/8' ],
+                       [ '::fe:f:2001/0' ],
+               ];
+       }
+
+       /**
+        * @covers IP::isValidBlock
+        * @dataProvider provideValidBlocks
+        */
+       public function testValidBlocks( $block ) {
+               $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" );
+       }
+
+       /**
+        * @covers IP::isValidBlock
+        * @dataProvider provideInvalidBlocks
+        */
+       public function testInvalidBlocks( $invalid ) {
+               $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" );
+       }
+
+       public function provideInvalidBlocks() {
+               return [
+                       [ '116.17.184.5/33' ],
+                       [ '0.17.184.5/130' ],
+                       [ '16.17.184.1/-1' ],
+                       [ '10.232.52.13/*' ],
+                       [ '7.232.52.13/ab' ],
+                       [ '11.232.52.13/' ],
+                       [ '::e:f:2001/129' ],
+                       [ '::c:f:2001/228' ],
+                       [ '::10:f:2001/-1' ],
+                       [ '::6d:f:2001/*' ],
+                       [ '::86:f:2001/ab' ],
+                       [ '::23:f:2001/' ],
+               ];
+       }
+
+       /**
+        * @covers IP::sanitizeIP
+        * @dataProvider provideSanitizeIP
+        */
+       public function testSanitizeIP( $expected, $input ) {
+               $result = IP::sanitizeIP( $input );
+               $this->assertEquals( $expected, $result );
+       }
+
+       /**
+        * Provider for IP::testSanitizeIP()
+        */
+       public static function provideSanitizeIP() {
+               return [
+                       [ '0.0.0.0', '0.0.0.0' ],
+                       [ '0.0.0.0', '00.00.00.00' ],
+                       [ '0.0.0.0', '000.000.000.000' ],
+                       [ '141.0.11.253', '141.000.011.253' ],
+                       [ '1.2.4.5', '1.2.4.5' ],
+                       [ '1.2.4.5', '01.02.04.05' ],
+                       [ '1.2.4.5', '001.002.004.005' ],
+                       [ '10.0.0.1', '010.0.000.1' ],
+                       [ '80.72.250.4', '080.072.250.04' ],
+                       [ 'Foo.1000.00', 'Foo.1000.00' ],
+                       [ 'Bar.01', 'Bar.01' ],
+                       [ 'Bar.010', 'Bar.010' ],
+                       [ null, '' ],
+                       [ null, ' ' ]
+               ];
+       }
+
+       /**
+        * @covers IP::toHex
+        * @dataProvider provideToHex
+        */
+       public function testToHex( $expected, $input ) {
+               $result = IP::toHex( $input );
+               $this->assertTrue( $result === false || is_string( $result ) );
+               $this->assertEquals( $expected, $result );
+       }
+
+       /**
+        * Provider for IP::testToHex()
+        */
+       public static function provideToHex() {
+               return [
+                       [ '00000001', '0.0.0.1' ],
+                       [ '01020304', '1.2.3.4' ],
+                       [ '7F000001', '127.0.0.1' ],
+                       [ '80000000', '128.0.0.0' ],
+                       [ 'DEADCAFE', '222.173.202.254' ],
+                       [ 'FFFFFFFF', '255.255.255.255' ],
+                       [ '8D000BFD', '141.000.11.253' ],
+                       [ false, 'IN.VA.LI.D' ],
+                       [ 'v6-00000000000000000000000000000001', '::1' ],
+                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ],
+                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ],
+                       [ false, 'IN:VA::LI:D' ],
+                       [ false, ':::1' ]
+               ];
+       }
+
+       /**
+        * @covers IP::isPublic
+        * @dataProvider provideIsPublic
+        */
+       public function testIsPublic( $expected, $input ) {
+               $result = IP::isPublic( $input );
+               $this->assertEquals( $expected, $result );
+       }
+
+       /**
+        * Provider for IP::testIsPublic()
+        */
+       public static function provideIsPublic() {
+               return [
+                       [ false, 'fc00::3' ], # RFC 4193 (local)
+                       [ false, 'fc00::ff' ], # RFC 4193 (local)
+                       [ false, '127.1.2.3' ], # loopback
+                       [ false, '::1' ], # loopback
+                       [ false, 'fe80::1' ], # link-local
+                       [ false, '169.254.1.1' ], # link-local
+                       [ false, '10.0.0.1' ], # RFC 1918 (private)
+                       [ false, '172.16.0.1' ], # RFC 1918 (private)
+                       [ false, '192.168.0.1' ], # RFC 1918 (private)
+                       [ true, '2001:5c0:1000:a::133' ], # public
+                       [ true, 'fc::3' ], # public
+                       [ true, '00FC::' ] # public
+               ];
+       }
+
+       // Private wrapper used to test CIDR Parsing.
+       private function assertFalseCIDR( $CIDR, $msg = '' ) {
+               $ff = [ false, false ];
+               $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg );
+       }
+
+       // Private wrapper to test network shifting using only dot notation
+       private function assertNet( $expected, $CIDR ) {
+               $parse = IP::parseCIDR( $CIDR );
+               $this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" );
+       }
+
+       /**
+        * @covers IP::hexToQuad
+        * @dataProvider provideIPsAndHexes
+        */
+       public function testHexToQuad( $ip, $hex ) {
+               $this->assertEquals( $ip, IP::hexToQuad( $hex ) );
+       }
+
+       /**
+        * Provide some IP addresses and their equivalent hex representations
+        */
+       public function provideIPsandHexes() {
+               return [
+                       [ '0.0.0.1', '00000001' ],
+                       [ '255.0.0.0', 'FF000000' ],
+                       [ '255.255.255.255', 'FFFFFFFF' ],
+                       [ '10.188.222.255', '0ABCDEFF' ],
+                       // hex not left-padded...
+                       [ '0.0.0.0', '0' ],
+                       [ '0.0.0.1', '1' ],
+                       [ '0.0.0.255', 'FF' ],
+                       [ '0.0.255.0', 'FF00' ],
+               ];
+       }
+
+       /**
+        * @covers IP::hexToOctet
+        * @dataProvider provideOctetsAndHexes
+        */
+       public function testHexToOctet( $octet, $hex ) {
+               $this->assertEquals( $octet, IP::hexToOctet( $hex ) );
+       }
+
+       /**
+        * Provide some hex and octet representations of the same IPs
+        */
+       public function provideOctetsAndHexes() {
+               return [
+                       [ '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ],
+                       [ '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ],
+                       [ '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ],
+                       [ '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ],
+                       [ 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ],
+                       // hex not left-padded...
+                       [ '0:0:0:0:0:0:0:0', '0' ],
+                       [ '0:0:0:0:0:0:0:1', '1' ],
+                       [ '0:0:0:0:0:0:0:FF', 'FF' ],
+                       [ '0:0:0:0:0:0:0:FFD0', 'FFD0' ],
+                       [ '0:0:0:0:0:0:FA00:0', 'FA000000' ],
+                       [ '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ],
+               ];
+       }
+
+       /**
+        * IP::parseCIDR() returns an array containing a signed IP address
+        * representing the network mask and the bit mask.
+        * @covers IP::parseCIDR
+        */
+       public function testCIDRParsing() {
+               $this->assertFalseCIDR( '192.0.2.0', "missing mask" );
+               $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" );
+
+               // Verify if statement
+               $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" );
+               $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" );
+               $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" );
+               $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" );
+
+               // Check internal logic
+               # 0 mask always result in array(0,0)
+               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '192.0.0.2/0' ) );
+               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '0.0.0.0/0' ) );
+               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '255.255.255.255/0' ) );
+
+               // @todo FIXME: Add more tests.
+
+               # This part test network shifting
+               $this->assertNet( '192.0.0.0', '192.0.0.2/24' );
+               $this->assertNet( '192.168.5.0', '192.168.5.13/24' );
+               $this->assertNet( '10.0.0.160', '10.0.0.161/28' );
+               $this->assertNet( '10.0.0.0', '10.0.0.3/28' );
+               $this->assertNet( '10.0.0.0', '10.0.0.3/30' );
+               $this->assertNet( '10.0.0.4', '10.0.0.4/30' );
+               $this->assertNet( '172.17.32.0', '172.17.35.48/21' );
+               $this->assertNet( '10.128.0.0', '10.135.0.0/9' );
+               $this->assertNet( '134.0.0.0', '134.0.5.1/8' );
+       }
+
+       /**
+        * @covers IP::canonicalize
+        */
+       public function testIPCanonicalizeOnValidIp() {
+               $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ),
+                       'Canonicalization of a valid IP returns it unchanged' );
+       }
+
+       /**
+        * @covers IP::canonicalize
+        */
+       public function testIPCanonicalizeMappedAddress() {
+               $this->assertEquals(
+                       '192.0.2.152',
+                       IP::canonicalize( '::ffff:192.0.2.152' )
+               );
+               $this->assertEquals(
+                       '192.0.2.152',
+                       IP::canonicalize( '::192.0.2.152' )
+               );
+       }
+
+       /**
+        * Issues there are most probably from IP::toHex() or IP::parseRange()
+        * @covers IP::isInRange
+        * @dataProvider provideIPsAndRanges
+        */
+       public function testIPIsInRange( $expected, $addr, $range, $message = '' ) {
+               $this->assertEquals(
+                       $expected,
+                       IP::isInRange( $addr, $range ),
+                       $message
+               );
+       }
+
+       /** Provider for testIPIsInRange() */
+       public static function provideIPsAndRanges() {
+               # Format: (expected boolean, address, range, optional message)
+               return [
+                       # IPv4
+                       [ true, '192.0.2.0', '192.0.2.0/24', 'Network address' ],
+                       [ true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ],
+                       [ true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ],
+
+                       [ false, '0.0.0.0', '192.0.2.0/24' ],
+                       [ false, '255.255.255', '192.0.2.0/24' ],
+
+                       # IPv6
+                       [ false, '::1', '2001:DB8::/32' ],
+                       [ false, '::', '2001:DB8::/32' ],
+                       [ false, 'FE80::1', '2001:DB8::/32' ],
+
+                       [ true, '2001:DB8::', '2001:DB8::/32' ],
+                       [ true, '2001:0DB8::', '2001:DB8::/32' ],
+                       [ true, '2001:DB8::1', '2001:DB8::/32' ],
+                       [ true, '2001:0DB8::1', '2001:DB8::/32' ],
+                       [ true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF',
+                               '2001:DB8::/32' ],
+
+                       [ false, '2001:0DB8:F::', '2001:DB8::/96' ],
+               ];
+       }
+
+       /**
+        * Test for IP::splitHostAndPort().
+        * @dataProvider provideSplitHostAndPort
+        */
+       public function testSplitHostAndPort( $expected, $input, $description ) {
+               $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description );
+       }
+
+       /**
+        * Provider for IP::splitHostAndPort()
+        */
+       public static function provideSplitHostAndPort() {
+               return [
+                       [ false, '[', 'Unclosed square bracket' ],
+                       [ false, '[::', 'Unclosed square bracket 2' ],
+                       [ [ '::', false ], '::', 'Bare IPv6 0' ],
+                       [ [ '::1', false ], '::1', 'Bare IPv6 1' ],
+                       [ [ '::', false ], '[::]', 'Bracketed IPv6 0' ],
+                       [ [ '::1', false ], '[::1]', 'Bracketed IPv6 1' ],
+                       [ [ '::1', 80 ], '[::1]:80', 'Bracketed IPv6 with port' ],
+                       [ false, '::x', 'Double colon but no IPv6' ],
+                       [ [ 'x', 80 ], 'x:80', 'Hostname and port' ],
+                       [ false, 'x:x', 'Hostname and invalid port' ],
+                       [ [ 'x', false ], 'x', 'Plain hostname' ]
+               ];
+       }
+
+       /**
+        * Test for IP::combineHostAndPort()
+        * @dataProvider provideCombineHostAndPort
+        */
+       public function testCombineHostAndPort( $expected, $input, $description ) {
+               list( $host, $port, $defaultPort ) = $input;
+               $this->assertEquals(
+                       $expected,
+                       IP::combineHostAndPort( $host, $port, $defaultPort ),
+                       $description );
+       }
+
+       /**
+        * Provider for IP::combineHostAndPort()
+        */
+       public static function provideCombineHostAndPort() {
+               return [
+                       [ '[::1]', [ '::1', 2, 2 ], 'IPv6 default port' ],
+                       [ '[::1]:2', [ '::1', 2, 3 ], 'IPv6 non-default port' ],
+                       [ 'x', [ 'x', 2, 2 ], 'Normal default port' ],
+                       [ 'x:2', [ 'x', 2, 3 ], 'Normal non-default port' ],
+               ];
+       }
+
+       /**
+        * Test for IP::sanitizeRange()
+        * @dataProvider provideIPCIDRs
+        */
+       public function testSanitizeRange( $input, $expected, $description ) {
+               $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description );
+       }
+
+       /**
+        * Provider for IP::testSanitizeRange()
+        */
+       public static function provideIPCIDRs() {
+               return [
+                       [ '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ],
+                       [ '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ],
+                       [ '5.36.71.252/32', '5.36.71.252/32', 'IPv4 silly range' ],
+                       [ '5.36.71.252', '5.36.71.252', 'IPv4 non-range' ],
+                       [ '0:1:2:3:4:c5:f6:7/96', '0:1:2:3:4:C5:0:0/96', 'IPv6 range' ],
+                       [ '0:1:2:3:4:5:6:7/120', '0:1:2:3:4:5:6:0/120', 'IPv6 range' ],
+                       [ '0:e1:2:3:4:5:e6:7/128', '0:E1:2:3:4:5:E6:7/128', 'IPv6 silly range' ],
+                       [ '0:c1:A2:3:4:5:c6:7', '0:C1:A2:3:4:5:C6:7', 'IPv6 non range' ],
+               ];
+       }
+
+       /**
+        * Test for IP::prettifyIP()
+        * @dataProvider provideIPsToPrettify
+        */
+       public function testPrettifyIP( $ip, $prettified ) {
+               $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" );
+       }
+
+       /**
+        * Provider for IP::testPrettifyIP()
+        */
+       public static function provideIPsToPrettify() {
+               return [
+                       [ '0:0:0:0:0:0:0:0', '::' ],
+                       [ '0:0:0::0:0:0', '::' ],
+                       [ '0:0:0:1:0:0:0:0', '0:0:0:1::' ],
+                       [ '0:0::f', '::f' ],
+                       [ '0::0:0:0:33:fef:b', '::33:fef:b' ],
+                       [ '3f:535:0:0:0:0:e:fbb', '3f:535::e:fbb' ],
+                       [ '0:0:fef:0:0:0:e:fbb', '0:0:fef::e:fbb' ],
+                       [ 'abbc:2004::0:0:0:0', 'abbc:2004::' ],
+                       [ 'cebc:2004:f:0:0:0:0:0', 'cebc:2004:f::' ],
+                       [ '0:0:0:0:0:0:0:0/16', '::/16' ],
+                       [ '0:0:0::0:0:0/64', '::/64' ],
+                       [ '0:0::f/52', '::f/52' ],
+                       [ '::0:0:33:fef:b/52', '::33:fef:b/52' ],
+                       [ '3f:535:0:0:0:0:e:fbb/48', '3f:535::e:fbb/48' ],
+                       [ '0:0:fef:0:0:0:e:fbb/96', '0:0:fef::e:fbb/96' ],
+                       [ 'abbc:2004:0:0::0:0/40', 'abbc:2004::/40' ],
+                       [ 'aebc:2004:f:0:0:0:0:0/80', 'aebc:2004:f::/80' ],
+               ];
+       }
+}
diff --git a/tests/phpunit/includes/utils/IPTest.php b/tests/phpunit/includes/utils/IPTest.php
deleted file mode 100644 (file)
index 5e0626b..0000000
+++ /dev/null
@@ -1,670 +0,0 @@
-<?php
-/**
- * Tests for IP validity functions.
- *
- * Ported from /t/inc/IP.t by avar.
- *
- * @group IP
- * @todo Test methods in this call should be split into a method and a
- * dataprovider.
- */
-
-class IPTest extends PHPUnit_Framework_TestCase {
-       /**
-        * @covers IP::isIPAddress
-        * @dataProvider provideInvalidIPs
-        */
-       public function isNotIPAddress( $val, $desc ) {
-               $this->assertFalse( IP::isIPAddress( $val ), $desc );
-       }
-
-       /**
-        * Provide a list of things that aren't IP addresses
-        */
-       public function provideInvalidIPs() {
-               return [
-                       [ false, 'Boolean false is not an IP' ],
-                       [ true, 'Boolean true is not an IP' ],
-                       [ '', 'Empty string is not an IP' ],
-                       [ 'abc', 'Garbage IP string' ],
-                       [ ':', 'Single ":" is not an IP' ],
-                       [ '2001:0DB8::A:1::1', 'IPv6 with a double :: occurrence' ],
-                       [ '2001:0DB8::A:1::', 'IPv6 with a double :: occurrence, last at end' ],
-                       [ '::2001:0DB8::5:1', 'IPv6 with a double :: occurrence, firt at beginning' ],
-                       [ '124.24.52', 'IPv4 not enough quads' ],
-                       [ '24.324.52.13', 'IPv4 out of range' ],
-                       [ '.24.52.13', 'IPv4 starts with period' ],
-                       [ 'fc:100:300', 'IPv6 with only 3 words' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isIPAddress
-        */
-       public function testisIPAddress() {
-               $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' );
-               $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' );
-               $this->assertTrue( IP::isIPAddress( '74.24.52.13/20', 'IPv4 range' ) );
-               $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24' ), 'IPv6 range' );
-               $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96' ), 'IPv6 range with "::"' );
-
-               $validIPs = [ 'fc:100::', 'fc:100:a:d:1:e:ac::', 'fc::100', '::fc:100:a:d:1:e:ac',
-                       '::fc', 'fc::100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0', '124.24.52.13', '1.24.52.13' ];
-               foreach ( $validIPs as $ip ) {
-                       $this->assertTrue( IP::isIPAddress( $ip ), "$ip is a valid IP address" );
-               }
-       }
-
-       /**
-        * @covers IP::isIPv6
-        */
-       public function testisIPv6() {
-               $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' );
-               $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
-               $this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' );
-               $this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
-               $this->assertTrue( IP::isIPv6( 'fc:100::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
-
-               $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
-               $this->assertFalse(
-                       IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ),
-                       'IPv6 with 9 words ending with "::"'
-               );
-
-               $this->assertFalse( IP::isIPv6( ':::' ) );
-               $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
-
-               $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
-               $this->assertTrue( IP::isIPv6( '::0' ) );
-               $this->assertTrue( IP::isIPv6( '::fc' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
-               $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
-
-               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
-               $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
-
-               $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
-               $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' );
-               $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
-
-               $this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ) );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' );
-               $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
-               $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
-
-               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' );
-               $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' );
-
-               $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) );
-       }
-
-       /**
-        * @covers IP::isIPv4
-        * @dataProvider provideInvalidIPv4Addresses
-        */
-       public function testisNotIPv4( $bogusIP, $desc ) {
-               $this->assertFalse( IP::isIPv4( $bogusIP ), $desc );
-       }
-
-       public function provideInvalidIPv4Addresses() {
-               return [
-                       [ false, 'Boolean false is not an IP' ],
-                       [ true, 'Boolean true is not an IP' ],
-                       [ '', 'Empty string is not an IP' ],
-                       [ 'abc', 'Letters are not an IP' ],
-                       [ ':', 'A colon is not an IP' ],
-                       [ '124.24.52', 'IPv4 not enough quads' ],
-                       [ '24.324.52.13', 'IPv4 out of range' ],
-                       [ '.24.52.13', 'IPv4 starts with period' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isIPv4
-        * @dataProvider provideValidIPv4Address
-        */
-       public function testIsIPv4( $ip, $desc ) {
-               $this->assertTrue( IP::isIPv4( $ip ), $desc );
-       }
-
-       /**
-        * Provide some IPv4 addresses and ranges
-        */
-       public function provideValidIPv4Address() {
-               return [
-                       [ '124.24.52.13', 'Valid IPv4 address' ],
-                       [ '1.24.52.13', 'Another valid IPv4 address' ],
-                       [ '74.24.52.13/20', 'An IPv4 range' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isValid
-        */
-       public function testValidIPs() {
-               foreach ( range( 0, 255 ) as $i ) {
-                       $a = sprintf( "%03d", $i );
-                       $b = sprintf( "%02d", $i );
-                       $c = sprintf( "%01d", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f.$f.$f.$f";
-                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" );
-                       }
-               }
-               foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) {
-                       $a = sprintf( "%04x", $i );
-                       $b = sprintf( "%03x", $i );
-                       $c = sprintf( "%02x", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
-                               $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" );
-                       }
-               }
-               // test with some abbreviations
-               $this->assertFalse( IP::isValid( ':fc:100::' ), 'IPv6 starting with lone ":"' );
-               $this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
-               $this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' );
-               $this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
-               $this->assertTrue( IP::isValid( 'fc:100::' ) );
-               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) );
-               $this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) );
-
-               $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
-               $this->assertTrue( IP::isValid( '2001::df' ), 'IPv6 with "::" and 2 words' );
-               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
-               $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
-               $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
-
-               $this->assertFalse(
-                       IP::isValid( 'fc:100:a:d:1:e:ac:0::' ),
-                       'IPv6 with 8 words ending with "::"'
-               );
-               $this->assertFalse(
-                       IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ),
-                       'IPv6 with 9 words ending with "::"'
-               );
-       }
-
-       /**
-        * @covers IP::isValid
-        */
-       public function testInvalidIPs() {
-               // Out of range...
-               foreach ( range( 256, 999 ) as $i ) {
-                       $a = sprintf( "%03d", $i );
-                       $b = sprintf( "%02d", $i );
-                       $c = sprintf( "%01d", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f.$f.$f.$f";
-                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" );
-                       }
-               }
-               foreach ( range( 'g', 'z' ) as $i ) {
-                       $a = sprintf( "%04s", $i );
-                       $b = sprintf( "%03s", $i );
-                       $c = sprintf( "%02s", $i );
-                       foreach ( array_unique( [ $a, $b, $c ] ) as $f ) {
-                               $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
-                               $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" );
-                       }
-               }
-               // Have CIDR
-               $ipCIDRs = [
-                       '212.35.31.121/32',
-                       '212.35.31.121/18',
-                       '212.35.31.121/24',
-                       '::ff:d:321:5/96',
-                       'ff::d3:321:5/116',
-                       'c:ff:12:1:ea:d:321:5/120',
-               ];
-               foreach ( $ipCIDRs as $i ) {
-                       $this->assertFalse( IP::isValid( $i ),
-                               "$i is an invalid IP address because it is a block" );
-               }
-               // Incomplete/garbage
-               $invalid = [
-                       'www.xn--var-xla.net',
-                       '216.17.184.G',
-                       '216.17.184.1.',
-                       '216.17.184',
-                       '216.17.184.',
-                       '256.17.184.1'
-               ];
-               foreach ( $invalid as $i ) {
-                       $this->assertFalse( IP::isValid( $i ), "$i is an invalid IP address" );
-               }
-       }
-
-       /**
-        * Provide some valid IP blocks
-        */
-       public function provideValidBlocks() {
-               return [
-                       [ '116.17.184.5/32' ],
-                       [ '0.17.184.5/30' ],
-                       [ '16.17.184.1/24' ],
-                       [ '30.242.52.14/1' ],
-                       [ '10.232.52.13/8' ],
-                       [ '30.242.52.14/0' ],
-                       [ '::e:f:2001/96' ],
-                       [ '::c:f:2001/128' ],
-                       [ '::10:f:2001/70' ],
-                       [ '::fe:f:2001/1' ],
-                       [ '::6d:f:2001/8' ],
-                       [ '::fe:f:2001/0' ],
-               ];
-       }
-
-       /**
-        * @covers IP::isValidBlock
-        * @dataProvider provideValidBlocks
-        */
-       public function testValidBlocks( $block ) {
-               $this->assertTrue( IP::isValidBlock( $block ), "$block is a valid IP block" );
-       }
-
-       /**
-        * @covers IP::isValidBlock
-        * @dataProvider provideInvalidBlocks
-        */
-       public function testInvalidBlocks( $invalid ) {
-               $this->assertFalse( IP::isValidBlock( $invalid ), "$invalid is not a valid IP block" );
-       }
-
-       public function provideInvalidBlocks() {
-               return [
-                       [ '116.17.184.5/33' ],
-                       [ '0.17.184.5/130' ],
-                       [ '16.17.184.1/-1' ],
-                       [ '10.232.52.13/*' ],
-                       [ '7.232.52.13/ab' ],
-                       [ '11.232.52.13/' ],
-                       [ '::e:f:2001/129' ],
-                       [ '::c:f:2001/228' ],
-                       [ '::10:f:2001/-1' ],
-                       [ '::6d:f:2001/*' ],
-                       [ '::86:f:2001/ab' ],
-                       [ '::23:f:2001/' ],
-               ];
-       }
-
-       /**
-        * @covers IP::sanitizeIP
-        * @dataProvider provideSanitizeIP
-        */
-       public function testSanitizeIP( $expected, $input ) {
-               $result = IP::sanitizeIP( $input );
-               $this->assertEquals( $expected, $result );
-       }
-
-       /**
-        * Provider for IP::testSanitizeIP()
-        */
-       public static function provideSanitizeIP() {
-               return [
-                       [ '0.0.0.0', '0.0.0.0' ],
-                       [ '0.0.0.0', '00.00.00.00' ],
-                       [ '0.0.0.0', '000.000.000.000' ],
-                       [ '141.0.11.253', '141.000.011.253' ],
-                       [ '1.2.4.5', '1.2.4.5' ],
-                       [ '1.2.4.5', '01.02.04.05' ],
-                       [ '1.2.4.5', '001.002.004.005' ],
-                       [ '10.0.0.1', '010.0.000.1' ],
-                       [ '80.72.250.4', '080.072.250.04' ],
-                       [ 'Foo.1000.00', 'Foo.1000.00' ],
-                       [ 'Bar.01', 'Bar.01' ],
-                       [ 'Bar.010', 'Bar.010' ],
-                       [ null, '' ],
-                       [ null, ' ' ]
-               ];
-       }
-
-       /**
-        * @covers IP::toHex
-        * @dataProvider provideToHex
-        */
-       public function testToHex( $expected, $input ) {
-               $result = IP::toHex( $input );
-               $this->assertTrue( $result === false || is_string( $result ) );
-               $this->assertEquals( $expected, $result );
-       }
-
-       /**
-        * Provider for IP::testToHex()
-        */
-       public static function provideToHex() {
-               return [
-                       [ '00000001', '0.0.0.1' ],
-                       [ '01020304', '1.2.3.4' ],
-                       [ '7F000001', '127.0.0.1' ],
-                       [ '80000000', '128.0.0.0' ],
-                       [ 'DEADCAFE', '222.173.202.254' ],
-                       [ 'FFFFFFFF', '255.255.255.255' ],
-                       [ '8D000BFD', '141.000.11.253' ],
-                       [ false, 'IN.VA.LI.D' ],
-                       [ 'v6-00000000000000000000000000000001', '::1' ],
-                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ],
-                       [ 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ],
-                       [ false, 'IN:VA::LI:D' ],
-                       [ false, ':::1' ]
-               ];
-       }
-
-       /**
-        * @covers IP::isPublic
-        * @dataProvider provideIsPublic
-        */
-       public function testIsPublic( $expected, $input ) {
-               $result = IP::isPublic( $input );
-               $this->assertEquals( $expected, $result );
-       }
-
-       /**
-        * Provider for IP::testIsPublic()
-        */
-       public static function provideIsPublic() {
-               return [
-                       [ false, 'fc00::3' ], # RFC 4193 (local)
-                       [ false, 'fc00::ff' ], # RFC 4193 (local)
-                       [ false, '127.1.2.3' ], # loopback
-                       [ false, '::1' ], # loopback
-                       [ false, 'fe80::1' ], # link-local
-                       [ false, '169.254.1.1' ], # link-local
-                       [ false, '10.0.0.1' ], # RFC 1918 (private)
-                       [ false, '172.16.0.1' ], # RFC 1918 (private)
-                       [ false, '192.168.0.1' ], # RFC 1918 (private)
-                       [ true, '2001:5c0:1000:a::133' ], # public
-                       [ true, 'fc::3' ], # public
-                       [ true, '00FC::' ] # public
-               ];
-       }
-
-       // Private wrapper used to test CIDR Parsing.
-       private function assertFalseCIDR( $CIDR, $msg = '' ) {
-               $ff = [ false, false ];
-               $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg );
-       }
-
-       // Private wrapper to test network shifting using only dot notation
-       private function assertNet( $expected, $CIDR ) {
-               $parse = IP::parseCIDR( $CIDR );
-               $this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" );
-       }
-
-       /**
-        * @covers IP::hexToQuad
-        * @dataProvider provideIPsAndHexes
-        */
-       public function testHexToQuad( $ip, $hex ) {
-               $this->assertEquals( $ip, IP::hexToQuad( $hex ) );
-       }
-
-       /**
-        * Provide some IP addresses and their equivalent hex representations
-        */
-       public function provideIPsandHexes() {
-               return [
-                       [ '0.0.0.1', '00000001' ],
-                       [ '255.0.0.0', 'FF000000' ],
-                       [ '255.255.255.255', 'FFFFFFFF' ],
-                       [ '10.188.222.255', '0ABCDEFF' ],
-                       // hex not left-padded...
-                       [ '0.0.0.0', '0' ],
-                       [ '0.0.0.1', '1' ],
-                       [ '0.0.0.255', 'FF' ],
-                       [ '0.0.255.0', 'FF00' ],
-               ];
-       }
-
-       /**
-        * @covers IP::hexToOctet
-        * @dataProvider provideOctetsAndHexes
-        */
-       public function testHexToOctet( $octet, $hex ) {
-               $this->assertEquals( $octet, IP::hexToOctet( $hex ) );
-       }
-
-       /**
-        * Provide some hex and octet representations of the same IPs
-        */
-       public function provideOctetsAndHexes() {
-               return [
-                       [ '0:0:0:0:0:0:0:1', '00000000000000000000000000000001' ],
-                       [ '0:0:0:0:0:0:FF:3', '00000000000000000000000000FF0003' ],
-                       [ '0:0:0:0:0:0:FF00:6', '000000000000000000000000FF000006' ],
-                       [ '0:0:0:0:0:0:FCCF:FAFF', '000000000000000000000000FCCFFAFF' ],
-                       [ 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ],
-                       // hex not left-padded...
-                       [ '0:0:0:0:0:0:0:0', '0' ],
-                       [ '0:0:0:0:0:0:0:1', '1' ],
-                       [ '0:0:0:0:0:0:0:FF', 'FF' ],
-                       [ '0:0:0:0:0:0:0:FFD0', 'FFD0' ],
-                       [ '0:0:0:0:0:0:FA00:0', 'FA000000' ],
-                       [ '0:0:0:0:0:0:FCCF:FAFF', 'FCCFFAFF' ],
-               ];
-       }
-
-       /**
-        * IP::parseCIDR() returns an array containing a signed IP address
-        * representing the network mask and the bit mask.
-        * @covers IP::parseCIDR
-        */
-       public function testCIDRParsing() {
-               $this->assertFalseCIDR( '192.0.2.0', "missing mask" );
-               $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" );
-
-               // Verify if statement
-               $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" );
-               $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" );
-               $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" );
-               $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" );
-
-               // Check internal logic
-               # 0 mask always result in array(0,0)
-               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '192.0.0.2/0' ) );
-               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '0.0.0.0/0' ) );
-               $this->assertEquals( [ 0, 0 ], IP::parseCIDR( '255.255.255.255/0' ) );
-
-               // @todo FIXME: Add more tests.
-
-               # This part test network shifting
-               $this->assertNet( '192.0.0.0', '192.0.0.2/24' );
-               $this->assertNet( '192.168.5.0', '192.168.5.13/24' );
-               $this->assertNet( '10.0.0.160', '10.0.0.161/28' );
-               $this->assertNet( '10.0.0.0', '10.0.0.3/28' );
-               $this->assertNet( '10.0.0.0', '10.0.0.3/30' );
-               $this->assertNet( '10.0.0.4', '10.0.0.4/30' );
-               $this->assertNet( '172.17.32.0', '172.17.35.48/21' );
-               $this->assertNet( '10.128.0.0', '10.135.0.0/9' );
-               $this->assertNet( '134.0.0.0', '134.0.5.1/8' );
-       }
-
-       /**
-        * @covers IP::canonicalize
-        */
-       public function testIPCanonicalizeOnValidIp() {
-               $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ),
-                       'Canonicalization of a valid IP returns it unchanged' );
-       }
-
-       /**
-        * @covers IP::canonicalize
-        */
-       public function testIPCanonicalizeMappedAddress() {
-               $this->assertEquals(
-                       '192.0.2.152',
-                       IP::canonicalize( '::ffff:192.0.2.152' )
-               );
-               $this->assertEquals(
-                       '192.0.2.152',
-                       IP::canonicalize( '::192.0.2.152' )
-               );
-       }
-
-       /**
-        * Issues there are most probably from IP::toHex() or IP::parseRange()
-        * @covers IP::isInRange
-        * @dataProvider provideIPsAndRanges
-        */
-       public function testIPIsInRange( $expected, $addr, $range, $message = '' ) {
-               $this->assertEquals(
-                       $expected,
-                       IP::isInRange( $addr, $range ),
-                       $message
-               );
-       }
-
-       /** Provider for testIPIsInRange() */
-       public static function provideIPsAndRanges() {
-               # Format: (expected boolean, address, range, optional message)
-               return [
-                       # IPv4
-                       [ true, '192.0.2.0', '192.0.2.0/24', 'Network address' ],
-                       [ true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ],
-                       [ true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ],
-
-                       [ false, '0.0.0.0', '192.0.2.0/24' ],
-                       [ false, '255.255.255', '192.0.2.0/24' ],
-
-                       # IPv6
-                       [ false, '::1', '2001:DB8::/32' ],
-                       [ false, '::', '2001:DB8::/32' ],
-                       [ false, 'FE80::1', '2001:DB8::/32' ],
-
-                       [ true, '2001:DB8::', '2001:DB8::/32' ],
-                       [ true, '2001:0DB8::', '2001:DB8::/32' ],
-                       [ true, '2001:DB8::1', '2001:DB8::/32' ],
-                       [ true, '2001:0DB8::1', '2001:DB8::/32' ],
-                       [ true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF',
-                               '2001:DB8::/32' ],
-
-                       [ false, '2001:0DB8:F::', '2001:DB8::/96' ],
-               ];
-       }
-
-       /**
-        * Test for IP::splitHostAndPort().
-        * @dataProvider provideSplitHostAndPort
-        */
-       public function testSplitHostAndPort( $expected, $input, $description ) {
-               $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description );
-       }
-
-       /**
-        * Provider for IP::splitHostAndPort()
-        */
-       public static function provideSplitHostAndPort() {
-               return [
-                       [ false, '[', 'Unclosed square bracket' ],
-                       [ false, '[::', 'Unclosed square bracket 2' ],
-                       [ [ '::', false ], '::', 'Bare IPv6 0' ],
-                       [ [ '::1', false ], '::1', 'Bare IPv6 1' ],
-                       [ [ '::', false ], '[::]', 'Bracketed IPv6 0' ],
-                       [ [ '::1', false ], '[::1]', 'Bracketed IPv6 1' ],
-                       [ [ '::1', 80 ], '[::1]:80', 'Bracketed IPv6 with port' ],
-                       [ false, '::x', 'Double colon but no IPv6' ],
-                       [ [ 'x', 80 ], 'x:80', 'Hostname and port' ],
-                       [ false, 'x:x', 'Hostname and invalid port' ],
-                       [ [ 'x', false ], 'x', 'Plain hostname' ]
-               ];
-       }
-
-       /**
-        * Test for IP::combineHostAndPort()
-        * @dataProvider provideCombineHostAndPort
-        */
-       public function testCombineHostAndPort( $expected, $input, $description ) {
-               list( $host, $port, $defaultPort ) = $input;
-               $this->assertEquals(
-                       $expected,
-                       IP::combineHostAndPort( $host, $port, $defaultPort ),
-                       $description );
-       }
-
-       /**
-        * Provider for IP::combineHostAndPort()
-        */
-       public static function provideCombineHostAndPort() {
-               return [
-                       [ '[::1]', [ '::1', 2, 2 ], 'IPv6 default port' ],
-                       [ '[::1]:2', [ '::1', 2, 3 ], 'IPv6 non-default port' ],
-                       [ 'x', [ 'x', 2, 2 ], 'Normal default port' ],
-                       [ 'x:2', [ 'x', 2, 3 ], 'Normal non-default port' ],
-               ];
-       }
-
-       /**
-        * Test for IP::sanitizeRange()
-        * @dataProvider provideIPCIDRs
-        */
-       public function testSanitizeRange( $input, $expected, $description ) {
-               $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description );
-       }
-
-       /**
-        * Provider for IP::testSanitizeRange()
-        */
-       public static function provideIPCIDRs() {
-               return [
-                       [ '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ],
-                       [ '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ],
-                       [ '5.36.71.252/32', '5.36.71.252/32', 'IPv4 silly range' ],
-                       [ '5.36.71.252', '5.36.71.252', 'IPv4 non-range' ],
-                       [ '0:1:2:3:4:c5:f6:7/96', '0:1:2:3:4:C5:0:0/96', 'IPv6 range' ],
-                       [ '0:1:2:3:4:5:6:7/120', '0:1:2:3:4:5:6:0/120', 'IPv6 range' ],
-                       [ '0:e1:2:3:4:5:e6:7/128', '0:E1:2:3:4:5:E6:7/128', 'IPv6 silly range' ],
-                       [ '0:c1:A2:3:4:5:c6:7', '0:C1:A2:3:4:5:C6:7', 'IPv6 non range' ],
-               ];
-       }
-
-       /**
-        * Test for IP::prettifyIP()
-        * @dataProvider provideIPsToPrettify
-        */
-       public function testPrettifyIP( $ip, $prettified ) {
-               $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" );
-       }
-
-       /**
-        * Provider for IP::testPrettifyIP()
-        */
-       public static function provideIPsToPrettify() {
-               return [
-                       [ '0:0:0:0:0:0:0:0', '::' ],
-                       [ '0:0:0::0:0:0', '::' ],
-                       [ '0:0:0:1:0:0:0:0', '0:0:0:1::' ],
-                       [ '0:0::f', '::f' ],
-                       [ '0::0:0:0:33:fef:b', '::33:fef:b' ],
-                       [ '3f:535:0:0:0:0:e:fbb', '3f:535::e:fbb' ],
-                       [ '0:0:fef:0:0:0:e:fbb', '0:0:fef::e:fbb' ],
-                       [ 'abbc:2004::0:0:0:0', 'abbc:2004::' ],
-                       [ 'cebc:2004:f:0:0:0:0:0', 'cebc:2004:f::' ],
-                       [ '0:0:0:0:0:0:0:0/16', '::/16' ],
-                       [ '0:0:0::0:0:0/64', '::/64' ],
-                       [ '0:0::f/52', '::f/52' ],
-                       [ '::0:0:33:fef:b/52', '::33:fef:b/52' ],
-                       [ '3f:535:0:0:0:0:e:fbb/48', '3f:535::e:fbb/48' ],
-                       [ '0:0:fef:0:0:0:e:fbb/96', '0:0:fef::e:fbb/96' ],
-                       [ 'abbc:2004:0:0::0:0/40', 'abbc:2004::/40' ],
-                       [ 'aebc:2004:f:0:0:0:0:0/80', 'aebc:2004:f::/80' ],
-               ];
-       }
-}