* Added temporary special-case AOL proxy detection, they're automatically counted...
[lhc/web/wiklou.git] / includes / IP.php
1 <?php
2 /*
3 * Collection of public static functions to play with IP address
4 * and IP blocks.
5 *
6 * @Author "Ashar Voultoiz" <hashar@altern.org>
7 * @License GPL v2 or later
8 */
9
10 // Some regex definition to "play" with IP address and IP address blocks
11
12 // An IP is made of 4 bytes from x00 to xFF which is d0 to d255
13 define( 'RE_IP_BYTE', '(25[0-5]|2[0-4]\d|1?\d{1,2})');
14 define( 'RE_IP_ADD' , RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
15 // An IP block is an IP address and a prefix (d1 to d32)
16 define( 'RE_IP_PREFIX' , '(3[0-2]|[12]?\d)');
17 define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX);
18
19 class IP {
20
21 /**
22 * Validate an IP address.
23 * @return boolean True if it is valid.
24 */
25 public static function isValid( $ip ) {
26 return preg_match( '/^' . RE_IP_ADD . '$/', $ip, $matches) ;
27 }
28
29 /**
30 * Validate an IP Block.
31 * @return boolean True if it is valid.
32 */
33 public static function isValidBlock( $ipblock ) {
34 return ( count(self::toArray($ipblock)) == 1 + 5 );
35 }
36
37 /**
38 * Determine if an IP address really is an IP address, and if it is public,
39 * i.e. not RFC 1918 or similar
40 * Comes from ProxyTools.php
41 */
42 public static function isPublic( $ip ) {
43 $n = IP::toUnsigned( $ip );
44 if ( !$n ) {
45 return false;
46 }
47
48 // ip2long accepts incomplete addresses, as well as some addresses
49 // followed by garbage characters. Check that it's really valid.
50 if( $ip != long2ip( $n ) ) {
51 return false;
52 }
53
54 static $privateRanges = false;
55 if ( !$privateRanges ) {
56 $privateRanges = array(
57 array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
58 array( '172.16.0.0', '172.31.255.255' ), # "
59 array( '192.168.0.0', '192.168.255.255' ), # "
60 array( '0.0.0.0', '0.255.255.255' ), # this network
61 array( '127.0.0.0', '127.255.255.255' ), # loopback
62 );
63 }
64
65 foreach ( $privateRanges as $r ) {
66 $start = IP::toUnsigned( $r[0] );
67 $end = IP::toUnsigned( $r[1] );
68 if ( $n >= $start && $n <= $end ) {
69 return false;
70 }
71 }
72 return true;
73 }
74
75 /**
76 * Split out an IP block as an array of 4 bytes and a mask,
77 * return false if it cant be determined
78 *
79 * @parameter $ip string A quad dotted IP address
80 * @return array
81 */
82 public static function toArray( $ipblock ) {
83 if(! preg_match( '/^' . RE_IP_ADD . '(?:\/(?:'.RE_IP_PREFIX.'))?' . '$/', $ipblock, $matches ) ) {
84 return false;
85 } else {
86 return $matches;
87 }
88 }
89
90 /**
91 * Return a zero-padded hexadecimal representation of an IP address.
92 *
93 * Hexadecimal addresses are used because they can easily be extended to
94 * IPv6 support. To separate the ranges, the return value from this
95 * function for an IPv6 address will be prefixed with "v6-", a non-
96 * hexadecimal string which sorts after the IPv4 addresses.
97 *
98 * @param $ip Quad dotted IP address.
99 */
100 public static function toHex( $ip ) {
101 $n = self::toUnsigned( $ip );
102 if ( $n !== false ) {
103 $n = sprintf( '%08X', $n );
104 }
105 return $n;
106 }
107
108 /**
109 * Given an IP address in dotted-quad notation, returns an unsigned integer.
110 * Like ip2long() except that it actually works and has a consistent error return value.
111 * Comes from ProxyTools.php
112 * @param $ip Quad dotted IP address.
113 */
114 public static function toUnsigned( $ip ) {
115 if ( $ip == '255.255.255.255' ) {
116 $n = -1;
117 } else {
118 $n = ip2long( $ip );
119 if ( $n == -1 || $n === false ) { # Return value on error depends on PHP version
120 $n = false;
121 }
122 }
123 if ( $n < 0 ) {
124 $n += pow( 2, 32 );
125 }
126 return $n;
127 }
128
129 /**
130 * Convert a dotted-quad IP to a signed integer
131 * Returns false on failure
132 */
133 public static function toSigned( $ip ) {
134 if ( $ip == '255.255.255.255' ) {
135 $n = -1;
136 } else {
137 $n = ip2long( $ip );
138 if ( $n == -1 ) {
139 $n = false;
140 }
141 }
142 return $n;
143 }
144
145 /**
146 * Convert a network specification in CIDR notation to an integer network and a number of bits
147 */
148 public static function parseCIDR( $range ) {
149 $parts = explode( '/', $range, 2 );
150 if ( count( $parts ) != 2 ) {
151 return array( false, false );
152 }
153 $network = IP::toSigned( $parts[0] );
154 if ( $network !== false && is_numeric( $parts[1] ) && $parts[1] >= 0 && $parts[1] <= 32 ) {
155 $bits = $parts[1];
156 if ( $bits == 0 ) {
157 $network = 0;
158 } else {
159 $network &= ~((1 << (32 - $bits)) - 1);
160 }
161 # Convert to unsigned
162 if ( $network < 0 ) {
163 $network += pow( 2, 32 );
164 }
165 } else {
166 $network = false;
167 $bits = false;
168 }
169 return array( $network, $bits );
170 }
171
172 /**
173 * Given a string range in a number of formats, return the start and end of
174 * the range in hexadecimal.
175 *
176 * Formats are:
177 * 1.2.3.4/24 CIDR
178 * 1.2.3.4 - 1.2.3.5 Explicit range
179 * 1.2.3.4 Single IP
180 */
181 public static function parseRange( $range ) {
182 if ( strpos( $range, '/' ) !== false ) {
183 # CIDR
184 list( $network, $bits ) = IP::parseCIDR( $range );
185 if ( $network === false ) {
186 $start = $end = false;
187 } else {
188 $start = sprintf( '%08X', $network );
189 $end = sprintf( '%08X', $network + pow( 2, (32 - $bits) ) - 1 );
190 }
191 } elseif ( strpos( $range, '-' ) !== false ) {
192 # Explicit range
193 list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
194 if ( $start > $end ) {
195 $start = $end = false;
196 } else {
197 $start = IP::toHex( $start );
198 $end = IP::toHex( $end );
199 }
200 } else {
201 # Single IP
202 $start = $end = IP::toHex( $range );
203 }
204 if ( $start === false || $end === false ) {
205 return array( false, false );
206 } else {
207 return array( $start, $end );
208 }
209 }
210
211 /**
212 * Determine if a given integer IPv4 address is in a given CIDR network
213 * @param $addr The address to check against the given range.
214 * @param $range The range to check the given address against.
215 * @return bool Whether or not the given address is in the given range.
216 */
217 function isInRange( $addr, $range ) {
218 $unsignedIP = IP::toUnsigned($addr);
219 list( $start, $end ) = IP::parseRange($range);
220
221 return (($unsignedIP >= $start) && ($unsignedIP <= $end));
222 }
223 }
224 ?>