axios 1.18.1 → 1.19.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.
@@ -19,6 +19,7 @@ import platform from '../platform/index.js';
19
19
  import fromDataURI from '../helpers/fromDataURI.js';
20
20
  import stream from 'stream';
21
21
  import AxiosHeaders from '../core/AxiosHeaders.js';
22
+ import setFormDataHeaders from '../core/setFormDataHeaders.js';
22
23
  import AxiosTransformStream from '../helpers/AxiosTransformStream.js';
23
24
  import { EventEmitter } from 'events';
24
25
  import formDataToStream from '../helpers/formDataToStream.js';
@@ -33,7 +34,7 @@ import {
33
34
  progressEventDecorator,
34
35
  asyncDecorator,
35
36
  } from '../helpers/progressEventReducer.js';
36
- import estimateDataURLDecodedBytes from '../helpers/estimateDataURLDecodedBytes.js';
37
+ import { estimateDataURLBufferAllocation } from '../helpers/estimateDataURLDecodedBytes.js';
37
38
 
38
39
  const zlibOptions = {
39
40
  flush: zlib.constants.Z_SYNC_FLUSH,
@@ -54,24 +55,12 @@ const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
54
55
  const isZstdSupported = utils.isFunction(zlib.createZstdDecompress);
55
56
  const ACCEPT_ENCODING = 'gzip, compress, deflate' + (isBrotliSupported ? ', br' : '');
56
57
  const ACCEPT_ENCODING_WITH_ZSTD = ACCEPT_ENCODING + (isZstdSupported ? ', zstd' : '');
58
+ const scheduleProgress =
59
+ typeof process !== 'undefined' && process.nextTick ? process.nextTick.bind(process) : utils.asap;
57
60
 
58
61
  const { http: httpFollow, https: httpsFollow } = followRedirects;
59
62
 
60
63
  const isHttps = /https:?/;
61
- const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length'];
62
-
63
- function setFormDataHeaders(headers, formHeaders, policy) {
64
- if (policy !== 'content-only') {
65
- headers.set(formHeaders);
66
- return;
67
- }
68
-
69
- Object.entries(formHeaders).forEach(([key, val]) => {
70
- if (FORM_DATA_CONTENT_HEADERS.includes(key.toLowerCase())) {
71
- headers.set(key, val);
72
- }
73
- });
74
- }
75
64
 
76
65
  // Symbols used to bind a single 'error' listener to a pooled socket and track
77
66
  // the request currently owning that socket across keep-alive reuse (issue #10780).
@@ -675,7 +664,7 @@ export default isHttpAdapterSupported &&
675
664
  if (maxContentLength > -1) {
676
665
  // Use the exact string passed to fromDataURI (the configured url); fall back to fullPath if needed.
677
666
  const dataUrl = String(own('url') || fullPath || '');
678
- const estimated = estimateDataURLDecodedBytes(dataUrl);
667
+ const estimated = estimateDataURLBufferAllocation(dataUrl);
679
668
 
680
669
  if (estimated > maxContentLength) {
681
670
  return reject(
@@ -842,7 +831,7 @@ export default isHttpAdapterSupported &&
842
831
  data,
843
832
  progressEventDecorator(
844
833
  contentLength,
845
- progressEventReducer(asyncDecorator(onUploadProgress), false, 3)
834
+ progressEventReducer(asyncDecorator(onUploadProgress, scheduleProgress), false, 3)
846
835
  )
847
836
  )
848
837
  );
@@ -1080,7 +1069,7 @@ export default isHttpAdapterSupported &&
1080
1069
  transformStream,
1081
1070
  progressEventDecorator(
1082
1071
  responseLength,
1083
- progressEventReducer(asyncDecorator(onDownloadProgress), true, 3)
1072
+ progressEventReducer(asyncDecorator(onDownloadProgress, scheduleProgress), true, 3)
1084
1073
  )
1085
1074
  )
1086
1075
  );
