axios 1.18.0 → 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.
- package/CHANGELOG.md +32 -0
- package/README.md +192 -23
- package/dist/axios.js +410 -101
- package/dist/axios.min.js +3 -3
- package/dist/axios.min.js.map +1 -1
- package/dist/browser/axios.cjs +484 -119
- package/dist/esm/axios.js +484 -119
- package/dist/esm/axios.min.js +2 -2
- package/dist/esm/axios.min.js.map +1 -1
- package/dist/node/axios.cjs +589 -139
- package/index.d.cts +114 -74
- package/index.d.ts +112 -68
- package/lib/adapters/adapters.js +1 -1
- package/lib/adapters/fetch.js +27 -12
- package/lib/adapters/http.js +95 -34
- package/lib/adapters/xhr.js +1 -0
- package/lib/core/Axios.js +24 -6
- package/lib/core/AxiosError.js +46 -3
- package/lib/core/AxiosHeaders.js +124 -1
- package/lib/core/buildFullPath.js +45 -7
- package/lib/core/mergeConfig.js +19 -3
- package/lib/core/setFormDataHeaders.js +27 -0
- package/lib/env/data.js +1 -1
- package/lib/helpers/AxiosURLSearchParams.js +1 -3
- package/lib/helpers/HttpStatusCode.js +1 -0
- package/lib/helpers/buildURL.js +1 -0
- package/lib/helpers/combineURLs.js +11 -3
- package/lib/helpers/composeSignals.js +12 -1
- package/lib/helpers/cookies.js +5 -1
- package/lib/helpers/estimateDataURLDecodedBytes.js +115 -54
- package/lib/helpers/formDataToJSON.js +11 -5
- package/lib/helpers/fromDataURI.js +4 -2
- package/lib/helpers/parseHeaders.js +5 -3
- package/lib/helpers/progressEventReducer.js +3 -3
- package/lib/helpers/resolveConfig.js +10 -19
- package/lib/helpers/shouldBypassProxy.js +120 -1
- package/lib/helpers/toFormData.js +8 -1
- package/lib/helpers/validator.js +1 -1
- package/lib/platform/node/classes/Buffer.js +11 -0
- package/lib/utils.js +17 -5
- package/package.json +22 -20
package/lib/adapters/xhr.js
CHANGED
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
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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;
|
package/lib/core/AxiosError.js
CHANGED
|
@@ -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,10 +72,53 @@ 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
|
-
|
|
78
|
-
|
|
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);
|
|
109
|
+
// Match native `Error` `cause` semantics: non-enumerable. The wrapped
|
|
110
|
+
// error often carries circular internals (sockets, requests, agents), so
|
|
111
|
+
// an enumerable `cause` makes structured loggers (pino/winston) and any
|
|
112
|
+
// own-property walk throw "Converting circular structure to JSON".
|
|
113
|
+
// Regression from #6982; see #7205. `__proto__: null` mirrors the
|
|
114
|
+
// `message` descriptor below (prototype-pollution-safe descriptor).
|
|
115
|
+
Object.defineProperty(axiosError, 'cause', {
|
|
116
|
+
__proto__: null,
|
|
117
|
+
value: error,
|
|
118
|
+
writable: true,
|
|
119
|
+
enumerable: false,
|
|
120
|
+
configurable: true,
|
|
121
|
+
});
|
|
79
122
|
axiosError.name = error.name;
|
|
80
123
|
|
|
81
124
|
// Preserve status from the original error if not already set from response
|
package/lib/core/AxiosHeaders.js
CHANGED
|
@@ -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
|
-
|
|
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'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
AxiosError
|
|
27
|
-
|
|
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
|
|
package/lib/core/mergeConfig.js
CHANGED
|
@@ -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.
|
|
@@ -16,6 +27,7 @@ const headersToObject = (thing) => (thing instanceof AxiosHeaders ? { ...thing }
|
|
|
16
27
|
*/
|
|
17
28
|
export default function mergeConfig(config1, config2) {
|
|
18
29
|
// eslint-disable-next-line no-param-reassign
|
|
30
|
+
config1 = config1 || {};
|
|
19
31
|
config2 = config2 || {};
|
|
20
32
|
|
|
21
33
|
// Use a null-prototype object so that downstream reads such as `config.auth`
|
|
@@ -69,7 +81,9 @@ export default function mergeConfig(config1, config2) {
|
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
function getMergedTransitionalOption(prop) {
|
|
72
|
-
const transitional2 = utils.hasOwnProp(config2, 'transitional')
|
|
84
|
+
const transitional2 = utils.hasOwnProp(config2, 'transitional')
|
|
85
|
+
? config2.transitional
|
|
86
|
+
: undefined;
|
|
73
87
|
|
|
74
88
|
if (!utils.isUndefined(transitional2)) {
|
|
75
89
|
if (utils.isPlainObject(transitional2)) {
|
|
@@ -81,7 +95,9 @@ export default function mergeConfig(config1, config2) {
|
|
|
81
95
|
}
|
|
82
96
|
}
|
|
83
97
|
|
|
84
|
-
const transitional1 = utils.hasOwnProp(config1, 'transitional')
|
|
98
|
+
const transitional1 = utils.hasOwnProp(config1, 'transitional')
|
|
99
|
+
? config1.transitional
|
|
100
|
+
: undefined;
|
|
85
101
|
|
|
86
102
|
if (utils.isPlainObject(transitional1) && utils.hasOwnProp(transitional1, prop)) {
|
|
87
103
|
return transitional1[prop];
|
|
@@ -133,7 +149,7 @@ export default function mergeConfig(config1, config2) {
|
|
|
133
149
|
mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true),
|
|
134
150
|
};
|
|
135
151
|
|
|
136
|
-
utils.forEach(
|
|
152
|
+
utils.forEach(ownEnumerableKeys({ ...config1, ...config2 }), function computeConfigValue(prop) {
|
|
137
153
|
if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') return;
|
|
138
154
|
const merge = utils.hasOwnProp(mergeMap, prop) ? mergeMap[prop] : mergeDeepProperties;
|
|
139
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.
|
|
1
|
+
export const VERSION = "1.19.0";
|
|
@@ -46,9 +46,7 @@ prototype.append = function append(name, value) {
|
|
|
46
46
|
|
|
47
47
|
prototype.toString = function toString(encoder) {
|
|
48
48
|
const _encode = encoder
|
|
49
|
-
?
|
|
50
|
-
return encoder.call(this, value, encode);
|
|
51
|
-
}
|
|
49
|
+
? (value) => encoder.call(this, value, encode)
|
|
52
50
|
: encode;
|
|
53
51
|
|
|
54
52
|
return this._pairs
|
package/lib/helpers/buildURL.js
CHANGED
|
@@ -9,7 +9,15 @@
|
|
|
9
9
|
* @returns {string} The combined URL
|
|
10
10
|
*/
|
|
11
11
|
export default function combineURLs(baseURL, relativeURL) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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) =>
|
|
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
|
|
package/lib/helpers/cookies.js
CHANGED
|
@@ -40,7 +40,11 @@ export default platform.hasStandardBrowserEnv
|
|
|
40
40
|
const cookie = cookies[i].replace(/^\s+/, '');
|
|
41
41
|
const eq = cookie.indexOf('=');
|
|
42
42
|
if (eq !== -1 && cookie.slice(0, eq) === name) {
|
|
43
|
-
|
|
43
|
+
try {
|
|
44
|
+
return decodeURIComponent(cookie.slice(eq + 1));
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return cookie.slice(eq + 1);
|
|
47
|
+
}
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
return null;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Estimate
|
|
3
|
-
* -
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @param {string} url
|
|
8
|
-
* @returns {number}
|
|
2
|
+
* Estimate data: URL byte lengths *without* allocating large buffers.
|
|
3
|
+
* - Fetch percent-decodes a base64 body before decoding it.
|
|
4
|
+
* - Node's Buffer.from(body, 'base64') sizes its backing allocation from the
|
|
5
|
+
* raw body, including ignored characters and content after padding.
|
|
6
|
+
* - Non-base64 data is percent-decoded and then encoded as UTF-8.
|
|
9
7
|
*/
|
|
10
8
|
const isHexDigit = (charCode) =>
|
|
11
9
|
(charCode >= 48 && charCode <= 57) ||
|
|
@@ -15,7 +13,89 @@ const isHexDigit = (charCode) =>
|
|
|
15
13
|
const isPercentEncodedByte = (str, i, len) =>
|
|
16
14
|
i + 2 < len && isHexDigit(str.charCodeAt(i + 1)) && isHexDigit(str.charCodeAt(i + 2));
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
const hexValue = (charCode) => (charCode <= 57 ? charCode - 48 : (charCode & 0xdf) - 55);
|
|
17
|
+
|
|
18
|
+
const isBase64Char = (charCode) =>
|
|
19
|
+
(charCode >= 65 && charCode <= 90) || // A-Z
|
|
20
|
+
(charCode >= 97 && charCode <= 122) || // a-z
|
|
21
|
+
(charCode >= 48 && charCode <= 57) || // 0-9
|
|
22
|
+
charCode === 43 || // +
|
|
23
|
+
charCode === 47 || // /
|
|
24
|
+
charCode === 45 || // - (base64url)
|
|
25
|
+
charCode === 95; // _ (base64url)
|
|
26
|
+
|
|
27
|
+
const isBase64Whitespace = (charCode) =>
|
|
28
|
+
charCode === 9 || charCode === 10 || charCode === 12 || charCode === 13 || charCode === 32;
|
|
29
|
+
|
|
30
|
+
const base64Bytes = (significant) => {
|
|
31
|
+
const groups = Math.floor(significant / 4);
|
|
32
|
+
const remainder = significant % 4;
|
|
33
|
+
return groups * 3 + (remainder === 2 ? 1 : remainder === 3 ? 2 : 0);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Buffer.byteLength(body, 'base64') uses the raw string length as an allocation
|
|
37
|
+
// upper bound even when Buffer.from later ignores characters or stops at '='.
|
|
38
|
+
const estimateBase64BufferAllocation = (body) => {
|
|
39
|
+
const len = body.length;
|
|
40
|
+
let padding = 0;
|
|
41
|
+
|
|
42
|
+
if (len > 0 && body.charCodeAt(len - 1) === 61 /* '=' */) {
|
|
43
|
+
padding++;
|
|
44
|
+
|
|
45
|
+
if (len > 1 && body.charCodeAt(len - 2) === 61 /* '=' */) {
|
|
46
|
+
padding++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return Math.floor(((len - padding) * 3) / 4);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const estimatePercentDecodedBase64Bytes = (body) => {
|
|
54
|
+
const len = body.length;
|
|
55
|
+
let significant = 0;
|
|
56
|
+
let padding = 0;
|
|
57
|
+
let invalid = false;
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < len; i++) {
|
|
60
|
+
let code = body.charCodeAt(i);
|
|
61
|
+
|
|
62
|
+
if (code === 37 /* '%' */ && isPercentEncodedByte(body, i, len)) {
|
|
63
|
+
code = hexValue(body.charCodeAt(i + 1)) * 16 + hexValue(body.charCodeAt(i + 2));
|
|
64
|
+
i += 2;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (isBase64Whitespace(code)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (code === 61 /* '=' */) {
|
|
72
|
+
padding++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!isBase64Char(code) || padding > 0) {
|
|
77
|
+
invalid = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
significant++;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Fetch rejects malformed forgiving-base64 input. Returning the raw-size
|
|
85
|
+
// allocation bound keeps that invalid input from becoming a pre-check bypass.
|
|
86
|
+
if (
|
|
87
|
+
invalid ||
|
|
88
|
+
padding > 2 ||
|
|
89
|
+
(padding > 0 && (significant + padding) % 4 !== 0) ||
|
|
90
|
+
significant % 4 === 1
|
|
91
|
+
) {
|
|
92
|
+
return estimateBase64BufferAllocation(body);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return base64Bytes(significant);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const estimateDataURLBytes = (url, estimateBase64) => {
|
|
19
99
|
if (!url || typeof url !== 'string') return 0;
|
|
20
100
|
if (!url.startsWith('data:')) return 0;
|
|
21
101
|
|
|
@@ -27,52 +107,7 @@ export default function estimateDataURLDecodedBytes(url) {
|
|
|
27
107
|
const isBase64 = /;base64/i.test(meta);
|
|
28
108
|
|
|
29
109
|
if (isBase64) {
|
|
30
|
-
|
|
31
|
-
const len = body.length; // cache length
|
|
32
|
-
|
|
33
|
-
for (let i = 0; i < len; i++) {
|
|
34
|
-
if (body.charCodeAt(i) === 37 /* '%' */ && i + 2 < len) {
|
|
35
|
-
const a = body.charCodeAt(i + 1);
|
|
36
|
-
const b = body.charCodeAt(i + 2);
|
|
37
|
-
const isHex = isHexDigit(a) && isHexDigit(b);
|
|
38
|
-
|
|
39
|
-
if (isHex) {
|
|
40
|
-
effectiveLen -= 2;
|
|
41
|
-
i += 2;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
let pad = 0;
|
|
47
|
-
let idx = len - 1;
|
|
48
|
-
|
|
49
|
-
const tailIsPct3D = (j) =>
|
|
50
|
-
j >= 2 &&
|
|
51
|
-
body.charCodeAt(j - 2) === 37 && // '%'
|
|
52
|
-
body.charCodeAt(j - 1) === 51 && // '3'
|
|
53
|
-
(body.charCodeAt(j) === 68 || body.charCodeAt(j) === 100); // 'D' or 'd'
|
|
54
|
-
|
|
55
|
-
if (idx >= 0) {
|
|
56
|
-
if (body.charCodeAt(idx) === 61 /* '=' */) {
|
|
57
|
-
pad++;
|
|
58
|
-
idx--;
|
|
59
|
-
} else if (tailIsPct3D(idx)) {
|
|
60
|
-
pad++;
|
|
61
|
-
idx -= 3;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (pad === 1 && idx >= 0) {
|
|
66
|
-
if (body.charCodeAt(idx) === 61 /* '=' */) {
|
|
67
|
-
pad++;
|
|
68
|
-
} else if (tailIsPct3D(idx)) {
|
|
69
|
-
pad++;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const groups = Math.floor(effectiveLen / 4);
|
|
74
|
-
const bytes = groups * 3 - (pad || 0);
|
|
75
|
-
return bytes > 0 ? bytes : 0;
|
|
110
|
+
return estimateBase64(body);
|
|
76
111
|
}
|
|
77
112
|
|
|
78
113
|
// Compute UTF-8 byte length directly from UTF-16 code units without allocating
|
|
@@ -102,4 +137,30 @@ export default function estimateDataURLDecodedBytes(url) {
|
|
|
102
137
|
}
|
|
103
138
|
}
|
|
104
139
|
return bytes;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Estimate the percent-decoded payload size used by Fetch data: URLs.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} url
|
|
146
|
+
* @returns {number}
|
|
147
|
+
*/
|
|
148
|
+
export default function estimateDataURLDecodedBytes(url) {
|
|
149
|
+
// Fetch removes URL fragments before processing a data: URL.
|
|
150
|
+
const fragmentIndex = typeof url === 'string' ? url.indexOf('#') : -1;
|
|
151
|
+
|
|
152
|
+
return estimateDataURLBytes(
|
|
153
|
+
fragmentIndex === -1 ? url : url.slice(0, fragmentIndex),
|
|
154
|
+
estimatePercentDecodedBase64Bytes
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Estimate the Buffer backing allocation used by Node's raw base64 decoder.
|
|
160
|
+
*
|
|
161
|
+
* @param {string} url
|
|
162
|
+
* @returns {number}
|
|
163
|
+
*/
|
|
164
|
+
export function estimateDataURLBufferAllocation(url) {
|
|
165
|
+
return estimateDataURLBytes(url, estimateBase64BufferAllocation);
|
|
105
166
|
}
|