{"record":{"id":"35b9905f7b10627e","repo":"can1357/oh-my-pi","slug":"invalid-lzh-archive-truncated-what","errorCode":null,"errorMessage":"Invalid LZH archive: truncated ${what}","messagePattern":"Invalid LZH archive: truncated (.+?)","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/lzh.ts","lineNumber":304,"sourceCode":"function u32(bytes: Uint8Array, offset: number): number {\n\treturn (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16) | (bytes[offset + 3]! << 24)) >>> 0;\n}\n\nfunction u64(bytes: Uint8Array, offset: number): number {\n\tconst value = u32(bytes, offset) + u32(bytes, offset + 4) * 0x100000000;\n\tif (!Number.isSafeInteger(value)) throw new ArchiveError(\"LZH uses sizes too large to read safely\");\n\treturn value;\n}\n\nfunction assertRange(bytes: Uint8Array, start: number, end: number, what: string): void {\n\tif (\n\t\t!Number.isSafeInteger(start) ||\n\t\t!Number.isSafeInteger(end) ||\n\t\tstart < 0 ||\n\t\tend < start ||\n\t\tend > bytes.byteLength\n\t) {\n\t\tthrow new ArchiveError(`Invalid LZH archive: truncated ${what}`);\n\t}\n}\n\nfunction decodeLegacy(bytes: Uint8Array): string {\n\tlet end = bytes.indexOf(0);\n\tif (end < 0) end = bytes.byteLength;\n\treturn LEGACY_DECODER.decode(bytes.subarray(0, end));\n}\n\nfunction decodeUtf16(bytes: Uint8Array): string {\n\tif ((bytes.byteLength & 1) !== 0) throw new ArchiveError(\"Invalid LZH Unicode path header: odd UTF-16 length\");\n\tlet end = bytes.byteLength;\n\twhile (end >= 2 && bytes[end - 1] === 0 && bytes[end - 2] === 0) end -= 2;\n\treturn UTF16LE_DECODER.decode(bytes.subarray(0, end));\n}\n\nfunction dosTimeToMs(value: number): number | undefined {\n\tif (value === 0) return undefined;","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/lzh.ts#L286-L322","documentation":"assertRange validates that a [start,end) byte range for a header field (name, method, size, CRC, etc. named by `what`) lies fully inside the buffer. Any non-integer, negative, reversed, or out-of-bounds range means the archive is truncated, so parsing aborts with a message naming the field.","triggerScenarios":"parseLzhHeader on a buffer shorter than the header claims — truncated download, partial file, or a computed end offset past byteLength because a prior size field was misread.","commonSituations":"Interrupted uploads/downloads; reading an LZH member from a truncated container; misparsed size fields inflating header offsets; fuzzed inputs.","solutions":["Check file size and re-download/re-copy the complete archive","Validate the archive container before extracting (CRC / archive-level integrity)","Ensure earlier header size reads (including extended headers) use the right widths/offsets","Catch ArchiveError and report the entry as truncated"],"exampleFix":"// before\nconst header = new Uint8Array(packed.buffer, 0, 21); // may exceed packed.length\nconst entry = parseLzhHeader(header);\n// after\nconst header = packed.subarray(0, Math.min(21, packed.length));\nif (packed.length < 21) throw new Error(\"file truncated before header\");\nconst entry = parseLzhHeader(header);","handlingStrategy":"validation","validationCode":"function isTruncated(file: Uint8Array, needed: number): boolean {\n  return file.length < needed;\n}\nif (isTruncated(packed, 21)) throw new Error(\"file truncated before LZH header\");","typeGuard":"function hasBytes(buf: Uint8Array, start: number, end: number): boolean {\n  return Number.isSafeInteger(start) && Number.isSafeInteger(end) &&\n    start >= 0 && end >= start && end <= buf.byteLength;\n}","tryCatchPattern":"try {\n  const entry = parseLzhHeader(packed.subarray(offset));\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"truncated\")) {\n    return { ok: false, reason: \"truncated archive\" };\n  }\n  throw err;\n}","preventionTips":["Check file size against expected length before parsing","Re-download or re-copy incomplete files","Validate archive-level integrity before member extraction","Never trust computed offsets from previously parsed (possibly corrupt) size fields"],"tags":["archive","truncation","lzh","validation"],"backgroundTag":"truncated-archive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}