From 109e0a9f1ae68eb8d9da69b582c5d5f2f026873d Mon Sep 17 00:00:00 2001 From: Evgeny Anisiforov Date: Mon, 3 Aug 2020 19:33:24 +0200 Subject: [PATCH] [HttpFoundation] add support for X_FORWARDED_PREFIX header --- .../Component/HttpFoundation/CHANGELOG.md | 5 +++ .../Component/HttpFoundation/Request.php | 40 +++++++++++++---- .../HttpFoundation/Tests/RequestTest.php | 45 +++++++++++++++++++ 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index a3aaa04d6f..f36d045e67 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3.0 +----- + +* added support for `X-Forwarded-Prefix` header + 5.2.0 ----- diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index ce1e779eaa..2fccdedb7e 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -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 = []; diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 8986be52c7..358d2b140a 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -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);