@@ -27,7 +27,7 @@ class IpAddress implements MiddlewareInterface
27
27
*
28
28
* @var array
29
29
*/
30
- protected $ trustedProxies ;
30
+ protected $ trustedProxies = [] ;
31
31
32
32
/**
33
33
* List of trusted proxy IP wildcard ranges
@@ -83,7 +83,7 @@ public function __construct(
83
83
84
84
$ this ->checkProxyHeaders = $ checkProxyHeaders ;
85
85
86
- if ($ trustedProxies ) {
86
+ if (is_array ( $ trustedProxies) ) {
87
87
foreach ($ trustedProxies as $ proxy ) {
88
88
if (strpos ($ proxy , '* ' ) !== false ) {
89
89
// Wildcard IP address
@@ -175,54 +175,102 @@ public function __invoke(ServerRequestInterface $request, ResponseInterface $res
175
175
* @param ServerRequestInterface $request PSR-7 Request
176
176
* @return string
177
177
*/
178
- protected function determineClientIpAddress ($ request )
178
+ protected function determineClientIpAddress ($ request ): ? string
179
179
{
180
- $ ipAddress = '' ;
180
+ $ ipAddress = null ;
181
181
182
182
$ serverParams = $ request ->getServerParams ();
183
183
if (isset ($ serverParams ['REMOTE_ADDR ' ])) {
184
184
$ remoteAddr = $ this ->extractIpAddress ($ serverParams ['REMOTE_ADDR ' ]);
185
- if ($ this -> isValidIpAddress ($ remoteAddr )) {
185
+ if (filter_var ($ remoteAddr, FILTER_VALIDATE_IP )) {
186
186
$ ipAddress = $ remoteAddr ;
187
187
}
188
188
}
189
+ if (!$ this ->checkProxyHeaders ) {
190
+ // do not check if configured to not check
191
+ return $ ipAddress ;
192
+ }
189
193
190
- if ($ this ->shouldCheckProxyHeaders ($ ipAddress )) {
191
- foreach ($ this ->headersToInspect as $ header ) {
192
- if ($ request ->hasHeader ($ header )) {
193
- $ ip = $ this ->getFirstIpAddressFromHeader ($ request , $ header );
194
- if ($ this ->isValidIpAddress ($ ip )) {
195
- $ ipAddress = $ ip ;
196
- break ;
197
- }
194
+ // If trustedProxies is empty, then the remote address is the trusted proxy
195
+ $ trustedProxies = $ this ->trustedProxies ;
196
+ if (empty ($ trustedProxies ) && empty ($ this ->trustedWildcards ) && empty ($ this ->trustedCidrs )) {
197
+ $ trustedProxies [] = $ ipAddress ;
198
+ }
199
+
200
+ // find the first non-empty header from the headersToInspect list and use just that one
201
+ foreach ($ this ->headersToInspect as $ header ) {
202
+ if ($ request ->hasHeader ($ header )) {
203
+ $ headerValue = $ request ->getHeaderLine ($ header );
204
+ if (!empty ($ headerValue )) {
205
+ $ ipAddress = $ this ->getIpAddressFromHeader (
206
+ $ header ,
207
+ $ headerValue ,
208
+ $ ipAddress ,
209
+ $ trustedProxies
210
+ );
211
+ break ;
198
212
}
199
213
}
200
214
}
201
215
202
216
return empty ($ ipAddress ) ? null : $ ipAddress ;
203
217
}
204
218
205
- /**
206
- * Determine whether we should check proxy headers for specified ip address
207
- */
208
- protected function shouldCheckProxyHeaders (string $ ipAddress ): bool
209
- {
210
- //do not check if configured to not check
211
- if (!$ this ->checkProxyHeaders ) {
212
- return false ;
219
+ public function getIpAddressFromHeader (
220
+ string $ headerName ,
221
+ string $ headerValue ,
222
+ string $ ipAddress ,
223
+ array $ trustedProxies
224
+ ) {
225
+ if (strtolower ($ headerName ) == 'forwarded ' ) {
226
+ // The Forwarded header is different, so we need to extract the for= values. Note that we perform a
227
+ // simple extraction here, and do not support the full RFC 7239 specification.
228
+ preg_match_all ('/for=([^,;]+)/i ' , $ headerValue , $ matches );
229
+ $ ipList = $ matches [1 ];
230
+
231
+ // If any of the items in the list are not an IP address, then we ignore the entire list for now
232
+ foreach ($ ipList as $ ip ) {
233
+ $ ip = $ this ->extractIpAddress ($ ip );
234
+ if (!filter_var ($ ip , FILTER_VALIDATE_IP )) {
235
+ return $ ipAddress ;
236
+ }
237
+ }
238
+ } else {
239
+ $ ipList = explode (', ' , $ headerValue );
213
240
}
241
+ $ ipList [] = $ ipAddress ;
214
242
215
- //if configured to check but no constraints
216
- if (!$ this ->trustedProxies && !$ this ->trustedWildcards && !$ this ->trustedCidrs ) {
217
- return true ;
243
+ // Remove port from each item in the list
244
+ $ ipList = array_map (function ($ ip ) {
245
+ return $ this ->extractIpAddress (trim ($ ip ));
246
+ }, $ ipList );
247
+
248
+ // Ensure all IPs are valid and return $ipAddress if not
249
+ foreach ($ ipList as $ ip ) {
250
+ if (!filter_var ($ ip , FILTER_VALIDATE_IP )) {
251
+ return $ ipAddress ;
252
+ }
218
253
}
219
254
220
- // Exact Match for trusted proxies
221
- if ($ this ->trustedProxies && in_array ($ ipAddress , $ this ->trustedProxies )) {
255
+ // walk list from right to left removing known proxy IP addresses.
256
+ $ ipList = array_reverse ($ ipList );
257
+ foreach ($ ipList as $ ip ) {
258
+ $ ip = trim ($ ip );
259
+ if (!empty ($ ip ) && !$ this ->isTrustedProxy ($ ip , $ trustedProxies )) {
260
+ return $ ip ;
261
+ }
262
+ }
263
+
264
+ return $ ipAddress ;
265
+ }
266
+
267
+ protected function isTrustedProxy (string $ ipAddress , array $ trustedProxies ): bool
268
+ {
269
+ if (in_array ($ ipAddress , $ trustedProxies )) {
222
270
return true ;
223
271
}
224
272
225
- // Wildcard Match
273
+ // Do we match a wildcard?
226
274
if ($ this ->trustedWildcards ) {
227
275
// IPv4 has 4 parts separated by '.'
228
276
// IPv6 has 8 parts separated by ':'
@@ -252,7 +300,7 @@ protected function shouldCheckProxyHeaders(string $ipAddress): bool
252
300
}
253
301
}
254
302
255
- // CIDR Match
303
+ // Do we match a CIDR address?
256
304
if ($ this ->trustedCidrs ) {
257
305
// Only IPv4 is supported for CIDR matching
258
306
$ ipAsLong = ip2long ($ ipAddress );
@@ -265,7 +313,6 @@ protected function shouldCheckProxyHeaders(string $ipAddress): bool
265
313
}
266
314
}
267
315
268
- //default - not check
269
316
return false ;
270
317
}
271
318
@@ -280,48 +327,25 @@ protected function shouldCheckProxyHeaders(string $ipAddress): bool
280
327
protected function extractIpAddress ($ ipAddress )
281
328
{
282
329
$ parts = explode (': ' , $ ipAddress );
330
+ if (count ($ parts ) == 1 ) {
331
+ return $ ipAddress ;
332
+ }
283
333
if (count ($ parts ) == 2 ) {
284
334
if (filter_var ($ parts [0 ], FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 ) !== false ) {
285
335
return $ parts [0 ];
286
336
}
287
337
}
288
338
289
- return $ ipAddress ;
290
- }
291
-
292
- /**
293
- * Check that a given string is a valid IP address
294
- *
295
- * @param string $ip
296
- * @return boolean
297
- */
298
- protected function isValidIpAddress (string $ ip ): bool
299
- {
300
- return filter_var ($ ip , FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 ) !== false ;
301
- }
302
-
303
- /**
304
- * Find out the client's IP address from the headers available to us
305
- *
306
- * @param ServerRequestInterface $request PSR-7 Request
307
- * @param string $header Header name
308
- * @return string
309
- */
310
- private function getFirstIpAddressFromHeader (MessageInterface $ request , string $ header ): string
311
- {
312
- $ items = explode (', ' , $ request ->getHeaderLine ($ header ));
313
- $ headerValue = trim (reset ($ items ));
314
-
315
- if (ucfirst ($ header ) == 'Forwarded ' ) {
316
- foreach (explode ('; ' , $ headerValue ) as $ headerPart ) {
317
- if (strtolower (substr ($ headerPart , 0 , 4 )) == 'for= ' ) {
318
- $ for = explode ('] ' , $ headerPart );
319
- $ headerValue = trim (substr (reset ($ for ), 4 ), " \t\n\r\0\x0B" . "\"[] " );
320
- break ;
321
- }
322
- }
339
+ // If the $ipAddress starts with a [ and ends with ] or ]:port, then it is an IPv6 address and
340
+ // we can extract the IP address
341
+ $ ipAddress = trim ($ ipAddress , '" \'' );
342
+ if (substr ($ ipAddress , 0 , 1 ) === '[ '
343
+ && (substr ($ ipAddress , -1 ) === '] ' || preg_match ('/\]:\d+$/ ' , $ ipAddress ))) {
344
+ // Extract IPv6 address between brackets
345
+ preg_match ('/\[(.*?)\]/ ' , $ ipAddress , $ matches );
346
+ $ ipAddress = $ matches [1 ];
323
347
}
324
348
325
- return $ this -> extractIpAddress ( $ headerValue ) ;
349
+ return $ ipAddress ;
326
350
}
327
351
}
0 commit comments