got 15.0.1 → 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.
|
@@ -69,12 +69,18 @@ export default function asPromise(firstRequest) {
|
|
|
69
69
|
: (Object.hasOwn(updatedOptions, 'body') && updatedOptions.body !== undefined)
|
|
70
70
|
|| (Object.hasOwn(updatedOptions, 'json') && updatedOptions.json !== undefined)
|
|
71
71
|
|| (Object.hasOwn(updatedOptions, 'form') && updatedOptions.form !== undefined);
|
|
72
|
+
const clearsCookieJar = Object.hasOwn(updatedOptions, 'cookieJar') && updatedOptions.cookieJar === undefined;
|
|
72
73
|
if (hasExplicitBody && !reusesRequestOptions) {
|
|
73
74
|
options.clearBody();
|
|
74
75
|
}
|
|
76
|
+
if (!reusesRequestOptions && clearsCookieJar) {
|
|
77
|
+
options.cookieJar = undefined;
|
|
78
|
+
}
|
|
75
79
|
if (!reusesRequestOptions) {
|
|
76
80
|
options.merge(updatedOptions);
|
|
81
|
+
options.syncCookieHeaderAfterMerge(previousState, updatedOptions.headers);
|
|
77
82
|
}
|
|
83
|
+
options.clearUnchangedCookieHeader(previousState, reusesRequestOptions ? changedState : undefined);
|
|
78
84
|
if (updatedOptions.url) {
|
|
79
85
|
const nextUrl = reusesRequestOptions
|
|
80
86
|
? options.url
|
|
@@ -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) {
|
|
@@ -1525,17 +1528,46 @@ export default class Request extends Duplex {
|
|
|
1525
1528
|
}
|
|
1526
1529
|
async _makeRequest() {
|
|
1527
1530
|
const { options } = this;
|
|
1528
|
-
const
|
|
1529
|
-
const
|
|
1530
|
-
const
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
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);
|
|
1534
1546
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
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
|
+
}
|
|
1537
1560
|
}
|
|
1538
|
-
|
|
1561
|
+
return currentHeaders;
|
|
1562
|
+
};
|
|
1563
|
+
const getCookieHeader = async (cookieJar) => {
|
|
1564
|
+
if (!cookieJar) {
|
|
1565
|
+
return undefined;
|
|
1566
|
+
}
|
|
1567
|
+
const cookieString = await cookieJar.getCookieString(options.url.toString());
|
|
1568
|
+
return is.nonEmptyString(cookieString) ? cookieString : undefined;
|
|
1569
|
+
};
|
|
1570
|
+
const headers = sanitizeHeaders();
|
|
1539
1571
|
if (options.decompress && is.undefined(headers['accept-encoding'])) {
|
|
1540
1572
|
const encodings = ['gzip', 'deflate'];
|
|
1541
1573
|
if (supportsBrotli) {
|
|
@@ -1546,34 +1578,93 @@ export default class Request extends Duplex {
|
|
|
1546
1578
|
}
|
|
1547
1579
|
options.setInternalHeader('accept-encoding', encodings.join(', '));
|
|
1548
1580
|
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
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);
|
|
1552
1587
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
options.setInternalHeader('cookie', cookieString);
|
|
1588
|
+
if (!cookieWasInitiallyOmitted) {
|
|
1589
|
+
generatedCookieHeader = await getCookieHeader(cookieJar);
|
|
1590
|
+
if (!is.undefined(generatedCookieHeader)) {
|
|
1591
|
+
options.setInternalHeader('cookie', generatedCookieHeader);
|
|
1558
1592
|
}
|
|
1559
1593
|
}
|
|
1560
1594
|
let request;
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
const
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
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
|
+
});
|
|
1568
1658
|
}
|
|
1569
|
-
}
|
|
1570
|
-
if (!is.undefined(headers['transfer-encoding']) && !is.undefined(headers['content-length'])) {
|
|
1571
|
-
// TODO: Throw instead of silently dropping `content-length` in the next major version.
|
|
1572
|
-
options.deleteInternalHeader('content-length');
|
|
1573
1659
|
}
|
|
1574
1660
|
request ??= options.getRequestFunction();
|
|
1575
|
-
const url =
|
|
1661
|
+
const url = shouldOmitRequestUrlCredentials
|
|
1662
|
+
? new URL(stripUrlAuth(options.url))
|
|
1663
|
+
: options.url;
|
|
1576
1664
|
this._requestOptions = options.createNativeRequestOptions();
|
|
1665
|
+
if (shouldOmitRequestUrlCredentials) {
|
|
1666
|
+
this._requestOptions.auth = undefined;
|
|
1667
|
+
}
|
|
1577
1668
|
if (options.cache) {
|
|
1578
1669
|
this._requestOptions._request = request;
|
|
1579
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;
|
|
@@ -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;
|
|
@@ -1406,6 +1406,35 @@ export default class Options {
|
|
|
1406
1406
|
this.deleteInternalHeader(header);
|
|
1407
1407
|
}
|
|
1408
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
|
+
}
|
|
1409
1438
|
stripUnchangedCrossOriginState(previousState, changedState, { clearBody = true } = {}) {
|
|
1410
1439
|
const headers = this.getInternalHeaders();
|
|
1411
1440
|
const url = this.#internals.url;
|
|
@@ -2111,6 +2140,8 @@ export default class Options {
|
|
|
2111
2140
|
}
|
|
2112
2141
|
export const snapshotCrossOriginState = (options) => ({
|
|
2113
2142
|
headers: { ...options.getInternalHeaders() },
|
|
2143
|
+
hadCookieJar: options.cookieJar !== undefined,
|
|
2144
|
+
cookieWasExplicitlySet: options.isHeaderExplicitlySet('cookie'),
|
|
2114
2145
|
username: options.username,
|
|
2115
2146
|
password: options.password,
|
|
2116
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
|
}
|
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
|
}
|