axios 0.27.2 → 1.0.0

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.

Potentially problematic release.


This version of axios might be problematic. Click here for more details.

Files changed (75) hide show
  1. package/CHANGELOG.md +178 -940
  2. package/LICENSE +4 -16
  3. package/README.md +292 -93
  4. package/SECURITY.md +4 -3
  5. package/UPGRADE_GUIDE.md +1 -166
  6. package/bin/ssl_hotfix.js +22 -0
  7. package/dist/axios.js +2537 -2211
  8. package/dist/axios.js.map +1 -0
  9. package/dist/axios.min.js +2 -3
  10. package/dist/axios.min.js.map +1 -0
  11. package/dist/esm/axios.js +2942 -0
  12. package/dist/esm/axios.js.map +1 -0
  13. package/dist/esm/axios.min.js +2 -0
  14. package/dist/esm/axios.min.js.map +1 -0
  15. package/dist/node/axios.cjs +3750 -0
  16. package/dist/node/axios.cjs.map +1 -0
  17. package/gulpfile.js +88 -0
  18. package/index.d.ts +293 -70
  19. package/index.js +2 -1
  20. package/karma.conf.cjs +250 -0
  21. package/lib/adapters/http.js +371 -212
  22. package/lib/adapters/index.js +33 -0
  23. package/lib/adapters/xhr.js +81 -57
  24. package/lib/axios.js +34 -22
  25. package/lib/cancel/CancelToken.js +91 -89
  26. package/lib/cancel/CanceledError.js +9 -6
  27. package/lib/cancel/isCancel.js +2 -2
  28. package/lib/core/Axios.js +127 -99
  29. package/lib/core/AxiosError.js +22 -8
  30. package/lib/core/AxiosHeaders.js +274 -0
  31. package/lib/core/InterceptorManager.js +62 -45
  32. package/lib/core/buildFullPath.js +5 -4
  33. package/lib/core/dispatchRequest.js +21 -32
  34. package/lib/core/mergeConfig.js +8 -7
  35. package/lib/core/settle.js +6 -4
  36. package/lib/core/transformData.js +15 -9
  37. package/lib/defaults/index.js +77 -38
  38. package/lib/defaults/transitional.js +1 -1
  39. package/lib/env/classes/FormData.js +2 -0
  40. package/lib/env/data.js +1 -3
  41. package/lib/helpers/AxiosTransformStream.js +191 -0
  42. package/lib/helpers/AxiosURLSearchParams.js +58 -0
  43. package/lib/helpers/bind.js +3 -7
  44. package/lib/helpers/buildURL.js +24 -38
  45. package/lib/helpers/combineURLs.js +3 -2
  46. package/lib/helpers/cookies.js +43 -44
  47. package/lib/helpers/deprecatedMethod.js +4 -2
  48. package/lib/helpers/formDataToJSON.js +92 -0
  49. package/lib/helpers/fromDataURI.js +53 -0
  50. package/lib/helpers/isAbsoluteURL.js +3 -2
  51. package/lib/helpers/isAxiosError.js +4 -3
  52. package/lib/helpers/isURLSameOrigin.js +44 -45
  53. package/lib/helpers/null.js +1 -1
  54. package/lib/helpers/parseHeaders.js +24 -22
  55. package/lib/helpers/parseProtocol.js +3 -3
  56. package/lib/helpers/speedometer.js +55 -0
  57. package/lib/helpers/spread.js +3 -2
  58. package/lib/helpers/throttle.js +33 -0
  59. package/lib/helpers/toFormData.js +193 -36
  60. package/lib/helpers/toURLEncodedForm.js +18 -0
  61. package/lib/helpers/validator.js +20 -15
  62. package/lib/platform/browser/classes/FormData.js +3 -0
  63. package/lib/platform/browser/classes/URLSearchParams.js +4 -0
  64. package/lib/platform/browser/index.js +43 -0
  65. package/lib/platform/index.js +3 -0
  66. package/lib/platform/node/classes/FormData.js +3 -0
  67. package/lib/platform/node/classes/URLSearchParams.js +4 -0
  68. package/lib/platform/node/index.js +12 -0
  69. package/lib/utils.js +320 -177
  70. package/package.json +69 -23
  71. package/rollup.config.js +90 -0
  72. package/dist/axios.map +0 -1
  73. package/dist/axios.min.map +0 -1
  74. package/lib/defaults/env/FormData.js +0 -2
  75. package/lib/helpers/normalizeHeaderName.js +0 -12
