/**
 * Node.js HTTP CONNECT proxy server.
 *
 * Handles HTTP CONNECT (for HTTPS) and plain HTTP forward requests.
 * Domain filtering is delegated to a callback — the proxy has no
 * opinion about how allowlists are managed.
 *
 * Runs in the host Node.js process. No IPC, no Python, no WSL.
 */
import * as net from 'net';
/**
 * Start an HTTP CONNECT proxy on a random localhost port.
 *
 * Supports:
 * - CONNECT tunneling (HTTPS, WebSocket, etc.)
 * - Plain HTTP forward (GET http://host/path → rewrite + relay)
 */
export function startHttpProxy(options) {
    return new Promise((resolve, reject) => {
        const sockets = new Set();
        const server = net.createServer((client) => {
            sockets.add(client);
            client.on('close', () => sockets.delete(client));
            handleClient(client, options.checkDomain);
        });
        server.on('error', reject);
        // Bind to all interfaces — the socat bridge in WSL connects via the host IP
        // (172.29.x.x), not 127.0.0.1. Windows Firewall blocks external access.
        server.listen(0, '0.0.0.0', () => {
            const addr = server.address();
            if (!addr || typeof addr === 'string') {
                server.close();
                reject(new Error('Failed to get server address'));
                return;
            }
            resolve({
                port: addr.port,
                stop: () => {
                    for (const socket of sockets)
                        socket.destroy();
                    server.close();
                }
            });
        });
    });
}
// ---------------------------------------------------------------------------
// Connection handler
// ---------------------------------------------------------------------------
function handleClient(client, checkDomain) {
    let buffer = Buffer.alloc(0);
    let handled = false;
    const onData = (chunk) => {
        buffer = Buffer.concat([buffer, chunk]);
        // Wait for the full first line (ends with \r\n)
        const newlineIdx = buffer.indexOf('\r\n');
        if (newlineIdx === -1) {
            if (buffer.length > 8192) {
                client.end('HTTP/1.1 400 Bad Request\r\n\r\n');
                handled = true;
            }
            return;
        }
        client.removeListener('data', onData);
        handled = true;
        const requestLine = buffer.subarray(0, newlineIdx).toString('utf-8');
        const remaining = buffer.subarray(newlineIdx + 2);
        const parsed = parseRequestLine(requestLine);
        if (!parsed) {
            client.end('HTTP/1.1 400 Bad Request\r\n\r\n');
            return;
        }
        if (parsed.method === 'CONNECT') {
            handleConnect(client, parsed.host, parsed.port, checkDomain);
        }
        else {
            handlePlainHttp(client, parsed, remaining, checkDomain);
        }
    };
    client.on('data', onData);
    client.on('error', () => { });
    setTimeout(() => {
        if (!handled) {
            client.destroy();
        }
    }, 10000);
}
// ---------------------------------------------------------------------------
// Target connection helper — shared by CONNECT and plain HTTP
// ---------------------------------------------------------------------------
function pipeToTarget(client, host, port, onConnect, onError) {
    const target = net.createConnection({ host, port }, () => {
        onConnect(target);
        client.pipe(target);
        target.pipe(client);
    });
    target.on('error', () => onError());
    client.on('error', () => target.destroy());
    client.on('close', () => target.destroy());
    target.on('close', () => client.destroy());
    target.setTimeout(10000, () => {
        onError();
        target.destroy();
    });
}
// ---------------------------------------------------------------------------
// CONNECT tunneling (HTTPS, WebSocket, etc.)
// ---------------------------------------------------------------------------
async function handleConnect(client, host, port, checkDomain) {
    try {
        const allowed = await checkDomain(host);
        if (!allowed) {
            client.end('HTTP/1.1 403 Forbidden\r\n\r\nBlocked by sandbox policy\r\n');
            return;
        }
        pipeToTarget(client, host, port, () => client.write('HTTP/1.1 200 Connection Established\r\n\r\n'), () => client.end('HTTP/1.1 502 Bad Gateway\r\n\r\n'));
    }
    catch {
        client.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
    }
}
// ---------------------------------------------------------------------------
// Plain HTTP forward (GET http://host/path → relay)
// ---------------------------------------------------------------------------
async function handlePlainHttp(client, parsed, remainingData, checkDomain) {
    try {
        const allowed = await checkDomain(parsed.host);
        if (!allowed) {
            client.end('HTTP/1.1 403 Forbidden\r\n\r\nBlocked by sandbox policy\r\n');
            return;
        }
        const rewritten = `${parsed.method} ${parsed.path} ${parsed.httpVersion}\r\n`;
        pipeToTarget(client, parsed.host, parsed.port, (target) => {
            target.write(rewritten);
            if (remainingData.length > 0)
                target.write(remainingData);
        }, () => client.end('HTTP/1.1 502 Bad Gateway\r\n\r\n'));
    }
    catch {
        client.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
    }
}
function parseRequestLine(line) {
    const parts = line.split(' ');
    if (parts.length < 2)
        return null;
    const method = parts[0];
    const target = parts[1];
    const httpVersion = parts[2] ?? 'HTTP/1.1';
    if (method === 'CONNECT') {
        const [host, portStr] = target.split(':');
        if (!host)
            return null;
        return {
            method,
            host,
            port: portStr ? parseInt(portStr, 10) : 443,
            path: '',
            httpVersion
        };
    }
    if (target.startsWith('http://')) {
        try {
            const url = new URL(target);
            return {
                method,
                host: url.hostname,
                port: url.port ? parseInt(url.port, 10) : 80,
                path: url.pathname + url.search,
                httpVersion
            };
        }
        catch {
            return null;
        }
    }
    return null;
}
//# sourceMappingURL=http-proxy.js.map