Passed
Push — ipv6 ( 5c173f...2a70ad )
by Richard
08:32
created

XffTrustProvider::isIpInRange()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 6
dl 0
loc 11
rs 9.6111
c 1
b 1
f 0
cc 5
nc 3
nop 3
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 * ACC Development Team. Please see team.json for a list of contributors.     *
5
 *                                                                            *
6
 * This is free and unencumbered software released into the public domain.    *
7
 * Please see LICENSE.md for the full licencing statement.                    *
8
 ******************************************************************************/
9
10
namespace Waca\Providers;
11
12
use PDOStatement;
13
use Waca\PdoDatabase;
14
use Waca\Providers\Interfaces\IXffTrustProvider;
15
16
/**
17
 * XffTrustProvider short summary.
18
 *
19
 * XffTrustProvider description.
20
 *
21
 * @version 1.0
22
 * @author  stwalkerster
23
 */
24
class XffTrustProvider implements IXffTrustProvider
25
{
26
    /**
27
     * Array of IP addresses which are TRUSTED proxies
28
     * @var string[]
29
     */
30
    private $trustedCache;
31
    /**
32
     * Array of IP addresses which are UNTRUSTED proxies
33
     * @var string[]
34
     */
35
    private $untrustedCache = array();
36
    /** @var PDOStatement */
37
    private $trustedQuery;
38
    /**
39
     * @var PdoDatabase
40
     */
41
    private $database;
42
43
    /**
44
     * Creates a new instance of the trust provider
45
     *
46
     * @param string[]    $squidIpList List of IP addresses to pre-approve
47
     * @param PdoDatabase $database
48
     */
49
    public function __construct($squidIpList, PdoDatabase $database)
50
    {
51
        $this->trustedCache = $squidIpList;
52
        $this->database = $database;
53
    }
54
55
    /**
56
     * Returns a value if the IP address is a trusted proxy
57
     *
58
     * @param string $ip
59
     *
60
     * @return bool
61
     */
62
    public function isTrusted($ip)
63
    {
64
        if (in_array($ip, $this->trustedCache)) {
65
            return true;
66
        }
67
68
        if (in_array($ip, $this->untrustedCache)) {
69
            return false;
70
        }
71
72
        if ($this->trustedQuery === null) {
73
            $query = "SELECT COUNT(id) FROM xfftrustcache WHERE ip = :ip;";
74
            $this->trustedQuery = $this->database->prepare($query);
75
        }
76
77
        $this->trustedQuery->execute(array(":ip" => $ip));
78
        $result = $this->trustedQuery->fetchColumn();
79
        $this->trustedQuery->closeCursor();
80
81
        if ($result == 0) {
82
            $this->untrustedCache[] = $ip;
83
84
            return false;
85
        }
86
87
        if ($result >= 1) {
88
            $this->trustedCache[] = $ip;
89
90
            return true;
91
        }
92
93
        // something weird has happened if we've got here.
94
        // default to untrusted.
95
        return false;
96
    }
97
98
    /**
99
     * Gets the last trusted IP in the proxy chain.
100
     *
101
     * @param string $ip      The IP address from REMOTE_ADDR
102
     * @param string $proxyIp The contents of the XFF header.
103
     *
104
     * @return string Trusted source IP address
105
     */
106
    public function getTrustedClientIp($ip, $proxyIp)
107
    {
108
        $clientIpAddress = $ip;
109
        if ($proxyIp) {
110
            $ipList = explode(",", $proxyIp);
111
            $ipList[] = $clientIpAddress;
112
            $ipList = array_reverse($ipList);
113
114
            foreach ($ipList as $ipNumber => $ipAddress) {
115
                if ($this->isTrusted(trim($ipAddress)) && $ipNumber < (count($ipList) - 1)) {
116
                    continue;
117
                }
118
119
                $clientIpAddress = $ipAddress;
120
                break;
121
            }
122
        }
123
124
        return trim($clientIpAddress);
125
    }
126
127
    /**
128
     * Checks if an IP address is in a range, supporting both IPv4 and IPv6.
129
     *
130
     * @param string $low  The lower bound of the range.
131
     * @param string $high The upper bound of the range.
132
     * @param string $ip   The IP address to check.
133
     *
134
     * @return bool True if the IP is in the range, false otherwise.
135
     */
136
    private function isIpInRange($low, $high, $ip)
137
    {
138
        $lowBinary = inet_pton($low);
139
        $highBinary = inet_pton($high);
140
        $ipBinary = inet_pton($ip);
141
142
        if ($lowBinary === false || $highBinary === false || $ipBinary === false) {
143
            return false; // Invalid IP address
144
        }
145
146
        return strcmp($lowBinary, $ipBinary) <= 0 && strcmp($highBinary, $ipBinary) >= 0;
147
    }
148
149
    /**
150
     * Takes an array( "low" => "high" ) values, and returns true if $needle is in at least one of them.
151
     *
152
     * @param array  $haystack
153
     * @param string $ip
154
     *
155
     * @return bool
156
     */
157
    public function ipInRange($haystack, $ip)
158
    {
159
        foreach ($haystack as $low => $high) {
160
            if ($this->isIpInRange($low, $high, $ip)) {
161
                return true;
162
            }
163
        }
164
165
        return false;
166
    }
167
168
    /**
169
     * Explodes a CIDR range into an array of addresses
170
     *
171
     * @param string $range A CIDR-format range
172
     *
173
     * @return array An array containing every IP address in the range
174
     */
175
    public function explodeCidr($range)
176
    {
177
        $cidrData = explode('/', $range);
178
179
        if (!isset($cidrData[1])) {
180
            return array($range);
181
        }
182
183
        $blow = (
184
            str_pad(decbin(ip2long($cidrData[0])), 32, "0", STR_PAD_LEFT) &
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise & or did you mean &&?
Loading history...
185
            str_pad(str_pad("", $cidrData[1], "1"), 32, "0")
0 ignored issues
show
Bug introduced by
$cidrData[1] of type string is incompatible with the type integer expected by parameter $length of str_pad(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

185
            str_pad(str_pad("", /** @scrutinizer ignore-type */ $cidrData[1], "1"), 32, "0")
Loading history...
186
        );
187
        $bhigh = ($blow | str_pad(str_pad("", $cidrData[1], "0"), 32, "1"));
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
188
189
        $list = array();
190
191
        $bindecBHigh = bindec($bhigh);
192
        for ($x = bindec($blow); $x <= $bindecBHigh; $x++) {
193
            $list[] = long2ip($x);
0 ignored issues
show
Bug introduced by
It seems like $x can also be of type double; however, parameter $ip of long2ip() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
            $list[] = long2ip(/** @scrutinizer ignore-type */ $x);
Loading history...
194
        }
195
196
        return $list;
197
    }
198
}
199