got 14.6.6 → 15.0.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.
Files changed (37) hide show
  1. package/dist/source/as-promise/index.d.ts +2 -2
  2. package/dist/source/as-promise/index.js +105 -85
  3. package/dist/source/as-promise/types.d.ts +10 -23
  4. package/dist/source/as-promise/types.js +1 -17
  5. package/dist/source/core/calculate-retry-delay.js +1 -4
  6. package/dist/source/core/diagnostics-channel.js +12 -21
  7. package/dist/source/core/errors.d.ts +2 -1
  8. package/dist/source/core/errors.js +10 -13
  9. package/dist/source/core/index.d.ts +19 -7
  10. package/dist/source/core/index.js +748 -327
  11. package/dist/source/core/options.d.ts +117 -116
  12. package/dist/source/core/options.js +620 -309
  13. package/dist/source/core/response.d.ts +5 -3
  14. package/dist/source/core/response.js +26 -3
  15. package/dist/source/core/timed-out.d.ts +1 -1
  16. package/dist/source/core/timed-out.js +4 -4
  17. package/dist/source/core/utils/defer-to-connect.js +5 -17
  18. package/dist/source/core/utils/get-body-size.d.ts +1 -1
  19. package/dist/source/core/utils/get-body-size.js +3 -20
  20. package/dist/source/core/utils/is-unix-socket-url.d.ts +1 -1
  21. package/dist/source/core/utils/is-unix-socket-url.js +3 -4
  22. package/dist/source/core/utils/proxy-events.d.ts +1 -1
  23. package/dist/source/core/utils/proxy-events.js +3 -3
  24. package/dist/source/core/utils/strip-url-auth.d.ts +1 -0
  25. package/dist/source/core/utils/strip-url-auth.js +9 -0
  26. package/dist/source/core/utils/timer.js +5 -7
  27. package/dist/source/core/utils/unhandle.js +1 -2
  28. package/dist/source/create.js +83 -27
  29. package/dist/source/index.d.ts +2 -3
  30. package/dist/source/index.js +0 -4
  31. package/dist/source/types.d.ts +39 -70
  32. package/package.json +34 -38
  33. package/readme.md +2 -5
  34. package/dist/source/core/utils/is-form-data.d.ts +0 -7
  35. package/dist/source/core/utils/is-form-data.js +0 -4
  36. package/dist/source/core/utils/url-to-options.d.ts +0 -14
  37. package/dist/source/core/utils/url-to-options.js +0 -22
@@ -1,5 +1,5 @@
1
1
  import process from 'node:process';
2
- import { promisify, inspect } from 'node:util';
2
+ import { promisify, inspect, isDeepStrictEqual, } from 'node:util';
3
3
  import { checkServerIdentity } from 'node:tls';
4
4
  // DO NOT use destructuring for `https.request` and `http.request` as it's not compatible with `nock`.
5
5
  import https from 'node:https';
@@ -8,8 +8,8 @@ import is, { assert } from '@sindresorhus/is';
8
8
  import lowercaseKeys from 'lowercase-keys';
9
9
  import CacheableLookup from 'cacheable-lookup';
10
10
  import http2wrapper from 'http2-wrapper';
11
- import { isFormData } from 'form-data-encoder';
12
11
  import parseLinkHeader from './parse-link-header.js';
12
+ import { getUnixSocketPath } from './utils/is-unix-socket-url.js';
13
13
  const [major, minor] = process.versions.node.split('.').map(Number);
