got 15.0.2 → 15.0.3
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.
|
@@ -25,6 +25,11 @@ import { generateRequestId, publishRequestCreate, publishRequestStart, publishRe
|
|
|
25
25
|
const supportsBrotli = is.string(process.versions.brotli);
|
|
26
26
|
const supportsZstd = is.string(process.versions.zstd);
|
|
27
27
|
const methodsWithoutBody = new Set(['GET', 'HEAD']);
|
|
28
|
+
const singleValueRequestHeaders = new Set([
|
|
29
|
+
'authorization',
|
|
30
|
+
'content-length',
|
|
31
|
+
'proxy-authorization',
|
|
32
|
+
]);
|
|
28
33
|
const cacheableStore = new WeakableMap();
|
|
29
34
|
const redirectCodes = new Set([301, 302, 303, 307, 308]);
|
|
30
35
|
export { crossOriginStripHeaders } from './options.js';
|
|
@@ -698,6 +703,9 @@ export default class Request extends Duplex {
|
|
|
698
703
|
response = decompressResponse(response);
|
|
699
704
|
typedResponse = prepareResponse(response);
|
|
700
705
|
}
|
|
706
|
+
// `decompressResponse` wraps the response stream when it decompresses,
|
|
707
|
+
// so `response !== nativeResponse` indicates decompression happened.
|
|
708
|
+
const wasDecompressed = response !== nativeResponse;
|
|
701
709
|
this._responseSize = Number(response.headers['content-length']) || undefined;
|
|
702
710
|
this.response = typedResponse;
|
|
703
711
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
@@ -711,10 +719,26 @@ export default class Request extends Duplex {
|
|
|
711
719
|
isFromCache: typedResponse.isFromCache,
|
|
712
720
|
});
|
|
713
721
|
response.once('error', (error) => {
|
|
722
|
+
// Node synthesizes ECONNRESET for close-delimited responses after all body
|
|
723
|
+
// bytes have been delivered. Only ignore that late synthetic error on the
|
|
724
|
+
// native response. Wrapped decompression streams surface real checksum and
|
|
725
|
+
// truncation failures after the underlying response has completed.
|
|
726
|
+
if (!wasDecompressed
|
|
727
|
+
&& response.complete
|
|
728
|
+
&& this._responseSize === undefined
|
|
729
|
+
&& error.code === 'ECONNRESET') {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
714
732
|
this._aborted = true;
|
|
715
733
|
this._beforeError(new ReadError(error, this));
|
|
716
734
|
});
|
|
717
735
|
response.once('aborted', () => {
|
|
736
|
+
// Without Content-Length, connection close is the intended EOF signal (RFC 9110 §8.6),
|
|
737
|
+
// not a premature abort. For wrapped decompression streams, rely on the native
|
|
738
|
+
// response completion state because the wrapper strips `content-length`.
|
|
739
|
+
if (this._responseSize === undefined && nativeResponse.complete) {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
718
742
|
this._aborted = true;
|
|
719
743
|
// Check if there's a content-length mismatch to provide a more specific error
|
|
720
744
|
if (!this._checkContentLengthMismatch()) {
|
|
@@ -907,9 +931,6 @@ export default class Request extends Duplex {
|
|
|
907
931
|
this._beforeError(new HTTPError(typedResponse));
|
|
908
932
|
return;
|
|
909
933
|
}
|
|
910
|
-
// `decompressResponse` wraps the response stream when it decompresses,
|
|
911
|
-
// so `response !== nativeResponse` indicates decompression happened.
|
|
912
|
-
const wasDecompressed = response !== nativeResponse;
|
|
913
934
|
// Store the expected content-length from the native response for validation.
|
|
914
935
|
// This is the content-length before decompression, which is what actually gets transferred.
|
|
915
936
|
// Skip storing for responses that shouldn't have bodies per RFC 9110.
|
|
@@ -1528,11 +1549,6 @@ export default class Request extends Duplex {
|
|
|
1528
1549
|
}
|
|
1529
1550
|
async _makeRequest() {
|
|
1530
1551
|
const { options } = this;
|
|
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
1552
|
const shouldDeleteGeneratedHeader = (currentHeader, generatedHeader) => currentHeader === generatedHeader || is.undefined(currentHeader);
|
|
1537
1553
|
const syncGeneratedHeader = (name, { currentHeader, explicitHeader, nextHeader, staleGeneratedHeader, }) => {
|
|
1538
1554
|
if (!is.undefined(nextHeader)) {
|
|
@@ -1557,6 +1573,22 @@ export default class Request extends Duplex {
|
|
|
1557
1573
|
else if (is.null(currentHeaders[key])) {
|
|
1558
1574
|
throw new TypeError(`Use \`undefined\` instead of \`null\` to delete the \`${key}\` header`);
|
|
1559
1575
|
}
|
|
1576
|
+
else if (Array.isArray(currentHeaders[key]) && key === 'transfer-encoding') {
|
|
1577
|
+
// Node serializes request header arrays as repeated field lines. Keep framing
|
|
1578
|
+
// unambiguous by allowing only one transfer-encoding value here.
|
|
1579
|
+
if (currentHeaders[key].length !== 1) {
|
|
1580
|
+
throw new TypeError(`The \`${key}\` header must be a single value`);
|
|
1581
|
+
}
|
|
1582
|
+
options.setInternalHeader(key, currentHeaders[key][0]);
|
|
1583
|
+
}
|
|
1584
|
+
else if (Array.isArray(currentHeaders[key]) && singleValueRequestHeaders.has(key)) {
|
|
1585
|
+
// Duplicate credential and content-length lines are not allowed on requests.
|
|
1586
|
+
// Normalize a single-element array to match the long-supported string path.
|
|
1587
|
+
if (currentHeaders[key].length !== 1) {
|
|
1588
|
+
throw new TypeError(`The \`${key}\` header must be a single value`);
|
|
1589
|
+
}
|
|
1590
|
+
options.setInternalHeader(key, currentHeaders[key][0]);
|
|
1591
|
+
}
|
|
1560
1592
|
}
|
|
1561
1593
|
return currentHeaders;
|
|
1562
1594
|
};
|
|
@@ -1568,6 +1600,12 @@ export default class Request extends Duplex {
|
|
|
1568
1600
|
return is.nonEmptyString(cookieString) ? cookieString : undefined;
|
|
1569
1601
|
};
|
|
1570
1602
|
const headers = sanitizeHeaders();
|
|
1603
|
+
const initialHeaders = options.getInternalHeaders();
|
|
1604
|
+
const authorizationWasInitiallyExplicit = options.isHeaderExplicitlySet('authorization');
|
|
1605
|
+
const explicitAuthorizationHeader = authorizationWasInitiallyExplicit ? initialHeaders.authorization : undefined;
|
|
1606
|
+
const explicitCookieHeader = options.isHeaderExplicitlySet('cookie') ? initialHeaders.cookie : undefined;
|
|
1607
|
+
const authorizationWasInitiallyOmitted = options.isHeaderExplicitlySet('authorization') && is.undefined(initialHeaders.authorization);
|
|
1608
|
+
const cookieWasInitiallyOmitted = options.isHeaderExplicitlySet('cookie') && is.undefined(initialHeaders.cookie);
|
|
1571
1609
|
if (options.decompress && is.undefined(headers['accept-encoding'])) {
|
|
1572
1610
|
const encodings = ['gzip', 'deflate'];
|
|
1573
1611
|
if (supportsBrotli) {
|
|
@@ -1580,7 +1618,11 @@ export default class Request extends Duplex {
|
|
|
1580
1618
|
}
|
|
1581
1619
|
const { username, password } = options;
|
|
1582
1620
|
const cookieJar = options.cookieJar;
|
|
1583
|
-
|
|
1621
|
+
// Preserve an explicit Authorization header over URL-derived Basic auth. This keeps
|
|
1622
|
+
// normalized single-element arrays aligned with the long-supported string behavior.
|
|
1623
|
+
const generatedAuthorizationHeader = is.undefined(explicitAuthorizationHeader)
|
|
1624
|
+
? getAuthorizationHeader(username, password, authorizationWasInitiallyOmitted)
|
|
1625
|
+
: undefined;
|
|
1584
1626
|
let generatedCookieHeader;
|
|
1585
1627
|
if (!is.undefined(generatedAuthorizationHeader)) {
|
|
1586
1628
|
options.setInternalHeader('authorization', generatedAuthorizationHeader);
|
|
@@ -1610,11 +1652,21 @@ export default class Request extends Duplex {
|
|
|
1610
1652
|
// `headers.authorization = undefined` / `headers.cookie = undefined` is an
|
|
1611
1653
|
// explicit opt-out. Respect that instead of regenerating values from URL
|
|
1612
1654
|
// credentials or the cookie jar later in request setup.
|
|
1613
|
-
const isHeaderExplicitlyOmitted = (header) => options.isHeaderExplicitlySet(header)
|
|
1614
|
-
|
|
1615
|
-
|
|
1655
|
+
const isHeaderExplicitlyOmitted = (header) => options.isHeaderExplicitlySet(header)
|
|
1656
|
+
&& Object.hasOwn(currentHeaders, header)
|
|
1657
|
+
&& is.undefined(currentHeaders[header]);
|
|
1616
1658
|
const currentAuthorizationHeader = currentHeaders.authorization;
|
|
1617
1659
|
const currentCookieHeader = currentHeaders.cookie;
|
|
1660
|
+
// Authorization follows a small contract:
|
|
1661
|
+
// - A concrete Authorization header is sent as-is.
|
|
1662
|
+
// - `authorization = undefined` means omit Authorization entirely, including URL auth.
|
|
1663
|
+
// - Deleting an Authorization header that started explicit also means omit it.
|
|
1664
|
+
// - Otherwise, if the request did not start with explicit Authorization, Got may
|
|
1665
|
+
// generate Basic auth from the current username/password.
|
|
1666
|
+
const authorizationWasExplicitlyOmitted = isHeaderExplicitlyOmitted('authorization')
|
|
1667
|
+
|| (authorizationWasInitiallyExplicit && is.undefined(currentAuthorizationHeader));
|
|
1668
|
+
const cookieWasExplicitlyOmitted = is.undefined(currentCookieHeader)
|
|
1669
|
+
&& (cookieWasInitiallyOmitted || isHeaderExplicitlyOmitted('cookie'));
|
|
1618
1670
|
sanitizeHeaders();
|
|
1619
1671
|
if (!is.undefined(currentHeaders['transfer-encoding']) && !is.undefined(currentHeaders['content-length'])) {
|
|
1620
1672
|
options.deleteInternalHeader('content-length');
|
|
@@ -1626,15 +1678,22 @@ export default class Request extends Duplex {
|
|
|
1626
1678
|
delete options.headers.authorization;
|
|
1627
1679
|
}
|
|
1628
1680
|
}
|
|
1629
|
-
const authorizationHeader =
|
|
1681
|
+
const authorizationHeader = !authorizationWasInitiallyExplicit
|
|
1682
|
+
&& !authorizationWasInitiallyOmitted
|
|
1683
|
+
&& !authorizationWasExplicitlyOmitted
|
|
1684
|
+
? getAuthorizationHeader(options.username, options.password, authorizationWasExplicitlyOmitted)
|
|
1685
|
+
: undefined;
|
|
1630
1686
|
const cookieJar = options.cookieJar;
|
|
1631
|
-
if (changedState.has('authorization')) {
|
|
1687
|
+
if (changedState.has('authorization') && !is.undefined(currentAuthorizationHeader)) {
|
|
1632
1688
|
// A beforeRequest hook intentionally set the outgoing Authorization header.
|
|
1633
1689
|
}
|
|
1634
1690
|
else {
|
|
1691
|
+
const restorableAuthorizationHeader = changedState.has('authorization') && is.undefined(currentAuthorizationHeader)
|
|
1692
|
+
? undefined
|
|
1693
|
+
: explicitAuthorizationHeader;
|
|
1635
1694
|
syncGeneratedHeader('authorization', {
|
|
1636
1695
|
currentHeader: currentAuthorizationHeader,
|
|
1637
|
-
explicitHeader:
|
|
1696
|
+
explicitHeader: restorableAuthorizationHeader,
|
|
1638
1697
|
nextHeader: authorizationHeader,
|
|
1639
1698
|
staleGeneratedHeader: generatedAuthorizationHeader,
|
|
1640
1699
|
});
|
|
@@ -1649,10 +1708,13 @@ export default class Request extends Duplex {
|
|
|
1649
1708
|
// A beforeRequest hook intentionally set the outgoing Cookie header.
|
|
1650
1709
|
}
|
|
1651
1710
|
else {
|
|
1711
|
+
const cookieHeader = !cookieWasInitiallyOmitted && !cookieWasExplicitlyOmitted
|
|
1712
|
+
? await getCookieHeader(cookieJar)
|
|
1713
|
+
: undefined;
|
|
1652
1714
|
syncGeneratedHeader('cookie', {
|
|
1653
1715
|
currentHeader: currentCookieHeader,
|
|
1654
1716
|
explicitHeader: explicitCookieHeader,
|
|
1655
|
-
nextHeader:
|
|
1717
|
+
nextHeader: cookieHeader,
|
|
1656
1718
|
staleGeneratedHeader: generatedCookieHeader,
|
|
1657
1719
|
});
|
|
1658
1720
|
}
|
|
@@ -1464,6 +1464,7 @@ export default class Options {
|
|
|
1464
1464
|
set strictContentLength(value: boolean);
|
|
1465
1465
|
toJSON(): {
|
|
1466
1466
|
timeout: Delays;
|
|
1467
|
+
localAddress: string | undefined;
|
|
1467
1468
|
headers: Headers;
|
|
1468
1469
|
request: RequestFunction | undefined;
|
|
1469
1470
|
json: unknown;
|
|
@@ -1496,7 +1497,6 @@ export default class Options {
|
|
|
1496
1497
|
dnsLookupIpVersion: DnsLookupIpVersion;
|
|
1497
1498
|
parseJson: ParseJsonFunction;
|
|
1498
1499
|
stringifyJson: StringifyJsonFunction;
|
|
1499
|
-
localAddress: string | undefined;
|
|
1500
1500
|
method: Method;
|
|
1501
1501
|
createConnection: CreateConnectionFunction | undefined;
|
|
1502
1502
|
cacheOptions: CacheOptions;
|
|
@@ -93,13 +93,14 @@ function assertValidHeaderName(name) {
|
|
|
93
93
|
Safely assign own properties from source to target, skipping `__proto__` to prevent prototype pollution from JSON.parse'd input.
|
|
94
94
|
*/
|
|
95
95
|
function safeObjectAssign(target, source) {
|
|
96
|
-
for (const key of Object.
|
|
96
|
+
for (const [key, value] of Object.entries(source)) {
|
|
97
97
|
if (key === '__proto__') {
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
|
-
target
|
|
100
|
+
Reflect.set(target, key, value);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
const isToughCookieJar = (cookieJar) => cookieJar.setCookie.length === 4 && cookieJar.getCookieString.length === 0;
|
|
103
104
|
function validateSearchParameters(searchParameters) {
|
|
104
105
|
for (const key of Object.keys(searchParameters)) {
|
|
105
106
|
if (key === '__proto__') {
|
|
@@ -916,10 +917,10 @@ export default class Options {
|
|
|
916
917
|
assert.function(setCookie);
|
|
917
918
|
assert.function(getCookieString);
|
|
918
919
|
/* istanbul ignore next: Horrible `tough-cookie` v3 check */
|
|
919
|
-
if (
|
|
920
|
+
if (isToughCookieJar(value)) {
|
|
920
921
|
this.#internals.cookieJar = {
|
|
921
|
-
setCookie: promisify(setCookie.bind(value)),
|
|
922
|
-
getCookieString: promisify(getCookieString.bind(value)),
|
|
922
|
+
setCookie: promisify(value.setCookie.bind(value)),
|
|
923
|
+
getCookieString: promisify(value.getCookieString.bind(value)),
|
|
923
924
|
};
|
|
924
925
|
}
|
|
925
926
|
else {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { errorMonitor } from 'node:events';
|
|
2
2
|
import { types } from 'node:util';
|
|
3
3
|
import deferToConnect from './defer-to-connect.js';
|
|
4
|
+
const getInitialConnectionTimings = (socket) => Reflect.get(socket, '__initial_connection_timings__');
|
|
5
|
+
const setInitialConnectionTimings = (socket, timings) => {
|
|
6
|
+
Reflect.set(socket, '__initial_connection_timings__', timings);
|
|
7
|
+
};
|
|
4
8
|
const timer = (request) => {
|
|
5
9
|
if (request.timings) {
|
|
6
10
|
return request.timings;
|
|
@@ -58,11 +62,12 @@ const timer = (request) => {
|
|
|
58
62
|
// original connection so they're not lost.
|
|
59
63
|
timings.lookup = timings.socket;
|
|
60
64
|
timings.connect = timings.socket;
|
|
61
|
-
|
|
65
|
+
const initialConnectionTimings = getInitialConnectionTimings(socket);
|
|
66
|
+
if (initialConnectionTimings) {
|
|
62
67
|
// Restore the phase timings from the initial connection
|
|
63
|
-
timings.phases.dns =
|
|
64
|
-
timings.phases.tcp =
|
|
65
|
-
timings.phases.tls =
|
|
68
|
+
timings.phases.dns = initialConnectionTimings.dnsPhase;
|
|
69
|
+
timings.phases.tcp = initialConnectionTimings.tcpPhase;
|
|
70
|
+
timings.phases.tls = initialConnectionTimings.tlsPhase;
|
|
66
71
|
// Set secureConnect timestamp if there was TLS
|
|
67
72
|
if (timings.phases.tls !== undefined) {
|
|
68
73
|
timings.secureConnect = timings.socket;
|
|
@@ -100,18 +105,21 @@ const timer = (request) => {
|
|
|
100
105
|
timings.phases.dns = 0;
|
|
101
106
|
}
|
|
102
107
|
// Store connection phase timings on socket for potential reuse
|
|
103
|
-
socket
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
if (!getInitialConnectionTimings(socket)) {
|
|
109
|
+
setInitialConnectionTimings(socket, {
|
|
110
|
+
dnsPhase: timings.phases.dns,
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- TypeScript can't prove this is defined due to callback structure
|
|
112
|
+
tcpPhase: timings.phases.tcp,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
108
115
|
},
|
|
109
116
|
secureConnect() {
|
|
110
117
|
timings.secureConnect = Date.now();
|
|
111
118
|
timings.phases.tls = timings.secureConnect - timings.connect;
|
|
112
119
|
// Update stored timings with TLS phase timing
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
const initialConnectionTimings = getInitialConnectionTimings(socket);
|
|
121
|
+
if (initialConnectionTimings) {
|
|
122
|
+
initialConnectionTimings.tlsPhase = timings.phases.tls;
|
|
115
123
|
}
|
|
116
124
|
},
|
|
117
125
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "got",
|
|
3
|
-
"version": "15.0.
|
|
3
|
+
"version": "15.0.3",
|
|
4
4
|
"description": "Human-friendly and powerful HTTP request library for Node.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "sindresorhus/got",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"ky"
|
|
52
52
|
],
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@sindresorhus/is": "^
|
|
54
|
+
"@sindresorhus/is": "^8.0.0",
|
|
55
55
|
"byte-counter": "^0.1.0",
|
|
56
56
|
"cacheable-lookup": "^7.0.0",
|
|
57
57
|
"cacheable-request": "^13.0.18",
|
|
@@ -61,27 +61,27 @@
|
|
|
61
61
|
"keyv": "^5.6.0",
|
|
62
62
|
"lowercase-keys": "^4.0.1",
|
|
63
63
|
"responselike": "^4.0.2",
|
|
64
|
-
"type-fest": "^5.
|
|
64
|
+
"type-fest": "^5.6.0",
|
|
65
65
|
"uint8array-extras": "^1.5.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@hapi/bourne": "^3.0.0",
|
|
69
69
|
"@sindresorhus/tsconfig": "^8.1.0",
|
|
70
|
-
"@sinonjs/fake-timers": "^15.
|
|
70
|
+
"@sinonjs/fake-timers": "^15.3.2",
|
|
71
71
|
"@types/benchmark": "^2.1.5",
|
|
72
72
|
"@types/express": "^5.0.6",
|
|
73
|
-
"@types/node": "^25.
|
|
73
|
+
"@types/node": "^25.6.0",
|
|
74
74
|
"@types/pem": "^1.14.4",
|
|
75
75
|
"@types/readable-stream": "^4.0.23",
|
|
76
76
|
"@types/request": "^2.48.13",
|
|
77
77
|
"@types/sinon": "^21.0.0",
|
|
78
78
|
"@types/sinonjs__fake-timers": "^15.0.1",
|
|
79
79
|
"ava": "^6.4.1",
|
|
80
|
-
"axios": "^1.
|
|
80
|
+
"axios": "^1.15.1",
|
|
81
81
|
"benchmark": "^2.1.4",
|
|
82
82
|
"bluebird": "^3.7.2",
|
|
83
83
|
"body-parser": "^2.2.2",
|
|
84
|
-
"c8": "^
|
|
84
|
+
"c8": "^11.0.0",
|
|
85
85
|
"create-cert": "^1.0.6",
|
|
86
86
|
"create-test-server": "^3.0.1",
|
|
87
87
|
"del-cli": "^7.0.0",
|
|
@@ -90,14 +90,14 @@
|
|
|
90
90
|
"express": "^5.2.1",
|
|
91
91
|
"get-stream": "^9.0.1",
|
|
92
92
|
"node-fetch": "^3.3.2",
|
|
93
|
-
"np": "^11.0
|
|
93
|
+
"np": "^11.2.0",
|
|
94
94
|
"p-event": "^7.1.0",
|
|
95
95
|
"pem": "^1.14.8",
|
|
96
96
|
"pify": "^6.1.0",
|
|
97
97
|
"quick-lru": "^7.3.0",
|
|
98
98
|
"readable-stream": "^4.7.0",
|
|
99
99
|
"request": "^2.88.2",
|
|
100
|
-
"sinon": "^21.
|
|
100
|
+
"sinon": "^21.1.2",
|
|
101
101
|
"slow-stream": "0.0.4",
|
|
102
102
|
"tempy": "^3.2.0",
|
|
103
103
|
"then-busboy": "^5.2.1",
|