1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* get Client IP. DEPRECATED! use getClientIP(). |
5
|
|
|
* @param array $server |
6
|
|
|
* @return string |
7
|
|
|
* @deprecated |
8
|
|
|
* @see getClientIp(); |
9
|
|
|
*/ |
10
|
|
|
function getIPVisitor(array $server = []) : string |
11
|
|
|
{ |
12
|
|
|
$IP2Check = ''; |
13
|
|
|
if (array_key_exists('HTTP_X_FORWARDED_FOR', $server) && trim($server['HTTP_X_FORWARDED_FOR']) != '') { |
14
|
|
|
$IP2Check = $server['HTTP_X_FORWARDED_FOR']; |
15
|
|
|
} elseif (array_key_exists('REMOTE_ADDR', $server) && trim($server['REMOTE_ADDR'])) { |
16
|
|
|
$IP2Check = $server['REMOTE_ADDR']; |
17
|
|
|
} |
18
|
|
|
|
19
|
|
|
if (strpos($IP2Check, ',') === false) { |
20
|
|
|
return $IP2Check; |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
// Header can contain multiple IP-s of proxies that are passed through. |
24
|
|
|
// Only the IP added by the last proxy (last IP in the list) can be trusted. |
25
|
|
|
$arrIps = explode(',', $IP2Check); |
26
|
|
|
if (!is_array($arrIps) || count($arrIps) < 1) { |
27
|
|
|
return ''; |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
return trim(head($arrIps)); |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* anonimizeIp masquerade last 3 digit of IP address |
35
|
|
|
* @param string $ip |
36
|
|
|
* @return string masked IP |
37
|
|
|
*/ |
38
|
|
|
function anonimizeIp(string $ip) : string |
39
|
|
|
{ |
40
|
|
|
if ($ip === null || strlen($ip) < 2 || strrpos($ip, ".") === false) { |
41
|
|
|
return $ip; |
42
|
|
|
} |
43
|
|
|
return substr($ip, 0, strrpos($ip, ".") + 1) . '0'; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* getHost Get the Internet host name corresponding to a given IP address |
48
|
|
|
* @param string $ip the IP to resolve. |
49
|
|
|
* @return string Returns the host name of the Internet host specified by ip_address on success, |
50
|
|
|
* the unmodified ip_address on failure, |
51
|
|
|
* or FALSE on malformed input. |
52
|
|
|
*/ |
53
|
|
|
function getHost($ip) |
54
|
|
|
{ |
55
|
|
|
return gethostbyaddr($ip); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Returns the client IP addresses. |
60
|
|
|
* |
61
|
|
|
* In the returned array the most trusted IP address is first, and the |
62
|
|
|
* least trusted one last. The "real" client IP address is the last one, |
63
|
|
|
* but this is also the least trusted one. |
64
|
|
|
* Trusted proxies are stripped if passed. |
65
|
|
|
* |
66
|
|
|
* Use this method carefully; you should use getClientIp() instead. |
67
|
|
|
* |
68
|
|
|
* @param array $server |
69
|
|
|
* @param array $trustedProxies |
70
|
|
|
* |
71
|
|
|
* @return array The client IP addresses |
72
|
|
|
* |
73
|
|
|
* @see getClientIp() |
74
|
|
|
* @see https://github.com/symfony/symfony/blob/0b39ce23150c34c990e3eccfae4e375161c0d352/src/Symfony/Component/HttpFoundation/Request.php#L831 |
75
|
|
|
*/ |
76
|
|
|
function getClientIps(array $server, array $trustedProxies = []) : array |
77
|
|
|
{ |
78
|
|
|
if (empty($server)) { |
79
|
|
|
return ['']; |
80
|
|
|
} |
81
|
|
|
$clientIps = array(); |
82
|
|
|
$ip = $server['REMOTE_ADDR']; |
83
|
|
|
if (!isFromTrustedProxy($trustedProxies, $ip)) { |
84
|
|
|
return array($ip); |
85
|
|
|
} |
86
|
|
|
if (array_key_exists('FORWARDED', $server) && trim($server['FORWARDED']) != '') { |
87
|
|
|
$forwardedHeader = $server['FORWARDED']; |
88
|
|
|
preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches); |
89
|
|
|
$clientIps = $matches[3]; |
90
|
|
|
} elseif (array_key_exists('X_FORWARDED_FOR', $server) && trim($server['X_FORWARDED_FOR']) != '') { |
91
|
|
|
$clientIps = array_map('trim', explode(',', $server['X_FORWARDED_FOR'])); |
92
|
|
|
} |
93
|
|
|
$clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from |
94
|
|
|
$ip = $clientIps[0]; // Fallback to this when the client IP falls into the range of trusted proxies |
95
|
|
|
foreach ($clientIps as $key => $clientIp) { |
96
|
|
|
// Remove port (unfortunately, it does happen) |
97
|
|
|
if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) { |
98
|
|
|
$clientIps[$key] = $clientIp = $match[1]; |
99
|
|
|
} |
100
|
|
|
if (checkIp($clientIp, $trustedProxies)) { |
101
|
|
|
unset($clientIps[$key]); |
102
|
|
|
} |
103
|
|
|
} |
104
|
|
|
// Now the IP chain contains only untrusted proxies and the client IP |
105
|
|
|
return $clientIps ? array_reverse($clientIps) : array($ip); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Returns the client IP address. |
110
|
|
|
* |
111
|
|
|
* This method can read the client IP address from the "X-Forwarded-For" header |
112
|
|
|
* when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" |
113
|
|
|
* header value is a comma+space separated list of IP addresses, the left-most |
114
|
|
|
* being the original client, and each successive proxy that passed the request |
115
|
|
|
* adding the IP address where it received the request from. |
116
|
|
|
* |
117
|
|
|
* If your reverse proxy uses a different header name than "X-Forwarded-For", |
118
|
|
|
* ("Client-Ip" for instance), configure it via "setTrustedHeaderName()" with |
119
|
|
|
* the "client-ip" key. |
120
|
|
|
* |
121
|
|
|
* @param array $server |
122
|
|
|
* @param array $trustedProxies |
123
|
|
|
* |
124
|
|
|
* @return string The client IP address |
125
|
|
|
* |
126
|
|
|
* @see getClientIps() |
127
|
|
|
* @see https://github.com/symfony/symfony/blob/0b39ce23150c34c990e3eccfae4e375161c0d352/src/Symfony/Component/HttpFoundation/Request.php#L886 |
128
|
|
|
* @see http://en.wikipedia.org/wiki/X-Forwarded-For |
129
|
|
|
* |
130
|
|
|
*/ |
131
|
|
|
function getClientIp(array $server, array $trustedProxies = []) : string |
132
|
|
|
{ |
133
|
|
|
$ipAddresses = getClientIps($server, $trustedProxies); |
134
|
|
|
return $ipAddresses[0]; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. |
139
|
|
|
* |
140
|
|
|
* @param string $requestIp IP to check |
141
|
|
|
* @param string|array $ips List of IPs or subnets (can be a string if only a single one) |
142
|
|
|
* |
143
|
|
|
* @return bool Whether the IP is valid |
144
|
|
|
* @see https://github.com/symfony/http-foundation/blob/master/IpUtils.php |
145
|
|
|
*/ |
146
|
|
|
function checkIp($requestIp, $ips) : bool |
147
|
|
|
{ |
148
|
|
|
if (empty($ips)) { |
149
|
|
|
return false; |
150
|
|
|
} |
151
|
|
|
if (!is_array($ips)) { |
152
|
|
|
$ips = array($ips); |
153
|
|
|
} |
154
|
|
|
$method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4'; |
155
|
|
|
foreach ($ips as $ip) { |
156
|
|
|
if ($method($requestIp, $ip)) { |
157
|
|
|
return true; |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
return false; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Compares two IPv4 addresses. |
165
|
|
|
* In case a subnet is given, it checks if it contains the request IP. |
166
|
|
|
* |
167
|
|
|
* @param string $requestIp IPv4 address to check |
168
|
|
|
* @param string $ip IPv4 address or subnet in CIDR notation |
169
|
|
|
* |
170
|
|
|
* @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet |
171
|
|
|
* @see https://github.com/symfony/http-foundation/blob/master/IpUtils.php |
172
|
|
|
*/ |
173
|
|
|
function checkIp4($requestIp, $ip) : bool |
174
|
|
|
{ |
175
|
|
|
if (false !== strpos($ip, '/')) { |
176
|
|
|
list($address, $netmask) = explode('/', $ip, 2); |
177
|
|
|
if ($netmask === '0') { |
178
|
|
|
// Ensure IP is valid - using ip2long below implicitly validates, but we need to do it manually here |
179
|
|
|
return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); |
180
|
|
|
} |
181
|
|
|
if ($netmask < 0 || $netmask > 32) { |
182
|
|
|
return false; |
183
|
|
|
} |
184
|
|
|
} else { |
185
|
|
|
$address = $ip; |
186
|
|
|
$netmask = 32; |
187
|
|
|
} |
188
|
|
|
return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, |
189
|
|
|
$netmask); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Compares two IPv6 addresses. |
194
|
|
|
* In case a subnet is given, it checks if it contains the request IP. |
195
|
|
|
* |
196
|
|
|
* @author David Soria Parra <dsp at php dot net> |
197
|
|
|
* |
198
|
|
|
* @see https://github.com/symfony/http-foundation/blob/master/IpUtils.php |
199
|
|
|
* @see https://github.com/dsp/v6tools |
200
|
|
|
* |
201
|
|
|
* @param string $requestIp IPv6 address to check |
202
|
|
|
* @param string $ip IPv6 address or subnet in CIDR notation |
203
|
|
|
* |
204
|
|
|
* @return bool Whether the IP is valid |
205
|
|
|
* |
206
|
|
|
* @throws \RuntimeException When IPV6 support is not enabled |
207
|
|
|
*/ |
208
|
|
|
function checkIp6($requestIp, $ip) : bool |
209
|
|
|
{ |
210
|
|
|
if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) { |
211
|
|
|
throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); |
212
|
|
|
} |
213
|
|
|
if (false !== strpos($ip, '/')) { |
214
|
|
|
list($address, $netmask) = explode('/', $ip, 2); |
215
|
|
|
if ($netmask < 1 || $netmask > 128) { |
216
|
|
|
return false; |
217
|
|
|
} |
218
|
|
|
} else { |
219
|
|
|
$address = $ip; |
220
|
|
|
$netmask = 128; |
221
|
|
|
} |
222
|
|
|
$bytesAddr = unpack('n*', @inet_pton($address)); |
223
|
|
|
$bytesTest = unpack('n*', @inet_pton($requestIp)); |
224
|
|
|
if (!$bytesAddr || !$bytesTest) { |
|
|
|
|
225
|
|
|
return false; |
226
|
|
|
} |
227
|
|
|
for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { |
228
|
|
|
$left = $netmask - 16 * ($i - 1); |
229
|
|
|
$left = ($left <= 16) ? $left : 16; |
230
|
|
|
$mask = ~(0xffff >> $left) & 0xffff; |
231
|
|
|
if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { |
232
|
|
|
return false; |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
return true; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Check if $ip is contained in $trustedProxies array. |
240
|
|
|
* @param array $trustedProxies |
241
|
|
|
* @param $ip |
242
|
|
|
* @return bool |
243
|
|
|
*/ |
244
|
|
|
function isFromTrustedProxy(array $trustedProxies, $ip) |
245
|
|
|
{ |
246
|
|
|
return $trustedProxies && checkIp($ip, $trustedProxies); |
|
|
|
|
247
|
|
|
} |
248
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.