14
14
  /**
15
15
  Generic helper that wraps any assertion function to add context to error messages.
@@ -43,9 +43,68 @@ function assertPlainObject(optionName, value) {
43
43
  assert.plainObject(value);
44
44
  });
45
45
  }
46
+ export function isSameOrigin(previousUrl, nextUrl) {
47
+ return previousUrl.origin === nextUrl.origin
48
+ && getUnixSocketPath(previousUrl) === getUnixSocketPath(nextUrl);
49
+ }
50
+ export const crossOriginStripHeaders = ['host', 'cookie', 'cookie2', 'authorization', 'proxy-authorization'];
51
+ const bodyHeaderNames = ['content-length', 'content-encoding', 'content-language', 'content-location', 'content-type', 'transfer-encoding'];
52
+ function usesUnixSocket(url) {
53
+ return url.protocol === 'unix:' || getUnixSocketPath(url) !== undefined;
54
+ }
55
+ function hasCredentialInUrl(url, credential) {
56
+ if (url instanceof URL) {
57
+ return url[credential] !== '';
58
+ }
59
+ if (!is.string(url)) {
60
+ return false;
61
+ }
62
+ try {
63
+ return new URL(url)[credential] !== '';
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ export const hasExplicitCredentialInUrlChange = (changedState, url, credential) => (changedState.has(credential)
70
+ || (changedState.has('url') && url?.[credential] !== ''));
71
+ const hasProtocolSlashes = (value) => /^[a-z][\d+\-.a-z]*:\/\//iv.test(value);
72
+ const hasHttpProtocolWithoutSlashes = (value) => /^https?:(?!\/\/)/iv.test(value);
73
+ export function applyUrlOverride(options, url, { username, password } = {}) {
74
+ if (is.string(url) && options.url) {
75
+ url = new URL(url, options.url).toString();
76
+ }
77
+ options.prefixUrl = '';
78
+ options.url = url;
79
+ if (username !== undefined) {
80
+ options.username = username;
81
+ }
82
+ if (password !== undefined) {
83
+ options.password = password;
84
+ }
85
+ return options.url;
86
+ }
87
+ function assertValidHeaderName(name) {
88
+ if (name.startsWith(':')) {
89
+ throw new TypeError(`HTTP/2 pseudo-headers are not supported in \`options.headers\`: ${name}`);
90
+ }
91
+ }
92
+ /**
93
+ Safely assign own properties from source to target, skipping `__proto__` to prevent prototype pollution from JSON.parse'd input.
94
+ */
95
+ function safeObjectAssign(target, source) {
96
+ for (const key of Object.keys(source)) {
97
+ if (key === '__proto__') {
98
+ continue;
99
+ }
100
+ target[key] = source[key];
101
+ }
102
+ }
46
103
  function validateSearchParameters(searchParameters) {
47
- // eslint-disable-next-line guard-for-in
48
- for (const key in searchParameters) {
104
+ for (const key of Object.keys(searchParameters)) {
105
+ if (key === '__proto__') {
106
+ continue;
107
+ }
49
108
  const value = searchParameters[key];
50
109
  assertAny(`searchParams.${key}`, [is.string, is.number, is.boolean, is.null, is.undefined], value);
51
110
  }
@@ -138,7 +197,7 @@ const defaultInternals = {
138
197
  password: '',
139
198
  http2: false,
140
199
  allowGetBody: false,
141
- copyPipedHeaders: true,
200
+ copyPipedHeaders: false,
142
201
  headers: {
143
202
  'user-agent': 'got (https://github.com/sindresorhus/got)',
144
203
  },
@@ -182,8 +241,7 @@ const defaultInternals = {
182
241
  calculateDelay: ({ computedValue }) => computedValue,
183
242
  backoffLimit: Number.POSITIVE_INFINITY,
184
243
  noise: 100,
185
- // TODO: Change default to `true` in the next major version to fix https://github.com/sindresorhus/got/issues/2243
186
- enforceRetryRules: false,
244
+ enforceRetryRules: true,
187
245
  },
188
246
  localAddress: undefined,
189
247
  method: 'GET',
@@ -235,8 +293,9 @@ const defaultInternals = {
235
293
  const parsed = parseLinkHeader(rawLinkHeader);
236
294
  const next = parsed.find(entry => entry.parameters.rel === 'next' || entry.parameters.rel === '"next"');
237
295
  if (next) {
296
+ const baseUrl = response.request.options.url ?? response.url;
238
297
  return {
239
- url: new URL(next.reference, response.url),
298
+ url: new URL(next.reference, baseUrl),
240
299
  };
241
300
  }
242
301
  return false;
@@ -252,7 +311,7 @@ const defaultInternals = {
252
311
  maxHeaderSize: undefined,
253
312
  signal: undefined,
254
313
  enableUnixSockets: false,
255
- strictContentLength: false,
314
+ strictContentLength: true,
256
315
  };
257
316
  const cloneInternals = (internals) => {
258
317
  const { hooks, retry } = internals;
@@ -285,27 +344,24 @@ const cloneInternals = (internals) => {
285
344
  return result;
286
345
  };
287
346
  const cloneRaw = (raw) => {
288
- const { hooks, retry } = raw;
289
347
  const result = { ...raw };
290
- if (is.object(raw.context)) {
348
+ if (Object.hasOwn(raw, 'context') && is.object(raw.context)) {
291
349
  result.context = { ...raw.context };
292
350
  }
293
- if (is.object(raw.cacheOptions)) {
351
+ if (Object.hasOwn(raw, 'cacheOptions') && is.object(raw.cacheOptions)) {
294
352
  result.cacheOptions = { ...raw.cacheOptions };
295
353
  }
296
- if (is.object(raw.https)) {
354
+ if (Object.hasOwn(raw, 'https') && is.object(raw.https)) {
297
355
  result.https = { ...raw.https };
298
356
  }
299
- if (is.object(raw.cacheOptions)) {
300
- result.cacheOptions = { ...result.cacheOptions };
301
- }
302
- if (is.object(raw.agent)) {
357
+ if (Object.hasOwn(raw, 'agent') && is.object(raw.agent)) {
303
358
  result.agent = { ...raw.agent };
304
359
  }
305
- if (is.object(raw.headers)) {
360
+ if (Object.hasOwn(raw, 'headers') && is.object(raw.headers)) {
306
361
  result.headers = { ...raw.headers };
307
362
  }
308
- if (is.object(retry)) {
363
+ if (Object.hasOwn(raw, 'retry') && is.object(raw.retry)) {
364
+ const { retry } = raw;
309
365
  result.retry = { ...retry };
310
366
  if (is.array(retry.errorCodes)) {
311
367
  result.retry.errorCodes = [...retry.errorCodes];
@@ -317,10 +373,11 @@ const cloneRaw = (raw) => {
317
373
  result.retry.statusCodes = [...retry.statusCodes];
318
374
  }
319
375
  }
320
- if (is.object(raw.timeout)) {
376
+ if (Object.hasOwn(raw, 'timeout') && is.object(raw.timeout)) {
321
377
  result.timeout = { ...raw.timeout };
322
378
  }
323
- if (is.object(hooks)) {
379
+ if (Object.hasOwn(raw, 'hooks') && is.object(raw.hooks)) {
380
+ const { hooks } = raw;
324
381
  result.hooks = {
325
382
  ...hooks,
326
383
  };
@@ -346,7 +403,7 @@ const cloneRaw = (raw) => {
346
403
  result.hooks.afterResponse = [...hooks.afterResponse];
347
404
  }
348
405
  }
349
- if (raw.searchParams) {
406
+ if (Object.hasOwn(raw, 'searchParams') && raw.searchParams) {
350
407
  if (is.string(raw.searchParams)) {
351
408
  result.searchParams = raw.searchParams;
352
409
  }
@@ -357,17 +414,34 @@ const cloneRaw = (raw) => {
357
414
  result.searchParams = { ...raw.searchParams };
358
415
  }
359
416
  }
360
- if (is.object(raw.pagination)) {
417
+ if (Object.hasOwn(raw, 'pagination') && is.object(raw.pagination)) {
361
418
  result.pagination = { ...raw.pagination };
362
419
  }
363
420
  return result;
364
421
  };
365
422
  const getHttp2TimeoutOption = (internals) => {
366
423
  const delays = [internals.timeout.socket, internals.timeout.connect, internals.timeout.lookup, internals.timeout.request, internals.timeout.secureConnect].filter(delay => typeof delay === 'number');
367
- if (delays.length > 0) {
368
- return Math.min(...delays);
424
+ return delays.length > 0 ? Math.min(...delays) : undefined;
425
+ };
426
+ const trackStateMutation = (trackedStateMutations, name) => {
427
+ trackedStateMutations?.add(name);
428
+ };
429
+ const addExplicitHeader = (explicitHeaders, name) => {
430
+ explicitHeaders.add(name);
431
+ };
432
+ const markHeaderAsExplicit = (explicitHeaders, trackedStateMutations, name) => {
433
+ addExplicitHeader(explicitHeaders, name);
434
+ trackStateMutation(trackedStateMutations, name);
435
+ };
436
+ const trackReplacedHeaderMutations = (trackedStateMutations, previousHeaders, nextHeaders) => {
437
+ if (!trackedStateMutations) {
438
+ return;
439
+ }
440
+ for (const header of new Set([...Object.keys(previousHeaders), ...Object.keys(nextHeaders)])) {
441
+ if (previousHeaders[header] !== nextHeaders[header]) {
442
+ trackStateMutation(trackedStateMutations, header);
443
+ }
369
444
  }
370
- return undefined;
371
445
  };
372
446
  const init = (options, withOptions, self) => {
373
447
  const initHooks = options.hooks?.init;
@@ -377,11 +451,15 @@ const init = (options, withOptions, self) => {
377
451
  }
378
452
  }
379
453
  };
454
+ // Keys never merged: got.extend() internals, url (passed as first arg), control flags, security
455
+ const nonMergeableKeys = new Set(['mutableDefaults', 'handlers', 'url', 'preserveHooks', 'isStream', '__proto__']);
380
456
  export default class Options {
381
- _unixOptions;
382
- _internals;
383
- _merging = false;
384
- _init;
457
+ #internals;
458
+ #headersProxy;
459
+ #merging = false;
460
+ #init;
461
+ #explicitHeaders;
462
+ #trackedStateMutations;
385
463
  constructor(input, options, defaults) {
386
464
  assertAny('input', [is.string, is.urlInstance, is.object, is.undefined], input);
387
465
  assertAny('options', [is.object, is.undefined], options);
@@ -389,8 +467,17 @@ export default class Options {
389
467
  if (input instanceof Options || options instanceof Options) {
390
468
  throw new TypeError('The defaults must be passed as the third argument');
391
469
  }
392
- this._internals = cloneInternals(defaults?._internals ?? defaults ?? defaultInternals);
393
- this._init = [...(defaults?._init ?? [])];
470
+ if (defaults) {
471
+ this.#internals = cloneInternals(defaults.#internals);
472
+ this.#init = [...defaults.#init];
473
+ this.#explicitHeaders = new Set(defaults.#explicitHeaders);
474
+ }
475
+ else {
476
+ this.#internals = cloneInternals(defaultInternals);
477
+ this.#init = [];
478
+ this.#explicitHeaders = new Set();
479
+ }
480
+ this.#headersProxy = this.#createHeadersProxy();
394
481
  // This rule allows `finally` to be considered more important.
395
482
  // Meaning no matter the error thrown in the `try` block,
396
483
  // if `finally` throws then the `finally` error will be thrown.
@@ -399,7 +486,7 @@ export default class Options {
399
486
  // would get merged. Instead we set the `searchParams` first, then
400
487
  // `url.searchParams` is overwritten as expected.
401
488
  //
402
- /* 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 */
403
490
  try {
404
491
  if (is.plainObject(input)) {
405
492
  try {
@@ -440,9 +527,9 @@ export default class Options {
440
527
  return;
441
528
  }
442
529
  if (options instanceof Options) {
443
- // Create a copy of the _init array to avoid infinite loop
530
+ // Create a copy of the #init array to avoid infinite loop
444
531
  // when merging an Options instance with itself
445
- const initArray = [...options._init];
532
+ const initArray = [...options.#init];
446
533
  for (const init of initArray) {
447
534
  this.merge(init);
448
535
  }
@@ -451,24 +538,11 @@ export default class Options {
451
538
  options = cloneRaw(options);
452
539
  init(this, options, this);
453
540
  init(options, options, this);
454
- this._merging = true;
455
- // Always merge `isStream` first
456
- if ('isStream' in options) {
457
- this.isStream = options.isStream;
458
- }
541
+ this.#merging = true;
459
542
  try {
460
543
  let push = false;
461
- for (const key in options) {
462
- // `got.extend()` options
463
- if (key === 'mutableDefaults' || key === 'handlers') {
464
- continue;
465
- }
466
- // Never merge `url`
467
- if (key === 'url') {
468
- continue;
469
- }
470
- // Never merge `preserveHooks` - it's a control flag, not a persistent option
471
- if (key === 'preserveHooks') {
544
+ for (const key of Object.keys(options)) {
545
+ if (nonMergeableKeys.has(key)) {
472
546
  continue;
473
547
  }
474
548
  if (!(key in this)) {
@@ -484,11 +558,11 @@ export default class Options {
484
558
  push = true;
485
559
  }
486
560
  if (push) {
487
- this._init.push(options);
561
+ this.#init.push(options);
488
562
  }
489
563
  }
490
564
  finally {
491
- this._merging = false;
565
+ this.#merging = false;
492
566
  }
493
567
  }
494
568
  /**
@@ -498,11 +572,11 @@ export default class Options {
498
572
  @default http.request | https.request
499
573
  */
500
574
  get request() {
501
- return this._internals.request;
575
+ return this.#internals.request;
502
576
  }
503
577
  set request(value) {
504
578
  assertAny('request', [is.function, is.undefined], value);
505
- this._internals.request = value;
579
+ this.#internals.request = value;
506
580
  }
507
581
  /**
508
582
  An object representing `http`, `https` and `http2` keys for [`http.Agent`](https://nodejs.org/api/http.html#http_class_http_agent), [`https.Agent`](https://nodejs.org/api/https.html#https_class_https_agent) and [`http2wrapper.Agent`](https://github.com/szmarczak/http2-wrapper#new-http2agentoptions) instance.
@@ -527,47 +601,49 @@ export default class Options {
527
601
  ```
528
602
  */
529
603
  get agent() {
530
- return this._internals.agent;
604
+ return this.#internals.agent;
531
605
  }
532
606
  set agent(value) {
533
607
  assertPlainObject('agent', value);
534
- // eslint-disable-next-line guard-for-in
535
- for (const key in value) {
536
- if (!(key in this._internals.agent)) {
608
+ for (const key of Object.keys(value)) {
609
+ if (key === '__proto__') {
610
+ continue;
611
+ }
612
+ if (!(key in this.#internals.agent)) {
537
613
  throw new TypeError(`Unexpected agent option: ${key}`);
538
614
  }
539
615
  // @ts-expect-error - No idea why `value[key]` doesn't work here.
540
616
  assertAny(`agent.${key}`, [is.object, is.undefined, (v) => v === false], value[key]);
541
617
  }
542
- if (this._merging) {
543
- Object.assign(this._internals.agent, value);
618
+ if (this.#merging) {
619
+ safeObjectAssign(this.#internals.agent, value);
544
620
  }
545
621
  else {
546
- this._internals.agent = { ...value };
622
+ this.#internals.agent = { ...value };
547
623
  }
548
624
  }
549
625
  get h2session() {
550
- return this._internals.h2session;
626
+ return this.#internals.h2session;
551
627
  }
552
628
  set h2session(value) {
553
- this._internals.h2session = value;
629
+ this.#internals.h2session = value;
554
630
  }
555
631
  /**
556
632
  Decompress the response automatically.
557
633
 
558
634
  This will set the `accept-encoding` header to `gzip, deflate, br` unless you set it yourself.
559
635
 
560
- If this is disabled, a compressed response is returned as a `Buffer`.
636
+ If this is disabled, a compressed response is returned as a `Uint8Array`.
561
637
  This may be useful if you want to handle decompression yourself or stream the raw compressed data.
562
638
 
563
639
  @default true
564
640
  */
565
641
  get decompress() {
566
- return this._internals.decompress;
642
+ return this.#internals.decompress;
567
643
  }
568
644
  set decompress(value) {
569
645
  assert.boolean(value);
570
- this._internals.decompress = value;
646
+ this.#internals.decompress = value;
571
647
  }
572
648
  /**
573
649
  Milliseconds to wait for the server to end the response before aborting the request with `got.TimeoutError` error (a.k.a. `request` property).
@@ -587,23 +663,25 @@ export default class Options {
587
663
  get timeout() {
588
664
  // We always return `Delays` here.
589
665
  // It has to be `Delays | number`, otherwise TypeScript will error because the getter and the setter have incompatible types.
590
- return this._internals.timeout;
666
+ return this.#internals.timeout;
591
667
  }
592
668
  set timeout(value) {
593
669
  assertPlainObject('timeout', value);
594
- // eslint-disable-next-line guard-for-in
595
- for (const key in value) {
596
- if (!(key in this._internals.timeout)) {
670
+ for (const key of Object.keys(value)) {
671
+ if (key === '__proto__') {
672
+ continue;
673
+ }
674
+ if (!(key in this.#internals.timeout)) {
597
675
  throw new Error(`Unexpected timeout option: ${key}`);
598
676
  }
599
677
  // @ts-expect-error - No idea why `value[key]` doesn't work here.
600
678
  assertAny(`timeout.${key}`, [is.number, is.undefined], value[key]);
601
679
  }
602
- if (this._merging) {
603
- Object.assign(this._internals.timeout, value);
680
+ if (this.#merging) {
681
+ safeObjectAssign(this.#internals.timeout, value);
604
682
  }
605
683
  else {
606
- this._internals.timeout = { ...value };
684
+ this.#internals.timeout = { ...value };
607
685
  }
608
686
  }
609
687
  /**
@@ -648,23 +726,23 @@ export default class Options {
648
726
  get prefixUrl() {
649
727
  // We always return `string` here.
650
728
  // It has to be `string | URL`, otherwise TypeScript will error because the getter and the setter have incompatible types.
651
- return this._internals.prefixUrl;
729
+ return this.#internals.prefixUrl;
652
730
  }
653
731
  set prefixUrl(value) {
654
732
  assertAny('prefixUrl', [is.string, is.urlInstance], value);
655
733
  if (value === '') {
656
- this._internals.prefixUrl = '';
734
+ this.#internals.prefixUrl = '';
657
735
  return;
658
736
  }
659
737
  value = value.toString();
660
738
  if (!value.endsWith('/')) {
661
739
  value += '/';
662
740
  }
663
- if (this._internals.prefixUrl && this._internals.url) {
664
- const { href } = this._internals.url;
665
- this._internals.url.href = value + href.slice(this._internals.prefixUrl.length);
741
+ if (this.#internals.prefixUrl && this.#internals.url) {
742
+ const { href } = this.#internals.url;
743
+ this.#internals.url.href = value + href.slice(this.#internals.prefixUrl.length);
666
744
  }
667
- this._internals.prefixUrl = value;
745
+ this.#internals.prefixUrl = value;
668
746
  }
669
747
  /**
670
748
  __Note #1__: The `body` option cannot be used with the `json` or `form` option.
@@ -675,7 +753,7 @@ export default class Options {
675
753
 
676
754
  __Note #4__: This option is not enumerable and will not be merged with the instance defaults.
677
755
 
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`.
756
+ The `content-length` header will be automatically set if `body` is a `string` / `Uint8Array` / typed array, and `content-length` and `transfer-encoding` are not manually set in `options.headers`.
679
757
 
680
758
  Since Got 12, the `content-length` is not automatically set when `body` is a `fs.createReadStream`.
681
759
 
@@ -697,18 +775,19 @@ export default class Options {
697
775
  ```
698
776
  */
699
777
  get body() {
700
- return this._internals.body;
778
+ return this.#internals.body;
701
779
  }
702
780
  set body(value) {
703
- assertAny('body', [is.string, is.buffer, is.nodeStream, is.generator, is.asyncGenerator, is.iterable, is.asyncIterable, isFormData, is.typedArray, is.undefined], value);
781
+ assertAny('body', [is.string, is.buffer, is.nodeStream, is.generator, is.asyncGenerator, is.iterable, is.asyncIterable, is.typedArray, is.undefined], value);
704
782
  if (is.nodeStream(value)) {
705
783
  assert.truthy(value.readable);
706
784
  }
707
785
  if (value !== undefined) {
708
- assert.undefined(this._internals.form);
709
- assert.undefined(this._internals.json);
786
+ assert.undefined(this.#internals.form);
787
+ assert.undefined(this.#internals.json);
710
788
  }
711
- this._internals.body = value;
789
+ this.#internals.body = value;
790
+ trackStateMutation(this.#trackedStateMutations, 'body');
712
791
  }
713
792
  /**
714
793
  The form body is converted to a query string using [`(new URLSearchParams(object)).toString()`](https://nodejs.org/api/url.html#url_constructor_new_urlsearchparams_obj).
@@ -720,15 +799,16 @@ export default class Options {
720
799
  __Note #2__: This option is not enumerable and will not be merged with the instance defaults.
721
800
  */
722
801
  get form() {
723
- return this._internals.form;
802
+ return this.#internals.form;
724
803
  }
725
804
  set form(value) {
726
805
  assertAny('form', [is.plainObject, is.undefined], value);
727
806
  if (value !== undefined) {
728
- assert.undefined(this._internals.body);
729
- assert.undefined(this._internals.json);
807
+ assert.undefined(this.#internals.body);
808
+ assert.undefined(this.#internals.json);
730
809
  }
731
- this._internals.form = value;
810
+ this.#internals.form = value;
811
+ trackStateMutation(this.#trackedStateMutations, 'form');
732
812
  }
733
813
  /**
734
814
  JSON request body. If the `content-type` header is not set, it will be set to `application/json`.
@@ -740,14 +820,15 @@ export default class Options {
740
820
  __Note #2__: This option is not enumerable and will not be merged with the instance defaults.
741
821
  */
742
822
  get json() {
743
- return this._internals.json;
823
+ return this.#internals.json;
744
824
  }
745
825
  set json(value) {
746
826
  if (value !== undefined) {
747
- assert.undefined(this._internals.body);
748
- assert.undefined(this._internals.form);
827
+ assert.undefined(this.#internals.body);
828
+ assert.undefined(this.#internals.form);
749
829
  }
750
- this._internals.json = value;
830
+ this.#internals.json = value;
831
+ trackStateMutation(this.#trackedStateMutations, 'json');
751
832
  }
752
833
  /**
753
834
  The URL to request, as a string, a [`https.request` options object](https://nodejs.org/api/https.html#https_https_request_options_callback), or a [WHATWG `URL`](https://nodejs.org/api/url.html#url_class_url).
@@ -768,24 +849,34 @@ export default class Options {
768
849
  ```
769
850
  */
770
851
  get url() {
771
- return this._internals.url;
852
+ return this.#internals.url;
772
853
  }
773
854
  set url(value) {
774
855
  assertAny('url', [is.string, is.urlInstance, is.undefined], value);
775
856
  if (value === undefined) {
776
- this._internals.url = undefined;
857
+ this.#internals.url = undefined;
858
+ trackStateMutation(this.#trackedStateMutations, 'url');
777
859
  return;
778
860
  }
779
861
  if (is.string(value) && value.startsWith('/')) {
780
862
  throw new Error('`url` must not start with a slash');
781
863
  }
782
- // Detect if URL is already absolute (has a protocol/scheme)
783
864
  const valueString = value.toString();
784
- const isAbsolute = is.urlInstance(value) || /^[a-z][a-z\d+.-]*:\/\//i.test(valueString);
865
+ if (is.string(value)
866
+ && !this.prefixUrl
867
+ && hasHttpProtocolWithoutSlashes(valueString)) {
868
+ throw new Error('`url` protocol must be followed by `//`');
869
+ }
870
+ // Detect if URL is already absolute (has a protocol/scheme)
871
+ const isAbsolute = is.urlInstance(value) || hasProtocolSlashes(valueString);
785
872
  // Only concatenate prefixUrl if the URL is relative
786
873
  const urlString = isAbsolute ? valueString : `${this.prefixUrl}${valueString}`;
787
874
  const url = new URL(urlString);
788
- this._internals.url = url;
875
+ this.#internals.url = url;
876
+ trackStateMutation(this.#trackedStateMutations, 'url');
877
+ if (usesUnixSocket(url) && !this.#internals.enableUnixSockets) {
878
+ throw new Error('Using UNIX domain sockets but option `enableUnixSockets` is not enabled');
879
+ }
789
880
  if (url.protocol === 'unix:') {
790
881
  url.href = `http://unix${url.pathname}${url.search}`;
791
882
  }
@@ -794,37 +885,18 @@ export default class Options {
794
885
  error.code = 'ERR_UNSUPPORTED_PROTOCOL';
795
886
  throw error;
796
887
  }
797
- if (this._internals.username) {
798
- url.username = this._internals.username;
799
- this._internals.username = '';
888
+ if (this.#internals.username) {
889
+ url.username = this.#internals.username;
890
+ this.#internals.username = '';
800
891
  }
801
- if (this._internals.password) {
802
- url.password = this._internals.password;
803
- this._internals.password = '';
892
+ if (this.#internals.password) {
893
+ url.password = this.#internals.password;
894
+ this.#internals.password = '';
804
895
  }
805
- if (this._internals.searchParams) {
806
- url.search = this._internals.searchParams.toString();
807
- this._internals.searchParams = undefined;
808
- }
809
- if (url.hostname === 'unix') {
810
- if (!this._internals.enableUnixSockets) {
811
- throw new Error('Using UNIX domain sockets but option `enableUnixSockets` is not enabled');
812
- }
813
- const matches = /(?<socketPath>.+?):(?<path>.+)/.exec(`${url.pathname}${url.search}`);
814
- if (matches?.groups) {
815
- const { socketPath, path } = matches.groups;
816
- this._unixOptions = {
817
- socketPath,
818
- path,
819
- host: '',
820
- };
821
- }
822
- else {
823
- this._unixOptions = undefined;
824
- }
825
- return;
896
+ if (this.#internals.searchParams) {
897
+ url.search = this.#internals.searchParams.toString();
898
+ this.#internals.searchParams = undefined;
826
899
  }
827
- this._unixOptions = undefined;
828
900
  }
829
901
  /**
830
902
  Cookie support. You don't have to care about parsing or how to store them.
@@ -832,28 +904,26 @@ export default class Options {
832
904
  __Note__: If you provide this option, `options.headers.cookie` will be overridden.
833
905
  */
834
906
  get cookieJar() {
835
- return this._internals.cookieJar;
907
+ return this.#internals.cookieJar;
836
908
  }
837
909
  set cookieJar(value) {
838
910
  assertAny('cookieJar', [is.object, is.undefined], value);
839
911
  if (value === undefined) {
840
- this._internals.cookieJar = undefined;
912
+ this.#internals.cookieJar = undefined;
841
913
  return;
842
914
  }
843
- let { setCookie, getCookieString } = value;
915
+ const { setCookie, getCookieString } = value;
844
916
  assert.function(setCookie);
845
917
  assert.function(getCookieString);
846
918
  /* istanbul ignore next: Horrible `tough-cookie` v3 check */
847
919
  if (setCookie.length === 4 && getCookieString.length === 0) {
848
- setCookie = promisify(setCookie.bind(value));
849
- getCookieString = promisify(getCookieString.bind(value));
850
- this._internals.cookieJar = {
851
- setCookie,
852
- getCookieString: getCookieString,
920
+ this.#internals.cookieJar = {
921
+ setCookie: promisify(setCookie.bind(value)),
922
+ getCookieString: promisify(getCookieString.bind(value)),
853
923
  };
854
924
  }
855
925
  else {
856
- this._internals.cookieJar = value;
926
+ this.#internals.cookieJar = value;
857
927
  }
858
928
  }
859
929
  /**
@@ -875,11 +945,11 @@ export default class Options {
875
945
  ```
876
946
  */
877
947
  get signal() {
878
- return this._internals.signal;
948
+ return this.#internals.signal;
879
949
  }
880
950
  set signal(value) {
881
- assert.object(value);
882
- this._internals.signal = value;
951
+ assertAny('signal', [is.object, is.undefined], value);
952
+ this.#internals.signal = value;
883
953
  }
884
954
  /**
885
955
  Ignore invalid cookies instead of throwing an error.
@@ -888,11 +958,11 @@ export default class Options {
888
958
  @default false
889
959
  */
890
960
  get ignoreInvalidCookies() {
891
- return this._internals.ignoreInvalidCookies;
961
+ return this.#internals.ignoreInvalidCookies;
892
962
  }
893
963
  set ignoreInvalidCookies(value) {
894
964
  assert.boolean(value);
895
- this._internals.ignoreInvalidCookies = value;
965
+ this.#internals.ignoreInvalidCookies = value;
896
966
  }
897
967
  /**
898
968
  Query string that will be added to the request URL.
@@ -913,19 +983,17 @@ export default class Options {
913
983
  ```
914
984
  */
915
985
  get searchParams() {
916
- if (this._internals.url) {
917
- return this._internals.url.searchParams;
986
+ if (this.#internals.url) {
987
+ return this.#internals.url.searchParams;
918
988
  }
919
- if (this._internals.searchParams === undefined) {
920
- this._internals.searchParams = new URLSearchParams();
921
- }
922
- return this._internals.searchParams;
989
+ this.#internals.searchParams ??= new URLSearchParams();
990
+ return this.#internals.searchParams;
923
991
  }
924
992
  set searchParams(value) {
925
993
  assertAny('searchParams', [is.string, is.object, is.undefined], value);
926
- const url = this._internals.url;
994
+ const url = this.#internals.url;
927
995
  if (value === undefined) {
928
- this._internals.searchParams = undefined;
996
+ this.#internals.searchParams = undefined;
929
997
  if (url) {
930
998
  url.search = '';
931
999
  }
@@ -942,8 +1010,10 @@ export default class Options {
942
1010
  else {
943
1011
  validateSearchParameters(value);
944
1012
  updated = new URLSearchParams();
945
- // eslint-disable-next-line guard-for-in
946
- for (const key in value) {
1013
+ for (const key of Object.keys(value)) {
1014
+ if (key === '__proto__') {
1015
+ continue;
1016
+ }
947
1017
  const entry = value[key];
948
1018
  if (entry === null) {
949
1019
  updated.append(key, '');
@@ -956,7 +1026,7 @@ export default class Options {
956
1026
  }
957
1027
  }
958
1028
  }
959
- if (this._merging) {
1029
+ if (this.#merging) {
960
1030
  // These keys will be replaced
961
1031
  for (const key of updated.keys()) {
962
1032
  searchParameters.delete(key);
@@ -969,7 +1039,7 @@ export default class Options {
969
1039
  url.search = searchParameters.toString();
970
1040
  }
971
1041
  else {
972
- this._internals.searchParams = searchParameters;
1042
+ this.#internals.searchParams = searchParameters;
973
1043
  }
974
1044
  }
975
1045
  get searchParameters() {
@@ -979,11 +1049,11 @@ export default class Options {
979
1049
  throw new Error('The `searchParameters` option does not exist. Use `searchParams` instead.');
980
1050
  }
981
1051
  get dnsLookup() {
982
- return this._internals.dnsLookup;
1052
+ return this.#internals.dnsLookup;
983
1053
  }
984
1054
  set dnsLookup(value) {
985
1055
  assertAny('dnsLookup', [is.function, is.undefined], value);
986
- this._internals.dnsLookup = value;
1056
+ this.#internals.dnsLookup = value;
987
1057
  }
988
1058
  /**
989
1059
  An instance of [`CacheableLookup`](https://github.com/szmarczak/cacheable-lookup) used for making DNS lookups.
@@ -996,18 +1066,18 @@ export default class Options {
996
1066
  @default false
997
1067
  */
998
1068
  get dnsCache() {
999
- return this._internals.dnsCache;
1069
+ return this.#internals.dnsCache;
1000
1070
  }
1001
1071
  set dnsCache(value) {
1002
1072
  assertAny('dnsCache', [is.object, is.boolean, is.undefined], value);
1003
1073
  if (value === true) {
1004
- this._internals.dnsCache = getGlobalDnsCache();
1074
+ this.#internals.dnsCache = getGlobalDnsCache();
1005
1075
  }
1006
1076
  else if (value === false) {
1007
- this._internals.dnsCache = undefined;
1077
+ this.#internals.dnsCache = undefined;
1008
1078
  }
1009
1079
  else {
1010
- this._internals.dnsCache = value;
1080
+ this.#internals.dnsCache = value;
1011
1081
  }
1012
1082
  }
1013
1083
  /**
@@ -1042,15 +1112,15 @@ export default class Options {
1042
1112
  ```
1043
1113
  */
1044
1114
  get context() {
1045
- return this._internals.context;
1115
+ return this.#internals.context;
1046
1116
  }
1047
1117
  set context(value) {
1048
1118
  assert.object(value);
1049
- if (this._merging) {
1050
- Object.assign(this._internals.context, value);
1119
+ if (this.#merging) {
1120
+ safeObjectAssign(this.#internals.context, value);
1051
1121
  }
1052
1122
  else {
1053
- this._internals.context = { ...value };
1123
+ this.#internals.context = { ...value };
1054
1124
  }
1055
1125
  }
1056
1126
  /**
@@ -1058,13 +1128,15 @@ export default class Options {
1058
1128
  Hook functions may be async and are run serially.
1059
1129
  */
1060
1130
  get hooks() {
1061
- return this._internals.hooks;
1131
+ return this.#internals.hooks;
1062
1132
  }
1063
1133
  set hooks(value) {
1064
1134
  assert.object(value);
1065
- // eslint-disable-next-line guard-for-in
1066
- for (const knownHookEvent in value) {
1067
- if (!(knownHookEvent in this._internals.hooks)) {
1135
+ for (const knownHookEvent of Object.keys(value)) {
1136
+ if (knownHookEvent === '__proto__') {
1137
+ continue;
1138
+ }
1139
+ if (!(knownHookEvent in this.#internals.hooks)) {
1068
1140
  throw new Error(`Unexpected hook event: ${knownHookEvent}`);
1069
1141
  }
1070
1142
  const typedKnownHookEvent = knownHookEvent;
@@ -1075,10 +1147,10 @@ export default class Options {
1075
1147
  assert.function(hook);
1076
1148
  }
1077
1149
  }
1078
- if (this._merging) {
1150
+ if (this.#merging) {
1079
1151
  if (hooks) {
1080
1152
  // @ts-expect-error FIXME
1081
- this._internals.hooks[typedKnownHookEvent].push(...hooks);
1153
+ this.#internals.hooks[typedKnownHookEvent].push(...hooks);
1082
1154
  }
1083
1155
  }
1084
1156
  else {
@@ -1086,7 +1158,7 @@ export default class Options {
1086
1158
  throw new Error(`Missing hook event: ${knownHookEvent}`);
1087
1159
  }
1088
1160
  // @ts-expect-error FIXME
1089
- this._internals.hooks[knownHookEvent] = [...hooks];
1161
+ this.#internals.hooks[knownHookEvent] = [...hooks];
1090
1162
  }
1091
1163
  }
1092
1164
  }
@@ -1097,15 +1169,16 @@ export default class Options {
1097
1169
 
1098
1170
  Note that if a `303` is sent by the server in response to any request type (`POST`, `DELETE`, etc.), Got will automatically request the resource pointed to in the location header via `GET`.
1099
1171
  This is in accordance with [the spec](https://tools.ietf.org/html/rfc7231#section-6.4.4). You can optionally turn on this behavior also for other redirect codes - see `methodRewriting`.
1172
+ On cross-origin redirects, Got strips `host`, `cookie`, `cookie2`, `authorization`, and `proxy-authorization`. When a redirect rewrites the request to `GET`, Got also strips request body headers. Use `hooks.beforeRedirect` for app-specific sensitive headers.
1100
1173
 
1101
1174
  @default true
1102
1175
  */
1103
1176
  get followRedirect() {
1104
- return this._internals.followRedirect;
1177
+ return this.#internals.followRedirect;
1105
1178
  }
1106
1179
  set followRedirect(value) {
1107
1180
  assertAny('followRedirect', [is.boolean, is.function], value);
1108
- this._internals.followRedirect = value;
1181
+ this.#internals.followRedirect = value;
1109
1182
  }
1110
1183
  get followRedirects() {
1111
1184
  throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.');
@@ -1119,11 +1192,11 @@ export default class Options {
1119
1192
  @default 10
1120
1193
  */
1121
1194
  get maxRedirects() {
1122
- return this._internals.maxRedirects;
1195
+ return this.#internals.maxRedirects;
1123
1196
  }
1124
1197
  set maxRedirects(value) {
1125
1198
  assert.number(value);
1126
- this._internals.maxRedirects = value;
1199
+ this.#internals.maxRedirects = value;
1127
1200
  }
1128
1201
  /**
1129
1202
  A cache adapter instance for storing cached response data.
@@ -1131,18 +1204,18 @@ export default class Options {
1131
1204
  @default false
1132
1205
  */
1133
1206
  get cache() {
1134
- return this._internals.cache;
1207
+ return this.#internals.cache;
1135
1208
  }
1136
1209
  set cache(value) {
1137
1210
  assertAny('cache', [is.object, is.string, is.boolean, is.undefined], value);
1138
1211
  if (value === true) {
1139
- this._internals.cache = globalCache;
1212
+ this.#internals.cache = globalCache;
1140
1213
  }
1141
1214
  else if (value === false) {
1142
- this._internals.cache = undefined;
1215
+ this.#internals.cache = undefined;
1143
1216
  }
1144
1217
  else {
1145
- this._internals.cache = wrapQuickLruIfNeeded(value);
1218
+ this.#internals.cache = wrapQuickLruIfNeeded(value);
1146
1219
  }
1147
1220
  }
1148
1221
  /**
@@ -1154,43 +1227,45 @@ export default class Options {
1154
1227
  @default true
1155
1228
  */
1156
1229
  get throwHttpErrors() {
1157
- return this._internals.throwHttpErrors;
1230
+ return this.#internals.throwHttpErrors;
1158
1231
  }
1159
1232
  set throwHttpErrors(value) {
1160
1233
  assert.boolean(value);
1161
- this._internals.throwHttpErrors = value;
1234
+ this.#internals.throwHttpErrors = value;
1162
1235
  }
1163
1236
  get username() {
1164
- const url = this._internals.url;
1165
- const value = url ? url.username : this._internals.username;
1237
+ const url = this.#internals.url;
1238
+ const value = url ? url.username : this.#internals.username;
1166
1239
  return decodeURIComponent(value);
1167
1240
  }
1168
1241
  set username(value) {
1169
1242
  assert.string(value);
1170
- const url = this._internals.url;
1243
+ const url = this.#internals.url;
1171
1244
  const fixedValue = encodeURIComponent(value);
1172
1245
  if (url) {
1173
1246
  url.username = fixedValue;
1174
1247
  }
1175
1248
  else {
1176
- this._internals.username = fixedValue;
1249
+ this.#internals.username = fixedValue;
1177
1250
  }
1251
+ trackStateMutation(this.#trackedStateMutations, 'username');
1178
1252
  }
1179
1253
  get password() {
1180
- const url = this._internals.url;
1181
- const value = url ? url.password : this._internals.password;
1254
+ const url = this.#internals.url;
1255
+ const value = url ? url.password : this.#internals.password;
1182
1256
  return decodeURIComponent(value);
1183
1257
  }
1184
1258
  set password(value) {
1185
1259
  assert.string(value);
1186
- const url = this._internals.url;
1260
+ const url = this.#internals.url;
1187
1261
  const fixedValue = encodeURIComponent(value);
1188
1262
  if (url) {
1189
1263
  url.password = fixedValue;
1190
1264
  }
1191
1265
  else {
1192
- this._internals.password = fixedValue;
1266
+ this.#internals.password = fixedValue;
1193
1267
  }
1268
+ trackStateMutation(this.#trackedStateMutations, 'password');
1194
1269
  }
1195
1270
  /**
1196
1271
  If set to `true`, Got will additionally accept HTTP2 requests.
@@ -1214,11 +1289,11 @@ export default class Options {
1214
1289
  ```
1215
1290
  */
1216
1291
  get http2() {
1217
- return this._internals.http2;
1292
+ return this.#internals.http2;
1218
1293
  }
1219
1294
  set http2(value) {
1220
1295
  assert.boolean(value);
1221
- this._internals.http2 = value;
1296
+ this.#internals.http2 = value;
1222
1297
  }
1223
1298
  /**
1224
1299
  Set this to `true` to allow sending body for the `GET` method.
@@ -1230,36 +1305,35 @@ export default class Options {
1230
1305
  @default false
1231
1306
  */
1232
1307
  get allowGetBody() {
1233
- return this._internals.allowGetBody;
1308
+ return this.#internals.allowGetBody;
1234
1309
  }
1235
1310
  set allowGetBody(value) {
1236
1311
  assert.boolean(value);
1237
- this._internals.allowGetBody = value;
1312
+ this.#internals.allowGetBody = value;
1238
1313
  }
1239
1314
  /**
1240
1315
  Automatically copy headers from piped streams.
1241
1316
 
1242
1317
  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
1318
 
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.
1319
+ Note: Explicitly set headers take precedence over piped headers. Piped headers are only copied when a header is not already explicitly set.
1245
1320
 
1246
- Useful for proxy scenarios, but you may want to disable this to filter out headers like `Host`, `Connection`, `Authorization`, etc.
1321
+ Useful for proxy scenarios when explicitly enabled, but you may still want to filter out headers like `Host`, `Connection`, `Authorization`, etc.
1247
1322
 
1248
- @default true
1323
+ @default false
1249
1324
 
1250
1325
  @example
1251
1326
  ```
1252
1327
  import got from 'got';
1253
1328
  import {pipeline} from 'node:stream/promises';
1254
1329
 
1255
- // Disable automatic header copying and manually copy only safe headers
1330
+ // Opt in to automatic header copying for proxy scenarios
1256
1331
  server.get('/proxy', async (request, response) => {
1257
1332
  const gotStream = got.stream('https://example.com', {
1258
- copyPipedHeaders: false,
1333
+ copyPipedHeaders: true,
1334
+ // Explicit headers win over piped headers
1259
1335
  headers: {
1260
- 'user-agent': request.headers['user-agent'],
1261
- 'accept': request.headers['accept'],
1262
- // Explicitly NOT copying host, connection, authorization, etc.
1336
+ host: 'example.com',
1263
1337
  }
1264
1338
  });
1265
1339
 
@@ -1270,27 +1344,114 @@ export default class Options {
1270
1344
  @example
1271
1345
  ```
1272
1346
  import got from 'got';
1347
+ import {pipeline} from 'node:stream/promises';
1273
1348
 
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
- }
1349
+ // Keep it disabled and manually copy only safe headers
1350
+ server.get('/proxy', async (request, response) => {
1351
+ const gotStream = got.stream('https://example.com', {
1352
+ headers: {
1353
+ 'user-agent': request.headers['user-agent'],
1354
+ 'accept': request.headers['accept'],
1355
+ // Explicitly NOT copying host, connection, authorization, etc.
1356
+ }
1357
+ });
1358
+
1359
+ await pipeline(request, gotStream, response);
1285
1360
  });
1286
1361
  ```
1287
1362
  */
1288
1363
  get copyPipedHeaders() {
1289
- return this._internals.copyPipedHeaders;
1364
+ return this.#internals.copyPipedHeaders;
1290
1365
  }
1291
1366
  set copyPipedHeaders(value) {
1292
1367
  assert.boolean(value);
1293
- this._internals.copyPipedHeaders = value;
1368
+ this.#internals.copyPipedHeaders = value;
1369
+ }
1370
+ isHeaderExplicitlySet(name) {
1371
+ return this.#explicitHeaders.has(name.toLowerCase());
1372
+ }
1373
+ shouldCopyPipedHeader(name) {
1374
+ return !this.isHeaderExplicitlySet(name);
1375
+ }
1376
+ setPipedHeader(name, value) {
1377
+ assertValidHeaderName(name);
1378
+ this.#internals.headers[name.toLowerCase()] = value;
1379
+ }
1380
+ getInternalHeaders() {
1381
+ return this.#internals.headers;
1382
+ }
1383
+ setInternalHeader(name, value) {
1384
+ assertValidHeaderName(name);
1385
+ this.#internals.headers[name.toLowerCase()] = value;
1386
+ }
1387
+ deleteInternalHeader(name) {
1388
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
1389
+ delete this.#internals.headers[name.toLowerCase()];
1390
+ }
1391
+ async trackStateMutations(operation) {
1392
+ const changedState = new Set();
1393
+ this.#trackedStateMutations = changedState;
1394
+ try {
1395
+ return await operation(changedState);
1396
+ }
1397
+ finally {
1398
+ this.#trackedStateMutations = undefined;
1399
+ }
1400
+ }
1401
+ clearBody() {
1402
+ this.body = undefined;
1403
+ this.json = undefined;
1404
+ this.form = undefined;
1405
+ for (const header of bodyHeaderNames) {
1406
+ this.deleteInternalHeader(header);
1407
+ }
1408
+ }
1409
+ stripUnchangedCrossOriginState(previousState, changedState, { clearBody = true } = {}) {
1410
+ const headers = this.getInternalHeaders();
1411
+ const url = this.#internals.url;
1412
+ for (const header of crossOriginStripHeaders) {
1413
+ if (!changedState.has(header) && headers[header] === previousState.headers[header]) {
1414
+ this.deleteInternalHeader(header);
1415
+ }
1416
+ }
1417
+ if (!hasExplicitCredentialInUrlChange(changedState, url, 'username')) {
1418
+ this.username = '';
1419
+ }
1420
+ if (!hasExplicitCredentialInUrlChange(changedState, url, 'password')) {
1421
+ this.password = '';
1422
+ }
1423
+ if (clearBody && !changedState.has('body') && !changedState.has('json') && !changedState.has('form') && isBodyUnchanged(this, previousState)) {
1424
+ this.clearBody();
1425
+ }
1426
+ }
1427
+ /**
1428
+ Strip sensitive headers and credentials when navigating to a different origin.
1429
+ Headers and credentials explicitly provided in `userOptions` are preserved.
1430
+ */
1431
+ stripSensitiveHeaders(previousUrl, nextUrl, userOptions) {
1432
+ if (isSameOrigin(previousUrl, nextUrl)) {
1433
+ return;
1434
+ }
1435
+ const headers = lowercaseKeys(userOptions.headers ?? {});
1436
+ for (const header of crossOriginStripHeaders) {
1437
+ if (headers[header] === undefined) {
1438
+ this.deleteInternalHeader(header);
1439
+ }
1440
+ }
1441
+ const explicitUsername = Object.hasOwn(userOptions, 'username') ? userOptions.username : undefined;
1442
+ const explicitPassword = Object.hasOwn(userOptions, 'password') ? userOptions.password : undefined;
1443
+ const hasExplicitUsername = explicitUsername !== undefined
1444
+ || hasCredentialInUrl(userOptions.url, 'username')
1445
+ || isCrossOriginCredentialChanged(previousUrl, nextUrl, 'username');
1446
+ const hasExplicitPassword = explicitPassword !== undefined
1447
+ || hasCredentialInUrl(userOptions.url, 'password')
1448
+ || isCrossOriginCredentialChanged(previousUrl, nextUrl, 'password');
1449
+ if (!hasExplicitUsername && this.username) {
1450
+ this.username = '';
1451
+ }
1452
+ if (!hasExplicitPassword && this.password) {
1453
+ this.password = '';
1454
+ }
1294
1455
  }
1295
1456
  /**
1296
1457
  Request headers.
@@ -1300,33 +1461,49 @@ export default class Options {
1300
1461
  @default {}
1301
1462
  */
1302
1463
  get headers() {
1303
- return this._internals.headers;
1464
+ return this.#headersProxy;
1304
1465
  }
1305
1466
  set headers(value) {
1306
1467
  assertPlainObject('headers', value);
1307
- if (this._merging) {
1308
- Object.assign(this._internals.headers, lowercaseKeys(value));
1468
+ const normalizedHeaders = lowercaseKeys(value);
1469
+ for (const header of Object.keys(normalizedHeaders)) {
1470
+ assertValidHeaderName(header);
1471
+ }
1472
+ if (this.#merging) {
1473
+ safeObjectAssign(this.#internals.headers, normalizedHeaders);
1309
1474
  }
1310
1475
  else {
1311
- this._internals.headers = lowercaseKeys(value);
1476
+ const previousHeaders = this.#internals.headers;
1477
+ this.#internals.headers = normalizedHeaders;
1478
+ this.#headersProxy = this.#createHeadersProxy();
1479
+ this.#explicitHeaders.clear();
1480
+ trackReplacedHeaderMutations(this.#trackedStateMutations, previousHeaders, normalizedHeaders);
1481
+ }
1482
+ for (const header of Object.keys(normalizedHeaders)) {
1483
+ if (this.#merging) {
1484
+ markHeaderAsExplicit(this.#explicitHeaders, this.#trackedStateMutations, header);
1485
+ }
1486
+ else {
1487
+ addExplicitHeader(this.#explicitHeaders, header);
1488
+ }
1312
1489
  }
1313
1490
  }
1314
1491
  /**
1315
1492
  Specifies if the HTTP request method should be [rewritten as `GET`](https://tools.ietf.org/html/rfc7231#section-6.4) on redirects.
1316
1493
 
1317
- As the [specification](https://tools.ietf.org/html/rfc7231#section-6.4) prefers to rewrite the HTTP method only on `303` responses, this is Got's default behavior.
1318
- Setting `methodRewriting` to `true` will also rewrite `301` and `302` responses, as allowed by the spec. This is the behavior followed by `curl` and browsers.
1494
+ As the [specification](https://tools.ietf.org/html/rfc7231#section-6.4) prefers to rewrite the HTTP method only on `303` responses, this is Got's default behavior. Cross-origin `301` and `302` redirects also rewrite `POST` requests to `GET` by default to avoid forwarding request bodies to another origin.
1495
+ Setting `methodRewriting` to `true` will also rewrite same-origin `301` and `302` responses, as allowed by the spec. This is the behavior followed by `curl` and browsers.
1319
1496
 
1320
1497
  __Note__: Got never performs method rewriting on `307` and `308` responses, as this is [explicitly prohibited by the specification](https://www.rfc-editor.org/rfc/rfc7231#section-6.4.7).
1321
1498
 
1322
1499
  @default false
1323
1500
  */
1324
1501
  get methodRewriting() {
1325
- return this._internals.methodRewriting;
1502
+ return this.#internals.methodRewriting;
1326
1503
  }
1327
1504
  set methodRewriting(value) {
1328
1505
  assert.boolean(value);
1329
- this._internals.methodRewriting = value;
1506
+ this.#internals.methodRewriting = value;
1330
1507
  }
1331
1508
  /**
1332
1509
  Indicates which DNS record family to use.
@@ -1339,13 +1516,13 @@ export default class Options {
1339
1516
  @default undefined
1340
1517
  */
1341
1518
  get dnsLookupIpVersion() {
1342
- return this._internals.dnsLookupIpVersion;
1519
+ return this.#internals.dnsLookupIpVersion;
1343
1520
  }
1344
1521
  set dnsLookupIpVersion(value) {
1345
1522
  if (value !== undefined && value !== 4 && value !== 6) {
1346
1523
  throw new TypeError(`Invalid DNS lookup IP version: ${value}`);
1347
1524
  }
1348
- this._internals.dnsLookupIpVersion = value;
1525
+ this.#internals.dnsLookupIpVersion = value;
1349
1526
  }
1350
1527
  /**
1351
1528
  A function used to parse JSON responses.
@@ -1363,11 +1540,11 @@ export default class Options {
1363
1540
  ```
1364
1541
  */
1365
1542
  get parseJson() {
1366
- return this._internals.parseJson;
1543
+ return this.#internals.parseJson;
1367
1544
  }
1368
1545
  set parseJson(value) {
1369
1546
  assert.function(value);
1370
- this._internals.parseJson = value;
1547
+ this.#internals.parseJson = value;
1371
1548
  }
1372
1549
  /**
1373
1550
  A function used to stringify the body of JSON requests.
@@ -1411,11 +1588,11 @@ export default class Options {
1411
1588
  ```
1412
1589
  */
1413
1590
  get stringifyJson() {
1414
- return this._internals.stringifyJson;
1591
+ return this.#internals.stringifyJson;
1415
1592
  }
1416
1593
  set stringifyJson(value) {
1417
1594
  assert.function(value);
1418
- this._internals.stringifyJson = value;
1595
+ this.#internals.stringifyJson = value;
1419
1596
  }
1420
1597
  /**
1421
1598
  An object representing `limit`, `calculateDelay`, `methods`, `statusCodes`, `maxRetryAfter` and `errorCodes` fields for maximum retry count, retry handler, allowed methods, allowed status codes, maximum [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) time and allowed error codes.
@@ -1425,9 +1602,9 @@ export default class Options {
1425
1602
  The `calculateDelay` property is a `function` that receives an object with `attemptCount`, `retryOptions`, `error` and `computedValue` properties for current retry count, the retry options, error and default computed value.
1426
1603
  The function must return a delay in milliseconds (or a Promise resolving with it) (`0` return value cancels retry).
1427
1604
 
1428
- The `enforceRetryRules` property is a `boolean` that, when set to `true`, enforces the `limit`, `methods`, `statusCodes`, and `errorCodes` options before calling `calculateDelay`. Your `calculateDelay` function is only invoked when a retry is allowed based on these criteria. When `false` (default), `calculateDelay` receives the computed value but can override all retry logic.
1605
+ The `enforceRetryRules` property is a `boolean` that, when set to `true` (default), enforces the `limit`, `methods`, `statusCodes`, and `errorCodes` options before calling `calculateDelay`. Your `calculateDelay` function is only invoked when a retry is allowed based on these criteria. When `false`, `calculateDelay` receives the computed value but can override all retry logic.
1429
1606
 
1430
- __Note:__ When `enforceRetryRules` is `false`, you must check `computedValue` in your `calculateDelay` function to respect the default retry logic. When `true`, the retry rules are enforced automatically.
1607
+ __Note:__ When `enforceRetryRules` is `false`, you must check `computedValue` in your `calculateDelay` function to respect retry rules. When `true` (default), the retry rules are enforced automatically.
1431
1608
 
1432
1609
  By default, it retries *only* on the specified methods, status codes, and on these network errors:
1433
1610
 
@@ -1444,7 +1621,7 @@ export default class Options {
1444
1621
  __Note__: If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will cancel the request.
1445
1622
  */
1446
1623
  get retry() {
1447
- return this._internals.retry;
1624
+ return this.#internals.retry;
1448
1625
  }
1449
1626
  set retry(value) {
1450
1627
  assertPlainObject('retry', value);
@@ -1459,18 +1636,21 @@ export default class Options {
1459
1636
  if (value.noise && Math.abs(value.noise) > 100) {
1460
1637
  throw new Error(`The maximum acceptable retry noise is +/- 100ms, got ${value.noise}`);
1461
1638
  }
1462
- for (const key in value) {
1463
- if (!(key in this._internals.retry)) {
1639
+ for (const key of Object.keys(value)) {
1640
+ if (key === '__proto__') {
1641
+ continue;
1642
+ }
1643
+ if (!(key in this.#internals.retry)) {
1464
1644
  throw new Error(`Unexpected retry option: ${key}`);
1465
1645
  }
1466
1646
  }
1467
- if (this._merging) {
1468
- Object.assign(this._internals.retry, value);
1647
+ if (this.#merging) {
1648
+ safeObjectAssign(this.#internals.retry, value);
1469
1649
  }
1470
1650
  else {
1471
- this._internals.retry = { ...value };
1651
+ this.#internals.retry = { ...value };
1472
1652
  }
1473
- const { retry } = this._internals;
1653
+ const { retry } = this.#internals;
1474
1654
  retry.methods = [...new Set(retry.methods.map(method => method.toUpperCase()))];
1475
1655
  retry.statusCodes = [...new Set(retry.statusCodes)];
1476
1656
  retry.errorCodes = [...new Set(retry.errorCodes)];
@@ -1481,11 +1661,11 @@ export default class Options {
1481
1661
  The IP address used to send the request from.
1482
1662
  */
1483
1663
  get localAddress() {
1484
- return this._internals.localAddress;
1664
+ return this.#internals.localAddress;
1485
1665
  }
1486
1666
  set localAddress(value) {
1487
1667
  assertAny('localAddress', [is.string, is.undefined], value);
1488
- this._internals.localAddress = value;
1668
+ this.#internals.localAddress = value;
1489
1669
  }
1490
1670
  /**
1491
1671
  The HTTP method used to make the request.
@@ -1493,18 +1673,18 @@ export default class Options {
1493
1673
  @default 'GET'
1494
1674
  */
1495
1675
  get method() {
1496
- return this._internals.method;
1676
+ return this.#internals.method;
1497
1677
  }
1498
1678
  set method(value) {
1499
1679
  assert.string(value);
1500
- this._internals.method = value.toUpperCase();
1680
+ this.#internals.method = value.toUpperCase();
1501
1681
  }
1502
1682
  get createConnection() {
1503
- return this._internals.createConnection;
1683
+ return this.#internals.createConnection;
1504
1684
  }
1505
1685
  set createConnection(value) {
1506
1686
  assertAny('createConnection', [is.function, is.undefined], value);
1507
- this._internals.createConnection = value;
1687
+ this.#internals.createConnection = value;
1508
1688
  }
1509
1689
  /**
1510
1690
  From `http-cache-semantics`
@@ -1512,7 +1692,7 @@ export default class Options {
1512
1692
  @default {}
1513
1693
  */
1514
1694
  get cacheOptions() {
1515
- return this._internals.cacheOptions;
1695
+ return this.#internals.cacheOptions;
1516
1696
  }
1517
1697
  set cacheOptions(value) {
1518
1698
  assertPlainObject('cacheOptions', value);
@@ -1520,23 +1700,26 @@ export default class Options {
1520
1700
  assertAny('cacheOptions.cacheHeuristic', [is.number, is.undefined], value.cacheHeuristic);
1521
1701
  assertAny('cacheOptions.immutableMinTimeToLive', [is.number, is.undefined], value.immutableMinTimeToLive);
1522
1702
  assertAny('cacheOptions.ignoreCargoCult', [is.boolean, is.undefined], value.ignoreCargoCult);
1523
- for (const key in value) {
1524
- if (!(key in this._internals.cacheOptions)) {
1703
+ for (const key of Object.keys(value)) {
1704
+ if (key === '__proto__') {
1705
+ continue;
1706
+ }
1707
+ if (!(key in this.#internals.cacheOptions)) {
1525
1708
  throw new Error(`Cache option \`${key}\` does not exist`);
1526
1709
  }
1527
1710
  }
1528
- if (this._merging) {
1529
- Object.assign(this._internals.cacheOptions, value);
1711
+ if (this.#merging) {
1712
+ safeObjectAssign(this.#internals.cacheOptions, value);
1530
1713
  }
1531
1714
  else {
1532
- this._internals.cacheOptions = { ...value };
1715
+ this.#internals.cacheOptions = { ...value };
1533
1716
  }
1534
1717
  }
1535
1718
  /**
1536
1719
  Options for the advanced HTTPS API.
1537
1720
  */
1538
1721
  get https() {
1539
- return this._internals.https;
1722
+ return this.#internals.https;
1540
1723
  }
1541
1724
  set https(value) {
1542
1725
  assertPlainObject('https', value);
@@ -1559,22 +1742,25 @@ export default class Options {
1559
1742
  assertAny('https.ecdhCurve', [is.string, is.undefined], value.ecdhCurve);
1560
1743
  assertAny('https.certificateRevocationLists', [is.string, is.buffer, is.array, is.undefined], value.certificateRevocationLists);
1561
1744
  assertAny('https.secureOptions', [is.number, is.undefined], value.secureOptions);
1562
- for (const key in value) {
1563
- if (!(key in this._internals.https)) {
1745
+ for (const key of Object.keys(value)) {
1746
+ if (key === '__proto__') {
1747
+ continue;
1748
+ }
1749
+ if (!(key in this.#internals.https)) {
1564
1750
  throw new Error(`HTTPS option \`${key}\` does not exist`);
1565
1751
  }
1566
1752
  }
1567
- if (this._merging) {
1568
- Object.assign(this._internals.https, value);
1753
+ if (this.#merging) {
1754
+ safeObjectAssign(this.#internals.https, value);
1569
1755
  }
1570
1756
  else {
1571
- this._internals.https = { ...value };
1757
+ this.#internals.https = { ...value };
1572
1758
  }
1573
1759
  }
1574
1760
  /**
1575
1761
  [Encoding](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) to be used on `setEncoding` of the response data.
1576
1762
 
1577
- To get a [`Buffer`](https://nodejs.org/api/buffer.html), you need to set `responseType` to `buffer` instead.
1763
+ To get a [`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), you need to set `responseType` to `buffer` instead.
1578
1764
  Don't set this option to `null`.
1579
1765
 
1580
1766
  __Note__: This doesn't affect streams! Instead, you need to do `got.stream(...).setEncoding(encoding)`.
@@ -1582,14 +1768,14 @@ export default class Options {
1582
1768
  @default 'utf-8'
1583
1769
  */
1584
1770
  get encoding() {
1585
- return this._internals.encoding;
1771
+ return this.#internals.encoding;
1586
1772
  }
1587
1773
  set encoding(value) {
1588
1774
  if (value === null) {
1589
- throw new TypeError('To get a Buffer, set `options.responseType` to `buffer` instead');
1775
+ throw new TypeError('To get a Uint8Array, set `options.responseType` to `buffer` instead');
1590
1776
  }
1591
1777
  assertAny('encoding', [is.string, is.undefined], value);
1592
- this._internals.encoding = value;
1778
+ this.#internals.encoding = value;
1593
1779
  }
1594
1780
  /**
1595
1781
  When set to `true` the promise will return the Response body instead of the Response object.
@@ -1597,24 +1783,25 @@ export default class Options {
1597
1783
  @default false
1598
1784
  */
1599
1785
  get resolveBodyOnly() {
1600
- return this._internals.resolveBodyOnly;
1786
+ return this.#internals.resolveBodyOnly;
1601
1787
  }
1602
1788
  set resolveBodyOnly(value) {
1603
1789
  assert.boolean(value);
1604
- this._internals.resolveBodyOnly = value;
1790
+ this.#internals.resolveBodyOnly = value;
1605
1791
  }
1606
1792
  /**
1607
1793
  Returns a `Stream` instead of a `Promise`.
1608
- This is equivalent to calling `got.stream(url, options?)`.
1794
+ Set internally by `got.stream()`.
1609
1795
 
1610
1796
  @default false
1797
+ @internal
1611
1798
  */
1612
1799
  get isStream() {
1613
- return this._internals.isStream;
1800
+ return this.#internals.isStream;
1614
1801
  }
1615
1802
  set isStream(value) {
1616
1803
  assert.boolean(value);
1617
- this._internals.isStream = value;
1804
+ this.#internals.isStream = value;
1618
1805
  }
1619
1806
  /**
1620
1807
  The parsing method.
@@ -1633,7 +1820,7 @@ export default class Options {
1633
1820
 
1634
1821
  const [response, buffer, json] = Promise.all([responsePromise, bufferPromise, jsonPromise]);
1635
1822
  // `response` is an instance of Got Response
1636
- // `buffer` is an instance of Buffer
1823
+ // `buffer` is an instance of Uint8Array
1637
1824
  // `json` is an object
1638
1825
  ```
1639
1826
 
@@ -1647,28 +1834,28 @@ export default class Options {
1647
1834
  ```
1648
1835
  */
1649
1836
  get responseType() {
1650
- return this._internals.responseType;
1837
+ return this.#internals.responseType;
1651
1838
  }
1652
1839
  set responseType(value) {
1653
1840
  if (value === undefined) {
1654
- this._internals.responseType = 'text';
1841
+ this.#internals.responseType = 'text';
1655
1842
  return;
1656
1843
  }
1657
1844
  if (value !== 'text' && value !== 'buffer' && value !== 'json') {
1658
1845
  throw new Error(`Invalid \`responseType\` option: ${value}`);
1659
1846
  }
1660
- this._internals.responseType = value;
1847
+ this.#internals.responseType = value;
1661
1848
  }
1662
1849
  get pagination() {
1663
- return this._internals.pagination;
1850
+ return this.#internals.pagination;
1664
1851
  }
1665
1852
  set pagination(value) {
1666
1853
  assert.object(value);
1667
- if (this._merging) {
1668
- Object.assign(this._internals.pagination, value);
1854
+ if (this.#merging) {
1855
+ safeObjectAssign(this.#internals.pagination, value);
1669
1856
  }
1670
1857
  else {
1671
- this._internals.pagination = value;
1858
+ this.#internals.pagination = value;
1672
1859
  }
1673
1860
  }
1674
1861
  get auth() {
@@ -1678,25 +1865,25 @@ export default class Options {
1678
1865
  throw new Error('Parameter `auth` is deprecated. Use `username` / `password` instead.');
1679
1866
  }
1680
1867
  get setHost() {
1681
- return this._internals.setHost;
1868
+ return this.#internals.setHost;
1682
1869
  }
1683
1870
  set setHost(value) {
1684
1871
  assert.boolean(value);
1685
- this._internals.setHost = value;
1872
+ this.#internals.setHost = value;
1686
1873
  }
1687
1874
  get maxHeaderSize() {
1688
- return this._internals.maxHeaderSize;
1875
+ return this.#internals.maxHeaderSize;
1689
1876
  }
1690
1877
  set maxHeaderSize(value) {
1691
1878
  assertAny('maxHeaderSize', [is.number, is.undefined], value);
1692
- this._internals.maxHeaderSize = value;
1879
+ this.#internals.maxHeaderSize = value;
1693
1880
  }
1694
1881
  get enableUnixSockets() {
1695
- return this._internals.enableUnixSockets;
1882
+ return this.#internals.enableUnixSockets;
1696
1883
  }
1697
1884
  set enableUnixSockets(value) {
1698
1885
  assert.boolean(value);
1699
- this._internals.enableUnixSockets = value;
1886
+ this.#internals.enableUnixSockets = value;
1700
1887
  }
1701
1888
  /**
1702
1889
  Throw an error if the server response's `content-length` header value doesn't match the number of bytes received.
@@ -1706,24 +1893,24 @@ export default class Options {
1706
1893
  __Note__: Responses without a `content-length` header are not validated.
1707
1894
  __Note__: When enabled and validation fails, a `ReadError` with code `ERR_HTTP_CONTENT_LENGTH_MISMATCH` will be thrown.
1708
1895
 
1709
- @default false
1896
+ @default true
1710
1897
  */
1711
1898
  get strictContentLength() {
1712
- return this._internals.strictContentLength;
1899
+ return this.#internals.strictContentLength;
1713
1900
  }
1714
1901
  set strictContentLength(value) {
1715
1902
  assert.boolean(value);
1716
- this._internals.strictContentLength = value;
1903
+ this.#internals.strictContentLength = value;
1717
1904
  }
1718
1905
  // eslint-disable-next-line @typescript-eslint/naming-convention
1719
1906
  toJSON() {
1720
- return { ...this._internals };
1907
+ return { ...this.#internals };
1721
1908
  }
1722
1909
  [Symbol.for('nodejs.util.inspect.custom')](_depth, options) {
1723
- return inspect(this._internals, options);
1910
+ return inspect(this.#internals, options);
1724
1911
  }
1725
1912
  createNativeRequestOptions() {
1726
- const internals = this._internals;
1913
+ const internals = this.#internals;
1727
1914
  const url = internals.url;
1728
1915
  let agent;
1729
1916
  if (url.protocol === 'https:') {
@@ -1750,9 +1937,20 @@ export default class Options {
1750
1937
  passphrase: object.passphrase,
1751
1938
  }));
1752
1939
  }
1940
+ const unixSocketPath = getUnixSocketPath(url);
1941
+ if (usesUnixSocket(url) && !internals.enableUnixSockets) {
1942
+ throw new Error('Using UNIX domain sockets but option `enableUnixSockets` is not enabled');
1943
+ }
1944
+ let unixSocketGroups;
1945
+ if (unixSocketPath !== undefined) {
1946
+ unixSocketGroups = /^(?<socketPath>[^:]+):(?<path>.+)$/v.exec(`${url.pathname}${url.search}`)?.groups;
1947
+ }
1948
+ const unixOptions = unixSocketGroups
1949
+ ? { socketPath: unixSocketGroups.socketPath, path: unixSocketGroups.path, host: '' }
1950
+ : undefined;
1753
1951
  return {
1754
1952
  ...internals.cacheOptions,
1755
- ...this._unixOptions,
1953
+ ...unixOptions,
1756
1954
  // HTTPS options
1757
1955
  // eslint-disable-next-line @typescript-eslint/naming-convention
1758
1956
  ALPNProtocols: https.alpnProtocols,
@@ -1760,7 +1958,7 @@ export default class Options {
1760
1958
  cert: https.certificate,
1761
1959
  key: https.key,
1762
1960
  passphrase: https.passphrase,
1763
- pfx: https.pfx,
1961
+ pfx,
1764
1962
  rejectUnauthorized: https.rejectUnauthorized,
1765
1963
  checkServerIdentity: https.checkServerIdentity ?? checkServerIdentity,
1766
1964
  servername: https.serverName,
@@ -1790,33 +1988,24 @@ export default class Options {
1790
1988
  };
1791
1989
  }
1792
1990
  getRequestFunction() {
1793
- const url = this._internals.url;
1794
- const { request } = this._internals;
1795
- if (!request && url) {
1796
- return this.getFallbackRequestFunction();
1797
- }
1798
- return request;
1799
- }
1800
- getFallbackRequestFunction() {
1801
- const url = this._internals.url;
1802
- if (!url) {
1803
- return;
1804
- }
1805
- if (url.protocol === 'https:') {
1806
- if (this._internals.http2) {
1807
- if (major < 15 || (major === 15 && minor < 10)) {
1808
- const error = new Error('To use the `http2` option, install Node.js 15.10.0 or above');
1809
- error.code = 'EUNSUPPORTED';
1810
- throw error;
1811
- }
1812
- return http2wrapper.auto;
1991
+ const { request: customRequest } = this.#internals;
1992
+ if (!customRequest) {
1993
+ return this.#getFallbackRequestFunction();
1994
+ }
1995
+ const requestWithFallback = (url, options, callback) => {
1996
+ const result = customRequest(url, options, callback);
1997
+ if (is.promise(result)) {
1998
+ return this.#resolveRequestWithFallback(result, url, options, callback);
1813
1999
  }
1814
- return https.request;
1815
- }
1816
- return http.request;
2000
+ if (result !== undefined) {
2001
+ return result;
2002
+ }
2003
+ return this.#callFallbackRequest(url, options, callback);
2004
+ };
2005
+ return requestWithFallback;
1817
2006
  }
1818
2007
  freeze() {
1819
- const options = this._internals;
2008
+ const options = this.#internals;
1820
2009
  Object.freeze(options);
1821
2010
  Object.freeze(options.hooks);
1822
2011
  Object.freeze(options.hooks.afterResponse);
@@ -1835,4 +2024,126 @@ export default class Options {
1835
2024
  Object.freeze(options.retry.methods);
1836
2025
  Object.freeze(options.retry.statusCodes);
1837
2026
  }
2027
+ #createHeadersProxy() {
2028
+ return new Proxy(this.#internals.headers, {
2029
+ get(target, property, receiver) {
2030
+ if (typeof property === 'string') {
2031
+ if (Reflect.has(target, property)) {
2032
+ return Reflect.get(target, property, receiver);
2033
+ }
2034
+ const normalizedProperty = property.toLowerCase();
2035
+ return Reflect.get(target, normalizedProperty, receiver);
2036
+ }
2037
+ return Reflect.get(target, property, receiver);
2038
+ },
2039
+ set: (target, property, value) => {
2040
+ if (typeof property === 'string') {
2041
+ const normalizedProperty = property.toLowerCase();
2042
+ assertValidHeaderName(normalizedProperty);
2043
+ const isSuccess = Reflect.set(target, normalizedProperty, value);
2044
+ if (isSuccess) {
2045
+ markHeaderAsExplicit(this.#explicitHeaders, this.#trackedStateMutations, normalizedProperty);
2046
+ }
2047
+ return isSuccess;
2048
+ }
2049
+ return Reflect.set(target, property, value);
2050
+ },
2051
+ deleteProperty: (target, property) => {
2052
+ if (typeof property === 'string') {
2053
+ const normalizedProperty = property.toLowerCase();
2054
+ const isSuccess = Reflect.deleteProperty(target, normalizedProperty);
2055
+ if (isSuccess) {
2056
+ this.#explicitHeaders.delete(normalizedProperty);
2057
+ trackStateMutation(this.#trackedStateMutations, normalizedProperty);
2058
+ }
2059
+ return isSuccess;
2060
+ }
2061
+ return Reflect.deleteProperty(target, property);
2062
+ },
2063
+ });
2064
+ }
2065
+ #getFallbackRequestFunction() {
2066
+ const url = this.#internals.url;
2067
+ if (!url) {
2068
+ return;
2069
+ }
2070
+ if (url.protocol === 'https:') {
2071
+ if (this.#internals.http2) {
2072
+ if (major < 15 || (major === 15 && minor < 10)) {
2073
+ const error = new Error('To use the `http2` option, install Node.js 15.10.0 or above');
2074
+ error.code = 'EUNSUPPORTED';
2075
+ throw error;
2076
+ }
2077
+ return http2wrapper.auto;
2078
+ }
2079
+ return https.request;
2080
+ }
2081
+ return http.request;
2082
+ }
2083
+ #callFallbackRequest(url, options, callback) {
2084
+ const fallbackRequest = this.#getFallbackRequestFunction();
2085
+ if (!fallbackRequest) {
2086
+ throw new TypeError('The request function must return a value');
2087
+ }
2088
+ const fallbackResult = fallbackRequest(url, options, callback);
2089
+ if (fallbackResult === undefined) {
2090
+ throw new TypeError('The request function must return a value');
2091
+ }
2092
+ if (is.promise(fallbackResult)) {
2093
+ return this.#resolveFallbackRequestResult(fallbackResult);
2094
+ }
2095
+ return fallbackResult;
2096
+ }
2097
+ async #resolveRequestWithFallback(requestResult, url, options, callback) {
2098
+ const result = await requestResult;
2099
+ if (result !== undefined) {
2100
+ return result;
2101
+ }
2102
+ return this.#callFallbackRequest(url, options, callback);
2103
+ }
2104
+ async #resolveFallbackRequestResult(fallbackResult) {
2105
+ const resolvedFallbackResult = await fallbackResult;
2106
+ if (resolvedFallbackResult === undefined) {
2107
+ throw new TypeError('The request function must return a value');
2108
+ }
2109
+ return resolvedFallbackResult;
2110
+ }
1838
2111
  }
2112
+ export const snapshotCrossOriginState = (options) => ({
2113
+ headers: { ...options.getInternalHeaders() },
2114
+ username: options.username,
2115
+ password: options.password,
2116
+ body: options.body,
2117
+ json: options.json,
2118
+ form: options.form,
2119
+ bodySnapshot: cloneCrossOriginBodyValue(options.body),
2120
+ jsonSnapshot: cloneCrossOriginBodyValue(options.json),
2121
+ formSnapshot: cloneCrossOriginBodyValue(options.form),
2122
+ });
2123
+ const cloneCrossOriginBodyValue = (value) => {
2124
+ if (value === undefined || value === null || typeof value !== 'object') {
2125
+ return value;
2126
+ }
2127
+ try {
2128
+ return structuredClone(value);
2129
+ }
2130
+ catch {
2131
+ return undefined;
2132
+ }
2133
+ };
2134
+ const isUnchangedCrossOriginBodyValue = (currentValue, previousValue, previousSnapshot) => {
2135
+ if (currentValue !== previousValue) {
2136
+ return false;
2137
+ }
2138
+ if (currentValue === undefined || currentValue === null || typeof currentValue !== 'object') {
2139
+ return true;
2140
+ }
2141
+ if (previousSnapshot === undefined) {
2142
+ return true;
2143
+ }
2144
+ return isDeepStrictEqual(currentValue, previousSnapshot);
2145
+ };
2146
+ export const isCrossOriginCredentialChanged = (previousUrl, nextUrl, credential) => (nextUrl[credential] !== '' && nextUrl[credential] !== previousUrl[credential]);
2147
+ export const isBodyUnchanged = (options, previousState) => isUnchangedCrossOriginBodyValue(options.body, previousState.body, previousState.bodySnapshot)
2148
+ && isUnchangedCrossOriginBodyValue(options.json, previousState.json, previousState.jsonSnapshot)
2149
+ && isUnchangedCrossOriginBodyValue(options.form, previousState.form, previousState.formSnapshot);