https-proxy-agent
Advanced tools
Comparing version 2.2.2 to 3.0.0
declare module 'https-proxy-agent' { | ||
import * as https from 'https' | ||
import * as https from 'https'; | ||
namespace HttpsProxyAgent { | ||
interface HttpsProxyAgentOptions { | ||
host: string | ||
port: number | ||
secureProxy?: boolean | ||
headers?: { | ||
[key: string]: string | ||
} | ||
[key: string]: any | ||
} | ||
} | ||
// HttpsProxyAgent doesnt *actually* extend https.Agent, but for my purposes I want it to pretend that it does | ||
class HttpsProxyAgent extends https.Agent { | ||
constructor(opts: HttpsProxyAgent.HttpsProxyAgentOptions | string) | ||
} | ||
namespace HttpsProxyAgent { | ||
interface HttpsProxyAgentOptions { | ||
host: string; | ||
port: number | string; | ||
secureProxy?: boolean; | ||
headers?: { | ||
[key: string]: string; | ||
}; | ||
[key: string]: any; | ||
} | ||
} | ||
export = HttpsProxyAgent | ||
// HttpsProxyAgent doesnt *actually* extend https.Agent, but for my purposes I want it to pretend that it does | ||
class HttpsProxyAgent extends https.Agent { | ||
constructor(opts: HttpsProxyAgent.HttpsProxyAgentOptions | string); | ||
} | ||
export = HttpsProxyAgent; | ||
} |
332
index.js
@@ -8,2 +8,3 @@ /** | ||
var url = require('url'); | ||
var events = require('events'); | ||
var Agent = require('agent-base'); | ||
@@ -27,36 +28,38 @@ var inherits = require('util').inherits; | ||
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); | ||
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); | ||
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; | ||
// 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); | ||
// 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'] | ||
} | ||
// 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; | ||
} | ||
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; | ||
this.defaultPort = 443; | ||
this.proxy = proxy; | ||
this.defaultPort = 443; | ||
} | ||
@@ -72,161 +75,172 @@ inherits(HttpsProxyAgent, Agent); | ||
HttpsProxyAgent.prototype.callback = function connect(req, opts, fn) { | ||
var proxy = this.proxy; | ||
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); | ||
} | ||
// 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; | ||
// 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 read() { | ||
var b = socket.read(); | ||
if (b) ondata(b); | ||
else socket.once('readable', read); | ||
} | ||
function cleanup() { | ||
socket.removeListener('data', ondata); | ||
socket.removeListener('end', onend); | ||
socket.removeListener('error', onerror); | ||
socket.removeListener('close', onclose); | ||
socket.removeListener('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 onclose(err) { | ||
debug('onclose had error %o', err); | ||
} | ||
function onend() { | ||
debug('onend'); | ||
} | ||
function onend() { | ||
debug('onend'); | ||
} | ||
function onerror(err) { | ||
cleanup(); | ||
fn(err); | ||
} | ||
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'); | ||
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...'); | ||
if (socket.read) { | ||
read(); | ||
} else { | ||
socket.once('data', ondata); | ||
} | ||
return; | ||
} | ||
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); | ||
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; | ||
if (200 == statusCode) { | ||
// 200 Connected status code! | ||
var sock = socket; | ||
// nullify the buffered data since we won't be needing it | ||
buffers = buffered = null; | ||
// 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); | ||
} | ||
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(); | ||
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 user can parse and handle the error status code | ||
cleanup(); | ||
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(); | ||
// save a reference to the concat'd Buffer for the `onsocket` callback | ||
buffers = buffered; | ||
// the original socket is closed, and a "fake socket" EventEmitter 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 events.EventEmitter(); | ||
// need to wait for the "socket" event to re-play the "data" events | ||
req.once('socket', onsocket); | ||
fn(null, socket); | ||
} | ||
} | ||
// save a reference to the concat'd Buffer for the `onsocket` callback | ||
buffers = buffered; | ||
function onsocket(socket) { | ||
// replay the "buffers" Buffer onto the `socket`, since at this point | ||
// the HTTP module machinery has been hooked up for the user | ||
if ('function' == typeof socket.ondata) { | ||
// node <= v0.11.3, the `ondata` function is set on the socket | ||
socket.ondata(buffers, 0, buffers.length); | ||
} else if (socket.listeners('data').length > 0) { | ||
// node > v0.11.3, the "data" event is listened for directly | ||
socket.emit('data', buffers); | ||
} else { | ||
// never? | ||
throw new Error('should not happen...'); | ||
} | ||
// need to wait for the "socket" event to re-play the "data" events | ||
req.once('socket', onsocket); | ||
// nullify the cached Buffer instance | ||
buffers = null; | ||
} | ||
fn(null, socket); | ||
} | ||
} | ||
socket.on('error', onerror); | ||
socket.on('close', onclose); | ||
socket.on('end', onend); | ||
function onsocket(socket) { | ||
debug('replaying proxy buffer for failed request'); | ||
if (socket.read) { | ||
read(); | ||
} else { | ||
socket.once('data', ondata); | ||
} | ||
// replay the "buffers" Buffer onto the `socket`, since at this point | ||
// the HTTP module machinery has been hooked up for the user | ||
if (socket.listenerCount('data') > 0) { | ||
socket.emit('data', buffers); | ||
} else { | ||
// never? | ||
throw new Error('should not happen...'); | ||
} | ||
var hostname = opts.host + ':' + opts.port; | ||
var msg = 'CONNECT ' + hostname + ' HTTP/1.1\r\n'; | ||
// nullify the cached Buffer instance | ||
buffers = null; | ||
} | ||
var headers = Object.assign({}, proxy.headers); | ||
if (proxy.auth) { | ||
headers['Proxy-Authorization'] = | ||
'Basic ' + Buffer.from(proxy.auth).toString('base64'); | ||
} | ||
socket.on('error', onerror); | ||
socket.on('close', onclose); | ||
socket.on('end', onend); | ||
// 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; | ||
read(); | ||
headers['Connection'] = 'close'; | ||
Object.keys(headers).forEach(function(name) { | ||
msg += name + ': ' + headers[name] + '\r\n'; | ||
}); | ||
var hostname = opts.host + ':' + opts.port; | ||
var msg = 'CONNECT ' + hostname + ' HTTP/1.1\r\n'; | ||
socket.write(msg + '\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)); | ||
return Boolean((!secure && port === 80) || (secure && port === 443)); | ||
} |
{ | ||
"name": "https-proxy-agent", | ||
"version": "2.2.2", | ||
"version": "3.0.0", | ||
"description": "An HTTP(s) proxy `http.Agent` implementation for HTTPS", | ||
@@ -30,4 +30,4 @@ "main": "./index.js", | ||
"devDependencies": { | ||
"mocha": "^3.4.2", | ||
"proxy": "^0.2.4" | ||
"mocha": "^6.2.0", | ||
"proxy": "1" | ||
}, | ||
@@ -34,0 +34,0 @@ "engines": { |
@@ -106,3 +106,3 @@ https-proxy-agent | ||
* `port` - Number - Proxy port to connect to. Required. | ||
* `secureProxy` - Boolean - If `true`, then use TLS to connect to the proxy. Defaults to `false`. | ||
* `protocol` - String - If `https:`, then use TLS to connect to the proxy. | ||
* `headers` - Object - Additional HTTP headers to be sent on the HTTP CONNECT method. | ||
@@ -109,0 +109,0 @@ * Any other options given are passed to the `net.connect()`/`tls.connect()` functions. |
19603
7
304