Completed
Push — master ( 38deb0...7544ac )
by Anton
11s
created

IpRule::parseRangeUsingWildcards()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Bluz Framework Component
4
 *
5
 * @copyright Bluz PHP Team
6
 * @link      https://github.com/bluzphp/framework
7
 */
8
9
namespace Bluz\Validator\Rule;
10
11
use Bluz\Validator\Exception\ComponentException;
12
13
/**
14
 * Check for IP
15
 *
16
 * Strict mode disabled for this file, because function long2ip() was changed in PHP 7.1
17
 *
18
 * @package Bluz\Validator\Rule
19
 */
20
class IpRule extends AbstractRule
21
{
22
    /**
23
     * @var integer setup options
24
     */
25
    protected $options;
26
27
    /**
28
     * @var array network range
29
     */
30
    protected $networkRange;
31
32
    /**
33
     * Setup validation rule
34
     *
35
     * @param mixed $options
36
     *
37
     * @throws \Bluz\Validator\Exception\ComponentException
38
     */
39 41
    public function __construct($options = null)
40
    {
41 41
        if (is_int($options)) {
42 1
            $this->options = $options;
43 1
            return;
44
        }
45
46 40
        $this->networkRange = $this->parseRange($options);
47 32
    }
48
49
    /**
50
     * Parse IP range
51
     *
52
     * @param  string $input
53
     *
54
     * @return array
55
     * @throws \Bluz\Validator\Exception\ComponentException
56
     */
57 40
    protected function parseRange($input)
58
    {
59 40
        if ($input === null || $input === '*' || $input === '*.*.*.*'
60 40
            || $input === '0.0.0.0-255.255.255.255'
61
        ) {
62 9
            return null;
63
        }
64
65 31
        $range = ['min' => null, 'max' => null, 'mask' => null];
66
67 31
        if (strpos($input, '-') !== false) {
68 9
            list($range['min'], $range['max']) = explode('-', $input);
69 22
        } elseif (strpos($input, '*') !== false) {
70 11
            $this->parseRangeUsingWildcards($input, $range);
71 11
        } elseif (strpos($input, '/') !== false) {
72 9
            $this->parseRangeUsingCidr($input, $range);
73
        } else {
74 2
            throw new ComponentException('Invalid network range');
75
        }
76
77 26
        if (!$this->verifyAddress($range['min'])) {
78 1
            throw new ComponentException('Invalid network range');
79
        }
80
81 25
        if (isset($range['max']) && !$this->verifyAddress($range['max'])) {
82 2
            throw new ComponentException('Invalid network range');
83
        }
84
85 23
        return $range;
86
    }
87
88
    /**
89
     * Fill address
90
     *
91
     * @param string $input
92
     * @param string $char
93
     */
94 20
    protected function fillAddress(&$input, $char = '*')
95
    {
96 20
        while (substr_count($input, '.') < 3) {
97 5
            $input .= '.' . $char;
98
        }
99 20
    }
100
101
    /**
102
     * Parse range using wildcards
103
     *
104
     * @param string $input
105
     * @param array  $range
106
     */
107 11
    protected function parseRangeUsingWildcards($input, &$range)
108
    {
109 11
        $this->fillAddress($input);
110
111 11
        $range['min'] = strtr($input, '*', '0');
112 11
        $range['max'] = str_replace('*', '255', $input);
113 11
    }
114
115
    /**
116
     * Parse range using Classless Inter-Domain Routing (CIDR)
117
     *
118
     * @param  string $input
119
     * @param  array  $range
120
     *
121
     * @throws \Bluz\Validator\Exception\ComponentException
122
     * @link http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
123
     */
124 9
    protected function parseRangeUsingCidr($input, &$range)
125
    {
126 9
        $input = explode('/', $input);
127 9
        $this->fillAddress($input[0], '0');
128
129 9
        $range['min'] = $input[0];
130 9
        $isAddressMask = strpos($input[1], '.') !== false;
131
132 9
        if ($isAddressMask && $this->verifyAddress($input[1])) {
133 2
            $range['mask'] = sprintf('%032b', ip2long($input[1]));
134 2
            return;
135
        }
136
137 7
        if ($isAddressMask || $input[1] < 8 || $input[1] > 30) {
138 3
            throw new ComponentException('Invalid network mask');
139
        }
140
141 4
        $range['mask'] = sprintf('%032b', ip2long(long2ip(~(pow(2, (32 - $input[1])) - 1))));
142 4
    }
143
144
    /**
145
     * Check input data
146
     *
147
     * @param  string $input
148
     *
149
     * @return bool
150
     */
151 33
    public function validate($input): bool
152
    {
153 33
        return $this->verifyAddress($input) && $this->verifyNetwork($input);
154
    }
155
156
    /**
157
     * Verify IP address
158
     *
159
     * @param  string $address
160
     *
161
     * @return bool
162
     */
163 38
    protected function verifyAddress($address)
164
    {
165 38
        return (boolean)filter_var(
166 38
            $address,
167 38
            FILTER_VALIDATE_IP,
168
            [
169 38
                'flags' => $this->options
170
            ]
171
        );
172
    }
173
174
    /**
175
     * Verify Network by mask
176
     *
177
     * @param  string $input
178
     *
179
     * @return bool
180
     */
181 27
    protected function verifyNetwork($input)
182
    {
183 27
        if ($this->networkRange === null) {
184 4
            return true;
185
        }
186
187 23
        if (isset($this->networkRange['mask'])) {
188 6
            return $this->belongsToSubnet($input);
189
        }
190
191 17
        $input = sprintf('%u', ip2long($input));
192
193 17
        $min = sprintf('%u', ip2long($this->networkRange['min']));
194 17
        $max = sprintf('%u', ip2long($this->networkRange['max']));
195
196 17
        return ($input >= $min) && ($input <= $max);
197
    }
198
199
    /**
200
     * Check subnet
201
     *
202
     * @param  string $input
203
     *
204
     * @return bool
205
     */
206 6
    protected function belongsToSubnet($input)
207
    {
208 6
        $range = $this->networkRange;
209 6
        $min = sprintf('%032b', ip2long($range['min']));
210 6
        $input = sprintf('%032b', ip2long($input));
211
212 6
        return ($input & $range['mask']) === ($min & $range['mask']);
213
    }
214
215
    /**
216
     * Get error template
217
     *
218
     * @return string
219
     */
220 33
    public function getDescription() : string
221
    {
222 33
        if (!empty($this->networkRange)) {
223 23
            $message = $this->networkRange['min'];
224 23
            if (isset($this->networkRange['max'])) {
225 17
                $message .= '-' . $this->networkRange['max'];
226
            } else {
227 6
                $message .= '/' . long2ip((string)bindec($this->networkRange['mask']));
228
            }
229 23
            return __('must be an IP address in the "%s" range', $message);
230
        }
231 10
        return __('must be an IP address');
232
    }
233
}
234