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