Skip to content

Commit 3fd485a

Browse files
patrickkusebauchdg
authored andcommitted
Implemented RFC 7239 - "Forwarded HTTP Extension" (#94)
1 parent dff9775 commit 3fd485a

File tree

3 files changed

+153
-20
lines changed

3 files changed

+153
-20
lines changed

src/Http/RequestFactory.php

+57-19
Original file line numberDiff line numberDiff line change
@@ -195,30 +195,68 @@ public function createHttpRequest()
195195
$usingTrustedProxy = $remoteAddr && array_filter($this->proxies, function ($proxy) use ($remoteAddr) {
196196
return Helpers::ipMatch($remoteAddr, $proxy);
197197
});
198-
199198
if ($usingTrustedProxy) {
200-
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
201-
$url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http');
202-
}
199+
if (!empty($_SERVER['HTTP_FORWARDED'])) {
200+
$forwardParams = preg_split('/[,;]/', $_SERVER['HTTP_FORWARDED']);
201+
foreach ($forwardParams as $forwardParam) {
202+
list($key, $value) = explode('=', $forwardParam, 2) + [1 => NULL];
203+
$proxyParams[strtolower(trim($key))][] = trim($value, " \t\"");
204+
}
203205

204-
if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
205-
$url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']);
206-
}
206+
if (isset($proxyParams['for'])) {
207+
$address = $proxyParams['for'][0];
208+
if (strpos($address, '[') === FALSE) { //IPv4
209+
$remoteAddr = explode(':', $address)[0];
210+
} else { //IPv6
211+
$remoteAddr = substr($address, 1, strpos($address, ']') - 1);
212+
}
213+
}
214+
215+
if (isset($proxyParams['host']) && count($proxyParams['host']) === 1) {
216+
$host = $proxyParams['host'][0];
217+
$startingDelimiterPosition = strpos($host, '[');
218+
if ($startingDelimiterPosition === FALSE) { //IPv4
219+
$remoteHostArr = explode(':', $host);
220+
$remoteHost = $remoteHostArr[0];
221+
if (isset($remoteHostArr[1])) {
222+
$url->setPort((int) $remoteHostArr[1]);
223+
}
224+
} else { //IPv6
225+
$endingDelimiterPosition = strpos($host, ']');
226+
$remoteHost = substr($host, strpos($host, '[') + 1, $endingDelimiterPosition - 1);
227+
$remoteHostArr = explode(':', substr($host, $endingDelimiterPosition));
228+
if (isset($remoteHostArr[1])) {
229+
$url->setPort((int) $remoteHostArr[1]);
230+
}
231+
}
232+
}
233+
234+
$scheme = (isset($proxyParams['scheme']) && count($proxyParams['scheme']) === 1) ? $proxyParams['scheme'][0] : 'http';
235+
$url->setScheme(strcasecmp($scheme, 'https') === 0 ? 'https' : 'http');
236+
} else {
237+
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
238+
$url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http');
239+
}
240+
241+
if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
242+
$url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']);
243+
}
207244

208-
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
209-
$xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function ($ip) {
210-
return !array_filter($this->proxies, function ($proxy) use ($ip) {
211-
return Helpers::ipMatch(trim($ip), $proxy);
245+
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
246+
$xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function ($ip) {
247+
return !array_filter($this->proxies, function ($proxy) use ($ip) {
248+
return Helpers::ipMatch(trim($ip), $proxy);
249+
});
212250
});
213-
});
214-
$remoteAddr = trim(end($xForwardedForWithoutProxies));
215-
$xForwardedForRealIpKey = key($xForwardedForWithoutProxies);
216-
}
251+
$remoteAddr = trim(end($xForwardedForWithoutProxies));
252+
$xForwardedForRealIpKey = key($xForwardedForWithoutProxies);
253+
}
217254

