/**
 * Checks if a URL is a public HuggingFace or jsDelivr URL.
 * Used to determine if Authorization headers should be stripped.
 * Safely parses the URL to avoid substring matching vulnerabilities.
 * 
 * @param url The URL to check
 * @returns true if the URL is from a public whitelist
 */
import { URL_CONSTANTS } from "../constants";

export function isSafeUrl(url: string): boolean {
    try {
        const parsedUrl = new URL(url);
        const host = parsedUrl.hostname;

        return URL_CONSTANTS.TRUSTED_DOMAINS.some(domain =>
            host === domain || host.endsWith(`.${domain}`)
        );
    } catch {
        // If the URL is invalid, it's certainly not a known public one.
        return false;
    }
}

/**
 * Validates if a URL is safe to fetch from a server-side context (SSRF protection).
 * Strictly blocks local, private, and loopback addresses by default.
 * 
 * @param urlString The URL to validate
 * @param allowLocal If true, allows access to private IP ranges and localhost
 * @returns true if the URL is external and non-private (or local access is allowed)
 */
export function isExternalUrl(urlString: string, allowLocal: boolean = false): boolean {
    try {
        const url = new URL(urlString);

        // 1. Strict Protocol Allowlist (ALWAYS BLOCKED if not http/https)
        if (url.protocol !== 'http:' && url.protocol !== 'https:') {
            return false;
        }

        // 2. Defeat DNS Rebinding SSRF via TLS Validation natively
        // If local access is explicitly disabled, we ONLY permit https:
        // Ensures that even if the DNS is poisoned/rebound to 127.0.0.1 immediately before the fetch executes,
        // Chromium's TLS/SNI check will instantly terminate it because the local developer loopback
        // will not possess a valid SSL certificate for the attacker's public hostname (e.g., attacker.com).
        if (!allowLocal && url.protocol === 'http:') {
            return false;
        }

        let host = url.hostname.toLowerCase().replace(/\.$/, '');
        
        // Normalize IPv4-mapped IPv6 loopbacks and variants to capture them in regular rules
        host = host.replace(/^\[(?:0:0:0:0:0:ffff:|::ffff:)?(.*)\]$/, '$1');
        host = host.replace(/^(?:0:0:0:0:0:ffff:|::ffff:)/, '');

        // 2. Cloud Metadata Service (ALWAYS BLOCKED regardless of setting)
        if (host === '169.254.169.254') return false;

        // 3. Opt-In Bypass (Power user accepts the risk for localhost/private network)
        if (allowLocal) return true;

        // 4. Default Blocklist (Runs only if allowLocal is false)

        // Exact Loopback / Any Address Matches
        const blockedHosts = [
            'localhost',
            '127.0.0.1',
            '0.0.0.0',
            '[::1]',
            '[::]',
            '::1',
            '::',
            '0:0:0:0:0:0:0:1',
            '0:0:0:0:0:0:0:0',
            '7f00:1'
        ];
        if (blockedHosts.includes(host)) return false;

        // Loopback Ranges (127.0.0.0/8)
        if (host.startsWith('127.')) return false;

        // Private IP Ranges (IPv4)
        // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
        const isPrivate = /^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(host);
        if (isPrivate) return false;

        // Block IPv4-mapped IPv6 loopbacks and metadata (Node format converts them to hex)
        if (host === 'a9fe:a9fe' || host.startsWith('7f00:')) return false;
        if (/^a\.|^c0\.a8\.|^ac\.(1[0-9]|2[0-9]|3[0-1])\./.test(host)) return false; // (Hex mappings for private)

        return true;
    } catch {
        return false;
    }
}
