From 983858e3a38ef5c7920ec780131cb0cb84ddb995 Mon Sep 17 00:00:00 2001 From: jaf2bj Date: Wed, 1 Mar 2023 14:00:07 +0100 Subject: [PATCH 1/9] added getScheme method --- src/Http/RequestFactory.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 1656976a..afd433c6 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -75,10 +75,19 @@ public function fromGlobals(): Request ); } + private function getScheme(): string + { + return + (!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off')) || + (!empty($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https') || + (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') + ? 'https' + : 'http'; + } private function getServer(Url $url): void { - $url->setScheme(!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https' : 'http'); + $url->setScheme($this->getScheme()); if ( (isset($_SERVER[$tmp = 'HTTP_HOST']) || isset($_SERVER[$tmp = 'SERVER_NAME'])) From 83ff0531c7cccea6644f737528157613927bdfa7 Mon Sep 17 00:00:00 2001 From: jaf2bj Date: Fri, 3 Mar 2023 10:55:11 +0100 Subject: [PATCH 2/9] changed tests, added forwarded port --- src/Http/RequestFactory.php | 4 +++- tests/Http/RequestFactory.port.phpt | 8 ++++---- tests/Http/RequestFactory.scheme.phpt | 5 +++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index afd433c6..49289195 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -96,7 +96,9 @@ private function getServer(Url $url): void $url->setHost(rtrim(strtolower($pair[1]), '.')); if (isset($pair[2])) { $url->setPort((int) substr($pair[2], 1)); - } elseif (isset($_SERVER['SERVER_PORT'])) { + } elseif (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); + } elseif (isset($_SERVER['SERVER_PORT'])) { $url->setPort((int) $_SERVER['SERVER_PORT']); } } diff --git a/tests/Http/RequestFactory.port.phpt b/tests/Http/RequestFactory.port.phpt index 6223f189..4b7fd433 100644 --- a/tests/Http/RequestFactory.port.phpt +++ b/tests/Http/RequestFactory.port.phpt @@ -39,10 +39,10 @@ class RequestFactoryPortTest extends Tester\TestCase [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']], + [666, ['HTTP_HOST' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']], + [666, ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']], + [666, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], + [666, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], [44443, ['HTTPS' => 'on', 'SERVER_NAME' => 'localhost:44443', 'HTTP_X_FORWARDED_PORT' => '666']], ]; } diff --git a/tests/Http/RequestFactory.scheme.phpt b/tests/Http/RequestFactory.scheme.phpt index 30f04d9b..5cfab0fb 100644 --- a/tests/Http/RequestFactory.scheme.phpt +++ b/tests/Http/RequestFactory.scheme.phpt @@ -34,14 +34,15 @@ class RequestFactorySchemeTest extends Tester\TestCase ['http', ['SERVER_NAME' => 'localhost:80']], ['http', ['SERVER_NAME' => 'localhost:80', 'HTTPS' => '']], ['http', ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off']], - ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https']], + ['https', ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PROTO' => 'https']], ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PORT' => '443']], - ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '443']], + ['https', ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '443']], ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on']], ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'anything']], ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PORT' => '80']], ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '80']], + ['https', ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '443']] ]; } From 30d4bfd44fadbdb9027668cc439d5be388a8b0ca Mon Sep 17 00:00:00 2001 From: jaf2bj Date: Fri, 3 Mar 2023 10:56:25 +0100 Subject: [PATCH 3/9] changed tests, added forwarded port --- tests/Http/RequestFactory.scheme.phpt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Http/RequestFactory.scheme.phpt b/tests/Http/RequestFactory.scheme.phpt index 5cfab0fb..9ef37600 100644 --- a/tests/Http/RequestFactory.scheme.phpt +++ b/tests/Http/RequestFactory.scheme.phpt @@ -41,8 +41,7 @@ class RequestFactorySchemeTest extends Tester\TestCase ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'anything']], ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PORT' => '80']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '80']], - ['https', ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '443']] + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '80']] ]; } From 58ccf9f8fbbd7c6407408a4ce58bc9f9f005cb36 Mon Sep 17 00:00:00 2001 From: jaf2bj Date: Thu, 9 Mar 2023 16:13:19 +0100 Subject: [PATCH 4/9] rewritten PR --- src/Http/RequestFactory.php | 722 +++++++++++++------------- tests/Http/RequestFactory.port.phpt | 116 +++-- tests/Http/RequestFactory.scheme.phpt | 128 ++--- 3 files changed, 480 insertions(+), 486 deletions(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 49289195..03b2b2fc 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -10,6 +10,7 @@ namespace Nette\Http; use Nette; +use Nette\Utils\Arrays; use Nette\Utils\Strings; @@ -18,372 +19,363 @@ */ class RequestFactory { - use Nette\SmartObject; - - /** @internal */ - private const ValidChars = '\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}'; - - public array $urlFilters = [ - 'path' => ['#//#' => '/'], // '%20' => '' - 'url' => [], // '#[.,)]$#D' => '' - ]; - - private bool $binary = false; - - /** @var string[] */ - private array $proxies = []; - - - public function setBinary(bool $binary = true): static - { - $this->binary = $binary; - return $this; - } - - - /** - * @param string|string[] $proxy - */ - public function setProxy($proxy): static - { - $this->proxies = (array) $proxy; - return $this; - } - - - /** - * Returns new Request instance, using values from superglobals. - */ - public function fromGlobals(): Request - { - $url = new Url; - $this->getServer($url); - $this->getPathAndQuery($url); - [$post, $cookies] = $this->getGetPostCookie($url); - [$remoteAddr, $remoteHost] = $this->getClient($url); - - return new Request( - new UrlScript($url, $this->getScriptPath($url)), - $post, - $this->getFiles(), - $cookies, - $this->getHeaders(), - $this->getMethod(), - $remoteAddr, - $remoteHost, - fn(): string => file_get_contents('php://input') - ); - } - - private function getScheme(): string + use Nette\SmartObject; + + /** @internal */ + private const ValidChars = '\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}'; + + public array $urlFilters = [ + 'path' => ['#//#' => '/'], // '%20' => '' + 'url' => [], // '#[.,)]$#D' => '' + ]; + + private bool $binary = false; + + /** @var string[] */ + private array $proxies = []; + + + public function setBinary(bool $binary = true): static { - return - (!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off')) || - (!empty($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https') || - (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') - ? 'https' - : 'http'; + $this->binary = $binary; + return $this; } - private function getServer(Url $url): void - { - $url->setScheme($this->getScheme()); - - if ( - (isset($_SERVER[$tmp = 'HTTP_HOST']) || isset($_SERVER[$tmp = 'SERVER_NAME'])) - && preg_match('#^([a-z0-9_.-]+|\[[a-f0-9:]+\])(:\d+)?$#Di', $_SERVER[$tmp], $pair) - ) { - $url->setHost(rtrim(strtolower($pair[1]), '.')); - if (isset($pair[2])) { - $url->setPort((int) substr($pair[2], 1)); - } elseif (isset($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); + + /** + * @param string|string[] $proxy + */ + public function setProxy($proxy): static + { + $this->proxies = (array) $proxy; + return $this; + } + + + /** + * Returns new Request instance, using values from superglobals. + */ + public function fromGlobals(): Request + { + $url = new Url; + $this->getServer($url); + $this->getPathAndQuery($url); + [$post, $cookies] = $this->getGetPostCookie($url); + [$remoteAddr, $remoteHost] = $this->getClient($url); + + return new Request( + new UrlScript($url, $this->getScriptPath($url)), + $post, + $this->getFiles(), + $cookies, + $this->getHeaders(), + $this->getMethod(), + $remoteAddr, + $remoteHost, + fn(): string => file_get_contents('php://input') + ); + } + + + private function getServer(Url $url): void + { + $url->setScheme(!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https' : 'http'); + + if ( + (isset($_SERVER[$tmp = 'HTTP_HOST']) || isset($_SERVER[$tmp = 'SERVER_NAME'])) + && preg_match('#^([a-z0-9_.-]+|\[[a-f0-9:]+\])(:\d+)?$#Di', $_SERVER[$tmp], $pair) + ) { + $url->setHost(rtrim(strtolower($pair[1]), '.')); + if (isset($pair[2])) { + $url->setPort((int) substr($pair[2], 1)); } elseif (isset($_SERVER['SERVER_PORT'])) { - $url->setPort((int) $_SERVER['SERVER_PORT']); - } - } - } - - - private function getPathAndQuery(Url $url): void - { - $requestUrl = $_SERVER['REQUEST_URI'] ?? '/'; - $requestUrl = preg_replace('#^\w++://[^/]++#', '', $requestUrl); - $requestUrl = Strings::replace($requestUrl, $this->urlFilters['url']); - - $tmp = explode('?', $requestUrl, 2); - $path = Url::unescape($tmp[0], '%/?#'); - $path = Strings::fixEncoding(Strings::replace($path, $this->urlFilters['path'])); - $url->setPath($path); - $url->setQuery($tmp[1] ?? ''); - } - - - private function getScriptPath(Url $url): string - { - if (PHP_SAPI === 'cli-server') { - return '/'; - } - - $path = $url->getPath(); - $lpath = strtolower($path); - $script = strtolower($_SERVER['SCRIPT_NAME'] ?? ''); - if ($lpath !== $script) { - $max = min(strlen($lpath), strlen($script)); - for ($i = 0; $i < $max && $lpath[$i] === $script[$i]; $i++); - $path = $i - ? substr($path, 0, strrpos($path, '/', $i - strlen($path) - 1) + 1) - : '/'; - } - - return $path; - } - - - private function getGetPostCookie(Url $url): array - { - $useFilter = (!in_array((string) ini_get('filter.default'), ['', 'unsafe_raw'], true) || ini_get('filter.default_flags')); - - $query = $url->getQueryParameters(); - $post = $useFilter - ? filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW) - : (empty($_POST) ? [] : $_POST); - $cookies = $useFilter - ? filter_input_array(INPUT_COOKIE, FILTER_UNSAFE_RAW) - : (empty($_COOKIE) ? [] : $_COOKIE); - - // remove invalid characters - $reChars = '#^[' . self::ValidChars . ']*+$#Du'; - if (!$this->binary) { - $list = [&$query, &$post, &$cookies]; - foreach ($list as $key => &$val) { - foreach ($val as $k => $v) { - if (is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { - unset($list[$key][$k]); - - } elseif (is_array($v)) { - $list[$key][$k] = $v; - $list[] = &$list[$key][$k]; - - } elseif (is_string($v)) { - $list[$key][$k] = (string) preg_replace('#[^' . self::ValidChars . ']+#u', '', $v); - - } else { - throw new Nette\InvalidStateException(sprintf('Invalid value in $_POST/$_COOKIE in key %s, expected string, %s given.', "'$k'", gettype($v))); - } - } - } - - unset($list, $key, $val, $k, $v); - } - - $url->setQuery($query); - return [$post, $cookies]; - } - - - private function getFiles(): array - { - $reChars = '#^[' . self::ValidChars . ']*+$#Du'; - $files = []; - $list = []; - foreach ($_FILES ?? [] as $k => $v) { - if ( - !is_array($v) - || !isset($v['name'], $v['type'], $v['size'], $v['tmp_name'], $v['error']) - || (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) - ) { - continue; - } - - $v['@'] = &$files[$k]; - $list[] = $v; - } - - // create FileUpload objects - foreach ($list as &$v) { - if (!isset($v['name'])) { - continue; - - } elseif (!is_array($v['name'])) { - if (!$this->binary && (!preg_match($reChars, $v['name']) || preg_last_error())) { - $v['name'] = ''; - } - - if ($v['error'] !== UPLOAD_ERR_NO_FILE) { - $v['@'] = new FileUpload($v); - } - - continue; - } - - foreach ($v['name'] as $k => $foo) { - if (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { - continue; - } - - $list[] = [ - 'name' => $v['name'][$k], - 'type' => $v['type'][$k], - 'size' => $v['size'][$k], - 'full_path' => $v['full_path'][$k] ?? null, - 'tmp_name' => $v['tmp_name'][$k], - 'error' => $v['error'][$k], - '@' => &$v['@'][$k], - ]; - } - } - - return $files; - } - - - private function getHeaders(): array - { - if (function_exists('apache_request_headers')) { - $headers = apache_request_headers(); - } else { - $headers = []; - foreach ($_SERVER as $k => $v) { - if (strncmp($k, 'HTTP_', 5) === 0) { - $k = substr($k, 5); - } elseif (strncmp($k, 'CONTENT_', 8)) { - continue; - } - - $headers[strtr($k, '_', '-')] = $v; - } - } - - if (!isset($headers['Authorization'])) { - if (isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) { - $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW']); - } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { - $headers['Authorization'] = 'Digest ' . $_SERVER['PHP_AUTH_DIGEST']; - } - } - - return $headers; - } - - - private function getMethod(): string - { - $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; - if ( - $method === 'POST' - && preg_match('#^[A-Z]+$#D', $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? '') - ) { - $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; - } - - return $method; - } - - - private function getClient(Url $url): array - { - $remoteAddr = !empty($_SERVER['REMOTE_ADDR']) - ? $_SERVER['REMOTE_ADDR'] - : null; - $remoteHost = !empty($_SERVER['REMOTE_HOST']) - ? $_SERVER['REMOTE_HOST'] - : null; - - // use real client address and host if trusted proxy is used - $usingTrustedProxy = $remoteAddr && array_filter($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($remoteAddr, $proxy)); - if ($usingTrustedProxy) { - empty($_SERVER['HTTP_FORWARDED']) - ? $this->useNonstandardProxy($url, $remoteAddr, $remoteHost) - : $this->useForwardedProxy($url, $remoteAddr, $remoteHost); - } - - return [$remoteAddr, $remoteHost]; - } - - - private function useForwardedProxy(Url $url, &$remoteAddr, &$remoteHost): void - { - $forwardParams = preg_split('/[,;]/', $_SERVER['HTTP_FORWARDED']); - foreach ($forwardParams as $forwardParam) { - [$key, $value] = explode('=', $forwardParam, 2) + [1 => '']; - $proxyParams[strtolower(trim($key))][] = trim($value, " \t\""); - } - - if (isset($proxyParams['for'])) { - $address = $proxyParams['for'][0]; - $remoteAddr = str_contains($address, '[') - ? substr($address, 1, strpos($address, ']') - 1) // IPv6 - : explode(':', $address)[0]; // IPv4 - } - - if (isset($proxyParams['host']) && count($proxyParams['host']) === 1) { - $host = $proxyParams['host'][0]; - $startingDelimiterPosition = strpos($host, '['); - if ($startingDelimiterPosition === false) { //IPv4 - $remoteHostArr = explode(':', $host); - $remoteHost = $remoteHostArr[0]; - $url->setHost($remoteHost); - if (isset($remoteHostArr[1])) { - $url->setPort((int) $remoteHostArr[1]); - } - } else { //IPv6 - $endingDelimiterPosition = strpos($host, ']'); - $remoteHost = substr($host, strpos($host, '[') + 1, $endingDelimiterPosition - 1); - $url->setHost($remoteHost); - $remoteHostArr = explode(':', substr($host, $endingDelimiterPosition)); - if (isset($remoteHostArr[1])) { - $url->setPort((int) $remoteHostArr[1]); - } - } - } - - $scheme = (isset($proxyParams['proto']) && count($proxyParams['proto']) === 1) - ? $proxyParams['proto'][0] - : 'http'; - $url->setScheme(strcasecmp($scheme, 'https') === 0 ? 'https' : 'http'); - } - - - private function useNonstandardProxy(Url $url, &$remoteAddr, &$remoteHost): void - { - if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { - $url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http'); - $url->setPort($url->getScheme() === 'https' ? 443 : 80); - } - - if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); - } - - if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $xForwardedForWithoutProxies = array_filter( - explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), - fn(string $ip): bool => !array_filter( - $this->proxies, - fn(string $proxy): bool => filter_var(trim($ip), FILTER_VALIDATE_IP) !== false && Helpers::ipMatch(trim($ip), $proxy), - ), - ); - if ($xForwardedForWithoutProxies) { - $remoteAddr = trim(end($xForwardedForWithoutProxies)); - $xForwardedForRealIpKey = key($xForwardedForWithoutProxies); - } - } - - if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { - $xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); - if (isset($xForwardedHost[$xForwardedForRealIpKey])) { - $remoteHost = trim($xForwardedHost[$xForwardedForRealIpKey]); - $url->setHost($remoteHost); - } - } - } - - - /** @deprecated use fromGlobals() */ - public function createHttpRequest(): Request - { - trigger_error(__METHOD__ . '() is deprecated, use fromGlobals()', E_USER_DEPRECATED); - return $this->fromGlobals(); - } -} + $url->setPort((int) $_SERVER['SERVER_PORT']); + } + } + } + + + private function getPathAndQuery(Url $url): void + { + $requestUrl = $_SERVER['REQUEST_URI'] ?? '/'; + $requestUrl = preg_replace('#^\w++://[^/]++#', '', $requestUrl); + $requestUrl = Strings::replace($requestUrl, $this->urlFilters['url']); + + $tmp = explode('?', $requestUrl, 2); + $path = Url::unescape($tmp[0], '%/?#'); + $path = Strings::fixEncoding(Strings::replace($path, $this->urlFilters['path'])); + $url->setPath($path); + $url->setQuery($tmp[1] ?? ''); + } + + + private function getScriptPath(Url $url): string + { + if (PHP_SAPI === 'cli-server') { + return '/'; + } + + $path = $url->getPath(); + $lpath = strtolower($path); + $script = strtolower($_SERVER['SCRIPT_NAME'] ?? ''); + if ($lpath !== $script) { + $max = min(strlen($lpath), strlen($script)); + for ($i = 0; $i < $max && $lpath[$i] === $script[$i]; $i++); + $path = $i + ? substr($path, 0, strrpos($path, '/', $i - strlen($path) - 1) + 1) + : '/'; + } + + return $path; + } + + + private function getGetPostCookie(Url $url): array + { + $useFilter = (!in_array((string) ini_get('filter.default'), ['', 'unsafe_raw'], true) || ini_get('filter.default_flags')); + + $query = $url->getQueryParameters(); + $post = $useFilter + ? filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW) + : (empty($_POST) ? [] : $_POST); + $cookies = $useFilter + ? filter_input_array(INPUT_COOKIE, FILTER_UNSAFE_RAW) + : (empty($_COOKIE) ? [] : $_COOKIE); + + // remove invalid characters + $reChars = '#^[' . self::ValidChars . ']*+$#Du'; + if (!$this->binary) { + $list = [&$query, &$post, &$cookies]; + foreach ($list as $key => &$val) { + foreach ($val as $k => $v) { + if (is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { + unset($list[$key][$k]); + + } elseif (is_array($v)) { + $list[$key][$k] = $v; + $list[] = &$list[$key][$k]; + + } elseif (is_string($v)) { + $list[$key][$k] = (string) preg_replace('#[^' . self::ValidChars . ']+#u', '', $v); + + } else { + throw new Nette\InvalidStateException(sprintf('Invalid value in $_POST/$_COOKIE in key %s, expected string, %s given.', "'$k'", gettype($v))); + } + } + } + + unset($list, $key, $val, $k, $v); + } + + $url->setQuery($query); + return [$post, $cookies]; + } + + + private function getFiles(): array + { + $reChars = '#^[' . self::ValidChars . ']*+$#Du'; + $files = []; + $list = []; + foreach ($_FILES ?? [] as $k => $v) { + if ( + !is_array($v) + || !isset($v['name'], $v['type'], $v['size'], $v['tmp_name'], $v['error']) + || (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) + ) { + continue; + } + + $v['@'] = &$files[$k]; + $list[] = $v; + } + + // create FileUpload objects + foreach ($list as &$v) { + if (!isset($v['name'])) { + continue; + + } elseif (!is_array($v['name'])) { + if (!$this->binary && (!preg_match($reChars, $v['name']) || preg_last_error())) { + $v['name'] = ''; + } + + if ($v['error'] !== UPLOAD_ERR_NO_FILE) { + $v['@'] = new FileUpload($v); + } + + continue; + } + + foreach ($v['name'] as $k => $foo) { + if (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { + continue; + } + + $list[] = [ + 'name' => $v['name'][$k], + 'type' => $v['type'][$k], + 'size' => $v['size'][$k], + 'full_path' => $v['full_path'][$k] ?? null, + 'tmp_name' => $v['tmp_name'][$k], + 'error' => $v['error'][$k], + '@' => &$v['@'][$k], + ]; + } + } + + return $files; + } + + + private function getHeaders(): array + { + if (function_exists('apache_request_headers')) { + $headers = apache_request_headers(); + } else { + $headers = []; + foreach ($_SERVER as $k => $v) { + if (strncmp($k, 'HTTP_', 5) === 0) { + $k = substr($k, 5); + } elseif (strncmp($k, 'CONTENT_', 8)) { + continue; + } + + $headers[strtr($k, '_', '-')] = $v; + } + } + + if (!isset($headers['Authorization'])) { + if (isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) { + $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW']); + } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = 'Digest ' . $_SERVER['PHP_AUTH_DIGEST']; + } + } + + return $headers; + } + + + private function getMethod(): string + { + $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; + if ( + $method === 'POST' + && preg_match('#^[A-Z]+$#D', $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? '') + ) { + $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; + } + + return $method; + } + + + private function getClient(Url $url): array + { + $remoteAddr = !empty($_SERVER['REMOTE_ADDR']) + ? $_SERVER['REMOTE_ADDR'] + : null; + $remoteHost = !empty($_SERVER['REMOTE_HOST']) + ? $_SERVER['REMOTE_HOST'] + : null; + + // use real client address and host if trusted proxy is used + $usingTrustedProxy = $remoteAddr && Arrays::some($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($remoteAddr, $proxy)); + if ($usingTrustedProxy) { + empty($_SERVER['HTTP_FORWARDED']) + ? $this->useNonstandardProxy($url, $remoteAddr, $remoteHost) + : $this->useForwardedProxy($url, $remoteAddr, $remoteHost); + } + + return [$remoteAddr, $remoteHost]; + } + + + private function useForwardedProxy(Url $url, &$remoteAddr, &$remoteHost): void + { + $forwardParams = preg_split('/[,;]/', $_SERVER['HTTP_FORWARDED']); + foreach ($forwardParams as $forwardParam) { + [$key, $value] = explode('=', $forwardParam, 2) + [1 => '']; + $proxyParams[strtolower(trim($key))][] = trim($value, " \t\""); + } + + if (isset($proxyParams['for'])) { + $address = $proxyParams['for'][0]; + $remoteAddr = str_contains($address, '[') + ? substr($address, 1, strpos($address, ']') - 1) // IPv6 + : explode(':', $address)[0]; // IPv4 + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); + } + + if (isset($proxyParams['host']) && count($proxyParams['host']) === 1) { + $host = $proxyParams['host'][0]; + $startingDelimiterPosition = strpos($host, '['); + if ($startingDelimiterPosition === false) { //IPv4 + $remoteHostArr = explode(':', $host); + $remoteHost = $remoteHostArr[0]; + $url->setHost($remoteHost); + if (isset($remoteHostArr[1])) { + $url->setPort((int) $remoteHostArr[1]); + } + } else { //IPv6 + $endingDelimiterPosition = strpos($host, ']'); + $remoteHost = substr($host, strpos($host, '[') + 1, $endingDelimiterPosition - 1); + $url->setHost($remoteHost); + $remoteHostArr = explode(':', substr($host, $endingDelimiterPosition)); + if (isset($remoteHostArr[1])) { + $url->setPort((int) $remoteHostArr[1]); + } + } + } + + $scheme = (isset($proxyParams['proto']) && count($proxyParams['proto']) === 1) + ? $proxyParams['proto'][0] + : 'http'; + $url->setScheme(strcasecmp($scheme, 'https') === 0 ? 'https' : 'http'); + } + + + private function useNonstandardProxy(Url $url, &$remoteAddr, &$remoteHost): void + { + if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http'); + $url->setPort($url->getScheme() === 'https' ? 443 : 80); + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $xForwardedForWithoutProxies = array_filter( + explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), + fn(string $ip): bool => filter_var($ip = trim($ip), FILTER_VALIDATE_IP) === false + || !Arrays::some($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($ip, $proxy)), + ); + if ($xForwardedForWithoutProxies) { + $remoteAddr = trim(end($xForwardedForWithoutProxies)); + $xForwardedForRealIpKey = key($xForwardedForWithoutProxies); + } + } + + if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { + $xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); + if (isset($xForwardedHost[$xForwardedForRealIpKey])) { + $remoteHost = trim($xForwardedHost[$xForwardedForRealIpKey]); + $url->setHost($remoteHost); + } + } + } + + + /** @deprecated use fromGlobals() */ + public function createHttpRequest(): Request + { + trigger_error(__METHOD__ . '() is deprecated, use fromGlobals()', E_USER_DEPRECATED); + return $this->fromGlobals(); + } +} \ No newline at end of file diff --git a/tests/Http/RequestFactory.port.phpt b/tests/Http/RequestFactory.port.phpt index 4b7fd433..e7bd612e 100644 --- a/tests/Http/RequestFactory.port.phpt +++ b/tests/Http/RequestFactory.port.phpt @@ -11,72 +11,74 @@ require __DIR__ . '/../bootstrap.php'; */ class RequestFactoryPortTest extends Tester\TestCase { - /** - * @dataProvider providerCreateHttpRequest - */ - public function testCreateHttpRequest($expectedPort, array $server) - { - $_SERVER = $server; + /** + * @dataProvider providerCreateHttpRequest + */ + public function testCreateHttpRequest($expectedPort, array $server) + { + $_SERVER = $server; - $factory = new Nette\Http\RequestFactory; - Assert::same($expectedPort, $factory->fromGlobals()->getUrl()->getPort()); - } + $factory = new Nette\Http\RequestFactory; + Assert::same($expectedPort, $factory->fromGlobals()->getUrl()->getPort()); + } - public function providerCreateHttpRequest(): array - { - 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']], + public function providerCreateHttpRequest(): array + { + 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']], - [666, ['HTTP_HOST' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']], - [666, ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']], - [666, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], - [666, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']], - [44443, ['HTTPS' => 'on', 'SERVER_NAME' => 'localhost:44443', 'HTTP_X_FORWARDED_PORT' => '666']], - ]; - } + [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 - */ - public function testCreateHttpRequestWithTrustedProxy($expectedPort, array $server) - { - $_SERVER = array_merge(['REMOTE_ADDR' => '10.0.0.1'], $server); + /** + * @dataProvider providerCreateHttpRequestWithTrustedProxy + */ + public function testCreateHttpRequestWithTrustedProxy($expectedPort, array $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->fromGlobals()->getUrl()->getPort()); - } + $factory = new Nette\Http\RequestFactory; + $factory->setProxy(['10.0.0.1']); + Assert::same($expectedPort, $factory->fromGlobals()->getUrl()->getPort()); + } - public function providerCreateHttpRequestWithTrustedProxy(): array - { - 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']], - ]; - } + public function providerCreateHttpRequestWithTrustedProxy(): array + { + 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']], + [443, ['HTTP_X_FORWARDED_PORT' => '443', 'HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com;proto=https']], + [44443, ['HTTP_X_FORWARDED_PORT' => '8080', 'HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com:44443;proto=https']], + ]; + } } $test = new RequestFactoryPortTest; -$test->run(); +$test->run(); \ No newline at end of file diff --git a/tests/Http/RequestFactory.scheme.phpt b/tests/Http/RequestFactory.scheme.phpt index 9ef37600..766c3b45 100644 --- a/tests/Http/RequestFactory.scheme.phpt +++ b/tests/Http/RequestFactory.scheme.phpt @@ -12,71 +12,71 @@ require __DIR__ . '/../bootstrap.php'; */ class RequestFactorySchemeTest extends Tester\TestCase { - /** - * @covers RequestFactory::getScheme - * @dataProvider providerCreateHttpRequest - */ - public function testCreateHttpRequest($expectedScheme, array $server) - { - $_SERVER = $server; - - $factory = new Nette\Http\RequestFactory; - $url = $factory->fromGlobals()->getUrl(); - - Assert::same($expectedScheme, $url->getScheme()); - Assert::same($expectedScheme === 'https' ? 443 : 80, $url->getPort()); - } - - - public function providerCreateHttpRequest(): array - { - return [ - ['http', ['SERVER_NAME' => 'localhost:80']], - ['http', ['SERVER_NAME' => 'localhost:80', 'HTTPS' => '']], - ['http', ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off']], - ['https', ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PROTO' => 'https']], - ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PORT' => '443']], - ['https', ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '443']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'anything']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PORT' => '80']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '80']] - ]; - } - - - /** - * @covers RequestFactory::getScheme - * @dataProvider providerCreateHttpRequestWithTrustedProxy - */ - public function testCreateHttpRequestWithTrustedProxy($expectedScheme, $expectedPort, array $server) - { - $_SERVER = array_merge(['REMOTE_ADDR' => '10.0.0.1'], $server); - - $factory = new Nette\Http\RequestFactory; - $factory->setProxy(['10.0.0.1']); - $url = $factory->fromGlobals()->getUrl(); - - Assert::same($expectedScheme, $url->getScheme()); - Assert::same($expectedPort, $url->getPort()); - } - - - public function providerCreateHttpRequestWithTrustedProxy(): array - { - return [ - ['http', 80, ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'http']], - ['http', 80, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], - ['http', 80, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'something-unexpected']], - ['http', 443, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '443']], - ['https', 443, ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https']], - ['https', 443, ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https']], - ['https', 80, ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '80']], - ]; - } + /** + * @covers RequestFactory::getScheme + * @dataProvider providerCreateHttpRequest + */ + public function testCreateHttpRequest($expectedScheme, array $server) + { + $_SERVER = $server; + + $factory = new Nette\Http\RequestFactory; + $url = $factory->fromGlobals()->getUrl(); + + Assert::same($expectedScheme, $url->getScheme()); + Assert::same($expectedScheme === 'https' ? 443 : 80, $url->getPort()); + } + + + public function providerCreateHttpRequest(): array + { + return [ + ['http', ['SERVER_NAME' => 'localhost:80']], + ['http', ['SERVER_NAME' => 'localhost:80', 'HTTPS' => '']], + ['http', ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off']], + ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https']], + ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PORT' => '443']], + ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '443']], + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on']], + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'anything']], + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PORT' => '80']], + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '80']], + ]; + } + + + /** + * @covers RequestFactory::getScheme + * @dataProvider providerCreateHttpRequestWithTrustedProxy + */ + public function testCreateHttpRequestWithTrustedProxy($expectedScheme, $expectedPort, array $server) + { + $_SERVER = array_merge(['REMOTE_ADDR' => '10.0.0.1'], $server); + + $factory = new Nette\Http\RequestFactory; + $factory->setProxy(['10.0.0.1']); + $url = $factory->fromGlobals()->getUrl(); + + Assert::same($expectedScheme, $url->getScheme()); + Assert::same($expectedPort, $url->getPort()); + } + + + public function providerCreateHttpRequestWithTrustedProxy(): array + { + return [ + ['http', 80, ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'http']], + ['http', 80, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], + ['http', 80, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'something-unexpected']], + ['http', 443, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '443']], + ['https', 443, ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https']], + ['https', 443, ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https']], + ['https', 80, ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '80']], + ]; + } } $test = new RequestFactorySchemeTest; -$test->run(); +$test->run(); \ No newline at end of file From 9194bb1aff13189e2e35683f8b2dec3a01a2d24e Mon Sep 17 00:00:00 2001 From: jaf2bj Date: Thu, 9 Mar 2023 16:21:02 +0100 Subject: [PATCH 5/9] rewritten PR --- src/Http/RequestFactory.php | 713 ++++++++++++++-------------- tests/Http/RequestFactory.port.phpt | 114 ++--- 2 files changed, 414 insertions(+), 413 deletions(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 03b2b2fc..8ff8c237 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -10,7 +10,6 @@ namespace Nette\Http; use Nette; -use Nette\Utils\Arrays; use Nette\Utils\Strings; @@ -19,363 +18,365 @@ */ class RequestFactory { - use Nette\SmartObject; - - /** @internal */ - private const ValidChars = '\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}'; - - public array $urlFilters = [ - 'path' => ['#//#' => '/'], // '%20' => '' - 'url' => [], // '#[.,)]$#D' => '' - ]; - - private bool $binary = false; - - /** @var string[] */ - private array $proxies = []; - - - public function setBinary(bool $binary = true): static - { - $this->binary = $binary; - return $this; - } - - - /** - * @param string|string[] $proxy - */ - public function setProxy($proxy): static - { - $this->proxies = (array) $proxy; - return $this; - } - - - /** - * Returns new Request instance, using values from superglobals. - */ - public function fromGlobals(): Request - { - $url = new Url; - $this->getServer($url); - $this->getPathAndQuery($url); - [$post, $cookies] = $this->getGetPostCookie($url); - [$remoteAddr, $remoteHost] = $this->getClient($url); - - return new Request( - new UrlScript($url, $this->getScriptPath($url)), - $post, - $this->getFiles(), - $cookies, - $this->getHeaders(), - $this->getMethod(), - $remoteAddr, - $remoteHost, - fn(): string => file_get_contents('php://input') - ); - } - - - private function getServer(Url $url): void - { - $url->setScheme(!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https' : 'http'); - - if ( - (isset($_SERVER[$tmp = 'HTTP_HOST']) || isset($_SERVER[$tmp = 'SERVER_NAME'])) - && preg_match('#^([a-z0-9_.-]+|\[[a-f0-9:]+\])(:\d+)?$#Di', $_SERVER[$tmp], $pair) - ) { - $url->setHost(rtrim(strtolower($pair[1]), '.')); - if (isset($pair[2])) { - $url->setPort((int) substr($pair[2], 1)); - } elseif (isset($_SERVER['SERVER_PORT'])) { - $url->setPort((int) $_SERVER['SERVER_PORT']); - } - } - } - - - private function getPathAndQuery(Url $url): void - { - $requestUrl = $_SERVER['REQUEST_URI'] ?? '/'; - $requestUrl = preg_replace('#^\w++://[^/]++#', '', $requestUrl); - $requestUrl = Strings::replace($requestUrl, $this->urlFilters['url']); - - $tmp = explode('?', $requestUrl, 2); - $path = Url::unescape($tmp[0], '%/?#'); - $path = Strings::fixEncoding(Strings::replace($path, $this->urlFilters['path'])); - $url->setPath($path); - $url->setQuery($tmp[1] ?? ''); - } - - - private function getScriptPath(Url $url): string - { - if (PHP_SAPI === 'cli-server') { - return '/'; - } - - $path = $url->getPath(); - $lpath = strtolower($path); - $script = strtolower($_SERVER['SCRIPT_NAME'] ?? ''); - if ($lpath !== $script) { - $max = min(strlen($lpath), strlen($script)); - for ($i = 0; $i < $max && $lpath[$i] === $script[$i]; $i++); - $path = $i - ? substr($path, 0, strrpos($path, '/', $i - strlen($path) - 1) + 1) - : '/'; - } - - return $path; - } - - - private function getGetPostCookie(Url $url): array - { - $useFilter = (!in_array((string) ini_get('filter.default'), ['', 'unsafe_raw'], true) || ini_get('filter.default_flags')); - - $query = $url->getQueryParameters(); - $post = $useFilter - ? filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW) - : (empty($_POST) ? [] : $_POST); - $cookies = $useFilter - ? filter_input_array(INPUT_COOKIE, FILTER_UNSAFE_RAW) - : (empty($_COOKIE) ? [] : $_COOKIE); - - // remove invalid characters - $reChars = '#^[' . self::ValidChars . ']*+$#Du'; - if (!$this->binary) { - $list = [&$query, &$post, &$cookies]; - foreach ($list as $key => &$val) { - foreach ($val as $k => $v) { - if (is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { - unset($list[$key][$k]); - - } elseif (is_array($v)) { - $list[$key][$k] = $v; - $list[] = &$list[$key][$k]; - - } elseif (is_string($v)) { - $list[$key][$k] = (string) preg_replace('#[^' . self::ValidChars . ']+#u', '', $v); - - } else { - throw new Nette\InvalidStateException(sprintf('Invalid value in $_POST/$_COOKIE in key %s, expected string, %s given.', "'$k'", gettype($v))); - } - } - } - - unset($list, $key, $val, $k, $v); - } - - $url->setQuery($query); - return [$post, $cookies]; - } - - - private function getFiles(): array - { - $reChars = '#^[' . self::ValidChars . ']*+$#Du'; - $files = []; - $list = []; - foreach ($_FILES ?? [] as $k => $v) { - if ( - !is_array($v) - || !isset($v['name'], $v['type'], $v['size'], $v['tmp_name'], $v['error']) - || (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) - ) { - continue; - } - - $v['@'] = &$files[$k]; - $list[] = $v; - } - - // create FileUpload objects - foreach ($list as &$v) { - if (!isset($v['name'])) { - continue; - - } elseif (!is_array($v['name'])) { - if (!$this->binary && (!preg_match($reChars, $v['name']) || preg_last_error())) { - $v['name'] = ''; - } - - if ($v['error'] !== UPLOAD_ERR_NO_FILE) { - $v['@'] = new FileUpload($v); - } - - continue; - } - - foreach ($v['name'] as $k => $foo) { - if (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { - continue; - } - - $list[] = [ - 'name' => $v['name'][$k], - 'type' => $v['type'][$k], - 'size' => $v['size'][$k], - 'full_path' => $v['full_path'][$k] ?? null, - 'tmp_name' => $v['tmp_name'][$k], - 'error' => $v['error'][$k], - '@' => &$v['@'][$k], - ]; - } - } - - return $files; - } - - - private function getHeaders(): array - { - if (function_exists('apache_request_headers')) { - $headers = apache_request_headers(); - } else { - $headers = []; - foreach ($_SERVER as $k => $v) { - if (strncmp($k, 'HTTP_', 5) === 0) { - $k = substr($k, 5); - } elseif (strncmp($k, 'CONTENT_', 8)) { - continue; - } - - $headers[strtr($k, '_', '-')] = $v; - } - } - - if (!isset($headers['Authorization'])) { - if (isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) { - $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW']); - } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { - $headers['Authorization'] = 'Digest ' . $_SERVER['PHP_AUTH_DIGEST']; - } - } - - return $headers; - } - - - private function getMethod(): string - { - $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; - if ( - $method === 'POST' - && preg_match('#^[A-Z]+$#D', $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? '') - ) { - $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; - } - - return $method; - } - - - private function getClient(Url $url): array - { - $remoteAddr = !empty($_SERVER['REMOTE_ADDR']) - ? $_SERVER['REMOTE_ADDR'] - : null; - $remoteHost = !empty($_SERVER['REMOTE_HOST']) - ? $_SERVER['REMOTE_HOST'] - : null; - - // use real client address and host if trusted proxy is used - $usingTrustedProxy = $remoteAddr && Arrays::some($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($remoteAddr, $proxy)); - if ($usingTrustedProxy) { - empty($_SERVER['HTTP_FORWARDED']) - ? $this->useNonstandardProxy($url, $remoteAddr, $remoteHost) - : $this->useForwardedProxy($url, $remoteAddr, $remoteHost); - } - - return [$remoteAddr, $remoteHost]; - } - - - private function useForwardedProxy(Url $url, &$remoteAddr, &$remoteHost): void - { - $forwardParams = preg_split('/[,;]/', $_SERVER['HTTP_FORWARDED']); - foreach ($forwardParams as $forwardParam) { - [$key, $value] = explode('=', $forwardParam, 2) + [1 => '']; - $proxyParams[strtolower(trim($key))][] = trim($value, " \t\""); - } - - if (isset($proxyParams['for'])) { - $address = $proxyParams['for'][0]; - $remoteAddr = str_contains($address, '[') - ? substr($address, 1, strpos($address, ']') - 1) // IPv6 - : explode(':', $address)[0]; // IPv4 - } + use Nette\SmartObject; + + /** @internal */ + private const ValidChars = '\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}'; + + public array $urlFilters = [ + 'path' => ['#//#' => '/'], // '%20' => '' + 'url' => [], // '#[.,)]$#D' => '' + ]; + + private bool $binary = false; + + /** @var string[] */ + private array $proxies = []; + + + public function setBinary(bool $binary = true): static + { + $this->binary = $binary; + return $this; + } + + + /** + * @param string|string[] $proxy + */ + public function setProxy($proxy): static + { + $this->proxies = (array) $proxy; + return $this; + } + + + /** + * Returns new Request instance, using values from superglobals. + */ + public function fromGlobals(): Request + { + $url = new Url; + $this->getServer($url); + $this->getPathAndQuery($url); + [$post, $cookies] = $this->getGetPostCookie($url); + [$remoteAddr, $remoteHost] = $this->getClient($url); + + return new Request( + new UrlScript($url, $this->getScriptPath($url)), + $post, + $this->getFiles(), + $cookies, + $this->getHeaders(), + $this->getMethod(), + $remoteAddr, + $remoteHost, + fn(): string => file_get_contents('php://input') + ); + } + + + private function getServer(Url $url): void + { + $url->setScheme(!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https' : 'http'); + + if ( + (isset($_SERVER[$tmp = 'HTTP_HOST']) || isset($_SERVER[$tmp = 'SERVER_NAME'])) + && preg_match('#^([a-z0-9_.-]+|\[[a-f0-9:]+\])(:\d+)?$#Di', $_SERVER[$tmp], $pair) + ) { + $url->setHost(rtrim(strtolower($pair[1]), '.')); + if (isset($pair[2])) { + $url->setPort((int) substr($pair[2], 1)); + } elseif (isset($_SERVER['SERVER_PORT'])) { + $url->setPort((int) $_SERVER['SERVER_PORT']); + } + } + } + + + private function getPathAndQuery(Url $url): void + { + $requestUrl = $_SERVER['REQUEST_URI'] ?? '/'; + $requestUrl = preg_replace('#^\w++://[^/]++#', '', $requestUrl); + $requestUrl = Strings::replace($requestUrl, $this->urlFilters['url']); + + $tmp = explode('?', $requestUrl, 2); + $path = Url::unescape($tmp[0], '%/?#'); + $path = Strings::fixEncoding(Strings::replace($path, $this->urlFilters['path'])); + $url->setPath($path); + $url->setQuery($tmp[1] ?? ''); + } + + + private function getScriptPath(Url $url): string + { + if (PHP_SAPI === 'cli-server') { + return '/'; + } + + $path = $url->getPath(); + $lpath = strtolower($path); + $script = strtolower($_SERVER['SCRIPT_NAME'] ?? ''); + if ($lpath !== $script) { + $max = min(strlen($lpath), strlen($script)); + for ($i = 0; $i < $max && $lpath[$i] === $script[$i]; $i++); + $path = $i + ? substr($path, 0, strrpos($path, '/', $i - strlen($path) - 1) + 1) + : '/'; + } + + return $path; + } + + + private function getGetPostCookie(Url $url): array + { + $useFilter = (!in_array((string) ini_get('filter.default'), ['', 'unsafe_raw'], true) || ini_get('filter.default_flags')); + + $query = $url->getQueryParameters(); + $post = $useFilter + ? filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW) + : (empty($_POST) ? [] : $_POST); + $cookies = $useFilter + ? filter_input_array(INPUT_COOKIE, FILTER_UNSAFE_RAW) + : (empty($_COOKIE) ? [] : $_COOKIE); + + // remove invalid characters + $reChars = '#^[' . self::ValidChars . ']*+$#Du'; + if (!$this->binary) { + $list = [&$query, &$post, &$cookies]; + foreach ($list as $key => &$val) { + foreach ($val as $k => $v) { + if (is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { + unset($list[$key][$k]); + + } elseif (is_array($v)) { + $list[$key][$k] = $v; + $list[] = &$list[$key][$k]; + + } elseif (is_string($v)) { + $list[$key][$k] = (string) preg_replace('#[^' . self::ValidChars . ']+#u', '', $v); + + } else { + throw new Nette\InvalidStateException(sprintf('Invalid value in $_POST/$_COOKIE in key %s, expected string, %s given.', "'$k'", gettype($v))); + } + } + } + + unset($list, $key, $val, $k, $v); + } + + $url->setQuery($query); + return [$post, $cookies]; + } + + + private function getFiles(): array + { + $reChars = '#^[' . self::ValidChars . ']*+$#Du'; + $files = []; + $list = []; + foreach ($_FILES ?? [] as $k => $v) { + if ( + !is_array($v) + || !isset($v['name'], $v['type'], $v['size'], $v['tmp_name'], $v['error']) + || (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) + ) { + continue; + } + + $v['@'] = &$files[$k]; + $list[] = $v; + } + + // create FileUpload objects + foreach ($list as &$v) { + if (!isset($v['name'])) { + continue; + + } elseif (!is_array($v['name'])) { + if (!$this->binary && (!preg_match($reChars, $v['name']) || preg_last_error())) { + $v['name'] = ''; + } + + if ($v['error'] !== UPLOAD_ERR_NO_FILE) { + $v['@'] = new FileUpload($v); + } + + continue; + } + + foreach ($v['name'] as $k => $foo) { + if (!$this->binary && is_string($k) && (!preg_match($reChars, $k) || preg_last_error())) { + continue; + } + + $list[] = [ + 'name' => $v['name'][$k], + 'type' => $v['type'][$k], + 'size' => $v['size'][$k], + 'full_path' => $v['full_path'][$k] ?? null, + 'tmp_name' => $v['tmp_name'][$k], + 'error' => $v['error'][$k], + '@' => &$v['@'][$k], + ]; + } + } + + return $files; + } + + + private function getHeaders(): array + { + if (function_exists('apache_request_headers')) { + $headers = apache_request_headers(); + } else { + $headers = []; + foreach ($_SERVER as $k => $v) { + if (strncmp($k, 'HTTP_', 5) === 0) { + $k = substr($k, 5); + } elseif (strncmp($k, 'CONTENT_', 8)) { + continue; + } + + $headers[strtr($k, '_', '-')] = $v; + } + } + + if (!isset($headers['Authorization'])) { + if (isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) { + $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW']); + } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { + $headers['Authorization'] = 'Digest ' . $_SERVER['PHP_AUTH_DIGEST']; + } + } + + return $headers; + } + + + private function getMethod(): string + { + $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; + if ( + $method === 'POST' + && preg_match('#^[A-Z]+$#D', $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? '') + ) { + $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; + } + + return $method; + } + + + private function getClient(Url $url): array + { + $remoteAddr = !empty($_SERVER['REMOTE_ADDR']) + ? $_SERVER['REMOTE_ADDR'] + : null; + $remoteHost = !empty($_SERVER['REMOTE_HOST']) + ? $_SERVER['REMOTE_HOST'] + : null; + + // use real client address and host if trusted proxy is used + $usingTrustedProxy = $remoteAddr && array_filter($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($remoteAddr, $proxy)); + if ($usingTrustedProxy) { + empty($_SERVER['HTTP_FORWARDED']) + ? $this->useNonstandardProxy($url, $remoteAddr, $remoteHost) + : $this->useForwardedProxy($url, $remoteAddr, $remoteHost); + } + + return [$remoteAddr, $remoteHost]; + } + + + private function useForwardedProxy(Url $url, &$remoteAddr, &$remoteHost): void + { + $forwardParams = preg_split('/[,;]/', $_SERVER['HTTP_FORWARDED']); + foreach ($forwardParams as $forwardParam) { + [$key, $value] = explode('=', $forwardParam, 2) + [1 => '']; + $proxyParams[strtolower(trim($key))][] = trim($value, " \t\""); + } + + if (isset($proxyParams['for'])) { + $address = $proxyParams['for'][0]; + $remoteAddr = str_contains($address, '[') + ? substr($address, 1, strpos($address, ']') - 1) // IPv6 + : explode(':', $address)[0]; // IPv4 + } if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); } - if (isset($proxyParams['host']) && count($proxyParams['host']) === 1) { - $host = $proxyParams['host'][0]; - $startingDelimiterPosition = strpos($host, '['); - if ($startingDelimiterPosition === false) { //IPv4 - $remoteHostArr = explode(':', $host); - $remoteHost = $remoteHostArr[0]; - $url->setHost($remoteHost); - if (isset($remoteHostArr[1])) { - $url->setPort((int) $remoteHostArr[1]); - } - } else { //IPv6 - $endingDelimiterPosition = strpos($host, ']'); - $remoteHost = substr($host, strpos($host, '[') + 1, $endingDelimiterPosition - 1); - $url->setHost($remoteHost); - $remoteHostArr = explode(':', substr($host, $endingDelimiterPosition)); - if (isset($remoteHostArr[1])) { - $url->setPort((int) $remoteHostArr[1]); - } - } - } - - $scheme = (isset($proxyParams['proto']) && count($proxyParams['proto']) === 1) - ? $proxyParams['proto'][0] - : 'http'; - $url->setScheme(strcasecmp($scheme, 'https') === 0 ? 'https' : 'http'); - } - - - private function useNonstandardProxy(Url $url, &$remoteAddr, &$remoteHost): void - { - if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { - $url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http'); - $url->setPort($url->getScheme() === 'https' ? 443 : 80); - } - - if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); - } - - if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $xForwardedForWithoutProxies = array_filter( - explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), - fn(string $ip): bool => filter_var($ip = trim($ip), FILTER_VALIDATE_IP) === false - || !Arrays::some($this->proxies, fn(string $proxy): bool => Helpers::ipMatch($ip, $proxy)), - ); - if ($xForwardedForWithoutProxies) { - $remoteAddr = trim(end($xForwardedForWithoutProxies)); - $xForwardedForRealIpKey = key($xForwardedForWithoutProxies); - } - } - - if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { - $xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); - if (isset($xForwardedHost[$xForwardedForRealIpKey])) { - $remoteHost = trim($xForwardedHost[$xForwardedForRealIpKey]); - $url->setHost($remoteHost); - } - } - } - - - /** @deprecated use fromGlobals() */ - public function createHttpRequest(): Request - { - trigger_error(__METHOD__ . '() is deprecated, use fromGlobals()', E_USER_DEPRECATED); - return $this->fromGlobals(); - } -} \ No newline at end of file + if (isset($proxyParams['host']) && count($proxyParams['host']) === 1) { + $host = $proxyParams['host'][0]; + $startingDelimiterPosition = strpos($host, '['); + if ($startingDelimiterPosition === false) { //IPv4 + $remoteHostArr = explode(':', $host); + $remoteHost = $remoteHostArr[0]; + $url->setHost($remoteHost); + if (isset($remoteHostArr[1])) { + $url->setPort((int) $remoteHostArr[1]); + } + } else { //IPv6 + $endingDelimiterPosition = strpos($host, ']'); + $remoteHost = substr($host, strpos($host, '[') + 1, $endingDelimiterPosition - 1); + $url->setHost($remoteHost); + $remoteHostArr = explode(':', substr($host, $endingDelimiterPosition)); + if (isset($remoteHostArr[1])) { + $url->setPort((int) $remoteHostArr[1]); + } + } + } + + $scheme = (isset($proxyParams['proto']) && count($proxyParams['proto']) === 1) + ? $proxyParams['proto'][0] + : 'http'; + $url->setScheme(strcasecmp($scheme, 'https') === 0 ? 'https' : 'http'); + } + + + private function useNonstandardProxy(Url $url, &$remoteAddr, &$remoteHost): void + { + if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http'); + $url->setPort($url->getScheme() === 'https' ? 443 : 80); + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $xForwardedForWithoutProxies = array_filter( + explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), + fn(string $ip): bool => !array_filter( + $this->proxies, + fn(string $proxy): bool => filter_var(trim($ip), FILTER_VALIDATE_IP) !== false && Helpers::ipMatch(trim($ip), $proxy), + ), + ); + if ($xForwardedForWithoutProxies) { + $remoteAddr = trim(end($xForwardedForWithoutProxies)); + $xForwardedForRealIpKey = key($xForwardedForWithoutProxies); + } + } + + if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { + $xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); + if (isset($xForwardedHost[$xForwardedForRealIpKey])) { + $remoteHost = trim($xForwardedHost[$xForwardedForRealIpKey]); + $url->setHost($remoteHost); + } + } + } + + + /** @deprecated use fromGlobals() */ + public function createHttpRequest(): Request + { + trigger_error(__METHOD__ . '() is deprecated, use fromGlobals()', E_USER_DEPRECATED); + return $this->fromGlobals(); + } +} diff --git a/tests/Http/RequestFactory.port.phpt b/tests/Http/RequestFactory.port.phpt index e7bd612e..213e3a58 100644 --- a/tests/Http/RequestFactory.port.phpt +++ b/tests/Http/RequestFactory.port.phpt @@ -11,74 +11,74 @@ require __DIR__ . '/../bootstrap.php'; */ class RequestFactoryPortTest extends Tester\TestCase { - /** - * @dataProvider providerCreateHttpRequest - */ - public function testCreateHttpRequest($expectedPort, array $server) - { - $_SERVER = $server; + /** + * @dataProvider providerCreateHttpRequest + */ + public function testCreateHttpRequest($expectedPort, array $server) + { + $_SERVER = $server; - $factory = new Nette\Http\RequestFactory; - Assert::same($expectedPort, $factory->fromGlobals()->getUrl()->getPort()); - } + $factory = new Nette\Http\RequestFactory; + Assert::same($expectedPort, $factory->fromGlobals()->getUrl()->getPort()); + } - public function providerCreateHttpRequest(): array - { - 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']], + public function providerCreateHttpRequest(): array + { + 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']], - ]; - } + [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 - */ - public function testCreateHttpRequestWithTrustedProxy($expectedPort, array $server) - { - $_SERVER = array_merge(['REMOTE_ADDR' => '10.0.0.1'], $server); + /** + * @dataProvider providerCreateHttpRequestWithTrustedProxy + */ + public function testCreateHttpRequestWithTrustedProxy($expectedPort, array $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->fromGlobals()->getUrl()->getPort()); - } + $factory = new Nette\Http\RequestFactory; + $factory->setProxy(['10.0.0.1']); + Assert::same($expectedPort, $factory->fromGlobals()->getUrl()->getPort()); + } - public function providerCreateHttpRequestWithTrustedProxy(): array - { - 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']], + public function providerCreateHttpRequestWithTrustedProxy(): array + { + 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']], [443, ['HTTP_X_FORWARDED_PORT' => '443', 'HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com;proto=https']], [44443, ['HTTP_X_FORWARDED_PORT' => '8080', 'HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com:44443;proto=https']], - ]; - } + ]; + } } $test = new RequestFactoryPortTest; -$test->run(); \ No newline at end of file +$test->run(); From 9397658b1eb3386c3de116dfabb586b9245e7248 Mon Sep 17 00:00:00 2001 From: jaf2bj Date: Thu, 9 Mar 2023 16:30:10 +0100 Subject: [PATCH 6/9] rewritten PR --- tests/Http/RequestFactory.port.phpt | 4 +- tests/Http/RequestFactory.scheme.phpt | 126 +++++++++++++------------- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/tests/Http/RequestFactory.port.phpt b/tests/Http/RequestFactory.port.phpt index 213e3a58..8ed28d68 100644 --- a/tests/Http/RequestFactory.port.phpt +++ b/tests/Http/RequestFactory.port.phpt @@ -74,8 +74,8 @@ class RequestFactoryPortTest extends Tester\TestCase [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']], - [443, ['HTTP_X_FORWARDED_PORT' => '443', 'HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com;proto=https']], - [44443, ['HTTP_X_FORWARDED_PORT' => '8080', 'HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com:44443;proto=https']], + [443, ['HTTP_X_FORWARDED_PORT' => '443', 'HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com;proto=https']], + [44443, ['HTTP_X_FORWARDED_PORT' => '8080', 'HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com:44443;proto=https']], ]; } } diff --git a/tests/Http/RequestFactory.scheme.phpt b/tests/Http/RequestFactory.scheme.phpt index 766c3b45..5fd82455 100644 --- a/tests/Http/RequestFactory.scheme.phpt +++ b/tests/Http/RequestFactory.scheme.phpt @@ -12,69 +12,69 @@ require __DIR__ . '/../bootstrap.php'; */ class RequestFactorySchemeTest extends Tester\TestCase { - /** - * @covers RequestFactory::getScheme - * @dataProvider providerCreateHttpRequest - */ - public function testCreateHttpRequest($expectedScheme, array $server) - { - $_SERVER = $server; - - $factory = new Nette\Http\RequestFactory; - $url = $factory->fromGlobals()->getUrl(); - - Assert::same($expectedScheme, $url->getScheme()); - Assert::same($expectedScheme === 'https' ? 443 : 80, $url->getPort()); - } - - - public function providerCreateHttpRequest(): array - { - return [ - ['http', ['SERVER_NAME' => 'localhost:80']], - ['http', ['SERVER_NAME' => 'localhost:80', 'HTTPS' => '']], - ['http', ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off']], - ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https']], - ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PORT' => '443']], - ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '443']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'anything']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PORT' => '80']], - ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '80']], - ]; - } - - - /** - * @covers RequestFactory::getScheme - * @dataProvider providerCreateHttpRequestWithTrustedProxy - */ - public function testCreateHttpRequestWithTrustedProxy($expectedScheme, $expectedPort, array $server) - { - $_SERVER = array_merge(['REMOTE_ADDR' => '10.0.0.1'], $server); - - $factory = new Nette\Http\RequestFactory; - $factory->setProxy(['10.0.0.1']); - $url = $factory->fromGlobals()->getUrl(); - - Assert::same($expectedScheme, $url->getScheme()); - Assert::same($expectedPort, $url->getPort()); - } - - - public function providerCreateHttpRequestWithTrustedProxy(): array - { - return [ - ['http', 80, ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'http']], - ['http', 80, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], - ['http', 80, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'something-unexpected']], - ['http', 443, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '443']], - ['https', 443, ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https']], - ['https', 443, ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https']], - ['https', 80, ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '80']], - ]; - } + /** + * @covers RequestFactory::getScheme + * @dataProvider providerCreateHttpRequest + */ + public function testCreateHttpRequest($expectedScheme, array $server) + { + $_SERVER = $server; + + $factory = new Nette\Http\RequestFactory; + $url = $factory->fromGlobals()->getUrl(); + + Assert::same($expectedScheme, $url->getScheme()); + Assert::same($expectedScheme === 'https' ? 443 : 80, $url->getPort()); + } + + + public function providerCreateHttpRequest(): array + { + return [ + ['http', ['SERVER_NAME' => 'localhost:80']], + ['http', ['SERVER_NAME' => 'localhost:80', 'HTTPS' => '']], + ['http', ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off']], + ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https']], + ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PORT' => '443']], + ['http', ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '443']], + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on']], + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'anything']], + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PORT' => '80']], + ['https', ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '80']], + ]; + } + + + /** + * @covers RequestFactory::getScheme + * @dataProvider providerCreateHttpRequestWithTrustedProxy + */ + public function testCreateHttpRequestWithTrustedProxy($expectedScheme, $expectedPort, array $server) + { + $_SERVER = array_merge(['REMOTE_ADDR' => '10.0.0.1'], $server); + + $factory = new Nette\Http\RequestFactory; + $factory->setProxy(['10.0.0.1']); + $url = $factory->fromGlobals()->getUrl(); + + Assert::same($expectedScheme, $url->getScheme()); + Assert::same($expectedPort, $url->getPort()); + } + + + public function providerCreateHttpRequestWithTrustedProxy(): array + { + return [ + ['http', 80, ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'http']], + ['http', 80, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']], + ['http', 80, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'something-unexpected']], + ['http', 443, ['SERVER_NAME' => 'localhost:443', 'HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '443']], + ['https', 443, ['SERVER_NAME' => 'localhost:80', 'HTTP_X_FORWARDED_PROTO' => 'https']], + ['https', 443, ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https']], + ['https', 80, ['SERVER_NAME' => 'localhost:80', 'HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '80']], + ]; + } } From 935598c40255654ceb8fc71255684c1adb37f672 Mon Sep 17 00:00:00 2001 From: jaf2bj Date: Thu, 9 Mar 2023 16:30:47 +0100 Subject: [PATCH 7/9] rewritten PR --- src/Http/RequestFactory.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index 8ff8c237..b2be0e3a 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -306,9 +306,9 @@ private function useForwardedProxy(Url $url, &$remoteAddr, &$remoteHost): void : explode(':', $address)[0]; // IPv4 } - if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); - } + if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); + } if (isset($proxyParams['host']) && count($proxyParams['host']) === 1) { $host = $proxyParams['host'][0]; From b3972892d6a41209c099ee8ba50e64d63f0ef47b Mon Sep 17 00:00:00 2001 From: jaf2bj Date: Thu, 9 Mar 2023 16:31:31 +0100 Subject: [PATCH 8/9] rewritten PR --- tests/Http/RequestFactory.scheme.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Http/RequestFactory.scheme.phpt b/tests/Http/RequestFactory.scheme.phpt index 5fd82455..30f04d9b 100644 --- a/tests/Http/RequestFactory.scheme.phpt +++ b/tests/Http/RequestFactory.scheme.phpt @@ -79,4 +79,4 @@ class RequestFactorySchemeTest extends Tester\TestCase $test = new RequestFactorySchemeTest; -$test->run(); \ No newline at end of file +$test->run(); From 87468093ec786ece92942b693ec39f44b30dbe4d Mon Sep 17 00:00:00 2001 From: jaf2bj Date: Fri, 10 Mar 2023 11:23:54 +0100 Subject: [PATCH 9/9] Forwarded instead of X-Forwarded- --- src/Http/RequestFactory.php | 10 +++------- tests/Http/RequestFactory.port.phpt | 6 ++++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index b2be0e3a..9973ef4c 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -306,8 +306,9 @@ private function useForwardedProxy(Url $url, &$remoteAddr, &$remoteHost): void : explode(':', $address)[0]; // IPv4 } - if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); + if (isset($proxyParams['proto']) && count($proxyParams['proto']) === 1) { + $url->setScheme(strcasecmp($proxyParams['proto'][0], 'https') === 0 ? 'https' : 'http'); + $url->setPort($url->getScheme() === 'https' ? 443 : 80); } if (isset($proxyParams['host']) && count($proxyParams['host']) === 1) { @@ -330,11 +331,6 @@ private function useForwardedProxy(Url $url, &$remoteAddr, &$remoteHost): void } } } - - $scheme = (isset($proxyParams['proto']) && count($proxyParams['proto']) === 1) - ? $proxyParams['proto'][0] - : 'http'; - $url->setScheme(strcasecmp($scheme, 'https') === 0 ? 'https' : 'http'); } diff --git a/tests/Http/RequestFactory.port.phpt b/tests/Http/RequestFactory.port.phpt index 8ed28d68..03a0d107 100644 --- a/tests/Http/RequestFactory.port.phpt +++ b/tests/Http/RequestFactory.port.phpt @@ -74,8 +74,10 @@ class RequestFactoryPortTest extends Tester\TestCase [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']], - [443, ['HTTP_X_FORWARDED_PORT' => '443', 'HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com;proto=https']], - [44443, ['HTTP_X_FORWARDED_PORT' => '8080', 'HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com:44443;proto=https']], + [443, ['HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com;proto=https']], + [44443, ['HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com:44443;proto=https']], + [80, ['HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com;proto=http']], + [8080, ['HTTP_FORWARDED' => 'for=192.168.1.1;host=example.com:8080;proto=http']], ]; } }