@infersec/conduit 1.86.0 → 1.87.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 (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.sea.cjs CHANGED
@@ -26930,7 +26930,13 @@ function requireRequest$2 () {
26930
26930
  } else if (typeof val[i] === 'object') {
26931
26931
  throw new InvalidArgumentError(`invalid ${key} header`)
26932
26932
  } else {
26933
- arr.push(`${val[i]}`);
26933
+ // Coerce primitives (and reject unsafe coercions such as functions
26934
+ // with a crafted toString/Symbol.toPrimitive).
26935
+ const str = `${val[i]}`;
26936
+ if (!isValidHeaderValue(str)) {
26937
+ throw new InvalidArgumentError(`invalid ${key} header`)
26938
+ }
26939
+ arr.push(str);
26934
26940
  }
26935
26941
  }
26936
26942
  val = arr;
@@ -26941,7 +26947,12 @@ function requireRequest$2 () {
26941
26947
  } else if (val === null) {
26942
26948
  val = '';
26943
26949
  } else {
26950
+ // Coerce primitives (and reject unsafe coercions such as functions
26951
+ // with a crafted toString/Symbol.toPrimitive).
26944
26952
  val = `${val}`;
26953
+ if (!isValidHeaderValue(val)) {
26954
+ throw new InvalidArgumentError(`invalid ${key} header`)
26955
+ }
26945
26956
  }
26946
26957
 
26947
26958
  if (headerName === 'host') {
@@ -33423,6 +33434,7 @@ function requireClientH1 () {
33423
33434
  RequestContentLengthMismatchError,
33424
33435
  ResponseContentLengthMismatchError,
33425
33436
  RequestAbortedError,
33437
+ InvalidArgumentError,
33426
33438
  HeadersTimeoutError,
33427
33439
  HeadersOverflowError,
33428
33440
  SocketError,
@@ -34547,8 +34559,16 @@ function requireClientH1 () {
34547
34559
  }
34548
34560
  body = bodyStream.stream;
34549
34561
  contentLength = bodyStream.length;
34550
- } else if (util.isBlobLike(body) && request.contentType == null && body.type) {
34551
- headers.push('content-type', body.type);
34562
+ } else if (util.isBlobLike(body) && request.contentType == null) {
34563
+ const contentType = body.type;
34564
+ if (contentType) {
34565
+ const contentTypeValue = `${contentType}`;
34566
+ if (!util.isValidHeaderValue(contentTypeValue)) {
34567
+ util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header'));
34568
+ return false
34569
+ }
34570
+ headers.push('content-type', contentTypeValue);
34571
+ }
34552
34572
  }
34553
34573
 
34554
34574
  if (body && typeof body.read === 'function') {
@@ -39259,6 +39279,26 @@ function requireRetryHandler () {
39259
39279
  return isNaN(retryTime) ? 0 : retryTime - Date.now()
39260
39280
  }
39261
39281
 
39282
+ function validatePartialResponseContentLength (headers, range, statusCode, retryCount) {
39283
+ const contentLength = headers['content-length'];
39284
+ if (contentLength == null) {
39285
+ return
39286
+ }
39287
+
39288
+ if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) {
39289
+ return
39290
+ }
39291
+
39292
+ const length = Number(contentLength);
39293
+ const expectedLength = range.end - range.start + 1;
39294
+ if (!Number.isFinite(length) || length !== expectedLength) {
39295
+ throw new RequestRetryError('Content-Length mismatch', statusCode, {
39296
+ headers,
39297
+ data: { count: retryCount }
39298
+ })
39299
+ }
39300
+ }
39301
+
39262
39302
  class RetryHandler {
39263
39303
  constructor (opts, { dispatch, handler }) {
39264
39304
  const { retryOptions, ...dispatchOpts } = opts;
@@ -39473,6 +39513,8 @@ function requireRetryHandler () {
39473
39513
  })
39474
39514
  }
39475
39515
 
39516
+ validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount);
39517
+
39476
39518
  const { start, size, end = size ? size - 1 : null } = contentRange;
39477
39519
 
39478
39520
  assert(this.start === start, 'content-range mismatch');
@@ -39497,6 +39539,8 @@ function requireRetryHandler () {
39497
39539
  return
39498
39540
  }
39499
39541
 
39542
+ validatePartialResponseContentLength(headers, range, statusCode, this.retryCount);
39543
+
39500
39544
  const { start, size, end = size ? size - 1 : null } = range;
