misuzu/src/Net/IPAddress.php

113 lines
3.2 KiB
PHP

<?php
namespace Misuzu\Net;
use Exception;
use InvalidArgumentException;
use GeoIp2\Exception\AddressNotFoundException;
final class IPAddress {
public const VERSION_UNKNOWN = 0;
public const VERSION_4 = 4;
public const VERSION_6 = 6;
private const SIZES = [
self::VERSION_4 => 4,
self::VERSION_6 => 16,
];
public const DEFAULT_V4 = '127.1';
public const DEFAULT_V6 = '::1';
public static function remote(string $fallback = self::DEFAULT_V6): string {
return $_SERVER['REMOTE_ADDR'] ?? $fallback;
}
public static function country(string $address, string $fallback = 'XX'): string {
if(!GeoIP::isAvailable())
return $fallback;
try {
return GeoIP::resolveCountry($address)->country->isoCode ?? $fallback;
} catch(AddressNotFoundException $e) {
return $fallback;
}
}
public static function rawWidth(int $version): int {
return isset(self::SIZES[$version]) ? self::SIZES[$version] : 0;
}
public static function detectStringVersion(string $address): int {
if(filter_var($address, FILTER_VALIDATE_IP) !== false) {
if(filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false)
return self::VERSION_6;
if(filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false)
return self::VERSION_4;
}
return self::VERSION_UNKNOWN;
}
public static function detectRawVersion(string $address): int {
$addressLength = strlen($address);
foreach(self::SIZES as $version => $length) {
if($length === $addressLength)
return $version;
}
return self::VERSION_UNKNOWN;
}
public static function cidrToRaw(string $cidr): ?array {
if(strpos($cidr, '/') !== false) {
[$subnet, $mask] = explode('/', $cidr, 2);
} else {
$subnet = $cidr;
}
try {
$subnet = inet_pton($subnet);
} catch(Exception $ex) {
return null;
}
$mask = empty($mask) ? null : (int)$mask;
return compact('subnet', 'mask');
}
public static function matchCidr(string $address, string $cidr): bool {
$address = inet_pton($address);
$cidr = self::cidrToRaw($cidr);
return self::matchCidrRaw($address, $cidr['subnet'], $cidr['mask']);
}
public static function matchCidrRaw(string $address, string $subnet, ?int $mask = null): bool {
$version = self::detectRawVersion($subnet);
if($version === self::VERSION_UNKNOWN)
return false;
$bits = self::SIZES[$version] * 8;
if(empty($mask))
$mask = $bits;
if($mask < 1 || $mask > $bits || $version !== self::detectRawVersion($subnet))
return false;
for($i = 0; $i < ceil($mask / 8); $i++) {
$byteMask = (0xFF00 >> max(0, min(8, $mask - ($i * 8)))) & 0xFF;
$addressByte = ord($address[$i]) & $byteMask;
$subnetByte = ord($subnet[$i]) & $byteMask;
if($addressByte !== $subnetByte)
return false;
}
return true;
}
}