Conversation
xlsx files were download-only. Add a read-only, virtualized preview (sheet tabs, number formats, merges, theme colours) parsed entirely in a browser Web Worker with exceljs and fflate, loaded only when a spreadsheet is opened. The workbook is checked against ZIP-bomb, entry and cell limits before exceljs loads; cell text is written with textContent, formulas are never evaluated and nothing referenced by the workbook is fetched. On the server xlsx only joins the existing allowlist and classification, with a 10 MB cap on ?preview=true. xls and ods stay download-only.
|
Thanks a lot for this, @aakhter. It adds a read-only XLSX preview to the file-preview overlay, parsed entirely in a browser worker, and the overall design is exactly right: no server-side parsing, no new path handling, lazy worker-only vendor bundles, While testing it with real ExcelJS workbooks through the worker I hit three bugs that need fixing before merge:
Two smaller things that fit in the same round:
Everything else (the route changes, the allowlist addition, the packaging and the tests) is in good shape, so once these land it is ready to merge. Thanks again for the careful work on this. |
What
.xlsxfiles were download-only. This adds a read-only preview in the file-preview overlay: sheet tabs, number formats, merged cells and theme colours, virtualized so large sheets scroll smoothly. It works for workspace files, attachments, and xlsx paths printed in the terminal (added to the file-path link pattern andFILE_PREVIEW_EXTENSIONS)..xlsand.odsstay download-only, since ExcelJS only reads xlsx; tests pin both.How
spreadsheet-preview-worker.js+spreadsheet-xlsx-core.js) usingexceljs@4.4.0andfflate@0.8.2(both MIT, pinned exactly as devDependencies and copied intovendor/by postinstall and the build, like the other vendored bundles). The server does no parsing.resolveFileTarget,resolveServableAttachmentPath). There is no new path handling.?preview=trueis capped at 10 MB (413 above it) on bothfile-rawand the attachment raw route; downloads are unchanged._openSpreadsheetPreview/_disposeSpreadsheetPreview), torn down from_stopFilePreviewMediaon open and close. The worker is created viaCodemanBase.urland loads its scripts by relative URL, so--base-urlmounts work.Safety
The workbook is untrusted input:
admitXlsx): at most 5000 ZIP entries, 64 MB inflated in total, 32 MB per entry, a 100:1 compression ratio, 50 worksheets, 250k cells (100k per sheet), 5000 merges per sheet and 5000 styles. Encrypted and ZIP64 files are refused. There is also a 20 s timeout.textContent. The generated style block only accepts validated#rrggbbcolours and a fixed keyword set. At most 2500 cells are drawn per tile.Cost
Page load gains only
spreadsheet-preview.js(5.0 KB gz). Opening a spreadsheet then loads the worker (3.0 KB), core (7.4 KB), fflate (12.5 KB) and exceljs (256 KB gz, 948 KB raw), about 284 KB gz in total. ExcelJS is only fetched after the workbook passes the checks.check:public-assetsenforces a 1.1 MB vendor budget, and a content-hashSPREADSHEET_ASSET_VERSIONcache-busts the worker.dependency-security.test.tsgains one exact exemption: exceljs pinsuuid@8.3.2(our own uuid stays >= 14). The advisory is MODERATE, outside that suite's CRITICAL/HIGH policy, and covers v3/v5/v6 with a caller-supplied buffer, while exceljs only callsv4(). The browser also loads exceljs's own prebuiltdistbundle, and nothing server-side imports exceljs.Testing
BROWSER_TEST_GLOBS). Also 6 new route tests acrossfile-routesand the attachment path guard.textContentswapped forinnerHTMLtypecheck,lint,check:frontend-syntax,format:check,check:public-assets,check:lockfileandbuildare clean.<img onerror>displayed as text with no image element created, no console errors, and none of the four preview requests were made until a spreadsheet was opened.