{"version":3,"file":"index.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","_step","sourceKeys","Object","keys","targetKeys","_iterator","_createForOfIteratorHelperLoose","done","_step2","field","value","sourceValue","targetValue","_iterator2","key","toLowerCase","_source$key","toString","trim","_iterator3","_step3","_target$_key","VALID_HEADER_NAME_REGEX","parse","headerStr","maxLength","includes","values","Set","i","length","char","start","headerName","slice","test","add","size","Array","from"],"mappings":"6yBAoCgBA,EACdC,EACAC,EACAC,GAGA,GAAa,MAATF,GAAyB,OAATA,EAClB,OACF,EAKA,IAHA,IAGwBG,EAHlBC,EAAaC,OAAOC,KAAKL,GACzBM,EAAaF,OAAOC,KAAKJ,GAE/BM,EAAAC,EAAoBT,KAAIG,EAAAK,KAAAE,MAAE,CAKxB,IALwB,IAKIC,EALnBC,EAAKT,EAAAU,MACVC,OACJ,EAAIC,OAGJ,EAAAC,EAAAP,EAAkBL,KAAUO,EAAAK,KAAAN,MAAE,CAAnB,IAAAO,EAAGN,EAAAE,MACZ,GAAII,EAAIC,gBAAkBN,EAAO,CAAAO,IAAAA,EAC/BL,EAAyB,OAAdK,EAAGlB,EAAOgB,KAAgB,OAAZE,EAAXA,EAAaC,iBAAU,EAAvBD,EAAyBE,OACvC,KACF,CACF,CAGA,IAAAC,IAA4BC,EAA5BD,EAAAb,EAAkBF,KAAUgB,EAAAD,KAAAZ,MAAE,CAAA,IAAnBO,EAAGM,EAAAV,MACZ,GAAII,EAAIC,gBAAkBN,EAAO,CAAA,IAAAY,EAC/BT,EAAyBS,OAAdA,EAAGtB,EAAOe,KAAPO,OAAWA,EAAXA,EAAaJ,iBAAbI,EAAAA,EAAyBH,OACvC,KACF,CACF,CAGA,GAAIP,GAAeC,EACjB,OACF,CACF,CAEA,OACF,CAAA,CC1EA,IAAMU,EAA0B,yBAoChBC,EAAMC,EAAoBC,GAExC,YAFwCA,IAAAA,EAAY,IAE3B,iBAAdD,EACT,OACF,KAGA,GAAIA,EAAUE,SAAS,KACrB,MAAO,IAKT,IAFA,IAAMC,EAAS,IAAIC,IAEVC,EAAI,EAAGA,EAAIL,EAAUM,OAAQD,IAAK,CACzC,IAAME,EAAOP,EAAUK,GAEvB,GAAa,MAATE,GAAyB,OAATA,GAA0B,MAATA,EAArC,CAMA,IAFA,IAAMC,EAAQH,EAEPA,EAAIL,EAAUM,QAGN,MAFAN,EAAUK,IAMvBA,IAGF,IAAMI,EAAaT,EAAUU,MAAMF,EAAOH,GAAGX,OAAOH,cAGpD,GAA0B,IAAtBkB,EAAWH,QAAiBR,EAAwBa,KAAKF,KAI7DN,EAAOS,IAAIH,GAGPN,EAAOU,MAAQZ,GACjB,KAzBF,CA2BF,CAGA,OAAoB,IAAhBE,EAAOU,KACF,KAGFC,MAAMC,KAAKZ,EACpB"}