can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: truncated CFHEADER

Error message

Invalid CAB archive: truncated CFHEADER

What it means

Thrown by readCabArchive when the ByteSource is smaller than the 36-byte fixed CFHEADER, so no CAB header could possibly be present. This is the first structural check in CAB indexing — it fires on empty files, tiny files, or streams that ended before the header arrived.

Source

Thrown at packages/utils/src/ar/cab.ts:244

		this.#offset = offset;
		this.#declaredSize = size;
	}

	async read(size: number, memberPath: string): Promise<Uint8Array> {
		if (size !== this.#declaredSize) {
			throw new ArchiveError(`Invalid CAB archive: size changed while extracting '${memberPath}'`);
		}
		const folder = await this.#folder.readAll();
		const end = this.#offset + size;
		if (!Number.isSafeInteger(end) || this.#offset < 0 || end > folder.byteLength) {
			throw new ArchiveError(`Invalid CAB archive: member '${memberPath}' is outside its folder data`);
		}
		return folder.slice(this.#offset, end);
	}
}

async function readCabArchive(source: ByteSource, options: Parameters<FormatReader>[1]): Promise<ArchiveIndexEntry[]> {
	if (source.size < FIXED_HEADER_SIZE) throw new ArchiveError("Invalid CAB archive: truncated CFHEADER");
	const fixed = await readExact(source, 0, FIXED_HEADER_SIZE);
	if (!hasSignature(fixed)) throw new ArchiveError(`Invalid CAB archive: expected ${CAB_SIGNATURE} signature`);
	if (readUInt32LE(fixed, 4) !== 0 || readUInt32LE(fixed, 12) !== 0 || readUInt32LE(fixed, 20) !== 0) {
		throw new ArchiveError("Invalid CAB archive: reserved CFHEADER fields must be zero");
	}
	const cabinetSize = readUInt32LE(fixed, 8);
	if (cabinetSize < FIXED_HEADER_SIZE || cabinetSize > source.size) {
		throw new ArchiveError("Invalid CAB archive: declared cabinet size is out of bounds");
	}
	const fileTableOffset = readUInt32LE(fixed, 16);
	if (fileTableOffset < FIXED_HEADER_SIZE || fileTableOffset > cabinetSize) {
		throw new ArchiveError("Invalid CAB archive: CFFILE table offset is out of bounds");
	}
	if (fixed[24] !== 3 || fixed[25] !== 1) {
		throw new ArchiveError(`Unsupported CAB format version ${fixed[25]}.${fixed[24]} (expected 1.3)`);
	}
	const folderCount = readUInt16LE(fixed, 26);
	const fileCount = readUInt16LE(fixed, 28);

View on GitHub (pinned to 9690622007)

Solutions

  1. Check the file size on disk (ls -l / stat) — if it is tiny or 0, re-download or re-copy the archive
  2. Confirm you are pointing the reader at the .cab file, not an index/manifest or temp file
  3. Verify the upstream transfer completed (Content-Length match, checksum comparison)
  4. If reading from a stream, ensure the source reports the true size and was fully consumed/flushed before indexing

Example fix

// before
const archive = Bun.file(maybePath);
await readCab(new ByteSource(archive), opts);
// after
const archive = Bun.file(maybePath);
if (archive.size < 36) throw new Error(`${maybePath} is too small to be a CAB archive (${archive.size} bytes)`);
await readCab(new ByteSource(archive), opts);
Defensive patterns

Strategy: validation

Validate before calling

const file = Bun.file(path);
const stat = await file.stat();
if (stat.size < 36) throw new Error(`${path} is ${stat.size} bytes — too small to be a CAB archive`);

Try / catch

try {
  await readCab(source, opts);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('truncated CFHEADER'))
    throw new Error(`File is not a complete CAB archive (${source.size} bytes): ${path}`);
  throw err;
}

Prevention

When it happens

Trigger: Calling readCab on a source with size < 36: empty file, a few-byte stub, a truncated download, or passing the wrong file/path to the reader.

Common situations: Zero-byte files created by failed downloads; accidentally pointing the extractor at a lock/partial file; reading from a truncated network response.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/83262ae60ae76aa8. Report an issue: GitHub.