{"record":{"id":"775ce2e3e3451ea3","repo":"can1357/oh-my-pi","slug":"invalid-cab-archive-truncated-data","errorCode":null,"errorMessage":"Invalid CAB archive: truncated data","messagePattern":"Invalid CAB archive: truncated data","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/cab.ts","lineNumber":52,"sourceCode":"}\n\nasync function readExact(\n\tsource: ByteSource,\n\tstart: number,\n\tend: number,\n\tcabinetSize = source.size,\n): Promise<Uint8Array> {\n\tif (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end > cabinetSize) {\n\t\tthrow new ArchiveError(\"Invalid CAB archive: metadata range is out of bounds\");\n\t}\n\tlet bytes: Uint8Array;\n\ttry {\n\t\tbytes = await source.read(start, end);\n\t} catch (error) {\n\t\tif (error instanceof ArchiveError) throw error;\n\t\tthrow new ArchiveError(`Unable to read CAB archive: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tif (bytes.byteLength !== end - start) throw new ArchiveError(\"Invalid CAB archive: truncated data\");\n\treturn bytes;\n}\n\nfunction hasSignature(bytes: Uint8Array): boolean {\n\treturn bytes.byteLength >= 4 && bytes[0] === 0x4d && bytes[1] === 0x53 && bytes[2] === 0x43 && bytes[3] === 0x46;\n}\n\nfunction cabChecksum(bytes: Uint8Array, initial = 0): number {\n\tlet checksum = initial >>> 0;\n\tlet offset = 0;\n\twhile (offset + 4 <= bytes.byteLength) {\n\t\tchecksum ^= readUInt32LE(bytes, offset);\n\t\toffset += 4;\n\t}\n\tconst remaining = bytes.byteLength - offset;\n\tlet remainder = 0;\n\tif (remaining === 3) {\n\t\tremainder = (bytes[offset]! << 16) | (bytes[offset + 1]! << 8) | bytes[offset + 2]!;","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/cab.ts#L34-L70","documentation":"readExact verifies that source.read(start, end) returned exactly end-start bytes; if fewer bytes came back the CAB file is shorter than its structure demands and the archive is declared truncated. CAB files carry offset/size fields, so a short file fails this exact-length check. This is a strict validation: partial reads are never accepted.","triggerScenarios":"Any header(), bytes(), fixed(), reserveHeader(), or fileTable() call where the underlying source returns a Uint8Array with byteLength < end - start — most often a file smaller than the header sizes/offsets declared inside it, or an offset pointing past EOF.","commonSituations":"Incomplete download of a .cab file; a truncated transfer (HTTP 200 body cut short); a corrupted/corrupted-by-antivirus archive; a file renamed to .cab that is not actually a CAB; passing the wrong start/end offsets to a custom source that silently clamps reads.","solutions":["Re-download or re-copy the .cab file and compare its byte size (or checksum) against the original source.","Verify the file starts with the MSCF signature (0x4D 0x53 0x43 0x46) — hasSignature() — to confirm it is actually a CAB.","Check the file size on disk; if it is smaller than expected for this archive, the transfer was cut short.","If using a custom ArchiveSource, ensure read() throws instead of returning short buffers when EOF is hit mid-range.","Try extracting with an external tool (cabextract -t) to confirm the archive itself is intact before blaming your code."],"exampleFix":"// before: trusting a possibly-incomplete download\nconst reader = await openCab(downloadedPath);\nawait reader.fileTable(); // Invalid CAB archive: truncated data\n// after: verify size/signature first\nconst stat = await fs.stat(downloadedPath);\nif (stat.size < expectedMinSize) throw new Error('CAB download incomplete, re-fetching');\nconst reader = await openCab(downloadedPath);\nawait reader.fileTable();","handlingStrategy":"validation","validationCode":"const stat = await fs.stat(cabPath);\nif (stat.size < 8) throw new Error(`File too small to be a CAB: ${stat.size} bytes`);\nconst head = new Uint8Array(await Bun.file(cabPath).slice(0, 4).arrayBuffer());\nif (!(head[0] === 0x4d && head[1] === 0x53 && head[2] === 0x43 && head[3] === 0x46))\n  throw new Error('Missing MSCF signature — not a CAB file');","typeGuard":"function hasCabSignature(b: Uint8Array): boolean {\n  return b.byteLength >= 4 && b[0] === 0x4d && b[1] === 0x53 && b[2] === 0x43 && b[3] === 0x46;\n}","tryCatchPattern":"try {\n  return await readCabArchive(bytes);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('truncated data')) {\n    throw new Error(`CAB file is incomplete (${bytes.byteLength} bytes) — re-download it`);\n  }\n  throw err;\n}","preventionTips":["Verify downloads against published sizes/checksums before parsing.","Check the MSCF signature before opening.","Avoid reading files while they are still being written or downloaded.","Run cabextract -t on archives in CI fixtures to catch silent truncation."],"tags":["archive","cab","truncated","validation"],"backgroundTag":"archive-truncated","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}