IpRule::fillAddress()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 2
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Bluz Framework Component
5
 *
6
 * @copyright Bluz PHP Team
7
 * @link      https://github.com/bluzphp/framework
8
 */
9
10
namespace Bluz\Validator\Rule;
11
12
use Bluz\Validator\Exception\ComponentException;
13
14
/**
15
 * Check for IP
16
 *
17
 * Strict mode disabled for this file, because function long2ip() was changed in PHP 7.1
18
 *
19
 * @package Bluz\Validator\Rule
20
 */
21
class IpRule extends AbstractRule
22
{
23
    /**
24
     * @var integer setup options
25
     */
26
    protected $options;
27
28
    /**
29
     * @var array network range
30
     */
31
    protected $networkRange;
32
33
    /**
34
     * Setup validation rule
35
     *
36
     * @param mixed $options
37
     *
38
     * @throws ComponentException
39
     */
40 41
    public function __construct($options = null)
41
    {
42 41
        if (is_int($options)) {
43 1
            $this->options = $options;
44 1
            return;
45
        }
46
47 40
        $this->networkRange = $this->parseRange($options);
48 32
    }
49
50
    /**
51
     * Check input data
52
     *
53
     * @param  string $input
54
     *
55
     * @return bool
56
     */
57 33
    public function validate($input): bool
58
    {
59 33
        return $this->verifyAddress($input) && $this->verifyNetwork($input);
60
    }
61
62
    /**
63
     * Get error template
64
     *
65
     * @return string
66
     */
67 33
    public function getDescription(): string
68
    {
69 33
        if (!empty($this->networkRange)) {
70 23
            $message = $this->networkRange['min'];
71 23
            if (isset($this->networkRange['max'])) {
72 17
                $message .= '-' . $this->networkRange['max'];
73
            } else {
74 6
                $message .= '/' . long2ip((string)bindec($this->networkRange['mask']));
0 ignored issues
show
Bug introduced by
(string)bindec($this->networkRange['mask']) of type string is incompatible with the type integer expected by parameter $ip of long2ip(). ( Ignorable by Annotation )

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

74
                $message .= '/' . long2ip(/** @scrutinizer ignore-type */ (string)bindec($this->networkRange['mask']));
Loading history...
75
            }
76 23
            return __('must be an IP address in the "%s" range', $message);
77
        }
78 10
        return __('must be an IP address');
79
    }
80
81
    /**
82
     * Parse IP range
83
     *
84
     * @param  string $input
85
     *
86
     * @return array|null
87
     * @throws ComponentException
88
     */
89 40
    protected function parseRange($input): ?array
90
    {
91
        if (
92 40
            $input === null || $input === '*' || $input === '*.*.*.*'
93 40
            || $input === '0.0.0.0-255.255.255.255'
94
        ) {
95 9
            return null;
96
        }
97
98 31
        $range = ['min' => null, 'max' => null, 'mask' => null];
99
100 31
        if (strpos($input, '-') !== false) {
101 9
            [$range['min'], $range['max']] = explode('-', $input);
102 22
        } elseif (strpos($input, '*') !== false) {
103 11
            $this->parseRangeUsingWildcards($input, $range);
104 11
        } elseif (strpos($input, '/') !== false) {
105 9
            $this->parseRangeUsingCidr($input, $range);
106
        } else {
107 2
            throw new ComponentException('Invalid network range');
108
        }
109
110 26
        if (!$this->verifyAddress($range['min'])) {
111 1
            throw new ComponentException('Invalid network range');
112
        }
113
114 25
        if (isset($range['max']) && !$this->verifyAddress($range['max'])) {
115 2
            throw new ComponentException('Invalid network range');
116
        }
117
118 23
        return $range;
119
    }
120
121
    /**
122
     * Fill address
123
     *
124
     * @param string $input
125
     * @param string $char
126
     */
127 20
    protected function fillAddress(&$input, $char = '*'): void
128
    {
129 20
        while (substr_count($input, '.') < 3) {
130 5
            $input .= '.' . $char;
131
        }
132 20
    }
133
134
    /**
135
     * Parse range using wildcards
136
     *
137
     * @param string $input
138
     * @param array  $range
139
     */
140 11
    protected function parseRangeUsingWildcards($input, &$range): void
141
    {
142 11
        $this->fillAddress($input);
143
144 11
        $range['min'] = str_replace('*', '0', $input);
145 11
        $range['max'] = str_replace('*', '255', $input);
146 11
    }
147
148
    /**
149
     * Parse range using Classless Inter-Domain Routing (CIDR)
150
     *
151
     * @param  string $input
152
     * @param  array  $range
153
     *
154
     * @throws ComponentException
155
     * @link http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
156
     */
157 9
    protected function parseRangeUsingCidr($input, &$range): void
158
    {
159 9
        $input = explode('/', $input);
160 9
        $this->fillAddress($input[0], '0');
161
162 9
        $range['min'] = $input[0];
163 9
        $isAddressMask = strpos($input[1], '.') !== false;
164
165 9
        if ($isAddressMask && $this->verifyAddress($input[1])) {
166 2
            $range['mask'] = sprintf('%032b', ip2long($input[1]));
167 2
            return;
168
        }
169
170 7
        if ($isAddressMask || $input[1] < 8 || $input[1] > 30) {
171 3
            throw new ComponentException('Invalid network mask');
172
        }
173
174 4
        $range['mask'] = sprintf('%032b', ip2long(long2ip(~(2 ** (32 - $input[1]) - 1))));
175 4
    }
176
177
    /**
178
     * Verify IP address
179
     *
180
     * @param  string $address
181
     *
182
     * @return bool
183
     */
184 38
    protected function verifyAddress($address): bool
185
    {
186 38
        return (bool)filter_var(
187 38
            $address,
188 38
            FILTER_VALIDATE_IP,
189
            [
190 38
                'flags' => $this->options
191
            ]
192
        );
193
    }
194
195
    /**
196
     * Verify Network by mask
197
     *
198
     * @param  string $input
199
     *
200
     * @return bool
201
     */
202 27
    protected function verifyNetwork($input): bool
203
    {
204 27
        if ($this->networkRange === null) {
205 4
            return true;
206
        }
207
208 23
        if (isset($this->networkRange['mask'])) {
209 6
            return $this->belongsToSubnet($input);
210
        }
211
212 17
        $input = sprintf('%u', ip2long($input));
213
214 17
        $min = sprintf('%u', ip2long($this->networkRange['min']));
215 17
        $max = sprintf('%u', ip2long($this->networkRange['max']));
216
217 17
        return ($input >= $min) && ($input <= $max);
218
    }
219
220
    /**
221
     * Check subnet
222
     *
223
     * @param  string $input
224
     *
225
     * @return bool
226
     */
227 6
    protected function belongsToSubnet($input): bool
228
    {
229 6
        $range = $this->networkRange;
230 6
        $min = sprintf('%032b', ip2long($range['min']));
231 6
        $input = sprintf('%032b', ip2long($input));
232
233 6
        return ($input & $range['mask']) === ($min & $range['mask']);
234
    }
235
}
236