Failed Conditions
Push — master ( 7aeedc...f7e5fc )
by Adrien
05:38 queued 02:24
created

IPRange::matchesIPv4()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 26
ccs 17
cts 17
cp 1
rs 9.2222
cc 6
nc 6
nop 2
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Validator;
6
7
abstract class IPRange
8
{
9 22
    final public static function matches(string $ip, string $cidr): bool
10
    {
11 22
        if (str_contains($ip, ':')) {
12 9
            return self::matchesIPv6($ip, $cidr);
13
        }
14
15 13
        return self::matchesIPv4($ip, $cidr);
16
    }
17
18 13
    private static function matchesIPv4(string $ip, string $cidr): bool
19
    {
20 13
        $mask = 32;
21 13
        $subnet = $cidr;
22
23 13
        if (str_contains($cidr, '/')) {
24 7
            [$subnet, $mask] = explode('/', $cidr, 2);
25 7
            $mask = (int) $mask;
26
        }
27
28 13
        if ($mask < 0 || $mask > 32) {
29 1
            return false;
30
        }
31
32 12
        $ip = ip2long($ip);
33 12
        $subnet = ip2long($subnet);
34 12
        if (false === $ip || false === $subnet) {
35
            // Invalid data
36 5
            return false;
37
        }
38
39 7
        return 0 === substr_compare(
40 7
            sprintf('%032b', $ip),
41 7
            sprintf('%032b', $subnet),
42 7
            0,
43 7
            $mask
44 7
        );
45
    }
46
47 9
    private static function matchesIPv6(string $ip, string $cidr): bool
48
    {
49 9
        $mask = 128;
50 9
        $subnet = $cidr;
51
52 9
        if (str_contains($cidr, '/')) {
53 4
            [$subnet, $mask] = explode('/', $cidr, 2);
54 4
            $mask = (int) $mask;
55
        }
56
57 9
        if ($mask < 0 || $mask > 128) {
58
            return false;
59
        }
60
61 9
        $ip = inet_pton($ip);
62 9
        $subnet = inet_pton($subnet);
63
64 9
        if (false === $ip || false === $subnet) {
65
            // Invalid data
66 2
            return false;
67
        }
68
69
        // mask 0: if it's a valid IP, it's valid
70 7
        if ($mask === 0) {
71 2
            return (bool) unpack('n*', $ip);
72
        }
73
74
        /** @see http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet, MW answer */
75 5
        $binMask = str_repeat('f', (int) ($mask / 4));
76 5
        switch ($mask % 4) {
77 5
            case 0:
78 3
                break;
79 2
            case 1:
80 2
                $binMask .= '8';
81
82 2
                break;
83
            case 2:
84
                $binMask .= 'c';
85
86
                break;
87
            case 3:
88
                $binMask .= 'e';
89
90
                break;
91
        }
92
93 5
        $binMask = str_pad($binMask, 32, '0');
94 5
        $binMask = pack('H*', $binMask);
95
96 5
        return ($ip & $binMask) === $subnet;
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise & or did you mean &&?
Loading history...
97
    }
98
}
99