// 公开检测入口对接口地址多挡一层:只许公网主机。 // // Workers 本身连不到内网,这里挡的是「拿我们的 Worker 去打 localhost / 内网名 / 保留地址」这类请求,回一句清楚的错, // 不让它们跑满 17 个请求再一个个失败。只看地址里写的主机名 / IP 字面量,不做 DNS 解析(Workers 里做不了,也拦不住 // 解析到内网的公网名 —— 那种请求 Workers 自己会连不上)。 const BLOCKED_SUFFIXES = [".localhost", ".local", ".internal", ".lan", ".home.arpa", ".intranet", ".corp"]; function ipv4(host: string): number[] | null { const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host); if (!m) return null; const parts = m.slice(1).map(Number); return parts.every((n) => n >= 0 && n <= 255) ? parts : null; } function privateV4([a, b]: number[]): boolean { if (a === undefined || b === undefined) return true; return ( a === 0 || // 0.0.0.0/8 a === 10 || a === 127 || (a === 100 && b >= 64 && b <= 127) || // 100.64.0.0/10 运营商级 NAT (a === 169 && b === 254) || // 链路本地 (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168) || (a === 192 && b === 0) || // 192.0.0.0/24、192.0.2.0/24 (a === 198 && (b === 18 || b === 19)) || // 基准测试网段 a >= 224 // 组播与保留 ); } function privateV6(host: string): boolean { const h = host.replace(/^\[|\]$/g, "").toLowerCase(); if (h === "::" || h === "::1") return true; if (/^f[cd][0-9a-f]{2}:/.test(h)) return true; // fc00::/7 唯一本地 if (/^fe[89ab][0-9a-f]:/.test(h)) return true; // fe80::/10 链路本地 const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(h); if (mapped) { const v4 = ipv4(mapped[1]!); return v4 === null || privateV4(v4); } // URL 解析器把 ::ffff:10.0.0.1 规范成十六进制的 ::ffff:a00:1 const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(h); if (hex) { const hi = parseInt(hex[1]!, 16); const lo = parseInt(hex[2]!, 16); return privateV4([hi >> 8, hi & 255, lo >> 8, lo & 255]); } return false; } /** 接口地址的主机能不能拿来公开检测;不能回 false。 */ export function isPublicHost(url: URL): boolean { const host = url.hostname.toLowerCase().replace(/\.$/, ""); if (!host || host === "localhost" || !host.includes(".") && !host.startsWith("[")) return false; if (BLOCKED_SUFFIXES.some((s) => host.endsWith(s))) return false; if (host.startsWith("[")) return !privateV6(host); const v4 = ipv4(host); if (v4) return !privateV4(v4); // 十进制 / 十六进制写法的 IP(http://2130706433/):URL 解析器会把它们规范成点分十进制,走到上面;剩下的纯数字一律不收。 if (/^[0-9.]+$/.test(host)) return false; return true; }