File & PDF Tools

Extract ZIP Archive

Inspect a ZIP archive and download its extracted files.

Extract ZIP archive
Inspect and download files from a ZIP archive.
Local processing

Extract ZIP Archive

Read the ZIP central directory, validate declared entry sizes, extract locally, and download individual files.

Safety Limits

  • 50 MB input and 500 MB total extracted output
  • Up to 2,000 entries and 100 MB per file
  • Encrypted ZIP and ZIP64 are not supported

Reads a ZIP archive in the browser, walks its central directory to list every entry, and lets you download files one by one without uploading anything. Suited to checking what a downloaded archive really contains before extracting it locally, and to pulling a single file out of a packaged bundle.

The index sits at the end: the EOCD scan

A ZIP file carries no index at its front. Reading starts at the tail, comparing each position against the 4-byte little-endian signature 0x06054b50 of the 22-byte End of Central Directory record. That record may be followed by a comment of up to 65,535 bytes, so the scan steps backwards from length minus 22 across at most 65,557 bytes. Two fields then drive everything else: the total entry count at offset +10 (uint16) and the central directory offset at +16 (uint32). An archive whose local headers are intact but whose tail was truncated — an interrupted download, for instance — can never be listed, because no valid EOCD exists to point at the directory.

Walking 46-byte records, and what the size caps really check

Each central directory entry opens with signature 0x02014b50 and a fixed 46-byte header, followed by a variable filename, extra field and comment. The stride is 46 plus the three uint16 lengths at +28, +30 and +32, so a reader that skips the extra field drifts out of alignment and fails the signature test on the next record — the "central directory damaged" error. The uncompressed size lives at +24; the tool sums those declared values and refuses past 100 MB per file, 500 MB in total or 2,000 entries. Those are declarations, read before any inflation happens, and deflate reaches roughly 1000:1 on repetitive input, so the caps catch accidental blowups rather than a deliberately crafted archive.

Filename encoding, flag bits, and the download path

The only standard signal for a filename encoding is general purpose bit 11 (0x0800) at offset +8. Set, it means UTF-8, and a name such as 中文名.txt round-trips exactly. Clear, the app note prescribes CP437 while Windows archivers in practice wrote GBK — neither is UTF-8, so decoding as UTF-8 produces mojibake no byte-level repair can reliably reverse. The low bit of the same field, 0x0001, marks encryption and is rejected outright. After unpacking, entries ending in a slash are dropped as directory markers, and backslashes or slashes inside a name become underscores before the browser save dialog is invoked, which cannot represent a path.

Worked example

Reading a two-entry archive from its trailing bytes

Input
t.zip, 219 bytes; last 22 bytes: 504b05060000000002000200660000005f0000000000
Output
Archive contents: 2 files · 24 B — a.txt 6 B, b.txt 18 B

0x0002 at +10 is the entry count, 0x66 (102 bytes) at +12 the directory size and 0x5f (95) at +16 its offset, exactly two 51-byte records. Both entries use method 8 (deflate), and a.txt shows why compressed can exceed uncompressed: 6 bytes become 8.

Limits and pitfalls

  • The archive itself must be 50 MB or smaller; that cap is checked the moment the file is selected, before any entry is read.
  • ZIP64 is refused: an entry count of 0xFFFF or a directory offset of 0xFFFFFFFF is treated as the ZIP64 sentinel, so archives holding 65,535 entries or more never open.
  • Encrypted archives are rejected when general purpose bit 0 of any entry is set, and there is no password prompt.
  • The 100 MB per-file and 500 MB total caps read the declared sizes in the central directory, so they cannot prove real expansion ahead of inflation.
  • Files download one at a time and slashes in names become underscores, so the directory tree is flattened instead of recreated.

Related tools

When it goes wrong

The page is in Chinese but the red status line reads unknown compression type 14, with no explanation around it.

CauseinspectZip() checks only the central-record signature, the encryption bit and the declared sizes — it never reads the compression method at +10 of a record. Inflation is handed to fflate's unzipSync, which implements method 0 (stored) and method 8 (deflate) and nothing else, so Deflate64 (9), bzip2 (12), LZMA (14) and zstd (93) raise fflate error code 14. friendlyFileError() has no branch matching that string, so the library message is printed verbatim.

FixRe-pack with a deflate writer — 7z a -tzip -mm=Deflate out.zip folder and zip -r out.zip folder both emit method 8 — and drop the new file in. Re-saving with -mm=Deflate64 reproduces the same failure.

