Skip to content

Commit

Permalink
added a way to configure the X-Forwarded-XXX header names and a way t…
Browse files Browse the repository at this point in the history
…o disable trusting them
  • Loading branch information
fabpot committed Nov 29, 2012
1 parent b45873a commit 67e12f3
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 9 deletions.
79 changes: 73 additions & 6 deletions src/Symfony/Component/HttpFoundation/Request.php
Expand Up @@ -24,6 +24,20 @@ class Request

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(
'client_ip' => 'X_FORWARDED_FOR',
'client_host' => 'X_FORWARDED_HOST',
'client_proto' => 'X_FORWARDED_PROTO',
'client_port' => 'X_FORWARDED_PORT',
);

/**
* @var \Symfony\Component\HttpFoundation\ParameterBag
*
Expand Down Expand Up @@ -381,6 +395,30 @@ public static function setTrustedProxies(array $proxies)
self::$trustProxyData = $proxies ? true : false;
}

/**
* Sets the name for trusted headers.
*
* The following header keys are supported:
*
* * client_ip: defaults to X-Forwarded-For (see getClientIp())
* * client_host: defaults to X-Forwarded-Host (see getClientHost())
* * client_port: defaults to X-Forwarded-Port (see getClientPort())
* * 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;
}

/**
* Gets a "parameter" value.
*
Expand Down Expand Up @@ -461,6 +499,10 @@ public function setSession(Session $session)
* 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.
*
* @param Boolean $proxy Whether the current request has been made behind a proxy or not (deprecated)
*
* @return string The client IP address
Expand All @@ -479,11 +521,11 @@ public function getClientIp($proxy = false)
return $ip;
}

if (!$this->server->has('HTTP_X_FORWARDED_FOR')) {
if (!self::$trustedHeaders['client_ip'] || !$this->headers->has(self::$trustedHeaders['client_ip'])) {
return $ip;
}

$clientIps = array_map('trim', explode(',', $this->server->get('HTTP_X_FORWARDED_FOR')));
$clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders['client_ip'])));
$clientIps[] = $ip;

$trustedProxies = ($proxy || self::$trustProxyData) && !self::$trustedProxies ? array($ip) : self::$trustedProxies;
Expand Down Expand Up @@ -586,14 +628,22 @@ public function getScheme()
/**
* 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::$trustProxyData && $this->headers->has('X-Forwarded-Port')) {
return $this->headers->get('X-Forwarded-Port');
if (self::$trustProxyData && self::$trustedHeaders['client_port'] && $port = $this->headers->get(self::$trustedHeaders['client_port'])) {
return $port;
}

return $this->server->get('SERVER_PORT');
Expand Down Expand Up @@ -706,6 +756,15 @@ public function getQueryString()
/**
* 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
Expand All @@ -717,20 +776,28 @@ public function isSecure()
||
(self::$trustProxyData && strtolower($this->headers->get('SSL_HTTPS')) == 'on' || $this->headers->get('SSL_HTTPS') == 1)
||
(self::$trustProxyData && strtolower($this->headers->get('X_FORWARDED_PROTO')) == 'https')
(self::$trustProxyData && self::$trustedHeaders['client_proto'] && strtolower($this->headers->get(self::$trustedHeaders['client_proto'])) == '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::$trustProxyData && $host = $this->headers->get('X_FORWARDED_HOST')) {
if (self::$trustProxyData && self::$trustedHeaders['client_host'] && $host = $this->headers->get(self::$trustedHeaders['client_host'])) {
$elements = explode(',', $host);

$host = trim($elements[count($elements) - 1]);
Expand Down
41 changes: 38 additions & 3 deletions tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php
Expand Up @@ -433,6 +433,9 @@ public function testGetHost()
$request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.exemple.com'));
$this->assertEquals('www.exemple.com', $request->getHost(), '->getHost() from 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 ');

Request::setTrustedProxies(array('1.1.1.1'));

// X_FORWARDED_HOST
Expand All @@ -453,10 +456,22 @@ public function testGetHost()
$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 ');
// custom X_FORWARDED_HOST header name
Request::setTrustedHeaderName('client_host', 'X_MY_HOST');
$request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.exemple.com', 'HTTP_X_MY_HOST' => 'www.forward.com'));
$this->assertEquals('www.forward.com', $request->getHost(), '->getHost() value from custom header name has priority over SERVER_NAME ');

// X_FORWARDED_HOST ignored when custom header name is empty
Request::setTrustedHeaderName('client_host', null);
$request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.exemple.com', 'HTTP_X_FORWARDED_HOST' => 'www.forward.com'));
$this->assertEquals('www.exemple.com', $request->getHost(), '->getHost() value from X_FORWARDED_HOST has priority over SERVER_NAME ');

Request::setTrustedHeaderName('client_host', 'X_FORWARDED_HOST');
Request::setTrustedProxies(array());

// X_FORWARDED_HOST ignored when no trusted proxies
$request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.exemple.com', 'HTTP_X_FORWARDED_HOST' => 'www.forward.com'));
$this->assertEquals('www.exemple.com', $request->getHost(), '->getHost() value from X_FORWARDED_HOST has priority over SERVER_NAME ');
}

/**
Expand Down Expand Up @@ -510,6 +525,8 @@ public function testGetClientIp($expected, $proxy, $remoteAddr, $httpForwardedFo

$request->initialize(array(), array(), array(), array(), array(), $server);
$this->assertEquals($expected, $request->getClientIp($proxy));

Request::setTrustedProxies(array());
}

public function testGetClientIpProvider()
Expand Down Expand Up @@ -619,7 +636,9 @@ public function testOverrideGlobals()

$request->headers->set('X_FORWARDED_PROTO', 'https');

Request::setTrustedProxies(array('1.1.1.1'));
$this->assertTrue($request->isSecure());
Request::setTrustedProxies(array());

$request->overrideGlobals();

Expand Down Expand Up @@ -813,12 +832,28 @@ public function testGetRequestFormat()

public function testForwardedSecure()
{
$request = new Request();
$request = Request::create('http://test.com/');
$request->headers->set('X-Forwarded-Proto', 'https');
$request->headers->set('X-Forwarded-Port', 443);

$this->assertFalse($request->isSecure());
$this->assertEquals(80, $request->getPort());

Request::setTrustedProxies(array('1.1.1.1'));

$this->assertTrue($request->isSecure());
$this->assertEquals(443, $request->getPort());

// custom header names
Request::setTrustedHeaderName('client_proto', 'X-My-Proto');
Request::setTrustedHeaderName('client_port', 'X-My-Port');
$request->headers->set('X-My-Proto', 'http');
$request->headers->set('X-My-Port', 81);

$this->assertFalse($request->isSecure());
$this->assertEquals(81, $request->getPort());

Request::setTrustedProxies(array());
}

public function testHasSession()
Expand Down

0 comments on commit 67e12f3

Please sign in to comment.