Skip to content

Commit dadd667

Browse files
Jakub Chabekdg
Jakub Chabek
authored andcommitted
RequestFactory: correctly detects scheme and port if the server is behind a trusted proxy [Closes #81][Closes #4]
1 parent e0a1de7 commit dadd667

File tree

3 files changed

+186
-13
lines changed

3 files changed

+186
-13
lines changed

src/Http/RequestFactory.php

+22-13
Original file line numberDiff line numberDiff line change
@@ -185,24 +185,33 @@ public function createHttpRequest()
185185
}
186186
}
187187

188+
$remoteAddr = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL;
189+
$remoteHost = !empty($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : NULL;
188190

189-
$remoteAddr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL;
190-
$remoteHost = isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : NULL;
191+
// use real client address and host if trusted proxy is used
192+
$usingTrustedProxy = $remoteAddr && array_filter($this->proxies, function ($proxy) use ($remoteAddr) {
193+
return Helpers::ipMatch($remoteAddr, $proxy);
194+
});
191195

192-
// proxy
193-
foreach ($this->proxies as $proxy) {
194-
if (Helpers::ipMatch($remoteAddr, $proxy)) {
195-
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
196-
$remoteAddr = trim(current(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])));
197-
}
198-
if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
199-
$remoteHost = trim(current(explode(',', $_SERVER['HTTP_X_FORWARDED_HOST'])));
200-
}
201-
break;
196+
if ($usingTrustedProxy) {
197+
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
198+
$url->setScheme(strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 ? 'https' : 'http');
199+
}
200+
201+
if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
202+
$url->setPort((int) $_SERVER['HTTP_X_FORWARDED_PORT']);
202203
}
203-
}
204204

205+
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
206+
$remoteAddr = trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
207+
}
208+
209+
if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
210+
$remoteHost = trim(explode(',', $_SERVER['HTTP_X_FORWARDED_HOST'])[0]);
211+
}
212+
}
205213

214+
// method, eg. GET, PUT, ...
206215
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : NULL;
207216
if ($method === 'POST' && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])
208217
&& preg_match('#^[A-Z]+\z#', $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])

