IPRange   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 106
Duplicated Lines 0 %

Test Coverage

Coverage 87.72%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 22
eloc 52
c 1
b 0
f 0
dl 0
loc 106
ccs 50
cts 57
cp 0.8772
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A matches() 0 9 3
B matchesIPv6() 0 50 11
A matchesOne() 0 7 2
A matchesIPv4() 0 26 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Validator;
6
7
abstract class IPRange
8
{
9
    /**
10
     * Whether the given IP address matches at least one of the given CIDR.
11
     *
12
     * @param string[] $cidrs
13
     */
14 27
    final public static function matches(string $ip, array $cidrs): bool
15
    {
16 27
        foreach ($cidrs as $cidr) {
17 27
            if (self::matchesOne($ip, $cidr)) {
18 12
                return true;
19
            }
20
        }
21
22 16
        return false;
23
    }
24
25 27
    private static function matchesOne(string $ip, string $cidr): bool
26
    {
27 27
        if (str_contains($ip, ':')) {
28 9
            return self::matchesIPv6($ip, $cidr);
29
        }
30
31 18
        return self::matchesIPv4($ip, $cidr);
32
    }
33
34 18
    private static function matchesIPv4(string $ip, string $cidr): bool
35
    {
36 18
        $mask = 32;
37 18
        $subnet = $cidr;
38
39 18
        if (str_contains($cidr, '/')) {
40 11
            [$subnet, $mask] = explode('/', $cidr, 2);
41 11
            $mask = (int) $mask;
42
        }
43
44 18
        if ($mask < 0 || $mask > 32) {
45 5
            return false;
46
        }
47
48 17
        $ip = ip2long($ip);
49 17
        $subnet = ip2long($subnet);
50 17
        if (false === $ip || false === $subnet) {
51
            // Invalid data
52 5
            return false;
53
        }
54
55 12
        return 0 === substr_compare(
56 12
            sprintf('%032b', $ip),
57 12
            sprintf('%032b', $subnet),
58 12
            0,
59 12
            $mask
60 12
        );
61
    }
62
63 9
    private static function matchesIPv6(string $ip, string $cidr): bool
64
    {
65 9
        $mask = 128;
66 9
        $subnet = $cidr;
67
68 9
        if (str_contains($cidr, '/')) {
69 4
            [$subnet, $mask] = explode('/', $cidr, 2);
70 4
            $mask = (int) $mask;
71
        }
72
73 9
        if ($mask < 0 || $mask > 128) {
74
            return false;
75
        }
76
77 9
        $ip = inet_pton($ip);
78 9
        $subnet = inet_pton($subnet);
79
80 9
        if (false === $ip || false === $subnet) {
81
            // Invalid data
82 2
            return false;
83
        }
84
85
        // mask 0: if it's a valid IP, it's valid
86 7
        if ($mask === 0) {
87 2
            return (bool) unpack('n*', $ip);
88
        }
89
90
        /** @see http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet, MW answer */
91 5
        $binMask = str_repeat('f', (int) ($mask / 4));
92 5
        switch ($mask % 4) {
93 5
            case 0:
94 3
                break;
95 2
            case 1:
96 2
                $binMask .= '8';
97
98 2
                break;
99
            case 2:
100
                $binMask .= 'c';
101
102
                break;
103
            case 3:
104
                $binMask .= 'e';
105
106
                break;
107
        }
108
109 5
        $binMask = str_pad($binMask, 32, '0');
110 5
        $binMask = pack('H*', $binMask);
111
112 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...
113
    }
114
}
115