@@ -1,96 +1,237 @@
1
1
  'use strict';
2
2
 
3
- var utils = require('./../utils');
4
- var settle = require('./../core/settle');
5
- var buildFullPath = require('../core/buildFullPath');
6
- var buildURL = require('./../helpers/buildURL');
7
- var http = require('http');
8
- var https = require('https');
9
- var httpFollow = require('follow-redirects').http;
10
- var httpsFollow = require('follow-redirects').https;
11
- var url = require('url');
12
- var zlib = require('zlib');
13
- var VERSION = require('./../env/data').version;
14
- var transitionalDefaults = require('../defaults/transitional');
15
- var AxiosError = require('../core/AxiosError');
16
- var CanceledError = require('../cancel/CanceledError');
17
-
18
- var isHttps = /https:?/;
19
-
20
- var supportedProtocols = [ 'http:', 'https:', 'file:' ];
3
+ import utils from './../utils.js';
4
+ import settle from './../core/settle.js';
5
+ import buildFullPath from '../core/buildFullPath.js';
6
+ import buildURL from './../helpers/buildURL.js';
7
+ import {getProxyForUrl} from 'proxy-from-env';
8
+ import http from 'http';
9
+ import https from 'https';
10
+ import followRedirects from 'follow-redirects';
11
+ import zlib from 'zlib';
12
+ import {VERSION} from '../env/data.js';
13
+ import transitionalDefaults from '../defaults/transitional.js';
14
+ import AxiosError from '../core/AxiosError.js';
15
+ import CanceledError from '../cancel/CanceledError.js';
16
+ import platform from '../platform/index.js';
17
+ import fromDataURI from '../helpers/fromDataURI.js';
18
+ import stream from 'stream';
19
+ import AxiosHeaders from '../core/AxiosHeaders.js';
20
+ import AxiosTransformStream from '../helpers/AxiosTransformStream.js';
21
+ import EventEmitter from 'events';
22
+
23
+ const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
24
+
25
+ const {http: httpFollow, https: httpsFollow} = followRedirects;
26
+
27
+ const isHttps = /https:?/;
28
+
29
+ const supportedProtocols = platform.protocols.map(protocol => {
30
+ return protocol + ':';
31
+ });
21
32
 
