Index: trunk/phase3/maintenance/tests/phpunit/includes/IPTest.php |
— | — | @@ -7,23 +7,71 @@ |
8 | 8 | // not sure it should be tested with boolean false. hashar 20100924 |
9 | 9 | public function testisIPAddress() { |
10 | 10 | $this->assertFalse( IP::isIPAddress( false ) ); |
| 11 | + $this->assertFalse( IP::isIPAddress( "" ) ); |
| 12 | + $this->assertFalse( IP::isIPAddress( 'abc' ) ); |
| 13 | + $this->assertFalse( IP::isIPAddress( ':' ) ); |
11 | 14 | $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1'), 'IPv6 with a double :: occurence' ); |
12 | 15 | $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::'), 'IPv6 with a double :: occurence, last at end' ); |
13 | 16 | $this->assertFalse( IP::isIPAddress( '::2001:0DB8::5:1'), 'IPv6 with a double :: occurence, firt at beginning' ); |
| 17 | + $this->assertFalse( IP::isIPAddress( '124.24.52' ), 'IPv4 not enough quads' ); |
| 18 | + $this->assertFalse( IP::isIPAddress( '24.324.52.13' ), 'IPv4 out of range' ); |
| 19 | + $this->assertFalse( IP::isIPAddress( '.24.52.13' ), 'IPv4 starts with period' ); |
| 20 | + |
| 21 | + $this->assertTrue( IP::isIPAddress( 'fc:100::' ) ); |
| 22 | + $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac::' ) ); |
| 23 | + $this->assertTrue( IP::isIPAddress( '::' ), 'IPv6 zero address' ); |
| 24 | + $this->assertTrue( IP::isIPAddress( '::fc' ) ); |
| 25 | + $this->assertTrue( IP::isIPAddress( '::fc:100:a:d:1:e:ac' ) ); |
| 26 | + $this->assertTrue( IP::isIPAddress( 'fc::100' ) ); |
| 27 | + $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac' ) ); |
| 28 | + $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96', 'IPv6 range with "::"' ) ); |
| 29 | + $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0' ) ); |
| 30 | + $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24', 'IPv6 range' ) ); |
| 31 | + $this->assertTrue( IP::isIPAddress( '124.24.52.13' ) ); |
| 32 | + $this->assertTrue( IP::isIPAddress( '1.24.52.13' ) ); |
| 33 | + $this->assertTrue( IP::isIPAddress( '74.24.52.13/20', 'IPv4 range' ) ); |
14 | 34 | } |
15 | 35 | |
16 | | - /** |
17 | | - * @expectedException MWException |
18 | | - */ |
19 | | - public function testArrayIsNotIPAddress() { |
20 | | - IP::isIPAddress( array('') ); |
| 36 | + public function testisIPv6() { |
| 37 | + $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' ); |
| 38 | + $this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' ); |
| 39 | + $this->assertTrue( IP::isIPv6( 'fc:100::' ) ); |
| 40 | + $this->assertTrue( IP::isIPv6( 'fc:100:a::' ) ); |
| 41 | + $this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) ); |
| 42 | + $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) ); |
| 43 | + $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) ); |
| 44 | + $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) ); |
| 45 | + $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 ending it "::" with 8 words' ); |
| 46 | + $this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ), 'IPv6 with 9 words' ); |
| 47 | + |
| 48 | + $this->assertFalse( IP::isIPv6( ':::' ) ); |
| 49 | + $this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' ); |
| 50 | + $this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' ); |
| 51 | + $this->assertTrue( IP::isIPv6( '::0' ) ); |
| 52 | + $this->assertTrue( IP::isIPv6( '::fc' ) ); |
| 53 | + $this->assertTrue( IP::isIPv6( '::fc:100' ) ); |
| 54 | + $this->assertTrue( IP::isIPv6( '::fc:100:a' ) ); |
| 55 | + $this->assertTrue( IP::isIPv6( '::fc:100:a:d' ) ); |
| 56 | + $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) ); |
| 57 | + $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) ); |
| 58 | + $this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) ); |
| 59 | + $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 octets' ); |
| 60 | + $this->assertFalse( IP::isIPv6( '::fc:100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 octets' ); |
| 61 | + |
| 62 | + $this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' ); |
| 63 | + $this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending in lone ":"' ); |
| 64 | + $this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' ); |
| 65 | + $this->assertTrue( IP::isIPv6( 'fc::100' ) ); |
| 66 | + $this->assertTrue( IP::isIPv6( 'fc::100:a' ) ); |
| 67 | + $this->assertTrue( IP::isIPv6( 'fc::100:a:d' ) ); |
| 68 | + $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ) ); |
| 69 | + $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ) ); |
| 70 | + $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ) ); |
| 71 | + $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 octets' ); |
| 72 | + $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 octets' ); |
| 73 | + |
| 74 | + $this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) ); |
21 | 75 | } |
22 | | - /** |
23 | | - * @expectedException MWException |
24 | | - */ |
25 | | - public function testArrayIsNotIPv6() { |
26 | | - IP::isIPv6( array('') ); |
27 | | - } |
28 | 76 | |
29 | 77 | public function testValidIPs() { |
30 | 78 | foreach ( range( 0, 255 ) as $i ) { |
— | — | @@ -35,6 +83,15 @@ |
36 | 84 | $this->assertTrue( IP::isValid( $ip ) , "$ip is a valid IPv4 address" ); |
37 | 85 | } |
38 | 86 | } |
| 87 | + foreach ( range( 0, 65535 ) as $i ) { |
| 88 | + $a = sprintf( "%04x", $i ); |
| 89 | + $b = sprintf( "%03x", $i ); |
| 90 | + $c = sprintf( "%02x", $i ); |
| 91 | + foreach ( array_unique( array( $a, $b, $c ) ) as $f ) { |
| 92 | + $ip = "$f:$f:$f:$f:$f:$f:$f:$f"; |
| 93 | + $this->assertTrue( IP::isValid( $ip ) , "$ip is a valid IPv6 address" ); |
| 94 | + } |
| 95 | + } |
39 | 96 | } |
40 | 97 | |
41 | 98 | public function testInvalidIPs() { |
— | — | @@ -47,6 +104,15 @@ |
48 | 105 | $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" ); |
49 | 106 | } |
50 | 107 | } |
| 108 | + foreach ( range( 'g', 'z' ) as $i ) { |
| 109 | + $a = sprintf( "%04", $i ); |
| 110 | + $b = sprintf( "%03", $i ); |
| 111 | + $c = sprintf( "%02", $i ); |
| 112 | + foreach ( array_unique( array( $a, $b, $c ) ) as $f ) { |
| 113 | + $ip = "$f:$f:$f:$f:$f:$f:$f:$f"; |
| 114 | + $this->assertFalse( IP::isValid( $ip ) , "$ip is not a valid IPv6 address" ); |
| 115 | + } |
| 116 | + } |
51 | 117 | } |
52 | 118 | |
53 | 119 | public function testBogusIPs() { |
— | — | @@ -92,19 +158,38 @@ |
93 | 159 | $this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" ); |
94 | 160 | } |
95 | 161 | |
96 | | - |
97 | 162 | public function testHexToQuad() { |
98 | | - $this->assertEquals( '0.0.0.0' , IP::hexToQuad( '0' ) ); |
99 | 163 | $this->assertEquals( '0.0.0.1' , IP::hexToQuad( '00000001' ) ); |
100 | 164 | $this->assertEquals( '255.0.0.0' , IP::hexToQuad( 'FF000000' ) ); |
101 | 165 | $this->assertEquals( '255.255.255.255', IP::hexToQuad( 'FFFFFFFF' ) ); |
102 | 166 | $this->assertEquals( '10.188.222.255' , IP::hexToQuad( '0ABCDEFF' ) ); |
103 | | - |
104 | | - $this->assertNotEquals( '0.0.0.1' , IP::hexToQuad( '1' ) ); |
105 | | - $this->assertNotEquals( '0.0.0.255' , IP::hexToQuad( 'FF' ) ); |
106 | | - $this->assertNotEquals( '0.0.255.0' , IP::hexToQuad( 'FF00' ) ); |
| 167 | + // hex not left-padded... |
| 168 | + $this->assertEquals( '0.0.0.0' , IP::hexToQuad( '0' ) ); |
| 169 | + $this->assertEquals( '0.0.0.1' , IP::hexToQuad( '1' ) ); |
| 170 | + $this->assertEquals( '0.0.0.255' , IP::hexToQuad( 'FF' ) ); |
| 171 | + $this->assertEquals( '0.0.255.0' , IP::hexToQuad( 'FF00' ) ); |
107 | 172 | } |
108 | 173 | |
| 174 | + public function testHexToOctet() { |
| 175 | + $this->assertEquals( '0:0:0:0:0:0:0:1', |
| 176 | + IP::hexToOctet( '00000000000000000000000000000001' ) ); |
| 177 | + $this->assertEquals( '0:0:0:0:0:0:FF:3', |
| 178 | + IP::hexToOctet( '00000000000000000000000000FF0003' ) ); |
| 179 | + $this->assertEquals( '0:0:0:0:0:0:FF00:6', |
| 180 | + IP::hexToOctet( '000000000000000000000000FF000006' ) ); |
| 181 | + $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', |
| 182 | + IP::hexToOctet( '000000000000000000000000FCCFFAFF' ) ); |
| 183 | + $this->assertEquals( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', |
| 184 | + IP::hexToOctet( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ) ); |
| 185 | + // hex not left-padded... |
| 186 | + $this->assertEquals( '0:0:0:0:0:0:0:0' , IP::hexToOctet( '0' ) ); |
| 187 | + $this->assertEquals( '0:0:0:0:0:0:0:1' , IP::hexToOctet( '1' ) ); |
| 188 | + $this->assertEquals( '0:0:0:0:0:0:0:FF' , IP::hexToOctet( 'FF' ) ); |
| 189 | + $this->assertEquals( '0:0:0:0:0:0:0:FFD0' , IP::hexToOctet( 'FFD0' ) ); |
| 190 | + $this->assertEquals( '0:0:0:0:0:0:FA00:0' , IP::hexToOctet( 'FA000000' ) ); |
| 191 | + $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) ); |
| 192 | + } |
| 193 | + |
109 | 194 | /* |
110 | 195 | * IP::parseCIDR() returns an array containing a signed IP address |
111 | 196 | * representing the network mask and the bit mask. |
Index: trunk/phase3/includes/IP.php |
— | — | @@ -18,7 +18,7 @@ |
19 | 19 | * http://www.gnu.org/copyleft/gpl.html |
20 | 20 | * |
21 | 21 | * @file |
22 | | - * @author Ashar Voultoiz <hashar at free dot fr> |
| 22 | + * @author Ashar Voultoiz <hashar at free dot fr>, Aaron Schulz |
23 | 23 | */ |
24 | 24 | |
25 | 25 | // Some regex definition to "play" with IP address and IP address blocks |
— | — | @@ -69,9 +69,6 @@ |
70 | 70 | * @return bool |
71 | 71 | */ |
72 | 72 | public static function isIPAddress( $ip ) { |
73 | | - if ( !$ip ) { |
74 | | - return false; |
75 | | - } |
76 | 73 | return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip ); |
77 | 74 | } |
78 | 75 | |
— | — | @@ -82,9 +79,6 @@ |
83 | 80 | * @return bool |
84 | 81 | */ |
85 | 82 | public static function isIPv6( $ip ) { |
86 | | - if ( !$ip ) { |
87 | | - return false; |
88 | | - } |
89 | 83 | return (bool)preg_match( '/^' . RE_IPV6_ADD . '(\/' . RE_IPV6_PREFIX . '|)$/', $ip ); |
90 | 84 | } |
91 | 85 | |
— | — | @@ -95,9 +89,6 @@ |
96 | 90 | * @return bool |
97 | 91 | */ |
98 | 92 | public static function isIPv4( $ip ) { |
99 | | - if ( !$ip ) { |
100 | | - return false; |
101 | | - } |
102 | 93 | return (bool)preg_match( '/^' . RE_IP_ADD . '(\/' . RE_IP_PREFIX . '|)$/', $ip ); |
103 | 94 | } |
104 | 95 | |
— | — | @@ -116,7 +107,7 @@ |
117 | 108 | if ( self::isIPv6( $ip ) ) { |
118 | 109 | return $ip; |
119 | 110 | } |
120 | | - // IPv4 CIDRs |
| 111 | + // IPv4 address with CIDR |
121 | 112 | if ( strpos( $ip, '/' ) !== false ) { |
122 | 113 | $parts = explode( '/', $ip, 2 ); |
123 | 114 | if ( count( $parts ) != 2 ) { |
— | — | @@ -134,27 +125,17 @@ |
135 | 126 | return self::toOctet( self::toUnsigned( $ip ) ); |
136 | 127 | } |
137 | 128 | |
138 | | - /** |
139 | | - * Given an IPv6 address in octet notation, returns an unsigned integer. |
140 | | - * @param string $ip octet ipv6 IP address. |
141 | | - * @return string |
142 | | - */ |
143 | | - public static function toUnsigned6( $ip ) { |
144 | | - if ( !$ip ) { |
145 | | - return null; |
| 129 | + private static function toUnsigned6( $ip ) { |
| 130 | + if ( self::isIPv6( $ip ) ) { |
| 131 | + return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 ); |
146 | 132 | } |
147 | | - $ip = explode( ':', self::sanitizeIP( $ip ) ); |
148 | | - $r_ip = ''; |
149 | | - foreach ( $ip as $v ) { |
150 | | - $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT ); |
151 | | - } |
152 | | - $r_ip = wfBaseConvert( $r_ip, 16, 10 ); |
153 | | - return $r_ip; |
| 133 | + return false; |
154 | 134 | } |
155 | 135 | |
156 | 136 | /** |
157 | | - * Given an IPv6 address in octet notation, returns the expanded octet. |
158 | | - * IPv4 IPs will be trimmed, thats it... |
| 137 | + * Convert an IP into a nice standard form. |
| 138 | + * IPv6 addresses in octet notation are expanded to 8 octets. |
| 139 | + * IPv4 addresses are just trimmed. |
159 | 140 | * @param string $ip IP address in quad or octet form (CIDR or not). |
160 | 141 | * @return string |
161 | 142 | */ |
— | — | @@ -205,20 +186,12 @@ |
206 | 187 | |
207 | 188 | /** |
208 | 189 | * Given an unsigned integer, returns an IPv6 address in octet notation |
209 | | - * @param int $ip_int IP address. |
| 190 | + * @param string $ip_int IP address. |
210 | 191 | * @return string |
211 | 192 | */ |
212 | 193 | public static function toOctet( $ip_int ) { |
213 | | - // Convert to padded uppercase hex |
214 | | - $ip_hex = wfBaseConvert( $ip_int, 10, 16, 32, false ); |
215 | | - // Separate into 8 octets |
216 | | - $ip_oct = substr( $ip_hex, 0, 4 ); |
217 | | - for ( $n = 1; $n < 8; $n++ ) { |
218 | | - $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 ); |
219 | | - } |
220 | | - // NO leading zeroes |
221 | | - $ip_oct = preg_replace( '/(^|:)0+' . RE_IPV6_WORD . '/', '$1$2', $ip_oct ); |
222 | | - return $ip_oct; |
| 194 | + $ip_hex = wfBaseConvert( $ip_int, 10, 16, 32, false ); // uppercase hex |
| 195 | + return self::hexToOctet( $ip_hex ); |
223 | 196 | } |
224 | 197 | |
225 | 198 | /** |
— | — | @@ -236,12 +209,12 @@ |
237 | 210 | |
238 | 211 | /** |
239 | 212 | * Converts a hexadecimal number to an IPv6 address in octet notation |
240 | | - * @param string $ip_hex hex IP |
| 213 | + * @param string $ip_hex pure hex (no v6- prefix) |
241 | 214 | * @return string (of format a:b:c:d:e:f:g:h) |
242 | 215 | */ |
243 | 216 | public static function hexToOctet( $ip_hex ) { |
244 | 217 | // Convert to padded uppercase hex |
245 | | - $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0' ); |
| 218 | + $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT ); |
246 | 219 | // Separate into 8 octets |
247 | 220 | $ip_oct = substr( $ip_hex, 0, 4 ); |
248 | 221 | for ( $n = 1; $n < 8; $n++ ) { |
— | — | @@ -254,16 +227,19 @@ |
255 | 228 | |
256 | 229 | /** |
257 | 230 | * Converts a hexadecimal number to an IPv4 address in quad-dotted notation |
258 | | - * @param string $ip Hex IP |
| 231 | + * @param string $ip_hex pure hex |
259 | 232 | * @return string (of format a.b.c.d) |
260 | 233 | */ |
261 | | - public static function hexToQuad( $ip ) { |
| 234 | + public static function hexToQuad( $ip_hex ) { |
| 235 | + // Convert to padded uppercase hex |
| 236 | + $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT ); |
| 237 | + // Separate into four quads |
262 | 238 | $s = ''; |
263 | 239 | for ( $i = 0; $i < 4; $i++ ) { |
264 | 240 | if ( $s !== '' ) { |
265 | 241 | $s .= '.'; |
266 | 242 | } |
267 | | - $s .= base_convert( substr( $ip, $i * 2, 2 ), 16, 10 ); |
| 243 | + $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 ); |
268 | 244 | } |
269 | 245 | return $s; |
270 | 246 | } |
— | — | @@ -273,21 +249,21 @@ |
274 | 250 | * integer network and a number of bits |
275 | 251 | * @return array(string, int) |
276 | 252 | */ |
277 | | - public static function parseCIDR6( $range ) { |
| 253 | + private static function parseCIDR6( $range ) { |
278 | 254 | # Explode into <expanded IP,range> |
279 | 255 | $parts = explode( '/', IP::sanitizeIP( $range ), 2 ); |
280 | 256 | if ( count( $parts ) != 2 ) { |
281 | 257 | return array( false, false ); |
282 | 258 | } |
283 | 259 | list( $network, $bits ) = $parts; |
284 | | - $network = self::toUnsigned6( $network ); |
| 260 | + $network = self::IPv6ToRawHex( $network ); |
285 | 261 | if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) { |
286 | 262 | if ( $bits == 0 ) { |
287 | 263 | $network = "0"; |
288 | 264 | } else { |
289 | 265 | # Native 32 bit functions WONT work here!!! |
290 | 266 | # Convert to a padded binary number |
291 | | - $network = wfBaseConvert( $network, 10, 2, 128 ); |
| 267 | + $network = wfBaseConvert( $network, 16, 2, 128 ); |
292 | 268 | # Truncate the last (128-$bits) bits and replace them with zeros |
293 | 269 | $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT ); |
294 | 270 | # Convert back to an integer |
— | — | @@ -301,8 +277,8 @@ |
302 | 278 | } |
303 | 279 | |
304 | 280 | /** |
305 | | - * Given a string range in a number of formats, return the start and end of |
306 | | - * the range in hexadecimal. For IPv6. |
| 281 | + * Given a string range in a number of formats, return the |
| 282 | + * start and end of the range in hexadecimal. For IPv6. |
307 | 283 | * |
308 | 284 | * Formats are: |
309 | 285 | * 2001:0db8:85a3::7344/96 CIDR |
— | — | @@ -310,7 +286,7 @@ |
311 | 287 | * 2001:0db8:85a3::7344/96 Single IP |
312 | 288 | * @return array(string, int) |
313 | 289 | */ |
314 | | - public static function parseRange6( $range ) { |
| 290 | + private static function parseRange6( $range ) { |
315 | 291 | # Expand any IPv6 IP |
316 | 292 | $range = IP::sanitizeIP( $range ); |
317 | 293 | // CIDR notation... |
— | — | @@ -356,21 +332,23 @@ |
357 | 333 | } |
358 | 334 | |
359 | 335 | /** |
360 | | - * Validate an IP address. |
| 336 | + * Validate an IP address. Ranges are NOT considered valid. |
361 | 337 | * @param string $ip |
362 | 338 | * @return boolean True if it is valid. |
363 | 339 | */ |
364 | 340 | public static function isValid( $ip ) { |
365 | | - return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip ) || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) ); |
| 341 | + return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip ) |
| 342 | + || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) ); |
366 | 343 | } |
367 | 344 | |
368 | 345 | /** |
369 | | - * Validate an IP Block. |
| 346 | + * Validate an IP Block (valid address WITH a valid prefix). |
370 | 347 | * @param string $ipblock |
371 | 348 | * @return boolean True if it is valid. |
372 | 349 | */ |
373 | 350 | public static function isValidBlock( $ipblock ) { |
374 | | - return ( count( self::toArray( $ipblock ) ) == 1 + 5 ); |
| 351 | + return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock ) |
| 352 | + || preg_match( '/^' . RE_IPV4_BLOCK . '$/', $ipblock ) ); |
375 | 353 | } |
376 | 354 | |
377 | 355 | /** |
— | — | @@ -381,6 +359,9 @@ |
382 | 360 | * @return bool |
383 | 361 | */ |
384 | 362 | public static function isPublic( $ip ) { |
| 363 | + if ( self::isIPv6( $ip ) ) { |
| 364 | + return self::isPublic6( $ip ); |
| 365 | + } |
385 | 366 | $n = self::toUnsigned( $ip ); |
386 | 367 | if ( !$n ) { |
387 | 368 | return false; |
— | — | @@ -414,25 +395,32 @@ |
415 | 396 | } |
416 | 397 | |
417 | 398 | /** |
418 | | - * Split out an IP block as an array of 4 bytes and a mask, |
419 | | - * return false if it can't be determined |
420 | | - * |
421 | | - * @param string $ipblock A quad dotted/octet IP address |
422 | | - * @return array |
| 399 | + * Determine if an IPv6 address really is an IP address, and if it is public, |
| 400 | + * i.e. not RFC 4193 or similar |
| 401 | + * @param string $ip |
| 402 | + * @return bool |
423 | 403 | */ |
424 | | - public static function toArray( $ipblock ) { |
425 | | - $matches = array(); |
426 | | - if( preg_match( '/^' . RE_IP_ADD . '(?:\/(?:' . RE_IP_PREFIX . '))?' . '$/', $ipblock, $matches ) ) { |
427 | | - return $matches; |
428 | | - } elseif ( preg_match( '/^' . RE_IPV6_ADD . '(?:\/(?:' . RE_IPV6_PREFIX . '))?' . '$/', $ipblock, $matches ) ) { |
429 | | - return $matches; |
430 | | - } else { |
431 | | - return false; |
| 404 | + private static function isPublic6( $ip ) { |
| 405 | + static $privateRanges = false; |
| 406 | + if ( !$privateRanges ) { |
| 407 | + $privateRanges = array( |
| 408 | + array( 'fc::', 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ), # RFC 4193 (local) |
| 409 | + array( '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1' ), # loopback |
| 410 | + ); |
432 | 411 | } |
| 412 | + $n = self::toHex( $ip ); |
| 413 | + foreach ( $privateRanges as $r ) { |
| 414 | + $start = self::toHex( $r[0] ); |
| 415 | + $end = self::toHex( $r[1] ); |
| 416 | + if ( $n >= $start && $n <= $end ) { |
| 417 | + return false; |
| 418 | + } |
| 419 | + } |
| 420 | + return true; |
433 | 421 | } |
434 | 422 | |
435 | 423 | /** |
436 | | - * Return a zero-padded hexadecimal representation of an IP address. |
| 424 | + * Return a zero-padded upper case hexadecimal representation of an IP address. |
437 | 425 | * |
438 | 426 | * Hexadecimal addresses are used because they can easily be extended to |
439 | 427 | * IPv6 support. To separate the ranges, the return value from this |
— | — | @@ -443,11 +431,13 @@ |
444 | 432 | * @return string |
445 | 433 | */ |
446 | 434 | public static function toHex( $ip ) { |
447 | | - $n = self::toUnsigned( $ip ); |
448 | | - if ( $n !== false ) { |
449 | | - $n = self::isIPv6( $ip ) |
450 | | - ? 'v6-' . wfBaseConvert( $n, 10, 16, 32, false ) |
451 | | - : wfBaseConvert( $n, 10, 16, 8, false ); |
| 435 | + if ( self::isIPv6( $ip ) ) { |
| 436 | + $n = 'v6-' . self::IPv6ToRawHex( $ip ); |
| 437 | + } else { |
| 438 | + $n = self::toUnsigned( $ip ); |
| 439 | + if ( $n !== false ) { |
| 440 | + $n = wfBaseConvert( $n, 10, 16, 8, false ); |
| 441 | + } |
452 | 442 | } |
453 | 443 | return $n; |
454 | 444 | } |
— | — | @@ -457,28 +447,45 @@ |
458 | 448 | * Like ip2long() except that it actually works and has a consistent error return value. |
459 | 449 | * Comes from ProxyTools.php |
460 | 450 | * @param string $ip Quad dotted IP address. |
461 | | - * @return integer |
| 451 | + * @return mixed (string/int/false) |
462 | 452 | */ |
463 | 453 | public static function toUnsigned( $ip ) { |
464 | | - // Use IPv6 functions if needed |
465 | 454 | if ( self::isIPv6( $ip ) ) { |
466 | | - return self::toUnsigned6( $ip ); |
467 | | - } |
468 | | - if ( $ip == '255.255.255.255' ) { |
469 | | - $n = -1; |
| 455 | + $n = wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 ); |
470 | 456 | } else { |
471 | | - $n = ip2long( $ip ); |
472 | | - if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version |
473 | | - $n = false; |
| 457 | + if ( $ip == '255.255.255.255' ) { |
| 458 | + $n = -1; |
| 459 | + } else { |
| 460 | + $n = ip2long( $ip ); |
| 461 | + if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version |
| 462 | + $n = false; |
| 463 | + } |
474 | 464 | } |
| 465 | + if ( $n < 0 ) { |
| 466 | + $n += pow( 2, 32 ); |
| 467 | + } |
475 | 468 | } |
476 | | - if ( $n < 0 ) { |
477 | | - $n += pow( 2, 32 ); |
478 | | - } |
479 | 469 | return $n; |
480 | 470 | } |
481 | 471 | |
482 | 472 | /** |
| 473 | + * Given an IPv6 address in octet notation, returns a pure hex string. |
| 474 | + * @param string $ip octet ipv6 IP address. |
| 475 | + * @return string hex (uppercase) |
| 476 | + */ |
| 477 | + private static function IPv6ToRawHex( $ip ) { |
| 478 | + $ip = self::sanitizeIP( $ip ); |
| 479 | + if ( !$ip ) { |
| 480 | + return null; |
| 481 | + } |
| 482 | + $r_ip = ''; |
| 483 | + foreach ( explode( ':', $ip ) as $v ) { |
| 484 | + $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT ); |
| 485 | + } |
| 486 | + return $r_ip; |
| 487 | + } |
| 488 | + |
| 489 | + /** |
483 | 490 | * Convert a dotted-quad IP to a signed integer |
484 | 491 | * @param string $ip |
485 | 492 | * @return mixed (string/false) |
— | — | @@ -586,7 +593,6 @@ |
587 | 594 | * @return bool Whether or not the given address is in the given range. |
588 | 595 | */ |
589 | 596 | public static function isInRange( $addr, $range ) { |
590 | | - // Convert to IPv6 if needed |
591 | 597 | $hexIP = self::toHex( $addr ); |
592 | 598 | list( $start, $end ) = self::parseRange( $range ); |
593 | 599 | return ( strcmp( $hexIP, $start ) >= 0 && |
Index: trunk/extensions/CheckUser/CheckUser_body.php |
— | — | @@ -1151,7 +1151,7 @@ |
1152 | 1152 | if ( $matches[1] < 96 || $matches[1] > 128 ) { |
1153 | 1153 | return false; // invalid |
1154 | 1154 | } |
1155 | | - list( $start, $end ) = IP::parseRange6( $ip ); |
| 1155 | + list( $start, $end ) = IP::parseRange( $ip ); |
1156 | 1156 | return array( 'cuc_' . $type . '_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) ); |
1157 | 1157 | } elseif ( preg_match( '#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#', $ip ) ) { |
1158 | 1158 | // 32 bit IPv4 |