Index: trunk/phase3/includes/IP.php |
— | — | @@ -36,12 +36,15 @@ |
37 | 37 | // An IPv6 block is an IP address and a prefix (d1 to d128) |
38 | 38 | define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)'); |
39 | 39 | // An IPv6 IP is made up of 8 octets. However abbreviations like "::" can be used. |
40 | | -// This is lax! Number of octets/double colons validation not done. |
| 40 | +// This is lax! The number of colon groups is checked (1 to 7) but |
| 41 | +// the number of double colons is not validated (must be 0 to 1). |
41 | 42 | define( 'RE_IPV6_ADD', |
42 | 43 | '(' . |
43 | | - ':(:' . RE_IPV6_WORD . '){1,7}' . // IPs that start with ":" |
| 44 | + ':(:' . RE_IPV6_WORD . '){1,7}' . // starts with "::" |
44 | 45 | '|' . |
45 | | - RE_IPV6_WORD . '(:{1,2}' . RE_IPV6_WORD . '|::$){1,7}' . // IPs that don't start with ":" |
| 46 | + RE_IPV6_WORD . '(:{1,2}' . RE_IPV6_WORD . '){0,6}::' . // ends with "::" |
| 47 | + '|' . |
| 48 | + RE_IPV6_WORD . '(:{1,2}' . RE_IPV6_WORD . '){1,7}' . // neither of the above |
46 | 49 | ')' |
47 | 50 | ); |
48 | 51 | define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX ); |
— | — | @@ -60,9 +63,9 @@ |
61 | 64 | */ |
62 | 65 | class IP { |
63 | 66 | /** |
64 | | - * Given a string, determine if it as valid IP |
65 | | - * Unlike isValid(), this looks for networks too |
66 | | - * @param $ip IP address. |
| 67 | + * Given a string, determine if it as valid IP. |
| 68 | + * Note: Unlike isValid(), this looks for networks too. |
| 69 | + * @param $ip string possible IP address |
67 | 70 | * @return string |
68 | 71 | */ |
69 | 72 | public static function isIPAddress( $ip ) { |
— | — | @@ -70,30 +73,40 @@ |
71 | 74 | return false; |
72 | 75 | } |
73 | 76 | if ( is_array( $ip ) ) { |
74 | | - throw new MWException( 'invalid value passed to ' . __METHOD__ ); |
| 77 | + throw new MWException( 'invalid value passed to ' . __METHOD__ ); |
75 | 78 | } |
76 | | - // IPv6 IPs with two "::" strings are ambiguous and thus invalid |
77 | | - return preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip ) && ( substr_count( $ip, '::' ) < 2 ); |
| 79 | + return preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip ) |
| 80 | + && ( substr_count( $ip, '::' ) <= 1 ); // IPv6 IPs with 2+ "::" are ambiguous |
78 | 81 | } |
79 | 82 | |
| 83 | + /** |
| 84 | + * Given a string, determine if it as valid IP in IPv6 only. |
| 85 | + * Note: Unlike isValid(), this looks for networks too. |
| 86 | + * @param $ip string possible IP address |
| 87 | + * @return string |
| 88 | + */ |
80 | 89 | public static function isIPv6( $ip ) { |
81 | 90 | if ( !$ip ) { |
82 | 91 | return false; |
83 | 92 | } |
84 | | - if( is_array( $ip ) ) { |
| 93 | + if ( is_array( $ip ) ) { |
85 | 94 | throw new MWException( 'invalid value passed to ' . __METHOD__ ); |
86 | 95 | } |
87 | | - $doubleColons = substr_count( $ip, '::' ); |
88 | | - // IPv6 IPs with two "::" strings are ambiguous and thus invalid |
89 | 96 | return preg_match( '/^' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)$/', $ip ) |
90 | | - && ( $doubleColons == 1 || substr_count( $ip, ':' ) == 7 ); |
| 97 | + && ( substr_count( $ip, '::' ) <= 1 ); // IPv6 IPs with 2+ "::" are ambiguous |
91 | 98 | } |
92 | 99 | |
| 100 | + /** |
| 101 | + * Given a string, determine if it as valid IP in IPv4 only. |
| 102 | + * Note: Unlike isValid(), this looks for networks too. |
| 103 | + * @param $ip string possible IP address |
| 104 | + * @return string |
| 105 | + */ |
93 | 106 | public static function isIPv4( $ip ) { |
94 | 107 | if ( !$ip ) { |
95 | 108 | return false; |
96 | 109 | } |
97 | | - return preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip); |
| 110 | + return preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip ); |
98 | 111 | } |
99 | 112 | |
100 | 113 | /** |
— | — | @@ -117,9 +130,10 @@ |
118 | 131 | if ( count( $parts ) != 2 ) { |
119 | 132 | return false; |
120 | 133 | } |
121 | | - $network = self::toUnsigned( $parts[0] ); |
122 | | - if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) { |
123 | | - $bits = $parts[1] + 96; |
| 134 | + list( $network, $bits ) = $parts; |
| 135 | + $network = self::toUnsigned( $network ); |
| 136 | + if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) { |
| 137 | + $bits += 96; |
124 | 138 | return self::toOctet( $network ) . "/$bits"; |
125 | 139 | } else { |
126 | 140 | return false; |
— | — | @@ -186,7 +200,10 @@ |
187 | 201 | $extra = ':'; |
188 | 202 | $pad = 8; // 6+2 (due to '::') |
189 | 203 | } |
190 | | - $ip = str_replace( '::', str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra, $ip ); |
| 204 | + $ip = str_replace( '::', |
| 205 | + str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra, |
| 206 | + $ip |
| 207 | + ); |
191 | 208 | } |
192 | 209 | // Remove leading zereos from each bloc as needed |
193 | 210 | $ip = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip ); |
— | — | @@ -213,21 +230,23 @@ |
214 | 231 | |
215 | 232 | /** |
216 | 233 | * Convert an IPv4 or IPv6 hexadecimal representation back to readable format |
| 234 | + * @param $hex string number, with "v6-" prefix if it is IPv6 |
| 235 | + * @return string quad-dotted (IPv4) or octet notation (IPv6) |
217 | 236 | */ |
218 | 237 | public static function formatHex( $hex ) { |
219 | | - if ( substr( $hex, 0, 3 ) == 'v6-' ) { |
220 | | - return self::hexToOctet( $hex ); |
221 | | - } else { |
| 238 | + if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6 |
| 239 | + return self::hexToOctet( substr( $hex, 3 ) ); |
| 240 | + } else { // IPv4 |
222 | 241 | return self::hexToQuad( $hex ); |
223 | 242 | } |
224 | 243 | } |
225 | 244 | |
226 | 245 | /** |
227 | | - * Given a hexadecimal number, returns to an IPv6 address in octet notation |
| 246 | + * Converts a hexadecimal number to an IPv6 address in octet notation |
228 | 247 | * @param $ip_hex string hex IP |
229 | | - * @return string |
| 248 | + * @return string (of format a:b:c:d:e:f:g:h) |
230 | 249 | */ |
231 | | - public static function hextoOctet( $ip_hex ) { |
| 250 | + public static function hexToOctet( $ip_hex ) { |
232 | 251 | // Convert to padded uppercase hex |
233 | 252 | $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0' ); |
234 | 253 | // Separate into 8 octets |
— | — | @@ -241,12 +260,11 @@ |
242 | 261 | } |
243 | 262 | |
244 | 263 | /** |
245 | | - * Converts a hexadecimal number to an IPv4 address in octet notation |
| 264 | + * Converts a hexadecimal number to an IPv4 address in quad-dotted notation |
246 | 265 | * @param $ip string Hex IP |
247 | | - * @return string |
| 266 | + * @return string (of format a.b.c.d) |
248 | 267 | */ |
249 | 268 | public static function hexToQuad( $ip ) { |
250 | | - // Converts a hexadecimal IP to nnn.nnn.nnn.nnn format |
251 | 269 | $s = ''; |
252 | 270 | for ( $i = 0; $i < 4; $i++ ) { |
253 | 271 | if ( $s !== '' ) { |
— | — | @@ -258,34 +276,35 @@ |
259 | 277 | } |
260 | 278 | |
261 | 279 | /** |
262 | | - * Convert a network specification in IPv6 CIDR notation to an integer network and a number of bits |
| 280 | + * Convert a network specification in IPv6 CIDR notation to an |
| 281 | + * integer network and a number of bits |
263 | 282 | * @return array(string, int) |
264 | 283 | */ |
265 | 284 | public static function parseCIDR6( $range ) { |
266 | | - # Expand any IPv6 IP |
| 285 | + # Explode into <expanded IP,range> |
267 | 286 | $parts = explode( '/', IP::sanitizeIP( $range ), 2 ); |
268 | 287 | if ( count( $parts ) != 2 ) { |
269 | 288 | return array( false, false ); |
270 | 289 | } |
271 | | - $network = self::toUnsigned6( $parts[0] ); |
272 | | - if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 128 ) { |
273 | | - $bits = $parts[1]; |
| 290 | + list( $network, $bits ) = $parts; |
| 291 | + $network = self::toUnsigned6( $network ); |
| 292 | + if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) { |
274 | 293 | if ( $bits == 0 ) { |
275 | | - $network = 0; |
| 294 | + $network = "0"; |
276 | 295 | } else { |
277 | | - # Native 32 bit functions WONT work here!!! |
278 | | - # Convert to a padded binary number |
| 296 | + # Native 32 bit functions WONT work here!!! |
| 297 | + # Convert to a padded binary number |
279 | 298 | $network = wfBaseConvert( $network, 10, 2, 128 ); |
280 | | - # Truncate the last (128-$bits) bits and replace them with zeros |
| 299 | + # Truncate the last (128-$bits) bits and replace them with zeros |
281 | 300 | $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT ); |
282 | | - # Convert back to an integer |
| 301 | + # Convert back to an integer |
283 | 302 | $network = wfBaseConvert( $network, 2, 10 ); |
284 | 303 | } |
285 | 304 | } else { |
286 | 305 | $network = false; |
287 | 306 | $bits = false; |
288 | 307 | } |
289 | | - return array( $network, $bits ); |
| 308 | + return array( $network, (int)$bits ); |
290 | 309 | } |
291 | 310 | |
292 | 311 | /** |
— | — | @@ -301,8 +320,8 @@ |
302 | 321 | public static function parseRange6( $range ) { |
303 | 322 | # Expand any IPv6 IP |
304 | 323 | $range = IP::sanitizeIP( $range ); |
| 324 | + // CIDR notation... |
305 | 325 | if ( strpos( $range, '/' ) !== false ) { |
306 | | - # CIDR |
307 | 326 | list( $network, $bits ) = self::parseCIDR6( $range ); |
308 | 327 | if ( $network === false ) { |
309 | 328 | $start = $end = false; |
— | — | @@ -318,8 +337,8 @@ |
319 | 338 | $start = "v6-$start"; |
320 | 339 | $end = "v6-$end"; |
321 | 340 | } |
| 341 | + // Explicit range notation... |
322 | 342 | } elseif ( strpos( $range, '-' ) !== false ) { |
323 | | - # Explicit range |
324 | 343 | list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) ); |
325 | 344 | $start = self::toUnsigned6( $start ); |
326 | 345 | $end = self::toUnsigned6( $end ); |
— | — | @@ -478,16 +497,16 @@ |
479 | 498 | |
480 | 499 | /** |
481 | 500 | * Convert a network specification in CIDR notation to an integer network and a number of bits |
482 | | - * @return array(string, int) |
| 501 | + * @return array(int, int) |
483 | 502 | */ |
484 | 503 | public static function parseCIDR( $range ) { |
485 | 504 | $parts = explode( '/', $range, 2 ); |
486 | 505 | if ( count( $parts ) != 2 ) { |
487 | 506 | return array( false, false ); |
488 | 507 | } |
489 | | - $network = self::toSigned( $parts[0] ); |
490 | | - if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) { |
491 | | - $bits = $parts[1]; |
| 508 | + list( $network, $bits ) = $parts; |
| 509 | + $network = self::toSigned( $network ); |
| 510 | + if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) { |
492 | 511 | if ( $bits == 0 ) { |
493 | 512 | $network = 0; |
494 | 513 | } else { |