Skip to content

Commit d3a2506

Browse files
jeff1985fabpot
authored andcommitted
[HttpFoundation] add support for X_FORWARDED_PREFIX header
1 parent 6d9784c commit d3a2506

File tree

3 files changed

+81
-9
lines changed

3 files changed

+81
-9
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.3.0
5+
-----
6+
7+
* added support for `X-Forwarded-Prefix` header
8+
49
5.2.0
510
-----
611

Request.php

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,16 @@ class_exists(ServerBag::class);
3939
*/
4040
class Request
4141
{
42-
const HEADER_FORWARDED = 0b00001; // When using RFC 7239
43-
const HEADER_X_FORWARDED_FOR = 0b00010;
44-
const HEADER_X_FORWARDED_HOST = 0b00100;
45-
const HEADER_X_FORWARDED_PROTO = 0b01000;
46-
const HEADER_X_FORWARDED_PORT = 0b10000;
47-
const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers
48-
const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host
42+
const HEADER_FORWARDED = 0b000001; // When using RFC 7239
43+
const HEADER_X_FORWARDED_FOR = 0b000010;
44+
const HEADER_X_FORWARDED_HOST = 0b000100;
45+
const HEADER_X_FORWARDED_PROTO = 0b001000;
46+
const HEADER_X_FORWARDED_PORT = 0b010000;
47+
const HEADER_X_FORWARDED_PREFIX = 0b100000;
48+
49+
const HEADER_X_FORWARDED_ALL = 0b011110; // All "X-Forwarded-*" headers sent by "usual" reverse proxy
50+
const HEADER_X_FORWARDED_AWS_ELB = 0b011010; // AWS ELB doesn't send X-Forwarded-Host
51+
const HEADER_X_FORWARDED_TRAEFIK = 0b111110; // All "X-Forwarded-*" headers sent by Traefik reverse proxy
4952

5053
const METHOD_HEAD = 'HEAD';
5154
const METHOD_GET = 'GET';
@@ -237,6 +240,7 @@ class Request
237240
self::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
238241
self::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
239242
self::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
243+
self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX',
240244
];
241245

242246
/**
@@ -894,6 +898,24 @@ public function getBasePath()
894898
* @return string The raw URL (i.e. not urldecoded)
895899
*/
896900
public function getBaseUrl()
901+
{
902+
$trustedPrefix = '';
903+
904+
// the proxy prefix must be prepended to any prefix being needed at the webserver level
905+
if ($this->isFromTrustedProxy() && $trustedPrefixValues = $this->getTrustedValues(self::HEADER_X_FORWARDED_PREFIX)) {
906+
$trustedPrefix = rtrim($trustedPrefixValues[0], '/');
907+
}
908+
909+
return $trustedPrefix.$this->getBaseUrlReal();
910+
}
911+
912+
/**
913+
* Returns the real base URL received by the webserver from which this request is executed.
914+
* The URL does not include trusted reverse proxy prefix.
915+
*
916+
* @return string The raw URL (i.e. not urldecoded)
917+
*/
918+
private function getBaseUrlReal()
897919
{
898920
if (null === $this->baseUrl) {
899921
$this->baseUrl = $this->prepareBaseUrl();
@@ -1910,7 +1932,7 @@ protected function preparePathInfo()
19101932
$requestUri = '/'.$requestUri;
19111933
}
19121934

1913-
if (null === ($baseUrl = $this->getBaseUrl())) {
1935+
if (null === ($baseUrl = $this->getBaseUrlReal())) {
19141936
return $requestUri;
19151937
}
19161938

@@ -2014,7 +2036,7 @@ private function getTrustedValues(int $type, string $ip = null): array
20142036
}
20152037
}
20162038

2017-
if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
2039+
if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && (isset(self::$forwardedParams[$type])) && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
20182040
$forwarded = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
20192041
$parts = HeaderUtils::split($forwarded, ',;=');
20202042
$forwardedValues = [];

Tests/RequestTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2278,6 +2278,51 @@ public function testTrustedHost()
22782278
$this->assertSame(443, $request->getPort());
22792279
}
22802280

2281+
public function testTrustedPrefix()
2282+
{
2283+
Request::setTrustedProxies(['1.1.1.1'], Request::HEADER_X_FORWARDED_TRAEFIK);
2284+
2285+
//test with index deployed under root
2286+
$request = Request::create('/method');
2287+
$request->server->set('REMOTE_ADDR', '1.1.1.1');
2288+
$request->headers->set('X-Forwarded-Prefix', '/myprefix');
2289+
$request->headers->set('Forwarded', 'host=localhost:8080');
2290+
2291+
$this->assertSame('/myprefix', $request->getBaseUrl());
2292+
$this->assertSame('/myprefix', $request->getBasePath());
2293+
$this->assertSame('/method', $request->getPathInfo());
2294+
}
2295+
2296+
public function testTrustedPrefixWithSubdir()
2297+
{
2298+
Request::setTrustedProxies(['1.1.1.1'], Request::HEADER_X_FORWARDED_TRAEFIK);
2299+
2300+
$server = [
2301+
'SCRIPT_FILENAME' => '/var/hidden/app/public/public/index.php',
2302+
'SCRIPT_NAME' => '/public/index.php',
2303+
'PHP_SELF' => '/public/index.php',
2304+
];
2305+
2306+
//test with index file deployed in subdir, i.e. local dev server (insecure!!)
2307+
$request = Request::create('/public/method', 'GET', [], [], [], $server);
2308+
$request->server->set('REMOTE_ADDR', '1.1.1.1');
2309+
$request->headers->set('X-Forwarded-Prefix', '/prefix');
2310+
$request->headers->set('Forwarded', 'host=localhost:8080');
2311+
2312+
$this->assertSame('/prefix/public', $request->getBaseUrl());
2313+
$this->assertSame('/prefix/public', $request->getBasePath());
2314+
$this->assertSame('/method', $request->getPathInfo());
2315+
}
2316+
2317+
public function testTrustedPrefixEmpty()
2318+
{
2319+
//check that there is no error, if no prefix is provided
2320+
Request::setTrustedProxies(['1.1.1.1'], Request::HEADER_X_FORWARDED_TRAEFIK);
2321+
$request = Request::create('/method');
2322+
$request->server->set('REMOTE_ADDR', '1.1.1.1');
2323+
$this->assertSame('', $request->getBaseUrl());
2324+
}
2325+
22812326
public function testTrustedPort()
22822327
{
22832328
Request::setTrustedProxies(['1.1.1.1'], -1);

0 commit comments

Comments
 (0)