@poe-platform/safe-bash 0.1.754 → 0.1.755

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 (51) hide show
  1. package/dist/image-ast/index.browser.js +13 -1
  2. package/dist/image-ast/index.js +13 -1
  3. package/dist/pdf-ast/fonts/truetype.d.ts +1 -0
  4. package/dist/pdf-ast/index.js +13 -1
  5. package/dist/safe-bash/chunks/{chunk-R2WG64VB.js → chunk-6XDKK63A.js} +14 -2
  6. package/dist/safe-bash/chunks/{chunk-3W6CRM25.js → chunk-JEFBTRY7.js} +553 -289
  7. package/dist/safe-bash/chunks/{chunk-WNB4XH45.js → chunk-QHB7HBLA.js} +3 -3
  8. package/dist/safe-bash/chunks/{chunk-37Y7GZTK.js → chunk-RSAWYAEH.js} +2 -2
  9. package/dist/safe-bash/chunks/{pdf-renderer-DHQDZC6N.js → pdf-renderer-XIFUTX34.js} +3 -3
  10. package/dist/safe-bash/chunks/{src-S52LORVD.js → src-V4VCM5OX.js} +5 -3
  11. package/dist/safe-bash/command-chunks/{chunk-IJHV6I6X.js → chunk-3DOHQQJR.js} +8 -6
  12. package/dist/safe-bash/command-chunks/chunk-IDR3WWKI.js +46 -0
  13. package/dist/safe-bash/command-chunks/{chunk-XAZ3NTJ4.js → chunk-KB2LQ4HG.js} +2 -2
  14. package/dist/safe-bash/command-chunks/{chunk-YY7HJMBG.js → chunk-KOM3FNCE.js} +1 -37
  15. package/dist/safe-bash/command-chunks/{chunk-CM4SVTIA.js → chunk-MENC7RPR.js} +1 -1
  16. package/dist/safe-bash/command-chunks/chunk-QQOAK7JQ.js +888 -0
  17. package/dist/safe-bash/command-chunks/{chunk-BE4AM2JW.js → chunk-UGX6MXDD.js} +5 -3
  18. package/dist/safe-bash/command-chunks/{chunk-PUOJAS3O.js → chunk-XRVNFK7Z.js} +13 -1
  19. package/dist/safe-bash/commands/ssconvert/index.d.ts +1 -2
  20. package/dist/safe-bash/commands/ssconvert/index.js +6 -260
  21. package/dist/safe-bash/core.browser.js +5 -207
  22. package/dist/safe-bash-command-csvkit/index.js +277 -49
  23. package/dist/safe-bash-command-ffmpeg/index.js +1723 -1717
  24. package/dist/safe-bash-command-imagemagick/index.js +2 -2
  25. package/dist/safe-bash-command-pandoc/command.js +3 -3
  26. package/dist/safe-bash-command-pandoc/index.js +3 -3
  27. package/dist/safe-bash-command-pdfimages/index.js +13 -1
  28. package/dist/safe-bash-command-pdfinfo/index.js +13 -1
  29. package/dist/safe-bash-command-pdftk/index.js +13 -1
  30. package/dist/safe-bash-command-pdftoppm/index.js +13 -1
  31. package/dist/safe-bash-command-pdftotext/index.js +13 -1
  32. package/dist/safe-bash-command-qpdf/index.js +13 -1
  33. package/dist/safe-bash-command-sips/index.js +2 -2
  34. package/dist/safe-bash-command-soffice/index.js +5 -4
  35. package/dist/safe-bash-command-ssconvert/formats/csv.js +4 -3
  36. package/dist/safe-bash-command-ssconvert/formats/spreadsheetml.js +4 -2
  37. package/dist/safe-bash-command-ssconvert/formats/xls.js +10 -666
  38. package/dist/safe-bash-command-ssconvert/index.d.ts +1 -0
  39. package/dist/safe-bash-command-ssconvert/index.js +680 -390
  40. package/dist/safe-bash-command-ssconvert/sync.d.ts +7 -0
  41. package/dist/safe-bash-command-timeout/index.js +1 -1
  42. package/dist/safe-bash-command-wkhtmltopdf/index.js +13 -1
  43. package/dist/spreadsheet-format-csv/text-export.d.ts +14 -0
  44. package/dist/spreadsheet-format-csv/text-export.js +2 -2
  45. package/dist/spreadsheet-format-xls/biff-binary.d.ts +7 -2
  46. package/dist/spreadsheet-format-xls/biff-binary.js +10 -1
  47. package/dist/spreadsheet-format-xls/biff-strings.d.ts +2 -3
  48. package/dist/spreadsheet-format-xls/biff-strings.js +14 -2
  49. package/package.json +2 -3
  50. package/third-party/pdf-ast/THIRD_PARTY_NOTICES.md +7 -0
  51. package/dist/safe-bash/command-chunks/chunk-MPUNHXWI.js +0 -205
