https-proxy-agent
Advanced tools
Comparing version 2.2.3 to 2.2.4
declare module 'https-proxy-agent' { | ||
import * as https from 'https' | ||
import * as https from 'https'; | ||
namespace HttpsProxyAgent { | ||
interface HttpsProxyAgentOptions { | ||
host: string | ||
port: number | string | ||
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; | ||
} |
316
index.js
@@ -8,3 +8,3 @@ /** | ||
var url = require('url'); | ||
var events = require('events'); | ||
var assert = require('assert'); | ||
var Agent = require('agent-base'); | ||
@@ -28,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; | ||
} | ||
@@ -73,157 +75,155 @@ 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('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...'); | ||
read(); | ||
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(); | ||
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(); | ||
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 "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(); | ||
// 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); | ||
// save a reference to the concat'd Buffer for the `onsocket` callback | ||
buffers = buffered; | ||
fn(null, socket); | ||
} | ||
} | ||
// need to wait for the "socket" event to re-play the "data" events | ||
req.once('socket', onsocket); | ||
function onsocket(socket) { | ||
debug('replaying proxy buffer for failed request'); | ||
fn(null, socket); | ||
} | ||
} | ||
// 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...'); | ||
} | ||
function onsocket(socket) { | ||
debug('replaying proxy buffer for failed request'); | ||
assert(socket.listenerCount('data') > 0); | ||
// nullify the cached Buffer instance | ||
buffers = null; | ||
} | ||
// 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); | ||
socket.on('error', onerror); | ||
socket.on('close', onclose); | ||
socket.on('end', onend); | ||
// nullify the cached Buffer instance | ||
buffers = null; | ||
} | ||
read(); | ||
socket.on('error', onerror); | ||
socket.on('close', onclose); | ||
socket.on('end', onend); | ||
var hostname = opts.host + ':' + opts.port; | ||
var msg = 'CONNECT ' + hostname + ' HTTP/1.1\r\n'; | ||
read(); | ||
var headers = Object.assign({}, proxy.headers); | ||
if (proxy.auth) { | ||
headers['Proxy-Authorization'] = | ||
'Basic ' + Buffer.from(proxy.auth).toString('base64'); | ||
} | ||
var hostname = opts.host + ':' + opts.port; | ||
var msg = 'CONNECT ' + hostname + ' HTTP/1.1\r\n'; | ||
// 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; | ||
var headers = Object.assign({}, proxy.headers); | ||
if (proxy.auth) { | ||
headers['Proxy-Authorization'] = | ||
'Basic ' + Buffer.from(proxy.auth).toString('base64'); | ||
} | ||
headers['Connection'] = 'close'; | ||
Object.keys(headers).forEach(function(name) { | ||
msg += name + ': ' + headers[name] + '\r\n'; | ||
}); | ||
// 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; | ||
socket.write(msg + '\r\n'); | ||
headers['Connection'] = 'close'; | ||
Object.keys(headers).forEach(function(name) { | ||
msg += name + ': ' + headers[name] + '\r\n'; | ||
}); | ||
socket.write(msg + '\r\n'); | ||
}; | ||
@@ -239,7 +239,7 @@ | ||
function resume(socket) { | ||
socket.resume(); | ||
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.3", | ||
"version": "2.2.4", | ||
"description": "An HTTP(s) proxy `http.Agent` implementation for HTTPS", | ||
@@ -5,0 +5,0 @@ "main": "./index.js", |
19534
7
301