Passed
Push — master ( c312a7...6efd67 )
by Arnold
01:53
created

ip_in_cidr()   B

Complexity

Conditions 9
Paths 21

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 14
nc 21
nop 2
dl 0
loc 23
ccs 13
cts 13
cp 1
crap 9
rs 8.0555
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Jasny;
6
7
/**
8
 * Convert an IPv4 address or CIDR into an IP6 address or CIDR.
9
 *
10
 * @param string $ip
11
 * @return string
12
 * @throws \InvalidArgumentException if ip isn't valid
13
 */
14
function ipv4_to_ipv6(string $ip): string
15
{
16 6
    if ($ip === '0.0.0.0/0') {
17 1
        return '::';
18
    }
19
20 5
    list($address, $mask) = explode('/', $ip, 2) + [null, null];
21
22 5
    if (!(bool)filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) || (isset($mask) && !ctype_digit($mask))) {
23 2
        throw new \InvalidArgumentException("'$ip' is not a valid IPv4 address or cidr");
24
    }
25
    
26 3
    $bytes = array_map('dechex', explode('.', $address));
27
    
28 3
    return vsprintf('0:0:0:0:0:ffff:%02s%02s:%02s%02s', $bytes) . (isset($mask) ? '/' . ($mask + 96) : '');
29
}
30
31
/**
32
 * Check if IP address is in CIDR block
33
 *
34
 * @param string $ip     An IPv4 or IPv6
35
 * @param string $cidr   An IPv4 CIDR block or IPv6 CIDR block
36
 * @return bool
37
 */
38
function ip_in_cidr(string $ip, string $cidr): bool
39
{
40 37
    if ($cidr === '0.0.0.0/0' || $cidr === '::/0' || $cidr === '::') {
41 4
        return true;
42
    }
43
    
44 33
    $ipv = strpos($ip, ':') === false ? 4 : 6;
45 33
    $cidrv = strpos($cidr, ':') === false ? 4 : 6;
46
    
47
    try {
48 33
        if ($ipv < $cidrv) {
49 5
            $ip = ipv4_to_ipv6($ip);
50 4
            $ipv = 6;
51 28
        } elseif ($ipv > $cidrv) {
52 32
            $cidr = ipv4_to_ipv6($cidr);
53
        }
54 2
    } catch (\InvalidArgumentException $e) {
55 2
        return false;
56
    }
57
    
58 31
    $fn = __NAMESPACE__ . "\\ipv{$ipv}_in_cidr";
59
    
60 31
    return $fn($ip, $cidr);
61
}
62
63
/**
64
 * Check if IPv4 address is in CIDR block
65
 *
66
 * @param string $ip
67
 * @param string $cidr
68
 * @return bool
69
 */
70
function ipv4_in_cidr(string $ip, string $cidr): bool
71
{
72 13
    list($subnet, $mask) = explode('/', $cidr, 2) + [null, '32'];
73
    
74 13
    if (!(bool)filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ||
75 13
        !(bool)filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)
76
    ) {
77 3
        return false;
78
    }
79
    
80 10
    $ipLong = ip2long($ip);
81 10
    $subnetLong = ip2long($subnet);
82
    
83 10
    $ipMasked = $ipLong & ~((1 << (32 - $mask)) - 1);
84 10
    $subnetMasked = $subnetLong & ~((1 << (32 - $mask)) - 1);
85
    
86 10
    return $ipMasked == $subnetMasked;
87
}
88
89
/**
90
 * Check if IPv6 address is in CIDR block
91
 *
92
 * @param string $ip
93
 * @param string $cidr
94
 * @return bool
95
 */
96
function ipv6_in_cidr(string $ip, string $cidr): bool
97
{
98 14
    if (!(bool)filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
99 2
        return false;
100
    }
101
    
102 12
    $inetIp = inet_pton($ip);
103 12
    $binaryIp = inet_to_bits($inetIp);
104
105 12
    if (strpos($cidr, '/') === false) {
106 3
        $net = $cidr;
107 3
        $mask = $cidr === '::' ? 0 : substr_count(rtrim($cidr, ':'), ':') * 16;
108
    } else {
109 9
        list($net, $mask) = explode('/', $cidr, 2);
110 9
        $mask = (int)$mask;
111
    }
112
113 12
    if (!(bool)filter_var($net, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
114 1
        return false;
115
    }
116
        
117 11
    $inetNet = inet_pton($net);
118 11
    $binaryNet = inet_to_bits($inetNet);
119
120 11
    $ipNetBits = substr($binaryIp, 0, $mask);
121 11
    $netBits = substr($binaryNet, 0, $mask);
122
123 11
    return $ipNetBits === $netBits;
124
}
125
126
/**
127
 * Converts inet_pton output to string with bits.
128
 *
129
 * @param string $inet
130
 * @return string
131
 */
132
function inet_to_bits(string $inet): string
133
{
134 12
    $unpackedArr = unpack('A16', $inet);
135 12
    $unpacked = str_split($unpackedArr[1]);
136
    
137 12
    $binaryip = '';
138
139 12
    foreach ($unpacked as $char) {
140 12
        $binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
141
    }
142
143 12
    return str_pad($binaryip, 128, '0', STR_PAD_RIGHT);
144
}
145