mirror of
https://github.com/axios/axios.git
synced 2026-04-11 02:11:50 +08:00
feat: added https data protection via package
This commit is contained in:
parent
4d8931ca8a
commit
7320936afa
@ -8,6 +8,7 @@ import https from 'https';
|
||||
import http2 from 'http2';
|
||||
import util from 'util';
|
||||
import followRedirects from 'follow-redirects';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import zlib from 'zlib';
|
||||
import { VERSION } from '../env/data.js';
|
||||
import transitionalDefaults from '../defaults/transitional.js';
|
||||
@ -187,7 +188,7 @@ function dispatchBeforeRedirect(options, responseDetails) {
|
||||
*
|
||||
* @returns {http.ClientRequestArgs}
|
||||
*/
|
||||
function setProxy(options, configProxy, location) {
|
||||
function setProxy(options, configProxy, location, agentOptions = {}) {
|
||||
let proxy = configProxy;
|
||||
if (!proxy && proxy !== false) {
|
||||
const proxyUrl = getProxyForUrl(location);
|
||||
@ -210,28 +211,65 @@ function setProxy(options, configProxy, location) {
|
||||
} else if (typeof proxy.auth === 'object') {
|
||||
throw new AxiosError('Invalid proxy authorization', AxiosError.ERR_BAD_OPTION, { proxy });
|
||||
}
|
||||
|
||||
const base64 = Buffer.from(proxy.auth, 'utf8').toString('base64');
|
||||
|
||||
options.headers['Proxy-Authorization'] = 'Basic ' + base64;
|
||||
}
|
||||
|
||||
options.headers.host = options.hostname + (options.port ? ':' + options.port : '');
|
||||
const targetIsHttps = isHttps.test(options.protocol);
|
||||
const proxyProtocol = proxy.protocol
|
||||
? proxy.protocol.includes(':')
|
||||
? proxy.protocol
|
||||
: `${proxy.protocol}:`
|
||||
: 'http:';
|
||||
const proxyHost = proxy.hostname || proxy.host;
|
||||
options.hostname = proxyHost;
|
||||
// Replace 'host' since options is not a URL object
|
||||
options.host = proxyHost;
|
||||
options.port = proxy.port;
|
||||
options.path = location;
|
||||
if (proxy.protocol) {
|
||||
options.protocol = proxy.protocol.includes(':') ? proxy.protocol : `${proxy.protocol}:`;
|
||||
|
||||
if (targetIsHttps && proxyProtocol === 'http:') {
|
||||
// HTTPS target over an HTTP proxy must use CONNECT tunneling.
|
||||
if (proxy.auth) {
|
||||
const [username, ...passwordParts] = String(proxy.auth).split(':');
|
||||
const password = passwordParts.join(':');
|
||||
const proxyUrl = `http://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${proxyHost}:${proxy.port}`;
|
||||
options.agent = new HttpsProxyAgent(proxyUrl, agentOptions);
|
||||
} else {
|
||||
options.agent = new HttpsProxyAgent(`http://${proxyHost}:${proxy.port}`, agentOptions);
|
||||
}
|
||||
|
||||
// The tunnel agent handles proxy auth during CONNECT. Do not forward it to origin.
|
||||
delete options.headers['Proxy-Authorization'];
|
||||
delete options.headers['proxy-authorization'];
|
||||
|
||||
if (options.agents) {
|
||||
options.agents.https = options.agent;
|
||||
}
|
||||
|
||||
// Preserve TLS settings from config.httpsAgent for the tunneled HTTPS request.
|
||||
['rejectUnauthorized', 'ca', 'cert', 'key', 'passphrase', 'pfx', 'servername'].forEach(
|
||||
(key) => {
|
||||
if (!utils.isUndefined(agentOptions[key])) {
|
||||
options[key] = agentOptions[key];
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if (proxy.auth) {
|
||||
const base64 = Buffer.from(proxy.auth, 'utf8').toString('base64');
|
||||
options.headers['Proxy-Authorization'] = 'Basic ' + base64;
|
||||
}
|
||||
|
||||
options.headers.host = options.hostname + (options.port ? ':' + options.port : '');
|
||||
options.hostname = proxyHost;
|
||||
// Replace 'host' since options is not a URL object
|
||||
options.host = proxyHost;
|
||||
options.port = proxy.port;
|
||||
options.path = location;
|
||||
if (proxy.protocol) {
|
||||
options.protocol = proxy.protocol.includes(':') ? proxy.protocol : `${proxy.protocol}:`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options.beforeRedirects.proxy = 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);
|
||||
setProxy(redirectOptions, configProxy, redirectOptions.href, agentOptions);
|
||||
};
|
||||
}
|
||||
|
||||
@ -658,6 +696,9 @@ export default isHttpAdapterSupported &&
|
||||
if (config.socketPath) {
|
||||
options.socketPath = config.socketPath;
|
||||
} else {
|
||||
const httpsAgentOptions =
|
||||
config.httpsAgent instanceof https.Agent ? config.httpsAgent.options || {} : {};
|
||||
|
||||
options.hostname = parsed.hostname.startsWith('[')
|
||||
? parsed.hostname.slice(1, -1)
|
||||
: parsed.hostname;
|
||||
@ -665,13 +706,14 @@ export default isHttpAdapterSupported &&
|
||||
setProxy(
|
||||
options,
|
||||
config.proxy,
|
||||
protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path
|
||||
protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path,
|
||||
httpsAgentOptions
|
||||
);
|
||||
}
|
||||
|
||||
let transport;
|
||||
const isHttpsRequest = isHttps.test(options.protocol);
|
||||
options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
|
||||
options.agent = options.agent || (isHttpsRequest ? config.httpsAgent : config.httpAgent);
|
||||
|
||||
if (isHttp2) {
|
||||
transport = http2Transport;
|
||||
|
||||
37
package-lock.json
generated
37
package-lock.json
generated
@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"https-proxy-agent": "^8.0.0",
|
||||
"proxy-from-env": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -2373,6 +2374,20 @@
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/agent/node_modules/https-proxy-agent": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.2",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/agent/node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
@ -5685,7 +5700,6 @@
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@ -7999,19 +8013,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||
"dev": true,
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-8.0.0.tgz",
|
||||
"integrity": "sha512-YYeW+iCnAS3xhvj2dvVoWgsbca3RfQy/IlaNHHOtDmU0jMqPI9euIq3Y9BJETdxk16h9NHHCKqp/KB9nIMStCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.2",
|
||||
"debug": "4"
|
||||
"agent-base": "8.0.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent/node_modules/agent-base": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-8.0.0.tgz",
|
||||
"integrity": "sha512-QT8i0hCz6C/KQ+KTAbSNwCHDGdmUJl2tp2ZpNlGSWCfhUNVbYG2WLE3MdZGBAgXPV4GAvjGMxo+C1hroyxmZEg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
|
||||
@ -10066,7 +10088,6 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/multer": {
|
||||
|
||||
@ -151,6 +151,7 @@
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"https-proxy-agent": "^8.0.0",
|
||||
"proxy-from-env": "^2.1.0"
|
||||
},
|
||||
"contributors": [
|
||||
|
||||
@ -16,15 +16,9 @@ import util from 'util';
|
||||
import NodeFormData from 'form-data';
|
||||
|
||||
const SERVER_PORT = 8010;
|
||||
const LOCAL_SERVER_URL = `http://localhost:${SERVER_PORT}`;
|
||||
|
||||
const pipelineAsync = util.promisify(stream.pipeline);
|
||||
|
||||
const fetchAxios = axios.create({
|
||||
baseURL: LOCAL_SERVER_URL,
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () => {
|
||||
describe('responses', () => {
|
||||
it('should support text response type', async () => {
|
||||
@ -35,7 +29,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
});
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'text',
|
||||
});
|
||||
|
||||
@ -53,7 +51,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
});
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
|
||||
@ -74,7 +76,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
});
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
|
||||
@ -92,7 +98,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
});
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'stream',
|
||||
});
|
||||
|
||||
@ -123,7 +133,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
);
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'formdata',
|
||||
});
|
||||
|
||||
@ -146,7 +160,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
});
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
@ -188,7 +206,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
|
||||
const samples = [];
|
||||
|
||||
const { data } = await fetchAxios.post(
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.post(
|
||||
`http://localhost:${server.address().port}/`,
|
||||
readable,
|
||||
{
|
||||
@ -241,7 +263,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
const server = await startHTTPServer((req, res) => res.end('OK'), { port: SERVER_PORT });
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
onUploadProgress() {},
|
||||
});
|
||||
|
||||
@ -284,7 +310,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
|
||||
const samples = [];
|
||||
|
||||
const { data } = await fetchAxios.post(
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.post(
|
||||
`http://localhost:${server.address().port}/`,
|
||||
readable,
|
||||
{
|
||||
@ -359,7 +389,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
const server = await startHTTPServer(async (req, res) => res.end('OK'), { port: SERVER_PORT });
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.post(
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.post(
|
||||
`http://localhost:${server.address().port}/`,
|
||||
stream.Readable.from('OK')
|
||||
);
|
||||
@ -388,14 +422,14 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
}, 500);
|
||||
|
||||
await assert.rejects(async () => {
|
||||
await fetchAxios.post(
|
||||
`http://localhost:${server.address().port}/`,
|
||||
makeReadableStream(),
|
||||
{
|
||||
responseType: 'stream',
|
||||
signal: controller.signal,
|
||||
}
|
||||
);
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
await instance.post(`http://localhost:${server.address().port}/`, makeReadableStream(), {
|
||||
responseType: 'stream',
|
||||
signal: controller.signal,
|
||||
});
|
||||
}, /CanceledError/);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
@ -419,7 +453,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
controller.abort(new Error('test'));
|
||||
}, 800);
|
||||
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'stream',
|
||||
signal: controller.signal,
|
||||
});
|
||||
@ -448,7 +486,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
const ts = Date.now();
|
||||
|
||||
await assert.rejects(async () => {
|
||||
await fetchAxios(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
timeout,
|
||||
});
|
||||
}, /timeout/);
|
||||
@ -464,9 +506,14 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
it('should combine baseURL and url', async () => {
|
||||
const server = await startHTTPServer(async (req, res) => res.end('OK'), { port: SERVER_PORT });
|
||||
try {
|
||||
const res = await fetchAxios('/foo');
|
||||
const instance = axios.create({
|
||||
baseURL: `http://localhost:${server.address().port}`,
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
assert.equal(res.config.baseURL, LOCAL_SERVER_URL);
|
||||
const res = await instance.get(`/foo`);
|
||||
|
||||
assert.equal(res.config.baseURL, `http://localhost:${server.address().port}`);
|
||||
assert.equal(res.config.url, '/foo');
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
@ -476,7 +523,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
it('should support params', async () => {
|
||||
const server = await startHTTPServer((req, res) => res.end(req.url), { port: SERVER_PORT });
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/?test=1`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/?test=1`, {
|
||||
params: {
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
@ -491,7 +542,12 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
|
||||
it('should handle fetch failed error as an AxiosError with ERR_NETWORK code', async () => {
|
||||
try {
|
||||
await fetchAxios('http://notExistsUrl.in.nowhere');
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
await instance.get('http://notExistsUrl.in.nowhere');
|
||||
|
||||
assert.fail('should fail');
|
||||
} catch (err) {
|
||||
assert.strictEqual(String(err), 'AxiosError: Network Error');
|
||||
@ -509,7 +565,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
);
|
||||
|
||||
try {
|
||||
const { headers } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { headers } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
responseType: 'stream',
|
||||
});
|
||||
|
||||
@ -534,7 +594,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
);
|
||||
|
||||
try {
|
||||
await fetchAxios.post(`http://localhost:${server.address().port}/form`, form);
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
await instance.post(`http://localhost:${server.address().port}/form`, form);
|
||||
} finally {
|
||||
await stopHTTPServer(server);
|
||||
}
|
||||
@ -543,7 +607,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
|
||||
describe('env config', () => {
|
||||
it('should respect env fetch API configuration', async () => {
|
||||
const { data, headers } = await fetchAxios.get('/', {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data, headers } = await instance.get(`http://localhost:${SERVER_PORT}/`, {
|
||||
env: {
|
||||
fetch() {
|
||||
return {
|
||||
@ -565,7 +633,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
|
||||
form.append('x', '1');
|
||||
|
||||
const { data, headers } = await fetchAxios.post('/', form, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data, headers } = await instance.post('/', form, {
|
||||
onUploadProgress() {
|
||||
// dummy listener to activate streaming
|
||||
},
|
||||
@ -587,7 +659,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
});
|
||||
|
||||
it('should be able to handle response with lack of Response object', async () => {
|
||||
const { data, headers } = await fetchAxios.get('/', {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data, headers } = await instance.get('/', {
|
||||
onDownloadProgress() {
|
||||
// dummy listener to activate streaming
|
||||
},
|
||||
@ -613,7 +689,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
const server = await startHTTPServer((req, res) => res.end('OK'), { port: SERVER_PORT });
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
env: {
|
||||
fetch: undefined,
|
||||
},
|
||||
@ -640,7 +720,11 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
|
||||
const server = await startHTTPServer((req, res) => res.end('OK'), { port: SERVER_PORT });
|
||||
|
||||
try {
|
||||
const { data } = await fetchAxios.get(`http://localhost:${server.address().port}/`, {
|
||||
const instance = axios.create({
|
||||
adapter: 'fetch',
|
||||
});
|
||||
|
||||
const { data } = await instance.get(`http://localhost:${server.address().port}/`, {
|
||||
env: {
|
||||
fetch: undefined,
|
||||
},
|
||||
|
||||
@ -1253,6 +1253,10 @@ describe('supports http with nodejs', () => {
|
||||
|
||||
const closeServer = (server) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (typeof server.closeAllConnections === 'function') {
|
||||
server.closeAllConnections();
|
||||
}
|
||||
|
||||
server.close((error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
@ -1335,6 +1339,223 @@ describe('supports http with nodejs', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should use CONNECT tunnel for HTTPS target via HTTP proxy', async () => {
|
||||
const tlsOptions = {
|
||||
key: fs.readFileSync(path.join(adaptersTestsDir, 'key.pem')),
|
||||
cert: fs.readFileSync(path.join(adaptersTestsDir, 'cert.pem')),
|
||||
};
|
||||
|
||||
const targetServer = await new Promise((resolve, reject) => {
|
||||
const server = https
|
||||
.createServer(tlsOptions, (req, res) => {
|
||||
res.end('secure-data');
|
||||
})
|
||||
.listen(0, () => resolve(server));
|
||||
|
||||
server.on('error', reject);
|
||||
});
|
||||
|
||||
let connectSeen = false;
|
||||
let plaintextForwardSeen = false;
|
||||
|
||||
const proxyServer = await new Promise((resolve, reject) => {
|
||||
const server = http
|
||||
.createServer((request, response) => {
|
||||
plaintextForwardSeen = true;
|
||||
response.statusCode = 500;
|
||||
response.end('unexpected plaintext proxying');
|
||||
})
|
||||
.on('connect', (req, clientSocket, head) => {
|
||||
connectSeen = true;
|
||||
const serverSocket = net.connect(targetServer.address().port, 'localhost', () => {
|
||||
clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
||||
if (head && head.length) {
|
||||
serverSocket.write(head);
|
||||
}
|
||||
serverSocket.pipe(clientSocket);
|
||||
clientSocket.pipe(serverSocket);
|
||||
});
|
||||
})
|
||||
.listen(0, () => resolve(server));
|
||||
|
||||
server.on('error', reject);
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await axios.get(`https://localhost:${targetServer.address().port}/`, {
|
||||
proxy: {
|
||||
host: 'localhost',
|
||||
port: proxyServer.address().port,
|
||||
protocol: 'http:',
|
||||
},
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
});
|
||||
|
||||
assert.strictEqual(connectSeen, true, 'proxy should receive CONNECT request');
|
||||
assert.strictEqual(
|
||||
plaintextForwardSeen,
|
||||
false,
|
||||
'proxy should not receive plaintext HTTPS request data'
|
||||
);
|
||||
assert.strictEqual(String(response.data), 'secure-data');
|
||||
} finally {
|
||||
await Promise.all([stopHTTPServer(targetServer, 200), stopHTTPServer(proxyServer, 200)]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should include Proxy-Authorization in CONNECT for authenticated HTTP proxy', async () => {
|
||||
const tlsOptions = {
|
||||
key: fs.readFileSync(path.join(adaptersTestsDir, 'key.pem')),
|
||||
cert: fs.readFileSync(path.join(adaptersTestsDir, 'cert.pem')),
|
||||
};
|
||||
|
||||
const targetServer = await new Promise((resolve, reject) => {
|
||||
const server = https
|
||||
.createServer(tlsOptions, (req, res) => {
|
||||
res.end('ok');
|
||||
})
|
||||
.listen(0, () => resolve(server));
|
||||
|
||||
server.on('error', reject);
|
||||
});
|
||||
|
||||
let proxyAuthorization;
|
||||
|
||||
const proxyServer = await new Promise((resolve, reject) => {
|
||||
const server = http
|
||||
.createServer()
|
||||
.on('connect', (req, clientSocket, head) => {
|
||||
proxyAuthorization = req.headers['proxy-authorization'];
|
||||
const serverSocket = net.connect(targetServer.address().port, 'localhost', () => {
|
||||
clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
||||
if (head && head.length) {
|
||||
serverSocket.write(head);
|
||||
}
|
||||
serverSocket.pipe(clientSocket);
|
||||
clientSocket.pipe(serverSocket);
|
||||
});
|
||||
})
|
||||
.listen(0, () => resolve(server));
|
||||
|
||||
server.on('error', reject);
|
||||
});
|
||||
|
||||
try {
|
||||
await axios.get(`https://localhost:${targetServer.address().port}/`, {
|
||||
proxy: {
|
||||
host: 'localhost',
|
||||
port: proxyServer.address().port,
|
||||
protocol: 'http:',
|
||||
auth: {
|
||||
username: 'user',
|
||||
password: 'secret',
|
||||
},
|
||||
},
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
});
|
||||
|
||||
assert.ok(proxyAuthorization, 'Proxy-Authorization should be set on CONNECT request');
|
||||
assert.strictEqual(
|
||||
proxyAuthorization,
|
||||
'Basic ' + Buffer.from('user:secret').toString('base64')
|
||||
);
|
||||
} finally {
|
||||
await Promise.all([stopHTTPServer(targetServer, 200), stopHTTPServer(proxyServer, 200)]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should use CONNECT tunnel for HTTPS redirect via HTTP proxy', async () => {
|
||||
const tlsOptions = {
|
||||
key: fs.readFileSync(path.join(adaptersTestsDir, 'key.pem')),
|
||||
cert: fs.readFileSync(path.join(adaptersTestsDir, 'cert.pem')),
|
||||
};
|
||||
|
||||
const httpsTargetServer = await new Promise((resolve, reject) => {
|
||||
const server = https
|
||||
.createServer(tlsOptions, (req, res) => {
|
||||
res.end('redirected-data');
|
||||
})
|
||||
.listen(0, () => resolve(server));
|
||||
|
||||
server.on('error', reject);
|
||||
});
|
||||
|
||||
const redirectServer = await startHTTPServer(
|
||||
(req, res) => {
|
||||
res.writeHead(302, { Location: `https://localhost:${httpsTargetServer.address().port}/` });
|
||||
res.end();
|
||||
},
|
||||
{ port: 0 }
|
||||
);
|
||||
|
||||
let connectCount = 0;
|
||||
|
||||
const proxyServer = await new Promise((resolve, reject) => {
|
||||
const server = http
|
||||
.createServer((request, response) => {
|
||||
const parsed = new URL(request.url);
|
||||
const opts = {
|
||||
host: parsed.hostname,
|
||||
port: parsed.port,
|
||||
path: `${parsed.pathname}${parsed.search}`,
|
||||
method: request.method,
|
||||
};
|
||||
|
||||
const proxyRequest = http.request(opts, (res) => {
|
||||
response.writeHead(res.statusCode || 500, res.headers);
|
||||
stream.pipeline(res, response, () => {});
|
||||
});
|
||||
|
||||
proxyRequest.on('error', () => {
|
||||
response.statusCode = 502;
|
||||
response.end();
|
||||
});
|
||||
|
||||
request.pipe(proxyRequest);
|
||||
})
|
||||
.on('connect', (req, clientSocket, head) => {
|
||||
connectCount += 1;
|
||||
const serverSocket = net.connect(httpsTargetServer.address().port, 'localhost', () => {
|
||||
clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
||||
if (head && head.length) {
|
||||
serverSocket.write(head);
|
||||
}
|
||||
serverSocket.pipe(clientSocket);
|
||||
clientSocket.pipe(serverSocket);
|
||||
});
|
||||
})
|
||||
.listen(0, () => resolve(server));
|
||||
|
||||
server.on('error', reject);
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await axios.get(`http://localhost:${redirectServer.address().port}/`, {
|
||||
proxy: {
|
||||
host: 'localhost',
|
||||
port: proxyServer.address().port,
|
||||
protocol: 'http:',
|
||||
},
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
});
|
||||
|
||||
assert.ok(connectCount >= 1, 'CONNECT should be used for HTTPS redirect');
|
||||
assert.strictEqual(String(response.data), 'redirected-data');
|
||||
} finally {
|
||||
await Promise.all([
|
||||
stopHTTPServer(redirectServer, 200),
|
||||
stopHTTPServer(httpsTargetServer, 200),
|
||||
stopHTTPServer(proxyServer, 200),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should not pass through disabled proxy', async () => {
|
||||
const originalHttpProxy = process.env.http_proxy;
|
||||
process.env.http_proxy = 'http://does-not-exists.example.com:4242/';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user