axios 1.16.0 → 1.17.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.
@@ -0,0 +1,119 @@
1
+ 'use strict';
2
+
3
+ // Node-only: relies on the built-in `http2` module. Browser/react-native
4
+ // builds replace `lib/adapters/http.js` (the sole importer) with `lib/helpers/null.js`
5
+ // via the `browser` package.json field, so this module is never reached in
6
+ // those environments. Do not import it from any browser-reachable code path.
7
+
8
+ import http2 from 'http2';
9
+ import util from 'util';
10
+
11
+ class Http2Sessions {
12
+ constructor() {
13
+ this.sessions = Object.create(null);
14
+ }
15
+
16
+ getSession(authority, options) {
17
+ options = Object.assign(
18
+ {
19
+ sessionTimeout: 1000,
20
+ },
21
+ options
22
+ );
23
+
24
+ let authoritySessions = this.sessions[authority];
25
+
26
+ if (authoritySessions) {
27
+ let len = authoritySessions.length;
28
+
29
+ for (let i = 0; i < len; i++) {
30
+ const [sessionHandle, sessionOptions] = authoritySessions[i];
31
+ if (
32
+ !sessionHandle.destroyed &&
33
+ !sessionHandle.closed &&
34
+ util.isDeepStrictEqual(sessionOptions, options)
35
+ ) {
36
+ return sessionHandle;
37
+ }
38
+ }
39
+ }
40
+
41
+ const session = http2.connect(authority, options);
42
+
43
+ let removed;
44
+ let timer;
45
+
46
+ const removeSession = () => {
47
+ if (removed) {
48
+ return;
49
+ }
50
+
51
+ removed = true;
52
+
53
+ if (timer) {
54
+ clearTimeout(timer);
55
+ timer = null;
56
+ }
57
+
58
+ let entries = authoritySessions,
59
+ len = entries.length,
60
+ i = len;
61
+
62
+ while (i--) {
63
+ if (entries[i][0] === session) {
64
+ if (len === 1) {
65
+ delete this.sessions[authority];
66
+ } else {
67
+ entries.splice(i, 1);
68
+ }
69
+ if (!session.closed) {
70
+ session.close();
71
+ }
72
+ return;
73
+ }
74
+ }
75
+ };
76
+
77
+ const originalRequestFn = session.request;
78
+
79
+ const { sessionTimeout } = options;
80
+
81
+ if (sessionTimeout != null) {
82
+ let streamsCount = 0;
83
+
84
+ session.request = function () {
85
+ const stream = originalRequestFn.apply(this, arguments);
86
+
87
+ streamsCount++;
88
+
89
+ if (timer) {
90
+ clearTimeout(timer);
91
+ timer = null;
92
+ }
93
+
94
+ stream.once('close', () => {
95
+ if (!--streamsCount) {
96
+ timer = setTimeout(() => {
97
+ timer = null;
98
+ removeSession();
99
+ }, sessionTimeout);
100
+ }
101
+ });
102
+
103
+ return stream;
104
+ };
105
+ }
106
+
107
+ session.once('close', removeSession);
108
+
109
+ let entry = [session, options];
110
+
111
+ authoritySessions
112
+ ? authoritySessions.push(entry)
113
+ : (authoritySessions = this.sessions[authority] = [entry]);
114
+
115
+ return session;
116
+ }
117
+ }
118
+
119
+ export default Http2Sessions;
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  import utils from '../utils.js';
4
- import AxiosURLSearchParams from '../helpers/AxiosURLSearchParams.js';
4
+ import AxiosURLSearchParams from './AxiosURLSearchParams.js';
5
5
 
