can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: truncated CFDATA header

Error message

Invalid CAB archive: truncated CFDATA header

What it means

While walking description.blockCount CFDATA blocks inside a folder's data region, the reader checks that each block's fixed header (DATA_BLOCK_SIZE plus the folder's reserve area) fits in the bytes read from the source. When it does not, the folder claims more blocks than its data can hold, so the archive is declared truncated. This catches size-field lies in CFHEADER/CFFOLDER versus actual file length.

Source

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

		const description = this.#description;
		if (description.method === 2) {
			throw new ArchiveError(`Unsupported CAB compression method: Quantum (level ${description.parameter})`);
		}
		if (description.method > 3) {
			throw new ArchiveError(`Unsupported CAB compression method: ${description.method}`);
		}
		if (description.method === 3 && (description.parameter < 15 || description.parameter > 21)) {
			throw new ArchiveError(`Unsupported CAB LZX window size: ${description.parameter} bits (expected 15-21)`);
		}

		const compressedSize = description.dataEnd - description.dataStart;
		assertInMemorySize(compressedSize, this.#limits);
		const bytes = await readExact(this.#source, description.dataStart, description.dataEnd);
		let position = 0;
		let outputSize = 0;
		for (let block = 0; block < description.blockCount; block++) {
			if (position + DATA_BLOCK_SIZE + this.#dataReserveSize > bytes.byteLength) {
				throw new ArchiveError("Invalid CAB archive: truncated CFDATA header");
			}
			const compressed = readUInt16LE(bytes, position + 4);
			const uncompressed = readUInt16LE(bytes, position + 6);
			if (uncompressed === 0) throw new ArchiveError("Unsupported multi-volume CAB archive: split CFDATA block");
			if (uncompressed > MAX_DATA_OUTPUT) {
				throw new ArchiveError(`Invalid CAB archive: CFDATA expands to ${uncompressed} bytes (maximum 32768)`);
			}
			const payloadStart = position + DATA_BLOCK_SIZE + this.#dataReserveSize;
			const payloadEnd = payloadStart + compressed;
			if (payloadEnd > bytes.byteLength) throw new ArchiveError("Invalid CAB archive: truncated CFDATA payload");
			const expectedChecksum = readUInt32LE(bytes, position);
			if (expectedChecksum !== 0) {
				const payloadChecksum = cabChecksum(bytes.subarray(payloadStart, payloadEnd));
				const actualChecksum = cabChecksum(bytes.subarray(position + 4, payloadStart), payloadChecksum);
				if (actualChecksum !== expectedChecksum) {
					throw new ArchiveError(`Invalid CAB archive: CFDATA block ${block} checksum mismatch`);
				}
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-download/re-copy the .cab and verify its size or SHA against the publisher's checksum.
  2. Run `cabextract -t file.cab` to confirm truncation independently before debugging your code.
  3. Compare the folder's cbData and blockCount against the file's actual remaining length to identify which field is wrong.
  4. If the file is one volume of a set, ensure all volumes are present and you are reading the correct one (multi-volume CABs are rejected elsewhere).
  5. If you generate these CABs, fix the writer so blockCount/cbData match the emitted data.

Example fix

// before: trusting a partial download
await readCabArchive(await Bun.file('part.cab').bytes()); // truncated CFDATA header
// after: verify completeness first
const expected = 48317234;
const buf = await Bun.file('part.cab').bytes();
if (buf.byteLength < expected) throw new Error(`CAB incomplete: ${buf.byteLength}/${expected} bytes`);
await readCabArchive(buf);
Defensive patterns

Strategy: validation

Validate before calling

const size = (await Bun.stat(cabPath)).size;
if (size !== expectedSize)
  throw new Error(`CAB size mismatch: got ${size}, expected ${expectedSize} — download is truncated`);

Try / catch

try {
  return await readCabArchive(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('truncated CFDATA')) {
    throw new Error(`CAB folder sizes exceed file length (${bytes.byteLength} bytes) — re-acquire the archive`);
  }
  throw err;
}

Prevention

When it happens

Trigger: readAll() where a CFFOLDER's cbData/blockCount fields imply more or larger CFDATA headers than the bytes between dataStart and dataEnd — typically a CAB cut short or with corrupted folder sizes.

Common situations: Interrupted downloads of multi-hundred-MB .cab distribution files; antivirus quarantining the tail of a file; archives assembled by concatenating partial volumes; offsets corrupted by a bad disk sector.

Related errors


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