Completed
Push — director-middleware ( 059969...3cff84 )
by Sam
08:52
created

TrustedProxyMiddleware::setProxyHostHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control;
4
5
/**
6
 * This middleware will rewrite headers that provide IP and host details from an upstream proxy.
7
 */
8
class TrustedProxyMiddleware implements HTTPMiddleware
9
{
10
11
    private $trustedProxyIPs = null;
12
13
    private $proxyHostHeaders = [
14
        'X-Forwarded-Host'
15
    ];
16
17
    private $proxyIPHeaders = [
18
        'Client-IP',
19
        'X-Forwarded-For'
20
    ];
21
22
    private $proxySchemeHeaders = [
23
        'X-Forwarded-Protocol',
24
        'X-Forwarded-Proto',
25
    ];
26
27
    public function getTrustedProxyIPs()
28
    {
29
        return $this->trustedProxyIPs;
30
    }
31
32
    public function setTrustedProxyIPs($trustedProxyIPs)
33
    {
34
        $this->trustedProxyIPs = $trustedProxyIPs;
35
    }
36
37
    public function getProxyHostHeaders()
38
    {
39
        return $this->proxyHostHeaders;
40
    }
41
42
    public function setProxyHostHeaders($proxyHostHeaders)
43
    {
44
        $this->proxyHostHeaders = $proxyHostHeaders;
45
    }
46
47
    public function getProxyIPHeaders()
48
    {
49
        return $this->proxyIPHeaders;
50
    }
51
52
    public function setProxyIPHeaders($proxyIPHeaders)
53
    {
54
        $this->proxyIPHeaders = $proxyIPHeaders;
55
    }
56
57
    public function getProxySchemeHeaders()
58
    {
59
        return $this->proxySchemeHeaders;
60
    }
61
62
    public function setProxySchemeHeaders($proxySchemeHeaders)
63
    {
64
        $this->proxySchemeHeaders = $proxySchemeHeaders;
65
    }
66
67
    public function process(HTTPRequest $request, callable $delegate)
68
    {
69
        // If this is a trust proxy
70
        if ($this->isTrustedProxy($request)) {
71
            // Replace host
72
            foreach ($this->proxyHostHeaders as $header) {
73
                $hostList = $request->getHeader($header);
74
                if ($hostList) {
75
                    $request->setHeader('Host', strtok($hostList, ','));
76
                    break;
77
                }
78
            }
79
80
            // Replace scheme
81
            foreach ($this->proxySchemeHeaders as $header) {
82
                $scheme = $request->getHeader($header);
83
                if ($scheme) {
84
                    $request->setScheme(strtolower($scheme));
85
                    break;
86
                }
87
            }
88
89
            // Replace IP
90
            foreach ($this->proxyIPHeaders as $header) {
91
                $ipHeader = $this->getIPFromHeaderValue($request->getHeader($header));
92
                if ($ipHeader) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ipHeader of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
93
                    $request->setIP($ipHeader);
94
                    break;
95
                }
96
            }
97
        }
98
99
        return $delegate($request);
100
    }
101
102
    /**
103
     * Determine if the current request is coming from a trusted proxy
104
     *
105
     * @return boolean True if the request's source IP is a trusted proxy
106
     */
107
    protected function isTrustedProxy($request)
108
    {
109
        // Disabled
110
        if (empty($this->trustedProxyIPs) || $trustedIPs === 'none') {
0 ignored issues
show
Bug introduced by
The variable $trustedIPs does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
111
            return false;
112
        }
113
114
        // Allow all
115
        if ($trustedIPs === '*') {
116
            return true;
117
        }
118
119
        // Validate IP address
120
        if ($ip = $request->getIP()) {
121
            return IPUtils::checkIP($ip, explode(',', $trustedIPs));
122
        }
123
124
        return false;
125
    }
126
127
    /**
128
     * Extract an IP address from a header value that has been obtained.
129
     * Accepts single IP or comma separated string of IPs
130
     *
131
     * @param string $headerValue The value from a trusted header
132
     * @return string The IP address
133
     */
134
    protected function getIPFromHeaderValue($headerValue)
135
    {
136
        if (strpos($headerValue, ',') !== false) {
137
            //sometimes the IP from a load balancer could be "x.x.x.x, y.y.y.y, z.z.z.z" so we need to find the most
138
            // likely candidate
139
            $ips = explode(',', $headerValue);
140
            foreach ($ips as $ip) {
141
                $ip = trim($ip);
142
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
143
                    return $ip;
144
                } else {
145
                    return null;
146
                }
147
            }
148
        }
149
        return $headerValue;
150
    }
151
}
152