@@ -27,7 +27,7 @@ class IpAddress implements MiddlewareInterface
2727 *
2828 * @var array
2929 */
30- protected $ trustedProxies ;
30+ protected $ trustedProxies = [] ;
3131
3232 /**
3333 * List of trusted proxy IP wildcard ranges
@@ -83,7 +83,7 @@ public function __construct(
8383
8484 $ this ->checkProxyHeaders = $ checkProxyHeaders ;
8585
86- if ($ trustedProxies ) {
86+ if (is_array ( $ trustedProxies) ) {
8787 foreach ($ trustedProxies as $ proxy ) {
8888 if (strpos ($ proxy , '* ' ) !== false ) {
8989 // Wildcard IP address
@@ -175,54 +175,102 @@ public function __invoke(ServerRequestInterface $request, ResponseInterface $res
175175 * @param ServerRequestInterface $request PSR-7 Request
176176 * @return string
177177 */
178- protected function determineClientIpAddress ($ request )
178+ protected function determineClientIpAddress ($ request ): ? string
179179 {
180- $ ipAddress = '' ;
180+ $ ipAddress = null ;
181181
182182 $ serverParams = $ request ->getServerParams ();
183183 if (isset ($ serverParams ['REMOTE_ADDR ' ])) {
184184 $ remoteAddr = $ this ->extractIpAddress ($ serverParams ['REMOTE_ADDR ' ]);
185- if ($ this -> isValidIpAddress ($ remoteAddr )) {
185+ if (filter_var ($ remoteAddr, FILTER_VALIDATE_IP )) {
186186 $ ipAddress = $ remoteAddr ;
187187 }
188188 }
189+ if (!$ this ->checkProxyHeaders ) {
190+ // do not check if configured to not check
191+ return $ ipAddress ;
192+ }
189193
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 ;
198212 }
199213 }
200214 }
201215
202216 return empty ($ ipAddress ) ? null : $ ipAddress ;
203217 }
204218
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 );
213240 }
241+ $ ipList [] = $ ipAddress ;
214242
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+ }
218253 }
219254
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 )) {
222270 return true ;
223271 }
224272
225- // Wildcard Match
273+ // Do we match a wildcard?
226274 if ($ this ->trustedWildcards ) {
227275 // IPv4 has 4 parts separated by '.'
228276 // IPv6 has 8 parts separated by ':'
@@ -252,7 +300,7 @@ protected function shouldCheckProxyHeaders(string $ipAddress): bool
252300 }
253301 }
254302
255- // CIDR Match
303+ // Do we match a CIDR address?
256304 if ($ this ->trustedCidrs ) {
257305 // Only IPv4 is supported for CIDR matching
258306 $ ipAsLong = ip2long ($ ipAddress );
@@ -265,7 +313,6 @@ protected function shouldCheckProxyHeaders(string $ipAddress): bool
265313 }
266314 }
267315
268- //default - not check
269316 return false ;
270317 }
271318
@@ -280,48 +327,25 @@ protected function shouldCheckProxyHeaders(string $ipAddress): bool
280327 protected function extractIpAddress ($ ipAddress )
281328 {
282329 $ parts = explode (': ' , $ ipAddress );
330+ if (count ($ parts ) == 1 ) {
331+ return $ ipAddress ;
332+ }
283333 if (count ($ parts ) == 2 ) {
284334 if (filter_var ($ parts [0 ], FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 ) !== false ) {
285335 return $ parts [0 ];
286336 }
287337 }
288338
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 ];
323347 }
324348
325- return $ this -> extractIpAddress ( $ headerValue ) ;
349+ return $ ipAddress ;
326350 }
327351}
0 commit comments