@@ -0,0 +1,7 @@
1
+ import type { CommandHandler } from "../safe-bash-contracts/index.js";
2
+ export interface SynchronousSpreadsheetArchive {
3
+ read(bytes: Uint8Array): ReadonlyMap<string, Uint8Array>;
4
+ write(entries: Readonly<Record<string, Uint8Array>>): Uint8Array;
5
+ }
6
+ export declare function createSyncSsconvertEvaluator(archive: SynchronousSpreadsheetArchive): (execute: CommandHandler, opArgs: readonly string[], inBytes?: Uint8Array, readFileSync?: (filePath: string) => Uint8Array | undefined, writeFileSync?: (filePath: string, bytes: Uint8Array) => boolean) => string | undefined;
7
+ //# sourceMappingURL=sync.d.ts.map
@@ -441,7 +441,7 @@ function evalSyncTimeout(args) {
441
441
  return void 0;
442
442
  const durStr = args[i];
443
443
  const parsed = parseDuration(durStr);
444
- if (parsed.kind !== "value" || parsed.milliseconds < 0)
444
+ if (parsed.kind !== "value" || parsed.milliseconds <= 0)
445
445
  return void 0;
446
446
  if (killAfterMs !== void 0 && killAfterMs !== 0 && parsed.milliseconds !== 0 && parsed.milliseconds !== Infinity) {
447
447
  return void 0;
@@ -5773,17 +5773,22 @@ function parseTrueTypeFont(bytes) {
5773
5773
  const cmapNumTables = cmap ? readU16(view, cmapOffset + 2) : 0;
5774
5774
  let format4Offset = 0;
5775
5775
  let format12Offset = 0;
5776
+ let format4IsSymbolic = false;
5777
+ let format12IsSymbolic = false;
5776
5778
  for (let i = 0; i < cmapNumTables; i++) {
5777
5779
  const rec = cmapOffset + 4 + i * 8;
5778
5780
  if (rec + 8 > bytes.byteLength) break;
5779
5781
  const platformID = readU16(view, rec);
5782
+ const encodingID = readU16(view, rec + 2);
5780
5783
  const subOffset = cmapOffset + readU32(view, rec + 4);
5781
5784
  if (subOffset + 4 > bytes.byteLength) continue;
5782
5785
  const fmt = readU16(view, subOffset);
5783
5786
  if (fmt === 12 && (platformID === 3 || platformID === 0)) {
5784
5787
  format12Offset = subOffset;
5788
+ format12IsSymbolic = platformID === 3 && encodingID === 0;
5785
5789
  } else if (fmt === 4 && (platformID === 3 || platformID === 0)) {
5786
5790
  format4Offset = subOffset;
5791
+ format4IsSymbolic = platformID === 3 && encodingID === 0;
5787
5792
  }
5788
5793
  }
5789
5794
  if (format12Offset > 0) {
@@ -6072,6 +6077,7 @@ function parseTrueTypeFont(bytes) {
6072
6077
  numGlyphs,
6073
6078
  isCff: renderCffGlyph !== void 0,
6074
6079
  hasCmap: cmap !== void 0,
6080
+ isSymbolicCmap: format12Offset > 0 ? format12IsSymbolic : format4IsSymbolic,
6075
6081
  glyphNames,
6076
6082
  getGlyphId(codePoint) {
6077
6083
  return codePointToGlyph.get(codePoint) ?? 0;
@@ -10587,7 +10593,13 @@ function resolvePageFonts(doc, resourcesDict) {
10587
10593
  embeddedCff = parseEmbeddedCffFont(doc.decodeStream(program), baseEncoding?.kind === "name" ? baseEncoding.decoded : void 0, glyphNames);
10588
10594
  } else {
10589
10595
  embeddedTrueType = parseTrueTypeFont(doc.decodeStream(program));
10590
- if (subtype !== "Type0" && !embeddedTrueType.hasCmap) {
10596
+ if (subtype !== "Type0" && embeddedTrueType.isSymbolicCmap) {
10597
+ simpleToGid = /* @__PURE__ */ new Map();
10598
+ for (let code = 0; code < 256; code++) {
10599
+ const gid = embeddedTrueType.getGlyphId(61440 + code) || embeddedTrueType.getGlyphId(code);
10600
+ if (gid > 0) simpleToGid.set(code, gid);
10601
+ }
10602
+ } else if (subtype !== "Type0" && !embeddedTrueType.hasCmap) {
10591
10603
  simpleToGid = /* @__PURE__ */ new Map();
10592
10604
  const encoding = baseEncoding?.kind === "name" ? getEncoding(baseEncoding.decoded) : void 0;
10593
10605
  for (let code = 0; code < 256; code++) {
@@ -1,3 +1,17 @@
1
+ import { type TextFormatMode } from "../spreadsheet-engine/formatting.js";
1
2
  import type { Codec } from "../spreadsheet-engine/codecs/types.js";
3
+ interface TextOptions {
4
+ separator: string;
5
+ quote: string;
6
+ eol: string;
7
+ mode: "never" | "auto" | "always";
8
+ whitespace: boolean;
9
+ format: TextFormatMode;
10
+ charset: string;
11
+ transliterate: boolean;
12
+ locale?: string;
13
+ }
14
+ export declare function appendTextField(text: string, options: Pick<TextOptions, "mode" | "quote" | "whitespace">, append: (text: string) => void): void;
2
15
  export declare const writeConfigurableText: NonNullable<Codec["write"]>;
3
16
  export declare const writePlainCsv: NonNullable<Codec["write"]>;
17
+ export {};
@@ -70,7 +70,7 @@ function byteField(source, options, maximum, tick) {
70
70
  append([quote]);
71
71
  return Uint8Array.from(result);
72
72
  }
73
- function appendField(text, options, append) {
73
+ export function appendTextField(text, options, append) {
74
74
  // Preserve NUL and scan the entire field. Triggers retain the initial
75
75
  // configuration even after separator, quote and eol properties change via -O.
76
76
  const whitespace = (c) => c !== undefined && c !== "\u000b" && c !== "\ufeff" && c.trim() === "";
@@ -151,7 +151,7 @@ async function exportText(args, options) {
151
151
  chunks.push(bytes);
152
152
  }
153
153
  else
154
- appendField(cell ? await renderCellText(cell, book, renderingContext, options.format) : "", options, append);
154
+ appendTextField(cell ? await renderCellText(cell, book, renderingContext, options.format) : "", options, append);
155
155
  }
156
156
  append(options.eol);
157
157
  }
@@ -1,4 +1,3 @@
1
- import { type CapabilityContext } from "../spreadsheet-engine/contracts.js";
2
1
  export declare function invalidBiff(message: string): never;
3
2
  /** All binary reads, including slices, require explicit admission. */
4
3
  export declare class Binary {
@@ -29,4 +28,10 @@ export interface BiffRecord {
29
28
  readonly offset: number;
30
29
  readonly data: Binary;
31
30
  }
32
- export declare function readBiffRecords(bytes: Uint8Array, context: CapabilityContext): BiffRecord[];
31
+ export interface BiffReadContext extends CfbReadContext {
32
+ readonly limits: CfbReadContext["limits"] & {
33
+ readonly workbookNodes?: number;
34
+ readonly workbookTextBytes?: number;
35
+ };
36
+ }
37
+ export declare function readBiffRecords(bytes: Uint8Array, context: BiffReadContext): BiffRecord[];
@@ -174,15 +174,24 @@ export function readCfb(bytes, context) {
174
174
  return result;
175
175
  }
176
176
  export function readBiffRecords(bytes, context) {
177
+ context.signal.throwIfAborted();
178
+ if (bytes.length > context.limits.inputBytes)
179
+ throw new SsconvertError("resource-limit", "ssconvert input bytes limit exceeded");
177
180
  const binary = new Binary(bytes), records = [];
178
181
  for (let offset = 0; offset < bytes.length;) {
179
182
  context.signal.throwIfAborted();
183
+ context.work?.();
180
184
  binary.check(offset, 4);
181
185
  const opcode = binary.u16(offset), length = binary.u16(offset + 2);
182
- if (opcode === 0 && length === 0 && bytes.subarray(offset).every(byte => byte === 0))
186
+ if (opcode === 0 && length === 0 && bytes.subarray(offset).every(byte => {
187
+ context.signal.throwIfAborted();
188
+ context.work?.();
189
+ return byte === 0;
190
+ }))
183
191
  break;
184
192
  if (records.length >= (context.limits.workbookNodes ?? context.limits.inputBytes))
185
193
  throw new SsconvertError("resource-limit", "ssconvert BIFF record limit exceeded");
194
+ context.retain?.(128);
186
195
  records.push({ opcode, offset, data: new Binary(binary.slice(offset + 4, length)) });
187
196
  offset += length + 4;
188
197
  }
@@ -1,5 +1,4 @@
1
- import { type CapabilityContext } from "../spreadsheet-engine/contracts.js";
2
- import { Binary } from "./biff-binary.js";
1
+ import { Binary, type BiffReadContext } from "./biff-binary.js";
3
2
  import type { RichTextRun } from "../spreadsheet-ast/index.js";
4
3
  export declare function biffOverrideCodepage(encoding: string | undefined): number | undefined;
5
4
  export declare function biffDecode(bytes: Uint8Array, codepage: number): string;
@@ -10,7 +9,7 @@ export declare class BiffStrings {
10
9
  private readonly codepage;
11
10
  private part;
12
11
  private offset;
13
- constructor(parts: readonly Binary[], context: CapabilityContext, codepage: number);
12
+ constructor(parts: readonly Binary[], context: BiffReadContext, codepage: number);
14
13
  get consumedBytes(): number;
15
14
  private advance;
16
15
  byte(): number;
@@ -12,6 +12,8 @@ export function biffOverrideCodepage(encoding) {
12
12
  return encoding === undefined ? undefined : overrideCodepages[encoding];
13
13
  }
14
14
  export function biffDecode(bytes, codepage) {
15
+ if (codepage === 65001)
16
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
15
17
  const multibyte = biffDbcsTables[codepage];
16
18
  if (multibyte) {
17
19
  const characters = [];
@@ -28,7 +30,7 @@ export function biffDecode(bytes, codepage) {
28
30
  }
29
31
  return characters.join("");
30
32
  }
31
- const name = codepage === 1200 || codepage === 1201 ? "iso-8859-1" :
33
+ const name = codepage === 20127 ? "ascii" : codepage === 1200 || codepage === 1201 || codepage === 28591 ? "iso-8859-1" :
32
34
  codepage === 10000 ? "macintosh" : codepage >= 1250 && codepage <= 1258 ? `windows-${codepage}` : `cp${codepage}`;
33
35
  const table = singleByteTables[name];
34
36
  if (!table)
@@ -59,13 +61,20 @@ export class BiffStrings {
59
61
  }
60
62
  advance() {
61
63
  while (this.part < this.parts.length && this.offset === this.parts[this.part].bytes.length) {
64
+ this.context.signal.throwIfAborted();
65
+ this.context.work?.();
62
66
  this.part++;
63
67
  this.offset = 0;
64
68
  }
65
69
  if (this.part >= this.parts.length)
66
70
  invalidBiff("truncated string/CONTINUE");
67
71
  }
68
- byte() { this.advance(); return this.parts[this.part].u8(this.offset++); }
72
+ byte() {
73
+ this.context.signal.throwIfAborted();
74
+ this.context.work?.();
75
+ this.advance();
76
+ return this.parts[this.part].u8(this.offset++);
77
+ }
69
78
  word() { const low = this.byte(); return low + this.byte() * 256; }
70
79
  dword() { const low = this.word(); return low + this.word() * 65536; }
71
80
  legacy(length) {
@@ -79,6 +88,8 @@ export class BiffStrings {
79
88
  this.context.signal.throwIfAborted();
80
89
  if (length > (this.context.limits.workbookTextBytes ?? this.context.limits.inputBytes))
81
90
  throw new SsconvertError("resource-limit", "ssconvert BIFF string limit exceeded");
91
+ // Covers temporary byte/character arrays, decoded text and rich-run objects.
92
+ this.context.retain?.(length * 16);
82
93
  }
83
94
  unicode(length) {
84
95
  this.admit(length * 3);
@@ -91,6 +102,7 @@ export class BiffStrings {
91
102
  const characters = [];
92
103
  for (let i = 0; i < length; i++) {
93
104
  this.context.signal.throwIfAborted();
105
+ this.context.work?.();
94
106
  if (this.offset === this.parts[this.part].bytes.length) {
95
107
  this.advance();
96
108
  const width = this.byte();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-bash",
3
- "version": "0.1.754",
3
+ "version": "0.1.755",
4
4
  "description": "Extensible virtual shell with pluggable filesystems and streaming commands",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -837,12 +837,11 @@
837
837
  "access": "public"
838
838
  },
839
839
  "dependencies": {
840
- "@poe-platform/safe-fs": "0.1.754",
840
+ "@poe-platform/safe-fs": "0.1.755",
841
841
  "pdf-lib": "1.17.1",
842
842
  "@noble/hashes": "2.4.0",
843
843
  "pako": "3.0.1",
844
844
  "@types/node": "^25.2.2",
845
- "@e965/xlsx": "0.20.3",
846
845
  "@noble/ciphers": "2.4.0",
847
846
  "buffer": "6.0.3",
848
847
  "@kayahr/text-encoding": "^2.2.0",
@@ -288,3 +288,10 @@ and checking every copy and save/reopen. Page copying follows `_writer.py`
288
288
  `_add_page`: each occurrence gets a distinct page dictionary while resources
289
289
  can remain shared. Content edits replace the page's Contents reference so
290
290
  other pages retain their original stream. pypdf attribution/license is above.
291
+
292
+ Windows Symbol cmap selection in `src/content/evaluator.ts` follows PDF.js
293
+ `src/core/fonts.js` `checkAndRepair`: use encoded character codes for (3,0)
294
+ tables and clear the high byte only for F000–F0FF. The TrueType reader records
295
+ the selected cmap's encoding. Tests in `src/fonts/cid-to-gid.test.ts` cover
296
+ raw and prefixed symbol codes plus the original issue2948 fixture at the
297
+ same pinned PDF.js revision documented above.
@@ -1,205 +0,0 @@
1
- import {
2
- SsconvertError
3
- } from "./chunk-F5MORDSU.js";
4
- import {
5
- init_buffer
6
- } from "./chunk-HX3SHTCE.js";
7
-
8
- // packages/spreadsheet-format-xls/src/biff-binary.ts
9
- init_buffer();
10
- function invalidBiff(message) {
11
- throw new SsconvertError("io", `E Invalid Excel BIFF: ${message}`);
12
- }
13
- var Binary = class {
14
- constructor(bytes) {
15
- this.bytes = bytes;
16
- this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
17
- }
18
- bytes;
19
- view;
20
- check(offset, length) {
21
- if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(length) || offset < 0 || length < 0 || offset > this.bytes.length - length) invalidBiff("truncated binary data");
22
- }
23
- u8(offset) {
24
- this.check(offset, 1);
25
- return this.view.getUint8(offset);
26
- }
27
- u16(offset) {
28
- this.check(offset, 2);
29
- return this.view.getUint16(offset, true);
30
- }
31
- u32(offset) {
32
- this.check(offset, 4);
33
- return this.view.getUint32(offset, true);
34
- }
35
- f64(offset) {
36
- this.check(offset, 8);
37
- return this.view.getFloat64(offset, true);
38
- }
39
- slice(offset, length) {
40
- this.check(offset, length);
41
- return this.bytes.subarray(offset, offset + length);
42
- }
43
- };
44
- var signature = [208, 207, 17, 224, 161, 177, 26, 225];
45
- var free = 4294967295;
46
- var end = 4294967294;
47
- function isCfb(bytes) {
48
- return signature.every((byte, index) => bytes[index] === byte);
49
- }
50
- function readCfb(bytes, context) {
51
- context.signal.throwIfAborted();
52
- context.work?.();
53
- if (bytes.length > context.limits.inputBytes) throw new SsconvertError("resource-limit", "ssconvert input bytes limit exceeded");
54
- context.retain?.(256);
55
- const file = new Binary(bytes);
56
- file.check(0, 512);
57
- if (!isCfb(bytes) || file.u16(28) !== 65534) invalidBiff("invalid CFB header");
58
- const version = file.u16(26), shift = file.u16(30);
59
- if (!(version === 3 && shift === 9 || version === 4 && shift === 12) || file.u16(32) !== 6)
60
- invalidBiff("unsupported CFB sector layout");
61
- const sectorSize = 2 ** shift, sectorCount = Math.floor(bytes.length / sectorSize) - 1;
62
- if (sectorCount < 0 || bytes.length % sectorSize !== 0 || file.u32(56) !== 4096) invalidBiff("invalid CFB size");
63
- const sector = (id) => {
64
- context.signal.throwIfAborted();
65
- context.work?.();
66
- if (id >= sectorCount) invalidBiff("CFB sector outside file");
67
- return new Binary(file.slice((id + 1) * sectorSize, sectorSize));
68
- };
69
- const fatCount = file.u32(44), difatCount = file.u32(72), miniCount = file.u32(64);
70
- if (fatCount > sectorCount || difatCount > sectorCount || miniCount > sectorCount) invalidBiff("invalid CFB chain count");
71
- const fatIds = [], seenFat = /* @__PURE__ */ new Set(), seenDifat = /* @__PURE__ */ new Set();
72
- const addFat = (id) => {
73
- context.work?.();
74
- if (id === free) return;
75
- if (id >= sectorCount || fatIds.length >= fatCount || seenFat.has(id)) invalidBiff("invalid CFB FAT sector");
76
- context.retain?.(64);
77
- seenFat.add(id);
78
- fatIds.push(id);
79
- };
80
- for (let offset = 76; offset < 512; offset += 4) addFat(file.u32(offset));
81
- let difat = file.u32(68);
82
- for (let index = 0; index < difatCount; index++) {
83
- context.work?.();
84
- if (seenDifat.has(difat)) invalidBiff("CFB DIFAT cycle");
85
- context.retain?.(64);
86
- seenDifat.add(difat);
87
- const data = sector(difat);
88
- for (let offset = 0; offset < sectorSize - 4; offset += 4) addFat(data.u32(offset));
89
- difat = data.u32(sectorSize - 4);
90
- }
91
- if (difatCount && difat !== end || fatIds.length !== fatCount) invalidBiff("CFB FAT count mismatch");
92
- context.retain?.(fatIds.length * 64);
93
- const fatSectors = fatIds.map(sector), fatWidth = sectorSize / 4;
94
- const nextFat = (id) => {
95
- if (id >= sectorCount || !fatSectors[Math.floor(id / fatWidth)]) invalidBiff("CFB FAT reference outside table");
96
- return fatSectors[Math.floor(id / fatWidth)].u32(id % fatWidth * 4);
97
- };
98
- const chain = (start, maximum, next) => {
99
- const ids = [], seen = /* @__PURE__ */ new Set();
100
- for (let id = start; id !== end; id = next(id)) {
101
- context.signal.throwIfAborted();
102
- context.work?.();
103
- if (seen.has(id)) invalidBiff("CFB chain cycle");
104
- if (ids.length >= maximum) invalidBiff("CFB chain exceeds declared size");
105
- context.retain?.(64);
106
- seen.add(id);
107
- ids.push(id);
108
- }
109
- return ids;
110
- };
111
- let allocated = 0;
112
- const collect = (ids, size, width, source) => {
113
- if (size > context.limits.inputBytes - allocated) throw new SsconvertError("resource-limit", "ssconvert CFB decoded bytes limit exceeded");
114
- if (ids.length !== Math.ceil(size / width)) invalidBiff("CFB stream size mismatch");
115
- context.retain?.(size);
116
- allocated += size;
117
- const result2 = new Uint8Array(size);
118
- for (let i = 0; i < ids.length; i++) {
119
- context.work?.();
120
- result2.set(source(ids[i]).slice(0, Math.min(width, size - i * width)), i * width);
121
- }
122
- return result2;
123
- };
124
- const dirIds = chain(file.u32(48), sectorCount, nextFat);
125
- if (version === 4 && dirIds.length !== file.u32(40)) invalidBiff("CFB directory count mismatch");
126
- const directory = new Binary(collect(dirIds, dirIds.length * sectorSize, sectorSize, sector));
127
- const entries = [];
128
- for (let offset = 0; offset < directory.bytes.length; offset += 128) {
129
- context.signal.throwIfAborted();
130
- context.work?.();
131
- const type = directory.u8(offset + 66), length = directory.u16(offset + 64);
132
- if (type && (length < 2 || length > 64 || length % 2 || directory.u16(offset + length - 2) !== 0)) invalidBiff("invalid CFB directory name");
133
- if (![0, 1, 2, 5].includes(type)) invalidBiff("invalid CFB entry type");
134
- const high = directory.u32(offset + 124), low = directory.u32(offset + 120);
135
- const size = version === 3 ? low : high * 2 ** 32 + low;
136
- if (!Number.isSafeInteger(size) || type && size > bytes.length) invalidBiff("invalid CFB stream size");
137
- context.retain?.(128 + (type ? length : 0));
138
- entries.push({
139
- name: type ? new TextDecoder("utf-16le", { fatal: true }).decode(directory.slice(offset, length - 2)) : "",
140
- type,
141
- left: directory.u32(offset + 68),
142
- right: directory.u32(offset + 72),
143
- child: directory.u32(offset + 76),
144
- start: directory.u32(offset + 116),
145
- size
146
- });
147
- }
148
- const root = entries[0];
149
- if (!root || root.type !== 5) invalidBiff("missing CFB root");
150
- const miniIds = chain(file.u32(60), miniCount, nextFat);
151
- if (miniIds.length !== miniCount) invalidBiff("CFB miniFAT count mismatch");
152
- const miniFat = new Binary(collect(miniIds, miniCount * sectorSize, sectorSize, sector));
153
- const miniStream = new Binary(collect(chain(root.start, Math.ceil(root.size / sectorSize), nextFat), root.size, sectorSize, sector));
154
- const miniSector = (id) => new Binary(miniStream.slice(id * 64, 64));
155
- const nextMini = (id) => {
156
- if (id >= Math.ceil(root.size / 64)) invalidBiff("CFB mini sector outside stream");
157
- return miniFat.u32(id * 4);
158
- };
159
- const result = /* @__PURE__ */ new Map(), visited = /* @__PURE__ */ new Set(), pending = [root.child];
160
- while (pending.length) {
161
- context.signal.throwIfAborted();
162
- context.work?.();
163
- const id = pending.pop();
164
- if (id === free) continue;
165
- if (visited.has(id)) invalidBiff("CFB directory cycle");
166
- context.retain?.(96);
167
- visited.add(id);
168
- const entry = entries[id];
169
- if (!entry || !entry.type || entry.type === 5) invalidBiff("invalid CFB directory reference");
170
- pending.push(entry.left, entry.right);
171
- if (entry.type !== 2) continue;
172
- if (result.has(entry.name)) invalidBiff("duplicate CFB stream name");
173
- const mini = entry.size < 4096;
174
- context.retain?.(64);
175
- result.set(entry.name, collect(
176
- chain(entry.start, Math.ceil(entry.size / (mini ? 64 : sectorSize)), mini ? nextMini : nextFat),
177
- entry.size,
178
- mini ? 64 : sectorSize,
179
- mini ? miniSector : sector
180
- ));
181
- }
182
- return result;
183
- }
184
- function readBiffRecords(bytes, context) {
185
- const binary = new Binary(bytes), records = [];
186
- for (let offset = 0; offset < bytes.length; ) {
187
- context.signal.throwIfAborted();
188
- binary.check(offset, 4);
189
- const opcode = binary.u16(offset), length = binary.u16(offset + 2);
190
- if (opcode === 0 && length === 0 && bytes.subarray(offset).every((byte) => byte === 0)) break;
191
- if (records.length >= (context.limits.workbookNodes ?? context.limits.inputBytes))
192
- throw new SsconvertError("resource-limit", "ssconvert BIFF record limit exceeded");
193
- records.push({ opcode, offset, data: new Binary(binary.slice(offset + 4, length)) });
194
- offset += length + 4;
195
- }
196
- return records;
197
- }
198
-
199
- export {
200
- invalidBiff,
201
- Binary,
202
- isCfb,
203
- readCfb,
204
- readBiffRecords
205
- };