const ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
const PRIVATE_HOSTNAMES = /* @__PURE__ */ new Set([
  "localhost",
  "localhost.localdomain",
  // Common SSRF metadata hostnames; cheap to reject by name even though the
  // IPv4 guards below would catch the canonical addresses.
  "metadata.google.internal"
]);
function isLoopbackIpv4(host) {
  if (host === "0.0.0.0") return true;
  const m = host.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
  if (!m) return false;
  const o1 = Number(m[1]);
  const o2 = Number(m[2]);
  if (o1 === 127) return true;
  if (o1 === 0) return true;
  if (o1 === 10) return true;
  if (o1 === 192 && o2 === 168) return true;
  if (o1 === 172 && o2 >= 16 && o2 <= 31) return true;
  if (o1 === 169 && o2 === 254) return true;
  return false;
}
function isPrivateIpv6(host) {
  const h = host.replace(/^\[|\]$/g, "").toLowerCase();
  if (h === "::1") return true;
  if (h === "::") return true;
  if (h === "0:0:0:0:0:0:0:1") return true;
  if (h === "0:0:0:0:0:0:0:0") return true;
  if (h.startsWith("fe80:") || h.startsWith("fe8") || h.startsWith("fe9") || h.startsWith("fea") || h.startsWith("feb")) return true;
  if (h.startsWith("fc") || h.startsWith("fd")) return true;
  const v4mappedDotted = h.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
  if (v4mappedDotted && isLoopbackIpv4(v4mappedDotted[1])) return true;
  const v4mappedHex = h.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
  if (v4mappedHex) {
    const high = parseInt(v4mappedHex[1], 16);
    const low = parseInt(v4mappedHex[2], 16);
    if (!Number.isNaN(high) && !Number.isNaN(low)) {
      const dotted = `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
      if (isLoopbackIpv4(dotted)) return true;
    }
  }
  const v4compatDotted = h.match(/^::(\d+\.\d+\.\d+\.\d+)$/);
  if (v4compatDotted && isLoopbackIpv4(v4compatDotted[1])) return true;
  const v4compatHex = h.match(/^::([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
  if (v4compatHex) {
    const high = parseInt(v4compatHex[1], 16);
    const low = parseInt(v4compatHex[2], 16);
    if (!Number.isNaN(high) && !Number.isNaN(low)) {
      const dotted = `${high >> 8 & 255}.${high & 255}.${low >> 8 & 255}.${low & 255}`;
      if (isLoopbackIpv4(dotted)) return true;
    }
  }
  return false;
}
function guardUrl(raw, fieldLabel) {
  if (typeof raw !== "string" || raw.trim() === "") {
    return {
      ok: false,
      reason: `${fieldLabel} is required and must be a non-empty string`,
      hint: "Pass a fully qualified http(s) URL."
    };
  }
  let parsed;
  try {
    parsed = new URL(raw);
  } catch {
    return {
      ok: false,
      reason: `${fieldLabel} is not a valid URL`,
      hint: 'Pass a fully qualified http(s) URL (e.g. "https://example.com/path").'
    };
  }
  if (!ALLOWED_PROTOCOLS.has(parsed.protocol)) {
    return {
      ok: false,
      reason: `${fieldLabel} uses a forbidden protocol (${parsed.protocol})`,
      hint: "Only http: and https: are allowed."
    };
  }
  const host = parsed.hostname.toLowerCase();
  if (PRIVATE_HOSTNAMES.has(host)) {
    return {
      ok: false,
      reason: `${fieldLabel} hostname is a loopback/private alias (${host})`,
      hint: "Use a public hostname; localhost / metadata aliases are blocked."
    };
  }
  if (isLoopbackIpv4(host)) {
    return {
      ok: false,
      reason: `${fieldLabel} resolves to a loopback / private IPv4 (${host})`,
      hint: "Public addresses only \u2014 10/8, 127/8, 172.16/12, 192.168/16, 169.254/16, 0.0.0.0 are blocked."
    };
  }
  if (host.includes(":") || /^\[/.test(parsed.host)) {
    if (isPrivateIpv6(host)) {
      return {
        ok: false,
        reason: `${fieldLabel} resolves to a loopback / private IPv6 (${host})`,
        hint: "Public addresses only \u2014 ::1, fe80::/10, fc00::/7 are blocked."
      };
    }
  }
  return { ok: true, url: parsed };
}
export {
  guardUrl
};
//# sourceMappingURL=ssrf.js.map