Skip to content

Commit e819eff

Browse files
patrickkusebauchdg
authored andcommitted
Implemented RFC 7239 - "Forwarded HTTP Extension" (#94)
* Implemented RFC 7239 - " Forwarded HTTP Extension" handling in RequestFactory * Implemented RFC 7239 - " Forwarded HTTP Extension" handling in RequestFactory * Deleted echo statement * case- insensitive handling of tokens * Proper handle of quoted strings. Will now work with IPv6. Added tests for port and scheme of the URL * Tests refactoring: - Split test into "x-forwarded" and "forwarded" files for proxy - Added tests for default scheme. - Added tests for every combination of IPv4/IPv6 with and without port for both "host" and "for" headers * Code simplifications * Fixed coding standards for tests
1 parent dff9775 commit e819eff

File tree

3 files changed

+149
-20
lines changed

3 files changed

+149
-20
lines changed

src/Http/RequestFactory.php

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -195,30 +195,64 @@ 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])) $url->setPort((int) $remoteHostArr[1]);
222+
} else { //IPv6
223+
$endingDelimiterPosition = strpos($host, ']');
224+
$remoteHost = substr($host, strpos($host, '[')+1, $endingDelimiterPosition-1);
225+
$remoteHostArr = explode(':', substr($host, $endingDelimiterPosition));
226+
if(isset($remoteHostArr[1])) $url->setPort((int) $remoteHostArr[1]);
227+
}
228+
}
229+
230+
$scheme = (isset($proxyParams['scheme']) && count($proxyParams['scheme']) === 1) ? $proxyParams['scheme'][0] : 'http';
231+
$url->setScheme(strcasecmp($scheme, 'https') === 0 ? 'https' : 'http');
232+
} else {
233+
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
234+
$url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http');
235+
}
236+
237+
if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
238+
$url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']);
239+
}
207240

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);
241+
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
242+
$xForwardedForWithoutProxies = array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']), function ($ip) {
243+
return !array_filter($this->proxies, function ($proxy) use ($ip) {
244+
return Helpers::ipMatch(trim($ip), $proxy);
245+
});
212246
});
213-
});
214-
$remoteAddr = trim(end($xForwardedForWithoutProxies));
215-
$xForwardedForRealIpKey = key($xForwardedForWithoutProxies);
216-
}
247+
$remoteAddr = trim(end($xForwardedForWithoutProxies));
248+
$xForwardedForRealIpKey = key($xForwardedForWithoutProxies);
249+
}
217250

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]);
251+
if (isset($xForwardedForRealIpKey) && !empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
252+
$xForwardedHost = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
253+
if (isset($xForwardedHost[$xForwardedForRealIpKey])) {
254+
$remoteHost = trim($xForwardedHost[$xForwardedForRealIpKey]);
255+
}
222256
}
223257
}
224258
}
Lines changed: 95 additions & 0 deletions
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 renamed to tests/Http/RequestFactory.proxy.x-forwarded.phpt

Lines changed: 1 addition & 1 deletion
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)