A .zip the browser just finished downloading is 34 KB on disk, yet this page answers that a valid ZIP directory was not found — or shows an absurd entry count and then calls the central directory damaged.

CauseThe scan walks backwards from the last 22 bytes hunting the four bytes of 0x06054b50, and there are ordinary ways to miss. A login page or error page saved under a .zip name begins 3c 21 44 4f rather than 50 4b 03 04, so no signature exists. More than 65,535 bytes appended after the record push the real one past the 65,557-byte window. A comment that itself contains the signature fails the other way: the walk stops on the comment, and the count at +10 and offset at +16 are read out of prose.

FixRead the first four bytes before retrying: 50 4b 03 04 is a local header, 3c 21 44 4f is HTML, so re-download instead of extracting. If those bytes are right, open the file in 7-Zip and save a copy — that strips appended data and the comment, and the copy reads here.

Two files from different folders download under one and the same name — the browser either warns about replacing the existing copy or silently saves the second as index (1).html.

CauseThe download handler only replaces slashes inside the entry name with underscores, so a/index.html and b/index.html both arrive as index.html. The collision counter uniqueName() exists solely on the zip-create path; the extract path never runs it, and there is no batch download that could disambiguate.

FixSave them one at a time and rename the first before fetching the second, or extract locally with unzip -d out whenever the folder structure is the reason you are looking inside.

In the file picker, .docx, .jar and .apk are greyed out, yet dragging the very same file in works and lists its internals.

CauseThe dropzone puts accept=".zip,application/zip" on the hidden <input type="file"> and nowhere else. Drops and pastes of a file are read from dataTransfer.files and clipboardData.files without any extension filter, and the reader itself only walks the end record and the 46-byte central headers, so every ZIP container passes regardless of what the name says.

FixUse the gap deliberately: drop a .docx in to pull out word/document.xml. To decide whether a mystery file is a ZIP, check its first four bytes for 504b0304 — when they differ, no extractor on this page will open it.

Extraction says the archive holds no extractable files, even though the archive is clearly not empty.

CauseEntries whose inflated name ends in a slash are dropped as directory markers, and the code throws as soon as the surviving list is empty; an archive of nothing but folder entries lands there. It also lands there when the end record declares 0 entries, because fflate's unzipSync returns an empty object the moment the count is zero.

FixInspect it locally with unzip -l archive.zip to see the real entry list, then rebuild with zip -r out.zip folder, which writes file entries rather than skeleton directories.

I dropped three archives at once to compare them and only one got read; the other two vanished without a message.

CauseIn extract mode selectFiles() commits files.value = [selected[0]], truncating the whole drop array to its first element. The tool is built around a single archive, so a multi-file drop silently discards the rest instead of queueing them.

FixDrop one archive per run and finish downloading what you need before selecting the next, because each selection rebuilds the entry list from scratch and nothing is retained in between.

One concrete scenario

2,050 daily exports against a 2,000-entry ceiling

A nightly job writes one CSV per region, 2,050 files in total. Packed, the archive is 11 MB and the largest entry declares under 40 KB, so all three size gates pass with room to spare: 11,000,000 bytes against the 52,428,800-byte input ceiling, 40 KB against the 104,857,600-byte per-file ceiling, and 11 MB against the 524,288,000-byte total. The reader still refuses, because inspectZip() takes the entry count from the end record at +10 and stops above 2,000 — before a single byte is inflated. The directory itself is only 2,050 × (46 + a 13-character name) ≈ 120,950 bytes, so dropping 50 entries saves nothing worth measuring; re-pack as two archives of 1,025 and both open. No setting on this page raises the limit, so past 2,000 entries the choice is split or go local.

Frequently Asked Questions

Extracted Chinese filenames are mojibake — why?
The classic encoding clash: ZIPs created on Windows encode names in GBK, which turns to garbage when read as UTF-8. This tool handles the standard cases and outputs sanitized names; for stubborn archives, extract on the system that created them.
Can it open password-protected ZIPs?
Encrypted ZIP is not supported. Use your system’s extractor for those — browser-side decryption adds little (someone still types the password), so the effort focuses on big-file and privacy scenarios.
What are the limits on large archives?
To prevent zip bombs (a tiny ZIP exploding into gigabytes), safety caps apply: 50 MB input, 500 MB total extraction, 100 MB per file, 2,000 entries max. Beyond that, use system tools or split volumes.

Content reviewed: