Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Completed
Push — master ( b7043b...985b6f )
by Henrique
03:04
created

Ip::parseRange()   B

Complexity

Conditions 9
Paths 7

Size

Total Lines 33
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 9.4453

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 33
ccs 14
cts 17
cp 0.8235
rs 8.0555
c 0
b 0
f 0
cc 9
nc 7
nop 1
crap 9.4453
1
<?php
2
3
/*
4
 * This file is part of Respect/Validation.
5
 *
6
 * (c) Alexandre Gomes Gaigalas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the "LICENSE.md"
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Respect\Validation\Rules;
15
16
use Respect\Validation\Exceptions\ComponentException;
17
use function bccomp;
18
use function explode;
19
use function filter_var;
20
use function ip2long;
21
use function is_string;
22
use function long2ip;
23
use function mb_strpos;
24
use function mb_substr_count;
25
use function sprintf;
26
use function str_repeat;
27
use function str_replace;
28
use function strtr;
29
use function var_dump;
30
31
/**
32
 * Validates whether the input is a valid IP address.
33
 *
34
 * This validator uses the native filter_var() PHP function.
35
 *
36
 * @author Alexandre Gomes Gaigalas <[email protected]>
37
 * @author Danilo Benevides <[email protected]>
38
 * @author Henrique Moody <[email protected]>
39
 * @author Luís Otávio Cobucci Oblonczyk <[email protected]>
40
 */
41
final class Ip extends AbstractRule
42
{
43
    /**
44
     * @var string|null
45
     */
46
    private $range;
47
48
    /**
49
     * @var int|null
50
     */
51
    private $options;
52
53
    /**
54
     * @var string|null
55
     */
56
    private $startAddress;
57
58
    /**
59
     * @var string|null
60
     */
61
    private $endAddress;
62
63
    /**
64
     * @var string|null
65
     */
66
    private $mask;
67
68
    /**
69
     * Initializes the rule defining the range and some options for filter_var().
70
     *
71
     * @param string $range
72
     * @param int|null $options
73
     *
74
     * @throws ComponentException In case the range is invalid
75
     */
76 8
    public function __construct(string $range = '*', int $options = null)
77
    {
78 8
        $this->parseRange($range);
79 1
        $this->range = $this->createRange();
80 1
        $this->options = $options;
81 1
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86 35
    public function validate($input): bool
87
    {
88 35
        if (!is_string($input)) {
89 1
            return false;
90
        }
91
92 34
        if (!$this->verifyAddress($input)) {
93 6
            return false;
94
        }
95
96 29
        if ($this->mask) {
97 6
            return $this->belongsToSubnet($input);
98
        }
99
100 23
        if ($this->startAddress && $this->endAddress) {
101 19
            return $this->verifyNetwork($input);
102
        }
103
104 5
        return true;
105
    }
106
107 1
    private function createRange(): ?string
108
    {
109 1
        if ($this->endAddress && $this->endAddress) {
110 1
            return $this->startAddress.'-'.$this->endAddress;
111
        }
112
113 1
        if ($this->startAddress && $this->mask) {
114
            return $this->startAddress.'/'.long2ip((int) $this->mask);
115
        }
116
117 1
        return null;
118
    }
119
120 8
    private function parseRange(string $input): void
121
    {
122 8
        if ('*' == $input || '*.*.*.*' == $input || '0.0.0.0-255.255.255.255' == $input) {
123 1
            return;
124
        }
125
126 8
        if (false !== mb_strpos($input, '-')) {
127 2
            list($this->startAddress, $this->endAddress) = explode('-', $input);
128
129 2
            if (!$this->verifyAddress($this->startAddress)) {
130
                throw new ComponentException('Invalid network range');
131
            }
132
133 2
            if (!$this->verifyAddress($this->endAddress)) {
134 2
                throw new ComponentException('Invalid network range');
135
            }
136
137
            return;
138
        }
139
140 6
        if (false !== mb_strpos($input, '*')) {
141 1
            $this->parseRangeUsingWildcards($input);
142
143 1
            return;
144
        }
145
146 5
        if (false !== mb_strpos($input, '/')) {
147 3
            $this->parseRangeUsingCidr($input);
148
149
            return;
150
        }
151
152 2
        throw new ComponentException('Invalid network range');
153
    }
154
155 4
    private function fillAddress(string $address, string $fill = '*'): string
156
    {
157 4
        return $address.str_repeat('.'.$fill, 3 - mb_substr_count($address, '.'));
158
    }
159
160 1
    private function parseRangeUsingWildcards(string $input): void
161
    {
162 1
        $address = $this->fillAddress($input);
163
164 1
        $this->startAddress = strtr($address, '*', '0');
165 1
        $this->endAddress = str_replace('*', '255', $address);
166 1
    }
167
168 3
    private function parseRangeUsingCidr(string $input): void
169
    {
170 3
        $parts = explode('/', $input);
171
172 3
        $this->startAddress = $this->fillAddress($parts[0], '0');
173 3
        $isAddressMask = false !== mb_strpos($parts[1], '.');
174
175 3
        if ($isAddressMask && $this->verifyAddress($parts[1])) {
176
            $this->mask = sprintf('%032b', ip2long($parts[1]));
177
178
            return;
179
        }
180
181 3
        if ($isAddressMask || $parts[1] < 8 || $parts[1] > 30) {
182 3
            throw new ComponentException('Invalid network mask');
183
        }
184
185
        $this->mask = sprintf('%032b', ip2long(long2ip(~(2 ** (32 - (int) $parts[1]) - 1))));
186
    }
187
188 38
    private function verifyAddress(string $address): bool
189
    {
190 38
        return false !== filter_var($address, FILTER_VALIDATE_IP, ['flags' => $this->options]);
191
    }
192
193 19
    private function verifyNetwork(string $input): bool
194
    {
195 19
        $input = sprintf('%u', ip2long($input));
196
197 19
        return bccomp($input, sprintf('%u', ip2long($this->startAddress))) >= 0
198 19
               && bccomp($input, sprintf('%u', ip2long($this->endAddress))) <= 0;
199
    }
200
201 6
    private function belongsToSubnet(string $input): bool
202
    {
203 6
        if (null === $this->mask) {
204
            return false;
205
        }
206
207 6
        $min = sprintf('%032b', ip2long($this->startAddress));
208 6
        $input = sprintf('%032b', ip2long($input));
209
210 6
        return ($input & $this->mask) === ($min & $this->mask);
211
    }
212
}
213