got 14.5.0 → 14.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,11 +11,43 @@ import http2wrapper from 'http2-wrapper';
11
11
  import { isFormData } from 'form-data-encoder';
12
12
  import parseLinkHeader from './parse-link-header.js';
13
13
  const [major, minor] = process.versions.node.split('.').map(Number);
14
+ /**
15
+ Generic helper that wraps any assertion function to add context to error messages.
16
+ */
17
+ function wrapAssertionWithContext(optionName, assertionFn) {
18
+ try {
19
+ assertionFn();
20
+ }
21
+ catch (error) {
22
+ if (error instanceof Error) {
23
+ error.message = `Option '${optionName}': ${error.message}`;
24
+ }
25
+ throw error;
26
+ }
27
+ }
28
+ /**
29
+ Helper function that wraps assert.any() to provide better error messages.
30
+ When assertion fails, it includes the option name in the error message.
31
+ */
32
+ function assertAny(optionName, validators, value) {
33
+ wrapAssertionWithContext(optionName, () => {
34
+ assert.any(validators, value);
35
+ });
36
+ }
37
+ /**
38
+ Helper function that wraps assert.plainObject() to provide better error messages.
39
+ When assertion fails, it includes the option name in the error message.
40
+ */
41
+ function assertPlainObject(optionName, value) {
42
+ wrapAssertionWithContext(optionName, () => {
43
+ assert.plainObject(value);
44
+ });
45
+ }
14
46
  function validateSearchParameters(searchParameters) {
15
47
  // eslint-disable-next-line guard-for-in
16
48
  for (const key in searchParameters) {
17
49
  const value = searchParameters[key];
18
- assert.any([is.string, is.number, is.boolean, is.null, is.undefined], value);
50
+ assertAny(`searchParams.${key}`, [is.string, is.number, is.boolean, is.null, is.undefined], value);
19
51
  }
20
52
  }
21
53
  const globalCache = new Map();
@@ -95,6 +127,7 @@ const defaultInternals = {
95
127
  beforeError: [],
96
128
  beforeRedirect: [],
97
129
  beforeRetry: [],
130
+ beforeCache: [],
98
131
  afterResponse: [],
99
132
  },
100
133
  followRedirect: true,
@@ -105,6 +138,7 @@ const defaultInternals = {
105
138
  password: '',
106
139
  http2: false,
107
140
  allowGetBody: false,
141
+ copyPipedHeaders: true,
108
142
  headers: {
109
143
  'user-agent': 'got (https://github.com/sindresorhus/got)',
110
144
  },
@@ -179,6 +213,7 @@ const defaultInternals = {
179
213
  dhparam: undefined,
180
214
  ecdhCurve: undefined,
181
215
  certificateRevocationLists: undefined,
216
+ secureOptions: undefined,
182
217
  },
183
218
  encoding: undefined,
184
219
  resolveBodyOnly: false,
@@ -217,6 +252,7 @@ const defaultInternals = {
217
252
  maxHeaderSize: undefined,
218
253
  signal: undefined,
219
254
  enableUnixSockets: false,
255
+ strictContentLength: false,
220
256
  };
221
257
  const cloneInternals = (internals) => {
222
258
  const { hooks, retry } = internals;
@@ -240,14 +276,12 @@ const cloneInternals = (internals) => {
240
276
  beforeError: [...hooks.beforeError],
241
277
  beforeRedirect: [...hooks.beforeRedirect],
242
278
  beforeRetry: [...hooks.beforeRetry],
279
+ beforeCache: [...hooks.beforeCache],
243
280
  afterResponse: [...hooks.afterResponse],
244
281
  },
245
282
  searchParams: internals.searchParams ? new URLSearchParams(internals.searchParams) : undefined,
246
283
  pagination: { ...internals.pagination },
247
284
  };
248
- if (result.url !== undefined) {
249
- result.prefixUrl = '';
250
- }
251
285
  return result;
252
286
  };
