@particle-academy/holy-sheet 2.10.0 → 2.10.2

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/dist/index.cjs CHANGED
@@ -242,12 +242,12 @@ function parseRows(csv, delimiter, quote2) {
242
242
  // src/workbook/cell-address.ts
243
243
  var CellAddress = {
244
244
  /** 0-based column index → Excel-style letters. */
245
- letter(index) {
246
- if (index < 0) {
247
- throw new Error(`[holy-sheet] column index must be \u2265 0, got ${index}`);
245
+ letter(index2) {
246
+ if (index2 < 0) {
247
+ throw new Error(`[holy-sheet] column index must be \u2265 0, got ${index2}`);
248
248
  }
249
249
  let letters = "";
250
- let n = index;
250
+ let n = index2;
251
251
  do {
252
252
  letters = String.fromCharCode(65 + n % 26) + letters;
253
253
  n = Math.floor(n / 26) - 1;
@@ -300,8 +300,8 @@ var ColumnWidths = {
300
300
  return Number.isInteger(key) && key >= 0 && key <= ColumnWidths.MAX_INDEX ? key : null;
301
301
  }
302
302
  if (typeof key === "string" && /^[0-9]{1,5}$/.test(key)) {
303
- const index = parseInt(key, 10);
304
- return index <= ColumnWidths.MAX_INDEX ? index : null;
303
+ const index2 = parseInt(key, 10);
304
+ return index2 <= ColumnWidths.MAX_INDEX ? index2 : null;
305
305
  }
306
306
  return null;
307
307
  },
@@ -560,6 +560,14 @@ var DateConverter = {
560
560
  };
561
561
 
562
562
  // src/exceptions.ts
563
+ var ArchiveRefused = class _ArchiveRefused extends Error {
564
+ constructor(reason) {
565
+ super(`Source archive refused: ${reason}`);
566
+ this.reason = reason;
567
+ this.name = "ArchiveRefused";
568
+ Object.setPrototypeOf(this, _ArchiveRefused.prototype);
569
+ }
570
+ };
563
571
  var SchemaException = class _SchemaException extends Error {
564
572
  constructor(errors, message) {
565
573
  super(message ?? summarize(errors));
@@ -800,23 +808,23 @@ var Normalizer = class {
800
808
  for (const [colIdx, columnDef] of (sheet.columns ?? []).entries()) {
801
809
  if (columnDef === null || typeof columnDef !== "object") continue;
802
810
  if (columnDef.width === void 0) continue;
803
- const index = ColumnWidths.index(colIdx);
811
+ const index2 = ColumnWidths.index(colIdx);
804
812
  const width = ColumnWidths.width(columnDef.width);
805
- if (index === null || width === null) continue;
806
- out.set(index, width);
813
+ if (index2 === null || width === null) continue;
814
+ out.set(index2, width);
807
815
  }
808
- for (const [index, width] of this.normalizeColumnWidths(sheet.columnWidths ?? {})) {
809
- out.set(index, width);
816
+ for (const [index2, width] of this.normalizeColumnWidths(sheet.columnWidths ?? {})) {
817
+ out.set(index2, width);
810
818
  }
811
819
  return new Map([...out.entries()].sort((a, b) => a[0] - b[0]));
812
820
  }
813
821
  normalizeColumnWidths(widths) {
814
822
  const out = /* @__PURE__ */ new Map();
815
823
  for (const [key, px] of Object.entries(widths)) {
816
- const index = ColumnWidths.index(key);
824
+ const index2 = ColumnWidths.index(key);
817
825
  const width = ColumnWidths.width(px);
818
- if (index === null || width === null) continue;
819
- out.set(index, width);
826
+ if (index2 === null || width === null) continue;
827
+ out.set(index2, width);
820
828
  }
821
829
  return out;
822
830
  }
@@ -928,14 +936,14 @@ function normalizePoints(points2, count) {
928
936
  const byIndex = /* @__PURE__ */ new Map();
929
937
  for (const p of Object.values(points2)) {
930
938
  const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
931
- const index = rec ? rec.index : void 0;
939
+ const index2 = rec ? rec.index : void 0;
932
940
  const color2 = rec ? rec.color : void 0;
933
941
  const m = typeof color2 === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(color2) : null;
934
- if (typeof index === "number" && Number.isInteger(index) && index >= 0 && index < count && m) {
935
- byIndex.set(index, m[1].toUpperCase());
942
+ if (typeof index2 === "number" && Number.isInteger(index2) && index2 >= 0 && index2 < count && m) {
943
+ byIndex.set(index2, m[1].toUpperCase());
936
944
  }
937
945
  }
938
- return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index, color2]) => ({ index, color: color2 }));
946
+ return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index2, color2]) => ({ index: index2, color: color2 }));
939
947
  }
940
948
  var CHART_TYPES = ["column", "bar", "line", "pie"];
941
949
  function normalizeCharts(charts, sheetName2, cells) {
@@ -1069,13 +1077,13 @@ var FormulaLinter = class {
1069
1077
  return issues;
1070
1078
  }
1071
1079
  buildIndex(wb) {
1072
- const index = /* @__PURE__ */ new Map();
1080
+ const index2 = /* @__PURE__ */ new Map();
1073
1081
  for (const sheet of wb.sheets) {
1074
1082
  for (const [address, cell] of sheet.cells) {
1075
- index.set(sheet.name + "!" + address, cell);
1083
+ index2.set(sheet.name + "!" + address, cell);
1076
1084
  }
1077
1085
  }
1078
- return index;
1086
+ return index2;
1079
1087
  }
1080
1088
  isError(v) {
1081
1089
  return typeof v === "string" && v.length > 0 && v[0] === "#";
@@ -1589,21 +1597,21 @@ var Repairer = class {
1589
1597
  const widths = {};
1590
1598
  let changed = false;
1591
1599
  for (const [key, px] of Object.entries(sheet.columnWidths)) {
1592
- let index = ColumnWidths.index(key);
1593
- if (index === null) {
1600
+ let index2 = ColumnWidths.index(key);
1601
+ if (index2 === null) {
1594
1602
  const letters = ColumnWidths.fromLetters(key);
1595
1603
  if (letters !== null) {
1596
- index = letters;
1597
- this.repairs.push(`converted '${path}.columnWidths.${key}' to column index ${index}`);
1604
+ index2 = letters;
1605
+ this.repairs.push(`converted '${path}.columnWidths.${key}' to column index ${index2}`);
1598
1606
  changed = true;
1599
1607
  }
1600
1608
  }
1601
- if (index === null || ColumnWidths.width(px) === null) {
1609
+ if (index2 === null || ColumnWidths.width(px) === null) {
1602
1610
  this.repairs.push(`dropped '${path}.columnWidths.${key}' (not a column index and a width)`);
1603
1611
  changed = true;
1604
1612
  continue;
1605
1613
  }
1606
- widths[String(index)] = px;
1614
+ widths[String(index2)] = px;
1607
1615
  }
1608
1616
  if (changed) {
1609
1617
  sheet.columnWidths = widths;
@@ -2027,14 +2035,14 @@ function validatePoints(points2, at2, values2) {
2027
2035
  const errors = [];
2028
2036
  points2.forEach((p, k) => {
2029
2037
  const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
2030
- const index = rec ? rec.index ?? null : null;
2038
+ const index2 = rec ? rec.index ?? null : null;
2031
2039
  const color2 = rec ? rec.color ?? null : null;
2032
- if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index >= count) {
2040
+ if (typeof index2 !== "number" || !Number.isInteger(index2) || index2 < 0 || index2 >= count) {
2033
2041
  errors.push(error(
2034
2042
  `${at2}[${k}].index`,
2035
2043
  "an index within values (0 to " + Math.max(0, count - 1) + ")",
2036
- typeOf(index),
2037
- index,
2044
+ typeOf(index2),
2045
+ index2,
2038
2046
  'The 0-based position of the point in its values range: "B5:B7" has points 0, 1 and 2.'
2039
2047
  ));
2040
2048
  }
@@ -2429,8 +2437,17 @@ function buildTree(t, lengths, off, num) {
2429
2437
  let i;
2430
2438
  let sum = 0;
2431
2439
  for (i = 0; i < 16; i++) t.table[i] = 0;
2432
- for (i = 0; i < num; i++) t.table[lengths[off + i]]++;
2440
+ for (i = 0; i < num; i++) {
2441
+ const length = lengths[off + i];
2442
+ if (length === void 0 || length > 15) throw new Error("holy-sheet: invalid DEFLATE tree");
2443
+ t.table[length]++;
2444
+ }
2433
2445
  t.table[0] = 0;
2446
+ let slots = 1;
2447
+ for (i = 1; i <= 15; i++) {
2448
+ slots = slots * 2 - t.table[i];
2449
+ if (slots < 0) throw new Error("holy-sheet: oversubscribed DEFLATE tree");
2450
+ }
2434
2451
  for (i = 0; i < 16; i++) {
2435
2452
  OFFS[i] = sum;
2436
2453
  sum += t.table[i];
@@ -2450,6 +2467,7 @@ var Reader = class {
2450
2467
  };
2451
2468
  function getBit(d) {
2452
2469
  if (d.bitcount-- === 0) {
2470
+ if (d.index >= d.source.length) throw new Error("holy-sheet: truncated DEFLATE input");
2453
2471
  d.tag = d.source[d.index++];
2454
2472
  d.bitcount = 7;
2455
2473
  }
@@ -2470,6 +2488,7 @@ function decodeSymbol(d, t) {
2470
2488
  do {
2471
2489
  cur = 2 * cur + getBit(d);
2472
2490
  len++;
2491
+ if (len > 15) throw new Error("holy-sheet: invalid DEFLATE code");
2473
2492
  sum += t.table[len];
2474
2493
  cur -= t.table[len];
2475
2494
  } while (cur >= 0);
@@ -2480,6 +2499,7 @@ function decodeTrees(d, lt, dt) {
2480
2499
  const hlit = readBits(d, 5, 257);
2481
2500
  const hdist = readBits(d, 5, 1);
2482
2501
  const hclen = readBits(d, 4, 4);
2502
+ if (hlit > 286) throw new Error("holy-sheet: invalid DEFLATE literal count");
2483
2503
  let i;
2484
2504
  for (i = 0; i < 19; i++) lengths[i] = 0;
2485
2505
  for (i = 0; i < hclen; i++) {
@@ -2492,32 +2512,44 @@ function decodeTrees(d, lt, dt) {
2492
2512
  const sym = decodeSymbol(d, codeTree);
2493
2513
  switch (sym) {
2494
2514
  case 16: {
2515
+ if (num === 0) throw new Error("holy-sheet: invalid DEFLATE repeat");
2495
2516
  const prev = lengths[num - 1];
2496
- for (let length = readBits(d, 2, 3); length; length--) lengths[num++] = prev;
2517
+ const repeat = readBits(d, 2, 3);
2518
+ if (num + repeat > hlit + hdist) throw new Error("holy-sheet: invalid DEFLATE repeat");
2519
+ for (let length = repeat; length; length--) lengths[num++] = prev;
2497
2520
  break;
2498
2521
  }
2499
- case 17:
2500
- for (let length = readBits(d, 3, 3); length; length--) lengths[num++] = 0;
2522
+ case 17: {
2523
+ const repeat = readBits(d, 3, 3);
2524
+ if (num + repeat > hlit + hdist) throw new Error("holy-sheet: invalid DEFLATE repeat");
2525
+ for (let length = repeat; length; length--) lengths[num++] = 0;
2501
2526
  break;
2502
- case 18:
2503
- for (let length = readBits(d, 7, 11); length; length--) lengths[num++] = 0;
2527
+ }
2528
+ case 18: {
2529
+ const repeat = readBits(d, 7, 11);
2530
+ if (num + repeat > hlit + hdist) throw new Error("holy-sheet: invalid DEFLATE repeat");
2531
+ for (let length = repeat; length; length--) lengths[num++] = 0;
2504
2532
  break;
2533
+ }
2505
2534
  default:
2506
2535
  lengths[num++] = sym;
2507
2536
  break;
2508
2537
  }
2509
2538
  }
2539
+ if (lengths[256] === 0) throw new Error("holy-sheet: missing DEFLATE end code");
2510
2540
  buildTree(lt, lengths, 0, hlit);
2511
2541
  buildTree(dt, lengths, hlit, hdist);
2512
2542
  }
2513
2543
  var Out = class {
2514
- constructor() {
2515
- this.buf = new Uint8Array(1024);
2544
+ constructor(limit) {
2545
+ this.limit = limit;
2516
2546
  this.len = 0;
2547
+ this.buf = new Uint8Array(Math.min(1024, limit));
2517
2548
  }
2518
2549
  push(b) {
2550
+ if (this.len >= this.limit) throw new Error("holy-sheet: DEFLATE output limit exceeded");
2519
2551
  if (this.len >= this.buf.length) {
2520
- const next = new Uint8Array(this.buf.length * 2);
2552
+ const next = new Uint8Array(Math.min(this.limit, Math.max(1, this.buf.length * 2)));
2521
2553
  next.set(this.buf);
2522
2554
  this.buf = next;
2523
2555
  }
@@ -2532,9 +2564,12 @@ function inflateBlockData(d, out, lt, dt) {
2532
2564
  out.push(sym);
2533
2565
  } else {
2534
2566
  const lengthSym = sym - 257;
2567
+ if (lengthSym < 0 || lengthSym >= LENGTH_BITS.length) throw new Error("holy-sheet: invalid DEFLATE length");
2535
2568
  const length = readBits(d, LENGTH_BITS[lengthSym], LENGTH_BASE[lengthSym]);
2536
2569
  const distSym = decodeSymbol(d, dt);
2570
+ if (distSym < 0 || distSym >= DIST_BITS.length) throw new Error("holy-sheet: invalid DEFLATE distance");
2537
2571
  const dist = readBits(d, DIST_BITS[distSym], DIST_BASE[distSym]);
2572
+ if (dist > out.len) throw new Error("holy-sheet: DEFLATE distance before output");
2538
2573
  const offs = out.len - dist;
2539
2574
  for (let i = 0; i < length; i++) out.push(out.buf[offs + i]);
2540
2575
  }
@@ -2542,13 +2577,20 @@ function inflateBlockData(d, out, lt, dt) {
2542
2577
  }
2543
2578
  function inflateUncompressedBlock(d, out) {
2544
2579
  d.bitcount = 0;
2580
+ if (d.index + 4 > d.source.length) throw new Error("holy-sheet: truncated DEFLATE input");
2545
2581
  let length = d.source[d.index + 1] * 256 + d.source[d.index];
2582
+ const complement = d.source[d.index + 3] * 256 + d.source[d.index + 2];
2583
+ if ((length ^ complement) !== 65535) throw new Error("holy-sheet: invalid DEFLATE stored length");
2546
2584
  d.index += 4;
2585
+ if (d.index + length > d.source.length) throw new Error("holy-sheet: truncated DEFLATE input");
2547
2586
  for (; length; length--) out.push(d.source[d.index++]);
2548
2587
  }
2549
- function inflateRaw(source) {
2588
+ function inflateRaw(source, maxOutputBytes = Number.POSITIVE_INFINITY) {
2589
+ if (maxOutputBytes !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 0)) {
2590
+ throw new Error("holy-sheet: invalid DEFLATE output limit");
2591
+ }
2550
2592
  const d = new Reader(source);
2551
- const out = new Out();
2593
+ const out = new Out(maxOutputBytes);
2552
2594
  const lt = new Tree();
2553
2595
  const dt = new Tree();
2554
2596
  let bfinal;
@@ -2570,41 +2612,166 @@ function inflateRaw(source) {
2570
2612
  return out.buf.subarray(0, out.len);
2571
2613
  }
2572
2614
 
2573
- // src/zip/zip-reader.ts
2574
- var decoder = new TextDecoder();
2575
- function unzipSync(data) {
2576
- const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
2577
- let eocd = -1;
2578
- for (let i = data.length - 22; i >= 0; i--) {
2579
- if (dv.getUint32(i, true) === 101010256) {
2580
- eocd = i;
2615
+ // src/zip/source-archive.ts
2616
+ var DEFAULT_ARCHIVE_LIMITS = Object.freeze({
2617
+ maxArchiveBytes: 67108864,
2618
+ maxEntries: 16384,
2619
+ maxPartBytes: 67108864,
2620
+ maxExpandedBytes: 268435456,
2621
+ maxCompressionRatio: 1e3
2622
+ });
2623
+ var corrupt = () => {
2624
+ throw new ArchiveRefused("archive_corrupt");
2625
+ };
2626
+ var SourceArchive = class _SourceArchive {
2627
+ constructor(original, parts) {
2628
+ this.original = original;
2629
+ this.parts = parts;
2630
+ }
2631
+ static open(input, options = {}) {
2632
+ for (const [key, value] of Object.entries(options)) {
2633
+ if (!Object.prototype.hasOwnProperty.call(DEFAULT_ARCHIVE_LIMITS, key) || !Number.isSafeInteger(value) || value < 1) throw new ArchiveRefused("archive_limits");
2634
+ }
2635
+ const limits = { ...DEFAULT_ARCHIVE_LIMITS, ...options };
2636
+ if (input.length > limits.maxArchiveBytes) throw new ArchiveRefused("archive_limit");
2637
+ const bytes = input.slice();
2638
+ const records = index(bytes, limits);
2639
+ const parts = /* @__PURE__ */ new Map();
2640
+ for (const record of records) {
2641
+ const compressed = bytes.subarray(record.start, record.start + record.compressed);
2642
+ let payload;
2643
+ try {
2644
+ payload = record.method === 0 ? compressed.slice() : inflateRaw(compressed, record.size + 1);
2645
+ } catch {
2646
+ throw new ArchiveRefused("archive_corrupt");
2647
+ }
2648
+ if (payload.length !== record.size || crc32(payload) !== record.crc) corrupt();
2649
+ parts.set(record.name, payload);
2650
+ }
2651
+ return new _SourceArchive(bytes, parts);
2652
+ }
2653
+ names() {
2654
+ return [...this.parts.keys()];
2655
+ }
2656
+ read(name) {
2657
+ return this.parts.get(name)?.slice() ?? null;
2658
+ }
2659
+ toBytes() {
2660
+ return this.original.slice();
2661
+ }
2662
+ };
2663
+ function index(bytes, limits) {
2664
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2665
+ const bounds = (at3, size, end2 = bytes.length) => {
2666
+ if (at3 < 0 || size < 0 || at3 + size > end2 || at3 + size > bytes.length) corrupt();
2667
+ };
2668
+ const u16 = (at3) => {
2669
+ bounds(at3, 2);
2670
+ return view.getUint16(at3, true);
2671
+ };
2672
+ const u32 = (at3) => {
2673
+ bounds(at3, 4);
2674
+ return view.getUint32(at3, true);
2675
+ };
2676
+ let end = -1;
2677
+ for (let at3 = bytes.length - 22; at3 >= Math.max(0, bytes.length - 65557); at3--) {
2678
+ if (u32(at3) === 101010256 && at3 + 22 + u16(at3 + 20) === bytes.length && u32(at3 + 16) + u32(at3 + 12) === at3) {
2679
+ end = at3;
2581
2680
  break;
2582
2681
  }
2583
2682
  }
2584
- if (eocd < 0) throw new Error("holy-sheet: not a zip archive (no EOCD record)");
2585
- const count = dv.getUint16(eocd + 10, true);
2586
- let cd = dv.getUint32(eocd + 16, true);
2587
- const result = {};
2588
- for (let n = 0; n < count; n++) {
2589
- if (dv.getUint32(cd, true) !== 33639248) break;
2590
- const method = dv.getUint16(cd + 10, true);
2591
- const compSize = dv.getUint32(cd + 20, true);
2592
- const nameLen = dv.getUint16(cd + 28, true);
2593
- const extraLen = dv.getUint16(cd + 30, true);
2594
- const commentLen = dv.getUint16(cd + 32, true);
2595
- const localOffset = dv.getUint32(cd + 42, true);
2596
- const name = decoder.decode(data.subarray(cd + 46, cd + 46 + nameLen));
2597
- const lhNameLen = dv.getUint16(localOffset + 26, true);
2598
- const lhExtraLen = dv.getUint16(localOffset + 28, true);
2599
- const dataStart = localOffset + 30 + lhNameLen + lhExtraLen;
2600
- const raw = data.subarray(dataStart, dataStart + compSize);
2601
- let content;
2602
- if (method === 0) content = raw.slice();
2603
- else if (method === 8) content = inflateRaw(raw);
2604
- else throw new Error(`holy-sheet: unsupported zip method ${method} for "${name}"`);
2605
- result[name] = content;
2606
- cd += 46 + nameLen + extraLen + commentLen;
2683
+ if (end === -1) corrupt();
2684
+ const count = u16(end + 10), directory = u32(end + 16);
2685
+ if (u16(end + 4) !== 0 || u16(end + 6) !== 0 || u16(end + 8) !== count || count === 65535) throw new ArchiveRefused("archive_unsupported");
2686
+ if (count > limits.maxEntries) throw new ArchiveRefused("archive_limit");
2687
+ if (end - directory < count * 46) corrupt();
2688
+ const records = [], seen = /* @__PURE__ */ new Set(), regions = [];
2689
+ let at2 = directory, expanded = 0;
2690
+ const extra = (start, length) => {
2691
+ const finish = start + length;
2692
+ while (start < finish) {
2693
+ bounds(start, 4, finish);
2694
+ const id = u16(start), size = u16(start + 2);
2695
+ bounds(start + 4, size, finish);
2696
+ if (id === 1 || id === 39169) throw new ArchiveRefused("archive_unsupported");
2697
+ start += 4 + size;
2698
+ }
2699
+ };
2700
+ for (let i = 0; i < count; i++) {
2701
+ bounds(at2, 46, end);
2702
+ if (u32(at2) !== 33639248) corrupt();
2703
+ const flags = u16(at2 + 8), method = u16(at2 + 10), crc = u32(at2 + 16);
2704
+ const compressed = u32(at2 + 20), size = u32(at2 + 24), local = u32(at2 + 42);
2705
+ const nameLength = u16(at2 + 28), extraLength = u16(at2 + 30), commentLength = u16(at2 + 32);
2706
+ if ((flags & -2063) !== 0 || ![0, 8].includes(method) || method === 0 && (flags & 6) !== 0 || u16(at2 + 34) !== 0 || [compressed, size, local].includes(4294967295) || u16(at2 + 6) > 20) throw new ArchiveRefused("archive_unsupported");
2707
+ bounds(at2 + 46, nameLength + extraLength + commentLength, end);
2708
+ const name = partName(bytes.subarray(at2 + 46, at2 + 46 + nameLength), flags);
2709
+ if (seen.has(name)) throw new ArchiveRefused("archive_duplicate");
2710
+ seen.add(name);
2711
+ expanded += size;
2712
+ if (size > limits.maxPartBytes || expanded > limits.maxExpandedBytes || size > Math.max(1, compressed) * limits.maxCompressionRatio) throw new ArchiveRefused("archive_limit");
2713
+ if (method === 0 && compressed !== size) corrupt();
2714
+ extra(at2 + 46 + nameLength, extraLength);
2715
+ bounds(local, 30, directory);
2716
+ if (u32(local) !== 67324752 || u16(local + 4) !== u16(at2 + 6) || u16(local + 6) !== flags || u16(local + 8) !== method) corrupt();
2717
+ const localName2 = u16(local + 26), localExtra = u16(local + 28);
2718
+ const start = local + 30 + localName2 + localExtra;
2719
+ bounds(local + 30, localName2 + localExtra + compressed, directory);
2720
+ const rawName = bytes.subarray(local + 30, local + 30 + localName2);
2721
+ const centralName = bytes.subarray(at2 + 46, at2 + 46 + nameLength);
2722
+ if (rawName.length !== centralName.length || !rawName.every((byte, pos) => byte === centralName[pos])) corrupt();
2723
+ extra(local + 30 + localName2, localExtra);
2724
+ for (const [offset, expected] of [[14, crc], [18, compressed], [22, size]]) {
2725
+ const value = u32(local + offset);
2726
+ if (value !== expected && !((flags & 8) !== 0 && value === 0)) corrupt();
2727
+ }
2728
+ let finish = start + compressed;
2729
+ if ((flags & 8) !== 0) {
2730
+ let found = false;
2731
+ for (const prefix of [0, 4]) {
2732
+ if (finish + prefix + 12 <= directory && (prefix === 0 || u32(finish) === 134695760) && u32(finish + prefix) === crc && u32(finish + prefix + 4) === compressed && u32(finish + prefix + 8) === size) {
2733
+ finish += prefix + 12;
2734
+ found = true;
2735
+ break;
2736
+ }
2737
+ }
2738
+ if (!found) corrupt();
2739
+ }
2740
+ records.push({ name, method, crc, size, compressed, start });
2741
+ regions.push([local, finish]);
2742
+ at2 += 46 + nameLength + extraLength + commentLength;
2743
+ }
2744
+ if (at2 !== end) corrupt();
2745
+ regions.sort((a, b) => a[0] - b[0]);
2746
+ let previous = 0;
2747
+ for (const [start, finish] of regions) {
2748
+ if (start < previous) corrupt();
2749
+ previous = finish;
2750
+ }
2751
+ return records;
2752
+ }
2753
+ function partName(bytes, flags) {
2754
+ let name;
2755
+ try {
2756
+ name = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
2757
+ } catch {
2758
+ throw new ArchiveRefused("archive_path");
2607
2759
  }
2760
+ if (name === "" || name.startsWith("/") || name.includes(String.fromCharCode(92)) || name.includes(":")) throw new ArchiveRefused("archive_path");
2761
+ for (const byte of bytes) {
2762
+ if (byte < 32 || byte === 127) throw new ArchiveRefused("archive_path");
2763
+ if (byte > 127 && (flags & 2048) === 0) throw new ArchiveRefused("archive_unsupported");
2764
+ }
2765
+ const parts = (name.endsWith("/") ? name.slice(0, -1) : name).split("/");
2766
+ if (parts.some((part2) => part2 === "" || part2 === "." || part2 === "..")) throw new ArchiveRefused("archive_path");
2767
+ return name;
2768
+ }
2769
+
2770
+ // src/zip/zip-reader.ts
2771
+ function unzipSync(data, limits = {}) {
2772
+ const source = SourceArchive.open(data, limits);
2773
+ const result = /* @__PURE__ */ Object.create(null);
2774
+ for (const name of source.names()) result[name] = source.read(name);
2608
2775
  return result;
2609
2776
  }
2610
2777
 
@@ -2652,23 +2819,56 @@ var ODS_MIMETYPES = /* @__PURE__ */ new Set([
2652
2819
  "application/vnd.oasis.opendocument.spreadsheet",
2653
2820
  "application/vnd.oasis.opendocument.spreadsheet-template"
2654
2821
  ]);
2655
- var decoder2 = new TextDecoder();
2822
+ var decoder = new TextDecoder();
2656
2823
  var FormatSniffer = {
2657
2824
  sniff(bytes, path = null) {
2658
2825
  let files;
2659
2826
  try {
2660
2827
  files = unzipSync(bytes);
2661
2828
  } catch (e) {
2829
+ if (e instanceof ArchiveRefused && (e.reason !== "archive_corrupt" || bytes[0] === 80 && bytes[1] === 75)) throw e;
2662
2830
  if (e instanceof Error && e.message.includes("unsupported zip method")) throw e;
2663
2831
  throw UnsupportedFormatException.notAZip(path);
2664
2832
  }
2665
- const mimetype = files["mimetype"] ? phpTrim(decoder2.decode(files["mimetype"])) : null;
2833
+ const mimetype = files["mimetype"] ? phpTrim(decoder.decode(files["mimetype"])) : null;
2666
2834
  if (mimetype !== null && ODS_MIMETYPES.has(mimetype)) return { format: "ods", files };
2667
2835
  if (files["xl/workbook.xml"]) return { format: "xlsx", files };
2668
2836
  throw UnsupportedFormatException.unknownPackage(path, mimetype);
2669
2837
  }
2670
2838
  };
2671
2839
 
2840
+ // src/reader/file-input.ts
2841
+ async function readFileBounded(path) {
2842
+ const fs = await import('fs');
2843
+ if (!fs.statSync(path).isFile()) throw new ArchiveRefused("archive_path");
2844
+ const fd = fs.openSync(path, "r");
2845
+ const limit = DEFAULT_ARCHIVE_LIMITS.maxArchiveBytes;
2846
+ try {
2847
+ const stat = fs.fstatSync(fd);
2848
+ if (!stat.isFile()) throw new ArchiveRefused("archive_path");
2849
+ if (stat.size > limit) throw new ArchiveRefused("archive_limit");
2850
+ const chunks = [];
2851
+ let total = 0;
2852
+ for (; ; ) {
2853
+ const chunk = new Uint8Array(Math.min(65536, limit + 1 - total));
2854
+ const count = fs.readSync(fd, chunk, 0, chunk.length, null);
2855
+ if (count === 0) break;
2856
+ if (total + count > limit) throw new ArchiveRefused("archive_limit");
2857
+ chunks.push(chunk.subarray(0, count));
2858
+ total += count;
2859
+ }
2860
+ const bytes = new Uint8Array(total);
2861
+ let at2 = 0;
2862
+ for (const chunk of chunks) {
2863
+ bytes.set(chunk, at2);
2864
+ at2 += chunk.length;
2865
+ }
2866
+ return bytes;
2867
+ } finally {
2868
+ fs.closeSync(fd);
2869
+ }
2870
+ }
2871
+
2672
2872
  // src/reader/ods/ods-formula.ts
2673
2873
  function odsFormulaToA1(formula2) {
2674
2874
  let grammar = "of";
@@ -3448,7 +3648,7 @@ function at(node, name) {
3448
3648
  var MAX_ROWS = 1048576;
3449
3649
  var MAX_COLUMNS = 16384;
3450
3650
  var UNIX_EPOCH_SERIAL = 25569;
3451
- var decoder3 = new TextDecoder();
3651
+ var decoder2 = new TextDecoder();
3452
3652
  var OdsReader = class {
3453
3653
  describe(bytes) {
3454
3654
  return WorkbookSchema.fromWorkbook(this.readWorkbook(bytes));
@@ -3458,7 +3658,7 @@ var OdsReader = class {
3458
3658
  }
3459
3659
  /** Read an already-unzipped package (name -> bytes). */
3460
3660
  readFiles(files) {
3461
- const getText = (name) => files[name] ? decoder3.decode(files[name]) : null;
3661
+ const getText = (name) => files[name] ? decoder2.decode(files[name]) : null;
3462
3662
  const content = getText("content.xml");
3463
3663
  if (content === null) throw new Error("[holy-sheet] missing content.xml");
3464
3664
  const contentDoc = parseXml(content, { namespaces: true });
@@ -3820,7 +4020,7 @@ function points(ser) {
3820
4020
  byIndex.set(parseInt(idx, 10), color2);
3821
4021
  }
3822
4022
  }
3823
- return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index, color2]) => ({ index, color: color2 }));
4023
+ return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index2, color2]) => ({ index: index2, color: color2 }));
3824
4024
  }
3825
4025
  function seriesColor(ser) {
3826
4026
  const spPr = el(ser, "spPr");
@@ -4128,7 +4328,7 @@ function parseFrozen(xml) {
4128
4328
  }
4129
4329
 
4130
4330
  // src/reader/xlsx-reader.ts
4131
- var decoder4 = new TextDecoder();
4331
+ var decoder3 = new TextDecoder();
4132
4332
  var XlsxReader = class {
4133
4333
  describe(bytes) {
4134
4334
  return WorkbookSchema.fromWorkbook(this.readWorkbook(bytes));
@@ -4138,7 +4338,7 @@ var XlsxReader = class {
4138
4338
  }
4139
4339
  /** Read an already-unzipped package (name -> bytes), so a caller that sniffed it does not unzip twice. */
4140
4340
  readFiles(files) {
4141
- const getText = (name) => files[name] ? decoder4.decode(files[name]) : null;
4341
+ const getText = (name) => files[name] ? decoder3.decode(files[name]) : null;
4142
4342
  const stylesXml = getText("xl/styles.xml");
4143
4343
  const stylesIndex = stylesXml ? StylesParser.parse(stylesXml) : [];
4144
4344
  const sharedStringsXml = getText("xl/sharedStrings.xml");
@@ -4479,7 +4679,8 @@ var ChartXml = {
4479
4679
  } else {
4480
4680
  plot = `<c:barChart><c:barDir val="${type === "bar" ? "bar" : "col"}"/><c:grouping val="clustered"/><c:varyColors val="0"/>` + series + labels + '<c:gapWidth val="80"/><c:axId val="10"/><c:axId val="20"/></c:barChart>';
4481
4681
  }
4482
- const axesXml = type === "pie" ? "" : axes(type === "bar", chart.format);
4682
+ const zeroBase = type !== "line" && nonNegative(chart, workbook);
4683
+ const axesXml = type === "pie" ? "" : axes(type === "bar", chart.format, zeroBase);
4483
4684
  const legend = chart.legend ?? (chart.series.length > 1 || type === "pie");
4484
4685
  const title = chart.title !== null ? `<c:title><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:pPr><a:defRPr sz="1200" b="1"/></a:pPr><a:r><a:rPr lang="en-US" sz="1200" b="1"/><a:t>${text(chart.title)}</a:t></a:r></a:p></c:rich></c:tx><c:overlay val="0"/></c:title><c:autoTitleDeleted val="0"/>` : '<c:autoTitleDeleted val="1"/>';
4485
4686
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><c:chartSpace xmlns:c="${NS_C}" xmlns:a="${NS_A}" xmlns:r="${NS_R}"><c:roundedCorners val="0"/><c:chart>` + title + "<c:plotArea><c:layout/>" + plot + axesXml + "</c:plotArea>" + (legend ? '<c:legend><c:legendPos val="b"/><c:overlay val="0"/></c:legend>' : "") + '<c:plotVisOnly val="1"/><c:dispBlanksAs val="gap"/></c:chart><c:spPr><a:noFill/><a:ln><a:noFill/></a:ln></c:spPr>' + textProps("595959", 900) + "</c:chartSpace>";
@@ -4540,11 +4741,14 @@ function dataLabels(type, format) {
4540
4741
  const pos = type === "line" ? "t" : type === "pie" ? "bestFit" : "outEnd";
4541
4742
  return "<c:dLbls>" + (format !== null ? `<c:numFmt formatCode="${attr2(format)}" sourceLinked="0"/>` : "") + "<c:spPr><a:noFill/><a:ln><a:noFill/></a:ln></c:spPr>" + textProps("404040", 900) + `<c:dLblPos val="${pos}"/><c:showLegendKey val="0"/><c:showVal val="1"/><c:showCatName val="0"/><c:showSerName val="0"/><c:showPercent val="0"/><c:showBubbleSize val="0"/></c:dLbls>`;
4542
4743
  }
4543
- function axes(horizontal, format) {
4744
+ function nonNegative(chart, workbook) {
4745
+ return chart.series.every((s) => values(s.values, workbook).every((v) => typeof v !== "number" || !(v < 0)));
4746
+ }
4747
+ function axes(horizontal, format, zeroBase = false) {
4544
4748
  const catPos = horizontal ? "l" : "b";
4545
4749
  const valPos = horizontal ? "b" : "l";
4546
4750
  const numFmt = format !== null ? `<c:numFmt formatCode="${attr2(format)}" sourceLinked="0"/>` : '<c:numFmt formatCode="General" sourceLinked="1"/>';
4547
- return `<c:catAx><c:axId val="10"/><c:scaling><c:orientation val="${horizontal ? "maxMin" : "minMax"}"/></c:scaling><c:delete val="0"/><c:axPos val="${catPos}"/><c:numFmt formatCode="General" sourceLinked="1"/><c:majorTickMark val="none"/><c:minorTickMark val="none"/><c:tickLblPos val="low"/><c:spPr><a:ln w="9525"><a:solidFill><a:srgbClr val="BFBFBF"/></a:solidFill></a:ln></c:spPr><c:crossAx val="20"/><c:crosses val="autoZero"/><c:auto val="1"/><c:lblAlgn val="ctr"/><c:lblOffset val="100"/><c:noMultiLvlLbl val="0"/></c:catAx><c:valAx><c:axId val="20"/><c:scaling><c:orientation val="minMax"/></c:scaling><c:delete val="0"/><c:axPos val="${valPos}"/><c:majorGridlines><c:spPr><a:ln w="9525"><a:solidFill><a:srgbClr val="E5E7EB"/></a:solidFill></a:ln></c:spPr></c:majorGridlines>` + numFmt + `<c:majorTickMark val="none"/><c:minorTickMark val="none"/><c:tickLblPos val="nextTo"/><c:spPr><a:ln><a:noFill/></a:ln></c:spPr><c:crossAx val="10"/><c:crosses val="${horizontal ? "max" : "autoZero"}"/><c:crossBetween val="between"/></c:valAx>`;
4751
+ return `<c:catAx><c:axId val="10"/><c:scaling><c:orientation val="${horizontal ? "maxMin" : "minMax"}"/></c:scaling><c:delete val="0"/><c:axPos val="${catPos}"/><c:numFmt formatCode="General" sourceLinked="1"/><c:majorTickMark val="none"/><c:minorTickMark val="none"/><c:tickLblPos val="low"/><c:spPr><a:ln w="9525"><a:solidFill><a:srgbClr val="BFBFBF"/></a:solidFill></a:ln></c:spPr><c:crossAx val="20"/><c:crosses val="autoZero"/><c:auto val="1"/><c:lblAlgn val="ctr"/><c:lblOffset val="100"/><c:noMultiLvlLbl val="0"/></c:catAx><c:valAx><c:axId val="20"/><c:scaling><c:orientation val="minMax"/>` + (zeroBase ? '<c:min val="0"/>' : "") + `</c:scaling><c:delete val="0"/><c:axPos val="${valPos}"/><c:majorGridlines><c:spPr><a:ln w="9525"><a:solidFill><a:srgbClr val="E5E7EB"/></a:solidFill></a:ln></c:spPr></c:majorGridlines>` + numFmt + `<c:majorTickMark val="none"/><c:minorTickMark val="none"/><c:tickLblPos val="nextTo"/><c:spPr><a:ln><a:noFill/></a:ln></c:spPr><c:crossAx val="10"/><c:crosses val="${horizontal ? "max" : "autoZero"}"/><c:crossBetween val="between"/></c:valAx>`;
4548
4752
  }
4549
4753
  function textProps(color2, size) {
4550
4754
  return `<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr><a:defRPr sz="${size}"><a:solidFill><a:srgbClr val="${color2}"/></a:solidFill></a:defRPr></a:pPr><a:endParaRPr lang="en-US"/></a:p></c:txPr>`;
@@ -5004,11 +5208,11 @@ var SheetReducer = {
5004
5208
  if (!isArr(get(op, "sheet"))) {
5005
5209
  return schema;
5006
5210
  }
5007
- const index = has(op, "index") ? phpInteger(op.index) : sheets.length;
5008
- if (index === null) {
5211
+ const index2 = has(op, "index") ? phpInteger(op.index) : sheets.length;
5212
+ if (index2 === null) {
5009
5213
  return schema;
5010
5214
  }
5011
- sheets.splice(Math.max(0, Math.min(sheets.length, index)), 0, op.sheet);
5215
+ sheets.splice(Math.max(0, Math.min(sheets.length, index2)), 0, op.sheet);
5012
5216
  return { ...schema, sheets };
5013
5217
  }
5014
5218
  const at2 = find(sheets, phpString(get(op, "sheet") ?? ""));
@@ -5184,11 +5388,11 @@ function shiftColumns(sheet, at2, delta) {
5184
5388
  });
5185
5389
  if (isArr(get(sheet, "columnWidths"))) {
5186
5390
  const widths = /* @__PURE__ */ new Map();
5187
- for (const [index, width] of entriesOf(sheet.columnWidths)) {
5188
- if (!isIndexKey(index)) {
5391
+ for (const [index2, width] of entriesOf(sheet.columnWidths)) {
5392
+ if (!isIndexKey(index2)) {
5189
5393
  continue;
5190
5394
  }
5191
- const i = phpInt2(index);
5395
+ const i = phpInt2(index2);
5192
5396
  const number = i + 1;
5193
5397
  if (number < at2) {
5194
5398
  widths.set(i, width);
@@ -5197,8 +5401,8 @@ function shiftColumns(sheet, at2, delta) {
5197
5401
  }
5198
5402
  }
5199
5403
  const sorted = {};
5200
- for (const [index, width] of [...widths.entries()].sort((x, y) => x[0] - y[0])) {
5201
- sorted[index] = width;
5404
+ for (const [index2, width] of [...widths.entries()].sort((x, y) => x[0] - y[0])) {
5405
+ sorted[index2] = width;
5202
5406
  }
5203
5407
  sheet = setOrUnset(sheet, "columnWidths", sorted, EMPTY);
5204
5408
  }
@@ -5461,22 +5665,22 @@ function granular(a, b) {
5461
5665
  ops.push({ type: "rename_sheet", sheet: name, name: renames.get(name) });
5462
5666
  }
5463
5667
  }
5464
- namesB.forEach((name, index) => {
5668
+ namesB.forEach((name, index2) => {
5465
5669
  if (unmatchedAdded.includes(name)) {
5466
- ops.push({ type: "add_sheet", index, sheet: sheetsB[index] });
5670
+ ops.push({ type: "add_sheet", index: index2, sheet: sheetsB[index2] });
5467
5671
  }
5468
5672
  });
5469
5673
  let state = SheetReducer.applyAll(a, ops);
5470
- namesB.forEach((name, index) => {
5674
+ namesB.forEach((name, index2) => {
5471
5675
  const current = new Map(entriesOf(get(state, "sheets") ?? []).map(([key, sheet]) => [key, sheetName(sheet)]));
5472
- if ((current.get(String(index)) ?? null) !== name) {
5473
- const op = { type: "move_sheet", sheet: name, toIndex: index };
5676
+ if ((current.get(String(index2)) ?? null) !== name) {
5677
+ const op = { type: "move_sheet", sheet: name, toIndex: index2 };
5474
5678
  ops.push(op);
5475
5679
  state = SheetReducer.apply(state, op);
5476
5680
  }
5477
5681
  });
5478
- sheetsB.forEach((target, index) => {
5479
- const current = state.sheets[index];
5682
+ sheetsB.forEach((target, index2) => {
5683
+ const current = state.sheets[index2];
5480
5684
  if (SheetDiff.same(current, target)) {
5481
5685
  return;
5482
5686
  }
@@ -6127,7 +6331,7 @@ var holy_sheet_schema_default = {
6127
6331
  };
6128
6332
 
6129
6333
  // src/agent.ts
6130
- var VERSION = "2.10.0";
6334
+ var VERSION = "2.10.2";
6131
6335
  function toU8(input) {
6132
6336
  return input instanceof Uint8Array ? input : new Uint8Array(input);
6133
6337
  }
@@ -6172,8 +6376,8 @@ var Agent = {
6172
6376
  async describe(path) {
6173
6377
  const fs = await import('fs');
6174
6378
  if (!fs.existsSync(path)) return { error: "not_found", path };
6175
- const bytes = fs.readFileSync(path);
6176
- return readSpreadsheet(new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength), path);
6379
+ const bytes = await readFileBounded(path);
6380
+ return readSpreadsheet(bytes, path);
6177
6381
  },
6178
6382
  /** Validate + conservative repairs in one call. */
6179
6383
  validateAndRepair(schema) {
@@ -6291,6 +6495,7 @@ var HolySheet = class {
6291
6495
  HolySheet.VERSION = VERSION;
6292
6496
 
6293
6497
  exports.Agent = Agent;
6498
+ exports.ArchiveRefused = ArchiveRefused;
6294
6499
  exports.ArrayBuilder = ArrayBuilder;
6295
6500
  exports.CellAddress = CellAddress;
6296
6501
  exports.CsvBuilder = CsvBuilder;