tests/Http/RequestFactory.port.phpt

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
use Tester\Assert;
4+
5+
require __DIR__ . '/../bootstrap.php';
6+
7+
/**
8+
* Test of Nette\Http\RequestFactory port detection
9+
*/
10+
class RequestFactoryPortTest extends Tester\TestCase
11+
{
12+
13+
/**
14+
* @dataProvider providerCreateHttpRequest
15+
*/
16+
public function testCreateHttpRequest($expectedPort, array $server)
17+
{
18+
$_SERVER = $server;
19+
20+
$factory = new Nette\Http\RequestFactory;
21+
Assert::same($expectedPort, $factory->createHttpRequest()->getUrl()->getPort());
22+
}
23+
24+
/**
25+
* @return array
26+
*/
27+
public function providerCreateHttpRequest()
28+
{
29+
return [
30+
[80, []],
31+
[8080, ['HTTP_HOST' => 'localhost:8080']],
32+
[8080, ['SERVER_NAME' => 'localhost:8080']],
33+
[8080, ['HTTP_HOST' => 'localhost:8080', 'SERVER_PORT' => '666']],
34+
[8080, ['SERVER_NAME' => 'localhost:8080', 'SERVER_PORT' => '666']],
35+
[8080, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080']],
36+
[8080, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '8080']],
37+
38+
[80, ['HTTP_X_FORWARDED_PORT' => '8080']],
39+
[8080, ['HTTP_HOST' => 'localhost:8080', 'HTTP_X_FORWARDED_PORT' => '666']],
40+
[8080, ['SERVER_NAME' => 'localhost:8080', 'HTTP_X_FORWARDED_PORT' => '666']],
41+
[8080, ['HTTP_HOST' => 'localhost:8080', 'SERVER_PORT' => '80', 'HTTP_X_FORWARDED_PORT' => '666']],
42+
[8080, ['SERVER_NAME' => 'localhost:8080', 'SERVER_PORT' => '80', 'HTTP_X_FORWARDED_PORT' => '666']],
43+
[80, ['HTTP_HOST' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']],
44+
[80, ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '666']],
45+
[8080, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']],
46+
[8080, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '8080', 'HTTP_X_FORWARDED_PORT' => '666']],
47+
[44443, ['HTTPS' => 'on', 'SERVER_NAME' => 'localhost:44443', 'HTTP_X_FORWARDED_PORT' => '666']],
48+
];
49+
}
50+
51+
/**
52+
* @dataProvider providerCreateHttpRequestWithTrustedProxy
53+
*/
54+
public function testCreateHttpRequestWithTrustedProxy($expectedPort, array $server)
55+
{
56+
$_SERVER = array_merge(['REMOTE_ADDR' => '10.0.0.1'], $server);
57+
58+
$factory = new Nette\Http\RequestFactory;
59+
$factory->setProxy(['10.0.0.1']);
60+
Assert::same($expectedPort, $factory->createHttpRequest()->getUrl()->getPort());
61+
}
62+
63+
/**
64+
* @return array
65+
*/
66+
public function providerCreateHttpRequestWithTrustedProxy()
67+
{
68+
return [
69+
[8080, ['HTTP_X_FORWARDED_PORT' => '8080']],
70+
[8080, ['HTTP_HOST' => 'localhost:666', 'HTTP_X_FORWARDED_PORT' => '8080']],
71+
[8080, ['SERVER_NAME' => 'localhost:666', 'HTTP_X_FORWARDED_PORT' => '8080']],
72+
[8080, ['HTTP_HOST' => 'localhost:666', 'SERVER_PORT' => '80', 'HTTP_X_FORWARDED_PORT' => '8080']],
73+
[8080, ['SERVER_NAME' => 'localhost:666', 'SERVER_PORT' => '80', 'HTTP_X_FORWARDED_PORT' => '8080']],
74+
[8080, ['HTTP_HOST' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '8080']],
75+
[8080, ['SERVER_NAME' => 'localhost', 'HTTP_X_FORWARDED_PORT' => '8080']],
76+
[8080, ['HTTP_HOST' => 'localhost', 'SERVER_PORT' => '666', 'HTTP_X_FORWARDED_PORT' => '8080']],
77+
[8080, ['SERVER_NAME' => 'localhost', 'SERVER_PORT' => '666', 'HTTP_X_FORWARDED_PORT' => '8080']],
78+
[44443, ['HTTPS' => 'on', 'SERVER_NAME' => 'localhost:666', 'HTTP_X_FORWARDED_PORT' => '44443']],
79+
];
80+
}
81+
82+
}
83+
84+
$test = new RequestFactoryPortTest();
85+
$test->run();

tests/Http/RequestFactory.scheme.phpt

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
use Tester\Assert;
4+
5+
require __DIR__ . '/../bootstrap.php';
6+
7+
/**
8+
* Test of Nette\Http\RequestFactory schema detection
9+
*/
10+
class RequestFactorySchemeTest extends Tester\TestCase
11+
{
12+
13+
/**
14+
* @covers RequestFactory::getScheme
15+
* @dataProvider providerCreateHttpRequest
16+
*/
17+
public function testCreateHttpRequest($expectedScheme, array $server)
18+
{
19+
$_SERVER = $server;
20+
21+
$factory = new Nette\Http\RequestFactory;
22+
Assert::same($expectedScheme, $factory->createHttpRequest()->getUrl()->getScheme());
23+
}
24+
25+
/**
26+
* @return array
27+
*/
28+
public function providerCreateHttpRequest()
29+
{
30+
return [
31+
['http', []],
32+
['http', ['HTTPS' => '']],
33+
['http', ['HTTPS' => 'off']],
34+
['http', ['HTTP_X_FORWARDED_PROTO' => 'https']],
35+
['http', ['HTTP_X_FORWARDED_PORT' => '443']],
36+
['http', ['HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '443']],
37+
38+
['https', ['HTTPS' => 'on']],
39+
['https', ['HTTPS' => 'anything']],
40+
['https', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']],
41+
['https', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PORT' => '80']],
42+
['https', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '80']],
43+
];
44+
}
45+
46+
/**
47+
* @covers RequestFactory::getScheme
48+
* @dataProvider providerCreateHttpRequestWithTrustedProxy
49+
*/
50+
public function testCreateHttpRequestWithTrustedProxy($expectedScheme, array $server)
51+
{
52+
$_SERVER = array_merge(['REMOTE_ADDR' => '10.0.0.1'], $server);
53+
54+
$factory = new Nette\Http\RequestFactory;
55+
$factory->setProxy(['10.0.0.1']);
56+
Assert::same($expectedScheme, $factory->createHttpRequest()->getUrl()->getScheme());
57+
}
58+
59+
/**
60+
* @return array
61+
*/
62+
public function providerCreateHttpRequestWithTrustedProxy()
63+
{
64+
return [
65+
['http', ['HTTP_X_FORWARDED_PROTO' => 'http']],
66+
['http', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http']],
67+
['http', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'something-unexpected']],
68+
['http', ['HTTPS' => 'on', 'HTTP_X_FORWARDED_PROTO' => 'http', 'HTTP_X_FORWARDED_PORT' => '443']],
69+
70+
['https', ['HTTP_X_FORWARDED_PROTO' => 'https']],
71+
['https', ['HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https']],
72+
['https', ['HTTPS' => 'off', 'HTTP_X_FORWARDED_PROTO' => 'https', 'HTTP_X_FORWARDED_PORT' => '80']],
73+
];
74+
}
75+
76+
}
77+
78+
$test = new RequestFactorySchemeTest();
79+
$test->run();

0 commit comments

Comments
 (0)