mirror of
https://github.com/axios/axios.git
synced 2026-04-11 14:21:59 +08:00
Merge 24c45391b6 into 71be4e5c80
This commit is contained in:
commit
c38e691c35
@ -19,6 +19,7 @@ var platform = require('../platform');
|
||||
var fromDataURI = require('../helpers/fromDataURI');
|
||||
var stream = require('stream');
|
||||
var estimateDataURLDecodedBytes = require('../helpers/estimateDataURLDecodedBytes.js');
|
||||
var shouldBypassProxy = require('../helpers/shouldBypassProxy');
|
||||
|
||||
var isHttps = /https:?/;
|
||||
|
||||
@ -46,9 +47,11 @@ function setProxy(options, configProxy, location) {
|
||||
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;
|
||||
if (!shouldBypassProxy(location)) {
|
||||
proxy = url.parse(proxyUrl);
|
||||
// replace 'host' since the proxy object is not a URL object
|
||||
proxy.host = proxy.hostname;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (proxy) {
|
||||
@ -273,7 +276,7 @@ module.exports = function httpAdapter(config) {
|
||||
} else {
|
||||
options.hostname = parsed.hostname;
|
||||
options.port = parsed.port;
|
||||
setProxy(options, config.proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path);
|
||||
setProxy(options, config.proxy, protocol + '//' + parsed.host + options.path);
|
||||
}
|
||||
|
||||
var transport;
|
||||
|
||||
@ -6,6 +6,7 @@ var isCancel = require('../cancel/isCancel');
|
||||
var defaults = require('../defaults');
|
||||
var CanceledError = require('../cancel/CanceledError');
|
||||
var normalizeHeaderName = require('../helpers/normalizeHeaderName');
|
||||
var sanitizeHeaderValue = require('../helpers/sanitizeHeaderValue');
|
||||
|
||||
/**
|
||||
* Throws a `CanceledError` if cancellation has been requested.
|
||||
@ -58,6 +59,10 @@ module.exports = function dispatchRequest(config) {
|
||||
}
|
||||
);
|
||||
|
||||
utils.forEach(config.headers, function sanitizeHeaderConfigValue(value, header) {
|
||||
config.headers[header] = sanitizeHeaderValue(value);
|
||||
});
|
||||
|
||||
var adapter = config.adapter || defaults.adapter;
|
||||
|
||||
return adapter(config).then(function onAdapterResolution(response) {
|
||||
|
||||
22
lib/helpers/sanitizeHeaderValue.js
Normal file
22
lib/helpers/sanitizeHeaderValue.js
Normal file
@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
var utils = require('../utils');
|
||||
|
||||
var INVALID_HEADER_VALUE_RE = /[^\x09\x20-\x7E\x80-\xFF]/g;
|
||||
var BOUNDARY_WHITESPACE_RE = /^[\x09\x20]|[\x09\x20]$/g;
|
||||
|
||||
function sanitizeHeaderValue(value) {
|
||||
if (value === false || value == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (utils.isArray(value)) {
|
||||
return value.map(sanitizeHeaderValue);
|
||||
}
|
||||
|
||||
return String(value)
|
||||
.replace(INVALID_HEADER_VALUE_RE, '')
|
||||
.replace(BOUNDARY_WHITESPACE_RE, '');
|
||||
}
|
||||
|
||||
module.exports = sanitizeHeaderValue;
|
||||
129
lib/helpers/shouldBypassProxy.js
Normal file
129
lib/helpers/shouldBypassProxy.js
Normal file
@ -0,0 +1,129 @@
|
||||
'use strict';
|
||||
|
||||
var URL = require('url').URL;
|
||||
|
||||
var DEFAULT_PORTS = {
|
||||
http: 80,
|
||||
https: 443,
|
||||
ws: 80,
|
||||
wss: 443,
|
||||
ftp: 21
|
||||
};
|
||||
|
||||
function parseNoProxyEntry(entry) {
|
||||
var entryHost = entry;
|
||||
var entryPort = 0;
|
||||
|
||||
if (entryHost.charAt(0) === '[') {
|
||||
var bracketIndex = entryHost.indexOf(']');
|
||||
|
||||
if (bracketIndex !== -1) {
|
||||
var host = entryHost.slice(1, bracketIndex);
|
||||
var rest = entryHost.slice(bracketIndex + 1);
|
||||
|
||||
if (rest.charAt(0) === ':' && /^\d+$/.test(rest.slice(1))) {
|
||||
entryPort = parseInt(rest.slice(1), 10);
|
||||
}
|
||||
|
||||
return [host, entryPort];
|
||||
}
|
||||
}
|
||||
|
||||
var firstColon = entryHost.indexOf(':');
|
||||
var lastColon = entryHost.lastIndexOf(':');
|
||||
|
||||
if (firstColon !== -1 && firstColon === lastColon && /^\d+$/.test(entryHost.slice(lastColon + 1))) {
|
||||
entryPort = parseInt(entryHost.slice(lastColon + 1), 10);
|
||||
entryHost = entryHost.slice(0, lastColon);
|
||||
}
|
||||
|
||||
return [entryHost, entryPort];
|
||||
}
|
||||
|
||||
function normalizeNoProxyHost(hostname) {
|
||||
if (!hostname) {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
if (hostname.charAt(0) === '[' && hostname.charAt(hostname.length - 1) === ']') {
|
||||
hostname = hostname.slice(1, -1);
|
||||
}
|
||||
|
||||
return hostname.replace(/\.+$/, '');
|
||||
}
|
||||
|
||||
function isLoopbackIPv4(hostname) {
|
||||
var octets = hostname.split('.');
|
||||
|
||||
if (octets.length !== 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (octets[0] !== '127') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return octets.every(function testOctet(octet) {
|
||||
return /^\d+$/.test(octet) && Number(octet) >= 0 && Number(octet) <= 255;
|
||||
});
|
||||
}
|
||||
|
||||
function isLoopbackHost(hostname) {
|
||||
return hostname === 'localhost' || hostname === '::1' || isLoopbackIPv4(hostname);
|
||||
}
|
||||
|
||||
module.exports = function shouldBypassProxy(location) {
|
||||
var parsed;
|
||||
|
||||
try {
|
||||
parsed = new URL(location);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var noProxy = (process.env.no_proxy || process.env.NO_PROXY || '').toLowerCase();
|
||||
|
||||
if (!noProxy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (noProxy === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
var protocol = parsed.protocol.split(':', 1)[0];
|
||||
var port = parseInt(parsed.port, 10) || DEFAULT_PORTS[protocol] || 0;
|
||||
var hostname = normalizeNoProxyHost(parsed.hostname.toLowerCase());
|
||||
|
||||
return noProxy.split(/[\s,]+/).some(function testNoProxyEntry(entry) {
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var entryParts = parseNoProxyEntry(entry);
|
||||
var entryHost = normalizeNoProxyHost(entryParts[0]);
|
||||
var entryPort = entryParts[1];
|
||||
|
||||
if (!entryHost) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entryPort && entryPort !== port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isLoopbackHost(hostname) && isLoopbackHost(entryHost)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entryHost.charAt(0) === '*') {
|
||||
entryHost = entryHost.slice(1);
|
||||
}
|
||||
|
||||
if (entryHost.charAt(0) === '.') {
|
||||
return hostname.slice(-entryHost.length) === entryHost;
|
||||
}
|
||||
|
||||
return hostname === entryHost;
|
||||
});
|
||||
};
|
||||
@ -112,4 +112,17 @@ describe('headers', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should sanitize headers containing invalid characters', function (done) {
|
||||
axios('/foo', {
|
||||
headers: {
|
||||
'x-test': ' ok\r\nInjected: yes\t'
|
||||
}
|
||||
});
|
||||
|
||||
getAjaxRequest().then(function (request) {
|
||||
testHeaderValue(request.requestHeaders, 'x-test', 'okInjected: yes');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -31,8 +31,39 @@ describe('supports http with nodejs', function () {
|
||||
proxy = null;
|
||||
}
|
||||
delete process.env.http_proxy;
|
||||
delete process.env.HTTP_PROXY;
|
||||
delete process.env.https_proxy;
|
||||
delete process.env.no_proxy;
|
||||
delete process.env.NO_PROXY;
|
||||
});
|
||||
|
||||
it('should sanitize request headers containing invalid characters', function (done) {
|
||||
server = http.createServer(function (req, res) {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end(req.headers['x-test']);
|
||||
}).listen(4444, function () {
|
||||
axios.get('http://localhost:4444/', {
|
||||
headers: {
|
||||
'x-test': ' ok\r\nInjected: yes\t'
|
||||
}
|
||||
}).then(function (response) {
|
||||
assert.equal(response.data, 'okInjected: yes');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve request error for unavailable host with invalid characters', function (done) {
|
||||
axios.get('http://localhost:1/', {
|
||||
headers: {
|
||||
'x-test': 'ok\r\nInjected: yes'
|
||||
}
|
||||
}).then(function () {
|
||||
done(new Error('request should not succeed'));
|
||||
}).catch(function (error) {
|
||||
assert.notEqual(error.message, 'Invalid character in header content ["x-test"]');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the timeout property is not parsable as a number', function (done) {
|
||||
@ -967,6 +998,98 @@ describe('supports http with nodejs', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use proxy for localhost with trailing dot when listed in no_proxy', function (done) {
|
||||
var proxyRequests = 0;
|
||||
|
||||
proxy = http.createServer(function (request, response) {
|
||||
proxyRequests += 1;
|
||||
response.end('proxied');
|
||||
}).listen(4000, function () {
|
||||
process.env.http_proxy = 'http://localhost:4000/';
|
||||
process.env.HTTP_PROXY = 'http://localhost:4000/';
|
||||
process.env.no_proxy = 'localhost,127.0.0.1,::1';
|
||||
process.env.NO_PROXY = 'localhost,127.0.0.1,::1';
|
||||
|
||||
axios.get('http://localhost.:1/', {
|
||||
timeout: 100
|
||||
}).then(function () {
|
||||
done(new Error('request should not succeed'));
|
||||
}).catch(function () {
|
||||
assert.equal(proxyRequests, 0, 'should not use proxy for localhost with trailing dot');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use proxy for bracketed IPv6 loopback when listed in no_proxy', function (done) {
|
||||
var proxyRequests = 0;
|
||||
|
||||
proxy = http.createServer(function (request, response) {
|
||||
proxyRequests += 1;
|
||||
response.end('proxied');
|
||||
}).listen(4000, function () {
|
||||
process.env.http_proxy = 'http://localhost:4000/';
|
||||
process.env.HTTP_PROXY = 'http://localhost:4000/';
|
||||
process.env.no_proxy = 'localhost,127.0.0.1,::1';
|
||||
process.env.NO_PROXY = 'localhost,127.0.0.1,::1';
|
||||
|
||||
axios.get('http://[::1]:1/', {
|
||||
timeout: 100
|
||||
}).then(function () {
|
||||
done(new Error('request should not succeed'));
|
||||
}).catch(function () {
|
||||
assert.equal(proxyRequests, 0, 'should not use proxy for IPv6 loopback');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use proxy for 127.0.0.1 when no_proxy is localhost', function (done) {
|
||||
var proxyRequests = 0;
|
||||
|
||||
proxy = http.createServer(function (request, response) {
|
||||
proxyRequests += 1;
|
||||
response.end('proxied');
|
||||
}).listen(4000, function () {
|
||||
process.env.http_proxy = 'http://localhost:4000/';
|
||||
process.env.HTTP_PROXY = 'http://localhost:4000/';
|
||||
process.env.no_proxy = 'localhost';
|
||||
process.env.NO_PROXY = 'localhost';
|
||||
|
||||
axios.get('http://127.0.0.1:1/', {
|
||||
timeout: 100
|
||||
}).then(function () {
|
||||
done(new Error('request should not succeed'));
|
||||
}).catch(function () {
|
||||
assert.equal(proxyRequests, 0, 'should not use proxy for IPv4 loopback alias');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use proxy for [::1] when no_proxy is localhost', function (done) {
|
||||
var proxyRequests = 0;
|
||||
|
||||
proxy = http.createServer(function (request, response) {
|
||||
proxyRequests += 1;
|
||||
response.end('proxied');
|
||||
}).listen(4000, function () {
|
||||
process.env.http_proxy = 'http://localhost:4000/';
|
||||
process.env.HTTP_PROXY = 'http://localhost:4000/';
|
||||
process.env.no_proxy = 'localhost';
|
||||
process.env.NO_PROXY = 'localhost';
|
||||
|
||||
axios.get('http://[::1]:1/', {
|
||||
timeout: 100
|
||||
}).then(function () {
|
||||
done(new Error('request should not succeed'));
|
||||
}).catch(function () {
|
||||
assert.equal(proxyRequests, 0, 'should not use proxy for IPv6 loopback alias');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should use proxy for domains not in no_proxy', function (done) {
|
||||
server = http.createServer(function (req, res) {
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
|
||||
|
||||
20
test/unit/helpers/sanitizeHeaderValue.js
Normal file
20
test/unit/helpers/sanitizeHeaderValue.js
Normal file
@ -0,0 +1,20 @@
|
||||
var assert = require('assert');
|
||||
var sanitizeHeaderValue = require('../../../lib/helpers/sanitizeHeaderValue');
|
||||
|
||||
describe('helpers::sanitizeHeaderValue', function () {
|
||||
it('should remove invalid header characters', function () {
|
||||
assert.strictEqual(sanitizeHeaderValue('ok\r\nInjected: yes'), 'okInjected: yes');
|
||||
assert.strictEqual(sanitizeHeaderValue('ok\x01bad'), 'okbad');
|
||||
});
|
||||
|
||||
it('should remove boundary whitespace', function () {
|
||||
assert.strictEqual(sanitizeHeaderValue(' value\t'), 'value');
|
||||
});
|
||||
|
||||
it('should sanitize array values recursively', function () {
|
||||
assert.deepStrictEqual(
|
||||
sanitizeHeaderValue([' safe=1 ', 'unsafe=1\nInjected: true']),
|
||||
['safe=1', 'unsafe=1Injected: true']
|
||||
);
|
||||
});
|
||||
});
|
||||
74
test/unit/helpers/shouldBypassProxy.js
Normal file
74
test/unit/helpers/shouldBypassProxy.js
Normal file
@ -0,0 +1,74 @@
|
||||
var assert = require('assert');
|
||||
var shouldBypassProxy = require('../../../lib/helpers/shouldBypassProxy');
|
||||
|
||||
var originalNoProxy = process.env.no_proxy;
|
||||
var originalNOProxy = process.env.NO_PROXY;
|
||||
|
||||
function setNoProxy(value) {
|
||||
process.env.no_proxy = value;
|
||||
process.env.NO_PROXY = value;
|
||||
}
|
||||
|
||||
describe('helpers::shouldBypassProxy', function () {
|
||||
afterEach(function () {
|
||||
if (originalNoProxy === undefined) {
|
||||
delete process.env.no_proxy;
|
||||
} else {
|
||||
process.env.no_proxy = originalNoProxy;
|
||||
}
|
||||
|
||||
if (originalNOProxy === undefined) {
|
||||
delete process.env.NO_PROXY;
|
||||
} else {
|
||||
process.env.NO_PROXY = originalNOProxy;
|
||||
}
|
||||
});
|
||||
|
||||
it('should bypass proxy for localhost with a trailing dot', function () {
|
||||
setNoProxy('localhost,127.0.0.1,::1');
|
||||
assert.strictEqual(shouldBypassProxy('http://localhost.:8080/'), true);
|
||||
});
|
||||
|
||||
it('should bypass proxy for bracketed ipv6 loopback', function () {
|
||||
setNoProxy('localhost,127.0.0.1,::1');
|
||||
assert.strictEqual(shouldBypassProxy('http://[::1]:8080/'), true);
|
||||
});
|
||||
|
||||
it('should support bracketed ipv6 entries in no_proxy', function () {
|
||||
setNoProxy('[::1]');
|
||||
assert.strictEqual(shouldBypassProxy('http://[::1]:8080/'), true);
|
||||
});
|
||||
|
||||
it('should match wildcard and explicit ports', function () {
|
||||
setNoProxy('*.example.com,localhost:8080');
|
||||
|
||||
assert.strictEqual(shouldBypassProxy('http://api.example.com/'), true);
|
||||
assert.strictEqual(shouldBypassProxy('http://localhost:8080/'), true);
|
||||
assert.strictEqual(shouldBypassProxy('http://localhost:8081/'), false);
|
||||
});
|
||||
|
||||
it('should treat localhost and loopback IP aliases as equivalent', function () {
|
||||
setNoProxy('localhost');
|
||||
|
||||
assert.strictEqual(shouldBypassProxy('http://127.0.0.1:8080/'), true);
|
||||
assert.strictEqual(shouldBypassProxy('http://[::1]:8080/'), true);
|
||||
|
||||
setNoProxy('127.0.0.1');
|
||||
|
||||
assert.strictEqual(shouldBypassProxy('http://localhost:8080/'), true);
|
||||
assert.strictEqual(shouldBypassProxy('http://[::1]:8080/'), true);
|
||||
|
||||
setNoProxy('::1');
|
||||
|
||||
assert.strictEqual(shouldBypassProxy('http://localhost:8080/'), true);
|
||||
assert.strictEqual(shouldBypassProxy('http://127.0.0.1:8080/'), true);
|
||||
});
|
||||
|
||||
it('should keep loopback alias matching port-aware', function () {
|
||||
setNoProxy('localhost:8080');
|
||||
|
||||
assert.strictEqual(shouldBypassProxy('http://127.0.0.1:8080/'), true);
|
||||
assert.strictEqual(shouldBypassProxy('http://[::1]:8080/'), true);
|
||||
assert.strictEqual(shouldBypassProxy('http://127.0.0.1:8081/'), false);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user