got 15.0.0 → 15.0.2
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/dist/source/as-promise/index.js +88 -80
- package/dist/source/core/errors.js +3 -3
- package/dist/source/core/index.js +179 -85
- package/dist/source/core/options.d.ts +31 -26
- package/dist/source/core/options.js +39 -10
- package/dist/source/core/parse-link-header.js +52 -2
- package/dist/source/core/timed-out.js +1 -1
- package/dist/source/core/utils/is-unix-socket-url.d.ts +1 -1
- package/dist/source/core/utils/is-unix-socket-url.js +3 -4
- package/dist/source/create.js +6 -0
- package/dist/source/types.d.ts +6 -7
- package/package.json +1 -1
- package/readme.md +0 -3
|
@@ -24,97 +24,105 @@ export default function asPromise(firstRequest) {
|
|
|
24
24
|
request.retryCount = retryCount;
|
|
25
25
|
request._noPipe = true;
|
|
26
26
|
globalRequest = request;
|
|
27
|
-
request.once('response',
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
else {
|
|
36
|
-
try {
|
|
37
|
-
response.body = parseBody(response, options.responseType, options.parseJson, options.encoding);
|
|
27
|
+
request.once('response', (response) => {
|
|
28
|
+
void (async () => {
|
|
29
|
+
// Parse body
|
|
30
|
+
const contentEncoding = (response.headers['content-encoding'] ?? '').toLowerCase();
|
|
31
|
+
const isCompressed = compressedEncodings.has(contentEncoding);
|
|
32
|
+
const { options } = request;
|
|
33
|
+
if (isCompressed && !options.decompress) {
|
|
34
|
+
response.body = response.rawBody;
|
|
38
35
|
}
|
|
39
|
-
|
|
40
|
-
// Fall back to `utf8`
|
|
36
|
+
else {
|
|
41
37
|
try {
|
|
42
|
-
response.body =
|
|
38
|
+
response.body = parseBody(response, options.responseType, options.parseJson, options.encoding);
|
|
43
39
|
}
|
|
44
40
|
catch (error) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (isResponseOk(response)) {
|
|
49
|
-
request._beforeError(normalizeError(error));
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
const hooks = options.hooks.afterResponse;
|
|
56
|
-
for (const [index, hook] of hooks.entries()) {
|
|
57
|
-
const previousUrl = options.url ? new URL(options.url) : undefined;
|
|
58
|
-
const previousState = previousUrl ? snapshotCrossOriginState(options) : undefined;
|
|
59
|
-
const requestOptions = response.request.options;
|
|
60
|
-
const responseSnapshot = response;
|
|
61
|
-
// @ts-expect-error TS doesn't notice that RequestPromise is a Promise
|
|
62
|
-
// eslint-disable-next-line no-await-in-loop
|
|
63
|
-
response = await requestOptions.trackStateMutations(async (changedState) => hook(responseSnapshot, async (updatedOptions) => {
|
|
64
|
-
const preserveHooks = updatedOptions.preserveHooks ?? false;
|
|
65
|
-
const reusesRequestOptions = updatedOptions === requestOptions;
|
|
66
|
-
const hasExplicitBody = reusesRequestOptions
|
|
67
|
-
? changedState.has('body') || changedState.has('json') || changedState.has('form')
|
|
68
|
-
: (Object.hasOwn(updatedOptions, 'body') && updatedOptions.body !== undefined)
|
|
69
|
-
|| (Object.hasOwn(updatedOptions, 'json') && updatedOptions.json !== undefined)
|
|
70
|
-
|| (Object.hasOwn(updatedOptions, 'form') && updatedOptions.form !== undefined);
|
|
71
|
-
if (hasExplicitBody && !reusesRequestOptions) {
|
|
72
|
-
options.clearBody();
|
|
41
|
+
// Fall back to `utf8`
|
|
42
|
+
try {
|
|
43
|
+
response.body = decodeUint8Array(response.rawBody);
|
|
73
44
|
}
|
|
74
|
-
|
|
75
|
-
|
|
45
|
+
catch (error) {
|
|
46
|
+
request._beforeError(new ParseError(normalizeError(error), response));
|
|
47
|
+
return;
|
|
76
48
|
}
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
49
|
+
if (isResponseOk(response)) {
|
|
50
|
+
request._beforeError(normalizeError(error));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const hooks = options.hooks.afterResponse;
|
|
57
|
+
for (const [index, hook] of hooks.entries()) {
|
|
58
|
+
const previousUrl = options.url ? new URL(options.url) : undefined;
|
|
59
|
+
const previousState = previousUrl ? snapshotCrossOriginState(options) : undefined;
|
|
60
|
+
const requestOptions = response.request.options;
|
|
61
|
+
const responseSnapshot = response;
|
|
62
|
+
// @ts-expect-error TS doesn't notice that RequestPromise is a Promise
|
|
63
|
+
// eslint-disable-next-line no-await-in-loop
|
|
64
|
+
response = await requestOptions.trackStateMutations(async (changedState) => hook(responseSnapshot, async (updatedOptions) => {
|
|
65
|
+
const preserveHooks = updatedOptions.preserveHooks ?? false;
|
|
66
|
+
const reusesRequestOptions = updatedOptions === requestOptions;
|
|
67
|
+
const hasExplicitBody = reusesRequestOptions
|
|
68
|
+
? changedState.has('body') || changedState.has('json') || changedState.has('form')
|
|
69
|
+
: (Object.hasOwn(updatedOptions, 'body') && updatedOptions.body !== undefined)
|
|
70
|
+
|| (Object.hasOwn(updatedOptions, 'json') && updatedOptions.json !== undefined)
|
|
71
|
+
|| (Object.hasOwn(updatedOptions, 'form') && updatedOptions.form !== undefined);
|
|
72
|
+
const clearsCookieJar = Object.hasOwn(updatedOptions, 'cookieJar') && updatedOptions.cookieJar === undefined;
|
|
73
|
+
if (hasExplicitBody && !reusesRequestOptions) {
|
|
74
|
+
options.clearBody();
|
|
75
|
+
}
|
|
76
|
+
if (!reusesRequestOptions && clearsCookieJar) {
|
|
77
|
+
options.cookieJar = undefined;
|
|
78
|
+
}
|
|
79
|
+
if (!reusesRequestOptions) {
|
|
80
|
+
options.merge(updatedOptions);
|
|
81
|
+
options.syncCookieHeaderAfterMerge(previousState, updatedOptions.headers);
|
|
82
|
+
}
|
|
83
|
+
options.clearUnchangedCookieHeader(previousState, reusesRequestOptions ? changedState : undefined);
|
|
84
|
+
if (updatedOptions.url) {
|
|
85
|
+
const nextUrl = reusesRequestOptions
|
|
86
|
+
? options.url
|
|
87
|
+
: applyUrlOverride(options, updatedOptions.url, updatedOptions);
|
|
88
|
+
if (previousUrl) {
|
|
89
|
+
if (reusesRequestOptions && !isSameOrigin(previousUrl, nextUrl)) {
|
|
90
|
+
options.stripUnchangedCrossOriginState(previousState, changedState, { clearBody: !hasExplicitBody });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
options.stripSensitiveHeaders(previousUrl, nextUrl, updatedOptions);
|
|
94
|
+
if (!isSameOrigin(previousUrl, nextUrl) && !hasExplicitBody) {
|
|
95
|
+
options.clearBody();
|
|
96
|
+
}
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
99
|
}
|
|
100
|
+
// Remove any further hooks for that request, because we'll call them anyway.
|
|
101
|
+
// The loop continues. We don't want duplicates (asPromise recursion).
|
|
102
|
+
// Unless preserveHooks is true, in which case we keep the remaining hooks.
|
|
103
|
+
if (!preserveHooks) {
|
|
104
|
+
options.hooks.afterResponse = options.hooks.afterResponse.slice(0, index);
|
|
105
|
+
}
|
|
106
|
+
throw new RetryError(request);
|
|
107
|
+
}));
|
|
108
|
+
if (!(is.object(response) && is.number(response.statusCode) && 'body' in response)) {
|
|
109
|
+
throw new TypeError('The `afterResponse` hook returned an invalid value');
|
|
92
110
|
}
|
|
93
|
-
// Remove any further hooks for that request, because we'll call them anyway.
|
|
94
|
-
// The loop continues. We don't want duplicates (asPromise recursion).
|
|
95
|
-
// Unless preserveHooks is true, in which case we keep the remaining hooks.
|
|
96
|
-
if (!preserveHooks) {
|
|
97
|
-
options.hooks.afterResponse = options.hooks.afterResponse.slice(0, index);
|
|
98
|
-
}
|
|
99
|
-
throw new RetryError(request);
|
|
100
|
-
}));
|
|
101
|
-
if (!(is.object(response) && is.number(response.statusCode) && 'body' in response)) {
|
|
102
|
-
throw new TypeError('The `afterResponse` hook returned an invalid value');
|
|
103
111
|
}
|
|
104
112
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
catch (error) {
|
|
114
|
+
request._beforeError(normalizeError(error));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
globalResponse = response;
|
|
118
|
+
if (!isResponseOk(response)) {
|
|
119
|
+
request._beforeError(new HTTPError(response));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
request.destroy();
|
|
123
|
+
promiseSettled = true;
|
|
124
|
+
resolve(request.options.resolveBodyOnly ? response.body : response);
|
|
125
|
+
})();
|
|
118
126
|
});
|
|
119
127
|
let handledFinalError = false;
|
|
120
128
|
const onError = (error) => {
|
|
@@ -191,7 +199,7 @@ export default function asPromise(firstRequest) {
|
|
|
191
199
|
const { options } = globalResponse.request;
|
|
192
200
|
if (responseType === 'text') {
|
|
193
201
|
const text = decodeUint8Array(globalResponse.rawBody, options.encoding);
|
|
194
|
-
return (isUtf8Encoding(options.encoding) ? text.replace(/^\
|
|
202
|
+
return (isUtf8Encoding(options.encoding) ? text.replace(/^\u{FEFF}/v, '') : text);
|
|
195
203
|
}
|
|
196
204
|
return parseBody(globalResponse, responseType, options.parseJson, options.encoding);
|
|
197
205
|
})();
|
|
@@ -41,13 +41,13 @@ export class RequestError extends Error {
|
|
|
41
41
|
// Recover the original stacktrace
|
|
42
42
|
if (is.string(error.stack) && is.string(this.stack)) {
|
|
43
43
|
const indexOfMessage = this.stack.indexOf(this.message) + this.message.length;
|
|
44
|
-
const thisStackTrace = this.stack.slice(indexOfMessage).split('\n').
|
|
45
|
-
const errorStackTrace = error.stack.slice(error.stack.indexOf(error.message) + error.message.length).split('\n').
|
|
44
|
+
const thisStackTrace = this.stack.slice(indexOfMessage).split('\n').toReversed();
|
|
45
|
+
const errorStackTrace = error.stack.slice(error.stack.indexOf(error.message) + error.message.length).split('\n').toReversed();
|
|
46
46
|
// Remove duplicated traces
|
|
47
47
|
while (errorStackTrace.length > 0 && errorStackTrace[0] === thisStackTrace[0]) {
|
|
48
48
|
thisStackTrace.shift();
|
|
49
49
|
}
|
|
50
|
-
this.stack = `${this.stack.slice(0, indexOfMessage)}${thisStackTrace.
|
|
50
|
+
this.stack = `${this.stack.slice(0, indexOfMessage)}${thisStackTrace.toReversed().join('\n')}${errorStackTrace.toReversed().join('\n')}`;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -725,6 +725,40 @@ export default class Request extends Duplex {
|
|
|
725
725
|
}, this));
|
|
726
726
|
}
|
|
727
727
|
});
|
|
728
|
+
let canFinalizeResponse = false;
|
|
729
|
+
const handleResponseEnd = () => {
|
|
730
|
+
if (!canFinalizeResponse
|
|
731
|
+
|| !response.readableEnded) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
canFinalizeResponse = false;
|
|
735
|
+
if (this._stopReading) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
// Validate content-length if it was provided
|
|
739
|
+
// Per RFC 9112: "If the sender closes the connection before the indicated number
|
|
740
|
+
// of octets are received, the recipient MUST consider the message to be incomplete"
|
|
741
|
+
if (this._checkContentLengthMismatch()) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
this._responseSize = this._downloadedSize;
|
|
745
|
+
this.emit('downloadProgress', this.downloadProgress);
|
|
746
|
+
// Publish response end event
|
|
747
|
+
publishResponseEnd({
|
|
748
|
+
requestId: this._requestId,
|
|
749
|
+
url: typedResponse.url,
|
|
750
|
+
statusCode,
|
|
751
|
+
bodySize: this._downloadedSize,
|
|
752
|
+
timings: this.timings,
|
|
753
|
+
});
|
|
754
|
+
this.push(null);
|
|
755
|
+
};
|
|
756
|
+
if (!shouldFollowRedirect) {
|
|
757
|
+
// `set-cookie` handling below awaits the cookie jar. A fast response can fully
|
|
758
|
+
// end during that await, so we need to observe `end` early without completing
|
|
759
|
+
// the outward stream until cookie handling has finished.
|
|
760
|
+
response.once('end', handleResponseEnd);
|
|
761
|
+
}
|
|
728
762
|
const noPipeCookieJarRawBodyPromise = this._noPipe
|
|
729
763
|
&& is.object(options.cookieJar)
|
|
730
764
|
&& !isRedirect
|
|
@@ -828,6 +862,7 @@ export default class Request extends Duplex {
|
|
|
828
862
|
}
|
|
829
863
|
return changedState;
|
|
830
864
|
});
|
|
865
|
+
updatedOptions.clearUnchangedCookieHeader(preHookState, changedState);
|
|
831
866
|
// If a beforeRedirect hook changed the URL to a different origin,
|
|
832
867
|
// strip sensitive headers that were preserved for the original origin.
|
|
833
868
|
// When isDifferentOrigin was already true, headers were already stripped above.
|
|
@@ -836,15 +871,7 @@ export default class Request extends Duplex {
|
|
|
836
871
|
const hookUrl = updatedOptions.url;
|
|
837
872
|
if (!isSameOrigin(state.url, hookUrl)) {
|
|
838
873
|
this._stripUnchangedCrossOriginState(updatedOptions, hookUrl, shouldDropBody, {
|
|
839
|
-
|
|
840
|
-
username: state.username,
|
|
841
|
-
password: state.password,
|
|
842
|
-
body: state.body,
|
|
843
|
-
json: state.json,
|
|
844
|
-
form: state.form,
|
|
845
|
-
bodySnapshot: state.bodySnapshot,
|
|
846
|
-
jsonSnapshot: state.jsonSnapshot,
|
|
847
|
-
formSnapshot: state.formSnapshot,
|
|
874
|
+
...state,
|
|
848
875
|
changedState,
|
|
849
876
|
preserveUsername: hasExplicitCredentialInUrlChange(changedState, hookUrl, 'username')
|
|
850
877
|
|| isCrossOriginCredentialChanged(state.url, hookUrl, 'username'),
|
|
@@ -870,6 +897,8 @@ export default class Request extends Duplex {
|
|
|
870
897
|
}
|
|
871
898
|
return;
|
|
872
899
|
}
|
|
900
|
+
canFinalizeResponse = true;
|
|
901
|
+
handleResponseEnd();
|
|
873
902
|
// `HTTPError`s always have `error.response.body` defined.
|
|
874
903
|
// Therefore, we cannot retry if `options.throwHttpErrors` is false.
|
|
875
904
|
// On the last retry, if `options.throwHttpErrors` is false, we would need to return the body,
|
|
@@ -894,32 +923,6 @@ export default class Request extends Duplex {
|
|
|
894
923
|
}
|
|
895
924
|
}
|
|
896
925
|
}
|
|
897
|
-
// Set up end listener AFTER redirect check to avoid emitting progress for redirect responses
|
|
898
|
-
let responseEndHandled = false;
|
|
899
|
-
const handleResponseEnd = () => {
|
|
900
|
-
if (responseEndHandled) {
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
responseEndHandled = true;
|
|
904
|
-
// Validate content-length if it was provided
|
|
905
|
-
// Per RFC 9112: "If the sender closes the connection before the indicated number
|
|
906
|
-
// of octets are received, the recipient MUST consider the message to be incomplete"
|
|
907
|
-
if (this._checkContentLengthMismatch()) {
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
this._responseSize = this._downloadedSize;
|
|
911
|
-
this.emit('downloadProgress', this.downloadProgress);
|
|
912
|
-
// Publish response end event
|
|
913
|
-
publishResponseEnd({
|
|
914
|
-
requestId: this._requestId,
|
|
915
|
-
url: typedResponse.url,
|
|
916
|
-
statusCode,
|
|
917
|
-
bodySize: this._downloadedSize,
|
|
918
|
-
timings: this.timings,
|
|
919
|
-
});
|
|
920
|
-
this.push(null);
|
|
921
|
-
};
|
|
922
|
-
response.once('end', handleResponseEnd);
|
|
923
926
|
this.emit('downloadProgress', this.downloadProgress);
|
|
924
927
|
response.on('readable', () => {
|
|
925
928
|
if (this._triggerRead) {
|
|
@@ -1319,7 +1322,8 @@ export default class Request extends Duplex {
|
|
|
1319
1322
|
try {
|
|
1320
1323
|
const result = iterableBody.return();
|
|
1321
1324
|
if (result instanceof Promise) {
|
|
1322
|
-
|
|
1325
|
+
// eslint-disable-next-line promise/prefer-await-to-then
|
|
1326
|
+
result.catch(noop);
|
|
1323
1327
|
}
|
|
1324
1328
|
}
|
|
1325
1329
|
catch { }
|
|
@@ -1493,28 +1497,30 @@ export default class Request extends Duplex {
|
|
|
1493
1497
|
});
|
|
1494
1498
|
let request;
|
|
1495
1499
|
// TODO: Fix `cacheable-response`. This is ugly.
|
|
1496
|
-
const cacheRequest = cacheableStore.get(options.cache)(options,
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1500
|
+
const cacheRequest = cacheableStore.get(options.cache)(options, (response) => {
|
|
1501
|
+
void (async () => {
|
|
1502
|
+
response._readableState.autoDestroy = false;
|
|
1503
|
+
if (request) {
|
|
1504
|
+
const fix = () => {
|
|
1505
|
+
// For ResponseLike objects from cache, set complete to true if not already set.
|
|
1506
|
+
// For real HTTP responses, copy from the underlying response.
|
|
1507
|
+
if (response.req) {
|
|
1508
|
+
response.complete = response.req.res.complete;
|
|
1509
|
+
}
|
|
1510
|
+
else if (response.complete === undefined) {
|
|
1511
|
+
// ResponseLike from cache should have complete = true
|
|
1512
|
+
response.complete = true;
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
response.prependOnceListener('end', fix);
|
|
1516
|
+
fix();
|
|
1517
|
+
(await request).emit('cacheableResponse', response);
|
|
1518
|
+
}
|
|
1519
|
+
resolve(response);
|
|
1520
|
+
})();
|
|
1515
1521
|
});
|
|
1516
1522
|
cacheRequest.once('error', reject);
|
|
1517
|
-
cacheRequest.once('request',
|
|
1523
|
+
cacheRequest.once('request', (requestOrPromise) => {
|
|
1518
1524
|
request = requestOrPromise;
|
|
1519
1525
|
resolve(request);
|
|
1520
1526
|
});
|
|
@@ -1522,17 +1528,46 @@ export default class Request extends Duplex {
|
|
|
1522
1528
|
}
|
|
1523
1529
|
async _makeRequest() {
|
|
1524
1530
|
const { options } = this;
|
|
1525
|
-
const
|
|
1526
|
-
const
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
+
const initialHeaders = options.getInternalHeaders();
|
|
1532
|
+
const explicitAuthorizationHeader = options.isHeaderExplicitlySet('authorization') ? initialHeaders.authorization : undefined;
|
|
1533
|
+
const explicitCookieHeader = options.isHeaderExplicitlySet('cookie') ? initialHeaders.cookie : undefined;
|
|
1534
|
+
const authorizationWasInitiallyOmitted = options.isHeaderExplicitlySet('authorization') && is.undefined(initialHeaders.authorization);
|
|
1535
|
+
const cookieWasInitiallyOmitted = options.isHeaderExplicitlySet('cookie') && is.undefined(initialHeaders.cookie);
|
|
1536
|
+
const shouldDeleteGeneratedHeader = (currentHeader, generatedHeader) => currentHeader === generatedHeader || is.undefined(currentHeader);
|
|
1537
|
+
const syncGeneratedHeader = (name, { currentHeader, explicitHeader, nextHeader, staleGeneratedHeader, }) => {
|
|
1538
|
+
if (!is.undefined(nextHeader)) {
|
|
1539
|
+
options.setInternalHeader(name, nextHeader);
|
|
1540
|
+
}
|
|
1541
|
+
else if (!is.undefined(explicitHeader) && currentHeader === staleGeneratedHeader) {
|
|
1542
|
+
options.setInternalHeader(name, explicitHeader);
|
|
1543
|
+
}
|
|
1544
|
+
else if (shouldDeleteGeneratedHeader(currentHeader, staleGeneratedHeader)) {
|
|
1545
|
+
options.deleteInternalHeader(name);
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
const getAuthorizationHeader = (username, password, isExplicitlyOmitted) => !isExplicitlyOmitted && (username || password)
|
|
1549
|
+
? `Basic ${stringToBase64(`${username}:${password}`)}`
|
|
1550
|
+
: undefined;
|
|
1551
|
+
const sanitizeHeaders = () => {
|
|
1552
|
+
const currentHeaders = options.getInternalHeaders();
|
|
1553
|
+
for (const key in currentHeaders) {
|
|
1554
|
+
if (is.undefined(currentHeaders[key])) {
|
|
1555
|
+
options.deleteInternalHeader(key);
|
|
1556
|
+
}
|
|
1557
|
+
else if (is.null(currentHeaders[key])) {
|
|
1558
|
+
throw new TypeError(`Use \`undefined\` instead of \`null\` to delete the \`${key}\` header`);
|
|
1559
|
+
}
|
|
1531
1560
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1561
|
+
return currentHeaders;
|
|
1562
|
+
};
|
|
1563
|
+
const getCookieHeader = async (cookieJar) => {
|
|
1564
|
+
if (!cookieJar) {
|
|
1565
|
+
return undefined;
|
|
1534
1566
|
}
|
|
1535
|
-
|
|
1567
|
+
const cookieString = await cookieJar.getCookieString(options.url.toString());
|
|
1568
|
+
return is.nonEmptyString(cookieString) ? cookieString : undefined;
|
|
1569
|
+
};
|
|
1570
|
+
const headers = sanitizeHeaders();
|
|
1536
1571
|
if (options.decompress && is.undefined(headers['accept-encoding'])) {
|
|
1537
1572
|
const encodings = ['gzip', 'deflate'];
|
|
1538
1573
|
if (supportsBrotli) {
|
|
@@ -1543,34 +1578,93 @@ export default class Request extends Duplex {
|
|
|
1543
1578
|
}
|
|
1544
1579
|
options.setInternalHeader('accept-encoding', encodings.join(', '));
|
|
1545
1580
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1581
|
+
const { username, password } = options;
|
|
1582
|
+
const cookieJar = options.cookieJar;
|
|
1583
|
+
const generatedAuthorizationHeader = getAuthorizationHeader(username, password, authorizationWasInitiallyOmitted);
|
|
1584
|
+
let generatedCookieHeader;
|
|
1585
|
+
if (!is.undefined(generatedAuthorizationHeader)) {
|
|
1586
|
+
options.setInternalHeader('authorization', generatedAuthorizationHeader);
|
|
1549
1587
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
options.setInternalHeader('cookie', cookieString);
|
|
1588
|
+
if (!cookieWasInitiallyOmitted) {
|
|
1589
|
+
generatedCookieHeader = await getCookieHeader(cookieJar);
|
|
1590
|
+
if (!is.undefined(generatedCookieHeader)) {
|
|
1591
|
+
options.setInternalHeader('cookie', generatedCookieHeader);
|
|
1555
1592
|
}
|
|
1556
1593
|
}
|
|
1557
1594
|
let request;
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1595
|
+
let shouldOmitRequestUrlCredentials = false;
|
|
1596
|
+
const changedState = await options.trackStateMutations(async (changedState) => {
|
|
1597
|
+
for (const hook of options.hooks.beforeRequest) {
|
|
1598
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1599
|
+
const result = await hook(options, { retryCount: this.retryCount });
|
|
1600
|
+
if (!is.undefined(result)) {
|
|
1601
|
+
// @ts-expect-error Skip the type mismatch to support abstract responses
|
|
1602
|
+
request = () => result;
|
|
1603
|
+
break;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return changedState;
|
|
1607
|
+
});
|
|
1608
|
+
if (request === undefined) {
|
|
1609
|
+
const currentHeaders = options.getInternalHeaders();
|
|
1610
|
+
// `headers.authorization = undefined` / `headers.cookie = undefined` is an
|
|
1611
|
+
// explicit opt-out. Respect that instead of regenerating values from URL
|
|
1612
|
+
// credentials or the cookie jar later in request setup.
|
|
1613
|
+
const isHeaderExplicitlyOmitted = (header) => options.isHeaderExplicitlySet(header) && is.undefined(currentHeaders[header]);
|
|
1614
|
+
const authorizationWasExplicitlyOmitted = isHeaderExplicitlyOmitted('authorization');
|
|
1615
|
+
const cookieWasExplicitlyOmitted = isHeaderExplicitlyOmitted('cookie');
|
|
1616
|
+
const currentAuthorizationHeader = currentHeaders.authorization;
|
|
1617
|
+
const currentCookieHeader = currentHeaders.cookie;
|
|
1618
|
+
sanitizeHeaders();
|
|
1619
|
+
if (!is.undefined(currentHeaders['transfer-encoding']) && !is.undefined(currentHeaders['content-length'])) {
|
|
1620
|
+
options.deleteInternalHeader('content-length');
|
|
1621
|
+
}
|
|
1622
|
+
if (authorizationWasExplicitlyOmitted) {
|
|
1623
|
+
shouldOmitRequestUrlCredentials = true;
|
|
1624
|
+
options.deleteInternalHeader('authorization');
|
|
1625
|
+
if (changedState.has('authorization') && is.undefined(explicitAuthorizationHeader) && !authorizationWasInitiallyOmitted) {
|
|
1626
|
+
delete options.headers.authorization;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
const authorizationHeader = getAuthorizationHeader(options.username, options.password, authorizationWasExplicitlyOmitted);
|
|
1630
|
+
const cookieJar = options.cookieJar;
|
|
1631
|
+
if (changedState.has('authorization')) {
|
|
1632
|
+
// A beforeRequest hook intentionally set the outgoing Authorization header.
|
|
1633
|
+
}
|
|
1634
|
+
else {
|
|
1635
|
+
syncGeneratedHeader('authorization', {
|
|
1636
|
+
currentHeader: currentAuthorizationHeader,
|
|
1637
|
+
explicitHeader: explicitAuthorizationHeader,
|
|
1638
|
+
nextHeader: authorizationHeader,
|
|
1639
|
+
staleGeneratedHeader: generatedAuthorizationHeader,
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
if (cookieWasExplicitlyOmitted) {
|
|
1643
|
+
options.deleteInternalHeader('cookie');
|
|
1644
|
+
if (changedState.has('cookie') && is.undefined(explicitCookieHeader) && !cookieWasInitiallyOmitted) {
|
|
1645
|
+
delete options.headers.cookie;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
else if (changedState.has('cookie')) {
|
|
1649
|
+
// A beforeRequest hook intentionally set the outgoing Cookie header.
|
|
1650
|
+
}
|
|
1651
|
+
else {
|
|
1652
|
+
syncGeneratedHeader('cookie', {
|
|
1653
|
+
currentHeader: currentCookieHeader,
|
|
1654
|
+
explicitHeader: explicitCookieHeader,
|
|
1655
|
+
nextHeader: await getCookieHeader(cookieJar),
|
|
1656
|
+
staleGeneratedHeader: generatedCookieHeader,
|
|
1657
|
+
});
|
|
1565
1658
|
}
|
|
1566
|
-
}
|
|
1567
|
-
if (!is.undefined(headers['transfer-encoding']) && !is.undefined(headers['content-length'])) {
|
|
1568
|
-
// TODO: Throw instead of silently dropping `content-length` in the next major version.
|
|
1569
|
-
options.deleteInternalHeader('content-length');
|
|
1570
1659
|
}
|
|
1571
1660
|
request ??= options.getRequestFunction();
|
|
1572
|
-
const url =
|
|
1661
|
+
const url = shouldOmitRequestUrlCredentials
|
|
1662
|
+
? new URL(stripUrlAuth(options.url))
|
|
1663
|
+
: options.url;
|
|
1573
1664
|
this._requestOptions = options.createNativeRequestOptions();
|
|
1665
|
+
if (shouldOmitRequestUrlCredentials) {
|
|
1666
|
+
this._requestOptions.auth = undefined;
|
|
1667
|
+
}
|
|
1574
1668
|
if (options.cache) {
|
|
1575
1669
|
this._requestOptions._request = request;
|
|
1576
1670
|
this._requestOptions.cache = options.cache;
|
|
@@ -31,6 +31,8 @@ export type Agents = {
|
|
|
31
31
|
export type Headers = Record<string, string | string[] | undefined>;
|
|
32
32
|
export type CrossOriginState = {
|
|
33
33
|
headers: Headers;
|
|
34
|
+
hadCookieJar: boolean;
|
|
35
|
+
cookieWasExplicitlySet: boolean;
|
|
34
36
|
username: string;
|
|
35
37
|
password: string;
|
|
36
38
|
body: unknown;
|
|
@@ -91,20 +93,20 @@ export type Hooks = {
|
|
|
91
93
|
|
|
92
94
|
@default []
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
Note:
|
|
95
97
|
> - This hook must be synchronous.
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
Note:
|
|
98
100
|
> - This is called every time options are merged.
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
Note:
|
|
101
103
|
> - The `options` object may not have the `url` property. To modify it, use a `beforeRequest` hook instead.
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
Note:
|
|
104
106
|
> - This hook is called when a new instance of `Options` is created.
|
|
105
107
|
> - Do not confuse this with the creation of `Request` or `got(…)`.
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
Note:
|
|
108
110
|
> - When using `got(url)` or `got(url, undefined, defaults)` this hook will **not** be called.
|
|
109
111
|
|
|
110
112
|
This is especially useful in conjunction with `got.extend()` when the input needs custom handling.
|
|
@@ -183,10 +185,10 @@ export type Hooks = {
|
|
|
183
185
|
|
|
184
186
|
@default []
|
|
185
187
|
|
|
186
|
-
|
|
188
|
+
Note:
|
|
187
189
|
> - Got will make no further changes to the request before it is sent.
|
|
188
190
|
|
|
189
|
-
|
|
191
|
+
Note:
|
|
190
192
|
> - Changing `options.json` or `options.form` has no effect on the request. You should change `options.body` instead. If needed, update the `options.headers` accordingly.
|
|
191
193
|
|
|
192
194
|
@example
|
|
@@ -209,7 +211,7 @@ export type Hooks = {
|
|
|
209
211
|
);
|
|
210
212
|
```
|
|
211
213
|
|
|
212
|
-
|
|
214
|
+
Example using `context.retryCount`:
|
|
213
215
|
|
|
214
216
|
```
|
|
215
217
|
import got from 'got';
|
|
@@ -231,7 +233,7 @@ export type Hooks = {
|
|
|
231
233
|
});
|
|
232
234
|
```
|
|
233
235
|
|
|
234
|
-
|
|
236
|
+
Tip:
|
|
235
237
|
> - You can indirectly override the `request` function by early returning a [`ClientRequest`-like](https://nodejs.org/api/http.html#http_class_http_clientrequest) instance or a [`IncomingMessage`-like](https://nodejs.org/api/http.html#http_class_http_incomingmessage) instance. This is very useful when creating a custom cache mechanism.
|
|
236
238
|
> - [Read more about this tip](https://github.com/sindresorhus/got/blob/main/documentation/cache.md#advanced-caching-mechanisms).
|
|
237
239
|
*/
|
|
@@ -241,7 +243,7 @@ export type Hooks = {
|
|
|
241
243
|
|
|
242
244
|
@default []
|
|
243
245
|
|
|
244
|
-
|
|
246
|
+
Tip:
|
|
245
247
|
> - This is especially useful when you want to avoid dead sites.
|
|
246
248
|
|
|
247
249
|
@example
|
|
@@ -326,13 +328,13 @@ export type Hooks = {
|
|
|
326
328
|
|
|
327
329
|
@default []
|
|
328
330
|
|
|
329
|
-
|
|
331
|
+
Note:
|
|
330
332
|
> - When using the Stream API, this hook is ignored.
|
|
331
333
|
|
|
332
|
-
|
|
334
|
+
Note:
|
|
333
335
|
> - When retrying, the `beforeRequest` hook is called afterwards.
|
|
334
336
|
|
|
335
|
-
|
|
337
|
+
Note:
|
|
336
338
|
> - If no retry occurs, the `beforeError` hook is called instead.
|
|
337
339
|
|
|
338
340
|
This hook is especially useful when you want to retrieve the cause of a retry.
|
|
@@ -361,31 +363,31 @@ export type Hooks = {
|
|
|
361
363
|
|
|
362
364
|
@default []
|
|
363
365
|
|
|
364
|
-
|
|
366
|
+
Return value:
|
|
365
367
|
> - `false` - Prevent caching (remaining hooks are skipped)
|
|
366
368
|
> - `void`/`undefined` - Use default caching behavior (mutations take effect)
|
|
367
369
|
|
|
368
|
-
|
|
370
|
+
Modifying the response:
|
|
369
371
|
> - Hooks can directly mutate response properties like `headers`, `statusCode`, and `statusMessage`
|
|
370
372
|
> - Mutations to `response.headers` affect how the caching layer decides whether to cache the response and for how long
|
|
371
373
|
> - Changes are applied to what gets cached
|
|
372
374
|
|
|
373
|
-
|
|
375
|
+
Note:
|
|
374
376
|
> - This hook is only called when the `cache` option is enabled.
|
|
375
377
|
|
|
376
|
-
|
|
378
|
+
Note:
|
|
377
379
|
> - This hook must be synchronous. It cannot return a Promise. If you need async logic to determine caching behavior, use a `beforeRequest` hook instead.
|
|
378
380
|
|
|
379
|
-
|
|
381
|
+
Note:
|
|
380
382
|
> - When returning `false`, remaining hooks are skipped and the response will not be cached.
|
|
381
383
|
|
|
382
|
-
|
|
384
|
+
Note:
|
|
383
385
|
> - Returning anything other than `false` or `undefined` will throw a TypeError.
|
|
384
386
|
|
|
385
|
-
|
|
387
|
+
Note:
|
|
386
388
|
> - If a hook throws an error, it will be propagated and the request will fail. This is consistent with how other hooks in Got handle errors.
|
|
387
389
|
|
|
388
|
-
|
|
390
|
+
Note:
|
|
389
391
|
> - At this stage, the response body has not been read yet - it's still a stream. Properties like `response.body` and `response.rawBody` are not available. You can only inspect/modify response headers and status code.
|
|
390
392
|
|
|
391
393
|
@example
|
|
@@ -425,17 +427,17 @@ export type Hooks = {
|
|
|
425
427
|
|
|
426
428
|
@default []
|
|
427
429
|
|
|
428
|
-
|
|
430
|
+
Note:
|
|
429
431
|
> - When using the Stream API, this hook is ignored.
|
|
430
432
|
|
|
431
|
-
|
|
433
|
+
Note:
|
|
432
434
|
> - Calling the `retryWithMergedOptions` function will trigger `beforeRetry` hooks. By default, remaining `afterResponse` hooks are removed to prevent duplicate execution. To preserve remaining hooks on retry, set `preserveHooks: true` in the options passed to `retryWithMergedOptions`. In case of an error, `beforeRetry` hooks will be called instead.
|
|
433
435
|
Meanwhile the `init`, `beforeRequest` , `beforeRedirect` as well as already executed `afterResponse` hooks will be skipped.
|
|
434
436
|
|
|
435
|
-
|
|
437
|
+
Note:
|
|
436
438
|
> - To preserve remaining `afterResponse` hooks after calling `retryWithMergedOptions`, set `preserveHooks: true` in the options passed to `retryWithMergedOptions`. This is useful when you want hooks to run on retried requests.
|
|
437
439
|
|
|
438
|
-
|
|
440
|
+
Warning:
|
|
439
441
|
> - Be cautious when using `preserveHooks: true`. If a hook unconditionally calls `retryWithMergedOptions` with `preserveHooks: true`, it will create an infinite retry loop. Always ensure hooks have proper conditional logic to avoid infinite retries.
|
|
440
442
|
|
|
441
443
|
@example
|
|
@@ -788,7 +790,7 @@ export declare function applyUrlOverride(options: Options, url: string | URL, {
|
|
|
788
790
|
All parsing methods supported by Got.
|
|
789
791
|
*/
|
|
790
792
|
export type ResponseType = 'json' | 'buffer' | 'text';
|
|
791
|
-
type OptionsToSkip = 'searchParameters' | 'followRedirects' | 'auth' | 'toJSON' | 'merge' | 'isHeaderExplicitlySet' | 'shouldCopyPipedHeader' | 'setPipedHeader' | 'getInternalHeaders' | 'setInternalHeader' | 'deleteInternalHeader' | 'trackStateMutations' | 'clearBody' | 'stripUnchangedCrossOriginState' | 'stripSensitiveHeaders' | 'createNativeRequestOptions' | 'getRequestFunction' | 'freeze';
|
|
793
|
+
type OptionsToSkip = 'searchParameters' | 'followRedirects' | 'auth' | 'toJSON' | 'merge' | 'isHeaderExplicitlySet' | 'shouldCopyPipedHeader' | 'setPipedHeader' | 'getInternalHeaders' | 'setInternalHeader' | 'deleteInternalHeader' | 'trackStateMutations' | 'clearBody' | 'clearUnchangedCookieHeader' | 'restoreCookieHeader' | 'syncCookieHeaderAfterMerge' | 'stripUnchangedCrossOriginState' | 'stripSensitiveHeaders' | 'createNativeRequestOptions' | 'getRequestFunction' | 'freeze';
|
|
792
794
|
export type InternalsType = Except<Options, OptionsToSkip>;
|
|
793
795
|
export type OptionsError = NodeJS.ErrnoException & {
|
|
794
796
|
options?: Options;
|
|
@@ -1226,6 +1228,9 @@ export default class Options {
|
|
|
1226
1228
|
deleteInternalHeader(name: string): void;
|
|
1227
1229
|
trackStateMutations<Value>(operation: (changedState: Set<string>) => Promisable<Value>): Promise<Value>;
|
|
1228
1230
|
clearBody(): void;
|
|
1231
|
+
clearUnchangedCookieHeader(previousState: CrossOriginState | undefined, changedState?: Set<string>): void;
|
|
1232
|
+
restoreCookieHeader(previousState: CrossOriginState | undefined, headers?: Headers): void;
|
|
1233
|
+
syncCookieHeaderAfterMerge(previousState: CrossOriginState | undefined, headers?: Headers): void;
|
|
1229
1234
|
stripUnchangedCrossOriginState(previousState: CrossOriginState, changedState: Set<string>, { clearBody }?: {
|
|
1230
1235
|
clearBody?: boolean;
|
|
1231
1236
|
}): void;
|
|
@@ -68,8 +68,8 @@ function hasCredentialInUrl(url, credential) {
|
|
|
68
68
|
}
|
|
69
69
|
export const hasExplicitCredentialInUrlChange = (changedState, url, credential) => (changedState.has(credential)
|
|
70
70
|
|| (changedState.has('url') && url?.[credential] !== ''));
|
|
71
|
-
const hasProtocolSlashes = (value) => /^[a-z][a-z
|
|
72
|
-
const hasHttpProtocolWithoutSlashes = (value) => /^https?:(?!\/\/)/
|
|
71
|
+
const hasProtocolSlashes = (value) => /^[a-z][\d+\-.a-z]*:\/\//iv.test(value);
|
|
72
|
+
const hasHttpProtocolWithoutSlashes = (value) => /^https?:(?!\/\/)/iv.test(value);
|
|
73
73
|
export function applyUrlOverride(options, url, { username, password } = {}) {
|
|
74
74
|
if (is.string(url) && options.url) {
|
|
75
75
|
url = new URL(url, options.url).toString();
|
|
@@ -486,7 +486,7 @@ export default class Options {
|
|
|
486
486
|
// would get merged. Instead we set the `searchParams` first, then
|
|
487
487
|
// `url.searchParams` is overwritten as expected.
|
|
488
488
|
//
|
|
489
|
-
/* eslint-disable no-unsafe-finally */
|
|
489
|
+
/* eslint-disable no-unsafe-finally -- `finally` is used intentionally here to ensure `url` is always set last, overwriting any merged searchParams */
|
|
490
490
|
try {
|
|
491
491
|
if (is.plainObject(input)) {
|
|
492
492
|
try {
|
|
@@ -912,16 +912,14 @@ export default class Options {
|
|
|
912
912
|
this.#internals.cookieJar = undefined;
|
|
913
913
|
return;
|
|
914
914
|
}
|
|
915
|
-
|
|
915
|
+
const { setCookie, getCookieString } = value;
|
|
916
916
|
assert.function(setCookie);
|
|
917
917
|
assert.function(getCookieString);
|
|
918
918
|
/* istanbul ignore next: Horrible `tough-cookie` v3 check */
|
|
919
919
|
if (setCookie.length === 4 && getCookieString.length === 0) {
|
|
920
|
-
setCookie = promisify(setCookie.bind(value));
|
|
921
|
-
getCookieString = promisify(getCookieString.bind(value));
|
|
922
920
|
this.#internals.cookieJar = {
|
|
923
|
-
setCookie,
|
|
924
|
-
getCookieString: getCookieString,
|
|
921
|
+
setCookie: promisify(setCookie.bind(value)),
|
|
922
|
+
getCookieString: promisify(getCookieString.bind(value)),
|
|
925
923
|
};
|
|
926
924
|
}
|
|
927
925
|
else {
|
|
@@ -1408,6 +1406,35 @@ export default class Options {
|
|
|
1408
1406
|
this.deleteInternalHeader(header);
|
|
1409
1407
|
}
|
|
1410
1408
|
}
|
|
1409
|
+
clearUnchangedCookieHeader(previousState, changedState) {
|
|
1410
|
+
if (previousState?.hadCookieJar
|
|
1411
|
+
&& this.cookieJar === undefined
|
|
1412
|
+
&& !this.isHeaderExplicitlySet('cookie')
|
|
1413
|
+
&& !changedState?.has('cookie')
|
|
1414
|
+
&& this.headers.cookie === previousState.headers.cookie) {
|
|
1415
|
+
this.deleteInternalHeader('cookie');
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
restoreCookieHeader(previousState, headers) {
|
|
1419
|
+
if (!previousState) {
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
if (Object.hasOwn(headers ?? {}, 'cookie')) {
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
if (previousState.cookieWasExplicitlySet) {
|
|
1426
|
+
this.headers.cookie = previousState.headers.cookie;
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
delete this.headers.cookie;
|
|
1430
|
+
if (previousState.headers.cookie !== undefined) {
|
|
1431
|
+
this.setInternalHeader('cookie', previousState.headers.cookie);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
syncCookieHeaderAfterMerge(previousState, headers) {
|
|
1435
|
+
this.restoreCookieHeader(previousState, headers);
|
|
1436
|
+
this.clearUnchangedCookieHeader(previousState);
|
|
1437
|
+
}
|
|
1411
1438
|
stripUnchangedCrossOriginState(previousState, changedState, { clearBody = true } = {}) {
|
|
1412
1439
|
const headers = this.getInternalHeaders();
|
|
1413
1440
|
const url = this.#internals.url;
|
|
@@ -1792,11 +1819,11 @@ export default class Options {
|
|
|
1792
1819
|
this.#internals.resolveBodyOnly = value;
|
|
1793
1820
|
}
|
|
1794
1821
|
/**
|
|
1795
|
-
@internal
|
|
1796
1822
|
Returns a `Stream` instead of a `Promise`.
|
|
1797
1823
|
Set internally by `got.stream()`.
|
|
1798
1824
|
|
|
1799
1825
|
@default false
|
|
1826
|
+
@internal
|
|
1800
1827
|
*/
|
|
1801
1828
|
get isStream() {
|
|
1802
1829
|
return this.#internals.isStream;
|
|
@@ -1945,7 +1972,7 @@ export default class Options {
|
|
|
1945
1972
|
}
|
|
1946
1973
|
let unixSocketGroups;
|
|
1947
1974
|
if (unixSocketPath !== undefined) {
|
|
1948
|
-
unixSocketGroups =
|
|
1975
|
+
unixSocketGroups = /^(?<socketPath>[^:]+):(?<path>.+)$/v.exec(`${url.pathname}${url.search}`)?.groups;
|
|
1949
1976
|
}
|
|
1950
1977
|
const unixOptions = unixSocketGroups
|
|
1951
1978
|
? { socketPath: unixSocketGroups.socketPath, path: unixSocketGroups.path, host: '' }
|
|
@@ -2113,6 +2140,8 @@ export default class Options {
|
|
|
2113
2140
|
}
|
|
2114
2141
|
export const snapshotCrossOriginState = (options) => ({
|
|
2115
2142
|
headers: { ...options.getInternalHeaders() },
|
|
2143
|
+
hadCookieJar: options.cookieJar !== undefined,
|
|
2144
|
+
cookieWasExplicitlySet: options.isHeaderExplicitlySet('cookie'),
|
|
2116
2145
|
username: options.username,
|
|
2117
2146
|
password: options.password,
|
|
2118
2147
|
body: options.body,
|
|
@@ -1,9 +1,56 @@
|
|
|
1
|
+
const splitHeaderValue = (value, separator) => {
|
|
2
|
+
const values = [];
|
|
3
|
+
let current = '';
|
|
4
|
+
let inQuotes = false;
|
|
5
|
+
let inReference = false;
|
|
6
|
+
let isEscaped = false;
|
|
7
|
+
for (const character of value) {
|
|
8
|
+
if (inQuotes && isEscaped) {
|
|
9
|
+
current += character;
|
|
10
|
+
isEscaped = false;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (inQuotes && character === '\\') {
|
|
14
|
+
current += character;
|
|
15
|
+
isEscaped = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (character === '"') {
|
|
19
|
+
inQuotes = !inQuotes;
|
|
20
|
+
current += character;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (!inQuotes && character === '<') {
|
|
24
|
+
inReference = true;
|
|
25
|
+
current += character;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (!inQuotes && character === '>') {
|
|
29
|
+
inReference = false;
|
|
30
|
+
current += character;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
// Link headers use both quoted strings and <URI-reference> values, so raw
|
|
34
|
+
// splitting on `,` / `;` would break valid values containing those characters.
|
|
35
|
+
if (!inQuotes && !inReference && character === separator) {
|
|
36
|
+
values.push(current);
|
|
37
|
+
current = '';
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
current += character;
|
|
41
|
+
}
|
|
42
|
+
if (inQuotes || isEscaped) {
|
|
43
|
+
throw new Error(`Failed to parse Link header: ${value}`);
|
|
44
|
+
}
|
|
45
|
+
values.push(current);
|
|
46
|
+
return values;
|
|
47
|
+
};
|
|
1
48
|
export default function parseLinkHeader(link) {
|
|
2
49
|
const parsed = [];
|
|
3
|
-
const items = link
|
|
50
|
+
const items = splitHeaderValue(link, ',');
|
|
4
51
|
for (const item of items) {
|
|
5
52
|
// https://tools.ietf.org/html/rfc5988#section-5
|
|
6
|
-
const [rawUriReference, ...rawLinkParameters] = item
|
|
53
|
+
const [rawUriReference, ...rawLinkParameters] = splitHeaderValue(item, ';');
|
|
7
54
|
const trimmedUriReference = rawUriReference.trim();
|
|
8
55
|
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
|
|
9
56
|
if (trimmedUriReference[0] !== '<' || trimmedUriReference.at(-1) !== '>') {
|
|
@@ -11,6 +58,9 @@ export default function parseLinkHeader(link) {
|
|
|
11
58
|
}
|
|
12
59
|
const reference = trimmedUriReference.slice(1, -1);
|
|
13
60
|
const parameters = {};
|
|
61
|
+
if (reference.includes('<') || reference.includes('>')) {
|
|
62
|
+
throw new Error(`Invalid format of the Link header reference: ${trimmedUriReference}`);
|
|
63
|
+
}
|
|
14
64
|
if (rawLinkParameters.length === 0) {
|
|
15
65
|
throw new Error(`Unexpected end of Link header parameters: ${rawLinkParameters.join(';')}`);
|
|
16
66
|
}
|
|
@@ -81,7 +81,7 @@ export default function timedOut(request, delays, options) {
|
|
|
81
81
|
const { socketPath } = request;
|
|
82
82
|
/* istanbul ignore next: hard to test */
|
|
83
83
|
if (socket.connecting) {
|
|
84
|
-
const hasPath = Boolean(socketPath ?? net.isIP(hostname ?? host ?? '') !== 0);
|
|
84
|
+
const hasPath = Boolean(socketPath ?? (net.isIP(hostname ?? host ?? '') !== 0));
|
|
85
85
|
if (hasLookup && !hasPath && socket.address().address === undefined) {
|
|
86
86
|
const cancelTimeout = addTimeout(delays.lookup, timeoutHandler, 'lookup');
|
|
87
87
|
once(socket, 'lookup', cancelTimeout);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export default function isUnixSocketURL(url) {
|
|
1
|
+
export default function isUnixSocketUrl(url) {
|
|
3
2
|
return url.protocol === 'unix:' || url.hostname === 'unix';
|
|
4
3
|
}
|
|
5
4
|
/**
|
|
@@ -18,8 +17,8 @@ getUnixSocketPath(new URL('http://example.com'));
|
|
|
18
17
|
```
|
|
19
18
|
*/
|
|
20
19
|
export function getUnixSocketPath(url) {
|
|
21
|
-
if (!
|
|
20
|
+
if (!isUnixSocketUrl(url)) {
|
|
22
21
|
return undefined;
|
|
23
22
|
}
|
|
24
|
-
return
|
|
23
|
+
return /^(?<socketPath>[^:]+):/v.exec(`${url.pathname}${url.search}`)?.groups?.socketPath;
|
|
25
24
|
}
|
package/dist/source/create.js
CHANGED
|
@@ -180,6 +180,7 @@ const create = (defaults) => {
|
|
|
180
180
|
}
|
|
181
181
|
if (optionsToMerge === response.request.options) {
|
|
182
182
|
normalizedOptions = response.request.options;
|
|
183
|
+
normalizedOptions.clearUnchangedCookieHeader(previousState, changedState);
|
|
183
184
|
if (previousUrl) {
|
|
184
185
|
const nextUrl = normalizedOptions.url;
|
|
185
186
|
if (nextUrl && !isSameOrigin(previousUrl, nextUrl)) {
|
|
@@ -192,10 +193,15 @@ const create = (defaults) => {
|
|
|
192
193
|
const hasExplicitBody = (Object.hasOwn(optionsToMerge, 'body') && optionsToMerge.body !== undefined)
|
|
193
194
|
|| (Object.hasOwn(optionsToMerge, 'json') && optionsToMerge.json !== undefined)
|
|
194
195
|
|| (Object.hasOwn(optionsToMerge, 'form') && optionsToMerge.form !== undefined);
|
|
196
|
+
const clearsCookieJar = Object.hasOwn(optionsToMerge, 'cookieJar') && optionsToMerge.cookieJar === undefined;
|
|
195
197
|
if (hasExplicitBody) {
|
|
196
198
|
normalizedOptions.clearBody();
|
|
197
199
|
}
|
|
200
|
+
if (clearsCookieJar) {
|
|
201
|
+
normalizedOptions.cookieJar = undefined;
|
|
202
|
+
}
|
|
198
203
|
normalizedOptions.merge(optionsToMerge);
|
|
204
|
+
normalizedOptions.syncCookieHeaderAfterMerge(previousState, optionsToMerge.headers);
|
|
199
205
|
try {
|
|
200
206
|
assert.any([is.string, is.urlInstance, is.undefined], optionsToMerge.url);
|
|
201
207
|
}
|
package/dist/source/types.d.ts
CHANGED
|
@@ -59,10 +59,9 @@ export type ExtendOptions = {
|
|
|
59
59
|
*/
|
|
60
60
|
mutableDefaults?: boolean;
|
|
61
61
|
} & Except<OptionsInit, 'url'>;
|
|
62
|
-
type
|
|
63
|
-
export type StrictOptions = Except<
|
|
64
|
-
export type
|
|
65
|
-
export type OptionsWithPagination<T = unknown, R = unknown> = Merge<OptionsInitWithoutUrl, {
|
|
62
|
+
export type StreamOptions = Except<OptionsInit, 'url'>;
|
|
63
|
+
export type StrictOptions = Except<StreamOptions, 'responseType' | 'resolveBodyOnly'>;
|
|
64
|
+
export type OptionsWithPagination<T = unknown, R = unknown> = Merge<StreamOptions, {
|
|
66
65
|
pagination?: PaginationOptions<T, R>;
|
|
67
66
|
}>;
|
|
68
67
|
/**
|
|
@@ -188,9 +187,9 @@ export type GotRequestFunction<U extends ExtendOptions = Record<string, unknown>
|
|
|
188
187
|
(options: OptionsOfTextResponseBodyOnly): RequestPromise<string>;
|
|
189
188
|
<T>(options: OptionsOfJSONResponseBodyOnly): RequestPromise<T>;
|
|
190
189
|
(options: OptionsOfBufferResponseBodyOnly): RequestPromise<Uint8Array<ArrayBuffer>>;
|
|
191
|
-
(url: string | URL, options?:
|
|
192
|
-
(options:
|
|
193
|
-
(url: undefined, options: undefined, defaults: Options): RequestPromise
|
|
190
|
+
(url: string | URL, options?: StreamOptions): RequestPromise;
|
|
191
|
+
(options: StreamOptions): RequestPromise;
|
|
192
|
+
(url: undefined, options: undefined, defaults: Options): RequestPromise;
|
|
194
193
|
};
|
|
195
194
|
/**
|
|
196
195
|
All available HTTP request methods provided by Got.
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -254,13 +254,11 @@ By default, Got will retry on failure. To disable this option, set [`options.ret
|
|
|
254
254
|
[s3]: https://www.npmjs.com/package/superagent
|
|
255
255
|
|
|
256
256
|
<!-- COVERAGE -->
|
|
257
|
-
[gc]: https://img.shields.io/coveralls/github/sindresorhus/got?color=0b9062&label
|
|
258
257
|
[kc]: https://img.shields.io/codecov/c/github/sindresorhus/ky?color=0b9062&label
|
|
259
258
|
[nc]: https://img.shields.io/coveralls/github/bitinn/node-fetch?color=0b9062&label
|
|
260
259
|
[ac]: https://img.shields.io/coveralls/github/mzabriskie/axios?color=0b9062&label
|
|
261
260
|
[sc]: https://img.shields.io/codecov/c/github/visionmedia/superagent?color=0b9062&label
|
|
262
261
|
|
|
263
|
-
[g4]: https://coveralls.io/github/sindresorhus/got
|
|
264
262
|
[k4]: https://codecov.io/gh/sindresorhus/ky
|
|
265
263
|
[n4]: https://coveralls.io/github/bitinn/node-fetch
|
|
266
264
|
[a4]: https://coveralls.io/github/mzabriskie/axios
|
|
@@ -342,7 +340,6 @@ By default, Got will retry on failure. To disable this option, set [`options.ret
|
|
|
342
340
|
[k10]: https://github.com/sindresorhus/ky
|
|
343
341
|
[n10]: https://github.com/node-fetch/node-fetch
|
|
344
342
|
[a10]: https://github.com/axios/axios
|
|
345
|
-
[s10]: https://github.com/visionmedia/superagent
|
|
346
343
|
|
|
347
344
|
<!-- LAST COMMIT -->
|
|
348
345
|
[glc]: https://img.shields.io/github/last-commit/sindresorhus/got?color=gray&label
|