fix(preview): bound what ExcelJS expands during XLSX admission

ExcelJS 4.4.0 expands three constructs into one object per cell or column
at load time, so a few KB admitted as one cell could cost a gigabyte:

- a <mergeCell> now costs its full area against the per-sheet and total
  cell caps, and a ref that does not parse is refused
- a <col> whose min or max is past 16384 is refused
- the worker loads with ignoreNodes: ['dataValidations']; the preview
  never shows validations, and a whole-column dropdown took 5 s

The XML counter now scans up to the last complete tag and carries the
rest, so a merge or col tag cut by an inflate-chunk edge is read whole.
A central-directory compressedSize that runs past the file is refused,
since the ratio cap divides by it.

The renderer and core axis offsets use prefix sums with a binary search
instead of walking every override per call.
This commit is contained in:
Aamer Akhter
2026-10-01 09:18:35 -04:00
parent 4edb7b8f80
commit bfab172608
7 changed files with 259 additions and 27 deletions
+4 -1
View File
@@ -125,7 +125,10 @@ async function loadWorkbook(bytes) {
const admitted = core.buildAdmittedArchive(admission, self.fflate);
if (!self.ExcelJS) importScripts(`vendor/exceljs.min.js${spreadsheetAssetQuery}`);
const nextWorkbook = new self.ExcelJS.Workbook();
await nextWorkbook.xlsx.load(admitted);
// ExcelJS expands every address of a `<dataValidation sqref>` into its own
// object (a whole-column dropdown is a million), and the preview never shows
// validations, so they are not parsed at all.
await nextWorkbook.xlsx.load(admitted, { ignoreNodes: ['dataValidations'] });
const nextSheets = new Map();
const nextRows = new Map();
normalizedStyles = [];