import { Agent, createServer } from 'node:http';
import { request as httpRequest } from 'node:http';
import { request as httpsRequest } from 'node:https';
import { connect } from 'node:net';
import { URL } from 'node:url';
import { logForDebugging } from '../utils/debug.js';
import { connectViaParentProxy, dialDirect, openConnectTunnel, proxyAuthHeader, selectParentProxyUrl, shouldBypassParentProxy, stripBrackets, stripHopByHop, } from './parent-proxy.js';
export function createHttpProxyServer(options) {
    const server = createServer();
    // Handle CONNECT requests for HTTPS traffic
    server.on('connect', async (req, socket, head) => {
        // Attach error handler immediately to prevent unhandled errors
        socket.on('error', err => {
            logForDebugging(`Client socket error: ${err.message}`, { level: 'error' });
        });
        // Track client liveness so we can abort the upstream dial if they bail.
        let clientGone = false;
        socket.once('close', () => {
            clientGone = true;
        });
        try {
            const target = parseConnectTarget(req.url);
            if (!target) {
                logForDebugging(`Invalid CONNECT request: ${req.url}`, {
                    level: 'error',
                });
                socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
                return;
            }
            const { hostname, port } = target;
            const allowed = await options.filter(port, hostname, socket);
            if (!allowed) {
                logForDebugging(`Connection blocked to ${hostname}:${port}`, {
                    level: 'error',
                });
                socket.end('HTTP/1.1 403 Forbidden\r\n' +
                    'Content-Type: text/plain\r\n' +
                    'X-Proxy-Error: blocked-by-allowlist\r\n' +
                    '\r\n' +
                    'Connection blocked by network allowlist');
                return;
            }
            // Decide upstream route: MITM unix socket > parent HTTP proxy > direct.
            const mitmSocketPath = options.getMitmSocketPath?.(hostname);
            const parentUrl = !mitmSocketPath &&
                options.parentProxy &&
                !shouldBypassParentProxy(options.parentProxy, hostname)
                ? selectParentProxyUrl(options.parentProxy, { isHttps: true })
                : undefined;
            let upstream;
            try {
                if (mitmSocketPath) {
                    logForDebugging(`Routing CONNECT ${hostname}:${port} through MITM proxy at ${mitmSocketPath}`);
                    upstream = await openConnectTunnel({
                        dial: () => connect({ path: mitmSocketPath }),
                        readyEvent: 'connect',
                        destHost: hostname,
                        destPort: port,
                    });
                }
                else if (parentUrl) {
                    upstream = await connectViaParentProxy(parentUrl, hostname, port);
                }
                else {
                    upstream = await dialDirect(hostname, port);
                }
            }
            catch (err) {
                logForDebugging(`CONNECT tunnel failed: ${err.message}`, {
                    level: 'error',
                });
                socket.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
                return;
            }
            if (clientGone) {
                upstream.on('error', () => { }); // swallow post-resolve errors
                upstream.destroy();
                return;
            }
            socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
            // Forward any bytes the client sent in the same packet as the CONNECT
            // (Node delivers these as the `head` buffer, not via the socket stream).
            if (head.length)
                upstream.write(head);
            upstream.pipe(socket);
            socket.pipe(upstream);
            upstream.on('error', err => {
                logForDebugging(`CONNECT tunnel failed: ${err.message}`, {
                    level: 'error',
                });
                socket.destroy();
            });
            socket.on('close', () => upstream.destroy());
            upstream.on('close', () => socket.destroy());
        }
        catch (err) {
            logForDebugging(`Error handling CONNECT: ${err}`, { level: 'error' });
            socket.end('HTTP/1.1 500 Internal Server Error\r\n\r\n');
        }
    });
    // Handle regular HTTP requests
    server.on('request', async (req, res) => {
        try {
            const url = new URL(req.url);
            const hostname = stripBrackets(url.hostname);
            const port = url.port
                ? parseInt(url.port, 10)
                : url.protocol === 'https:'
                    ? 443
                    : 80;
            const allowed = await options.filter(port, hostname, req.socket);
            if (!allowed) {
                logForDebugging(`HTTP request blocked to ${hostname}:${port}`, {
                    level: 'error',
                });
                res.writeHead(403, {
                    'Content-Type': 'text/plain',
                    'X-Proxy-Error': 'blocked-by-allowlist',
                });
                res.end('Connection blocked by network allowlist');
                return;
            }
            // Client may have disconnected while we awaited the filter; bail now
            // rather than dialing an upstream nobody will read from.
            if (req.socket.destroyed)
                return;
            const fwdHeaders = { ...stripHopByHop(req.headers), host: url.host };
            // Decide upstream route: MITM unix socket > parent HTTP proxy > direct.
            const mitmSocketPath = options.getMitmSocketPath?.(hostname);
            const parentUrl = !mitmSocketPath &&
                options.parentProxy &&
                !shouldBypassParentProxy(options.parentProxy, hostname)
                ? selectParentProxyUrl(options.parentProxy, {
                    isHttps: url.protocol === 'https:',
                })
                : undefined;
            // Reconstruct the absolute URI from parsed components rather than
            // forwarding the client's raw req.url. This ensures the upstream proxy
            // sees exactly the host we allowlist-checked, closing URL-parser
            // differential bypasses.
            const absUrl = `${url.protocol}//${url.host}${url.pathname}${url.search}`;
            let proxyReq;
            if (mitmSocketPath) {
                logForDebugging(`Routing HTTP ${req.method} ${hostname}:${port} through MITM proxy at ${mitmSocketPath}`);
                const mitmAgent = new Agent({
                    // @ts-expect-error - socketPath is valid but not in types
                    socketPath: mitmSocketPath,
                });
                proxyReq = httpRequest({
                    agent: mitmAgent,
                    path: absUrl,
                    method: req.method,
                    headers: fwdHeaders,
                }, proxyRes => {
                    res.writeHead(proxyRes.statusCode, stripHopByHop(proxyRes.headers));
                    proxyRes.pipe(res);
                });
            }
            else if (parentUrl) {
                const parentHost = stripBrackets(parentUrl.hostname);
                const parentPort = Number(parentUrl.port) || (parentUrl.protocol === 'https:' ? 443 : 80);
                const auth = proxyAuthHeader(parentUrl);
                const requestFn = parentUrl.protocol === 'https:' ? httpsRequest : httpRequest;
                proxyReq = requestFn({
                    hostname: parentHost,
                    port: parentPort,
                    path: absUrl,
                    method: req.method,
                    headers: auth
                        ? { ...fwdHeaders, 'proxy-authorization': auth }
                        : fwdHeaders,
                }, proxyRes => {
                    res.writeHead(proxyRes.statusCode, stripHopByHop(proxyRes.headers));
                    proxyRes.pipe(res);
                });
            }
            else {
                const requestFn = url.protocol === 'https:' ? httpsRequest : httpRequest;
                proxyReq = requestFn({
                    hostname,
                    port,
                    path: url.pathname + url.search,
                    method: req.method,
                    headers: fwdHeaders,
                }, proxyRes => {
                    res.writeHead(proxyRes.statusCode, stripHopByHop(proxyRes.headers));
                    proxyRes.pipe(res);
                });
            }
            proxyReq.on('error', err => {
                logForDebugging(`Proxy request failed: ${err.message}`, {
                    level: 'error',
                });
                if (!res.headersSent) {
                    res.writeHead(502, { 'Content-Type': 'text/plain' });
                    res.end('Bad Gateway');
                }
                else {
                    res.destroy();
                }
            });
            // Tear down the upstream request if the client goes away mid-flight.
            res.on('close', () => proxyReq.destroy());
            req.pipe(proxyReq);
        }
        catch (err) {
            logForDebugging(`Error handling HTTP request: ${err}`, { level: 'error' });
            if (!res.headersSent) {
                res.writeHead(500, { 'Content-Type': 'text/plain' });
                res.end('Internal Server Error');
            }
            else {
                res.destroy();
            }
        }
    });
    return server;
}
/**
 * Parse a CONNECT request-target into host + port. Handles both plain
 * `host:port` and bracketed IPv6 `[::1]:port`.
 */
function parseConnectTarget(target) {
    const m = /^\[([^\]]+)\]:(\d+)$/.exec(target) ?? /^([^:]+):(\d+)$/.exec(target);
    if (!m)
        return undefined;
    const port = Number(m[2]);
    if (!Number.isInteger(port) || port < 1 || port > 65535)
        return undefined;
    return { hostname: m[1], port };
}//# sourceMappingURL=https://main.vscode-cdn.net/sourcemaps/6a44c352bd24569c417e530095901b649960f9f8/node_modules/@vscode/sandbox-runtime/dist/sandbox/http-proxy.js.map