Spaces:
Paused
Paused
| /** | |
| * Module dependencies. | |
| */ | |
| var net = require('net'); | |
| var tls = require('tls'); | |
| var url = require('url'); | |
| var assert = require('assert'); | |
| var Agent = require('agent-base'); | |
| var inherits = require('util').inherits; | |
| var debug = require('debug')('https-proxy-agent'); | |
| /** | |
| * Module exports. | |
| */ | |
| module.exports = HttpsProxyAgent; | |
| /** | |
| * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to the | |
| * specified "HTTP(s) proxy server" in order to proxy HTTPS requests. | |
| * | |
| * @api public | |
| */ | |
| function HttpsProxyAgent(opts) { | |
| if (!(this instanceof HttpsProxyAgent)) return new HttpsProxyAgent(opts); | |
| if ('string' == typeof opts) opts = url.parse(opts); | |
| if (!opts) | |
| throw new Error( | |
| 'an HTTP(S) proxy server `host` and `port` must be specified!' | |
| ); | |
| debug('creating new HttpsProxyAgent instance: %o', opts); | |
| Agent.call(this, opts); | |
| var proxy = Object.assign({}, opts); | |
| // if `true`, then connect to the proxy server over TLS. defaults to `false`. | |
| this.secureProxy = proxy.protocol | |
| ? /^https:?$/i.test(proxy.protocol) | |
| : false; | |
| // prefer `hostname` over `host`, and set the `port` if needed | |
| proxy.host = proxy.hostname || proxy.host; | |
| proxy.port = +proxy.port || (this.secureProxy ? 443 : 80); | |
| // ALPN is supported by Node.js >= v5. | |
| // attempt to negotiate http/1.1 for proxy servers that support http/2 | |
| if (this.secureProxy && !('ALPNProtocols' in proxy)) { | |
| proxy.ALPNProtocols = ['http 1.1']; | |
| } | |
| if (proxy.host && proxy.path) { | |
| // if both a `host` and `path` are specified then it's most likely the | |
| // result of a `url.parse()` call... we need to remove the `path` portion so | |
| // that `net.connect()` doesn't attempt to open that as a unix socket file. | |
| delete proxy.path; | |
| delete proxy.pathname; | |
| } | |
| this.proxy = proxy; | |
| } | |
| inherits(HttpsProxyAgent, Agent); | |
| /** | |
| * Called when the node-core HTTP client library is creating a new HTTP request. | |
| * | |
| * @api public | |
| */ | |
| HttpsProxyAgent.prototype.callback = function connect(req, opts, fn) { | |
| var proxy = this.proxy; | |
| // create a socket connection to the proxy server | |
| var socket; | |
| if (this.secureProxy) { | |
| socket = tls.connect(proxy); | |
| } else { | |
| socket = net.connect(proxy); | |
| } | |
| // we need to buffer any HTTP traffic that happens with the proxy before we get | |
| // the CONNECT response, so that if the response is anything other than an "200" | |
| // response code, then we can re-play the "data" events on the socket once the | |
| // HTTP parser is hooked up... | |
| var buffers = []; | |
| var buffersLength = 0; | |
| function read() { | |
| var b = socket.read(); | |
| if (b) ondata(b); | |
| else socket.once('readable', read); | |
| } | |
| function cleanup() { | |
| socket.removeListener('end', onend); | |
| socket.removeListener('error', onerror); | |
| socket.removeListener('close', onclose); | |
| socket.removeListener('readable', read); | |
| } | |
| function onclose(err) { | |
| debug('onclose had error %o', err); | |
| } | |
| function onend() { | |
| debug('onend'); | |
| } | |
| function onerror(err) { | |
| cleanup(); | |
| fn(err); | |
| } | |
| function ondata(b) { | |
| buffers.push(b); | |
| buffersLength += b.length; | |
| var buffered = Buffer.concat(buffers, buffersLength); | |
| var str = buffered.toString('ascii'); | |
| if (!~str.indexOf('\r\n\r\n')) { | |
| // keep buffering | |
| debug('have not received end of HTTP headers yet...'); | |
| read(); | |
| return; | |
| } | |
| var firstLine = str.substring(0, str.indexOf('\r\n')); | |
| var statusCode = +firstLine.split(' ')[1]; | |
| debug('got proxy server response: %o', firstLine); | |
| if (200 == statusCode) { | |
| // 200 Connected status code! | |
| var sock = socket; | |
| // nullify the buffered data since we won't be needing it | |
| buffers = buffered = null; | |
| if (opts.secureEndpoint) { | |
| // since the proxy is connecting to an SSL server, we have | |
| // to upgrade this socket connection to an SSL connection | |
| debug( | |
| 'upgrading proxy-connected socket to TLS connection: %o', | |
| opts.host | |
| ); | |
| opts.socket = socket; | |
| opts.servername = opts.servername || opts.host; | |
| opts.host = null; | |
| opts.hostname = null; | |
| opts.port = null; | |
| sock = tls.connect(opts); | |
| } | |
| cleanup(); | |
| req.once('socket', resume); | |
| fn(null, sock); | |
| } else { | |
| // some other status code that's not 200... need to re-play the HTTP header | |
| // "data" events onto the socket once the HTTP machinery is attached so | |
| // that the node core `http` can parse and handle the error status code | |
| cleanup(); | |
| // the original socket is closed, and a new closed socket is | |
| // returned instead, so that the proxy doesn't get the HTTP request | |
| // written to it (which may contain `Authorization` headers or other | |
| // sensitive data). | |
| // | |
| // See: https://hackerone.com/reports/541502 | |
| socket.destroy(); | |
| socket = new net.Socket(); | |
| socket.readable = true; | |
| // save a reference to the concat'd Buffer for the `onsocket` callback | |
| buffers = buffered; | |
| // need to wait for the "socket" event to re-play the "data" events | |
| req.once('socket', onsocket); | |
| fn(null, socket); | |
| } | |
| } | |
| function onsocket(socket) { | |
| debug('replaying proxy buffer for failed request'); | |
| assert(socket.listenerCount('data') > 0); | |
| // replay the "buffers" Buffer onto the `socket`, since at this point | |
| // the HTTP module machinery has been hooked up for the user | |
| socket.push(buffers); | |
| // nullify the cached Buffer instance | |
| buffers = null; | |
| } | |
| socket.on('error', onerror); | |
| socket.on('close', onclose); | |
| socket.on('end', onend); | |
| read(); | |
| var hostname = opts.host + ':' + opts.port; | |
| var msg = 'CONNECT ' + hostname + ' HTTP/1.1\r\n'; | |
| var headers = Object.assign({}, proxy.headers); | |
| if (proxy.auth) { | |
| headers['Proxy-Authorization'] = | |
| 'Basic ' + Buffer.from(proxy.auth).toString('base64'); | |
| } | |
| // the Host header should only include the port | |
| // number when it is a non-standard port | |
| var host = opts.host; | |
| if (!isDefaultPort(opts.port, opts.secureEndpoint)) { | |
| host += ':' + opts.port; | |
| } | |
| headers['Host'] = host; | |
| headers['Connection'] = 'close'; | |
| Object.keys(headers).forEach(function(name) { | |
| msg += name + ': ' + headers[name] + '\r\n'; | |
| }); | |
| socket.write(msg + '\r\n'); | |
| }; | |
| /** | |
| * Resumes a socket. | |
| * | |
| * @param {(net.Socket|tls.Socket)} socket The socket to resume | |
| * @api public | |
| */ | |
| function resume(socket) { | |
| socket.resume(); | |
| } | |
| function isDefaultPort(port, secure) { | |
| return Boolean((!secure && port === 80) || (secure && port === 443)); | |
| } | |