Added new Forwarded header support for Request::getClientIps

This commit is contained in:
Tony Cosentino 2014-07-11 19:10:41 +02:00 committed by Fabien Potencier
parent 51a8b11ecd
commit 4c8a25a6e2
2 changed files with 60 additions and 7 deletions

View File

@ -30,6 +30,7 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
*/
class Request
{
const HEADER_FORWARDED = 'forwarded';
const HEADER_CLIENT_IP = 'client_ip';
const HEADER_CLIENT_HOST = 'client_host';
const HEADER_CLIENT_PROTO = 'client_proto';
@ -46,6 +47,9 @@ class Request
const METHOD_TRACE = 'TRACE';
const METHOD_CONNECT = 'CONNECT';
/**
* @var string[]
*/
protected static $trustedProxies = array();
/**
@ -62,10 +66,13 @@ class Request
* Names for headers that can be trusted when
* using trusted proxies.
*
* The default names are non-standard, but widely used
* The FORWARDED header is the standard as of rfc7239.
*
* The other headers are non-standard, but widely used
* by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
*/
protected static $trustedHeaders = array(
self::HEADER_FORWARDED => 'FORWARDED',
self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
@ -823,24 +830,26 @@ class Request
*/
public function getClientIps()
{
$clientIps = array();
$ip = $this->server->get('REMOTE_ADDR');
if (!self::$trustedProxies) {
return array($ip);
}
if (!self::$trustedHeaders[self::HEADER_CLIENT_IP] || !$this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
return array($ip);
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
$forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches);
$clientIps = $matches[3];
} elseif (self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
$clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
}
$clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
$clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
$ip = $clientIps[0]; // Fallback to this when the client IP falls into the range of trusted proxies
// Eliminate all IPs from the forwarded IP chain which are trusted proxies
foreach ($clientIps as $key => $clientIp) {
// Remove port on IPv4 address (unfortunately, it does happen)
// Remove port (unfortunately, it does happen)
if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
$clientIps[$key] = $clientIp = $match[1];
}

View File

@ -875,6 +875,31 @@ class RequestTest extends \PHPUnit_Framework_TestCase
Request::setTrustedProxies(array());
}
/**
* @dataProvider testGetClientIpsForwardedProvider
*/
public function testGetClientIpsForwarded($expected, $remoteAddr, $httpForwarded, $trustedProxies)
{
$request = $this->getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies);
$this->assertEquals($expected, $request->getClientIps());
Request::setTrustedProxies(array());
}
public function testGetClientIpsForwardedProvider()
{
// $expected $remoteAddr $httpForwarded $trustedProxies
return array(
array(array('127.0.0.1'), '127.0.0.1', 'for="_gazonk"', null),
array(array('_gazonk'), '127.0.0.1', 'for="_gazonk"', array('127.0.0.1')),
array(array('88.88.88.88'), '127.0.0.1', 'for="88.88.88.88:80"', array('127.0.0.1')),
array(array('192.0.2.60'), '::1', 'for=192.0.2.60;proto=http;by=203.0.113.43', array('::1')),
array(array('2620:0:1cfe:face:b00c::3', '192.0.2.43'), '::1', 'for=192.0.2.43, for=2620:0:1cfe:face:b00c::3', array('::1')),
array(array('2001:db8:cafe::17'), '::1', 'for="[2001:db8:cafe::17]:4711', array('::1')),
);
}
public function testGetClientIpsProvider()
{
// $expected $remoteAddr $httpForwardedFor $trustedProxies
@ -1467,6 +1492,25 @@ class RequestTest extends \PHPUnit_Framework_TestCase
return $request;
}
private function getRequestInstanceForClientIpsForwardedTests($remoteAddr, $httpForwarded, $trustedProxies)
{
$request = new Request();
$server = array('REMOTE_ADDR' => $remoteAddr);
if (null !== $httpForwarded) {
$server['HTTP_FORWARDED'] = $httpForwarded;
}
if ($trustedProxies) {
Request::setTrustedProxies($trustedProxies);
}
$request->initialize(array(), array(), array(), array(), array(), $server);
return $request;
}
public function testTrustedProxies()
{
$request = Request::create('http://example.com/');