@infersec/conduit 1.85.0 → 1.86.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 (3) hide show
  1. package/dist/cli.js +1192 -302
  2. package/dist/cli.sea.cjs +1192 -302
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -26916,7 +26916,13 @@ function requireRequest$2 () {
26916
26916
  } else if (typeof val[i] === 'object') {
26917
26917
  throw new InvalidArgumentError(`invalid ${key} header`)
26918
26918
  } else {
26919
- arr.push(`${val[i]}`);
26919
+ // Coerce primitives (and reject unsafe coercions such as functions
26920
+ // with a crafted toString/Symbol.toPrimitive).
26921
+ const str = `${val[i]}`;
26922
+ if (!isValidHeaderValue(str)) {
26923
+ throw new InvalidArgumentError(`invalid ${key} header`)
26924
+ }
26925
+ arr.push(str);
26920
26926
  }
26921
26927
  }
26922
26928
  val = arr;
@@ -26927,7 +26933,12 @@ function requireRequest$2 () {
26927
26933
  } else if (val === null) {
26928
26934
  val = '';
26929
26935
  } else {
26936
+ // Coerce primitives (and reject unsafe coercions such as functions
26937
+ // with a crafted toString/Symbol.toPrimitive).
26930
26938
  val = `${val}`;
26939
+ if (!isValidHeaderValue(val)) {
26940
+ throw new InvalidArgumentError(`invalid ${key} header`)
26941
+ }
26931
26942
  }
26932
26943
 
26933
26944
  if (headerName === 'host') {
@@ -33409,6 +33420,7 @@ function requireClientH1 () {
33409
33420
  RequestContentLengthMismatchError,
33410
33421
  ResponseContentLengthMismatchError,
33411
33422
  RequestAbortedError,
33423
+ InvalidArgumentError,
33412
33424
  HeadersTimeoutError,
33413
33425
  HeadersOverflowError,
33414
33426
  SocketError,
@@ -34533,8 +34545,16 @@ function requireClientH1 () {
34533
34545
  }
34534
34546
  body = bodyStream.stream;
34535
34547
  contentLength = bodyStream.length;
34536
- } else if (util.isBlobLike(body) && request.contentType == null && body.type) {
34537
- headers.push('content-type', body.type);
34548
+ } else if (util.isBlobLike(body) && request.contentType == null) {
34549
+ const contentType = body.type;
34550
+ if (contentType) {
34551
+ const contentTypeValue = `${contentType}`;
34552
+ if (!util.isValidHeaderValue(contentTypeValue)) {
34553
+ util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header'));
34554
+ return false
34555
+ }
34556
+ headers.push('content-type', contentTypeValue);
34557
+ }
34538
34558
  }
34539
34559
 
34540
34560
  if (body && typeof body.read === 'function') {
@@ -39245,6 +39265,26 @@ function requireRetryHandler () {
39245
39265
  return isNaN(retryTime) ? 0 : retryTime - Date.now()
39246
39266
  }
39247
39267
 
39268
+ function validatePartialResponseContentLength (headers, range, statusCode, retryCount) {
39269
+ const contentLength = headers['content-length'];
39270
+ if (contentLength == null) {
39271
+ return
39272
+ }
39273
+
39274
+ if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) {
39275
+ return
39276
+ }
39277
+
39278
+ const length = Number(contentLength);
39279
+ const expectedLength = range.end - range.start + 1;
39280
+ if (!Number.isFinite(length) || length !== expectedLength) {
39281
+ throw new RequestRetryError('Content-Length mismatch', statusCode, {
39282
+ headers,
39283
+ data: { count: retryCount }
39284
+ })
39285
+ }
39286
+ }
39287
+
39248
39288
  class RetryHandler {
39249
39289
  constructor (opts, { dispatch, handler }) {
39250
39290
  const { retryOptions, ...dispatchOpts } = opts;
@@ -39459,6 +39499,8 @@ function requireRetryHandler () {
39459
39499
  })
39460
39500
  }
39461
39501
 
39502
+ validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount);
39503
+
39462
39504
  const { start, size, end = size ? size - 1 : null } = contentRange;
39463
39505
 
39464
39506
  assert(this.start === start, 'content-range mismatch');
@@ -39483,6 +39525,8 @@ function requireRetryHandler () {
39483
39525
  return
39484
39526
  }
39485
39527
 
39528
+ validatePartialResponseContentLength(headers, range, statusCode, this.retryCount);
39529
+
39486
39530
  const { start, size, end = size ? size - 1 : null } = range;
