[HttpFoundation] add support for X_FORWARDED_PREFIX header

This commit is contained in:
Evgeny Anisiforov 2020-08-03 19:33:24 +02:00 committed by Fabien Potencier
parent 2ddacad477
commit 109e0a9f1a
3 changed files with 81 additions and 9 deletions

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
5.3.0
-----
* added support for `X-Forwarded-Prefix` header
5.2.0
-----

View File

@ -39,13 +39,16 @@ class_exists(ServerBag::class);
*/
class Request
{
const HEADER_FORWARDED = 0b00001; // When using RFC 7239
const HEADER_X_FORWARDED_FOR = 0b00010;
const HEADER_X_FORWARDED_HOST = 0b00100;
const HEADER_X_FORWARDED_PROTO = 0b01000;
const HEADER_X_FORWARDED_PORT = 0b10000;
const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers
const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host
const HEADER_FORWARDED = 0b000001; // When using RFC 7239
const HEADER_X_FORWARDED_FOR = 0b000010;
const HEADER_X_FORWARDED_HOST = 0b000100;
const HEADER_X_FORWARDED_PROTO = 0b001000;
const HEADER_X_FORWARDED_PORT = 0b010000;
const HEADER_X_FORWARDED_PREFIX = 0b100000;
const HEADER_X_FORWARDED_ALL = 0b011110; // All "X-Forwarded-*" headers sent by "usual" reverse proxy
const HEADER_X_FORWARDED_AWS_ELB = 0b011010; // AWS ELB doesn't send X-Forwarded-Host
const HEADER_X_FORWARDED_TRAEFIK = 0b111110; // All "X-Forwarded-*" headers sent by Traefik reverse proxy
const METHOD_HEAD = 'HEAD';
const METHOD_GET = 'GET';
@ -237,6 +240,7 @@ class Request
self::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
self::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
self::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX',
];
/**
@ -894,6 +898,24 @@ class Request
* @return string The raw URL (i.e. not urldecoded)
*/
public function getBaseUrl()
{
$trustedPrefix = '';
// the proxy prefix must be prepended to any prefix being needed at the webserver level
if ($this->isFromTrustedProxy() && $trustedPrefixValues = $this->getTrustedValues(self::HEADER_X_FORWARDED_PREFIX)) {
$trustedPrefix = rtrim($trustedPrefixValues[0], '/');
}
return $trustedPrefix.$this->getBaseUrlReal();
}
/**
* Returns the real base URL received by the webserver from which this request is executed.
* The URL does not include trusted reverse proxy prefix.
*
* @return string The raw URL (i.e. not urldecoded)
*/
private function getBaseUrlReal()
{
if (null === $this->baseUrl) {
$this->baseUrl = $this->prepareBaseUrl();
@ -1910,7 +1932,7 @@ class Request
$requestUri = '/'.$requestUri;
}
if (null === ($baseUrl = $this->getBaseUrl())) {
if (null === ($baseUrl = $this->getBaseUrlReal())) {
return $requestUri;
}
@ -2014,7 +2036,7 @@ class Request
}
}
if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && (isset(self::$forwardedParams[$type])) && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
$forwarded = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
$parts = HeaderUtils::split($forwarded, ',;=');
$forwardedValues = [];

View File

@ -2278,6 +2278,51 @@ class RequestTest extends TestCase
$this->assertSame(443, $request->getPort());
}
public function testTrustedPrefix()
{
Request::setTrustedProxies(['1.1.1.1'], Request::HEADER_X_FORWARDED_TRAEFIK);
//test with index deployed under root
$request = Request::create('/method');
$request->server->set('REMOTE_ADDR', '1.1.1.1');
$request->headers->set('X-Forwarded-Prefix', '/myprefix');
$request->headers->set('Forwarded', 'host=localhost:8080');
$this->assertSame('/myprefix', $request->getBaseUrl());
$this->assertSame('/myprefix', $request->getBasePath());
$this->assertSame('/method', $request->getPathInfo());
}
public function testTrustedPrefixWithSubdir()
{
Request::setTrustedProxies(['1.1.1.1'], Request::HEADER_X_FORWARDED_TRAEFIK);
$server = [
'SCRIPT_FILENAME' => '/var/hidden/app/public/public/index.php',
'SCRIPT_NAME' => '/public/index.php',
'PHP_SELF' => '/public/index.php',
];
//test with index file deployed in subdir, i.e. local dev server (insecure!!)
$request = Request::create('/public/method', 'GET', [], [], [], $server);
$request->server->set('REMOTE_ADDR', '1.1.1.1');
$request->headers->set('X-Forwarded-Prefix', '/prefix');
$request->headers->set('Forwarded', 'host=localhost:8080');
$this->assertSame('/prefix/public', $request->getBaseUrl());
$this->assertSame('/prefix/public', $request->getBasePath());
$this->assertSame('/method', $request->getPathInfo());
}
public function testTrustedPrefixEmpty()
{
//check that there is no error, if no prefix is provided
Request::setTrustedProxies(['1.1.1.1'], Request::HEADER_X_FORWARDED_TRAEFIK);
$request = Request::create('/method');
$request->server->set('REMOTE_ADDR', '1.1.1.1');
$this->assertSame('', $request->getBaseUrl());
}
public function testTrustedPort()
{
Request::setTrustedProxies(['1.1.1.1'], -1);