Java ZipFile Tutorial

Jakob Jenkov
Last update: 2019-11-30

The Java ZipFile class (java.util.zip.ZipFile) can be used to read files from a ZIP file. The ZipFile class is actually quite easy to use. This tutorial will show you how to use the ZipFile class.

Creating a ZipFile Instance

In order to use the Java ZipFile class you must first create a ZipFile instance. Here is an example of creating a Java ZipFile instance:

 ZipFile zipFile = new ZipFile("d:\\data\\myzipfile.zip");

As you can see, the ZipFile class takes a single parameter in its constructor. This parameter is the path to the ZIP file to open.

Getting a ZipEntry

Each file in the ZIP file is represented by a ZipEntry (java.util.zip.ZipEntry). To extract a file from the ZIP file you can call the method getEntry() method on the ZipFile class. Here is an example of calling getEntry():

ZipEntry zipEntry = zipFile.getEntry("file1.txt");

This example gets a ZipEntry representing the file file1.txt which is contained in the ZIP file.

If the file you want to extract is located in one or more directories inside the ZIP file, include the directories in the path, like this:

ZipEntry zipEntry = zipFile.getEntry("dir/subdir/file1.txt");

Reading the File

To read the file represented by a ZipEntry you can obtain an InputStream from the ZipFile like this:

ZipEntry zipEntry = zipFile.getEntry("dir/subdir/file1.txt");

InputStream inputStream = this.zipFile.getInputStream(zipEntry);

The InputStream obtained from the getInputStream() of the ZipFile class can be read like any other Java InputStream.

Listing All Entries in a ZipFile

You can list all entries contained in a ZipFile using the entries() method. Here is an example of calling the ZipFile entries() :

Enumeration<? extends ZipEntry> entries = zipFile.entries();

You can iterate the Enumeration returned by the entries() method like this:

Enumeration<? extends ZipEntry> entries = zipFile.entries();

while(entries.hasMoreElements()){
    ZipEntry entry = entries.nextElement();
    if(entry.isDirectory()){
        System.out.println("dir  : " + entry.getName());
    } else {
        System.out.println("file : " + entry.getName());
    }
}

Unzipping All Entries in ZipFile

There is no easy way to unzip all entries of a ZipFile. You will have to do that yourself. To make it easier for you, I will show you an example of the code needed to unzip all entries in a Java ZipFile. Here is the code:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class FileUnzipper {

    private String zipFileDir  = null;
    private String zipFileName = null;
    private String unzipDir    = null;

    public FileUnzipper(String zipFileDir, String zipFileName, String unzipDir) {
        this.zipFileDir  = zipFileDir;
        this.zipFileName = zipFileName;
        this.unzipDir    = unzipDir;
    }

    public void unzip() {
        String zipFilePath = this.zipFileDir + File.separator + this.zipFileName;
        try{
            System.out.println("zipFilePath = " + zipFilePath);
            ZipFile zipFile = new ZipFile(zipFilePath);

            Enumeration<? extends ZipEntry> entries = zipFile.entries();

            while(entries.hasMoreElements()){
                ZipEntry entry = entries.nextElement();
                if(entry.isDirectory()){
                    System.out.print("dir  : " + entry.getName());
                    String destPath = this.unzipDir + File.separator + entry.getName();
                    System.out.println(" => " + destPath);

                    //todo check destPath for Zip Slip problem - see further down this page.


                    File file = new File(destPath);
                    file.mkdirs();
                } else {
                    String destPath = this.unzipDir + File.separator + entry.getName();

                    //todo check destPath for Zip Slip problem - see further down this page.

                    try(InputStream inputStream = zipFile.getInputStream(entry);
                        FileOutputStream outputStream = new FileOutputStream(destPath);
                    ){
                        int data = inputStream.read();
                        while(data != -1){
                            outputStream.write(data);
                            data = inputStream.read();
                        }
                    }
                    System.out.println("file : " + entry.getName() + " => " + destPath);
                }
            }
        } catch(IOException e){
            throw new RuntimeException("Error unzipping file " + zipFilePath, e);
        }
    }

    private boolean isValidDestPath(String destPath) {
        // validate the destination path of a ZipFile entry,
        // and return true or false telling if it's valid or not.
    }
}

