xls-codec 4.9.0 → 4.11.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 +43 -19
- package/dist/biff/ptg-functions.cjs +3 -0
- package/dist/biff/ptg-functions.d.cts +3 -1
- package/dist/biff/ptg-functions.d.ts +3 -1
- package/dist/biff/ptg-functions.js +3 -1
- package/dist/biff/ptg-writer.cjs +546 -0
- package/dist/biff/ptg-writer.d.cts +7 -0
- package/dist/biff/ptg-writer.d.ts +7 -0
- package/dist/biff/ptg-writer.js +545 -0
- package/dist/biff/string-writer.cjs +6 -0
- package/dist/biff/string-writer.d.cts +3 -1
- package/dist/biff/string-writer.d.ts +3 -1
- package/dist/biff/string-writer.js +6 -1
- package/dist/content.cjs +1 -1
- package/dist/content.d.cts +1 -1
- package/dist/content.d.ts +1 -1
- package/dist/content.js +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/workbook/comment-writer.cjs +75 -0
- package/dist/workbook/comment-writer.d.cts +10 -0
- package/dist/workbook/comment-writer.d.ts +10 -0
- package/dist/workbook/comment-writer.js +74 -0
- package/dist/workbook/encryption.cjs +67 -20
- package/dist/workbook/encryption.d.cts +2 -2
- package/dist/workbook/encryption.d.ts +2 -2
- package/dist/workbook/encryption.js +68 -21
- package/dist/workbook/sheet-writer.cjs +50 -2
- package/dist/workbook/sheet-writer.d.cts +1 -1
- package/dist/workbook/sheet-writer.d.ts +1 -1
- package/dist/workbook/sheet-writer.js +50 -2
- package/dist/written-cells.cjs +3 -3
- package/dist/written-cells.d.cts +2 -2
- package/dist/written-cells.d.ts +2 -2
- package/dist/written-cells.js +3 -3
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -6,7 +6,7 @@ import { BIFF8_VERSION, BOF_TYPE_CHART, BOF_TYPE_MACRO, BOF_TYPE_WORKBOOK, BOF_T
|
|
|
6
6
|
import { concatRecords, writeRecord } from "./biff/record-writer.cjs";
|
|
7
7
|
import { i as readRecords, n as BiffRecord, r as HEADER_SIZE, t as BiffFormatError } from "./records-G82UEVq8.cjs";
|
|
8
8
|
import { decodeRkNumber } from "./biff/rk.cjs";
|
|
9
|
-
import { writeRichExtendedString, writeShortXLUnicodeString, writeXLUnicodeString } from "./biff/string-writer.cjs";
|
|
9
|
+
import { writeRichExtendedString, writeShortXLUnicodeString, writeXLUnicodeString, writeXLUnicodeStringNoCch } from "./biff/string-writer.cjs";
|
|
10
10
|
import { readRichExtendedString, readShortXLUnicodeString, readXLUnicodeString, readXLUnicodeStringNoCch } from "./biff/strings.cjs";
|
|
11
11
|
import { a as splitSubstreams, i as recordByteLength, n as Substream, r as groupRecords, t as RecordGroup } from "./substreams-XDNgDvRJ.cjs";
|
|
12
12
|
import { BiffWriteError } from "./biff/write-errors.cjs";
|
|
@@ -22,4 +22,4 @@ import { RawCell, RawCellValue, RawColumn, RawPrintSettings, RawRange, RawRow, R
|
|
|
22
22
|
import { SheetWriteContext, buildWorksheetSubstream } from "./workbook/sheet-writer.cjs";
|
|
23
23
|
import { writeXls, writeXlsContent } from "./write.cjs";
|
|
24
24
|
export * from "excel-number-format";
|
|
25
|
-
export { BIFF8_VERSION, BOF_TYPE_CHART, BOF_TYPE_MACRO, BOF_TYPE_WORKBOOK, BOF_TYPE_WORKSHEET, BiffFormatError, BiffRecord, BiffWriteError, BlockCursor, CellFormat, CellXfPlanEntry, DEFAULT_COLUMN_WIDTH_CHARS, DEFAULT_ROW_HEIGHT_PT, GENERAL_CELL_XF_INDEX, HEADER_SIZE, MAX_RECORD_DATA_SIZE, RECORD_AI, RECORD_ARRAY, RECORD_BLANK, RECORD_BOF, RECORD_BOOLERR, RECORD_BOTTOMMARGIN, RECORD_BOUNDSHEET8, RECORD_CALCCOUNT, RECORD_CALCDELTA, RECORD_CALCITER, RECORD_CALCREFMODE, RECORD_CALCSAVERECALC, RECORD_CF, RECORD_CF12, RECORD_CFEX, RECORD_COLINFO, RECORD_CONDFMT, RECORD_CONDFMT12, RECORD_CONTINUE, RECORD_CONTINUEFRT12, RECORD_DATE1904, RECORD_DEFAULTROWHEIGHT, RECORD_DEFCOLWIDTH, RECORD_DIMENSIONS, RECORD_DV, RECORD_DVAL, RECORD_EOF, RECORD_EXTERNSHEET, RECORD_FILELOCK, RECORD_FILEPASS, RECORD_FONT, RECORD_FORMAT, RECORD_FORMULA, RECORD_HORIZONTALPAGEBREAKS, RECORD_INTERFACEHDR, RECORD_LABEL, RECORD_LABELSST, RECORD_LBL, RECORD_LEFTMARGIN, RECORD_MERGECELLS, RECORD_MSODRAWING, RECORD_MSODRAWINGGROUP, RECORD_MSODRAWINGSELECTION, RECORD_MULBLANK, RECORD_MULRK, RECORD_NOTE, RECORD_NUMBER, RECORD_OBJ, RECORD_PALETTE, RECORD_PRINTGRID, RECORD_PRINTROWCOL, RECORD_RIGHTMARGIN, RECORD_RK, RECORD_ROW, RECORD_RRDHEAD, RECORD_RRDINFO, RECORD_SERIES, RECORD_SERIESTEXT, RECORD_SETUP, RECORD_SHRFMLA, RECORD_SIINDEX, RECORD_SST, RECORD_STRING, RECORD_STYLE, RECORD_SUPBOOK, RECORD_TABLE, RECORD_TOPMARGIN, RECORD_TXO, RECORD_USREXCL, RECORD_VERTICALPAGEBREAKS, RECORD_WSBOOL, RECORD_XF, RawCell, RawCellValue, RawColumn, RawPrintSettings, RawRange, RawRow, RawSheet, RecordBuilder, RecordGroup, SUMMARY_INFORMATION_STREAM, SheetEntry, SheetWriteContext, Substream, WorkbookGlobals, WorkbookGlobalsBuild, WorkbookGlobalsPlan, WorkbookStreams, XlsContentDocument, buildWorkbookGlobals, buildWorksheetSubstream, columnWidthToPoints, concatRecords, decodeRkNumber, errorCodeOf, errorTextOf, formatCodeOf, groupRecords, inchesToPoints, isXlsFile, isoDateTimeToSerial, isoDateToSerial, isoTimeToSerial, layoutMetadataToSummaryInformation, millimetresToPoints, pointsToColumnWidth, pointsToInches, pointsToTwips, readRecords, readRichExtendedString, readSheetRecords, readShortXLUnicodeString, readWorkbookGlobals, readWorkbookStreams, readXLUnicodeString, readXLUnicodeStringNoCch, readXls, readXlsContent, recordByteLength, serialToIsoDate, serialToIsoDateTime, serialToIsoTime, splitSubstreams, twipsToPoints, writeBofData, writeCellXfRecord, writeFontRecord, writeFormatRecord, writePaletteRecord, writeRecord, writeRichExtendedString, writeShortXLUnicodeString, writeStyleRecord, writeStyleXfRecord, writeXLUnicodeString, writeXls, writeXlsContent };
|
|
25
|
+
export { BIFF8_VERSION, BOF_TYPE_CHART, BOF_TYPE_MACRO, BOF_TYPE_WORKBOOK, BOF_TYPE_WORKSHEET, BiffFormatError, BiffRecord, BiffWriteError, BlockCursor, CellFormat, CellXfPlanEntry, DEFAULT_COLUMN_WIDTH_CHARS, DEFAULT_ROW_HEIGHT_PT, GENERAL_CELL_XF_INDEX, HEADER_SIZE, MAX_RECORD_DATA_SIZE, RECORD_AI, RECORD_ARRAY, RECORD_BLANK, RECORD_BOF, RECORD_BOOLERR, RECORD_BOTTOMMARGIN, RECORD_BOUNDSHEET8, RECORD_CALCCOUNT, RECORD_CALCDELTA, RECORD_CALCITER, RECORD_CALCREFMODE, RECORD_CALCSAVERECALC, RECORD_CF, RECORD_CF12, RECORD_CFEX, RECORD_COLINFO, RECORD_CONDFMT, RECORD_CONDFMT12, RECORD_CONTINUE, RECORD_CONTINUEFRT12, RECORD_DATE1904, RECORD_DEFAULTROWHEIGHT, RECORD_DEFCOLWIDTH, RECORD_DIMENSIONS, RECORD_DV, RECORD_DVAL, RECORD_EOF, RECORD_EXTERNSHEET, RECORD_FILELOCK, RECORD_FILEPASS, RECORD_FONT, RECORD_FORMAT, RECORD_FORMULA, RECORD_HORIZONTALPAGEBREAKS, RECORD_INTERFACEHDR, RECORD_LABEL, RECORD_LABELSST, RECORD_LBL, RECORD_LEFTMARGIN, RECORD_MERGECELLS, RECORD_MSODRAWING, RECORD_MSODRAWINGGROUP, RECORD_MSODRAWINGSELECTION, RECORD_MULBLANK, RECORD_MULRK, RECORD_NOTE, RECORD_NUMBER, RECORD_OBJ, RECORD_PALETTE, RECORD_PRINTGRID, RECORD_PRINTROWCOL, RECORD_RIGHTMARGIN, RECORD_RK, RECORD_ROW, RECORD_RRDHEAD, RECORD_RRDINFO, RECORD_SERIES, RECORD_SERIESTEXT, RECORD_SETUP, RECORD_SHRFMLA, RECORD_SIINDEX, RECORD_SST, RECORD_STRING, RECORD_STYLE, RECORD_SUPBOOK, RECORD_TABLE, RECORD_TOPMARGIN, RECORD_TXO, RECORD_USREXCL, RECORD_VERTICALPAGEBREAKS, RECORD_WSBOOL, RECORD_XF, RawCell, RawCellValue, RawColumn, RawPrintSettings, RawRange, RawRow, RawSheet, RecordBuilder, RecordGroup, SUMMARY_INFORMATION_STREAM, SheetEntry, SheetWriteContext, Substream, WorkbookGlobals, WorkbookGlobalsBuild, WorkbookGlobalsPlan, WorkbookStreams, XlsContentDocument, buildWorkbookGlobals, buildWorksheetSubstream, columnWidthToPoints, concatRecords, decodeRkNumber, errorCodeOf, errorTextOf, formatCodeOf, groupRecords, inchesToPoints, isXlsFile, isoDateTimeToSerial, isoDateToSerial, isoTimeToSerial, layoutMetadataToSummaryInformation, millimetresToPoints, pointsToColumnWidth, pointsToInches, pointsToTwips, readRecords, readRichExtendedString, readSheetRecords, readShortXLUnicodeString, readWorkbookGlobals, readWorkbookStreams, readXLUnicodeString, readXLUnicodeStringNoCch, readXls, readXlsContent, recordByteLength, serialToIsoDate, serialToIsoDateTime, serialToIsoTime, splitSubstreams, twipsToPoints, writeBofData, writeCellXfRecord, writeFontRecord, writeFormatRecord, writePaletteRecord, writeRecord, writeRichExtendedString, writeShortXLUnicodeString, writeStyleRecord, writeStyleXfRecord, writeXLUnicodeString, writeXLUnicodeStringNoCch, writeXls, writeXlsContent };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { BIFF8_VERSION, BOF_TYPE_CHART, BOF_TYPE_MACRO, BOF_TYPE_WORKBOOK, BOF_T
|
|
|
6
6
|
import { concatRecords, writeRecord } from "./biff/record-writer.js";
|
|
7
7
|
import { i as readRecords, n as BiffRecord, r as HEADER_SIZE, t as BiffFormatError } from "./records-G82UEVq8.js";
|
|
8
8
|
import { decodeRkNumber } from "./biff/rk.js";
|
|
9
|
-
import { writeRichExtendedString, writeShortXLUnicodeString, writeXLUnicodeString } from "./biff/string-writer.js";
|
|
9
|
+
import { writeRichExtendedString, writeShortXLUnicodeString, writeXLUnicodeString, writeXLUnicodeStringNoCch } from "./biff/string-writer.js";
|
|
10
10
|
import { readRichExtendedString, readShortXLUnicodeString, readXLUnicodeString, readXLUnicodeStringNoCch } from "./biff/strings.js";
|
|
11
11
|
import { a as splitSubstreams, i as recordByteLength, n as Substream, r as groupRecords, t as RecordGroup } from "./substreams-CgTHx5kX.js";
|
|
12
12
|
import { BiffWriteError } from "./biff/write-errors.js";
|
|
@@ -22,4 +22,4 @@ import { RawCell, RawCellValue, RawColumn, RawPrintSettings, RawRange, RawRow, R
|
|
|
22
22
|
import { SheetWriteContext, buildWorksheetSubstream } from "./workbook/sheet-writer.js";
|
|
23
23
|
import { writeXls, writeXlsContent } from "./write.js";
|
|
24
24
|
export * from "excel-number-format";
|
|
25
|
-
export { BIFF8_VERSION, BOF_TYPE_CHART, BOF_TYPE_MACRO, BOF_TYPE_WORKBOOK, BOF_TYPE_WORKSHEET, BiffFormatError, BiffRecord, BiffWriteError, BlockCursor, CellFormat, CellXfPlanEntry, DEFAULT_COLUMN_WIDTH_CHARS, DEFAULT_ROW_HEIGHT_PT, GENERAL_CELL_XF_INDEX, HEADER_SIZE, MAX_RECORD_DATA_SIZE, RECORD_AI, RECORD_ARRAY, RECORD_BLANK, RECORD_BOF, RECORD_BOOLERR, RECORD_BOTTOMMARGIN, RECORD_BOUNDSHEET8, RECORD_CALCCOUNT, RECORD_CALCDELTA, RECORD_CALCITER, RECORD_CALCREFMODE, RECORD_CALCSAVERECALC, RECORD_CF, RECORD_CF12, RECORD_CFEX, RECORD_COLINFO, RECORD_CONDFMT, RECORD_CONDFMT12, RECORD_CONTINUE, RECORD_CONTINUEFRT12, RECORD_DATE1904, RECORD_DEFAULTROWHEIGHT, RECORD_DEFCOLWIDTH, RECORD_DIMENSIONS, RECORD_DV, RECORD_DVAL, RECORD_EOF, RECORD_EXTERNSHEET, RECORD_FILELOCK, RECORD_FILEPASS, RECORD_FONT, RECORD_FORMAT, RECORD_FORMULA, RECORD_HORIZONTALPAGEBREAKS, RECORD_INTERFACEHDR, RECORD_LABEL, RECORD_LABELSST, RECORD_LBL, RECORD_LEFTMARGIN, RECORD_MERGECELLS, RECORD_MSODRAWING, RECORD_MSODRAWINGGROUP, RECORD_MSODRAWINGSELECTION, RECORD_MULBLANK, RECORD_MULRK, RECORD_NOTE, RECORD_NUMBER, RECORD_OBJ, RECORD_PALETTE, RECORD_PRINTGRID, RECORD_PRINTROWCOL, RECORD_RIGHTMARGIN, RECORD_RK, RECORD_ROW, RECORD_RRDHEAD, RECORD_RRDINFO, RECORD_SERIES, RECORD_SERIESTEXT, RECORD_SETUP, RECORD_SHRFMLA, RECORD_SIINDEX, RECORD_SST, RECORD_STRING, RECORD_STYLE, RECORD_SUPBOOK, RECORD_TABLE, RECORD_TOPMARGIN, RECORD_TXO, RECORD_USREXCL, RECORD_VERTICALPAGEBREAKS, RECORD_WSBOOL, RECORD_XF, RawCell, RawCellValue, RawColumn, RawPrintSettings, RawRange, RawRow, RawSheet, RecordBuilder, RecordGroup, SUMMARY_INFORMATION_STREAM, SheetEntry, SheetWriteContext, Substream, WorkbookGlobals, WorkbookGlobalsBuild, WorkbookGlobalsPlan, WorkbookStreams, XlsContentDocument, buildWorkbookGlobals, buildWorksheetSubstream, columnWidthToPoints, concatRecords, decodeRkNumber, errorCodeOf, errorTextOf, formatCodeOf, groupRecords, inchesToPoints, isXlsFile, isoDateTimeToSerial, isoDateToSerial, isoTimeToSerial, layoutMetadataToSummaryInformation, millimetresToPoints, pointsToColumnWidth, pointsToInches, pointsToTwips, readRecords, readRichExtendedString, readSheetRecords, readShortXLUnicodeString, readWorkbookGlobals, readWorkbookStreams, readXLUnicodeString, readXLUnicodeStringNoCch, readXls, readXlsContent, recordByteLength, serialToIsoDate, serialToIsoDateTime, serialToIsoTime, splitSubstreams, twipsToPoints, writeBofData, writeCellXfRecord, writeFontRecord, writeFormatRecord, writePaletteRecord, writeRecord, writeRichExtendedString, writeShortXLUnicodeString, writeStyleRecord, writeStyleXfRecord, writeXLUnicodeString, writeXls, writeXlsContent };
|
|
25
|
+
export { BIFF8_VERSION, BOF_TYPE_CHART, BOF_TYPE_MACRO, BOF_TYPE_WORKBOOK, BOF_TYPE_WORKSHEET, BiffFormatError, BiffRecord, BiffWriteError, BlockCursor, CellFormat, CellXfPlanEntry, DEFAULT_COLUMN_WIDTH_CHARS, DEFAULT_ROW_HEIGHT_PT, GENERAL_CELL_XF_INDEX, HEADER_SIZE, MAX_RECORD_DATA_SIZE, RECORD_AI, RECORD_ARRAY, RECORD_BLANK, RECORD_BOF, RECORD_BOOLERR, RECORD_BOTTOMMARGIN, RECORD_BOUNDSHEET8, RECORD_CALCCOUNT, RECORD_CALCDELTA, RECORD_CALCITER, RECORD_CALCREFMODE, RECORD_CALCSAVERECALC, RECORD_CF, RECORD_CF12, RECORD_CFEX, RECORD_COLINFO, RECORD_CONDFMT, RECORD_CONDFMT12, RECORD_CONTINUE, RECORD_CONTINUEFRT12, RECORD_DATE1904, RECORD_DEFAULTROWHEIGHT, RECORD_DEFCOLWIDTH, RECORD_DIMENSIONS, RECORD_DV, RECORD_DVAL, RECORD_EOF, RECORD_EXTERNSHEET, RECORD_FILELOCK, RECORD_FILEPASS, RECORD_FONT, RECORD_FORMAT, RECORD_FORMULA, RECORD_HORIZONTALPAGEBREAKS, RECORD_INTERFACEHDR, RECORD_LABEL, RECORD_LABELSST, RECORD_LBL, RECORD_LEFTMARGIN, RECORD_MERGECELLS, RECORD_MSODRAWING, RECORD_MSODRAWINGGROUP, RECORD_MSODRAWINGSELECTION, RECORD_MULBLANK, RECORD_MULRK, RECORD_NOTE, RECORD_NUMBER, RECORD_OBJ, RECORD_PALETTE, RECORD_PRINTGRID, RECORD_PRINTROWCOL, RECORD_RIGHTMARGIN, RECORD_RK, RECORD_ROW, RECORD_RRDHEAD, RECORD_RRDINFO, RECORD_SERIES, RECORD_SERIESTEXT, RECORD_SETUP, RECORD_SHRFMLA, RECORD_SIINDEX, RECORD_SST, RECORD_STRING, RECORD_STYLE, RECORD_SUPBOOK, RECORD_TABLE, RECORD_TOPMARGIN, RECORD_TXO, RECORD_USREXCL, RECORD_VERTICALPAGEBREAKS, RECORD_WSBOOL, RECORD_XF, RawCell, RawCellValue, RawColumn, RawPrintSettings, RawRange, RawRow, RawSheet, RecordBuilder, RecordGroup, SUMMARY_INFORMATION_STREAM, SheetEntry, SheetWriteContext, Substream, WorkbookGlobals, WorkbookGlobalsBuild, WorkbookGlobalsPlan, WorkbookStreams, XlsContentDocument, buildWorkbookGlobals, buildWorksheetSubstream, columnWidthToPoints, concatRecords, decodeRkNumber, errorCodeOf, errorTextOf, formatCodeOf, groupRecords, inchesToPoints, isXlsFile, isoDateTimeToSerial, isoDateToSerial, isoTimeToSerial, layoutMetadataToSummaryInformation, millimetresToPoints, pointsToColumnWidth, pointsToInches, pointsToTwips, readRecords, readRichExtendedString, readSheetRecords, readShortXLUnicodeString, readWorkbookGlobals, readWorkbookStreams, readXLUnicodeString, readXLUnicodeStringNoCch, readXls, readXlsContent, recordByteLength, serialToIsoDate, serialToIsoDateTime, serialToIsoTime, splitSubstreams, twipsToPoints, writeBofData, writeCellXfRecord, writeFontRecord, writeFormatRecord, writePaletteRecord, writeRecord, writeRichExtendedString, writeShortXLUnicodeString, writeStyleRecord, writeStyleXfRecord, writeXLUnicodeString, writeXLUnicodeStringNoCch, writeXls, writeXlsContent };
|
package/dist/index.js
CHANGED
|
@@ -15,11 +15,11 @@ import { decodeRkNumber } from "./biff/rk.js";
|
|
|
15
15
|
import { readSheetRecords } from "./workbook/sheet.js";
|
|
16
16
|
import { readXls, readXlsContent } from "./content.js";
|
|
17
17
|
import { writeBofData } from "./biff/bof-writer.js";
|
|
18
|
-
import { writeRichExtendedString, writeShortXLUnicodeString, writeXLUnicodeString } from "./biff/string-writer.js";
|
|
18
|
+
import { writeRichExtendedString, writeShortXLUnicodeString, writeXLUnicodeString, writeXLUnicodeStringNoCch } from "./biff/string-writer.js";
|
|
19
19
|
import { writeCellXfRecord, writeFontRecord, writeFormatRecord, writePaletteRecord, writeStyleRecord, writeStyleXfRecord } from "./biff/xf-writer.js";
|
|
20
20
|
import { layoutMetadataToSummaryInformation } from "./metadata.js";
|
|
21
21
|
import { GENERAL_CELL_XF_INDEX, buildWorkbookGlobals } from "./workbook/globals-writer.js";
|
|
22
22
|
import { buildWorksheetSubstream } from "./workbook/sheet-writer.js";
|
|
23
23
|
import { writeXls, writeXlsContent } from "./write.js";
|
|
24
24
|
export * from "excel-number-format";
|
|
25
|
-
export { BIFF8_VERSION, BOF_TYPE_CHART, BOF_TYPE_MACRO, BOF_TYPE_WORKBOOK, BOF_TYPE_WORKSHEET, BiffFormatError, BiffWriteError, BlockCursor, DEFAULT_COLUMN_WIDTH_CHARS, DEFAULT_ROW_HEIGHT_PT, GENERAL_CELL_XF_INDEX, HEADER_SIZE, MAX_RECORD_DATA_SIZE, RECORD_AI, RECORD_ARRAY, RECORD_BLANK, RECORD_BOF, RECORD_BOOLERR, RECORD_BOTTOMMARGIN, RECORD_BOUNDSHEET8, RECORD_CALCCOUNT, RECORD_CALCDELTA, RECORD_CALCITER, RECORD_CALCREFMODE, RECORD_CALCSAVERECALC, RECORD_CF, RECORD_CF12, RECORD_CFEX, RECORD_COLINFO, RECORD_CONDFMT, RECORD_CONDFMT12, RECORD_CONTINUE, RECORD_CONTINUEFRT12, RECORD_DATE1904, RECORD_DEFAULTROWHEIGHT, RECORD_DEFCOLWIDTH, RECORD_DIMENSIONS, RECORD_DV, RECORD_DVAL, RECORD_EOF, RECORD_EXTERNSHEET, RECORD_FILELOCK, RECORD_FILEPASS, RECORD_FONT, RECORD_FORMAT, RECORD_FORMULA, RECORD_HORIZONTALPAGEBREAKS, RECORD_INTERFACEHDR, RECORD_LABEL, RECORD_LABELSST, RECORD_LBL, RECORD_LEFTMARGIN, RECORD_MERGECELLS, RECORD_MSODRAWING, RECORD_MSODRAWINGGROUP, RECORD_MSODRAWINGSELECTION, RECORD_MULBLANK, RECORD_MULRK, RECORD_NOTE, RECORD_NUMBER, RECORD_OBJ, RECORD_PALETTE, RECORD_PRINTGRID, RECORD_PRINTROWCOL, RECORD_RIGHTMARGIN, RECORD_RK, RECORD_ROW, RECORD_RRDHEAD, RECORD_RRDINFO, RECORD_SERIES, RECORD_SERIESTEXT, RECORD_SETUP, RECORD_SHRFMLA, RECORD_SIINDEX, RECORD_SST, RECORD_STRING, RECORD_STYLE, RECORD_SUPBOOK, RECORD_TABLE, RECORD_TOPMARGIN, RECORD_TXO, RECORD_USREXCL, RECORD_VERTICALPAGEBREAKS, RECORD_WSBOOL, RECORD_XF, RecordBuilder, SUMMARY_INFORMATION_STREAM, buildWorkbookGlobals, buildWorksheetSubstream, columnWidthToPoints, concatRecords, decodeRkNumber, errorCodeOf, errorTextOf, formatCodeOf, groupRecords, inchesToPoints, isXlsFile, isoDateTimeToSerial, isoDateToSerial, isoTimeToSerial, layoutMetadataToSummaryInformation, millimetresToPoints, pointsToColumnWidth, pointsToInches, pointsToTwips, readRecords, readRichExtendedString, readSheetRecords, readShortXLUnicodeString, readWorkbookGlobals, readWorkbookStreams, readXLUnicodeString, readXLUnicodeStringNoCch, readXls, readXlsContent, recordByteLength, serialToIsoDate, serialToIsoDateTime, serialToIsoTime, splitSubstreams, twipsToPoints, writeBofData, writeCellXfRecord, writeFontRecord, writeFormatRecord, writePaletteRecord, writeRecord, writeRichExtendedString, writeShortXLUnicodeString, writeStyleRecord, writeStyleXfRecord, writeXLUnicodeString, writeXls, writeXlsContent };
|
|
25
|
+
export { BIFF8_VERSION, BOF_TYPE_CHART, BOF_TYPE_MACRO, BOF_TYPE_WORKBOOK, BOF_TYPE_WORKSHEET, BiffFormatError, BiffWriteError, BlockCursor, DEFAULT_COLUMN_WIDTH_CHARS, DEFAULT_ROW_HEIGHT_PT, GENERAL_CELL_XF_INDEX, HEADER_SIZE, MAX_RECORD_DATA_SIZE, RECORD_AI, RECORD_ARRAY, RECORD_BLANK, RECORD_BOF, RECORD_BOOLERR, RECORD_BOTTOMMARGIN, RECORD_BOUNDSHEET8, RECORD_CALCCOUNT, RECORD_CALCDELTA, RECORD_CALCITER, RECORD_CALCREFMODE, RECORD_CALCSAVERECALC, RECORD_CF, RECORD_CF12, RECORD_CFEX, RECORD_COLINFO, RECORD_CONDFMT, RECORD_CONDFMT12, RECORD_CONTINUE, RECORD_CONTINUEFRT12, RECORD_DATE1904, RECORD_DEFAULTROWHEIGHT, RECORD_DEFCOLWIDTH, RECORD_DIMENSIONS, RECORD_DV, RECORD_DVAL, RECORD_EOF, RECORD_EXTERNSHEET, RECORD_FILELOCK, RECORD_FILEPASS, RECORD_FONT, RECORD_FORMAT, RECORD_FORMULA, RECORD_HORIZONTALPAGEBREAKS, RECORD_INTERFACEHDR, RECORD_LABEL, RECORD_LABELSST, RECORD_LBL, RECORD_LEFTMARGIN, RECORD_MERGECELLS, RECORD_MSODRAWING, RECORD_MSODRAWINGGROUP, RECORD_MSODRAWINGSELECTION, RECORD_MULBLANK, RECORD_MULRK, RECORD_NOTE, RECORD_NUMBER, RECORD_OBJ, RECORD_PALETTE, RECORD_PRINTGRID, RECORD_PRINTROWCOL, RECORD_RIGHTMARGIN, RECORD_RK, RECORD_ROW, RECORD_RRDHEAD, RECORD_RRDINFO, RECORD_SERIES, RECORD_SERIESTEXT, RECORD_SETUP, RECORD_SHRFMLA, RECORD_SIINDEX, RECORD_SST, RECORD_STRING, RECORD_STYLE, RECORD_SUPBOOK, RECORD_TABLE, RECORD_TOPMARGIN, RECORD_TXO, RECORD_USREXCL, RECORD_VERTICALPAGEBREAKS, RECORD_WSBOOL, RECORD_XF, RecordBuilder, SUMMARY_INFORMATION_STREAM, buildWorkbookGlobals, buildWorksheetSubstream, columnWidthToPoints, concatRecords, decodeRkNumber, errorCodeOf, errorTextOf, formatCodeOf, groupRecords, inchesToPoints, isXlsFile, isoDateTimeToSerial, isoDateToSerial, isoTimeToSerial, layoutMetadataToSummaryInformation, millimetresToPoints, pointsToColumnWidth, pointsToInches, pointsToTwips, readRecords, readRichExtendedString, readSheetRecords, readShortXLUnicodeString, readWorkbookGlobals, readWorkbookStreams, readXLUnicodeString, readXLUnicodeStringNoCch, readXls, readXlsContent, recordByteLength, serialToIsoDate, serialToIsoDateTime, serialToIsoTime, splitSubstreams, twipsToPoints, writeBofData, writeCellXfRecord, writeFontRecord, writeFormatRecord, writePaletteRecord, writeRecord, writeRichExtendedString, writeShortXLUnicodeString, writeStyleRecord, writeStyleXfRecord, writeXLUnicodeString, writeXLUnicodeStringNoCch, writeXls, writeXlsContent };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("../biff/record-types.cjs");
|
|
3
|
+
const require_biff_write_errors = require("../biff/write-errors.cjs");
|
|
4
|
+
const require_biff_builder = require("../biff/builder.cjs");
|
|
5
|
+
const require_biff_record_writer = require("../biff/record-writer.cjs");
|
|
6
|
+
const require_biff_string_writer = require("../biff/string-writer.cjs");
|
|
7
|
+
//#region src/workbook/comment-writer.ts
|
|
8
|
+
/** [MS-XLS] 2.5.213 ObjId: the drawing-object type a comment's Obj record names -- workbook/comments.ts's own OBJECT_TYPE_NOTE, restated here since the two modules read/write the identical constant independently rather than sharing an import across the read/write boundary (matching this package's existing convention of one module per direction). */
|
|
9
|
+
const OBJECT_TYPE_NOTE = 25;
|
|
10
|
+
const FTCMO_FT = 21;
|
|
11
|
+
const FTCMO_CB = 18;
|
|
12
|
+
const FTNTS_FT = 13;
|
|
13
|
+
const FTNTS_CB = 22;
|
|
14
|
+
/** FtCmo ([MS-XLS] 2.5.92, 22 bytes): the common properties every Obj record opens with. Every flag bit beyond ot/id is either a UI concern (locked, default size, printed) this writer has no data for, or explicitly reserved -- so grbit and the three trailing "unused" fields are all written zero. */
|
|
15
|
+
function writeFtCmo(objId) {
|
|
16
|
+
return new require_biff_builder.RecordBuilder().u16(FTCMO_FT).u16(FTCMO_CB).u16(OBJECT_TYPE_NOTE).u16(objId).u16(0).u32(0).u32(0).u32(0).build();
|
|
17
|
+
}
|
|
18
|
+
/** A pseudo-random 16-byte GUID for FtNts's own comment identifier -- [MS-XLS] requires the field to be present and does not require it to be globally unique across files, only present, so a fresh random value per comment (rather than a fixed or zero one) is what a real producer's own GUID allocation looks like without this package needing a GUID-formatting dependency. Worker-isomorphic: globalThis.crypto is available in both Node and a Cloudflare Workers isolate, unlike Node's own node:crypto module. */
|
|
19
|
+
function randomGuidBytes() {
|
|
20
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
21
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
22
|
+
return bytes;
|
|
23
|
+
}
|
|
24
|
+
/** FtNts ([MS-XLS] 2.5.163... 2.5.b0991167, 26 bytes): the note-specific properties following FtCmo in a comment's own Obj record. fSharedNote is always written clear -- this writer has no shared-comment concept to express. */
|
|
25
|
+
function writeFtNts() {
|
|
26
|
+
return new require_biff_builder.RecordBuilder().u16(FTNTS_FT).u16(FTNTS_CB).bytes(randomGuidBytes()).u16(0).u32(0).build();
|
|
27
|
+
}
|
|
28
|
+
/** Obj ([MS-XLS] 2.4.181) for a Note-type object: FtCmo, then FtNts (present if and only if cmo.ot is 0x19, which this writer's OBJECT_TYPE_NOTE always is), then the mandatory trailing 4-byte reserved field every Obj not naming a list-box/dropdown object (ot 0x12/0x14) carries. */
|
|
29
|
+
function writeObjRecordForNote(objId) {
|
|
30
|
+
const data = new require_biff_builder.RecordBuilder().bytes(writeFtCmo(objId)).bytes(writeFtNts()).u32(0).build();
|
|
31
|
+
return require_biff_record_writer.writeRecord(93, data);
|
|
32
|
+
}
|
|
33
|
+
/** TxORuns' own minimal shape for plain, unformatted text ([MS-XLS] 2.5.31cd7d1e/d738ffef/6fb4c0e3): one Run (an all-default FormatRun -- ich 0, ifnt 0 -- plus its own six reserved bytes) followed by the mandatory TxOLastRun sentinel naming cchText again. 16 bytes total, the minimum [MS-XLS]'s own "cbRuns MUST be >= 16 and a multiple of 8" rule allows -- this writer never carries real per-character formatting for a comment's text (workbook/comments.ts's own read side does not model TxORuns either, for the identical reason). */
|
|
34
|
+
function writeMinimalTxoRuns(cchText) {
|
|
35
|
+
return new require_biff_builder.RecordBuilder().u16(0).u16(0).u16(0).u16(0).u16(cchText).u16(0).u32(0).build();
|
|
36
|
+
}
|
|
37
|
+
/** TxO ([MS-XLS] 2.4.329) plus the one Continue record carrying its text and formatting-run trailer -- see this module's own top comment for why both text and runs can share a single Continue rather than needing one each. cchText is 0 for an empty comment, in which case [MS-XLS] itself requires cbRuns to be 0 too and no Continue record follows at all. */
|
|
38
|
+
function writeTxoRecords(text) {
|
|
39
|
+
const cchText = text.length;
|
|
40
|
+
const cbRuns = cchText === 0 ? 0 : 16;
|
|
41
|
+
const txoData = new require_biff_builder.RecordBuilder().u16(18).u16(0).u16(0).u32(0).u16(cchText).u16(cbRuns).u16(0).u16(0).build();
|
|
42
|
+
const txo = require_biff_record_writer.writeRecord(438, txoData);
|
|
43
|
+
if (cchText === 0) return [txo];
|
|
44
|
+
const continueData = new require_biff_builder.RecordBuilder().bytes(require_biff_string_writer.writeXLUnicodeStringNoCch(text)).bytes(writeMinimalTxoRuns(cchText)).build();
|
|
45
|
+
return [txo, require_biff_record_writer.writeRecord(60, continueData)];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Note ([MS-XLS] 2.4.179, wrapping a NoteSh structure): the comment's own cell anchor and author, naming the Obj record that carries its text through idObj. fShow/fRwHidden/fColHidden are always written clear -- this writer tracks none of the on-screen display state they carry.
|
|
49
|
+
*
|
|
50
|
+
* A comment with no recorded author writes stAuthor as an empty string, rather than a non-empty placeholder -- [MS-XLS] 2.5.163 itself documents stAuthor's own length as "MUST be greater than or equal to 1", but this reader (workbook/comments.ts's own readNoteAnchor) only ever promotes a NON-empty stAuthor to ContentSheetCellComment.author, meaning a placeholder would round-trip back as a fabricated author nobody wrote. An empty string is the one spelling that round-trips through this reader as "no author", matching every other lossless-round-trip choice this writer makes in favour of what its own reader reads back over strict textual conformance to a field length rule real producers routinely leave unenforced anyway.
|
|
51
|
+
*/
|
|
52
|
+
function writeNoteRecord(cell, objId) {
|
|
53
|
+
const data = new require_biff_builder.RecordBuilder().u16(cell.row).u16(cell.column).u16(0).u16(objId).bytes(require_biff_string_writer.writeXLUnicodeString(cell.comment?.author ?? "")).u8(0).build();
|
|
54
|
+
return require_biff_record_writer.writeRecord(28, data);
|
|
55
|
+
}
|
|
56
|
+
const MAX_OBJECT_ID = 65535;
|
|
57
|
+
/**
|
|
58
|
+
* Every Note/Obj/TxO record a sheet's own commented cells need, in the order described above -- for `cells` already filtered to exactly those carrying a `comment` (workbook/sheet-writer.ts's own caller does the filtering, since only it knows the sheet's full cell list).
|
|
59
|
+
*
|
|
60
|
+
* Object ids are assigned sequentially from 1: [MS-XLS] 2.5.92's own FtCmo.id must be unique "among all Obj records within ... Worksheet Substream ABNF", and this writer never emits any other kind of Obj record (no shapes, charts, or form controls yet -- see this package's README), so a per-sheet counter starting at 1 is already unique on its own.
|
|
61
|
+
*/
|
|
62
|
+
function writeSheetComments(commentedCells) {
|
|
63
|
+
if (commentedCells.length > MAX_OBJECT_ID) throw new require_biff_write_errors.BiffWriteError(`sheet carries ${commentedCells.length} cell comments, more than the ${MAX_OBJECT_ID} distinct object ids [MS-XLS] 2.5.92's own 16-bit FtCmo.id field can hold`);
|
|
64
|
+
const ordered = [...commentedCells].sort((a, b) => a.row !== b.row ? a.row - b.row : a.column - b.column);
|
|
65
|
+
const pieces = ordered.map((cell, index) => writeNoteRecord(cell, index + 1));
|
|
66
|
+
ordered.forEach((cell, index) => {
|
|
67
|
+
const objId = index + 1;
|
|
68
|
+
const comment = cell.comment;
|
|
69
|
+
if (comment === void 0) throw new require_biff_write_errors.BiffWriteError(`internal error: writeSheetComments was called with a cell at row ${cell.row}, column ${cell.column} carrying no comment`);
|
|
70
|
+
pieces.push(writeObjRecordForNote(objId), ...writeTxoRecords(comment.text));
|
|
71
|
+
});
|
|
72
|
+
return pieces;
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
exports.writeSheetComments = writeSheetComments;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ContentSheetCell } from "document-schema.js";
|
|
2
|
+
//#region src/workbook/comment-writer.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Every Note/Obj/TxO record a sheet's own commented cells need, in the order described above -- for `cells` already filtered to exactly those carrying a `comment` (workbook/sheet-writer.ts's own caller does the filtering, since only it knows the sheet's full cell list).
|
|
5
|
+
*
|
|
6
|
+
* Object ids are assigned sequentially from 1: [MS-XLS] 2.5.92's own FtCmo.id must be unique "among all Obj records within ... Worksheet Substream ABNF", and this writer never emits any other kind of Obj record (no shapes, charts, or form controls yet -- see this package's README), so a per-sheet counter starting at 1 is already unique on its own.
|
|
7
|
+
*/
|
|
8
|
+
declare function writeSheetComments(commentedCells: readonly ContentSheetCell[]): Uint8Array<ArrayBuffer>[];
|
|
9
|
+
//#endregion
|
|
10
|
+
export { writeSheetComments };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ContentSheetCell } from "document-schema.js";
|
|
2
|
+
//#region src/workbook/comment-writer.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Every Note/Obj/TxO record a sheet's own commented cells need, in the order described above -- for `cells` already filtered to exactly those carrying a `comment` (workbook/sheet-writer.ts's own caller does the filtering, since only it knows the sheet's full cell list).
|
|
5
|
+
*
|
|
6
|
+
* Object ids are assigned sequentially from 1: [MS-XLS] 2.5.92's own FtCmo.id must be unique "among all Obj records within ... Worksheet Substream ABNF", and this writer never emits any other kind of Obj record (no shapes, charts, or form controls yet -- see this package's README), so a per-sheet counter starting at 1 is already unique on its own.
|
|
7
|
+
*/
|
|
8
|
+
declare function writeSheetComments(commentedCells: readonly ContentSheetCell[]): Uint8Array<ArrayBuffer>[];
|
|
9
|
+
//#endregion
|
|
10
|
+
export { writeSheetComments };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import "../biff/record-types.js";
|
|
2
|
+
import { BiffWriteError } from "../biff/write-errors.js";
|
|
3
|
+
import { RecordBuilder } from "../biff/builder.js";
|
|
4
|
+
import { writeRecord } from "../biff/record-writer.js";
|
|
5
|
+
import { writeXLUnicodeString, writeXLUnicodeStringNoCch } from "../biff/string-writer.js";
|
|
6
|
+
//#region src/workbook/comment-writer.ts
|
|
7
|
+
/** [MS-XLS] 2.5.213 ObjId: the drawing-object type a comment's Obj record names -- workbook/comments.ts's own OBJECT_TYPE_NOTE, restated here since the two modules read/write the identical constant independently rather than sharing an import across the read/write boundary (matching this package's existing convention of one module per direction). */
|
|
8
|
+
const OBJECT_TYPE_NOTE = 25;
|
|
9
|
+
const FTCMO_FT = 21;
|
|
10
|
+
const FTCMO_CB = 18;
|
|
11
|
+
const FTNTS_FT = 13;
|
|
12
|
+
const FTNTS_CB = 22;
|
|
13
|
+
/** FtCmo ([MS-XLS] 2.5.92, 22 bytes): the common properties every Obj record opens with. Every flag bit beyond ot/id is either a UI concern (locked, default size, printed) this writer has no data for, or explicitly reserved -- so grbit and the three trailing "unused" fields are all written zero. */
|
|
14
|
+
function writeFtCmo(objId) {
|
|
15
|
+
return new RecordBuilder().u16(FTCMO_FT).u16(FTCMO_CB).u16(OBJECT_TYPE_NOTE).u16(objId).u16(0).u32(0).u32(0).u32(0).build();
|
|
16
|
+
}
|
|
17
|
+
/** A pseudo-random 16-byte GUID for FtNts's own comment identifier -- [MS-XLS] requires the field to be present and does not require it to be globally unique across files, only present, so a fresh random value per comment (rather than a fixed or zero one) is what a real producer's own GUID allocation looks like without this package needing a GUID-formatting dependency. Worker-isomorphic: globalThis.crypto is available in both Node and a Cloudflare Workers isolate, unlike Node's own node:crypto module. */
|
|
18
|
+
function randomGuidBytes() {
|
|
19
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
20
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
21
|
+
return bytes;
|
|
22
|
+
}
|
|
23
|
+
/** FtNts ([MS-XLS] 2.5.163... 2.5.b0991167, 26 bytes): the note-specific properties following FtCmo in a comment's own Obj record. fSharedNote is always written clear -- this writer has no shared-comment concept to express. */
|
|
24
|
+
function writeFtNts() {
|
|
25
|
+
return new RecordBuilder().u16(FTNTS_FT).u16(FTNTS_CB).bytes(randomGuidBytes()).u16(0).u32(0).build();
|
|
26
|
+
}
|
|
27
|
+
/** Obj ([MS-XLS] 2.4.181) for a Note-type object: FtCmo, then FtNts (present if and only if cmo.ot is 0x19, which this writer's OBJECT_TYPE_NOTE always is), then the mandatory trailing 4-byte reserved field every Obj not naming a list-box/dropdown object (ot 0x12/0x14) carries. */
|
|
28
|
+
function writeObjRecordForNote(objId) {
|
|
29
|
+
const data = new RecordBuilder().bytes(writeFtCmo(objId)).bytes(writeFtNts()).u32(0).build();
|
|
30
|
+
return writeRecord(93, data);
|
|
31
|
+
}
|
|
32
|
+
/** TxORuns' own minimal shape for plain, unformatted text ([MS-XLS] 2.5.31cd7d1e/d738ffef/6fb4c0e3): one Run (an all-default FormatRun -- ich 0, ifnt 0 -- plus its own six reserved bytes) followed by the mandatory TxOLastRun sentinel naming cchText again. 16 bytes total, the minimum [MS-XLS]'s own "cbRuns MUST be >= 16 and a multiple of 8" rule allows -- this writer never carries real per-character formatting for a comment's text (workbook/comments.ts's own read side does not model TxORuns either, for the identical reason). */
|
|
33
|
+
function writeMinimalTxoRuns(cchText) {
|
|
34
|
+
return new RecordBuilder().u16(0).u16(0).u16(0).u16(0).u16(cchText).u16(0).u32(0).build();
|
|
35
|
+
}
|
|
36
|
+
/** TxO ([MS-XLS] 2.4.329) plus the one Continue record carrying its text and formatting-run trailer -- see this module's own top comment for why both text and runs can share a single Continue rather than needing one each. cchText is 0 for an empty comment, in which case [MS-XLS] itself requires cbRuns to be 0 too and no Continue record follows at all. */
|
|
37
|
+
function writeTxoRecords(text) {
|
|
38
|
+
const cchText = text.length;
|
|
39
|
+
const cbRuns = cchText === 0 ? 0 : 16;
|
|
40
|
+
const txoData = new RecordBuilder().u16(18).u16(0).u16(0).u32(0).u16(cchText).u16(cbRuns).u16(0).u16(0).build();
|
|
41
|
+
const txo = writeRecord(438, txoData);
|
|
42
|
+
if (cchText === 0) return [txo];
|
|
43
|
+
const continueData = new RecordBuilder().bytes(writeXLUnicodeStringNoCch(text)).bytes(writeMinimalTxoRuns(cchText)).build();
|
|
44
|
+
return [txo, writeRecord(60, continueData)];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Note ([MS-XLS] 2.4.179, wrapping a NoteSh structure): the comment's own cell anchor and author, naming the Obj record that carries its text through idObj. fShow/fRwHidden/fColHidden are always written clear -- this writer tracks none of the on-screen display state they carry.
|
|
48
|
+
*
|
|
49
|
+
* A comment with no recorded author writes stAuthor as an empty string, rather than a non-empty placeholder -- [MS-XLS] 2.5.163 itself documents stAuthor's own length as "MUST be greater than or equal to 1", but this reader (workbook/comments.ts's own readNoteAnchor) only ever promotes a NON-empty stAuthor to ContentSheetCellComment.author, meaning a placeholder would round-trip back as a fabricated author nobody wrote. An empty string is the one spelling that round-trips through this reader as "no author", matching every other lossless-round-trip choice this writer makes in favour of what its own reader reads back over strict textual conformance to a field length rule real producers routinely leave unenforced anyway.
|
|
50
|
+
*/
|
|
51
|
+
function writeNoteRecord(cell, objId) {
|
|
52
|
+
const data = new RecordBuilder().u16(cell.row).u16(cell.column).u16(0).u16(objId).bytes(writeXLUnicodeString(cell.comment?.author ?? "")).u8(0).build();
|
|
53
|
+
return writeRecord(28, data);
|
|
54
|
+
}
|
|
55
|
+
const MAX_OBJECT_ID = 65535;
|
|
56
|
+
/**
|
|
57
|
+
* Every Note/Obj/TxO record a sheet's own commented cells need, in the order described above -- for `cells` already filtered to exactly those carrying a `comment` (workbook/sheet-writer.ts's own caller does the filtering, since only it knows the sheet's full cell list).
|
|
58
|
+
*
|
|
59
|
+
* Object ids are assigned sequentially from 1: [MS-XLS] 2.5.92's own FtCmo.id must be unique "among all Obj records within ... Worksheet Substream ABNF", and this writer never emits any other kind of Obj record (no shapes, charts, or form controls yet -- see this package's README), so a per-sheet counter starting at 1 is already unique on its own.
|
|
60
|
+
*/
|
|
61
|
+
function writeSheetComments(commentedCells) {
|
|
62
|
+
if (commentedCells.length > MAX_OBJECT_ID) throw new BiffWriteError(`sheet carries ${commentedCells.length} cell comments, more than the ${MAX_OBJECT_ID} distinct object ids [MS-XLS] 2.5.92's own 16-bit FtCmo.id field can hold`);
|
|
63
|
+
const ordered = [...commentedCells].sort((a, b) => a.row !== b.row ? a.row - b.row : a.column - b.column);
|
|
64
|
+
const pieces = ordered.map((cell, index) => writeNoteRecord(cell, index + 1));
|
|
65
|
+
ordered.forEach((cell, index) => {
|
|
66
|
+
const objId = index + 1;
|
|
67
|
+
const comment = cell.comment;
|
|
68
|
+
if (comment === void 0) throw new BiffWriteError(`internal error: writeSheetComments was called with a cell at row ${cell.row}, column ${cell.column} carrying no comment`);
|
|
69
|
+
pieces.push(writeObjRecordForNote(objId), ...writeTxoRecords(comment.text));
|
|
70
|
+
});
|
|
71
|
+
return pieces;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
export { writeSheetComments };
|
|
@@ -4,7 +4,8 @@ const require_biff_records = require("../biff/records.cjs");
|
|
|
4
4
|
const require_biff_cursor = require("../biff/cursor.cjs");
|
|
5
5
|
let archive_codec = require("archive-codec");
|
|
6
6
|
//#region src/workbook/encryption.ts
|
|
7
|
-
/** [MS-XLS] 2.4.117's own wEncryptionType
|
|
7
|
+
/** [MS-XLS] 2.4.117's own wEncryptionType. */
|
|
8
|
+
const ENCRYPTION_TYPE_XOR = 0;
|
|
8
9
|
const ENCRYPTION_TYPE_RC4 = 1;
|
|
9
10
|
/** [MS-OFFCRYPTO] 2.3.6.1's own EncryptionVersionInfo: vMajor/vMinor MUST both be 1 for the "RC4 encryption header" this module reads. vMajor 2/3/4 with vMinor 2 names the RC4 CryptoAPI encryption header instead (2.3.5.1), a different, unimplemented header shape. */
|
|
10
11
|
const RC4_HEADER_VERSION_MAJOR = 1;
|
|
@@ -21,29 +22,28 @@ const NEVER_ENCRYPTED_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
|
21
22
|
406,
|
|
22
23
|
312
|
|
23
24
|
]);
|
|
24
|
-
/** Reads FilePass's own fields, rejecting the
|
|
25
|
-
function
|
|
25
|
+
/** Reads FilePass's own fields, dispatching on wEncryptionType, and rejecting RC4 CryptoAPI (the one scheme this module does not implement) with a specific message rather than a generic parse failure. */
|
|
26
|
+
function readFilePassHeader(filePassRecord) {
|
|
26
27
|
const cursor = new require_biff_cursor.BlockCursor([filePassRecord.data]);
|
|
27
28
|
const encryptionType = cursor.u16();
|
|
28
|
-
if (encryptionType
|
|
29
|
+
if (encryptionType === ENCRYPTION_TYPE_XOR) return {
|
|
30
|
+
kind: "xor",
|
|
31
|
+
key: cursor.u16(),
|
|
32
|
+
verificationBytes: cursor.u16()
|
|
33
|
+
};
|
|
34
|
+
if (encryptionType !== ENCRYPTION_TYPE_RC4) throw new require_biff_records.BiffFormatError(`workbook uses FilePass wEncryptionType 0x${encryptionType.toString(16).padStart(4, "0")}, which this reader does not decrypt`);
|
|
29
35
|
const versionMajor = cursor.u16();
|
|
30
36
|
const versionMinor = cursor.u16();
|
|
31
37
|
if (versionMajor !== RC4_HEADER_VERSION_MAJOR || versionMinor !== RC4_HEADER_VERSION_MINOR) throw new require_biff_records.BiffFormatError(`workbook uses RC4 CryptoAPI encryption (EncryptionVersionInfo ${versionMajor}.${versionMinor}), which this reader does not decrypt`);
|
|
32
38
|
return {
|
|
39
|
+
kind: "rc4",
|
|
33
40
|
salt: cursor.take(archive_codec.OFFICE_RC4_VERIFIER_LENGTH),
|
|
34
41
|
encryptedVerifier: cursor.take(archive_codec.OFFICE_RC4_VERIFIER_LENGTH),
|
|
35
42
|
encryptedVerifierHash: cursor.take(archive_codec.OFFICE_RC4_VERIFIER_LENGTH)
|
|
36
43
|
};
|
|
37
44
|
}
|
|
38
|
-
/**
|
|
39
|
-
function
|
|
40
|
-
const decryptedVerifier = (0, archive_codec.decryptOfficeRc4)(baseHash, 0, header.encryptedVerifier);
|
|
41
|
-
const decryptedVerifierHash = (0, archive_codec.decryptOfficeRc4)(baseHash, archive_codec.OFFICE_RC4_VERIFIER_LENGTH, header.encryptedVerifierHash);
|
|
42
|
-
const computedHash = (0, archive_codec.md5)(decryptedVerifier);
|
|
43
|
-
if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new require_biff_records.BiffFormatError("incorrect password for RC4-encrypted workbook");
|
|
44
|
-
}
|
|
45
|
-
/** Decrypts one record's own data in place against the derived base hash, honouring every [MS-XLS] 2.2.10 exclusion: a never-encrypted record's data is returned unchanged, BoundSheet8's own lbPlyPos prefix is preserved while the rest of its data is decrypted, and everything else is decrypted whole. `record.offset` is the record's own header start, so its data begins `HEADER_SIZE` bytes further into the stream -- the position [MS-OFFCRYPTO]'s block-keyed keystream is defined against. */
|
|
46
|
-
function decryptRecord(record, baseHash) {
|
|
45
|
+
/** Decrypts one record's own data against the derived RC4 base hash, honouring every [MS-XLS] 2.2.10 exclusion: a never-encrypted record's data is returned unchanged, BoundSheet8's own lbPlyPos prefix is preserved while the rest of its data is decrypted, and everything else is decrypted whole. `record.offset` is the record's own header start, so its data begins `HEADER_SIZE` bytes further into the stream -- the position [MS-OFFCRYPTO]'s block-keyed keystream is defined against. */
|
|
46
|
+
function decryptRecordRc4(record, baseHash) {
|
|
47
47
|
if (NEVER_ENCRYPTED_RECORD_TYPES.has(record.type)) return record;
|
|
48
48
|
const dataOffset = record.offset + 4;
|
|
49
49
|
if (record.type === 133) {
|
|
@@ -62,17 +62,64 @@ function decryptRecord(record, baseHash) {
|
|
|
62
62
|
data: (0, archive_codec.decryptOfficeRc4)(baseHash, dataOffset, record.data)
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
+
/** [MS-XLS] 2.2.10's own XorArrayIndex rule for a record's decrypted span starting `spanOffset` bytes into the Workbook stream: `(streamOffset + recordDataLength) % 16`, where `recordDataLength` is the record's own FULL declared data length -- not the length of `spanOffset`'s own remaining span, which for BoundSheet8 is 4 bytes shorter than the record's own declared size. Confirmed against Apache POI's own `XORDecryptor.invokeCipher` comment ("XorArrayIndex = (FileOffset + Data.Length) % 16") and LibreOffice's `XclImpBiff5Decrypter::OnUpdate`, and directly against a real Excel-generated XOR-obfuscated fixture -- see archive-codec's own crypto/xor-obfuscation.test.ts. */
|
|
66
|
+
function xorArrayIndexFor(spanOffset, recordDataLength) {
|
|
67
|
+
return (spanOffset + recordDataLength) % archive_codec.XOR_OBFUSCATION_ARRAY_LENGTH;
|
|
68
|
+
}
|
|
69
|
+
/** Decrypts one record's own data against the derived XOR obfuscation array, honouring the same [MS-XLS] 2.2.10 exclusions decryptRecordRc4 does. */
|
|
70
|
+
function decryptRecordXor(record, array) {
|
|
71
|
+
if (NEVER_ENCRYPTED_RECORD_TYPES.has(record.type)) return record;
|
|
72
|
+
const dataOffset = record.offset + 4;
|
|
73
|
+
if (record.type === 133) {
|
|
74
|
+
const lbPlyPos = record.data.subarray(0, BOUNDSHEET8_LBPLYPOS_SIZE);
|
|
75
|
+
const spanOffset = dataOffset + BOUNDSHEET8_LBPLYPOS_SIZE;
|
|
76
|
+
const decryptedRest = (0, archive_codec.decryptXorObfuscationMethod1)(array, record.data.subarray(BOUNDSHEET8_LBPLYPOS_SIZE), xorArrayIndexFor(spanOffset, record.data.length));
|
|
77
|
+
const data = new Uint8Array(record.data.length);
|
|
78
|
+
data.set(lbPlyPos, 0);
|
|
79
|
+
data.set(decryptedRest, BOUNDSHEET8_LBPLYPOS_SIZE);
|
|
80
|
+
return {
|
|
81
|
+
...record,
|
|
82
|
+
data
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
...record,
|
|
87
|
+
data: (0, archive_codec.decryptXorObfuscationMethod1)(array, record.data, xorArrayIndexFor(dataOffset, record.data.length))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** [MS-OFFCRYPTO] 2.3.6.4's own password verification: block 0's key decrypts EncryptedVerifier, and MD5 of the result must equal the same block's decryption of EncryptedVerifierHash. Both verifier fields sit in one continuous keystream starting at position 0, not two independently-reset ones -- though decryptOfficeRc4 regenerates its keystream fresh from block start on every call regardless, so decrypting them as two separate 16-byte calls at offsets 0 and 16 is equivalent to one 32-byte call, not merely close to it. */
|
|
91
|
+
function decryptWorkbookRecordsRc4(records, header, password) {
|
|
92
|
+
const baseHash = (0, archive_codec.deriveOfficeRc4BaseHash)(password, header.salt);
|
|
93
|
+
const decryptedVerifier = (0, archive_codec.decryptOfficeRc4)(baseHash, 0, header.encryptedVerifier);
|
|
94
|
+
const decryptedVerifierHash = (0, archive_codec.decryptOfficeRc4)(baseHash, archive_codec.OFFICE_RC4_VERIFIER_LENGTH, header.encryptedVerifierHash);
|
|
95
|
+
const computedHash = (0, archive_codec.md5)(decryptedVerifier);
|
|
96
|
+
if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new require_biff_records.BiffFormatError("incorrect password for RC4-encrypted workbook");
|
|
97
|
+
return records.map((record) => decryptRecordRc4(record, baseHash));
|
|
98
|
+
}
|
|
99
|
+
/** Verifies the password against both of XORObfuscation's own fields (its `key` and `verificationBytes`, matching Apache POI's own `XORDecryptor.verifyPassword`, which checks both rather than either alone) before decrypting every record. */
|
|
100
|
+
function decryptWorkbookRecordsXor(records, header, password) {
|
|
101
|
+
let computedKey;
|
|
102
|
+
let computedVerifier;
|
|
103
|
+
try {
|
|
104
|
+
computedKey = (0, archive_codec.createXorObfuscationKey)(password);
|
|
105
|
+
computedVerifier = (0, archive_codec.createXorObfuscationPasswordVerifier)(password);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error instanceof RangeError) throw new require_biff_records.BiffFormatError("incorrect password for XOR-obfuscated workbook");
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
if (computedKey !== header.key || computedVerifier !== header.verificationBytes) throw new require_biff_records.BiffFormatError("incorrect password for XOR-obfuscated workbook");
|
|
111
|
+
const array = (0, archive_codec.createXorObfuscationArray)(password, archive_codec.XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1);
|
|
112
|
+
return records.map((record) => decryptRecordXor(record, array));
|
|
113
|
+
}
|
|
65
114
|
/**
|
|
66
|
-
* Decrypts every record of a workbook stream protected by [MS-OFFCRYPTO] 2.3.6.1
|
|
115
|
+
* Decrypts every record of a workbook stream protected by [MS-XLS] 2.4.117's FilePass record, under whichever of the two schemes it names (the [MS-OFFCRYPTO] 2.3.6.1 RC4 encryption header, or 2.3.7's XOR obfuscation), given the `FilePass` record already located within `records` and the password to decrypt it with.
|
|
67
116
|
*
|
|
68
|
-
* Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI
|
|
117
|
+
* Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
|
|
69
118
|
*/
|
|
70
119
|
function decryptWorkbookRecords(records, filePassRecord, password) {
|
|
71
|
-
const header =
|
|
72
|
-
if (password === void 0) throw new require_biff_records.BiffFormatError(
|
|
73
|
-
|
|
74
|
-
verifyOfficeRc4Password(baseHash, header);
|
|
75
|
-
return records.map((record) => decryptRecord(record, baseHash));
|
|
120
|
+
const header = readFilePassHeader(filePassRecord);
|
|
121
|
+
if (password === void 0) throw new require_biff_records.BiffFormatError(`workbook is ${header.kind === "xor" ? "XOR-obfuscated" : "RC4-encrypted"} (FilePass record); call readXlsContent with a password to decrypt it`);
|
|
122
|
+
return header.kind === "xor" ? decryptWorkbookRecordsXor(records, header, password) : decryptWorkbookRecordsRc4(records, header, password);
|
|
76
123
|
}
|
|
77
124
|
//#endregion
|
|
78
125
|
exports.decryptWorkbookRecords = decryptWorkbookRecords;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as BiffRecord } from "../records-G82UEVq8.cjs";
|
|
2
2
|
//#region src/workbook/encryption.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* Decrypts every record of a workbook stream protected by [MS-OFFCRYPTO] 2.3.6.1
|
|
4
|
+
* Decrypts every record of a workbook stream protected by [MS-XLS] 2.4.117's FilePass record, under whichever of the two schemes it names (the [MS-OFFCRYPTO] 2.3.6.1 RC4 encryption header, or 2.3.7's XOR obfuscation), given the `FilePass` record already located within `records` and the password to decrypt it with.
|
|
5
5
|
*
|
|
6
|
-
* Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI
|
|
6
|
+
* Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
|
|
7
7
|
*/
|
|
8
8
|
declare function decryptWorkbookRecords(records: readonly BiffRecord[], filePassRecord: BiffRecord, password: string | undefined): readonly BiffRecord[];
|
|
9
9
|
//#endregion
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as BiffRecord } from "../records-G82UEVq8.js";
|
|
2
2
|
//#region src/workbook/encryption.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* Decrypts every record of a workbook stream protected by [MS-OFFCRYPTO] 2.3.6.1
|
|
4
|
+
* Decrypts every record of a workbook stream protected by [MS-XLS] 2.4.117's FilePass record, under whichever of the two schemes it names (the [MS-OFFCRYPTO] 2.3.6.1 RC4 encryption header, or 2.3.7's XOR obfuscation), given the `FilePass` record already located within `records` and the password to decrypt it with.
|
|
5
5
|
*
|
|
6
|
-
* Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI
|
|
6
|
+
* Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
|
|
7
7
|
*/
|
|
8
8
|
declare function decryptWorkbookRecords(records: readonly BiffRecord[], filePassRecord: BiffRecord, password: string | undefined): readonly BiffRecord[];
|
|
9
9
|
//#endregion
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { RECORD_BOF } from "../biff/record-types.js";
|
|
2
2
|
import { BiffFormatError } from "../biff/records.js";
|
|
3
3
|
import { BlockCursor } from "../biff/cursor.js";
|
|
4
|
-
import { OFFICE_RC4_VERIFIER_LENGTH, decryptOfficeRc4, deriveOfficeRc4BaseHash, md5 } from "archive-codec";
|
|
4
|
+
import { OFFICE_RC4_VERIFIER_LENGTH, XOR_OBFUSCATION_ARRAY_LENGTH, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1, createXorObfuscationArray, createXorObfuscationKey, createXorObfuscationPasswordVerifier, decryptOfficeRc4, decryptXorObfuscationMethod1, deriveOfficeRc4BaseHash, md5 } from "archive-codec";
|
|
5
5
|
//#region src/workbook/encryption.ts
|
|
6
|
-
/** [MS-XLS] 2.4.117's own wEncryptionType
|
|
6
|
+
/** [MS-XLS] 2.4.117's own wEncryptionType. */
|
|
7
|
+
const ENCRYPTION_TYPE_XOR = 0;
|
|
7
8
|
const ENCRYPTION_TYPE_RC4 = 1;
|
|
8
9
|
/** [MS-OFFCRYPTO] 2.3.6.1's own EncryptionVersionInfo: vMajor/vMinor MUST both be 1 for the "RC4 encryption header" this module reads. vMajor 2/3/4 with vMinor 2 names the RC4 CryptoAPI encryption header instead (2.3.5.1), a different, unimplemented header shape. */
|
|
9
10
|
const RC4_HEADER_VERSION_MAJOR = 1;
|
|
@@ -20,29 +21,28 @@ const NEVER_ENCRYPTED_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
|
20
21
|
406,
|
|
21
22
|
312
|
|
22
23
|
]);
|
|
23
|
-
/** Reads FilePass's own fields, rejecting the
|
|
24
|
-
function
|
|
24
|
+
/** Reads FilePass's own fields, dispatching on wEncryptionType, and rejecting RC4 CryptoAPI (the one scheme this module does not implement) with a specific message rather than a generic parse failure. */
|
|
25
|
+
function readFilePassHeader(filePassRecord) {
|
|
25
26
|
const cursor = new BlockCursor([filePassRecord.data]);
|
|
26
27
|
const encryptionType = cursor.u16();
|
|
27
|
-
if (encryptionType
|
|
28
|
+
if (encryptionType === ENCRYPTION_TYPE_XOR) return {
|
|
29
|
+
kind: "xor",
|
|
30
|
+
key: cursor.u16(),
|
|
31
|
+
verificationBytes: cursor.u16()
|
|
32
|
+
};
|
|
33
|
+
if (encryptionType !== ENCRYPTION_TYPE_RC4) throw new BiffFormatError(`workbook uses FilePass wEncryptionType 0x${encryptionType.toString(16).padStart(4, "0")}, which this reader does not decrypt`);
|
|
28
34
|
const versionMajor = cursor.u16();
|
|
29
35
|
const versionMinor = cursor.u16();
|
|
30
36
|
if (versionMajor !== RC4_HEADER_VERSION_MAJOR || versionMinor !== RC4_HEADER_VERSION_MINOR) throw new BiffFormatError(`workbook uses RC4 CryptoAPI encryption (EncryptionVersionInfo ${versionMajor}.${versionMinor}), which this reader does not decrypt`);
|
|
31
37
|
return {
|
|
38
|
+
kind: "rc4",
|
|
32
39
|
salt: cursor.take(OFFICE_RC4_VERIFIER_LENGTH),
|
|
33
40
|
encryptedVerifier: cursor.take(OFFICE_RC4_VERIFIER_LENGTH),
|
|
34
41
|
encryptedVerifierHash: cursor.take(OFFICE_RC4_VERIFIER_LENGTH)
|
|
35
42
|
};
|
|
36
43
|
}
|
|
37
|
-
/**
|
|
38
|
-
function
|
|
39
|
-
const decryptedVerifier = decryptOfficeRc4(baseHash, 0, header.encryptedVerifier);
|
|
40
|
-
const decryptedVerifierHash = decryptOfficeRc4(baseHash, OFFICE_RC4_VERIFIER_LENGTH, header.encryptedVerifierHash);
|
|
41
|
-
const computedHash = md5(decryptedVerifier);
|
|
42
|
-
if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new BiffFormatError("incorrect password for RC4-encrypted workbook");
|
|
43
|
-
}
|
|
44
|
-
/** Decrypts one record's own data in place against the derived base hash, honouring every [MS-XLS] 2.2.10 exclusion: a never-encrypted record's data is returned unchanged, BoundSheet8's own lbPlyPos prefix is preserved while the rest of its data is decrypted, and everything else is decrypted whole. `record.offset` is the record's own header start, so its data begins `HEADER_SIZE` bytes further into the stream -- the position [MS-OFFCRYPTO]'s block-keyed keystream is defined against. */
|
|
45
|
-
function decryptRecord(record, baseHash) {
|
|
44
|
+
/** Decrypts one record's own data against the derived RC4 base hash, honouring every [MS-XLS] 2.2.10 exclusion: a never-encrypted record's data is returned unchanged, BoundSheet8's own lbPlyPos prefix is preserved while the rest of its data is decrypted, and everything else is decrypted whole. `record.offset` is the record's own header start, so its data begins `HEADER_SIZE` bytes further into the stream -- the position [MS-OFFCRYPTO]'s block-keyed keystream is defined against. */
|
|
45
|
+
function decryptRecordRc4(record, baseHash) {
|
|
46
46
|
if (NEVER_ENCRYPTED_RECORD_TYPES.has(record.type)) return record;
|
|
47
47
|
const dataOffset = record.offset + 4;
|
|
48
48
|
if (record.type === 133) {
|
|
@@ -61,17 +61,64 @@ function decryptRecord(record, baseHash) {
|
|
|
61
61
|
data: decryptOfficeRc4(baseHash, dataOffset, record.data)
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
+
/** [MS-XLS] 2.2.10's own XorArrayIndex rule for a record's decrypted span starting `spanOffset` bytes into the Workbook stream: `(streamOffset + recordDataLength) % 16`, where `recordDataLength` is the record's own FULL declared data length -- not the length of `spanOffset`'s own remaining span, which for BoundSheet8 is 4 bytes shorter than the record's own declared size. Confirmed against Apache POI's own `XORDecryptor.invokeCipher` comment ("XorArrayIndex = (FileOffset + Data.Length) % 16") and LibreOffice's `XclImpBiff5Decrypter::OnUpdate`, and directly against a real Excel-generated XOR-obfuscated fixture -- see archive-codec's own crypto/xor-obfuscation.test.ts. */
|
|
65
|
+
function xorArrayIndexFor(spanOffset, recordDataLength) {
|
|
66
|
+
return (spanOffset + recordDataLength) % XOR_OBFUSCATION_ARRAY_LENGTH;
|
|
67
|
+
}
|
|
68
|
+
/** Decrypts one record's own data against the derived XOR obfuscation array, honouring the same [MS-XLS] 2.2.10 exclusions decryptRecordRc4 does. */
|
|
69
|
+
function decryptRecordXor(record, array) {
|
|
70
|
+
if (NEVER_ENCRYPTED_RECORD_TYPES.has(record.type)) return record;
|
|
71
|
+
const dataOffset = record.offset + 4;
|
|
72
|
+
if (record.type === 133) {
|
|
73
|
+
const lbPlyPos = record.data.subarray(0, BOUNDSHEET8_LBPLYPOS_SIZE);
|
|
74
|
+
const spanOffset = dataOffset + BOUNDSHEET8_LBPLYPOS_SIZE;
|
|
75
|
+
const decryptedRest = decryptXorObfuscationMethod1(array, record.data.subarray(BOUNDSHEET8_LBPLYPOS_SIZE), xorArrayIndexFor(spanOffset, record.data.length));
|
|
76
|
+
const data = new Uint8Array(record.data.length);
|
|
77
|
+
data.set(lbPlyPos, 0);
|
|
78
|
+
data.set(decryptedRest, BOUNDSHEET8_LBPLYPOS_SIZE);
|
|
79
|
+
return {
|
|
80
|
+
...record,
|
|
81
|
+
data
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
...record,
|
|
86
|
+
data: decryptXorObfuscationMethod1(array, record.data, xorArrayIndexFor(dataOffset, record.data.length))
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** [MS-OFFCRYPTO] 2.3.6.4's own password verification: block 0's key decrypts EncryptedVerifier, and MD5 of the result must equal the same block's decryption of EncryptedVerifierHash. Both verifier fields sit in one continuous keystream starting at position 0, not two independently-reset ones -- though decryptOfficeRc4 regenerates its keystream fresh from block start on every call regardless, so decrypting them as two separate 16-byte calls at offsets 0 and 16 is equivalent to one 32-byte call, not merely close to it. */
|
|
90
|
+
function decryptWorkbookRecordsRc4(records, header, password) {
|
|
91
|
+
const baseHash = deriveOfficeRc4BaseHash(password, header.salt);
|
|
92
|
+
const decryptedVerifier = decryptOfficeRc4(baseHash, 0, header.encryptedVerifier);
|
|
93
|
+
const decryptedVerifierHash = decryptOfficeRc4(baseHash, OFFICE_RC4_VERIFIER_LENGTH, header.encryptedVerifierHash);
|
|
94
|
+
const computedHash = md5(decryptedVerifier);
|
|
95
|
+
if (!(computedHash.length === decryptedVerifierHash.length && computedHash.every((byte, index) => byte === decryptedVerifierHash[index]))) throw new BiffFormatError("incorrect password for RC4-encrypted workbook");
|
|
96
|
+
return records.map((record) => decryptRecordRc4(record, baseHash));
|
|
97
|
+
}
|
|
98
|
+
/** Verifies the password against both of XORObfuscation's own fields (its `key` and `verificationBytes`, matching Apache POI's own `XORDecryptor.verifyPassword`, which checks both rather than either alone) before decrypting every record. */
|
|
99
|
+
function decryptWorkbookRecordsXor(records, header, password) {
|
|
100
|
+
let computedKey;
|
|
101
|
+
let computedVerifier;
|
|
102
|
+
try {
|
|
103
|
+
computedKey = createXorObfuscationKey(password);
|
|
104
|
+
computedVerifier = createXorObfuscationPasswordVerifier(password);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error instanceof RangeError) throw new BiffFormatError("incorrect password for XOR-obfuscated workbook");
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
if (computedKey !== header.key || computedVerifier !== header.verificationBytes) throw new BiffFormatError("incorrect password for XOR-obfuscated workbook");
|
|
110
|
+
const array = createXorObfuscationArray(password, XOR_OBFUSCATION_ROTATE_DISTANCE_METHOD1);
|
|
111
|
+
return records.map((record) => decryptRecordXor(record, array));
|
|
112
|
+
}
|
|
64
113
|
/**
|
|
65
|
-
* Decrypts every record of a workbook stream protected by [MS-OFFCRYPTO] 2.3.6.1
|
|
114
|
+
* Decrypts every record of a workbook stream protected by [MS-XLS] 2.4.117's FilePass record, under whichever of the two schemes it names (the [MS-OFFCRYPTO] 2.3.6.1 RC4 encryption header, or 2.3.7's XOR obfuscation), given the `FilePass` record already located within `records` and the password to decrypt it with.
|
|
66
115
|
*
|
|
67
|
-
* Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI
|
|
116
|
+
* Throws `BiffFormatError` for a missing password, an incorrect one, or an encryption scheme this module does not implement (RC4 CryptoAPI) -- there is no partial or best-effort result to return in any of those cases.
|
|
68
117
|
*/
|
|
69
118
|
function decryptWorkbookRecords(records, filePassRecord, password) {
|
|
70
|
-
const header =
|
|
71
|
-
if (password === void 0) throw new BiffFormatError(
|
|
72
|
-
|
|
73
|
-
verifyOfficeRc4Password(baseHash, header);
|
|
74
|
-
return records.map((record) => decryptRecord(record, baseHash));
|
|
119
|
+
const header = readFilePassHeader(filePassRecord);
|
|
120
|
+
if (password === void 0) throw new BiffFormatError(`workbook is ${header.kind === "xor" ? "XOR-obfuscated" : "RC4-encrypted"} (FilePass record); call readXlsContent with a password to decrypt it`);
|
|
121
|
+
return header.kind === "xor" ? decryptWorkbookRecordsXor(records, header, password) : decryptWorkbookRecordsRc4(records, header, password);
|
|
75
122
|
}
|
|
76
123
|
//#endregion
|
|
77
124
|
export { decryptWorkbookRecords };
|