got 14.6.5 → 15.0.0

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 (35) hide show
  1. package/dist/source/as-promise/index.d.ts +2 -2
  2. package/dist/source/as-promise/index.js +59 -41
  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 +7 -10
  9. package/dist/source/core/index.d.ts +19 -7
  10. package/dist/source/core/index.js +726 -311
  11. package/dist/source/core/options.d.ts +92 -91
  12. package/dist/source/core/options.js +616 -303
  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 +3 -3
  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/proxy-events.d.ts +1 -1
  21. package/dist/source/core/utils/proxy-events.js +3 -3
  22. package/dist/source/core/utils/strip-url-auth.d.ts +1 -0
  23. package/dist/source/core/utils/strip-url-auth.js +9 -0
  24. package/dist/source/core/utils/timer.js +5 -7
  25. package/dist/source/core/utils/unhandle.js +1 -2
  26. package/dist/source/create.js +83 -27
  27. package/dist/source/index.d.ts +2 -3
  28. package/dist/source/index.js +0 -4
  29. package/dist/source/types.d.ts +42 -70
  30. package/package.json +34 -38
  31. package/readme.md +2 -2
  32. package/dist/source/core/utils/is-form-data.d.ts +0 -7
  33. package/dist/source/core/utils/is-form-data.js +0 -4
  34. package/dist/source/core/utils/url-to-options.d.ts +0 -14
  35. 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][a-z\d+.-]*:\/\//i.test(value);
72
+ const hasHttpProtocolWithoutSlashes = (value) => /^https?:(?!\/\/)/i.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.
@@ -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,12 +904,12 @@ 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
915
  let { setCookie, getCookieString } = value;
@@ -847,13 +919,13 @@ export default class Options {
847
919
  if (setCookie.length === 4 && getCookieString.length === 0) {
848
920
  setCookie = promisify(setCookie.bind(value));
849
921
  getCookieString = promisify(getCookieString.bind(value));
850
- this._internals.cookieJar = {
922
+ this.#internals.cookieJar = {
851
923
  setCookie,
852
924
  getCookieString: getCookieString,
853
925
  };
854
926
  }
855
927
  else {
856
- this._internals.cookieJar = value;
928
+ this.#internals.cookieJar = value;
857
929
  }
858
930
  }
859
931
  /**
@@ -875,11 +947,11 @@ export default class Options {
875
947
  ```
876
948
  */
877
949
  get signal() {
878
- return this._internals.signal;
950
+ return this.#internals.signal;
879
951
  }
880
952
  set signal(value) {
881
- assert.object(value);
882
- this._internals.signal = value;
953
+ assertAny('signal', [is.object, is.undefined], value);
954
+ this.#internals.signal = value;
883
955
  }
884
956
  /**
885
957
  Ignore invalid cookies instead of throwing an error.
@@ -888,11 +960,11 @@ export default class Options {
888
960
  @default false
889
961
  */
890
962
  get ignoreInvalidCookies() {
891
- return this._internals.ignoreInvalidCookies;
963
+ return this.#internals.ignoreInvalidCookies;
892
964
  }
893
965
  set ignoreInvalidCookies(value) {
894
966
  assert.boolean(value);
895
- this._internals.ignoreInvalidCookies = value;
967
+ this.#internals.ignoreInvalidCookies = value;
896
968
  }
897
969
  /**
898
970
  Query string that will be added to the request URL.
@@ -913,19 +985,17 @@ export default class Options {
913
985
  ```
914
986
  */
