feature #37734 [HttpFoundation] add support for X_FORWARDED_PREFIX header (jeff1985)
This PR was squashed before being merged into the 5.2-dev branch.
Discussion
----------
[HttpFoundation] add support for X_FORWARDED_PREFIX header
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | Fix #36809
| License | MIT
Add support for `X-Forwarded-Prefix` header added by the popular Traefik HTTP LoadBalancer and Reverse Proxy. This ensures that the links rendered by symfony application deployed behind LB are valid even if the application is deployed via prefix URL.
Example routing setup:
route `/admin/(.*)` => symfony backend `/$1`
in this case links rendered by symfony backend must start with `/admin/`
To accept traefik prefix in your symfony app, you must modify index.php to allow accepting this header:
Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_TRAEFIK ^ Request::HEADER_X_FORWARDED_HOST );`
Commits
-------
109e0a9f1a
[HttpFoundation] add support for X_FORWARDED_PREFIX header
This commit is contained in:
commit
c281867227
@ -1,6 +1,11 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.3.0
|
||||
-----
|
||||
|
||||
* added support for `X-Forwarded-Prefix` header
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
|
@ -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 = [];
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user