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.
Files changed (44) hide show
  1. package/CHANGELOG.md +103 -6
  2. package/README.md +396 -25
  3. package/dist/axios.js +1455 -1109
  4. package/dist/axios.js.map +1 -1
  5. package/dist/axios.min.js +3 -3
  6. package/dist/axios.min.js.map +1 -1
  7. package/dist/browser/axios.cjs +1569 -1174
  8. package/dist/browser/axios.cjs.map +1 -1
  9. package/dist/esm/axios.js +1569 -1173
  10. package/dist/esm/axios.js.map +1 -1
  11. package/dist/esm/axios.min.js +2 -2
  12. package/dist/esm/axios.min.js.map +1 -1
  13. package/dist/node/axios.cjs +1395 -915
  14. package/dist/node/axios.cjs.map +1 -1
  15. package/index.d.cts +25 -13
  16. package/index.d.ts +21 -4
  17. package/index.js +2 -0
  18. package/lib/adapters/adapters.js +4 -2
  19. package/lib/adapters/fetch.js +131 -11
  20. package/lib/adapters/http.js +298 -69
  21. package/lib/adapters/xhr.js +8 -3
  22. package/lib/core/Axios.js +7 -3
  23. package/lib/core/AxiosError.js +86 -1
  24. package/lib/core/AxiosHeaders.js +4 -33
  25. package/lib/core/dispatchRequest.js +19 -7
  26. package/lib/core/mergeConfig.js +6 -3
  27. package/lib/core/settle.js +7 -11
  28. package/lib/defaults/index.js +1 -1
  29. package/lib/env/data.js +1 -1
  30. package/lib/helpers/buildURL.js +1 -1
  31. package/lib/helpers/composeSignals.js +48 -47
  32. package/lib/helpers/cookies.js +14 -2
  33. package/lib/helpers/estimateDataURLDecodedBytes.js +28 -1
  34. package/lib/helpers/formDataToJSON.js +1 -1
  35. package/lib/helpers/formDataToStream.js +1 -1
  36. package/lib/helpers/fromDataURI.js +18 -5
  37. package/lib/helpers/parseProtocol.js +1 -1
  38. package/lib/helpers/progressEventReducer.js +3 -0
  39. package/lib/helpers/resolveConfig.js +33 -17
  40. package/lib/helpers/sanitizeHeaderValue.js +60 -0
  41. package/lib/helpers/shouldBypassProxy.js +26 -1
  42. package/lib/helpers/validator.js +1 -1
  43. package/lib/utils.js +35 -22
  44. package/package.json +19 -24
@@ -1,6 +1,76 @@
1
1
  'use strict';
2
2
 
3
3
  import utils from '../utils.js';
4
+ import AxiosHeaders from './AxiosHeaders.js';
5
+
6
+ const REDACTED = '[REDACTED ****]';
7
+
8
+ function hasOwnOrPrototypeToJSON(source) {
9
+ if (utils.hasOwnProp(source, 'toJSON')) {
10
+ return true;
11
+ }
12
+
13
+ let prototype = Object.getPrototypeOf(source);
14
+
15
+ while (prototype && prototype !== Object.prototype) {
16
+ if (utils.hasOwnProp(prototype, 'toJSON')) {
17
+ return true;
18
+ }
19
+
20
+ prototype = Object.getPrototypeOf(prototype);
21
+ }
22
+
23
+ return false;
24
+ }
25
+
26
+ // Build a plain-object snapshot of `config` and replace the value of any key
27
+ // (case-insensitive) listed in `redactKeys` with REDACTED. Walks through arrays
28
+ // and AxiosHeaders, and short-circuits on circular references.
29
+ function redactConfig(config, redactKeys) {
30
+ const lowerKeys = new Set(redactKeys.map((k) => String(k).toLowerCase()));
31
+ const seen = [];
32
+
33
+ const visit = (source) => {
34
+ if (source === null || typeof source !== 'object') return source;
35
+ if (utils.isBuffer(source)) return source;
36
+ if (seen.indexOf(source) !== -1) return undefined;
37
+
38
+ if (source instanceof AxiosHeaders) {
39
+ source = source.toJSON();
40
+ }
41
+
42
+ seen.push(source);
43
+
44
+ let result;
45
+ if (utils.isArray(source)) {
46
+ result = [];
47
+ source.forEach((v, i) => {
48
+ const reducedValue = visit(v);
49
+ if (!utils.isUndefined(reducedValue)) {
50
+ result[i] = reducedValue;
51
+ }
52
+ });
53
+ } else {
54
+ if (!utils.isPlainObject(source) && hasOwnOrPrototypeToJSON(source)) {
55
+ seen.pop();
56
+ return source;
57
+ }
58
+
59
+ result = Object.create(null);
60
+ for (const [key, value] of Object.entries(source)) {
61
+ const reducedValue = lowerKeys.has(key.toLowerCase()) ? REDACTED : visit(value);
62
+ if (!utils.isUndefined(reducedValue)) {
63
+ result[key] = reducedValue;
64
+ }
65
+ }
66
+ }
67
+
68
+ seen.pop();
69
+ return result;
70
+ };
71
+
72
+ return visit(config);
73
+ }
4
74
 