218-
if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
219-
$xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
220-
if (isset($xForwardedHost[$xForwardedForRealIpKey])) {
221-
$remoteHost = trim($xForwardedHost[$xForwardedForRealIpKey]);
255+
if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
256+
$xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
257+
if (isset($xForwardedHost[$xForwardedForRealIpKey])) {
258+
$remoteHost = trim($xForwardedHost[$xForwardedForRealIpKey]);
259+
}
222260
}
223261
}
224262
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Http\RequestFactory and proxy with "Forwarded" header.
5+
*/
6+
7+
use Nette\Http\RequestFactory;
8+
use Tester\Assert;
9+
10+
11+
require __DIR__ . '/../bootstrap.php';
12+
13+
test(function () {
14+
$_SERVER = [
15+
'REMOTE_ADDR' => '127.0.0.3',
16+
'REMOTE_HOST' => 'localhost',
17+
'HTTP_FORWARDED' => 'for=23.75.45.200;host=192.168.0.1',
18+
];
19+
20+
$factory = new RequestFactory;
21+
$factory->setProxy('127.0.0.1');
22+
Assert::same('127.0.0.3', $factory->createHttpRequest()->getRemoteAddress());
23+
Assert::same('localhost', $factory->createHttpRequest()->getRemoteHost());
24+
25+
$factory->setProxy('127.0.0.1/8');
26+
Assert::same('23.75.45.200', $factory->createHttpRequest()->getRemoteAddress());
27+
Assert::same('192.168.0.1', $factory->createHttpRequest()->getRemoteHost());
28+
29+
$url = $factory->createHttpRequest()->getUrl();
30+
Assert::same('http', $url->getScheme());
31+
});
32+
33+
test(function () {
34+
$_SERVER = [
35+
'REMOTE_ADDR' => '127.0.0.3',
36+
'REMOTE_HOST' => 'localhost',
37+
'HTTP_FORWARDED' => 'for=23.75.45.200:8080;host=192.168.0.1:8080',
38+
];
39+
40+
$factory = new RequestFactory;
41+
42+
$factory->setProxy('127.0.0.3');
43+
Assert::same('23.75.45.200', $factory->createHttpRequest()->getRemoteAddress());
44+
Assert::same('192.168.0.1', $factory->createHttpRequest()->getRemoteHost());
45+
46+
$url = $factory->createHttpRequest()->getUrl();
47+
Assert::same(8080, $url->getPort());
48+
});
49+
50+
51+
test(function () {
52+
$_SERVER = [
53+
'REMOTE_ADDR' => '127.0.0.3',
54+
'REMOTE_HOST' => 'localhost',
55+
'HTTP_FORWARDED' => 'for="[2001:db8:cafe::17]";host="[2001:db8:cafe::18]"',
56+
];
57+
58+
$factory = new RequestFactory;
59+
60+
$factory->setProxy('127.0.0.3');
61+
Assert::same('2001:db8:cafe::17', $factory->createHttpRequest()->getRemoteAddress());
62+
Assert::same('2001:db8:cafe::18', $factory->createHttpRequest()->getRemoteHost());
63+
});
64+
65+
test(function () {
66+
$_SERVER = [
67+
'REMOTE_ADDR' => '127.0.0.3',
68+
'REMOTE_HOST' => 'localhost',
69+
'HTTP_FORWARDED' => 'for="[2001:db8:cafe::17]:47831";host="[2001:db8:cafe::18]:47832"',
70+
];
71+
72+
$factory = new RequestFactory;
73+
74+
$factory->setProxy('127.0.0.3');
75+
Assert::same('2001:db8:cafe::17', $factory->createHttpRequest()->getRemoteAddress());
76+
Assert::same('2001:db8:cafe::18', $factory->createHttpRequest()->getRemoteHost());
77+
78+
$url = $factory->createHttpRequest()->getUrl();
79+
Assert::same(47832, $url->getPort());
80+
});
81+
82+
83+
test(function () {
84+
$_SERVER = [
85+
'REMOTE_ADDR' => '127.0.0.3',
86+
'REMOTE_HOST' => 'localhost',
87+
'HTTP_FORWARDED' => 'for="[2001:db8:cafe::17]:47831" ; host="[2001:db8:cafe::18]:47832" ; scheme=https',
88+
];
89+
90+
$factory = new RequestFactory;
91+
$factory->setProxy('127.0.0.3');
92+
93+
$url = $factory->createHttpRequest()->getUrl();
94+
Assert::same('https', $url->getScheme());
95+
});

tests/Http/RequestFactory.proxy.phpt tests/Http/RequestFactory.proxy.x-forwarded.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22

33
/**
4-
* Test: Nette\Http\RequestFactory and proxy.
4+
* Test: Nette\Http\RequestFactory and proxy with "X-forwarded" headers.
55
*/
66

77
use Nette\Http\RequestFactory;

0 commit comments

Comments
 (0)