From 8ca387bea3a02f2d9474515a7e3a55fb526518e9 Mon Sep 17 00:00:00 2001 From: Jakub Chabek Date: Tue, 2 Feb 2016 15:35:43 +0100 Subject: [PATCH 1/4] RequestFactory: fix behavior behind proxy Correctly detect scheme and port if the server is behind a trusted proxy. --- src/Http/RequestFactory.php | 43 ++++++++----- tests/Http/RequestFactory.port.phpt | 91 +++++++++++++++++++++++++++ tests/Http/RequestFactory.scheme.phpt | 85 +++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 tests/Http/RequestFactory.port.phpt create mode 100644 tests/Http/RequestFactory.scheme.phpt diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 4489cffd..296a306e 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -62,11 +62,11 @@ public function createHttpRequest() { // DETECTS URI, base path and script path of the request. $url = new UrlScript; - $url->setScheme(!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https' : 'http'); + $url->setScheme(!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') !== 0 ? 'https' : 'http'); $url->setUser(isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : ''); $url->setPassword(isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''); - // host & port + // host and port if ((isset($_SERVER[$tmp = 'HTTP_HOST']) || isset($_SERVER[$tmp = 'SERVER_NAME'])) && preg_match('#^([a-z0-9_.-]+|\[[a-f0-9:]+\])(:\d+)?\z#i', $_SERVER[$tmp], $pair) ) { @@ -76,9 +76,11 @@ public function createHttpRequest() } elseif (isset($_SERVER['SERVER_PORT'])) { $url->setPort((int) $_SERVER['SERVER_PORT']); } + } else if (!empty($_SERVER['SERVER_PORT'])) { + $url->setPort((int) $_SERVER['SERVER_PORT']); } - // path & query + // path and query $requestUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/'; $requestUrl = Strings::replace($requestUrl, $this->urlFilters['url']); $tmp = explode('?', $requestUrl, 2); @@ -185,24 +187,33 @@ public function createHttpRequest() } } + $remoteAddr = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL; + $remoteHost = !empty($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : NULL; - $remoteAddr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL; - $remoteHost = isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : NULL; + // use real client address and host if trusted proxy is used + $usingTrustedProxy = (bool)array_filter($this->proxies, function ($proxy) use ($remoteAddr) { + return $remoteAddr !== NULL && Helpers::ipMatch($remoteAddr, $proxy); + }); - // proxy - foreach ($this->proxies as $proxy) { - if (Helpers::ipMatch($remoteAddr, $proxy)) { - if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $remoteAddr = trim(current(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']))); - } - if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { - $remoteHost = trim(current(explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']))); - } - break; + if ($usingTrustedProxy) { + if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http'); + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); } - } + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $remoteAddr = trim(current(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']))); + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { + $remoteHost = trim(current(explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']))); + } + } + // method, eg. GET, PUT, ... $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : NULL; if ($method === 'POST' && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']) && preg_match('#^[A-Z]+\z#', $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']) diff --git a/tests/Http/RequestFactory.port.phpt b/tests/Http/RequestFactory.port.phpt new file mode 100644 index 00000000..e67aa709 --- /dev/null +++ b/tests/Http/RequestFactory.port.phpt @@ -0,0 +1,91 @@ +createHttpRequest()->getUrl()->getPort()); + } + + /** + * @return array + */ + public function providerCreateHttpRequest() + { + return [ + [80, []], + [8080, ['HTTP_HOST' => 'localhost:8080']], + [8080, ['SERVER_NAME' => 'localhost:8080']], + [8080, ['HTTP_HOST' => 'localhost:8080', 'SERVER_PORT' => '666']], + [8080, ['SERVER_NAME' => 'localhost:8080', 'SERVER_PORT' => '666']], + [8080, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080']], + [8080, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '8080']], + + [80, ['HTTP_X_FORWARDED_PORT' => '8080']], + [8080, ['HTTP_HOST' => 'localhost:8080', 'HTTP_X_FORWARDED_PORT' => '666']], + [8080, ['SERVER_NAME' => 'localhost:8080', 'HTTP_X_FORWARDED_PORT' => '666']], + [8080, ['HTTP_HOST' => 'localhost:8080', 'SERVER_PORT' => '80', 'HTTP_X_FORWARDED_PORT' => '666']], + [8080, ['SERVER_NAME' => 'localhost:8080', 'SERVER_PORT' => '80', 'HTTP_X_FORWARDED_PORT' => '666']], + [80, ['HTTP_HOST' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']], + [80, ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']], + [8080, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], + [8080, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], + [44443, ['HTTPS' => 'on', 'SERVER_PORT' => '44443', 'HTTP_X_FORWARDED_PORT' => '666']], + ]; + } + + /** + * @dataProvider providerCreateHttpRequestWithTrustedProxy + * + * @param string + * @param array + */ + public function testCreateHttpRequestWithTrustedProxy($expectedPort, $server) + { + $_SERVER = array_merge(['REMOTE_ADDR' => '10.0.0.1'], $server); + + $factory = new Nette\Http\RequestFactory; + $factory->setProxy(['10.0.0.1']); + Assert::same($expectedPort, $factory->createHttpRequest()->getUrl()->getPort()); + } + + /** + * @return array + */ + public function providerCreateHttpRequestWithTrustedProxy() + { + return [ + [8080, ['HTTP_X_FORWARDED_PORT' => '8080']], + [8080, ['HTTP_HOST' => 'localhost:666', 'HTTP_X_FORWARDED_PORT' => '8080']], + [8080, ['SERVER_NAME' => 'localhost:666', 'HTTP_X_FORWARDED_PORT' => '8080']], + [8080, ['HTTP_HOST' => 'localhost:666', 'SERVER_PORT' => '80', 'HTTP_X_FORWARDED_PORT' => '8080']], + [8080, ['SERVER_NAME' => 'localhost:666', 'SERVER_PORT' => '80', 'HTTP_X_FORWARDED_PORT' => '8080']], + [8080, ['HTTP_HOST' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '8080']], + [8080, ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '8080']], + [8080, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '666', 'HTTP_X_FORWARDED_PORT' => '8080']], + [8080, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '666', 'HTTP_X_FORWARDED_PORT' => '8080']], + [44443, ['HTTPS' => 'on', 'SERVER_PORT' => '666', 'HTTP_X_FORWARDED_PORT' => '44443']], + ]; + } + +} + +$test = new RequestFactoryPortTest(); +$test->run(); diff --git a/tests/Http/RequestFactory.scheme.phpt b/tests/Http/RequestFactory.scheme.phpt new file mode 100644 index 00000000..0ffca2c8 --- /dev/null +++ b/tests/Http/RequestFactory.scheme.phpt @@ -0,0 +1,85 @@ +createHttpRequest()->getUrl()->getScheme()); + } + + /** + * @return array + */ + public function providerCreateHttpRequest() + { + return [ + ['http', []], + ['http', ['HTTPS' => '']], + ['http', ['HTTPS' => 'off']], + ['http', ['HTTP_X_FORWARDED_PROTO' => 'https']], + ['http', ['HTTP_X_FORWARDED_PORT' => '443']], + ['http', ['HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '443']], + + ['https', ['HTTPS' => 'on']], + ['https', ['HTTPS' => 'anything']], + ['https', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], + ['https', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PORT' => '80']], + ['https', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '80']], + ]; + } + + /** + * @covers RequestFactory::getScheme + * @dataProvider providerCreateHttpRequestWithTrustedProxy + * + * @param string + * @param array + */ + public function testCreateHttpRequestWithTrustedProxy($expectedScheme, $server) + { + $_SERVER = array_merge(['REMOTE_ADDR' => '10.0.0.1'], $server); + + $factory = new Nette\Http\RequestFactory; + $factory->setProxy(['10.0.0.1']); + Assert::same($expectedScheme, $factory->createHttpRequest()->getUrl()->getScheme()); + } + + /** + * @return array + */ + public function providerCreateHttpRequestWithTrustedProxy() + { + return [ + ['http', ['HTTP_X_FORWARDED_PROTO' => 'http']], + ['http', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], + ['http', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'something-unexpected']], + ['http', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '443']], + + ['https', ['HTTP_X_FORWARDED_PROTO' => 'https']], + ['https', ['HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https']], + ['https', ['HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '80']], + ]; + } + +} + +$test = new RequestFactorySchemeTest(); +$test->run(); From 35ef4af6f537f457961397ee404c6b5d104c12ec Mon Sep 17 00:00:00 2001 From: Jakub Chabek Date: Tue, 2 Feb 2016 15:39:56 +0100 Subject: [PATCH 2/4] fixup! RequestFactory: fix behavior behind proxy --- src/Http/RequestFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 296a306e..6c3a075a 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -191,7 +191,7 @@ public function createHttpRequest() $remoteHost = !empty($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : NULL; // use real client address and host if trusted proxy is used - $usingTrustedProxy = (bool)array_filter($this->proxies, function ($proxy) use ($remoteAddr) { + $usingTrustedProxy = (bool) array_filter($this->proxies, function ($proxy) use ($remoteAddr) { return $remoteAddr !== NULL && Helpers::ipMatch($remoteAddr, $proxy); }); From 73ab79f6dbe03691aa860c582eacc0900f85c49e Mon Sep 17 00:00:00 2001 From: Jakub Chabek Date: Wed, 3 Feb 2016 08:47:10 +0100 Subject: [PATCH 3/4] fixup! RequestFactory: fix behavior behind proxy --- src/Http/RequestFactory.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 6c3a075a..7169c2aa 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -191,9 +191,10 @@ public function createHttpRequest() $remoteHost = !empty($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : NULL; // use real client address and host if trusted proxy is used - $usingTrustedProxy = (bool) array_filter($this->proxies, function ($proxy) use ($remoteAddr) { - return $remoteAddr !== NULL && Helpers::ipMatch($remoteAddr, $proxy); - }); + $usingTrustedProxy = $remoteAddr !== NULL + && (bool) array_filter($this->proxies, function ($proxy) use ($remoteAddr) { + return Helpers::ipMatch($remoteAddr, $proxy); + }); if ($usingTrustedProxy) { if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { From fc5798ae5b18daa10cfd8996c62dbb3e705f0794 Mon Sep 17 00:00:00 2001 From: Jakub Chabek Date: Wed, 3 Feb 2016 13:47:22 +0100 Subject: [PATCH 4/4] fixup! RequestFactory: fix behavior behind proxy --- src/Http/RequestFactory.php | 2 -- tests/Http/RequestFactory.port.phpt | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 7169c2aa..925b8bba 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -76,8 +76,6 @@ public function createHttpRequest() } elseif (isset($_SERVER['SERVER_PORT'])) { $url->setPort((int) $_SERVER['SERVER_PORT']); } - } else if (!empty($_SERVER['SERVER_PORT'])) { - $url->setPort((int) $_SERVER['SERVER_PORT']); } // path and query diff --git a/tests/Http/RequestFactory.port.phpt b/tests/Http/RequestFactory.port.phpt index e67aa709..328b78b5 100644 --- a/tests/Http/RequestFactory.port.phpt +++ b/tests/Http/RequestFactory.port.phpt @@ -47,7 +47,7 @@ class RequestFactoryPortTest extends Tester\TestCase [80, ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']], [8080, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], [8080, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], - [44443, ['HTTPS' => 'on', 'SERVER_PORT' => '44443', 'HTTP_X_FORWARDED_PORT' => '666']], + [44443, ['HTTPS' => 'on', 'SERVER_NAME' => 'localhost:44443', 'HTTP_X_FORWARDED_PORT' => '666']], ]; } @@ -81,7 +81,7 @@ class RequestFactoryPortTest extends Tester\TestCase [8080, ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '8080']], [8080, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '666', 'HTTP_X_FORWARDED_PORT' => '8080']], [8080, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '666', 'HTTP_X_FORWARDED_PORT' => '8080']], - [44443, ['HTTPS' => 'on', 'SERVER_PORT' => '666', 'HTTP_X_FORWARDED_PORT' => '44443']], + [44443, ['HTTPS' => 'on', 'SERVER_NAME' => 'localhost:666', 'HTTP_X_FORWARDED_PORT' => '44443']], ]; }