@shbernal/ts-xlsx 3.0.0 → 3.2.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 +34 -24
- package/dist/bytes.d.ts +10 -2
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +96 -2
- package/dist/core/address.js +87 -33
- package/dist/core/array-formula-ranges.d.ts +33 -0
- package/dist/core/array-formula-ranges.js +62 -0
- package/dist/core/autofilter.d.ts +4 -3
- package/dist/core/autofilter.js +31 -30
- package/dist/core/axis-handle.d.ts +2 -0
- package/dist/core/axis-handle.js +5 -2
- package/dist/core/cell.d.ts +4 -3
- package/dist/core/cell.js +4 -6
- package/dist/core/clone.d.ts +29 -0
- package/dist/core/clone.js +24 -0
- package/dist/core/color-resolution.js +4 -6
- package/dist/core/column.d.ts +1 -0
- package/dist/core/column.js +6 -2
- package/dist/core/comment-thread.d.ts +7 -3
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +11 -1
- package/dist/core/conditional-formatting-overlay.js +30 -3
- package/dist/core/conditional-formatting.d.ts +107 -11
- package/dist/core/conditional-formatting.js +76 -19
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +7 -1
- package/dist/core/data-validation-overlay.js +19 -4
- package/dist/core/data-validation.d.ts +22 -0
- package/dist/core/data-validation.js +30 -4
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +170 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +19 -3
- package/dist/core/formula-references.d.ts +26 -0
- package/dist/core/formula-references.js +173 -0
- package/dist/core/formula-scan.d.ts +2 -0
- package/dist/core/formula-scan.js +58 -0
- package/dist/core/formula.d.ts +76 -17
- package/dist/core/formula.js +87 -80
- package/dist/core/function-values.d.ts +4 -0
- package/dist/core/function-values.js +58 -0
- package/dist/core/future-functions.d.ts +3 -0
- package/dist/core/{modern-functions.js → future-functions.js} +119 -88
- package/dist/core/grid-edits.d.ts +30 -3
- package/dist/core/grid-edits.js +248 -81
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/hyperlink.d.ts +39 -0
- package/dist/core/hyperlink.js +88 -0
- package/dist/core/image.d.ts +49 -14
- package/dist/core/image.js +16 -5
- package/dist/core/internal.d.ts +10 -2
- package/dist/core/limits.d.ts +25 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge.d.ts +18 -4
- package/dist/core/merge.js +33 -20
- package/dist/core/page-setup.d.ts +26 -11
- package/dist/core/pivot-table.d.ts +49 -10
- package/dist/core/pivot-table.js +112 -60
- package/dist/core/protection.d.ts +66 -4
- package/dist/core/protection.js +6 -11
- package/dist/core/range.js +34 -30
- package/dist/core/row-input.js +2 -2
- package/dist/core/row.d.ts +1 -0
- package/dist/core/row.js +6 -2
- package/dist/core/structured-reference.d.ts +7 -0
- package/dist/core/structured-reference.js +7 -0
- package/dist/core/style.d.ts +85 -70
- package/dist/core/style.js +92 -28
- package/dist/core/table-style.d.ts +1 -1
- package/dist/core/table-style.js +4 -4
- package/dist/core/table.d.ts +64 -16
- package/dist/core/table.js +87 -40
- package/dist/core/theme.d.ts +2 -0
- package/dist/core/theme.js +9 -5
- package/dist/core/value.d.ts +60 -28
- package/dist/core/value.js +48 -43
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +83 -1
- package/dist/core/workbook-styles.js +4 -0
- package/dist/core/workbook-theme.js +5 -3
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +41 -3
- package/dist/core/workbook.js +46 -41
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +49 -0
- package/dist/core/worksheet-merges.js +65 -0
- package/dist/core/worksheet-model.d.ts +10 -2
- package/dist/core/worksheet-model.js +28 -9
- package/dist/core/worksheet-pictures.d.ts +3 -3
- package/dist/core/worksheet-pictures.js +7 -6
- package/dist/core/worksheet.d.ts +165 -27
- package/dist/core/worksheet.js +206 -89
- package/dist/customui/ribbon.js +2 -1
- package/dist/entries/core.d.ts +21 -14
- package/dist/entries/core.js +14 -7
- package/dist/entries/errors.d.ts +1 -0
- package/dist/entries/errors.js +1 -0
- package/dist/entries/xlsx.d.ts +1 -1
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +22 -0
- package/dist/hex.d.ts +8 -0
- package/dist/hex.js +3 -0
- package/dist/io/cell-metadata/metadata.d.ts +42 -0
- package/dist/io/cell-metadata/metadata.js +35 -0
- package/dist/io/cell-metadata/rich-values.d.ts +11 -0
- package/dist/io/cell-metadata/rich-values.js +80 -0
- package/dist/io/csv/delimiter.d.ts +21 -0
- package/dist/io/csv/delimiter.js +18 -0
- package/dist/io/csv/errors.d.ts +14 -0
- package/dist/io/csv/errors.js +5 -0
- package/dist/io/csv/read.d.ts +6 -1
- package/dist/io/csv/read.js +51 -22
- package/dist/io/csv/write.d.ts +19 -5
- package/dist/io/csv/write.js +24 -44
- package/dist/io/opc/inflate.js +14 -0
- package/dist/io/opc/namespaces.d.ts +16 -0
- package/dist/io/opc/namespaces.js +4 -0
- package/dist/io/opc/part-paths.d.ts +9 -0
- package/dist/io/opc/part-paths.js +5 -5
- package/dist/io/opc/read-opc.d.ts +58 -14
- package/dist/io/opc/read-opc.js +49 -21
- package/dist/io/opc/read-options.d.ts +5 -1
- package/dist/io/opc/sniff-format.d.ts +3 -3
- package/dist/io/opc/sniff-format.js +2 -2
- package/dist/io/opc/strict-relationships.d.ts +2 -0
- package/dist/io/opc/strict-relationships.js +12 -0
- package/dist/io/read-policy/column-budget.d.ts +26 -0
- package/dist/io/read-policy/column-budget.js +18 -0
- package/dist/io/read-policy/read-repair.d.ts +74 -0
- package/dist/io/read-policy/read-repair.js +119 -0
- package/dist/io/style/cell-style-resolution.d.ts +25 -0
- package/dist/io/style/cell-style-resolution.js +30 -0
- package/dist/io/style/xf-style.d.ts +23 -1
- package/dist/io/style/xf-style.js +8 -0
- package/dist/io/xlsb/formula.d.ts +2 -2
- package/dist/io/xlsb/formula.js +52 -35
- package/dist/io/xlsb/primitives.js +8 -14
- package/dist/io/xlsb/ptg-functions.d.ts +19 -10
- package/dist/io/xlsb/ptg-functions.js +495 -596
- package/dist/io/xlsb/read-metadata.d.ts +7 -0
- package/dist/io/xlsb/read-metadata.js +69 -0
- package/dist/io/xlsb/read-styles.js +45 -81
- package/dist/io/xlsb/read-worksheet.d.ts +26 -1
- package/dist/io/xlsb/read-worksheet.js +148 -106
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +70 -31
- package/dist/io/xlsb/record-stream.d.ts +30 -0
- package/dist/io/xlsb/record-stream.js +20 -0
- package/dist/io/xlsb/record-types.d.ts +14 -5
- package/dist/io/xlsb/record-types.js +14 -5
- package/dist/io/xlsb/sheet-protection.d.ts +20 -0
- package/dist/io/xlsb/sheet-protection.js +60 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +24 -9
- package/dist/io/xlsx/cell-accumulator.js +74 -21
- package/dist/io/xlsx/cell-metadata.d.ts +39 -0
- package/dist/io/xlsx/cell-metadata.js +162 -0
- package/dist/io/xlsx/cell-value.d.ts +19 -5
- package/dist/io/xlsx/cell-value.js +45 -30
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-span.d.ts +11 -0
- package/dist/io/xlsx/column-span.js +9 -0
- package/dist/io/xlsx/comments.d.ts +9 -30
- package/dist/io/xlsx/comments.js +25 -83
- package/dist/io/xlsx/conditional-formatting.d.ts +43 -23
- package/dist/io/xlsx/conditional-formatting.js +437 -138
- package/dist/io/xlsx/data-validation.d.ts +12 -7
- package/dist/io/xlsx/data-validation.js +67 -53
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +52 -52
- package/dist/io/xlsx/font-xml.d.ts +7 -0
- package/dist/io/xlsx/font-xml.js +104 -0
- package/dist/io/xlsx/hyperlinks.d.ts +19 -21
- package/dist/io/xlsx/hyperlinks.js +12 -32
- package/dist/io/xlsx/images.d.ts +49 -16
- package/dist/io/xlsx/images.js +149 -50
- package/dist/io/xlsx/namespaces.d.ts +24 -0
- package/dist/io/xlsx/namespaces.js +11 -0
- package/dist/io/xlsx/package-plan.d.ts +60 -15
- package/dist/io/xlsx/package-plan.js +83 -18
- package/dist/io/xlsx/part-names.d.ts +6 -8
- package/dist/io/xlsx/part-names.js +3 -7
- package/dist/io/xlsx/pivot.d.ts +5 -5
- package/dist/io/xlsx/pivot.js +26 -24
- package/dist/io/xlsx/preserved-splices.d.ts +6 -0
- package/dist/io/xlsx/preserved-splices.js +74 -0
- package/dist/io/xlsx/read-comments.d.ts +29 -0
- package/dist/io/xlsx/read-comments.js +75 -0
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +259 -0
- package/dist/io/xlsx/read-pivot.js +11 -4
- package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +8 -5
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +24 -4
- package/dist/io/xlsx/read-rows.d.ts +35 -11
- package/dist/io/xlsx/read-rows.js +73 -49
- package/dist/io/xlsx/read-shared-strings.js +3 -2
- package/dist/io/xlsx/read-styles.d.ts +0 -30
- package/dist/io/xlsx/read-styles.js +45 -105
- package/dist/io/xlsx/read-workbook-xml.d.ts +42 -0
- package/dist/io/xlsx/read-workbook-xml.js +175 -0
- package/dist/io/xlsx/read-worksheet.d.ts +9 -3
- package/dist/io/xlsx/read-worksheet.js +70 -177
- package/dist/io/xlsx/read.d.ts +3 -14
- package/dist/io/xlsx/read.js +74 -411
- package/dist/io/xlsx/relationships.d.ts +3 -0
- package/dist/io/xlsx/relationships.js +4 -0
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +20 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +139 -0
- package/dist/io/xlsx/row-xml.js +222 -0
- package/dist/io/xlsx/shared-formulas.js +2 -2
- package/dist/io/xlsx/sheet-properties.d.ts +10 -1
- package/dist/io/xlsx/sheet-properties.js +182 -20
- package/dist/io/xlsx/strict-parts.d.ts +7 -0
- package/dist/io/xlsx/strict-parts.js +171 -0
- package/dist/io/xlsx/style-elements.d.ts +41 -0
- package/dist/io/xlsx/style-elements.js +136 -0
- package/dist/io/xlsx/styles.d.ts +17 -3
- package/dist/io/xlsx/styles.js +13 -209
- package/dist/io/xlsx/tables.d.ts +10 -2
- package/dist/io/xlsx/tables.js +55 -52
- package/dist/io/xlsx/theme-xml.d.ts +32 -4
- package/dist/io/xlsx/theme-xml.js +106 -71
- package/dist/io/xlsx/threaded-comments.d.ts +14 -11
- package/dist/io/xlsx/threaded-comments.js +16 -8
- package/dist/io/xlsx/workbook-xml.d.ts +45 -6
- package/dist/io/xlsx/workbook-xml.js +62 -54
- package/dist/io/xlsx/worksheet-xml.d.ts +26 -70
- package/dist/io/xlsx/worksheet-xml.js +69 -282
- package/dist/io/xlsx/write-stream.d.ts +90 -19
- package/dist/io/xlsx/write-stream.js +80 -25
- package/dist/io/xlsx/write.d.ts +12 -6
- package/dist/io/xlsx/write.js +226 -123
- package/dist/rel-type.d.ts +25 -0
- package/dist/rel-type.js +9 -0
- package/dist/token-set.d.ts +13 -0
- package/dist/token-set.js +4 -1
- package/dist/vba/bytes.d.ts +16 -5
- package/dist/vba/bytes.js +27 -6
- package/dist/vba/cfb-format.d.ts +55 -0
- package/dist/vba/cfb-format.js +35 -0
- package/dist/vba/cfb-writer.d.ts +8 -0
- package/dist/vba/cfb-writer.js +40 -40
- package/dist/vba/cfb.d.ts +21 -4
- package/dist/vba/cfb.js +124 -90
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/dir-records.d.ts +6 -0
- package/dist/vba/dir-records.js +10 -2
- package/dist/vba/errors.d.ts +11 -4
- package/dist/vba/index.d.ts +0 -2
- package/dist/vba/index.js +0 -2
- package/dist/vba/ms-ovba.d.ts +6 -2
- package/dist/vba/ms-ovba.js +66 -22
- package/dist/vba/project-editor.js +129 -119
- package/dist/vba/project.d.ts +15 -1
- package/dist/vba/project.js +38 -13
- package/dist/vba/vba-encoding.d.ts +4 -10
- package/dist/vba/vba-encoding.js +3 -17
- package/dist/xml/xml-attrs.d.ts +37 -0
- package/dist/xml/xml-attrs.js +30 -0
- package/dist/xml/xml-chars.d.ts +40 -0
- package/dist/xml/xml-chars.js +12 -0
- package/dist/xml/xml-namespaces.d.ts +31 -0
- package/dist/xml/xml-namespaces.js +50 -0
- package/dist/xml/xml-read.d.ts +101 -11
- package/dist/xml/xml-read.js +150 -18
- package/dist/xml/xml-scan.d.ts +25 -34
- package/dist/xml/xml-scan.js +69 -46
- package/dist/xml/xml.d.ts +59 -26
- package/dist/xml/xml.js +45 -23
- package/package.json +15 -9
- package/skills/ts-xlsx-upstream/SKILL.md +21 -13
- package/dist/core/modern-functions.d.ts +0 -1
package/dist/vba/cfb-format.d.ts
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
/** The header's first eight bytes, as the two little-endian `u32`s both directions handle them as. */
|
|
2
|
+
export declare const CFB_SIGNATURE: {
|
|
3
|
+
readonly low: 3759263696;
|
|
4
|
+
readonly high: 3776623009;
|
|
5
|
+
};
|
|
6
|
+
/** Byte offsets of the header fields either direction reads or writes ([MS-CFB] 2.2). */
|
|
7
|
+
export declare const HEADER_FIELD: {
|
|
8
|
+
readonly signatureLow: 0;
|
|
9
|
+
readonly signatureHigh: 4;
|
|
10
|
+
readonly minorVersion: 24;
|
|
11
|
+
readonly majorVersion: 26;
|
|
12
|
+
readonly byteOrder: 28;
|
|
13
|
+
readonly sectorShift: 30;
|
|
14
|
+
readonly miniSectorShift: 32;
|
|
15
|
+
readonly fatSectors: 44;
|
|
16
|
+
readonly firstDirectorySector: 48;
|
|
17
|
+
readonly miniStreamCutoff: 56;
|
|
18
|
+
readonly firstMiniFatSector: 60;
|
|
19
|
+
readonly miniFatSectors: 64;
|
|
20
|
+
readonly firstDifatSector: 68;
|
|
21
|
+
readonly difatSectors: 72;
|
|
22
|
+
readonly difat: 76;
|
|
23
|
+
};
|
|
24
|
+
/** Byte offsets within a directory entry, from the entry's start ([MS-CFB] 2.6.1). The name's UTF-16
|
|
25
|
+
* code units begin at offset 0. */
|
|
26
|
+
export declare const DIR_ENTRY_FIELD: {
|
|
27
|
+
readonly nameLength: 64;
|
|
28
|
+
readonly objectType: 66;
|
|
29
|
+
readonly color: 67;
|
|
30
|
+
readonly leftSibling: 68;
|
|
31
|
+
readonly rightSibling: 72;
|
|
32
|
+
readonly child: 76;
|
|
33
|
+
readonly startSector: 116;
|
|
34
|
+
readonly sizeLow: 120;
|
|
35
|
+
readonly sizeHigh: 124;
|
|
36
|
+
};
|
|
37
|
+
/** FAT-sector pointers the header holds before a DIFAT sector is needed ([MS-CFB] 2.2). */
|
|
38
|
+
export declare const HEADER_DIFAT_SLOTS = 109;
|
|
39
|
+
/** 64-byte mini sectors: [MS-CFB] 2.2 fixes the shift at 6 for both versions. */
|
|
40
|
+
export declare const MINI_SECTOR_SHIFT = 6;
|
|
1
41
|
/** Sector chain markers ([MS-CFB] 2.2). */
|
|
2
42
|
export declare const FREESECT = 4294967295;
|
|
3
43
|
export declare const ENDOFCHAIN = 4294967294;
|
|
@@ -17,8 +57,23 @@ export declare const TYPE_EMPTY = 0;
|
|
|
17
57
|
export declare const TYPE_STORAGE = 1;
|
|
18
58
|
export declare const TYPE_STREAM = 2;
|
|
19
59
|
export declare const TYPE_ROOT = 5;
|
|
60
|
+
/**
|
|
61
|
+
* The stream size at or above which a stream lives in the regular FAT rather than the mini stream
|
|
62
|
+
* ([MS-CFB] 2.2, header offset 56). The spec fixes it at 4096, so the reader checks the header's value
|
|
63
|
+
* against this rather than believing it: a crafted 0 or 0xFFFFFFFF routes every stream through the
|
|
64
|
+
* wrong allocator, and since both destinations are bounds-checked the result is a module's source read
|
|
65
|
+
* back as something nobody wrote rather than a clean rejection.
|
|
66
|
+
*/
|
|
67
|
+
export declare const MINI_STREAM_CUTOFF = 4096;
|
|
20
68
|
/** A directory entry is a fixed 128 bytes ([MS-CFB] 2.6.1), whatever the sector size. */
|
|
21
69
|
export declare const DIR_ENTRY_SIZE = 128;
|
|
22
70
|
/** 32 UTF-16 code units including the NUL terminator. This is also why a VBA module name is capped
|
|
23
71
|
* at 31: the module is a stream, and the stream's name is the module's. */
|
|
24
72
|
export declare const MAX_NAME_CHARS = 31;
|
|
73
|
+
/**
|
|
74
|
+
* Whether two directory-entry names are the same entry. [MS-CFB] 2.6.4 orders siblings by length and
|
|
75
|
+
* then by their uppercase forms, and forbids two siblings that compare equal, so a host looking a name
|
|
76
|
+
* up treats `dir` and `DIR` as one entry. A lookup here does the same, or a file whose producer cased a
|
|
77
|
+
* name differently would read as missing a stream it has.
|
|
78
|
+
*/
|
|
79
|
+
export declare function sameEntryName(a: string, b: string): boolean;
|
package/dist/vba/cfb-format.js
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
export const CFB_SIGNATURE = { low: 0xe011cfd0, high: 0xe11ab1a1 };
|
|
2
|
+
export const HEADER_FIELD = {
|
|
3
|
+
signatureLow: 0,
|
|
4
|
+
signatureHigh: 4,
|
|
5
|
+
minorVersion: 24,
|
|
6
|
+
majorVersion: 26,
|
|
7
|
+
byteOrder: 28,
|
|
8
|
+
sectorShift: 30,
|
|
9
|
+
miniSectorShift: 32,
|
|
10
|
+
fatSectors: 44,
|
|
11
|
+
firstDirectorySector: 48,
|
|
12
|
+
miniStreamCutoff: 56,
|
|
13
|
+
firstMiniFatSector: 60,
|
|
14
|
+
miniFatSectors: 64,
|
|
15
|
+
firstDifatSector: 68,
|
|
16
|
+
difatSectors: 72,
|
|
17
|
+
difat: 76,
|
|
18
|
+
};
|
|
19
|
+
export const DIR_ENTRY_FIELD = {
|
|
20
|
+
nameLength: 64,
|
|
21
|
+
objectType: 66,
|
|
22
|
+
color: 67,
|
|
23
|
+
leftSibling: 68,
|
|
24
|
+
rightSibling: 72,
|
|
25
|
+
child: 76,
|
|
26
|
+
startSector: 116,
|
|
27
|
+
sizeLow: 120,
|
|
28
|
+
sizeHigh: 124,
|
|
29
|
+
};
|
|
30
|
+
export const HEADER_DIFAT_SLOTS = 109;
|
|
31
|
+
export const MINI_SECTOR_SHIFT = 6;
|
|
1
32
|
export const FREESECT = 0xffffffff;
|
|
2
33
|
export const ENDOFCHAIN = 0xfffffffe;
|
|
3
34
|
export const FATSECT = 0xfffffffd;
|
|
@@ -8,5 +39,9 @@ export const TYPE_EMPTY = 0;
|
|
|
8
39
|
export const TYPE_STORAGE = 1;
|
|
9
40
|
export const TYPE_STREAM = 2;
|
|
10
41
|
export const TYPE_ROOT = 5;
|
|
42
|
+
export const MINI_STREAM_CUTOFF = 4096;
|
|
11
43
|
export const DIR_ENTRY_SIZE = 128;
|
|
12
44
|
export const MAX_NAME_CHARS = 31;
|
|
45
|
+
export function sameEntryName(a, b) {
|
|
46
|
+
return a.length === b.length && a.toUpperCase() === b.toUpperCase();
|
|
47
|
+
}
|
package/dist/vba/cfb-writer.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ export interface CfbStorage {
|
|
|
8
8
|
readonly children: readonly CfbNode[];
|
|
9
9
|
}
|
|
10
10
|
export type CfbNode = CfbStream | CfbStorage;
|
|
11
|
+
/**
|
|
12
|
+
* Which arm of {@link CfbNode} a node is.
|
|
13
|
+
*
|
|
14
|
+
* Exported beside the union rather than kept private, because the discriminant is `'data' in node`
|
|
15
|
+
* and that is the sort of test callers re-spell inline: `project-editor.ts` had it three times, once
|
|
16
|
+
* negated. The name says what the test means, and a node it answers `false` for is a storage.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isStream(node: CfbNode): node is CfbStream;
|
|
11
19
|
/**
|
|
12
20
|
* Encode a hierarchy of storages and streams into a v3 compound file. The Root Entry is synthesized
|
|
13
21
|
* automatically; `root` is its top-level children. Every stream becomes a directory entry reachable both
|
package/dist/vba/cfb-writer.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { quoted } from '../errors.js';
|
|
2
|
+
import { CFB_SIGNATURE, DIFSECT, DIR_ENTRY_FIELD, DIR_ENTRY_SIZE, ENDOFCHAIN, FATSECT, FREESECT, HEADER_DIFAT_SLOTS, HEADER_FIELD, MAX_NAME_CHARS, MINI_SECTOR_SHIFT, MINI_STREAM_CUTOFF, NOSTREAM, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
|
|
2
3
|
import { VbaAuthorError } from './errors.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
export function isStream(node) {
|
|
5
|
+
return 'data' in node;
|
|
6
|
+
}
|
|
7
|
+
const SECTOR_SHIFT = 9;
|
|
8
|
+
const SECTOR = 1 << SECTOR_SHIFT;
|
|
9
|
+
const MINI_SECTOR = 1 << MINI_SECTOR_SHIFT;
|
|
6
10
|
const ENTRIES_PER_DIR_SECTOR = SECTOR / DIR_ENTRY_SIZE;
|
|
7
11
|
const FAT_ENTRIES_PER_SECTOR = SECTOR / 4;
|
|
8
|
-
const DIFAT_HEADER_SLOTS = 109;
|
|
9
12
|
const COLOR_BLACK = 1;
|
|
10
|
-
function isStream(node) {
|
|
11
|
-
return 'data' in node;
|
|
12
|
-
}
|
|
13
13
|
export function writeCompoundFile(root) {
|
|
14
14
|
const entries = [];
|
|
15
15
|
const addEntry = (name, type, startSector) => {
|
|
@@ -38,7 +38,7 @@ export function writeCompoundFile(root) {
|
|
|
38
38
|
siblings.push(entry);
|
|
39
39
|
if (node.data.length === 0) {
|
|
40
40
|
}
|
|
41
|
-
else if (node.data.length >=
|
|
41
|
+
else if (node.data.length >= MINI_STREAM_CUTOFF) {
|
|
42
42
|
bigStreams.push({ entry, data: node.data, sectors: Math.ceil(node.data.length / SECTOR) });
|
|
43
43
|
}
|
|
44
44
|
else {
|
|
@@ -79,16 +79,16 @@ export function writeCompoundFile(root) {
|
|
|
79
79
|
for (;;) {
|
|
80
80
|
const total = baseSectors + fatSectors + difatSectors;
|
|
81
81
|
const needFat = Math.ceil(total / FAT_ENTRIES_PER_SECTOR);
|
|
82
|
-
const needDifat = needFat >
|
|
83
|
-
? Math.ceil((needFat -
|
|
82
|
+
const needDifat = needFat > HEADER_DIFAT_SLOTS
|
|
83
|
+
? Math.ceil((needFat - HEADER_DIFAT_SLOTS) / (FAT_ENTRIES_PER_SECTOR - 1))
|
|
84
84
|
: 0;
|
|
85
85
|
if (needFat === fatSectors && needDifat === difatSectors)
|
|
86
86
|
break;
|
|
87
87
|
fatSectors = needFat;
|
|
88
88
|
difatSectors = needDifat;
|
|
89
89
|
}
|
|
90
|
-
if (fatSectors >
|
|
91
|
-
throw new VbaAuthorError(`project needs ${fatSectors} FAT sectors, exceeding the ${
|
|
90
|
+
if (fatSectors > HEADER_DIFAT_SLOTS) {
|
|
91
|
+
throw new VbaAuthorError(`project needs ${fatSectors} FAT sectors, exceeding the ${HEADER_DIFAT_SLOTS}-sector single-header bound`);
|
|
92
92
|
}
|
|
93
93
|
let cursor = 0;
|
|
94
94
|
const dirStart = cursor;
|
|
@@ -155,7 +155,7 @@ export function writeCompoundFile(root) {
|
|
|
155
155
|
for (let d = 0; d < difatSectors; d++) {
|
|
156
156
|
const base = at(difatStart + d);
|
|
157
157
|
for (let i = 0; i < FAT_ENTRIES_PER_SECTOR - 1; i++) {
|
|
158
|
-
const fatIdx =
|
|
158
|
+
const fatIdx = HEADER_DIFAT_SLOTS + d * (FAT_ENTRIES_PER_SECTOR - 1) + i;
|
|
159
159
|
dv.setUint32(base + i * 4, fatIdx < fatSectors ? fatStart + fatIdx : FREESECT, true);
|
|
160
160
|
}
|
|
161
161
|
dv.setUint32(base + (FAT_ENTRIES_PER_SECTOR - 1) * 4, d < difatSectors - 1 ? difatStart + d + 1 : ENDOFCHAIN, true);
|
|
@@ -168,10 +168,10 @@ function validateSiblingNames(storageName, siblings) {
|
|
|
168
168
|
if (node.name.length === 0)
|
|
169
169
|
throw new VbaAuthorError('entry name must not be empty');
|
|
170
170
|
if (node.name.length > MAX_NAME_CHARS) {
|
|
171
|
-
throw new VbaAuthorError(`entry name
|
|
171
|
+
throw new VbaAuthorError(`entry name ${quoted(node.name)} exceeds the ${MAX_NAME_CHARS}-character CFB limit`);
|
|
172
172
|
}
|
|
173
173
|
if (seen.has(node.name)) {
|
|
174
|
-
throw new VbaAuthorError(`duplicate entry name
|
|
174
|
+
throw new VbaAuthorError(`duplicate entry name ${quoted(node.name)} under storage ${quoted(storageName)}`);
|
|
175
175
|
}
|
|
176
176
|
seen.add(node.name);
|
|
177
177
|
if (!isStream(node))
|
|
@@ -198,33 +198,33 @@ function compareNames(a, b) {
|
|
|
198
198
|
return ua < ub ? -1 : ua > ub ? 1 : 0;
|
|
199
199
|
}
|
|
200
200
|
function writeHeader(dv, p) {
|
|
201
|
-
dv.setUint32(
|
|
202
|
-
dv.setUint32(
|
|
203
|
-
dv.setUint16(
|
|
204
|
-
dv.setUint16(
|
|
205
|
-
dv.setUint16(
|
|
206
|
-
dv.setUint16(
|
|
207
|
-
dv.setUint16(
|
|
208
|
-
dv.setUint32(
|
|
209
|
-
dv.setUint32(
|
|
210
|
-
dv.setUint32(
|
|
211
|
-
dv.setUint32(
|
|
212
|
-
dv.setUint32(
|
|
213
|
-
dv.setUint32(
|
|
214
|
-
dv.setUint32(
|
|
215
|
-
for (let i = 0; i <
|
|
216
|
-
dv.setUint32(
|
|
201
|
+
dv.setUint32(HEADER_FIELD.signatureLow, CFB_SIGNATURE.low, true);
|
|
202
|
+
dv.setUint32(HEADER_FIELD.signatureHigh, CFB_SIGNATURE.high, true);
|
|
203
|
+
dv.setUint16(HEADER_FIELD.minorVersion, 0x003e, true);
|
|
204
|
+
dv.setUint16(HEADER_FIELD.majorVersion, 0x0003, true);
|
|
205
|
+
dv.setUint16(HEADER_FIELD.byteOrder, 0xfffe, true);
|
|
206
|
+
dv.setUint16(HEADER_FIELD.sectorShift, SECTOR_SHIFT, true);
|
|
207
|
+
dv.setUint16(HEADER_FIELD.miniSectorShift, MINI_SECTOR_SHIFT, true);
|
|
208
|
+
dv.setUint32(HEADER_FIELD.fatSectors, p.fatSectors, true);
|
|
209
|
+
dv.setUint32(HEADER_FIELD.firstDirectorySector, p.dirStart, true);
|
|
210
|
+
dv.setUint32(HEADER_FIELD.miniStreamCutoff, MINI_STREAM_CUTOFF, true);
|
|
211
|
+
dv.setUint32(HEADER_FIELD.firstMiniFatSector, p.miniFatStart, true);
|
|
212
|
+
dv.setUint32(HEADER_FIELD.miniFatSectors, p.miniFatSectors, true);
|
|
213
|
+
dv.setUint32(HEADER_FIELD.firstDifatSector, p.difatSectors > 0 ? p.difatStart : ENDOFCHAIN, true);
|
|
214
|
+
dv.setUint32(HEADER_FIELD.difatSectors, p.difatSectors, true);
|
|
215
|
+
for (let i = 0; i < HEADER_DIFAT_SLOTS; i++) {
|
|
216
|
+
dv.setUint32(HEADER_FIELD.difat + i * 4, i < p.fatSectors ? p.fatStart + i : FREESECT, true);
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
function writeDirEntry(dv, off, e) {
|
|
220
220
|
for (let i = 0; i < e.name.length; i++)
|
|
221
221
|
dv.setUint16(off + i * 2, e.name.charCodeAt(i), true);
|
|
222
|
-
dv.setUint16(off +
|
|
223
|
-
dv.setUint8(off +
|
|
224
|
-
dv.setUint8(off +
|
|
225
|
-
dv.setUint32(off +
|
|
226
|
-
dv.setUint32(off +
|
|
227
|
-
dv.setUint32(off +
|
|
228
|
-
dv.setUint32(off +
|
|
229
|
-
dv.setUint32(off +
|
|
222
|
+
dv.setUint16(off + DIR_ENTRY_FIELD.nameLength, (e.name.length + 1) * 2, true);
|
|
223
|
+
dv.setUint8(off + DIR_ENTRY_FIELD.objectType, e.type);
|
|
224
|
+
dv.setUint8(off + DIR_ENTRY_FIELD.color, COLOR_BLACK);
|
|
225
|
+
dv.setUint32(off + DIR_ENTRY_FIELD.leftSibling, e.left, true);
|
|
226
|
+
dv.setUint32(off + DIR_ENTRY_FIELD.rightSibling, e.right, true);
|
|
227
|
+
dv.setUint32(off + DIR_ENTRY_FIELD.child, e.child, true);
|
|
228
|
+
dv.setUint32(off + DIR_ENTRY_FIELD.startSector, e.startSector, true);
|
|
229
|
+
dv.setUint32(off + DIR_ENTRY_FIELD.sizeLow, e.size, true);
|
|
230
230
|
}
|
package/dist/vba/cfb.d.ts
CHANGED
|
@@ -2,15 +2,32 @@ import type { CfbNode } from './cfb-writer.ts';
|
|
|
2
2
|
export declare class CompoundFile {
|
|
3
3
|
#private;
|
|
4
4
|
constructor(buf: Uint8Array);
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Read a stream's raw bytes by its storage path from the root (`['VBA', 'dir']`), or `undefined` when
|
|
7
|
+
* no stream sits there. Each segment is matched among one storage's children, as [MS-CFB] compares
|
|
8
|
+
* names, by walking the sibling tree that storage links.
|
|
9
|
+
*
|
|
10
|
+
* A path because a name is not an address. [MS-OVBA] puts `PROJECT` at the root, `dir` and every
|
|
11
|
+
* module stream in `VBA`, and each UserForm's `f` and `o` in a storage of its own, so a bare name
|
|
12
|
+
* repeats. Looked up by name, the first entry in directory order won: a module named `f` read a
|
|
13
|
+
* UserForm's form data, and an edit wrote a module named `PROJECT` over the root `PROJECT`.
|
|
14
|
+
*
|
|
15
|
+
* @returns bytes that may be a **view onto the caller's buffer** rather than a copy. A stream small
|
|
16
|
+
* enough to fit in one sector is assembled by `concat` from a single chunk, and `concat` hands a
|
|
17
|
+
* lone chunk straight back; anything larger is copied into a fresh buffer. So whether the result
|
|
18
|
+
* aliases the source depends on the stream's size, which is a rule no caller should have to know
|
|
19
|
+
* and none here relies on: nothing mutates what this returns. Stated because it is the sort of
|
|
20
|
+
* thing that is true until someone writes the first mutation, and then true only for large
|
|
21
|
+
* streams. Copy before mutating.
|
|
22
|
+
*/
|
|
23
|
+
readStream(path: readonly string[]): Uint8Array | undefined;
|
|
9
24
|
/**
|
|
10
25
|
* Reconstruct the container's top-level children as the writer's node shape, recursing into every
|
|
11
26
|
* storage, so a caller can swap one stream and re-emit the whole hierarchy with {@link writeCompoundFile}.
|
|
12
27
|
* Walks the red-black sibling tree each storage navigates (not the linear directory scan), so any part
|
|
13
28
|
* a host reaches is carried through. Cycle- and bounds-guarded like every other chain walk here.
|
|
29
|
+
*
|
|
30
|
+
* @returns nodes whose stream bytes carry {@link readStream}'s aliasing caveat.
|
|
14
31
|
*/
|
|
15
32
|
tree(): CfbNode[];
|
|
16
33
|
}
|
package/dist/vba/cfb.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { quoted } from '../errors.js';
|
|
1
2
|
import { concat, decodeUtf16le, readU16, readU32 } from './bytes.js';
|
|
2
|
-
import { DIR_ENTRY_SIZE, MAX_REGULAR_SECTOR, NOSTREAM, TYPE_EMPTY, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
|
|
3
|
+
import { CFB_SIGNATURE, DIR_ENTRY_FIELD, DIR_ENTRY_SIZE, HEADER_DIFAT_SLOTS, HEADER_FIELD, MAX_REGULAR_SECTOR, MINI_SECTOR_SHIFT, MINI_STREAM_CUTOFF, NOSTREAM, sameEntryName, TYPE_EMPTY, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
|
|
3
4
|
import { VbaParseError } from './errors.js';
|
|
4
|
-
const
|
|
5
|
-
const CFB_SIGNATURE_HI = 0xe11ab1a1;
|
|
5
|
+
const MAX_TREE_DEPTH = 256;
|
|
6
6
|
export class CompoundFile {
|
|
7
7
|
#buf;
|
|
8
8
|
#sectorSize;
|
|
@@ -17,25 +17,29 @@ export class CompoundFile {
|
|
|
17
17
|
this.#buf = buf;
|
|
18
18
|
if (buf.length < 512)
|
|
19
19
|
throw new VbaParseError('compound file shorter than its 512-byte header');
|
|
20
|
-
if (readU32(this.#buf,
|
|
20
|
+
if (readU32(this.#buf, HEADER_FIELD.signatureLow) !== CFB_SIGNATURE.low ||
|
|
21
|
+
readU32(this.#buf, HEADER_FIELD.signatureHigh) !== CFB_SIGNATURE.high) {
|
|
21
22
|
throw new VbaParseError('not a compound file (bad OLE2 signature)');
|
|
22
23
|
}
|
|
23
|
-
const sectorShift = readU16(this.#buf,
|
|
24
|
-
const miniSectorShift = readU16(this.#buf,
|
|
24
|
+
const sectorShift = readU16(this.#buf, HEADER_FIELD.sectorShift);
|
|
25
|
+
const miniSectorShift = readU16(this.#buf, HEADER_FIELD.miniSectorShift);
|
|
25
26
|
if (sectorShift !== 9 && sectorShift !== 12) {
|
|
26
27
|
throw new VbaParseError(`unsupported sector shift ${sectorShift}`);
|
|
27
28
|
}
|
|
28
|
-
if (miniSectorShift !==
|
|
29
|
+
if (miniSectorShift !== MINI_SECTOR_SHIFT)
|
|
29
30
|
throw new VbaParseError(`unsupported mini-sector shift ${miniSectorShift}`);
|
|
30
31
|
this.#sectorSize = 1 << sectorShift;
|
|
31
32
|
this.#miniSectorSize = 1 << miniSectorShift;
|
|
32
33
|
this.#maxSector = Math.floor(buf.length / this.#sectorSize);
|
|
33
|
-
const numFatSectors = readU32(this.#buf,
|
|
34
|
-
const firstDirSector = readU32(this.#buf,
|
|
35
|
-
this.#miniCutoff = readU32(this.#buf,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
const numFatSectors = readU32(this.#buf, HEADER_FIELD.fatSectors);
|
|
35
|
+
const firstDirSector = readU32(this.#buf, HEADER_FIELD.firstDirectorySector);
|
|
36
|
+
this.#miniCutoff = readU32(this.#buf, HEADER_FIELD.miniStreamCutoff);
|
|
37
|
+
if (this.#miniCutoff !== MINI_STREAM_CUTOFF) {
|
|
38
|
+
throw new VbaParseError(`unsupported mini-stream cutoff ${this.#miniCutoff}`);
|
|
39
|
+
}
|
|
40
|
+
const firstMiniFatSector = readU32(this.#buf, HEADER_FIELD.firstMiniFatSector);
|
|
41
|
+
const firstDifatSector = readU32(this.#buf, HEADER_FIELD.firstDifatSector);
|
|
42
|
+
const numDifatSectors = readU32(this.#buf, HEADER_FIELD.difatSectors);
|
|
39
43
|
const fatSectorIds = this.#readDifat(numFatSectors, firstDifatSector, numDifatSectors);
|
|
40
44
|
this.#fat = this.#readFat(fatSectorIds);
|
|
41
45
|
this.#miniFat = this.#readChainValues(firstMiniFatSector);
|
|
@@ -45,29 +49,51 @@ export class CompoundFile {
|
|
|
45
49
|
throw new VbaParseError('compound file has no root storage entry');
|
|
46
50
|
this.#miniStream = this.#readViaFat(root.startSector, root.size);
|
|
47
51
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
readStream(path) {
|
|
53
|
+
const rootIdx = this.#rootIndex();
|
|
54
|
+
const seen = new Set([rootIdx]);
|
|
55
|
+
let firstChild = this.#dir[rootIdx].child;
|
|
56
|
+
for (const [depth, segment] of path.entries()) {
|
|
57
|
+
const stream = depth === path.length - 1;
|
|
58
|
+
const entry = this.#childIndices(firstChild, seen, depth)
|
|
59
|
+
.map((idx) => this.#dir[idx])
|
|
60
|
+
.find((e) => (stream ? e.type === TYPE_STREAM : e.type === TYPE_STORAGE) &&
|
|
61
|
+
sameEntryName(e.name, segment));
|
|
62
|
+
if (entry === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (stream)
|
|
65
|
+
return this.#readEntryData(entry);
|
|
66
|
+
firstChild = entry.child;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
58
69
|
}
|
|
59
70
|
tree() {
|
|
71
|
+
const rootIdx = this.#rootIndex();
|
|
72
|
+
return this.#buildSiblings(this.#dir[rootIdx].child, new Set([rootIdx]));
|
|
73
|
+
}
|
|
74
|
+
#rootIndex() {
|
|
60
75
|
const rootIdx = this.#dir.findIndex((e) => e.type === TYPE_ROOT);
|
|
61
76
|
if (rootIdx < 0)
|
|
62
77
|
throw new VbaParseError('compound file has no root storage entry');
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
return rootIdx;
|
|
79
|
+
}
|
|
80
|
+
#buildSiblings(firstChild, seen, depth = 0) {
|
|
81
|
+
if (depth > MAX_TREE_DEPTH)
|
|
82
|
+
throw new VbaParseError('directory tree nests too deep');
|
|
83
|
+
return this.#childIndices(firstChild, seen, depth).map((idx) => {
|
|
84
|
+
const e = this.#dir[idx];
|
|
85
|
+
return e.type === TYPE_STORAGE
|
|
86
|
+
? { name: e.name, children: this.#buildSiblings(e.child, seen, depth + 1) }
|
|
87
|
+
: { name: e.name, data: this.#readEntryData(e) };
|
|
88
|
+
});
|
|
65
89
|
}
|
|
66
|
-
#
|
|
67
|
-
const
|
|
68
|
-
const walk = (idx) => {
|
|
90
|
+
#childIndices(firstChild, seen, depth) {
|
|
91
|
+
const indices = [];
|
|
92
|
+
const walk = (idx, siblingDepth) => {
|
|
69
93
|
if (idx >= MAX_REGULAR_SECTOR)
|
|
70
94
|
return;
|
|
95
|
+
if (siblingDepth > MAX_TREE_DEPTH)
|
|
96
|
+
throw new VbaParseError('directory tree nests too deep');
|
|
71
97
|
if (idx >= this.#dir.length)
|
|
72
98
|
throw new VbaParseError('directory sibling index out of range');
|
|
73
99
|
if (seen.has(idx))
|
|
@@ -76,17 +102,12 @@ export class CompoundFile {
|
|
|
76
102
|
const e = this.#dir[idx];
|
|
77
103
|
if (e.type === TYPE_EMPTY)
|
|
78
104
|
throw new VbaParseError('directory tree links an empty entry');
|
|
79
|
-
walk(e.left);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
nodes.push({ name: e.name, data: this.#readEntryData(e) });
|
|
85
|
-
}
|
|
86
|
-
walk(e.right);
|
|
105
|
+
walk(e.left, siblingDepth + 1);
|
|
106
|
+
indices.push(idx);
|
|
107
|
+
walk(e.right, siblingDepth + 1);
|
|
87
108
|
};
|
|
88
|
-
walk(firstChild);
|
|
89
|
-
return
|
|
109
|
+
walk(firstChild, depth);
|
|
110
|
+
return indices;
|
|
90
111
|
}
|
|
91
112
|
#readEntryData(entry) {
|
|
92
113
|
if (entry.size >= this.#miniCutoff)
|
|
@@ -94,9 +115,10 @@ export class CompoundFile {
|
|
|
94
115
|
return this.#readViaMiniFat(entry.startSector, entry.size);
|
|
95
116
|
}
|
|
96
117
|
#readDifat(numFatSectors, firstDifat, numDifat) {
|
|
118
|
+
const wanted = Math.min(numFatSectors, this.#maxSector);
|
|
97
119
|
const ids = [];
|
|
98
|
-
for (let i = 0; i <
|
|
99
|
-
const v = readU32(this.#buf,
|
|
120
|
+
for (let i = 0; i < HEADER_DIFAT_SLOTS && ids.length < wanted; i++) {
|
|
121
|
+
const v = readU32(this.#buf, HEADER_FIELD.difat + i * 4);
|
|
100
122
|
if (v >= MAX_REGULAR_SECTOR)
|
|
101
123
|
break;
|
|
102
124
|
ids.push(v);
|
|
@@ -104,12 +126,12 @@ export class CompoundFile {
|
|
|
104
126
|
const seen = new Set();
|
|
105
127
|
let sector = firstDifat;
|
|
106
128
|
const perSector = this.#sectorSize / 4 - 1;
|
|
107
|
-
for (let s = 0; s < numDifat && sector < MAX_REGULAR_SECTOR; s++) {
|
|
129
|
+
for (let s = 0; s < numDifat && sector < MAX_REGULAR_SECTOR && ids.length < wanted; s++) {
|
|
108
130
|
if (seen.has(sector))
|
|
109
131
|
throw new VbaParseError('cycle in DIFAT sector chain');
|
|
110
132
|
seen.add(sector);
|
|
111
133
|
const base = this.#dataSectorOffset(sector);
|
|
112
|
-
for (let i = 0; i < perSector; i++) {
|
|
134
|
+
for (let i = 0; i < perSector && ids.length < wanted; i++) {
|
|
113
135
|
const v = readU32(this.#buf, base + i * 4);
|
|
114
136
|
if (v < MAX_REGULAR_SECTOR)
|
|
115
137
|
ids.push(v);
|
|
@@ -123,23 +145,28 @@ export class CompoundFile {
|
|
|
123
145
|
const perSector = this.#sectorSize / 4;
|
|
124
146
|
for (const sid of fatSectorIds) {
|
|
125
147
|
const base = this.#dataSectorOffset(sid);
|
|
126
|
-
for (let i = 0; i < perSector; i++)
|
|
148
|
+
for (let i = 0; i < perSector && fat.length < this.#maxSector; i++) {
|
|
127
149
|
fat.push(readU32(this.#buf, base + i * 4));
|
|
150
|
+
}
|
|
151
|
+
if (fat.length >= this.#maxSector)
|
|
152
|
+
break;
|
|
128
153
|
}
|
|
129
154
|
return fat;
|
|
130
155
|
}
|
|
131
156
|
#readChainValues(firstSector) {
|
|
132
157
|
const values = [];
|
|
133
158
|
const perSector = this.#sectorSize / 4;
|
|
159
|
+
const cap = this.#maxSector * (this.#sectorSize / this.#miniSectorSize);
|
|
134
160
|
const seen = new Set();
|
|
135
161
|
let sector = firstSector;
|
|
136
|
-
while (sector < MAX_REGULAR_SECTOR) {
|
|
162
|
+
while (sector < MAX_REGULAR_SECTOR && values.length < cap) {
|
|
137
163
|
if (seen.has(sector))
|
|
138
164
|
throw new VbaParseError('cycle in mini-FAT sector chain');
|
|
139
165
|
seen.add(sector);
|
|
140
166
|
const base = this.#dataSectorOffset(sector);
|
|
141
|
-
for (let i = 0; i < perSector; i++)
|
|
167
|
+
for (let i = 0; i < perSector && values.length < cap; i++) {
|
|
142
168
|
values.push(readU32(this.#buf, base + i * 4));
|
|
169
|
+
}
|
|
143
170
|
sector = this.#nextInFat(sector);
|
|
144
171
|
}
|
|
145
172
|
return values;
|
|
@@ -148,16 +175,16 @@ export class CompoundFile {
|
|
|
148
175
|
const raw = this.#readChainFull(firstDirSector);
|
|
149
176
|
const entries = [];
|
|
150
177
|
for (let off = 0; off + DIR_ENTRY_SIZE <= raw.length; off += DIR_ENTRY_SIZE) {
|
|
151
|
-
const type = raw[off +
|
|
178
|
+
const type = raw[off + DIR_ENTRY_FIELD.objectType];
|
|
152
179
|
if (type !== TYPE_EMPTY &&
|
|
153
180
|
type !== TYPE_STORAGE &&
|
|
154
181
|
type !== TYPE_STREAM &&
|
|
155
182
|
type !== TYPE_ROOT) {
|
|
156
183
|
throw new VbaParseError(`directory entry has invalid object type ${type}`);
|
|
157
184
|
}
|
|
158
|
-
const left = readU32(raw, off +
|
|
159
|
-
const right = readU32(raw, off +
|
|
160
|
-
const child = readU32(raw, off +
|
|
185
|
+
const left = readU32(raw, off + DIR_ENTRY_FIELD.leftSibling);
|
|
186
|
+
const right = readU32(raw, off + DIR_ENTRY_FIELD.rightSibling);
|
|
187
|
+
const child = readU32(raw, off + DIR_ENTRY_FIELD.child);
|
|
161
188
|
if (type === TYPE_EMPTY) {
|
|
162
189
|
entries.push({
|
|
163
190
|
name: '',
|
|
@@ -170,70 +197,77 @@ export class CompoundFile {
|
|
|
170
197
|
});
|
|
171
198
|
continue;
|
|
172
199
|
}
|
|
173
|
-
const nameLen = readU16(raw, off +
|
|
200
|
+
const nameLen = readU16(raw, off + DIR_ENTRY_FIELD.nameLength);
|
|
174
201
|
if (nameLen > 64 || nameLen % 2 !== 0) {
|
|
175
202
|
throw new VbaParseError(`directory entry has invalid name length ${nameLen}`);
|
|
176
203
|
}
|
|
177
204
|
const name = decodeUtf16le(raw.subarray(off, off + Math.max(0, nameLen - 2)));
|
|
178
|
-
const startSector = readU32(raw, off +
|
|
179
|
-
const size = readU32(raw, off +
|
|
205
|
+
const startSector = readU32(raw, off + DIR_ENTRY_FIELD.startSector);
|
|
206
|
+
const size = readU32(raw, off + DIR_ENTRY_FIELD.sizeLow);
|
|
207
|
+
if (readU32(raw, off + DIR_ENTRY_FIELD.sizeHigh) !== 0) {
|
|
208
|
+
throw new VbaParseError(`directory entry ${quoted(name)} declares a stream larger than 4 GiB`);
|
|
209
|
+
}
|
|
180
210
|
entries.push({ name, type, startSector, size, left, right, child });
|
|
181
211
|
}
|
|
182
212
|
return entries;
|
|
183
213
|
}
|
|
184
214
|
#readChainFull(startSector) {
|
|
185
|
-
|
|
186
|
-
const seen = new Set();
|
|
187
|
-
let sector = startSector;
|
|
188
|
-
while (sector < MAX_REGULAR_SECTOR) {
|
|
189
|
-
if (seen.has(sector))
|
|
190
|
-
throw new VbaParseError('cycle in FAT sector chain');
|
|
191
|
-
seen.add(sector);
|
|
192
|
-
const base = this.#dataSectorOffset(sector);
|
|
193
|
-
chunks.push(this.#buf.subarray(base, base + this.#sectorSize));
|
|
194
|
-
sector = this.#nextInFat(sector);
|
|
195
|
-
}
|
|
196
|
-
return concat(chunks);
|
|
215
|
+
return this.#walkChain(startSector, undefined, this.#fatChain('FAT sector chain'));
|
|
197
216
|
}
|
|
198
217
|
#readViaFat(startSector, size) {
|
|
199
|
-
|
|
200
|
-
const seen = new Set();
|
|
201
|
-
let sector = startSector;
|
|
202
|
-
let remaining = size;
|
|
203
|
-
while (sector < MAX_REGULAR_SECTOR && remaining > 0) {
|
|
204
|
-
if (seen.has(sector))
|
|
205
|
-
throw new VbaParseError('cycle in stream FAT chain');
|
|
206
|
-
seen.add(sector);
|
|
207
|
-
const base = this.#dataSectorOffset(sector);
|
|
208
|
-
const take = Math.min(this.#sectorSize, remaining);
|
|
209
|
-
chunks.push(this.#buf.subarray(base, base + take));
|
|
210
|
-
remaining -= take;
|
|
211
|
-
sector = this.#nextInFat(sector);
|
|
212
|
-
}
|
|
213
|
-
return concat(chunks);
|
|
218
|
+
return this.#walkChain(startSector, size, this.#fatChain('stream FAT chain'));
|
|
214
219
|
}
|
|
215
220
|
#readViaMiniFat(startSector, size) {
|
|
221
|
+
return this.#walkChain(startSector, size, this.#miniFatChain());
|
|
222
|
+
}
|
|
223
|
+
#walkChain(startSector, size, chain) {
|
|
216
224
|
const chunks = [];
|
|
217
225
|
const seen = new Set();
|
|
218
226
|
let sector = startSector;
|
|
219
|
-
let remaining = size;
|
|
227
|
+
let remaining = size ?? Infinity;
|
|
220
228
|
while (sector < MAX_REGULAR_SECTOR && remaining > 0) {
|
|
221
229
|
if (seen.has(sector))
|
|
222
|
-
throw new VbaParseError(
|
|
230
|
+
throw new VbaParseError(`cycle in ${chain.what}`);
|
|
223
231
|
seen.add(sector);
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
if (base + take > this.#miniStream.length) {
|
|
227
|
-
throw new VbaParseError('mini-stream sector runs past end of the mini stream');
|
|
228
|
-
}
|
|
229
|
-
chunks.push(this.#miniStream.subarray(base, base + take));
|
|
232
|
+
const take = Math.min(chain.sectorSize, remaining);
|
|
233
|
+
chunks.push(chain.sliceAt(sector, take));
|
|
230
234
|
remaining -= take;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
235
|
+
sector = chain.next(sector);
|
|
236
|
+
}
|
|
237
|
+
if (size !== undefined && remaining > 0) {
|
|
238
|
+
throw new VbaParseError(`${chain.what} ends after ${size - remaining} bytes, but ${size} were declared`);
|
|
234
239
|
}
|
|
235
240
|
return concat(chunks);
|
|
236
241
|
}
|
|
242
|
+
#fatChain(what) {
|
|
243
|
+
return {
|
|
244
|
+
what,
|
|
245
|
+
sectorSize: this.#sectorSize,
|
|
246
|
+
sliceAt: (sector, take) => {
|
|
247
|
+
const base = this.#dataSectorOffset(sector);
|
|
248
|
+
return this.#buf.subarray(base, base + take);
|
|
249
|
+
},
|
|
250
|
+
next: (sector) => this.#nextInFat(sector),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
#miniFatChain() {
|
|
254
|
+
return {
|
|
255
|
+
what: 'stream mini-FAT chain',
|
|
256
|
+
sectorSize: this.#miniSectorSize,
|
|
257
|
+
sliceAt: (sector, take) => {
|
|
258
|
+
const base = sector * this.#miniSectorSize;
|
|
259
|
+
if (base + take > this.#miniStream.length) {
|
|
260
|
+
throw new VbaParseError('mini-stream sector runs past end of the mini stream');
|
|
261
|
+
}
|
|
262
|
+
return this.#miniStream.subarray(base, base + take);
|
|
263
|
+
},
|
|
264
|
+
next: (sector) => {
|
|
265
|
+
if (sector >= this.#miniFat.length)
|
|
266
|
+
throw new VbaParseError('mini-FAT index out of range');
|
|
267
|
+
return this.#miniFat[sector];
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
237
271
|
#nextInFat(sector) {
|
|
238
272
|
if (sector >= this.#fat.length)
|
|
239
273
|
throw new VbaParseError('FAT index out of range');
|
package/dist/vba/codepage.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hex } from '../hex.js';
|
|
1
2
|
import { VbaAuthorError, VbaParseError } from './errors.js';
|
|
2
3
|
const CODEPAGE_LABEL = new Map([
|
|
3
4
|
[1250, 'windows-1250'],
|
|
@@ -56,7 +57,7 @@ export function encoderForCodePage(codePage) {
|
|
|
56
57
|
const code = text.charCodeAt(i);
|
|
57
58
|
const byte = charToByte.get(code);
|
|
58
59
|
if (byte === undefined) {
|
|
59
|
-
throw new VbaAuthorError(`character U+${code
|
|
60
|
+
throw new VbaAuthorError(`character U+${hex(code, 4)} is not representable in code page ${codePage}`);
|
|
60
61
|
}
|
|
61
62
|
out[i] = byte;
|
|
62
63
|
}
|