I am a beginner at programming with Java and am currently writing an application which must be able to compress and decompress .zip files. I can use the following code to decompress a zipfile in Java using the built-in Java zip functionality as well as the Apache Commons IO library:
public static void decompressZipfile(String file, String outputDir) throws IOException {
if (!new File(outputDir).exists()) {
new File(outputDir).mkdirs();
}
ZipFile zipFile = new ZipFile(file);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
File entryDestination = new File(outputDir, entry.getName());
if (entry.isDirectory()) {
entryDestination.mkdirs();
} else {
InputStream in = zipFile.getInputStream(entry);
OutputStream out = new FileOutputStream(entryDestination);
IOUtils.copy(in, out);
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}
}
How would I go about creating a zipfile from a directory using no external libraries other than what I am already using? (Java standard libraries and Commons IO)
The following method(s) seem to successfully compress a directory recursively:
public static void compressZipfile(String sourceDir, String outputFile) throws IOException, FileNotFoundException {
ZipOutputStream zipFile = new ZipOutputStream(new FileOutputStream(outputFile));
compressDirectoryToZipfile(sourceDir, sourceDir, zipFile);
IOUtils.closeQuietly(zipFile);
}
private static void compressDirectoryToZipfile(String rootDir, String sourceDir, ZipOutputStream out) throws IOException, FileNotFoundException {
for (File file : new File(sourceDir).listFiles()) {
if (file.isDirectory()) {
compressDirectoryToZipfile(rootDir, sourceDir + File.separator + file.getName(), out);
} else {
ZipEntry entry = new ZipEntry(sourceDir.replace(rootDir, "") + file.getName());
out.putNextEntry(entry);
FileInputStream in = new FileInputStream(sourceDir + file.getName());
IOUtils.copy(in, out);
IOUtils.closeQuietly(in);
}
}
}
As seen in my compression code snippet, I'm using IOUtils.copy() to handle stream data transfer.
I fix above error and it works perfect.
public static void compressZipfile(String sourceDir, String outputFile) throws IOException, FileNotFoundException {
ZipOutputStream zipFile = new ZipOutputStream(new FileOutputStream(outputFile));
Path srcPath = Paths.get(sourceDir);
compressDirectoryToZipfile(srcPath.getParent().toString(), srcPath.getFileName().toString(), zipFile);
IOUtils.closeQuietly(zipFile);
}
private static void compressDirectoryToZipfile(String rootDir, String sourceDir, ZipOutputStream out) throws IOException, FileNotFoundException {
String dir = Paths.get(rootDir, sourceDir).toString();
for (File file : new File(dir).listFiles()) {
if (file.isDirectory()) {
compressDirectoryToZipfile(rootDir, Paths.get(sourceDir,file.getName()).toString(), out);
} else {
ZipEntry entry = new ZipEntry(Paths.get(sourceDir,file.getName()).toString());
out.putNextEntry(entry);
FileInputStream in = new FileInputStream(Paths.get(rootDir, sourceDir, file.getName()).toString());
IOUtils.copy(in, out);
IOUtils.closeQuietly(in);
}
}
}
Looks like the answer is a bit outdated. Refreshed it for latest Java for now.
Also in ZIP file file names will be relative to given folder for compression. In original answer they were absolute with full paths.
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Zipper {
public static void compressFolder(String sourceDir, String outputFile) throws IOException {
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(outputFile))) {
compressDirectoryToZipFile((new File(sourceDir)).toURI(), new File(sourceDir), zipOutputStream);
}
}
private static void compressDirectoryToZipFile(URI basePath, File dir, ZipOutputStream out) throws IOException {
List<File> fileList = Files.list(Paths.get(dir.getAbsolutePath()))
.map(Path::toFile)
.collect(Collectors.toList());
for (File file : fileList) {
if (file.isDirectory()) {
compressDirectoryToZipFile(basePath, file, out);
} else {
out.putNextEntry(new ZipEntry(basePath.relativize(file.toURI()).getPath()));
try (FileInputStream in = new FileInputStream(file)) {
IOUtils.copy(in, out);
}
}
}
}
}
Full class ZipUtils based on answers above.
public final class ZipUtils {
private ZipUtils() {
}
// For testing
public static void main(String[] args) throws IOException {
compressFile(new File("./file.test"), new File("test1.zip"));
compressDirectory(new File("./test1"), new File("test2.zip"));
extractArchive(new File("./test2"), new File("test3.zip"));
}
public static void compressDirectory(File sourceDirectory, File zipFile) throws IOException {
Preconditions.checkState(sourceDirectory.exists(), "Source directory is not exists: %s", sourceDirectory);
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile))) {
compressDirectory(sourceDirectory.getAbsoluteFile(), sourceDirectory, out);
}
}
private static void compressDirectory(File rootDir, File sourceDir, ZipOutputStream out) throws IOException {
for (File file : Preconditions.checkNotNull(sourceDir.listFiles())) {
if (file.isDirectory()) {
compressDirectory(rootDir, new File(sourceDir, file.getName()), out);
} else {
String zipEntryName = getRelativeZipEntryName(rootDir, file);
compressFile(out, file, zipEntryName);
}
}
}
private static String getRelativeZipEntryName(File rootDir, File file) {
return StringUtils.removeStart(file.getAbsolutePath(), rootDir.getAbsolutePath());
}
public static void compressFile(File file, File zipFile) throws IOException {
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile))) {
compressFile(out, file, file.getName());
}
}
private static void compressFile(ZipOutputStream out, File file, String zipEntityName) throws IOException {
ZipEntry entry = new ZipEntry(zipEntityName);
out.putNextEntry(entry);
try (FileInputStream in = new FileInputStream(file)) {
IOUtils.copy(in, out);
}
}
public static void extractArchive(File targetDirectory, File zipFile) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
extractStream(targetDirectory, zis);
}
}
private static void extractStream(File targetDirectory, ZipInputStream zis) throws IOException {
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
extractEntry(targetDirectory, zis, zipEntry);
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
}
private static void extractEntry(File targetDirectory, ZipInputStream zis, ZipEntry zipEntry) throws IOException {
File newFile = newFile(targetDirectory, zipEntry);
if (zipEntry.isDirectory()) {
FileUtils.forceMkdir(newFile);
} else {
FileUtils.forceMkdirParent(newFile);
try (FileOutputStream fos = new FileOutputStream(newFile)) {
IOUtils.copy(zis, fos);
}
}
}
private static File newFile(File targetDirectory, ZipEntry zipEntry) throws IOException {
File targetFile = new File(targetDirectory, zipEntry.getName());
String targetDirPath = targetDirectory.getCanonicalPath();
String targetFilePath = targetFile.getCanonicalPath();
if (!targetFilePath.startsWith(targetDirPath + File.separator)) {
throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
}
return targetFile;
}
}
Related
I have some Java code that does this (on a Windows 10 laptop with Windows Defender on):
unzip a .zip file into a folder (using a zip stream)
immediately list the folder's contents (using File#list)
There are ten files in the zip, from 100kB to 40MB. Normally only the first two files are listed -- the other 8 are silently lost. I know they actually make it to the directory because I can see them when I navigate to the directory myself.
I know there are better ways to implement this code, but I'm curious: is this expected? Is "write file into folder" and "file is listed when you list folder contents" not atomic on Windows? Is it a quirk of the underlying file system? Does Windows Defender make the file invisible for some period of time after it's written?
I am not facing the issue you mentioned and my code is able to list all the files correctly:
package com.test;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ZipTest {
private static final int BUFFER_SIZE = 4096;
private static String INPUT_LOCATION = "C:/temp/zip/test.zip";
private static String OUTPUT_LOCATION = "C:/temp/unzip";
public static void main(String[] args) throws IOException {
unzip(INPUT_LOCATION, OUTPUT_LOCATION);
for (String s : new File(OUTPUT_LOCATION).list()) {
System.out.println(s);
}
}
public static void unzip(String zipFilePath, String destDirectory) throws IOException {
File destDir = new File(destDirectory);
if (!destDir.exists()) {
destDir.mkdir();
}
ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry entry = zipIn.getNextEntry();
while (entry != null) {
String filePath = destDirectory + File.separator + entry.getName();
if (!entry.isDirectory()) {
extractFile(zipIn, filePath);
} else {
File dir = new File(filePath);
dir.mkdir();
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
zipIn.close();
}
private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
byte[] bytesIn = new byte[BUFFER_SIZE];
int read = 0;
while ((read = zipIn.read(bytesIn)) != -1) {
bos.write(bytesIn, 0, read);
}
bos.close();
}
}
I am trying to zip two folder with each having some text file. Now I want that 2 folders should zip as one folder with their respective files.
I tried to code, but there is some issue with the zip.
The Zip contains multiple folders.
Here is the code:
package com.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipFolders
{
public static void main(String[] args) throws IOException
{
List<String> listOfDir = new ArrayList<String>();
String dirpath1 = "/home/administrator/Documents/ZipTest/folder1";
String dirpath2 = "/home/administrator/Documents/ZipTest/folder2";
String ZipName = "/home/administrator/Documents/ZipTest/output.zip";
listOfDir.add(dirpath1);
listOfDir.add(dirpath2);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(ZipName));
zipDirectories(listOfDir,zos);
zos.close();
System.out.println("Zip Created Successfully");
}
private static void zipDirectories(List<String> listOfDir, ZipOutputStream zos) {
for(String dirPath:listOfDir){
try {
zipdirectory(dirPath, zos);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void zipdirectory(String dirpath, ZipOutputStream zos) throws IOException
{
File f = new File(dirpath);
String[] flist = f.list();
for(int i=0; i<flist.length; i++)
{
File ff = new File(f,flist[i]);
if(ff.isDirectory())
{
zipdirectory(ff.getPath(),zos);
continue;
}
String filepath = ff.getPath();
ZipEntry entries = new ZipEntry(filepath);
zos.putNextEntry(entries);
FileInputStream fis = new FileInputStream(ff);
int buffersize = 1024;
byte[] buffer = new byte[buffersize];
int count;
while((count = fis.read(buffer)) != -1)
{
zos.write(buffer,0,count);
}
fis.close();
}
}
}
But the output I am getting is not as expected I am getting folder 1 & folder 2
like
output.zip//home/administrator/Documents/ZipTest/ then here we are having our folder1 & folder2.
My expected output was :- inside the zip only the two folders should exist with there files.
Your zipdirectory method is used recursive here:
if(ff.isDirectory())
{
zipdirectory(ff.getPath(),zos);
continue;
}
That is creating all the folders you see.
You need to distiquish between the path of each file/dir and the path of the zip entry:
private static void zipDirectories(List<String> listOfDir, ZipOutputStream zos) {
for(String dirPath:listOfDir){
try {
File dir = new File(dirPath);
zipdirectory(dir, dir.getName(), zos);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void zipdirectory(File dir, String dirpath,
ZipOutputStream zos) throws IOException
{
String[] flist = dir.list();
for(int i=0; i<flist.length; i++)
{
String fn = flist[i];
String fp = dirpath == null || dirpath.isEmpty()
? fn : dirpath + "/" + fn;
File ff = new File(dir, fn);
if(ff.isDirectory())
{
zipdirectory(ff, fp,zos);
continue;
}
ZipEntry entries = new ZipEntry(fp);
zos.putNextEntry(entries);
FileInputStream fis = new FileInputStream(ff);
int buffersize = 1024;
byte[] buffer = new byte[buffersize];
int count;
while((count = fis.read(buffer)) != -1)
{
zos.write(buffer,0,count);
}
fis.close();
}
}
Copy , Paste the Code and Run it for the result :-
package com.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipFolders
{
public static void main(String[] args) throws IOException
{
List<String> listOfDir = new ArrayList<String>();
String dirpath1 = "/home/administrator/Documents/ZipTest/folder1";
String dirpath2 = "/home/administrator/Documents/ZipTest/folder2";
String ZipName = "/home/administrator/Documents/ZipTest/output.zip";
listOfDir.add(dirpath1);
listOfDir.add(dirpath2);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(ZipName));
zipDirectories(listOfDir,zos);
zos.close();
System.out.println("Zip Created Successfully");
}
private static void zipDirectories(List<String> listOfDir, ZipOutputStream zos) {
for(String dirPath:listOfDir){
try {
zipdirectory(dirPath, zos);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void zipdirectory(String dirpath, ZipOutputStream zos) throws IOException
{
File f = new File(dirpath);
String[] flist = f.list();
for(int i=0; i<flist.length; i++)
{
File ff = new File(f,flist[i]);
if(ff.isDirectory())
{
zipdirectory(ff.getPath(),zos);
continue;
}
String fileName = ff.getPath().substring(ff.getPath().lastIndexOf('/'));
String folder = dirpath.substring(dirpath.lastIndexOf('/')+1);
ZipEntry entries = new ZipEntry(folder+fileName);
zos.putNextEntry(entries);
FileInputStream fis = new FileInputStream(ff);
int buffersize = 1024;
byte[] buffer = new byte[buffersize];
int count;
while((count = fis.read(buffer)) != -1)
{
zos.write(buffer,0,count);
}
fis.close();
}
}
}
So i created a zip and created a new sub folder in the zip file by creating a zip entry that ends in "\". How would i write to the subfolder?
My problem is i have a putnextEntry call on my ZipOutputStream in a for loop so after the folder gets created i then jump into a for loop where the different zip files are written. But they are written at the same level as the subdir within the zip.
What i think is happening is because i use putNextEntry with the first actual zip(not dir) entry it is closing the subfolder and writing to the root of the zip. Any ideas?
Code below
private int endprocess() {
try {
zipFolder(ripPath, zipOutputPath, "rips");
//zipFolder(destPDFfiles, zipOutputPath, "pdfs");
this.returnCode = 0;
//log.debug ( "Accumulator count: " + acount);
log.debug("Equivest count: " + ecount);
//log.debug ( "Assoc count: " + scount);
processEndOfEnvelope();
} catch (Exception reportException) {
log.logError("Caught exception in creating.");
reportException.printStackTrace();
this.returnCode = 15;
}
return (this.returnCode);
}
public static void zipFolder(String srcFolder, String dest, String outputFolder){
try{
ZipOutputStream zos = null;
FileOutputStream fos = null;
fos = new FileOutputStream(dest + "\\newzip.zip");
zos = new ZipOutputStream(fos);
addFolderToZip(srcFolder, zos, outputFolder);
zos.flush();
zos.close();
}catch(IOException e){
log.logError("**********************");
log.logError("IO Exception occurred");
log.logError(e.getMessage());
e.printStackTrace();
}
}
private static void addFileToZip(String srcFile, ZipOutputStream zos, String outputFolder){
try {
File folder = new File(srcFile);
if (folder.isDirectory()) {
addFolderToZip(srcFile, zos, outputFolder);
} else {
byte[] buffer = new byte[1024];
int length;
FileInputStream fis = new FileInputStream(srcFile);
ZipEntry ze = new ZipEntry("C:\\AWDAAV\\zip\\newzip.zip\\" + outputFolder + "\\" + folder.getName());
zos.putNextEntry(ze);
while ((length = fis.read(buffer)) > 0) {
zos.write(buffer, 0, length);
}
}
}catch(Exception e){
log.logError("**********************");
log.logError("Exception occurred");
log.logError(e.getMessage());
e.printStackTrace();
}
}
private static void addFolderToZip(String srcFolder, ZipOutputStream zos, String outputFolder){
try{
File folder = new File(srcFolder);
zos.putNextEntry(new ZipEntry(outputFolder + "\\"));
for(String fileName : folder.list()){
addFileToZip(srcFolder + "\\" + fileName, zos, outputFolder);
}
}catch(Exception e){
log.logError("**********************");
log.logError("Exception occurred");
log.logError(e.getMessage());
e.printStackTrace();
}
}
I had a look at your code and i think the ZIP API works just as you think.
Only you had some logic errors with the path names.
You need to convert the path names from the local names to the relative locations in the zip file.
Maybe you got confused somewhere in that area.
Here is my suggestion:
File: org/example/Main.java
package org.example;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) throws IOException {
MyZip myZip = new MyZip();
Path sourcePath = Paths.get("C:/Users/David/Desktop/a");
Path targetPath = Paths.get("C:/Users/David/Desktop/zip/out.zip");
Path zipPath = Paths.get("tadaa");
myZip.zipFolder(sourcePath, targetPath.toFile(), zipPath);
}
}
Update: Explanation of variables in main method
The sourcePath is a directory which content you want to include in the zip archive.
The targetPath is the output path of the zip file. For example the new zip file will be created at exactly that location.
The zipPath is the subdirectory within the zip directory where your content from sourcePath will be placed. This variable may also be set to null. If it is null the sourcePath will be put in the root of the new zip archive.
File: org/example/MyZip.java
package org.example;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class MyZip {
public void zipFolder(Path base, File dest, Path zipFolder) throws IOException {
try (FileOutputStream fos = new FileOutputStream(dest);
ZipOutputStream zos = new ZipOutputStream(fos)) {
addFolderToZip(base.getParent(), base, zos, zipFolder);
}
}
private void addFolderToZip(Path base, Path currentFolder, ZipOutputStream zos, Path zipFolder) throws IOException {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(currentFolder)) {
for(Path path : stream) {
Path relativePath = base != null ? base.relativize(path) : path;
Path pathInZip = zipFolder != null ? zipFolder.resolve(relativePath) : relativePath;
if(path.toFile().isDirectory()) {
zos.putNextEntry(new ZipEntry(pathInZip.toString() + "/"));
// recurse to sub directories
addFolderToZip(base, path, zos, zipFolder);
} else {
addFileToZip(path, pathInZip, zos);
}
}
}
}
private void addFileToZip(Path sourcePath, Path pathInZip, ZipOutputStream zos) throws IOException {
byte[] buffer = new byte[1024];
int length;
try (FileInputStream fis = new FileInputStream(sourcePath.toFile())) {
ZipEntry ze = new ZipEntry(pathInZip.toString());
zos.putNextEntry(ze);
while ((length = fis.read(buffer)) > 0) {
zos.write(buffer, 0, length);
}
}
}
}
ZipEntry.setLevel(filepath[in your case, \folder]);
I'm writing a java program that will extract zip file and rename the file inside it to the zip file name. For example: the zip file name is zip.zip and the file inside it is content.txt. Here i want to extract the zip file and the content.txt has to be renamed to zip.txt. I'm trying the below program.
And here there would be only one file in the zip file
Zip.Java
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class zip {
private static final int BUFFER_SIZE = 4096;
public void unzip(String zipFilePath, String destDirectory) throws IOException {
File destDir = new File(destDirectory);
if (!destDir.exists()) {
destDir.mkdir();
}
ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry entry = zipIn.getNextEntry();
while (entry != null) {
String filePath = destDirectory + File.separator + entry.getName();
if (!entry.isDirectory()) {
// if the entry is a file, extracts it
extractFile(zipIn, filePath, zipFilePath);
} else {
// if the entry is a directory, make the directory
File dir = new File(filePath);
dir.mkdir();
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
zipIn.close();
}
private void extractFile(ZipInputStream zipIn, String filePath, String zipFilePath) throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
byte[] bytesIn = new byte[BUFFER_SIZE];
int read = 0;
while ((read = zipIn.read(bytesIn)) != -1) {
bos.write(bytesIn, 0, read);
}
File oldName = new File(filePath);
System.out.println(oldName);
String str = zipFilePath.substring(zipFilePath.lastIndexOf("\\") + 1, zipFilePath.lastIndexOf("."));
System.out.println(str);
File zipPath = new File(zipFilePath);
System.out.println(zipPath.getParent());
File newName = new File(zipPath.getParent() + "\\" + str);
System.out.println(newName);
if (oldName.renameTo(newName)) {
System.out.println("Renamed");
} else {
System.out.println("Not Renamed");
}
bos.close();
}
}
UnZip.Java
public class UnZip {
public static void main(String[] args) {
String zipFilePath = "C:\\Users\\u0138039\\Desktop\\Proview\\Zip\\New Companies Ordinance (Vol Two)_xml.zip";
String destDirectory = "C:\\Users\\u0138039\\Desktop\\Proview\\Zip";
zip unzipper = new zip();
try {
unzipper.unzip(zipFilePath, destDirectory);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Here i was able to extract the file but unable to rename it. please let me knw where am i going wrong and how to fix it.
Thanks
Close your BufferedOutputStream directly after the last write instruction (after the while loop). Only then will it release its lock on the file and will you be able to rename the file.
See: http://docs.oracle.com/javase/7/docs/api/java/io/FileOutputStream.html#close()
Is there any sample code, how to particaly unzip folder from ZIP into my desired directory? I have read all files from folder "FOLDER" into byte array, how do I recreate from its file structure?
I am not sure what do you mean by particaly?
Do you mean do it yourself without of API help?
In the case you don't mind using some opensource library,
there is a cool API for that out there called zip4J
It is easy to use and I think there is good feedback about it.
See this example:
String source = "folder/source.zip";
String destination = "folder/source/";
try {
ZipFile zipFile = new ZipFile(source);
zipFile.extractAll(destination);
} catch (ZipException e) {
e.printStackTrace();
}
If the files you want to unzip have passwords, you can try this:
String source = "folder/source.zip";
String destination = "folder/source/";
String password = "password";
try {
ZipFile zipFile = new ZipFile(source);
if (zipFile.isEncrypted()) {
zipFile.setPassword(password);
}
zipFile.extractAll(destination);
} catch (ZipException e) {
e.printStackTrace();
}
I hope this is useful.
Here is the code I'm using. Change BUFFER_SIZE for your needs.
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public final class ZipUtils {
private static final int BUFFER_SIZE = 4096;
public static void extract(ZipInputStream zip, File target) throws IOException {
try {
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null) {
File file = new File(target, entry.getName());
if (!file.toPath().normalize().startsWith(target.toPath())) {
throw new IOException("Bad zip entry");
}
if (entry.isDirectory()) {
file.mkdirs();
continue;
}
byte[] buffer = new byte[BUFFER_SIZE];
file.getParentFile().mkdirs();
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
int count;
while ((count = zip.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
out.close();
}
} finally {
zip.close();
}
}
}
A most concise, library-free, Java 7+ variant:
public static void unzip(InputStream is, Path targetDir) throws IOException {
targetDir = targetDir.toAbsolutePath();
try (ZipInputStream zipIn = new ZipInputStream(is)) {
for (ZipEntry ze; (ze = zipIn.getNextEntry()) != null; ) {
Path resolvedPath = targetDir.resolve(ze.getName()).normalize();
if (!resolvedPath.startsWith(targetDir)) {
// see: https://snyk.io/research/zip-slip-vulnerability
throw new RuntimeException("Entry with an illegal path: "
+ ze.getName());
}
if (ze.isDirectory()) {
Files.createDirectories(resolvedPath);
} else {
Files.createDirectories(resolvedPath.getParent());
Files.copy(zipIn, resolvedPath);
}
}
}
}
The createDirectories is needed in both branches because zip files not always contain all the parent directories as a separate entries, but might contain them only to represent empty directories.
The code addresses the ZIP-slip vulnerability, it fails if some ZIP entry would go outside of the targetDir. Such ZIPs are not created using the usual tools and are very likely hand-crafted to exploit the vulnerability.
Same can be achieved using Ant Compress library. It will preserve the folder structure.
Maven dependency:-
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant-compress</artifactId>
<version>1.2</version>
</dependency>
Sample code:-
Unzip unzipper = new Unzip();
unzipper.setSrc(theZIPFile);
unzipper.setDest(theTargetFolder);
unzipper.execute();
Here's an easy solution which follows more modern conventions. You may want to change the buffer size to be smaller if you're unzipping larger files. This is so you don't keep all of the files info in-memory.
public static void unzip(File source, String out) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(source))) {
ZipEntry entry = zis.getNextEntry();
while (entry != null) {
File file = new File(out, entry.getName());
if (entry.isDirectory()) {
file.mkdirs();
} else {
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
int bufferSize = Math.toIntExact(entry.getSize());
byte[] buffer = new byte[bufferSize > 0 ? bufferSize : 1];
int location;
while ((location = zis.read(buffer)) != -1) {
bos.write(buffer, 0, location);
}
}
}
entry = zis.getNextEntry();
}
}
}
This is the code I used to unzip a zip file with multiple directories. No external libraries used.
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class UnzipFile
{
public static void main(String[] args) throws IOException
{
String fileZip = "src/main/resources/abcd/abc.zip";
File destDir = new File("src/main/resources/abcd/abc");
try (ZipFile file = new ZipFile(fileZip))
{
Enumeration<? extends ZipEntry> zipEntries = file.entries();
while (zipEntries.hasMoreElements())
{
ZipEntry zipEntry = zipEntries.nextElement();
File newFile = new File(destDir, zipEntry.getName());
//create sub directories
newFile.getParentFile().mkdirs();
if (!zipEntry.isDirectory())
{
try (FileOutputStream outputStream = new FileOutputStream(newFile))
{
BufferedInputStream inputStream = new BufferedInputStream(file.getInputStream(zipEntry));
while (inputStream.available() > 0)
{
outputStream.write(inputStream.read());
}
inputStream.close();
}
}
}
}
}
}
Here is more "modern" complete code based on this post but refactored (and using Lombok):
import lombok.var;
import lombok.val;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipInputStream;
import static java.nio.file.Files.createDirectories;
public class UnZip
{
public static void unZip(String sourceZipFile, String outputDirectory) throws IOException
{
val folder = new File(outputDirectory);
createDirectories(folder.toPath());
try (val zipInputStream = new ZipInputStream(new FileInputStream(sourceZipFile, Charset.forName("Cp437"))))
{
var nextEntry = zipInputStream.getNextEntry();
while (nextEntry != null)
{
val fileName = nextEntry.getName();
val newFile = new File(outputDirectory + File.separator + fileName);
newFile.getParentFile().mkdirs();
if(fileName.endsWith("/")){
newFile.mkdirs();
} else {
writeFile(zipInputStream, newFile);
}
writeFile(zipInputStream, newFile);
nextEntry = zipInputStream.getNextEntry();
}
zipInputStream.closeEntry();
}
}
private static void writeFile(ZipInputStream inputStream, File file) throws IOException
{
val buffer = new byte[1024];
file.createNewFile();
try (val fileOutputStream = new FileOutputStream(file))
{
int length;
while ((length = inputStream.read(buffer)) > 0)
{
fileOutputStream.write(buffer, 0, length);
}
}
}
}
After using the other libraries I stumbled upon this one: https://github.com/thrau/jarchivelib
Far superior.
Gradle: implementation group: 'org.rauschig', name: 'jarchivelib', version: '1.2.0'
import org.rauschig.jarchivelib.ArchiveFormat;
import org.rauschig.jarchivelib.Archiver;
import org.rauschig.jarchivelib.ArchiverFactory;
import org.rauschig.jarchivelib.CompressionType;
public static void unzip(File zipFile, File targetDirectory) throws IOException, IllegalAccessException {
Archiver archiver = ArchiverFactory.createArchiver(ArchiveFormat.ZIP);
archiver.extract(zipFile, targetDirectory);
}
public static void unTarGz(File tarFile, File targetDirectory) throws IOException {
Archiver archiver = ArchiverFactory.createArchiver(ArchiveFormat.TAR, CompressionType.GZIP);
archiver.extract(tarFile, targetDirectory);
}
The other libraries get too complex for this simple task. That's why I love this library - 2 lines, done.
You should get all entries from your zip file:
Enumeration entries = zipFile.getEntries();
Then iterating over this enumeration get the ZipEntry from it, check whether it is a directory or not, and create directory or just extract a file respectively.
Based on petrs's answer, here's a kotlin version, that I am now using:
fun ZipInputStream.extractTo(target: File) = use { zip ->
var entry: ZipEntry
while (zip.nextEntry.also { entry = it ?: return } != null) {
val file = File(target, entry.name)
if (entry.isDirectory) {
file.mkdirs()
} else {
file.parentFile.mkdirs()
zip.copyTo(file.outputStream())
}
}
}