summaryrefslogtreecommitdiffstats
path: root/inc/iputil.inc.php
diff options
context:
space:
mode:
Diffstat (limited to 'inc/iputil.inc.php')
-rw-r--r--inc/iputil.inc.php91
1 files changed, 91 insertions, 0 deletions
diff --git a/inc/iputil.inc.php b/inc/iputil.inc.php
index 9ec78f50..a9b79573 100644
--- a/inc/iputil.inc.php
+++ b/inc/iputil.inc.php
@@ -75,4 +75,95 @@ class IpUtil
return ['start' => $ip & ~$bits, 'end' => $ip | $bits];
}
+ /**
+ * Normalizes an IP address to the canonical form.
+ * This supports all numeric IPv4, IPv6 and IPv4-mapped IPv6 addresses.
+ * As well as odd formats like IPv4 with multiple octets collapsed into a single one.
+ * Returns null if the address is invalid.
+ */
+ public static function normalizeIp(string $ip): ?string
+ {
+ $ip = trim($ip);
+ if ($ip === '')
+ return null;
+
+ // Check simple case first
+ $addr = @inet_pton($ip);
+ if ($addr !== false) {
+ $ret = inet_ntop($addr);
+ if (substr($ret, 0, 7) === '::ffff:')
+ return substr($ret, 7);
+ return $ret;
+ }
+ if (strpos($ip, ':') !== false)
+ return null;
+
+ // IPv4 handling
+ // Support octal, hex and shorthand notations
+ $parts = explode('.', $ip);
+ $numParts = count($parts);
+ if ($numParts > 4)
+ return null;
+
+ $values = [];
+ foreach ($parts as $part) {
+ $part = trim($part);
+ if ($part === '')
+ return null;
+ // Check for hex (0x...)
+ if (strncasecmp($part, '0x', 2) === 0) {
+ if (!ctype_xdigit(substr($part, 2)))
+ return null;
+ $values[] = hexdec($part);
+ } elseif ($part[0] === '0' && strlen($part) > 1) {
+ // Octal (starts with 0, but not just 0)
+ if (!preg_match('/^[0-7]+$/', $part))
+ return null;
+ $values[] = octdec($part);
+ } else {
+ // Decimal
+ if (!preg_match('/^[0-9]+$/', $part))
+ return null;
+ $values[] = (int)$part;
+ }
+ }
+
+ if (empty($values))
+ return null;
+
+ // Validate values and handle shorthands
+ // Rules:
+ // 1 part: a (32 bits)
+ // 2 parts: a.b (a: 8 bits, b: 24 bits)
+ // 3 parts: a.b.c (a: 8 bits, b: 8 bits, c: 16 bits)
+ // 4 parts: a.b.c.d (a: 8 bits, b: 8 bits, c: 8 bits, d: 8 bits)
+ $ipv4int = 0;
+ for ($i = 0; $i < $numParts; $i++) {
+ $val = $values[$i];
+ if ($i === $numParts - 1) {
+ // Last part
+ $max = (2 ** (8 * (4 - $i))) - 1;
+ if ($val < 0 || $val > $max)
+ return null;
+ $ipv4int = ($ipv4int << (8 * (4 - $i))) | (int)$val;
+ } else {
+ if ($val < 0 || $val > 255)
+ return null;
+ $ipv4int = ($ipv4int << 8) | (int)$val;
+ }
+ }
+
+ return long2ip($ipv4int);
+ }
+
+ /**
+ * Check if given string is a valid IP address.
+ * Supports all kinds of IP address formats that PHP supports,
+ * including IPv6, IPv4-mapped IPv6, IPv4-in-IPv6, etc.
+ */
+ public static function isValidIp(string $ip): bool
+ {
+ return self::normalizeIp($ip) !== null;
+ }
+
}