6
6
  /**
7
7
  * It replaces URL-encoded forms of `:`, `$`, `,`, and spaces with
@@ -3,54 +3,55 @@ import AxiosError from '../core/AxiosError.js';
3
3
  import utils from '../utils.js';
4
4
 
5
5
  const composeSignals = (signals, timeout) => {
6
- const { length } = (signals = signals ? signals.filter(Boolean) : []);
7
-
8
- if (timeout || length) {
9
- let controller = new AbortController();
10
-
11
- let aborted;
12
-
13
- const onabort = function (reason) {
14
- if (!aborted) {
15
- aborted = true;
16
- unsubscribe();
17
- const err = reason instanceof Error ? reason : this.reason;
18
- controller.abort(
19
- err instanceof AxiosError
20
- ? err
21
- : new CanceledError(err instanceof Error ? err.message : err)
22
- );
23
- }
24
- };
25
-
26
- let timer =
27
- timeout &&
28
- setTimeout(() => {
29
- timer = null;
30
- onabort(new AxiosError(`timeout of ${timeout}ms exceeded`, AxiosError.ETIMEDOUT));
31
- }, timeout);
32
-
33
- const unsubscribe = () => {
34
- if (signals) {
35
- timer && clearTimeout(timer);
36
- timer = null;
37
- signals.forEach((signal) => {
38
- signal.unsubscribe
39
- ? signal.unsubscribe(onabort)
40
- : signal.removeEventListener('abort', onabort);
41
- });
42
- signals = null;
43
- }
44
- };
45
-
46
- signals.forEach((signal) => signal.addEventListener('abort', onabort));
47
-
48
- const { signal } = controller;
49
-
50
- signal.unsubscribe = () => utils.asap(unsubscribe);
51
-
52
- return signal;
6
+ signals = signals ? signals.filter(Boolean) : [];
7
+
8
+ if (!timeout && !signals.length) {
9
+ return;
53
10
  }
11
+
12
+ const controller = new AbortController();
13
+
14
+ let aborted = false;
15
+
16
+ const onabort = function (reason) {
17
+ if (!aborted) {
18
+ aborted = true;
19
+ unsubscribe();
20
+ const err = reason instanceof Error ? reason : this.reason;
21
+ controller.abort(
22
+ err instanceof AxiosError
23
+ ? err
24
+ : new CanceledError(err instanceof Error ? err.message : err)
25
+ );
26
+ }
27
+ };
28
+
29
+ let timer =
30
+ timeout &&
31
+ setTimeout(() => {
32
+ timer = null;
33
+ onabort(new AxiosError(`timeout of ${timeout}ms exceeded`, AxiosError.ETIMEDOUT));
34
+ }, timeout);
35
+
36
+ const unsubscribe = () => {
37
+ if (!signals) { return; }
38
+ timer && clearTimeout(timer);
39
+ timer = null;
40
+ signals.forEach((signal) => {
41
+ signal.unsubscribe
42
+ ? signal.unsubscribe(onabort)
43
+ : signal.removeEventListener('abort', onabort);
44
+ });
45
+ signals = null;
46
+ };
47
+
48
+ signals.forEach((signal) => signal.addEventListener('abort', onabort));
49
+
50
+ const { signal } = controller;
51
+
52
+ signal.unsubscribe = () => utils.asap(unsubscribe);
53
+
54
+ return signal;
54
55
  };
55
56
 
56
57
  export default composeSignals;
@@ -68,7 +68,7 @@ function formDataToJSON(formData) {
68
68
  return !isNumericKey;
69
69
  }
70
70
 
71
- if (!target[name] || !utils.isObject(target[name])) {
71
+ if (!utils.hasOwnProp(target, name) || !utils.isObject(target[name])) {
72
72
  target[name] = [];
73
73
  }
74
74
 
@@ -73,11 +73,11 @@ const formDataToStream = (form, headersHandler, options) => {
73
73
  } = options || {};
74
74
 
75
75
  if (!utils.isFormData(form)) {
76
- throw TypeError('FormData instance required');
76
+ throw new TypeError('FormData instance required');
77
77
  }
78
78
 
79
79
  if (boundary.length < 1 || boundary.length > 70) {
80
- throw Error('boundary must be 1-70 characters long');
80
+ throw new Error('boundary must be 1-70 characters long');
81
81
  }
82
82
 
83
83
  const boundaryBytes = textEncoder.encode('--' + boundary + CRLF);
@@ -4,7 +4,9 @@ import AxiosError from '../core/AxiosError.js';
4
4
  import parseProtocol from './parseProtocol.js';
5
5
  import platform from '../platform/index.js';
6
6
 
7
- const DATA_URL_PATTERN = /^(?:([^;]+);)?(?:[^;]+;)?(base64|),([\s\S]*)$/;
7
+ // RFC 2397: data:[<mediatype>][;base64],<data>
8
+ // mediatype = type/subtype followed by optional ;name=value parameters
9
+ const DATA_URL_PATTERN = /^([^,;]+\/[^,;]+)?((?:;[^,;=]+=[^,;]+)*)(;base64)?,([\s\S]*)$/;
8
10
 
9
11
  /**
10
12
  * Parse data uri to a Buffer or Blob
@@ -33,10 +35,21 @@ export default function fromDataURI(uri, asBlob, options) {
33
35
  throw new AxiosError('Invalid URL', AxiosError.ERR_INVALID_URL);
34
36
  }
35
37
 
36
- const mime = match[1];
37
- const isBase64 = match[2];
38
- const body = match[3];
39
- const buffer = Buffer.from(decodeURIComponent(body), isBase64 ? 'base64' : 'utf8');
38
+ const type = match[1];
39
+ const params = match[2];
40
+ const encoding = match[3] ? 'base64' : 'utf8';
41
+ const body = match[4];
42
+
43
+ // RFC 2397 section 3: default mediatype is text/plain;charset=US-ASCII
44
+ // Bare `data:,` leaves mime undefined; Blob normalises that to "" per spec.
45
+ let mime;
46
+ if (type) {
47
+ mime = params ? type + params : type;
48
+ } else if (params) {
49
+ mime = 'text/plain' + params;
50
+ }
51
+
52
+ const buffer = Buffer.from(decodeURIComponent(body), encoding);
40
53
 
41
54
  if (asBlob) {
42
55
  if (!_Blob) {
@@ -7,6 +7,9 @@ export const progressEventReducer = (listener, isDownloadStream, freq = 3) => {
7
7
  const _speedometer = speedometer(50, 250);
8
8
 
9
9
  return throttle((e) => {
10
+ if (!e || typeof e.loaded !== 'number') {
11
+ return;
12
+ }
10
13
  const rawLoaded = e.loaded;
11
14
  const total = e.lengthComputable ? e.total : undefined;
12
15
  const loaded = total != null ? Math.min(rawLoaded, total) : rawLoaded;
@@ -35,7 +35,7 @@ const encodeUTF8 = (str) =>
35
35
  String.fromCharCode(parseInt(hex, 16))
36
36
  );
37
37
 
38
- export default (config) => {
38
+ function resolveConfig(config) {
39
39
  const newConfig = mergeConfig({}, config);
40
40
 
41
41
  // Read only own properties to prevent prototype pollution gadgets
@@ -56,8 +56,8 @@ export default (config) => {
56
56
 
57
57
  newConfig.url = buildURL(
58
58
  buildFullPath(baseURL, url, allowAbsoluteUrls),
59
- config.params,
60
- config.paramsSerializer
59
+ own('params'),
60
+ own('paramsSerializer')
61
61
  );
62
62
 
63
63
  // HTTP basic authentication
@@ -70,8 +70,12 @@ export default (config) => {
70
70
  }
71
71
 
72
72
  if (utils.isFormData(data)) {
73
- if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) {
74
- headers.setContentType(undefined); // browser handles it
73
+ if (
74
+ platform.hasStandardBrowserEnv ||
75
+ platform.hasStandardBrowserWebWorkerEnv ||
76
+ utils.isReactNative(data)
77
+ ) {
78
+ headers.setContentType(undefined); // browser/web worker/RN handles it
75
79
  } else if (utils.isFunction(data.getHeaders)) {
76
80
  // Node.js FormData (like form-data package)
77
81
  setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
@@ -103,4 +107,6 @@ export default (config) => {
103
107
  }
104
108
 
105
109
  return newConfig;
106
- };
110
+ }
111
+
112
+ export default resolveConfig;
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ import utils from '../utils.js';
4
+
5
+ function trimSPorHTAB(str) {
6
+ let start = 0;
7
+ let end = str.length;
8
+
9
+ while (start < end) {
10
+ const code = str.charCodeAt(start);
11
+
12
+ if (code !== 0x09 && code !== 0x20) {
13
+ break;
14
+ }
15
+
16
+ start += 1;
17
+ }
18
+
19
+ while (end > start) {
20
+ const code = str.charCodeAt(end - 1);
21
+
22
+ if (code !== 0x09 && code !== 0x20) {
23
+ break;
24
+ }
25
+
26
+ end -= 1;
27
+ }
28
+
29
+ return start === 0 && end === str.length ? str : str.slice(start, end);
30
+ }
31
+
32
+ // The control-code ranges are intentional: header sanitization strips C0/DEL bytes.
33
+ // eslint-disable-next-line no-control-regex
34
+ const INVALID_UNICODE_HEADER_VALUE_CHARS = new RegExp('[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+', 'g');
35
+ // eslint-disable-next-line no-control-regex
36
+ const INVALID_BYTE_STRING_HEADER_VALUE_CHARS = new RegExp('[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+', 'g');
37
+
38
+ function sanitizeValue(value, invalidChars) {
39
+ if (utils.isArray(value)) {
40
+ return value.map((item) => sanitizeValue(item, invalidChars));
41
+ }
42
+
43
+ return trimSPorHTAB(String(value).replace(invalidChars, ''));
44
+ }
45
+
46
+ export const sanitizeHeaderValue = (value) =>
47
+ sanitizeValue(value, INVALID_UNICODE_HEADER_VALUE_CHARS);
48
+
49
+ export const sanitizeByteStringHeaderValue = (value) =>
50
+ sanitizeValue(value, INVALID_BYTE_STRING_HEADER_VALUE_CHARS);
51
+
52
+ export function toByteStringHeaderObject(headers) {
53
+ const byteStringHeaders = Object.create(null);
54
+
55
+ utils.forEach(headers.toJSON(), (value, header) => {
56
+ byteStringHeaders[header] = sanitizeByteStringHeaderValue(value);
57
+ });
58
+
59
+ return byteStringHeaders;
60
+ }
@@ -219,7 +219,7 @@ function toFormData(obj, formData, options) {
219
219
  }
220
220
 
221
221
  if (stack.indexOf(value) !== -1) {
222
- throw Error('Circular reference detected in ' + path.join('.'));
222
+ throw new Error('Circular reference detected in ' + path.join('.'));
223
223
  }
224
224
 
225
225
  stack.push(value);
package/lib/utils.js CHANGED
@@ -412,7 +412,9 @@ function merge(...objs) {
412
412
  return;
413
413
  }
414
414
 
415
- const targetKey = (caseless && findKey(result, key)) || key;
415
+ // findKey lowercases the key, so caseless lookup only applies to strings —
416
+ // symbol keys are identity-matched.
417
+ const targetKey = (caseless && typeof key === 'string' && findKey(result, key)) || key;
416
418
  // Read via own-prop only — a bare `result[targetKey]` walks the prototype
417
419
  // chain, so a polluted Object.prototype value could surface here and get
418
420
  // copied into the merged result.
@@ -429,7 +431,24 @@ function merge(...objs) {
429
431
  };
430
432
 
431
433
  for (let i = 0, l = objs.length; i < l; i++) {
432
- objs[i] && forEach(objs[i], assignValue);
434
+ const source = objs[i];
435
+ if (!source || isBuffer(source)) {
436
+ continue;
437
+ }
438
+
439
+ forEach(source, assignValue);
440
+
441
+ if (typeof source !== 'object' || isArray(source)) {
442
+ continue;
443
+ }
444
+
445
+ const symbols = Object.getOwnPropertySymbols(source);
446
+ for (let j = 0; j < symbols.length; j++) {
447
+ const symbol = symbols[j];
448
+ if (propertyIsEnumerable.call(source, symbol)) {
449
+ assignValue(source[symbol], symbol);
450
+ }
451
+ }
433
452
  }
434
453
  return result;
435
454
  }
@@ -658,6 +677,8 @@ const hasOwnProperty = (
658
677
  hasOwnProperty.call(obj, prop)
659
678
  )(Object.prototype);
660
679
 
680
+ const { propertyIsEnumerable } = Object.prototype;
681
+
661
682
  /**
662
683
  * Determine if a value is a RegExp object
663
684
  *
@@ -763,11 +784,11 @@ function isSpecCompliantForm(thing) {
763
784
  * @returns {Object} The JSON-compatible object.
764
785
  */
