export class RssUrlUnsafeError extends Error { constructor(message: string) { super(message); this.name = "RssUrlUnsafeError"; } } const PRIVATE_IPV4 = /^(127\.|10\.|172\.(1[6-9]|2\d|3[0-1])\.|192\.168\.|0\.|169\.254\.)/; /** 仅允许 http(s),主机名为非内网字面量(粗略)。完整 DNS 解析防 SSRF 可在 fetch 前再加强。 */ export function assertSafeRssUrl(raw: string) { let url: URL; try { url = new URL(raw); } catch { throw new RssUrlUnsafeError("无效的 URL"); } if (url.protocol !== "http:" && url.protocol !== "https:") { throw new RssUrlUnsafeError("仅支持 http/https"); } if (!url.hostname) { throw new RssUrlUnsafeError("缺少主机名"); } const host = url.hostname.toLowerCase(); if (host === "localhost" || host.endsWith(".localhost")) { throw new RssUrlUnsafeError("禁止 localhost"); } if (PRIVATE_IPV4.test(host)) { throw new RssUrlUnsafeError("禁止内网地址"); } return url.toString(); }