{"record":{"id":"8028ecab1d86db46","repo":"can1357/oh-my-pi","slug":"invalid-arj-field-missing-terminator","errorCode":null,"errorMessage":"Invalid ARJ ${field}: missing terminator","messagePattern":"Invalid ARJ (.+?): missing terminator","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/arj.ts","lineNumber":49,"sourceCode":"\t}\n}\n\nfunction dosTimeToMs(value: number): number | undefined {\n\tif (value === 0) return undefined;\n\tconst year = 1980 + ((value >>> 25) & 0x7f);\n\tconst month = (value >>> 21) & 0x0f;\n\tconst day = (value >>> 16) & 0x1f;\n\tconst hour = (value >>> 11) & 0x1f;\n\tconst minute = (value >>> 5) & 0x3f;\n\tconst second = (value & 0x1f) * 2;\n\tif (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59) return undefined;\n\treturn Date.UTC(year, month - 1, day, hour, minute, second);\n}\n\nfunction readCString(bytes: Uint8Array, start: number, end: number, field: string): { value: string; next: number } {\n\tlet terminator = start;\n\twhile (terminator < end && bytes[terminator] !== 0) terminator++;\n\tif (terminator === end) throw new ArchiveError(`Invalid ARJ ${field}: missing terminator`);\n\treturn { value: LEGACY_DECODER.decode(bytes.subarray(start, terminator)), next: terminator + 1 };\n}\n\ninterface ArjBlock {\n\tbodyStart: number;\n\tbodySize: number;\n\tnextOffset: number;\n\tmetadataSize: number;\n\tisEnd: boolean;\n}\n\nfunction parseArjBlock(bytes: Uint8Array, offset: number, options: FormatReadOptions): ArjBlock {\n\tassertRange(bytes, offset, offset + 4, \"header signature\");\n\tif (bytes[offset] !== ARJ_SIGNATURE_0 || bytes[offset + 1] !== ARJ_SIGNATURE_1) {\n\t\tthrow new ArchiveError(\"Invalid ARJ header signature\");\n\t}\n\tconst bodySize = u16(bytes, offset + 2);\n\tif (bodySize === 0)","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/arj.ts#L31-L67","documentation":"ARJ stores header fields (e.g. archive name, file name, comment) as NUL-terminated windows-1252 strings. readCString scans from `start` for a 0 byte before the field's `end` boundary; if none exists the field is malformed, so the reader refuses to guess where the string ends and throws instead of returning garbage or an unterminated value.","triggerScenarios":"Parsing an ARJ basic header whose name/comment field runs all the way to its declared end without a 0x00 terminator — i.e. the byte range [start, end) contains no NUL. Reached via readArj() -> parseArjBlock() -> header field decoding (filename, archive name).","commonSituations":"A truncated or corrupted ARJ file (partial download, bad disk copy), a file that is not actually ARJ but happens to start with 0x60 0xEA, or a hand-crafted/fuzzed archive where a header size was edited without inserting the NUL terminator.","solutions":["Re-obtain the archive from a trusted source and verify it with an independent tool (e.g. `arj l archive.arj` or 7-Zip) to confirm corruption.","Check the file is complete: compare its size against the expected size or re-download it.","Verify the file really is ARJ (first bytes 0x60 0xEA) and not another format or a text/PE file misnamed .arj.","If the file is user-supplied input, treat the ArchiveError as normal control flow: reject the input and report which field failed — do not attempt partial parsing."],"exampleFix":"// before: assuming the read succeeds on any input\nconst { value } = readCString(bytes, start, end, \"filename\");\n// after: validate the terminator up front and fail fast with context\nfor (let i = start; i < end; i++) {\n  if (bytes[i] === 0) break;\n  if (i === end - 1) throw new Error(`ARJ header truncated: filename field has no NUL before offset ${end}`);\n}\nconst { value } = readCString(bytes, start, end, \"filename\");","handlingStrategy":"try-catch","validationCode":"// Check the field's byte range contains a NUL before parsing the header\nfunction hasCStringTerminator(bytes: Uint8Array, start: number, end: number): boolean {\n  for (let i = start; i < end; i++) if (bytes[i] === 0) return true;\n  return false;\n}","typeGuard":null,"tryCatchPattern":"import { ArchiveError } from \"@oh-my-pi/pi-utils/ar/error\";\ntry {\n  return readArj(data, options);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"missing terminator\")) {\n    throw new Error(`ARJ header string field is corrupt (no NUL terminator): ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["Validate magic bytes and run an external integrity test on archives from untrusted sources before parsing.","Treat ArchiveError as expected control flow for user-supplied files; never let it crash a batch job.","Re-download rather than repair corrupted archives."],"tags":["archive","corrupt-file","parsing","validation"],"backgroundTag":"corrupt-archive-missing-string-terminator","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}