5
75
  class AxiosError extends Error {
6
76
  static from(error, code, config, request, response, customProps) {
@@ -35,6 +105,9 @@ class AxiosError extends Error {
35
105
  // The native Error constructor sets message as non-enumerable,
36
106
  // but axios < v1.13.3 had it as enumerable
37
107
  Object.defineProperty(this, 'message', {
108
+ // Null-proto descriptor so a polluted Object.prototype.get cannot turn
109
+ // this data descriptor into an accessor descriptor on the way in.
110
+ __proto__: null,
38
111
  value: message,
39
112
  enumerable: true,
40
113
  writable: true,
@@ -53,6 +126,17 @@ class AxiosError extends Error {
53
126
  }
54
127
 
55
128
  toJSON() {
129
+ // Opt-in redaction: when the request config carries a `redact` array, the
130
+ // value of any matching key (case-insensitive, at any depth) is replaced
131
+ // with REDACTED in the serialized snapshot. Undefined or empty leaves the
132
+ // existing serialization behavior unchanged.
133
+ const config = this.config;
134
+ const redactKeys = config && utils.hasOwnProp(config, 'redact') ? config.redact : undefined;
135
+ const serializedConfig =
136
+ utils.isArray(redactKeys) && redactKeys.length > 0
137
+ ? redactConfig(config, redactKeys)
138
+ : utils.toJSONObject(config);
139
+
56
140
  return {
57
141
  // Standard
58
142
  message: this.message,
@@ -66,7 +150,7 @@ class AxiosError extends Error {
66
150
  columnNumber: this.columnNumber,
67
151
  stack: this.stack,
68
152
  // Axios
69
- config: utils.toJSONObject(this.config),
153
+ config: serializedConfig,
70
154
  code: this.code,
71
155
  status: this.status,
72
156
  };
@@ -78,6 +162,7 @@ AxiosError.ERR_BAD_OPTION_VALUE = 'ERR_BAD_OPTION_VALUE';
78
162
  AxiosError.ERR_BAD_OPTION = 'ERR_BAD_OPTION';
79
163
  AxiosError.ECONNABORTED = 'ECONNABORTED';
80
164
  AxiosError.ETIMEDOUT = 'ETIMEDOUT';
165
+ AxiosError.ECONNREFUSED = 'ECONNREFUSED';
81
166
  AxiosError.ERR_NETWORK = 'ERR_NETWORK';
82
167
  AxiosError.ERR_FR_TOO_MANY_REDIRECTS = 'ERR_FR_TOO_MANY_REDIRECTS';
83
168
  AxiosError.ERR_DEPRECATED = 'ERR_DEPRECATED';
@@ -2,46 +2,14 @@
2
2
 
3
3
  import utils from '../utils.js';
4
4
  import parseHeaders from '../helpers/parseHeaders.js';
5
+ import { sanitizeHeaderValue } from '../helpers/sanitizeHeaderValue.js';
5
6
 
6
7
  const $internals = Symbol('internals');
7
8
 
8
- const INVALID_HEADER_VALUE_CHARS_RE = /[^\x09\x20-\x7E\x80-\xFF]/g;
9
-
10
- function trimSPorHTAB(str) {
11
- let start = 0;
12
- let end = str.length;
13
-
14
- while (start < end) {
15
- const code = str.charCodeAt(start);
16
-
17
- if (code !== 0x09 && code !== 0x20) {
18
- break;
19
- }
20
-
21
- start += 1;
22
- }
23
-
24
- while (end > start) {
25
- const code = str.charCodeAt(end - 1);
26
-
27
- if (code !== 0x09 && code !== 0x20) {
28
- break;
29
- }
30
-
31
- end -= 1;
32
- }
33
-
34
- return start === 0 && end === str.length ? str : str.slice(start, end);
35
- }
36
-
37
9
  function normalizeHeader(header) {
38
10
  return header && String(header).trim().toLowerCase();
39
11
  }
40
12
 
41
- function sanitizeHeaderValue(str) {
42
- return trimSPorHTAB(str.replace(INVALID_HEADER_VALUE_CHARS_RE, ''));
43
- }
44
-
45
13
  function normalizeValue(value) {
46
14
  if (value === false || value == null) {
47
15
  return value;
@@ -98,6 +66,9 @@ function buildAccessors(obj, header) {
98
66
 
99
67
  ['get', 'set', 'has'].forEach((methodName) => {
100
68
  Object.defineProperty(obj, methodName + accessorName, {
69
+ // Null-proto descriptor so a polluted Object.prototype.get cannot turn
70
+ // this data descriptor into an accessor descriptor on the way in.
71
+ __proto__: null,
101
72
  value: function (arg1, arg2, arg3) {
102
73
  return this[methodName].call(this, header, arg1, arg2, arg3);
103
74
  },
@@ -49,8 +49,15 @@ export default function dispatchRequest(config) {
49
49
  function onAdapterResolution(response) {
50
50
  throwIfCancellationRequested(config);
51
51
 
52
- // Transform response data
53
- response.data = transformData.call(config, config.transformResponse, response);
52
+ // Expose the current response on config so that transformResponse can
53
+ // attach it to any AxiosError it throws (e.g. on JSON parse failure).
54
+ // We clean it up afterwards to avoid polluting the config object.
55
+ config.response = response;
56
+ try {
57
+ response.data = transformData.call(config, config.transformResponse, response);
58
+ } finally {
59
+ delete config.response;
60
+ }
54
61
 
55
62
  response.headers = AxiosHeaders.from(response.headers);
56
63
 
@@ -62,11 +69,16 @@ export default function dispatchRequest(config) {
62
69
 
63
70
  // Transform response data
64
71
  if (reason && reason.response) {
65
- reason.response.data = transformData.call(
66
- config,
67
- config.transformResponse,
68
- reason.response
69
- );
72
+ config.response = reason.response;
73
+ try {
74
+ reason.response.data = transformData.call(
75
+ config,
76
+ config.transformResponse,
77
+ reason.response
78
+ );
79
+ } finally {
80
+ delete config.response;
81
+ }
70
82
  reason.response.headers = AxiosHeaders.from(reason.response.headers);
71
83
  }
72
84
  }
@@ -19,11 +19,14 @@ export default function mergeConfig(config1, config2) {
19
19
  config2 = config2 || {};
20
20
 
21
21
  // Use a null-prototype object so that downstream reads such as `config.auth`
22
- // or `config.baseURL` cannot inherit polluted values from Object.prototype
23
- // (see GHSA-q8qp-cvcw-x6jj). `hasOwnProperty` is restored as a non-enumerable
24
- // own slot to preserve ergonomics for user code that relies on it.
22
+ // or `config.baseURL` cannot inherit polluted values from Object.prototype.
23
+ // `hasOwnProperty` is restored as a non-enumerable own slot to preserve
24
+ // ergonomics for user code that relies on it.
25
25
  const config = Object.create(null);
26
26
  Object.defineProperty(config, 'hasOwnProperty', {
27
+ // Null-proto descriptor so a polluted Object.prototype.get cannot turn
28
+ // this data descriptor into an accessor descriptor on the way in.
29
+ __proto__: null,
27
30
  value: Object.prototype.hasOwnProperty,
28
31
  enumerable: false,
29
32
  writable: true,
@@ -16,16 +16,12 @@ export default function settle(resolve, reject, response) {
16
16
  if (!response.status || !validateStatus || validateStatus(response.status)) {
17
17
  resolve(response);
18
18
  } else {
19
- reject(
20
- new AxiosError(
21
- 'Request failed with status code ' + response.status,
22
- [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][
23
- Math.floor(response.status / 100) - 4
24
- ],
25
- response.config,
26
- response.request,
27
- response
28
- )
29
- );
19
+ reject(new AxiosError(
20
+ 'Request failed with status code ' + response.status,
21
+ response.status >= 400 && response.status < 500 ? AxiosError.ERR_BAD_REQUEST : AxiosError.ERR_BAD_RESPONSE,
22
+ response.config,
23
+ response.request,
24
+ response
25
+ ));
30
26
  }
31
27
  }
@@ -170,7 +170,7 @@ const defaults = {
170
170
  },
171
171
  };
172
172
 
173
- utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch'], (method) => {
173
+ utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'query'], (method) => {
174
174
  defaults.headers[method] = {};
175
175
  });
176
176
 
package/lib/env/data.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "1.15.2";
1
+ export const VERSION = "1.16.1";
@@ -11,7 +11,7 @@ import AxiosURLSearchParams from '../helpers/AxiosURLSearchParams.js';
11
11
  *
12
12
  * @returns {string} The encoded value.
13
13
  */
14
- function encode(val) {
14
+ export function encode(val) {
15
15
  return encodeURIComponent(val)
16
16
  .replace(/%3A/gi, ':')
17
17
  .replace(/%24/g, '$')
@@ -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;
@@ -30,8 +30,20 @@ export default platform.hasStandardBrowserEnv
30
30
 
31
31
  read(name) {
32
32
  if (typeof document === 'undefined') return null;
33
- const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
34
- return match ? decodeURIComponent(match[1]) : null;
33
+ // Match name=value by splitting on the semicolon separator instead of building a
34
+ // RegExp from `name` interpolating an unescaped string into a RegExp would let
35
+ // metacharacters (e.g. `.+?` in an attacker-influenced cookie name) cause ReDoS or
36
+ // match the wrong cookie. Browsers may serialize cookie pairs as either ";" or
37
+ // "; ", so ignore optional whitespace before each cookie name.
38
+ const cookies = document.cookie.split(';');
39
+ for (let i = 0; i < cookies.length; i++) {
40
+ const cookie = cookies[i].replace(/^\s+/, '');
41
+ const eq = cookie.indexOf('=');
42
+ if (eq !== -1 && cookie.slice(0, eq) === name) {
43
+ return decodeURIComponent(cookie.slice(eq + 1));
44
+ }
45
+ }
46
+ return null;
35
47
  },
36
48
 
37
49
  remove(name) {
@@ -69,5 +69,32 @@ export default function estimateDataURLDecodedBytes(url) {
69
69
  return bytes > 0 ? bytes : 0;
70
70
  }
71
71
 
72
- return Buffer.byteLength(body, 'utf8');
72
+ if (typeof Buffer !== 'undefined' && typeof Buffer.byteLength === 'function') {
73
+ return Buffer.byteLength(body, 'utf8');
74
+ }
75
+
76
+ // Compute UTF-8 byte length directly from UTF-16 code units without allocating
77
+ // a byte buffer (TextEncoder.encode would defeat the DoS guard on large bodies).
78
+ // Using body.length here would undercount non-ASCII (e.g. '€' is 1 code unit
79
+ // but 3 UTF-8 bytes).
80
+ let bytes = 0;
81
+ for (let i = 0, len = body.length; i < len; i++) {
82
+ const c = body.charCodeAt(i);
83
+ if (c < 0x80) {
84
+ bytes += 1;
85
+ } else if (c < 0x800) {
86
+ bytes += 2;
87
+ } else if (c >= 0xd800 && c <= 0xdbff && i + 1 < len) {
88
+ const next = body.charCodeAt(i + 1);
89
+ if (next >= 0xdc00 && next <= 0xdfff) {
90
+ bytes += 4;
91
+ i++;
92
+ } else {
93
+ bytes += 3;
94
+ }
95
+ } else {
96
+ bytes += 3;
97
+ }
98
+ }
99
+ return bytes;
73
100
  }
@@ -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
 
@@ -77,7 +77,7 @@ const formDataToStream = (form, headersHandler, options) => {
77
77
  }
78
78
 
79
79
  if (boundary.length < 1 || boundary.length > 70) {
80
- throw Error('boundary must be 10-70 characters long');
80
+ throw 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) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  export default function parseProtocol(url) {
4
- const match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url);
4
+ const match = /^([-+\w]{1,25}):(?:\/\/)?/.exec(url);
5
5
  return (match && match[1]) || '';
6
6
  }
@@ -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;
@@ -7,11 +7,39 @@ import mergeConfig from '../core/mergeConfig.js';
7
7
  import AxiosHeaders from '../core/AxiosHeaders.js';
8
8
  import buildURL from './buildURL.js';
9
9
 
10
+ const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length'];
11
+
12
+ function setFormDataHeaders(headers, formHeaders, policy) {
13
+ if (policy !== 'content-only') {
14
+ headers.set(formHeaders);
15
+ return;
16
+ }
17
+
18
+ Object.entries(formHeaders).forEach(([key, val]) => {
19
+ if (FORM_DATA_CONTENT_HEADERS.includes(key.toLowerCase())) {
20
+ headers.set(key, val);
21
+ }
22
+ });
23
+ }
24
+
25
+ /**
26
+ * Encode a UTF-8 string to a Latin-1 byte string for use with btoa().
27
+ * This is a modern replacement for the deprecated unescape(encodeURIComponent(str)) pattern.
28
+ *
29
+ * @param {string} str The string to encode
30
+ *
31
+ * @returns {string} UTF-8 bytes as a Latin-1 string
32
+ */
33
+ const encodeUTF8 = (str) =>
34
+ encodeURIComponent(str).replace(/%([0-9A-F]{2})/gi, (_, hex) =>
35
+ String.fromCharCode(parseInt(hex, 16))
36
+ );
37
+
10
38
  export default (config) => {
11
39
  const newConfig = mergeConfig({}, config);
12
40
 
13
41
  // Read only own properties to prevent prototype pollution gadgets
14
- // (e.g. Object.prototype.baseURL = 'https://evil.com'). See GHSA-q8qp-cvcw-x6jj.
42
+ // (e.g. Object.prototype.baseURL = 'https://evil.com').
15
43
  const own = (key) => (utils.hasOwnProp(newConfig, key) ? newConfig[key] : undefined);
16
44
 
17
45
  const data = own('data');
@@ -37,11 +65,7 @@ export default (config) => {
37
65
  headers.set(
38
66
  'Authorization',
39
67
  'Basic ' +
40
- btoa(
41
- (auth.username || '') +
42
- ':' +
43
- (auth.password ? unescape(encodeURIComponent(auth.password)) : '')
44
- )
68
+ btoa((auth.username || '') + ':' + (auth.password ? encodeUTF8(auth.password) : ''))
45
69
  );
46
70
  }
47
71
 
@@ -50,14 +74,7 @@ export default (config) => {
50
74
  headers.setContentType(undefined); // browser handles it
51
75
  } else if (utils.isFunction(data.getHeaders)) {
52
76
  // Node.js FormData (like form-data package)
53
- const formHeaders = data.getHeaders();
54
- // Only set safe headers to avoid overwriting security headers
55
- const allowedHeaders = ['content-type', 'content-length'];
56
- Object.entries(formHeaders).forEach(([key, val]) => {
57
- if (allowedHeaders.includes(key.toLowerCase())) {
58
- headers.set(key, val);
59
- }
60
- });
77
+ setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
61
78
  }
62
79
  }
63
80
 
@@ -72,10 +89,9 @@ export default (config) => {
72
89
 
73
90
  // Strict boolean check — prevents proto-pollution gadgets (e.g. Object.prototype.withXSRFToken = 1)
74
91
  // and misconfigurations (e.g. "false") from short-circuiting the same-origin check and leaking
75
- // the XSRF token cross-origin. See GHSA-xx6v-rp6x-q39c.
92
+ // the XSRF token cross-origin.
76
93
  const shouldSendXSRF =
77
- withXSRFToken === true ||
78
- (withXSRFToken == null && isURLSameOrigin(newConfig.url));
94
+ withXSRFToken === true || (withXSRFToken == null && isURLSameOrigin(newConfig.url));
79
95
 
80
96
  if (shouldSendXSRF) {
81
97
  const xsrfValue = xsrfHeaderName && xsrfCookieName && cookies.read(xsrfCookieName);
@@ -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
+ }