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