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
![]() |
|||
113 | } |
||
114 | } |
||
115 |