import { getLogger } from "../../../../logger.js";
import { BlockedDisallowedPortError, BlockedEmbeddedCredentialsError, BlockedUnsupportedSchemeError, BlockedUrlResolvingToPrivateIpError, FileSecurityError, InvalidExternalFileUrlError, NoIpResolvedError, UnableToResolveHostError } from "./file-security-errors.js";
import { ALLOWED_HTTP_PORTS } from "./file-security-constants.js";
import { lookup } from "node:dns/promises";
import { isIP } from "node:net";
import ipaddr from "ipaddr.js";

//#region src/domains/run/services/blob-storage/file-url-security.ts
const logger = getLogger("file-security");
function makeSanitizedSourceUrl(rawUrl) {
	const parsed = new URL(rawUrl);
	parsed.search = "";
	parsed.hash = "";
	return parsed.toString();
}
function validateExternalFileUrl(rawUrl) {
	let parsed;
	try {
		parsed = new URL(rawUrl);
	} catch {
		throw new InvalidExternalFileUrlError(rawUrl);
	}
	const protocol = parsed.protocol.toLowerCase();
	if (protocol !== "http:" && protocol !== "https:") throw new BlockedUnsupportedSchemeError(protocol);
	if (!ALLOWED_HTTP_PORTS.has(parsed.port)) throw new BlockedDisallowedPortError(parsed.port);
	if (parsed.username || parsed.password) throw new BlockedEmbeddedCredentialsError();
	return parsed;
}
async function validateUrlResolvesToPublicIp(url) {
	const hostname = url.hostname;
	let candidateIps;
	try {
		candidateIps = await resolveCandidateIps(hostname);
	} catch (error) {
		if (error instanceof FileSecurityError) throw error;
		throw new UnableToResolveHostError(hostname, { cause: error });
	}
	if (candidateIps.length === 0) throw new NoIpResolvedError(hostname);
	for (const ip of candidateIps) if (isBlockedIpAddress(ip)) {
		logger.warn({
			host: hostname,
			ip
		}, "Blocked external file URL resolving to private IP");
		throw new BlockedUrlResolvingToPrivateIpError(ip);
	}
}
async function resolveCandidateIps(hostname) {
	if (isIP(hostname) !== 0) return [hostname];
	try {
		return (await lookup(hostname, {
			all: true,
			verbatim: true
		})).map((result) => result.address);
	} catch (error) {
		logger.warn({
			host: hostname,
			error
		}, "DNS resolution failed for external file URL");
		throw new UnableToResolveHostError(hostname, { cause: error });
	}
}
function isBlockedIpAddress(ipAddress) {
	if (ipaddr.IPv4.isValid(ipAddress)) {
		const range = ipaddr.IPv4.parse(ipAddress).range();
		return range === "private" || range === "loopback" || range === "linkLocal" || range === "multicast" || range === "carrierGradeNat" || range === "reserved" || range === "unspecified" || range === "broadcast";
	}
	if (ipaddr.IPv6.isValid(ipAddress)) {
		const range = ipaddr.IPv6.parse(ipAddress).range();
		return range === "uniqueLocal" || range === "loopback" || range === "linkLocal" || range === "multicast" || range === "ipv4Mapped" || range === "rfc6145" || range === "rfc6052" || range === "6to4" || range === "teredo" || range === "reserved" || range === "unspecified";
	}
	return true;
}

//#endregion
export { isBlockedIpAddress, makeSanitizedSourceUrl, validateExternalFileUrl, validateUrlResolvesToPublicIp };