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

References

Leave a Reply

Your email address will not be published. Required fields are marked *