@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.
Files changed (277) hide show
  1. package/README.md +34 -24
  2. package/dist/bytes.d.ts +10 -2
  3. package/dist/bytes.js +22 -0
  4. package/dist/core/address.d.ts +96 -2
  5. package/dist/core/address.js +87 -33
  6. package/dist/core/array-formula-ranges.d.ts +33 -0
  7. package/dist/core/array-formula-ranges.js +62 -0
  8. package/dist/core/autofilter.d.ts +4 -3
  9. package/dist/core/autofilter.js +31 -30
  10. package/dist/core/axis-handle.d.ts +2 -0
  11. package/dist/core/axis-handle.js +5 -2
  12. package/dist/core/cell.d.ts +4 -3
  13. package/dist/core/cell.js +4 -6
  14. package/dist/core/clone.d.ts +29 -0
  15. package/dist/core/clone.js +24 -0
  16. package/dist/core/color-resolution.js +4 -6
  17. package/dist/core/column.d.ts +1 -0
  18. package/dist/core/column.js +6 -2
  19. package/dist/core/comment-thread.d.ts +7 -3
  20. package/dist/core/comment-thread.js +2 -1
  21. package/dist/core/conditional-formatting-overlay.d.ts +11 -1
  22. package/dist/core/conditional-formatting-overlay.js +30 -3
  23. package/dist/core/conditional-formatting.d.ts +107 -11
  24. package/dist/core/conditional-formatting.js +76 -19
  25. package/dist/core/containers.d.ts +12 -0
  26. package/dist/core/containers.js +5 -0
  27. package/dist/core/data-validation-overlay.d.ts +7 -1
  28. package/dist/core/data-validation-overlay.js +19 -4
  29. package/dist/core/data-validation.d.ts +22 -0
  30. package/dist/core/data-validation.js +30 -4
  31. package/dist/core/date-format.d.ts +20 -0
  32. package/dist/core/date-format.js +170 -0
  33. package/dist/core/date.d.ts +43 -8
  34. package/dist/core/date.js +19 -3
  35. package/dist/core/formula-references.d.ts +26 -0
  36. package/dist/core/formula-references.js +173 -0
  37. package/dist/core/formula-scan.d.ts +2 -0
  38. package/dist/core/formula-scan.js +58 -0
  39. package/dist/core/formula.d.ts +76 -17
  40. package/dist/core/formula.js +87 -80
  41. package/dist/core/function-values.d.ts +4 -0
  42. package/dist/core/function-values.js +58 -0
  43. package/dist/core/future-functions.d.ts +3 -0
  44. package/dist/core/{modern-functions.js → future-functions.js} +119 -88
  45. package/dist/core/grid-edits.d.ts +30 -3
  46. package/dist/core/grid-edits.js +248 -81
  47. package/dist/core/grid-shift.d.ts +58 -9
  48. package/dist/core/grid-shift.js +26 -4
  49. package/dist/core/hyperlink.d.ts +39 -0
  50. package/dist/core/hyperlink.js +88 -0
  51. package/dist/core/image.d.ts +49 -14
  52. package/dist/core/image.js +16 -5
  53. package/dist/core/internal.d.ts +10 -2
  54. package/dist/core/limits.d.ts +25 -0
  55. package/dist/core/limits.js +4 -0
  56. package/dist/core/merge.d.ts +18 -4
  57. package/dist/core/merge.js +33 -20
  58. package/dist/core/page-setup.d.ts +26 -11
  59. package/dist/core/pivot-table.d.ts +49 -10
  60. package/dist/core/pivot-table.js +112 -60
  61. package/dist/core/protection.d.ts +66 -4
  62. package/dist/core/protection.js +6 -11
  63. package/dist/core/range.js +34 -30
  64. package/dist/core/row-input.js +2 -2
  65. package/dist/core/row.d.ts +1 -0
  66. package/dist/core/row.js +6 -2
  67. package/dist/core/structured-reference.d.ts +7 -0
  68. package/dist/core/structured-reference.js +7 -0
  69. package/dist/core/style.d.ts +85 -70
  70. package/dist/core/style.js +92 -28
  71. package/dist/core/table-style.d.ts +1 -1
  72. package/dist/core/table-style.js +4 -4
  73. package/dist/core/table.d.ts +64 -16
  74. package/dist/core/table.js +87 -40
  75. package/dist/core/theme.d.ts +2 -0
  76. package/dist/core/theme.js +9 -5
  77. package/dist/core/value.d.ts +60 -28
  78. package/dist/core/value.js +48 -43
  79. package/dist/core/workbook-media.d.ts +34 -0
  80. package/dist/core/workbook-media.js +44 -0
  81. package/dist/core/workbook-styles.d.ts +83 -1
  82. package/dist/core/workbook-styles.js +4 -0
  83. package/dist/core/workbook-theme.js +5 -3
  84. package/dist/core/workbook-vba.js +3 -2
  85. package/dist/core/workbook.d.ts +41 -3
  86. package/dist/core/workbook.js +46 -41
  87. package/dist/core/worksheet-comments.d.ts +2 -1
  88. package/dist/core/worksheet-comments.js +20 -13
  89. package/dist/core/worksheet-merges.d.ts +49 -0
  90. package/dist/core/worksheet-merges.js +65 -0
  91. package/dist/core/worksheet-model.d.ts +10 -2
  92. package/dist/core/worksheet-model.js +28 -9
  93. package/dist/core/worksheet-pictures.d.ts +3 -3
  94. package/dist/core/worksheet-pictures.js +7 -6
  95. package/dist/core/worksheet.d.ts +165 -27
  96. package/dist/core/worksheet.js +206 -89
  97. package/dist/customui/ribbon.js +2 -1
  98. package/dist/entries/core.d.ts +21 -14
  99. package/dist/entries/core.js +14 -7
  100. package/dist/entries/errors.d.ts +1 -0
  101. package/dist/entries/errors.js +1 -0
  102. package/dist/entries/xlsx.d.ts +1 -1
  103. package/dist/errors.d.ts +64 -0
  104. package/dist/errors.js +22 -0
  105. package/dist/hex.d.ts +8 -0
  106. package/dist/hex.js +3 -0
  107. package/dist/io/cell-metadata/metadata.d.ts +42 -0
  108. package/dist/io/cell-metadata/metadata.js +35 -0
  109. package/dist/io/cell-metadata/rich-values.d.ts +11 -0
  110. package/dist/io/cell-metadata/rich-values.js +80 -0
  111. package/dist/io/csv/delimiter.d.ts +21 -0
  112. package/dist/io/csv/delimiter.js +18 -0
  113. package/dist/io/csv/errors.d.ts +14 -0
  114. package/dist/io/csv/errors.js +5 -0
  115. package/dist/io/csv/read.d.ts +6 -1
  116. package/dist/io/csv/read.js +51 -22
  117. package/dist/io/csv/write.d.ts +19 -5
  118. package/dist/io/csv/write.js +24 -44
  119. package/dist/io/opc/inflate.js +14 -0
  120. package/dist/io/opc/namespaces.d.ts +16 -0
  121. package/dist/io/opc/namespaces.js +4 -0
  122. package/dist/io/opc/part-paths.d.ts +9 -0
  123. package/dist/io/opc/part-paths.js +5 -5
  124. package/dist/io/opc/read-opc.d.ts +58 -14
  125. package/dist/io/opc/read-opc.js +49 -21
  126. package/dist/io/opc/read-options.d.ts +5 -1
  127. package/dist/io/opc/sniff-format.d.ts +3 -3
  128. package/dist/io/opc/sniff-format.js +2 -2
  129. package/dist/io/opc/strict-relationships.d.ts +2 -0
  130. package/dist/io/opc/strict-relationships.js +12 -0
  131. package/dist/io/read-policy/column-budget.d.ts +26 -0
  132. package/dist/io/read-policy/column-budget.js +18 -0
  133. package/dist/io/read-policy/read-repair.d.ts +74 -0
  134. package/dist/io/read-policy/read-repair.js +119 -0
  135. package/dist/io/style/cell-style-resolution.d.ts +25 -0
  136. package/dist/io/style/cell-style-resolution.js +30 -0
  137. package/dist/io/style/xf-style.d.ts +23 -1
  138. package/dist/io/style/xf-style.js +8 -0
  139. package/dist/io/xlsb/formula.d.ts +2 -2
  140. package/dist/io/xlsb/formula.js +52 -35
  141. package/dist/io/xlsb/primitives.js +8 -14
  142. package/dist/io/xlsb/ptg-functions.d.ts +19 -10
  143. package/dist/io/xlsb/ptg-functions.js +495 -596
  144. package/dist/io/xlsb/read-metadata.d.ts +7 -0
  145. package/dist/io/xlsb/read-metadata.js +69 -0
  146. package/dist/io/xlsb/read-styles.js +45 -81
  147. package/dist/io/xlsb/read-worksheet.d.ts +26 -1
  148. package/dist/io/xlsb/read-worksheet.js +148 -106
  149. package/dist/io/xlsb/read.d.ts +6 -3
  150. package/dist/io/xlsb/read.js +70 -31
  151. package/dist/io/xlsb/record-stream.d.ts +30 -0
  152. package/dist/io/xlsb/record-stream.js +20 -0
  153. package/dist/io/xlsb/record-types.d.ts +14 -5
  154. package/dist/io/xlsb/record-types.js +14 -5
  155. package/dist/io/xlsb/sheet-protection.d.ts +20 -0
  156. package/dist/io/xlsb/sheet-protection.js +60 -0
  157. package/dist/io/xlsx/cell-accumulator.d.ts +24 -9
  158. package/dist/io/xlsx/cell-accumulator.js +74 -21
  159. package/dist/io/xlsx/cell-metadata.d.ts +39 -0
  160. package/dist/io/xlsx/cell-metadata.js +162 -0
  161. package/dist/io/xlsx/cell-value.d.ts +19 -5
  162. package/dist/io/xlsx/cell-value.js +45 -30
  163. package/dist/io/xlsx/color-xml.js +3 -2
  164. package/dist/io/xlsx/column-span.d.ts +11 -0
  165. package/dist/io/xlsx/column-span.js +9 -0
  166. package/dist/io/xlsx/comments.d.ts +9 -30
  167. package/dist/io/xlsx/comments.js +25 -83
  168. package/dist/io/xlsx/conditional-formatting.d.ts +43 -23
  169. package/dist/io/xlsx/conditional-formatting.js +437 -138
  170. package/dist/io/xlsx/data-validation.d.ts +12 -7
  171. package/dist/io/xlsx/data-validation.js +67 -53
  172. package/dist/io/xlsx/edit-vba.d.ts +7 -2
  173. package/dist/io/xlsx/edit-vba.js +52 -52
  174. package/dist/io/xlsx/font-xml.d.ts +7 -0
  175. package/dist/io/xlsx/font-xml.js +104 -0
  176. package/dist/io/xlsx/hyperlinks.d.ts +19 -21
  177. package/dist/io/xlsx/hyperlinks.js +12 -32
  178. package/dist/io/xlsx/images.d.ts +49 -16
  179. package/dist/io/xlsx/images.js +149 -50
  180. package/dist/io/xlsx/namespaces.d.ts +24 -0
  181. package/dist/io/xlsx/namespaces.js +11 -0
  182. package/dist/io/xlsx/package-plan.d.ts +60 -15
  183. package/dist/io/xlsx/package-plan.js +83 -18
  184. package/dist/io/xlsx/part-names.d.ts +6 -8
  185. package/dist/io/xlsx/part-names.js +3 -7
  186. package/dist/io/xlsx/pivot.d.ts +5 -5
  187. package/dist/io/xlsx/pivot.js +26 -24
  188. package/dist/io/xlsx/preserved-splices.d.ts +6 -0
  189. package/dist/io/xlsx/preserved-splices.js +74 -0
  190. package/dist/io/xlsx/read-comments.d.ts +29 -0
  191. package/dist/io/xlsx/read-comments.js +75 -0
  192. package/dist/io/xlsx/read-parts.d.ts +59 -0
  193. package/dist/io/xlsx/read-parts.js +259 -0
  194. package/dist/io/xlsx/read-pivot.js +11 -4
  195. package/dist/io/xlsx/{rich-runs.d.ts → read-rich-runs.d.ts} +8 -5
  196. package/dist/io/xlsx/{rich-runs.js → read-rich-runs.js} +24 -4
  197. package/dist/io/xlsx/read-rows.d.ts +35 -11
  198. package/dist/io/xlsx/read-rows.js +73 -49
  199. package/dist/io/xlsx/read-shared-strings.js +3 -2
  200. package/dist/io/xlsx/read-styles.d.ts +0 -30
  201. package/dist/io/xlsx/read-styles.js +45 -105
  202. package/dist/io/xlsx/read-workbook-xml.d.ts +42 -0
  203. package/dist/io/xlsx/read-workbook-xml.js +175 -0
  204. package/dist/io/xlsx/read-worksheet.d.ts +9 -3
  205. package/dist/io/xlsx/read-worksheet.js +70 -177
  206. package/dist/io/xlsx/read.d.ts +3 -14
  207. package/dist/io/xlsx/read.js +74 -411
  208. package/dist/io/xlsx/relationships.d.ts +3 -0
  209. package/dist/io/xlsx/relationships.js +4 -0
  210. package/dist/io/xlsx/rich-text.js +1 -1
  211. package/dist/io/xlsx/row-position.d.ts +20 -0
  212. package/dist/io/xlsx/row-position.js +11 -0
  213. package/dist/io/xlsx/row-xml.d.ts +139 -0
  214. package/dist/io/xlsx/row-xml.js +222 -0
  215. package/dist/io/xlsx/shared-formulas.js +2 -2
  216. package/dist/io/xlsx/sheet-properties.d.ts +10 -1
  217. package/dist/io/xlsx/sheet-properties.js +182 -20
  218. package/dist/io/xlsx/strict-parts.d.ts +7 -0
  219. package/dist/io/xlsx/strict-parts.js +171 -0
  220. package/dist/io/xlsx/style-elements.d.ts +41 -0
  221. package/dist/io/xlsx/style-elements.js +136 -0
  222. package/dist/io/xlsx/styles.d.ts +17 -3
  223. package/dist/io/xlsx/styles.js +13 -209
  224. package/dist/io/xlsx/tables.d.ts +10 -2
  225. package/dist/io/xlsx/tables.js +55 -52
  226. package/dist/io/xlsx/theme-xml.d.ts +32 -4
  227. package/dist/io/xlsx/theme-xml.js +106 -71
  228. package/dist/io/xlsx/threaded-comments.d.ts +14 -11
  229. package/dist/io/xlsx/threaded-comments.js +16 -8
  230. package/dist/io/xlsx/workbook-xml.d.ts +45 -6
  231. package/dist/io/xlsx/workbook-xml.js +62 -54
  232. package/dist/io/xlsx/worksheet-xml.d.ts +26 -70
  233. package/dist/io/xlsx/worksheet-xml.js +69 -282
  234. package/dist/io/xlsx/write-stream.d.ts +90 -19
  235. package/dist/io/xlsx/write-stream.js +80 -25
  236. package/dist/io/xlsx/write.d.ts +12 -6
  237. package/dist/io/xlsx/write.js +226 -123
  238. package/dist/rel-type.d.ts +25 -0
  239. package/dist/rel-type.js +9 -0
  240. package/dist/token-set.d.ts +13 -0
  241. package/dist/token-set.js +4 -1
  242. package/dist/vba/bytes.d.ts +16 -5
  243. package/dist/vba/bytes.js +27 -6
  244. package/dist/vba/cfb-format.d.ts +55 -0
  245. package/dist/vba/cfb-format.js +35 -0
  246. package/dist/vba/cfb-writer.d.ts +8 -0
  247. package/dist/vba/cfb-writer.js +40 -40
  248. package/dist/vba/cfb.d.ts +21 -4
  249. package/dist/vba/cfb.js +124 -90
  250. package/dist/vba/codepage.js +2 -1
  251. package/dist/vba/dir-records.d.ts +6 -0
  252. package/dist/vba/dir-records.js +10 -2
  253. package/dist/vba/errors.d.ts +11 -4
  254. package/dist/vba/index.d.ts +0 -2
  255. package/dist/vba/index.js +0 -2
  256. package/dist/vba/ms-ovba.d.ts +6 -2
  257. package/dist/vba/ms-ovba.js +66 -22
  258. package/dist/vba/project-editor.js +129 -119
  259. package/dist/vba/project.d.ts +15 -1
  260. package/dist/vba/project.js +38 -13
  261. package/dist/vba/vba-encoding.d.ts +4 -10
  262. package/dist/vba/vba-encoding.js +3 -17
  263. package/dist/xml/xml-attrs.d.ts +37 -0
  264. package/dist/xml/xml-attrs.js +30 -0
  265. package/dist/xml/xml-chars.d.ts +40 -0
  266. package/dist/xml/xml-chars.js +12 -0
  267. package/dist/xml/xml-namespaces.d.ts +31 -0
  268. package/dist/xml/xml-namespaces.js +50 -0
  269. package/dist/xml/xml-read.d.ts +101 -11
  270. package/dist/xml/xml-read.js +150 -18
  271. package/dist/xml/xml-scan.d.ts +25 -34
  272. package/dist/xml/xml-scan.js +69 -46
  273. package/dist/xml/xml.d.ts +59 -26
  274. package/dist/xml/xml.js +45 -23
  275. package/package.json +15 -9
  276. package/skills/ts-xlsx-upstream/SKILL.md +21 -13
  277. package/dist/core/modern-functions.d.ts +0 -1