22
33
  /**
34
+ * If the proxy or config beforeRedirects functions are defined, call them with the options
35
+ * object.
36
+ *
37
+ * @param {Object<string, any>} options - The options object that was passed to the request.
38
+ *
39
+ * @returns {Object<string, any>}
40
+ */
41
+ function dispatchBeforeRedirect(options) {
42
+ if (options.beforeRedirects.proxy) {
43
+ options.beforeRedirects.proxy(options);
44
+ }
45
+ if (options.beforeRedirects.config) {
46
+ options.beforeRedirects.config(options);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * If the proxy or config afterRedirects functions are defined, call them with the options
23
52
  *
24
53
  * @param {http.ClientRequestArgs} options
25
- * @param {AxiosProxyConfig} proxy
54
+ * @param {AxiosProxyConfig} configProxy
26
55
  * @param {string} location
56
+ *
57
+ * @returns {http.ClientRequestArgs}
27
58
  */
28
- function setProxy(options, proxy, location) {
29
- options.hostname = proxy.host;
30
- options.host = proxy.host;
31
- options.port = proxy.port;
32
- options.path = location;
33
-
34
- // Basic proxy authorization
35
- if (proxy.auth) {
36
- var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');
37
- options.headers['Proxy-Authorization'] = 'Basic ' + base64;
59
+ function setProxy(options, configProxy, location) {
60
+ let proxy = configProxy;
61
+ if (!proxy && proxy !== false) {
62
+ const proxyUrl = getProxyForUrl(location);
63
+ if (proxyUrl) {
64
+ proxy = new URL(proxyUrl);
65
+ }
38
66
  }
67
+ if (proxy) {
68
+ // Basic proxy authorization
69
+ if (proxy.username) {
70
+ proxy.auth = (proxy.username || '') + ':' + (proxy.password || '');
71
+ }
72
+
73
+ if (proxy.auth) {
74
+ // Support proxy auth object form
75
+ if (proxy.auth.username || proxy.auth.password) {
76
+ proxy.auth = (proxy.auth.username || '') + ':' + (proxy.auth.password || '');
77
+ }
78
+ const base64 = Buffer
79
+ .from(proxy.auth, 'utf8')
80
+ .toString('base64');
81
+ options.headers['Proxy-Authorization'] = 'Basic ' + base64;
82
+ }
39
83
 
40
- // If a proxy is used, any redirects must also pass through the proxy
41
- options.beforeRedirect = function beforeRedirect(redirection) {
42
- redirection.headers.host = redirection.host;
43
- setProxy(redirection, proxy, redirection.href);
84
+ options.headers.host = options.hostname + (options.port ? ':' + options.port : '');
85
+ options.hostname = proxy.hostname;
86
+ // Replace 'host' since options is not a URL object
87
+ options.host = proxy.hostname;
88
+ options.port = proxy.port;
89
+ options.path = location;
90
+ if (proxy.protocol) {
91
+ options.protocol = proxy.protocol;
92
+ }
93
+ }
94
+
95
+ options.beforeRedirects.proxy = function beforeRedirect(redirectOptions) {
96
+ // Configure proxy for redirected request, passing the original config proxy to apply
97
+ // the exact same logic as if the redirected request was performed by axios directly.
98
+ setProxy(redirectOptions, configProxy, redirectOptions.href);
44
99
  };
45
100
  }
46
101
 
47
102
  /*eslint consistent-return:0*/
48
- module.exports = function httpAdapter(config) {
103
+ export default function httpAdapter(config) {
49
104
  return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {
50
- var onCanceled;
51
- function done() {
105
+ let data = config.data;
106
+ const responseType = config.responseType;
107
+ const responseEncoding = config.responseEncoding;
108
+ const method = config.method.toUpperCase();
109
+ let isFinished;
110
+ let isDone;
111
+ let rejected = false;
112
+ let req;
113
+
114
+ // temporary internal emitter until the AxiosRequest class will be implemented
115
+ const emitter = new EventEmitter();
116
+
117
+ function onFinished() {
118
+ if (isFinished) return;
119
+ isFinished = true;
120
+
52
121
  if (config.cancelToken) {
53
- config.cancelToken.unsubscribe(onCanceled);
122
+ config.cancelToken.unsubscribe(abort);
54
123
  }
55
124
 
56
125
  if (config.signal) {
57
- config.signal.removeEventListener('abort', onCanceled);
126
+ config.signal.removeEventListener('abort', abort);
58
127
  }
128
+
129
+ emitter.removeAllListeners();
59
130
  }
60
- var resolve = function resolve(value) {
61
- done();
62
- resolvePromise(value);
131
+
132
+ function done(value, isRejected) {
133
+ if (isDone) return;
134
+
135
+ isDone = true;
136
+
137
+ if (isRejected) {
138
+ rejected = true;
139
+ onFinished();
140
+ }
141
+
142
+ isRejected ? rejectPromise(value) : resolvePromise(value);
143
+ }
144
+
145
+ const resolve = function resolve(value) {
146
+ done(value);
63
147
  };
64
- var rejected = false;
65
- var reject = function reject(value) {
66
- done();
67
- rejected = true;
68
- rejectPromise(value);
148
+
149
+ const reject = function reject(value) {
150
+ done(value, true);
69
151
  };
70
- var data = config.data;
71
- var headers = config.headers;
72
- var headerNames = {};
73
152
 
74
- Object.keys(headers).forEach(function storeLowerName(name) {
75
- headerNames[name.toLowerCase()] = name;
76
- });
153
+ function abort(reason) {
154
+ emitter.emit('abort', !reason || reason.type ? new CanceledError(null, config, req) : reason);
155
+ }
77
156
 
78
- // Set User-Agent (required by some servers)
79
- // See https://github.com/axios/axios/issues/69
80
- if ('user-agent' in headerNames) {
81
- // User-Agent is specified; handle case where no UA header is desired
82
- if (!headers[headerNames['user-agent']]) {
83
- delete headers[headerNames['user-agent']];
157
+ emitter.once('abort', reject);
158
+
159
+ if (config.cancelToken || config.signal) {
160
+ config.cancelToken && config.cancelToken.subscribe(abort);
161
+ if (config.signal) {
162
+ config.signal.aborted ? abort() : config.signal.addEventListener('abort', abort);
84
163
  }
85
- // Otherwise, use specified value
86
- } else {
87
- // Only set header if it hasn't been set in config
88
- headers['User-Agent'] = 'axios/' + VERSION;
89
164
  }
90
165
 
166
+ // Parse url
167
+ const fullPath = buildFullPath(config.baseURL, config.url);
168
+ const parsed = new URL(fullPath);
169
+ const protocol = parsed.protocol || supportedProtocols[0];
170
+
171
+ if (protocol === 'data:') {
172
+ let convertedData;
173
+
174
+ if (method !== 'GET') {
175
+ return settle(resolve, reject, {
176
+ status: 405,
177
+ statusText: 'method not allowed',
178
+ headers: {},
179
+ config
180
+ });
181
+ }
182
+
183
+ try {
184
+ convertedData = fromDataURI(config.url, responseType === 'blob', {
185
+ Blob: config.env && config.env.Blob
186
+ });
187
+ } catch (err) {
188
+ throw AxiosError.from(err, AxiosError.ERR_BAD_REQUEST, config);
189
+ }
190
+
191
+ if (responseType === 'text') {
192
+ convertedData = convertedData.toString(responseEncoding);
193
+
194
+ if (!responseEncoding || responseEncoding === 'utf8') {
195
+ data = utils.stripBOM(convertedData);
196
+ }
197
+ } else if (responseType === 'stream') {
198
+ convertedData = stream.Readable.from(convertedData);
199
+ }
200
+
201
+ return settle(resolve, reject, {
202
+ data: convertedData,
203
+ status: 200,
204
+ statusText: 'OK',
205
+ headers: {},
206
+ config
207
+ });
208
+ }
209
+
210
+ if (supportedProtocols.indexOf(protocol) === -1) {
211
+ return reject(new AxiosError(
212
+ 'Unsupported protocol ' + protocol,
213
+ AxiosError.ERR_BAD_REQUEST,
214
+ config
215
+ ));
216
+ }
217
+
218
+ const headers = AxiosHeaders.from(config.headers).normalize();
219
+
220
+ // Set User-Agent (required by some servers)
221
+ // See https://github.com/axios/axios/issues/69
222
+ // User-Agent is specified; handle case where no UA header is desired
223
+ // Only set header if it hasn't been set in config
224
+ headers.set('User-Agent', 'axios/' + VERSION, false);
225
+
226
+ const onDownloadProgress = config.onDownloadProgress;
227
+ const onUploadProgress = config.onUploadProgress;
228
+ const maxRate = config.maxRate;
229
+ let maxUploadRate = undefined;
230
+ let maxDownloadRate = undefined;
231
+
91
232
  // support for https://www.npmjs.com/package/form-data api
92
233
  if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
93
- Object.assign(headers, data.getHeaders());
234
+ headers.set(data.getHeaders());
94
235
  } else if (data && !utils.isStream(data)) {
95
236
  if (Buffer.isBuffer(data)) {
96
237
  // Nothing to do...
@@ -106,6 +247,9 @@ module.exports = function httpAdapter(config) {
106
247
  ));
107
248
  }
108
249
 
250
+ // Add Content-Length header if data exists
251
+ headers.set('Content-Length', data.length, false);
252
+
109
253
  if (config.maxBodyLength > -1 && data.length > config.maxBodyLength) {
110
254
  return reject(new AxiosError(
111
255
  'Request body larger than maxBodyLength limit',
@@ -113,65 +257,72 @@ module.exports = function httpAdapter(config) {
113
257
  config
114
258
  ));
115
259
  }
260
+ }
116
261
 
117
- // Add Content-Length header if data exists
118
- if (!headerNames['content-length']) {
119
- headers['Content-Length'] = data.length;
262
+ const contentLength = +headers.getContentLength();
263
+
264
+ if (utils.isArray(maxRate)) {
265
+ maxUploadRate = maxRate[0];
266
+ maxDownloadRate = maxRate[1];
267
+ } else {
268
+ maxUploadRate = maxDownloadRate = maxRate;
269
+ }
270
+
271
+ if (data && (onUploadProgress || maxUploadRate)) {
272
+ if (!utils.isStream(data)) {
273
+ data = stream.Readable.from(data, {objectMode: false});
120
274
  }
275
+
276
+ data = stream.pipeline([data, new AxiosTransformStream({
277
+ length: utils.toFiniteNumber(contentLength),
278
+ maxRate: utils.toFiniteNumber(maxUploadRate)
279
+ })], utils.noop);
280
+
281
+ onUploadProgress && data.on('progress', progress => {
282
+ onUploadProgress(Object.assign(progress, {
283
+ upload: true
284
+ }));
285
+ });
121
286
  }
122
287
 
123
288
  // HTTP basic authentication
124
- var auth = undefined;
289
+ let auth = undefined;
125
290
  if (config.auth) {
126
- var username = config.auth.username || '';
127
- var password = config.auth.password || '';
291
+ const username = config.auth.username || '';
292
+ const password = config.auth.password || '';
128
293
  auth = username + ':' + password;
129
294
  }
130
295
 
131
- // Parse url
132
- var fullPath = buildFullPath(config.baseURL, config.url);
133
- var parsed = url.parse(fullPath);
134
- var protocol = parsed.protocol || supportedProtocols[0];
135
-
136
- if (supportedProtocols.indexOf(protocol) === -1) {
137
- return reject(new AxiosError(
138
- 'Unsupported protocol ' + protocol,
139
- AxiosError.ERR_BAD_REQUEST,
140
- config
141
- ));
142
- }
143
-
144
- if (!auth && parsed.auth) {
145
- var urlAuth = parsed.auth.split(':');
146
- var urlUsername = urlAuth[0] || '';
147
- var urlPassword = urlAuth[1] || '';
296
+ if (!auth && parsed.username) {
297
+ const urlUsername = parsed.username;
298
+ const urlPassword = parsed.password;
148
299
  auth = urlUsername + ':' + urlPassword;
149
300
  }
150
301
 
151
- if (auth && headerNames.authorization) {
152
- delete headers[headerNames.authorization];
153
- }
154
-
155
- var isHttpsRequest = isHttps.test(protocol);
156
- var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
302
+ auth && headers.delete('authorization');
157
303
 
304
+ const path = parsed.pathname.concat(parsed.searchParams);
158
305
  try {
159
- buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, '');
306
+ buildURL(path, config.params, config.paramsSerializer).replace(/^\?/, '');
160
307
  } catch (err) {
161
- var customErr = new Error(err.message);
308
+ const customErr = new Error(err.message);
162
309
  customErr.config = config;
163
310
  customErr.url = config.url;
164
311
  customErr.exists = true;
165
- reject(customErr);
312
+ return reject(customErr);
166
313
  }
167
314
 
168
- var options = {
169
- path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''),
170
- method: config.method.toUpperCase(),
171
- headers: headers,
172
- agent: agent,
315
+ headers.set('Accept-Encoding', 'gzip, deflate, br', false);
316
+
317
+ const options = {
318
+ path: buildURL(path, config.params, config.paramsSerializer).replace(/^\?/, ''),
319
+ method: method,
320
+ headers: headers.toJSON(),
173
321
  agents: { http: config.httpAgent, https: config.httpsAgent },
174
- auth: auth
322
+ auth,
323
+ protocol,
324
+ beforeRedirect: dispatchBeforeRedirect,
325
+ beforeRedirects: {}
175
326
  };
176
327
 
177
328
  if (config.socketPath) {
@@ -179,79 +330,31 @@ module.exports = function httpAdapter(config) {
179
330
  } else {
180
331
  options.hostname = parsed.hostname;
181
332
  options.port = parsed.port;
333
+ setProxy(options, config.proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path);
182
334
  }
183
335
 
184
- var proxy = config.proxy;
185
- if (!proxy && proxy !== false) {
186
- var proxyEnv = protocol.slice(0, -1) + '_proxy';
187
- var proxyUrl = process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()];
188
- if (proxyUrl) {
189
- var parsedProxyUrl = url.parse(proxyUrl);
190
- var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY;
191
- var shouldProxy = true;
192
-
193
- if (noProxyEnv) {
194
- var noProxy = noProxyEnv.split(',').map(function trim(s) {
195
- return s.trim();
196
- });
197
-
198
- shouldProxy = !noProxy.some(function proxyMatch(proxyElement) {
199
- if (!proxyElement) {
200
- return false;
201
- }
202
- if (proxyElement === '*') {
203
- return true;
204
- }
205
- if (proxyElement[0] === '.' &&
206
- parsed.hostname.substr(parsed.hostname.length - proxyElement.length) === proxyElement) {
207
- return true;
208
- }
209
-
210
- return parsed.hostname === proxyElement;
211
- });
212
- }
213
-
214
- if (shouldProxy) {
215
- proxy = {
216
- host: parsedProxyUrl.hostname,
217
- port: parsedProxyUrl.port,
218
- protocol: parsedProxyUrl.protocol
219
- };
220
-
221
- if (parsedProxyUrl.auth) {
222
- var proxyUrlAuth = parsedProxyUrl.auth.split(':');
223
- proxy.auth = {
224
- username: proxyUrlAuth[0],
225
- password: proxyUrlAuth[1]
226
- };
227
- }
228
- }
229
- }
230
- }
231
-
232
- if (proxy) {
233
- options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : '');
234
- setProxy(options, proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path);
235
- }
236
-
237
- var transport;
238
- var isHttpsProxy = isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true);
336
+ let transport;
337
+ const isHttpsRequest = isHttps.test(options.protocol);
338
+ options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
239
339
  if (config.transport) {
240
340
  transport = config.transport;
241
341
  } else if (config.maxRedirects === 0) {
242
- transport = isHttpsProxy ? https : http;
342
+ transport = isHttpsRequest ? https : http;
243
343
  } else {
244
344
  if (config.maxRedirects) {
245
345
  options.maxRedirects = config.maxRedirects;
246
346
  }
247
347
  if (config.beforeRedirect) {
248
- options.beforeRedirect = config.beforeRedirect;
348
+ options.beforeRedirects.config = config.beforeRedirect;
249
349
  }
250
- transport = isHttpsProxy ? httpsFollow : httpFollow;
350
+ transport = isHttpsRequest ? httpsFollow : httpFollow;
251
351
  }
252
352
 
253
353
  if (config.maxBodyLength > -1) {
254
354
  options.maxBodyLength = config.maxBodyLength;
355
+ } else {
356
+ // follow-redirects does not skip comparison, so it should always succeed for axios -1 unlimited
357
+ options.maxBodyLength = Infinity;
255
358
  }
256
359
 
257
360
  if (config.insecureHTTPParser) {
@@ -259,84 +362,123 @@ module.exports = function httpAdapter(config) {
259
362
  }
260
363
 
261
364
  // Create the request
262
- var req = transport.request(options, function handleResponse(res) {
263
- if (req.aborted) return;
365
+ req = transport.request(options, function handleResponse(res) {
366
+ if (req.destroyed) return;
367
+
368
+ const streams = [res];
264
369
 
265
370
  // uncompress the response body transparently if required
266
- var stream = res;
371
+ let responseStream = res;
267
372
 
268
373
  // return the last request in case of redirects
269
- var lastRequest = res.req || req;
374
+ const lastRequest = res.req || req;
270
375
 
376
+ // if decompress disabled we should not decompress
377
+ if (config.decompress !== false) {
378
+ // if no content, but headers still say that it is encoded,
379
+ // remove the header not confuse downstream operations
380
+ if (data && data.length === 0 && res.headers['content-encoding']) {
381
+ delete res.headers['content-encoding'];
382
+ }
271
383
 
272
- // if no content, is HEAD request or decompress disabled we should not decompress
273
- if (res.statusCode !== 204 && lastRequest.method !== 'HEAD' && config.decompress !== false) {
274
384
  switch (res.headers['content-encoding']) {
275
385
  /*eslint default-case:0*/
276
386
  case 'gzip':
277
387
  case 'compress':
278
388
  case 'deflate':
279
- // add the unzipper to the body stream processing pipeline
280
- stream = stream.pipe(zlib.createUnzip());
389
+ // add the unzipper to the body stream processing pipeline
390
+ streams.push(zlib.createUnzip());
281
391
 
282
392
  // remove the content-encoding in order to not confuse downstream operations
283
393
  delete res.headers['content-encoding'];
284
394
  break;
395
+ case 'br':
396
+ if (isBrotliSupported) {
397
+ streams.push(zlib.createBrotliDecompress());
398
+ delete res.headers['content-encoding'];
399
+ }
285
400
  }
286
401
  }
287
402
 
288
- var response = {
403
+ if (onDownloadProgress) {
404
+ const responseLength = +res.headers['content-length'];
405
+
406
+ const transformStream = new AxiosTransformStream({
407
+ length: utils.toFiniteNumber(responseLength),
408
+ maxRate: utils.toFiniteNumber(maxDownloadRate)
409
+ });
410
+
411
+ onDownloadProgress && transformStream.on('progress', progress => {
412
+ onDownloadProgress(Object.assign(progress, {
413
+ download: true
414
+ }));
415
+ });
416
+
417
+ streams.push(transformStream);
418
+ }
419
+
420
+ responseStream = streams.length > 1 ? stream.pipeline(streams, utils.noop) : streams[0];
421
+
422
+ const offListeners = stream.finished(responseStream, () => {
423
+ offListeners();
424
+ onFinished();
425
+ });
426
+
427
+ const response = {
289
428
  status: res.statusCode,
290
429
  statusText: res.statusMessage,
291
- headers: res.headers,
292
- config: config,
430
+ headers: new AxiosHeaders(res.headers),
431
+ config,
293
432
  request: lastRequest
294
433
  };
295
434
 
296
- if (config.responseType === 'stream') {
297
- response.data = stream;
435
+ if (responseType === 'stream') {
436
+ response.data = responseStream;
298
437
  settle(resolve, reject, response);
299
438
  } else {
300
- var responseBuffer = [];
301
- var totalResponseBytes = 0;
302
- stream.on('data', function handleStreamData(chunk) {
439
+ const responseBuffer = [];
440
+ let totalResponseBytes = 0;
441
+
442
+ responseStream.on('data', function handleStreamData(chunk) {
303
443
  responseBuffer.push(chunk);
304
444
  totalResponseBytes += chunk.length;
305
445
 
306
446
  // make sure the content length is not over the maxContentLength if specified
307
447
  if (config.maxContentLength > -1 && totalResponseBytes > config.maxContentLength) {
308
- // stream.destoy() emit aborted event before calling reject() on Node.js v16
448
+ // stream.destroy() emit aborted event before calling reject() on Node.js v16
309
449
  rejected = true;
310
- stream.destroy();
450
+ responseStream.destroy();
311
451
  reject(new AxiosError('maxContentLength size of ' + config.maxContentLength + ' exceeded',
312
452
  AxiosError.ERR_BAD_RESPONSE, config, lastRequest));
313
453
  }
314
454
  });
315
455
 
316
- stream.on('aborted', function handlerStreamAborted() {
456
+ responseStream.on('aborted', function handlerStreamAborted() {
317
457
  if (rejected) {
318
458
  return;
319
459
  }
320
- stream.destroy();
321
- reject(new AxiosError(
460
+
461
+ const err = new AxiosError(
322
462
  'maxContentLength size of ' + config.maxContentLength + ' exceeded',
323
463
  AxiosError.ERR_BAD_RESPONSE,
324
464
  config,
325
465
  lastRequest
326
- ));
466
+ );
467
+ responseStream.destroy(err);
468
+ reject(err);
327
469
  });
328
470
 
329
- stream.on('error', function handleStreamError(err) {
330
- if (req.aborted) return;
471
+ responseStream.on('error', function handleStreamError(err) {
472
+ if (req.destroyed) return;
331
473
  reject(AxiosError.from(err, null, config, lastRequest));
332
474
  });
333
475
 
334
- stream.on('end', function handleStreamEnd() {
476
+ responseStream.on('end', function handleStreamEnd() {
335
477
  try {
336
- var responseData = responseBuffer.length === 1 ? responseBuffer[0] : Buffer.concat(responseBuffer);
337
- if (config.responseType !== 'arraybuffer') {
338
- responseData = responseData.toString(config.responseEncoding);
339
- if (!config.responseEncoding || config.responseEncoding === 'utf8') {
478
+ let responseData = responseBuffer.length === 1 ? responseBuffer[0] : Buffer.concat(responseBuffer);
479
+ if (responseType !== 'arraybuffer') {
480
+ responseData = responseData.toString(responseEncoding);
481
+ if (!responseEncoding || responseEncoding === 'utf8') {
340
482
  responseData = utils.stripBOM(responseData);
341
483
  }
342
484
  }
@@ -347,6 +489,18 @@ module.exports = function httpAdapter(config) {
347
489
  settle(resolve, reject, response);
348
490
  });
349
491
  }
492
+
493
+ emitter.once('abort', err => {
494
+ if (!responseStream.destroyed) {
495
+ responseStream.emit('error', err);
496
+ responseStream.destroy();
497
+ }
498
+ });
499
+ });
500
+
501
+ emitter.once('abort', err => {
502
+ reject(err);
503
+ req.destroy(err);
350
504
  });
351
505
 
352
506
  // Handle errors
@@ -365,7 +519,7 @@ module.exports = function httpAdapter(config) {
365
519
  // Handle request timeout
366
520
  if (config.timeout) {
367
521
  // This is forcing a int timeout to avoid problems if the `req` interface doesn't handle other types.
368
- var timeout = parseInt(config.timeout, 10);
522
+ const timeout = parseInt(config.timeout, 10);
369
523
 
370
524
  if (isNaN(timeout)) {
371
525
  reject(new AxiosError(
@@ -381,44 +535,49 @@ module.exports = function httpAdapter(config) {
381
535
  // Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.
382
536
  // And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.
383
537
  // 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.
384
- // And then these socket which be hang up will devoring CPU little by little.
538
+ // And then these socket which be hang up will devouring CPU little by little.
385
539
  // ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
386
540
  req.setTimeout(timeout, function handleRequestTimeout() {
387
- req.abort();
388
- var transitional = config.transitional || transitionalDefaults;
541
+ if (isDone) return;
542
+ let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
543
+ const transitional = config.transitional || transitionalDefaults;
544
+ if (config.timeoutErrorMessage) {
545
+ timeoutErrorMessage = config.timeoutErrorMessage;
546
+ }
389
547
  reject(new AxiosError(
390
- 'timeout of ' + timeout + 'ms exceeded',
548
+ timeoutErrorMessage,
391
549
  transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
392
550
  config,
393
551
  req
394
552
  ));
553
+ abort();
395
554
  });
396
555
  }
397
556
 
398
- if (config.cancelToken || config.signal) {
399
- // Handle cancellation
400
- // eslint-disable-next-line func-names
401
- onCanceled = function(cancel) {
402
- if (req.aborted) return;
403
557
 
404
- req.abort();
405
- reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel);
406
- };
558
+ // Send the request
559
+ if (utils.isStream(data)) {
560
+ let ended = false;
561
+ let errored = false;
407
562
 
408
- config.cancelToken && config.cancelToken.subscribe(onCanceled);
409
- if (config.signal) {
410
- config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
411
- }
412
- }
563
+ data.on('end', () => {
564
+ ended = true;
565
+ });
413
566
 
567
+ data.once('error', err => {
568
+ errored = true;
569
+ req.destroy(err);
570
+ });
414
571
 
415
- // Send the request
416
- if (utils.isStream(data)) {
417
- data.on('error', function handleStreamError(err) {
418
- reject(AxiosError.from(err, config, null, req));
419
- }).pipe(req);
572
+ data.on('close', () => {
573
+ if (!ended && !errored) {
574
+ abort(new CanceledError('Request stream has been aborted', config, req));
575
+ }
576
+ });
577
+
578
+ data.pipe(req);
420
579
  } else {
421
580
  req.end(data);
422
581
  }
423
582
  });
424
- };
583
+ }