|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- 'use strict';
- var assert = require('chai').assert;
- var sinon = require('sinon');
- var assign = require('lodash/assign');
- var pick = require('lodash/pick');
-
- // Deliberate: node and 3rd party modules before global-tunnel
- var EventEmitter = require('events').EventEmitter;
- var net = require('net');
- var tls = require('tls');
- var http = require('http');
- var globalHttpAgent = http.globalAgent;
- var https = require('https');
- var globalHttpsAgent = https.globalAgent;
- var request = require('request');
-
- // Deliberate: load after all 3rd party modules
- var globalTunnel = require('../index');
-
- function newFakeAgent() {
- var fakeAgent = {
- addRequest: sinon.stub()
- };
- return fakeAgent;
- }
-
- // This function replaces 'host' by 'hostname' in the options for http.request()
- // background: http.request() allows to use either 'host' or 'hostname' to be used,
- // both needs to be tested
- function replaceHostByHostname(useHostname, options) {
- if (useHostname) {
- options.hostname = options.host;
- delete options.host;
- }
- return options;
- }
-
- var origEnv;
- function saveEnv() {
- origEnv = process.env.http_proxy;
- delete process.env.http_proxy;
- }
- function restoreEnv() {
- if (origEnv !== undefined) {
- process.env.http_proxy = origEnv; // eslint-disable-line camelcase
- }
- }
-
- describe('global-proxy', function() {
- // Save and restore http_proxy environment variable (yes, it's lower-case by
- // convention).
- before(saveEnv);
- after(restoreEnv);
-
- // Sinon setup & teardown
- var sandbox;
- var origHttpCreateConnection;
-
- before(function() {
- sandbox = sinon.createSandbox();
-
- sandbox.stub(globalHttpAgent, 'addRequest');
- sandbox.stub(globalHttpsAgent, 'addRequest');
-
- assert.equal(http.Agent.prototype.addRequest, https.Agent.prototype.addRequest);
- sandbox.spy(http.Agent.prototype, 'addRequest');
-
- sandbox.stub(net, 'createConnection').callsFake(function() {
- return new EventEmitter();
- });
- sandbox.stub(tls, 'connect').callsFake(function() {
- return new EventEmitter();
- });
-
- // This is needed as at some point Node HTTP aggent implementation started
- // plucking the createConnection method from the `net` module
- // instead of doing `net.createConnection`
- origHttpCreateConnection = http.Agent.prototype.createConnection;
- http.Agent.prototype.createConnection = net.createConnection;
- });
-
- afterEach(function() {
- sandbox.resetHistory();
- });
-
- after(function() {
- sandbox.restore();
- http.Agent.prototype.createConnection = origHttpCreateConnection;
- });
-
- describe('invalid configs', function() {
- it('requires a host', function() {
- var conf = { host: null, port: 1234 };
- assert.throws(function() {
- globalTunnel.initialize(conf);
- }, 'upstream proxy host is required');
- globalTunnel.end();
- });
-
- it('requires a port', function() {
- var conf = { host: '10.2.3.4', port: 0 };
- assert.throws(function() {
- globalTunnel.initialize(conf);
- }, 'upstream proxy port is required');
- globalTunnel.end();
- });
-
- it('clamps tunnel types', function() {
- var conf = { host: '10.2.3.4', port: 1234, connect: 'INVALID' };
- assert.throws(function() {
- globalTunnel.initialize(conf);
- }, 'valid connect options are "neither", "https", or "both"');
- globalTunnel.end();
- });
- });
-
- describe('exposed config', function() {
- afterEach(function() {
- globalTunnel.end();
- });
-
- it('has the same params as the passed config', function() {
- var conf = {
- host: 'proxy.com',
- port: 1234,
- proxyAuth: 'user:pwd',
- protocol: 'https:'
- };
- globalTunnel.initialize(conf);
- assert.deepEqual(
- conf,
- pick(globalTunnel.proxyConfig, ['host', 'port', 'proxyAuth', 'protocol'])
- );
- });
-
- it('has the expected defaults', function() {
- var conf = { host: 'proxy.com', port: 1234, proxyAuth: 'user:pwd' };
- globalTunnel.initialize(conf);
- assert.equal(globalTunnel.proxyConfig.protocol, 'http:');
- });
- });
-
- describe('stringified config', function() {
- afterEach(function() {
- globalTunnel.end();
- });
-
- it('has the same params as the passed config', function() {
- var conf = {
- host: 'proxy.com',
- port: 1234,
- proxyAuth: 'user:pwd',
- protocol: 'https'
- };
- globalTunnel.initialize(conf);
- assert.equal(globalTunnel.proxyUrl, 'https://user:pwd@proxy.com:1234');
- });
-
- it('encodes url', function() {
- var conf = {
- host: 'proxy.com',
- port: 1234,
- proxyAuth: 'user:4P@S$W0_r-D',
- protocol: 'https'
- };
- globalTunnel.initialize(conf);
- assert.equal(globalTunnel.proxyUrl, 'https://user:4P%40S%24W0_r-D@proxy.com:1234');
- });
- });
-
- function proxyEnabledTests(testParams) {
- function connected(innerProto) {
- var innerSecure = innerProto === 'https:';
-
- var called;
- if (testParams.isHttpsProxy) {
- called = tls.connect;
- sinon.assert.notCalled(net.createConnection);
- } else {
- called = net.createConnection;
- sinon.assert.notCalled(tls.connect);
- }
-
- sinon.assert.calledOnce(called);
- if (typeof called.getCall(0).args[0] === 'object') {
- sinon.assert.calledWith(called, sinon.match.has('port', testParams.port));
- sinon.assert.calledWith(called, sinon.match.has('host', '10.2.3.4'));
- } else {
- sinon.assert.calledWith(called, testParams.port, '10.2.3.4');
- }
-
- var isCONNECT =
- testParams.connect === 'both' || (innerSecure && testParams.connect === 'https');
- if (isCONNECT) {
- var expectConnect = 'example.dev:' + (innerSecure ? 443 : 80);
- var whichAgent = innerSecure ? https.globalAgent : http.globalAgent;
-
- sinon.assert.calledOnce(whichAgent.request);
- sinon.assert.calledWith(whichAgent.request, sinon.match.has('method', 'CONNECT'));
- sinon.assert.calledWith(
- whichAgent.request,
- sinon.match.has('path', expectConnect)
- );
- } else {
- sinon.assert.calledOnce(http.Agent.prototype.addRequest);
- var req = http.Agent.prototype.addRequest.getCall(0).args[0];
-
- var method = req.method;
- assert.equal(method, 'GET');
-
- var path = req.path;
- if (innerSecure) {
- assert.match(path, new RegExp('^https://example\\.dev:443/'));
- } else {
- assert.match(path, new RegExp('^http://example\\.dev:80/'));
- }
- }
- }
-
- var localSandbox;
- beforeEach(function() {
- localSandbox = sinon.createSandbox();
- if (testParams.connect === 'both') {
- localSandbox.spy(http.globalAgent, 'request');
- }
- if (testParams.connect !== 'neither') {
- localSandbox.spy(https.globalAgent, 'request');
- }
- });
- afterEach(function() {
- localSandbox.restore();
- });
-
- it('(got proxying set up)', function() {
- assert.isTrue(globalTunnel.isProxying);
- });
-
- describe('with the request library', function() {
- it('will proxy http requests', function(done) {
- assert.isTrue(globalTunnel.isProxying);
- var dummyCb = sinon.stub();
- request.get('http://example.dev/', dummyCb);
- setImmediate(function() {
- connected('http:');
- sinon.assert.notCalled(globalHttpAgent.addRequest);
- sinon.assert.notCalled(globalHttpsAgent.addRequest);
- done();
- });
- });
-
- it('will proxy https requests', function(done) {
- assert.isTrue(globalTunnel.isProxying);
- var dummyCb = sinon.stub();
- request.get('https://example.dev/', dummyCb);
- setImmediate(function() {
- connected('https:');
- sinon.assert.notCalled(globalHttpAgent.addRequest);
- sinon.assert.notCalled(globalHttpsAgent.addRequest);
- done();
- });
- });
- });
-
- describe('using raw request interface', function() {
- function rawRequest(useHostname) {
- var req = http.request(
- replaceHostByHostname(useHostname, {
- method: 'GET',
- path: '/raw-http',
- host: 'example.dev'
- }),
- function() {}
- );
- req.end();
-
- connected('http:');
- sinon.assert.notCalled(globalHttpAgent.addRequest);
- sinon.assert.notCalled(globalHttpsAgent.addRequest);
- }
- it('will proxy http requests (`host`)', function() {
- rawRequest(false);
- });
- it('will proxy http requests (`hostname`)', function() {
- rawRequest(true);
- });
-
- it('will proxy https requests', function() {
- var req = https.request(
- replaceHostByHostname(false, {
- method: 'GET',
- path: '/raw-https',
- host: 'example.dev'
- }),
- function() {}
- );
- req.end();
-
- connected('https:');
- sinon.assert.notCalled(globalHttpAgent.addRequest);
- sinon.assert.notCalled(globalHttpsAgent.addRequest);
- });
-
- it('request respects explicit agent param', function() {
- var agent = newFakeAgent();
- var req = http.request(
- replaceHostByHostname(false, {
- method: 'GET',
- path: '/raw-http-w-agent',
- host: 'example.dev',
- agent: agent
- }),
- function() {}
- );
- req.end();
-
- sinon.assert.notCalled(globalHttpAgent.addRequest);
- sinon.assert.notCalled(globalHttpsAgent.addRequest);
- sinon.assert.notCalled(net.createConnection);
- sinon.assert.notCalled(tls.connect);
- sinon.assert.calledOnce(agent.addRequest);
- });
-
- describe('request with `null` agent and defined `createConnection`', function() {
- before(function() {
- sinon.stub(http.ClientRequest.prototype, 'onSocket');
- });
- after(function() {
- http.ClientRequest.prototype.onSocket.restore();
- });
-
- function noAgent(useHostname) {
- var createConnection = sinon.stub();
- var req = http.request(
- replaceHostByHostname(useHostname, {
- method: 'GET',
- path: '/no-agent',
- host: 'example.dev',
- agent: null,
- createConnection: createConnection
- }),
- function() {} // eslint-disable-line max-nested-callbacks
- );
- req.end();
-
- sinon.assert.notCalled(globalHttpAgent.addRequest);
- sinon.assert.notCalled(globalHttpsAgent.addRequest);
- sinon.assert.calledOnce(createConnection);
- }
- it('uses no agent (`host`)', function() {
- noAgent(false);
- });
- it('uses no agent (`hostname`)', function() {
- noAgent(true);
- });
- });
- });
- }
-
- function enabledBlock(conf, testParams) {
- before(function() {
- globalTunnel.initialize(conf);
- });
- after(function() {
- globalTunnel.end();
- });
-
- testParams = assign(
- {
- port: conf && conf.port,
- isHttpsProxy: conf && conf.protocol === 'https:',
- connect: (conf && conf.connect) || 'https'
- },
- testParams
- );
-
- proxyEnabledTests(testParams);
- }
-
- describe('with http proxy in intercept mode', function() {
- enabledBlock({
- connect: 'neither',
- protocol: 'http:',
- host: '10.2.3.4',
- port: 3333
- });
- });
-
- describe('with https proxy in intercept mode', function() {
- enabledBlock({
- connect: 'neither',
- protocol: 'https:',
- host: '10.2.3.4',
- port: 3334
- });
- });
-
- describe('with http proxy in CONNECT mode', function() {
- enabledBlock({
- connect: 'both',
- protocol: 'http:',
- host: '10.2.3.4',
- port: 3335
- });
- });
-
- describe('with https proxy in CONNECT mode', function() {
- enabledBlock({
- connect: 'both',
- protocol: 'https:',
- host: '10.2.3.4',
- port: 3336
- });
- });
-
- describe('with http proxy in mixed mode', function() {
- enabledBlock({
- protocol: 'http:',
- host: '10.2.3.4',
- port: 3337
- });
- });
-
- describe('with https proxy in mixed mode', function() {
- enabledBlock({
- protocol: 'https:',
- host: '10.2.3.4',
- port: 3338
- });
- });
-
- describe('using env var', function() {
- after(function() {
- delete process.env.http_proxy;
- assert.isUndefined(process.env.http_proxy);
- });
-
- describe('for http', function() {
- before(function() {
- process.env.http_proxy = 'http://10.2.3.4:1234'; // eslint-disable-line camelcase
- });
- enabledBlock(null, { isHttpsProxy: false, connect: 'https', port: 1234 });
- });
-
- describe('for https', function() {
- before(function() {
- process.env.http_proxy = 'https://10.2.3.4:1235'; // eslint-disable-line camelcase
- });
- enabledBlock(null, { isHttpsProxy: true, connect: 'https', port: 1235 });
- });
- });
-
- describe('using npm config', function() {
- var expectedProxy = { isHttpsProxy: false, connect: 'https', port: 1234 };
- var npmConfig = { get: function() {} };
- var npmConfigStub = sinon.stub(npmConfig, 'get');
-
- function configNpm(key, value) {
- return function() {
- global.__GLOBAL_TUNNEL_DEPENDENCY_NPMCONF__ = function() {
- return npmConfig;
- };
-
- npmConfigStub.withArgs(key).returns(value || 'http://10.2.3.4:1234');
- };
- }
-
- after(function() {
- global.__GLOBAL_TUNNEL_DEPENDENCY_NPMCONF__ = undefined;
- });
-
- describe('https-proxy', function() {
- before(configNpm('https-proxy'));
- enabledBlock(null, expectedProxy);
- });
-
- describe('http-proxy', function() {
- before(configNpm('http-proxy'));
- enabledBlock(null, expectedProxy);
- });
-
- describe('proxy', function() {
- before(configNpm('proxy'));
- enabledBlock(null, expectedProxy);
- });
- describe('order', function() {
- before(function() {
- configNpm('proxy')();
- configNpm('https-proxy', 'http://10.2.3.4:12345')();
- configNpm('http-proxy')();
- });
-
- enabledBlock(null, { isHttpsProxy: false, connect: 'https', port: 12345 });
- });
-
- describe('also using env var', function() {
- before(function() {
- configNpm('proxy')();
- process.env.http_proxy = 'http://10.2.3.4:1234'; // eslint-disable-line camelcase
- });
-
- after(function() {
- delete process.env.http_proxy;
- });
-
- enabledBlock(null, expectedProxy);
- });
- });
-
- // Deliberately after the block above
- describe('with proxy disabled', function() {
- it('claims to be disabled', function() {
- assert.isFalse(globalTunnel.isProxying);
- });
-
- it('will NOT proxy http requests', function(done) {
- var dummyCb = sinon.stub();
- request.get('http://example.dev/', dummyCb);
- setImmediate(function() {
- sinon.assert.calledOnce(globalHttpAgent.addRequest);
- sinon.assert.notCalled(globalHttpsAgent.addRequest);
- done();
- });
- });
-
- it('will NOT proxy https requests', function(done) {
- var dummyCb = sinon.stub();
- request.get('https://example.dev/', dummyCb);
- setImmediate(function() {
- sinon.assert.notCalled(globalHttpAgent.addRequest);
- sinon.assert.calledOnce(globalHttpsAgent.addRequest);
- done();
- });
- });
- });
- });
|