Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
ForwardedFor
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
2 / 2
17
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
 modifyServerData
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
11
1<?php
2
3namespace Miniframe\Middleware;
4
5use Miniframe\Core\AbstractMiddleware;
6use Miniframe\Core\Config;
7use Miniframe\Core\HostMatch;
8use Miniframe\Core\Request;
9use ReflectionClass;
10
11class 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}