Added new Forwarded header support for Request::getClientIps
This commit is contained in:
parent
51a8b11ecd
commit
4c8a25a6e2
|
@ -30,6 +30,7 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
*/
|
*/
|
||||||
class Request
|
class Request
|
||||||
{
|
{
|
||||||
|
const HEADER_FORWARDED = 'forwarded';
|
||||||
const HEADER_CLIENT_IP = 'client_ip';
|
const HEADER_CLIENT_IP = 'client_ip';
|
||||||
const HEADER_CLIENT_HOST = 'client_host';
|
const HEADER_CLIENT_HOST = 'client_host';
|
||||||
const HEADER_CLIENT_PROTO = 'client_proto';
|
const HEADER_CLIENT_PROTO = 'client_proto';
|
||||||
|
@ -46,6 +47,9 @@ class Request
|
||||||
const METHOD_TRACE = 'TRACE';
|
const METHOD_TRACE = 'TRACE';
|
||||||
const METHOD_CONNECT = 'CONNECT';
|
const METHOD_CONNECT = 'CONNECT';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
protected static $trustedProxies = array();
|
protected static $trustedProxies = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,10 +66,13 @@ class Request
|
||||||
* Names for headers that can be trusted when
|
* Names for headers that can be trusted when
|
||||||
* using trusted proxies.
|
* 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).
|
* by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
|
||||||
*/
|
*/
|
||||||
protected static $trustedHeaders = array(
|
protected static $trustedHeaders = array(
|
||||||
|
self::HEADER_FORWARDED => 'FORWARDED',
|
||||||
self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
|
self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
|
||||||
self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
|
self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
|
||||||
self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
|
self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
|
||||||
|
@ -823,24 +830,26 @@ class Request
|
||||||
*/
|
*/
|
||||||
public function getClientIps()
|
public function getClientIps()
|
||||||
{
|
{
|
||||||
|
$clientIps = array();
|
||||||
$ip = $this->server->get('REMOTE_ADDR');
|
$ip = $this->server->get('REMOTE_ADDR');
|
||||||
|
|
||||||
if (!self::$trustedProxies) {
|
if (!self::$trustedProxies) {
|
||||||
return array($ip);
|
return array($ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!self::$trustedHeaders[self::HEADER_CLIENT_IP] || !$this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
|
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
|
||||||
return array($ip);
|
$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
|
$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
|
$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) {
|
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)) {
|
if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
|
||||||
$clientIps[$key] = $clientIp = $match[1];
|
$clientIps[$key] = $clientIp = $match[1];
|
||||||
}
|
}
|
||||||
|
|
|
@ -875,6 +875,31 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||||
Request::setTrustedProxies(array());
|
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()
|
public function testGetClientIpsProvider()
|
||||||
{
|
{
|
||||||
// $expected $remoteAddr $httpForwardedFor $trustedProxies
|
// $expected $remoteAddr $httpForwardedFor $trustedProxies
|
||||||
|
@ -1467,6 +1492,25 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||||
return $request;
|
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()
|
public function testTrustedProxies()
|
||||||
{
|
{
|
||||||
$request = Request::create('http://example.com/');
|
$request = Request::create('http://example.com/');
|
||||||
|
|
Reference in New Issue