@office-kit/xlsx 0.13.0 → 0.15.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.
- package/README.md +26 -0
- package/dist/{cell-style-BFmJOmcx.mjs → cell-style-CNsET6WU.mjs} +2 -2
- package/dist/{cell-style-BFmJOmcx.mjs.map → cell-style-CNsET6WU.mjs.map} +1 -1
- package/dist/{differential-H9SjEeIU.mjs → differential-BPKa7wXi.mjs} +2 -2
- package/dist/{differential-H9SjEeIU.mjs.map → differential-BPKa7wXi.mjs.map} +1 -1
- package/dist/io.mjs +2 -2
- package/dist/iterparse-DZE0zy2M.mjs +187 -0
- package/dist/iterparse-DZE0zy2M.mjs.map +1 -0
- package/dist/{load-CJdGh50R.mjs → load-CspGc399.mjs} +5 -5
- package/dist/{load-CJdGh50R.mjs.map → load-CspGc399.mjs.map} +1 -1
- package/dist/node.mjs +8 -4
- package/dist/node.mjs.map +1 -1
- package/dist/{reader-BNaUTVCy.mjs → reader-DHMxLBQV.mjs} +81 -26
- package/dist/reader-DHMxLBQV.mjs.map +1 -0
- package/dist/{save-CjarR7Rp.mjs → save-DgPf1DyF.mjs} +84 -44
- package/dist/save-DgPf1DyF.mjs.map +1 -0
- package/dist/streaming.mjs +156 -62
- package/dist/streaming.mjs.map +1 -1
- package/dist/styles.mjs +3 -3
- package/dist/{stylesheet-writer-D7Uug85X.mjs → stylesheet-writer-TxsinwbO.mjs} +71 -67
- package/dist/stylesheet-writer-TxsinwbO.mjs.map +1 -0
- package/dist/{table-Ccro4rrz.mjs → table-BrP0RwUd.mjs} +2 -2
- package/dist/{table-Ccro4rrz.mjs.map → table-BrP0RwUd.mjs.map} +1 -1
- package/dist/utf8-OAkCDG5g.mjs +21 -0
- package/dist/utf8-OAkCDG5g.mjs.map +1 -0
- package/dist/{workbook-B15-T4cs.mjs → workbook-DQCslHzY.mjs} +3 -3
- package/dist/{workbook-B15-T4cs.mjs.map → workbook-DQCslHzY.mjs.map} +1 -1
- package/dist/workbook.mjs +1 -1
- package/dist/worksheet/index.d.ts +1 -1
- package/dist/worksheet/ref-batch.d.ts +15 -0
- package/dist/worksheet/worksheet.d.ts +33 -0
- package/dist/worksheet/writer.d.ts +23 -4
- package/dist/{worksheet-4xW-i8j9.mjs → worksheet-B2NOQ6dM.mjs} +100 -2
- package/dist/worksheet-B2NOQ6dM.mjs.map +1 -0
- package/dist/worksheet.mjs +3 -3
- package/dist/{writer-Z7cF_CBk.mjs → writer-C38RBZCy.mjs} +2 -2
- package/dist/writer-C38RBZCy.mjs.map +1 -0
- package/dist/xml.mjs +2 -1
- package/dist/xml.mjs.map +1 -1
- package/dist/zip/decompression-guard.d.ts +22 -6
- package/dist/zip/inflate-cache.d.ts +22 -0
- package/dist/zip/reader.d.ts +11 -6
- package/dist/zip/writer.d.ts +6 -2
- package/dist/zip.mjs +2 -2
- package/package.json +1 -1
- package/dist/reader-BNaUTVCy.mjs.map +0 -1
- package/dist/save-CjarR7Rp.mjs.map +0 -1
- package/dist/stylesheet-writer-D7Uug85X.mjs.map +0 -1
- package/dist/utf8-Tn3nzyik.mjs +0 -143
- package/dist/utf8-Tn3nzyik.mjs.map +0 -1
- package/dist/worksheet-4xW-i8j9.mjs.map +0 -1
- package/dist/writer-Z7cF_CBk.mjs.map +0 -1
package/dist/worksheet.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as iterCells, $t as
|
|
2
|
-
import { a as makeCfRule, i as makeDataValidation, n as makeTableColumn, o as makeConditionalFormatting, r as makeTableDefinition, t as addExcelTable } from "./table-
|
|
1
|
+
import { $ as iterCells, $t as makeFreezePane, A as getCellsInRange, At as setFreezePanes, B as getPopulatedColumnIndices, Bt as ungroupColumns, C as findCells, Ct as setColumnDimension, Dt as setComments, E as getCellByCoord, Et as setComment, F as getMaxCol, Ft as setRowHeight, G as groupColumns, Gt as unhideRows, H as getRangeValues, Ht as unhideColumn, I as getMaxRow, It as setRowHeights, J as hideColumns, Jt as writeRange, K as groupRows, Kt as unmergeCells, L as getMergedCells, Lt as setSheetTabColor, M as getColumnDimension, Mt as setHyperlinks, N as getDataExtent, Nt as setRangeValues, Ot as setDefaultColumnWidth, P as getFreezePanes, Pt as setRowDimension, Q as isWorksheetEmpty, Qt as freezePaneRef, R as getMergedRangeAt, Rt as setSheetViewMode, S as expandRowGroup, St as setCellByCoord, T as getCell, Tt as setColumnWidths, U as getRowDimension, Ut as unhideColumns, V as getPopulatedRowIndices, Vt as ungroupRows, W as getTable, Wt as unhideRow, X as hideRows, Xt as makeSheetProperties, Y as hideRow, Yt as makeHyperlink, Z as isMergedCell, Zt as makeLegacyComment, _ as countCellsByKind, _n as unionRange, _t as removeHyperlink, a as addTable, an as isCellInRange, at as listTables, b as ensureCellByCoord, bt as setAutoFilter, c as applyToRange, ct as moveRange, d as clearAllCells, dn as rangeArea, dt as removeAllDataValidations, en as makeSheetView, et as iterRows, f as clearRange, fn as rangeContainsCell, ft as removeAllHyperlinks, g as countCells, gn as shiftRange, gt as removeDataValidations, h as copyRange, hn as rangesOverlap, ht as removeCellWatches, i as addIgnoredError, in as intersectionRange, it as listHyperlinks, j as getCellsInRow, jt as setHyperlink, k as getCellsInColumn, kt as setDefaultRowHeight, l as autofitColumns, lt as removeAllComments, m as collapseRowGroup, mt as removeAllTables, n as addConditionalFormatting, nn as makeRowDimension, nt as listComments, o as appendRow, on as isRangeInRange, ot as makeWorksheet, p as collapseColumnGroup, pn as rangeContainsRange, pt as removeAllMergedRanges, q as hideColumn, qt as unmergeCellsAt, r as addDataValidation, rn as expandRangeStr, rt as listDataValidations, s as appendRows, st as mergeCells, t as addCellWatch, tn as makeColumnDimension, tt as iterValues, ut as removeAllConditionalFormatting, v as deleteCell, vt as removeIgnoredErrors, w as getAutoFilter, wt as setColumnWidth, x as expandColumnGroup, xt as setCell, y as ensureCell, yt as removeTable, z as getNonEmptyCellCount, zt as setSheetZoom } from "./worksheet-B2NOQ6dM.mjs";
|
|
2
|
+
import { a as makeCfRule, i as makeDataValidation, n as makeTableColumn, o as makeConditionalFormatting, r as makeTableDefinition, t as addExcelTable } from "./table-BrP0RwUd.mjs";
|
|
3
3
|
//#region src/worksheet/auto-filter.ts
|
|
4
4
|
function makeAutoFilter(opts) {
|
|
5
5
|
return {
|
|
@@ -242,6 +242,6 @@ const makeScenarioList = (opts = {}) => ({
|
|
|
242
242
|
...opts.sqref !== void 0 ? { sqref: opts.sqref } : {}
|
|
243
243
|
});
|
|
244
244
|
//#endregion
|
|
245
|
-
export { HEADER_FOOTER_CODES, addCellWatch, addConditionalFormatting, addDataValidation, addExcelTable, addIgnoredError, addTable, appendRow, appendRows, applyToRange, autofitColumns, buildHeaderFooterText, clearAllCells, clearRange, collapseColumnGroup, collapseRowGroup, copyRange, countCells, countCellsByKind, deleteCell, ensureCell, ensureCellByCoord, expandColumnGroup, expandRangeStr, expandRowGroup, findCells, freezePaneRef, getAutoFilter, getCell, getCellByCoord, getCellsInColumn, getCellsInRange, getCellsInRow, getColumnDimension, getDataExtent, getFreezePanes, getMaxCol, getMaxRow, getMergedCells, getMergedRangeAt, getNonEmptyCellCount, getPopulatedColumnIndices, getPopulatedRowIndices, getRangeValues, getRowDimension, getTable, groupColumns, groupRows, hideColumn, hideColumns, hideRow, hideRows, intersectionRange, isCellInRange, isMergedCell, isRangeInRange, isWorksheetEmpty, iterCells, iterRows, iterValues, listComments, listDataValidations, listHyperlinks, listTables, makeAutoFilter, makeCellSmartTag, makeCellSmartTagProperty, makeCellSmartTags, makeCellWatch, makeCfRule, makeColumnDimension, makeConditionalFormatting, makeCustomSheetView, makeDataConsolidate, makeDataValidation, makeFilterColumn, makeFormControl, makeFreezePane, makeHeaderFooter, makeHyperlink, makeIgnoredError, makeLegacyComment, makeOleObject, makePageBreak, makePageMargins, makePageSetup, makePrintOptions, makeProtectedRange, makeRowDimension, makeScenario, makeScenarioInputCell, makeScenarioList, makeSheetProperties, makeSheetProtection, makeSheetView, makeSortCondition, makeSortState, makeTableColumn, makeTableDefinition, makeWebPublishItem, makeWorksheet, makeWorksheetCustomProperty, makeWorksheetPhoneticProperties, mergeCells, moveRange, rangeArea, rangeContainsCell, rangeContainsRange, rangesOverlap, removeAllComments, removeAllConditionalFormatting, removeAllDataValidations, removeAllHyperlinks, removeAllMergedRanges, removeAllTables, removeCellWatches, removeDataValidations, removeHyperlink, removeIgnoredErrors, removeTable, setAutoFilter, setCell, setCellByCoord, setColumnDimension, setColumnWidth, setColumnWidths, setComment, setDefaultColumnWidth, setDefaultRowHeight, setFreezePanes, setHyperlink, setRangeValues, setRowDimension, setRowHeight, setRowHeights, setSheetTabColor, setSheetViewMode, setSheetZoom, shiftRange, ungroupColumns, ungroupRows, unhideColumn, unhideColumns, unhideRow, unhideRows, unionRange, unmergeCells, unmergeCellsAt, writeRange };
|
|
245
|
+
export { HEADER_FOOTER_CODES, addCellWatch, addConditionalFormatting, addDataValidation, addExcelTable, addIgnoredError, addTable, appendRow, appendRows, applyToRange, autofitColumns, buildHeaderFooterText, clearAllCells, clearRange, collapseColumnGroup, collapseRowGroup, copyRange, countCells, countCellsByKind, deleteCell, ensureCell, ensureCellByCoord, expandColumnGroup, expandRangeStr, expandRowGroup, findCells, freezePaneRef, getAutoFilter, getCell, getCellByCoord, getCellsInColumn, getCellsInRange, getCellsInRow, getColumnDimension, getDataExtent, getFreezePanes, getMaxCol, getMaxRow, getMergedCells, getMergedRangeAt, getNonEmptyCellCount, getPopulatedColumnIndices, getPopulatedRowIndices, getRangeValues, getRowDimension, getTable, groupColumns, groupRows, hideColumn, hideColumns, hideRow, hideRows, intersectionRange, isCellInRange, isMergedCell, isRangeInRange, isWorksheetEmpty, iterCells, iterRows, iterValues, listComments, listDataValidations, listHyperlinks, listTables, makeAutoFilter, makeCellSmartTag, makeCellSmartTagProperty, makeCellSmartTags, makeCellWatch, makeCfRule, makeColumnDimension, makeConditionalFormatting, makeCustomSheetView, makeDataConsolidate, makeDataValidation, makeFilterColumn, makeFormControl, makeFreezePane, makeHeaderFooter, makeHyperlink, makeIgnoredError, makeLegacyComment, makeOleObject, makePageBreak, makePageMargins, makePageSetup, makePrintOptions, makeProtectedRange, makeRowDimension, makeScenario, makeScenarioInputCell, makeScenarioList, makeSheetProperties, makeSheetProtection, makeSheetView, makeSortCondition, makeSortState, makeTableColumn, makeTableDefinition, makeWebPublishItem, makeWorksheet, makeWorksheetCustomProperty, makeWorksheetPhoneticProperties, mergeCells, moveRange, rangeArea, rangeContainsCell, rangeContainsRange, rangesOverlap, removeAllComments, removeAllConditionalFormatting, removeAllDataValidations, removeAllHyperlinks, removeAllMergedRanges, removeAllTables, removeCellWatches, removeDataValidations, removeHyperlink, removeIgnoredErrors, removeTable, setAutoFilter, setCell, setCellByCoord, setColumnDimension, setColumnWidth, setColumnWidths, setComment, setComments, setDefaultColumnWidth, setDefaultRowHeight, setFreezePanes, setHyperlink, setHyperlinks, setRangeValues, setRowDimension, setRowHeight, setRowHeights, setSheetTabColor, setSheetViewMode, setSheetZoom, shiftRange, ungroupColumns, ungroupRows, unhideColumn, unhideColumns, unhideRow, unhideRows, unionRange, unmergeCells, unmergeCellsAt, writeRange };
|
|
246
246
|
|
|
247
247
|
//# sourceMappingURL=worksheet.mjs.map
|
|
@@ -263,11 +263,11 @@ function createZipWriter(sink, opts = {}) {
|
|
|
263
263
|
zipFinishResolve();
|
|
264
264
|
zipFinishResolve = void 0;
|
|
265
265
|
}
|
|
266
|
-
writer.abort?.(cause);
|
|
266
|
+
return writer.abort?.(cause);
|
|
267
267
|
}
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
270
|
//#endregion
|
|
271
271
|
export { createZipWriter as t };
|
|
272
272
|
|
|
273
|
-
//# sourceMappingURL=writer-
|
|
273
|
+
//# sourceMappingURL=writer-C38RBZCy.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer-C38RBZCy.mjs","names":["ZIP32_MAX_ENTRIES"],"sources":["../src/zip/zip64-patch.ts","../src/zip/writer.ts"],"sourcesContent":["// ZIP64 post-processing for archives whose entry count exceeds the\n// 16-bit ZIP32 cap (65535). fflate's `Zip` writer always emits a\n// plain ZIP32 EOCD; for archives with more entries we keep its\n// per-entry LFH/CDH layout (correct as long as no individual size or\n// offset overflows 32 bits) and splice in a ZIP64 End-of-Central-\n// Directory record + locator before the EOCD, then patch the EOCD's\n// entry-count fields with the 0xFFFF sentinel that signals \"consult\n// the ZIP64 record for the real values\".\n//\n// The input is fflate's *final* chunk — the trailing [CD | EOCD] block\n// it emits in one ondata callback when `Zip.end()` is called. The\n// preceding entry-data chunks have already been streamed to the sink,\n// so we operate on the final chunk in isolation. The global EOCD\n// offset (needed for the ZIP64 locator) is derivable from cd_offset +\n// cd_size carried in the EOCD itself, so no external bookkeeping is\n// required.\n//\n// Out of scope: per-entry sizes or central-directory offsets > 4 GiB.\n// xlsx archives don't approach those limits in practice; we throw a\n// clear error if we detect overflow there.\n\nimport { OpenXmlIoError, OpenXmlNotImplementedError } from '../utils/exceptions.js';\n\nconst ZIP32_MAX_ENTRIES = 0xffff;\nconst ZIP32_MAX_U32 = 0xffffffff;\nconst SIG_EOCD = 0x06054b50;\nconst SIG_ZIP64_EOCD = 0x06064b50;\nconst SIG_ZIP64_EOCD_LOCATOR = 0x07064b50;\n\nconst ZIP64_EOCD_SIZE = 56;\nconst ZIP64_LOCATOR_SIZE = 20;\n\nconst u16 = (b: Uint8Array, o: number): number => (b[o] ?? 0) | ((b[o + 1] ?? 0) << 8);\n\nconst u32 = (b: Uint8Array, o: number): number => {\n const v0 = b[o] ?? 0;\n const v1 = b[o + 1] ?? 0;\n const v2 = b[o + 2] ?? 0;\n const v3 = b[o + 3] ?? 0;\n return (v0 | (v1 << 8) | (v2 << 16) | (v3 << 24)) >>> 0;\n};\n\nconst writeU16 = (b: Uint8Array, o: number, v: number): void => {\n b[o] = v & 0xff;\n b[o + 1] = (v >>> 8) & 0xff;\n};\n\nconst writeU32 = (b: Uint8Array, o: number, v: number): void => {\n b[o] = v & 0xff;\n b[o + 1] = (v >>> 8) & 0xff;\n b[o + 2] = (v >>> 16) & 0xff;\n b[o + 3] = (v >>> 24) & 0xff;\n};\n\nconst writeU64 = (b: Uint8Array, o: number, v: number): void => {\n // JS Number safely represents integers up to 2^53 - 1, well beyond\n // anything we'd ever emit here. Split via Math.floor + modulo to\n // avoid bit-shift truncation at 32 bits.\n const lo = v >>> 0;\n const hi = Math.floor(v / 0x100000000) >>> 0;\n writeU32(b, o, lo);\n writeU32(b, o + 4, hi);\n};\n\nconst findEocdOffset = (bytes: Uint8Array): number => {\n // EOCD is min 22 bytes and may be followed by up to 65535 bytes of\n // archive comment. Scan backwards from the latest possible position.\n const minOffset = Math.max(0, bytes.length - (22 + 0xffff));\n for (let p = bytes.length - 22; p >= minOffset; p--) {\n if (u32(bytes, p) === SIG_EOCD) {\n const commentLen = u16(bytes, p + 20);\n if (p + 22 + commentLen === bytes.length) return p;\n }\n }\n throw new OpenXmlIoError('zip64-patch: no End-of-Central-Directory signature found');\n};\n\n/**\n * Splice ZIP64 EOCD record + locator into fflate's final chunk and\n * patch the trailing EOCD entry-count fields with the 0xFFFF sentinel.\n *\n * `finalChunk` must be the [CD | EOCD] block fflate emits as its last\n * ondata callback (everything before it is per-entry LFH/data/DD that\n * we leave untouched). Returns a new chunk; the input is not mutated.\n *\n * Assumes per-entry sizes and central-directory offset fit in 32 bits;\n * throws if not (xlsx archives never approach those limits).\n */\nexport function applyZip64EntryCountPatch(finalChunk: Uint8Array, totalEntries: number): Uint8Array {\n if (totalEntries <= ZIP32_MAX_ENTRIES) return finalChunk;\n\n const eocdOffset = findEocdOffset(finalChunk);\n\n const cdSize = u32(finalChunk, eocdOffset + 12);\n const cdOffset = u32(finalChunk, eocdOffset + 16);\n const commentLen = u16(finalChunk, eocdOffset + 20);\n\n if (cdSize === ZIP32_MAX_U32 || cdOffset === ZIP32_MAX_U32) {\n throw new OpenXmlNotImplementedError(\n 'zip64-patch: archive size or central-directory offset exceeds 4 GiB; full ZIP64 size support is not implemented (xlsx in practice stays well under 4 GiB).',\n );\n }\n\n // Where the EOCD starts in the *global* archive (before our patch).\n // CD precedes EOCD with no gap, so global EOCD offset is just\n // cd_offset + cd_size — the locator points here.\n const globalEocdOffset = cdOffset + cdSize;\n\n const eocdLen = 22 + commentLen;\n const newChunkLen = eocdOffset + ZIP64_EOCD_SIZE + ZIP64_LOCATOR_SIZE + eocdLen;\n const out = new Uint8Array(newChunkLen);\n\n // Original CD bytes (everything before the EOCD).\n out.set(finalChunk.subarray(0, eocdOffset), 0);\n\n // ZIP64 EOCD record (56 bytes total).\n const zip64Eocd = out.subarray(eocdOffset, eocdOffset + ZIP64_EOCD_SIZE);\n writeU32(zip64Eocd, 0, SIG_ZIP64_EOCD);\n // size_of_zip64_eocd = total_size - 12 (size field excludes signature + this field itself)\n writeU64(zip64Eocd, 4, ZIP64_EOCD_SIZE - 12);\n writeU16(zip64Eocd, 12, 45); // version made by (4.5 — first ZIP64 spec)\n writeU16(zip64Eocd, 14, 45); // version needed\n writeU32(zip64Eocd, 16, 0); // disk_number\n writeU32(zip64Eocd, 20, 0); // disk_with_cd\n writeU64(zip64Eocd, 24, totalEntries); // entries_on_this_disk\n writeU64(zip64Eocd, 32, totalEntries); // total_entries\n writeU64(zip64Eocd, 40, cdSize); // cd_size\n writeU64(zip64Eocd, 48, cdOffset); // cd_offset\n\n // ZIP64 EOCD locator (20 bytes).\n const locOffset = eocdOffset + ZIP64_EOCD_SIZE;\n const locator = out.subarray(locOffset, locOffset + ZIP64_LOCATOR_SIZE);\n writeU32(locator, 0, SIG_ZIP64_EOCD_LOCATOR);\n writeU32(locator, 4, 0); // disk_with_zip64_eocd\n writeU64(locator, 8, globalEocdOffset);\n writeU32(locator, 16, 1); // total_disks\n\n // New EOCD: copy original then patch entry counts to the 0xFFFF\n // sentinel. (fflate writes the low 16 bits of the true count there,\n // which confuses readers that don't first look for the ZIP64 record.)\n const newEocdOffset = locOffset + ZIP64_LOCATOR_SIZE;\n out.set(finalChunk.subarray(eocdOffset, eocdOffset + eocdLen), newEocdOffset);\n writeU16(out, newEocdOffset + 8, ZIP32_MAX_ENTRIES);\n writeU16(out, newEocdOffset + 10, ZIP32_MAX_ENTRIES);\n\n return out;\n}\n","// ZIP write layer. Streaming-deflate via fflate's `Zip` + per-entry\n// `ZipDeflate` / `ZipPassThrough` so the writer never holds the whole archive\n// in memory. Each addEntry pushes its bytes through the deflate stream and the\n// resulting ZIP chunks land on the sink one at a time — the buffered\n// `toBytes()` sink concatenates them on finish, while a streaming sink can\n// flush them as they arrive.\n//\n// ZIP64 (entry count > 65535): fflate's `Zip` emits a plain ZIP32 EOCD in all\n// cases, so on finalize we splice in a ZIP64 EOCD record + locator when needed\n// via `applyZip64EntryCountPatch`. That keeps the per-entry LFH/CDH layout\n// fflate produces and only rewrites the trailing records.\n//\n// Scope: this covers the entry-count-overflow case (the limit xlsx archives\n// realistically hit — `tens of millions of cells` → tens of thousands of\n// worksheet entries via the streaming writer). Per-entry compressed/uncompressed\n// sizes and the central-directory offset must still fit in 32 bits (≤ 4 GiB\n// each); a single >4 GiB entry would need full ZIP64 size support and\n// `applyZip64EntryCountPatch` throws `OpenXmlNotImplementedError` if we ever\n// detect that. xlsx workbooks don't approach that limit in practice, but the\n// constraint is real — surface it in your own size estimates.\n\nimport { Zip, ZipDeflate, ZipPassThrough } from 'fflate';\nimport type { XlsxSink } from '../io/sink.js';\nimport { OpenXmlIoError } from '../utils/exceptions.js';\nimport { applyZip64EntryCountPatch } from './zip64-patch.js';\n\nconst ZIP32_MAX_ENTRIES = 0xffff;\nconst LOCAL_TIMESTAMP_OFFSET = 10;\nconst CENTRAL_TIMESTAMP_OFFSET = 12;\nconst CENTRAL_HEADER_SIZE = 46;\nconst CENTRAL_NAME_LENGTH_OFFSET = 28;\nconst CENTRAL_EXTRA_LENGTH_OFFSET = 30;\nconst CENTRAL_COMMENT_LENGTH_OFFSET = 32;\n\n/** Deflate effort: 0 skips compression, 9 is the slowest and smallest. */\nexport type CompressionLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;\n\n/** Supported year range of the fflate ZIP backend. */\nconst MIN_ZIP_YEAR = 1980;\nconst MAX_ZIP_YEAR = 2099;\n\nexport interface ZipWriterOptions {\n /**\n * Last-modified timestamp stamped into every entry's local header and\n * central-directory record. ZIP has no \"no timestamp\" encoding, so fflate\n * defaults each entry to the wall clock and two archives built from\n * identical input differ in bytes. Pin this to get reproducible output for\n * golden-file tests or content-addressed caching.\n *\n * Recorded as the date's UTC wall time, to a two-second resolution, with the\n * year required to fall in 1980-2099. The DOS field carries no timezone, so\n * writing local components would leave the bytes depending on the writer's\n * `TZ`, which is the opposite of what pinning a stamp is for.\n */\n mtime?: Date;\n /** Deflate level handed to fflate. Defaults to fflate's own 6. */\n compressionLevel?: CompressionLevel;\n}\n\nexport interface ZipWriter {\n /**\n * Stage an entry. Bytes are pushed through fflate's `ZipDeflate` /\n * `ZipPassThrough` stream synchronously, so the deflated chunks land on the\n * sink as the call runs (no per-entry buffering — see the streaming-behaviour\n * test in `tests/phase-1/zip/writer.test.ts`). Streams (`ReadableStream`)\n * are not accepted today; pass an already-materialised entry, or use\n * {@link addStreamingEntry} for chunked writes.\n *\n * `compress` defaults to `true`. Pass `false` for already-compressed payloads\n * (PNG/JPEG/zip-as-binary content like vbaProject.bin) so we don't pay\n * deflate costs for no gain.\n */\n addEntry(path: string, bytes: Uint8Array | ReadableStream<Uint8Array>, opts?: { compress?: boolean }): Promise<void>;\n\n /**\n * Open a streaming entry. Returns a writer the caller can `write()` chunks to\n * and `end()` to seal the entry. Each chunk pushes through the same fflate\n * `ZipDeflate` / `ZipPassThrough` machinery as `addEntry`, so peak memory\n * stays at one chunk + deflate scratch even for multi-GB worksheets.\n *\n * Sequencing: only one streaming entry may be open at a time — `addEntry` and\n * a second `addStreamingEntry` both throw until the current entry's `end()`\n * resolves.\n */\n addStreamingEntry(path: string, opts?: { compress?: boolean }): StreamingEntryWriter;\n\n /**\n * Build the central directory and flush all bytes through the sink.\n * Idempotent; subsequent calls resolve to the same payload.\n */\n finalize(): Promise<Uint8Array>;\n\n /**\n * Release the sink and underlying writer without producing a valid archive.\n * Use this from a surrounding catch block when serialization fails part-way\n * through. Without it, streaming sinks (`toFile` / `toWritable`) keep their\n * file descriptors / writables open and the half-written xlsx looks valid on\n * disk. Idempotent; safe to call after `finalize()`.\n *\n * Await the result before reporting the failure: `toFile` removes its partial\n * file asynchronously, so an unawaited abort can still have the file on disk\n * when the caller sees the error.\n */\n abort(cause?: unknown): void | Promise<void>;\n}\n\n/** Writer handle for a single streaming entry. */\nexport interface StreamingEntryWriter {\n /** Push a chunk of bytes (already-encoded). Throws after `end()`. */\n write(chunk: Uint8Array): void;\n /** Seal the entry. Subsequent `write()` throws. Idempotent. */\n end(): Promise<void>;\n}\n\n/** Encode UTC components directly: local dates cannot represent a DST gap. */\nconst toZipStamp = (mtime: Date): number => {\n const ms = mtime.getTime();\n if (Number.isNaN(ms)) {\n throw new OpenXmlIoError('createZipWriter: mtime is an invalid Date');\n }\n const year = mtime.getUTCFullYear();\n if (year < MIN_ZIP_YEAR || year > MAX_ZIP_YEAR) {\n throw new OpenXmlIoError(\n `createZipWriter: mtime ${mtime.toISOString()} is outside the supported ZIP timestamp range (${MIN_ZIP_YEAR}-${MAX_ZIP_YEAR})`,\n );\n }\n return ((year - MIN_ZIP_YEAR) << 25)\n | ((mtime.getUTCMonth() + 1) << 21)\n | (mtime.getUTCDate() << 16)\n | (mtime.getUTCHours() << 11)\n | (mtime.getUTCMinutes() << 5)\n | (mtime.getUTCSeconds() >> 1);\n};\n\n/**\n * fflate looks the level up in a table and falls back to 6 for anything off\n * the end, so an out-of-range level would quietly produce default output. The\n * union type catches that for TypeScript callers; this catches it for the rest.\n */\nconst validateCompressionLevel = (level: number): void => {\n if (!Number.isInteger(level) || level < 0 || level > 9) {\n throw new OpenXmlIoError(`createZipWriter: compressionLevel must be an integer in [0, 9]; got ${level}`);\n }\n};\n\n/**\n * ZIP writer backed by fflate's streaming `Zip` class. Entries are pushed\n * through `ZipDeflate` / `ZipPassThrough` streams as they arrive, so peak\n * memory stays at the size of the in-flight entry plus the output buffer rather\n * than the full archive.\n *\n * The sink contract is `toBytes()`, but that name is historical: the sink is\n * driven by a chunked `write(chunk)` API that fans bytes out as they arrive.\n * The buffered Node/browser sinks (`toBuffer`, `toBlob`, `toArrayBuffer`)\n * concatenate the chunks for a single-shot result; streaming sinks\n * (`toFile`, `toWritable`) forward each chunk to disk / the wrapped writable\n * without ever holding the full archive resident. Either kind plugs in here.\n */\nexport function createZipWriter(sink: XlsxSink, opts: ZipWriterOptions = {}): ZipWriter {\n // Both options are checked before the sink is opened: a bad one otherwise\n // surfaces from fflate half-way through the first entry, by which time a file\n // sink holds a partial archive.\n if (opts.compressionLevel !== undefined) validateCompressionLevel(opts.compressionLevel);\n const stamp = opts.mtime === undefined ? undefined : toZipStamp(opts.mtime);\n const writer = sink.toBytes();\n const deflateOpts = opts.compressionLevel === undefined ? undefined : { level: opts.compressionLevel };\n let pendingLocalHeader = false;\n // fflate clones mtime and reads local getters, so even a Date subclass cannot\n // represent UTC times in a local DST gap. Give it a safe placeholder and patch\n // only its header chunks below; payload chunks must never be signature-scanned.\n const newEntry = (path: string, compress: boolean): ZipDeflate | ZipPassThrough => {\n const file = compress ? new ZipDeflate(path, deflateOpts) : new ZipPassThrough(path);\n if (stamp !== undefined) {\n file.mtime = new Date(2000, 0, 1);\n pendingLocalHeader = true;\n }\n return file;\n };\n let finalised: Promise<Uint8Array> | undefined;\n let endCalled = false;\n const seen = new Set<string>();\n const errors: Error[] = [];\n // fflate emits the [CD | EOCD] block in a single ondata call with\n // `final=true`. We capture only that chunk so we can apply the ZIP64 patch on\n // finalize; all preceding entry-data chunks stream straight to the sink to\n // preserve the writer's incremental flushing contract.\n let finalChunk: Uint8Array | undefined;\n let zipFinishResolve: (() => void) | undefined;\n const zipFinishPromise = new Promise<void>((resolve) => {\n zipFinishResolve = resolve;\n });\n\n const zip = new Zip((err, chunk, final) => {\n if (err) {\n errors.push(err instanceof Error ? err : new Error(String(err)));\n return;\n }\n // ZipDeflate emits an empty trailer chunk on the final callback even when\n // there are no bytes; guard against pushing an undefined chunk.\n if (chunk && chunk.byteLength > 0) {\n if (stamp !== undefined && (final || pendingLocalHeader)) {\n const view = new DataView(chunk.buffer, chunk.byteOffset, chunk.byteLength);\n if (final) {\n // fflate emits the complete central directory plus EOCD as one chunk.\n let offset = 0;\n for (let i = 0; i < seen.size; i++) {\n view.setUint32(offset + CENTRAL_TIMESTAMP_OFFSET, stamp, true);\n offset += CENTRAL_HEADER_SIZE + view.getUint16(offset + CENTRAL_NAME_LENGTH_OFFSET, true)\n + view.getUint16(offset + CENTRAL_EXTRA_LENGTH_OFFSET, true)\n + view.getUint16(offset + CENTRAL_COMMENT_LENGTH_OFFSET, true);\n }\n } else if (pendingLocalHeader) {\n // With one entry open at a time, its first chunk is the local header.\n view.setUint32(LOCAL_TIMESTAMP_OFFSET, stamp, true);\n pendingLocalHeader = false;\n }\n }\n if (final) {\n // Buffer the trailing CD + EOCD block; written after possible patch.\n finalChunk = chunk;\n } else {\n writer.write(chunk);\n }\n }\n if (final && zipFinishResolve) {\n zipFinishResolve();\n zipFinishResolve = undefined;\n }\n });\n\n let streamingOpen = false;\n\n const guardAdd = (path: string): void => {\n if (finalised !== undefined) {\n throw new OpenXmlIoError('createZipWriter: addEntry after finalize');\n }\n if (streamingOpen) {\n throw new OpenXmlIoError('createZipWriter: a streaming entry is still open — call end() first');\n }\n if (seen.has(path)) {\n throw new OpenXmlIoError(`createZipWriter: duplicate entry \"${path}\"`);\n }\n };\n\n return {\n async addEntry(path, bytes, entryOpts) {\n if (!(bytes instanceof Uint8Array)) {\n throw new OpenXmlIoError(\n 'createZipWriter: ReadableStream entries are not yet supported (deferred to streaming writer)',\n );\n }\n guardAdd(path);\n seen.add(path);\n const file = newEntry(path, entryOpts?.compress ?? true);\n try {\n zip.add(file);\n file.push(bytes, /* final */ true);\n } catch (cause) {\n throw new OpenXmlIoError(`createZipWriter: failed to add entry \"${path}\"`, { cause });\n }\n if (errors.length > 0) {\n throw new OpenXmlIoError('createZipWriter: stream error during addEntry', { cause: errors[0] });\n }\n },\n\n addStreamingEntry(path, entryOpts) {\n guardAdd(path);\n seen.add(path);\n streamingOpen = true;\n const file = newEntry(path, entryOpts?.compress ?? true);\n try {\n zip.add(file);\n } catch (cause) {\n streamingOpen = false;\n throw new OpenXmlIoError(`createZipWriter: failed to open streaming entry \"${path}\"`, { cause });\n }\n let ended = false;\n return {\n write(chunk: Uint8Array): void {\n if (ended) throw new OpenXmlIoError(`createZipWriter: write after end on \"${path}\"`);\n if (!(chunk instanceof Uint8Array)) {\n throw new OpenXmlIoError(`createZipWriter: streaming entry \"${path}\" chunk is not a Uint8Array`);\n }\n if (chunk.byteLength === 0) return;\n try {\n file.push(chunk, /* final */ false);\n } catch (cause) {\n throw new OpenXmlIoError(`createZipWriter: failed to push chunk on \"${path}\"`, { cause });\n }\n if (errors.length > 0) {\n throw new OpenXmlIoError('createZipWriter: stream error during write', { cause: errors[0] });\n }\n },\n async end(): Promise<void> {\n if (ended) return;\n ended = true;\n try {\n file.push(new Uint8Array(0), /* final */ true);\n } catch (cause) {\n throw new OpenXmlIoError(`createZipWriter: failed to end streaming entry \"${path}\"`, { cause });\n }\n streamingOpen = false;\n if (errors.length > 0) {\n throw new OpenXmlIoError('createZipWriter: stream error during end', { cause: errors[0] });\n }\n },\n };\n },\n\n async finalize() {\n if (finalised !== undefined) return finalised;\n if (streamingOpen) {\n throw new OpenXmlIoError('createZipWriter: cannot finalize while a streaming entry is open');\n }\n finalised = (async () => {\n try {\n if (!endCalled) {\n zip.end();\n endCalled = true;\n }\n } catch (cause) {\n throw new OpenXmlIoError('createZipWriter: failed to finalize zip archive', { cause });\n }\n await zipFinishPromise;\n if (errors.length > 0) {\n throw new OpenXmlIoError('createZipWriter: stream error during finalize', { cause: errors[0] });\n }\n\n // Apply the ZIP64 patch to fflate's [CD | EOCD] tail when the entry\n // count exceeds ZIP32's 16-bit cap, then flush the (possibly patched)\n // tail to the sink.\n if (finalChunk) {\n const patched =\n seen.size > ZIP32_MAX_ENTRIES\n ? applyZip64EntryCountPatch(finalChunk, seen.size)\n : finalChunk;\n writer.write(patched);\n }\n return writer.finish();\n })();\n return finalised;\n },\n\n abort(cause?: unknown): void | Promise<void> {\n if (finalised !== undefined) return;\n // Mark finalised so any subsequent addEntry / finalize short-circuits.\n finalised = Promise.resolve(new Uint8Array(0));\n // Drop fflate's listener: further `ondata` callbacks no longer matter.\n if (zipFinishResolve) {\n zipFinishResolve();\n zipFinishResolve = undefined;\n }\n return writer.abort?.(cause);\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/xml.mjs
CHANGED
|
@@ -3,7 +3,8 @@ import { n as escapeXmlAttr, r as escapeXmlText } from "./escape-rnWSPpWY.mjs";
|
|
|
3
3
|
import { $ as PACKAGE_PIVOT_CACHE, A as COREPROPS_NS, At as parseQName, B as DCTERMS_PREFIX, C as CHARTSHEET_TYPE, Ct as XLSM_TYPE, D as COMMENTS_NS, Dt as XML_NS, E as CHART_TYPE, Et as XLTX_TYPE, F as CUSTPROPS_NS, G as EXTERNAL_LINK_TYPE, H as DRAWING_NS, I as CX_NS, J as PACKAGE_CHARTS, K as IMAGE_NS, L as DCMITYPE_NS, M as CPROPS_TYPE, N as CTRLPROPS_TYPE, O as COMMENTS_TYPE, Ot as XPROPS_NS, P as CUSTOMUI_NS, Q as PACKAGE_IMAGES, R as DCORE_NS, S as CHARTSHAPE_TYPE, St as X16_NS, T as CHART_NS, Tt as XLTM_TYPE, U as DRAWING_TYPE, V as DEFAULT_PREFIXES, W as EXTERNAL_LINK_NS, X as PACKAGE_CHARTSHEETS_RELS, Y as PACKAGE_CHARTSHEETS, Z as PACKAGE_DRAWINGS, _ as ARC_WORKBOOK_RELS, _t as VML_NS, a as findChildren, at as PACKAGE_WORKSHEET_RELS, b as C16_NS, bt as X14_NS, c as ARC_CONTENT_TYPES, ct as PKG_REL_NS, d as ARC_CUSTOM_UI, dt as SHEET_DRAWING_NS, et as PACKAGE_PIVOT_TABLE, f as ARC_ROOT_RELS, ft as SHEET_MAIN_NS, g as ARC_WORKBOOK, gt as VBA_TYPE, h as ARC_THEME, ht as THREADED_COMMENTS_NS, i as findChild, it as PACKAGE_WORKSHEETS, j as CPROPS_FMTID, jt as qname, k as CONTYPES_NS, kt as XSI_NS, l as ARC_CORE, lt as REL_NS, m as ARC_STYLE, mt as THEME_TYPE, n as el, nt as PACKAGE_RELS, o as ACTIVEX_TYPE, ot as PACKAGE_XL, p as ARC_SHARED_STRINGS, pt as STYLES_TYPE, q as MARKUP_COMPAT_NS, r as elNs, rt as PACKAGE_THEME, s as ARC_APP, st as PICTURE_NS, t as appendChild, tt as PACKAGE_PROPS, u as ARC_CUSTOM, ut as SHARED_STRINGS_TYPE, v as C14_NS, vt as VTYPES_NS, w as CHART_DRAWING_NS, wt as XLSX_TYPE, xt as X15_NS, y as C15_NS, yt as WORKSHEET_TYPE, z as DCTERMS_NS } from "./tree-BX-sRRVp.mjs";
|
|
4
4
|
import { t as parseXml } from "./parser-By6RWZVW.mjs";
|
|
5
5
|
import { t as serializeXml } from "./serializer-C6nm0mRJ.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { t as utf8ByteLength } from "./utf8-OAkCDG5g.mjs";
|
|
7
|
+
import { t as iterParse } from "./iterparse-DZE0zy2M.mjs";
|
|
7
8
|
//#region src/xml/stream-writer.ts
|
|
8
9
|
const DEFAULT_FLUSH_BYTES = 64 * 1024;
|
|
9
10
|
const encoder = new TextEncoder();
|
package/dist/xml.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xml.mjs","names":[],"sources":["../src/xml/stream-writer.ts"],"sourcesContent":["// Streaming XML emitter. Used by writers that need to compose larger documents\n// incrementally — chiefly the worksheet writer where the `<sheetData>` block is\n// emitted row-by-row to keep heap use bounded.\n//\n// Phase 1 §5: buffered output only (a chunk array materialised on `result()`).\n// Streaming via `WritableStream<Uint8Array>` is added in the phase-4 streaming\n// worksheet writer; the structural API stays the same so that change is\n// mechanical.\n//\n// The worksheet hot path emits cells through templated strings, NOT through\n// this writer's start / end / writeNode methods. Use `writeRaw` to splice those\n// in.\n\nimport { escapeXmlAttr, escapeXmlText } from '../utils/escape.js';\nimport { OpenXmlIoError } from '../utils/exceptions.js';\nimport { utf8ByteLength } from '../utils/utf8.js';\nimport { DEFAULT_PREFIXES, parseQName, XML_NS } from './namespaces.js';\nimport type { XmlNode } from './tree.js';\n\nexport interface XmlStreamWriterOptions {\n /** Map of namespace URI → prefix. Merged on top of DEFAULT_PREFIXES. */\n prefixMap?: Readonly<Record<string, string>>;\n /** Emit `<?xml … ?>` declaration first. Defaults to true. */\n xmlDeclaration?: boolean;\n /** `standalone` attribute on the declaration. Defaults to 'yes'. */\n standalone?: 'yes' | 'no' | 'omit';\n /**\n * Auto-flush threshold in bytes. Once the in-flight string buffer crosses\n * this size it gets encoded into a chunk and parked. Larger values trade\n * memory for fewer TextEncoder calls; smaller values lower peak memory at the\n * cost of CPU.\n */\n flushBytes?: number;\n}\n\nexport interface XmlStreamWriter {\n /**\n * Open an element. Names are in Clark notation (`{ns}local`); the writer\n * prefixes them via the configured prefix map.\n */\n start(name: string, attrs?: Record<string, string>): void;\n /** Emit a text node inside the currently open element. */\n text(s: string): void;\n /** Emit a complete subtree. Closes any pending start tag first. */\n writeNode(n: XmlNode): void;\n /** Emit pre-rendered XML bytes verbatim — escape-hatch for hot paths. */\n writeRaw(s: string): void;\n /** Close the currently open element (matched against the start stack). */\n end(): void;\n /** Force any buffered bytes into the chunk store immediately. */\n flush(): void;\n /**\n * Materialise everything written so far. Throws if any element is still open.\n * Idempotent.\n */\n result(): Uint8Array;\n}\n\nconst DEFAULT_FLUSH_BYTES = 64 * 1024;\nconst encoder = new TextEncoder();\n\nconst buildPrefixMap = (user: Readonly<Record<string, string>> | undefined): Map<string, string> => {\n const out = new Map<string, string>();\n for (const [ns, prefix] of Object.entries(DEFAULT_PREFIXES)) out.set(ns, prefix);\n // The xml prefix is reserved by the XMLNS spec for XML_NS; never override.\n out.set(XML_NS, 'xml');\n if (user !== undefined) {\n for (const [ns, prefix] of Object.entries(user)) out.set(ns, prefix);\n }\n return out;\n};\n\nexport function createXmlStreamWriter(opts: XmlStreamWriterOptions = {}): XmlStreamWriter {\n const { xmlDeclaration = true, standalone = 'yes', flushBytes = DEFAULT_FLUSH_BYTES } = opts;\n const prefixOf = buildPrefixMap(opts.prefixMap);\n\n const chunks: Uint8Array[] = [];\n let buf = '';\n // Running UTF-8 byte count of `buf`, maintained incrementally via `append()`.\n // Without this the flush threshold compares UTF-16 code units against a\n // byte budget — non-ASCII payloads then balloon past the configured limit.\n let bufBytes = 0;\n let openStartTag = false;\n let finalised = false;\n const stack: string[] = [];\n\n const append = (s: string): void => {\n buf += s;\n bufBytes += utf8ByteLength(s);\n };\n\n const elementName = (name: string): string => {\n const { ns, local } = parseQName(name);\n if (ns === '') return local;\n const prefix = prefixOf.get(ns);\n if (prefix === undefined || prefix === '') return local;\n return `${prefix}:${local}`;\n };\n const attributeName = (name: string): string => {\n const { ns, local } = parseQName(name);\n if (ns === '') return local;\n const prefix = prefixOf.get(ns);\n if (prefix === undefined || prefix === '') return local;\n return `${prefix}:${local}`;\n };\n\n const flushImpl = (): void => {\n if (buf.length === 0) return;\n chunks.push(encoder.encode(buf));\n buf = '';\n bufBytes = 0;\n };\n\n const maybeFlush = (): void => {\n if (bufBytes >= flushBytes) flushImpl();\n };\n\n const closeStartTagIfOpen = (): void => {\n if (!openStartTag) return;\n append('>');\n openStartTag = false;\n };\n\n if (xmlDeclaration) {\n append('<?xml version=\"1.0\" encoding=\"UTF-8\"');\n if (standalone !== 'omit') append(` standalone=\"${standalone}\"`);\n append('?>\\n');\n }\n\n // ---- writeNode internals\n // ---------------------------------------------------\n\n const emitNodeInline = (n: XmlNode): void => {\n const tag = elementName(n.name);\n append(`<${tag}`);\n for (const [name, value] of Object.entries(n.attrs)) {\n append(` ${attributeName(name)}=\"${escapeXmlAttr(value)}\"`);\n }\n const text = n.text;\n const hasText = text !== undefined && text !== '';\n const hasChildren = n.children.length > 0;\n if (!hasText && !hasChildren) {\n append('/>');\n return;\n }\n append('>');\n if (hasText) append(escapeXmlText(text));\n for (const c of n.children) emitNodeInline(c);\n append(`</${tag}>`);\n };\n\n // ---- public surface\n // --------------------------------------------------------\n\n return {\n start(name, attrs) {\n if (finalised) throw new OpenXmlIoError('XmlStreamWriter: start() after result()');\n closeStartTagIfOpen();\n const tag = elementName(name);\n append(`<${tag}`);\n if (attrs !== undefined) {\n for (const [k, v] of Object.entries(attrs)) {\n append(` ${attributeName(k)}=\"${escapeXmlAttr(v)}\"`);\n }\n }\n stack.push(tag);\n openStartTag = true;\n maybeFlush();\n },\n text(s) {\n if (finalised) throw new OpenXmlIoError('XmlStreamWriter: text() after result()');\n closeStartTagIfOpen();\n append(escapeXmlText(s));\n maybeFlush();\n },\n writeNode(n) {\n if (finalised) throw new OpenXmlIoError('XmlStreamWriter: writeNode() after result()');\n closeStartTagIfOpen();\n emitNodeInline(n);\n maybeFlush();\n },\n writeRaw(s) {\n if (finalised) throw new OpenXmlIoError('XmlStreamWriter: writeRaw() after result()');\n closeStartTagIfOpen();\n append(s);\n maybeFlush();\n },\n end() {\n if (finalised) throw new OpenXmlIoError('XmlStreamWriter: end() after result()');\n const tag = stack.pop();\n if (tag === undefined) throw new OpenXmlIoError('XmlStreamWriter: end() with no open element');\n if (openStartTag) {\n append('/>');\n openStartTag = false;\n } else {\n append(`</${tag}>`);\n }\n maybeFlush();\n },\n flush() {\n flushImpl();\n },\n result(): Uint8Array {\n if (stack.length > 0) {\n throw new OpenXmlIoError(`XmlStreamWriter: ${stack.length} unclosed element(s) at result()`);\n }\n flushImpl();\n finalised = true;\n let total = 0;\n for (const c of chunks) total += c.byteLength;\n const out = new Uint8Array(total);\n let off = 0;\n for (const c of chunks) {\n out.set(c, off);\n off += c.byteLength;\n }\n return out;\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"xml.mjs","names":[],"sources":["../src/xml/stream-writer.ts"],"sourcesContent":["// Streaming XML emitter. Used by writers that need to compose larger documents\n// incrementally — chiefly the worksheet writer where the `<sheetData>` block is\n// emitted row-by-row to keep heap use bounded.\n//\n// Phase 1 §5: buffered output only (a chunk array materialised on `result()`).\n// Streaming via `WritableStream<Uint8Array>` is added in the phase-4 streaming\n// worksheet writer; the structural API stays the same so that change is\n// mechanical.\n//\n// The worksheet hot path emits cells through templated strings, NOT through\n// this writer's start / end / writeNode methods. Use `writeRaw` to splice those\n// in.\n\nimport { escapeXmlAttr, escapeXmlText } from '../utils/escape.js';\nimport { OpenXmlIoError } from '../utils/exceptions.js';\nimport { utf8ByteLength } from '../utils/utf8.js';\nimport { DEFAULT_PREFIXES, parseQName, XML_NS } from './namespaces.js';\nimport type { XmlNode } from './tree.js';\n\nexport interface XmlStreamWriterOptions {\n /** Map of namespace URI → prefix. Merged on top of DEFAULT_PREFIXES. */\n prefixMap?: Readonly<Record<string, string>>;\n /** Emit `<?xml … ?>` declaration first. Defaults to true. */\n xmlDeclaration?: boolean;\n /** `standalone` attribute on the declaration. Defaults to 'yes'. */\n standalone?: 'yes' | 'no' | 'omit';\n /**\n * Auto-flush threshold in bytes. Once the in-flight string buffer crosses\n * this size it gets encoded into a chunk and parked. Larger values trade\n * memory for fewer TextEncoder calls; smaller values lower peak memory at the\n * cost of CPU.\n */\n flushBytes?: number;\n}\n\nexport interface XmlStreamWriter {\n /**\n * Open an element. Names are in Clark notation (`{ns}local`); the writer\n * prefixes them via the configured prefix map.\n */\n start(name: string, attrs?: Record<string, string>): void;\n /** Emit a text node inside the currently open element. */\n text(s: string): void;\n /** Emit a complete subtree. Closes any pending start tag first. */\n writeNode(n: XmlNode): void;\n /** Emit pre-rendered XML bytes verbatim — escape-hatch for hot paths. */\n writeRaw(s: string): void;\n /** Close the currently open element (matched against the start stack). */\n end(): void;\n /** Force any buffered bytes into the chunk store immediately. */\n flush(): void;\n /**\n * Materialise everything written so far. Throws if any element is still open.\n * Idempotent.\n */\n result(): Uint8Array;\n}\n\nconst DEFAULT_FLUSH_BYTES = 64 * 1024;\nconst encoder = new TextEncoder();\n\nconst buildPrefixMap = (user: Readonly<Record<string, string>> | undefined): Map<string, string> => {\n const out = new Map<string, string>();\n for (const [ns, prefix] of Object.entries(DEFAULT_PREFIXES)) out.set(ns, prefix);\n // The xml prefix is reserved by the XMLNS spec for XML_NS; never override.\n out.set(XML_NS, 'xml');\n if (user !== undefined) {\n for (const [ns, prefix] of Object.entries(user)) out.set(ns, prefix);\n }\n return out;\n};\n\nexport function createXmlStreamWriter(opts: XmlStreamWriterOptions = {}): XmlStreamWriter {\n const { xmlDeclaration = true, standalone = 'yes', flushBytes = DEFAULT_FLUSH_BYTES } = opts;\n const prefixOf = buildPrefixMap(opts.prefixMap);\n\n const chunks: Uint8Array[] = [];\n let buf = '';\n // Running UTF-8 byte count of `buf`, maintained incrementally via `append()`.\n // Without this the flush threshold compares UTF-16 code units against a\n // byte budget — non-ASCII payloads then balloon past the configured limit.\n let bufBytes = 0;\n let openStartTag = false;\n let finalised = false;\n const stack: string[] = [];\n\n const append = (s: string): void => {\n buf += s;\n bufBytes += utf8ByteLength(s);\n };\n\n const elementName = (name: string): string => {\n const { ns, local } = parseQName(name);\n if (ns === '') return local;\n const prefix = prefixOf.get(ns);\n if (prefix === undefined || prefix === '') return local;\n return `${prefix}:${local}`;\n };\n const attributeName = (name: string): string => {\n const { ns, local } = parseQName(name);\n if (ns === '') return local;\n const prefix = prefixOf.get(ns);\n if (prefix === undefined || prefix === '') return local;\n return `${prefix}:${local}`;\n };\n\n const flushImpl = (): void => {\n if (buf.length === 0) return;\n chunks.push(encoder.encode(buf));\n buf = '';\n bufBytes = 0;\n };\n\n const maybeFlush = (): void => {\n if (bufBytes >= flushBytes) flushImpl();\n };\n\n const closeStartTagIfOpen = (): void => {\n if (!openStartTag) return;\n append('>');\n openStartTag = false;\n };\n\n if (xmlDeclaration) {\n append('<?xml version=\"1.0\" encoding=\"UTF-8\"');\n if (standalone !== 'omit') append(` standalone=\"${standalone}\"`);\n append('?>\\n');\n }\n\n // ---- writeNode internals\n // ---------------------------------------------------\n\n const emitNodeInline = (n: XmlNode): void => {\n const tag = elementName(n.name);\n append(`<${tag}`);\n for (const [name, value] of Object.entries(n.attrs)) {\n append(` ${attributeName(name)}=\"${escapeXmlAttr(value)}\"`);\n }\n const text = n.text;\n const hasText = text !== undefined && text !== '';\n const hasChildren = n.children.length > 0;\n if (!hasText && !hasChildren) {\n append('/>');\n return;\n }\n append('>');\n if (hasText) append(escapeXmlText(text));\n for (const c of n.children) emitNodeInline(c);\n append(`</${tag}>`);\n };\n\n // ---- public surface\n // --------------------------------------------------------\n\n return {\n start(name, attrs) {\n if (finalised) throw new OpenXmlIoError('XmlStreamWriter: start() after result()');\n closeStartTagIfOpen();\n const tag = elementName(name);\n append(`<${tag}`);\n if (attrs !== undefined) {\n for (const [k, v] of Object.entries(attrs)) {\n append(` ${attributeName(k)}=\"${escapeXmlAttr(v)}\"`);\n }\n }\n stack.push(tag);\n openStartTag = true;\n maybeFlush();\n },\n text(s) {\n if (finalised) throw new OpenXmlIoError('XmlStreamWriter: text() after result()');\n closeStartTagIfOpen();\n append(escapeXmlText(s));\n maybeFlush();\n },\n writeNode(n) {\n if (finalised) throw new OpenXmlIoError('XmlStreamWriter: writeNode() after result()');\n closeStartTagIfOpen();\n emitNodeInline(n);\n maybeFlush();\n },\n writeRaw(s) {\n if (finalised) throw new OpenXmlIoError('XmlStreamWriter: writeRaw() after result()');\n closeStartTagIfOpen();\n append(s);\n maybeFlush();\n },\n end() {\n if (finalised) throw new OpenXmlIoError('XmlStreamWriter: end() after result()');\n const tag = stack.pop();\n if (tag === undefined) throw new OpenXmlIoError('XmlStreamWriter: end() with no open element');\n if (openStartTag) {\n append('/>');\n openStartTag = false;\n } else {\n append(`</${tag}>`);\n }\n maybeFlush();\n },\n flush() {\n flushImpl();\n },\n result(): Uint8Array {\n if (stack.length > 0) {\n throw new OpenXmlIoError(`XmlStreamWriter: ${stack.length} unclosed element(s) at result()`);\n }\n flushImpl();\n finalised = true;\n let total = 0;\n for (const c of chunks) total += c.byteLength;\n const out = new Uint8Array(total);\n let off = 0;\n for (const c of chunks) {\n out.set(c, off);\n off += c.byteLength;\n }\n return out;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -46,8 +46,30 @@ export declare function resolveDecompressionLimits(input: DecompressionLimitsInp
|
|
|
46
46
|
export interface DecompressionBudget {
|
|
47
47
|
readonly limits: ResolvedDecompressionLimits;
|
|
48
48
|
totalInflated: number;
|
|
49
|
+
/**
|
|
50
|
+
* Largest number of bytes any single inflate of an entry has charged to the
|
|
51
|
+
* archive total, keyed by path. {@link beginEntryInflate} keeps
|
|
52
|
+
* {@link DecompressionBudget.totalInflated} equal to the sum of these.
|
|
53
|
+
*/
|
|
54
|
+
readonly chargedByPath: Map<string, number>;
|
|
49
55
|
}
|
|
50
56
|
export declare function createBudget(limits: ResolvedDecompressionLimits): DecompressionBudget;
|
|
57
|
+
/**
|
|
58
|
+
* Start inflating `path`, returning the function every chunk of it must be
|
|
59
|
+
* recorded through. The recorder throws {@link OpenXmlDecompressionBombError}
|
|
60
|
+
* once the archive total is exceeded, naming `path`.
|
|
61
|
+
*
|
|
62
|
+
* The total bounds how much distinct payload an archive expands to, which is
|
|
63
|
+
* what `checkDeclaredTotals` enforces from the central directory by counting
|
|
64
|
+
* each entry once. An entry can be inflated more than once though: a
|
|
65
|
+
* `readStream` and a `read` of the same part, or a re-read of one the inflate
|
|
66
|
+
* cache turned away. So each path is charged its high-water mark rather than
|
|
67
|
+
* the sum of its reads, and a second inflate only adds what it pushes that mark
|
|
68
|
+
* past. Reads that overlap in any interleaving stay correct for the same
|
|
69
|
+
* reason. The per-entry cap is independent of this and is re-evaluated from
|
|
70
|
+
* scratch on every read.
|
|
71
|
+
*/
|
|
72
|
+
export declare function beginEntryInflate(budget: DecompressionBudget, path: string): (bytes: number) => void;
|
|
51
73
|
/**
|
|
52
74
|
* Per-entry inflate cap, accounting for both the absolute per-entry bound and
|
|
53
75
|
* the ratio-based bound. Returns the smaller of the two — whichever fires
|
|
@@ -60,11 +82,5 @@ export declare function checkDeclaredTotals(budget: DecompressionBudget, declare
|
|
|
60
82
|
compSize: number;
|
|
61
83
|
uncompSize: number;
|
|
62
84
|
}>): void;
|
|
63
|
-
/**
|
|
64
|
-
* Record `bytes` against the global budget. Throws when the running total
|
|
65
|
-
* crosses {@link ResolvedDecompressionLimits.maxTotalUncompressedBytes}. Called
|
|
66
|
-
* from both sync and streaming inflate code paths.
|
|
67
|
-
*/
|
|
68
|
-
export declare function recordInflated(budget: DecompressionBudget, path: string, bytes: number): void;
|
|
69
85
|
/** Build the message thrown when a single entry exceeds its cap mid-inflate. */
|
|
70
86
|
export declare function entryOverflowError(path: string, cap: number): OpenXmlDecompressionBombError;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Largest entry the cache accepts. Sized for the parts a load revisits, which
|
|
3
|
+
* are small; a sheet or a media blob is normally read once, so keeping one buys
|
|
4
|
+
* nothing and costs its full inflated size for the life of the archive.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CACHE_MAX_ENTRY_BYTES: number;
|
|
7
|
+
/**
|
|
8
|
+
* Ceiling on everything the cache holds, so an archive with thousands of small
|
|
9
|
+
* parts cannot accumulate without limit.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CACHE_MAX_TOTAL_BYTES: number;
|
|
12
|
+
export interface InflateCache {
|
|
13
|
+
/**
|
|
14
|
+
* Cached bytes for `path`, or `undefined`. The caller owns the array it gets
|
|
15
|
+
* back and may mutate it. A hit counts as a use for eviction order.
|
|
16
|
+
*/
|
|
17
|
+
get(path: string): Uint8Array | undefined;
|
|
18
|
+
/** Offer `bytes` for later re-reads. Entries above the size ceiling are ignored. */
|
|
19
|
+
set(path: string, bytes: Uint8Array): void;
|
|
20
|
+
clear(): void;
|
|
21
|
+
}
|
|
22
|
+
export declare function createInflateCache(): InflateCache;
|
package/dist/zip/reader.d.ts
CHANGED
|
@@ -3,7 +3,11 @@ import type { DecompressionLimits } from './decompression-guard.js';
|
|
|
3
3
|
export interface ZipArchive {
|
|
4
4
|
/** Sorted list of all entry paths in the archive. */
|
|
5
5
|
list(): string[];
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Synchronous read; throws OpenXmlIoError when the path is unknown. Each
|
|
8
|
+
* call returns an array the caller owns: mutating it changes neither the
|
|
9
|
+
* archive nor what a later read of the same path returns.
|
|
10
|
+
*/
|
|
7
11
|
read(path: string): Uint8Array;
|
|
8
12
|
/** Promise variant for symmetry with the future streaming reader. */
|
|
9
13
|
readAsync(path: string): Promise<Uint8Array>;
|
|
@@ -36,10 +40,11 @@ export interface OpenZipOptions {
|
|
|
36
40
|
/**
|
|
37
41
|
* Open a zip archive from any {@link XlsxSource}. The source is fully
|
|
38
42
|
* materialised in memory, the central directory is parsed once, and each
|
|
39
|
-
* entry is inflated on demand by {@link openRandomAccessArchive}
|
|
40
|
-
* stays at compressed-archive size plus per-entry inflate scratch
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* reader
|
|
43
|
+
* entry is inflated on demand by {@link openRandomAccessArchive}: peak memory
|
|
44
|
+
* stays at compressed-archive size, plus per-entry inflate scratch, plus a few
|
|
45
|
+
* MB at most of small entries kept for re-reads, rather than holding every
|
|
46
|
+
* uncompressed entry resident. The fflate `unzipSync` fallback is preserved
|
|
47
|
+
* internally for ZIP64 / non-standard archives the random-access reader
|
|
48
|
+
* rejects, and it does hold every entry inflated.
|
|
44
49
|
*/
|
|
45
50
|
export declare function openZip(source: XlsxSource, opts?: OpenZipOptions): Promise<ZipArchive>;
|
package/dist/zip/writer.d.ts
CHANGED
|
@@ -55,11 +55,15 @@ export interface ZipWriter {
|
|
|
55
55
|
/**
|
|
56
56
|
* Release the sink and underlying writer without producing a valid archive.
|
|
57
57
|
* Use this from a surrounding catch block when serialization fails part-way
|
|
58
|
-
* through
|
|
58
|
+
* through. Without it, streaming sinks (`toFile` / `toWritable`) keep their
|
|
59
59
|
* file descriptors / writables open and the half-written xlsx looks valid on
|
|
60
60
|
* disk. Idempotent; safe to call after `finalize()`.
|
|
61
|
+
*
|
|
62
|
+
* Await the result before reporting the failure: `toFile` removes its partial
|
|
63
|
+
* file asynchronously, so an unawaited abort can still have the file on disk
|
|
64
|
+
* when the caller sees the error.
|
|
61
65
|
*/
|
|
62
|
-
abort(cause?: unknown): void
|
|
66
|
+
abort(cause?: unknown): void | Promise<void>;
|
|
63
67
|
}
|
|
64
68
|
/** Writer handle for a single streaming entry. */
|
|
65
69
|
export interface StreamingEntryWriter {
|
package/dist/zip.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as DEFAULT_DECOMPRESSION_LIMITS, t as openZip } from "./reader-
|
|
2
|
-
import { t as createZipWriter } from "./writer-
|
|
1
|
+
import { n as DEFAULT_DECOMPRESSION_LIMITS, t as openZip } from "./reader-DHMxLBQV.mjs";
|
|
2
|
+
import { t as createZipWriter } from "./writer-C38RBZCy.mjs";
|
|
3
3
|
export { DEFAULT_DECOMPRESSION_LIMITS, createZipWriter, openZip };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"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.js';\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.js';\nimport {\n checkDeclaredTotals,\n createBudget,\n type DecompressionBudget,\n type DecompressionLimitsInput,\n entryInflateCap,\n entryOverflowError,\n recordInflated,\n resolveDecompressionLimits,\n} from './decompression-guard.js';\nimport type { ZipArchive } from './reader.js';\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.js';\nimport { OpenXmlIoError, OpenXmlNotImplementedError } from '../utils/exceptions.js';\nimport type { DecompressionLimits } from './decompression-guard.js';\nimport { openRandomAccessArchive } from './random-access-reader.js';\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":"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"}
|