255 || (strlen($part) > 1 && $part[0] === '0')) { return false; } } return true; } public static function isIpv6(string $text): bool { $text = trim($text, '[]'); if (!str_contains($text, ':') || !preg_match('/^[0-9a-f:.]+$/i', $text) || substr_count($text, '::') > 1 || str_contains($text, ':::')) { return false; } $groups = explode(':', $text); $last = end($groups); $count = count($groups); if (str_contains($last, '.')) { // an IPv4 tail counts as two groups if (!self::isIpv4($last)) { return false; } array_pop($groups); $count++; } foreach ($groups as $i => $g) { if ($g === '' ? !str_contains($text, '::') : !preg_match('/^[0-9a-f]{1,4}$/i', $g)) { return false; } } return str_contains($text, '::') ? $count <= 9 : $count === 8; } /** * An address of a local / private network, which the JumPHP server's bridge refuses (only the JumPHP runtime on the user's computer * reaches it from a browser): RFC 1918 (10/8, 172.16/12, 192.168/16), loopback 127/8, link-local 169.254/16, CGNAT 100.64/10 (VPNs such * as Tailscale), 0/8; IPv6 ::1, ::, fc00::/7, fe80::/10 and IPv4-mapped ones; names: localhost, no dot at all, or ending in .local, * .lan, .home, .internal, .localdomain, .home.arpa. */ public static function isLocal(string $host): bool { $host = strtolower(rtrim(trim(trim($host), '[]'), '.')); if ($host === '') { return false; } if (self::isIpv4($host)) { [$a, $b] = array_map('intval', explode('.', $host)); return $a === 10 || $a === 127 || $a === 0 || ($a === 172 && $b >= 16 && $b <= 31) || ($a === 192 && $b === 168) || ($a === 169 && $b === 254) || ($a === 100 && $b >= 64 && $b <= 127); } if (self::isIpv6($host)) { if (preg_match('/^::ffff:(\d+\.\d+\.\d+\.\d+)$/', $host, $m)) { return self::isLocal($m[1]); } if (preg_match('/^[0:]+1?$/', $host)) { // ::1, ::, 0:0:...:1 return true; } $first = explode(':', $host)[0]; $word = $first === '' ? 0 : hexdec($first); return ($word & 0xfe00) === 0xfc00 || ($word & 0xffc0) === 0xfe80; } if ($host === 'localhost' || !str_contains($host, '.')) { return true; } return (bool)preg_match('/\.(local|lan|home|internal|localdomain|localhost|home\.arpa)$/', $host); } /** name@domain.tld, what an alert's mail needs (not every RFC 5322 corner). */ public static function isEmail(string $text): bool { return (bool)preg_match('/^[A-Za-z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$/', $text); } }