The Zip Slip Problem

The example of unzipping all entries of a ZipFile into a directory is vulnerable to the Zip Slip attack. The Zip Slip attack consists of adding entries to a ZipFile that contains relative file paths with one or more /.. sections in the path. This way the final path of the file could end up being outside the directory into which the ZipFile is requested unzipped to. Let's look at an example:

You request a ZipFile to be unzipped to the directory /apps/myapp/data/unzipped-file. An entry in the ZipFile has the relative path ../../../../etc/hosts . The final path of that entry becomes: /apps/myapp/data/unzipped-file/../../../../etc/hosts which is equivalent of /etc/hosts .

Unzipping this file could potentially overwrite hour hosts file (on a Linux OS), enabling the attacker to point e.g. www.facebook.com to an IP address of their own choice. The next time you try to access Facebook from that computer, it won't be the real Facebook you are accessing, but the attacker's spoofed version. Once you login, the attacker now has your username and password, and your Facebook account can be hacked.

The way to avoid a Zip Slip attack is the check the final output path to see if it is outside the target directory. Here is how you can do that:

Path destPath           = Paths.get(destPath);
Path destPathNormalized = destPath.normalize(); //remove ../../ etc.

boolean isWithinTargetDir =
    destPathNormalized.toString().startsWith(targetDir + File.separator);

What you do if the ZipFile entry target path is outside the target directory is up to you to decide. You could throw an exception, or just ignore that file, and maybe write out to the console or log that this file was ignored because of invalid entry path.

Here is an ZipFile unzip example that throws an exception if the target path of an entry is invalid:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class FileUnzipper {

    private String zipFileDir  = null;
    private String zipFileName = null;
    private String unzipDir    = null;

    public FileUnzipper(String zipFileDir, String zipFileName, String unzipDir) {
        this.zipFileDir  = zipFileDir;
        this.zipFileName = zipFileName;
        this.unzipDir    = unzipDir;
    }

    public void unzip() {
        String zipFilePath = this.zipFileDir + File.separator + this.zipFileName;
        try{
            System.out.println("zipFilePath = " + zipFilePath);
            ZipFile zipFile = new ZipFile(zipFilePath);

            Enumeration<? extends ZipEntry> entries = zipFile.entries();

            while(entries.hasMoreElements()){
                ZipEntry entry = entries.nextElement();
                if(entry.isDirectory()){
                    System.out.print("dir  : " + entry.getName());
                    String destPath = this.unzipDir + File.separator + entry.getName();
                    System.out.println(" => " + destPath);

                    if(! isValidDestPath(zipFileDir, destPath)){
                        throw new IOException("Final directory output path is invalid: " + destPath);
                    }


                    File file = new File(destPath);
                    file.mkdirs();
                } else {
                    String destPath = this.unzipDir + File.separator + entry.getName();

                    if(! isValidDestPath(zipFileDir, destPath)){
                        throw new IOException("Final file output path is invalid: " + destPath);
                    }

                    try(InputStream inputStream = zipFile.getInputStream(entry);
                        FileOutputStream outputStream = new FileOutputStream(destPath);
                    ){
                        int data = inputStream.read();
                        while(data != -1){
                            outputStream.write(data);
                            data = inputStream.read();
                        }
                    }
                    System.out.println("file : " + entry.getName() + " => " + destPath);
                }
            }
        } catch(IOException e){
            throw new RuntimeException("Error unzipping file " + zipFilePath, e);
        }
    }

    private boolean isValidDestPath(String targetDir, String destPathStr) {
        // validate the destination path of a ZipFile entry,
        // and return true or false telling if it's valid or not.

        Path destPath           = Paths.get(destPathStr);
        Path destPathNormalized = destPath.normalize(); //remove ../../ etc.

        return destPathNormalized.toString().startsWith(targetDir + File.separator);
    }
}    

Jakob Jenkov

Featured Videos

Java Generics

Java ForkJoinPool

P2P Networks Introduction



















Close TOC
All Tutorial Trails
All Trails
Table of contents (TOC) for this tutorial trail
Trail TOC
Table of contents (TOC) for this tutorial
Page TOC
Previous tutorial in this tutorial trail
Previous
Next tutorial in this tutorial trail
Next