package/lib/core/Axios.js CHANGED
@@ -209,17 +209,35 @@ class Axios {
209
209
  const onFulfilled = requestInterceptorChain[i++];
210
210
  const onRejected = requestInterceptorChain[i++];
211
211
  try {
212
- newConfig = onFulfilled(newConfig);
212
+ newConfig = onFulfilled ? onFulfilled(newConfig) : newConfig;
213
213
  } catch (error) {
214
- onRejected.call(this, error);
214
+ if (!onRejected) {
215
+ promise = Promise.reject(error);
216
+ break;
217
+ }
218
+
219
+ try {
220
+ const rejectedResult = onRejected.call(this, error);
221
+
222
+ if (utils.isThenable(rejectedResult)) {
223
+ promise = Promise.resolve(rejectedResult).then(() =>
224
+ dispatchRequest.call(this, newConfig)
225
+ );
226
+ }
227
+ } catch (rejectedError) {
228
+ promise = Promise.reject(rejectedError);
229
+ }
230
+
215
231
  break;
216
232
  }
217
233
  }
218
234
 
219
- try {
220
- promise = dispatchRequest.call(this, newConfig);
221
- } catch (error) {
222
- return Promise.reject(error);
235
+ if (!promise) {
236
+ try {
237
+ promise = dispatchRequest.call(this, newConfig);
238
+ } catch (error) {
239
+ promise = Promise.reject(error);
240
+ }
223
241
  }
224
242
 
225
243
  i = 0;
@@ -3,7 +3,7 @@
3
3
  import utils from '../utils.js';
4
4
  import AxiosHeaders from './AxiosHeaders.js';
5
5
 
6
- const REDACTED = '[REDACTED ****]';
6
+ export const REDACTED = '[REDACTED ****]';
7
7
 
8
8
  function hasOwnOrPrototypeToJSON(source) {
9
9
  if (utils.hasOwnProp(source, 'toJSON')) {
@@ -72,9 +72,40 @@ function redactConfig(config, redactKeys) {
72
72
  return visit(config);
73
73
  }
74
74
 
75
+ function stringifySafely(value) {
76
+ try {
77
+ return String(value);
78
+ } catch (err) {
79
+ return '';
80
+ }
81
+ }
82
+
83
+ function aggregateErrorMessage(error) {
84
+ const message = error.errors
85
+ .map((entry) => {
86
+ try {
87
+ return entry && entry.message ? stringifySafely(entry.message) : stringifySafely(entry);
88
+ } catch (err) {
89
+ return '';
90
+ }
91
+ })
92
+ .filter(Boolean)
93
+ .join('; ');
94
+
95
+ return message || error.name || 'AggregateError';
96
+ }
97
+
75
98
  class AxiosError extends Error {
76
99
  static from(error, code, config, request, response, customProps) {
77
- const axiosError = new AxiosError(error.message, code || error.code, config, request, response);
100
+ // `AggregateError` (thrown by Node on dual-stack/Happy-Eyeballs connection
101
+ // failures) has an empty `message`; its detail lives in `errors[]`. Without
102
+ // this, the wrapped error surfaces with a blank message (see #6721).
103
+ let message = error.message;
104
+ if (!message && utils.isArray(error.errors) && error.errors.length) {
105
+ message = aggregateErrorMessage(error);
106
+ }
107
+
108
+ const axiosError = new AxiosError(message, code || error.code, config, request, response);
78
109
  // Match native `Error` `cause` semantics: non-enumerable. The wrapped
79
110
  // error often carries circular internals (sockets, requests, agents), so
80
111
  // an enumerable `cause` makes structured loggers (pino/winston) and any
@@ -30,6 +30,124 @@ function parseTokens(str) {
30
30
  return tokens;
31
31
  }
32
32
 
33
+ const parameterNameRE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
34
+
35
+ function trimOWS(value) {
36
+ let start = 0;
37
+ let end = value.length;
38
+
39
+ while (start < end) {
40
+ const code = value.charCodeAt(start);
41
+
42
+ if (code !== 0x09 && code !== 0x20) {
43
+ break;
44
+ }
45
+
46
+ start += 1;
47
+ }
48
+
49
+ while (end > start) {
50
+ const code = value.charCodeAt(end - 1);
51
+
52
+ if (code !== 0x09 && code !== 0x20) {
53
+ break;
54
+ }
55
+
56
+ end -= 1;
57
+ }
58
+
59
+ return start === 0 && end === value.length ? value : value.slice(start, end);
60
+ }
61
+
62
+ function decodeQuotedString(value) {
63
+ const last = value.length - 1;
64
+
65
+ if (last < 1 || value.charCodeAt(0) !== 0x22 || value.charCodeAt(last) !== 0x22) {
66
+ return value;
67
+ }
68
+
69
+ let decoded = '';
70
+
71
+ for (let i = 1; i < last; i++) {
72
+ const code = value.charCodeAt(i);
73
+
74
+ if (code === 0x22) {
75
+ return value;
76
+ }
77
+
78
+ if (code === 0x5c) {
79
+ i += 1;
80
+
81
+ if (i >= last) {
82
+ return value;
83
+ }
84
+ }
85
+
86
+ decoded += value[i];
87
+ }
88
+
89
+ return decoded;
90
+ }
91
+
92
+ function parseParameters(value) {
93
+ const parameters = Object.create(null);
94
+ const str = String(value);
95
+ let start = 0;
96
+ let quoted = false;
97
+ let escaped = false;
98
+
99
+ function parseParameter(end) {
100
+ const part = trimOWS(str.slice(start, end));
101
+ const equals = part.indexOf('=');
102
+
103
+ if (equals < 1) {
104
+ return;
105
+ }
106
+
107
+ const name = trimOWS(part.slice(0, equals));
108
+
109
+ if (!parameterNameRE.test(name)) {
110
+ return;
111
+ }
112
+
113
+ const normalizedName = name.toLowerCase();
114
+
115
+ if (
116
+ normalizedName === '__proto__' ||
117
+ normalizedName === 'constructor' ||
118
+ normalizedName === 'prototype'
119
+ ) {
120
+ return;
121
+ }
122
+
123
+ const parameterValue = trimOWS(part.slice(equals + 1));
124
+ parameters[normalizedName] = decodeQuotedString(parameterValue);
125
+ }
126
+
127
+ for (let i = 0; i < str.length; i++) {
128
+ const code = str.charCodeAt(i);
129
+
130
+ if (quoted) {
131
+ if (escaped) {
132
+ escaped = false;
133
+ } else if (code === 0x5c) {
134
+ escaped = true;
135
+ } else if (code === 0x22) {
136
+ quoted = false;
137
+ }
138
+ } else if (code === 0x22) {
139
+ quoted = true;
140
+ } else if (code === 0x2c || code === 0x3b) {
141
+ parseParameter(i);
142
+ start = i + 1;
143
+ }
144
+ }
145
+
146
+ parseParameter(str.length);
147
+
148
+ return parameters;
149
+ }
150
+
33
151
  const isValidHeaderName = (str) => /^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(str.trim());
34
152
 
35
153
  function matchHeaderValue(context, value, header, filter, isHeaderNameFilter) {
@@ -281,7 +399,8 @@ class AxiosHeaders {
281
399
  }
282
400
 
283
401
  getSetCookie() {
284
- return this.get('set-cookie') || [];
402
+ const value = this.get('set-cookie');
403
+ return utils.isArray(value) ? value : value == null || value === false ? [] : [value];
285
404
  }
286
405
 
287
406
  get [Symbol.toStringTag]() {
@@ -292,6 +411,10 @@ class AxiosHeaders {
292
411
  return thing instanceof this ? thing : new this(thing);
293
412
  }
294
413
 
414
+ static parseParameters(value) {
415
+ return parseParameters(value);
416
+ }
417
+
295
418
  static concat(first, ...targets) {
296
419
  const computed = new this(first);
297
420
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- import AxiosError from './AxiosError.js';
3
+ import AxiosError, { REDACTED } from './AxiosError.js';
4
4
  import isAbsoluteURL from '../helpers/isAbsoluteURL.js';
5
5
  import combineURLs from '../helpers/combineURLs.js';
6
6
 
@@ -19,13 +19,51 @@ function normalizeURLForProtocolCheck(url) {
19
19
  return stripLeadingC0ControlOrSpace(url).replace(httpProtocolControlCharacters, '');
20
20
  }
21
21
 
22
+ // Redact the parts of a URL that can carry secrets before it is embedded in an
23
+ // error message. AxiosError.toJSON() serializes `message` verbatim and errors
24
+ // are commonly logged, while the opt-in `config.redact` model only cleans
25
+ // config keys — it cannot reach the message. Redact only the genuinely
26
+ // sensitive substrings — userinfo (credentials), query parameter values and
27
+ // fragment contents — with the same REDACTED marker the config redaction uses,
28
+ // while keeping the scheme, host, path and parameter names so the offending
29
+ // request stays accurately identifiable.
30
+ function redactFragment(fragment) {
31
+ if (!fragment) {
32
+ return fragment;
33
+ }
34
+
35
+ return fragment.replace(/(^|&)([^=&]*=)?[^&]+/g, (match, separator, parameterName = '') => {
36
+ return `${separator}${parameterName}${REDACTED}`;
37
+ });
38
+ }
39
+
40
+ function redactSensitiveURLParts(url) {
41
+ const redactedURL = url.replace(/^(https?:\/{0,2})[^/?#]*@/i, `$1${REDACTED}@`);
42
+ const fragmentIndex = redactedURL.indexOf('#');
43
+ const urlWithoutFragment =
44
+ fragmentIndex === -1 ? redactedURL : redactedURL.slice(0, fragmentIndex);
45
+ const redactedURLWithoutFragment = urlWithoutFragment.replace(
46
+ /([?&][^=&#]*=)[^&#]*/g,
47
+ `$1${REDACTED}`
48
+ );
49
+
50
+ if (fragmentIndex === -1) {
51
+ return redactedURLWithoutFragment;
52
+ }
53
+
54
+ return `${redactedURLWithoutFragment}#${redactFragment(redactedURL.slice(fragmentIndex + 1))}`;
55
+ }
56
+
22
57
  function assertValidHttpProtocolURL(url, config) {
23
- if (typeof url === 'string' && malformedHttpProtocol.test(normalizeURLForProtocolCheck(url))) {
24
- throw new AxiosError(
25
- 'Invalid URL: missing "//" after protocol',
26
- AxiosError.ERR_INVALID_URL,
27
- config
28
- );
58
+ if (typeof url === 'string') {
59
+ const normalizedURL = normalizeURLForProtocolCheck(url);
60
+ if (malformedHttpProtocol.test(normalizedURL)) {
61
+ throw new AxiosError(
62
+ `Invalid URL ${JSON.stringify(redactSensitiveURLParts(normalizedURL))}: missing "//" after protocol`,
63
+ AxiosError.ERR_INVALID_URL,
64
+ config
65
+ );
66
+ }
29
67
  }
30
68
  }
31
69
 
@@ -5,6 +5,17 @@ import AxiosHeaders from './AxiosHeaders.js';
5
5
 
6
6
  const headersToObject = (thing) => (thing instanceof AxiosHeaders ? { ...thing } : thing);
7
7
 
8
+ const ownEnumerableKeys = (thing) => {
9
+ if (Object.getOwnPropertySymbols && Object.getOwnPropertyDescriptor) {
10
+ return Object.keys(thing).concat(
11
+ Object.getOwnPropertySymbols(thing).filter(
12
+ (symbol) => Object.getOwnPropertyDescriptor(thing, symbol).enumerable
13
+ )
14
+ );
15
+ }
16
+ return Object.keys(thing);
17
+ };
18
+
8
19
  /**
9
20
  * Config-specific merge-function which creates a new config-object
10
21
  * by merging two configuration objects together.
@@ -70,7 +81,9 @@ export default function mergeConfig(config1, config2) {
70
81
  }
71
82
 
72
83
  function getMergedTransitionalOption(prop) {
73
- const transitional2 = utils.hasOwnProp(config2, 'transitional') ? config2.transitional : undefined;
84
+ const transitional2 = utils.hasOwnProp(config2, 'transitional')
85
+ ? config2.transitional
86
+ : undefined;
74
87
 
75
88
  if (!utils.isUndefined(transitional2)) {
76
89
  if (utils.isPlainObject(transitional2)) {
@@ -82,7 +95,9 @@ export default function mergeConfig(config1, config2) {
82
95
  }
83
96
  }
84
97
 
85
- const transitional1 = utils.hasOwnProp(config1, 'transitional') ? config1.transitional : undefined;
98
+ const transitional1 = utils.hasOwnProp(config1, 'transitional')
99
+ ? config1.transitional
100
+ : undefined;
86
101
 
87
102
  if (utils.isPlainObject(transitional1) && utils.hasOwnProp(transitional1, prop)) {
88
103
  return transitional1[prop];
@@ -134,7 +149,7 @@ export default function mergeConfig(config1, config2) {
134
149
  mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true),
135
150
  };
136
151
 
137
- utils.forEach(Object.keys({ ...config1, ...config2 }), function computeConfigValue(prop) {
152
+ utils.forEach(ownEnumerableKeys({ ...config1, ...config2 }), function computeConfigValue(prop) {
138
153
  if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') return;
139
154
  const merge = utils.hasOwnProp(mergeMap, prop) ? mergeMap[prop] : mergeDeepProperties;
140
155
  const a = utils.hasOwnProp(config1, prop) ? config1[prop] : undefined;
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length'];
4
+
5
+ /**
6
+ * Apply the headers generated by a FormData implementation to the request headers,
7
+ * honoring the `formDataHeaderPolicy` option: with 'content-only', copy only the
8
+ * content-* headers; otherwise merge all of them.
9
+ *
10
+ * @param {AxiosHeaders} headers - the request headers to mutate
11
+ * @param {Object | null | undefined} formHeaders - headers produced by the FormData implementation
12
+ * @param {String} [policy] - the resolved `formDataHeaderPolicy` config value
13
+ *
14
+ * @returns {void}
15
+ */
16
+ export default function setFormDataHeaders(headers, formHeaders, policy) {
17
+ if (policy !== 'content-only') {
18
+ headers.set(formHeaders);
19
+ return;
20
+ }
21
+
22
+ Object.entries(formHeaders || {}).forEach(([key, val]) => {
23
+ if (FORM_DATA_CONTENT_HEADERS.includes(key.toLowerCase())) {
24
+ headers.set(key, val);
25
+ }
26
+ });
27
+ }
package/lib/env/data.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "1.18.1";
1
+ export const VERSION = "1.19.0";
@@ -62,6 +62,7 @@ const HttpStatusCode = {
62
62
  LoopDetected: 508,
63
63
  NotExtended: 510,
64
64
  NetworkAuthenticationRequired: 511,
65
+ WebServerReturnsAnUnknownError: 520,
65
66
  WebServerIsDown: 521,
66
67
  ConnectionTimedOut: 522,
67
68
  OriginIsUnreachable: 523,
@@ -9,7 +9,15 @@
9
9
  * @returns {string} The combined URL
10
10
  */
11
11
  export default function combineURLs(baseURL, relativeURL) {
12
- return relativeURL
13
- ? baseURL.replace(/\/?\/$/, '') + '/' + relativeURL.replace(/^\/+/, '')
14
- : baseURL;
12
+ if (!relativeURL) {
13
+ return baseURL;
14
+ }
15
+
16
+ let end = baseURL.length;
17
+
18
+ while (end > 0 && baseURL.charCodeAt(end - 1) === 47) {
19
+ end--;
20
+ }
21
+
22
+ return baseURL.slice(0, end) + '/' + relativeURL.replace(/^\/+/, '');
15
23
  }
@@ -45,7 +45,18 @@ const composeSignals = (signals, timeout) => {
45
45
  signals = null;
46
46
  };
47
47
 
48
- signals.forEach((signal) => signal.addEventListener('abort', onabort, { once: true }));
48
+ signals.forEach((signal) => {
49
+ if (aborted) {
50
+ return;
51
+ }
52
+
53
+ if (signal.aborted) {
54
+ onabort.call(signal);
55
+ return;
56
+ }
57
+
58
+ signal.addEventListener('abort', onabort, { once: true });
59
+ });
49
60
 
50
61
  const { signal } = controller;
51
62