Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
37 / 37 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
ForwardedFor | |
100.00% |
37 / 37 |
|
100.00% |
2 / 2 |
17 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
6 | |||
modifyServerData | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
11 |
1 | <?php |
2 | |
3 | namespace Miniframe\Middleware; |
4 | |
5 | use Miniframe\Core\AbstractMiddleware; |
6 | use Miniframe\Core\Config; |
7 | use Miniframe\Core\HostMatch; |
8 | use Miniframe\Core\Request; |
9 | use ReflectionClass; |
10 | |
11 | class ForwardedFor extends AbstractMiddleware |
12 | { |
13 | /** |
14 | * Initializes the ForwardedFor middleware |
15 | * |
16 | * @param Request $request Reference to the Request object. |
17 | * @param Config $config Reference to the Config object. |
18 | */ |
19 | public function __construct(Request $request, Config $config) |
20 | { |
21 | parent::__construct($request, $config); |
22 | |
23 | // Do we have an IP address? |
24 | $validateIp = $request->getServer('REMOTE_ADDR'); |
25 | if (!is_string($validateIp)) { |
26 | return; |
27 | } |
28 | |
29 | // Only continue when one of these headers exist |
30 | if ( |
31 | $request->getServer('HTTP_X_FORWARDED_FOR') === null |
32 | && $request->getServer('HTTP_X_FORWARDED_HOST') === null |
33 | && $request->getServer('HTTP_X_FORWARDED_PROTO') === null |
34 | ) { |
35 | return; |
36 | } |
37 | |
38 | // When there's no proxyhost match, skip (null = no directive, true = match proxyhost, false = no match) |
39 | if ((new HostMatch())->matchList($config, 'forwarded-for', 'proxyhost', $validateIp) === false) { |
40 | return; |
41 | } |
42 | |
43 | // Change Request object |
44 | $reflectionClass = new ReflectionClass(Request::class); |
45 | $reflectionProperty = $reflectionClass->getProperty('server_data'); |
46 | $reflectionProperty->setAccessible(true); |
47 | $serverData = $reflectionProperty->getValue($request); |
48 | $this->modifyServerData($serverData); |
49 | $reflectionProperty->setValue($request, $serverData); |
50 | |
51 | // Change global |
52 | $this->modifyServerData($_SERVER); |
53 | } |
54 | |
55 | /** |
56 | * Modifies the server data |
57 | * |
58 | * @param array<string, string> $serverData Server data array by reference. |
59 | * |
60 | * @return void |
61 | */ |
62 | private function modifyServerData(array &$serverData): void |
63 | { |
64 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For |
65 | if (isset($serverData['HTTP_X_FORWARDED_FOR'])) { |
66 | $serverData['REMOTE_ADDR'] = trim(explode(',', $serverData['HTTP_X_FORWARDED_FOR'], 2)[0]); |
67 | unset($serverData['HTTP_X_FORWARDED_FOR']); |
68 | } |
69 | |
70 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host |
71 | if (isset($serverData['HTTP_X_FORWARDED_HOST'])) { |
72 | $serverData['HTTP_HOST'] = $serverData['HTTP_X_FORWARDED_HOST']; |
73 | unset($serverData['HTTP_X_FORWARDED_HOST']); |
74 | } |
75 | |
76 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto |
77 | if (isset($serverData['HTTP_X_FORWARDED_PROTO'])) { |
78 | $currentPort = $serverData['SERVER_PORT']; |
79 | $currentProtocol = isset($serverData['HTTPS']) ? 'https' : 'http'; |
80 | if ( |
81 | strtolower($serverData['HTTP_X_FORWARDED_PROTO']) == 'https' |
82 | && $currentProtocol == 'http' |
83 | ) { |
84 | $serverData['HTTPS'] = 'on'; |
85 | if ($currentPort == 80) { |
86 | $serverData['SERVER_PORT'] = 443; |
87 | } |
88 | } elseif ( |
89 | $serverData['HTTP_X_FORWARDED_PROTO'] == 'http' |
90 | && $currentProtocol == 'https' |
91 | ) { |
92 | unset($serverData['HTTPS']); |
93 | if ($currentPort == 443) { |
94 | $serverData['SERVER_PORT'] = 80; |
95 | } |
96 | } |
97 | unset($serverData['HTTP_X_FORWARDED_PROTO']); |
98 | } |
99 | } |
100 | } |