RangeUtil::check()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 18
rs 9.9
1
<?php
2
3
namespace Bavix\IP;
4
5
class RangeUtil
6
{
7
8
    /**
9
     * @var array
10
     */
11
    protected $checked = [];
12
13
    /**
14
     * @var array
15
     */
16
    protected $results = [];
17
18
    /**
19
     * @var string
20
     */
21
    protected $lastIP;
22
23
    /**
24
     * @return RangeUtil
25
     */
26
    public static function sharedInstance(): self
27
    {
28
        static $object;
29
30
        if (!$object) {
31
            $object = new static();
32
        }
33
34
        return $object;
35
    }
36
37
    /**
38
     * @param string $ip
39
     * @param string $subnet
40
     * @return bool
41
     */
42
    protected function has(string $ip, string $subnet): bool
43
    {
44
        return isset($this->checked[$subnet][$ip]);
45
    }
46
47
    /**
48
     * @param string $ip
49
     * @param string $subnet
50
     * @return bool
51
     */
52
    protected function get(string $ip, string $subnet): bool
53
    {
54
        return $this->results[$subnet][$ip];
55
    }
56
57
    /**
58
     * @param string $ip
59
     * @param string $subnet
60
     * @param bool $value
61
     * @return bool
62
     */
63
    protected function set(string $ip, string $subnet, bool $value): bool
64
    {
65
        $this->checked[$subnet][$ip] = true;
66
        $this->results[$subnet][$ip] = $value;
67
        return $value;
68
    }
69
70
    /**
71
     * @param string $subnet
72
     * @param int $max
73
     *
74
     * @return int
75
     */
76
    protected function netMask(string $subnet, int $max): int
77
    {
78
        $netMask = $max;
79
        $parts = \explode('/', $subnet, 2);
80
81
        if (\count($parts) === 2) {
82
            $netMask = (int)\array_pop($parts);
83
        }
84
85
        $this->lastIP = \array_pop($parts);
86
        return \max(1, \min($max, $netMask));
87
    }
88
89
    /**
90
     * @param string $ip
91
     * @param string $subnet
92
     *
93
     * @return bool
94
     */
95
    protected function ipv4(string $ip, string $subnet): bool
96
    {
97
        $max = 32;
98
        $netMask = $this->netMask($subnet, $max);
99
        $ipSubnet = $this->lastIP;
100
        $networkLong = \ip2long($ipSubnet);
101
102
        $mask = 0xffffffff << ($max - $netMask);
103
        $ipLong = \ip2long($ip);
104
105
        return ($ipLong & $mask) === ($networkLong & $mask);
106
    }
107
108
    /**
109
     * @param string $ip
110
     * @param string $subnet
111
     *
112
     * @return bool
113
     */
114
    protected function ipv6(string $ip, string $subnet): bool
115
    {
116
        $max = 128;
117
        $netMask = $this->netMask($subnet, $max);
118
        $ipSubnet = $this->lastIP;
119
120
        $bytesSubnet = \unpack('n*', @\inet_pton($ipSubnet));
0 ignored issues
show
Bug introduced by
It seems like @inet_pton($ipSubnet) can also be of type false; however, parameter $string of unpack() does only seem to accept string, 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

120
        $bytesSubnet = \unpack('n*', /** @scrutinizer ignore-type */ @\inet_pton($ipSubnet));
Loading history...
121
        $bytesAddress = \unpack('n*', @\inet_pton($ip));
122
123
        if (empty($bytesSubnet) || empty($bytesAddress)) {
124
            return false;
125
        }
126
127
        for ($i = 1, $ceil = \ceil($netMask / 16); $i <= $ceil; ++$i) {
128
            $left = $netMask - 16 * ($i - 1);
129
            $left = ($left <= 16) ? $left : 16;
130
            $mask = ~(0xffff >> $left) & 0xffff;
131
132
            if (($bytesSubnet[$i] & $mask) !== ($bytesAddress[$i] & $mask)) {
133
                return false;
134
            }
135
        }
136
137
        return true;
138
    }
139
140
    /**
141
     * @param string $ip
142
     * @param string $subnet
143
     * @return bool
144
     */
145
    public function check(string $ip, string $subnet): bool
146
    {
147
        if ($this->has($ip, $subnet)) {
148
            return $this->get($ip, $subnet);
149
        }
150
151
        if (\strpos($ip, '.')) {
152
            return $this->set(
153
                $ip,
154
                $subnet,
155
                $this->ipv4($ip, $subnet)
156
            );
157
        }
158
159
        return $this->set(
160
            $ip,
161
            $subnet,
162
            $this->ipv6($ip, $subnet)
163
        );
164
    }
165
166
}
167