Issues (1)

src/IP.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace cse\helpers;
6
7
/**
8
 * Class IP
9
 *
10
 * @package cse\helpers
11
 */
12
class IP
13
{
14
    const IP_VERSION_4 = 4;
15
    const IP_VERSION_6 = 6;
16
17
    /**
18
     * Get real IP
19
     *
20
     * @return null|string
21
     */
22
    public static function getRealIP(): ?string
23
    {
24
        if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
25
            // Check ip from share internet
26
            $ip = $_SERVER['HTTP_X_REAL_IP'];
27
        } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
28
            // Check ip from share internet
29
            $ip = $_SERVER['HTTP_CLIENT_IP'];
30
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
31
            // To check ip is pass from proxy
32
            $ips = explode(',', str_replace(' ', '', $_SERVER['HTTP_X_FORWARDED_FOR']));
33
            $ips = array_filter($ips);
34
            if (count($ips)) $ip = array_pop($ips);
35
        }
36
37
        return empty($ip) ? ($_SERVER['REMOTE_ADDR'] ?? null) : $ip;
38
    }
39
40
    /**
41
     * Remove subnet mask to IPv6
42
     *
43
     * @param string $ip
44
     *
45
     * @return null|string
46
     */
47
    public static function removeSubnetMaskIPv6(string $ip): ?string
48
    {
49
        return preg_replace('/^(.*)(\/[\d]*)$/', '${1}', $ip);
50
    }
51
52
    /**
53
     * Check is IPv4 address
54
     *
55
     * @param string $ip
56
     *
57
     * @return bool
58
     */
59
    public static function isIPv4(string $ip): bool
60
    {
61
        return (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
62
    }
63
64
    /**
65
     * Check is IPv4 address
66
     *
67
     * @param string $ip
68
     *
69
     * @return bool
70
     */
71
    public static function isIPv6(string $ip): bool
72
    {
73
        return (bool) filter_var(self::removeSubnetMaskIPv6($ip), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
74
    }
75
76
    /**
77
     * Get version IP address
78
     *
79
     * @param string $ip
80
     *
81
     * @return int|null
82
     */
83
    public static function getVersionIP(string $ip): ?int
84
    {
85
        return self::isIPv4($ip) ? self::IP_VERSION_4 : (self::isIPv6($ip) ? self::IP_VERSION_6 : null);
86
    }
87
88
    /**
89
     * Is IP address
90
     *
91
     * @param string $ip
92
     *
93
     * @return bool
94
     */
95
    public static function isIP(string $ip): bool
96
    {
97
        return is_int(self::getVersionIP($ip));
98
    }
99
100
    /**
101
     * Get range IPv6 address
102
     *
103
     * @example 2a0a:2b40::4:60/124 => [2a0a:2b40::4:60, 2a0a:2b40::4:6f]
104
     *
105
     * @param $ip
106
     *
107
     * @return array
108
     */
109
    public static function getRangeIPv6(string $ip): array
110
    {
111
        list($first_addr_str, $prefix_len) = explode('/', $ip);
112
113
        // Parse the address into a binary string
114
        $first_addr_bin = inet_pton($first_addr_str);
115
        // Convert the binary string to a string with hexadecimal characters
116
        $addr_hex = unpack('H*', $first_addr_bin);
117
        $first_addr_hex = reset($addr_hex);
0 ignored issues
show
It seems like $addr_hex can also be of type false; however, parameter $array of reset() does only seem to accept array, 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

117
        $first_addr_hex = reset(/** @scrutinizer ignore-type */ $addr_hex);
Loading history...
118
        // Overwriting first address string to make sure notation is optimal
119
        $first_addr_str = inet_ntop($first_addr_bin);
120
        // Calculate the number of 'flexible' bits
121
        $flex_bits = 128 - $prefix_len;
122
        // Build the hexadecimal string of the last address
123
        $last_addr_hex = $first_addr_hex;
124
125
        // We start at the end of the string (which is always 32 characters long)
126
        $pos = 31;
127
        while ($flex_bits > 0) {
128
            // Get the character at this position
129
            $orig = substr($last_addr_hex, $pos, 1);
130
            // Convert it to an integer
131
            $origval = hexdec($orig);
132
            // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
133
            $new_val = $origval | (pow(2, min(4, $flex_bits)) - 1);
134
            // Convert it back to a hexadecimal character
135
            $new = dechex($new_val);
136
            // And put that character back in the string
137
            $last_addr_hex = substr_replace($last_addr_hex, $new, $pos, 1);
138
            // We processed one nibble, move to previous position
139
            $flex_bits -= 4;
140
            $pos -= 1;
141
        }
142
143
        // Convert the hexadecimal string to a binary string
144
        $last_addr_bin = pack('H*', $last_addr_hex);
145
        // And create an IPv6 address from the binary string
146
        $last_addr_str = inet_ntop($last_addr_bin);
147
148
        return [
149
            $first_addr_str,
150
            $last_addr_str,
151
        ];
152
    }
153
154
    /**
155
     * Filter IPs address
156
     *
157
     * @param array $ips
158
     * @param int|null $version
159
     *
160
     * @return array
161
     */
162
    public static function filterIPs(array $ips, ?int $version = null): array
163
    {
164
        $result = [self::IP_VERSION_4 => [], self::IP_VERSION_6 => []];
165
166
        foreach ($ips as $ip) {
167
            $current_version = self::getVersionIP($ip);
168
            if (is_int($current_version)) $result[$current_version][] = $ip;
169
        }
170
171
        return is_int($version) ? $result[$version] : $result;
172
    }
173
174
    /**
175
     * Get first ip by version
176
     *
177
     * @param array $ips
178
     * @param int|null $version
179
     *
180
     * @return null|string
181
     */
182
    public static function getFirstIPByVersion (array $ips, int $version): ?string
183
    {
184
        foreach ($ips as $ip) {
185
            $current_version = self::getVersionIP($ip);
186
            if (is_int($current_version) && $version == $current_version) return $ip;
187
        }
188
189
        return null;
190
    }
191
}