Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolving proxy from env on redirect #4436

120 changes: 44 additions & 76 deletions lib/adapters/http.js
Expand Up @@ -4,6 +4,7 @@ var utils = require('./../utils');
var settle = require('./../core/settle');
var buildFullPath = require('../core/buildFullPath');
var buildURL = require('./../helpers/buildURL');
var getProxyForUrl = require('proxy-from-env').getProxyForUrl;
var http = require('http');
var https = require('https');
var httpFollow = require('follow-redirects').http;
Expand All @@ -22,25 +23,46 @@ var supportedProtocols = [ 'http:', 'https:', 'file:' ];
/**
*
* @param {http.ClientRequestArgs} options
* @param {AxiosProxyConfig} proxy
* @param {AxiosProxyConfig} configProxy
* @param {string} location
*/
function setProxy(options, proxy, location) {
options.hostname = proxy.host;
options.host = proxy.host;
options.port = proxy.port;
options.path = location;

// Basic proxy authorization
if (proxy.auth) {
var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');
options.headers['Proxy-Authorization'] = 'Basic ' + base64;
function setProxy(options, configProxy, location) {
var proxy = configProxy;
if (!proxy && proxy !== false) {
var proxyUrl = getProxyForUrl(location);
if (proxyUrl) {
proxy = url.parse(proxyUrl);
// replace 'host' since the proxy object is not a URL object
proxy.host = proxy.hostname;
jasonsaayman marked this conversation as resolved.
Show resolved Hide resolved
}
}
if (proxy) {
// Basic proxy authorization
if (proxy.auth) {
// Support proxy auth object form
if (proxy.auth.username || proxy.auth.password) {
proxy.auth = (proxy.auth.username || '') + ':' + (proxy.auth.password || '');
}
var base64 = Buffer
.from(proxy.auth, 'utf8')
.toString('base64');
options.headers['Proxy-Authorization'] = 'Basic ' + base64;
}

options.headers.host = options.hostname + (options.port ? ':' + options.port : '');
jasonsaayman marked this conversation as resolved.
Show resolved Hide resolved
options.hostname = proxy.host;
options.host = proxy.host;
options.port = proxy.port;
options.path = location;
if (proxy.protocol) {
options.protocol = proxy.protocol;
}
}

// If a proxy is used, any redirects must also pass through the proxy
options.beforeRedirect = function beforeRedirect(redirection) {
redirection.headers.host = redirection.host;
setProxy(redirection, proxy, redirection.href);
options.beforeRedirect = function beforeRedirect(redirectOptions) {
// Configure proxy for redirected request, passing the original config proxy to apply
// the exact same logic as if the redirected request was performed by axios directly.
setProxy(redirectOptions, configProxy, redirectOptions.href);
};
}

Expand Down Expand Up @@ -152,9 +174,6 @@ module.exports = function httpAdapter(config) {
delete headers[headerNames.authorization];
}

var isHttpsRequest = isHttps.test(protocol);
var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;

try {
buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, '');
} catch (err) {
Expand All @@ -169,85 +188,34 @@ module.exports = function httpAdapter(config) {
path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''),
method: config.method.toUpperCase(),
headers: headers,
agent: agent,
agents: { http: config.httpAgent, https: config.httpsAgent },
auth: auth
auth: auth,
protocol: protocol
};

if (config.socketPath) {
options.socketPath = config.socketPath;
} else {
options.hostname = parsed.hostname;
options.port = parsed.port;
}

var proxy = config.proxy;
if (!proxy && proxy !== false) {
var proxyEnv = protocol.slice(0, -1) + '_proxy';
var proxyUrl = process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()];
if (proxyUrl) {
var parsedProxyUrl = url.parse(proxyUrl);
var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY;
var shouldProxy = true;

if (noProxyEnv) {
var noProxy = noProxyEnv.split(',').map(function trim(s) {
return s.trim();
});

shouldProxy = !noProxy.some(function proxyMatch(proxyElement) {
if (!proxyElement) {
return false;
}
if (proxyElement === '*') {
return true;
}
if (proxyElement[0] === '.' &&
parsed.hostname.slice(parsed.hostname.length - proxyElement.length) === proxyElement) {
return true;
}

return parsed.hostname === proxyElement;
});
}

if (shouldProxy) {
proxy = {
host: parsedProxyUrl.hostname,
port: parsedProxyUrl.port,
protocol: parsedProxyUrl.protocol
};

if (parsedProxyUrl.auth) {
var proxyUrlAuth = parsedProxyUrl.auth.split(':');
proxy.auth = {
username: proxyUrlAuth[0],
password: proxyUrlAuth[1]
};
}
}
}
}

if (proxy) {
options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : '');
setProxy(options, proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path);
setProxy(options, config.proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path);
}

var transport;
var isHttpsProxy = isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true);
var isHttpsRequest = isHttps.test(options.protocol);
jasonsaayman marked this conversation as resolved.
Show resolved Hide resolved
options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
if (config.transport) {
transport = config.transport;
} else if (config.maxRedirects === 0) {
transport = isHttpsProxy ? https : http;
transport = isHttpsRequest ? https : http;
} else {
if (config.maxRedirects) {
options.maxRedirects = config.maxRedirects;
}
if (config.beforeRedirect) {
options.beforeRedirect = config.beforeRedirect;
}
transport = isHttpsProxy ? httpsFollow : httpFollow;
transport = isHttpsRequest ? httpsFollow : httpFollow;
}

if (config.maxBodyLength > -1) {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -84,7 +84,8 @@
"typings": "./index.d.ts",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0"
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
},
"bundlesize": [
{
Expand Down
52 changes: 51 additions & 1 deletion test/unit/adapters/http.js
Expand Up @@ -687,7 +687,7 @@ describe('supports http with nodejs', function () {
proxy: {
host: 'localhost',
port: 4000,
protocol: 'https'
protocol: 'https:'
jasonsaayman marked this conversation as resolved.
Show resolved Hide resolved
},
httpsAgent: new https.Agent({
rejectUnauthorized: false
Expand Down Expand Up @@ -798,6 +798,56 @@ describe('supports http with nodejs', function () {
});
});

it('should re-evaluate proxy on redirect when proxy set via env var', function (done) {
process.env.http_proxy = 'http://localhost:4000'
process.env.no_proxy = 'localhost:4000'

var proxyUseCount = 0;

server = http.createServer(function (req, res) {
res.setHeader('Location', 'http://localhost:4000/redirected');
res.statusCode = 302;
res.end();
}).listen(4444, function () {
proxy = http.createServer(function (request, response) {
var parsed = url.parse(request.url);
if (parsed.pathname === '/redirected') {
jasonsaayman marked this conversation as resolved.
Show resolved Hide resolved
response.statusCode = 200;
response.end();
return;
}

proxyUseCount += 1;

var opts = {
host: parsed.hostname,
port: parsed.port,
path: parsed.path,
protocol: parsed.protocol,
rejectUnauthorized: false
};

http.get(opts, function (res) {
var body = '';
res.on('data', function (data) {
body += data;
});
res.on('end', function () {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.setHeader('Location', res.headers.location);
response.end(body);
});
});
}).listen(4000, function () {
axios.get('http://localhost:4444/').then(function(res) {
assert.equal(res.status, 200);
assert.equal(proxyUseCount, 1);
done();
}).catch(done);
});
});
});

it('should not use proxy for domains in no_proxy', function (done) {
server = http.createServer(function (req, res) {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
Expand Down