import { isIP } from "node:net";
//#region src/media-shared/http/private-network.ts
/**
* SSRF / private-network protection for provider HTTP calls.
*
* Provider URLs are mostly user-controllable (cfg.providers.<id>.baseUrl,
* imageBaseUrl, etc.). Without a guard a malicious config could make xopc
* fetch internal services (169.254.x.x metadata, 127.0.0.1, RFC1918, …).
*
* Policy:
*   - default: allow only http(s) + non-private hosts
*   - dev:     allow loopback when XOPC_PROVIDER_HTTP_ALLOW_LOOPBACK=1
*   - test:    same as dev; vitest sets NODE_ENV=test
*/
var BlockedPrivateNetworkError = class extends Error {
	url;
	host;
	constructor(url, host, reason) {
		super(`Provider HTTP call blocked: ${reason} (host=${host}, url=${url})`);
		this.name = "BlockedPrivateNetworkError";
		this.url = url;
		this.host = host;
	}
};
/** Map URL-layer private-network policy into SSRF guard options for `fetchWithTimeoutGuarded`. */
function privateNetworkPolicyToSsrfGuardOptions(policy) {
	const p = policy === void 0 ? defaultPolicy() : policy;
	return {
		allowPrivateNetwork: Boolean(p.allowPrivate || p.allowLoopback),
		hostnameAllowlist: p.allowHosts
	};
}
function defaultPolicy() {
	const env = process.env;
	return {
		allowLoopback: env.XOPC_PROVIDER_HTTP_ALLOW_LOOPBACK === "1" || env.XOPC_PROVIDER_HTTP_ALLOW_LOOPBACK === "true" || env.NODE_ENV === "test",
		allowPrivate: env.XOPC_PROVIDER_HTTP_ALLOW_PRIVATE === "1" || env.XOPC_PROVIDER_HTTP_ALLOW_PRIVATE === "true"
	};
}
/**
* Throws {@link BlockedPrivateNetworkError} when the URL targets a private host
* not permitted by the policy. Returns silently otherwise.
*
* Note: This is a best-effort guard at the URL layer. A truly hostile host
* could still resolve a public name to a private IP via DNS rebinding; for
* production deployments behind untrusted user input, also enforce egress
* firewall rules.
*/
function assertNotPrivateNetwork(rawUrl, policy = defaultPolicy()) {
	let url;
	try {
		url = new URL(rawUrl);
	} catch {
		throw new BlockedPrivateNetworkError(rawUrl, "", "invalid URL");
	}
	if (url.protocol !== "http:" && url.protocol !== "https:") throw new BlockedPrivateNetworkError(rawUrl, url.hostname, `unsupported protocol "${url.protocol}"`);
	const host = url.hostname.toLowerCase();
	if (policy.allowHosts?.includes(host)) return;
	switch (classifyHost(host)) {
		case "public": return;
		case "loopback":
			if (policy.allowLoopback) return;
			throw new BlockedPrivateNetworkError(rawUrl, host, "loopback address");
		case "private":
			if (policy.allowPrivate) return;
			throw new BlockedPrivateNetworkError(rawUrl, host, "private address");
		case "link-local":
			if (policy.allowPrivate) return;
			throw new BlockedPrivateNetworkError(rawUrl, host, "link-local address");
		case "invalid": throw new BlockedPrivateNetworkError(rawUrl, host, "invalid host");
	}
}
/** Classify a hostname/IP literal. Hostnames are conservatively treated as `public`. */
function classifyHost(host) {
	if (!host) return "invalid";
	const lower = host.toLowerCase();
	if (lower === "localhost" || lower.endsWith(".localhost")) return "loopback";
	const ipKind = isIP(lower);
	if (ipKind === 0) return "public";
	if (ipKind === 4) return classifyIPv4(lower);
	return classifyIPv6(lower);
}
function classifyIPv4(ip) {
	const parts = ip.split(".").map((s) => Number.parseInt(s, 10));
	if (parts.length !== 4 || parts.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return "invalid";
	const [a, b] = parts;
	if (a === 127) return "loopback";
	if (a === 10) return "private";
	if (a === 172 && b >= 16 && b <= 31) return "private";
	if (a === 192 && b === 168) return "private";
	if (a === 169 && b === 254) return "link-local";
	if (a === 0) return "invalid";
	if (a === 100 && b >= 64 && b <= 127) return "private";
	return "public";
}
function classifyIPv6(ip) {
	const lower = ip.toLowerCase();
	if (lower === "::1") return "loopback";
	if (lower === "::") return "invalid";
	if (lower.startsWith("fe80:")) return "link-local";
	if (lower.startsWith("fc") || lower.startsWith("fd")) return "private";
	if (lower.startsWith("::ffff:")) {
		const v4 = lower.slice(7);
		if (isIP(v4) === 4) return classifyIPv4(v4);
	}
	return "public";
}
//#endregion
export { BlockedPrivateNetworkError, assertNotPrivateNetwork, classifyHost, defaultPolicy, privateNetworkPolicyToSsrfGuardOptions };

//# sourceMappingURL=private-network.js.map