• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3    if (k2 === undefined) k2 = k;
4    var desc = Object.getOwnPropertyDescriptor(m, k);
5    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6      desc = { enumerable: true, get: function() { return m[k]; } };
7    }
8    Object.defineProperty(o, k2, desc);
9}) : (function(o, m, k, k2) {
10    if (k2 === undefined) k2 = k;
11    o[k2] = m[k];
12}));
13var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14    Object.defineProperty(o, "default", { enumerable: true, value: v });
15}) : function(o, v) {
16    o["default"] = v;
17});
18var __importStar = (this && this.__importStar) || function (mod) {
19    if (mod && mod.__esModule) return mod;
20    var result = {};
21    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22    __setModuleDefault(result, mod);
23    return result;
24};
25var __importDefault = (this && this.__importDefault) || function (mod) {
26    return (mod && mod.__esModule) ? mod : { "default": mod };
27};
28Object.defineProperty(exports, "__esModule", { value: true });
29exports.HttpsProxyAgent = void 0;
30const net = __importStar(require("net"));
31const tls = __importStar(require("tls"));
32const assert_1 = __importDefault(require("assert"));
33const debug_1 = __importDefault(require("debug"));
34const agent_base_1 = require("agent-base");
35const url_1 = require("url");
36const parse_proxy_response_1 = require("./parse-proxy-response");
37const debug = (0, debug_1.default)('https-proxy-agent');
38/**
39 * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to
40 * the specified "HTTP(s) proxy server" in order to proxy HTTPS requests.
41 *
42 * Outgoing HTTP requests are first tunneled through the proxy server using the
43 * `CONNECT` HTTP request method to establish a connection to the proxy server,
44 * and then the proxy server connects to the destination target and issues the
45 * HTTP request from the proxy server.
46 *
47 * `https:` requests have their socket connection upgraded to TLS once
48 * the connection to the proxy server has been established.
49 */
50class HttpsProxyAgent extends agent_base_1.Agent {
51    constructor(proxy, opts) {
52        super(opts);
53        this.options = { path: undefined };
54        this.proxy = typeof proxy === 'string' ? new url_1.URL(proxy) : proxy;
55        this.proxyHeaders = opts?.headers ?? {};
56        debug('Creating new HttpsProxyAgent instance: %o', this.proxy.href);
57        // Trim off the brackets from IPv6 addresses
58        const host = (this.proxy.hostname || this.proxy.host).replace(/^\[|\]$/g, '');
59        const port = this.proxy.port
60            ? parseInt(this.proxy.port, 10)
61            : this.proxy.protocol === 'https:'
62                ? 443
63                : 80;
64        this.connectOpts = {
65            // Attempt to negotiate http/1.1 for proxy servers that support http/2
66            ALPNProtocols: ['http/1.1'],
67            ...(opts ? omit(opts, 'headers') : null),
68            host,
69            port,
70        };
71    }
72    /**
73     * Called when the node-core HTTP client library is creating a
74     * new HTTP request.
75     */
76    async connect(req, opts) {
77        const { proxy } = this;
78        if (!opts.host) {
79            throw new TypeError('No "host" provided');
80        }
81        // Create a socket connection to the proxy server.
82        let socket;
83        if (proxy.protocol === 'https:') {
84            debug('Creating `tls.Socket`: %o', this.connectOpts);
85            const servername = this.connectOpts.servername || this.connectOpts.host;
86            socket = tls.connect({
87                ...this.connectOpts,
88                servername: servername && net.isIP(servername) ? undefined : servername,
89            });
90        }
91        else {
92            debug('Creating `net.Socket`: %o', this.connectOpts);
93            socket = net.connect(this.connectOpts);
94        }
95        const headers = typeof this.proxyHeaders === 'function'
96            ? this.proxyHeaders()
97            : { ...this.proxyHeaders };
98        const host = net.isIPv6(opts.host) ? `[${opts.host}]` : opts.host;
99        let payload = `CONNECT ${host}:${opts.port} HTTP/1.1\r\n`;
100        // Inject the `Proxy-Authorization` header if necessary.
101        if (proxy.username || proxy.password) {
102            const auth = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`;
103            headers['Proxy-Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}`;
104        }
105        headers.Host = `${host}:${opts.port}`;
106        if (!headers['Proxy-Connection']) {
107            headers['Proxy-Connection'] = this.keepAlive
108                ? 'Keep-Alive'
109                : 'close';
110        }
111        for (const name of Object.keys(headers)) {
112            payload += `${name}: ${headers[name]}\r\n`;
113        }
114        const proxyResponsePromise = (0, parse_proxy_response_1.parseProxyResponse)(socket);
115        socket.write(`${payload}\r\n`);
116        const { connect, buffered } = await proxyResponsePromise;
117        req.emit('proxyConnect', connect);
118        this.emit('proxyConnect', connect, req);
119        if (connect.statusCode === 200) {
120            req.once('socket', resume);
121            if (opts.secureEndpoint) {
122                // The proxy is connecting to a TLS server, so upgrade
123                // this socket connection to a TLS connection.
124                debug('Upgrading socket connection to TLS');
125                const servername = opts.servername || opts.host;
126                return tls.connect({
127                    ...omit(opts, 'host', 'path', 'port'),
128                    socket,
129                    servername: net.isIP(servername) ? undefined : servername,
130                });
131            }
132            return socket;
133        }
134        // Some other status code that's not 200... need to re-play the HTTP
135        // header "data" events onto the socket once the HTTP machinery is
136        // attached so that the node core `http` can parse and handle the
137        // error status code.
138        // Close the original socket, and a new "fake" socket is returned
139        // instead, so that the proxy doesn't get the HTTP request
140        // written to it (which may contain `Authorization` headers or other
141        // sensitive data).
142        //
143        // See: https://hackerone.com/reports/541502
144        socket.destroy();
145        const fakeSocket = new net.Socket({ writable: false });
146        fakeSocket.readable = true;
147        // Need to wait for the "socket" event to re-play the "data" events.
148        req.once('socket', (s) => {
149            debug('Replaying proxy buffer for failed request');
150            (0, assert_1.default)(s.listenerCount('data') > 0);
151            // Replay the "buffered" Buffer onto the fake `socket`, since at
152            // this point the HTTP module machinery has been hooked up for
153            // the user.
154            s.push(buffered);
155            s.push(null);
156        });
157        return fakeSocket;
158    }
159}
160HttpsProxyAgent.protocols = ['http', 'https'];
161exports.HttpsProxyAgent = HttpsProxyAgent;
162function resume(socket) {
163    socket.resume();
164}
165function omit(obj, ...keys) {
166    const ret = {};
167    let key;
168    for (key in obj) {
169        if (!keys.includes(key)) {
170            ret[key] = obj[key];
171        }
172    }
173    return ret;
174}
175//# sourceMappingURL=index.js.map