Ecodev /
felix
| 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
Loading history...
|
|||
| 113 | } |
||
| 114 | } |
||
| 115 |