Merge branch '2.0' into 2.1
* 2.0: replaced magic strings by proper constants refactored tests for Request fixed the logic in Request::isSecure() (if the information comes from a source that we trust, don't check other ones) added a way to configure the X-Forwarded-XXX header names and a way to disable trusting them fixed algorithm used to determine the trusted client IP removed the non-standard Client-IP HTTP header Conflicts: src/Symfony/Component/HttpFoundation/Request.php src/Symfony/Component/HttpFoundation/Tests/RequestTest.php
This commit is contained in:
commit
6c67476ef0
@ -30,7 +30,28 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
protected static $trustProxy = false;
|
||||
const HEADER_CLIENT_IP = 'client_ip';
|
||||
const HEADER_CLIENT_HOST = 'client_host';
|
||||
const HEADER_CLIENT_PROTO = 'client_proto';
|
||||
const HEADER_CLIENT_PORT = 'client_port';
|
||||
|
||||
protected static $trustProxyData = false;
|
||||
|
||||
protected static $trustedProxies = array();
|
||||
|
||||
/**
|
||||
* Names for headers that can be trusted when
|
||||
* using trusted proxies.
|
||||
*
|
||||
* The default names are non-standard, but widely used
|
||||
* by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
|
||||
*/
|
||||
protected static $trustedHeaders = array(
|
||||
self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
|
||||
self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
|
||||
self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
|
||||
self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\HttpFoundation\ParameterBag
|
||||
@ -439,14 +460,50 @@ class Request
|
||||
/**
|
||||
* Trusts $_SERVER entries coming from proxies.
|
||||
*
|
||||
* You should only call this method if your application
|
||||
* is hosted behind a reverse proxy that you manage.
|
||||
*
|
||||
* @api
|
||||
* @deprecated Deprecated since version 2.0, to be removed in 2.3. Use setTrustedProxies instead.
|
||||
*/
|
||||
public static function trustProxyData()
|
||||
{
|
||||
self::$trustProxy = true;
|
||||
self::$trustProxyData = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a list of trusted proxies.
|
||||
*
|
||||
* You should only list the reverse proxies that you manage directly.
|
||||
*
|
||||
* @param array $proxies A list of trusted proxies
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public static function setTrustedProxies(array $proxies)
|
||||
{
|
||||
self::$trustedProxies = $proxies;
|
||||
self::$trustProxyData = $proxies ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name for trusted headers.
|
||||
*
|
||||
* The following header keys are supported:
|
||||
*
|
||||
* * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp())
|
||||
* * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getClientHost())
|
||||
* * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getClientPort())
|
||||
* * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure())
|
||||
*
|
||||
* Setting an empty value allows to disable the trusted header for the given key.
|
||||
*
|
||||
* @param string $key The header key
|
||||
* @param string $value The header name
|
||||
*/
|
||||
public static function setTrustedHeaderName($key, $value)
|
||||
{
|
||||
if (!array_key_exists($key, self::$trustedHeaders)) {
|
||||
throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key));
|
||||
}
|
||||
|
||||
self::$trustedHeaders[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -582,31 +639,43 @@ class Request
|
||||
/**
|
||||
* Returns the client IP address.
|
||||
*
|
||||
* This method can read the client IP address from the "X-Forwarded-For" header
|
||||
* when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For"
|
||||
* header value is a comma+space separated list of IP addresses, the left-most
|
||||
* being the original client, and each successive proxy that passed the request
|
||||
* adding the IP address where it received the request from.
|
||||
*
|
||||
* If your reverse proxy uses a different header name than "X-Forwarded-For",
|
||||
* ("Client-Ip" for instance), configure it via "setTrustedHeaderName()" with
|
||||
* the "client-ip" key.
|
||||
*
|
||||
* @return string The client IP address
|
||||
*
|
||||
* @see http://en.wikipedia.org/wiki/X-Forwarded-For
|
||||
*
|
||||
* @deprecated The proxy argument is deprecated since version 2.0 and will be removed in 2.3. Use setTrustedProxies instead.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getClientIp()
|
||||
{
|
||||
if (self::$trustProxy) {
|
||||
if ($this->server->has('HTTP_CLIENT_IP')) {
|
||||
return $this->server->get('HTTP_CLIENT_IP');
|
||||
} elseif ($this->server->has('HTTP_X_FORWARDED_FOR')) {
|
||||
$clientIp = explode(',', $this->server->get('HTTP_X_FORWARDED_FOR'));
|
||||
$ip = $this->server->get('REMOTE_ADDR');
|
||||
|
||||
foreach ($clientIp as $ipAddress) {
|
||||
$cleanIpAddress = trim($ipAddress);
|
||||
|
||||
if (false !== filter_var($cleanIpAddress, FILTER_VALIDATE_IP)) {
|
||||
return $cleanIpAddress;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
if (!self::$trustProxyData) {
|
||||
return $ip;
|
||||
}
|
||||
|
||||
return $this->server->get('REMOTE_ADDR');
|
||||
if (!self::$trustedHeaders[self::HEADER_CLIENT_IP] || !$this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) {
|
||||
return $ip;
|
||||
}
|
||||
|
||||
$clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
|
||||
$clientIps[] = $ip;
|
||||
|
||||
$trustedProxies = self::$trustProxyData && !self::$trustedProxies ? array($ip) : self::$trustedProxies;
|
||||
$clientIps = array_diff($clientIps, $trustedProxies);
|
||||
|
||||
return array_pop($clientIps);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -705,14 +774,22 @@ class Request
|
||||
/**
|
||||
* Returns the port on which the request is made.
|
||||
*
|
||||
* This method can read the client port from the "X-Forwarded-Port" header
|
||||
* when trusted proxies were set via "setTrustedProxies()".
|
||||
*
|
||||
* The "X-Forwarded-Port" header must contain the client port.
|
||||
*
|
||||
* If your reverse proxy uses a different header name than "X-Forwarded-Port",
|
||||
* configure it via "setTrustedHeaderName()" with the "client-port" key.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getPort()
|
||||
{
|
||||
if (self::$trustProxy && $this->headers->has('X-Forwarded-Port')) {
|
||||
return $this->headers->get('X-Forwarded-Port');
|
||||
if (self::$trustProxyData && self::$trustedHeaders[self::HEADER_CLIENT_PORT] && $port = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PORT])) {
|
||||
return $port;
|
||||
}
|
||||
|
||||
return $this->server->get('SERVER_PORT');
|
||||
@ -858,31 +935,46 @@ class Request
|
||||
/**
|
||||
* Checks whether the request is secure or not.
|
||||
*
|
||||
* This method can read the client port from the "X-Forwarded-Proto" header
|
||||
* when trusted proxies were set via "setTrustedProxies()".
|
||||
*
|
||||
* The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
|
||||
*
|
||||
* If your reverse proxy uses a different header name than "X-Forwarded-Proto"
|
||||
* ("SSL_HTTPS" for instance), configure it via "setTrustedHeaderName()" with
|
||||
* the "client-proto" key.
|
||||
*
|
||||
* @return Boolean
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function isSecure()
|
||||
{
|
||||
return (
|
||||
(strtolower($this->server->get('HTTPS')) == 'on' || $this->server->get('HTTPS') == 1)
|
||||
||
|
||||
(self::$trustProxy && strtolower($this->headers->get('SSL_HTTPS')) == 'on' || $this->headers->get('SSL_HTTPS') == 1)
|
||||
||
|
||||
(self::$trustProxy && strtolower($this->headers->get('X_FORWARDED_PROTO')) == 'https')
|
||||
);
|
||||
if (self::$trustProxyData && self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && $proto = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO])) {
|
||||
return in_array(strtolower($proto), array('https', 'on', '1'));
|
||||
}
|
||||
|
||||
return 'on' == strtolower($this->server->get('HTTPS')) || 1 == $this->server->get('HTTPS');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host name.
|
||||
*
|
||||
* This method can read the client port from the "X-Forwarded-Host" header
|
||||
* when trusted proxies were set via "setTrustedProxies()".
|
||||
*
|
||||
* The "X-Forwarded-Host" header must contain the client host name.
|
||||
*
|
||||
* If your reverse proxy uses a different header name than "X-Forwarded-Host",
|
||||
* configure it via "setTrustedHeaderName()" with the "client-host" key.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getHost()
|
||||
{
|
||||
if (self::$trustProxy && $host = $this->headers->get('X_FORWARDED_HOST')) {
|
||||
if (self::$trustProxyData && self::$trustedHeaders[self::HEADER_CLIENT_HOST] && $host = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_HOST])) {
|
||||
$elements = explode(',', $host);
|
||||
|
||||
$host = trim($elements[count($elements) - 1]);
|
||||
|
@ -573,33 +573,14 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||
$request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.exemple.com'));
|
||||
$this->assertEquals('www.exemple.com', $request->getHost(), '->getHost() from Host Header');
|
||||
|
||||
// Host header with port number.
|
||||
// Host header with port number
|
||||
$request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.exemple.com:8080'));
|
||||
$this->assertEquals('www.exemple.com', $request->getHost(), '->getHost() from Host Header with port number');
|
||||
|
||||
// Server values.
|
||||
// Server values
|
||||
$request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.exemple.com'));
|
||||
$this->assertEquals('www.exemple.com', $request->getHost(), '->getHost() from server name');
|
||||
|
||||
$this->startTrustingProxyData();
|
||||
// X_FORWARDED_HOST.
|
||||
$request->initialize(array(), array(), array(), array(), array(), array('HTTP_X_FORWARDED_HOST' => 'www.exemple.com'));
|
||||
$this->assertEquals('www.exemple.com', $request->getHost(), '->getHost() from X_FORWARDED_HOST');
|
||||
|
||||
// X_FORWARDED_HOST
|
||||
$request->initialize(array(), array(), array(), array(), array(), array('HTTP_X_FORWARDED_HOST' => 'www.exemple.com, www.second.com'));
|
||||
$this->assertEquals('www.second.com', $request->getHost(), '->getHost() value from X_FORWARDED_HOST use last value');
|
||||
|
||||
// X_FORWARDED_HOST with port number
|
||||
$request->initialize(array(), array(), array(), array(), array(), array('HTTP_X_FORWARDED_HOST' => 'www.exemple.com, www.second.com:8080'));
|
||||
$this->assertEquals('www.second.com', $request->getHost(), '->getHost() value from X_FORWARDED_HOST with port number');
|
||||
|
||||
$request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.exemple.com', 'HTTP_X_FORWARDED_HOST' => 'www.forward.com'));
|
||||
$this->assertEquals('www.forward.com', $request->getHost(), '->getHost() value from X_FORWARDED_HOST has priority over Host');
|
||||
|
||||
$request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.exemple.com', 'HTTP_X_FORWARDED_HOST' => 'www.forward.com'));
|
||||
$this->assertEquals('www.forward.com', $request->getHost(), '->getHost() value from X_FORWARDED_HOST has priority over SERVER_NAME ');
|
||||
|
||||
$request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.exemple.com', 'HTTP_HOST' => 'www.host.com'));
|
||||
$this->assertEquals('www.host.com', $request->getHost(), '->getHost() value from Host header has priority over SERVER_NAME ');
|
||||
$this->stopTrustingProxyData();
|
||||
@ -646,42 +627,40 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* @dataProvider testGetClientIpProvider
|
||||
*/
|
||||
public function testGetClientIp($expected, $proxy, $remoteAddr, $httpClientIp, $httpForwardedFor)
|
||||
public function testGetClientIp($expected, $proxy, $remoteAddr, $httpForwardedFor, $trustedProxies)
|
||||
{
|
||||
$request = new Request();
|
||||
$this->assertEquals('', $request->getClientIp());
|
||||
|
||||
$server = array('REMOTE_ADDR' => $remoteAddr);
|
||||
if (null !== $httpClientIp) {
|
||||
$server['HTTP_CLIENT_IP'] = $httpClientIp;
|
||||
}
|
||||
if (null !== $httpForwardedFor) {
|
||||
$server['HTTP_X_FORWARDED_FOR'] = $httpForwardedFor;
|
||||
}
|
||||
|
||||
if ($proxy || $trustedProxies) {
|
||||
Request::setTrustedProxies(null === $trustedProxies ? array($remoteAddr) : $trustedProxies);
|
||||
}
|
||||
|
||||
$request->initialize(array(), array(), array(), array(), array(), $server);
|
||||
if ($proxy) {
|
||||
$this->startTrustingProxyData();
|
||||
}
|
||||
$this->assertEquals($expected, $request->getClientIp($proxy));
|
||||
if ($proxy) {
|
||||
$this->stopTrustingProxyData();
|
||||
}
|
||||
|
||||
Request::setTrustedProxies(array());
|
||||
}
|
||||
|
||||
public function testGetClientIpProvider()
|
||||
{
|
||||
return array(
|
||||
array('88.88.88.88', false, '88.88.88.88', null, null),
|
||||
array('127.0.0.1', false, '127.0.0.1', '88.88.88.88', null),
|
||||
array('88.88.88.88', true, '127.0.0.1', '88.88.88.88', null),
|
||||
array('127.0.0.1', false, '127.0.0.1', null, '88.88.88.88'),
|
||||
array('88.88.88.88', true, '127.0.0.1', null, '88.88.88.88'),
|
||||
array('::1', false, '::1', null, null),
|
||||
array('2620:0:1cfe:face:b00c::3', true, '::1', '2620:0:1cfe:face:b00c::3', null),
|
||||
array('2620:0:1cfe:face:b00c::3', true, '::1', null, '2620:0:1cfe:face:b00c::3, ::1'),
|
||||
array('88.88.88.88', true, '123.45.67.89', null, '88.88.88.88, 87.65.43.21, 127.0.0.1'),
|
||||
array('88.88.88.88', true, '123.45.67.89', null, 'unknown, 88.88.88.88'),
|
||||
array('88.88.88.88', false, '88.88.88.88', null, null),
|
||||
array('127.0.0.1', false, '127.0.0.1', null, null),
|
||||
array('::1', false, '::1', null, null),
|
||||
array('127.0.0.1', false, '127.0.0.1', '88.88.88.88', null),
|
||||
array('88.88.88.88', true, '127.0.0.1', '88.88.88.88', null),
|
||||
array('2620:0:1cfe:face:b00c::3', true, '::1', '2620:0:1cfe:face:b00c::3', null),
|
||||
array('88.88.88.88', true, '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', null),
|
||||
array('87.65.43.21', true, '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '88.88.88.88')),
|
||||
array('87.65.43.21', false, '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '88.88.88.88')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -790,8 +769,9 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||
$this->startTrustingProxyData();
|
||||
$request->headers->set('X_FORWARDED_PROTO', 'https');
|
||||
|
||||
Request::setTrustedProxies(array('1.1.1.1'));
|
||||
$this->assertTrue($request->isSecure());
|
||||
$this->stopTrustingProxyData();
|
||||
Request::setTrustedProxies(array());
|
||||
|
||||
$request->overrideGlobals();
|
||||
|
||||
@ -997,18 +977,6 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('foo', $request->getRequestFormat(null));
|
||||
}
|
||||
|
||||
public function testForwardedSecure()
|
||||
{
|
||||
$request = new Request();
|
||||
$request->headers->set('X-Forwarded-Proto', 'https');
|
||||
$request->headers->set('X-Forwarded-Port', 443);
|
||||
|
||||
$this->startTrustingProxyData();
|
||||
$this->assertTrue($request->isSecure());
|
||||
$this->assertEquals(443, $request->getPort());
|
||||
$this->stopTrustingProxyData();
|
||||
}
|
||||
|
||||
public function testHasSession()
|
||||
{
|
||||
$request = new Request();
|
||||
@ -1062,14 +1030,6 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testIsProxyTrusted()
|
||||
{
|
||||
$this->startTrustingProxyData();
|
||||
$this->assertTrue(Request::isProxyTrusted());
|
||||
$this->stopTrustingProxyData();
|
||||
$this->assertFalse(Request::isProxyTrusted());
|
||||
}
|
||||
|
||||
public function testIsMethod()
|
||||
{
|
||||
$request = new Request();
|
||||
@ -1188,10 +1148,74 @@ class RequestTest extends \PHPUnit_Framework_TestCase
|
||||
private function stopTrustingProxyData()
|
||||
{
|
||||
$class = new \ReflectionClass('Symfony\\Component\\HttpFoundation\\Request');
|
||||
$property = $class->getProperty('trustProxy');
|
||||
$property = $class->getProperty('trustProxyData');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue(false);
|
||||
}
|
||||
|
||||
public function testTrustedProxies()
|
||||
{
|
||||
$request = Request::create('http://example.com/');
|
||||
$request->server->set('REMOTE_ADDR', '3.3.3.3');
|
||||
$request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2');
|
||||
$request->headers->set('X_FORWARDED_HOST', 'foo.example.com, real.example.com:8080');
|
||||
$request->headers->set('X_FORWARDED_PROTO', 'https');
|
||||
$request->headers->set('X_FORWARDED_PORT', 443);
|
||||
$request->headers->set('X_MY_FOR', '3.3.3.3, 4.4.4.4');
|
||||
$request->headers->set('X_MY_HOST', 'my.example.com');
|
||||
$request->headers->set('X_MY_PROTO', 'http');
|
||||
$request->headers->set('X_MY_PORT', 81);
|
||||
|
||||
// no trusted proxies
|
||||
$this->assertEquals('3.3.3.3', $request->getClientIp());
|
||||
$this->assertEquals('example.com', $request->getHost());
|
||||
$this->assertEquals(80, $request->getPort());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
// trusted proxy via deprecated trustProxyData()
|
||||
Request::trustProxyData();
|
||||
$this->assertEquals('2.2.2.2', $request->getClientIp());
|
||||
$this->assertEquals('real.example.com', $request->getHost());
|
||||
$this->assertEquals(443, $request->getPort());
|
||||
$this->assertTrue($request->isSecure());
|
||||
|
||||
// disabling proxy trusting
|
||||
Request::setTrustedProxies(array());
|
||||
$this->assertEquals('3.3.3.3', $request->getClientIp());
|
||||
$this->assertEquals('example.com', $request->getHost());
|
||||
$this->assertEquals(80, $request->getPort());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
// trusted proxy via setTrustedProxies()
|
||||
Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2'));
|
||||
$this->assertEquals('1.1.1.1', $request->getClientIp());
|
||||
$this->assertEquals('real.example.com', $request->getHost());
|
||||
$this->assertEquals(443, $request->getPort());
|
||||
$this->assertTrue($request->isSecure());
|
||||
|
||||
// custom header names
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_MY_FOR');
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_MY_HOST');
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_MY_PORT');
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_MY_PROTO');
|
||||
$this->assertEquals('4.4.4.4', $request->getClientIp());
|
||||
$this->assertEquals('my.example.com', $request->getHost());
|
||||
$this->assertEquals(81, $request->getPort());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
// disabling via empty header names
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, null);
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, null);
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, null);
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, null);
|
||||
$this->assertEquals('3.3.3.3', $request->getClientIp());
|
||||
$this->assertEquals('example.com', $request->getHost());
|
||||
$this->assertEquals(80, $request->getPort());
|
||||
$this->assertFalse($request->isSecure());
|
||||
|
||||
// reset
|
||||
Request::setTrustedProxies(array());
|
||||
}
|
||||
}
|
||||
|
||||
class RequestContentProxy extends Request
|
||||
|
Reference in New Issue
Block a user