765
786
  const toJSONObject = (obj) => {
766
- const stack = new Array(10);
787
+ const visited = new WeakSet();
767
788
 
768
- const visit = (source, i) => {
789
+ const visit = (source) => {
769
790
  if (isObject(source)) {
770
- if (stack.indexOf(source) >= 0) {
791
+ if (visited.has(source)) {
771
792
  return;
772
793
  }
773
794
 
@@ -777,15 +798,16 @@ const toJSONObject = (obj) => {
777
798
  }
778
799
 
779
800
  if (!('toJSON' in source)) {
780
- stack[i] = source;
801
+ // add-on descent / delete-on-ascent: preserves path semantics, so DAG nodes serialise at every occurrence (see #7230).
802
+ visited.add(source);
781
803
  const target = isArray(source) ? [] : {};
782
804
 
783
805
  forEach(source, (value, key) => {
784
- const reducedValue = visit(value, i + 1);
806
+ const reducedValue = visit(value);
785
807
  !isUndefined(reducedValue) && (target[key] = reducedValue);
786
808
  });
787
809
 
788
- stack[i] = undefined;
810
+ visited.delete(source);
789
811
 
790
812
  return target;
791
813
  }
@@ -794,7 +816,7 @@ const toJSONObject = (obj) => {
794
816
  return source;
795
817
  };
796
818
 
797
- return visit(obj, 0);
819
+ return visit(obj);
798
820
  };
799
821
 
800
822
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axios",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Promise based HTTP client for the browser and node.js",
5
5
  "main": "./dist/node/axios.cjs",
6
6
  "module": "./index.js",
@@ -86,8 +86,8 @@
86
86
  "Justin Beckwith (https://github.com/JustinBeckwith)",
87
87
  "Martti Laine (https://github.com/codeclown)",
88
88
  "Xianming Zhong (https://github.com/chinesedfan)",
89
- "Willian Agostini (https://github.com/WillianAgostini)",
90
89
  "Shaan Majid (https://github.com/shaanmajid)",
90
+ "Willian Agostini (https://github.com/WillianAgostini)",
91
91
  "Remco Haszing (https://github.com/remcohaszing)",
92
92
  "Rikki Gibson (https://github.com/RikkiGibson)"
93
93
  ],
@@ -97,6 +97,22 @@
97
97
  "url": "https://github.com/axios/axios/issues"
98
98
  },
99
99
  "homepage": "https://axios-http.com",
100
+ "files": [
101
+ "index.js",
102
+ "index.d.ts",
103
+ "index.d.cts",
104
+ "CHANGELOG.md",
105
+ "MIGRATION_GUIDE.md",
106
+ "lib/",
107
+ "dist/axios.js",
108
+ "dist/axios.min.js",
109
+ "dist/axios.min.js.map",
110
+ "dist/esm/axios.js",
111
+ "dist/esm/axios.min.js",
112
+ "dist/esm/axios.min.js.map",
113
+ "dist/browser/axios.cjs",
114
+ "dist/node/axios.cjs"
115
+ ],
100
116
  "scripts": {
101
117
  "build": "gulp clear && cross-env NODE_ENV=production rollup -c -m",
102
118
  "version": "npm run build && git add package.json",
@@ -123,13 +139,14 @@
123
139
  "dependencies": {
124
140
  "follow-redirects": "^1.16.0",
125
141
  "form-data": "^4.0.5",
142
+ "https-proxy-agent": "^5.0.1",
126
143
  "proxy-from-env": "^2.1.0"
127
144
  },
128
145
  "devDependencies": {
129
146
  "@babel/core": "^7.29.0",
130
- "@babel/preset-env": "^7.29.2",
131
- "@commitlint/cli": "^20.5.0",
132
- "@commitlint/config-conventional": "^20.5.0",
147
+ "@babel/preset-env": "^7.29.5",
148
+ "@commitlint/cli": "^21.0.1",
149
+ "@commitlint/config-conventional": "^21.0.1",
133
150
  "@eslint/js": "^10.0.1",
134
151
  "@rollup/plugin-alias": "^6.0.0",
135
152
  "@rollup/plugin-babel": "^7.0.0",
@@ -137,33 +154,34 @@
137
154
  "@rollup/plugin-json": "^6.1.0",
138
155
  "@rollup/plugin-node-resolve": "^16.0.3",
139
156
  "@rollup/plugin-terser": "^1.0.0",
140
- "@vitest/browser": "^4.1.5",
141
- "@vitest/browser-playwright": "^4.1.5",
157
+ "@vitest/browser": "^4.1.7",
158
+ "@vitest/browser-playwright": "^4.1.7",
142
159
  "abortcontroller-polyfill": "^1.7.8",
160
+ "acorn": "^8.16.0",
143
161
  "body-parser": "^2.2.2",
144
162
  "chalk": "^5.6.2",
145
163
  "cross-env": "^10.1.0",
146
164
  "dev-null": "^0.1.1",
147
- "eslint": "^10.2.1",
165
+ "eslint": "^10.4.0",
148
166
  "express": "^5.2.1",
149
167
  "formdata-node": "^6.0.3",
150
168
  "formidable": "^3.5.4",
151
169
  "fs-extra": "^11.3.4",
152
170
  "get-stream": "^9.0.1",
153
- "globals": "^17.5.0",
171
+ "globals": "^17.6.0",
154
172
  "gulp": "^5.0.1",
155
173
  "husky": "^9.1.7",
156
- "lint-staged": "^16.4.0",
174
+ "lint-staged": "^17.0.5",
157
175
  "minimist": "^1.2.8",
158
176
  "multer": "^2.1.1",
159
- "playwright": "^1.59.1",
177
+ "playwright": "^1.60.0",
160
178
  "prettier": "^3.8.3",
161
- "rollup": "^4.60.2",
179
+ "rollup": "^4.60.4",
162
180
  "rollup-plugin-bundle-size": "^1.0.3",
163
181
  "selfsigned": "^5.5.0",
164
182
  "stream-throttle": "^0.1.3",
165
183
  "typescript": "^5.9.3",
166
- "vitest": "^4.1.5"
184
+ "vitest": "^4.1.7"
167
185
  },
168
186
  "commitlint": {
169
187
  "rules": {