import { gunzipSync } from "node:zlib"; /** A test-side view of every ustar header field the determinism rules constrain (ADR 0049). */ export interface TarEntry { readonly path: string; readonly typeflag: string; readonly mode: number; readonly uid: number; readonly gid: number; readonly uname: string; readonly gname: string; readonly mtime: number; readonly magic: string; readonly data: Buffer; } function text(block: Buffer, offset: number, length: number): string { const raw = block.subarray(offset, offset + length); const end = raw.indexOf(0); return raw.subarray(0, end === -1 ? length : end).toString("latin1"); } function octal(block: Buffer, offset: number, length: number): number { return Number.parseInt(text(block, offset, length).trim() || "0", 8); } export function readTarGz(archive: Buffer): readonly TarEntry[] { const raw = gunzipSync(archive); const entries: TarEntry[] = []; let offset = 0; while (offset + 512 <= raw.length) { const block = raw.subarray(offset, offset + 512); if (block.every((byte) => byte === 0)) { break; } const name = text(block, 0, 100); const prefix = text(block, 345, 155); const size = octal(block, 124, 12); entries.push({ data: Buffer.from(raw.subarray(offset + 512, offset + 512 + size)), gid: octal(block, 116, 8), gname: text(block, 297, 32), magic: `${text(block, 257, 6)}/${text(block, 263, 2)}`, mode: octal(block, 100, 8), mtime: octal(block, 136, 12), path: prefix === "" ? name : `${prefix}/${name}`, typeflag: text(block, 156, 1), uid: octal(block, 108, 8), uname: text(block, 265, 32), }); offset += 512 + Math.ceil(size / 512) * 512; } return entries; } export function fileEntries(archive: Buffer): Map { return new Map( readTarGz(archive) .filter((entry) => entry.typeflag === "0") .map((entry) => [entry.path, entry.data]), ); }