39501
39545
  assert(
39502
39546
  start != null && Number.isFinite(start),
@@ -45199,11 +45243,147 @@ function requireCache$2 () {
45199
45243
  const {
45200
45244
  safeHTTPMethods,
45201
45245
  pathHasQueryOrFragment,
45202
- hasSafeIterator
45246
+ hasSafeIterator,
45247
+ isValidHTTPToken
45203
45248
  } = requireUtil$7();
45204
45249
 
45205
45250
  const { serializePathWithQuery } = requireUtil$7();
45206
45251
 
45252
+ const MAX_DELTA_SECONDS = 2147483647;
45253
+ const RESTRICTIVE_DIRECTIVE_NAMES = ['no-store', 'private', 'no-cache'];
45254
+ const kInvalidCacheControlDirectives = Symbol('invalid cache-control directives');
45255
+
45256
+ function trimOWS (value) {
45257
+ return value.replace(/^[\t ]+|[\t ]+$/g, '')
45258
+ }
45259
+
45260
+ function arrayIncludes (array, value) {
45261
+ for (let i = 0; i < array.length; i++) {
45262
+ if (array[i] === value) {
45263
+ return true
45264
+ }
45265
+ }
45266
+
45267
+ return false
45268
+ }
45269
+
45270
+ function trimOWSStart (value) {
45271
+ return value.replace(/^[\t ]+/, '')
45272
+ }
45273
+
45274
+ function trimOWSEnd (value) {
45275
+ return value.replace(/[\t ]+$/, '')
45276
+ }
45277
+
45278
+ function findUnescapedQuote (value, start) {
45279
+ let escaped = false;
45280
+ for (let i = start; i < value.length; i++) {
45281
+ if (escaped) {
45282
+ escaped = false;
45283
+ } else if (value[i] === '\\') {
45284
+ escaped = true;
45285
+ } else if (value[i] === '"') {
45286
+ return i
45287
+ }
45288
+ }
45289
+
45290
+ return -1
45291
+ }
45292
+
45293
+ function splitCacheControlHeaderValue (value) {
45294
+ const directives = [];
45295
+ let start = 0;
45296
+ let quoteStart = -1;
45297
+ let inQuote = false;
45298
+ let escaped = false;
45299
+
45300
+ for (let i = 0; i < value.length; i++) {
45301
+ if (inQuote) {
45302
+ if (escaped) {
45303
+ escaped = false;
45304
+ } else if (value[i] === '\\') {
45305
+ escaped = true;
45306
+ } else if (value[i] === '"') {
45307
+ inQuote = false;
45308
+ quoteStart = -1;
45309
+ }
45310
+ } else if (value[i] === '"') {
45311
+ inQuote = true;
45312
+ quoteStart = i;
45313
+ } else if (value[i] === ',') {
45314
+ directives.push({ value: value.substring(start, i), fromMalformedQuote: false });
45315
+ start = i + 1;
45316
+ }
45317
+ }
45318
+
45319
+ if (!inQuote) {
45320
+ directives.push({ value: value.substring(start), fromMalformedQuote: false });
45321
+ return directives
45322
+ }
45323
+
45324
+ const tail = value.substring(start);
45325
+ const quoteOffset = quoteStart - start;
45326
+ let tailStart = 0;
45327
+ for (let i = 0; i < tail.length; i++) {
45328
+ if (tail[i] === ',') {
45329
+ directives.push({
45330
+ value: tail.substring(tailStart, i),
45331
+ fromMalformedQuote: tailStart > quoteOffset
45332
+ });
45333
+ tailStart = i + 1;
45334
+ }
45335
+ }
45336
+
45337
+ directives.push({
45338
+ value: tail.substring(tailStart),
45339
+ fromMalformedQuote: tailStart > quoteOffset
45340
+ });
45341
+ return directives
45342
+ }
45343
+
45344
+ function markInvalidCacheControlDirective (directives, key) {
45345
+ let invalidDirectives = directives[kInvalidCacheControlDirectives];
45346
+
45347
+ if (invalidDirectives === undefined) {
45348
+ invalidDirectives = new Set();
45349
+ Object.defineProperty(directives, kInvalidCacheControlDirectives, {
45350
+ value: invalidDirectives
45351
+ });
45352
+ }
45353
+
45354
+ invalidDirectives.add(key);
45355
+ }
45356
+
45357
+ function hasInvalidCacheControlDirective (directives, key) {
45358
+ return directives[kInvalidCacheControlDirectives]?.has(key) === true
45359
+ }
45360
+
45361
+ function getMalformedRestrictiveDirectiveName (key) {
45362
+ for (const directiveName of RESTRICTIVE_DIRECTIVE_NAMES) {
45363
+ if (
45364
+ key.startsWith(directiveName) &&
45365
+ key.length > directiveName.length &&
45366
+ !isValidHTTPToken(key[directiveName.length])
45367
+ ) {
45368
+ return directiveName
45369
+ }
45370
+ }
45371
+
45372
+ let tokenOnlyKey = '';
45373
+ let hasInvalidTokenChar = false;
45374
+ for (let i = 0; i < key.length; i++) {
45375
+ if (isValidHTTPToken(key[i])) {
45376
+ tokenOnlyKey += key[i];
45377
+ } else {
45378
+ hasInvalidTokenChar = true;
45379
+ }
45380
+ }
45381
+
45382
+ if (hasInvalidTokenChar && arrayIncludes(RESTRICTIVE_DIRECTIVE_NAMES, tokenOnlyKey)) {
45383
+ return tokenOnlyKey
45384
+ }
45385
+ }
45386
+
45207
45387
  /**
45208
45388
  * @param {import('../../types/dispatcher.d.ts').default.DispatchOptions} opts
45209
45389
  */
@@ -45226,6 +45406,20 @@ function requireCache$2 () {
45226
45406
  }
45227
45407
  }
45228
45408
 
45409
+ function appendHeader (headers, key, val) {
45410
+ const headerName = key.toLowerCase();
45411
+ const current = headers[headerName];
45412
+ const values = Array.isArray(val) ? val : [val];
45413
+
45414
+ if (current === undefined) {
45415
+ headers[headerName] = Array.isArray(val) ? val.slice() : val;
45416
+ } else if (Array.isArray(current)) {
45417
+ current.push(...values);
45418
+ } else {
45419
+ headers[headerName] = [current, ...values];
45420
+ }
45421
+ }
45422
+
45229
45423
  /**
45230
45424
  * @param {Record<string, string[] | string>}
45231
45425
  * @returns {Record<string, string[] | string>}
@@ -45246,11 +45440,11 @@ function requireCache$2 () {
45246
45440
  if (typeof key !== 'string' || typeof val !== 'string') {
45247
45441
  throw new Error('opts.headers is not a valid header map')
45248
45442
  }
45249
- headers[key.toLowerCase()] = val;
45443
+ appendHeader(headers, key, val);
45250
45444
  }
45251
45445
  } else {
45252
45446
  for (const key of Object.keys(opts.headers)) {
45253
- headers[key.toLowerCase()] = opts.headers[key];
45447
+ appendHeader(headers, key, opts.headers[key]);
45254
45448
  }
45255
45449
  }
45256
45450
  } else {
@@ -45322,29 +45516,37 @@ function requireCache$2 () {
45322
45516
  * @type {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives}
45323
45517
  */
45324
45518
  const output = {};
45519
+ const invalidNumericDirectives = new Set();
45520
+ const invalidNoArgumentDirectives = new Set();
45325
45521
 
45326
- let directives;
45327
- if (Array.isArray(header)) {
45328
- directives = [];
45329
-
45330
- for (const directive of header) {
45331
- directives.push(...directive.split(','));
45332
- }
45333
- } else {
45334
- directives = header.split(',');
45335
- }
45522
+ const directives = splitCacheControlHeaderValue(Array.isArray(header) ? header.join(',') : header);
45336
45523
 
45337
45524
  for (let i = 0; i < directives.length; i++) {
45338
- const directive = directives[i].toLowerCase();
45525
+ const directiveRecord = directives[i];
45526
+ const directive = directiveRecord.value.toLowerCase();
45527
+ const fromMalformedQuote = directiveRecord.fromMalformedQuote;
45339
45528
  const keyValueDelimiter = directive.indexOf('=');
45340
45529
 
45341
45530
  let key;
45342
45531
  let value;
45532
+ let keyHasTrailingWhitespace = false;
45533
+ let valueHasLeadingWhitespace = false;
45343
45534
  if (keyValueDelimiter !== -1) {
45344
- key = directive.substring(0, keyValueDelimiter).trimStart();
45345
- value = directive.substring(keyValueDelimiter + 1);
45535
+ const rawKey = directive.substring(0, keyValueDelimiter);
45536
+ const rawValue = directive.substring(keyValueDelimiter + 1);
45537
+
45538
+ keyHasTrailingWhitespace = trimOWSEnd(rawKey) !== rawKey;
45539
+ valueHasLeadingWhitespace = trimOWSStart(rawValue) !== rawValue;
45540
+ key = trimOWS(rawKey);
45541
+ value = trimOWSStart(rawValue);
45346
45542
  } else {
45347
- key = directive.trim();
45543
+ key = trimOWS(directive);
45544
+ }
45545
+
45546
+ const malformedRestrictiveDirectiveName = getMalformedRestrictiveDirectiveName(key);
45547
+ if (malformedRestrictiveDirectiveName !== undefined) {
45548
+ output[malformedRestrictiveDirectiveName] = true;
45549
+ continue
45348
45550
  }
45349
45551
 
45350
45552
  switch (key) {
@@ -45354,7 +45556,14 @@ function requireCache$2 () {
45354
45556
  case 's-maxage':
45355
45557
  case 'stale-while-revalidate':
45356
45558
  case 'stale-if-error': {
45357
- if (value === undefined || value[0] === ' ') {
45559
+ if (fromMalformedQuote || invalidNumericDirectives.has(key)) {
45560
+ continue
45561
+ }
45562
+
45563
+ if (value === undefined || keyHasTrailingWhitespace || valueHasLeadingWhitespace) {
45564
+ delete output[key];
45565
+ invalidNumericDirectives.add(key);
45566
+ markInvalidCacheControlDirective(output, key);
45358
45567
  continue
45359
45568
  }
45360
45569
 
@@ -45366,22 +45575,37 @@ function requireCache$2 () {
45366
45575
  value = value.substring(1, value.length - 1);
45367
45576
  }
45368
45577
 
45369
- const parsedValue = parseInt(value, 10);
45370
- // eslint-disable-next-line no-self-compare
45371
- if (parsedValue !== parsedValue) {
45578
+ if (!/^[0-9]+$/.test(value)) {
45579
+ delete output[key];
45580
+ invalidNumericDirectives.add(key);
45581
+ markInvalidCacheControlDirective(output, key);
45372
45582
  continue
45373
45583
  }
45374
45584
 
45375
- if (key === 'max-age' && key in output && output[key] >= parsedValue) {
45376
- continue
45377
- }
45585
+ const parsedValue = Math.min(parseInt(value, 10), MAX_DELTA_SECONDS);
45378
45586
 
45379
- output[key] = parsedValue;
45587
+ if (key === 'min-fresh') {
45588
+ if (!(key in output) || output[key] < parsedValue) {
45589
+ output[key] = parsedValue;
45590
+ }
45591
+ } else if (!(key in output) || output[key] > parsedValue) {
45592
+ output[key] = parsedValue;
45593
+ }
45380
45594
 
45381
45595
  break
45382
45596
  }
45383
45597
  case 'private':
45384
45598
  case 'no-cache': {
45599
+ if (fromMalformedQuote) {
45600
+ output[key] = true;
45601
+ break
45602
+ }
45603
+
45604
+ if (value !== undefined && value.length === 0) {
45605
+ output[key] = true;
45606
+ break
45607
+ }
45608
+
45385
45609
  if (value) {
45386
45610
  // The private and no-cache directives can be unqualified (aka just
45387
45611
  // `private` or `no-cache`) or qualified (w/ a value). When they're
@@ -45389,45 +45613,64 @@ function requireCache$2 () {
45389
45613
  // `no-cache="header1"`, or `no-cache="header1, header2"`
45390
45614
  // If we're given multiple headers, the comma messes us up since
45391
45615
  // we split the full header by commas. So, let's loop through the
45392
- // remaining parts in front of us until we find one that ends in a
45393
- // quote. We can then just splice all of the parts in between the
45394
- // starting quote and the ending quote out of the directives array
45395
- // and continue parsing like normal.
45616
+ // remaining parts in front of us until we find one that contains a
45617
+ // closing quote. We can then skip the consumed quoted-list fragments and
45618
+ // continue parsing like normal.
45396
45619
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-no-cache-2
45397
45620
  if (value[0] === '"') {
45398
45621
  // Something like `no-cache="some-header"` OR `no-cache="some-header, another-header"`.
45622
+ value = trimOWSEnd(value);
45399
45623
 
45400
- // Add the first header on and cut off the leading quote
45401
- const headers = [value.substring(1)];
45624
+ let fieldList = '';
45625
+ let lastQuotedPart = i;
45626
+ let foundEndingQuote = false;
45627
+ const closingQuote = findUnescapedQuote(value, 1);
45402
45628
 
45403
- let foundEndingQuote = value[value.length - 1] === '"';
45404
- if (!foundEndingQuote) {
45629
+ if (closingQuote !== -1) {
45630
+ fieldList = value.substring(1, closingQuote);
45631
+ foundEndingQuote = true;
45632
+ } else {
45405
45633
  // Something like `no-cache="some-header, another-header"`
45406
45634
  // This can still be something invalid, e.g. `no-cache="some-header, ...`
45635
+ const fieldListParts = [value.substring(1)];
45636
+
45407
45637
  for (let j = i + 1; j < directives.length; j++) {
45408
- const nextPart = directives[j];
45409
- const nextPartLength = nextPart.length;
45638
+ const nextPart = trimOWS(directives[j].value);
45639
+ const closingQuote = findUnescapedQuote(nextPart, 0);
45410
45640
 
45411
- headers.push(nextPart.trim());
45641
+ lastQuotedPart = j;
45412
45642
 
45413
- if (nextPartLength !== 0 && nextPart[nextPartLength - 1] === '"') {
45643
+ if (closingQuote !== -1) {
45644
+ fieldListParts.push(nextPart.substring(0, closingQuote));
45414
45645
  foundEndingQuote = true;
45415
45646
  break
45416
45647
  }
45648
+
45649
+ fieldListParts.push(nextPart);
45417
45650
  }
45651
+
45652
+ fieldList = fieldListParts.join(',');
45418
45653
  }
45419
45654
 
45420
- if (foundEndingQuote) {
45421
- let lastHeader = headers[headers.length - 1];
45422
- if (lastHeader[lastHeader.length - 1] === '"') {
45423
- lastHeader = lastHeader.substring(0, lastHeader.length - 1);
45424
- headers[headers.length - 1] = lastHeader;
45425
- }
45655
+ if (!foundEndingQuote) {
45656
+ output[key] = true;
45657
+ break
45658
+ }
45426
45659
 
45427
- for (let j = 0; j < headers.length; j++) {
45428
- headers[j] = headers[j].trim();
45660
+ i = lastQuotedPart;
45661
+
45662
+ const headers = fieldList.split(',');
45663
+ let validFieldNames = true;
45664
+ for (let j = 0; j < headers.length; j++) {
45665
+ headers[j] = trimOWS(headers[j]);
45666
+ if (!isValidHTTPToken(headers[j])) {
45667
+ validFieldNames = false;
45429
45668
  }
45669
+ }
45430
45670
 
45671
+ if (!validFieldNames) {
45672
+ output[key] = true;
45673
+ } else if (output[key] !== true) {
45431
45674
  if (key in output) {
45432
45675
  output[key] = output[key].concat(headers);
45433
45676
  } else {
@@ -45435,13 +45678,17 @@ function requireCache$2 () {
45435
45678
  }
45436
45679
  }
45437
45680
  } else {
45438
- // Something like `no-cache="some-header"`
45439
- const fieldName = value.trim();
45681
+ // Something like `no-cache=some-header`
45682
+ const fieldName = trimOWS(value);
45440
45683
 
45441
- if (key in output) {
45442
- output[key] = output[key].concat(fieldName);
45443
- } else {
45444
- output[key] = [fieldName];
45684
+ if (!isValidHTTPToken(fieldName)) {
45685
+ output[key] = true;
45686
+ } else if (output[key] !== true) {
45687
+ if (key in output) {
45688
+ output[key] = output[key].concat(fieldName);
45689
+ } else {
45690
+ output[key] = [fieldName];
45691
+ }
45445
45692
  }
45446
45693
  }
45447
45694
 
@@ -45450,19 +45697,27 @@ function requireCache$2 () {
45450
45697
  }
45451
45698
  // eslint-disable-next-line no-fallthrough
45452
45699
  case 'public':
45453
- case 'no-store':
45454
45700
  case 'must-revalidate':
45455
45701
  case 'proxy-revalidate':
45456
45702
  case 'immutable':
45457
45703
  case 'no-transform':
45458
45704
  case 'must-understand':
45459
45705
  case 'only-if-cached':
45460
- if (value) {
45706
+ if (fromMalformedQuote || invalidNoArgumentDirectives.has(key)) {
45707
+ continue
45708
+ }
45709
+
45710
+ if (value !== undefined) {
45461
45711
  // These are qualified (something like `public=...`) when they aren't
45462
- // allowed to be, skip
45712
+ // allowed to be, skip all instances of the malformed directive.
45713
+ delete output[key];
45714
+ invalidNoArgumentDirectives.add(key);
45463
45715
  continue
45464
45716
  }
45465
45717
 
45718
+ output[key] = true;
45719
+ break
45720
+ case 'no-store':
45466
45721
  output[key] = true;
45467
45722
  break
45468
45723
  default:
@@ -45474,31 +45729,79 @@ function requireCache$2 () {
45474
45729
  return output
45475
45730
  }
45476
45731
 
45732
+ /**
45733
+ * @param {string | string[]} varyHeader Vary header from the server
45734
+ * @returns {string[]}
45735
+ */
45736
+ function splitVaryHeader (varyHeader) {
45737
+ const values = Array.isArray(varyHeader) ? varyHeader : [varyHeader];
45738
+ const output = [];
45739
+
45740
+ for (let i = 0; i < values.length; i++) {
45741
+ const parts = values[i].split(',');
45742
+ for (let j = 0; j < parts.length; j++) {
45743
+ output.push(parts[j]);
45744
+ }
45745
+ }
45746
+
45747
+ return output
45748
+ }
45749
+
45750
+ /**
45751
+ * @param {string | string[]} varyHeader Vary header from the server
45752
+ * @returns {boolean}
45753
+ */
45754
+ function hasVaryStar (varyHeader) {
45755
+ const values = splitVaryHeader(varyHeader);
45756
+ for (let i = 0; i < values.length; i++) {
45757
+ if (trimOWS(values[i]).indexOf('*') !== -1) {
45758
+ return true
45759
+ }
45760
+ }
45761
+
45762
+ return false
45763
+ }
45764
+
45477
45765
  /**
45478
45766
  * @param {string | string[]} varyHeader Vary header from the server
45479
45767
  * @param {Record<string, string | string[]>} headers Request headers
45480
- * @returns {Record<string, string | string[]>}
45768
+ * @returns {Record<string, string | string[] | null> | undefined}
45481
45769
  */
45482
45770
  function parseVaryHeader (varyHeader, headers) {
45483
- if (typeof varyHeader === 'string' && varyHeader.includes('*')) {
45771
+ if (hasVaryStar(varyHeader)) {
45484
45772
  return headers
45485
45773
  }
45486
45774
 
45487
45775
  const output = /** @type {Record<string, string | string[] | null>} */ ({});
45488
45776
 
45489
- const varyingHeaders = typeof varyHeader === 'string'
45490
- ? varyHeader.split(',')
45491
- : varyHeader;
45777
+ const varyingHeaders = splitVaryHeader(varyHeader);
45492
45778
 
45493
45779
  for (const header of varyingHeaders) {
45494
- const trimmedHeader = header.trim().toLowerCase();
45780
+ const trimmedHeader = trimOWS(header).toLowerCase();
45781
+
45782
+ if (trimmedHeader.length === 0) {
45783
+ continue
45784
+ }
45785
+
45786
+ if (!isValidHTTPToken(trimmedHeader)) {
45787
+ return undefined
45788
+ }
45495
45789
 
45496
- output[trimmedHeader] = headers[trimmedHeader] ?? null;
45790
+ const headerValue = headers[trimmedHeader];
45791
+ output[trimmedHeader] = Array.isArray(headerValue) ? headerValue.slice() : headerValue ?? null;
45497
45792
  }
45498
45793
 
45499
45794
  return output
45500
45795
  }
45501
45796
 
45797
+ /**
45798
+ * @param {string | string[]} varyHeader Vary header from the server
45799
+ * @returns {boolean}
45800
+ */
45801
+ function isInvalidOrWildcardVaryHeader (varyHeader) {
45802
+ return hasVaryStar(varyHeader) || parseVaryHeader(varyHeader, {}) === undefined
45803
+ }
45804
+
45502
45805
  /**
45503
45806
  * Note: this deviates from the spec a little. Empty etags ("", W/"") are valid,
45504
45807
  * however, including them in cached resposnes serves little to no purpose.
@@ -45562,7 +45865,7 @@ function requireCache$2 () {
45562
45865
  }
45563
45866
 
45564
45867
  for (const method of methods) {
45565
- if (!safeHTTPMethods.includes(method)) {
45868
+ if (!arrayIncludes(safeHTTPMethods, method)) {
45566
45869
  throw new TypeError(`element of ${name}-array needs to be one of following values: ${safeHTTPMethods.join(', ')}, got ${method}`)
45567
45870
  }
45568
45871
  }
@@ -45602,7 +45905,10 @@ function requireCache$2 () {
45602
45905
  assertCacheKey,
45603
45906
  assertCacheValue,
45604
45907
  parseCacheControlHeader,
45908
+ hasInvalidCacheControlDirective,
45605
45909
  parseVaryHeader,
45910
+ hasVaryStar,
45911
+ isInvalidOrWildcardVaryHeader,
45606
45912
  isEtagUsable,
45607
45913
  assertCacheMethods,
45608
45914
  assertCacheStore,
@@ -45636,6 +45942,26 @@ function requireDate () {
45636
45942
  }
45637
45943
  }
45638
45944
 
45945
+ function makeDate (year, monthIdx, day, hour, minute, second, weekday) {
45946
+ const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second));
45947
+
45948
+ // Date.UTC treats years 0-99 as 1900-1999. Reset the full year so component
45949
+ // checks below validate the HTTP date as written.
45950
+ if (year >= 0 && year <= 99) {
45951
+ result.setUTCFullYear(year);
45952
+ }
45953
+
45954
+ return result.getUTCFullYear() === year &&
45955
+ result.getUTCMonth() === monthIdx &&
45956
+ result.getUTCDate() === day &&
45957
+ result.getUTCHours() === hour &&
45958
+ result.getUTCMinutes() === minute &&
45959
+ result.getUTCSeconds() === second &&
45960
+ result.getUTCDay() === weekday
45961
+ ? result
45962
+ : undefined
45963
+ }
45964
+
45639
45965
  /**
45640
45966
  * @see https://httpwg.org/specs/rfc9110.html#preferred.date.format
45641
45967
  *
@@ -45842,8 +46168,7 @@ function requireDate () {
45842
46168
  second = (code1 - 48) * 10 + (code2 - 48); // Convert ASCII codes to number
45843
46169
  }
45844
46170
 
45845
- const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second));
45846
- return result.getUTCDay() === weekday ? result : undefined
46171
+ return makeDate(year, monthIdx, day, hour, minute, second, weekday)
45847
46172
  }
45848
46173
 
45849
46174
  /**
@@ -46047,8 +46372,7 @@ function requireDate () {
46047
46372
  }
46048
46373
  const year = (yearDigit1 - 48) * 1000 + (yearDigit2 - 48) * 100 + (yearDigit3 - 48) * 10 + (yearDigit4 - 48);
46049
46374
 
46050
- const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second));
46051
- return result.getUTCDay() === weekday ? result : undefined
46375
+ return makeDate(year, monthIdx, day, hour, minute, second, weekday)
46052
46376
  }
46053
46377
 
46054
46378
  /**
@@ -46262,8 +46586,7 @@ function requireDate () {
46262
46586
  second = (code1 - 48) * 10 + (code2 - 48); // Convert ASCII codes to number
46263
46587
  }
46264
46588
 
46265
- const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second));
46266
- return result.getUTCDay() === weekday ? result : undefined
46589
+ return makeDate(year, monthIdx, day, hour, minute, second, weekday)
46267
46590
  }
46268
46591
 
46269
46592
  date = {
@@ -46282,7 +46605,10 @@ function requireCacheHandler () {
46282
46605
  const util = requireUtil$7();
46283
46606
  const {
46284
46607
  parseCacheControlHeader,
46608
+ hasInvalidCacheControlDirective,
46285
46609
  parseVaryHeader,
46610
+ hasVaryStar,
46611
+ isInvalidOrWildcardVaryHeader,
46286
46612
  isEtagUsable
46287
46613
  } = requireCache$2();
46288
46614
  const { parseHttpDate } = requireDate();
@@ -46305,6 +46631,92 @@ function requireCacheHandler () {
46305
46631
 
46306
46632
  const MAX_RESPONSE_AGE = 2147483647000;
46307
46633
 
46634
+ function trimOWS (value) {
46635
+ return value.replace(/^[\t ]+|[\t ]+$/g, '')
46636
+ }
46637
+
46638
+ function arrayIncludes (array, value) {
46639
+ for (let i = 0; i < array.length; i++) {
46640
+ if (array[i] === value) {
46641
+ return true
46642
+ }
46643
+ }
46644
+
46645
+ return false
46646
+ }
46647
+
46648
+ function appendConnectionHeaderTokens (headersToRemove, connectionHeader) {
46649
+ const values = Array.isArray(connectionHeader) ? connectionHeader : [connectionHeader];
46650
+
46651
+ for (let i = 0; i < values.length; i++) {
46652
+ const tokens = values[i].split(',');
46653
+ for (let j = 0; j < tokens.length; j++) {
46654
+ headersToRemove.push(trimOWS(tokens[j]).toLowerCase());
46655
+ }
46656
+ }
46657
+ }
46658
+
46659
+ function getSameOriginPath (cacheKey, location) {
46660
+ if (typeof location !== 'string') {
46661
+ return undefined
46662
+ }
46663
+
46664
+ let originUrl;
46665
+ let requestUrl;
46666
+ let locationUrl;
46667
+ try {
46668
+ originUrl = new URL(cacheKey.origin);
46669
+ requestUrl = new URL(cacheKey.path, originUrl);
46670
+ locationUrl = new URL(location, requestUrl);
46671
+ } catch {
46672
+ return undefined
46673
+ }
46674
+
46675
+ if (locationUrl.origin !== originUrl.origin) {
46676
+ return undefined
46677
+ }
46678
+
46679
+ return locationUrl.pathname + locationUrl.search
46680
+ }
46681
+
46682
+ function deleteCachedUri (store, cacheKey, path) {
46683
+ deleteCachedValue(store, {
46684
+ ...cacheKey,
46685
+ path
46686
+ });
46687
+
46688
+ for (let i = 0; i < util.safeHTTPMethods.length; i++) {
46689
+ const method = util.safeHTTPMethods[i];
46690
+ if (method !== cacheKey.method) {
46691
+ deleteCachedValue(store, {
46692
+ ...cacheKey,
46693
+ method,
46694
+ path
46695
+ });
46696
+ }
46697
+ }
46698
+ }
46699
+
46700
+ function deleteLocationTargets (store, cacheKey, headerValue) {
46701
+ if (headerValue === undefined) {
46702
+ return
46703
+ }
46704
+
46705
+ const values = Array.isArray(headerValue) ? headerValue : [headerValue];
46706
+ for (let i = 0; i < values.length; i++) {
46707
+ const path = getSameOriginPath(cacheKey, values[i]);
46708
+ if (path !== undefined) {
46709
+ deleteCachedUri(store, cacheKey, path);
46710
+ }
46711
+ }
46712
+ }
46713
+
46714
+ function invalidateUnsafeRequest (store, cacheKey, resHeaders) {
46715
+ deleteCachedUri(store, cacheKey, cacheKey.path);
46716
+ deleteLocationTargets(store, cacheKey, resHeaders.location);
46717
+ deleteLocationTargets(store, cacheKey, resHeaders['content-location']);
46718
+ }
46719
+
46308
46720
  /**
46309
46721
  * @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandler} DispatchHandler
46310
46722
  *
@@ -46386,28 +46798,28 @@ function requireCacheHandler () {
46386
46798
  const handler = this;
46387
46799
 
46388
46800
  if (
46389
- !util.safeHTTPMethods.includes(this.#cacheKey.method) &&
46801
+ !arrayIncludes(util.safeHTTPMethods, this.#cacheKey.method) &&
46390
46802
  statusCode >= 200 &&
46391
46803
  statusCode <= 399
46392
46804
  ) {
46393
46805
  // Successful response to an unsafe method, delete it from cache
46394
46806
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-response
46395
- try {
46396
- this.#store.delete(this.#cacheKey)?.catch?.(noop);
46397
- } catch {
46398
- // Fail silently
46399
- }
46807
+ invalidateUnsafeRequest(this.#store, this.#cacheKey, resHeaders);
46400
46808
  return downstreamOnHeaders()
46401
46809
  }
46402
46810
 
46403
46811
  const cacheControlHeader = resHeaders['cache-control'];
46404
- const heuristicallyCacheable = resHeaders['last-modified'] && HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode);
46812
+ const heuristicallyCacheable = resHeaders['last-modified'] && arrayIncludes(HEURISTICALLY_CACHEABLE_STATUS_CODES, statusCode);
46405
46813
  if (
46406
46814
  !cacheControlHeader &&
46407
46815
  !resHeaders['expires'] &&
46408
46816
  !heuristicallyCacheable &&
46409
46817
  !this.#cacheByDefault
46410
46818
  ) {
46819
+ if (statusCode === 304 && resHeaders.vary && isInvalidOrWildcardVaryHeader(resHeaders.vary)) {
46820
+ deleteCachedValue(this.#store, this.#cacheKey);
46821
+ }
46822
+
46411
46823
  // Don't have anything to tell us this response is cachable and we're not
46412
46824
  // caching by default
46413
46825
  return downstreamOnHeaders()
@@ -46415,31 +46827,46 @@ function requireCacheHandler () {
46415
46827
 
46416
46828
  const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {};
46417
46829
  if (!canCacheResponse(this.#cacheType, statusCode, resHeaders, cacheControlDirectives, this.#cacheKey.headers)) {
46830
+ if (statusCode === 304 && (cacheControlHeader || revalidationResponseDisallowsCachedReuse(this.#cacheType, resHeaders, cacheControlDirectives))) {
46831
+ deleteCachedValue(this.#store, this.#cacheKey);
46832
+ }
46833
+
46418
46834
  return downstreamOnHeaders()
46419
46835
  }
46420
46836
 
46421
46837
  const now = Date.now();
46422
- const resAge = resHeaders.age ? getAge(resHeaders.age) : undefined;
46423
- if (resAge && resAge >= MAX_RESPONSE_AGE) {
46838
+ const resAge = Object.hasOwn(resHeaders, 'age') ? getAge(resHeaders.age) : undefined;
46839
+ if (resAge !== undefined && resAge >= MAX_RESPONSE_AGE) {
46424
46840
  // Response considered stale
46841
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey);
46425
46842
  return downstreamOnHeaders()
46426
46843
  }
46427
46844
 
46428
- const resDate = typeof resHeaders.date === 'string'
46429
- ? parseHttpDate(resHeaders.date)
46430
- : undefined;
46845
+ const resDate = Object.hasOwn(resHeaders, 'date') ? getDate(resHeaders.date) : undefined;
46846
+ if (resDate === null) {
46847
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey);
46848
+ return downstreamOnHeaders()
46849
+ }
46850
+
46851
+ const apparentAge = resDate ? Math.max(0, now - resDate.getTime()) : 0;
46852
+ const currentAge = Math.max(apparentAge, resAge ?? 0);
46431
46853
 
46432
46854
  const staleAt =
46433
46855
  determineStaleAt(this.#cacheType, now, resAge, resHeaders, resDate, cacheControlDirectives) ??
46434
46856
  this.#cacheByDefault;
46435
- if (staleAt === undefined || (resAge && resAge > staleAt)) {
46857
+ if (staleAt === undefined || currentAge >= staleAt) {
46858
+ if (cacheControlHeader || staleAt !== undefined) {
46859
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey);
46860
+ }
46861
+
46436
46862
  return downstreamOnHeaders()
46437
46863
  }
46438
46864
 
46439
- const baseTime = resDate ? resDate.getTime() : now;
46865
+ const baseTime = now - currentAge;
46440
46866
  const absoluteStaleAt = staleAt + baseTime;
46441
46867
  if (now >= absoluteStaleAt) {
46442
46868
  // Response is already stale
46869
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey);
46443
46870
  return downstreamOnHeaders()
46444
46871
  }
46445
46872
 
@@ -46452,7 +46879,8 @@ function requireCacheHandler () {
46452
46879
  }
46453
46880
  }
46454
46881
 
46455
- const deleteAt = determineDeleteAt(baseTime, cacheControlDirectives, absoluteStaleAt);
46882
+ const cachedAt = baseTime;
46883
+ const deleteAt = determineDeleteAt(baseTime, now, cacheControlDirectives, absoluteStaleAt);
46456
46884
  const strippedHeaders = stripNecessaryHeaders(resHeaders, cacheControlDirectives);
46457
46885
 
46458
46886
  /**
@@ -46464,7 +46892,7 @@ function requireCacheHandler () {
46464
46892
  headers: strippedHeaders,
46465
46893
  vary: varyDirectives,
46466
46894
  cacheControlDirectives,
46467
- cachedAt: resAge ? now - resAge : now,
46895
+ cachedAt,
46468
46896
  staleAt: absoluteStaleAt,
46469
46897
  deleteAt
46470
46898
  };
@@ -46482,6 +46910,7 @@ function requireCacheHandler () {
46482
46910
  value.statusCode = cachedValue.statusCode;
46483
46911
  value.statusMessage = cachedValue.statusMessage;
46484
46912
  value.etag = cachedValue.etag;
46913
+ value.vary = varyDirectives ?? cachedValue.vary;
46485
46914
  value.headers = { ...cachedValue.headers, ...strippedHeaders };
46486
46915
 
46487
46916
  downstreamOnHeaders();
@@ -46612,6 +47041,36 @@ function requireCacheHandler () {
46612
47041
  }
46613
47042
  }
46614
47043
 
47044
+ /**
47045
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheStore} store
47046
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
47047
+ */
47048
+ function deleteCachedValue (store, cacheKey) {
47049
+ try {
47050
+ store.delete(cacheKey)?.catch?.(noop);
47051
+ } catch {
47052
+ // Fail silently
47053
+ }
47054
+ }
47055
+
47056
+ function deleteCachedValueIfNotModified (statusCode, store, cacheKey) {
47057
+ if (statusCode === 304) {
47058
+ deleteCachedValue(store, cacheKey);
47059
+ }
47060
+ }
47061
+
47062
+ /**
47063
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
47064
+ * @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
47065
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
47066
+ * @returns {boolean}
47067
+ */
47068
+ function revalidationResponseDisallowsCachedReuse (cacheType, resHeaders, cacheControlDirectives) {
47069
+ return cacheControlDirectives['no-store'] === true ||
47070
+ (cacheType === 'shared' && cacheControlDirectives.private === true) ||
47071
+ (resHeaders.vary ? isInvalidOrWildcardVaryHeader(resHeaders.vary) : false)
47072
+ }
47073
+
46615
47074
  /**
46616
47075
  * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
46617
47076
  *
@@ -46623,12 +47082,12 @@ function requireCacheHandler () {
46623
47082
  */
46624
47083
  function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives, reqHeaders) {
46625
47084
  // Status code must be final and understood.
46626
- if (statusCode < 200 || NOT_UNDERSTOOD_STATUS_CODES.includes(statusCode)) {
47085
+ if (statusCode < 200 || arrayIncludes(NOT_UNDERSTOOD_STATUS_CODES, statusCode)) {
46627
47086
  return false
46628
47087
  }
46629
47088
  // Responses with neither status codes that are heuristically cacheable, nor "explicit enough" caching
46630
47089
  // directives, are not cacheable. "Explicit enough": see https://www.rfc-editor.org/rfc/rfc9111.html#section-3
46631
- if (!HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode) && !resHeaders['expires'] &&
47090
+ if (!arrayIncludes(HEURISTICALLY_CACHEABLE_STATUS_CODES, statusCode) && !resHeaders['expires'] &&
46632
47091
  !cacheControlDirectives.public &&
46633
47092
  cacheControlDirectives['max-age'] === undefined &&
46634
47093
  // RFC 9111: a private response directive, if the cache is not shared
@@ -46647,12 +47106,12 @@ function requireCacheHandler () {
46647
47106
  }
46648
47107
 
46649
47108
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1-5
46650
- if (resHeaders.vary?.includes('*')) {
47109
+ if (resHeaders.vary && hasVaryStar(resHeaders.vary)) {
46651
47110
  return false
46652
47111
  }
46653
47112
 
46654
47113
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
46655
- if (reqHeaders?.authorization) {
47114
+ if (reqHeaders != null && Object.hasOwn(reqHeaders, 'authorization')) {
46656
47115
  if (
46657
47116
  !cacheControlDirectives.public &&
46658
47117
  !cacheControlDirectives['s-maxage'] &&
@@ -46667,14 +47126,14 @@ function requireCacheHandler () {
46667
47126
 
46668
47127
  if (
46669
47128
  Array.isArray(cacheControlDirectives['no-cache']) &&
46670
- cacheControlDirectives['no-cache'].includes('authorization')
47129
+ arrayIncludes(cacheControlDirectives['no-cache'], 'authorization')
46671
47130
  ) {
46672
47131
  return false
46673
47132
  }
46674
47133
 
46675
47134
  if (
46676
47135
  Array.isArray(cacheControlDirectives['private']) &&
46677
- cacheControlDirectives['private'].includes('authorization')
47136
+ arrayIncludes(cacheControlDirectives['private'], 'authorization')
46678
47137
  ) {
46679
47138
  return false
46680
47139
  }
@@ -46683,14 +47142,51 @@ function requireCacheHandler () {
46683
47142
  return true
46684
47143
  }
46685
47144
 
47145
+ /**
47146
+ * @param {string | string[]} dateHeader
47147
+ * @returns {Date | null | undefined}
47148
+ */
47149
+ function getDate (dateHeader) {
47150
+ let dateValue = dateHeader;
47151
+ if (Array.isArray(dateValue)) {
47152
+ if (dateValue.length !== 1) {
47153
+ return null
47154
+ }
47155
+
47156
+ dateValue = dateValue[0];
47157
+ }
47158
+
47159
+ if (typeof dateValue !== 'string') {
47160
+ return null
47161
+ }
47162
+
47163
+ return parseHttpDate(dateValue)
47164
+ }
47165
+
46686
47166
  /**
46687
47167
  * @param {string | string[]} ageHeader
46688
47168
  * @returns {number | undefined}
46689
47169
  */
46690
47170
  function getAge (ageHeader) {
46691
- const age = parseInt(Array.isArray(ageHeader) ? ageHeader[0] : ageHeader);
47171
+ let ageValue = ageHeader;
47172
+ if (Array.isArray(ageValue)) {
47173
+ if (ageValue.length !== 1) {
47174
+ return MAX_RESPONSE_AGE
47175
+ }
47176
+
47177
+ ageValue = ageValue[0];
47178
+ }
47179
+
47180
+ if (typeof ageValue !== 'string' || !/^[\t ]*[0-9]+[\t ]*$/.test(ageValue)) {
47181
+ return MAX_RESPONSE_AGE
47182
+ }
47183
+
47184
+ const age = BigInt(ageValue.replace(/^[\t ]+|[\t ]+$/g, ''));
47185
+ if (age >= BigInt(MAX_RESPONSE_AGE / 1000)) {
47186
+ return MAX_RESPONSE_AGE
47187
+ }
46692
47188
 
46693
- return isNaN(age) ? undefined : age * 1000
47189
+ return Number(age) * 1000
46694
47190
  }
46695
47191
 
46696
47192
  /**
@@ -46708,43 +47204,60 @@ function requireCacheHandler () {
46708
47204
  // Prioritize s-maxage since we're a shared cache
46709
47205
  // s-maxage > max-age > Expire
46710
47206
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10-3
47207
+ if (hasInvalidCacheControlDirective(cacheControlDirectives, 's-maxage')) {
47208
+ return 0
47209
+ }
47210
+
46711
47211
  const sMaxAge = cacheControlDirectives['s-maxage'];
46712
47212
  if (sMaxAge !== undefined) {
46713
- return sMaxAge > 0 ? sMaxAge * 1000 : undefined
47213
+ return sMaxAge * 1000
46714
47214
  }
46715
47215
  }
46716
47216
 
47217
+ if (hasInvalidCacheControlDirective(cacheControlDirectives, 'max-age')) {
47218
+ return 0
47219
+ }
47220
+
46717
47221
  const maxAge = cacheControlDirectives['max-age'];
46718
47222
  if (maxAge !== undefined) {
46719
- return maxAge > 0 ? maxAge * 1000 : undefined
47223
+ return maxAge * 1000
46720
47224
  }
46721
47225
 
46722
- if (typeof resHeaders.expires === 'string') {
47226
+ if (Object.hasOwn(resHeaders, 'expires')) {
46723
47227
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
47228
+ if (typeof resHeaders.expires !== 'string') {
47229
+ return 0
47230
+ }
47231
+
46724
47232
  const expiresDate = parseHttpDate(resHeaders.expires);
46725
- if (expiresDate) {
46726
- if (now >= expiresDate.getTime()) {
46727
- return undefined
46728
- }
47233
+ if (!expiresDate) {
47234
+ return 0
47235
+ }
46729
47236
 
46730
- if (responseDate) {
46731
- if (responseDate >= expiresDate) {
46732
- return undefined
46733
- }
47237
+ if (now >= expiresDate.getTime()) {
47238
+ return 0
47239
+ }
46734
47240
 
46735
- if (age !== undefined && age > (expiresDate - responseDate)) {
46736
- return undefined
46737
- }
47241
+ if (responseDate) {
47242
+ if (responseDate >= expiresDate) {
47243
+ return 0
47244
+ }
47245
+
47246
+ const freshnessLifetime = expiresDate.getTime() - responseDate.getTime();
47247
+ if (age !== undefined && age >= freshnessLifetime) {
47248
+ return 0
46738
47249
  }
46739
47250
 
46740
- return expiresDate.getTime() - now
47251
+ return freshnessLifetime
46741
47252
  }
47253
+
47254
+ return expiresDate.getTime() - now
46742
47255
  }
46743
47256
 
46744
47257
  if (typeof resHeaders['last-modified'] === 'string') {
46745
47258
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-heuristic-fresh
46746
- const lastModified = new Date(resHeaders['last-modified']);
46747
- if (isValidDate(lastModified)) {
47259
+ const lastModified = parseHttpDate(resHeaders['last-modified']);
47260
+ if (lastModified) {
46748
47261
  if (lastModified.getTime() >= now) {
46749
47262
  return undefined
46750
47263
  }
@@ -46757,18 +47270,19 @@ function requireCacheHandler () {
46757
47270
 
46758
47271
  if (cacheControlDirectives.immutable) {
46759
47272
  // https://www.rfc-editor.org/rfc/rfc8246.html#section-2.2
46760
- return 31536000
47273
+ return 31536000000
46761
47274
  }
46762
47275
 
46763
47276
  return undefined
46764
47277
  }
46765
47278
 
46766
47279
  /**
46767
- * @param {number} now
47280
+ * @param {number} baseTime
47281
+ * @param {number} cachedAt
46768
47282
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
46769
47283
  * @param {number} staleAt
46770
47284
  */
46771
- function determineDeleteAt (now, cacheControlDirectives, staleAt) {
47285
+ function determineDeleteAt (baseTime, cachedAt, cacheControlDirectives, staleAt) {
46772
47286
  let staleWhileRevalidate = -Infinity;
46773
47287
  let staleIfError = -Infinity;
46774
47288
  let immutable = -Infinity;
@@ -46782,15 +47296,21 @@ function requireCacheHandler () {
46782
47296
  }
46783
47297
 
46784
47298
  if (cacheControlDirectives.immutable && staleWhileRevalidate === -Infinity && staleIfError === -Infinity) {
46785
- immutable = now + 31536000000;
47299
+ immutable = cachedAt + 31536000000;
46786
47300
  }
46787
47301
 
46788
47302
  // When no stale directives or immutable flag, add a revalidation buffer
46789
47303
  // equal to the freshness lifetime so the entry survives past staleAt long
46790
47304
  // enough to be revalidated instead of silently disappearing.
47305
+ //
47306
+ // Response Date headers only have second precision, so baseTime can trail the
47307
+ // actual cache insertion time by up to ~1s. Pad the buffer by that bounded
47308
+ // skew so short-lived entries do not disappear exactly when they should be
47309
+ // revalidated.
46791
47310
  if (staleWhileRevalidate === -Infinity && staleIfError === -Infinity && immutable === -Infinity) {
46792
- const freshnessLifetime = staleAt - now;
46793
- return staleAt + freshnessLifetime
47311
+ const freshnessLifetime = staleAt - baseTime;
47312
+ const datePrecisionPadding = Math.min(Math.max(cachedAt - baseTime, 0), 1000);
47313
+ return staleAt + freshnessLifetime + datePrecisionPadding
46794
47314
  }
46795
47315
 
46796
47316
  return Math.max(staleAt, staleWhileRevalidate, staleIfError, immutable)
@@ -46817,14 +47337,7 @@ function requireCacheHandler () {
46817
47337
  ];
46818
47338
 
46819
47339
  if (resHeaders['connection']) {
46820
- if (Array.isArray(resHeaders['connection'])) {
46821
- // connection: a
46822
- // connection: b
46823
- headersToRemove.push(...resHeaders['connection'].map(header => header.trim()));
46824
- } else {
46825
- // connection: a, b
46826
- headersToRemove.push(...resHeaders['connection'].split(',').map(header => header.trim()));
46827
- }
47340
+ appendConnectionHeaderTokens(headersToRemove, resHeaders['connection']);
46828
47341
  }
46829
47342
 
46830
47343
  if (Array.isArray(cacheControlDirectives['no-cache'])) {
@@ -46837,7 +47350,7 @@ function requireCacheHandler () {
46837
47350
 
46838
47351
  let strippedHeaders;
46839
47352
  for (const headerName of headersToRemove) {
46840
- if (resHeaders[headerName]) {
47353
+ if (Object.hasOwn(resHeaders, headerName)) {
46841
47354
  strippedHeaders ??= { ...resHeaders };
46842
47355
  delete strippedHeaders[headerName];
46843
47356
  }
@@ -46846,14 +47359,6 @@ function requireCacheHandler () {
46846
47359
  return strippedHeaders ?? resHeaders
46847
47360
  }
46848
47361
 
46849
- /**
46850
- * @param {Date} date
46851
- * @returns {boolean}
46852
- */
46853
- function isValidDate (date) {
46854
- return date instanceof Date && Number.isFinite(date.valueOf())
46855
- }
46856
-
46857
47362
  cacheHandler = CacheHandler;
46858
47363
  return cacheHandler;
46859
47364
  }
@@ -47083,17 +47588,62 @@ function requireMemoryCacheStore () {
47083
47588
  }
47084
47589
 
47085
47590
  function findEntry (key, entries, now) {
47086
- return entries.find((entry) => (
47087
- entry.deleteAt > now &&
47088
- entry.method === key.method &&
47089
- (entry.vary == null || Object.keys(entry.vary).every(headerName => {
47090
- if (entry.vary[headerName] === null) {
47091
- return key.headers[headerName] === undefined
47591
+ for (let i = 0; i < entries.length; i++) {
47592
+ const entry = entries[i];
47593
+ if (
47594
+ entry.deleteAt > now &&
47595
+ entry.method === key.method &&
47596
+ varyMatches(key, entry)
47597
+ ) {
47598
+ return entry
47599
+ }
47600
+ }
47601
+ }
47602
+
47603
+ function varyMatches (key, entry) {
47604
+ if (entry.vary == null) {
47605
+ return true
47606
+ }
47607
+
47608
+ for (const headerName in entry.vary) {
47609
+ if (Object.hasOwn(entry.vary, headerName) && !headerValueEquals(key.headers?.[headerName], entry.vary[headerName])) {
47610
+ return false
47611
+ }
47612
+ }
47613
+
47614
+ return true
47615
+ }
47616
+
47617
+ /**
47618
+ * @param {string|string[]|null|undefined} lhs
47619
+ * @param {string|string[]|null|undefined} rhs
47620
+ * @returns {boolean}
47621
+ */
47622
+ function headerValueEquals (lhs, rhs) {
47623
+ if (lhs == null && rhs == null) {
47624
+ return true
47625
+ }
47626
+
47627
+ if ((lhs == null && rhs != null) ||
47628
+ (lhs != null && rhs == null)) {
47629
+ return false
47630
+ }
47631
+
47632
+ if (Array.isArray(lhs) && Array.isArray(rhs)) {
47633
+ if (lhs.length !== rhs.length) {
47634
+ return false
47635
+ }
47636
+
47637
+ for (let i = 0; i < lhs.length; i++) {
47638
+ if (lhs[i] !== rhs[i]) {
47639
+ return false
47092
47640
  }
47641
+ }
47093
47642
 
47094
- return entry.vary[headerName] === key.headers[headerName]
47095
- }))
47096
- ))
47643
+ return true
47644
+ }
47645
+
47646
+ return lhs === rhs
47097
47647
  }
47098
47648
 
47099
47649
  memoryCacheStore = MemoryCacheStore;
@@ -47126,7 +47676,7 @@ function requireCacheRevalidationHandler () {
47126
47676
  #successful = false
47127
47677
 
47128
47678
  /**
47129
- * @type {((boolean, any) => void) | null}
47679
+ * @type {((success: boolean, context?: any, statusCode?: number, headers?: import('../../types/header.d.ts').IncomingHttpHeaders) => void) | null}
47130
47680
  */
47131
47681
  #callback
47132
47682
 
@@ -47143,7 +47693,7 @@ function requireCacheRevalidationHandler () {
47143
47693
  #allowErrorStatusCodes
47144
47694
 
47145
47695
  /**
47146
- * @param {(boolean) => void} callback Function to call if the cached value is valid
47696
+ * @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
47147
47697
  * @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
47148
47698
  * @param {boolean} allowErrorStatusCodes
47149
47699
  */
@@ -47178,7 +47728,7 @@ function requireCacheRevalidationHandler () {
47178
47728
  // https://datatracker.ietf.org/doc/html/rfc5861#section-4
47179
47729
  this.#successful = statusCode === 304 ||
47180
47730
  (this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504);
47181
- this.#callback(this.#successful, this.#context);
47731
+ this.#callback(this.#successful, this.#context, statusCode, headers);
47182
47732
  this.#callback = null;
47183
47733
 
47184
47734
  if (this.#successful) {
@@ -47245,8 +47795,9 @@ function requireCache$1 () {
47245
47795
  const CacheHandler = requireCacheHandler();
47246
47796
  const MemoryCacheStore = requireMemoryCacheStore();
47247
47797
  const CacheRevalidationHandler = requireCacheRevalidationHandler();
47248
- const { assertCacheStore, assertCacheMethods, makeCacheKey, normalizeHeaders, parseCacheControlHeader } = requireCache$2();
47798
+ const { assertCacheStore, assertCacheMethods, makeCacheKey, normalizeHeaders, parseCacheControlHeader, isInvalidOrWildcardVaryHeader } = requireCache$2();
47249
47799
  const { AbortError } = requireErrors$1();
47800
+ const { parseHttpDate } = requireDate();
47250
47801
 
47251
47802
  /**
47252
47803
  * @param {(string | RegExp)[] | undefined} origins
@@ -47267,6 +47818,44 @@ function requireCache$1 () {
47267
47818
 
47268
47819
  const nop = () => {};
47269
47820
 
47821
+ function trimOWS (value) {
47822
+ return value.replace(/^[\t ]+|[\t ]+$/g, '')
47823
+ }
47824
+
47825
+ function arrayIncludes (array, value) {
47826
+ for (let i = 0; i < array.length; i++) {
47827
+ if (array[i] === value) {
47828
+ return true
47829
+ }
47830
+ }
47831
+
47832
+ return false
47833
+ }
47834
+
47835
+ function hasPragmaNoCache (headers) {
47836
+ const pragma = headers?.pragma;
47837
+ if (!pragma) {
47838
+ return false
47839
+ }
47840
+
47841
+ const values = Array.isArray(pragma) ? pragma : [pragma];
47842
+ for (let i = 0; i < values.length; i++) {
47843
+ const value = values[i];
47844
+ if (typeof value !== 'string') {
47845
+ continue
47846
+ }
47847
+
47848
+ const directives = value.split(',');
47849
+ for (let j = 0; j < directives.length; j++) {
47850
+ if (trimOWS(directives[j]).toLowerCase() === 'no-cache') {
47851
+ return true
47852
+ }
47853
+ }
47854
+ }
47855
+
47856
+ return false
47857
+ }
47858
+
47270
47859
  /**
47271
47860
  * @typedef {(options: import('../../types/dispatcher.d.ts').default.DispatchOptions, handler: import('../../types/dispatcher.d.ts').default.DispatchHandler) => void} DispatchFn
47272
47861
  */
@@ -47296,16 +47885,92 @@ function requireCache$1 () {
47296
47885
  return false
47297
47886
  }
47298
47887
 
47888
+ /**
47889
+ * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
47890
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
47891
+ * @returns {boolean}
47892
+ */
47893
+ function staleResponseRequiresRevalidation (result, cacheType) {
47894
+ return result.cacheControlDirectives?.['must-revalidate'] === true ||
47895
+ (cacheType === 'shared' && (
47896
+ result.cacheControlDirectives?.['proxy-revalidate'] === true ||
47897
+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10
47898
+ // s-maxage implies proxy-revalidate for shared caches.
47899
+ result.cacheControlDirectives?.['s-maxage'] !== undefined
47900
+ ))
47901
+ }
47902
+
47903
+ /**
47904
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
47905
+ * @param {import('../../types/header.d.ts').IncomingHttpHeaders} headers
47906
+ * @returns {boolean}
47907
+ */
47908
+ function revalidationResponseDisallowsCachedReuse (cacheType, headers) {
47909
+ if (headers.vary && isInvalidOrWildcardVaryHeader(headers.vary)) {
47910
+ return true
47911
+ }
47912
+
47913
+ const cacheControl = headers['cache-control'];
47914
+ if (!cacheControl) {
47915
+ return false
47916
+ }
47917
+
47918
+ const cacheControlDirectives = parseCacheControlHeader(cacheControl);
47919
+ return cacheControlDirectives['no-store'] === true ||
47920
+ (cacheType === 'shared' && cacheControlDirectives.private === true)
47921
+ }
47922
+
47923
+ function revalidationResponseUpdatesCacheControl (headers) {
47924
+ return headers['cache-control'] !== undefined
47925
+ }
47926
+
47927
+ function deleteCachedValue (store, cacheKey) {
47928
+ try {
47929
+ store.delete(cacheKey)?.catch?.(nop);
47930
+ } catch {
47931
+ // Fail silently
47932
+ }
47933
+ }
47934
+
47935
+ function getUsableLastModified (headers) {
47936
+ const lastModified = headers?.['last-modified'];
47937
+ if (typeof lastModified === 'string' && parseHttpDate(lastModified)) {
47938
+ return lastModified
47939
+ }
47940
+ }
47941
+
47942
+ function makeRevalidationHeaders (opts, result) {
47943
+ const headers = {
47944
+ ...opts.headers,
47945
+ 'if-modified-since': getUsableLastModified(result.headers) ?? new Date(result.cachedAt).toUTCString()
47946
+ };
47947
+
47948
+ if (result.etag) {
47949
+ headers['if-none-match'] = result.etag;
47950
+ }
47951
+
47952
+ if (result.vary) {
47953
+ for (const key in result.vary) {
47954
+ if (result.vary[key] != null) {
47955
+ headers[key] = result.vary[key];
47956
+ }
47957
+ }
47958
+ }
47959
+
47960
+ return headers
47961
+ }
47962
+
47299
47963
  /**
47300
47964
  * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
47301
47965
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives | undefined} cacheControlDirectives
47966
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
47302
47967
  * @returns {boolean}
47303
47968
  */
47304
- function isStale (result, cacheControlDirectives) {
47969
+ function isStale (result, cacheControlDirectives, cacheType) {
47305
47970
  const now = Date.now();
47306
47971
  if (now > result.staleAt) {
47307
47972
  // Response is stale
47308
- if (cacheControlDirectives?.['max-stale']) {
47973
+ if (!staleResponseRequiresRevalidation(result, cacheType) && cacheControlDirectives?.['max-stale']) {
47309
47974
  // There's a threshold where we can serve stale responses, let's see if
47310
47975
  // we're in it
47311
47976
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-max-stale
@@ -47332,11 +47997,12 @@ function requireCache$1 () {
47332
47997
  /**
47333
47998
  * Check if we're within the stale-while-revalidate window for a stale response
47334
47999
  * @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
48000
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
47335
48001
  * @returns {boolean}
47336
48002
  */
47337
- function withinStaleWhileRevalidateWindow (result) {
48003
+ function withinStaleWhileRevalidateWindow (result, cacheType) {
47338
48004
  const staleWhileRevalidate = result.cacheControlDirectives?.['stale-while-revalidate'];
47339
- if (!staleWhileRevalidate) {
48005
+ if (!staleWhileRevalidate || staleResponseRequiresRevalidation(result, cacheType)) {
47340
48006
  return false
47341
48007
  }
47342
48008
 
@@ -47506,14 +48172,10 @@ function requireCache$1 () {
47506
48172
  }
47507
48173
 
47508
48174
  const age = Math.round((now - result.cachedAt) / 1000);
47509
- if (reqCacheControl?.['max-age'] && age >= reqCacheControl['max-age']) {
47510
- // Response is considered expired for this specific request
47511
- // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1
47512
- return dispatch(opts, handler)
47513
- }
48175
+ const requestMaxAgeExpired = reqCacheControl?.['max-age'] !== undefined && age >= reqCacheControl['max-age'];
47514
48176
 
47515
- const stale = isStale(result, reqCacheControl);
47516
- const revalidate = needsRevalidation(result, reqCacheControl, opts);
48177
+ const stale = requestMaxAgeExpired || isStale(result, reqCacheControl, globalOpts.type);
48178
+ const revalidate = requestMaxAgeExpired || needsRevalidation(result, reqCacheControl, opts);
47517
48179
 
47518
48180
  // Check if the response is stale
47519
48181
  if (stale || revalidate) {
@@ -47525,28 +48187,13 @@ function requireCache$1 () {
47525
48187
 
47526
48188
  // RFC 5861: If we're within stale-while-revalidate window, serve stale immediately
47527
48189
  // and revalidate in background, unless immediate revalidation is necessary
47528
- if (!revalidate && withinStaleWhileRevalidateWindow(result)) {
48190
+ if (!revalidate && withinStaleWhileRevalidateWindow(result, globalOpts.type)) {
47529
48191
  // Serve stale response immediately
47530
48192
  sendCachedValue(handler, opts, result, age, null, true);
47531
48193
 
47532
48194
  // Start background revalidation (fire-and-forget)
47533
48195
  queueMicrotask(() => {
47534
- const headers = {
47535
- ...opts.headers,
47536
- 'if-modified-since': new Date(result.cachedAt).toUTCString()
47537
- };
47538
-
47539
- if (result.etag) {
47540
- headers['if-none-match'] = result.etag;
47541
- }
47542
-
47543
- if (result.vary) {
47544
- for (const key in result.vary) {
47545
- if (result.vary[key] != null) {
47546
- headers[key] = result.vary[key];
47547
- }
47548
- }
47549
- }
48196
+ const headers = makeRevalidationHeaders(opts, result);
47550
48197
 
47551
48198
  // Background revalidation - update cache if we get new data
47552
48199
  dispatch(
@@ -47570,28 +48217,15 @@ function requireCache$1 () {
47570
48217
  }
47571
48218
 
47572
48219
  let withinStaleIfErrorThreshold = false;
47573
- const staleIfErrorExpiry = result.cacheControlDirectives['stale-if-error'] ?? reqCacheControl?.['stale-if-error'];
47574
- if (staleIfErrorExpiry) {
47575
- withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000));
47576
- }
47577
-
47578
- const headers = {
47579
- ...opts.headers,
47580
- 'if-modified-since': new Date(result.cachedAt).toUTCString()
47581
- };
47582
-
47583
- if (result.etag) {
47584
- headers['if-none-match'] = result.etag;
47585
- }
47586
-
47587
- if (result.vary) {
47588
- for (const key in result.vary) {
47589
- if (result.vary[key] != null) {
47590
- headers[key] = result.vary[key];
47591
- }
48220
+ if (!staleResponseRequiresRevalidation(result, globalOpts.type)) {
48221
+ const staleIfErrorExpiry = result.cacheControlDirectives['stale-if-error'] ?? reqCacheControl?.['stale-if-error'];
48222
+ if (staleIfErrorExpiry) {
48223
+ withinStaleIfErrorThreshold = now < (result.staleAt + (staleIfErrorExpiry * 1000));
47592
48224
  }
47593
48225
  }
47594
48226
 
48227
+ const headers = makeRevalidationHeaders(opts, result);
48228
+
47595
48229
  // We need to revalidate the response
47596
48230
  return dispatch(
47597
48231
  {
@@ -47599,8 +48233,23 @@ function requireCache$1 () {
47599
48233
  headers
47600
48234
  },
47601
48235
  new CacheRevalidationHandler(
47602
- (success, context) => {
48236
+ (success, context, statusCode, headers) => {
47603
48237
  if (success) {
48238
+ if (statusCode === 304) {
48239
+ if (revalidationResponseDisallowsCachedReuse(globalOpts.type, headers)) {
48240
+ if (util.isStream(result.body)) {
48241
+ result.body.on('error', nop).destroy();
48242
+ }
48243
+
48244
+ deleteCachedValue(globalOpts.store, cacheKey);
48245
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
48246
+ }
48247
+
48248
+ if (revalidationResponseUpdatesCacheControl(headers)) {
48249
+ deleteCachedValue(globalOpts.store, cacheKey);
48250
+ }
48251
+ }
48252
+
47604
48253
  // TODO: successful revalidation should be considered fresh (not give stale warning).
47605
48254
  sendCachedValue(handler, opts, result, age, context, stale);
47606
48255
  } else if (util.isStream(result.body)) {
@@ -47657,11 +48306,17 @@ function requireCache$1 () {
47657
48306
  type
47658
48307
  };
47659
48308
 
47660
- const safeMethodsToNotCache = util.safeHTTPMethods.filter(method => methods.includes(method) === false);
48309
+ const safeMethodsToNotCache = [];
48310
+ for (let i = 0; i < util.safeHTTPMethods.length; i++) {
48311
+ const method = util.safeHTTPMethods[i];
48312
+ if (!arrayIncludes(methods, method)) {
48313
+ safeMethodsToNotCache.push(method);
48314
+ }
48315
+ }
47661
48316
 
47662
48317
  return dispatch => {
47663
48318
  return (opts, handler) => {
47664
- if (!opts.origin || safeMethodsToNotCache.includes(opts.method)) {
48319
+ if (!opts.origin || arrayIncludes(safeMethodsToNotCache, opts.method)) {
47665
48320
  // Not a method we want to cache or we don't have the origin, skip
47666
48321
  return dispatch(opts, handler)
47667
48322
  }
@@ -47696,7 +48351,9 @@ function requireCache$1 () {
47696
48351
 
47697
48352
  const reqCacheControl = opts.headers?.['cache-control']
47698
48353
  ? parseCacheControlHeader(opts.headers['cache-control'])
47699
- : undefined;
48354
+ : hasPragmaNoCache(opts.headers)
48355
+ ? { 'no-cache': true }
48356
+ : undefined;
47700
48357
 
47701
48358
  if (reqCacheControl?.['no-store']) {
47702
48359
  return dispatch(opts, handler)
@@ -49056,7 +49713,13 @@ function requireSqliteCacheStore () {
49056
49713
  return false
49057
49714
  }
49058
49715
 
49059
- return lhs.every((x, i) => x === rhs[i])
49716
+ for (let i = 0; i < lhs.length; i++) {
49717
+ if (lhs[i] !== rhs[i]) {
49718
+ return false
49719
+ }
49720
+ }
49721
+
49722
+ return true
49060
49723
  }
49061
49724
 
49062
49725
  return lhs === rhs
@@ -55456,7 +56119,7 @@ function requireUtil$4 () {
55456
56119
 
55457
56120
  if (
55458
56121
  code < 0x20 || // exclude CTLs (0-31)
55459
- code === 0x7F || // DEL
56122
+ code > 0x7E || // exclude DEL and non-ascii
55460
56123
  code === 0x3B // ;
55461
56124
  ) {
55462
56125
  throw new Error('Invalid cookie path')
@@ -55465,16 +56128,80 @@ function requireUtil$4 () {
55465
56128
  }
55466
56129
 
55467
56130
  /**
55468
- * I have no idea why these values aren't allowed to be honest,
55469
- * but Deno tests these. - Khafra
56131
+ * <let-dig> ::= <letter> | <digit>
56132
+ *
56133
+ * <letter> ::= any one of the 52 alphabetic characters A through Z in
56134
+ * upper case and a through z in lower case
56135
+ *
56136
+ * <digit> ::= any one of the ten digits 0 through 9r
56137
+ *
56138
+ * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
56139
+ * @param {number} code
56140
+ */
56141
+ function isLetterOrDigit (code) {
56142
+ return (
56143
+ (code >= 0x30 && code <= 0x39) || // 0-9
56144
+ (code >= 0x41 && code <= 0x5A) || // A-Z
56145
+ (code >= 0x61 && code <= 0x7A) // a-z
56146
+ )
56147
+ }
56148
+
56149
+ /**
56150
+ * Validates a cookie domain against the "preferred name syntax".
56151
+ *
56152
+ * <domain> ::= <subdomain> | " "
56153
+ * <subdomain> ::= <label> | <subdomain> "." <label>
56154
+ * <label> ::= <let-dig> [ [ <ldh-str> ] <let-dig> ]
56155
+ * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
56156
+ * <let-dig-hyp> ::= <let-dig> | "-"
56157
+ *
56158
+ * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
56159
+ * @see https://www.rfc-editor.org/rfc/rfc1123#section-2.1
56160
+ * @see https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4
55470
56161
  * @param {string} domain
55471
56162
  */
55472
56163
  function validateCookieDomain (domain) {
55473
- if (
55474
- domain.startsWith('-') ||
55475
- domain.endsWith('.') ||
55476
- domain.endsWith('-')
55477
- ) {
56164
+ // <domain> ::= <subdomain> | " "
56165
+ if (domain === ' ') {
56166
+ return
56167
+ }
56168
+
56169
+ if (domain.length > 255) {
56170
+ throw new Error('Invalid cookie domain')
56171
+ }
56172
+
56173
+ let labelLength = 0;
56174
+
56175
+ for (let i = 0; i < domain.length; ++i) {
56176
+ const code = domain.charCodeAt(i);
56177
+
56178
+ if (code === 0x2E) {
56179
+ if (labelLength === 0) {
56180
+ throw new Error('Invalid cookie domain')
56181
+ }
56182
+
56183
+ if (domain.charCodeAt(i - 1) === 0x2D) { // "-"
56184
+ throw new Error('Invalid cookie domain')
56185
+ }
56186
+
56187
+ labelLength = 0;
56188
+ continue
56189
+ }
56190
+
56191
+ if (labelLength === 0 && !isLetterOrDigit(code)) {
56192
+ throw new Error('Invalid cookie domain')
56193
+ }
56194
+
56195
+ if (!isLetterOrDigit(code) && code !== 0x2D) { // "-"
56196
+ throw new Error('Invalid cookie domain')
56197
+ }
56198
+
56199
+ if (++labelLength > 63) {
56200
+ throw new Error('Invalid cookie domain')
56201
+ }
56202
+ }
56203
+
56204
+ if (labelLength === 0 || domain.charCodeAt(domain.length - 1) === 0x2D) { // "-"
55478
56205
  throw new Error('Invalid cookie domain')
55479
56206
  }
55480
56207
  }
@@ -55617,7 +56344,13 @@ function requireUtil$4 () {
55617
56344
 
55618
56345
  const [key, ...value] = part.split('=');
55619
56346
 
55620
- out.push(`${key.trim()}=${value.join('=')}`);
56347
+ const trimmedKey = key.trim();
56348
+ const joinedValue = value.join('=');
56349
+
56350
+ validateCookieName(trimmedKey);
56351
+ validateCookieValue(joinedValue);
56352
+
56353
+ out.push(`${trimmedKey}=${joinedValue}`);
55621
56354
  }
55622
56355
 
55623
56356
  return out.join('; ')
@@ -87707,15 +88440,19 @@ function requireJson () {
87707
88440
  function json (options) {
87708
88441
  var opts = options || {};
87709
88442
 
87710
- var limit = typeof opts.limit !== 'number'
87711
- ? bytes.parse(opts.limit || '100kb')
87712
- : opts.limit;
88443
+ var limit = typeof opts.limit === 'undefined' || opts.limit === null
88444
+ ? 102400 // 100kb default
88445
+ : bytes.parse(opts.limit);
87713
88446
  var inflate = opts.inflate !== false;
87714
88447
  var reviver = opts.reviver;
87715
88448
  var strict = opts.strict !== false;
87716
88449
  var type = opts.type || 'application/json';
87717
88450
  var verify = opts.verify || false;
87718
88451
 
88452
+ if (limit === null) {
88453
+ throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
88454
+ }
88455
+
87719
88456
  if (verify !== false && typeof verify !== 'function') {
87720
88457
  throw new TypeError('option verify must be function')
87721
88458
  }
@@ -87938,12 +88675,16 @@ function requireRaw () {
87938
88675
  var opts = options || {};
87939
88676
 
87940
88677
  var inflate = opts.inflate !== false;
87941
- var limit = typeof opts.limit !== 'number'
87942
- ? bytes.parse(opts.limit || '100kb')
87943
- : opts.limit;
88678
+ var limit = typeof opts.limit === 'undefined' || opts.limit === null
88679
+ ? 102400 // 100kb default
88680
+ : bytes.parse(opts.limit);
87944
88681
  var type = opts.type || 'application/octet-stream';
87945
88682
  var verify = opts.verify || false;
87946
88683
 
88684
+ if (limit === null) {
88685
+ throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
88686
+ }
88687
+
87947
88688
  if (verify !== false && typeof verify !== 'function') {
87948
88689
  throw new TypeError('option verify must be function')
87949
88690
  }
@@ -88049,12 +88790,16 @@ function requireText () {
88049
88790
 
88050
88791
  var defaultCharset = opts.defaultCharset || 'utf-8';
88051
88792
  var inflate = opts.inflate !== false;
88052
- var limit = typeof opts.limit !== 'number'
88053
- ? bytes.parse(opts.limit || '100kb')
88054
- : opts.limit;
88793
+ var limit = typeof opts.limit === 'undefined' || opts.limit === null
88794
+ ? 102400 // 100kb default
88795
+ : bytes.parse(opts.limit);
88055
88796
  var type = opts.type || 'text/plain';
88056
88797
  var verify = opts.verify || false;
88057
88798
 
88799
+ if (limit === null) {
88800
+ throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
88801
+ }
88802
+
88058
88803
  if (verify !== false && typeof verify !== 'function') {
88059
88804
  throw new TypeError('option verify must be function')
88060
88805
  }
@@ -91321,12 +92066,16 @@ function requireUrlencoded () {
91321
92066
 
91322
92067
  var extended = opts.extended !== false;
91323
92068
  var inflate = opts.inflate !== false;
91324
- var limit = typeof opts.limit !== 'number'
91325
- ? bytes.parse(opts.limit || '100kb')
91326
- : opts.limit;
92069
+ var limit = typeof opts.limit === 'undefined' || opts.limit === null
92070
+ ? 102400 // 100kb default
92071
+ : bytes.parse(opts.limit);
91327
92072
  var type = opts.type || 'application/x-www-form-urlencoded';
91328
92073
  var verify = opts.verify || false;
91329
92074
 
92075
+ if (limit === null) {
92076
+ throw new TypeError('option limit "' + String(opts.limit) + '" is invalid')
92077
+ }
92078
+
91330
92079
  if (verify !== false && typeof verify !== 'function') {
91331
92080
  throw new TypeError('option verify must be function')
91332
92081
  }
@@ -112042,6 +112791,17 @@ const closePattern = /\\}/g;
112042
112791
  const commaPattern = /\\,/g;
112043
112792
  const periodPattern = /\\\./g;
112044
112793
  const EXPANSION_MAX = 100_000;
112794
+ // `EXPANSION_MAX` caps the *number* of expansions, but not their length. An
112795
+ // input like `'{a,b}'.repeat(1500)` stays under that count - its output is
112796
+ // truncated to 100k results - while making every result ~1500 characters
112797
+ // long. The result set, and the intermediate arrays built while combining
112798
+ // brace sets, then grow large enough to exhaust memory and crash the process
112799
+ // (CVE-2026-14257). `EXPANSION_MAX_LENGTH` bounds the total number of
112800
+ // characters the accumulator may hold at any point, so memory stays flat no
112801
+ // matter how many brace groups are chained. The limit sits well above any
112802
+ // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
112803
+ // characters) so legitimate input is unaffected.
112804
+ const EXPANSION_MAX_LENGTH = 4_000_000;
112045
112805
  function numeric(str) {
112046
112806
  return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
112047
112807
  }
@@ -112090,7 +112850,7 @@ function expand(str, options = {}) {
112090
112850
  if (!str) {
112091
112851
  return [];
112092
112852
  }
112093
- const { max = EXPANSION_MAX } = options;
112853
+ const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options;
112094
112854
  // I don't know why Bash 4.3 does this, but it does.
112095
112855
  // Anything starting with {} will have the first two bytes preserved
112096
112856
  // but *only* at the top level, so {},a}b will not expand to anything,
@@ -112100,7 +112860,7 @@ function expand(str, options = {}) {
112100
112860
  if (str.slice(0, 2) === '{}') {
112101
112861
  str = '\\{\\}' + str.slice(2);
112102
112862
  }
112103
- return expand_(escapeBraces(str), max, true).map(unescapeBraces);
112863
+ return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces);
112104
112864
  }
112105
112865
  function embrace(str) {
112106
112866
  return '{' + str + '}';
@@ -112114,22 +112874,117 @@ function lte(i, y) {
112114
112874
  function gte(i, y) {
112115
112875
  return i >= y;
112116
112876
  }
112117
- function expand_(str, max, isTop) {
112118
- /** @type {string[]} */
112119
- const expansions = [];
112120
- const m = balanced('{', '}', str);
112121
- if (!m)
112122
- return [str];
112123
- // no need to expand pre, since it is guaranteed to be free of brace-sets
112124
- const pre = m.pre;
112125
- const post = m.post.length ? expand_(m.post, max, false) : [''];
112126
- if (/\$$/.test(m.pre)) {
112127
- for (let k = 0; k < post.length && k < max; k++) {
112128
- const expansion = pre + '{' + m.body + '}' + post[k];
112129
- expansions.push(expansion);
112877
+ // Build `{ acc[a] + pre + values[v] }` for every combination, capping the
112878
+ // number of results at `max` and the total number of characters at `maxLength`.
112879
+ // This is the one place output grows, so bounding it here keeps the single
112880
+ // accumulator - and therefore memory - flat regardless of how many brace groups
112881
+ // are combined (CVE-2026-14257).
112882
+ function combine(acc, pre, values, max, maxLength, dropEmpties) {
112883
+ const out = [];
112884
+ let length = 0;
112885
+ for (let a = 0; a < acc.length; a++) {
112886
+ for (let v = 0; v < values.length; v++) {
112887
+ if (out.length >= max)
112888
+ return out;
112889
+ const expansion = acc[a] + pre + values[v];
112890
+ // Bash drops empty results at the top level. Skip them before they count
112891
+ // against `max`, so `max` bounds the number of *kept* results.
112892
+ if (dropEmpties && !expansion)
112893
+ continue;
112894
+ if (length + expansion.length > maxLength)
112895
+ return out;
112896
+ out.push(expansion);
112897
+ length += expansion.length;
112130
112898
  }
112131
112899
  }
112132
- else {
112900
+ return out;
112901
+ }
112902
+ // The expansion values of a single numeric (`1..5`) or alphabetic (`a..e..2`)
112903
+ // sequence body.
112904
+ function expandSequence(body, isAlphaSequence, max, maxLength) {
112905
+ const n = body.split(/\.\./);
112906
+ const N = [];
112907
+ // A sequence body always splits into two or three parts, but the compiler
112908
+ // can't know that.
112909
+ /* c8 ignore start */
112910
+ if (n[0] === undefined || n[1] === undefined) {
112911
+ return N;
112912
+ }
112913
+ /* c8 ignore stop */
112914
+ const x = numeric(n[0]);
112915
+ const y = numeric(n[1]);
112916
+ const width = Math.max(n[0].length, n[1].length);
112917
+ let incr = n.length === 3 && n[2] !== undefined ?
112918
+ Math.max(Math.abs(numeric(n[2])), 1)
112919
+ : 1;
112920
+ let test = lte;
112921
+ const reverse = y < x;
112922
+ if (reverse) {
112923
+ incr *= -1;
112924
+ test = gte;
112925
+ }
112926
+ const pad = n.some(isPadded);
112927
+ let length = 0;
112928
+ for (let i = x; test(i, y) && N.length < max; i += incr) {
112929
+ let c;
112930
+ if (isAlphaSequence) {
112931
+ c = String.fromCharCode(i);
112932
+ if (c === '\\') {
112933
+ c = '';
112934
+ }
112935
+ }
112936
+ else {
112937
+ c = String(i);
112938
+ if (pad) {
112939
+ const need = width - c.length;
112940
+ if (need > 0) {
112941
+ const z = new Array(need + 1).join('0');
112942
+ if (i < 0) {
112943
+ c = '-' + z + c.slice(1);
112944
+ }
112945
+ else {
112946
+ c = z + c;
112947
+ }
112948
+ }
112949
+ }
112950
+ }
112951
+ if (length + c.length > maxLength)
112952
+ break;
112953
+ N.push(c);
112954
+ length += c.length;
112955
+ }
112956
+ return N;
112957
+ }
112958
+ function expand_(str, max, maxLength, isTop) {
112959
+ // Consume the string's top-level brace groups left to right, threading a
112960
+ // running set of combined prefixes (`acc`). Expanding the tail iteratively -
112961
+ // rather than recursing on `m.post` once per group - keeps the native stack
112962
+ // depth constant, so deeply chained input (`'{a,b}'.repeat(3000)`) can no
112963
+ // longer overflow the stack, and leaves a single accumulator whose size
112964
+ // `maxLength` bounds directly (CVE-2026-14257).
112965
+ let acc = [''];
112966
+ // Bash drops empty results, but only when the *first* top-level group is a
112967
+ // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
112968
+ // is on the final strings, so it is applied to whichever `combine` produces
112969
+ // them (the one with no brace set left in the tail).
112970
+ let dropEmpties = false;
112971
+ let firstGroup = true;
112972
+ for (;;) {
112973
+ const m = balanced('{', '}', str);
112974
+ // No brace set left: the rest of the string is literal.
112975
+ if (!m) {
112976
+ return combine(acc, str, [''], max, maxLength, dropEmpties);
112977
+ }
112978
+ // no need to expand pre, since it is guaranteed to be free of brace-sets
112979
+ const pre = m.pre;
112980
+ if (/\$$/.test(pre)) {
112981
+ acc = combine(acc, pre + '{' + m.body + '}', [''], max, maxLength, dropEmpties && !m.post.length);
112982
+ firstGroup = false;
112983
+ if (!m.post.length)
112984
+ break;
112985
+ str = m.post;
112986
+ continue;
112987
+ }
112133
112988
  const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
112134
112989
  const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
112135
112990
  const isSequence = isNumericSequence || isAlphaSequence;
@@ -112138,87 +112993,69 @@ function expand_(str, max, isTop) {
112138
112993
  // {a},b}
112139
112994
  if (m.post.match(/,(?!,).*\}/)) {
112140
112995
  str = m.pre + '{' + m.body + escClose + m.post;
112141
- return expand_(str, max, true);
112996
+ isTop = true;
112997
+ continue;
112142
112998
  }
112143
- return [str];
112999
+ // Nothing here expands, so the whole remaining string is literal.
113000
+ return combine(acc, pre + '{' + m.body + '}' + m.post, [''], max, maxLength, dropEmpties);
112144
113001
  }
112145
- let n;
113002
+ if (firstGroup) {
113003
+ dropEmpties = isTop && !isSequence;
113004
+ firstGroup = false;
113005
+ }
113006
+ let values;
112146
113007
  if (isSequence) {
112147
- n = m.body.split(/\.\./);
113008
+ values = expandSequence(m.body, isAlphaSequence, max, maxLength);
112148
113009
  }
112149
113010
  else {
112150
- n = parseCommaParts(m.body);
113011
+ let n = parseCommaParts(m.body);
112151
113012
  if (n.length === 1 && n[0] !== undefined) {
112152
113013
  // x{{a,b}}y ==> x{a}y x{b}y
112153
- n = expand_(n[0], max, false).map(embrace);
113014
+ n = expand_(n[0], max, maxLength, false).map(embrace);
112154
113015
  //XXX is this necessary? Can't seem to hit it in tests.
112155
113016
  /* c8 ignore start */
112156
113017
  if (n.length === 1) {
112157
- return post.map(p => m.pre + n[0] + p);
113018
+ acc = combine(acc, pre + n[0], [''], max, maxLength, dropEmpties && !m.post.length);
113019
+ if (!m.post.length)
113020
+ break;
113021
+ str = m.post;
113022
+ continue;
112158
113023
  }
112159
113024
  /* c8 ignore stop */
112160
113025
  }
112161
- }
112162
- // at this point, n is the parts, and we know it's not a comma set
112163
- // with a single entry.
112164
- let N;
112165
- if (isSequence && n[0] !== undefined && n[1] !== undefined) {
112166
- const x = numeric(n[0]);
112167
- const y = numeric(n[1]);
112168
- const width = Math.max(n[0].length, n[1].length);
112169
- let incr = n.length === 3 && n[2] !== undefined ?
112170
- Math.max(Math.abs(numeric(n[2])), 1)
112171
- : 1;
112172
- let test = lte;
112173
- const reverse = y < x;
112174
- if (reverse) {
112175
- incr *= -1;
112176
- test = gte;
112177
- }
112178
- const pad = n.some(isPadded);
112179
- N = [];
112180
- for (let i = x; test(i, y) && N.length < max; i += incr) {
112181
- let c;
112182
- if (isAlphaSequence) {
112183
- c = String.fromCharCode(i);
112184
- if (c === '\\') {
112185
- c = '';
112186
- }
112187
- }
112188
- else {
112189
- c = String(i);
112190
- if (pad) {
112191
- const need = width - c.length;
112192
- if (need > 0) {
112193
- const z = new Array(need + 1).join('0');
112194
- if (i < 0) {
112195
- c = '-' + z + c.slice(1);
112196
- }
112197
- else {
112198
- c = z + c;
112199
- }
112200
- }
113026
+ // Values that `combine` is going to drop as empty produce no result, so
113027
+ // they must not count against `max` - otherwise `{a,,b}` with `max: 2`
113028
+ // would stop at `['a', '']` and yield one result instead of two. Skipping
113029
+ // them outright keeps `values` bounded while leaving `max` a bound on
113030
+ // *kept* results.
113031
+ let dropsEmpties = dropEmpties && !m.post.length && !pre;
113032
+ for (let d = 0; dropsEmpties && d < acc.length; d++) {
113033
+ if (acc[d]) {
113034
+ dropsEmpties = false;
113035
+ }
113036
+ }
113037
+ values = [];
113038
+ let valuesLength = 0;
113039
+ outer: for (let j = 0; j < n.length; j++) {
113040
+ const expanded = expand_(n[j], max, maxLength, false);
113041
+ for (let k = 0; k < expanded.length; k++) {
113042
+ const v = expanded[k];
113043
+ if (dropsEmpties && !v)
113044
+ continue;
113045
+ if (values.length >= max || valuesLength + v.length > maxLength) {
113046
+ break outer;
112201
113047
  }
112202
- }
112203
- N.push(c);
112204
- }
112205
- }
112206
- else {
112207
- N = [];
112208
- for (let j = 0; j < n.length; j++) {
112209
- N.push.apply(N, expand_(n[j], max, false));
112210
- }
112211
- }
112212
- for (let j = 0; j < N.length; j++) {
112213
- for (let k = 0; k < post.length && expansions.length < max; k++) {
112214
- const expansion = pre + N[j] + post[k];
112215
- if (!isTop || isSequence || expansion) {
112216
- expansions.push(expansion);
113048
+ values.push(v);
113049
+ valuesLength += v.length;
112217
113050
  }
112218
113051
  }
112219
113052
  }
113053
+ acc = combine(acc, pre, values, max, maxLength, dropEmpties && !m.post.length);
113054
+ if (!m.post.length)
113055
+ break;
113056
+ str = m.post;
112220
113057
  }
112221
- return expansions;
113058
+ return acc;
112222
113059
  }
112223
113060
 
112224
113061
  const MAX_PATTERN_LENGTH = 1024 * 64;
@@ -190418,7 +191255,12 @@ function requireFastUri () {
190418
191255
  */
190419
191256
  function resolve (baseURI, relativeURI, options) {
190420
191257
  const schemelessOptions = options ? Object.assign({ scheme: 'null' }, options) : { scheme: 'null' };
190421
- const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
191258
+ const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
191259
+ const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
191260
+ if (baseMalformed || relativeMalformed) {
191261
+ throw new Error(baseParsed.error || relativeParsed.error || 'URI is malformed.')
191262
+ }
191263
+ const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
190422
191264
  schemelessOptions.skipEscape = true;
190423
191265
  return serialize(resolved, schemelessOptions)
190424
191266
  }
@@ -190594,6 +191436,19 @@ function requireFastUri () {
190594
191436
 
190595
191437
  const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
190596
191438
 
191439
+ // Captures the authority component (between "//" and the next "/", "?" or "#"),
191440
+ // with or without a scheme prefix, for the literal-backslash rejection below.
191441
+ const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
191442
+
191443
+ // Captures the leading authority-introducer region after an optional scheme: a
191444
+ // run of forward slashes, backslashes, and the characters the WHATWG URL parser
191445
+ // removes before parsing (TAB U+0009, LF U+000A, CR U+000D). A valid introducer
191446
+ // is exactly "//". Node treats "\" as "/" on special schemes and strips those
191447
+ // characters first, so forms like "\\", "/\", "\/", "/<TAB>/", or a leading
191448
+ // "<TAB>//" reach an authority in Node while fast-uri's URI_PARSE folds them into
191449
+ // the path group (host confusion / SSRF / redirect bypass).
191450
+ const AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
191451
+
190597
191452
  /**
190598
191453
  * @param {import('./types/index').URIComponent} parsed
190599
191454
  * @param {RegExpMatchArray} matches
@@ -190640,6 +191495,41 @@ function requireFastUri () {
190640
191495
  }
190641
191496
  }
190642
191497
 
191498
+ // A literal backslash (U+005C) is not a valid RFC 3986 URI character and is
191499
+ // not an authority delimiter. Reject it in the authority rather than
191500
+ // rewriting it: normalizing "\" -> "/" (WHATWG error recovery) could silently
191501
+ // change the resource identified by an otherwise-invalid input, and lets "\"
191502
+ // act as a host delimiter here while Node's native URL parses a different
191503
+ // host (SSRF / redirect / origin-allowlist bypass). Percent-encoded %5C is
191504
+ // untouched and remains valid encoded data.
191505
+ const authorityMatch = uri.match(AUTHORITY_PREFIX);
191506
+ if (authorityMatch !== null && authorityMatch[1].indexOf('\\') !== -1) {
191507
+ parsed.error = 'URI authority must not contain a literal backslash.';
191508
+ malformedAuthorityOrPort = true;
191509
+ }
191510
+
191511
+ // Reject a malformed or whitespace-smuggled authority introducer. fast-uri
191512
+ // only recognizes a literal "//"; anything else in the leading separator run
191513
+ // (a backslash, or a "//" that appears only after removing the TAB/LF/CR that
191514
+ // Node strips) means the authority fast-uri parses differs from the one Node's
191515
+ // URL resolves. Reject rather than rewrite, mirroring the literal-backslash
191516
+ // guard above. Percent-encoded forms (%5C, %09) are untouched, valid data.
191517
+ const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
191518
+ if (introducerMatch !== null) {
191519
+ const region = introducerMatch[1];
191520
+ const normalizedRegion = region.replace(/[\t\n\r]/g, '');
191521
+ // Two or more leading separators introduce an authority.
191522
+ if (normalizedRegion.length >= 2) {
191523
+ if (normalizedRegion.slice(0, 2) !== '//') {
191524
+ parsed.error = parsed.error || 'URI authority must not contain a literal backslash.';
191525
+ malformedAuthorityOrPort = true;
191526
+ } else if (region.length !== normalizedRegion.length) {
191527
+ parsed.error = parsed.error || 'URI authority introducer must not contain whitespace.';
191528
+ malformedAuthorityOrPort = true;
191529
+ }
191530
+ }
191531
+ }
191532
+
190643
191533
  const matches = uri.match(URI_PARSE);
190644
191534
 
190645
191535
  if (matches) {
@@ -190697,7 +191587,7 @@ function requireFastUri () {
190697
191587
  if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) {
190698
191588
  // convert Unicode IDN -> ASCII IDN
190699
191589
  try {
190700
- parsed.host = URL.domainToASCII(parsed.host.toLowerCase());
191590
+ parsed.host = new URL('http://' + parsed.host).hostname;
190701
191591
  } catch (e) {
190702
191592
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
190703
191593
  }