65535) { return null; } } $normalized = 'https://'.$host; if (isset($parts['port'])) { $normalized .= ':'.(string) (int) $parts['port']; } return $normalized; } private static function ipIsPrivateOrReserved(string $ip): bool { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { $v = ip2long($ip); if ($v === false) { return true; } // PHP 在 macOS 上 ip2long 可能为有符号,强转为 int64 统一处理 $v = (int) $v; return self::ipInRangesV4((int) $v, [ // 0.0.0.0/8 ['base' => ip2long('0.0.0.0'), 'mask' => 0xFF000000], // 10.0.0.0/8 ['base' => ip2long('10.0.0.0'), 'mask' => 0xFF000000], // 127.0.0.0/8 ['base' => ip2long('127.0.0.0'), 'mask' => 0xFF000000], // 169.254.0.0/16 ['base' => ip2long('169.254.0.0'), 'mask' => 0xFFFF0000], // 172.16.0.0/12 ['base' => ip2long('172.16.0.0'), 'mask' => 0xFFF00000], // 192.168.0.0/16 ['base' => ip2long('192.168.0.0'), 'mask' => 0xFFFF0000], // 100.64.0.0/10 (CGNAT) ['base' => ip2long('100.64.0.0'), 'mask' => 0xFFC00000], // 192.0.0.0/24 (IETF Protocol Assignments) ['base' => ip2long('192.0.0.0'), 'mask' => 0xFFFFFF00], // 198.18.0.0/15 (benchmarking) ['base' => ip2long('198.18.0.0'), 'mask' => 0xFFFE0000], // 224.0.0.0/4 (multicast) ['base' => ip2long('224.0.0.0'), 'mask' => 0xF0000000], // 240.0.0.0/4 (reserved for future use) ['base' => ip2long('240.0.0.0'), 'mask' => 0xF0000000], ]); } // IPv6:仅做关键保守段拦截(避免复杂数值比较引入 bug) if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $bin = inet_pton($ip); if ($bin === false) { return true; } // IPv6 ::1 (loopback) if (substr($bin, 0, 15) === str_repeat("\0", 15) && $bin[15] === "\1") { return true; } // IPv6 unspecified :: if ($bin === str_repeat("\0", 16)) { return true; } $b0 = ord($bin[0]); $b1 = ord($bin[1]); // ff00::/8 multicast if ($b0 === 0xFF) { return true; } // fc00::/7 unique local => fc or fd if ($b0 === 0xFC || $b0 === 0xFD) { return true; } // fe80::/10 link-local => fe + (second byte & 0xC0) == 0x80 if ($b0 === 0xFE && (($b1 & 0xC0) === 0x80)) { return true; } // IPv4-mapped ::ffff:0:0/96 => 检查最后 4 字节映射的 IPv4 是否为私网 if (substr($bin, 0, 10) === str_repeat("\0", 10) && substr($bin, 10, 2) === "\xFF\xFF") { $v4bin = substr($bin, 12, 4); $v4 = inet_ntop($v4bin); // inet_ntop 对 v4bin 有时返回 false,这里保守返回 true if ($v4 === false) { return true; } return self::ipIsPrivateOrReserved($v4); } } // 非法 IP:保守拒绝 return true; } private static function ipInRangesV4(int $v, array $ranges): bool { foreach ($ranges as $r) { $base = (int) $r['base']; $mask = (int) $r['mask']; if (($v & $mask) === ($base & $mask)) { return true; } } return false; } }