diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 4489cffd..925b8bba 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) ) { @@ -78,7 +78,7 @@ public function createHttpRequest() } } - // 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 +185,34 @@ 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 = $remoteAddr !== NULL + && (bool) array_filter($this->proxies, function ($proxy) use ($remoteAddr) { + return 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..328b78b5 --- /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_NAME' => 'localhost: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_NAME' => 'localhost: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();