@@ -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;
@@ -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
+ }
@@ -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
@@ -1,15 +1,15 @@
1
- import { DIFSECT, DIR_ENTRY_SIZE, ENDOFCHAIN, FATSECT, FREESECT, MAX_NAME_CHARS, NOSTREAM, TYPE_ROOT, TYPE_STORAGE, TYPE_STREAM, } from './cfb-format.js';
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
- const SECTOR = 512;
4
- const MINI_SECTOR = 64;
5
- const MINI_CUTOFF = 4096;
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 >= MINI_CUTOFF) {
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 > DIFAT_HEADER_SLOTS
83
- ? Math.ceil((needFat - DIFAT_HEADER_SLOTS) / (FAT_ENTRIES_PER_SECTOR - 1))
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 > DIFAT_HEADER_SLOTS) {
91
- throw new VbaAuthorError(`project needs ${fatSectors} FAT sectors, exceeding the ${DIFAT_HEADER_SLOTS}-sector single-header bound`);
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 = DIFAT_HEADER_SLOTS + d * (FAT_ENTRIES_PER_SECTOR - 1) + i;
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 '${node.name}' exceeds the ${MAX_NAME_CHARS}-character CFB limit`);
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 '${node.name}' under storage '${storageName}'`);
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(0, 0xe011cfd0, true);
202
- dv.setUint32(4, 0xe11ab1a1, true);
203
- dv.setUint16(24, 0x003e, true);
204
- dv.setUint16(26, 0x0003, true);
205
- dv.setUint16(28, 0xfffe, true);
206
- dv.setUint16(30, 9, true);
207
- dv.setUint16(32, 6, true);
208
- dv.setUint32(44, p.fatSectors, true);
209
- dv.setUint32(48, p.dirStart, true);
210
- dv.setUint32(56, MINI_CUTOFF, true);
211
- dv.setUint32(60, p.miniFatStart, true);
212
- dv.setUint32(64, p.miniFatSectors, true);
213
- dv.setUint32(68, p.difatSectors > 0 ? p.difatStart : ENDOFCHAIN, true);
214
- dv.setUint32(72, p.difatSectors, true);
215
- for (let i = 0; i < DIFAT_HEADER_SLOTS; i++) {
216
- dv.setUint32(76 + i * 4, i < p.fatSectors ? p.fatStart + i : FREESECT, true);
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 + 64, (e.name.length + 1) * 2, true);
223
- dv.setUint8(off + 66, e.type);
224
- dv.setUint8(off + 67, COLOR_BLACK);
225
- dv.setUint32(off + 68, e.left, true);
226
- dv.setUint32(off + 72, e.right, true);
227
- dv.setUint32(off + 76, e.child, true);
228
- dv.setUint32(off + 116, e.startSector, true);
229
- dv.setUint32(off + 120, e.size, true);
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
- /** List every stream/storage name in the directory (order as stored). */
6
- names(): string[];
7
- /** Read a stream's raw bytes by exact entry name, or `undefined` if absent. */
8
- readStream(name: string): Uint8Array | undefined;
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 CFB_SIGNATURE_LO = 0xe011cfd0;
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, 0) !== CFB_SIGNATURE_LO || readU32(this.#buf, 4) !== CFB_SIGNATURE_HI) {
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, 30);
24
- const miniSectorShift = readU16(this.#buf, 32);
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 !== 6)
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, 44);
34
- const firstDirSector = readU32(this.#buf, 48);
35
- this.#miniCutoff = readU32(this.#buf, 56);
36
- const firstMiniFatSector = readU32(this.#buf, 60);
37
- const firstDifatSector = readU32(this.#buf, 68);
38
- const numDifatSectors = readU32(this.#buf, 72);
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
- names() {
49
- return this.#dir
50
- .filter((e) => e.type === TYPE_STREAM || e.type === TYPE_STORAGE)
51
- .map((e) => e.name);
52
- }
53
- readStream(name) {
54
- const entry = this.#dir.find((e) => e.type === TYPE_STREAM && e.name === name);
55
- if (!entry)
56
- return undefined;
57
- return this.#readEntryData(entry);
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
- const root = this.#dir[rootIdx];
64
- return this.#buildSiblings(root.child, new Set([rootIdx]));
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
- #buildSiblings(firstChild, seen) {
67
- const nodes = [];
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
- if (e.type === TYPE_STORAGE) {
81
- nodes.push({ name: e.name, children: this.#buildSiblings(e.child, seen) });
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 nodes;
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 < 109 && ids.length < numFatSectors; i++) {
99
- const v = readU32(this.#buf, 76 + i * 4);
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 + 66];
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 + 68);
159
- const right = readU32(raw, off + 72);
160
- const child = readU32(raw, off + 76);
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 + 64);
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 + 116);
179
- const size = readU32(raw, off + 120);
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
- const chunks = [];
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
- const chunks = [];
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('cycle in stream mini-FAT chain');
230
+ throw new VbaParseError(`cycle in ${chain.what}`);
223
231
  seen.add(sector);
224
- const base = sector * this.#miniSectorSize;
225
- const take = Math.min(this.#miniSectorSize, remaining);
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
- if (sector >= this.#miniFat.length)
232
- throw new VbaParseError('mini-FAT index out of range');
233
- sector = this.#miniFat[sector];
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');
@@ -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.toString(16).toUpperCase().padStart(4, '0')} is not representable in code page ${codePage}`);
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
  }