From 3fd485a2470ccdc49b0bb1e332f0452e2d3ed5a0 Mon Sep 17 00:00:00 2001 From: Patrick Kusebauch Date: Fri, 17 Jun 2016 20:15:21 +0200 Subject: [PATCH] Implemented RFC 7239 - "Forwarded HTTP Extension" (#94) --- src/Http/RequestFactory.php | 76 +++++++++++---- .../Http/RequestFactory.proxy.forwarded.phpt | 95 +++++++++++++++++++ ... => RequestFactory.proxy.x-forwarded.phpt} | 2 +- 3 files changed, 153 insertions(+), 20 deletions(-) create mode 100644 tests/Http/RequestFactory.proxy.forwarded.phpt rename tests/Http/{RequestFactory.proxy.phpt => RequestFactory.proxy.x-forwarded.phpt} (94%) diff --git a/src/Http/RequestFactory.php b/src/Http/RequestFactory.php index e1c381e8..25d395db 100644 --- a/src/Http/RequestFactory.php +++ b/src/Http/RequestFactory.php @@ -195,30 +195,68 @@ public function createHttpRequest() $usingTrustedProxy = $remoteAddr && array_filter($this->proxies, function ($proxy) use ($remoteAddr) { return Helpers::ipMatch($remoteAddr, $proxy); }); - 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_FORWARDED'])) { + $forwardParams = preg_split('/[,;]/', $_SERVER['HTTP_FORWARDED']); + foreach ($forwardParams as $forwardParam) { + list($key, $value) = explode('=', $forwardParam, 2) + [1 => NULL]; + $proxyParams[strtolower(trim($key))][] = trim($value, " \t\""); + } - if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { - $url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']); - } + if (isset($proxyParams['for'])) { + $address = $proxyParams['for'][0]; + if (strpos($address, '[') === FALSE) { //IPv4 + $remoteAddr = explode(':', $address)[0]; + } else { //IPv6 + $remoteAddr = substr($address, 1, strpos($address, ']') - 1); + } + } + + 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]; + if (isset($remoteHostArr[1])) { + $url->setPort((int) $remoteHostArr[1]); + } + } else { //IPv6 + $endingDelimiterPosition = strpos($host, ']'); + $remoteHost = substr($host, strpos($host, '[') + 1, $endingDelimiterPosition - 1); + $remoteHostArr = explode(':', substr($host, $endingDelimiterPosition)); + if (isset($remoteHostArr[1])) { + $url->setPort((int) $remoteHostArr[1]); + } + } + } + + $scheme = (isset($proxyParams['scheme']) && count($proxyParams['scheme']) === 1) ? $proxyParams['scheme'][0] : 'http'; + $url->setScheme(strcasecmp($scheme, 'https') === 0 ? 'https' : 'http'); + } else { + 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'])) { - $xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function ($ip) { - return !array_filter($this->proxies, function ($proxy) use ($ip) { - return Helpers::ipMatch(trim($ip), $proxy); + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function ($ip) { + return !array_filter($this->proxies, function ($proxy) use ($ip) { + return Helpers::ipMatch(trim($ip), $proxy); + }); }); - }); - $remoteAddr = trim(end($xForwardedForWithoutProxies)); - $xForwardedForRealIpKey = key($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]); + if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { + $xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); + if (isset($xForwardedHost[$xForwardedForRealIpKey])) { + $remoteHost = trim($xForwardedHost[$xForwardedForRealIpKey]); + } } } } diff --git a/tests/Http/RequestFactory.proxy.forwarded.phpt b/tests/Http/RequestFactory.proxy.forwarded.phpt new file mode 100644 index 00000000..bffd0e61 --- /dev/null +++ b/tests/Http/RequestFactory.proxy.forwarded.phpt @@ -0,0 +1,95 @@ + '127.0.0.3', + 'REMOTE_HOST' => 'localhost', + 'HTTP_FORWARDED' => 'for=23.75.45.200;host=192.168.0.1', + ]; + + $factory = new RequestFactory; + $factory->setProxy('127.0.0.1'); + Assert::same('127.0.0.3', $factory->createHttpRequest()->getRemoteAddress()); + Assert::same('localhost', $factory->createHttpRequest()->getRemoteHost()); + + $factory->setProxy('127.0.0.1/8'); + Assert::same('23.75.45.200', $factory->createHttpRequest()->getRemoteAddress()); + Assert::same('192.168.0.1', $factory->createHttpRequest()->getRemoteHost()); + + $url = $factory->createHttpRequest()->getUrl(); + Assert::same('http', $url->getScheme()); +}); + +test(function () { + $_SERVER = [ + 'REMOTE_ADDR' => '127.0.0.3', + 'REMOTE_HOST' => 'localhost', + 'HTTP_FORWARDED' => 'for=23.75.45.200:8080;host=192.168.0.1:8080', + ]; + + $factory = new RequestFactory; + + $factory->setProxy('127.0.0.3'); + Assert::same('23.75.45.200', $factory->createHttpRequest()->getRemoteAddress()); + Assert::same('192.168.0.1', $factory->createHttpRequest()->getRemoteHost()); + + $url = $factory->createHttpRequest()->getUrl(); + Assert::same(8080, $url->getPort()); +}); + + +test(function () { + $_SERVER = [ + 'REMOTE_ADDR' => '127.0.0.3', + 'REMOTE_HOST' => 'localhost', + 'HTTP_FORWARDED' => 'for="[2001:db8:cafe::17]";host="[2001:db8:cafe::18]"', + ]; + + $factory = new RequestFactory; + + $factory->setProxy('127.0.0.3'); + Assert::same('2001:db8:cafe::17', $factory->createHttpRequest()->getRemoteAddress()); + Assert::same('2001:db8:cafe::18', $factory->createHttpRequest()->getRemoteHost()); +}); + +test(function () { + $_SERVER = [ + 'REMOTE_ADDR' => '127.0.0.3', + 'REMOTE_HOST' => 'localhost', + 'HTTP_FORWARDED' => 'for="[2001:db8:cafe::17]:47831";host="[2001:db8:cafe::18]:47832"', + ]; + + $factory = new RequestFactory; + + $factory->setProxy('127.0.0.3'); + Assert::same('2001:db8:cafe::17', $factory->createHttpRequest()->getRemoteAddress()); + Assert::same('2001:db8:cafe::18', $factory->createHttpRequest()->getRemoteHost()); + + $url = $factory->createHttpRequest()->getUrl(); + Assert::same(47832, $url->getPort()); +}); + + +test(function () { + $_SERVER = [ + 'REMOTE_ADDR' => '127.0.0.3', + 'REMOTE_HOST' => 'localhost', + 'HTTP_FORWARDED' => 'for="[2001:db8:cafe::17]:47831" ; host="[2001:db8:cafe::18]:47832" ; scheme=https', + ]; + + $factory = new RequestFactory; + $factory->setProxy('127.0.0.3'); + + $url = $factory->createHttpRequest()->getUrl(); + Assert::same('https', $url->getScheme()); +}); diff --git a/tests/Http/RequestFactory.proxy.phpt b/tests/Http/RequestFactory.proxy.x-forwarded.phpt similarity index 94% rename from tests/Http/RequestFactory.proxy.phpt rename to tests/Http/RequestFactory.proxy.x-forwarded.phpt index 297b2589..6bafaf70 100644 --- a/tests/Http/RequestFactory.proxy.phpt +++ b/tests/Http/RequestFactory.proxy.x-forwarded.phpt @@ -1,7 +1,7 @@