915
987
  get searchParams() {
916
- if (this._internals.url) {
917
- return this._internals.url.searchParams;
988
+ if (this.#internals.url) {
989
+ return this.#internals.url.searchParams;
918
990
  }
919
- if (this._internals.searchParams === undefined) {
920
- this._internals.searchParams = new URLSearchParams();
921
- }
922
- return this._internals.searchParams;
991
+ this.#internals.searchParams ??= new URLSearchParams();
992
+ return this.#internals.searchParams;
923
993
  }
924
994
  set searchParams(value) {
925
995
  assertAny('searchParams', [is.string, is.object, is.undefined], value);
926
- const url = this._internals.url;
996
+ const url = this.#internals.url;
927
997
  if (value === undefined) {
928
- this._internals.searchParams = undefined;
998
+ this.#internals.searchParams = undefined;
929
999
  if (url) {
930
1000
  url.search = '';
931
1001
  }
@@ -942,8 +1012,10 @@ export default class Options {
942
1012
  else {
943
1013
  validateSearchParameters(value);
944
1014
  updated = new URLSearchParams();
945
- // eslint-disable-next-line guard-for-in
946
- for (const key in value) {
1015
+ for (const key of Object.keys(value)) {
1016
+ if (key === '__proto__') {
1017
+ continue;
1018
+ }
947
1019
  const entry = value[key];
948
1020
  if (entry === null) {
949
1021
  updated.append(key, '');
@@ -956,7 +1028,7 @@ export default class Options {
956
1028
  }
957
1029
  }
958
1030
  }
959
- if (this._merging) {
1031
+ if (this.#merging) {
960
1032
  // These keys will be replaced
961
1033
  for (const key of updated.keys()) {
962
1034
  searchParameters.delete(key);
@@ -969,7 +1041,7 @@ export default class Options {
969
1041
  url.search = searchParameters.toString();
970
1042
  }
971
1043
  else {
972
- this._internals.searchParams = searchParameters;
1044
+ this.#internals.searchParams = searchParameters;
973
1045
  }
974
1046
  }
975
1047
  get searchParameters() {
@@ -979,11 +1051,11 @@ export default class Options {
979
1051
  throw new Error('The `searchParameters` option does not exist. Use `searchParams` instead.');
980
1052
  }
981
1053
  get dnsLookup() {
982
- return this._internals.dnsLookup;
1054
+ return this.#internals.dnsLookup;
983
1055
  }
984
1056
  set dnsLookup(value) {
985
1057
  assertAny('dnsLookup', [is.function, is.undefined], value);
986
- this._internals.dnsLookup = value;
1058
+ this.#internals.dnsLookup = value;
987
1059
  }
988
1060
  /**
989
1061
  An instance of [`CacheableLookup`](https://github.com/szmarczak/cacheable-lookup) used for making DNS lookups.
@@ -996,18 +1068,18 @@ export default class Options {
996
1068
  @default false
997
1069
  */
998
1070
  get dnsCache() {
999
- return this._internals.dnsCache;
1071
+ return this.#internals.dnsCache;
1000
1072
  }
1001
1073
  set dnsCache(value) {
1002
1074
  assertAny('dnsCache', [is.object, is.boolean, is.undefined], value);
1003
1075
  if (value === true) {
1004
- this._internals.dnsCache = getGlobalDnsCache();
1076
+ this.#internals.dnsCache = getGlobalDnsCache();
1005
1077
  }
1006
1078
  else if (value === false) {
1007
- this._internals.dnsCache = undefined;
1079
+ this.#internals.dnsCache = undefined;
1008
1080
  }
1009
1081
  else {
1010
- this._internals.dnsCache = value;
1082
+ this.#internals.dnsCache = value;
1011
1083
  }
1012
1084
  }
1013
1085
  /**
@@ -1042,15 +1114,15 @@ export default class Options {
1042
1114
  ```
1043
1115
  */
1044
1116
  get context() {
1045
- return this._internals.context;
1117
+ return this.#internals.context;
1046
1118
  }
1047
1119
  set context(value) {
1048
1120
  assert.object(value);
1049
- if (this._merging) {
1050
- Object.assign(this._internals.context, value);
1121
+ if (this.#merging) {
1122
+ safeObjectAssign(this.#internals.context, value);
1051
1123
  }
1052
1124
  else {
1053
- this._internals.context = { ...value };
1125
+ this.#internals.context = { ...value };
1054
1126
  }
1055
1127
  }
1056
1128
  /**
@@ -1058,13 +1130,15 @@ export default class Options {
1058
1130
  Hook functions may be async and are run serially.
1059
1131
  */
1060
1132
  get hooks() {
1061
- return this._internals.hooks;
1133
+ return this.#internals.hooks;
1062
1134
  }
1063
1135
  set hooks(value) {
1064
1136
  assert.object(value);
1065
- // eslint-disable-next-line guard-for-in
1066
- for (const knownHookEvent in value) {
1067
- if (!(knownHookEvent in this._internals.hooks)) {
1137
+ for (const knownHookEvent of Object.keys(value)) {
1138
+ if (knownHookEvent === '__proto__') {
1139
+ continue;
1140
+ }
1141
+ if (!(knownHookEvent in this.#internals.hooks)) {
1068
1142
  throw new Error(`Unexpected hook event: ${knownHookEvent}`);
1069
1143
  }
1070
1144
  const typedKnownHookEvent = knownHookEvent;
@@ -1075,10 +1149,10 @@ export default class Options {
1075
1149
  assert.function(hook);
1076
1150
  }
1077
1151
  }
1078
- if (this._merging) {
1152
+ if (this.#merging) {
1079
1153
  if (hooks) {
1080
1154
  // @ts-expect-error FIXME
1081
- this._internals.hooks[typedKnownHookEvent].push(...hooks);
1155
+ this.#internals.hooks[typedKnownHookEvent].push(...hooks);
1082
1156
  }
1083
1157
  }
1084
1158
  else {
@@ -1086,7 +1160,7 @@ export default class Options {
1086
1160
  throw new Error(`Missing hook event: ${knownHookEvent}`);
1087
1161
  }
1088
1162
  // @ts-expect-error FIXME
1089
- this._internals.hooks[knownHookEvent] = [...hooks];
1163
+ this.#internals.hooks[knownHookEvent] = [...hooks];
1090
1164
  }
1091
1165
  }
1092
1166
  }
@@ -1097,15 +1171,16 @@ export default class Options {
1097
1171
 
1098
1172
  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
1173
  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`.
1174
+ 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
1175
 
1101
1176
  @default true
1102
1177
  */
1103
1178
  get followRedirect() {
1104
- return this._internals.followRedirect;
1179
+ return this.#internals.followRedirect;
1105
1180
  }
1106
1181
  set followRedirect(value) {
1107
1182
  assertAny('followRedirect', [is.boolean, is.function], value);
1108
- this._internals.followRedirect = value;
1183
+ this.#internals.followRedirect = value;
1109
1184
  }
1110
1185
  get followRedirects() {
1111
1186
  throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.');
@@ -1119,11 +1194,11 @@ export default class Options {
1119
1194
  @default 10
1120
1195
  */
1121
1196
  get maxRedirects() {
1122
- return this._internals.maxRedirects;
1197
+ return this.#internals.maxRedirects;
1123
1198
  }
1124
1199
  set maxRedirects(value) {
1125
1200
  assert.number(value);
1126
- this._internals.maxRedirects = value;
1201
+ this.#internals.maxRedirects = value;
1127
1202
  }
1128
1203
  /**
1129
1204
  A cache adapter instance for storing cached response data.
@@ -1131,18 +1206,18 @@ export default class Options {
1131
1206
  @default false
1132
1207
  */
1133
1208
  get cache() {
1134
- return this._internals.cache;
1209
+ return this.#internals.cache;
1135
1210
  }
1136
1211
  set cache(value) {
1137
1212
  assertAny('cache', [is.object, is.string, is.boolean, is.undefined], value);
1138
1213
  if (value === true) {
1139
- this._internals.cache = globalCache;
1214
+ this.#internals.cache = globalCache;
1140
1215
  }
1141
1216
  else if (value === false) {
1142
- this._internals.cache = undefined;
1217
+ this.#internals.cache = undefined;
1143
1218
  }
1144
1219
  else {
1145
- this._internals.cache = wrapQuickLruIfNeeded(value);
1220
+ this.#internals.cache = wrapQuickLruIfNeeded(value);
1146
1221
  }
1147
1222
  }
1148
1223
  /**
@@ -1154,43 +1229,45 @@ export default class Options {
1154
1229
  @default true
1155
1230
  */
1156
1231
  get throwHttpErrors() {
1157
- return this._internals.throwHttpErrors;
1232
+ return this.#internals.throwHttpErrors;
1158
1233
  }
1159
1234
  set throwHttpErrors(value) {
1160
1235
  assert.boolean(value);
1161
- this._internals.throwHttpErrors = value;
1236
+ this.#internals.throwHttpErrors = value;
1162
1237
  }
1163
1238
  get username() {
1164
- const url = this._internals.url;
1165
- const value = url ? url.username : this._internals.username;
1239
+ const url = this.#internals.url;
1240
+ const value = url ? url.username : this.#internals.username;
1166
1241
  return decodeURIComponent(value);
1167
1242
  }
1168
1243
  set username(value) {
1169
1244
  assert.string(value);
1170
- const url = this._internals.url;
1245
+ const url = this.#internals.url;
1171
1246
  const fixedValue = encodeURIComponent(value);
1172
1247
  if (url) {
1173
1248
  url.username = fixedValue;
1174
1249
  }
1175
1250
  else {
1176
- this._internals.username = fixedValue;
1251
+ this.#internals.username = fixedValue;
1177
1252
  }
1253
+ trackStateMutation(this.#trackedStateMutations, 'username');
1178
1254
  }
1179
1255
  get password() {
1180
- const url = this._internals.url;
1181
- const value = url ? url.password : this._internals.password;
1256
+ const url = this.#internals.url;
1257
+ const value = url ? url.password : this.#internals.password;
1182
1258
  return decodeURIComponent(value);
1183
1259
  }
1184
1260
  set password(value) {
1185
1261
  assert.string(value);
1186
- const url = this._internals.url;
1262
+ const url = this.#internals.url;
1187
1263
  const fixedValue = encodeURIComponent(value);
1188
1264
  if (url) {
1189
1265
  url.password = fixedValue;
1190
1266
  }
1191
1267
  else {
1192
- this._internals.password = fixedValue;
1268
+ this.#internals.password = fixedValue;
1193
1269
  }
1270
+ trackStateMutation(this.#trackedStateMutations, 'password');
1194
1271
  }
1195
1272
  /**
1196
1273
  If set to `true`, Got will additionally accept HTTP2 requests.
@@ -1214,11 +1291,11 @@ export default class Options {
1214
1291
  ```
1215
1292
  */
1216
1293
  get http2() {
1217
- return this._internals.http2;
1294
+ return this.#internals.http2;
1218
1295
  }
1219
1296
  set http2(value) {
1220
1297
  assert.boolean(value);
1221
- this._internals.http2 = value;
1298
+ this.#internals.http2 = value;
1222
1299
  }
1223
1300
  /**
1224
1301
  Set this to `true` to allow sending body for the `GET` method.
@@ -1230,36 +1307,35 @@ export default class Options {
1230
1307
  @default false
1231
1308
  */
1232
1309
  get allowGetBody() {
1233
- return this._internals.allowGetBody;
1310
+ return this.#internals.allowGetBody;
1234
1311
  }
1235
1312
  set allowGetBody(value) {
1236
1313
  assert.boolean(value);
1237
- this._internals.allowGetBody = value;
1314
+ this.#internals.allowGetBody = value;
1238
1315
  }
1239
1316
  /**
1240
1317
  Automatically copy headers from piped streams.
1241
1318
 
1242
1319
  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
1320
 
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.
1321
+ Note: Explicitly set headers take precedence over piped headers. Piped headers are only copied when a header is not already explicitly set.
1245
1322
 
1246
- Useful for proxy scenarios, but you may want to disable this to filter out headers like `Host`, `Connection`, `Authorization`, etc.
1323
+ Useful for proxy scenarios when explicitly enabled, but you may still want to filter out headers like `Host`, `Connection`, `Authorization`, etc.
1247
1324
 
1248
- @default true
1325
+ @default false
1249
1326
 
1250
1327
  @example
1251
1328
  ```
1252
1329
  import got from 'got';
1253
1330
  import {pipeline} from 'node:stream/promises';
1254
1331
 
1255
- // Disable automatic header copying and manually copy only safe headers
1332
+ // Opt in to automatic header copying for proxy scenarios
1256
1333
  server.get('/proxy', async (request, response) => {
1257
1334
  const gotStream = got.stream('https://example.com', {
1258
- copyPipedHeaders: false,
1335
+ copyPipedHeaders: true,
1336
+ // Explicit headers win over piped headers
1259
1337
  headers: {
1260
- 'user-agent': request.headers['user-agent'],
1261
- 'accept': request.headers['accept'],
1262
- // Explicitly NOT copying host, connection, authorization, etc.
1338
+ host: 'example.com',
1263
1339
  }
1264
1340
  });
1265
1341
 
@@ -1270,27 +1346,114 @@ export default class Options {
1270
1346
  @example
1271
1347
  ```
1272
1348
  import got from 'got';
1349
+ import {pipeline} from 'node:stream/promises';
1273
1350
 
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
- }
1351
+ // Keep it disabled and manually copy only safe headers
1352
+ server.get('/proxy', async (request, response) => {
1353
+ const gotStream = got.stream('https://example.com', {
1354
+ headers: {
1355
+ 'user-agent': request.headers['user-agent'],
1356
+ 'accept': request.headers['accept'],
1357
+ // Explicitly NOT copying host, connection, authorization, etc.
1358
+ }
1359
+ });
1360
+
1361
+ await pipeline(request, gotStream, response);
1285
1362
  });
1286
1363
  ```
1287
1364
  */
1288
1365
  get copyPipedHeaders() {
1289
- return this._internals.copyPipedHeaders;
1366
+ return this.#internals.copyPipedHeaders;
1290
1367
  }
1291
1368
  set copyPipedHeaders(value) {
1292
1369
  assert.boolean(value);
1293
- this._internals.copyPipedHeaders = value;
1370
+ this.#internals.copyPipedHeaders = value;
1371
+ }
1372
+ isHeaderExplicitlySet(name) {
1373
+ return this.#explicitHeaders.has(name.toLowerCase());
1374
+ }
1375
+ shouldCopyPipedHeader(name) {
1376
+ return !this.isHeaderExplicitlySet(name);
1377
+ }
1378
+ setPipedHeader(name, value) {
1379
+ assertValidHeaderName(name);
1380
+ this.#internals.headers[name.toLowerCase()] = value;
1381
+ }
1382
+ getInternalHeaders() {
1383
+ return this.#internals.headers;
1384
+ }
1385
+ setInternalHeader(name, value) {
1386
+ assertValidHeaderName(name);
1387
+ this.#internals.headers[name.toLowerCase()] = value;
1388
+ }
1389
+ deleteInternalHeader(name) {
1390
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
1391
+ delete this.#internals.headers[name.toLowerCase()];
1392
+ }
1393
+ async trackStateMutations(operation) {
1394
+ const changedState = new Set();
1395
+ this.#trackedStateMutations = changedState;
1396
+ try {
1397
+ return await operation(changedState);
1398
+ }
1399
+ finally {
1400
+ this.#trackedStateMutations = undefined;
1401
+ }
1402
+ }
1403
+ clearBody() {
1404
+ this.body = undefined;
1405
+ this.json = undefined;
1406
+ this.form = undefined;
1407
+ for (const header of bodyHeaderNames) {
1408
+ this.deleteInternalHeader(header);
1409
+ }
1410
+ }
1411
+ stripUnchangedCrossOriginState(previousState, changedState, { clearBody = true } = {}) {
1412
+ const headers = this.getInternalHeaders();
1413
+ const url = this.#internals.url;
1414
+ for (const header of crossOriginStripHeaders) {
1415
+ if (!changedState.has(header) && headers[header] === previousState.headers[header]) {
1416
+ this.deleteInternalHeader(header);
1417
+ }
1418
+ }
1419
+ if (!hasExplicitCredentialInUrlChange(changedState, url, 'username')) {
1420
+ this.username = '';
1421
+ }
1422
+ if (!hasExplicitCredentialInUrlChange(changedState, url, 'password')) {
1423
+ this.password = '';
1424
+ }
1425
+ if (clearBody && !changedState.has('body') && !changedState.has('json') && !changedState.has('form') && isBodyUnchanged(this, previousState)) {
1426
+ this.clearBody();
1427
+ }
1428
+ }
1429
+ /**
1430
+ Strip sensitive headers and credentials when navigating to a different origin.
1431
+ Headers and credentials explicitly provided in `userOptions` are preserved.
1432
+ */
1433
+ stripSensitiveHeaders(previousUrl, nextUrl, userOptions) {
1434
+ if (isSameOrigin(previousUrl, nextUrl)) {
1435
+ return;
1436
+ }
1437
+ const headers = lowercaseKeys(userOptions.headers ?? {});
1438
+ for (const header of crossOriginStripHeaders) {
1439
+ if (headers[header] === undefined) {
1440
+ this.deleteInternalHeader(header);
1441
+ }
1442
+ }
1443
+ const explicitUsername = Object.hasOwn(userOptions, 'username') ? userOptions.username : undefined;
1444
+ const explicitPassword = Object.hasOwn(userOptions, 'password') ? userOptions.password : undefined;
1445
+ const hasExplicitUsername = explicitUsername !== undefined
1446
+ || hasCredentialInUrl(userOptions.url, 'username')
1447
+ || isCrossOriginCredentialChanged(previousUrl, nextUrl, 'username');
1448
+ const hasExplicitPassword = explicitPassword !== undefined
1449
+ || hasCredentialInUrl(userOptions.url, 'password')
1450
+ || isCrossOriginCredentialChanged(previousUrl, nextUrl, 'password');
1451
+ if (!hasExplicitUsername && this.username) {
1452
+ this.username = '';
1453
+ }
1454
+ if (!hasExplicitPassword && this.password) {
1455
+ this.password = '';
1456
+ }
1294
1457
  }
1295
1458
  /**
1296
1459
  Request headers.
@@ -1300,33 +1463,49 @@ export default class Options {
1300
1463
  @default {}
1301
1464
  */
1302
1465
  get headers() {
1303
- return this._internals.headers;
1466
+ return this.#headersProxy;
1304
1467
  }
1305
1468
  set headers(value) {
1306
1469
  assertPlainObject('headers', value);
1307
- if (this._merging) {
1308
- Object.assign(this._internals.headers, lowercaseKeys(value));
1470
+ const normalizedHeaders = lowercaseKeys(value);
1471
+ for (const header of Object.keys(normalizedHeaders)) {
1472
+ assertValidHeaderName(header);
1473
+ }
1474
+ if (this.#merging) {
1475
+ safeObjectAssign(this.#internals.headers, normalizedHeaders);
1309
1476
  }
1310
1477
  else {
1311
- this._internals.headers = lowercaseKeys(value);
1478
+ const previousHeaders = this.#internals.headers;
1479
+ this.#internals.headers = normalizedHeaders;
1480
+ this.#headersProxy = this.#createHeadersProxy();
1481
+ this.#explicitHeaders.clear();
1482
+ trackReplacedHeaderMutations(this.#trackedStateMutations, previousHeaders, normalizedHeaders);
1483
+ }
1484
+ for (const header of Object.keys(normalizedHeaders)) {
1485
+ if (this.#merging) {
1486
+ markHeaderAsExplicit(this.#explicitHeaders, this.#trackedStateMutations, header);
1487
+ }
1488
+ else {
1489
+ addExplicitHeader(this.#explicitHeaders, header);
1490
+ }
1312
1491
  }
1313
1492
  }
1314
1493
  /**
1315
1494
  Specifies if the HTTP request method should be [rewritten as `GET`](https://tools.ietf.org/html/rfc7231#section-6.4) on redirects.
1316
1495
 
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.
1496
+ 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.
1497
+ 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
1498
 
1320
1499
  __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
1500
 
1322
1501
  @default false
1323
1502
  */
1324
1503
  get methodRewriting() {
1325
- return this._internals.methodRewriting;
1504
+ return this.#internals.methodRewriting;
1326
1505
  }
1327
1506
  set methodRewriting(value) {
1328
1507
  assert.boolean(value);
1329
- this._internals.methodRewriting = value;
1508
+ this.#internals.methodRewriting = value;
1330
1509
  }
1331
1510
  /**
1332
1511
  Indicates which DNS record family to use.
@@ -1339,13 +1518,13 @@ export default class Options {
1339
1518
  @default undefined
1340
1519
  */
1341
1520
  get dnsLookupIpVersion() {
1342
- return this._internals.dnsLookupIpVersion;
1521
+ return this.#internals.dnsLookupIpVersion;
1343
1522
  }
1344
1523
  set dnsLookupIpVersion(value) {
1345
1524
  if (value !== undefined && value !== 4 && value !== 6) {
1346
1525
  throw new TypeError(`Invalid DNS lookup IP version: ${value}`);
1347
1526
  }
1348
- this._internals.dnsLookupIpVersion = value;
1527
+ this.#internals.dnsLookupIpVersion = value;
1349
1528
  }
1350
1529
  /**
1351
1530
  A function used to parse JSON responses.
@@ -1363,11 +1542,11 @@ export default class Options {
1363
1542
  ```
1364
1543
  */
1365
1544
  get parseJson() {
1366
- return this._internals.parseJson;
1545
+ return this.#internals.parseJson;
1367
1546
  }
1368
1547
  set parseJson(value) {
1369
1548
  assert.function(value);
1370
- this._internals.parseJson = value;
1549
+ this.#internals.parseJson = value;
1371
1550
  }
1372
1551
  /**
1373
1552
  A function used to stringify the body of JSON requests.
@@ -1411,11 +1590,11 @@ export default class Options {
1411
1590
  ```
1412
1591
  */
1413
1592
  get stringifyJson() {
1414
- return this._internals.stringifyJson;
1593
+ return this.#internals.stringifyJson;
1415
1594
  }
1416
1595
  set stringifyJson(value) {
1417
1596
  assert.function(value);
1418
- this._internals.stringifyJson = value;
1597
+ this.#internals.stringifyJson = value;
1419
1598
  }
1420
1599
  /**
1421
1600
  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 +1604,9 @@ export default class Options {
1425
1604
  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
1605
  The function must return a delay in milliseconds (or a Promise resolving with it) (`0` return value cancels retry).
1427
1606
 
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.
1607
+ 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
1608
 
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.
1609
+ __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
1610
 
1432
1611
  By default, it retries *only* on the specified methods, status codes, and on these network errors:
1433
1612
 
@@ -1444,7 +1623,7 @@ export default class Options {
1444
1623
  __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
1624
  */
1446
1625
  get retry() {
1447
- return this._internals.retry;
1626
+ return this.#internals.retry;
1448
1627
  }
1449
1628
  set retry(value) {
1450
1629
  assertPlainObject('retry', value);
@@ -1459,18 +1638,21 @@ export default class Options {
1459
1638
  if (value.noise && Math.abs(value.noise) > 100) {
1460
1639
  throw new Error(`The maximum acceptable retry noise is +/- 100ms, got ${value.noise}`);
1461
1640
  }
1462
- for (const key in value) {
1463
- if (!(key in this._internals.retry)) {
1641
+ for (const key of Object.keys(value)) {
1642
+ if (key === '__proto__') {
1643
+ continue;
1644
+ }
1645
+ if (!(key in this.#internals.retry)) {
1464
1646
  throw new Error(`Unexpected retry option: ${key}`);
1465
1647
  }
1466
1648
  }
1467
- if (this._merging) {
1468
- Object.assign(this._internals.retry, value);
1649
+ if (this.#merging) {
1650
+ safeObjectAssign(this.#internals.retry, value);
1469
1651
  }
1470
1652
  else {
1471
- this._internals.retry = { ...value };
1653
+ this.#internals.retry = { ...value };
1472
1654
  }
1473
- const { retry } = this._internals;
1655
+ const { retry } = this.#internals;
1474
1656
  retry.methods = [...new Set(retry.methods.map(method => method.toUpperCase()))];
1475
1657
  retry.statusCodes = [...new Set(retry.statusCodes)];
1476
1658
  retry.errorCodes = [...new Set(retry.errorCodes)];
@@ -1481,11 +1663,11 @@ export default class Options {
1481
1663
  The IP address used to send the request from.
1482
1664
  */
1483
1665
  get localAddress() {
1484
- return this._internals.localAddress;
1666
+ return this.#internals.localAddress;
1485
1667
  }
1486
1668
  set localAddress(value) {
1487
1669
  assertAny('localAddress', [is.string, is.undefined], value);
1488
- this._internals.localAddress = value;
1670
+ this.#internals.localAddress = value;
1489
1671
  }
1490
1672
  /**
1491
1673
  The HTTP method used to make the request.
@@ -1493,18 +1675,18 @@ export default class Options {
1493
1675
  @default 'GET'
1494
1676
  */
1495
1677
  get method() {
1496
- return this._internals.method;
1678
+ return this.#internals.method;
1497
1679
  }
1498
1680
  set method(value) {
1499
1681
  assert.string(value);
1500
- this._internals.method = value.toUpperCase();
1682
+ this.#internals.method = value.toUpperCase();
1501
1683
  }
1502
1684
  get createConnection() {
1503
- return this._internals.createConnection;
1685
+ return this.#internals.createConnection;
1504
1686
  }
1505
1687
  set createConnection(value) {
1506
1688
  assertAny('createConnection', [is.function, is.undefined], value);
1507
- this._internals.createConnection = value;
1689
+ this.#internals.createConnection = value;
1508
1690
  }
1509
1691
  /**
1510
1692
  From `http-cache-semantics`
@@ -1512,7 +1694,7 @@ export default class Options {
1512
1694
  @default {}
1513
1695
  */
1514
1696
  get cacheOptions() {
1515
- return this._internals.cacheOptions;
1697
+ return this.#internals.cacheOptions;
1516
1698
  }
1517
1699
  set cacheOptions(value) {
1518
1700
  assertPlainObject('cacheOptions', value);
@@ -1520,23 +1702,26 @@ export default class Options {
1520
1702
  assertAny('cacheOptions.cacheHeuristic', [is.number, is.undefined], value.cacheHeuristic);
1521
1703
  assertAny('cacheOptions.immutableMinTimeToLive', [is.number, is.undefined], value.immutableMinTimeToLive);
1522
1704
  assertAny('cacheOptions.ignoreCargoCult', [is.boolean, is.undefined], value.ignoreCargoCult);
1523
- for (const key in value) {
1524
- if (!(key in this._internals.cacheOptions)) {
1705
+ for (const key of Object.keys(value)) {
1706
+ if (key === '__proto__') {
1707
+ continue;
1708
+ }
1709
+ if (!(key in this.#internals.cacheOptions)) {
1525
1710
  throw new Error(`Cache option \`${key}\` does not exist`);
1526
1711
  }
1527
1712
  }
1528
- if (this._merging) {
1529
- Object.assign(this._internals.cacheOptions, value);
1713
+ if (this.#merging) {
1714
+ safeObjectAssign(this.#internals.cacheOptions, value);
1530
1715
  }
1531
1716
  else {
1532
- this._internals.cacheOptions = { ...value };
1717
+ this.#internals.cacheOptions = { ...value };
1533
1718
  }
1534
1719
  }
1535
1720
  /**
1536
1721
  Options for the advanced HTTPS API.
1537
1722
  */
1538
1723
  get https() {
1539
- return this._internals.https;
1724
+ return this.#internals.https;
1540
1725
  }
1541
1726
  set https(value) {
1542
1727
  assertPlainObject('https', value);
@@ -1559,22 +1744,25 @@ export default class Options {
1559
1744
  assertAny('https.ecdhCurve', [is.string, is.undefined], value.ecdhCurve);
1560
1745
  assertAny('https.certificateRevocationLists', [is.string, is.buffer, is.array, is.undefined], value.certificateRevocationLists);
1561
1746
  assertAny('https.secureOptions', [is.number, is.undefined], value.secureOptions);
1562
- for (const key in value) {
1563
- if (!(key in this._internals.https)) {
1747
+ for (const key of Object.keys(value)) {
1748
+ if (key === '__proto__') {
1749
+ continue;
1750
+ }
1751
+ if (!(key in this.#internals.https)) {
1564
1752
  throw new Error(`HTTPS option \`${key}\` does not exist`);
1565
1753
  }
1566
1754
  }
1567
- if (this._merging) {
1568
- Object.assign(this._internals.https, value);
1755
+ if (this.#merging) {
1756
+ safeObjectAssign(this.#internals.https, value);
1569
1757
  }
1570
1758
  else {
1571
- this._internals.https = { ...value };
1759
+ this.#internals.https = { ...value };
1572
1760
  }
1573
1761
  }
1574
1762
  /**
1575
1763
  [Encoding](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) to be used on `setEncoding` of the response data.
1576
1764
 
1577
- To get a [`Buffer`](https://nodejs.org/api/buffer.html), you need to set `responseType` to `buffer` instead.
1765
+ To get a [`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), you need to set `responseType` to `buffer` instead.
1578
1766
  Don't set this option to `null`.
1579
1767
 
1580
1768
  __Note__: This doesn't affect streams! Instead, you need to do `got.stream(...).setEncoding(encoding)`.
@@ -1582,14 +1770,14 @@ export default class Options {
1582
1770
  @default 'utf-8'
1583
1771
  */
1584
1772
  get encoding() {
1585
- return this._internals.encoding;
1773
+ return this.#internals.encoding;
1586
1774
  }
1587
1775
  set encoding(value) {
1588
1776
  if (value === null) {
1589
- throw new TypeError('To get a Buffer, set `options.responseType` to `buffer` instead');
1777
+ throw new TypeError('To get a Uint8Array, set `options.responseType` to `buffer` instead');
1590
1778
  }
1591
1779
  assertAny('encoding', [is.string, is.undefined], value);
1592
- this._internals.encoding = value;
1780
+ this.#internals.encoding = value;
1593
1781
  }
1594
1782
  /**
1595
1783
  When set to `true` the promise will return the Response body instead of the Response object.
@@ -1597,24 +1785,25 @@ export default class Options {
1597
1785
  @default false
1598
1786
  */
1599
1787
  get resolveBodyOnly() {
1600
- return this._internals.resolveBodyOnly;
1788
+ return this.#internals.resolveBodyOnly;
1601
1789
  }
1602
1790
  set resolveBodyOnly(value) {
1603
1791
  assert.boolean(value);
1604
- this._internals.resolveBodyOnly = value;
1792
+ this.#internals.resolveBodyOnly = value;
1605
1793
  }
1606
1794
  /**
1795
+ @internal
1607
1796
  Returns a `Stream` instead of a `Promise`.
1608
- This is equivalent to calling `got.stream(url, options?)`.
1797
+ Set internally by `got.stream()`.
1609
1798
 
1610
1799
  @default false
1611
1800
  */
1612
1801
  get isStream() {
1613
- return this._internals.isStream;
1802
+ return this.#internals.isStream;
1614
1803
  }
1615
1804
  set isStream(value) {
1616
1805
  assert.boolean(value);
1617
- this._internals.isStream = value;
1806
+ this.#internals.isStream = value;
1618
1807
  }
1619
1808
  /**
1620
1809
  The parsing method.
@@ -1633,7 +1822,7 @@ export default class Options {
1633
1822
 
1634
1823
  const [response, buffer, json] = Promise.all([responsePromise, bufferPromise, jsonPromise]);
1635
1824
  // `response` is an instance of Got Response
1636
- // `buffer` is an instance of Buffer
1825
+ // `buffer` is an instance of Uint8Array
1637
1826
  // `json` is an object
1638
1827
  ```
1639
1828
 
@@ -1647,28 +1836,28 @@ export default class Options {
1647
1836
  ```
1648
1837
  */
1649
1838
  get responseType() {
1650
- return this._internals.responseType;
1839
+ return this.#internals.responseType;
1651
1840
  }
1652
1841
  set responseType(value) {
1653
1842
  if (value === undefined) {
1654
- this._internals.responseType = 'text';
1843
+ this.#internals.responseType = 'text';
1655
1844
  return;
1656
1845
  }
1657
1846
  if (value !== 'text' && value !== 'buffer' && value !== 'json') {
1658
1847
  throw new Error(`Invalid \`responseType\` option: ${value}`);
1659
1848
  }
1660
- this._internals.responseType = value;
1849
+ this.#internals.responseType = value;
1661
1850
  }
1662
1851
  get pagination() {
1663
- return this._internals.pagination;
1852
+ return this.#internals.pagination;
1664
1853
  }
1665
1854
  set pagination(value) {
1666
1855
  assert.object(value);
1667
- if (this._merging) {
1668
- Object.assign(this._internals.pagination, value);
1856
+ if (this.#merging) {
1857
+ safeObjectAssign(this.#internals.pagination, value);
1669
1858
  }
1670
1859
  else {
1671
- this._internals.pagination = value;
1860
+ this.#internals.pagination = value;
1672
1861
  }
1673
1862
  }
1674
1863
  get auth() {
@@ -1678,25 +1867,25 @@ export default class Options {
1678
1867
  throw new Error('Parameter `auth` is deprecated. Use `username` / `password` instead.');
1679
1868
  }
1680
1869
  get setHost() {
1681
- return this._internals.setHost;
1870
+ return this.#internals.setHost;
1682
1871
  }
1683
1872
  set setHost(value) {
1684
1873
  assert.boolean(value);
1685
- this._internals.setHost = value;
1874
+ this.#internals.setHost = value;
1686
1875
  }
1687
1876
  get maxHeaderSize() {
1688
- return this._internals.maxHeaderSize;
1877
+ return this.#internals.maxHeaderSize;
1689
1878
  }
1690
1879
  set maxHeaderSize(value) {
1691
1880
  assertAny('maxHeaderSize', [is.number, is.undefined], value);
1692
- this._internals.maxHeaderSize = value;
1881
+ this.#internals.maxHeaderSize = value;
1693
1882
  }
1694
1883
  get enableUnixSockets() {
1695
- return this._internals.enableUnixSockets;
1884
+ return this.#internals.enableUnixSockets;
1696
1885
  }
1697
1886
  set enableUnixSockets(value) {
1698
1887
  assert.boolean(value);
1699
- this._internals.enableUnixSockets = value;
1888
+ this.#internals.enableUnixSockets = value;
1700
1889
  }
1701
1890
  /**
1702
1891
  Throw an error if the server response's `content-length` header value doesn't match the number of bytes received.
@@ -1706,24 +1895,24 @@ export default class Options {
1706
1895
  __Note__: Responses without a `content-length` header are not validated.
1707
1896
  __Note__: When enabled and validation fails, a `ReadError` with code `ERR_HTTP_CONTENT_LENGTH_MISMATCH` will be thrown.
1708
1897
 
1709
- @default false
1898
+ @default true
1710
1899
  */
1711
1900
  get strictContentLength() {
1712
- return this._internals.strictContentLength;
1901
+ return this.#internals.strictContentLength;
1713
1902
  }
1714
1903
  set strictContentLength(value) {
1715
1904
  assert.boolean(value);
1716
- this._internals.strictContentLength = value;
1905
+ this.#internals.strictContentLength = value;
1717
1906
  }
1718
1907
  // eslint-disable-next-line @typescript-eslint/naming-convention
1719
1908
  toJSON() {
1720
- return { ...this._internals };
1909
+ return { ...this.#internals };
1721
1910
  }
1722
1911
  [Symbol.for('nodejs.util.inspect.custom')](_depth, options) {
1723
- return inspect(this._internals, options);
1912
+ return inspect(this.#internals, options);
1724
1913
  }
1725
1914
  createNativeRequestOptions() {
1726
- const internals = this._internals;
1915
+ const internals = this.#internals;
1727
1916
  const url = internals.url;
1728
1917
  let agent;
1729
1918
  if (url.protocol === 'https:') {
@@ -1750,9 +1939,20 @@ export default class Options {
1750
1939
  passphrase: object.passphrase,
1751
1940
  }));
1752
1941
  }
1942
+ const unixSocketPath = getUnixSocketPath(url);
1943
+ if (usesUnixSocket(url) && !internals.enableUnixSockets) {
1944
+ throw new Error('Using UNIX domain sockets but option `enableUnixSockets` is not enabled');
1945
+ }
1946
+ let unixSocketGroups;
1947
+ if (unixSocketPath !== undefined) {
1948
+ unixSocketGroups = /(?<socketPath>.+?):(?<path>.+)/.exec(`${url.pathname}${url.search}`)?.groups;
1949
+ }
1950
+ const unixOptions = unixSocketGroups
1951
+ ? { socketPath: unixSocketGroups.socketPath, path: unixSocketGroups.path, host: '' }
1952
+ : undefined;
1753
1953
  return {
1754
1954
  ...internals.cacheOptions,
1755
- ...this._unixOptions,
1955
+ ...unixOptions,
1756
1956
  // HTTPS options
1757
1957
  // eslint-disable-next-line @typescript-eslint/naming-convention
1758
1958
  ALPNProtocols: https.alpnProtocols,
@@ -1760,7 +1960,7 @@ export default class Options {
1760
1960
  cert: https.certificate,
1761
1961
  key: https.key,
1762
1962
  passphrase: https.passphrase,
1763
- pfx: https.pfx,
1963
+ pfx,
1764
1964
  rejectUnauthorized: https.rejectUnauthorized,
1765
1965
  checkServerIdentity: https.checkServerIdentity ?? checkServerIdentity,
1766
1966
  servername: https.serverName,
@@ -1790,33 +1990,24 @@ export default class Options {
1790
1990
  };
1791
1991
  }
1792
1992
  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;
1993
+ const { request: customRequest } = this.#internals;
1994
+ if (!customRequest) {
1995
+ return this.#getFallbackRequestFunction();
1996
+ }
1997
+ const requestWithFallback = (url, options, callback) => {
1998
+ const result = customRequest(url, options, callback);
1999
+ if (is.promise(result)) {
2000
+ return this.#resolveRequestWithFallback(result, url, options, callback);
1813
2001
  }
1814
- return https.request;
1815
- }
1816
- return http.request;
2002
+ if (result !== undefined) {
2003
+ return result;
2004
+ }
2005
+ return this.#callFallbackRequest(url, options, callback);
2006
+ };
2007
+ return requestWithFallback;
1817
2008
  }
1818
2009
  freeze() {
1819
- const options = this._internals;
2010
+ const options = this.#internals;
1820
2011
  Object.freeze(options);
1821
2012
  Object.freeze(options.hooks);
1822
2013
  Object.freeze(options.hooks.afterResponse);
@@ -1835,4 +2026,126 @@ export default class Options {
1835
2026
  Object.freeze(options.retry.methods);
1836
2027
  Object.freeze(options.retry.statusCodes);
1837
2028
  }
2029
+ #createHeadersProxy() {
2030
+ return new Proxy(this.#internals.headers, {
2031
+ get(target, property, receiver) {
2032
+ if (typeof property === 'string') {
2033
+ if (Reflect.has(target, property)) {
2034
+ return Reflect.get(target, property, receiver);
2035
+ }
2036
+ const normalizedProperty = property.toLowerCase();
2037
+ return Reflect.get(target, normalizedProperty, receiver);
2038
+ }
2039
+ return Reflect.get(target, property, receiver);
2040
+ },
2041
+ set: (target, property, value) => {
2042
+ if (typeof property === 'string') {
2043
+ const normalizedProperty = property.toLowerCase();
2044
+ assertValidHeaderName(normalizedProperty);
2045
+ const isSuccess = Reflect.set(target, normalizedProperty, value);
2046
+ if (isSuccess) {
2047
+ markHeaderAsExplicit(this.#explicitHeaders, this.#trackedStateMutations, normalizedProperty);
2048
+ }
2049
+ return isSuccess;
2050
+ }
2051
+ return Reflect.set(target, property, value);
2052
+ },
2053
+ deleteProperty: (target, property) => {
2054
+ if (typeof property === 'string') {
2055
+ const normalizedProperty = property.toLowerCase();
2056
+ const isSuccess = Reflect.deleteProperty(target, normalizedProperty);
2057
+ if (isSuccess) {
2058
+ this.#explicitHeaders.delete(normalizedProperty);
2059
+ trackStateMutation(this.#trackedStateMutations, normalizedProperty);
2060
+ }
2061
+ return isSuccess;
2062
+ }
2063
+ return Reflect.deleteProperty(target, property);
2064
+ },
2065
+ });
2066
+ }
2067
+ #getFallbackRequestFunction() {
2068
+ const url = this.#internals.url;
2069
+ if (!url) {
2070
+ return;
2071
+ }
2072
+ if (url.protocol === 'https:') {
2073
+ if (this.#internals.http2) {
2074
+ if (major < 15 || (major === 15 && minor < 10)) {
2075
+ const error = new Error('To use the `http2` option, install Node.js 15.10.0 or above');
2076
+ error.code = 'EUNSUPPORTED';
2077
+ throw error;
2078
+ }
2079
+ return http2wrapper.auto;
2080
+ }
2081
+ return https.request;
2082
+ }
2083
+ return http.request;
2084
+ }
2085
+ #callFallbackRequest(url, options, callback) {
2086
+ const fallbackRequest = this.#getFallbackRequestFunction();
2087
+ if (!fallbackRequest) {
2088
+ throw new TypeError('The request function must return a value');
2089
+ }
2090
+ const fallbackResult = fallbackRequest(url, options, callback);
2091
+ if (fallbackResult === undefined) {
2092
+ throw new TypeError('The request function must return a value');
2093
+ }
2094
+ if (is.promise(fallbackResult)) {
2095
+ return this.#resolveFallbackRequestResult(fallbackResult);
2096
+ }
2097
+ return fallbackResult;
2098
+ }
2099
+ async #resolveRequestWithFallback(requestResult, url, options, callback) {
2100
+ const result = await requestResult;
2101
+ if (result !== undefined) {
2102
+ return result;
2103
+ }
2104
+ return this.#callFallbackRequest(url, options, callback);
2105
+ }
2106
+ async #resolveFallbackRequestResult(fallbackResult) {
2107
+ const resolvedFallbackResult = await fallbackResult;
2108
+ if (resolvedFallbackResult === undefined) {
2109
+ throw new TypeError('The request function must return a value');
2110
+ }
2111
+ return resolvedFallbackResult;
2112
+ }
1838
2113
  }
2114
+ export const snapshotCrossOriginState = (options) => ({
2115
+ headers: { ...options.getInternalHeaders() },
2116
+ username: options.username,
2117
+ password: options.password,
2118
+ body: options.body,
2119
+ json: options.json,
2120
+ form: options.form,
2121
+ bodySnapshot: cloneCrossOriginBodyValue(options.body),
2122
+ jsonSnapshot: cloneCrossOriginBodyValue(options.json),
2123
+ formSnapshot: cloneCrossOriginBodyValue(options.form),
2124
+ });
2125
+ const cloneCrossOriginBodyValue = (value) => {
2126
+ if (value === undefined || value === null || typeof value !== 'object') {
2127
+ return value;
2128
+ }
2129
+ try {
2130
+ return structuredClone(value);
2131
+ }
2132
+ catch {
2133
+ return undefined;
2134
+ }
2135
+ };
2136
+ const isUnchangedCrossOriginBodyValue = (currentValue, previousValue, previousSnapshot) => {
2137
+ if (currentValue !== previousValue) {
2138
+ return false;
2139
+ }
2140
+ if (currentValue === undefined || currentValue === null || typeof currentValue !== 'object') {
2141
+ return true;
2142
+ }
2143
+ if (previousSnapshot === undefined) {
2144
+ return true;
2145
+ }
2146
+ return isDeepStrictEqual(currentValue, previousSnapshot);
2147
+ };
2148
+ export const isCrossOriginCredentialChanged = (previousUrl, nextUrl, credential) => (nextUrl[credential] !== '' && nextUrl[credential] !== previousUrl[credential]);
2149
+ export const isBodyUnchanged = (options, previousState) => isUnchangedCrossOriginBodyValue(options.body, previousState.body, previousState.bodySnapshot)
2150
+ && isUnchangedCrossOriginBodyValue(options.json, previousState.json, previousState.jsonSnapshot)
2151
+ && isUnchangedCrossOriginBodyValue(options.form, previousState.form, previousState.formSnapshot);