Java ZipFile Tutorial
Jakob Jenkov |
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); } }
Tweet | |
Jakob Jenkov |