{"version":3,"file":"reader-BNaUTVCy.mjs","names":[],"sources":["../src/zip/decompression-guard.ts","../src/zip/random-access-reader.ts","../src/zip/reader.ts"],"sourcesContent":["// Decompression-bomb safeguards for the ZIP reader.\n//\n// Zip-bombs encode pathological compression ratios (e.g. 100 B → 1 GB) so that\n// a naive `inflate` exhausts memory before the caller can react. The guards\n// here apply three orthogonal bounds:\n//\n// 1. Per-entry uncompressed cap — hard ceiling on a single payload.\n// 2. Total-archive uncompressed cap — hard ceiling summed across the archive.\n// 3. Per-entry compression ratio — bounds the *amplification factor* so that\n//    even small compressed entries can't decompress into hundreds of MB.\n//\n// Defaults are sized to admit any legitimate xlsx (xml compresses well but\n// rarely exceeds the per-entry cap; office templates with embedded media stay\n// well under the total cap) while rejecting plausible bombs. Trusted callers\n// (e.g. a backend that only ever loads xlsx it generated itself) can pass\n// `false` to disable the guard.\n\nimport { OpenXmlDecompressionBombError, OpenXmlError } from '../utils/exceptions';\n\n/** Per-archive limits enforced during {@link openZip}. */\nexport interface DecompressionLimits {\n  /**\n   * Maximum decompressed bytes for a single archive entry. Default 512 MiB.\n   * Legitimate xlsx sheets stay well below this even with millions of cells.\n   */\n  maxEntryUncompressedBytes?: number;\n  /**\n   * Maximum decompressed bytes summed across every entry the caller reads.\n   * Default 1 GiB.\n   */\n  maxTotalUncompressedBytes?: number;\n  /**\n   * Maximum allowed `uncompressed / compressed` ratio for a single entry.\n   * Default 1000. xml usually compresses 5–20×, but highly repetitive payloads\n   * (long runs of zeros, sparse worksheets) can hit several hundred ×\n   * legitimately. Classic zip-bombs run 10 000× and up, so a 1000× ceiling\n   * still catches them while admitting realistic content. The implementation\n   * treats compressed sizes below 64 B as exempt — there's no amplification\n   * budget worth policing on such small entries, and the absolute per-entry /\n   * archive limits already cover them.\n   */\n  maxCompressionRatio?: number;\n}\n\n/** Resolved limits with no `undefined` fields. */\nexport interface ResolvedDecompressionLimits {\n  readonly maxEntryUncompressedBytes: number;\n  readonly maxTotalUncompressedBytes: number;\n  readonly maxCompressionRatio: number;\n}\n\n/** Default safeguards applied when the caller doesn't override them. */\nexport const DEFAULT_DECOMPRESSION_LIMITS: ResolvedDecompressionLimits = {\n  maxEntryUncompressedBytes: 512 * 1024 * 1024,\n  maxTotalUncompressedBytes: 1024 * 1024 * 1024,\n  maxCompressionRatio: 1000,\n};\n\n/** Below this compressed size, the ratio check is skipped — see {@link DecompressionLimits}. */\nconst RATIO_CHECK_MIN_COMPRESSED_BYTES = 64;\n\n/**\n * `DecompressionLimits` plus the sentinel `false` to disable the guard.\n * Anything else is treated as \"use defaults\".\n */\nexport type DecompressionLimitsInput = DecompressionLimits | false | undefined;\n\n// Each limit must be a positive finite number to be meaningful; NaN / Infinity\n// / 0 / negatives silently turn `emitted > cap` and `totalInflated > cap` into\n// no-ops and disable the guard. Reject those at the boundary so the only way\n// the guard is off is the explicit `false` sentinel.\nconst requirePositiveFinite = (field: string, value: number): void => {\n  if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n    throw new OpenXmlError(\n      `resolveDecompressionLimits: ${field} must be a positive finite number; got ${String(value)}`,\n    );\n  }\n};\n\n/** Returns null when the guard is disabled. */\nexport function resolveDecompressionLimits(\n  input: DecompressionLimitsInput,\n): ResolvedDecompressionLimits | null {\n  if (input === false) return null;\n  if (!input) return DEFAULT_DECOMPRESSION_LIMITS;\n  const resolved: ResolvedDecompressionLimits = {\n    maxEntryUncompressedBytes:\n      input.maxEntryUncompressedBytes ?? DEFAULT_DECOMPRESSION_LIMITS.maxEntryUncompressedBytes,\n    maxTotalUncompressedBytes:\n      input.maxTotalUncompressedBytes ?? DEFAULT_DECOMPRESSION_LIMITS.maxTotalUncompressedBytes,\n    maxCompressionRatio:\n      input.maxCompressionRatio ?? DEFAULT_DECOMPRESSION_LIMITS.maxCompressionRatio,\n  };\n  requirePositiveFinite('maxEntryUncompressedBytes', resolved.maxEntryUncompressedBytes);\n  requirePositiveFinite('maxTotalUncompressedBytes', resolved.maxTotalUncompressedBytes);\n  requirePositiveFinite('maxCompressionRatio', resolved.maxCompressionRatio);\n  return resolved;\n}\n\n/**\n * Per-archive byte accounting shared across every read of an archive opened\n * with the given limits. Tracks total inflated bytes so the cap is enforced\n * even when individual entries stay below the per-entry ceiling.\n */\nexport interface DecompressionBudget {\n  readonly limits: ResolvedDecompressionLimits;\n  totalInflated: number;\n}\n\nexport function createBudget(limits: ResolvedDecompressionLimits): DecompressionBudget {\n  return { limits, totalInflated: 0 };\n}\n\n/**\n * Per-entry inflate cap, accounting for both the absolute per-entry bound and\n * the ratio-based bound. Returns the smaller of the two — whichever fires\n * first stops the inflate loop.\n */\nexport function entryInflateCap(budget: DecompressionBudget, compressedSize: number): number {\n  const { maxEntryUncompressedBytes, maxCompressionRatio } = budget.limits;\n  if (compressedSize < RATIO_CHECK_MIN_COMPRESSED_BYTES) {\n    return maxEntryUncompressedBytes;\n  }\n  const ratioCap = compressedSize * maxCompressionRatio;\n  return Math.min(maxEntryUncompressedBytes, ratioCap);\n}\n\n/** Throw if the declared central-directory totals already exceed the limits. */\nexport function checkDeclaredTotals(\n  budget: DecompressionBudget,\n  declaredEntries: ReadonlyArray<{ path: string; compSize: number; uncompSize: number }>,\n): void {\n  let declaredTotal = 0;\n  for (const entry of declaredEntries) {\n    declaredTotal += entry.uncompSize;\n    if (entry.uncompSize > budget.limits.maxEntryUncompressedBytes) {\n      throw new OpenXmlDecompressionBombError(\n        `openZip: entry \"${entry.path}\" declares ${entry.uncompSize} uncompressed bytes,` +\n          ` exceeding the ${budget.limits.maxEntryUncompressedBytes}-byte per-entry limit` +\n          ` (decompression-bomb guard).`,\n      );\n    }\n    if (\n      entry.compSize >= RATIO_CHECK_MIN_COMPRESSED_BYTES &&\n      entry.uncompSize > entry.compSize * budget.limits.maxCompressionRatio\n    ) {\n      throw new OpenXmlDecompressionBombError(\n        `openZip: entry \"${entry.path}\" declares ratio ${(entry.uncompSize / entry.compSize).toFixed(1)}x` +\n          ` (${entry.uncompSize}/${entry.compSize}), exceeding the ${budget.limits.maxCompressionRatio}x` +\n          ` per-entry limit (decompression-bomb guard).`,\n      );\n    }\n  }\n  if (declaredTotal > budget.limits.maxTotalUncompressedBytes) {\n    throw new OpenXmlDecompressionBombError(\n      `openZip: declared total uncompressed size ${declaredTotal} bytes exceeds the` +\n        ` ${budget.limits.maxTotalUncompressedBytes}-byte archive limit (decompression-bomb guard).`,\n    );\n  }\n}\n\n/**\n * Record `bytes` against the global budget. Throws when the running total\n * crosses {@link ResolvedDecompressionLimits.maxTotalUncompressedBytes}. Called\n * from both sync and streaming inflate code paths.\n */\nexport function recordInflated(budget: DecompressionBudget, path: string, bytes: number): void {\n  budget.totalInflated += bytes;\n  if (budget.totalInflated > budget.limits.maxTotalUncompressedBytes) {\n    throw new OpenXmlDecompressionBombError(\n      `openZip: archive-wide inflated size exceeded ${budget.limits.maxTotalUncompressedBytes} bytes` +\n        ` while reading \"${path}\" (decompression-bomb guard).`,\n    );\n  }\n}\n\n/** Build the message thrown when a single entry exceeds its cap mid-inflate. */\nexport function entryOverflowError(path: string, cap: number): OpenXmlDecompressionBombError {\n  return new OpenXmlDecompressionBombError(\n    `openZip: inflated size of \"${path}\" exceeded ${cap} bytes (decompression-bomb guard).`,\n  );\n}\n","// Random-access ZIP reader. / §2.3 streaming-read residual.\n//\n// The previous reader handed every entry to `fflate.unzipSync` up front, which\n// materialises *every* uncompressed payload into memory at once. For a 100 MB\n// xlsx with ~500 MB of decompressed sheet data the resident set spikes\n// accordingly. The random-access path keeps only the compressed archive bytes\n// resident, parses the central directory once (cheap — ~46 B per entry plus\n// filename), and inflates each entry lazily on `read(path)`.\n//\n// Limitations:\n// - ZIP64 reads only when the standard ZIP32 fields fit. EOCD with\n// sentinel values (0xFFFF / 0xFFFFFFFF) falls back to fflate's `unzipSync` so\n// external ZIP64 archives still load (the writer side has its own ZIP32 cap\n// guard).\n// - Compression methods: STORE (0) and DEFLATE (8). Anything else\n// throws OpenXmlIoError.\n\nimport { Inflate, unzipSync } from 'fflate';\nimport { OpenXmlDecompressionBombError, OpenXmlIoError } from '../utils/exceptions';\nimport {\n  checkDeclaredTotals,\n  createBudget,\n  type DecompressionBudget,\n  type DecompressionLimitsInput,\n  entryInflateCap,\n  entryOverflowError,\n  recordInflated,\n  resolveDecompressionLimits,\n} from './decompression-guard';\nimport type { ZipArchive } from './reader';\n\n/**\n * Chunk size used when feeding compressed bytes into fflate's `Inflate` for\n * streaming reads. 64 KB matches the saxes/SAX consumer's typical batch and\n * keeps peak transient memory bounded.\n */\nconst INFLATE_CHUNK_BYTES = 64 * 1024;\n\nconst singleChunkStream = (bytes: Uint8Array): ReadableStream<Uint8Array> =>\n  new ReadableStream<Uint8Array>({\n    start(controller) {\n      if (bytes.byteLength > 0) controller.enqueue(bytes);\n      controller.close();\n    },\n  });\n\nconst SIG_EOCD = 0x06054b50;\nconst SIG_CD = 0x02014b50;\nconst SIG_LFH = 0x04034b50;\nconst SIG_ZIP64_EOCD = 0x06064b50;\nconst SIG_ZIP64_EOCD_LOCATOR = 0x07064b50;\nconst ZIP32_MAX_U16 = 0xffff;\nconst ZIP32_MAX_U32 = 0xffffffff;\nconst ZIP64_LOCATOR_SIZE = 20;\nconst ZIP64_EXTRA_HEADER_ID = 0x0001;\nconst COMP_STORE = 0;\nconst COMP_DEFLATE = 8;\n\ninterface CdEntry {\n  path: string;\n  lfhOffset: number;\n  compMethod: number;\n  compSize: number;\n  uncompSize: number;\n  /** General-purpose bit flag, used to detect bit-3 (data descriptor) and bit-11 (UTF-8). */\n  gpFlag: number;\n}\n\nconst u16 = (b: Uint8Array, off: number): number => (b[off] ?? 0) | ((b[off + 1] ?? 0) << 8);\nconst u32 = (b: Uint8Array, off: number): number => {\n  const v0 = b[off] ?? 0;\n  const v1 = b[off + 1] ?? 0;\n  const v2 = b[off + 2] ?? 0;\n  const v3 = b[off + 3] ?? 0;\n  return (v0 | (v1 << 8) | (v2 << 16) | (v3 << 24)) >>> 0;\n};\n\n// Read a little-endian 64-bit value as a JS Number. Safe for values up to\n// 2^53-1 (Number.MAX_SAFE_INTEGER ~ 9 PiB), which is well past any realistic\n// xlsx archive — we throw if a parsed value exceeds the safe-integer range.\nconst u64 = (b: Uint8Array, off: number): number => {\n  const lo = u32(b, off);\n  const hi = u32(b, off + 4);\n  if (hi > 0x1fffff) {\n    throw new OpenXmlIoError(\n      `openZip: ZIP64 field at byte ${off} exceeds the safe-integer range (${hi}*2^32 + ${lo})`,\n    );\n  }\n  return hi * 0x100000000 + lo;\n};\n\n/** Find the End-of-Central-Directory record by scanning backwards from EOF. */\nfunction findEocd(b: Uint8Array): number {\n  const minStart = Math.max(0, b.length - 22 - 0xffff);\n  for (let i = b.length - 22; i >= minStart; i--) {\n    if (u32(b, i) === SIG_EOCD) return i;\n  }\n  throw new OpenXmlIoError('openZip: no End-of-Central-Directory signature found');\n}\n\ninterface CdSummary {\n  totalEntries: number;\n  cdSize: number;\n  cdOffset: number;\n}\n\n/**\n * Resolve the central-directory totals. When the EOCD carries ZIP64 sentinel\n * values (0xFFFF / 0xFFFFFFFF) the real totals live in a ZIP64 EOCD record\n * located via the ZIP64 EOCD locator just before the regular EOCD. ECMA-376\n * xlsx archives stay in ZIP32 territory; the ZIP64 path exists so the\n * decompression-bomb guards still apply to large external archives instead of\n * falling all the way back to `unzipSync` (which produces every entry's bytes\n * before any cap can fire).\n */\nfunction readCdSummary(b: Uint8Array, eocdOff: number): CdSummary {\n  let totalEntries = u16(b, eocdOff + 10);\n  let cdSize = u32(b, eocdOff + 12);\n  let cdOffset = u32(b, eocdOff + 16);\n\n  const usesZip64Eocd =\n    totalEntries === ZIP32_MAX_U16 || cdSize === ZIP32_MAX_U32 || cdOffset === ZIP32_MAX_U32;\n  if (!usesZip64Eocd) {\n    return { totalEntries, cdSize, cdOffset };\n  }\n\n  // Locate the ZIP64 EOCD locator. It sits immediately before the regular\n  // EOCD when one is present.\n  const locatorOff = eocdOff - ZIP64_LOCATOR_SIZE;\n  if (locatorOff < 0 || u32(b, locatorOff) !== SIG_ZIP64_EOCD_LOCATOR) {\n    throw new OpenXmlIoError('openZip: ZIP64 EOCD locator missing despite EOCD sentinel values');\n  }\n  const zip64EocdOff = u64(b, locatorOff + 8);\n  if (zip64EocdOff < 0 || zip64EocdOff + 56 > b.length) {\n    throw new OpenXmlIoError(`openZip: ZIP64 EOCD offset ${zip64EocdOff} out of bounds`);\n  }\n  if (u32(b, zip64EocdOff) !== SIG_ZIP64_EOCD) {\n    throw new OpenXmlIoError(`openZip: ZIP64 EOCD signature missing at byte ${zip64EocdOff}`);\n  }\n  totalEntries = u64(b, zip64EocdOff + 32);\n  cdSize = u64(b, zip64EocdOff + 40);\n  cdOffset = u64(b, zip64EocdOff + 48);\n  return { totalEntries, cdSize, cdOffset };\n}\n\n/** Walk the ZIP64 Extended Information extra field for sentinel-valued sizes/offset. */\nfunction readZip64Extra(\n  b: Uint8Array,\n  extraStart: number,\n  extraLen: number,\n  wantsUncompSize: boolean,\n  wantsCompSize: boolean,\n  wantsLfhOffset: boolean,\n  entryPath: string,\n): { uncompSize?: number; compSize?: number; lfhOffset?: number } {\n  let p = extraStart;\n  const end = extraStart + extraLen;\n  while (p + 4 <= end) {\n    const id = u16(b, p);\n    const size = u16(b, p + 2);\n    const dataStart = p + 4;\n    const next = dataStart + size;\n    if (next > end) break;\n    if (id === ZIP64_EXTRA_HEADER_ID) {\n      // Sanity-check that the declared extra-field size actually covers every\n      // u64 the CD asked us to read. A truncated / mis-sized ZIP64 extra\n      // field would otherwise let u64() interpret bytes belonging to the\n      // next extra record (or the next CD entry) as a size/offset, producing\n      // bogus values that downstream bomb checks then trust. Fail closed.\n      const needed = (wantsUncompSize ? 8 : 0) + (wantsCompSize ? 8 : 0) + (wantsLfhOffset ? 8 : 0);\n      if (size < needed) {\n        throw new OpenXmlIoError(\n          `openZip: ZIP64 extended-info field for \"${entryPath}\" declares ${size} bytes` +\n            ` but the central directory sentinels require ${needed}`,\n        );\n      }\n      let q = dataStart;\n      const result: { uncompSize?: number; compSize?: number; lfhOffset?: number } = {};\n      if (wantsUncompSize) {\n        result.uncompSize = u64(b, q);\n        q += 8;\n      }\n      if (wantsCompSize) {\n        result.compSize = u64(b, q);\n        q += 8;\n      }\n      if (wantsLfhOffset) {\n        result.lfhOffset = u64(b, q);\n        q += 8;\n      }\n      return result;\n    }\n    p = next;\n  }\n  return {};\n}\n\n/** Parse the central directory into an array of entry descriptors. */\nfunction parseCentralDirectory(b: Uint8Array, cdOffset: number, expectedCount: number): CdEntry[] {\n  const entries: CdEntry[] = [];\n  let p = cdOffset;\n  for (let i = 0; i < expectedCount; i++) {\n    if (u32(b, p) !== SIG_CD) {\n      throw new OpenXmlIoError(`openZip: malformed central directory at byte ${p}`);\n    }\n    const gpFlag = u16(b, p + 8);\n    const compMethod = u16(b, p + 10);\n    let compSize = u32(b, p + 20);\n    let uncompSize = u32(b, p + 24);\n    const nameLen = u16(b, p + 28);\n    const extraLen = u16(b, p + 30);\n    const commentLen = u16(b, p + 32);\n    let lfhOffset = u32(b, p + 42);\n    const nameBytes = b.subarray(p + 46, p + 46 + nameLen);\n    // Bit 11 (0x0800) signals UTF-8 filename. xlsx archives are almost always\n    // UTF-8 already; treat bit-0 as UTF-8 too since CP437 ⊃ ASCII and xlsx uses\n    // ASCII paths.\n    const path = new TextDecoder('utf-8').decode(nameBytes);\n\n    // ZIP64 Extended Information rewrites whichever of {uncompSize, compSize,\n    // lfhOffset} are 0xFFFFFFFF sentinels in the canonical fields. The extra\n    // field stores them in the order listed in the spec (and omits any that\n    // weren't sentinels), so we have to track which slots to read.\n    const wantsUncomp = uncompSize === ZIP32_MAX_U32;\n    const wantsComp = compSize === ZIP32_MAX_U32;\n    const wantsOffset = lfhOffset === ZIP32_MAX_U32;\n    if (wantsUncomp || wantsComp || wantsOffset) {\n      const extra = readZip64Extra(b, p + 46 + nameLen, extraLen, wantsUncomp, wantsComp, wantsOffset, path);\n      if (extra.uncompSize !== undefined) uncompSize = extra.uncompSize;\n      if (extra.compSize !== undefined) compSize = extra.compSize;\n      if (extra.lfhOffset !== undefined) lfhOffset = extra.lfhOffset;\n    }\n    entries.push({ path, lfhOffset, compMethod, compSize, uncompSize, gpFlag });\n    p += 46 + nameLen + extraLen + commentLen;\n  }\n  return entries;\n}\n\n/** Read the compressed bytes for a CD entry by walking its local file header. */\nfunction readCompressedBytes(b: Uint8Array, entry: CdEntry): Uint8Array {\n  if (u32(b, entry.lfhOffset) !== SIG_LFH) {\n    throw new OpenXmlIoError(`openZip: malformed local file header for \"${entry.path}\"`);\n  }\n  const nameLen = u16(b, entry.lfhOffset + 26);\n  const extraLen = u16(b, entry.lfhOffset + 28);\n  const dataStart = entry.lfhOffset + 30 + nameLen + extraLen;\n  return b.subarray(dataStart, dataStart + entry.compSize);\n}\n\n/**\n * Open a buffered xlsx archive in random-access mode. The archive bytes stay\n * resident; entries inflate on demand inside `read(path)`.\n *\n * Falls back to `fflate.unzipSync` when the central directory uses ZIP64\n * sentinel values (entry count == 0xFFFF or any size field == 0xFFFFFFFF) so\n * external ZIP64 archives still load. xlsx files in the wild fit comfortably in\n * ZIP32; the fallback exists for safety.\n *\n * `decompressionLimits` opts the archive into the zip-bomb safeguards\n * documented on {@link DecompressionLimits}; pass `false` to disable. Defaults\n * fit any legitimate xlsx.\n */\nexport function openRandomAccessArchive(\n  bytes: Uint8Array,\n  decompressionLimits?: DecompressionLimitsInput,\n): ZipArchive {\n  // Quick sanity on min archive size.\n  if (bytes.length < 22) {\n    throw new OpenXmlIoError('openZip: archive is shorter than the minimum EOCD size (22 bytes)');\n  }\n\n  let eocdOff: number;\n  try {\n    eocdOff = findEocd(bytes);\n  } catch (cause) {\n    throw new OpenXmlIoError('openZip: archive is not a valid zip', { cause });\n  }\n\n  const resolvedLimits = resolveDecompressionLimits(decompressionLimits);\n\n  // Detect ZIP64 up front so a malformed ZIP64 record fails closed instead of\n  // falling back to `unzipSync`. The fallback inflates every entry before any\n  // bomb cap runs; the whole point of the ZIP64 path is to keep the streaming\n  // caps in play, so we must not silently downgrade when ZIP64 is in use.\n  const eocdTotalEntries = u16(bytes, eocdOff + 10);\n  const eocdCdSize = u32(bytes, eocdOff + 12);\n  const eocdCdOffset = u32(bytes, eocdOff + 16);\n  const claimsZip64 =\n    eocdTotalEntries === ZIP32_MAX_U16 ||\n    eocdCdSize === ZIP32_MAX_U32 ||\n    eocdCdOffset === ZIP32_MAX_U32;\n\n  let summary: CdSummary;\n  try {\n    summary = readCdSummary(bytes, eocdOff);\n  } catch (cause) {\n    if (claimsZip64) {\n      // ZIP64 sentinels are set but the matching record / locator is missing\n      // or invalid. Fail closed — `unzipSync` would inflate every entry.\n      throw cause instanceof OpenXmlIoError\n        ? cause\n        : new OpenXmlIoError('openZip: ZIP64 central directory is malformed', { cause });\n    }\n    // Plain ZIP32 with an unparseable EOCD — fflate's `unzipSync` is more\n    // tolerant of quirky archives; the post-hoc bomb check still applies.\n    return openViaUnzipSync(bytes, resolvedLimits);\n  }\n\n  let entries: CdEntry[];\n  try {\n    entries = parseCentralDirectory(bytes, summary.cdOffset, summary.totalEntries);\n  } catch (cause) {\n    if (claimsZip64) {\n      throw cause instanceof OpenXmlIoError\n        ? cause\n        : new OpenXmlIoError('openZip: ZIP64 central directory is malformed', { cause });\n    }\n    // Malformed ZIP32 CD — fall back to fflate which is more tolerant.\n    return openViaUnzipSync(bytes, resolvedLimits);\n  }\n\n  const byPath = new Map<string, CdEntry>();\n  for (const e of entries) byPath.set(e.path, e);\n\n  const budget: DecompressionBudget | null = resolvedLimits ? createBudget(resolvedLimits) : null;\n  if (budget) {\n    // Declared CD totals are cheap to inspect — reject obvious bombs before\n    // wiring up the inflate state machine.\n    checkDeclaredTotals(budget, entries);\n  }\n\n  // Per-entry inflate cache so repeated reads of the same path don't re-inflate\n  // — `read(path)` is documented as cheap on the second call (loadWorkbook\n  // touches several files multiple times).\n  const inflateCache = new Map<string, Uint8Array>();\n  let live = true;\n  let archiveBytes: Uint8Array | undefined = bytes;\n\n  const ensureLive = (): Uint8Array => {\n    if (!live || !archiveBytes) {\n      throw new OpenXmlIoError('openZip: archive is closed');\n    }\n    return archiveBytes;\n  };\n\n  const readEntry = (path: string): Uint8Array => {\n    const buf = ensureLive();\n    const cached = inflateCache.get(path);\n    if (cached) return cached;\n    const entry = byPath.get(path);\n    if (!entry) {\n      throw new OpenXmlIoError(`openZip: no entry at \"${path}\"`);\n    }\n    const compressed = readCompressedBytes(buf, entry);\n    let out: Uint8Array;\n    if (entry.compMethod === COMP_STORE) {\n      // STORE has ratio 1, so the only thing left to check is the absolute\n      // size cap (and the running archive total).\n      if (budget) {\n        if (compressed.byteLength > budget.limits.maxEntryUncompressedBytes) {\n          throw entryOverflowError(path, budget.limits.maxEntryUncompressedBytes);\n        }\n        recordInflated(budget, path, compressed.byteLength);\n      }\n      // Copy so callers can safely mutate the returned bytes without perturbing\n      // the underlying archive view.\n      out = compressed.slice();\n    } else if (entry.compMethod === COMP_DEFLATE) {\n      out = inflateBounded(path, compressed, budget);\n    } else {\n      throw new OpenXmlIoError(`openZip: unsupported compression method ${entry.compMethod} for \"${path}\"`);\n    }\n    inflateCache.set(path, out);\n    return out;\n  };\n\n  const readEntryStream = (path: string): ReadableStream<Uint8Array> => {\n    const buf = ensureLive();\n    const cached = inflateCache.get(path);\n    if (cached) return singleChunkStream(cached);\n    const entry = byPath.get(path);\n    if (!entry) {\n      throw new OpenXmlIoError(`openZip: no entry at \"${path}\"`);\n    }\n    const compressed = readCompressedBytes(buf, entry);\n    if (entry.compMethod === COMP_STORE) {\n      // STORE means the bytes on disk are the bytes the caller wants; no need\n      // to involve the inflate state machine. Run the same caps the sync\n      // `read()` STORE branch applies — otherwise an uncompressed bomb entry\n      // reached via `readStream()` would bypass the per-entry / archive-total\n      // accounting. Ratio is fixed at 1, so only the absolute bounds apply.\n      if (budget) {\n        if (compressed.byteLength > budget.limits.maxEntryUncompressedBytes) {\n          throw entryOverflowError(path, budget.limits.maxEntryUncompressedBytes);\n        }\n        recordInflated(budget, path, compressed.byteLength);\n      }\n      // Copy because callers may mutate the returned bytes.\n      return singleChunkStream(compressed.slice());\n    }\n    if (entry.compMethod !== COMP_DEFLATE) {\n      throw new OpenXmlIoError(`openZip: unsupported compression method ${entry.compMethod} for \"${path}\"`);\n    }\n    // DEFLATE: drive fflate's `Inflate` from `pull()` so the consumer's\n    // demand controls how much we inflate. Each `pull()` either emits one\n    // already-buffered inflated chunk or pushes one more block of compressed\n    // input — never both — so the ReadableStream internal queue stays at\n    // depth 1 and the producer can't race ahead of the consumer.\n    const entryCap = budget ? entryInflateCap(budget, compressed.byteLength) : Number.POSITIVE_INFINITY;\n    let entryEmitted = 0;\n    const pending: Uint8Array[] = [];\n    let pushedOffset = 0;\n    let inflaterFinal = false;\n    let inflateError: Error | undefined;\n    const inflater = new Inflate((chunk, final) => {\n      if (inflateError) return;\n      if (chunk.byteLength > 0) {\n        entryEmitted += chunk.byteLength;\n        if (entryEmitted > entryCap) {\n          inflateError = entryOverflowError(path, entryCap);\n          return;\n        }\n        if (budget) {\n          try {\n            recordInflated(budget, path, chunk.byteLength);\n          } catch (err) {\n            inflateError = err as Error;\n            return;\n          }\n        }\n        pending.push(chunk);\n      }\n      if (final) inflaterFinal = true;\n    });\n    return new ReadableStream<Uint8Array>({\n      pull(controller) {\n        if (inflateError) {\n          controller.error(inflateError);\n          return;\n        }\n        // Emit at most one already-buffered inflated chunk per pull; subsequent\n        // pulls drain the rest. This caps the stream's internal queue at one\n        // chunk regardless of how many ondata callbacks fflate fired off the\n        // most recent push.\n        const buffered = pending.shift();\n        if (buffered) {\n          controller.enqueue(buffered);\n          if (inflaterFinal && pending.length === 0 && pushedOffset >= compressed.byteLength) {\n            controller.close();\n          }\n          return;\n        }\n        // No buffered output: push one block of compressed input and let\n        // inflate's ondata fill `pending`. We stop pushing the moment we have\n        // something to emit so the next pull can return it without racing\n        // further inflation. `inflaterFinal` is set inside the ondata callback\n        // during the same `push` call that sets `isLast`, so reaching\n        // `pushedOffset >= compressed.byteLength` always ends the loop too.\n        while (pending.length === 0 && pushedOffset < compressed.byteLength) {\n          const end = Math.min(pushedOffset + INFLATE_CHUNK_BYTES, compressed.byteLength);\n          const slice = compressed.subarray(pushedOffset, end);\n          const isLast = end >= compressed.byteLength;\n          try {\n            inflater.push(slice, isLast);\n          } catch (cause) {\n            if (!inflateError) {\n              inflateError = new OpenXmlIoError(`openZip: failed to inflate \"${path}\"`, { cause });\n            }\n            controller.error(inflateError);\n            return;\n          }\n          // The ondata callback may have set inflateError (decompression-bomb\n          // guard tripping mid-inflate). Surface it to the consumer before\n          // continuing — otherwise we'd loop forever on a CD-lying bomb whose\n          // chunks all land beyond the cap and never make it into `pending`.\n          if (inflateError) {\n            controller.error(inflateError);\n            return;\n          }\n          pushedOffset = end;\n        }\n        const next = pending.shift();\n        if (next) {\n          controller.enqueue(next);\n        }\n        if (inflaterFinal && pending.length === 0 && pushedOffset >= compressed.byteLength) {\n          controller.close();\n        }\n      },\n      cancel() {\n        // Consumer abandoned the stream early — drop buffered chunks and\n        // advance the cursor past the end so the inflater and the compressed\n        // slice are eligible for GC. fflate's `Inflate` has no terminate API\n        // but losing the only reference is enough.\n        pending.length = 0;\n        pushedOffset = compressed.byteLength;\n        inflaterFinal = true;\n      },\n    });\n  };\n\n  return {\n    list(): string[] {\n      ensureLive();\n      return [...byPath.keys()].sort();\n    },\n    has(path: string): boolean {\n      if (!live) return false;\n      return byPath.has(path);\n    },\n    read(path: string): Uint8Array {\n      return readEntry(path);\n    },\n    async readAsync(path: string): Promise<Uint8Array> {\n      return readEntry(path);\n    },\n    readStream(path: string): ReadableStream<Uint8Array> {\n      return readEntryStream(path);\n    },\n    close(): void {\n      live = false;\n      archiveBytes = undefined;\n      inflateCache.clear();\n      byPath.clear();\n    },\n  };\n}\n\n/**\n * Inflate `compressed` into a single `Uint8Array`, abort if it crosses the\n * configured per-entry cap or the archive-wide budget. Uses fflate's streaming\n * `Inflate` so the abort can fire on any internal block boundary rather than\n * after fflate has materialised the entire payload.\n */\nfunction inflateBounded(\n  path: string,\n  compressed: Uint8Array,\n  budget: DecompressionBudget | null,\n): Uint8Array {\n  const cap = budget ? entryInflateCap(budget, compressed.byteLength) : Number.POSITIVE_INFINITY;\n  const acc: Uint8Array[] = [];\n  let emitted = 0;\n  let aborted: Error | undefined;\n  const inflater = new Inflate((chunk) => {\n    if (aborted) return;\n    if (chunk.byteLength === 0) return;\n    emitted += chunk.byteLength;\n    if (emitted > cap) {\n      aborted = entryOverflowError(path, cap);\n      return;\n    }\n    if (budget) {\n      try {\n        recordInflated(budget, path, chunk.byteLength);\n      } catch (err) {\n        aborted = err as Error;\n        return;\n      }\n    }\n    acc.push(chunk);\n  });\n  let off = 0;\n  while (off < compressed.byteLength) {\n    const end = Math.min(off + INFLATE_CHUNK_BYTES, compressed.byteLength);\n    const isLast = end >= compressed.byteLength;\n    try {\n      inflater.push(compressed.subarray(off, end), isLast);\n    } catch (cause) {\n      if (aborted) throw aborted;\n      throw new OpenXmlIoError(`openZip: failed to inflate \"${path}\"`, { cause });\n    }\n    if (aborted) throw aborted;\n    off = end;\n  }\n  const out = new Uint8Array(emitted);\n  let cursor = 0;\n  for (const chunk of acc) {\n    out.set(chunk, cursor);\n    cursor += chunk.byteLength;\n  }\n  return out;\n}\n\n/**\n * Fallback for archives we can't parse ourselves. The random-access reader\n * now understands ZIP64 EOCD + the Zip64 Extended Information extra field, so\n * the only way to reach this path is a malformed central directory. fflate's\n * `unzipSync` is more forgiving but inflates every entry up front; for\n * adversarial archives we still rely on a post-hoc cap check (the only one\n * available without our own streaming decoder).\n */\nfunction openViaUnzipSync(\n  bytes: Uint8Array,\n  limits: ReturnType<typeof resolveDecompressionLimits>,\n): ZipArchive {\n  let entries: Record<string, Uint8Array> | undefined;\n  try {\n    entries = unzipSync(bytes);\n  } catch (cause) {\n    throw new OpenXmlIoError('openZip: archive is not a valid zip', { cause });\n  }\n  // fflate's `unzipSync` returns already-inflated bytes — we can't abort the\n  // inflate mid-flight here, but a post-hoc check still rejects a malicious\n  // archive *before* any caller-level code touches the bytes. The peak memory\n  // spike is bounded by what fflate just produced; the random-access path\n  // catches the common cases (ZIP64 + malformed-but-parseable CDs) up front.\n  if (limits) {\n    const budget = createBudget(limits);\n    for (const [path, payload] of Object.entries(entries)) {\n      if (payload.byteLength > limits.maxEntryUncompressedBytes) {\n        throw new OpenXmlDecompressionBombError(\n          `openZip: entry \"${path}\" inflated to ${payload.byteLength} bytes,` +\n            ` exceeding the ${limits.maxEntryUncompressedBytes}-byte per-entry limit` +\n            ` (decompression-bomb guard).`,\n        );\n      }\n      recordInflated(budget, path, payload.byteLength);\n    }\n  }\n  let live = true;\n  return {\n    list(): string[] {\n      if (!live || !entries) throw new OpenXmlIoError('openZip: archive is closed');\n      return Object.keys(entries).sort();\n    },\n    has(path: string): boolean {\n      if (!live || !entries) return false;\n      return Object.hasOwn(entries, path);\n    },\n    read(path: string): Uint8Array {\n      if (!live || !entries) throw new OpenXmlIoError('openZip: archive is closed');\n      const e = entries[path];\n      if (!e) throw new OpenXmlIoError(`openZip: no entry at \"${path}\"`);\n      return e;\n    },\n    async readAsync(path: string): Promise<Uint8Array> {\n      return this.read(path);\n    },\n    readStream(path: string): ReadableStream<Uint8Array> {\n      // The unzipSync fallback path already has the entry fully inflated\n      // (that's the price of dropping back from random-access). Hand it out as\n      // a single-chunk stream so callers using the streaming reader don't have\n      // to branch on the implementation.\n      const inflated = this.read(path);\n      return singleChunkStream(inflated);\n    },\n    close(): void {\n      live = false;\n      entries = undefined;\n    },\n  };\n}\n","// ZIP read layer.\n//\n// `openZip(source)` walks the central directory once and inflates each entry on\n// demand inside `read(path)` (see `./random-access-reader.ts`). That keeps peak\n// memory at compressed-archive size + per-entry inflate scratch, instead of\n// holding every uncompressed entry resident at once the way the old `unzipSync`\n// shortcut did. The fallback path through fflate's `unzipSync` is preserved for\n// ZIP64 / non-standard archives.\n\nimport type { XlsxSource } from '../io/source';\nimport { OpenXmlIoError, OpenXmlNotImplementedError } from '../utils/exceptions';\nimport type { DecompressionLimits } from './decompression-guard';\nimport { openRandomAccessArchive } from './random-access-reader';\n\nconst CFB_MAGIC = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1];\n\nconst isCfbCompoundDocument = (bytes: Uint8Array): boolean => {\n  if (bytes.length < CFB_MAGIC.length) return false;\n  for (let i = 0; i < CFB_MAGIC.length; i++) {\n    if (bytes[i] !== CFB_MAGIC[i]) return false;\n  }\n  return true;\n};\n\nexport interface ZipArchive {\n  /** Sorted list of all entry paths in the archive. */\n  list(): string[];\n  /** Synchronous read; throws OpenXmlIoError when the path is unknown. */\n  read(path: string): Uint8Array;\n  /** Promise variant for symmetry with the future streaming reader. */\n  readAsync(path: string): Promise<Uint8Array>;\n  /**\n   * Streaming read: returns the entry's inflated bytes as a Web\n   * `ReadableStream<Uint8Array>` chunk-by-chunk. Lets callers (the streaming\n   * worksheet iterator, in particular) push the inflated payload through a SAX\n   * parser without first materialising it in full — peak memory for a sheet\n   * walk drops to the inflate window + SAX state instead of the entire\n   * uncompressed worksheet body. Throws OpenXmlIoError when the path is\n   * unknown.\n   */\n  readStream(path: string): ReadableStream<Uint8Array>;\n  /** Whether the archive holds an entry at the given path. */\n  has(path: string): boolean;\n  /** Release the in-memory entry table. Subsequent reads throw. */\n  close(): void;\n}\n\n/** Options for {@link openZip}. */\nexport interface OpenZipOptions {\n  /**\n   * Decompression-bomb safeguards applied while inflating archive entries. The\n   * default limits admit any legitimate xlsx and reject pathological archives\n   * (extreme compression ratios, gigabyte-scale entries). Pass `false` to\n   * disable the guard entirely — only safe when the source is fully trusted.\n   * See {@link DecompressionLimits} for the individual knobs.\n   */\n  decompressionLimits?: DecompressionLimits | false;\n}\n\n/**\n * Open a zip archive from any {@link XlsxSource}. The source is fully\n * materialised in memory, the central directory is parsed once, and each\n * entry is inflated on demand by {@link openRandomAccessArchive} — peak memory\n * stays at compressed-archive size plus per-entry inflate scratch rather than\n * holding every uncompressed entry resident. The fflate `unzipSync` fallback\n * is preserved internally for ZIP64 / non-standard archives the random-access\n * reader rejects.\n */\nexport async function openZip(source: XlsxSource, opts: OpenZipOptions = {}): Promise<ZipArchive> {\n  let bytes: Uint8Array;\n  try {\n    bytes = await source.toBytes();\n  } catch (cause) {\n    throw new OpenXmlIoError('openZip: failed to read source bytes', { cause });\n  }\n\n  // Encrypted xlsx files (Excel 2007+ password protection) wrap the real\n  // package inside an OLE Compound File Binary container with the magic\n  // signature `D0 CF 11 E0 A1 B1 1A E1`. Detect that early and surface a clear\n  // \"decrypt first\" error rather than letting fflate fail with a generic\n  // invalid-zip message.\n  if (isCfbCompoundDocument(bytes)) {\n    throw new OpenXmlNotImplementedError(\n      'Encrypted xlsx is not supported. Decrypt with msoffcrypto-tool first.',\n    );\n  }\n\n  return openRandomAccessArchive(bytes, opts.decompressionLimits);\n}\n"],"mappings":";;;;AAoDA,MAAa,+BAA4D;CACvE,2BAA2B,MAAM,OAAO;CACxC,2BAA2B,OAAO,OAAO;CACzC,qBAAqB;AACvB;;AAGA,MAAM,mCAAmC;AAYzC,MAAM,yBAAyB,OAAe,UAAwB;CACpE,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,GACnE,MAAM,IAAI,aACR,+BAA+B,MAAM,yCAAyC,OAAO,KAAK,GAC5F;AAEJ;;AAGA,SAAgB,2BACd,OACoC;CACpC,IAAI,UAAU,OAAO,OAAO;CAC5B,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,WAAwC;EAC5C,2BACE,MAAM,6BAA6B,6BAA6B;EAClE,2BACE,MAAM,6BAA6B,6BAA6B;EAClE,qBACE,MAAM,uBAAuB,6BAA6B;CAC9D;CACA,sBAAsB,6BAA6B,SAAS,yBAAyB;CACrF,sBAAsB,6BAA6B,SAAS,yBAAyB;CACrF,sBAAsB,uBAAuB,SAAS,mBAAmB;CACzE,OAAO;AACT;AAYA,SAAgB,aAAa,QAA0D;CACrF,OAAO;EAAE;EAAQ,eAAe;CAAE;AACpC;;;;;;AAOA,SAAgB,gBAAgB,QAA6B,gBAAgC;CAC3F,MAAM,EAAE,2BAA2B,wBAAwB,OAAO;CAClE,IAAI,iBAAiB,kCACnB,OAAO;CAET,MAAM,WAAW,iBAAiB;CAClC,OAAO,KAAK,IAAI,2BAA2B,QAAQ;AACrD;;AAGA,SAAgB,oBACd,QACA,iBACM;CACN,IAAI,gBAAgB;CACpB,KAAK,MAAM,SAAS,iBAAiB;EACnC,iBAAiB,MAAM;EACvB,IAAI,MAAM,aAAa,OAAO,OAAO,2BACnC,MAAM,IAAI,8BACR,mBAAmB,MAAM,KAAK,aAAa,MAAM,WAAW,qCACxC,OAAO,OAAO,0BAA0B,kDAE9D;EAEF,IACE,MAAM,YAAY,oCAClB,MAAM,aAAa,MAAM,WAAW,OAAO,OAAO,qBAElD,MAAM,IAAI,8BACR,mBAAmB,MAAM,KAAK,oBAAoB,MAAM,aAAa,MAAM,SAAA,CAAU,QAAQ,CAAC,EAAE,KACzF,MAAM,WAAW,GAAG,MAAM,SAAS,mBAAmB,OAAO,OAAO,oBAAoB,8CAEjG;CAEJ;CACA,IAAI,gBAAgB,OAAO,OAAO,2BAChC,MAAM,IAAI,8BACR,6CAA6C,cAAc,qBACrD,OAAO,OAAO,0BAA0B,gDAChD;AAEJ;;;;;;AAOA,SAAgB,eAAe,QAA6B,MAAc,OAAqB;CAC7F,OAAO,iBAAiB;CACxB,IAAI,OAAO,gBAAgB,OAAO,OAAO,2BACvC,MAAM,IAAI,8BACR,gDAAgD,OAAO,OAAO,0BAA0B,wBACnE,KAAK,8BAC5B;AAEJ;;AAGA,SAAgB,mBAAmB,MAAc,KAA4C;CAC3F,OAAO,IAAI,8BACT,8BAA8B,KAAK,aAAa,IAAI,mCACtD;AACF;;;;;;;;ACjJA,MAAM,sBAAsB,KAAK;AAEjC,MAAM,qBAAqB,UACzB,IAAI,eAA2B,EAC7B,MAAM,YAAY;CAChB,IAAI,MAAM,aAAa,GAAG,WAAW,QAAQ,KAAK;CAClD,WAAW,MAAM;AACnB,EACF,CAAC;AAEH,MAAM,WAAW;AACjB,MAAM,SAAS;AACf,MAAM,UAAU;AAChB,MAAM,iBAAiB;AACvB,MAAM,yBAAyB;AAC/B,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;AACtB,MAAM,qBAAqB;AAC3B,MAAM,wBAAwB;AAC9B,MAAM,aAAa;AACnB,MAAM,eAAe;AAYrB,MAAM,OAAO,GAAe,SAAyB,EAAE,QAAQ,MAAO,EAAE,MAAM,MAAM,MAAM;AAC1F,MAAM,OAAO,GAAe,QAAwB;CAClD,MAAM,KAAK,EAAE,QAAQ;CACrB,MAAM,KAAK,EAAE,MAAM,MAAM;CACzB,MAAM,KAAK,EAAE,MAAM,MAAM;CACzB,MAAM,KAAK,EAAE,MAAM,MAAM;CACzB,QAAQ,KAAM,MAAM,IAAM,MAAM,KAAO,MAAM,QAAS;AACxD;AAKA,MAAM,OAAO,GAAe,QAAwB;CAClD,MAAM,KAAK,IAAI,GAAG,GAAG;CACrB,MAAM,KAAK,IAAI,GAAG,MAAM,CAAC;CACzB,IAAI,KAAK,SACP,MAAM,IAAI,eACR,gCAAgC,IAAI,mCAAmC,GAAG,UAAU,GAAG,EACzF;CAEF,OAAO,KAAK,aAAc;AAC5B;;AAGA,SAAS,SAAS,GAAuB;CACvC,MAAM,WAAW,KAAK,IAAI,GAAG,EAAE,SAAS,KAAK,KAAM;CACnD,KAAK,IAAI,IAAI,EAAE,SAAS,IAAI,KAAK,UAAU,KACzC,IAAI,IAAI,GAAG,CAAC,MAAM,UAAU,OAAO;CAErC,MAAM,IAAI,eAAe,sDAAsD;AACjF;;;;;;;;;;AAiBA,SAAS,cAAc,GAAe,SAA4B;CAChE,IAAI,eAAe,IAAI,GAAG,UAAU,EAAE;CACtC,IAAI,SAAS,IAAI,GAAG,UAAU,EAAE;CAChC,IAAI,WAAW,IAAI,GAAG,UAAU,EAAE;CAIlC,IAAI,EADF,iBAAiB,iBAAiB,WAAW,iBAAiB,aAAa,gBAE3E,OAAO;EAAE;EAAc;EAAQ;CAAS;CAK1C,MAAM,aAAa,UAAU;CAC7B,IAAI,aAAa,KAAK,IAAI,GAAG,UAAU,MAAM,wBAC3C,MAAM,IAAI,eAAe,kEAAkE;CAE7F,MAAM,eAAe,IAAI,GAAG,aAAa,CAAC;CAC1C,IAAI,eAAe,KAAK,eAAe,KAAK,EAAE,QAC5C,MAAM,IAAI,eAAe,8BAA8B,aAAa,eAAe;CAErF,IAAI,IAAI,GAAG,YAAY,MAAM,gBAC3B,MAAM,IAAI,eAAe,iDAAiD,cAAc;CAE1F,eAAe,IAAI,GAAG,eAAe,EAAE;CACvC,SAAS,IAAI,GAAG,eAAe,EAAE;CACjC,WAAW,IAAI,GAAG,eAAe,EAAE;CACnC,OAAO;EAAE;EAAc;EAAQ;CAAS;AAC1C;;AAGA,SAAS,eACP,GACA,YACA,UACA,iBACA,eACA,gBACA,WACgE;CAChE,IAAI,IAAI;CACR,MAAM,MAAM,aAAa;CACzB,OAAO,IAAI,KAAK,KAAK;EACnB,MAAM,KAAK,IAAI,GAAG,CAAC;EACnB,MAAM,OAAO,IAAI,GAAG,IAAI,CAAC;EACzB,MAAM,YAAY,IAAI;EACtB,MAAM,OAAO,YAAY;EACzB,IAAI,OAAO,KAAK;EAChB,IAAI,OAAO,uBAAuB;GAMhC,MAAM,UAAU,kBAAkB,IAAI,MAAM,gBAAgB,IAAI,MAAM,iBAAiB,IAAI;GAC3F,IAAI,OAAO,QACT,MAAM,IAAI,eACR,2CAA2C,UAAU,aAAa,KAAK,qDACrB,QACpD;GAEF,IAAI,IAAI;GACR,MAAM,SAAyE,CAAC;GAChF,IAAI,iBAAiB;IACnB,OAAO,aAAa,IAAI,GAAG,CAAC;IAC5B,KAAK;GACP;GACA,IAAI,eAAe;IACjB,OAAO,WAAW,IAAI,GAAG,CAAC;IAC1B,KAAK;GACP;GACA,IAAI,gBAAgB;IAClB,OAAO,YAAY,IAAI,GAAG,CAAC;IAC3B,KAAK;GACP;GACA,OAAO;EACT;EACA,IAAI;CACN;CACA,OAAO,CAAC;AACV;;AAGA,SAAS,sBAAsB,GAAe,UAAkB,eAAkC;CAChG,MAAM,UAAqB,CAAC;CAC5B,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,eAAe,KAAK;EACtC,IAAI,IAAI,GAAG,CAAC,MAAM,QAChB,MAAM,IAAI,eAAe,gDAAgD,GAAG;EAE9E,MAAM,SAAS,IAAI,GAAG,IAAI,CAAC;EAC3B,MAAM,aAAa,IAAI,GAAG,IAAI,EAAE;EAChC,IAAI,WAAW,IAAI,GAAG,IAAI,EAAE;EAC5B,IAAI,aAAa,IAAI,GAAG,IAAI,EAAE;EAC9B,MAAM,UAAU,IAAI,GAAG,IAAI,EAAE;EAC7B,MAAM,WAAW,IAAI,GAAG,IAAI,EAAE;EAC9B,MAAM,aAAa,IAAI,GAAG,IAAI,EAAE;EAChC,IAAI,YAAY,IAAI,GAAG,IAAI,EAAE;EAC7B,MAAM,YAAY,EAAE,SAAS,IAAI,IAAI,IAAI,KAAK,OAAO;EAIrD,MAAM,OAAO,IAAI,YAAY,OAAO,CAAC,CAAC,OAAO,SAAS;EAMtD,MAAM,cAAc,eAAe;EACnC,MAAM,YAAY,aAAa;EAC/B,MAAM,cAAc,cAAc;EAClC,IAAI,eAAe,aAAa,aAAa;GAC3C,MAAM,QAAQ,eAAe,GAAG,IAAI,KAAK,SAAS,UAAU,aAAa,WAAW,aAAa,IAAI;GACrG,IAAI,MAAM,eAAe,KAAA,GAAW,aAAa,MAAM;GACvD,IAAI,MAAM,aAAa,KAAA,GAAW,WAAW,MAAM;GACnD,IAAI,MAAM,cAAc,KAAA,GAAW,YAAY,MAAM;EACvD;EACA,QAAQ,KAAK;GAAE;GAAM;GAAW;GAAY;GAAU;GAAY;EAAO,CAAC;EAC1E,KAAK,KAAK,UAAU,WAAW;CACjC;CACA,OAAO;AACT;;AAGA,SAAS,oBAAoB,GAAe,OAA4B;CACtE,IAAI,IAAI,GAAG,MAAM,SAAS,MAAM,SAC9B,MAAM,IAAI,eAAe,6CAA6C,MAAM,KAAK,EAAE;CAErF,MAAM,UAAU,IAAI,GAAG,MAAM,YAAY,EAAE;CAC3C,MAAM,WAAW,IAAI,GAAG,MAAM,YAAY,EAAE;CAC5C,MAAM,YAAY,MAAM,YAAY,KAAK,UAAU;CACnD,OAAO,EAAE,SAAS,WAAW,YAAY,MAAM,QAAQ;AACzD;;;;;;;;;;;;;;AAeA,SAAgB,wBACd,OACA,qBACY;CAEZ,IAAI,MAAM,SAAS,IACjB,MAAM,IAAI,eAAe,mEAAmE;CAG9F,IAAI;CACJ,IAAI;EACF,UAAU,SAAS,KAAK;CAC1B,SAAS,OAAO;EACd,MAAM,IAAI,eAAe,uCAAuC,EAAE,MAAM,CAAC;CAC3E;CAEA,MAAM,iBAAiB,2BAA2B,mBAAmB;CAMrE,MAAM,mBAAmB,IAAI,OAAO,UAAU,EAAE;CAChD,MAAM,aAAa,IAAI,OAAO,UAAU,EAAE;CAC1C,MAAM,eAAe,IAAI,OAAO,UAAU,EAAE;CAC5C,MAAM,cACJ,qBAAqB,iBACrB,eAAe,iBACf,iBAAiB;CAEnB,IAAI;CACJ,IAAI;EACF,UAAU,cAAc,OAAO,OAAO;CACxC,SAAS,OAAO;EACd,IAAI,aAGF,MAAM,iBAAiB,iBACnB,QACA,IAAI,eAAe,iDAAiD,EAAE,MAAM,CAAC;EAInF,OAAO,iBAAiB,OAAO,cAAc;CAC/C;CAEA,IAAI;CACJ,IAAI;EACF,UAAU,sBAAsB,OAAO,QAAQ,UAAU,QAAQ,YAAY;CAC/E,SAAS,OAAO;EACd,IAAI,aACF,MAAM,iBAAiB,iBACnB,QACA,IAAI,eAAe,iDAAiD,EAAE,MAAM,CAAC;EAGnF,OAAO,iBAAiB,OAAO,cAAc;CAC/C;CAEA,MAAM,yBAAS,IAAI,IAAqB;CACxC,KAAK,MAAM,KAAK,SAAS,OAAO,IAAI,EAAE,MAAM,CAAC;CAE7C,MAAM,SAAqC,iBAAiB,aAAa,cAAc,IAAI;CAC3F,IAAI,QAGF,oBAAoB,QAAQ,OAAO;CAMrC,MAAM,+BAAe,IAAI,IAAwB;CACjD,IAAI,OAAO;CACX,IAAI,eAAuC;CAE3C,MAAM,mBAA+B;EACnC,IAAI,CAAC,QAAQ,CAAC,cACZ,MAAM,IAAI,eAAe,4BAA4B;EAEvD,OAAO;CACT;CAEA,MAAM,aAAa,SAA6B;EAC9C,MAAM,MAAM,WAAW;EACvB,MAAM,SAAS,aAAa,IAAI,IAAI;EACpC,IAAI,QAAQ,OAAO;EACnB,MAAM,QAAQ,OAAO,IAAI,IAAI;EAC7B,IAAI,CAAC,OACH,MAAM,IAAI,eAAe,yBAAyB,KAAK,EAAE;EAE3D,MAAM,aAAa,oBAAoB,KAAK,KAAK;EACjD,IAAI;EACJ,IAAI,MAAM,eAAe,YAAY;GAGnC,IAAI,QAAQ;IACV,IAAI,WAAW,aAAa,OAAO,OAAO,2BACxC,MAAM,mBAAmB,MAAM,OAAO,OAAO,yBAAyB;IAExE,eAAe,QAAQ,MAAM,WAAW,UAAU;GACpD;GAGA,MAAM,WAAW,MAAM;EACzB,OAAO,IAAI,MAAM,eAAe,cAC9B,MAAM,eAAe,MAAM,YAAY,MAAM;OAE7C,MAAM,IAAI,eAAe,2CAA2C,MAAM,WAAW,QAAQ,KAAK,EAAE;EAEtG,aAAa,IAAI,MAAM,GAAG;EAC1B,OAAO;CACT;CAEA,MAAM,mBAAmB,SAA6C;EACpE,MAAM,MAAM,WAAW;EACvB,MAAM,SAAS,aAAa,IAAI,IAAI;EACpC,IAAI,QAAQ,OAAO,kBAAkB,MAAM;EAC3C,MAAM,QAAQ,OAAO,IAAI,IAAI;EAC7B,IAAI,CAAC,OACH,MAAM,IAAI,eAAe,yBAAyB,KAAK,EAAE;EAE3D,MAAM,aAAa,oBAAoB,KAAK,KAAK;EACjD,IAAI,MAAM,eAAe,YAAY;GAMnC,IAAI,QAAQ;IACV,IAAI,WAAW,aAAa,OAAO,OAAO,2BACxC,MAAM,mBAAmB,MAAM,OAAO,OAAO,yBAAyB;IAExE,eAAe,QAAQ,MAAM,WAAW,UAAU;GACpD;GAEA,OAAO,kBAAkB,WAAW,MAAM,CAAC;EAC7C;EACA,IAAI,MAAM,eAAe,cACvB,MAAM,IAAI,eAAe,2CAA2C,MAAM,WAAW,QAAQ,KAAK,EAAE;EAOtG,MAAM,WAAW,SAAS,gBAAgB,QAAQ,WAAW,UAAU,IAAI,OAAO;EAClF,IAAI,eAAe;EACnB,MAAM,UAAwB,CAAC;EAC/B,IAAI,eAAe;EACnB,IAAI,gBAAgB;EACpB,IAAI;EACJ,MAAM,WAAW,IAAI,SAAS,OAAO,UAAU;GAC7C,IAAI,cAAc;GAClB,IAAI,MAAM,aAAa,GAAG;IACxB,gBAAgB,MAAM;IACtB,IAAI,eAAe,UAAU;KAC3B,eAAe,mBAAmB,MAAM,QAAQ;KAChD;IACF;IACA,IAAI,QACF,IAAI;KACF,eAAe,QAAQ,MAAM,MAAM,UAAU;IAC/C,SAAS,KAAK;KACZ,eAAe;KACf;IACF;IAEF,QAAQ,KAAK,KAAK;GACpB;GACA,IAAI,OAAO,gBAAgB;EAC7B,CAAC;EACD,OAAO,IAAI,eAA2B;GACpC,KAAK,YAAY;IACf,IAAI,cAAc;KAChB,WAAW,MAAM,YAAY;KAC7B;IACF;IAKA,MAAM,WAAW,QAAQ,MAAM;IAC/B,IAAI,UAAU;KACZ,WAAW,QAAQ,QAAQ;KAC3B,IAAI,iBAAiB,QAAQ,WAAW,KAAK,gBAAgB,WAAW,YACtE,WAAW,MAAM;KAEnB;IACF;IAOA,OAAO,QAAQ,WAAW,KAAK,eAAe,WAAW,YAAY;KACnE,MAAM,MAAM,KAAK,IAAI,eAAe,qBAAqB,WAAW,UAAU;KAC9E,MAAM,QAAQ,WAAW,SAAS,cAAc,GAAG;KACnD,MAAM,SAAS,OAAO,WAAW;KACjC,IAAI;MACF,SAAS,KAAK,OAAO,MAAM;KAC7B,SAAS,OAAO;MACd,IAAI,CAAC,cACH,eAAe,IAAI,eAAe,+BAA+B,KAAK,IAAI,EAAE,MAAM,CAAC;MAErF,WAAW,MAAM,YAAY;MAC7B;KACF;KAKA,IAAI,cAAc;MAChB,WAAW,MAAM,YAAY;MAC7B;KACF;KACA,eAAe;IACjB;IACA,MAAM,OAAO,QAAQ,MAAM;IAC3B,IAAI,MACF,WAAW,QAAQ,IAAI;IAEzB,IAAI,iBAAiB,QAAQ,WAAW,KAAK,gBAAgB,WAAW,YACtE,WAAW,MAAM;GAErB;GACA,SAAS;IAKP,QAAQ,SAAS;IACjB,eAAe,WAAW;IAC1B,gBAAgB;GAClB;EACF,CAAC;CACH;CAEA,OAAO;EACL,OAAiB;GACf,WAAW;GACX,OAAO,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK;EACjC;EACA,IAAI,MAAuB;GACzB,IAAI,CAAC,MAAM,OAAO;GAClB,OAAO,OAAO,IAAI,IAAI;EACxB;EACA,KAAK,MAA0B;GAC7B,OAAO,UAAU,IAAI;EACvB;EACA,MAAM,UAAU,MAAmC;GACjD,OAAO,UAAU,IAAI;EACvB;EACA,WAAW,MAA0C;GACnD,OAAO,gBAAgB,IAAI;EAC7B;EACA,QAAc;GACZ,OAAO;GACP,eAAe,KAAA;GACf,aAAa,MAAM;GACnB,OAAO,MAAM;EACf;CACF;AACF;;;;;;;AAQA,SAAS,eACP,MACA,YACA,QACY;CACZ,MAAM,MAAM,SAAS,gBAAgB,QAAQ,WAAW,UAAU,IAAI,OAAO;CAC7E,MAAM,MAAoB,CAAC;CAC3B,IAAI,UAAU;CACd,IAAI;CACJ,MAAM,WAAW,IAAI,SAAS,UAAU;EACtC,IAAI,SAAS;EACb,IAAI,MAAM,eAAe,GAAG;EAC5B,WAAW,MAAM;EACjB,IAAI,UAAU,KAAK;GACjB,UAAU,mBAAmB,MAAM,GAAG;GACtC;EACF;EACA,IAAI,QACF,IAAI;GACF,eAAe,QAAQ,MAAM,MAAM,UAAU;EAC/C,SAAS,KAAK;GACZ,UAAU;GACV;EACF;EAEF,IAAI,KAAK,KAAK;CAChB,CAAC;CACD,IAAI,MAAM;CACV,OAAO,MAAM,WAAW,YAAY;EAClC,MAAM,MAAM,KAAK,IAAI,MAAM,qBAAqB,WAAW,UAAU;EACrE,MAAM,SAAS,OAAO,WAAW;EACjC,IAAI;GACF,SAAS,KAAK,WAAW,SAAS,KAAK,GAAG,GAAG,MAAM;EACrD,SAAS,OAAO;GACd,IAAI,SAAS,MAAM;GACnB,MAAM,IAAI,eAAe,+BAA+B,KAAK,IAAI,EAAE,MAAM,CAAC;EAC5E;EACA,IAAI,SAAS,MAAM;EACnB,MAAM;CACR;CACA,MAAM,MAAM,IAAI,WAAW,OAAO;CAClC,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,KAAK;EACvB,IAAI,IAAI,OAAO,MAAM;EACrB,UAAU,MAAM;CAClB;CACA,OAAO;AACT;;;;;;;;;AAUA,SAAS,iBACP,OACA,QACY;CACZ,IAAI;CACJ,IAAI;EACF,UAAU,UAAU,KAAK;CAC3B,SAAS,OAAO;EACd,MAAM,IAAI,eAAe,uCAAuC,EAAE,MAAM,CAAC;CAC3E;CAMA,IAAI,QAAQ;EACV,MAAM,SAAS,aAAa,MAAM;EAClC,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,OAAO,GAAG;GACrD,IAAI,QAAQ,aAAa,OAAO,2BAC9B,MAAM,IAAI,8BACR,mBAAmB,KAAK,gBAAgB,QAAQ,WAAW,wBACvC,OAAO,0BAA0B,kDAEvD;GAEF,eAAe,QAAQ,MAAM,QAAQ,UAAU;EACjD;CACF;CACA,IAAI,OAAO;CACX,OAAO;EACL,OAAiB;GACf,IAAI,CAAC,QAAQ,CAAC,SAAS,MAAM,IAAI,eAAe,4BAA4B;GAC5E,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK;EACnC;EACA,IAAI,MAAuB;GACzB,IAAI,CAAC,QAAQ,CAAC,SAAS,OAAO;GAC9B,OAAO,OAAO,OAAO,SAAS,IAAI;EACpC;EACA,KAAK,MAA0B;GAC7B,IAAI,CAAC,QAAQ,CAAC,SAAS,MAAM,IAAI,eAAe,4BAA4B;GAC5E,MAAM,IAAI,QAAQ;GAClB,IAAI,CAAC,GAAG,MAAM,IAAI,eAAe,yBAAyB,KAAK,EAAE;GACjE,OAAO;EACT;EACA,MAAM,UAAU,MAAmC;GACjD,OAAO,KAAK,KAAK,IAAI;EACvB;EACA,WAAW,MAA0C;GAKnD,MAAM,WAAW,KAAK,KAAK,IAAI;GAC/B,OAAO,kBAAkB,QAAQ;EACnC;EACA,QAAc;GACZ,OAAO;GACP,UAAU,KAAA;EACZ;CACF;AACF;;;AC7nBA,MAAM,YAAY;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;AAAI;AAEjE,MAAM,yBAAyB,UAA+B;CAC5D,IAAI,MAAM,SAAS,UAAU,QAAQ,OAAO;CAC5C,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KACpC,IAAI,MAAM,OAAO,UAAU,IAAI,OAAO;CAExC,OAAO;AACT;;;;;;;;;;AA8CA,eAAsB,QAAQ,QAAoB,OAAuB,CAAC,GAAwB;CAChG,IAAI;CACJ,IAAI;EACF,QAAQ,MAAM,OAAO,QAAQ;CAC/B,SAAS,OAAO;EACd,MAAM,IAAI,eAAe,wCAAwC,EAAE,MAAM,CAAC;CAC5E;CAOA,IAAI,sBAAsB,KAAK,GAC7B,MAAM,IAAI,2BACR,uEACF;CAGF,OAAO,wBAAwB,OAAO,KAAK,mBAAmB;AAChE"}