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

  1. Identify the real format from the magic bytes (file(1), ContentType sniffing) and route it to the correct handler.
  2. Regenerate/re-download the artifact from a source that actually produces zip.
  3. 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

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


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/c235cc6c39121fc5. Report an issue: GitHub.