{"record":{"id":"0530db3a92813359","repo":"can1357/oh-my-pi","slug":"invalid-zip-archive-unicode-path-extra-field-is-t","errorCode":null,"errorMessage":"Invalid ZIP archive: Unicode path extra field is too small","messagePattern":"Invalid ZIP archive: Unicode path extra field is too small","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/zip.ts","lineNumber":269,"sourceCode":"\tconst value = new Date(year, month, day, hour, minute, second).getTime();\n\treturn Number.isFinite(value) ? value : undefined;\n}\n\nfunction parseExtra(extra: Uint8Array, rawName: Uint8Array): ParsedExtra {\n\tconst result: ParsedExtra = {};\n\tlet offset = 0;\n\twhile (offset < extra.byteLength) {\n\t\tif (offset + 4 > extra.byteLength) throw new ArchiveError(\"Invalid ZIP archive: truncated extra-field header\");\n\t\tconst id = readUInt16LE(extra, offset);\n\t\tconst size = readUInt16LE(extra, offset + 2);\n\t\tconst dataStart = offset + 4;\n\t\tconst dataEnd = dataStart + size;\n\t\tif (dataEnd > extra.byteLength) throw new ArchiveError(\"Invalid ZIP archive: malformed extra field\");\n\t\tconst data = extra.subarray(dataStart, dataEnd);\n\t\tif (id === 0x0001) {\n\t\t\tresult.zip64 = data;\n\t\t} else if (id === 0x7075) {\n\t\t\tif (data.byteLength < 5) throw new ArchiveError(\"Invalid ZIP archive: Unicode path extra field is too small\");\n\t\t\tif (data[0] === 1 && readUInt32LE(data, 1) === crc32(rawName)) {\n\t\t\t\ttry {\n\t\t\t\t\tresult.unicodePath = UTF8_FATAL_DECODER.decode(data.subarray(5));\n\t\t\t\t} catch {\n\t\t\t\t\t// A bad optional Unicode path falls back to the header name.\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (id === 0x5455) {\n\t\t\tif (data.byteLength < 1)\n\t\t\t\tthrow new ArchiveError(\"Invalid ZIP archive: extended timestamp extra field is too small\");\n\t\t\tif ((data[0]! & 1) !== 0) {\n\t\t\t\tif (data.byteLength < 5)\n\t\t\t\t\tthrow new ArchiveError(\"Invalid ZIP archive: extended timestamp extra field is too small\");\n\t\t\t\tresult.mtimeMs = (readUInt32LE(data, 1) | 0) * 1000;\n\t\t\t}\n\t\t} else if (id === 0x000a && result.mtimeMs === undefined) {\n\t\t\tresult.mtimeMs = parseNtfsMtime(data);\n\t\t}","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/zip.ts#L251-L287","documentation":"Thrown by parseExtra when an extra field with id 0x7075 (Info-ZIP Unicode Path) is present but its data is shorter than the minimum 5 bytes (version byte + 4-byte CRC32 of the original name). A valid Unicode Path record must carry at least the version and checksum before any UTF-8 name payload.","triggerScenarios":"Calling zip info/read on an archive whose entries include a 0x7075 extra record with data length < 5 — produced by a writer that emits the record header/id without the required version+CRC prefix.","commonSituations":"Archives from nonstandard or homegrown zip writers attempting Unicode filename support; archives with extra fields rewritten by lossy tooling; fuzzed zips.","solutions":["Test with `unzip -t`; standard tools may tolerate it, but rebuild anyway: `unzip` then `zip -r clean.zip .`.","Recreate the archive with Info-ZIP, 7-Zip, or Python zipfile so Unicode paths are encoded per spec (or via the UTF-8 flag instead of 0x7075).","Repair with `zip -FF` if the field region was corrupted.","If you control the writer, emit version=1 + CRC32(name) + UTF-8 name, or set the general-purpose UTF-8 flag (0x0800) instead."],"exampleFix":"// before: reading output of a custom writer emitting bare 0x7075 records\nconst zip = readZip(customUnicodeZip);\n// after: rebuild so Unicode names use the UTF-8 flag\nawait $`unzip -q customUnicode.zip -d tmp`;\nawait $`zip -qr clean.zip .`.cwd(\"tmp\"); // Info-ZIP sets UTF-8 flag / proper 0x7075\nconst zip = readZip(\"clean.zip\");","handlingStrategy":"try-catch","validationCode":"const probe = Bun.spawnSync([\"unzip\", \"-t\", path]);\nif (probe.exitCode !== 0) throw new Error(`archive invalid; rebuild with a standard zip tool before reading: ${path}`);","typeGuard":null,"tryCatchPattern":"try {\n  const zip = readZip(path);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"Unicode path extra field is too small\")) {\n    // producer's Unicode-path extras are nonstandard: re-zip to normalize\n    await $`unzip -q ${path} -d tmp`; await $`zip -qr clean.zip .`.cwd(\"tmp\");\n    return readZip(\"clean.zip\");\n  }\n  throw err;\n}","preventionTips":["For Unicode filenames, prefer writers that set the UTF-8 flag (0x0800) instead of hand-rolled 0x7075 records.","If emitting 0x7075 yourself, include version byte + CRC32(name) + UTF-8 payload — never a bare record.","Test producer output with `zipinfo -v` to spot nonstandard extras before shipping.","Rebuild legacy archives with a canonical tool during ingestion."],"tags":["zip","archive","extra-field","unicode"],"backgroundTag":"zip-extra-field-malformed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}