{"version":3,"file":"index.modern.mjs","sources":["../src/compare.ts","../src/parse.ts"],"sourcesContent":["import type { CompareHeaders, VaryHeader } from './types';\n\n/**\n * Checks if {@linkcode source} and {@linkcode target} headers are equivalent for the given\n * Vary header, as per\n * {@link https://www.rfc-editor.org/rfc/rfc9110.html#name-vary RFC 9110 Section 12.5.5}.\n *\n * This function determines if two requests would receive the same cached response based\n * on the Vary header requirements.\n *\n * @remarks\n * - Returns `false` for wildcard vary (`'*'`) as responses always differ\n * - Header name matching is case-insensitive (per RFC 9110)\n * - Missing headers are treated as `undefined`\n * - String values are trimmed before comparison\n * - Array values are converted to strings via `.toString()`\n * - Uses loose equality (!=) for comparison\n * - Empty strings are distinct from missing headers\n *\n * @example\n *\n * ```ts\n * const vary = ['accept-encoding', 'user-agent'];\n * const headers1 = { 'Accept-Encoding': 'gzip', 'User-Agent': 'Chrome' };\n * const headers2 = { 'Accept-Encoding': 'gzip', 'User-Agent': 'Chrome' };\n *\n * compare(vary, headers1, headers2);\n * // => true\n * ```\n *\n * @param {VaryHeader | null} vary - The Vary header specifying which fields to compare\n * @param {CompareHeaders} source - The first set of request headers\n * @param {CompareHeaders} target - The second set of request headers\n * @returns {boolean} `true` if the headers are equivalent for the given Vary header,\n *   `false` otherwise\n */\nexport function compare(\n  vary: VaryHeader | null,\n  source: CompareHeaders,\n  target: CompareHeaders\n): boolean {\n  // Wildcard and null always differ\n  if (vary === '*' || vary === null) {\n    return false;\n  }\n\n  const sourceKeys = Object.keys(source);\n  const targetKeys = Object.keys(target);\n\n  for (const field of vary) {\n    let sourceValue: string | undefined;\n    let targetValue: string | undefined;\n\n    // Case-insensitive header lookup in source\n    for (const key of sourceKeys) {\n      if (key.toLowerCase() === field) {\n        sourceValue = source[key]?.toString()?.trim();\n        break;\n      }\n    }\n\n    // Case-insensitive header lookup in target\n    for (const key of targetKeys) {\n      if (key.toLowerCase() === field) {\n        targetValue = target[key]?.toString()?.trim();\n        break;\n      }\n    }\n\n    // biome-ignore lint/suspicious/noDoubleEquals: Intentional loose comparison\n    if (sourceValue != targetValue) {\n      return false;\n    }\n  }\n\n  return true;\n}\n","import type { VaryHeader } from './types';\n\nconst VALID_HEADER_NAME_REGEX = /^[a-z0-9-]+$/i;\n\n/**\n * Parses the Vary header as defined in\n * {@link https://www.rfc-editor.org/rfc/rfc9110.html#name-vary RFC 9110 Section 12.5.5}.\n *\n * The Vary header indicates which request headers a server considers when selecting or\n * generating a response, enabling proper HTTP caching behavior.\n *\n * @remarks\n * - Header field names are normalized to lowercase\n * - Duplicate fields are automatically deduplicated\n * - Invalid header names (per RFC 9110) are silently skipped\n * - If the header contains `'*'`, the function returns `'*'` (wildcard)\n * - Returns `null` for invalid input or when no valid fields are found\n *\n * @example\n *\n * ```ts\n * parse('Accept-Encoding, User-Agent');\n * // => ['accept-encoding', 'user-agent']\n *\n * parse('*');\n * // => '*'\n *\n * parse('Invalid Header!');\n * // => null\n * ```\n *\n * @param {string} headerStr - The Vary header value to parse (e.g., \"Accept-Encoding,\n *   User-Agent\")\n * @param {number} [maxLength=16] - Maximum number of header fields to parse for DoS\n *   protection. Default is `16`\n * @returns {VaryHeader | null} The parsed Vary header as an array of lowercase field\n *   names, `'*'` for wildcard, or `null` if invalid.\n */\nexport function parse(headerStr?: string, maxLength = 16): VaryHeader | null {\n  // Invalid header name\n  if (typeof headerStr !== 'string') {\n    return null;\n  }\n\n  // RFC says only '*' is valid alone, but some servers may send invalid headers like '*, Accept-Encoding'\n  if (headerStr.includes('*')) {\n    return '*';\n  }\n\n  const values = new Set<string>();\n\n  for (let i = 0; i < headerStr.length; i++) {\n    const char = headerStr[i];\n\n    if (char === ' ' || char === '\\t' || char === ',') {\n      continue;\n    }\n\n    const start = i;\n\n    while (i < headerStr.length) {\n      const char = headerStr[i];\n\n      if (char === ',') {\n        break;\n      }\n\n      i++;\n    }\n\n    const headerName = headerStr.slice(start, i).trim().toLowerCase();\n\n    // Skip invalid header names\n    if (headerName.length === 0 || !VALID_HEADER_NAME_REGEX.test(headerName)) {\n      continue;\n    }\n\n    values.add(headerName);\n\n    // DOS protection to avoid overly large vary headers\n    if (values.size >= maxLength) {\n      break;\n    }\n  }\n\n  // Ensures no empty set is returned\n  if (values.size === 0) {\n    return null;\n  }\n\n  return Array.from(values);\n}\n"],"names":["compare","vary","source","target","sourceKeys","Object","keys","targetKeys","field","sourceValue","targetValue","key","toLowerCase","_source$key","toString","trim","_target$key","VALID_HEADER_NAME_REGEX","parse","headerStr","maxLength","includes","values","Set","i","length","char","start","headerName","slice","test","add","size","Array","from"],"mappings":"SAoCgBA,EACdC,EACAC,EACAC,GAGA,GAAa,MAATF,GAAyB,OAATA,EAClB,OACF,EAEA,MAAMG,EAAaC,OAAOC,KAAKJ,GACzBK,EAAaF,OAAOC,KAAKH,GAE/B,IAAK,MAAMK,KAASP,EAAM,CACxB,IAAIQ,EACAC,EAGJ,IAAK,MAAMC,KAAOP,EAChB,GAAIO,EAAIC,gBAAkBJ,EAAO,KAAAK,EAC/BJ,EAAyBI,OAAdA,EAAGX,EAAOS,KAAPE,OAAWA,EAAXA,EAAaC,iBAAbD,EAAAA,EAAyBE,OACvC,KACF,CAIF,IAAK,MAAMJ,KAAOJ,EAChB,GAAII,EAAIC,gBAAkBJ,EAAO,CAAA,IAAAQ,EAC/BN,SAAWM,EAAGb,EAAOQ,KAAgB,OAAZK,EAAXA,EAAaF,iBAAU,EAAvBE,EAAyBD,OACvC,KACF,CAIF,GAAIN,GAAeC,EACjB,QAEJ,CAEA,OACF,CAAA,CC1EA,MAAMO,EAA0B,yBAoChBC,EAAMC,EAAoBC,EAAY,IAEpD,GAAyB,iBAAdD,EACT,YAIF,GAAIA,EAAUE,SAAS,KACrB,MAAO,IAGT,MAAMC,EAAS,IAAIC,IAEnB,IAAK,IAAIC,EAAI,EAAGA,EAAIL,EAAUM,OAAQD,IAAK,CACzC,MAAME,EAAOP,EAAUK,GAEvB,GAAa,MAATE,GAAyB,OAATA,GAA0B,MAATA,EACnC,SAGF,MAAMC,EAAQH,EAEd,KAAOA,EAAIL,EAAUM,QAGN,MAFAN,EAAUK,IAMvBA,IAGF,MAAMI,EAAaT,EAAUU,MAAMF,EAAOH,GAAGT,OAAOH,cAGpD,GAA0B,IAAtBgB,EAAWH,QAAiBR,EAAwBa,KAAKF,KAI7DN,EAAOS,IAAIH,GAGPN,EAAOU,MAAQZ,GACjB,KAEJ,CAGA,OAAoB,IAAhBE,EAAOU,KACF,KAGFC,MAAMC,KAAKZ,EACpB"}