@shbernal/ts-xlsx 3.0.0 → 3.1.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 +4 -1
- package/dist/bytes.d.ts +7 -0
- package/dist/bytes.js +22 -0
- package/dist/core/address.d.ts +60 -2
- package/dist/core/address.js +40 -24
- 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 +2 -4
- package/dist/core/column.d.ts +1 -0
- package/dist/core/column.js +3 -0
- package/dist/core/comment-thread.js +2 -1
- package/dist/core/conditional-formatting-overlay.d.ts +2 -1
- package/dist/core/conditional-formatting-overlay.js +2 -2
- package/dist/core/conditional-formatting.d.ts +9 -1
- package/dist/core/conditional-formatting.js +34 -18
- package/dist/core/containers.d.ts +12 -0
- package/dist/core/containers.js +5 -0
- package/dist/core/data-validation-overlay.d.ts +2 -1
- package/dist/core/data-validation-overlay.js +4 -4
- package/dist/core/data-validation.d.ts +6 -0
- package/dist/core/data-validation.js +15 -4
- package/dist/core/date-format.d.ts +20 -0
- package/dist/core/date-format.js +160 -0
- package/dist/core/date.d.ts +43 -8
- package/dist/core/date.js +18 -2
- package/dist/core/formula.d.ts +26 -0
- package/dist/core/formula.js +20 -4
- package/dist/core/grid-edits.d.ts +2 -3
- package/dist/core/grid-edits.js +69 -70
- package/dist/core/grid-shift.d.ts +58 -9
- package/dist/core/grid-shift.js +26 -4
- package/dist/core/image.d.ts +13 -9
- package/dist/core/image.js +7 -5
- package/dist/core/internal.d.ts +8 -2
- package/dist/core/limits.d.ts +23 -0
- package/dist/core/limits.js +4 -0
- package/dist/core/merge.d.ts +5 -4
- package/dist/core/merge.js +18 -19
- package/dist/core/pivot-table.js +63 -45
- package/dist/core/protection.js +2 -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 +3 -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 +27 -9
- package/dist/core/table.js +51 -34
- package/dist/core/theme.js +6 -5
- package/dist/core/value.d.ts +9 -0
- package/dist/core/value.js +36 -34
- package/dist/core/workbook-media.d.ts +34 -0
- package/dist/core/workbook-media.js +44 -0
- package/dist/core/workbook-styles.d.ts +67 -1
- package/dist/core/workbook-vba.js +3 -2
- package/dist/core/workbook.d.ts +23 -2
- package/dist/core/workbook.js +24 -40
- package/dist/core/worksheet-comments.d.ts +2 -1
- package/dist/core/worksheet-comments.js +20 -13
- package/dist/core/worksheet-merges.d.ts +44 -0
- package/dist/core/worksheet-merges.js +59 -0
- package/dist/core/worksheet-model.d.ts +9 -1
- package/dist/core/worksheet-model.js +24 -9
- package/dist/core/worksheet.d.ts +55 -6
- package/dist/core/worksheet.js +36 -44
- package/dist/customui/ribbon.js +2 -1
- package/dist/entries/core.d.ts +18 -13
- package/dist/entries/core.js +12 -6
- 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/csv/delimiter.d.ts +9 -0
- package/dist/io/csv/delimiter.js +6 -0
- package/dist/io/csv/read.js +11 -4
- package/dist/io/csv/write.d.ts +9 -2
- package/dist/io/csv/write.js +9 -38
- 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 +1 -1
- package/dist/io/opc/read-opc.d.ts +54 -14
- package/dist/io/opc/read-opc.js +42 -20
- 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/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 +49 -34
- 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 +385 -596
- package/dist/io/xlsb/read-styles.js +45 -81
- package/dist/io/xlsb/read-worksheet.d.ts +20 -1
- package/dist/io/xlsb/read-worksheet.js +73 -89
- package/dist/io/xlsb/read.d.ts +6 -3
- package/dist/io/xlsb/read.js +44 -23
- 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 +1 -5
- package/dist/io/xlsb/record-types.js +1 -5
- package/dist/io/xlsx/cell-accumulator.d.ts +8 -1
- package/dist/io/xlsx/cell-accumulator.js +32 -9
- package/dist/io/xlsx/cell-style-resolution.d.ts +25 -0
- package/dist/io/xlsx/cell-style-resolution.js +29 -0
- package/dist/io/xlsx/cell-value.d.ts +5 -3
- package/dist/io/xlsx/cell-value.js +13 -15
- package/dist/io/xlsx/color-xml.js +3 -2
- package/dist/io/xlsx/column-budget.d.ts +26 -0
- package/dist/io/xlsx/column-budget.js +22 -0
- package/dist/io/xlsx/comments.d.ts +6 -2
- package/dist/io/xlsx/comments.js +36 -32
- package/dist/io/xlsx/conditional-formatting.d.ts +6 -3
- package/dist/io/xlsx/conditional-formatting.js +30 -26
- package/dist/io/xlsx/data-validation.js +29 -27
- package/dist/io/xlsx/edit-vba.d.ts +7 -2
- package/dist/io/xlsx/edit-vba.js +14 -21
- 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 +37 -5
- package/dist/io/xlsx/hyperlinks.js +22 -13
- package/dist/io/xlsx/images.js +11 -13
- package/dist/io/xlsx/namespaces.d.ts +16 -0
- package/dist/io/xlsx/namespaces.js +7 -0
- package/dist/io/xlsx/package-plan.d.ts +14 -2
- package/dist/io/xlsx/package-plan.js +39 -11
- package/dist/io/xlsx/pivot.js +8 -7
- package/dist/io/xlsx/read-parts.d.ts +59 -0
- package/dist/io/xlsx/read-parts.js +252 -0
- package/dist/io/xlsx/read-pivot.js +2 -1
- package/dist/io/xlsx/read-repair.d.ts +26 -0
- package/dist/io/xlsx/read-repair.js +39 -0
- package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +6 -3
- package/dist/io/xlsx/read-rows.d.ts +18 -5
- package/dist/io/xlsx/read-rows.js +45 -31
- package/dist/io/xlsx/read-shared-strings.js +3 -2
- package/dist/io/xlsx/read-styles.d.ts +1 -6
- package/dist/io/xlsx/read-styles.js +6 -71
- package/dist/io/xlsx/read-workbook-xml.d.ts +36 -0
- package/dist/io/xlsx/read-workbook-xml.js +170 -0
- package/dist/io/xlsx/read-worksheet.d.ts +2 -1
- package/dist/io/xlsx/read-worksheet.js +35 -164
- package/dist/io/xlsx/read.d.ts +5 -12
- package/dist/io/xlsx/read.js +73 -406
- package/dist/io/xlsx/rich-text.js +1 -1
- package/dist/io/xlsx/row-position.d.ts +18 -0
- package/dist/io/xlsx/row-position.js +11 -0
- package/dist/io/xlsx/row-xml.d.ts +128 -0
- package/dist/io/xlsx/row-xml.js +203 -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 +145 -9
- package/dist/io/xlsx/style-elements.d.ts +43 -0
- package/dist/io/xlsx/style-elements.js +156 -0
- package/dist/io/xlsx/styles.d.ts +12 -3
- package/dist/io/xlsx/styles.js +5 -207
- package/dist/io/xlsx/tables.js +10 -14
- package/dist/io/xlsx/theme-xml.d.ts +26 -4
- package/dist/io/xlsx/theme-xml.js +107 -69
- package/dist/io/xlsx/threaded-comments.d.ts +8 -10
- package/dist/io/xlsx/threaded-comments.js +2 -1
- package/dist/io/xlsx/workbook-xml.d.ts +54 -4
- package/dist/io/xlsx/workbook-xml.js +60 -34
- package/dist/io/xlsx/worksheet-xml.d.ts +23 -70
- package/dist/io/xlsx/worksheet-xml.js +48 -229
- package/dist/io/xlsx/write-stream.d.ts +65 -16
- package/dist/io/xlsx/write-stream.js +50 -20
- package/dist/io/xlsx/write.d.ts +1 -2
- package/dist/io/xlsx/write.js +114 -74
- package/dist/rel-type.d.ts +16 -0
- package/dist/rel-type.js +6 -0
- package/dist/token-set.d.ts +13 -0
- package/dist/token-set.js +4 -1
- package/dist/vba/bytes.d.ts +14 -5
- package/dist/vba/bytes.js +16 -6
- package/dist/vba/cfb-format.d.ts +8 -0
- package/dist/vba/cfb-format.js +1 -0
- package/dist/vba/cfb-writer.d.ts +9 -0
- package/dist/vba/cfb-writer.js +12 -9
- package/dist/vba/cfb.d.ts +13 -1
- package/dist/vba/cfb.js +73 -51
- package/dist/vba/codepage.js +2 -1
- package/dist/vba/errors.d.ts +7 -0
- package/dist/vba/ms-ovba.js +51 -16
- package/dist/vba/project-editor.js +35 -40
- package/dist/vba/project.d.ts +11 -1
- package/dist/vba/project.js +27 -8
- package/dist/vba/vba-encoding.js +4 -5
- 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 +72 -10
- package/dist/xml/xml-read.js +94 -18
- package/dist/xml/xml-scan.d.ts +15 -37
- package/dist/xml/xml-scan.js +12 -40
- package/dist/xml/xml.d.ts +28 -27
- package/dist/xml/xml.js +24 -24
- package/package.json +12 -7
- /package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +0 -0
package/dist/vba/cfb-format.js
CHANGED
package/dist/vba/cfb-writer.d.ts
CHANGED
|
@@ -8,6 +8,15 @@ 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 the pair says that a node is one or the other.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isStream(node: CfbNode): node is CfbStream;
|
|
19
|
+
export declare function isStorage(node: CfbNode): node is CfbStorage;
|
|
11
20
|
/**
|
|
12
21
|
* Encode a hierarchy of storages and streams into a v3 compound file. The Root Entry is synthesized
|
|
13
22
|
* 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,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { quoted } from '../errors.js';
|
|
2
|
+
import { DIFSECT, DIR_ENTRY_SIZE, ENDOFCHAIN, FATSECT, FREESECT, MAX_NAME_CHARS, MINI_STREAM_CUTOFF, NOSTREAM, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
|
|
2
3
|
import { VbaAuthorError } from './errors.js';
|
|
4
|
+
export function isStream(node) {
|
|
5
|
+
return 'data' in node;
|
|
6
|
+
}
|
|
7
|
+
export function isStorage(node) {
|
|
8
|
+
return !isStream(node);
|
|
9
|
+
}
|
|
3
10
|
const SECTOR = 512;
|
|
4
11
|
const MINI_SECTOR = 64;
|
|
5
|
-
const MINI_CUTOFF = 4096;
|
|
6
12
|
const ENTRIES_PER_DIR_SECTOR = SECTOR / DIR_ENTRY_SIZE;
|
|
7
13
|
const FAT_ENTRIES_PER_SECTOR = SECTOR / 4;
|
|
8
14
|
const DIFAT_HEADER_SLOTS = 109;
|
|
9
15
|
const COLOR_BLACK = 1;
|
|
10
|
-
function isStream(node) {
|
|
11
|
-
return 'data' in node;
|
|
12
|
-
}
|
|
13
16
|
export function writeCompoundFile(root) {
|
|
14
17
|
const entries = [];
|
|
15
18
|
const addEntry = (name, type, startSector) => {
|
|
@@ -38,7 +41,7 @@ export function writeCompoundFile(root) {
|
|
|
38
41
|
siblings.push(entry);
|
|
39
42
|
if (node.data.length === 0) {
|
|
40
43
|
}
|
|
41
|
-
else if (node.data.length >=
|
|
44
|
+
else if (node.data.length >= MINI_STREAM_CUTOFF) {
|
|
42
45
|
bigStreams.push({ entry, data: node.data, sectors: Math.ceil(node.data.length / SECTOR) });
|
|
43
46
|
}
|
|
44
47
|
else {
|
|
@@ -168,10 +171,10 @@ function validateSiblingNames(storageName, siblings) {
|
|
|
168
171
|
if (node.name.length === 0)
|
|
169
172
|
throw new VbaAuthorError('entry name must not be empty');
|
|
170
173
|
if (node.name.length > MAX_NAME_CHARS) {
|
|
171
|
-
throw new VbaAuthorError(`entry name
|
|
174
|
+
throw new VbaAuthorError(`entry name ${quoted(node.name)} exceeds the ${MAX_NAME_CHARS}-character CFB limit`);
|
|
172
175
|
}
|
|
173
176
|
if (seen.has(node.name)) {
|
|
174
|
-
throw new VbaAuthorError(`duplicate entry name
|
|
177
|
+
throw new VbaAuthorError(`duplicate entry name ${quoted(node.name)} under storage ${quoted(storageName)}`);
|
|
175
178
|
}
|
|
176
179
|
seen.add(node.name);
|
|
177
180
|
if (!isStream(node))
|
|
@@ -207,7 +210,7 @@ function writeHeader(dv, p) {
|
|
|
207
210
|
dv.setUint16(32, 6, true);
|
|
208
211
|
dv.setUint32(44, p.fatSectors, true);
|
|
209
212
|
dv.setUint32(48, p.dirStart, true);
|
|
210
|
-
dv.setUint32(56,
|
|
213
|
+
dv.setUint32(56, MINI_STREAM_CUTOFF, true);
|
|
211
214
|
dv.setUint32(60, p.miniFatStart, true);
|
|
212
215
|
dv.setUint32(64, p.miniFatSectors, true);
|
|
213
216
|
dv.setUint32(68, p.difatSectors > 0 ? p.difatStart : ENDOFCHAIN, true);
|
package/dist/vba/cfb.d.ts
CHANGED
|
@@ -4,13 +4,25 @@ export declare class CompoundFile {
|
|
|
4
4
|
constructor(buf: Uint8Array);
|
|
5
5
|
/** List every stream/storage name in the directory (order as stored). */
|
|
6
6
|
names(): string[];
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Read a stream's raw bytes by exact entry name, or `undefined` if absent.
|
|
9
|
+
*
|
|
10
|
+
* @returns bytes that may be a **view onto the caller's buffer** rather than a copy. A stream small
|
|
11
|
+
* enough to fit in one sector is assembled by `concat` from a single chunk, and `concat` hands a
|
|
12
|
+
* lone chunk straight back; anything larger is copied into a fresh buffer. So whether the result
|
|
13
|
+
* aliases the source depends on the stream's size, which is a rule no caller should have to know
|
|
14
|
+
* and none here relies on: nothing mutates what this returns. Stated because it is the sort of
|
|
15
|
+
* thing that is true until someone writes the first mutation, and then true only for large
|
|
16
|
+
* streams. Copy before mutating.
|
|
17
|
+
*/
|
|
8
18
|
readStream(name: string): Uint8Array | undefined;
|
|
9
19
|
/**
|
|
10
20
|
* Reconstruct the container's top-level children as the writer's node shape, recursing into every
|
|
11
21
|
* storage, so a caller can swap one stream and re-emit the whole hierarchy with {@link writeCompoundFile}.
|
|
12
22
|
* Walks the red-black sibling tree each storage navigates (not the linear directory scan), so any part
|
|
13
23
|
* a host reaches is carried through. Cycle- and bounds-guarded like every other chain walk here.
|
|
24
|
+
*
|
|
25
|
+
* @returns nodes whose stream bytes carry {@link readStream}'s aliasing caveat.
|
|
14
26
|
*/
|
|
15
27
|
tree(): CfbNode[];
|
|
16
28
|
}
|
package/dist/vba/cfb.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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 { DIR_ENTRY_SIZE, MAX_REGULAR_SECTOR, MINI_STREAM_CUTOFF, NOSTREAM, TYPE_EMPTY, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
|
|
3
4
|
import { VbaParseError } from './errors.js';
|
|
4
5
|
const CFB_SIGNATURE_LO = 0xe011cfd0;
|
|
5
6
|
const CFB_SIGNATURE_HI = 0xe11ab1a1;
|
|
7
|
+
const MAX_TREE_DEPTH = 256;
|
|
6
8
|
export class CompoundFile {
|
|
7
9
|
#buf;
|
|
8
10
|
#sectorSize;
|
|
@@ -33,6 +35,9 @@ export class CompoundFile {
|
|
|
33
35
|
const numFatSectors = readU32(this.#buf, 44);
|
|
34
36
|
const firstDirSector = readU32(this.#buf, 48);
|
|
35
37
|
this.#miniCutoff = readU32(this.#buf, 56);
|
|
38
|
+
if (this.#miniCutoff !== MINI_STREAM_CUTOFF) {
|
|
39
|
+
throw new VbaParseError(`unsupported mini-stream cutoff ${this.#miniCutoff}`);
|
|
40
|
+
}
|
|
36
41
|
const firstMiniFatSector = readU32(this.#buf, 60);
|
|
37
42
|
const firstDifatSector = readU32(this.#buf, 68);
|
|
38
43
|
const numDifatSectors = readU32(this.#buf, 72);
|
|
@@ -63,11 +68,15 @@ export class CompoundFile {
|
|
|
63
68
|
const root = this.#dir[rootIdx];
|
|
64
69
|
return this.#buildSiblings(root.child, new Set([rootIdx]));
|
|
65
70
|
}
|
|
66
|
-
#buildSiblings(firstChild, seen) {
|
|
71
|
+
#buildSiblings(firstChild, seen, depth = 0) {
|
|
72
|
+
if (depth > MAX_TREE_DEPTH)
|
|
73
|
+
throw new VbaParseError('directory tree nests too deep');
|
|
67
74
|
const nodes = [];
|
|
68
|
-
const walk = (idx) => {
|
|
75
|
+
const walk = (idx, siblingDepth) => {
|
|
69
76
|
if (idx >= MAX_REGULAR_SECTOR)
|
|
70
77
|
return;
|
|
78
|
+
if (siblingDepth > MAX_TREE_DEPTH)
|
|
79
|
+
throw new VbaParseError('directory tree nests too deep');
|
|
71
80
|
if (idx >= this.#dir.length)
|
|
72
81
|
throw new VbaParseError('directory sibling index out of range');
|
|
73
82
|
if (seen.has(idx))
|
|
@@ -76,16 +85,16 @@ export class CompoundFile {
|
|
|
76
85
|
const e = this.#dir[idx];
|
|
77
86
|
if (e.type === TYPE_EMPTY)
|
|
78
87
|
throw new VbaParseError('directory tree links an empty entry');
|
|
79
|
-
walk(e.left);
|
|
88
|
+
walk(e.left, siblingDepth + 1);
|
|
80
89
|
if (e.type === TYPE_STORAGE) {
|
|
81
|
-
nodes.push({ name: e.name, children: this.#buildSiblings(e.child, seen) });
|
|
90
|
+
nodes.push({ name: e.name, children: this.#buildSiblings(e.child, seen, depth + 1) });
|
|
82
91
|
}
|
|
83
92
|
else {
|
|
84
93
|
nodes.push({ name: e.name, data: this.#readEntryData(e) });
|
|
85
94
|
}
|
|
86
|
-
walk(e.right);
|
|
95
|
+
walk(e.right, siblingDepth + 1);
|
|
87
96
|
};
|
|
88
|
-
walk(firstChild);
|
|
97
|
+
walk(firstChild, depth);
|
|
89
98
|
return nodes;
|
|
90
99
|
}
|
|
91
100
|
#readEntryData(entry) {
|
|
@@ -94,8 +103,9 @@ export class CompoundFile {
|
|
|
94
103
|
return this.#readViaMiniFat(entry.startSector, entry.size);
|
|
95
104
|
}
|
|
96
105
|
#readDifat(numFatSectors, firstDifat, numDifat) {
|
|
106
|
+
const wanted = Math.min(numFatSectors, this.#maxSector);
|
|
97
107
|
const ids = [];
|
|
98
|
-
for (let i = 0; i < 109 && ids.length <
|
|
108
|
+
for (let i = 0; i < 109 && ids.length < wanted; i++) {
|
|
99
109
|
const v = readU32(this.#buf, 76 + i * 4);
|
|
100
110
|
if (v >= MAX_REGULAR_SECTOR)
|
|
101
111
|
break;
|
|
@@ -104,12 +114,12 @@ export class CompoundFile {
|
|
|
104
114
|
const seen = new Set();
|
|
105
115
|
let sector = firstDifat;
|
|
106
116
|
const perSector = this.#sectorSize / 4 - 1;
|
|
107
|
-
for (let s = 0; s < numDifat && sector < MAX_REGULAR_SECTOR; s++) {
|
|
117
|
+
for (let s = 0; s < numDifat && sector < MAX_REGULAR_SECTOR && ids.length < wanted; s++) {
|
|
108
118
|
if (seen.has(sector))
|
|
109
119
|
throw new VbaParseError('cycle in DIFAT sector chain');
|
|
110
120
|
seen.add(sector);
|
|
111
121
|
const base = this.#dataSectorOffset(sector);
|
|
112
|
-
for (let i = 0; i < perSector; i++) {
|
|
122
|
+
for (let i = 0; i < perSector && ids.length < wanted; i++) {
|
|
113
123
|
const v = readU32(this.#buf, base + i * 4);
|
|
114
124
|
if (v < MAX_REGULAR_SECTOR)
|
|
115
125
|
ids.push(v);
|
|
@@ -123,23 +133,28 @@ export class CompoundFile {
|
|
|
123
133
|
const perSector = this.#sectorSize / 4;
|
|
124
134
|
for (const sid of fatSectorIds) {
|
|
125
135
|
const base = this.#dataSectorOffset(sid);
|
|
126
|
-
for (let i = 0; i < perSector; i++)
|
|
136
|
+
for (let i = 0; i < perSector && fat.length < this.#maxSector; i++) {
|
|
127
137
|
fat.push(readU32(this.#buf, base + i * 4));
|
|
138
|
+
}
|
|
139
|
+
if (fat.length >= this.#maxSector)
|
|
140
|
+
break;
|
|
128
141
|
}
|
|
129
142
|
return fat;
|
|
130
143
|
}
|
|
131
144
|
#readChainValues(firstSector) {
|
|
132
145
|
const values = [];
|
|
133
146
|
const perSector = this.#sectorSize / 4;
|
|
147
|
+
const cap = this.#maxSector * (this.#sectorSize / this.#miniSectorSize);
|
|
134
148
|
const seen = new Set();
|
|
135
149
|
let sector = firstSector;
|
|
136
|
-
while (sector < MAX_REGULAR_SECTOR) {
|
|
150
|
+
while (sector < MAX_REGULAR_SECTOR && values.length < cap) {
|
|
137
151
|
if (seen.has(sector))
|
|
138
152
|
throw new VbaParseError('cycle in mini-FAT sector chain');
|
|
139
153
|
seen.add(sector);
|
|
140
154
|
const base = this.#dataSectorOffset(sector);
|
|
141
|
-
for (let i = 0; i < perSector; i++)
|
|
155
|
+
for (let i = 0; i < perSector && values.length < cap; i++) {
|
|
142
156
|
values.push(readU32(this.#buf, base + i * 4));
|
|
157
|
+
}
|
|
143
158
|
sector = this.#nextInFat(sector);
|
|
144
159
|
}
|
|
145
160
|
return values;
|
|
@@ -177,63 +192,70 @@ export class CompoundFile {
|
|
|
177
192
|
const name = decodeUtf16le(raw.subarray(off, off + Math.max(0, nameLen - 2)));
|
|
178
193
|
const startSector = readU32(raw, off + 116);
|
|
179
194
|
const size = readU32(raw, off + 120);
|
|
195
|
+
if (readU32(raw, off + 124) !== 0) {
|
|
196
|
+
throw new VbaParseError(`directory entry ${quoted(name)} declares a stream larger than 4 GiB`);
|
|
197
|
+
}
|
|
180
198
|
entries.push({ name, type, startSector, size, left, right, child });
|
|
181
199
|
}
|
|
182
200
|
return entries;
|
|
183
201
|
}
|
|
184
202
|
#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);
|
|
203
|
+
return this.#walkChain(startSector, undefined, this.#fatChain('FAT sector chain'));
|
|
197
204
|
}
|
|
198
205
|
#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);
|
|
206
|
+
return this.#walkChain(startSector, size, this.#fatChain('stream FAT chain'));
|
|
214
207
|
}
|
|
215
208
|
#readViaMiniFat(startSector, size) {
|
|
209
|
+
return this.#walkChain(startSector, size, this.#miniFatChain());
|
|
210
|
+
}
|
|
211
|
+
#walkChain(startSector, size, chain) {
|
|
216
212
|
const chunks = [];
|
|
217
213
|
const seen = new Set();
|
|
218
214
|
let sector = startSector;
|
|
219
|
-
let remaining = size;
|
|
215
|
+
let remaining = size ?? Infinity;
|
|
220
216
|
while (sector < MAX_REGULAR_SECTOR && remaining > 0) {
|
|
221
217
|
if (seen.has(sector))
|
|
222
|
-
throw new VbaParseError(
|
|
218
|
+
throw new VbaParseError(`cycle in ${chain.what}`);
|
|
223
219
|
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));
|
|
220
|
+
const take = Math.min(chain.sectorSize, remaining);
|
|
221
|
+
chunks.push(chain.sliceAt(sector, take));
|
|
230
222
|
remaining -= take;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
223
|
+
sector = chain.next(sector);
|
|
224
|
+
}
|
|
225
|
+
if (size !== undefined && remaining > 0) {
|
|
226
|
+
throw new VbaParseError(`${chain.what} ends after ${size - remaining} bytes, but ${size} were declared`);
|
|
234
227
|
}
|
|
235
228
|
return concat(chunks);
|
|
236
229
|
}
|
|
230
|
+
#fatChain(what) {
|
|
231
|
+
return {
|
|
232
|
+
what,
|
|
233
|
+
sectorSize: this.#sectorSize,
|
|
234
|
+
sliceAt: (sector, take) => {
|
|
235
|
+
const base = this.#dataSectorOffset(sector);
|
|
236
|
+
return this.#buf.subarray(base, base + take);
|
|
237
|
+
},
|
|
238
|
+
next: (sector) => this.#nextInFat(sector),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
#miniFatChain() {
|
|
242
|
+
return {
|
|
243
|
+
what: 'stream mini-FAT chain',
|
|
244
|
+
sectorSize: this.#miniSectorSize,
|
|
245
|
+
sliceAt: (sector, take) => {
|
|
246
|
+
const base = sector * this.#miniSectorSize;
|
|
247
|
+
if (base + take > this.#miniStream.length) {
|
|
248
|
+
throw new VbaParseError('mini-stream sector runs past end of the mini stream');
|
|
249
|
+
}
|
|
250
|
+
return this.#miniStream.subarray(base, base + take);
|
|
251
|
+
},
|
|
252
|
+
next: (sector) => {
|
|
253
|
+
if (sector >= this.#miniFat.length)
|
|
254
|
+
throw new VbaParseError('mini-FAT index out of range');
|
|
255
|
+
return this.#miniFat[sector];
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
237
259
|
#nextInFat(sector) {
|
|
238
260
|
if (sector >= this.#fat.length)
|
|
239
261
|
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
|
}
|
package/dist/vba/errors.d.ts
CHANGED
|
@@ -17,6 +17,13 @@ export declare class VbaParseError extends XlsxError {
|
|
|
17
17
|
* [MS-CFB] 31-character limit, a duplicate stream name, or a project so large it would exceed the
|
|
18
18
|
* writer's single-header DIFAT bound. This is a caller-side contract violation, distinct from
|
|
19
19
|
* {@link VbaParseError} (which reports a malformed blob *read* from an untrusted file).
|
|
20
|
+
*
|
|
21
|
+
* It shares `code: 'authoring'` with `AuthoringError` rather than being one, and it is the only
|
|
22
|
+
* subsystem in the tree that got its own authoring class. The reason is symmetry with
|
|
23
|
+
* {@link VbaParseError} and not the VBA subsystem's importance: a caller who catches the parse error
|
|
24
|
+
* to skip a macro project it cannot read is the same caller who catches the author error to skip one
|
|
25
|
+
* it cannot write, and the two halves of that pair have to be nameable the same way. A CSV or table
|
|
26
|
+
* authoring failure has no read-side twin to pair with, which is why those stay `AuthoringError`.
|
|
20
27
|
*/
|
|
21
28
|
export declare class VbaAuthorError extends XlsxError {
|
|
22
29
|
readonly name = "VbaAuthorError";
|
package/dist/vba/ms-ovba.js
CHANGED
|
@@ -3,6 +3,9 @@ import { VbaParseError } from './errors.js';
|
|
|
3
3
|
const MAX_CHUNK_DECOMPRESSED = 4096;
|
|
4
4
|
const CHUNK_SIGNATURE = 0b011 << 12;
|
|
5
5
|
const CHUNK_COMPRESSED_FLAG = 0x8000;
|
|
6
|
+
const MAX_MATCH_CANDIDATES = 64;
|
|
7
|
+
const HASH_BITS = 12;
|
|
8
|
+
const NO_CANDIDATE = -1;
|
|
6
9
|
const DEFAULT_MAX_OUTPUT = 64 * 1024 * 1024;
|
|
7
10
|
export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTPUT) {
|
|
8
11
|
if (start >= buf.length) {
|
|
@@ -11,7 +14,7 @@ export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTP
|
|
|
11
14
|
if (buf[start] !== 0x01) {
|
|
12
15
|
throw new VbaParseError(`compressed container must begin with a 0x01 signature byte, found 0x${(buf[start] ?? 0).toString(16)}`);
|
|
13
16
|
}
|
|
14
|
-
const out = new
|
|
17
|
+
const out = new ByteSink(maxOutput);
|
|
15
18
|
let pos = start + 1;
|
|
16
19
|
while (pos + 2 <= buf.length) {
|
|
17
20
|
const header = readU16(buf, pos);
|
|
@@ -55,16 +58,19 @@ export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTP
|
|
|
55
58
|
for (let i = 0; i < length; i++)
|
|
56
59
|
out.push(out.at(src + i));
|
|
57
60
|
}
|
|
61
|
+
if (out.length - chunkStart > MAX_CHUNK_DECOMPRESSED) {
|
|
62
|
+
throw new VbaParseError(`chunk decompressed to more than ${MAX_CHUNK_DECOMPRESSED} bytes, which [MS-OVBA] forbids`);
|
|
63
|
+
}
|
|
58
64
|
}
|
|
59
65
|
pos = chunkEnd;
|
|
60
66
|
}
|
|
61
67
|
return out.bytes();
|
|
62
68
|
}
|
|
63
|
-
class
|
|
69
|
+
class ByteSink {
|
|
64
70
|
#buf;
|
|
65
71
|
#length = 0;
|
|
66
72
|
#limit;
|
|
67
|
-
constructor(limit) {
|
|
73
|
+
constructor(limit = Infinity) {
|
|
68
74
|
this.#limit = limit;
|
|
69
75
|
this.#buf = new Uint8Array(Math.min(MAX_CHUNK_DECOMPRESSED, limit));
|
|
70
76
|
}
|
|
@@ -74,6 +80,9 @@ class DecompressedBytes {
|
|
|
74
80
|
at(index) {
|
|
75
81
|
return this.#buf[index];
|
|
76
82
|
}
|
|
83
|
+
set(index, byte) {
|
|
84
|
+
this.#buf[index] = byte;
|
|
85
|
+
}
|
|
77
86
|
push(byte) {
|
|
78
87
|
if (this.#length >= this.#limit) {
|
|
79
88
|
throw new VbaParseError(`decompressed output exceeds the ${this.#limit}-byte ceiling (possible bomb)`);
|
|
@@ -85,26 +94,40 @@ class DecompressedBytes {
|
|
|
85
94
|
}
|
|
86
95
|
this.#buf[this.#length++] = byte;
|
|
87
96
|
}
|
|
97
|
+
pushAll(bytes) {
|
|
98
|
+
for (const byte of bytes)
|
|
99
|
+
this.push(byte);
|
|
100
|
+
}
|
|
88
101
|
bytes() {
|
|
89
102
|
return this.#buf.slice(0, this.#length);
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
export function compressContainer(data) {
|
|
93
|
-
const out =
|
|
106
|
+
const out = new ByteSink();
|
|
107
|
+
out.push(0x01);
|
|
94
108
|
for (let start = 0; start < data.length; start += MAX_CHUNK_DECOMPRESSED) {
|
|
95
109
|
const chunk = data.subarray(start, Math.min(start + MAX_CHUNK_DECOMPRESSED, data.length));
|
|
96
110
|
const tokens = compressChunk(chunk);
|
|
97
111
|
const compressed = tokens.length < chunk.length;
|
|
98
112
|
const body = compressed ? tokens : chunk;
|
|
99
113
|
const header = (compressed ? CHUNK_COMPRESSED_FLAG : 0) | CHUNK_SIGNATURE | ((body.length - 1) & 0x0fff);
|
|
100
|
-
out.push(header & 0xff
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
out.push(header & 0xff);
|
|
115
|
+
out.push((header >> 8) & 0xff);
|
|
116
|
+
out.pushAll(body);
|
|
103
117
|
}
|
|
104
|
-
return
|
|
118
|
+
return out.bytes();
|
|
105
119
|
}
|
|
106
120
|
function compressChunk(chunk) {
|
|
107
|
-
const tokens =
|
|
121
|
+
const tokens = new ByteSink();
|
|
122
|
+
const heads = new Int32Array(1 << HASH_BITS).fill(NO_CANDIDATE);
|
|
123
|
+
const prev = new Int32Array(MAX_CHUNK_DECOMPRESSED).fill(NO_CANDIDATE);
|
|
124
|
+
const remember = (at) => {
|
|
125
|
+
if (at + 2 >= chunk.length)
|
|
126
|
+
return;
|
|
127
|
+
const bucket = hash3(chunk, at);
|
|
128
|
+
prev[at] = heads[bucket];
|
|
129
|
+
heads[bucket] = at;
|
|
130
|
+
};
|
|
108
131
|
let pos = 0;
|
|
109
132
|
while (pos < chunk.length) {
|
|
110
133
|
const flagIndex = tokens.length;
|
|
@@ -116,37 +139,49 @@ function compressChunk(chunk) {
|
|
|
116
139
|
const windowStart = Math.max(0, pos - (1 << bitCount));
|
|
117
140
|
let bestLength = 0;
|
|
118
141
|
let bestOffset = 0;
|
|
119
|
-
|
|
142
|
+
let candidate = pos + 2 < chunk.length ? heads[hash3(chunk, pos)] : NO_CANDIDATE;
|
|
143
|
+
for (let tried = 0; candidate >= windowStart && tried < MAX_MATCH_CANDIDATES; tried++) {
|
|
120
144
|
let len = 0;
|
|
121
145
|
while (len < maxLength &&
|
|
122
146
|
pos + len < chunk.length &&
|
|
123
|
-
chunk[
|
|
147
|
+
chunk[candidate + len] === chunk[pos + len]) {
|
|
124
148
|
len++;
|
|
125
149
|
}
|
|
126
150
|
if (len > bestLength) {
|
|
127
151
|
bestLength = len;
|
|
128
|
-
bestOffset = pos -
|
|
152
|
+
bestOffset = pos - candidate;
|
|
129
153
|
if (bestLength === maxLength)
|
|
130
154
|
break;
|
|
131
155
|
}
|
|
156
|
+
candidate = prev[candidate];
|
|
132
157
|
}
|
|
133
158
|
if (bestLength >= 3) {
|
|
134
159
|
const token = ((bestOffset - 1) << (16 - bitCount)) | (bestLength - 3);
|
|
135
|
-
tokens.push(token & 0xff
|
|
160
|
+
tokens.push(token & 0xff);
|
|
161
|
+
tokens.push((token >> 8) & 0xff);
|
|
136
162
|
flags |= 1 << bit;
|
|
163
|
+
for (let i = 0; i < bestLength; i++)
|
|
164
|
+
remember(pos + i);
|
|
137
165
|
pos += bestLength;
|
|
138
166
|
}
|
|
139
167
|
else {
|
|
140
168
|
tokens.push(chunk[pos]);
|
|
169
|
+
remember(pos);
|
|
141
170
|
pos++;
|
|
142
171
|
}
|
|
143
172
|
}
|
|
144
|
-
tokens
|
|
173
|
+
tokens.set(flagIndex, flags);
|
|
145
174
|
}
|
|
146
|
-
return tokens;
|
|
175
|
+
return tokens.bytes();
|
|
176
|
+
}
|
|
177
|
+
function hash3(chunk, pos) {
|
|
178
|
+
const first = chunk[pos];
|
|
179
|
+
const second = chunk[pos + 1];
|
|
180
|
+
const third = chunk[pos + 2];
|
|
181
|
+
return ((first << 8) ^ (second << 4) ^ third) & ((1 << HASH_BITS) - 1);
|
|
147
182
|
}
|
|
148
183
|
function copyTokenHelp(decompressedSoFar) {
|
|
149
|
-
const bitCount = Math.max(Math.ceil(Math.log2(Math.max(decompressedSoFar, 1))), 4);
|
|
184
|
+
const bitCount = Math.min(Math.max(Math.ceil(Math.log2(Math.max(decompressedSoFar, 1))), 4), Math.log2(MAX_CHUNK_DECOMPRESSED));
|
|
150
185
|
const lengthMask = 0xffff >> bitCount;
|
|
151
186
|
return { lengthMask, bitCount };
|
|
152
187
|
}
|