39487
39531
  assert(
39488
39532
  start != null && Number.isFinite(start),
@@ -45185,11 +45229,147 @@ function requireCache$2 () {
45185
45229
  const {
45186
45230
  safeHTTPMethods,
45187
45231
  pathHasQueryOrFragment,
45188
- hasSafeIterator
45232
+ hasSafeIterator,
45233
+ isValidHTTPToken
45189
45234
  } = requireUtil$6();
45190
45235
 
45191
45236
  const { serializePathWithQuery } = requireUtil$6();
45192
45237
 
45238
+ const MAX_DELTA_SECONDS = 2147483647;
45239
+ const RESTRICTIVE_DIRECTIVE_NAMES = ['no-store', 'private', 'no-cache'];
45240
+ const kInvalidCacheControlDirectives = Symbol('invalid cache-control directives');
45241
+
45242
+ function trimOWS (value) {
45243
+ return value.replace(/^[\t ]+|[\t ]+$/g, '')
45244
+ }
45245
+
45246
+ function arrayIncludes (array, value) {
45247
+ for (let i = 0; i < array.length; i++) {
45248
+ if (array[i] === value) {
45249
+ return true
45250
+ }
45251
+ }
45252
+
45253
+ return false
45254
+ }
45255
+
45256
+ function trimOWSStart (value) {
45257
+ return value.replace(/^[\t ]+/, '')
45258
+ }
45259
+
45260
+ function trimOWSEnd (value) {
45261
+ return value.replace(/[\t ]+$/, '')
45262
+ }
45263
+
45264
+ function findUnescapedQuote (value, start) {
45265
+ let escaped = false;
45266
+ for (let i = start; i < value.length; i++) {
45267
+ if (escaped) {
45268
+ escaped = false;
45269
+ } else if (value[i] === '\\') {
45270
+ escaped = true;
45271
+ } else if (value[i] === '"') {
45272
+ return i
45273
+ }
45274
+ }
45275
+
45276
+ return -1
45277
+ }
45278
+
45279
+ function splitCacheControlHeaderValue (value) {
45280
+ const directives = [];
45281
+ let start = 0;
45282
+ let quoteStart = -1;
45283
+ let inQuote = false;
45284
+ let escaped = false;
45285
+
45286
+ for (let i = 0; i < value.length; i++) {
45287
+ if (inQuote) {
45288
+ if (escaped) {
45289
+ escaped = false;
45290
+ } else if (value[i] === '\\') {
45291
+ escaped = true;
45292
+ } else if (value[i] === '"') {
45293
+ inQuote = false;
45294
+ quoteStart = -1;
45295
+ }
45296
+ } else if (value[i] === '"') {
45297
+ inQuote = true;
45298
+ quoteStart = i;
45299
+ } else if (value[i] === ',') {
45300
+ directives.push({ value: value.substring(start, i), fromMalformedQuote: false });
45301
+ start = i + 1;
45302
+ }
45303
+ }
45304
+
45305
+ if (!inQuote) {
45306
+ directives.push({ value: value.substring(start), fromMalformedQuote: false });
45307
+ return directives
45308
+ }
45309
+
45310
+ const tail = value.substring(start);
45311
+ const quoteOffset = quoteStart - start;
45312
+ let tailStart = 0;
45313
+ for (let i = 0; i < tail.length; i++) {
45314
+ if (tail[i] === ',') {
45315
+ directives.push({
45316
+ value: tail.substring(tailStart, i),
45317
+ fromMalformedQuote: tailStart > quoteOffset
45318
+ });
45319
+ tailStart = i + 1;
45320
+ }
45321
+ }
45322
+
45323
+ directives.push({
45324
+ value: tail.substring(tailStart),
45325
+ fromMalformedQuote: tailStart > quoteOffset
45326
+ });
45327
+ return directives
45328
+ }
45329
+
45330
+ function markInvalidCacheControlDirective (directives, key) {
45331
+ let invalidDirectives = directives[kInvalidCacheControlDirectives];
45332
+
45333
+ if (invalidDirectives === undefined) {
45334
+ invalidDirectives = new Set();
45335
+ Object.defineProperty(directives, kInvalidCacheControlDirectives, {
45336
+ value: invalidDirectives
45337
+ });
45338
+ }
45339
+
45340
+ invalidDirectives.add(key);
45341
+ }
45342
+
45343
+ function hasInvalidCacheControlDirective (directives, key) {
45344
+ return directives[kInvalidCacheControlDirectives]?.has(key) === true
45345
+ }
45346
+
45347
+ function getMalformedRestrictiveDirectiveName (key) {
45348
+ for (const directiveName of RESTRICTIVE_DIRECTIVE_NAMES) {
45349
+ if (
45350
+ key.startsWith(directiveName) &&
45351
+ key.length > directiveName.length &&
45352
+ !isValidHTTPToken(key[directiveName.length])
45353
+ ) {
45354
+ return directiveName
45355
+ }
45356
+ }
45357
+
45358
+ let tokenOnlyKey = '';
45359
+ let hasInvalidTokenChar = false;
45360
+ for (let i = 0; i < key.length; i++) {
45361
+ if (isValidHTTPToken(key[i])) {
45362
+ tokenOnlyKey += key[i];
45363
+ } else {
45364
+ hasInvalidTokenChar = true;
45365
+ }
45366
+ }
45367
+
45368
+ if (hasInvalidTokenChar && arrayIncludes(RESTRICTIVE_DIRECTIVE_NAMES, tokenOnlyKey)) {
45369
+ return tokenOnlyKey
45370
+ }
45371
+ }
45372
+
45193
45373
  /**
45194
45374
  * @param {import('../../types/dispatcher.d.ts').default.DispatchOptions} opts
45195
45375
  */
@@ -45212,6 +45392,20 @@ function requireCache$2 () {
45212
45392
  }
45213
45393
  }
45214
45394
 
45395
+ function appendHeader (headers, key, val) {
45396
+ const headerName = key.toLowerCase();
45397
+ const current = headers[headerName];
45398
+ const values = Array.isArray(val) ? val : [val];
45399
+
45400
+ if (current === undefined) {
45401
+ headers[headerName] = Array.isArray(val) ? val.slice() : val;
45402
+ } else if (Array.isArray(current)) {
45403
+ current.push(...values);
45404
+ } else {
45405
+ headers[headerName] = [current, ...values];
45406
+ }
45407
+ }
45408
+
45215
45409
  /**
45216
45410
  * @param {Record<string, string[] | string>}
45217
45411
  * @returns {Record<string, string[] | string>}
@@ -45232,11 +45426,11 @@ function requireCache$2 () {
45232
45426
  if (typeof key !== 'string' || typeof val !== 'string') {
45233
45427
  throw new Error('opts.headers is not a valid header map')
45234
45428
  }
45235
- headers[key.toLowerCase()] = val;
45429
+ appendHeader(headers, key, val);
45236
45430
  }
45237
45431
  } else {
45238
45432
  for (const key of Object.keys(opts.headers)) {
45239
- headers[key.toLowerCase()] = opts.headers[key];
45433
+ appendHeader(headers, key, opts.headers[key]);
45240
45434
  }
45241
45435
  }
45242
45436
  } else {
@@ -45308,29 +45502,37 @@ function requireCache$2 () {
45308
45502
  * @type {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives}
45309
45503
  */
45310
45504
  const output = {};
45505
+ const invalidNumericDirectives = new Set();
45506
+ const invalidNoArgumentDirectives = new Set();
45311
45507
 
45312
- let directives;
45313
- if (Array.isArray(header)) {
45314
- directives = [];
45315
-
45316
- for (const directive of header) {
45317
- directives.push(...directive.split(','));
45318
- }
45319
- } else {
45320
- directives = header.split(',');
45321
- }
45508
+ const directives = splitCacheControlHeaderValue(Array.isArray(header) ? header.join(',') : header);
45322
45509
 
45323
45510
  for (let i = 0; i < directives.length; i++) {
45324
- const directive = directives[i].toLowerCase();
45511
+ const directiveRecord = directives[i];
45512
+ const directive = directiveRecord.value.toLowerCase();
45513
+ const fromMalformedQuote = directiveRecord.fromMalformedQuote;
45325
45514
  const keyValueDelimiter = directive.indexOf('=');
45326
45515
 
45327
45516
  let key;
45328
45517
  let value;
45518
+ let keyHasTrailingWhitespace = false;
45519
+ let valueHasLeadingWhitespace = false;
45329
45520
  if (keyValueDelimiter !== -1) {
45330
- key = directive.substring(0, keyValueDelimiter).trimStart();
45331
- value = directive.substring(keyValueDelimiter + 1);
45521
+ const rawKey = directive.substring(0, keyValueDelimiter);
45522
+ const rawValue = directive.substring(keyValueDelimiter + 1);
45523
+
45524
+ keyHasTrailingWhitespace = trimOWSEnd(rawKey) !== rawKey;
45525
+ valueHasLeadingWhitespace = trimOWSStart(rawValue) !== rawValue;
45526
+ key = trimOWS(rawKey);
45527
+ value = trimOWSStart(rawValue);
45332
45528
  } else {
45333
- key = directive.trim();
45529
+ key = trimOWS(directive);
45530
+ }
45531
+
45532
+ const malformedRestrictiveDirectiveName = getMalformedRestrictiveDirectiveName(key);
45533
+ if (malformedRestrictiveDirectiveName !== undefined) {
45534
+ output[malformedRestrictiveDirectiveName] = true;
45535
+ continue
45334
45536
  }
45335
45537
 
45336
45538
  switch (key) {
@@ -45340,7 +45542,14 @@ function requireCache$2 () {
45340
45542
  case 's-maxage':
45341
45543
  case 'stale-while-revalidate':
45342
45544
  case 'stale-if-error': {
45343
- if (value === undefined || value[0] === ' ') {
45545
+ if (fromMalformedQuote || invalidNumericDirectives.has(key)) {
45546
+ continue
45547
+ }
45548
+
45549
+ if (value === undefined || keyHasTrailingWhitespace || valueHasLeadingWhitespace) {
45550
+ delete output[key];
45551
+ invalidNumericDirectives.add(key);
45552
+ markInvalidCacheControlDirective(output, key);
45344
45553
  continue
45345
45554
  }
45346
45555
 
@@ -45352,22 +45561,37 @@ function requireCache$2 () {
45352
45561
  value = value.substring(1, value.length - 1);
45353
45562
  }
45354
45563
 
45355
- const parsedValue = parseInt(value, 10);
45356
- // eslint-disable-next-line no-self-compare
45357
- if (parsedValue !== parsedValue) {
45564
+ if (!/^[0-9]+$/.test(value)) {
45565
+ delete output[key];
45566
+ invalidNumericDirectives.add(key);
45567
+ markInvalidCacheControlDirective(output, key);
45358
45568
  continue
45359
45569
  }
45360
45570
 
45361
- if (key === 'max-age' && key in output && output[key] >= parsedValue) {
45362
- continue
45363
- }
45571
+ const parsedValue = Math.min(parseInt(value, 10), MAX_DELTA_SECONDS);
45364
45572
 
45365
- output[key] = parsedValue;
45573
+ if (key === 'min-fresh') {
45574
+ if (!(key in output) || output[key] < parsedValue) {
45575
+ output[key] = parsedValue;
45576
+ }
45577
+ } else if (!(key in output) || output[key] > parsedValue) {
45578
+ output[key] = parsedValue;
45579
+ }
45366
45580
 
45367
45581
  break
45368
45582
  }
45369
45583
  case 'private':
45370
45584
  case 'no-cache': {
45585
+ if (fromMalformedQuote) {
45586
+ output[key] = true;
45587
+ break
45588
+ }
45589
+
45590
+ if (value !== undefined && value.length === 0) {
45591
+ output[key] = true;
45592
+ break
45593
+ }
45594
+
45371
45595
  if (value) {
45372
45596
  // The private and no-cache directives can be unqualified (aka just
45373
45597
  // `private` or `no-cache`) or qualified (w/ a value). When they're
@@ -45375,45 +45599,64 @@ function requireCache$2 () {
45375
45599
  // `no-cache="header1"`, or `no-cache="header1, header2"`
45376
45600
  // If we're given multiple headers, the comma messes us up since
45377
45601
  // we split the full header by commas. So, let's loop through the
45378
- // remaining parts in front of us until we find one that ends in a
45379
- // quote. We can then just splice all of the parts in between the
45380
- // starting quote and the ending quote out of the directives array
45381
- // and continue parsing like normal.
45602
+ // remaining parts in front of us until we find one that contains a
45603
+ // closing quote. We can then skip the consumed quoted-list fragments and
45604
+ // continue parsing like normal.
45382
45605
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-no-cache-2
45383
45606
  if (value[0] === '"') {
45384
45607
  // Something like `no-cache="some-header"` OR `no-cache="some-header, another-header"`.
45608
+ value = trimOWSEnd(value);
45385
45609
 
45386
- // Add the first header on and cut off the leading quote
45387
- const headers = [value.substring(1)];
45610
+ let fieldList = '';
45611
+ let lastQuotedPart = i;
45612
+ let foundEndingQuote = false;
45613
+ const closingQuote = findUnescapedQuote(value, 1);
45388
45614
 
45389
- let foundEndingQuote = value[value.length - 1] === '"';
45390
- if (!foundEndingQuote) {
45615
+ if (closingQuote !== -1) {
45616
+ fieldList = value.substring(1, closingQuote);
45617
+ foundEndingQuote = true;
45618
+ } else {
45391
45619
  // Something like `no-cache="some-header, another-header"`
45392
45620
  // This can still be something invalid, e.g. `no-cache="some-header, ...`
45621
+ const fieldListParts = [value.substring(1)];
45622
+
45393
45623
  for (let j = i + 1; j < directives.length; j++) {
45394
- const nextPart = directives[j];
45395
- const nextPartLength = nextPart.length;
45624
+ const nextPart = trimOWS(directives[j].value);
45625
+ const closingQuote = findUnescapedQuote(nextPart, 0);
45396
45626
 
45397
- headers.push(nextPart.trim());
45627
+ lastQuotedPart = j;
45398
45628
 
45399
- if (nextPartLength !== 0 && nextPart[nextPartLength - 1] === '"') {
45629
+ if (closingQuote !== -1) {
45630
+ fieldListParts.push(nextPart.substring(0, closingQuote));
45400
45631
  foundEndingQuote = true;
45401
45632
  break
45402
45633
  }
45634
+
45635
+ fieldListParts.push(nextPart);
45403
45636
  }
45637
+
45638
+ fieldList = fieldListParts.join(',');
45404
45639
  }
45405
45640
 
45406
- if (foundEndingQuote) {
45407
- let lastHeader = headers[headers.length - 1];
45408
- if (lastHeader[lastHeader.length - 1] === '"') {
45409
- lastHeader = lastHeader.substring(0, lastHeader.length - 1);
45410
- headers[headers.length - 1] = lastHeader;
45411
- }
45641
+ if (!foundEndingQuote) {
45642
+ output[key] = true;
45643
+ break
45644
+ }
45412
45645
 
45413
- for (let j = 0; j < headers.length; j++) {
45414
- headers[j] = headers[j].trim();
45646
+ i = lastQuotedPart;
45647
+
45648
+ const headers = fieldList.split(',');
45649
+ let validFieldNames = true;
45650
+ for (let j = 0; j < headers.length; j++) {
45651
+ headers[j] = trimOWS(headers[j]);
45652
+ if (!isValidHTTPToken(headers[j])) {
45653
+ validFieldNames = false;
45415
45654
  }
45655
+ }
45416
45656
 
45657
+ if (!validFieldNames) {
45658
+ output[key] = true;
45659
+ } else if (output[key] !== true) {
45417
45660
  if (key in output) {
45418
45661
  output[key] = output[key].concat(headers);
45419
45662
  } else {
@@ -45421,13 +45664,17 @@ function requireCache$2 () {
45421
45664
  }
45422
45665
  }
45423
45666
  } else {
45424
- // Something like `no-cache="some-header"`
45425
- const fieldName = value.trim();
45667
+ // Something like `no-cache=some-header`
45668
+ const fieldName = trimOWS(value);
45426
45669
 
45427
- if (key in output) {
45428
- output[key] = output[key].concat(fieldName);
45429
- } else {
45430
- output[key] = [fieldName];
45670
+ if (!isValidHTTPToken(fieldName)) {
45671
+ output[key] = true;
45672
+ } else if (output[key] !== true) {
45673
+ if (key in output) {
45674
+ output[key] = output[key].concat(fieldName);
45675
+ } else {
45676
+ output[key] = [fieldName];
45677
+ }
45431
45678
  }
45432
45679
  }
45433
45680
 
@@ -45436,19 +45683,27 @@ function requireCache$2 () {
45436
45683
  }
45437
45684
  // eslint-disable-next-line no-fallthrough
45438
45685
  case 'public':
45439
- case 'no-store':
45440
45686
  case 'must-revalidate':
45441
45687
  case 'proxy-revalidate':
45442
45688
  case 'immutable':
45443
45689
  case 'no-transform':
45444
45690
  case 'must-understand':
45445
45691
  case 'only-if-cached':
45446
- if (value) {
45692
+ if (fromMalformedQuote || invalidNoArgumentDirectives.has(key)) {
45693
+ continue
45694
+ }
45695
+
45696
+ if (value !== undefined) {
45447
45697
  // These are qualified (something like `public=...`) when they aren't
45448
- // allowed to be, skip
45698
+ // allowed to be, skip all instances of the malformed directive.
45699
+ delete output[key];
45700
+ invalidNoArgumentDirectives.add(key);
45449
45701
  continue
45450
45702
  }
45451
45703
 
45704
+ output[key] = true;
45705
+ break
45706
+ case 'no-store':
45452
45707
  output[key] = true;
45453
45708
  break
45454
45709
  default:
@@ -45460,31 +45715,79 @@ function requireCache$2 () {
45460
45715
  return output
45461
45716
  }
45462
45717
 
45718
+ /**
45719
+ * @param {string | string[]} varyHeader Vary header from the server
45720
+ * @returns {string[]}
45721
+ */
45722
+ function splitVaryHeader (varyHeader) {
45723
+ const values = Array.isArray(varyHeader) ? varyHeader : [varyHeader];
45724
+ const output = [];
45725
+
45726
+ for (let i = 0; i < values.length; i++) {
45727
+ const parts = values[i].split(',');
45728
+ for (let j = 0; j < parts.length; j++) {
45729
+ output.push(parts[j]);
45730
+ }
45731
+ }
45732
+
45733
+ return output
45734
+ }
45735
+
45736
+ /**
45737
+ * @param {string | string[]} varyHeader Vary header from the server
45738
+ * @returns {boolean}
45739
+ */
45740
+ function hasVaryStar (varyHeader) {
45741
+ const values = splitVaryHeader(varyHeader);
45742
+ for (let i = 0; i < values.length; i++) {
45743
+ if (trimOWS(values[i]).indexOf('*') !== -1) {
45744
+ return true
45745
+ }
45746
+ }
45747
+
45748
+ return false
45749
+ }
45750
+
45463
45751
  /**
45464
45752
  * @param {string | string[]} varyHeader Vary header from the server
45465
45753
  * @param {Record<string, string | string[]>} headers Request headers
45466
- * @returns {Record<string, string | string[]>}
45754
+ * @returns {Record<string, string | string[] | null> | undefined}
45467
45755
  */
45468
45756
  function parseVaryHeader (varyHeader, headers) {
45469
- if (typeof varyHeader === 'string' && varyHeader.includes('*')) {
45757
+ if (hasVaryStar(varyHeader)) {
45470
45758
  return headers
45471
45759
  }
45472
45760
 
45473
45761
  const output = /** @type {Record<string, string | string[] | null>} */ ({});
45474
45762
 
45475
- const varyingHeaders = typeof varyHeader === 'string'
45476
- ? varyHeader.split(',')
45477
- : varyHeader;
45763
+ const varyingHeaders = splitVaryHeader(varyHeader);
45478
45764
 
45479
45765
  for (const header of varyingHeaders) {
45480
- const trimmedHeader = header.trim().toLowerCase();
45766
+ const trimmedHeader = trimOWS(header).toLowerCase();
45767
+
45768
+ if (trimmedHeader.length === 0) {
45769
+ continue
45770
+ }
45481
45771
 
45482
- output[trimmedHeader] = headers[trimmedHeader] ?? null;
45772
+ if (!isValidHTTPToken(trimmedHeader)) {
45773
+ return undefined
45774
+ }
45775
+
45776
+ const headerValue = headers[trimmedHeader];
45777
+ output[trimmedHeader] = Array.isArray(headerValue) ? headerValue.slice() : headerValue ?? null;
45483
45778
  }
45484
45779
 
45485
45780
  return output
45486
45781
  }
45487
45782
 
45783
+ /**
45784
+ * @param {string | string[]} varyHeader Vary header from the server
45785
+ * @returns {boolean}
45786
+ */
45787
+ function isInvalidOrWildcardVaryHeader (varyHeader) {
45788
+ return hasVaryStar(varyHeader) || parseVaryHeader(varyHeader, {}) === undefined
45789
+ }
45790
+
45488
45791
  /**
45489
45792
  * Note: this deviates from the spec a little. Empty etags ("", W/"") are valid,
45490
45793
  * however, including them in cached resposnes serves little to no purpose.
@@ -45548,7 +45851,7 @@ function requireCache$2 () {
45548
45851
  }
45549
45852
 
45550
45853
  for (const method of methods) {
45551
- if (!safeHTTPMethods.includes(method)) {
45854
+ if (!arrayIncludes(safeHTTPMethods, method)) {
45552
45855
  throw new TypeError(`element of ${name}-array needs to be one of following values: ${safeHTTPMethods.join(', ')}, got ${method}`)
45553
45856
  }
45554
45857
  }
@@ -45588,7 +45891,10 @@ function requireCache$2 () {
45588
45891
  assertCacheKey,
45589
45892
  assertCacheValue,
45590
45893
  parseCacheControlHeader,
45894
+ hasInvalidCacheControlDirective,
45591
45895
  parseVaryHeader,
45896
+ hasVaryStar,
45897
+ isInvalidOrWildcardVaryHeader,
45592
45898
  isEtagUsable,
45593
45899
  assertCacheMethods,
45594
45900
  assertCacheStore,
@@ -45622,6 +45928,26 @@ function requireDate () {
45622
45928
  }
45623
45929
  }
45624
45930
 
45931
+ function makeDate (year, monthIdx, day, hour, minute, second, weekday) {
45932
+ const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second));
45933
+
45934
+ // Date.UTC treats years 0-99 as 1900-1999. Reset the full year so component
45935
+ // checks below validate the HTTP date as written.
45936
+ if (year >= 0 && year <= 99) {
45937
+ result.setUTCFullYear(year);
45938
+ }
45939
+
45940
+ return result.getUTCFullYear() === year &&
45941
+ result.getUTCMonth() === monthIdx &&
45942
+ result.getUTCDate() === day &&
45943
+ result.getUTCHours() === hour &&
45944
+ result.getUTCMinutes() === minute &&
45945
+ result.getUTCSeconds() === second &&
45946
+ result.getUTCDay() === weekday
45947
+ ? result
45948
+ : undefined
45949
+ }
45950
+
45625
45951
  /**
45626
45952
  * @see https://httpwg.org/specs/rfc9110.html#preferred.date.format
45627
45953
  *
@@ -45828,8 +46154,7 @@ function requireDate () {
45828
46154
  second = (code1 - 48) * 10 + (code2 - 48); // Convert ASCII codes to number
45829
46155
  }
45830
46156
 
45831
- const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second));
45832
- return result.getUTCDay() === weekday ? result : undefined
46157
+ return makeDate(year, monthIdx, day, hour, minute, second, weekday)
45833
46158
  }
45834
46159
 
45835
46160
  /**
@@ -46033,8 +46358,7 @@ function requireDate () {
46033
46358
  }
46034
46359
  const year = (yearDigit1 - 48) * 1000 + (yearDigit2 - 48) * 100 + (yearDigit3 - 48) * 10 + (yearDigit4 - 48);
46035
46360
 
46036
- const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second));
46037
- return result.getUTCDay() === weekday ? result : undefined
46361
+ return makeDate(year, monthIdx, day, hour, minute, second, weekday)
46038
46362
  }
46039
46363
 
46040
46364
  /**
@@ -46248,8 +46572,7 @@ function requireDate () {
46248
46572
  second = (code1 - 48) * 10 + (code2 - 48); // Convert ASCII codes to number
46249
46573
  }
46250
46574
 
46251
- const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second));
46252
- return result.getUTCDay() === weekday ? result : undefined
46575
+ return makeDate(year, monthIdx, day, hour, minute, second, weekday)
46253
46576
  }
46254
46577
 
46255
46578
  date = {
@@ -46268,7 +46591,10 @@ function requireCacheHandler () {
46268
46591
  const util = requireUtil$6();
46269
46592
  const {
46270
46593
  parseCacheControlHeader,
46594
+ hasInvalidCacheControlDirective,
46271
46595
  parseVaryHeader,
46596
+ hasVaryStar,
46597
+ isInvalidOrWildcardVaryHeader,
46272
46598
  isEtagUsable
46273
46599
  } = requireCache$2();
46274
46600
  const { parseHttpDate } = requireDate();
@@ -46291,6 +46617,92 @@ function requireCacheHandler () {
46291
46617
 
46292
46618
  const MAX_RESPONSE_AGE = 2147483647000;
46293
46619
 
46620
+ function trimOWS (value) {
46621
+ return value.replace(/^[\t ]+|[\t ]+$/g, '')
46622
+ }
46623
+
46624
+ function arrayIncludes (array, value) {
46625
+ for (let i = 0; i < array.length; i++) {
46626
+ if (array[i] === value) {
46627
+ return true
46628
+ }
46629
+ }
46630
+
46631
+ return false
46632
+ }
46633
+
46634
+ function appendConnectionHeaderTokens (headersToRemove, connectionHeader) {
46635
+ const values = Array.isArray(connectionHeader) ? connectionHeader : [connectionHeader];
46636
+
46637
+ for (let i = 0; i < values.length; i++) {
46638
+ const tokens = values[i].split(',');
46639
+ for (let j = 0; j < tokens.length; j++) {
46640
+ headersToRemove.push(trimOWS(tokens[j]).toLowerCase());
46641
+ }
46642
+ }
46643
+ }
46644
+
46645
+ function getSameOriginPath (cacheKey, location) {
46646
+ if (typeof location !== 'string') {
46647
+ return undefined
46648
+ }
46649
+
46650
+ let originUrl;
46651
+ let requestUrl;
46652
+ let locationUrl;
46653
+ try {
46654
+ originUrl = new URL(cacheKey.origin);
46655
+ requestUrl = new URL(cacheKey.path, originUrl);
46656
+ locationUrl = new URL(location, requestUrl);
46657
+ } catch {
46658
+ return undefined
46659
+ }
46660
+
46661
+ if (locationUrl.origin !== originUrl.origin) {
46662
+ return undefined
46663
+ }
46664
+
46665
+ return locationUrl.pathname + locationUrl.search
46666
+ }
46667
+
46668
+ function deleteCachedUri (store, cacheKey, path) {
46669
+ deleteCachedValue(store, {
46670
+ ...cacheKey,
46671
+ path
46672
+ });
46673
+
46674
+ for (let i = 0; i < util.safeHTTPMethods.length; i++) {
46675
+ const method = util.safeHTTPMethods[i];
46676
+ if (method !== cacheKey.method) {
46677
+ deleteCachedValue(store, {
46678
+ ...cacheKey,
46679
+ method,
46680
+ path
46681
+ });
46682
+ }
46683
+ }
46684
+ }
46685
+
46686
+ function deleteLocationTargets (store, cacheKey, headerValue) {
46687
+ if (headerValue === undefined) {
46688
+ return
46689
+ }
46690
+
46691
+ const values = Array.isArray(headerValue) ? headerValue : [headerValue];
46692
+ for (let i = 0; i < values.length; i++) {
46693
+ const path = getSameOriginPath(cacheKey, values[i]);
46694
+ if (path !== undefined) {
46695
+ deleteCachedUri(store, cacheKey, path);
46696
+ }
46697
+ }
46698
+ }
46699
+
46700
+ function invalidateUnsafeRequest (store, cacheKey, resHeaders) {
46701
+ deleteCachedUri(store, cacheKey, cacheKey.path);
46702
+ deleteLocationTargets(store, cacheKey, resHeaders.location);
46703
+ deleteLocationTargets(store, cacheKey, resHeaders['content-location']);
46704
+ }
46705
+
46294
46706
  /**
46295
46707
  * @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandler} DispatchHandler
46296
46708
  *
@@ -46372,28 +46784,28 @@ function requireCacheHandler () {
46372
46784
  const handler = this;
46373
46785
 
46374
46786
  if (
46375
- !util.safeHTTPMethods.includes(this.#cacheKey.method) &&
46787
+ !arrayIncludes(util.safeHTTPMethods, this.#cacheKey.method) &&
46376
46788
  statusCode >= 200 &&
46377
46789
  statusCode <= 399
46378
46790
  ) {
46379
46791
  // Successful response to an unsafe method, delete it from cache
46380
46792
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-response
46381
- try {
46382
- this.#store.delete(this.#cacheKey)?.catch?.(noop);
46383
- } catch {
46384
- // Fail silently
46385
- }
46793
+ invalidateUnsafeRequest(this.#store, this.#cacheKey, resHeaders);
46386
46794
  return downstreamOnHeaders()
46387
46795
  }
46388
46796
 
46389
46797
  const cacheControlHeader = resHeaders['cache-control'];
46390
- const heuristicallyCacheable = resHeaders['last-modified'] && HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode);
46798
+ const heuristicallyCacheable = resHeaders['last-modified'] && arrayIncludes(HEURISTICALLY_CACHEABLE_STATUS_CODES, statusCode);
46391
46799
  if (
46392
46800
  !cacheControlHeader &&
46393
46801
  !resHeaders['expires'] &&
46394
46802
  !heuristicallyCacheable &&
46395
46803
  !this.#cacheByDefault
46396
46804
  ) {
46805
+ if (statusCode === 304 && resHeaders.vary && isInvalidOrWildcardVaryHeader(resHeaders.vary)) {
46806
+ deleteCachedValue(this.#store, this.#cacheKey);
46807
+ }
46808
+
46397
46809
  // Don't have anything to tell us this response is cachable and we're not
46398
46810
  // caching by default
46399
46811
  return downstreamOnHeaders()
@@ -46401,31 +46813,46 @@ function requireCacheHandler () {
46401
46813
 
46402
46814
  const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {};
46403
46815
  if (!canCacheResponse(this.#cacheType, statusCode, resHeaders, cacheControlDirectives, this.#cacheKey.headers)) {
46816
+ if (statusCode === 304 && (cacheControlHeader || revalidationResponseDisallowsCachedReuse(this.#cacheType, resHeaders, cacheControlDirectives))) {
46817
+ deleteCachedValue(this.#store, this.#cacheKey);
46818
+ }
46819
+
46404
46820
  return downstreamOnHeaders()
46405
46821
  }
46406
46822
 
46407
46823
  const now = Date.now();
46408
- const resAge = resHeaders.age ? getAge(resHeaders.age) : undefined;
46409
- if (resAge && resAge >= MAX_RESPONSE_AGE) {
46824
+ const resAge = Object.hasOwn(resHeaders, 'age') ? getAge(resHeaders.age) : undefined;
46825
+ if (resAge !== undefined && resAge >= MAX_RESPONSE_AGE) {
46410
46826
  // Response considered stale
46827
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey);
46411
46828
  return downstreamOnHeaders()
46412
46829
  }
46413
46830
 
46414
- const resDate = typeof resHeaders.date === 'string'
46415
- ? parseHttpDate(resHeaders.date)
46416
- : undefined;
46831
+ const resDate = Object.hasOwn(resHeaders, 'date') ? getDate(resHeaders.date) : undefined;
46832
+ if (resDate === null) {
46833
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey);
46834
+ return downstreamOnHeaders()
46835
+ }
46836
+
46837
+ const apparentAge = resDate ? Math.max(0, now - resDate.getTime()) : 0;
46838
+ const currentAge = Math.max(apparentAge, resAge ?? 0);
46417
46839
 
46418
46840
  const staleAt =
46419
46841
  determineStaleAt(this.#cacheType, now, resAge, resHeaders, resDate, cacheControlDirectives) ??
46420
46842
  this.#cacheByDefault;
46421
- if (staleAt === undefined || (resAge && resAge > staleAt)) {
46843
+ if (staleAt === undefined || currentAge >= staleAt) {
46844
+ if (cacheControlHeader || staleAt !== undefined) {
46845
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey);
46846
+ }
46847
+
46422
46848
  return downstreamOnHeaders()
46423
46849
  }
46424
46850
 
46425
- const baseTime = resDate ? resDate.getTime() : now;
46851
+ const baseTime = now - currentAge;
46426
46852
  const absoluteStaleAt = staleAt + baseTime;
46427
46853
  if (now >= absoluteStaleAt) {
46428
46854
  // Response is already stale
46855
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey);
46429
46856
  return downstreamOnHeaders()
46430
46857
  }
46431
46858
 
@@ -46438,7 +46865,8 @@ function requireCacheHandler () {
46438
46865
  }
46439
46866
  }
46440
46867
 
46441
- const deleteAt = determineDeleteAt(baseTime, cacheControlDirectives, absoluteStaleAt);
46868
+ const cachedAt = baseTime;
46869
+ const deleteAt = determineDeleteAt(baseTime, now, cacheControlDirectives, absoluteStaleAt);
46442
46870
  const strippedHeaders = stripNecessaryHeaders(resHeaders, cacheControlDirectives);
46443
46871
 
46444
46872
  /**
@@ -46450,7 +46878,7 @@ function requireCacheHandler () {
46450
46878
  headers: strippedHeaders,
46451
46879
  vary: varyDirectives,
46452
46880
  cacheControlDirectives,
46453
- cachedAt: resAge ? now - resAge : now,
46881
+ cachedAt,
46454
46882
  staleAt: absoluteStaleAt,
46455
46883
  deleteAt
46456
46884
  };
@@ -46468,6 +46896,7 @@ function requireCacheHandler () {
46468
46896
  value.statusCode = cachedValue.statusCode;
46469
46897
  value.statusMessage = cachedValue.statusMessage;
46470
46898
  value.etag = cachedValue.etag;
46899
+ value.vary = varyDirectives ?? cachedValue.vary;
46471
46900
  value.headers = { ...cachedValue.headers, ...strippedHeaders };
46472
46901
 
46473
46902
  downstreamOnHeaders();
@@ -46598,6 +47027,36 @@ function requireCacheHandler () {
46598
47027
  }
46599
47028
  }
46600
47029
 
47030
+ /**
47031
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheStore} store
47032
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
47033
+ */
47034
+ function deleteCachedValue (store, cacheKey) {
47035
+ try {
47036
+ store.delete(cacheKey)?.catch?.(noop);
47037
+ } catch {
47038
+ // Fail silently
47039
+ }
47040
+ }
47041
+
47042
+ function deleteCachedValueIfNotModified (statusCode, store, cacheKey) {
47043
+ if (statusCode === 304) {
47044
+ deleteCachedValue(store, cacheKey);
47045
+ }
47046
+ }
47047
+
47048
+ /**
47049
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
47050
+ * @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
47051
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
47052
+ * @returns {boolean}
47053
+ */
47054
+ function revalidationResponseDisallowsCachedReuse (cacheType, resHeaders, cacheControlDirectives) {
47055
+ return cacheControlDirectives['no-store'] === true ||
47056
+ (cacheType === 'shared' && cacheControlDirectives.private === true) ||
47057
+ (resHeaders.vary ? isInvalidOrWildcardVaryHeader(resHeaders.vary) : false)
47058
+ }
47059
+
46601
47060
  /**
46602
47061
  * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
46603
47062
  *
@@ -46609,12 +47068,12 @@ function requireCacheHandler () {
46609
47068
  */
46610
47069
  function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives, reqHeaders) {
46611
47070
  // Status code must be final and understood.
46612
- if (statusCode < 200 || NOT_UNDERSTOOD_STATUS_CODES.includes(statusCode)) {
47071
+ if (statusCode < 200 || arrayIncludes(NOT_UNDERSTOOD_STATUS_CODES, statusCode)) {
46613
47072
  return false
46614
47073
  }
46615
47074
  // Responses with neither status codes that are heuristically cacheable, nor "explicit enough" caching
46616
47075
  // directives, are not cacheable. "Explicit enough": see https://www.rfc-editor.org/rfc/rfc9111.html#section-3
46617
- if (!HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode) && !resHeaders['expires'] &&
47076
+ if (!arrayIncludes(HEURISTICALLY_CACHEABLE_STATUS_CODES, statusCode) && !resHeaders['expires'] &&
46618
47077
  !cacheControlDirectives.public &&
46619
47078
  cacheControlDirectives['max-age'] === undefined &&
46620
47079
  // RFC 9111: a private response directive, if the cache is not shared
@@ -46633,12 +47092,12 @@ function requireCacheHandler () {
46633
47092
  }
46634
47093
 
46635
47094
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1-5
46636
- if (resHeaders.vary?.includes('*')) {
47095
+ if (resHeaders.vary && hasVaryStar(resHeaders.vary)) {
46637
47096
  return false
46638
47097
  }
46639
47098
 
46640
47099
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
46641
- if (reqHeaders?.authorization) {
47100
+ if (reqHeaders != null && Object.hasOwn(reqHeaders, 'authorization')) {
46642
47101
  if (
46643
47102
  !cacheControlDirectives.public &&
46644
47103
  !cacheControlDirectives['s-maxage'] &&
@@ -46653,14 +47112,14 @@ function requireCacheHandler () {
46653
47112
 
46654
47113
  if (
46655
47114
  Array.isArray(cacheControlDirectives['no-cache']) &&
46656
- cacheControlDirectives['no-cache'].includes('authorization')
47115
+ arrayIncludes(cacheControlDirectives['no-cache'], 'authorization')
46657
47116
  ) {
46658
47117
  return false
46659
47118
  }
46660
47119
 
46661
47120
  if (
46662
47121
  Array.isArray(cacheControlDirectives['private']) &&
46663
- cacheControlDirectives['private'].includes('authorization')
47122
+ arrayIncludes(cacheControlDirectives['private'], 'authorization')
46664
47123
  ) {
46665
47124
  return false
46666
47125
  }
@@ -46669,14 +47128,51 @@ function requireCacheHandler () {
46669
47128
  return true
46670
47129
  }
46671
47130
 
47131
+ /**
47132
+ * @param {string | string[]} dateHeader
47133
+ * @returns {Date | null | undefined}
47134
+ */
47135
+ function getDate (dateHeader) {
47136
+ let dateValue = dateHeader;
47137
+ if (Array.isArray(dateValue)) {
47138
+ if (dateValue.length !== 1) {
47139
+ return null
47140
+ }
47141
+
47142
+ dateValue = dateValue[0];
47143
+ }
47144
+
47145
+ if (typeof dateValue !== 'string') {
47146
+ return null
47147
+ }
47148
+
47149
+ return parseHttpDate(dateValue)
47150
+ }
47151
+
46672
47152
  /**
46673
47153
  * @param {string | string[]} ageHeader
46674
47154
  * @returns {number | undefined}
46675
47155
  */
46676
47156
  function getAge (ageHeader) {
46677
- const age = parseInt(Array.isArray(ageHeader) ? ageHeader[0] : ageHeader);
47157
+ let ageValue = ageHeader;
47158
+ if (Array.isArray(ageValue)) {
47159
+ if (ageValue.length !== 1) {
47160
+ return MAX_RESPONSE_AGE
47161
+ }
47162
+
47163
+ ageValue = ageValue[0];
47164
+ }
47165
+
47166
+ if (typeof ageValue !== 'string' || !/^[\t ]*[0-9]+[\t ]*$/.test(ageValue)) {
47167
+ return MAX_RESPONSE_AGE
47168
+ }
47169
+
47170
+ const age = BigInt(ageValue.replace(/^[\t ]+|[\t ]+$/g, ''));
47171
+ if (age >= BigInt(MAX_RESPONSE_AGE / 1000)) {
47172
+ return MAX_RESPONSE_AGE
47173
+ }
46678
47174
 
46679
- return isNaN(age) ? undefined : age * 1000
47175
+ return Number(age) * 1000
46680
47176
  }
46681
47177
 
46682
47178
  /**
@@ -46694,43 +47190,60 @@ function requireCacheHandler () {
46694
47190
  // Prioritize s-maxage since we're a shared cache
46695
47191
  // s-maxage > max-age > Expire
46696
47192
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10-3
47193
+ if (hasInvalidCacheControlDirective(cacheControlDirectives, 's-maxage')) {
47194
+ return 0
47195
+ }
47196
+
46697
47197
  const sMaxAge = cacheControlDirectives['s-maxage'];
46698
47198
  if (sMaxAge !== undefined) {
46699
- return sMaxAge > 0 ? sMaxAge * 1000 : undefined
47199
+ return sMaxAge * 1000
46700
47200
  }
46701
47201
  }
46702
47202
 
47203
+ if (hasInvalidCacheControlDirective(cacheControlDirectives, 'max-age')) {
47204
+ return 0
47205
+ }
47206
+
46703
47207
  const maxAge = cacheControlDirectives['max-age'];
46704
47208
  if (maxAge !== undefined) {
46705
- return maxAge > 0 ? maxAge * 1000 : undefined
47209
+ return maxAge * 1000
46706
47210
  }
46707
47211
 
46708
- if (typeof resHeaders.expires === 'string') {
47212
+ if (Object.hasOwn(resHeaders, 'expires')) {
46709
47213
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
47214
+ if (typeof resHeaders.expires !== 'string') {
47215
+ return 0
47216
+ }
47217
+
46710
47218
  const expiresDate = parseHttpDate(resHeaders.expires);
46711
- if (expiresDate) {
46712
- if (now >= expiresDate.getTime()) {
46713
- return undefined
46714
- }
47219
+ if (!expiresDate) {
47220
+ return 0
47221
+ }
46715
47222
 
46716
- if (responseDate) {
46717
- if (responseDate >= expiresDate) {
46718
- return undefined
46719
- }
47223
+ if (now >= expiresDate.getTime()) {
47224
+ return 0
47225
+ }
46720
47226
 
46721
- if (age !== undefined && age > (expiresDate - responseDate)) {
46722
- return undefined
46723
- }
47227
+ if (responseDate) {
47228
+ if (responseDate >= expiresDate) {
47229
+ return 0
47230
+ }
47231
+
47232
+ const freshnessLifetime = expiresDate.getTime() - responseDate.getTime();
47233
+ if (age !== undefined && age >= freshnessLifetime) {
47234
+ return 0
46724
47235
  }
46725
47236
 
46726
- return expiresDate.getTime() - now
47237
+ return freshnessLifetime
46727
47238
  }
47239
+
47240
+ return expiresDate.getTime() - now
46728
47241
  }
46729
47242
 
46730
47243
  if (typeof resHeaders['last-modified'] === 'string') {
46731
47244
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-heuristic-fresh
46732
- const lastModified = new Date(resHeaders['last-modified']);
46733
- if (isValidDate(lastModified)) {
47245
+ const lastModified = parseHttpDate(resHeaders['last-modified']);
47246
+ if (lastModified) {
46734
47247
  if (lastModified.getTime() >= now) {
46735
47248
  return undefined
46736
47249
  }
@@ -46743,18 +47256,19 @@ function requireCacheHandler () {
46743
47256
 
46744
47257
  if (cacheControlDirectives.immutable) {
46745
47258
  // https://www.rfc-editor.org/rfc/rfc8246.html#section-2.2
46746
- return 31536000
47259
+ return 31536000000
46747
47260
  }
46748
47261
 
46749
47262
  return undefined
46750
47263
  }
46751
47264
 
46752
47265
  /**
46753
- * @param {number} now
47266
+ * @param {number} baseTime
47267
+ * @param {number} cachedAt
46754
47268
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
46755
47269
  * @param {number} staleAt
46756
47270
  */
46757
- function determineDeleteAt (now, cacheControlDirectives, staleAt) {
47271
+ function determineDeleteAt (baseTime, cachedAt, cacheControlDirectives, staleAt) {
46758
47272
  let staleWhileRevalidate = -Infinity;
46759
47273
  let staleIfError = -Infinity;
46760
47274
  let immutable = -Infinity;
@@ -46768,15 +47282,21 @@ function requireCacheHandler () {
46768
47282
  }
46769
47283
 
46770
47284
  if (cacheControlDirectives.immutable && staleWhileRevalidate === -Infinity && staleIfError === -Infinity) {
46771
- immutable = now + 31536000000;
47285
+ immutable = cachedAt + 31536000000;
46772
47286
  }
46773
47287
 
46774
47288
  // When no stale directives or immutable flag, add a revalidation buffer
46775
47289
  // equal to the freshness lifetime so the entry survives past staleAt long
46776
47290
  // enough to be revalidated instead of silently disappearing.
47291
+ //
47292
+ // Response Date headers only have second precision, so baseTime can trail the
47293
+ // actual cache insertion time by up to ~1s. Pad the buffer by that bounded
47294
+ // skew so short-lived entries do not disappear exactly when they should be
47295
+ // revalidated.
46777
47296
  if (staleWhileRevalidate === -Infinity && staleIfError === -Infinity && immutable === -Infinity) {
46778
- const freshnessLifetime = staleAt - now;
46779
- return staleAt + freshnessLifetime
47297
+ const freshnessLifetime = staleAt - baseTime;
47298
+ const datePrecisionPadding = Math.min(Math.max(cachedAt - baseTime, 0), 1000);
47299
+ return staleAt + freshnessLifetime + datePrecisionPadding
46780
47300
  }
46781
47301
 
46782
47302
  return Math.max(staleAt, staleWhileRevalidate, staleIfError, immutable)
@@ -46803,14 +47323,7 @@ function requireCacheHandler () {
46803
47323
  ];
46804
47324
 
46805
47325
  if (resHeaders['connection']) {
46806
- if (Array.isArray(resHeaders['connection'])) {
46807
- // connection: a
46808
- // connection: b
46809
- headersToRemove.push(...resHeaders['connection'].map(header => header.trim()));
46810
- } else {
46811
- // connection: a, b
46812
- headersToRemove.push(...resHeaders['connection'].split(',').map(header => header.trim()));
46813
- }
47326
+ appendConnectionHeaderTokens(headersToRemove, resHeaders['connection']);
46814
47327
  }
46815
47328
 
46816
47329
  if (Array.isArray(cacheControlDirectives['no-cache'])) {
@@ -46823,7 +47336,7 @@ function requireCacheHandler () {
46823
47336
 
46824
47337
  let strippedHeaders;
46825
47338
  for (const headerName of headersToRemove) {
46826
- if (resHeaders[headerName]) {
47339
+ if (Object.hasOwn(resHeaders, headerName)) {
46827
47340
  strippedHeaders ??= { ...resHeaders };
46828
47341
  delete strippedHeaders[headerName];
46829
47342
  }
@@ -46832,14 +47345,6 @@ function requireCacheHandler () {
46832
47345
  return strippedHeaders ?? resHeaders
46833
47346
  }
46834
47347
 
46835
- /**
46836
- * @param {Date} date
46837
- * @returns {boolean}
46838
- */
46839
- function isValidDate (date) {
46840
- return date instanceof Date && Number.isFinite(date.valueOf())
46841
- }
46842
-
46843
47348
  cacheHandler = CacheHandler;
46844
47349
  return cacheHandler;
46845
47350
  }
@@ -47069,17 +47574,62 @@ function requireMemoryCacheStore () {
47069
47574
  }
47070
47575
 
47071
47576
  function findEntry (key, entries, now) {
47072
- return entries.find((entry) => (
47073
- entry.deleteAt > now &&
47074
- entry.method === key.method &&
47075
- (entry.vary == null || Object.keys(entry.vary).every(headerName => {
47076
- if (entry.vary[headerName] === null) {
47077
- return key.headers[headerName] === undefined
47577
+ for (let i = 0; i < entries.length; i++) {
47578
+ const entry = entries[i];
47579
+ if (
47580
+ entry.deleteAt > now &&
47581
+ entry.method === key.method &&
47582
+ varyMatches(key, entry)
47583
+ ) {
47584
+ return entry
47585
+ }
47586
+ }
47587
+ }
47588
+
47589
+ function varyMatches (key, entry) {
47590
+ if (entry.vary == null) {
47591
+ return true
47592
+ }
47593
+
47594
+ for (const headerName in entry.vary) {
47595
+ if (Object.hasOwn(entry.vary, headerName) && !headerValueEquals(key.headers?.[headerName], entry.vary[headerName])) {
47596
+ return false
47597
+ }
47598
+ }
47599
+
47600
+ return true
47601
+ }
47602
+
47603
+ /**
47604
+ * @param {string|string[]|null|undefined} lhs
47605
+ * @param {string|string[]|null|undefined} rhs
47606
+ * @returns {boolean}
47607
+ */
47608
+ function headerValueEquals (lhs, rhs) {
47609
+ if (lhs == null && rhs == null) {
47610
+ return true
47611
+ }
47612
+
47613
+ if ((lhs == null && rhs != null) ||
47614
+ (lhs != null && rhs == null)) {
47615
+ return false
47616
+ }
47617
+
47618
+ if (Array.isArray(lhs) && Array.isArray(rhs)) {
47619
+ if (lhs.length !== rhs.length) {
47620
+ return false
47621
+ }
47622
+
47623
+ for (let i = 0; i < lhs.length; i++) {
47624
+ if (lhs[i] !== rhs[i]) {
47625
+ return false
47078
47626
  }
47627
+ }
47079
47628
 
47080
- return entry.vary[headerName] === key.headers[headerName]
47081
- }))
47082
- ))
47629
+ return true
47630
+ }
47631
+
47632
+ return lhs === rhs
47083
47633
  }
47084
47634
 
47085
47635
  memoryCacheStore = MemoryCacheStore;
@@ -47112,7 +47662,7 @@ function requireCacheRevalidationHandler () {
47112
47662
  #successful = false
47113
47663
 
47114
47664
  /**
47115
- * @type {((boolean, any) => void) | null}
47665
+ * @type {((success: boolean, context?: any, statusCode?: number, headers?: import('../../types/header.d.ts').IncomingHttpHeaders) => void) | null}
47116
47666
  */
47117
47667
  #callback
47118
47668
 
@@ -47129,7 +47679,7 @@ function requireCacheRevalidationHandler () {
47129
47679
  #allowErrorStatusCodes
47130
47680
 
47131
47681
  /**
47132
- * @param {(boolean) => void} callback Function to call if the cached value is valid
47682
+ * @param {(success: boolean, context?: any, statusCode?: number, headers?: import('../../types/header.d.ts').IncomingHttpHeaders) => void} callback Function to call if the cached value is valid
47133
47683
  * @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
47134
47684
  * @param {boolean} allowErrorStatusCodes
47135
47685
  */
@@ -47164,7 +47714,7 @@ function requireCacheRevalidationHandler () {
47164
47714
  // https://datatracker.ietf.org/doc/html/rfc5861#section-4
47165
47715
  this.#successful = statusCode === 304 ||
47166
47716
  (this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504);
47167
- this.#callback(this.#successful, this.#context);
47717
+ this.#callback(this.#successful, this.#context, statusCode, headers);
47168
47718
  this.#callback = null;
47169
47719
 
47170
47720
  if (this.#successful) {
@@ -47231,8 +47781,9 @@ function requireCache$1 () {
47231
47781
  const CacheHandler = requireCacheHandler();
47232
47782
  const MemoryCacheStore = requireMemoryCacheStore();
47233
47783
  const CacheRevalidationHandler = requireCacheRevalidationHandler();
47234
- const { assertCacheStore, assertCacheMethods, makeCacheKey, normalizeHeaders, parseCacheControlHeader } = requireCache$2();
47784
+ const { assertCacheStore, assertCacheMethods, makeCacheKey, normalizeHeaders, parseCacheControlHeader, isInvalidOrWildcardVaryHeader } = requireCache$2();
47235
47785
  const { AbortError } = requireErrors$1();
47786
+ const { parseHttpDate } = requireDate();
47236
47787
 
47237
47788
  /**
47238
47789
  * @param {(string | RegExp)[] | undefined} origins
@@ -47253,6 +47804,44 @@ function requireCache$1 () {
47253
47804
 
47254
47805
  const nop = () => {};
47255
47806
 
47807
+ function trimOWS (value) {
47808
+ return value.replace(/^[\t ]+|[\t ]+$/g, '')
47809
+ }
47810
+
47811
+ function arrayIncludes (array, value) {
47812
+ for (let i = 0; i < array.length; i++) {
47813
+ if (array[i] === value) {
47814
+ return true
47815
+ }
47816
+ }
47817
+
47818
+ return false
47819
+ }
47820
+
47821
+ function hasPragmaNoCache (headers) {
47822
+ const pragma = headers?.pragma;
47823
+ if (!pragma) {
47824
+ return false
47825
+ }
47826
+
47827
+ const values = Array.isArray(pragma) ? pragma : [pragma];
47828
+ for (let i = 0; i < values.length; i++) {
47829
+ const value = values[i];
47830
+ if (typeof value !== 'string') {
47831
+ continue
47832
+ }
47833
+
47834
+ const directives = value.split(',');
47835
+ for (let j = 0; j < directives.length; j++) {
47836
+ if (trimOWS(directives[j]).toLowerCase() === 'no-cache') {
47837
+ return true
47838
+ }
47839
+ }
47840
+ }
47841
+
47842
+ return false
47843
+ }
47844
+
47256
47845
  /**
47257
47846
  * @typedef {(options: import('../../types/dispatcher.d.ts').default.DispatchOptions, handler: import('../../types/dispatcher.d.ts').default.DispatchHandler) => void} DispatchFn
47258
47847
  */
@@ -47282,16 +47871,92 @@ function requireCache$1 () {
47282
47871
  return false
47283
47872
  }
47284
47873
 
47874
+ /**
47875
+ * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
47876
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
47877
+ * @returns {boolean}
47878
+ */
47879
+ function staleResponseRequiresRevalidation (result, cacheType) {
47880
+ return result.cacheControlDirectives?.['must-revalidate'] === true ||
47881
+ (cacheType === 'shared' && (
47882
+ result.cacheControlDirectives?.['proxy-revalidate'] === true ||
47883
+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10
47884
+ // s-maxage implies proxy-revalidate for shared caches.
47885
+ result.cacheControlDirectives?.['s-maxage'] !== undefined
47886
+ ))
47887
+ }
47888
+
47889
+ /**
47890
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
47891
+ * @param {import('../../types/header.d.ts').IncomingHttpHeaders} headers
47892
+ * @returns {boolean}
47893
+ */
47894
+ function revalidationResponseDisallowsCachedReuse (cacheType, headers) {
47895
+ if (headers.vary && isInvalidOrWildcardVaryHeader(headers.vary)) {
47896
+ return true
47897
+ }
47898
+
47899
+ const cacheControl = headers['cache-control'];
47900
+ if (!cacheControl) {
47901
+ return false
47902
+ }
47903
+
47904
+ const cacheControlDirectives = parseCacheControlHeader(cacheControl);
47905
+ return cacheControlDirectives['no-store'] === true ||
47906
+ (cacheType === 'shared' && cacheControlDirectives.private === true)
47907
+ }
47908
+
47909
+ function revalidationResponseUpdatesCacheControl (headers) {
47910
+ return headers['cache-control'] !== undefined
47911
+ }
47912
+
47913
+ function deleteCachedValue (store, cacheKey) {
47914
+ try {
47915
+ store.delete(cacheKey)?.catch?.(nop);
47916
+ } catch {
47917
+ // Fail silently
47918
+ }
47919
+ }
47920
+
47921
+ function getUsableLastModified (headers) {
47922
+ const lastModified = headers?.['last-modified'];
47923
+ if (typeof lastModified === 'string' && parseHttpDate(lastModified)) {
47924
+ return lastModified
47925
+ }
47926
+ }
47927
+
47928
+ function makeRevalidationHeaders (opts, result) {
47929
+ const headers = {
47930
+ ...opts.headers,
47931
+ 'if-modified-since': getUsableLastModified(result.headers) ?? new Date(result.cachedAt).toUTCString()
47932
+ };
47933
+
47934
+ if (result.etag) {
47935
+ headers['if-none-match'] = result.etag;
47936
+ }
47937
+
47938
+ if (result.vary) {
47939
+ for (const key in result.vary) {
47940
+ if (result.vary[key] != null) {
47941
+ headers[key] = result.vary[key];
47942
+ }
47943
+ }
47944
+ }
47945
+
47946
+ return headers
47947
+ }
47948
+
47285
47949
  /**
47286
47950
  * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
47287
47951
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives | undefined} cacheControlDirectives
47952
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
47288
47953
  * @returns {boolean}
47289
47954
  */
47290
- function isStale (result, cacheControlDirectives) {
47955
+ function isStale (result, cacheControlDirectives, cacheType) {
47291
47956
  const now = Date.now();
47292
47957
  if (now > result.staleAt) {
47293
47958
  // Response is stale
47294
- if (cacheControlDirectives?.['max-stale']) {
47959
+ if (!staleResponseRequiresRevalidation(result, cacheType) && cacheControlDirectives?.['max-stale']) {
47295
47960
  // There's a threshold where we can serve stale responses, let's see if
47296
47961
  // we're in it
47297
47962
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-max-stale
@@ -47318,11 +47983,12 @@ function requireCache$1 () {
47318
47983
  /**
47319
47984
  * Check if we're within the stale-while-revalidate window for a stale response
47320
47985
  * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
47986
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
47321
47987
  * @returns {boolean}
47322
47988
  */
47323
- function withinStaleWhileRevalidateWindow (result) {
47989
+ function withinStaleWhileRevalidateWindow (result, cacheType) {
47324
47990
  const staleWhileRevalidate = result.cacheControlDirectives?.['stale-while-revalidate'];
47325
- if (!staleWhileRevalidate) {
47991
+ if (!staleWhileRevalidate || staleResponseRequiresRevalidation(result, cacheType)) {
47326
47992
  return false
47327
47993
  }
47328
47994
 
@@ -47492,14 +48158,10 @@ function requireCache$1 () {
47492
48158
  }
47493
48159
 
47494
48160
  const age = Math.round((now - result.cachedAt) / 1000);
47495
- if (reqCacheControl?.['max-age'] && age >= reqCacheControl['max-age']) {
47496
- // Response is considered expired for this specific request
47497
- // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1
47498
- return dispatch(opts, handler)
47499
- }
48161
+ const requestMaxAgeExpired = reqCacheControl?.['max-age'] !== undefined && age >= reqCacheControl['max-age'];
47500
48162
 
47501
- const stale = isStale(result, reqCacheControl);
47502
- const revalidate = needsRevalidation(result, reqCacheControl, opts);
48163
+ const stale = requestMaxAgeExpired || isStale(result, reqCacheControl, globalOpts.type);
48164
+ const revalidate = requestMaxAgeExpired || needsRevalidation(result, reqCacheControl, opts);
47503
48165
 
47504
48166
  // Check if the response is stale
47505
48167
  if (stale || revalidate) {
@@ -47511,28 +48173,13 @@ function requireCache$1 () {
47511
48173
 
47512
48174
  // RFC 5861: If we're within stale-while-revalidate window, serve stale immediately
47513
48175
  // and revalidate in background, unless immediate revalidation is necessary
47514
- if (!revalidate && withinStaleWhileRevalidateWindow(result)) {
48176
+ if (!revalidate && withinStaleWhileRevalidateWindow(result, globalOpts.type)) {
47515
48177
  // Serve stale response immediately
47516
48178
  sendCachedValue(handler, opts, result, age, null, true);
47517
48179
 
47518
48180
  // Start background revalidation (fire-and-forget)
47519
48181
  queueMicrotask(() => {
47520
- const headers = {
47521
- ...opts.headers,
47522
- 'if-modified-since': new Date(result.cachedAt).toUTCString()
47523
- };
47524
-
47525
- if (result.etag) {
47526
- headers['if-none-match'] = result.etag;
47527
- }
47528
-
47529
- if (result.vary) {
47530
- for (const key in result.vary) {
47531
- if (result.vary[key] != null) {
47532
- headers[key] = result.vary[key];
47533
- }
47534
- }
47535
- }
48182
+ const headers = makeRevalidationHeaders(opts, result);
47536
48183
 
47537
48184
  // Background revalidation - update cache if we get new data
47538
48185
  dispatch(
@@ -47556,28 +48203,15 @@ function requireCache$1 () {
47556
48203
  }
47557
48204
 
47558
48205
  let withinStaleIfErrorThreshold = false;
47559
- const staleIfErrorExpiry = result.cacheControlDirectives['stale-if-error'] ?? reqCacheControl?.['stale-if-error'];
47560
- if (staleIfErrorExpiry) {
47561
- withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000));
47562
- }
47563
-
47564
- const headers = {
47565
- ...opts.headers,
47566
- 'if-modified-since': new Date(result.cachedAt).toUTCString()
47567
- };
47568
-
47569
- if (result.etag) {
47570
- headers['if-none-match'] = result.etag;
47571
- }
47572
-
47573
- if (result.vary) {
47574
- for (const key in result.vary) {
47575
- if (result.vary[key] != null) {
47576
- headers[key] = result.vary[key];
47577
- }
48206
+ if (!staleResponseRequiresRevalidation(result, globalOpts.type)) {
48207
+ const staleIfErrorExpiry = result.cacheControlDirectives['stale-if-error'] ?? reqCacheControl?.['stale-if-error'];
48208
+ if (staleIfErrorExpiry) {
48209
+ withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000));
47578
48210
  }
47579
48211
  }
47580
48212
 
48213
+ const headers = makeRevalidationHeaders(opts, result);
48214
+
47581
48215
  // We need to revalidate the response
47582
48216
  return dispatch(
47583
48217
  {
@@ -47585,8 +48219,23 @@ function requireCache$1 () {
47585
48219
  headers
47586
48220
  },
47587
48221
  new CacheRevalidationHandler(
47588
- (success, context) => {
48222
+ (success, context, statusCode, headers) => {
47589
48223
  if (success) {
48224
+ if (statusCode === 304) {
48225
+ if (revalidationResponseDisallowsCachedReuse(globalOpts.type, headers)) {
48226
+ if (util.isStream(result.body)) {
48227
+ result.body.on('error', nop).destroy();
48228
+ }
48229
+
48230
+ deleteCachedValue(globalOpts.store, cacheKey);
48231
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
48232
+ }
48233
+
48234
+ if (revalidationResponseUpdatesCacheControl(headers)) {
48235
+ deleteCachedValue(globalOpts.store, cacheKey);
48236
+ }
48237
+ }
48238
+
47590
48239
  // TODO: successful revalidation should be considered fresh (not give stale warning).
47591
48240
  sendCachedValue(handler, opts, result, age, context, stale);
47592
48241
  } else if (util.isStream(result.body)) {
@@ -47643,11 +48292,17 @@ function requireCache$1 () {
47643
48292
  type
47644
48293
  };
47645
48294
 
47646
- const safeMethodsToNotCache = util.safeHTTPMethods.filter(method => methods.includes(method) === false);
48295
+ const safeMethodsToNotCache = [];
48296
+ for (let i = 0; i < util.safeHTTPMethods.length; i++) {
48297
+ const method = util.safeHTTPMethods[i];
48298
+ if (!arrayIncludes(methods, method)) {
48299
+ safeMethodsToNotCache.push(method);
48300
+ }
48301
+ }
47647
48302
 
47648
48303
  return dispatch => {
47649
48304
  return (opts, handler) => {
47650
- if (!opts.origin || safeMethodsToNotCache.includes(opts.method)) {
48305
+ if (!opts.origin || arrayIncludes(safeMethodsToNotCache, opts.method)) {
47651
48306
  // Not a method we want to cache or we don't have the origin, skip
47652
48307
  return dispatch(opts, handler)
47653
48308
  }
@@ -47682,7 +48337,9 @@ function requireCache$1 () {
47682
48337
 
47683
48338
  const reqCacheControl = opts.headers?.['cache-control']
47684
48339
  ? parseCacheControlHeader(opts.headers['cache-control'])
47685
- : undefined;
48340
+ : hasPragmaNoCache(opts.headers)
48341
+ ? { 'no-cache': true }
48342
+ : undefined;
47686
48343
 
47687
48344
  if (reqCacheControl?.['no-store']) {
47688
48345
  return dispatch(opts, handler)
@@ -49042,7 +49699,13 @@ function requireSqliteCacheStore () {
49042
49699
  return false
49043
49700
  }
49044
49701
 
49045
- return lhs.every((x, i) => x === rhs[i])
49702
+ for (let i = 0; i < lhs.length; i++) {
49703
+ if (lhs[i] !== rhs[i]) {
49704
+ return false
49705
+ }
49706
+ }
49707
+
49708
+ return true
49046
49709
  }
49047
49710
 
49048
49711
  return lhs === rhs
@@ -55442,7 +56105,7 @@ function requireUtil$3 () {
55442
56105
 
55443
56106
  if (
55444
56107
  code < 0x20 || // exclude CTLs (0-31)
55445
- code === 0x7F || // DEL
56108
+ code > 0x7E || // exclude DEL and non-ascii
55446
56109
  code === 0x3B // ;
55447
56110
  ) {
55448
56111
  throw new Error('Invalid cookie path')
@@ -55451,16 +56114,80 @@ function requireUtil$3 () {
55451
56114
  }
55452
56115
 
55453
56116
  /**
55454
- * I have no idea why these values aren't allowed to be honest,
55455
- * but Deno tests these. - Khafra
56117
+ * <let-dig> ::= <letter> | <digit>
56118
+ *
56119
+ * <letter> ::= any one of the 52 alphabetic characters A through Z in
56120
+ * upper case and a through z in lower case
56121
+ *
56122
+ * <digit> ::= any one of the ten digits 0 through 9r
56123
+ *
56124
+ * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
56125
+ * @param {number} code
56126
+ */
56127
+ function isLetterOrDigit (code) {
56128
+ return (
56129
+ (code >= 0x30 && code <= 0x39) || // 0-9
56130
+ (code >= 0x41 && code <= 0x5A) || // A-Z
56131
+ (code >= 0x61 && code <= 0x7A) // a-z
56132
+ )
56133
+ }
56134
+
56135
+ /**
56136
+ * Validates a cookie domain against the "preferred name syntax".
56137
+ *
56138
+ * <domain> ::= <subdomain> | " "
56139
+ * <subdomain> ::= <label> | <subdomain> "." <label>
56140
+ * <label> ::= <let-dig> [ [ <ldh-str> ] <let-dig> ]
56141
+ * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
56142
+ * <let-dig-hyp> ::= <let-dig> | "-"
56143
+ *
56144
+ * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
56145
+ * @see https://www.rfc-editor.org/rfc/rfc1123#section-2.1
56146
+ * @see https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4
55456
56147
  * @param {string} domain
55457
56148
  */
55458
56149
  function validateCookieDomain (domain) {
55459
- if (
55460
- domain.startsWith('-') ||
55461
- domain.endsWith('.') ||
55462
- domain.endsWith('-')
55463
- ) {
56150
+ // <domain> ::= <subdomain> | " "
56151
+ if (domain === ' ') {
56152
+ return
56153
+ }
56154
+
56155
+ if (domain.length > 255) {
56156
+ throw new Error('Invalid cookie domain')
56157
+ }
56158
+
56159
+ let labelLength = 0;
56160
+
56161
+ for (let i = 0; i < domain.length; ++i) {
56162
+ const code = domain.charCodeAt(i);
56163
+
56164
+ if (code === 0x2E) {
56165
+ if (labelLength === 0) {
56166
+ throw new Error('Invalid cookie domain')
56167
+ }
56168
+
56169
+ if (domain.charCodeAt(i - 1) === 0x2D) { // "-"
56170
+ throw new Error('Invalid cookie domain')
56171
+ }
56172
+
56173
+ labelLength = 0;
56174
+ continue
56175
+ }
56176
+
56177
+ if (labelLength === 0 && !isLetterOrDigit(code)) {
56178
+ throw new Error('Invalid cookie domain')
56179
+ }
56180
+
56181
+ if (!isLetterOrDigit(code) && code !== 0x2D) { // "-"
56182
+ throw new Error('Invalid cookie domain')
56183
+ }
56184
+
56185
+ if (++labelLength > 63) {
56186
+ throw new Error('Invalid cookie domain')
56187
+ }
56188
+ }
56189
+
56190
+ if (labelLength === 0 || domain.charCodeAt(domain.length - 1) === 0x2D) { // "-"
55464
56191
  throw new Error('Invalid cookie domain')
55465
56192
  }
55466
56193
  }
@@ -55603,7 +56330,13 @@ function requireUtil$3 () {
55603
56330
 
55604
56331
  const [key, ...value] = part.split('=');
55605
56332
 
55606
- out.push(`${key.trim()}=${value.join('=')}`);
56333
+ const trimmedKey = key.trim();
56334
+ const joinedValue = value.join('=');
56335
+
56336
+ validateCookieName(trimmedKey);
56337
+ validateCookieValue(joinedValue);
56338
+
56339
+ out.push(`${trimmedKey}=${joinedValue}`);
55607
56340
  }
55608
56341
 
55609
56342
  return out.join('; ')
@@ -87693,15 +88426,19 @@ function requireJson () {
87693
88426
  function json (options) {
87694
88427
  var opts = options || {};
87695
88428
 
87696
- var limit = typeof opts.limit !== 'number'
87697
- ? bytes.parse(opts.limit || '100kb')
87698
- : opts.limit;
88429
+ var limit = typeof opts.limit === 'undefined' || opts.limit === null
88430
+ ? 102400 // 100kb default
88431
+ : bytes.parse(opts.limit);
87699
88432
  var inflate = opts.inflate !== false;
87700
88433
  var reviver = opts.reviver;
87701
88434
  var strict = opts.strict !== false;
87702
88435
  var type = opts.type || 'application/json';
87703
88436
  var verify = opts.verify || false;
87704
88437
 
88438
+ if (limit === null) {
88439
+ throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
88440
+ }
88441
+
87705
88442
  if (verify !== false && typeof verify !== 'function') {
87706
88443
  throw new TypeError('option verify must be function')
87707
88444
  }
@@ -87924,12 +88661,16 @@ function requireRaw () {
87924
88661
  var opts = options || {};
87925
88662
 
87926
88663
  var inflate = opts.inflate !== false;
87927
- var limit = typeof opts.limit !== 'number'
87928
- ? bytes.parse(opts.limit || '100kb')
87929
- : opts.limit;
88664
+ var limit = typeof opts.limit === 'undefined' || opts.limit === null
88665
+ ? 102400 // 100kb default
88666
+ : bytes.parse(opts.limit);
87930
88667
  var type = opts.type || 'application/octet-stream';
87931
88668
  var verify = opts.verify || false;
87932
88669
 
88670
+ if (limit === null) {
88671
+ throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
88672
+ }
88673
+
87933
88674
  if (verify !== false && typeof verify !== 'function') {
87934
88675
  throw new TypeError('option verify must be function')
87935
88676
  }
@@ -88035,12 +88776,16 @@ function requireText () {
88035
88776
 
88036
88777
  var defaultCharset = opts.defaultCharset || 'utf-8';
88037
88778
  var inflate = opts.inflate !== false;
88038
- var limit = typeof opts.limit !== 'number'
88039
- ? bytes.parse(opts.limit || '100kb')
88040
- : opts.limit;
88779
+ var limit = typeof opts.limit === 'undefined' || opts.limit === null
88780
+ ? 102400 // 100kb default
88781
+ : bytes.parse(opts.limit);
88041
88782
  var type = opts.type || 'text/plain';
88042
88783
  var verify = opts.verify || false;
88043
88784
 
88785
+ if (limit === null) {
88786
+ throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
88787
+ }
88788
+
88044
88789
  if (verify !== false && typeof verify !== 'function') {
88045
88790
  throw new TypeError('option verify must be function')
88046
88791
  }
@@ -91307,12 +92052,16 @@ function requireUrlencoded () {
91307
92052
 
91308
92053
  var extended = opts.extended !== false;
91309
92054
  var inflate = opts.inflate !== false;
91310
- var limit = typeof opts.limit !== 'number'
91311
- ? bytes.parse(opts.limit || '100kb')
91312
- : opts.limit;
92055
+ var limit = typeof opts.limit === 'undefined' || opts.limit === null
92056
+ ? 102400 // 100kb default
92057
+ : bytes.parse(opts.limit);
91313
92058
  var type = opts.type || 'application/x-www-form-urlencoded';
91314
92059
  var verify = opts.verify || false;
91315
92060
 
92061
+ if (limit === null) {
92062
+ throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
92063
+ }
92064
+
91316
92065
  if (verify !== false && typeof verify !== 'function') {
91317
92066
  throw new TypeError('option verify must be function')
91318
92067
  }
@@ -112028,6 +112777,17 @@ const closePattern = /\\}/g;
112028
112777
  const commaPattern = /\\,/g;
112029
112778
  const periodPattern = /\\\./g;
112030
112779
  const EXPANSION_MAX = 100_000;
112780
+ // `EXPANSION_MAX` caps the *number* of expansions, but not their length. An
112781
+ // input like `'{a,b}'.repeat(1500)` stays under that count - its output is
112782
+ // truncated to 100k results - while making every result ~1500 characters
112783
+ // long. The result set, and the intermediate arrays built while combining
112784
+ // brace sets, then grow large enough to exhaust memory and crash the process
112785
+ // (CVE-2026-14257). `EXPANSION_MAX_LENGTH` bounds the total number of
112786
+ // characters the accumulator may hold at any point, so memory stays flat no
112787
+ // matter how many brace groups are chained. The limit sits well above any
112788
+ // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
112789
+ // characters) so legitimate input is unaffected.
112790
+ const EXPANSION_MAX_LENGTH = 4_000_000;
112031
112791
  function numeric(str) {
112032
112792
  return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
112033
112793
  }
@@ -112076,7 +112836,7 @@ function expand(str, options = {}) {
112076
112836
  if (!str) {
112077
112837
  return [];
112078
112838
  }
112079
- const { max = EXPANSION_MAX } = options;
112839
+ const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options;
112080
112840
  // I don't know why Bash 4.3 does this, but it does.
112081
112841
  // Anything starting with {} will have the first two bytes preserved
112082
112842
  // but *only* at the top level, so {},a}b will not expand to anything,
@@ -112086,7 +112846,7 @@ function expand(str, options = {}) {
112086
112846
  if (str.slice(0, 2) === '{}') {
112087
112847
  str = '\\{\\}' + str.slice(2);
112088
112848
  }
112089
- return expand_(escapeBraces(str), max, true).map(unescapeBraces);
112849
+ return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces);
112090
112850
  }
112091
112851
  function embrace(str) {
112092
112852
  return '{' + str + '}';
@@ -112100,22 +112860,117 @@ function lte(i, y) {
112100
112860
  function gte(i, y) {
112101
112861
  return i >= y;
112102
112862
  }
112103
- function expand_(str, max, isTop) {
112104
- /** @type {string[]} */
112105
- const expansions = [];
112106
- const m = balanced('{', '}', str);
112107
- if (!m)
112108
- return [str];
112109
- // no need to expand pre, since it is guaranteed to be free of brace-sets
112110
- const pre = m.pre;
112111
- const post = m.post.length ? expand_(m.post, max, false) : [''];
112112
- if (/\$$/.test(m.pre)) {
112113
- for (let k = 0; k < post.length && k < max; k++) {
112114
- const expansion = pre + '{' + m.body + '}' + post[k];
112115
- expansions.push(expansion);
112863
+ // Build `{ acc[a] + pre + values[v] }` for every combination, capping the
112864
+ // number of results at `max` and the total number of characters at `maxLength`.
112865
+ // This is the one place output grows, so bounding it here keeps the single
112866
+ // accumulator - and therefore memory - flat regardless of how many brace groups
112867
+ // are combined (CVE-2026-14257).
112868
+ function combine(acc, pre, values, max, maxLength, dropEmpties) {
112869
+ const out = [];
112870
+ let length = 0;
112871
+ for (let a = 0; a < acc.length; a++) {
112872
+ for (let v = 0; v < values.length; v++) {
112873
+ if (out.length >= max)
112874
+ return out;
112875
+ const expansion = acc[a] + pre + values[v];
112876
+ // Bash drops empty results at the top level. Skip them before they count
112877
+ // against `max`, so `max` bounds the number of *kept* results.
112878
+ if (dropEmpties && !expansion)
112879
+ continue;
112880
+ if (length + expansion.length > maxLength)
112881
+ return out;
112882
+ out.push(expansion);
112883
+ length += expansion.length;
112116
112884
  }
112117
112885
  }
112118
- else {
112886
+ return out;
112887
+ }
112888
+ // The expansion values of a single numeric (`1..5`) or alphabetic (`a..e..2`)
112889
+ // sequence body.
112890
+ function expandSequence(body, isAlphaSequence, max, maxLength) {
112891
+ const n = body.split(/\.\./);
112892
+ const N = [];
112893
+ // A sequence body always splits into two or three parts, but the compiler
112894
+ // can't know that.
112895
+ /* c8 ignore start */
112896
+ if (n[0] === undefined || n[1] === undefined) {
112897
+ return N;
112898
+ }
112899
+ /* c8 ignore stop */
112900
+ const x = numeric(n[0]);
112901
+ const y = numeric(n[1]);
112902
+ const width = Math.max(n[0].length, n[1].length);
112903
+ let incr = n.length === 3 && n[2] !== undefined ?
112904
+ Math.max(Math.abs(numeric(n[2])), 1)
112905
+ : 1;
112906
+ let test = lte;
112907
+ const reverse = y < x;
112908
+ if (reverse) {
112909
+ incr *= -1;
112910
+ test = gte;
112911
+ }
112912
+ const pad = n.some(isPadded);
112913
+ let length = 0;
112914
+ for (let i = x; test(i, y) && N.length < max; i += incr) {
112915
+ let c;
112916
+ if (isAlphaSequence) {
112917
+ c = String.fromCharCode(i);
112918
+ if (c === '\\') {
112919
+ c = '';
112920
+ }
112921
+ }
112922
+ else {
112923
+ c = String(i);
112924
+ if (pad) {
112925
+ const need = width - c.length;
112926
+ if (need > 0) {
112927
+ const z = new Array(need + 1).join('0');
112928
+ if (i < 0) {
112929
+ c = '-' + z + c.slice(1);
112930
+ }
112931
+ else {
112932
+ c = z + c;
112933
+ }
112934
+ }
112935
+ }
112936
+ }
112937
+ if (length + c.length > maxLength)
112938
+ break;
112939
+ N.push(c);
112940
+ length += c.length;
112941
+ }
112942
+ return N;
112943
+ }
112944
+ function expand_(str, max, maxLength, isTop) {
112945
+ // Consume the string's top-level brace groups left to right, threading a
112946
+ // running set of combined prefixes (`acc`). Expanding the tail iteratively -
112947
+ // rather than recursing on `m.post` once per group - keeps the native stack
112948
+ // depth constant, so deeply chained input (`'{a,b}'.repeat(3000)`) can no
112949
+ // longer overflow the stack, and leaves a single accumulator whose size
112950
+ // `maxLength` bounds directly (CVE-2026-14257).
112951
+ let acc = [''];
112952
+ // Bash drops empty results, but only when the *first* top-level group is a
112953
+ // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
112954
+ // is on the final strings, so it is applied to whichever `combine` produces
112955
+ // them (the one with no brace set left in the tail).
112956
+ let dropEmpties = false;
112957
+ let firstGroup = true;
112958
+ for (;;) {
112959
+ const m = balanced('{', '}', str);
112960
+ // No brace set left: the rest of the string is literal.
112961
+ if (!m) {
112962
+ return combine(acc, str, [''], max, maxLength, dropEmpties);
112963
+ }
112964
+ // no need to expand pre, since it is guaranteed to be free of brace-sets
112965
+ const pre = m.pre;
112966
+ if (/\$$/.test(pre)) {
112967
+ acc = combine(acc, pre + '{' + m.body + '}', [''], max, maxLength, dropEmpties && !m.post.length);
112968
+ firstGroup = false;
112969
+ if (!m.post.length)
112970
+ break;
112971
+ str = m.post;
112972
+ continue;
112973
+ }
112119
112974
  const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
112120
112975
  const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
112121
112976
  const isSequence = isNumericSequence || isAlphaSequence;
@@ -112124,87 +112979,69 @@ function expand_(str, max, isTop) {
112124
112979
  // {a},b}
112125
112980
  if (m.post.match(/,(?!,).*\}/)) {
112126
112981
  str = m.pre + '{' + m.body + escClose + m.post;
112127
- return expand_(str, max, true);
112982
+ isTop = true;
112983
+ continue;
112128
112984
  }
112129
- return [str];
112985
+ // Nothing here expands, so the whole remaining string is literal.
112986
+ return combine(acc, pre + '{' + m.body + '}' + m.post, [''], max, maxLength, dropEmpties);
112130
112987
  }
112131
- let n;
112988
+ if (firstGroup) {
112989
+ dropEmpties = isTop && !isSequence;
112990
+ firstGroup = false;
112991
+ }
112992
+ let values;
112132
112993
  if (isSequence) {
112133
- n = m.body.split(/\.\./);
112994
+ values = expandSequence(m.body, isAlphaSequence, max, maxLength);
112134
112995
  }
112135
112996
  else {
112136
- n = parseCommaParts(m.body);
112997
+ let n = parseCommaParts(m.body);
112137
112998
  if (n.length === 1 && n[0] !== undefined) {
112138
112999
  // x{{a,b}}y ==> x{a}y x{b}y
112139
- n = expand_(n[0], max, false).map(embrace);
113000
+ n = expand_(n[0], max, maxLength, false).map(embrace);
112140
113001
  //XXX is this necessary? Can't seem to hit it in tests.
112141
113002
  /* c8 ignore start */
112142
113003
  if (n.length === 1) {
112143
- return post.map(p => m.pre + n[0] + p);
113004
+ acc = combine(acc, pre + n[0], [''], max, maxLength, dropEmpties && !m.post.length);
113005
+ if (!m.post.length)
113006
+ break;
113007
+ str = m.post;
113008
+ continue;
112144
113009
  }
112145
113010
  /* c8 ignore stop */
112146
113011
  }
112147
- }
112148
- // at this point, n is the parts, and we know it's not a comma set
112149
- // with a single entry.
112150
- let N;
112151
- if (isSequence && n[0] !== undefined && n[1] !== undefined) {
112152
- const x = numeric(n[0]);
112153
- const y = numeric(n[1]);
112154
- const width = Math.max(n[0].length, n[1].length);
112155
- let incr = n.length === 3 && n[2] !== undefined ?
112156
- Math.max(Math.abs(numeric(n[2])), 1)
112157
- : 1;
112158
- let test = lte;
112159
- const reverse = y < x;
112160
- if (reverse) {
112161
- incr *= -1;
112162
- test = gte;
112163
- }
112164
- const pad = n.some(isPadded);
112165
- N = [];
112166
- for (let i = x; test(i, y) && N.length < max; i += incr) {
112167
- let c;
112168
- if (isAlphaSequence) {
112169
- c = String.fromCharCode(i);
112170
- if (c === '\\') {
112171
- c = '';
112172
- }
112173
- }
112174
- else {
112175
- c = String(i);
112176
- if (pad) {
112177
- const need = width - c.length;
112178
- if (need > 0) {
112179
- const z = new Array(need + 1).join('0');
112180
- if (i < 0) {
112181
- c = '-' + z + c.slice(1);
112182
- }
112183
- else {
112184
- c = z + c;
112185
- }
112186
- }
113012
+ // Values that `combine` is going to drop as empty produce no result, so
113013
+ // they must not count against `max` - otherwise `{a,,b}` with `max: 2`
113014
+ // would stop at `['a', '']` and yield one result instead of two. Skipping
113015
+ // them outright keeps `values` bounded while leaving `max` a bound on
113016
+ // *kept* results.
113017
+ let dropsEmpties = dropEmpties && !m.post.length && !pre;
113018
+ for (let d = 0; dropsEmpties && d < acc.length; d++) {
113019
+ if (acc[d]) {
113020
+ dropsEmpties = false;
113021
+ }
113022
+ }
113023
+ values = [];
113024
+ let valuesLength = 0;
113025
+ outer: for (let j = 0; j < n.length; j++) {
113026
+ const expanded = expand_(n[j], max, maxLength, false);
113027
+ for (let k = 0; k < expanded.length; k++) {
113028
+ const v = expanded[k];
113029
+ if (dropsEmpties && !v)
113030
+ continue;
113031
+ if (values.length >= max || valuesLength + v.length > maxLength) {
113032
+ break outer;
112187
113033
  }
112188
- }
112189
- N.push(c);
112190
- }
112191
- }
112192
- else {
112193
- N = [];
112194
- for (let j = 0; j < n.length; j++) {
112195
- N.push.apply(N, expand_(n[j], max, false));
112196
- }
112197
- }
112198
- for (let j = 0; j < N.length; j++) {
112199
- for (let k = 0; k < post.length && expansions.length < max; k++) {
112200
- const expansion = pre + N[j] + post[k];
112201
- if (!isTop || isSequence || expansion) {
112202
- expansions.push(expansion);
113034
+ values.push(v);
113035
+ valuesLength += v.length;
112203
113036
  }
112204
113037
  }
112205
113038
  }
113039
+ acc = combine(acc, pre, values, max, maxLength, dropEmpties && !m.post.length);
113040
+ if (!m.post.length)
113041
+ break;
113042
+ str = m.post;
112206
113043
  }
112207
- return expansions;
113044
+ return acc;
112208
113045
  }
112209
113046
 
112210
113047
  const MAX_PATTERN_LENGTH = 1024 * 64;
@@ -170189,7 +171026,12 @@ function requireFastUri () {
170189
171026
  */
170190
171027
  function resolve (baseURI, relativeURI, options) {
170191
171028
  const schemelessOptions = options ? Object.assign({ scheme: 'null' }, options) : { scheme: 'null' };
170192
- const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
171029
+ const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
171030
+ const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
171031
+ if (baseMalformed || relativeMalformed) {
171032
+ throw new Error(baseParsed.error || relativeParsed.error || 'URI is malformed.')
171033
+ }
171034
+ const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
170193
171035
  schemelessOptions.skipEscape = true;
170194
171036
  return serialize(resolved, schemelessOptions)
170195
171037
  }
@@ -170365,6 +171207,19 @@ function requireFastUri () {
170365
171207
 
170366
171208
  const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
170367
171209
 
171210
+ // Captures the authority component (between "//" and the next "/", "?" or "#"),
171211
+ // with or without a scheme prefix, for the literal-backslash rejection below.
171212
+ const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
171213
+
171214
+ // Captures the leading authority-introducer region after an optional scheme: a
171215
+ // run of forward slashes, backslashes, and the characters the WHATWG URL parser
171216
+ // removes before parsing (TAB U+0009, LF U+000A, CR U+000D). A valid introducer
171217
+ // is exactly "//". Node treats "\" as "/" on special schemes and strips those
171218
+ // characters first, so forms like "\\", "/\", "\/", "/<TAB>/", or a leading
171219
+ // "<TAB>//" reach an authority in Node while fast-uri's URI_PARSE folds them into
171220
+ // the path group (host confusion / SSRF / redirect bypass).
171221
+ const AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
171222
+
170368
171223
  /**
170369
171224
  * @param {import('./types/index').URIComponent} parsed
170370
171225
  * @param {RegExpMatchArray} matches
@@ -170411,6 +171266,41 @@ function requireFastUri () {
170411
171266
  }
170412
171267
  }
170413
171268
 
171269
+ // A literal backslash (U+005C) is not a valid RFC 3986 URI character and is
171270
+ // not an authority delimiter. Reject it in the authority rather than
171271
+ // rewriting it: normalizing "\" -> "/" (WHATWG error recovery) could silently
171272
+ // change the resource identified by an otherwise-invalid input, and lets "\"
171273
+ // act as a host delimiter here while Node's native URL parses a different
171274
+ // host (SSRF / redirect / origin-allowlist bypass). Percent-encoded %5C is
171275
+ // untouched and remains valid encoded data.
171276
+ const authorityMatch = uri.match(AUTHORITY_PREFIX);
171277
+ if (authorityMatch !== null && authorityMatch[1].indexOf('\\') !== -1) {
171278
+ parsed.error = 'URI authority must not contain a literal backslash.';
171279
+ malformedAuthorityOrPort = true;
171280
+ }
171281
+
171282
+ // Reject a malformed or whitespace-smuggled authority introducer. fast-uri
171283
+ // only recognizes a literal "//"; anything else in the leading separator run
171284
+ // (a backslash, or a "//" that appears only after removing the TAB/LF/CR that
171285
+ // Node strips) means the authority fast-uri parses differs from the one Node's
171286
+ // URL resolves. Reject rather than rewrite, mirroring the literal-backslash
171287
+ // guard above. Percent-encoded forms (%5C, %09) are untouched, valid data.
171288
+ const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
171289
+ if (introducerMatch !== null) {
171290
+ const region = introducerMatch[1];
171291
+ const normalizedRegion = region.replace(/[\t\n\r]/g, '');
171292
+ // Two or more leading separators introduce an authority.
171293
+ if (normalizedRegion.length >= 2) {
171294
+ if (normalizedRegion.slice(0, 2) !== '//') {
171295
+ parsed.error = parsed.error || 'URI authority must not contain a literal backslash.';
171296
+ malformedAuthorityOrPort = true;
171297
+ } else if (region.length !== normalizedRegion.length) {
171298
+ parsed.error = parsed.error || 'URI authority introducer must not contain whitespace.';
171299
+ malformedAuthorityOrPort = true;
171300
+ }
171301
+ }
171302
+ }
171303
+
170414
171304
  const matches = uri.match(URI_PARSE);
170415
171305
 
170416
171306
  if (matches) {
@@ -170468,7 +171358,7 @@ function requireFastUri () {
170468
171358
  if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) {
170469
171359
  // convert Unicode IDN -> ASCII IDN
170470
171360
  try {
170471
- parsed.host = URL.domainToASCII(parsed.host.toLowerCase());
171361
+ parsed.host = new URL('http://' + parsed.host).hostname;
170472
171362
  } catch (e) {
170473
171363
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
170474
171364
  }