axios 1.15.2 → 1.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -6
- package/README.md +396 -25
- package/dist/axios.js +1455 -1109
- package/dist/axios.js.map +1 -1
- package/dist/axios.min.js +3 -3
- package/dist/axios.min.js.map +1 -1
- package/dist/browser/axios.cjs +1569 -1174
- package/dist/browser/axios.cjs.map +1 -1
- package/dist/esm/axios.js +1569 -1173
- package/dist/esm/axios.js.map +1 -1
- package/dist/esm/axios.min.js +2 -2
- package/dist/esm/axios.min.js.map +1 -1
- package/dist/node/axios.cjs +1395 -915
- package/dist/node/axios.cjs.map +1 -1
- package/index.d.cts +25 -13
- package/index.d.ts +21 -4
- package/index.js +2 -0
- package/lib/adapters/adapters.js +4 -2
- package/lib/adapters/fetch.js +131 -11
- package/lib/adapters/http.js +298 -69
- package/lib/adapters/xhr.js +8 -3
- package/lib/core/Axios.js +7 -3
- package/lib/core/AxiosError.js +86 -1
- package/lib/core/AxiosHeaders.js +4 -33
- package/lib/core/dispatchRequest.js +19 -7
- package/lib/core/mergeConfig.js +6 -3
- package/lib/core/settle.js +7 -11
- package/lib/defaults/index.js +1 -1
- package/lib/env/data.js +1 -1
- package/lib/helpers/buildURL.js +1 -1
- package/lib/helpers/composeSignals.js +48 -47
- package/lib/helpers/cookies.js +14 -2
- package/lib/helpers/estimateDataURLDecodedBytes.js +28 -1
- package/lib/helpers/formDataToJSON.js +1 -1
- package/lib/helpers/formDataToStream.js +1 -1
- package/lib/helpers/fromDataURI.js +18 -5
- package/lib/helpers/parseProtocol.js +1 -1
- package/lib/helpers/progressEventReducer.js +3 -0
- package/lib/helpers/resolveConfig.js +33 -17
- package/lib/helpers/sanitizeHeaderValue.js +60 -0
- package/lib/helpers/shouldBypassProxy.js +26 -1
- package/lib/helpers/validator.js +1 -1
- package/lib/utils.js +35 -22
- package/package.json +19 -24
package/lib/adapters/http.js
CHANGED
|
@@ -3,6 +3,7 @@ import settle from '../core/settle.js';
|
|
|
3
3
|
import buildFullPath from '../core/buildFullPath.js';
|
|
4
4
|
import buildURL from '../helpers/buildURL.js';
|
|
5
5
|
import { getProxyForUrl } from 'proxy-from-env';
|
|
6
|
+
import HttpsProxyAgent from 'https-proxy-agent';
|
|
6
7
|
import http from 'http';
|
|
7
8
|
import https from 'https';
|
|
8
9
|
import http2 from 'http2';
|
|
@@ -25,6 +26,7 @@ import readBlob from '../helpers/readBlob.js';
|
|
|
25
26
|
import ZlibHeaderTransformStream from '../helpers/ZlibHeaderTransformStream.js';
|
|
26
27
|
import callbackify from '../helpers/callbackify.js';
|
|
27
28
|
import shouldBypassProxy from '../helpers/shouldBypassProxy.js';
|
|
29
|
+
import { toByteStringHeaderObject } from '../helpers/sanitizeHeaderValue.js';
|
|
28
30
|
import {
|
|
29
31
|
progressEventReducer,
|
|
30
32
|
progressEventDecorator,
|
|
@@ -47,16 +49,85 @@ const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
|
|
|
47
49
|
const { http: httpFollow, https: httpsFollow } = followRedirects;
|
|
48
50
|
|
|
49
51
|
const isHttps = /https:?/;
|
|
52
|
+
const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length'];
|
|
53
|
+
|
|
54
|
+
function setFormDataHeaders(headers, formHeaders, policy) {
|
|
55
|
+
if (policy !== 'content-only') {
|
|
56
|
+
headers.set(formHeaders);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
Object.entries(formHeaders).forEach(([key, val]) => {
|
|
61
|
+
if (FORM_DATA_CONTENT_HEADERS.includes(key.toLowerCase())) {
|
|
62
|
+
headers.set(key, val);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
50
66
|
|
|
51
67
|
// Symbols used to bind a single 'error' listener to a pooled socket and track
|
|
52
68
|
// the request currently owning that socket across keep-alive reuse (issue #10780).
|
|
53
69
|
const kAxiosSocketListener = Symbol('axios.http.socketListener');
|
|
54
70
|
const kAxiosCurrentReq = Symbol('axios.http.currentReq');
|
|
55
71
|
|
|
72
|
+
// Tags HttpsProxyAgent instances installed by setProxy() so the redirect path
|
|
73
|
+
// can strip them without clobbering a user-supplied agent that happens to be
|
|
74
|
+
// an HttpsProxyAgent.
|
|
75
|
+
const kAxiosInstalledTunnel = Symbol('axios.http.installedTunnel');
|
|
76
|
+
|
|
77
|
+
// Cache of CONNECT-tunneling agents keyed by proxy config so repeat requests
|
|
78
|
+
// through the same proxy reuse a single agent (and its socket pool). The
|
|
79
|
+
// keyspace is bounded by the set of distinct proxy configs the process uses,
|
|
80
|
+
// so unbounded growth is not a concern in practice.
|
|
81
|
+
const tunnelingAgentCache = new Map();
|
|
82
|
+
const tunnelingAgentCacheUser = new WeakMap();
|
|
83
|
+
|
|
84
|
+
function getTunnelingAgent(agentOptions, userHttpsAgent) {
|
|
85
|
+
const key =
|
|
86
|
+
agentOptions.protocol +
|
|
87
|
+
'//' +
|
|
88
|
+
agentOptions.hostname +
|
|
89
|
+
':' +
|
|
90
|
+
(agentOptions.port || '') +
|
|
91
|
+
'#' +
|
|
92
|
+
(agentOptions.auth || '');
|
|
93
|
+
const cache = userHttpsAgent
|
|
94
|
+
? (tunnelingAgentCacheUser.get(userHttpsAgent) ||
|
|
95
|
+
tunnelingAgentCacheUser.set(userHttpsAgent, new Map()).get(userHttpsAgent))
|
|
96
|
+
: tunnelingAgentCache;
|
|
97
|
+
let agent = cache.get(key);
|
|
98
|
+
if (agent) return agent;
|
|
99
|
+
// Forward the user's TLS options (custom CA, rejectUnauthorized, client cert,
|
|
100
|
+
// etc.) into the tunneling agent so they apply to the origin TLS upgrade
|
|
101
|
+
// performed after CONNECT. Our proxy fields take precedence on conflict.
|
|
102
|
+
const merged = userHttpsAgent && userHttpsAgent.options
|
|
103
|
+
? { ...userHttpsAgent.options, ...agentOptions }
|
|
104
|
+
: agentOptions;
|
|
105
|
+
agent = new HttpsProxyAgent(merged);
|
|
106
|
+
agent[kAxiosInstalledTunnel] = true;
|
|
107
|
+
cache.set(key, agent);
|
|
108
|
+
return agent;
|
|
109
|
+
}
|
|
110
|
+
|
|
56
111
|
const supportedProtocols = platform.protocols.map((protocol) => {
|
|
57
112
|
return protocol + ':';
|
|
58
113
|
});
|
|
59
114
|
|
|
115
|
+
// Node's WHATWG URL parser returns `username` and `password` percent-encoded.
|
|
116
|
+
// Decode before composing the `auth` option so credentials such as
|
|
117
|
+
// `my%40email.com:pass` are sent as `my@email.com:pass`. Falls back to the
|
|
118
|
+
// original value for malformed input so a bad encoding never throws.
|
|
119
|
+
const decodeURIComponentSafe = (value) => {
|
|
120
|
+
if (!utils.isString(value)) {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
return decodeURIComponent(value);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
60
131
|
const flushOnFinish = (stream, [throttled, flush]) => {
|
|
61
132
|
stream.on('end', flush).on('error', flush);
|
|
62
133
|
|
|
@@ -176,12 +247,12 @@ const http2Sessions = new Http2Sessions();
|
|
|
176
247
|
*
|
|
177
248
|
* @returns {Object<string, any>}
|
|
178
249
|
*/
|
|
179
|
-
function dispatchBeforeRedirect(options, responseDetails) {
|
|
250
|
+
function dispatchBeforeRedirect(options, responseDetails, requestDetails) {
|
|
180
251
|
if (options.beforeRedirects.proxy) {
|
|
181
252
|
options.beforeRedirects.proxy(options);
|
|
182
253
|
}
|
|
183
254
|
if (options.beforeRedirects.config) {
|
|
184
|
-
options.beforeRedirects.config(options, responseDetails);
|
|
255
|
+
options.beforeRedirects.config(options, responseDetails, requestDetails);
|
|
185
256
|
}
|
|
186
257
|
}
|
|
187
258
|
|
|
@@ -194,7 +265,7 @@ function dispatchBeforeRedirect(options, responseDetails) {
|
|
|
194
265
|
*
|
|
195
266
|
* @returns {http.ClientRequestArgs}
|
|
196
267
|
*/
|
|
197
|
-
function setProxy(options, configProxy, location) {
|
|
268
|
+
function setProxy(options, configProxy, location, isRedirect, configHttpsAgent) {
|
|
198
269
|
let proxy = configProxy;
|
|
199
270
|
if (!proxy && proxy !== false) {
|
|
200
271
|
const proxyUrl = getProxyForUrl(location);
|
|
@@ -204,43 +275,150 @@ function setProxy(options, configProxy, location) {
|
|
|
204
275
|
}
|
|
205
276
|
}
|
|
206
277
|
}
|
|
278
|
+
// On redirect re-invocation, strip any stale Proxy-Authorization header carried
|
|
279
|
+
// over from the prior request (e.g. new target no longer uses a proxy, or uses
|
|
280
|
+
// a different proxy). Skip on the initial request so user-supplied headers are
|
|
281
|
+
// preserved. Header names are case-insensitive, so remove every case variant.
|
|
282
|
+
if (isRedirect && options.headers) {
|
|
283
|
+
for (const name of Object.keys(options.headers)) {
|
|
284
|
+
if (name.toLowerCase() === 'proxy-authorization') {
|
|
285
|
+
delete options.headers[name];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Strip any tunneling agent we installed for the previous hop so a redirect
|
|
290
|
+
// that drops the proxy or crosses an HTTPS↔HTTP boundary doesn't reuse a
|
|
291
|
+
// stale one. Match on our Symbol marker so a user-supplied HttpsProxyAgent
|
|
292
|
+
// (which won't carry the marker) is left alone.
|
|
293
|
+
if (isRedirect && options.agent && options.agent[kAxiosInstalledTunnel]) {
|
|
294
|
+
options.agent = undefined;
|
|
295
|
+
}
|
|
207
296
|
if (proxy) {
|
|
297
|
+
// Read proxy fields without traversing the prototype chain. URL instances expose
|
|
298
|
+
// username/password/hostname/host/port/protocol via getters on URL.prototype (so
|
|
299
|
+
// direct reads are shielded), but plain object proxies — and the `auth` field
|
|
300
|
+
// (which URL does not expose) — must be guarded so a polluted Object.prototype
|
|
301
|
+
// (e.g. Object.prototype.auth = { username, password }) cannot inject
|
|
302
|
+
// attacker-controlled credentials into the Proxy-Authorization header or
|
|
303
|
+
// redirect proxying to an attacker-controlled host.
|
|
304
|
+
const isProxyURL = proxy instanceof URL;
|
|
305
|
+
const readProxyField = (key) =>
|
|
306
|
+
isProxyURL || utils.hasOwnProp(proxy, key) ? proxy[key] : undefined;
|
|
307
|
+
|
|
308
|
+
const proxyUsername = readProxyField('username');
|
|
309
|
+
const proxyPassword = readProxyField('password');
|
|
310
|
+
let proxyAuth = utils.hasOwnProp(proxy, 'auth') ? proxy.auth : undefined;
|
|
311
|
+
|
|
208
312
|
// Basic proxy authorization
|
|
209
|
-
if (
|
|
210
|
-
|
|
313
|
+
if (proxyUsername) {
|
|
314
|
+
proxyAuth = (proxyUsername || '') + ':' + (proxyPassword || '');
|
|
211
315
|
}
|
|
212
316
|
|
|
213
|
-
if (
|
|
214
|
-
// Support proxy auth object form
|
|
215
|
-
|
|
317
|
+
if (proxyAuth) {
|
|
318
|
+
// Support proxy auth object form. Read sub-fields via own-prop checks so a
|
|
319
|
+
// plain object inheriting from polluted Object.prototype cannot leak creds.
|
|
320
|
+
const authIsObject = typeof proxyAuth === 'object';
|
|
321
|
+
const authUsername =
|
|
322
|
+
authIsObject && utils.hasOwnProp(proxyAuth, 'username') ? proxyAuth.username : undefined;
|
|
323
|
+
const authPassword =
|
|
324
|
+
authIsObject && utils.hasOwnProp(proxyAuth, 'password') ? proxyAuth.password : undefined;
|
|
325
|
+
const validProxyAuth = Boolean(authUsername || authPassword);
|
|
216
326
|
|
|
217
327
|
if (validProxyAuth) {
|
|
218
|
-
|
|
219
|
-
} else if (
|
|
328
|
+
proxyAuth = (authUsername || '') + ':' + (authPassword || '');
|
|
329
|
+
} else if (authIsObject) {
|
|
220
330
|
throw new AxiosError('Invalid proxy authorization', AxiosError.ERR_BAD_OPTION, { proxy });
|
|
221
331
|
}
|
|
222
|
-
|
|
223
|
-
const base64 = Buffer.from(proxy.auth, 'utf8').toString('base64');
|
|
224
|
-
|
|
225
|
-
options.headers['Proxy-Authorization'] = 'Basic ' + base64;
|
|
226
332
|
}
|
|
227
333
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
options.
|
|
334
|
+
const targetIsHttps = isHttps.test(options.protocol);
|
|
335
|
+
|
|
336
|
+
if (targetIsHttps) {
|
|
337
|
+
// CONNECT-tunneling path for HTTPS targets. Preserves end-to-end TLS to
|
|
338
|
+
// the origin so the proxy cannot inspect the URL, headers, or body — the
|
|
339
|
+
// behavior already promised by THREATMODEL.md (T-R9). HttpsProxyAgent
|
|
340
|
+
// sends Proxy-Authorization on the CONNECT request only, never on the
|
|
341
|
+
// wrapped TLS request, which is why we don't stamp it onto
|
|
342
|
+
// options.headers here. If the user already supplied an HttpsProxyAgent,
|
|
343
|
+
// they own tunneling end-to-end and we leave them alone; otherwise we
|
|
344
|
+
// install our own tunneling agent and forward their TLS options (if any)
|
|
345
|
+
// so a custom httpsAgent for cert pinning / rejectUnauthorized still
|
|
346
|
+
// applies to the origin TLS upgrade.
|
|
347
|
+
if (!(configHttpsAgent instanceof HttpsProxyAgent)) {
|
|
348
|
+
const proxyHost = readProxyField('hostname') || readProxyField('host');
|
|
349
|
+
const proxyPort = readProxyField('port');
|
|
350
|
+
const rawProxyProtocol = readProxyField('protocol');
|
|
351
|
+
const normalizedProtocol = rawProxyProtocol
|
|
352
|
+
? rawProxyProtocol.includes(':')
|
|
353
|
+
? rawProxyProtocol
|
|
354
|
+
: `${rawProxyProtocol}:`
|
|
355
|
+
: 'http:';
|
|
356
|
+
// Bracket IPv6 literals for URL parsing; URL.hostname strips the
|
|
357
|
+
// brackets again on read so the agent receives the raw form.
|
|
358
|
+
const proxyHostForURL =
|
|
359
|
+
proxyHost && proxyHost.includes(':') && !proxyHost.startsWith('[')
|
|
360
|
+
? `[${proxyHost}]`
|
|
361
|
+
: proxyHost;
|
|
362
|
+
const proxyURL = new URL(
|
|
363
|
+
`${normalizedProtocol}//${proxyHostForURL}${proxyPort ? ':' + proxyPort : ''}`
|
|
364
|
+
);
|
|
365
|
+
const agentOptions = {
|
|
366
|
+
protocol: proxyURL.protocol,
|
|
367
|
+
hostname: proxyURL.hostname.replace(/^\[|\]$/g, ''),
|
|
368
|
+
port: proxyURL.port,
|
|
369
|
+
auth: proxyAuth && typeof proxyAuth === 'string' ? proxyAuth : undefined,
|
|
370
|
+
};
|
|
371
|
+
if (proxyURL.protocol === 'https:') {
|
|
372
|
+
agentOptions.ALPNProtocols = ['http/1.1'];
|
|
373
|
+
}
|
|
374
|
+
const tunnelingAgent = getTunnelingAgent(agentOptions, configHttpsAgent);
|
|
375
|
+
// Set both: `options.agent` is consumed by the native https.request path
|
|
376
|
+
// (config.maxRedirects === 0); `options.agents.https` is consumed by
|
|
377
|
+
// follow-redirects, which ignores `options.agent` when `options.agents`
|
|
378
|
+
// is present.
|
|
379
|
+
options.agent = tunnelingAgent;
|
|
380
|
+
if (options.agents) {
|
|
381
|
+
options.agents.https = tunnelingAgent;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
// Forward-proxy mode for plaintext HTTP targets. The request line carries
|
|
386
|
+
// the absolute URL and the proxy sees everything — acceptable for plain
|
|
387
|
+
// HTTP since the wire was already plaintext.
|
|
388
|
+
if (proxyAuth) {
|
|
389
|
+
const base64 = Buffer.from(proxyAuth, 'utf8').toString('base64');
|
|
390
|
+
options.headers['Proxy-Authorization'] = 'Basic ' + base64;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Preserve a user-supplied Host header (case-insensitive) so callers can override
|
|
394
|
+
// the value forwarded to the proxy; otherwise default to the request URL's host.
|
|
395
|
+
let hasUserHostHeader = false;
|
|
396
|
+
for (const name of Object.keys(options.headers)) {
|
|
397
|
+
if (name.toLowerCase() === 'host') {
|
|
398
|
+
hasUserHostHeader = true;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (!hasUserHostHeader) {
|
|
403
|
+
options.headers.host = options.hostname + (options.port ? ':' + options.port : '');
|
|
404
|
+
}
|
|
405
|
+
const proxyHost = readProxyField('hostname') || readProxyField('host');
|
|
406
|
+
options.hostname = proxyHost;
|
|
407
|
+
// Replace 'host' since options is not a URL object
|
|
408
|
+
options.host = proxyHost;
|
|
409
|
+
options.port = readProxyField('port');
|
|
410
|
+
options.path = location;
|
|
411
|
+
const proxyProtocol = readProxyField('protocol');
|
|
412
|
+
if (proxyProtocol) {
|
|
413
|
+
options.protocol = proxyProtocol.includes(':') ? proxyProtocol : `${proxyProtocol}:`;
|
|
414
|
+
}
|
|
237
415
|
}
|
|
238
416
|
}
|
|
239
417
|
|
|
240
418
|
options.beforeRedirects.proxy = function beforeRedirect(redirectOptions) {
|
|
241
419
|
// Configure proxy for redirected request, passing the original config proxy to apply
|
|
242
420
|
// the exact same logic as if the redirected request was performed by axios directly.
|
|
243
|
-
setProxy(redirectOptions, configProxy, redirectOptions.href);
|
|
421
|
+
setProxy(redirectOptions, configProxy, redirectOptions.href, true, configHttpsAgent);
|
|
244
422
|
};
|
|
245
423
|
}
|
|
246
424
|
|
|
@@ -352,6 +530,7 @@ export default isHttpAdapterSupported &&
|
|
|
352
530
|
let isDone;
|
|
353
531
|
let rejected = false;
|
|
354
532
|
let req;
|
|
533
|
+
let connectPhaseTimer;
|
|
355
534
|
|
|
356
535
|
httpVersion = +httpVersion;
|
|
357
536
|
|
|
@@ -396,9 +575,34 @@ export default isHttpAdapterSupported &&
|
|
|
396
575
|
}
|
|
397
576
|
}
|
|
398
577
|
|
|
578
|
+
function clearConnectPhaseTimer() {
|
|
579
|
+
if (connectPhaseTimer) {
|
|
580
|
+
clearTimeout(connectPhaseTimer);
|
|
581
|
+
connectPhaseTimer = null;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function createTimeoutError() {
|
|
586
|
+
let timeoutErrorMessage = config.timeout
|
|
587
|
+
? 'timeout of ' + config.timeout + 'ms exceeded'
|
|
588
|
+
: 'timeout exceeded';
|
|
589
|
+
const transitional = config.transitional || transitionalDefaults;
|
|
590
|
+
if (config.timeoutErrorMessage) {
|
|
591
|
+
timeoutErrorMessage = config.timeoutErrorMessage;
|
|
592
|
+
}
|
|
593
|
+
return new AxiosError(
|
|
594
|
+
timeoutErrorMessage,
|
|
595
|
+
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
|
|
596
|
+
config,
|
|
597
|
+
req
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
399
601
|
abortEmitter.once('abort', reject);
|
|
400
602
|
|
|
401
603
|
const onFinished = () => {
|
|
604
|
+
clearConnectPhaseTimer();
|
|
605
|
+
|
|
402
606
|
if (config.cancelToken) {
|
|
403
607
|
config.cancelToken.unsubscribe(abort);
|
|
404
608
|
}
|
|
@@ -419,6 +623,7 @@ export default isHttpAdapterSupported &&
|
|
|
419
623
|
|
|
420
624
|
onDone((response, isRejected) => {
|
|
421
625
|
isDone = true;
|
|
626
|
+
clearConnectPhaseTimer();
|
|
422
627
|
|
|
423
628
|
if (isRejected) {
|
|
424
629
|
rejected = true;
|
|
@@ -533,9 +738,12 @@ export default isHttpAdapterSupported &&
|
|
|
533
738
|
}
|
|
534
739
|
);
|
|
535
740
|
// support for https://www.npmjs.com/package/form-data api
|
|
536
|
-
} else if (
|
|
537
|
-
|
|
538
|
-
|
|
741
|
+
} else if (
|
|
742
|
+
utils.isFormData(data) &&
|
|
743
|
+
utils.isFunction(data.getHeaders) &&
|
|
744
|
+
data.getHeaders !== Object.prototype.getHeaders
|
|
745
|
+
) {
|
|
746
|
+
setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
|
|
539
747
|
|
|
540
748
|
if (!headers.hasContentLength()) {
|
|
541
749
|
try {
|
|
@@ -628,8 +836,8 @@ export default isHttpAdapterSupported &&
|
|
|
628
836
|
}
|
|
629
837
|
|
|
630
838
|
if (!auth && parsed.username) {
|
|
631
|
-
const urlUsername = parsed.username;
|
|
632
|
-
const urlPassword = parsed.password;
|
|
839
|
+
const urlUsername = decodeURIComponentSafe(parsed.username);
|
|
840
|
+
const urlPassword = decodeURIComponentSafe(parsed.password);
|
|
633
841
|
auth = urlUsername + ':' + urlPassword;
|
|
634
842
|
}
|
|
635
843
|
|
|
@@ -659,11 +867,10 @@ export default isHttpAdapterSupported &&
|
|
|
659
867
|
|
|
660
868
|
// Null-prototype to block prototype pollution gadgets on properties read
|
|
661
869
|
// directly by Node's http.request (e.g. insecureHTTPParser, lookup).
|
|
662
|
-
// See GHSA-q8qp-cvcw-x6jj.
|
|
663
870
|
const options = Object.assign(Object.create(null), {
|
|
664
871
|
path,
|
|
665
872
|
method: method,
|
|
666
|
-
headers: headers
|
|
873
|
+
headers: toByteStringHeaderObject(headers),
|
|
667
874
|
agents: { http: config.httpAgent, https: config.httpsAgent },
|
|
668
875
|
auth,
|
|
669
876
|
protocol,
|
|
@@ -678,11 +885,9 @@ export default isHttpAdapterSupported &&
|
|
|
678
885
|
|
|
679
886
|
if (config.socketPath) {
|
|
680
887
|
if (typeof config.socketPath !== 'string') {
|
|
681
|
-
return reject(
|
|
682
|
-
'socketPath must be a string',
|
|
683
|
-
|
|
684
|
-
config
|
|
685
|
-
));
|
|
888
|
+
return reject(
|
|
889
|
+
new AxiosError('socketPath must be a string', AxiosError.ERR_BAD_OPTION_VALUE, config)
|
|
890
|
+
);
|
|
686
891
|
}
|
|
687
892
|
|
|
688
893
|
if (config.allowedSocketPaths != null) {
|
|
@@ -696,11 +901,13 @@ export default isHttpAdapterSupported &&
|
|
|
696
901
|
);
|
|
697
902
|
|
|
698
903
|
if (!isAllowed) {
|
|
699
|
-
return reject(
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
904
|
+
return reject(
|
|
905
|
+
new AxiosError(
|
|
906
|
+
`socketPath "${config.socketPath}" is not permitted by allowedSocketPaths`,
|
|
907
|
+
AxiosError.ERR_BAD_OPTION_VALUE,
|
|
908
|
+
config
|
|
909
|
+
)
|
|
910
|
+
);
|
|
704
911
|
}
|
|
705
912
|
}
|
|
706
913
|
|
|
@@ -713,12 +920,19 @@ export default isHttpAdapterSupported &&
|
|
|
713
920
|
setProxy(
|
|
714
921
|
options,
|
|
715
922
|
config.proxy,
|
|
716
|
-
protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path
|
|
923
|
+
protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path,
|
|
924
|
+
false,
|
|
925
|
+
config.httpsAgent
|
|
717
926
|
);
|
|
718
927
|
}
|
|
719
928
|
let transport;
|
|
929
|
+
let isNativeTransport = false;
|
|
720
930
|
const isHttpsRequest = isHttps.test(options.protocol);
|
|
721
|
-
|
|
931
|
+
// Don't clobber a CONNECT-tunneling agent installed by setProxy() for an
|
|
932
|
+
// HTTPS target.
|
|
933
|
+
if (options.agent == null) {
|
|
934
|
+
options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
|
|
935
|
+
}
|
|
722
936
|
|
|
723
937
|
if (isHttp2) {
|
|
724
938
|
transport = http2Transport;
|
|
@@ -728,6 +942,7 @@ export default isHttpAdapterSupported &&
|
|
|
728
942
|
transport = configTransport;
|
|
729
943
|
} else if (config.maxRedirects === 0) {
|
|
730
944
|
transport = isHttpsRequest ? https : http;
|
|
945
|
+
isNativeTransport = true;
|
|
731
946
|
} else {
|
|
732
947
|
if (config.maxRedirects) {
|
|
733
948
|
options.maxRedirects = config.maxRedirects;
|
|
@@ -749,11 +964,13 @@ export default isHttpAdapterSupported &&
|
|
|
749
964
|
|
|
750
965
|
// Always set an explicit own value so a polluted
|
|
751
966
|
// Object.prototype.insecureHTTPParser cannot enable the lenient parser
|
|
752
|
-
// through Node's internal options copy
|
|
967
|
+
// through Node's internal options copy
|
|
753
968
|
options.insecureHTTPParser = Boolean(own('insecureHTTPParser'));
|
|
754
969
|
|
|
755
970
|
// Create the request
|
|
756
971
|
req = transport.request(options, function handleResponse(res) {
|
|
972
|
+
clearConnectPhaseTimer();
|
|
973
|
+
|
|
757
974
|
if (req.destroyed) return;
|
|
758
975
|
|
|
759
976
|
const streams = [res];
|
|
@@ -835,7 +1052,7 @@ export default isHttpAdapterSupported &&
|
|
|
835
1052
|
|
|
836
1053
|
if (responseType === 'stream') {
|
|
837
1054
|
// Enforce maxContentLength on streamed responses; previously this
|
|
838
|
-
// was applied only to buffered responses.
|
|
1055
|
+
// was applied only to buffered responses.
|
|
839
1056
|
if (config.maxContentLength > -1) {
|
|
840
1057
|
const limit = config.maxContentLength;
|
|
841
1058
|
const source = responseStream;
|
|
@@ -893,15 +1110,16 @@ export default isHttpAdapterSupported &&
|
|
|
893
1110
|
'stream has been aborted',
|
|
894
1111
|
AxiosError.ERR_BAD_RESPONSE,
|
|
895
1112
|
config,
|
|
896
|
-
lastRequest
|
|
1113
|
+
lastRequest,
|
|
1114
|
+
response
|
|
897
1115
|
);
|
|
898
1116
|
responseStream.destroy(err);
|
|
899
1117
|
reject(err);
|
|
900
1118
|
});
|
|
901
1119
|
|
|
902
1120
|
responseStream.on('error', function handleStreamError(err) {
|
|
903
|
-
if (
|
|
904
|
-
reject(AxiosError.from(err, null, config, lastRequest));
|
|
1121
|
+
if (rejected) return;
|
|
1122
|
+
reject(AxiosError.from(err, null, config, lastRequest, response));
|
|
905
1123
|
});
|
|
906
1124
|
|
|
907
1125
|
responseStream.on('end', function handleStreamEnd() {
|
|
@@ -944,6 +1162,16 @@ export default isHttpAdapterSupported &&
|
|
|
944
1162
|
});
|
|
945
1163
|
|
|
946
1164
|
// set tcp keep alive to prevent drop connection by peer
|
|
1165
|
+
// Track every socket bound to this outer RedirectableRequest so a single
|
|
1166
|
+
// 'close' listener can release ownership on all of them. follow-redirects
|
|
1167
|
+
// re-emits the 'socket' event for each hop's native request onto the same
|
|
1168
|
+
// outer request, so attaching per-request listeners inside this handler
|
|
1169
|
+
// would accumulate across hops and trigger MaxListenersExceededWarning at
|
|
1170
|
+
// >= 11 redirects. Clearing only the last-bound socket would leave stale
|
|
1171
|
+
// kAxiosCurrentReq refs on earlier hop sockets returned to the keep-alive
|
|
1172
|
+
// pool, causing an idle-pool 'error' to be attributed to a closed req.
|
|
1173
|
+
const boundSockets = new Set();
|
|
1174
|
+
|
|
947
1175
|
req.on('socket', function handleRequestSocket(socket) {
|
|
948
1176
|
// default interval of sending ack packet is 1 minute
|
|
949
1177
|
socket.setKeepAlive(true, 1000 * 60);
|
|
@@ -964,12 +1192,18 @@ export default isHttpAdapterSupported &&
|
|
|
964
1192
|
}
|
|
965
1193
|
|
|
966
1194
|
socket[kAxiosCurrentReq] = req;
|
|
1195
|
+
boundSockets.add(socket);
|
|
1196
|
+
});
|
|
967
1197
|
|
|
968
|
-
|
|
1198
|
+
req.once('close', function clearCurrentReq() {
|
|
1199
|
+
clearConnectPhaseTimer();
|
|
1200
|
+
|
|
1201
|
+
for (const socket of boundSockets) {
|
|
969
1202
|
if (socket[kAxiosCurrentReq] === req) {
|
|
970
1203
|
socket[kAxiosCurrentReq] = null;
|
|
971
1204
|
}
|
|
972
|
-
}
|
|
1205
|
+
}
|
|
1206
|
+
boundSockets.clear();
|
|
973
1207
|
});
|
|
974
1208
|
|
|
975
1209
|
// Handle request timeout
|
|
@@ -990,29 +1224,24 @@ export default isHttpAdapterSupported &&
|
|
|
990
1224
|
return;
|
|
991
1225
|
}
|
|
992
1226
|
|
|
1227
|
+
const handleTimeout = function handleTimeout() {
|
|
1228
|
+
if (isDone) return;
|
|
1229
|
+
abort(createTimeoutError());
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
if (isNativeTransport && timeout > 0) {
|
|
1233
|
+
// Native ClientRequest#setTimeout starts from the socket lifecycle and
|
|
1234
|
+
// may not fire while TCP connect is still pending. Mirror the
|
|
1235
|
+
// follow-redirects wall-clock timer for the maxRedirects === 0 path.
|
|
1236
|
+
connectPhaseTimer = setTimeout(handleTimeout, timeout);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
993
1239
|
// Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.
|
|
994
1240
|
// And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.
|
|
995
1241
|
// At this time, if we have a large number of request, nodejs will hang up some socket on background. and the number will up and up.
|
|
996
1242
|
// And then these socket which be hang up will devouring CPU little by little.
|
|
997
1243
|
// ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
|
|
998
|
-
req.setTimeout(timeout,
|
|
999
|
-
if (isDone) return;
|
|
1000
|
-
let timeoutErrorMessage = config.timeout
|
|
1001
|
-
? 'timeout of ' + config.timeout + 'ms exceeded'
|
|
1002
|
-
: 'timeout exceeded';
|
|
1003
|
-
const transitional = config.transitional || transitionalDefaults;
|
|
1004
|
-
if (config.timeoutErrorMessage) {
|
|
1005
|
-
timeoutErrorMessage = config.timeoutErrorMessage;
|
|
1006
|
-
}
|
|
1007
|
-
abort(
|
|
1008
|
-
new AxiosError(
|
|
1009
|
-
timeoutErrorMessage,
|
|
1010
|
-
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
|
|
1011
|
-
config,
|
|
1012
|
-
req
|
|
1013
|
-
)
|
|
1014
|
-
);
|
|
1015
|
-
});
|
|
1244
|
+
req.setTimeout(timeout, handleTimeout);
|
|
1016
1245
|
} else {
|
|
1017
1246
|
// explicitly reset the socket timeout value for a possible `keep-alive` request
|
|
1018
1247
|
req.setTimeout(0);
|
|
@@ -1040,7 +1269,7 @@ export default isHttpAdapterSupported &&
|
|
|
1040
1269
|
|
|
1041
1270
|
// Enforce maxBodyLength for streamed uploads on the native http/https
|
|
1042
1271
|
// transport (maxRedirects === 0); follow-redirects enforces it on the
|
|
1043
|
-
// other path.
|
|
1272
|
+
// other path.
|
|
1044
1273
|
let uploadStream = data;
|
|
1045
1274
|
if (config.maxBodyLength > -1 && config.maxRedirects === 0) {
|
|
1046
1275
|
const limit = config.maxBodyLength;
|
package/lib/adapters/xhr.js
CHANGED
|
@@ -8,6 +8,7 @@ import platform from '../platform/index.js';
|
|
|
8
8
|
import AxiosHeaders from '../core/AxiosHeaders.js';
|
|
9
9
|
import { progressEventReducer } from '../helpers/progressEventReducer.js';
|
|
10
10
|
import resolveConfig from '../helpers/resolveConfig.js';
|
|
11
|
+
import { toByteStringHeaderObject } from '../helpers/sanitizeHeaderValue.js';
|
|
11
12
|
|
|
12
13
|
const isXHRAdapterSupported = typeof XMLHttpRequest !== 'undefined';
|
|
13
14
|
|
|
@@ -91,7 +92,7 @@ export default isXHRAdapterSupported &&
|
|
|
91
92
|
// will return status as 0 even though it's a successful request
|
|
92
93
|
if (
|
|
93
94
|
request.status === 0 &&
|
|
94
|
-
!(request.responseURL && request.responseURL.
|
|
95
|
+
!(request.responseURL && request.responseURL.startsWith('file:'))
|
|
95
96
|
) {
|
|
96
97
|
return;
|
|
97
98
|
}
|
|
@@ -108,6 +109,7 @@ export default isXHRAdapterSupported &&
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
|
|
112
|
+
done();
|
|
111
113
|
|
|
112
114
|
// Clean up request
|
|
113
115
|
request = null;
|
|
@@ -123,6 +125,7 @@ export default isXHRAdapterSupported &&
|
|
|
123
125
|
// attach the underlying event for consumers who want details
|
|
124
126
|
err.event = event || null;
|
|
125
127
|
reject(err);
|
|
128
|
+
done();
|
|
126
129
|
request = null;
|
|
127
130
|
};
|
|
128
131
|
|
|
@@ -143,6 +146,7 @@ export default isXHRAdapterSupported &&
|
|
|
143
146
|
request
|
|
144
147
|
)
|
|
145
148
|
);
|
|
149
|
+
done();
|
|
146
150
|
|
|
147
151
|
// Clean up request
|
|
148
152
|
request = null;
|
|
@@ -153,7 +157,7 @@ export default isXHRAdapterSupported &&
|
|
|
153
157
|
|
|
154
158
|
// Add headers to the request
|
|
155
159
|
if ('setRequestHeader' in request) {
|
|
156
|
-
utils.forEach(requestHeaders
|
|
160
|
+
utils.forEach(toByteStringHeaderObject(requestHeaders), function setRequestHeader(val, key) {
|
|
157
161
|
request.setRequestHeader(key, val);
|
|
158
162
|
});
|
|
159
163
|
}
|
|
@@ -192,6 +196,7 @@ export default isXHRAdapterSupported &&
|
|
|
192
196
|
}
|
|
193
197
|
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
|
|
194
198
|
request.abort();
|
|
199
|
+
done();
|
|
195
200
|
request = null;
|
|
196
201
|
};
|
|
197
202
|
|
|
@@ -205,7 +210,7 @@ export default isXHRAdapterSupported &&
|
|
|
205
210
|
|
|
206
211
|
const protocol = parseProtocol(_config.url);
|
|
207
212
|
|
|
208
|
-
if (protocol && platform.protocols.
|
|
213
|
+
if (protocol && !platform.protocols.includes(protocol)) {
|
|
209
214
|
reject(
|
|
210
215
|
new AxiosError(
|
|
211
216
|
'Unsupported protocol ' + protocol + ':',
|
package/lib/core/Axios.js
CHANGED
|
@@ -148,7 +148,7 @@ class Axios {
|
|
|
148
148
|
let contextHeaders = headers && utils.merge(headers.common, headers[config.method]);
|
|
149
149
|
|
|
150
150
|
headers &&
|
|
151
|
-
utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], (method) => {
|
|
151
|
+
utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'query', 'common'], (method) => {
|
|
152
152
|
delete headers[method];
|
|
153
153
|
});
|
|
154
154
|
|
|
@@ -251,7 +251,7 @@ utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData
|
|
|
251
251
|
};
|
|
252
252
|
});
|
|
253
253
|
|
|
254
|
-
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
|
|
254
|
+
utils.forEach(['post', 'put', 'patch', 'query'], function forEachMethodWithData(method) {
|
|
255
255
|
function generateHTTPMethod(isForm) {
|
|
256
256
|
return function httpMethod(url, data, config) {
|
|
257
257
|
return this.request(
|
|
@@ -271,7 +271,11 @@ utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
|
|
|
271
271
|
|
|
272
272
|
Axios.prototype[method] = generateHTTPMethod();
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
// QUERY is a safe/idempotent read method; multipart form bodies don't fit
|
|
275
|
+
// its semantics, so no queryForm shorthand is generated.
|
|
276
|
+
if (method !== 'query') {
|
|
277
|
+
Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
|
|
278
|
+
}
|
|
275
279
|
});
|
|
276
280
|
|
|
277
281
|
export default Axios;
|