This article shows how to use ZipInputStream
and zip4j
library to unzip a zip file in Java.
To unzip a file manually, remember to add validation for the zip slip vulnerability.
Path targetDirResolved = targetDir.resolve(zipEntry.getName()); // make sure normalized file still has targetDir as its prefix Path normalizePath = targetDirResolved.normalize(); if (!normalizePath.startsWith(targetDir)) { // may be zip slip, better stop and throws exception throw new IOException("Bad zip entry: " + zipEntry.getName()); }
1. Zip File
The following zip file structure is created by this article – create zip file in Java. Later we will show how to unzip it into a new folder.
1.1 A zip file contains only files.
test-files-only.zip
$ unzip -l test-files-only.zip
Archive: test-files-only.zip
Length Date Time Name
--------- ---------- ----- ----
12 2022-05-20 13:54 test-a1.log
16 2022-05-20 13:54 data/db.debug.conf
12 2022-05-20 13:54 test-a2.log
9 2022-05-20 13:54 file.txt
1393 2022-05-20 13:54 Test.java
9 2022-05-20 13:54 test.log
12 2022-05-20 13:54 test-b/test-b2.txt
12 2022-05-20 13:54 test-b/test-d/test-d1.log
12 2022-05-20 13:54 test-b/test-d/test-d2.log
12 2022-05-20 13:54 test-b/test-c/test-c2.log
12 2022-05-20 13:54 test-b/test-c/test-c1.log
12 2022-05-20 13:54 test-b/test-b1.txt
11 2022-05-20 13:54 README.md
--------- -------
1534 13 files
1.2 A zip file contains folders and files.
test-files-folders.zip
$ unzip -l test-files-folders.zip
Archive: test-files-folders.zip
Length Date Time Name
--------- ---------- ----- ----
12 2022-05-17 16:43 test-a1.log
0 2022-05-20 13:54 data/
16 2022-05-20 13:01 data/db.debug.conf
12 2022-05-17 16:43 test-a2.log
9 2022-05-18 10:27 file.txt
1393 2022-05-20 11:34 Test.java
9 2022-05-18 22:22 test.log
0 2022-05-20 13:54 test-b/
12 2022-05-17 16:44 test-b/test-b2.txt
0 2022-05-20 13:54 test-b/test-d/
12 2022-05-17 16:45 test-b/test-d/test-d1.log
12 2022-05-17 17:02 test-b/test-d/test-d2.log
0 2022-05-20 13:54 test-b/test-c/
12 2022-05-17 16:45 test-b/test-c/test-c2.log
12 2022-05-17 16:44 test-b/test-c/test-c1.log
12 2022-05-17 16:43 test-b/test-b1.txt
11 2022-05-20 13:02 README.md
--------- -------
1534 17 files
2. Unzip file – ZipInputStream
To unzip a zip file, we use ZipInputStream
to read the zip file, and copying files from the zip file into a new folder (outside zip file).
This below example unzip a zip file /home/tvt/workspace/favtuts/test.zip
into a folder /home/tvt/workspace/favtuts/zip/
, also provides a validation to prevent the zip slip vulnerability.
ZipFileUnZipExample.java
package com.favtuts.io.howto; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class ZipFileUnZipExample { public static void main(String[] args) { //Path source = Paths.get("/home/tvt/workspace/favtuts/test-files-only.zip"); Path source = Paths.get("/home/tvt/workspace/favtuts/test-files-folders.zip"); Path target = Paths.get("/home/tvt/workspace/favtuts/zip/"); try { unzipFolder(source, target); System.out.println("Done"); } catch (IOException e) { e.printStackTrace(); } } public static void unzipFolder(Path source, Path target) throws IOException { try (ZipInputStream zis = new ZipInputStream(new FileInputStream(source.toFile()))) { // list files in zip ZipEntry zipEntry = zis.getNextEntry(); while (zipEntry != null) { boolean isDirectory = false; // example 1.1 // some zip stored files and folders separately // e.g data/ // data/folder/ // data/folder/file.txt if (zipEntry.getName().endsWith(File.separator)) { isDirectory = true; } Path newPath = zipSlipProtect(zipEntry, target); if (isDirectory) { Files.createDirectories(newPath); } else { // example 1.2 // some zip stored file path only, need create parent directories // e.g data/folder/file.txt if (newPath.getParent() != null) { if (Files.notExists(newPath.getParent())) { Files.createDirectories(newPath.getParent()); } } // copy files, nio Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING); // copy files, classic /*try (FileOutputStream fos = new FileOutputStream(newPath.toFile())) { byte[] buffer = new byte[1024]; int len; while ((len = zis.read(buffer)) > 0) { fos.write(buffer, 0, len); } }*/ } zipEntry = zis.getNextEntry(); } zis.closeEntry(); } } // protect zip slip attack public static Path zipSlipProtect(ZipEntry zipEntry, Path targetDir) throws IOException { // test zip slip vulnerability // Path targetDirResolved = targetDir.resolve("../../" + zipEntry.getName()); Path targetDirResolved = targetDir.resolve(zipEntry.getName()); // make sure normalized file still has targetDir as its prefix // else throws exception Path normalizePath = targetDirResolved.normalize(); if (!normalizePath.startsWith(targetDir)) { throw new IOException("Bad zip entry: " + zipEntry.getName()); } return normalizePath; } }
Output
$ pwd
/home/tvt/workspace/favtuts/zip
$ tree
.
├── data
│ └── db.debug.conf
├── file.txt
├── README.md
├── test-a1.log
├── test-a2.log
├── test-b
│ ├── test-b1.txt
│ ├── test-b2.txt
│ ├── test-c
│ │ ├── test-c1.log
│ │ └── test-c2.log
│ └── test-d
│ ├── test-d1.log
│ └── test-d2.log
├── Test.java
└── test.log
4 directories, 13 files
3. Unzip file – zip4j
This example uses the zip4j library to unzip a zip file.
pom.xml
<dependency> <groupId>net.lingala.zip4j</groupId> <artifactId>zip4j</artifactId> <version>2.6.1</version> </dependency>
package com.favtuts.io.howto; import java.io.*; import java.nio.file.*; import net.lingala.zip4j.*; public class ZipFileUnZipExample { public static void main(String[] args) { // Path source = Paths.get("/home/tvt/workspace/favtuts/test-files-only.zip"); Path source = Paths.get("/home/tvt/workspace/favtuts/test-files-folders.zip"); Path target = Paths.get("/home/tvt/workspace/favtuts/zip/"); try { unzipFolderZip4j(source, target); System.out.println("Done"); } catch (IOException e) { e.printStackTrace(); } } // it takes `File` as arguments public static void unzipFolderZip4j(Path source, Path target) throws IOException { new ZipFile(source.toFile()) .extractAll(target.toString()); } }
4. ZipException: invalid entry size
If we hit the following invalid entry size
exception, it means the zip file is corrupted during the copy, transfer, or creation process. There is no way to fix a corrupted file size, get a new zip file again.
java.util.zip.ZipException: invalid entry size (expected 0 but got 1282 bytes)
at java.base/java.util.zip.ZipInputStream.readEnd(ZipInputStream.java:398)
at java.base/java.util.zip.ZipInputStream.read(ZipInputStream.java:197)
at java.base/java.io.FilterInputStream.read(FilterInputStream.java:107)
at com.favtuts.io.howto.ZipFileUnZipExample.unzipFolder(ZipFileUnZipExample.java:63)
at com.favtuts.io.howto.ZipFileUnZipExample.main(ZipFileUnZipExample.java:22)
Download Source Code
$ git clone https://github.com/favtuts/java-core-tutorials-examples
$ cd java-io/howto