253
287
  const cloneRaw = (raw) => {
@@ -305,11 +339,24 @@ const cloneRaw = (raw) => {
305
339
  if (is.array(hooks.beforeRetry)) {
306
340
  result.hooks.beforeRetry = [...hooks.beforeRetry];
307
341
  }
342
+ if (is.array(hooks.beforeCache)) {
343
+ result.hooks.beforeCache = [...hooks.beforeCache];
344
+ }
308
345
  if (is.array(hooks.afterResponse)) {
309
346
  result.hooks.afterResponse = [...hooks.afterResponse];
310
347
  }
311
348
  }
312
- // TODO: raw.searchParams
349
+ if (raw.searchParams) {
350
+ if (is.string(raw.searchParams)) {
351
+ result.searchParams = raw.searchParams;
352
+ }
353
+ else if (raw.searchParams instanceof URLSearchParams) {
354
+ result.searchParams = new URLSearchParams(raw.searchParams);
355
+ }
356
+ else if (is.object(raw.searchParams)) {
357
+ result.searchParams = { ...raw.searchParams };
358
+ }
359
+ }
313
360
  if (is.object(raw.pagination)) {
314
361
  result.pagination = { ...raw.pagination };
315
362
  }
@@ -333,19 +380,17 @@ const init = (options, withOptions, self) => {
333
380
  export default class Options {
334
381
  _unixOptions;
335
382
  _internals;
336
- _merging;
383
+ _merging = false;
337
384
  _init;
338
385
  constructor(input, options, defaults) {
339
- assert.any([is.string, is.urlInstance, is.object, is.undefined], input);
340
- assert.any([is.object, is.undefined], options);
341
- assert.any([is.object, is.undefined], defaults);
386
+ assertAny('input', [is.string, is.urlInstance, is.object, is.undefined], input);
387
+ assertAny('options', [is.object, is.undefined], options);
388
+ assertAny('defaults', [is.object, is.undefined], defaults);
342
389
  if (input instanceof Options || options instanceof Options) {
343
390
  throw new TypeError('The defaults must be passed as the third argument');
344
391
  }
345
392
  this._internals = cloneInternals(defaults?._internals ?? defaults ?? defaultInternals);
346
393
  this._init = [...(defaults?._init ?? [])];
347
- this._merging = false;
348
- this._unixOptions = undefined;
349
394
  // This rule allows `finally` to be considered more important.
350
395
  // Meaning no matter the error thrown in the `try` block,
351
396
  // if `finally` throws then the `finally` error will be thrown.
@@ -456,7 +501,7 @@ export default class Options {
456
501
  return this._internals.request;
457
502
  }
458
503
  set request(value) {
459
- assert.any([is.function, is.undefined], value);
504
+ assertAny('request', [is.function, is.undefined], value);
460
505
  this._internals.request = value;
461
506
  }
462
507
  /**
@@ -485,14 +530,14 @@ export default class Options {
485
530
  return this._internals.agent;
486
531
  }
487
532
  set agent(value) {
488
- assert.plainObject(value);
533
+ assertPlainObject('agent', value);
489
534
  // eslint-disable-next-line guard-for-in
490
535
  for (const key in value) {
491
536
  if (!(key in this._internals.agent)) {
492
537
  throw new TypeError(`Unexpected agent option: ${key}`);
493
538
  }
494
539
  // @ts-expect-error - No idea why `value[key]` doesn't work here.
495
- assert.any([is.object, is.undefined, (v) => v === false], value[key]);
540
+ assertAny(`agent.${key}`, [is.object, is.undefined, (v) => v === false], value[key]);
496
541
  }
497
542
  if (this._merging) {
498
543
  Object.assign(this._internals.agent, value);
@@ -545,14 +590,14 @@ export default class Options {
545
590
  return this._internals.timeout;
546
591
  }
547
592
  set timeout(value) {
548
- assert.plainObject(value);
593
+ assertPlainObject('timeout', value);
549
594
  // eslint-disable-next-line guard-for-in
550
595
  for (const key in value) {
551
596
  if (!(key in this._internals.timeout)) {
552
597
  throw new Error(`Unexpected timeout option: ${key}`);
553
598
  }
554
599
  // @ts-expect-error - No idea why `value[key]` doesn't work here.
555
- assert.any([is.number, is.undefined], value[key]);
600
+ assertAny(`timeout.${key}`, [is.number, is.undefined], value[key]);
556
601
  }
557
602
  if (this._merging) {
558
603
  Object.assign(this._internals.timeout, value);
@@ -606,7 +651,7 @@ export default class Options {
606
651
  return this._internals.prefixUrl;
607
652
  }
608
653
  set prefixUrl(value) {
609
- assert.any([is.string, is.urlInstance], value);
654
+ assertAny('prefixUrl', [is.string, is.urlInstance], value);
610
655
  if (value === '') {
611
656
  this._internals.prefixUrl = '';
612
657
  return;
@@ -630,7 +675,7 @@ export default class Options {
630
675
 
631
676
  __Note #4__: This option is not enumerable and will not be merged with the instance defaults.
632
677
 
633
- The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
678
+ The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / typed array ([`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), etc.) / [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
634
679
 
635
680
  Since Got 12, the `content-length` is not automatically set when `body` is a `fs.createReadStream`.
636
681
 
@@ -655,7 +700,7 @@ export default class Options {
655
700
  return this._internals.body;
656
701
  }
657
702
  set body(value) {
658
- assert.any([is.string, is.buffer, is.nodeStream, is.generator, is.asyncGenerator, is.iterable, is.asyncIterable, isFormData, is.undefined], value);
703
+ assertAny('body', [is.string, is.buffer, is.nodeStream, is.generator, is.asyncGenerator, is.iterable, is.asyncIterable, isFormData, is.typedArray, is.undefined], value);
659
704
  if (is.nodeStream(value)) {
660
705
  assert.truthy(value.readable);
661
706
  }
@@ -678,7 +723,7 @@ export default class Options {
678
723
  return this._internals.form;
679
724
  }
680
725
  set form(value) {
681
- assert.any([is.plainObject, is.undefined], value);
726
+ assertAny('form', [is.plainObject, is.undefined], value);
682
727
  if (value !== undefined) {
683
728
  assert.undefined(this._internals.body);
684
729
  assert.undefined(this._internals.json);
@@ -686,7 +731,9 @@ export default class Options {
686
731
  this._internals.form = value;
687
732
  }
688
733
  /**
689
- JSON body. If the `Content-Type` header is not set, it will be set to `application/json`.
734
+ JSON request body. If the `content-type` header is not set, it will be set to `application/json`.
735
+
736
+ __Important__: This option only affects the request body you send to the server. To parse the response as JSON, you must either call `.json()` on the promise or set `responseType: 'json'` in the options.
690
737
 
691
738
  __Note #1__: If you provide this option, `got.stream()` will be read-only.
692
739
 
@@ -724,7 +771,7 @@ export default class Options {
724
771
  return this._internals.url;
725
772
  }
726
773
  set url(value) {
727
- assert.any([is.string, is.urlInstance, is.undefined], value);
774
+ assertAny('url', [is.string, is.urlInstance, is.undefined], value);
728
775
  if (value === undefined) {
729
776
  this._internals.url = undefined;
730
777
  return;
@@ -732,7 +779,11 @@ export default class Options {
732
779
  if (is.string(value) && value.startsWith('/')) {
733
780
  throw new Error('`url` must not start with a slash');
734
781
  }
735
- const urlString = `${this.prefixUrl}${value.toString()}`;
782
+ // Detect if URL is already absolute (has a protocol/scheme)
783
+ const valueString = value.toString();
784
+ const isAbsolute = is.urlInstance(value) || /^[a-z][a-z\d+.-]*:/i.test(valueString);
785
+ // Only concatenate prefixUrl if the URL is relative
786
+ const urlString = isAbsolute ? valueString : `${this.prefixUrl}${valueString}`;
736
787
  const url = new URL(urlString);
737
788
  this._internals.url = url;
738
789
  if (url.protocol === 'unix:') {
@@ -784,7 +835,7 @@ export default class Options {
784
835
  return this._internals.cookieJar;
785
836
  }
786
837
  set cookieJar(value) {
787
- assert.any([is.object, is.undefined], value);
838
+ assertAny('cookieJar', [is.object, is.undefined], value);
788
839
  if (value === undefined) {
789
840
  this._internals.cookieJar = undefined;
790
841
  return;
@@ -871,7 +922,7 @@ export default class Options {
871
922
  return this._internals.searchParams;
872
923
  }
873
924
  set searchParams(value) {
874
- assert.any([is.string, is.object, is.undefined], value);
925
+ assertAny('searchParams', [is.string, is.object, is.undefined], value);
875
926
  const url = this._internals.url;
876
927
  if (value === undefined) {
877
928
  this._internals.searchParams = undefined;
@@ -931,7 +982,7 @@ export default class Options {
931
982
  return this._internals.dnsLookup;
932
983
  }
933
984
  set dnsLookup(value) {
934
- assert.any([is.function, is.undefined], value);
985
+ assertAny('dnsLookup', [is.function, is.undefined], value);
935
986
  this._internals.dnsLookup = value;
936
987
  }
937
988
  /**
@@ -948,7 +999,7 @@ export default class Options {
948
999
  return this._internals.dnsCache;
949
1000
  }
950
1001
  set dnsCache(value) {
951
- assert.any([is.object, is.boolean, is.undefined], value);
1002
+ assertAny('dnsCache', [is.object, is.boolean, is.undefined], value);
952
1003
  if (value === true) {
953
1004
  this._internals.dnsCache = getGlobalDnsCache();
954
1005
  }
@@ -1018,7 +1069,7 @@ export default class Options {
1018
1069
  }
1019
1070
  const typedKnownHookEvent = knownHookEvent;
1020
1071
  const hooks = value[typedKnownHookEvent];
1021
- assert.any([is.array, is.undefined], hooks);
1072
+ assertAny(`hooks.${knownHookEvent}`, [is.array, is.undefined], hooks);
1022
1073
  if (hooks) {
1023
1074
  for (const hook of hooks) {
1024
1075
  assert.function(hook);
@@ -1053,7 +1104,7 @@ export default class Options {
1053
1104
  return this._internals.followRedirect;
1054
1105
  }
1055
1106
  set followRedirect(value) {
1056
- assert.any([is.boolean, is.function], value);
1107
+ assertAny('followRedirect', [is.boolean, is.function], value);
1057
1108
  this._internals.followRedirect = value;
1058
1109
  }
1059
1110
  get followRedirects() {
@@ -1083,7 +1134,7 @@ export default class Options {
1083
1134
  return this._internals.cache;
1084
1135
  }
1085
1136
  set cache(value) {
1086
- assert.any([is.object, is.string, is.boolean, is.undefined], value);
1137
+ assertAny('cache', [is.object, is.string, is.boolean, is.undefined], value);
1087
1138
  if (value === true) {
1088
1139
  this._internals.cache = globalCache;
1089
1140
  }
@@ -1186,6 +1237,62 @@ export default class Options {
1186
1237
  this._internals.allowGetBody = value;
1187
1238
  }
1188
1239
  /**
1240
+ Automatically copy headers from piped streams.
1241
+
1242
+ When piping a request into a Got stream (e.g., `request.pipe(got.stream(url))`), this controls whether headers from the source stream are automatically merged into the Got request headers.
1243
+
1244
+ Note: Piped headers overwrite any explicitly set headers with the same name. To override this, either set `copyPipedHeaders` to `false` and manually copy safe headers, or use a `beforeRequest` hook to force specific header values after piping.
1245
+
1246
+ Useful for proxy scenarios, but you may want to disable this to filter out headers like `Host`, `Connection`, `Authorization`, etc.
1247
+
1248
+ @default true
1249
+
1250
+ @example
1251
+ ```
1252
+ import got from 'got';
1253
+ import {pipeline} from 'node:stream/promises';
1254
+
1255
+ // Disable automatic header copying and manually copy only safe headers
1256
+ server.get('/proxy', async (request, response) => {
1257
+ const gotStream = got.stream('https://example.com', {
1258
+ copyPipedHeaders: false,
1259
+ headers: {
1260
+ 'user-agent': request.headers['user-agent'],
1261
+ 'accept': request.headers['accept'],
1262
+ // Explicitly NOT copying host, connection, authorization, etc.
1263
+ }
1264
+ });
1265
+
1266
+ await pipeline(request, gotStream, response);
1267
+ });
1268
+ ```
1269
+
1270
+ @example
1271
+ ```
1272
+ import got from 'got';
1273
+
1274
+ // Override piped headers using beforeRequest hook
1275
+ const gotStream = got.stream('https://example.com', {
1276
+ hooks: {
1277
+ beforeRequest: [
1278
+ options => {
1279
+ // Force specific header values after piping
1280
+ options.headers.host = 'example.com';
1281
+ delete options.headers.authorization;
1282
+ }
1283
+ ]
1284
+ }
1285
+ });
1286
+ ```
1287
+ */
1288
+ get copyPipedHeaders() {
1289
+ return this._internals.copyPipedHeaders;
1290
+ }
1291
+ set copyPipedHeaders(value) {
1292
+ assert.boolean(value);
1293
+ this._internals.copyPipedHeaders = value;
1294
+ }
1295
+ /**
1189
1296
  Request headers.
1190
1297
 
1191
1298
  Existing headers will be overwritten. Headers set to `undefined` will be omitted.
@@ -1196,7 +1303,7 @@ export default class Options {
1196
1303
  return this._internals.headers;
1197
1304
  }
1198
1305
  set headers(value) {
1199
- assert.plainObject(value);
1306
+ assertPlainObject('headers', value);
1200
1307
  if (this._merging) {
1201
1308
  Object.assign(this._internals.headers, lowercaseKeys(value));
1202
1309
  }
@@ -1340,15 +1447,15 @@ export default class Options {
1340
1447
  return this._internals.retry;
1341
1448
  }
1342
1449
  set retry(value) {
1343
- assert.plainObject(value);
1344
- assert.any([is.function, is.undefined], value.calculateDelay);
1345
- assert.any([is.number, is.undefined], value.maxRetryAfter);
1346
- assert.any([is.number, is.undefined], value.limit);
1347
- assert.any([is.array, is.undefined], value.methods);
1348
- assert.any([is.array, is.undefined], value.statusCodes);
1349
- assert.any([is.array, is.undefined], value.errorCodes);
1350
- assert.any([is.number, is.undefined], value.noise);
1351
- assert.any([is.boolean, is.undefined], value.enforceRetryRules);
1450
+ assertPlainObject('retry', value);
1451
+ assertAny('retry.calculateDelay', [is.function, is.undefined], value.calculateDelay);
1452
+ assertAny('retry.maxRetryAfter', [is.number, is.undefined], value.maxRetryAfter);
1453
+ assertAny('retry.limit', [is.number, is.undefined], value.limit);
1454
+ assertAny('retry.methods', [is.array, is.undefined], value.methods);
1455
+ assertAny('retry.statusCodes', [is.array, is.undefined], value.statusCodes);
1456
+ assertAny('retry.errorCodes', [is.array, is.undefined], value.errorCodes);
1457
+ assertAny('retry.noise', [is.number, is.undefined], value.noise);
1458
+ assertAny('retry.enforceRetryRules', [is.boolean, is.undefined], value.enforceRetryRules);
1352
1459
  if (value.noise && Math.abs(value.noise) > 100) {
1353
1460
  throw new Error(`The maximum acceptable retry noise is +/- 100ms, got ${value.noise}`);
1354
1461
  }
@@ -1377,7 +1484,7 @@ export default class Options {
1377
1484
  return this._internals.localAddress;
1378
1485
  }
1379
1486
  set localAddress(value) {
1380
- assert.any([is.string, is.undefined], value);
1487
+ assertAny('localAddress', [is.string, is.undefined], value);
1381
1488
  this._internals.localAddress = value;
1382
1489
  }
1383
1490
  /**
@@ -1396,7 +1503,7 @@ export default class Options {
1396
1503
  return this._internals.createConnection;
1397
1504
  }
1398
1505
  set createConnection(value) {
1399
- assert.any([is.function, is.undefined], value);
1506
+ assertAny('createConnection', [is.function, is.undefined], value);
1400
1507
  this._internals.createConnection = value;
1401
1508
  }
1402
1509
  /**
@@ -1408,11 +1515,11 @@ export default class Options {
1408
1515
  return this._internals.cacheOptions;
1409
1516
  }
1410
1517
  set cacheOptions(value) {
1411
- assert.plainObject(value);
1412
- assert.any([is.boolean, is.undefined], value.shared);
1413
- assert.any([is.number, is.undefined], value.cacheHeuristic);
1414
- assert.any([is.number, is.undefined], value.immutableMinTimeToLive);
1415
- assert.any([is.boolean, is.undefined], value.ignoreCargoCult);
1518
+ assertPlainObject('cacheOptions', value);
1519
+ assertAny('cacheOptions.shared', [is.boolean, is.undefined], value.shared);
1520
+ assertAny('cacheOptions.cacheHeuristic', [is.number, is.undefined], value.cacheHeuristic);
1521
+ assertAny('cacheOptions.immutableMinTimeToLive', [is.number, is.undefined], value.immutableMinTimeToLive);
1522
+ assertAny('cacheOptions.ignoreCargoCult', [is.boolean, is.undefined], value.ignoreCargoCult);
1416
1523
  for (const key in value) {
1417
1524
  if (!(key in this._internals.cacheOptions)) {
1418
1525
  throw new Error(`Cache option \`${key}\` does not exist`);
@@ -1432,25 +1539,26 @@ export default class Options {
1432
1539
  return this._internals.https;
1433
1540
  }
1434
1541
  set https(value) {
1435
- assert.plainObject(value);
1436
- assert.any([is.boolean, is.undefined], value.rejectUnauthorized);
1437
- assert.any([is.function, is.undefined], value.checkServerIdentity);
1438
- assert.any([is.string, is.undefined], value.serverName);
1439
- assert.any([is.string, is.object, is.array, is.undefined], value.certificateAuthority);
1440
- assert.any([is.string, is.object, is.array, is.undefined], value.key);
1441
- assert.any([is.string, is.object, is.array, is.undefined], value.certificate);
1442
- assert.any([is.string, is.undefined], value.passphrase);
1443
- assert.any([is.string, is.buffer, is.array, is.undefined], value.pfx);
1444
- assert.any([is.array, is.undefined], value.alpnProtocols);
1445
- assert.any([is.string, is.undefined], value.ciphers);
1446
- assert.any([is.string, is.buffer, is.undefined], value.dhparam);
1447
- assert.any([is.string, is.undefined], value.signatureAlgorithms);
1448
- assert.any([is.string, is.undefined], value.minVersion);
1449
- assert.any([is.string, is.undefined], value.maxVersion);
1450
- assert.any([is.boolean, is.undefined], value.honorCipherOrder);
1451
- assert.any([is.number, is.undefined], value.tlsSessionLifetime);
1452
- assert.any([is.string, is.undefined], value.ecdhCurve);
1453
- assert.any([is.string, is.buffer, is.array, is.undefined], value.certificateRevocationLists);
1542
+ assertPlainObject('https', value);
1543
+ assertAny('https.rejectUnauthorized', [is.boolean, is.undefined], value.rejectUnauthorized);
1544
+ assertAny('https.checkServerIdentity', [is.function, is.undefined], value.checkServerIdentity);
1545
+ assertAny('https.serverName', [is.string, is.undefined], value.serverName);
1546
+ assertAny('https.certificateAuthority', [is.string, is.object, is.array, is.undefined], value.certificateAuthority);
1547
+ assertAny('https.key', [is.string, is.object, is.array, is.undefined], value.key);
1548
+ assertAny('https.certificate', [is.string, is.object, is.array, is.undefined], value.certificate);
1549
+ assertAny('https.passphrase', [is.string, is.undefined], value.passphrase);
1550
+ assertAny('https.pfx', [is.string, is.buffer, is.array, is.undefined], value.pfx);
1551
+ assertAny('https.alpnProtocols', [is.array, is.undefined], value.alpnProtocols);
1552
+ assertAny('https.ciphers', [is.string, is.undefined], value.ciphers);
1553
+ assertAny('https.dhparam', [is.string, is.buffer, is.undefined], value.dhparam);
1554
+ assertAny('https.signatureAlgorithms', [is.string, is.undefined], value.signatureAlgorithms);
1555
+ assertAny('https.minVersion', [is.string, is.undefined], value.minVersion);
1556
+ assertAny('https.maxVersion', [is.string, is.undefined], value.maxVersion);
1557
+ assertAny('https.honorCipherOrder', [is.boolean, is.undefined], value.honorCipherOrder);
1558
+ assertAny('https.tlsSessionLifetime', [is.number, is.undefined], value.tlsSessionLifetime);
1559
+ assertAny('https.ecdhCurve', [is.string, is.undefined], value.ecdhCurve);
1560
+ assertAny('https.certificateRevocationLists', [is.string, is.buffer, is.array, is.undefined], value.certificateRevocationLists);
1561
+ assertAny('https.secureOptions', [is.number, is.undefined], value.secureOptions);
1454
1562
  for (const key in value) {
1455
1563
  if (!(key in this._internals.https)) {
1456
1564
  throw new Error(`HTTPS option \`${key}\` does not exist`);
@@ -1480,7 +1588,7 @@ export default class Options {
1480
1588
  if (value === null) {
1481
1589
  throw new TypeError('To get a Buffer, set `options.responseType` to `buffer` instead');
1482
1590
  }
1483
- assert.any([is.string, is.undefined], value);
1591
+ assertAny('encoding', [is.string, is.undefined], value);
1484
1592
  this._internals.encoding = value;
1485
1593
  }
1486
1594
  /**
@@ -1580,7 +1688,7 @@ export default class Options {
1580
1688
  return this._internals.maxHeaderSize;
1581
1689
  }
1582
1690
  set maxHeaderSize(value) {
1583
- assert.any([is.number, is.undefined], value);
1691
+ assertAny('maxHeaderSize', [is.number, is.undefined], value);
1584
1692
  this._internals.maxHeaderSize = value;
1585
1693
  }
1586
1694
  get enableUnixSockets() {
@@ -1590,6 +1698,23 @@ export default class Options {
1590
1698
  assert.boolean(value);
1591
1699
  this._internals.enableUnixSockets = value;
1592
1700
  }
1701
+ /**
1702
+ Throw an error if the server response's `content-length` header value doesn't match the number of bytes received.
1703
+
1704
+ This is useful for detecting truncated responses and follows RFC 9112 requirements for message completeness.
1705
+
1706
+ __Note__: Responses without a `content-length` header are not validated.
1707
+ __Note__: When enabled and validation fails, a `ReadError` with code `ERR_HTTP_CONTENT_LENGTH_MISMATCH` will be thrown.
1708
+
1709
+ @default false
1710
+ */
1711
+ get strictContentLength() {
1712
+ return this._internals.strictContentLength;
1713
+ }
1714
+ set strictContentLength(value) {
1715
+ assert.boolean(value);
1716
+ this._internals.strictContentLength = value;
1717
+ }
1593
1718
  // eslint-disable-next-line @typescript-eslint/naming-convention
1594
1719
  toJSON() {
1595
1720
  return { ...this._internals };
@@ -1648,6 +1773,7 @@ export default class Options {
1648
1773
  dhparam: https.dhparam,
1649
1774
  ecdhCurve: https.ecdhCurve,
1650
1775
  crl: https.certificateRevocationLists,
1776
+ secureOptions: https.secureOptions,
1651
1777
  // HTTP options
1652
1778
  lookup: internals.dnsLookup ?? internals.dnsCache?.lookup,
1653
1779
  family: internals.dnsLookupIpVersion,
@@ -101,6 +101,8 @@ An error to be thrown when server response code is 2xx, and parsing body fails.
101
101
  Includes a `response` property.
102
102
  */
103
103
  export declare class ParseError extends RequestError {
104
+ name: string;
105
+ code: string;
104
106
  readonly response: Response;
105
107
  constructor(error: Error, response: Response);
106
108
  }
@@ -11,11 +11,11 @@ An error to be thrown when server response code is 2xx, and parsing body fails.
11
11
  Includes a `response` property.
12
12
  */
13
13
  export class ParseError extends RequestError {
14
+ name = 'ParseError';
15
+ code = 'ERR_BODY_PARSE_FAILURE';
14
16
  constructor(error, response) {
15
17
  const { options } = response.request;
16
18
  super(`${error.message} in "${options.url.toString()}"`, error, response.request);
17
- this.name = 'ParseError';
18
- this.code = 'ERR_BODY_PARSE_FAILURE';
19
19
  }
20
20
  }
21
21
  export const parseBody = (response, responseType, parseJson, encoding) => {
@@ -18,6 +18,7 @@ export type Delays = {
18
18
  export type ErrorCode = 'ETIMEDOUT' | 'ECONNRESET' | 'EADDRINUSE' | 'ECONNREFUSED' | 'EPIPE' | 'ENOTFOUND' | 'ENETUNREACH' | 'EAI_AGAIN';
19
19
  export declare class TimeoutError extends Error {
20
20
  event: string;
21
+ name: string;
21
22
  code: ErrorCode;
22
23
  constructor(threshold: number, event: string);
23
24
  }
@@ -4,12 +4,11 @@ const reentry = Symbol('reentry');
4
4
  const noop = () => { };
5
5
  export class TimeoutError extends Error {
6
6
  event;
7
- code;
7
+ name = 'TimeoutError';
8
+ code = 'ETIMEDOUT';
8
9
  constructor(threshold, event) {
9
10
  super(`Timeout awaiting '${event}' for ${threshold}ms`);
10
11
  this.event = event;
11
- this.name = 'TimeoutError';
12
- this.code = 'ETIMEDOUT';
13
12
  }
14
13
  }
15
14
  export default function timedOut(request, delays, options) {
@@ -1,4 +1,3 @@
1
- import { Buffer } from 'node:buffer';
2
1
  import { promisify } from 'node:util';
3
2
  import is from '@sindresorhus/is';
4
3
  import isFormData from './is-form-data.js';
@@ -10,11 +9,14 @@ export default async function getBodySize(body, headers) {
10
9
  return 0;
11
10
  }
12
11
  if (is.string(body)) {
13
- return Buffer.byteLength(body);
12
+ return new TextEncoder().encode(body).byteLength;
14
13
  }
15
14
  if (is.buffer(body)) {
16
15
  return body.length;
17
16
  }
17
+ if (is.typedArray(body)) {
18
+ return body.byteLength;
19
+ }
18
20
  if (isFormData(body)) {
19
21
  try {
20
22
  return await promisify(body.getLength.bind(body))();
@@ -1 +1,17 @@
1
1
  export default function isUnixSocketURL(url: URL): boolean;
2
+ /**
3
+ Extract the socket path from a UNIX socket URL.
4
+
5
+ @example
6
+ ```
7
+ getUnixSocketPath(new URL('http://unix/foo:/path'));
8
+ //=> '/foo'
9
+
10
+ getUnixSocketPath(new URL('unix:/foo:/path'));
11
+ //=> '/foo'
12
+
13
+ getUnixSocketPath(new URL('http://example.com'));
14
+ //=> undefined
15
+ ```
16
+ */
17
+ export declare function getUnixSocketPath(url: URL): string | undefined;
@@ -2,3 +2,24 @@
2
2
  export default function isUnixSocketURL(url) {
3
3
  return url.protocol === 'unix:' || url.hostname === 'unix';
4
4
  }
5
+ /**
6
+ Extract the socket path from a UNIX socket URL.
7
+
8
+ @example
9
+ ```
10
+ getUnixSocketPath(new URL('http://unix/foo:/path'));
11
+ //=> '/foo'
12
+
13
+ getUnixSocketPath(new URL('unix:/foo:/path'));
14
+ //=> '/foo'
15
+
16
+ getUnixSocketPath(new URL('http://example.com'));
17
+ //=> undefined
18
+ ```
19
+ */
20
+ export function getUnixSocketPath(url) {
21
+ if (!isUnixSocketURL(url)) {
22
+ return undefined;
23
+ }
24
+ return /(?<socketPath>.+?):(?<path>.+)/.exec(`${url.pathname}${url.search}`)?.groups?.socketPath;
25
+ }
@@ -1,7 +1,6 @@
1
1
  export default class WeakableMap<K, V> {
2
2
  weakMap: WeakMap<Record<string, unknown>, V>;
3
3
  map: Map<K, V>;
4
- constructor();
5
4
  set(key: K, value: V): void;
6
5
  get(key: K): V | undefined;
7
6
  has(key: K): boolean;