axios 1.2.5 → 1.3.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.

package/index.d.cts CHANGED
@@ -10,6 +10,12 @@ type MethodsHeaders = Partial<{
10
10
 
11
11
  type AxiosHeaderMatcher = (this: AxiosHeaders, value: string, name: string, headers: RawAxiosHeaders) => boolean;
12
12
 
13
+ type CommonRequestHeadersList = 'Accept' | 'Content-Length' | 'User-Agent'| 'Content-Encoding' | 'Authorization';
14
+
15
+ type ContentType = AxiosHeaderValue | 'text/html' | 'text/plain' | 'multipart/form-data' | 'application/json' | 'application/x-www-form-urlencoded' | 'application/octet-stream';
16
+
17
+ type CommonResponseHeadersList = 'Server' | 'Content-Type' | 'Content-Length' | 'Cache-Control'| 'Content-Encoding';
18
+
13
19
  declare class AxiosHeaders {
14
20
  constructor(
15
21
  headers?: RawAxiosHeaders | AxiosHeaders
@@ -27,7 +33,7 @@ declare class AxiosHeaders {
27
33
 
28
34
  delete(header: string | string[], matcher?: AxiosHeaderMatcher): boolean;
29
35
 
30
- clear(): boolean;
36
+ clear(matcher?: AxiosHeaderMatcher): boolean;
31
37
 
32
38
  normalize(format: boolean): AxiosHeaders;
33
39
 
@@ -41,7 +47,7 @@ declare class AxiosHeaders {
41
47
 
42
48
  static concat(...targets: Array<AxiosHeaders | RawAxiosHeaders | string | undefined | null>): AxiosHeaders;
43
49
 
44
- setContentType(value: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
50
+ setContentType(value: ContentType, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
45
51
  getContentType(parser?: RegExp): RegExpExecArray | null;
46
52
  getContentType(matcher?: AxiosHeaderMatcher): AxiosHeaderValue;
47
53
  hasContentType(matcher?: AxiosHeaderMatcher): boolean;
@@ -200,16 +206,14 @@ type InternalAxiosError<T = unknown, D = any> = AxiosError<T, D>;
200
206
  declare namespace axios {
201
207
  type AxiosError<T = unknown, D = any> = InternalAxiosError<T, D>;
202
208
 
203
- type CommonRequestHeadersList = 'Accept' | 'Content-Type' | 'Content-Length' | 'User-Agent'| 'Content-Encoding';
204
-
205
209
  type RawAxiosRequestHeaders = Partial<RawAxiosHeaders & {
206
210
  [Key in CommonRequestHeadersList]: AxiosHeaderValue;
211
+ } & {
212
+ 'Content-Type': ContentType
207
213
  }>;
208
214
 
209
215
  type AxiosRequestHeaders = RawAxiosRequestHeaders & AxiosHeaders;
210
216
 
211
- type CommonResponseHeadersList = 'Server' | 'Content-Type' | 'Content-Length' | 'Cache-Control'| 'Content-Encoding';
212
-
213
217
  type RawCommonResponseHeaders = {
214
218
  [Key in CommonResponseHeadersList]: AxiosHeaderValue;
215
219
  } & {
package/index.d.ts CHANGED
@@ -28,7 +28,7 @@ export class AxiosHeaders {
28
28
 
29
29
  delete(header: string | string[], matcher?: AxiosHeaderMatcher): boolean;
30
30
 
31
- clear(): boolean;
31
+ clear(matcher?: AxiosHeaderMatcher): boolean;
32
32
 
33
33
  normalize(format: boolean): AxiosHeaders;
34
34
 
@@ -42,7 +42,7 @@ export class AxiosHeaders {
42
42
 
43
43
  static concat(...targets: Array<AxiosHeaders | RawAxiosHeaders | string | undefined | null>): AxiosHeaders;
44
44
 
45
- setContentType(value: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
45
+ setContentType(value: ContentType, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
46
46
  getContentType(parser?: RegExp): RegExpExecArray | null;
47
47
  getContentType(matcher?: AxiosHeaderMatcher): AxiosHeaderValue;
48
48
  hasContentType(matcher?: AxiosHeaderMatcher): boolean;
@@ -75,10 +75,14 @@ export class AxiosHeaders {
75
75
  [Symbol.iterator](): IterableIterator<[string, AxiosHeaderValue]>;
76
76
  }
77
77
 
78
- type CommonRequestHeadersList = 'Accept' | 'Content-Type' | 'Content-Length' | 'User-Agent' | 'Content-Encoding' | 'Authorization';
78
+ type CommonRequestHeadersList = 'Accept' | 'Content-Length' | 'User-Agent' | 'Content-Encoding' | 'Authorization';
79
+
80
+ type ContentType = AxiosHeaderValue | 'text/html' | 'text/plain' | 'multipart/form-data' | 'application/json' | 'application/x-www-form-urlencoded' | 'application/octet-stream';
79
81
 
80
82
  export type RawAxiosRequestHeaders = Partial<RawAxiosHeaders & {
81
83
  [Key in CommonRequestHeadersList]: AxiosHeaderValue;
84
+ } & {
85
+ 'Content-Type': ContentType
82
86
  }>;
83
87
 
84
88
  export type AxiosRequestHeaders = RawAxiosRequestHeaders & AxiosHeaders;
@@ -19,6 +19,9 @@ import stream from 'stream';
19
19
  import AxiosHeaders from '../core/AxiosHeaders.js';
20
20
  import AxiosTransformStream from '../helpers/AxiosTransformStream.js';
21
21
  import EventEmitter from 'events';
22
+ import formDataToStream from "../helpers/formDataToStream.js";
23
+ import readBlob from "../helpers/readBlob.js";
24
+ import ZlibHeaderTransformStream from '../helpers/ZlibHeaderTransformStream.js';
22
25
 
23
26
  const zlibOptions = {
24
27
  flush: zlib.constants.Z_SYNC_FLUSH,
@@ -242,9 +245,27 @@ export default isHttpAdapterSupported && function httpAdapter(config) {
242
245
  let maxUploadRate = undefined;
243
246
  let maxDownloadRate = undefined;
244
247
 
245
- // support for https://www.npmjs.com/package/form-data api
246
- if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
248
+ // support for spec compliant FormData objects
249
+ if (utils.isSpecCompliantForm(data)) {
250
+ const userBoundary = headers.getContentType(/boundary=([-_\w\d]{10,70})/i);
251
+
252
+ data = formDataToStream(data, (formHeaders) => {
253
+ headers.set(formHeaders);
254
+ }, {
255
+ tag: `axios-${VERSION}-boundary`,
256
+ boundary: userBoundary && userBoundary[1] || undefined
257
+ });
258
+ // support for https://www.npmjs.com/package/form-data api
259
+ } else if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
247
260
  headers.set(data.getHeaders());
261
+ if (utils.isFunction(data.getLengthSync)) { // check if the undocumented API exists
262
+ const knownLength = data.getLengthSync();
263
+ !utils.isUndefined(knownLength) && headers.setContentLength(knownLength, false);
264
+ }
265
+ } else if (utils.isBlob(data)) {
266
+ data.size && headers.setContentType(data.type || 'application/octet-stream');
267
+ headers.setContentLength(data.size || 0);
268
+ data = stream.Readable.from(readBlob(data));
248
269
  } else if (data && !utils.isStream(data)) {
249
270
  if (Buffer.isBuffer(data)) {
250
271
  // Nothing to do...
@@ -261,7 +282,7 @@ export default isHttpAdapterSupported && function httpAdapter(config) {
261
282
  }
262
283
 
263
284
  // Add Content-Length header if data exists
264
- headers.set('Content-Length', data.length, false);
285
+ headers.setContentLength(data.length, false);
265
286
 
266
287
  if (config.maxBodyLength > -1 && data.length > config.maxBodyLength) {
267
288
  return reject(new AxiosError(
@@ -425,7 +446,15 @@ export default isHttpAdapterSupported && function httpAdapter(config) {
425
446
  case 'x-gzip':
426
447
  case 'compress':
427
448
  case 'x-compress':
449
+ // add the unzipper to the body stream processing pipeline
450
+ streams.push(zlib.createUnzip(zlibOptions));
451
+
452
+ // remove the content-encoding in order to not confuse downstream operations
453
+ delete res.headers['content-encoding'];
454
+ break;
428
455
  case 'deflate':
456
+ streams.push(new ZlibHeaderTransformStream());
457
+
429
458
  // add the unzipper to the body stream processing pipeline
430
459
  streams.push(zlib.createUnzip(zlibOptions));
431
460
 
@@ -174,8 +174,20 @@ class AxiosHeaders {
174
174
  return deleted;
175
175
  }
176
176
 
177
- clear() {
178
- return Object.keys(this).forEach(this.delete.bind(this));
177
+ clear(matcher) {
178
+ const keys = Object.keys(this);
179
+ let i = keys.length;
180
+ let deleted = false;
181
+
182
+ while (i--) {
183
+ const key = keys[i];
184
+ if(!matcher || matchHeaderValue(this, this[key], key, matcher)) {
185
+ delete this[key];
186
+ deleted = true;
187
+ }
188
+ }
189
+
190
+ return deleted;
179
191
  }
180
192
 
181
193
  normalize(format) {
@@ -266,7 +278,7 @@ class AxiosHeaders {
266
278
  }
267
279
  }
268
280
 
269
- AxiosHeaders.accessor(['Content-Type', 'Content-Length', 'Accept', 'Accept-Encoding', 'User-Agent']);
281
+ AxiosHeaders.accessor(['Content-Type', 'Content-Length', 'Accept', 'Accept-Encoding', 'User-Agent', 'Authorization']);
270
282
 
271
283
  utils.freezeMethods(AxiosHeaders.prototype);
272
284
  utils.freezeMethods(AxiosHeaders);
@@ -1,2 +1,2 @@
1
- import FormData from 'form-data';
2
- export default FormData;
1
+ import _FormData from 'form-data';
2
+ export default typeof FormData !== 'undefined' ? FormData : _FormData;
package/lib/env/data.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "1.2.5";
1
+ export const VERSION = "1.3.0";
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+
3
+ import stream from "stream";
4
+
5
+ class ZlibHeaderTransformStream extends stream.Transform {
6
+ __transform(chunk, encoding, callback) {
7
+ this.push(chunk);
8
+ callback();
9
+ }
10
+
11
+ _transform(chunk, encoding, callback) {
12
+ if (chunk.length !== 0) {
13
+ this._transform = this.__transform;
14
+
15
+ // Add Default Compression headers if no zlib headers are present
16
+ if (chunk[0] !== 120) { // Hex: 78
17
+ const header = Buffer.alloc(2);
18
+ header[0] = 120; // Hex: 78
19
+ header[1] = 156; // Hex: 9C
20
+ this.push(header, encoding);
21
+ }
22
+ }
23
+
24
+ this.__transform(chunk, encoding, callback);
25
+ }
26
+ }
27
+
28
+ export default ZlibHeaderTransformStream;
@@ -0,0 +1,110 @@
1
+ import {Readable} from 'stream';
2
+ import utils from "../utils.js";
3
+ import readBlob from "./readBlob.js";
4
+
5
+ const BOUNDARY_ALPHABET = utils.ALPHABET.ALPHA_DIGIT + '-_';
6
+
7
+ const textEncoder = new TextEncoder();
8
+
9
+ const CRLF = '\r\n';
10
+ const CRLF_BYTES = textEncoder.encode(CRLF);
11
+ const CRLF_BYTES_COUNT = 2;
12
+
13
+ class FormDataPart {
14
+ constructor(name, value) {
15
+ const {escapeName} = this.constructor;
16
+ const isStringValue = utils.isString(value);
17
+
18
+ let headers = `Content-Disposition: form-data; name="${escapeName(name)}"${
19
+ !isStringValue && value.name ? `; filename="${escapeName(value.name)}"` : ''
20
+ }${CRLF}`;
21
+
22
+ if (isStringValue) {
23
+ value = textEncoder.encode(String(value).replace(/\r?\n|\r\n?/g, CRLF));
24
+ } else {
25
+ headers += `Content-Type: ${value.type || "application/octet-stream"}${CRLF}`
26
+ }
27
+
28
+ this.headers = textEncoder.encode(headers + CRLF);
29
+
30
+ this.contentLength = isStringValue ? value.byteLength : value.size;
31
+
32
+ this.size = this.headers.byteLength + this.contentLength + CRLF_BYTES_COUNT;
33
+
34
+ this.name = name;
35
+ this.value = value;
36
+ }
37
+
38
+ async *encode(){
39
+ yield this.headers;
40
+
41
+ const {value} = this;
42
+
43
+ if(utils.isTypedArray(value)) {
44
+ yield value;
45
+ } else {
46
+ yield* readBlob(value);
47
+ }
48
+
49
+ yield CRLF_BYTES;
50
+ }
51
+
52
+ static escapeName(name) {
53
+ return String(name).replace(/[\r\n"]/g, (match) => ({
54
+ '\r' : '%0D',
55
+ '\n' : '%0A',
56
+ '"' : '%22',
57
+ }[match]));
58
+ }
59
+ }
60
+
61
+ const formDataToStream = (form, headersHandler, options) => {
62
+ const {
63
+ tag = 'form-data-boundary',
64
+ size = 25,
65
+ boundary = tag + '-' + utils.generateString(size, BOUNDARY_ALPHABET)
66
+ } = options || {};
67
+
68
+ if(!utils.isFormData(form)) {
69
+ throw TypeError('FormData instance required');
70
+ }
71
+
72
+ if (boundary.length < 1 || boundary.length > 70) {
73
+ throw Error('boundary must be 10-70 characters long')
74
+ }
75
+
76
+ const boundaryBytes = textEncoder.encode('--' + boundary + CRLF);
77
+ const footerBytes = textEncoder.encode('--' + boundary + '--' + CRLF + CRLF);
78
+ let contentLength = footerBytes.byteLength;
79
+
80
+ const parts = Array.from(form.entries()).map(([name, value]) => {
81
+ const part = new FormDataPart(name, value);
82
+ contentLength += part.size;
83
+ return part;
84
+ });
85
+
86
+ contentLength += boundaryBytes.byteLength * parts.length;
87
+
88
+ contentLength = utils.toFiniteNumber(contentLength);
89
+
90
+ const computedHeaders = {
91
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`
92
+ }
93
+
94
+ if (Number.isFinite(contentLength)) {
95
+ computedHeaders['Content-Length'] = contentLength;
96
+ }
97
+
98
+ headersHandler && headersHandler(computedHeaders);
99
+
100
+ return Readable.from((async function *() {
101
+ for(const part of parts) {
102
+ yield boundaryBytes;
103
+ yield* part.encode();
104
+ }
105
+
106
+ yield footerBytes;
107
+ })());
108
+ };
109
+
110
+ export default formDataToStream;
@@ -0,0 +1,15 @@
1
+ const {asyncIterator} = Symbol;
2
+
3
+ const readBlob = async function* (blob) {
4
+ if (blob.stream) {
5
+ yield* blob.stream()
6
+ } else if (blob.arrayBuffer) {
7
+ yield await blob.arrayBuffer()
8
+ } else if (blob[asyncIterator]) {
9
+ yield* blob[asyncIterator]();
10
+ } else {
11
+ yield blob;
12
+ }
13
+ }
14
+
15
+ export default readBlob;
@@ -2,7 +2,8 @@
2
2
 
3
3
  import utils from '../utils.js';
4
4
  import AxiosError from '../core/AxiosError.js';
5
- import envFormData from '../env/classes/FormData.js';
5
+ // temporary hotfix to avoid circular references until AxiosURLSearchParams is refactored
6
+ import PlatformFormData from '../platform/node/classes/FormData.js';
6
7
 
7
8
  /**
8
9
  * Determines if the given thing is a array or js object.
@@ -59,17 +60,6 @@ const predicates = utils.toFlatObject(utils, {}, null, function filter(prop) {
59
60
  return /^is[A-Z]/.test(prop);
60
61
  });
61
62
 
62
- /**
63
- * If the thing is a FormData object, return true, otherwise return false.
64
- *
65
- * @param {unknown} thing - The thing to check.
66
- *
67
- * @returns {boolean}
68
- */
69
- function isSpecCompliant(thing) {
70
- return thing && utils.isFunction(thing.append) && thing[Symbol.toStringTag] === 'FormData' && thing[Symbol.iterator];
71
- }
72
-
73
63
  /**
74
64
  * Convert a data object to FormData
75
65
  *
@@ -99,7 +89,7 @@ function toFormData(obj, formData, options) {
99
89
  }
100
90
 
101
91
  // eslint-disable-next-line no-param-reassign
102
- formData = formData || new (envFormData || FormData)();
92
+ formData = formData || new (PlatformFormData || FormData)();
103
93
 
104
94
  // eslint-disable-next-line no-param-reassign
105
95
  options = utils.toFlatObject(options, {
@@ -117,7 +107,7 @@ function toFormData(obj, formData, options) {
117
107
  const dots = options.dots;
118
108
  const indexes = options.indexes;
119
109
  const _Blob = options.Blob || typeof Blob !== 'undefined' && Blob;
120
- const useBlob = _Blob && isSpecCompliant(formData);
110
+ const useBlob = _Blob && utils.isSpecCompliantForm(formData);
121
111
 
122
112
  if (!utils.isFunction(visitor)) {
123
113
  throw new TypeError('visitor must be a function');
package/lib/utils.js CHANGED
@@ -512,7 +512,7 @@ const matchAll = (regExp, str) => {
512
512
  const isHTMLForm = kindOfTest('HTMLFormElement');
513
513
 
514
514
  const toCamelCase = str => {
515
- return str.toLowerCase().replace(/[_-\s]([a-z\d])(\w*)/g,
515
+ return str.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,
516
516
  function replacer(m, p1, p2) {
517
517
  return p1.toUpperCase() + p2;
518
518
  }
@@ -596,6 +596,37 @@ const toFiniteNumber = (value, defaultValue) => {
596
596
  return Number.isFinite(value) ? value : defaultValue;
597
597
  }
598
598
 
599
+ const ALPHA = 'abcdefghijklmnopqrstuvwxyz'
600
+
601
+ const DIGIT = '0123456789';
602
+
603
+ const ALPHABET = {
604
+ DIGIT,
605
+ ALPHA,
606
+ ALPHA_DIGIT: ALPHA + ALPHA.toUpperCase() + DIGIT
607
+ }
608
+
609
+ const generateString = (size = 16, alphabet = ALPHABET.ALPHA_DIGIT) => {
610
+ let str = '';
611
+ const {length} = alphabet;
612
+ while (size--) {
613
+ str += alphabet[Math.random() * length|0]
614
+ }
615
+
616
+ return str;
617
+ }
618
+
619
+ /**
620
+ * If the thing is a FormData object, return true, otherwise return false.
621
+ *
622
+ * @param {unknown} thing - The thing to check.
623
+ *
624
+ * @returns {boolean}
625
+ */
626
+ function isSpecCompliantForm(thing) {
627
+ return !!(thing && isFunction(thing.append) && thing[Symbol.toStringTag] === 'FormData' && thing[Symbol.iterator]);
628
+ }
629
+
599
630
  const toJSONObject = (obj) => {
600
631
  const stack = new Array(10);
601
632
 
@@ -673,5 +704,8 @@ export default {
673
704
  findKey,
674
705
  global: _global,
675
706
  isContextDefined,
707
+ ALPHABET,
708
+ generateString,
709
+ isSpecCompliantForm,
676
710
  toJSONObject
677
711
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axios",
3
- "version": "1.2.5",
3
+ "version": "1.3.0",
4
4
  "description": "Promise based HTTP client for the browser and node.js",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -89,6 +89,7 @@
89
89
  "es6-promise": "^4.2.8",
90
90
  "eslint": "^8.17.0",
91
91
  "express": "^4.18.1",
92
+ "formdata-node": "^5.0.0",
92
93
  "formidable": "^2.0.1",
93
94
  "fs-extra": "^10.1.0",
94
95
  "get-stream": "^3.0.0",
@@ -126,7 +127,8 @@
126
127
  },
127
128
  "browser": {
128
129
  "./lib/adapters/http.js": "./lib/helpers/null.js",
129
- "./lib/platform/node/index.js": "./lib/platform/browser/index.js"
130
+ "./lib/platform/node/index.js": "./lib/platform/browser/index.js",
131
+ "./lib/platform/node/classes/FormData.js": "./lib/helpers/null.js"
130
132
  },
131
133
  "jsdelivr": "dist/axios.min.js",
132
134
  "unpkg": "dist/axios.min.js",
@@ -165,7 +167,8 @@
165
167
  "push": true,
166
168
  "commit": true,
167
169
  "tag": true,
168
- "requireCommits": false
170
+ "requireCommits": false,
171
+ "requireCleanWorkingDir": false
169
172
  },
170
173
  "github": {
171
174
  "release": true,
@@ -184,12 +187,19 @@
184
187
  },
185
188
  "hooks": {
186
189
  "before:init": "npm test",
187
- "after:bump": "gulp version --bump ${version} && npm run build && npm run test:build:version && git add ./dist",
190
+ "after:bump": "gulp version --bump ${version} && npm run build && npm run test:build:version && git add ./dist && git add ./package-lock.json",
188
191
  "before:release": "npm run release:changelog:fix",
189
192
  "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}."
190
193
  }
191
194
  },
192
195
  "commitlint": {
196
+ "rules": {
197
+ "header-max-length": [
198
+ 2,
199
+ "always",
200
+ 130
201
+ ]
202
+ },
193
203
  "extends": [
194
204
  "@commitlint/config-conventional"
195
205
  ]