Tencent/tinker · error · ZipException
Not a zip archive
Error message
Not a zip archive
What it means
After the minimum-length check, readCentralDir seeks to offset 0 and reads the first 4 bytes; if they are not the 'PK\x03\x04' local-file-header signature (LOCSIG), the file cannot be a zip at all and the constructor throws ZipException('Not a zip archive'). This is a content check, independent of the file extension — a .zip named file with non-zip contents fails here.
Source
Thrown at third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipFile.java:428
*
* <p>This is all a little wobbly. If the wrong value ends up in the EOCD
* area, we're hosed. This appears to be the way that everybody handles
* it though, so we're in good company if this fails.
*/
private void readCentralDir() throws IOException {
// Scan back, looking for the End Of Central Directory field. If the zip file doesn't
// have an overall comment (unrelated to any per-entry comments), we'll hit the EOCD
// on the first try.
// No need to synchronize raf here -- we only do this when we first open the zip file.
long scanOffset = raf.length() - ENDHDR;
if (scanOffset < 0) {
throw new ZipException("File too short to be a zip file: " + raf.length());
}
raf.seek(0);
final int headerMagic = Integer.reverseBytes(raf.readInt());
if (headerMagic != LOCSIG) {
throw new ZipException("Not a zip archive");
}
long stopOffset = scanOffset - 65536;
if (stopOffset < 0) {
stopOffset = 0;
}
while (true) {
raf.seek(scanOffset);
if (Integer.reverseBytes(raf.readInt()) == ENDSIG) {
break;
}
scanOffset--;
if (scanOffset < stopOffset) {
throw new ZipException("End Of Central Directory signature not found");
}
}View on GitHub (pinned to 1b7ea02c23)
Solutions
- Identify the real format from the magic bytes (file(1), ContentType sniffing) and route it to the correct handler.
- Regenerate/re-download the artifact from a source that actually produces zip.
- Add a content-type check at ingestion so non-zip payloads are rejected before reaching TinkerZipFile.
Example fix
// before
TinkerZipFile zf = new TinkerZipFile(new File(path));
// after
byte[] magic = new byte[4];
try (RandomAccessFile r = new RandomAccessFile(path, "r")) {
r.readFully(magic);
}
if (!Arrays.equals(magic, new byte[]{'P','K',3,4})) {
throw new IOException(path + " is not a zip archive");
}
TinkerZipFile zf = new TinkerZipFile(new File(path)); Defensive patterns
Strategy: validation
Validate before calling
static boolean looksLikeZip(File f) throws IOException {
if (f.length() < 4) return false;
try (RandomAccessFile r = new RandomAccessFile(f, "r")) {
byte[] b = new byte[4];
r.readFully(b);
return b[0]=='P' && b[1]=='K' && b[2]==3 && b[3]==4;
}
} Prevention
- Check magic bytes at ingestion; never trust file extensions.
- Verify Content-Type when artifacts arrive over HTTP.
- For self-extracting or stub-prefixed archives, strip the stub before this library reads them.
When it happens
Trigger: Opening a file whose first 4 bytes are not 'PK\x03\x04': tar/gz/rar archives renamed to .zip, raw binaries, text/HTML content, or a zip with a prepended stub in unusual cases where the EOCD scan would have succeeded but the head check does not.
Common situations: MIME-type confusion in an upload pipeline; a mirror/CDN serving a decompressed or transformed artifact; users renaming files to force them through a zip-based flow; build outputs configured with the wrong archiver.
Related errors
- file name:${filename}, file size${fileSize}, entry name:${en
- Local file header offset is after central directory
- Duplicate entry name: ${entryName}
- zipEntry is null when get from oldApk
- Expected ${DEX_IN_JAR_NAME} in ${file}
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/c235cc6c39121fc5.
Report an issue: GitHub.