{"record":{"id":"e8f743f5901d55ca","repo":"can1357/oh-my-pi","slug":"invalid-rpm-package-truncated-main-header","errorCode":null,"errorMessage":"Invalid RPM package: truncated main header","messagePattern":"Invalid RPM package: truncated main header","errorType":"validation","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/rpm.ts","lineNumber":146,"sourceCode":"\t\tthrow new ArchiveError(`Invalid RPM package: tag ${tag} must contain one string`);\n\t}\n\tconst start = indexSize + offset;\n\tconst limit = indexSize + dataSize;\n\tlet end = start;\n\twhile (end < limit && body[end] !== 0) end++;\n\tif (end === limit) throw new ArchiveError(`Invalid RPM package: tag ${tag} string is not NUL-terminated`);\n\tif (end - start > 4096) throw new ArchiveError(`Invalid RPM package: tag ${tag} string is too large`);\n\ttry {\n\t\treturn UTF8_FATAL_DECODER.decode(body.subarray(start, end));\n\t} catch {\n\t\tthrow new ArchiveError(`Invalid RPM package: tag ${tag} is not valid UTF-8`);\n\t}\n}\n\nfunction parseMainHeader(body: Uint8Array, intro: HeaderIntro): RpmMetadata {\n\tvalidateHeaderBody(body, intro, \"main\");\n\tconst indexSize = intro.indexCount * RPM_INDEX_ENTRY_SIZE;\n\tif (body.byteLength !== intro.bodySize) throw new ArchiveError(\"Invalid RPM package: truncated main header\");\n\tconst metadata: RpmMetadata = {};\n\tfor (let index = 0; index < intro.indexCount; index++) {\n\t\tconst recordOffset = index * RPM_INDEX_ENTRY_SIZE;\n\t\tconst tag = readUInt32BE(body, recordOffset);\n\t\tconst type = readUInt32BE(body, recordOffset + 4);\n\t\tconst offset = readUInt32BE(body, recordOffset + 8);\n\t\tconst count = readUInt32BE(body, recordOffset + 12);\n\t\tif (offset > intro.dataSize) throw new ArchiveError(`Invalid RPM package: tag ${tag} points outside header data`);\n\t\tif (\n\t\t\ttag !== RPM_TAG_NAME &&\n\t\t\ttag !== RPM_TAG_VERSION &&\n\t\t\ttag !== RPM_TAG_PAYLOAD_FORMAT &&\n\t\t\ttag !== RPM_TAG_PAYLOAD_COMPRESSOR &&\n\t\t\ttag !== RPM_TAG_PAYLOAD_FLAGS\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst value = readHeaderString(body, indexSize, intro.dataSize, offset, count, type, tag);","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/rpm.ts#L128-L164","documentation":"parseMainHeader validates that the RPM main header body it read is exactly intro.bodySize bytes (index entries plus data region declared in the header intro). A mismatch means the file is shorter than its own header claims, so the package is truncated or corrupt. The library refuses to parse a self-inconsistent header rather than return partial metadata.","triggerScenarios":"Calling the archive reader (readRpm/metadata path) on an RPM whose main header intro declares indexCount*16 + dataSize bytes that exceed the bytes actually available at the main header offset — i.e. the read body length !== intro.bodySize.","commonSituations":"Incomplete downloads (interrupted transfer of an .rpm), files copied/truncated by a failed upload or rsync cut short, a corrupted package on a mirror, or concatenation/rewriting tools that chopped the file.","solutions":["Re-download or re-copy the RPM and verify its checksum against the repository/mirror digest.","Check the file size: if it is smaller than the original package, the transfer was truncated; retry the download.","Test the package with rpm -qp <file>.rpm or rpm2cpio to confirm it is corrupt outside this library.","If the source is a build artifact, rebuild the package with rpmbuild to regenerate a valid header."],"exampleFix":"// before: parsing a partially downloaded file directly\nconst entries = await readArchive(fetchedBuffer);\n// after: verify integrity first\nconst expected = await fetchChecksumFromRepo(url);\nif (await Bun.hash(buffer) !== expected) throw new Error('RPM truncated/corrupt; re-download');","handlingStrategy":"validation","validationCode":"const stat = await Bun.file(path).size;\n// header intro dataSize/indexCount live at offset 96+8..96+16; body must fit in file\n// cheap pre-check: an RPM main header can't exceed the file itself\nif (stat < 96 + 16) throw new Error(`RPM too small (${stat} bytes); likely truncated`);","typeGuard":"function looksLikeCompleteRpm(size: number, introIndexCount: number, introDataSize: number): boolean {\n  return 96 + 16 + introIndexCount * 16 + introDataSize <= size;\n}","tryCatchPattern":"try {\n  const meta = await readRpm(path);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('truncated main header')) {\n    throw new Error('RPM file is truncated — re-download and verify checksum');\n  }\n  throw err;\n}","preventionTips":["Always verify download checksums before parsing RPMs.","Compare local file size to the repository's content-length after downloads.","Avoid resuming interrupted downloads into the final artifact without re-validation."],"tags":["rpm","archive","truncated-file","corruption"],"backgroundTag":"truncated-archive-header","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}