- Java ZipFile Tutorial
- Creating a ZipFile Instance
- Getting a ZipEntry
- Reading the File
- Listing All Entries in a ZipFile
- Unzipping All Entries in ZipFile
- The Zip Slip Problem
- Java NIO ZIP File System Provider Example
- Create ZIP Files Using ZFSP / Java NIO
- 1.Define ZIP File System Properties
- 2.Configure Physical ZIP File
- 3.Create ZIP File System
- 4.Copy Files to ZIP Archive
- Complete Program – Create ZIP File using ZPFS in Java NIO
- Java zip file system example
- ZipOutputStream. Запись архивов
- Чтение архивов. ZipInputStream
Java ZipFile Tutorial
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 entries = zipFile.entries();
You can iterate the Enumeration returned by the entries() method like this:
Enumeration 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); Enumerationentries = 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); Enumerationentries = 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); >>
Java NIO ZIP File System Provider Example
In this tutorial, we will discuss how to use ZIP File System Provider (ZFSP) which is capable of treating a ZIP file as a file system in Java and manipulate ZIP files using it with some Java examples. ZFSP can treat a ZIP file like a file system (i.e. a ZIP file can be managed as your C drive for example), and makes addition / deletion / creation of ZIP files a cakewalk. Not only this, you can also create your own custom file systems following this approach (More on this later). You need Java SE7 to work with the examples provided in this section. The different examples we will cover in this tutorial series are summarized below:
ZPFS in Java NIO — tutorials |
Let us start with the easiest of the examples first — Creating a ZIP file using ZFSP file system.
Create ZIP Files Using ZFSP / Java NIO
The high level steps involved in creating a ZIP file using ZFSP and adding some files into it are captured below:
Steps to create ZIP File using ZPFS in Java NIO |
1.Define ZIP File System Properties
In this step, we create a HashMap in Java, and define the configurable properties for the ZIP file. The properties of interest are “create” and “encoding” . Since we have to create a new ZIP file, we set create to true and encoding to UTF-8. You can also set other encoding schemes if you want to. A sample code to define the HashMap is shown below:
/* Define ZIP File System Properies in HashMap */ MapString, String> zip_properties = new HashMap>(); /* set create to true if you want to create a new ZIP file */ zip_properties.put("create", "true"); /* specify encoding to UTF-8 */ zip_properties.put("encoding", "UTF-8");
2.Configure Physical ZIP File
We use the URI class defined in java.net.URI to point to a physical file on disk. This should not be new to you, and here is the Java code segment to do this;
/* Locate File on disk for creation */ URI zip_disk = URI.create("jar:file:/my_zip_file.zip");
3.Create ZIP File System
You can create a new File System by using the method newFileSystem factory method defined in java.nio.file.FileSystems . You pass the URI and HashMap created in the steps earlier to this method and it returns an object of type java.nio.file.FileSystem which in our case is a ZIP File System. The Java code to create the File System is shown below:
try (FileSystem zipfs = FileSystems.newFileSystem(zip_disk, zip_properties))
4.Copy Files to ZIP Archive
Now that you have created a ZIP File System, you can easily copy (i.e. add ) files to archive, by using methods available in Path and Files class. We will add a simple docx file to the archive as shown below, you can adopt the same approaches to add any file.
/* Path to source file */ Path file_to_zip = Paths.get("tut1.docx"); /* Path inside ZIP File */ Path pathInZipfile = zipfs.getPath("tut1.docx"); /* Add file to archive */ Files.copy(file_to_zip,pathInZipfile);
Complete Program – Create ZIP File using ZPFS in Java NIO
The complete Java program to create a ZIP file using ZPFS is shown below: (Note that we are using new try with resources syntax in this example)
import java.util.*; import java.net.URI; import java.nio.file.Path; import java.nio.file.*; public class ZPFSCreate public static void main(String [] args) throws Exception /* Define ZIP File System Properies in HashMap */ MapString, String> zip_properties = new HashMap>(); /* set create to true if you want to create a new ZIP file */ zip_properties.put("create", "true"); /* specify encoding to UTF-8 */ zip_properties.put("encoding", "UTF-8"); /* Locate File on disk for creation */ URI zip_disk = URI.create("jar:file:/my_zip_file.zip"); /* Create ZIP file System */ try (FileSystem zipfs = FileSystems.newFileSystem(zip_disk, zip_properties)) /* Path to source file */ Path file_to_zip = Paths.get("tut1.docx"); /* Path inside ZIP File */ Path pathInZipfile = zipfs.getPath("tut1.docx"); /* Add file to archive */ Files.copy(file_to_zip,pathInZipfile); > > >
You will get a ZIP file created in your file system with a single document in it as per the example code provided above. You can try this example yourself and post a comment if you have any questions.
In the next post, we will examine how to read an archive and extract files from it using ZPFS. Stay connected.
Java zip file system example
Кроме общего функционала для работы с файлами Java предоставляет функциональность для работы с таким видом файлов как zip-архивы. Для этого в пакете java.util.zip определены два класса — ZipInputStream и ZipOutputStream
ZipOutputStream. Запись архивов
Для создания архива используется класс ZipOutputStream. Для создания объекта ZipOutputStream в его конструктор передается поток вывода:
ZipOutputStream(OutputStream out)
Для записи файлов в архив для каждого файла создается объект ZipEntry , в конструктор которого передается имя архивируемого файла. А чтобы добавить каждый объект ZipEntry в архив, применяется метод putNextEntry() .
import java.io.*; import java.util.zip.*; public class Program < public static void main(String[] args) < String filename = "notes.txt"; try(ZipOutputStream zout = new ZipOutputStream(new FileOutputStream("output.zip")); FileInputStream fis= new FileInputStream(filename);) < ZipEntry entry1=new ZipEntry("notes.txt"); zout.putNextEntry(entry1); // считываем содержимое файла в массив byte byte[] buffer = new byte[fis.available()]; fis.read(buffer); // добавляем содержимое к архиву zout.write(buffer); // закрываем текущую запись для новой записи zout.closeEntry(); >catch(Exception ex) < System.out.println(ex.getMessage()); >> >
После добавления объекта ZipEntry в поток нам также надо добавить в него и содержимое файла. Для этого используется метод write, записывающий в поток массив байтов: zout.write(buffer); . В конце надо закрыть ZipEntry с помощью метода closeEntry() . После этого можно добавлять в архив новые файлы — в этом случае все вышеописанные действия для каждого нового файла повторяются.
Чтение архивов. ZipInputStream
Для чтения архивов применяется класс ZipInputStream . В конструкторе он принимает поток, указывающий на zip-архив:
ZipInputStream(InputStream in)
Для считывания файлов из архива ZipInputStream использует метод getNextEntry() , который возвращает объект ZipEntry . Объект ZipEntry представляет отдельную запись в zip-архиве. Например, считаем какой-нибудь архив:
import java.io.*; import java.util.zip.*; public class Program < public static void main(String[] args) < try(ZipInputStream zin = new ZipInputStream(new FileInputStream("output.zip"))) < ZipEntry entry; String name; while((entry=zin.getNextEntry())!=null)< name = entry.getName(); // получим название файла System.out.printf("File name: %s \n", name); // распаковка FileOutputStream fout = new FileOutputStream("new" + name); for (int c = zin.read(); c != -1; c = zin.read()) < fout.write(c); >fout.flush(); zin.closeEntry(); fout.close(); > > catch(Exception ex) < System.out.println(ex.getMessage()); >> >
ZipInputStream в конструкторе получает ссылку на поток ввода. И затем в цикле выводятся все файлы и их размер в байтах, которые находятся в данном архиве.
Затем данные извлекаются из архива и сохраняются в новые файлы, которые находятся в той же папке и которые начинаются с «new».