Socket
Socket
Sign inDemoInstall

https-proxy-agent

Package Overview
Dependencies
3
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.2.0 to 0.3.0

.travis.yml

15

History.md

@@ -0,1 +1,16 @@

0.3.0 / 2013-09-16
==================
- https-proxy-agent: use "debug" module
- https-proxy-agent: update to the "agent-base" v1 API
- https-proxy-agent: default the "port" to 443 if not set
- https-proxy-agent: augment the `opts` object for the `tls.connect` function
- https-proxy-agent: use "extend" module
- https-proxy-agent: remove use of `this` as much as possible
- https-proxy-agent: listen for the "error" event of the socket
- test: refactor of tests to use "proxy" module
- test: add "error" event catching test
- test: add 407 proxy response test
- test: use "semver" module, disable the HTTPS over HTTPS test for node >= v0.11.3
0.2.0 / 2013-09-03

@@ -2,0 +17,0 @@ ==================

175

https-proxy-agent.js

@@ -9,4 +9,6 @@

var url = require('url');
var extend = require('extend');
var Agent = require('agent-base');
var inherits = require('util').inherits;
var debug = require('debug')('https-proxy-agent');

@@ -30,11 +32,13 @@ /**

if (!opts) throw new Error('an HTTP(S) proxy server `host` and `port` must be specified!');
Agent.call(this);
debug('creating new HttpsProxyAgent instance: %j', opts);
Agent.call(this, connect);
var proxy = clone(opts, {});
this.secureProxy = proxy.protocol && proxy.protocol == 'https:';
this.secureEndpoint = opts.secureEndpoint !== false; // `true` by default
if (!this.secureEndpoint) {
this.defaultPort = 80;
}
var proxy = extend({}, opts);
// if `true`, then connect to the proxy server over TLS. defaults to `false`.
this.secureProxy = proxy.protocol ? proxy.protocol == 'https:' : false;
// if `true`, then connect to the destination endpoint over TLS, defaults to `true`
this.secureEndpoint = opts.secureEndpoint !== false;
// prefer `hostname` over `host`, and set the `port` if needed

@@ -56,21 +60,37 @@ proxy.host = proxy.hostname || proxy.host;

/**
* Default port to connect to.
* Default options for the "connect" opts object.
*/
Agent.prototype.defaultPort = 443;
var defaults = { port: 80 };
var secureDefaults = { port: 443 };
/**
* Initiates a TCP connection to the specified HTTP proxy server.
* Called when the node-core HTTP client library is creating a new HTTP request.
*
* @api protected
* @api public
*/
HttpsProxyAgent.prototype.createConnection = function (opts, fn) {
function connect (req, _opts, fn) {
var proxy = this.proxy;
var secureProxy = this.secureProxy;
var secureEndpoint = this.secureEndpoint;
// these `opts` are the connect options to connect to the destination endpoint
var opts = extend({}, proxy, secureEndpoint ? secureDefaults : defaults, _opts);
var socket;
if (this.secureProxy) {
socket = tls.connect(this.proxy);
if (secureProxy) {
socket = tls.connect(proxy);
} else {
socket = net.connect(this.proxy);
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 () {

@@ -82,21 +102,114 @@ var b = socket.read();

var self = this;
function cleanup () {
socket.removeListener('data', ondata);
socket.removeListener('end', onend);
socket.removeListener('error', onerror);
socket.removeListener('close', onclose);
socket.removeListener('readable', read);
}
function onclose (err) {
console.error('onclose');
}
function onend (err) {
console.error('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... %j');
if (socket.read) {
read();
} else {
socket.once('data', ondata);
}
return;
}
var firstLine = str.substring(0, str.indexOf('\r\n'));
var statusCode = +firstLine.split(' ')[1];
debug('got proxy server response: "%s"', firstLine);
//console.log('statusCode: %d', statusCode);
//console.log(b.length, b, b.toString());
// TODO: verify that the socket is properly connected, check response...
var sock = socket;
if (200 == statusCode) {
// 200 Connected status code!
var sock = socket;
if (self.secureEndpoint) {
// since the proxy is connecting to an SSL server, we have
// to upgrade this socket connection to an SSL connection
sock = tls.connect({
socket: socket,
servername: opts.host
});
// nullify the buffered data since we won't be needing it
buffers = buffered = null;
if (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: "%s"', opts.host);
opts.socket = socket;
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();
// 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);
}
}
fn(null, sock);
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...');
}
buffers = null;
// XXX: not sure if forcing "end" here is appropriate, but otherwise the
// socket never closes and the tests fail since the proxy server never shuts
// down...
/*
if ('function' == typeof socket.onend) {
socket.onend();
} else {
socket.emit('end');
}
*/
//socket.destroy();
}
socket.on('error', onerror);
socket.on('close', onclose);
socket.on('end', onend);
if (socket.read) {

@@ -110,3 +223,3 @@ read();

var msg = 'CONNECT ' + hostname + ' HTTP/1.1\r\n';
var auth = this.proxy.auth;
var auth = proxy.auth;
if (auth) {

@@ -116,9 +229,5 @@ msg += 'Proxy-Authorization: Basic ' + new Buffer(auth).toString('base64') + '\r\n';

msg += 'Host: ' + hostname + '\r\n' +
'\r\n';
'Connection: close\r\n' +
'\r\n';
socket.write(msg);
};
function clone (src, dest) {
for (var i in src) dest[i] = src[i];
return dest;
}

10

package.json
{
"name": "https-proxy-agent",
"version": "0.2.0",
"version": "0.3.0",
"description": "An HTTP(s) proxy `http.Agent` implementation for HTTPS",

@@ -25,7 +25,11 @@ "main": "https-proxy-agent.js",

"dependencies": {
"agent-base": "~0.0.1"
"agent-base": "~1.0.1",
"debug": "~0.7.2",
"extend": "~1.2.0"
},
"devDependencies": {
"mocha": "~1.12.0"
"mocha": "~1.12.0",
"proxy": "~0.2.1",
"semver": "~2.1.0"
}
}
https-proxy-agent
================
### An HTTP(s) proxy `http.Agent` implementation for HTTPS
[![Build Status](https://travis-ci.org/TooTallNate/node-https-proxy-agent.png?branch=master)](https://travis-ci.org/TooTallNate/node-https-proxy-agent)

@@ -9,4 +10,4 @@ This module provides an `http.Agent` implementation that connects to a specified

Specifically, this `Agent` implementation connects to an intermediary "proxy"
server and issues the CONNECT HTTP method, which tells the proxy to open a
direct TCP connection to the endpoint server.
server and issues the [CONNECT HTTP method][CONNECT], which tells the proxy to
open a direct TCP connection to the destination server.

@@ -78,3 +79,3 @@ Since this agent implements the CONNECT HTTP method, it also works with other

// over "ws://", but `true` when connecting over "wss://"
opts.secureEndpoint = parsed.protocol && parsed.protocol == 'wss:';
opts.secureEndpoint = parsed.protocol ? parsed.protocol == 'wss:' : false;

@@ -97,2 +98,21 @@ var agent = new HttpsProxyAgent(opts);

API
---
### new HttpsProxyAgent(opts)
The `HttpsProxyAgent` class implements an `http.Agent` subclass that connects
to the specified "HTTP(s) proxy server" in order to proxy HTTPS and/or WebSocket
requests. This is achieved by using the [HTTP `CONNECT` method][CONNECT].
The `opts` argument may either be a string URI of the proxy server to use, or an
"options" object with more specific properties:
* `host` - String - Proxy host to connect to (may use `hostname` as well). Required.
* `port` - Number - Proxy port to connect to. Required.
* `secureProxy` - Boolean - If `true`, then use TLS to connect to the proxy. Defaults to `false`.
* `secureEndpoint` - Boolean - If `true` then then a TLS connection to the endpoint will be established on top of the proxy socket. Defaults to `true`.
* Any other options given are passed to the `net.connect()`/`tls.connect()` functions.
License

@@ -123,1 +143,3 @@ -------

SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[CONNECT]: http://en.wikipedia.org/wiki/HTTP_tunnel#HTTP_CONNECT_Tunneling

@@ -6,5 +6,10 @@

var fs = require('fs');
var url = require('url');
var http = require('http');
var https = require('https');
var assert = require('assert');
var Proxy = require('proxy');
var Semver = require('semver');
var version = new Semver(process.version);
var HttpsProxyAgent = require('../');

@@ -14,53 +19,170 @@

this.slow(5000);
this.timeout(10000);
var server;
var serverPort;
var link = process.env.LINK || 'https://graph.facebook.com/tootallnate';
var sslServer;
var sslServerPort;
it('should throw an Error if no "proxy" is given', function () {
assert.throws(function () {
new HttpsProxyAgent();
var proxy;
var proxyPort;
var sslProxy;
var sslProxyPort;
before(function (done) {
// setup target HTTP server
server = http.createServer();
server.listen(function () {
serverPort = server.address().port;
done();
});
});
it('should work over an HTTP proxy', function (done) {
var proxy = process.env.HTTP_PROXY || process.env.http_proxy || 'http://10.1.10.200:3128';
var agent = new HttpsProxyAgent(proxy);
before(function (done) {
// setup HTTP proxy server
proxy = Proxy();
proxy.listen(function () {
proxyPort = proxy.address().port;
done();
});
});
var opts = url.parse(link);
opts.agent = agent;
before(function (done) {
// setup target HTTPS server
var options = {
key: fs.readFileSync(__dirname + '/server.key'),
cert: fs.readFileSync(__dirname + '/server.crt')
};
sslServer = https.createServer(options);
sslServer.listen(function () {
sslServerPort = sslServer.address().port;
done();
});
});
https.get(opts, function (res) {
var data = '';
res.setEncoding('utf8');
res.on('data', function (b) {
data += b;
before(function (done) {
// setup SSL HTTP proxy server
var options = {
key: fs.readFileSync(__dirname + '/server.key'),
cert: fs.readFileSync(__dirname + '/server.crt')
};
sslProxy = Proxy(https.createServer(options));
sslProxy.listen(function () {
sslProxyPort = sslProxy.address().port;
done();
});
});
// shut down test HTTP server
after(function (done) {
server.once('close', function () { done(); });
server.close();
});
after(function (done) {
proxy.once('close', function () { done(); });
proxy.close();
});
after(function (done) {
sslServer.once('close', function () { done(); });
sslServer.close();
});
after(function (done) {
sslProxy.once('close', function () { done(); });
sslProxy.close();
});
describe('constructor', function () {
it('should throw an Error if no "proxy" argument is given', function () {
assert.throws(function () {
new HttpsProxyAgent();
});
res.on('end', function () {
data = JSON.parse(data);
assert.equal('tootallnate', data.username);
done();
});
it('should accept a "string" proxy argument', function () {
var agent = new HttpsProxyAgent('http://127.0.0.1:' + proxyPort);
assert.equal('127.0.0.1', agent.proxy.host);
assert.equal(proxyPort, agent.proxy.port);
});
it('should accept a `url.parse()` result object argument', function () {
var opts = url.parse('http://127.0.0.1:' + proxyPort);
var agent = new HttpsProxyAgent(opts);
assert.equal('127.0.0.1', agent.proxy.host);
assert.equal(proxyPort, agent.proxy.port);
});
describe('secureEndpoint', function () {
it('should default to `true`', function () {
var agent = new HttpsProxyAgent('http://127.0.0.1:' + proxyPort);
assert.equal(true, agent.secureEndpoint);
});
it('should be `false` when passed in as an option', function () {
var opts = url.parse('http://127.0.0.1:' + proxyPort);
opts.secureEndpoint = false;
var agent = new HttpsProxyAgent(opts);
assert.equal(false, agent.secureEndpoint);
});
it('should be `true` when passed in as an option', function () {
var opts = url.parse('http://127.0.0.1:' + proxyPort);
opts.secureEndpoint = true;
var agent = new HttpsProxyAgent(opts);
assert.equal(true, agent.secureEndpoint);
});
});
describe('secureProxy', function () {
it('should default to `false`', function () {
var agent = new HttpsProxyAgent({ port: proxyPort });
assert.equal(false, agent.secureProxy);
});
it('should be `false` when "http:" protocol is used', function () {
var agent = new HttpsProxyAgent({ port: proxyPort, protocol: 'http:' });
assert.equal(false, agent.secureProxy);
});
it('should be `true` when "https:" protocol is used', function () {
var agent = new HttpsProxyAgent({ port: proxyPort, protocol: 'https:' });
assert.equal(true, agent.secureProxy);
});
});
});
it('should work over an HTTPS proxy', function (done) {
var proxy = process.env.HTTPS_PROXY || process.env.https_proxy || 'https://10.1.10.200:3130';
proxy = url.parse(proxy);
proxy.rejectUnauthorized = false;
var agent = new HttpsProxyAgent(proxy);
describe('"http" module', function () {
var opts = url.parse(link);
opts.agent = agent;
opts.rejectUnauthorized = false;
beforeEach(function () {
delete proxy.authenticate;
});
https.get(opts, function (res) {
var data = '';
res.setEncoding('utf8');
res.on('data', function (b) {
data += b;
it('should receive the 407 authorization code on the `http.ClientResponse`', function (done) {
// set a proxy authentication function for this test
proxy.authenticate = function (req, fn) {
// reject all requests
fn(null, false);
};
var proxyUri = process.env.HTTP_PROXY || process.env.http_proxy || 'http://127.0.0.1:' + proxyPort;
var agent = new HttpsProxyAgent(proxyUri);
var opts = {};
// `host` and `port` don't really matter since the proxy will reject anyways
opts.host = '127.0.0.1';
opts.port = 80;
opts.agent = agent;
var req = http.get(opts, function (res) {
assert.equal(407, res.statusCode);
assert('proxy-authenticate' in res.headers);
done();
});
res.on('end', function () {
data = JSON.parse(data);
assert.equal('tootallnate', data.username);
});
it('should emit an "error" event on the `http.ClientRequest` if the proxy does not exist', function (done) {
// port 4 is a reserved, but "unassigned" port
var proxyUri = 'http://127.0.0.1:4';
var agent = new HttpsProxyAgent(proxyUri);
var opts = url.parse('http://nodejs.org');
opts.agent = agent;
var req = http.get(opts);
req.once('error', function (err) {
assert.equal('ECONNREFUSED', err.code);
req.abort();
done();

@@ -71,2 +193,77 @@ });

describe('"https" module', function () {
it('should work over an HTTP proxy', function (done) {
// set HTTP "request" event handler for this test
sslServer.once('request', function (req, res) {
res.end(JSON.stringify(req.headers));
});
var proxy = process.env.HTTP_PROXY || process.env.http_proxy || 'http://127.0.0.1:' + proxyPort;
proxy = url.parse(proxy);
// `rejectUnauthorized` shoudn't *technically* be necessary here,
// but up until node v0.11.6, the `http.Agent` class didn't have
// access to the *entire* request "options" object. Thus,
// `https-proxy-agent` will *also* merge in options you pass here
// to the destination endpoints…
proxy.rejectUnauthorized = false;
var agent = new HttpsProxyAgent(proxy);
var opts = url.parse('https://127.0.0.1:' + sslServerPort);
opts.rejectUnauthorized = false;
opts.agent = agent;
https.get(opts, function (res) {
var data = '';
res.setEncoding('utf8');
res.on('data', function (b) {
data += b;
});
res.on('end', function () {
data = JSON.parse(data);
assert.equal('127.0.0.1:' + sslServerPort, data.host);
done();
});
});
});
if (version.compare('0.11.3') < 0) {
// This test is disabled on node >= 0.11.3, since it currently segfaults :(
// See: https://github.com/joyent/node/issues/6204
it('should work over an HTTPS proxy', function (done) {
// set HTTP "request" event handler for this test
sslServer.once('request', function (req, res) {
res.end(JSON.stringify(req.headers));
});
var proxy = process.env.HTTPS_PROXY || process.env.https_proxy || 'https://127.0.0.1:' + sslProxyPort;
proxy = url.parse(proxy);
// `rejectUnauthorized` is actually necessary this time since the HTTPS
// proxy server itself is using a self-signed SSL certificate…
proxy.rejectUnauthorized = false;
var agent = new HttpsProxyAgent(proxy);
var opts = url.parse('https://127.0.0.1:' + sslServerPort);
opts.agent = agent;
opts.rejectUnauthorized = false;
https.get(opts, function (res) {
var data = '';
res.setEncoding('utf8');
res.on('data', function (b) {
data += b;
});
res.on('end', function () {
data = JSON.parse(data);
assert.equal('127.0.0.1:' + sslServerPort, data.host);
done();
});
});
});
} else {
it('should work over an HTTPS proxy');
}
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc