27

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?

Michael Berry
  • 70,193
  • 21
  • 157
  • 216
Waypoint
  • 17,283
  • 39
  • 116
  • 170

11 Answers11

41

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.

Valentin Michalak
  • 2,089
  • 1
  • 14
  • 27
javing
  • 12,307
  • 35
  • 138
  • 211
26

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.

Oliv
  • 10,221
  • 3
  • 55
  • 76
22

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();
        }
    }

}
dzikoysk
  • 1,560
  • 1
  • 15
  • 27
petrs
  • 405
  • 5
  • 10
11

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();
Kumar Sambhav
  • 7,503
  • 15
  • 63
  • 86
  • Hello, the class `org.apache.ant.compress.taskdefs.Unzip` don't have the method `setSrc`, `setDest` or `execute`. – Evan Hu Feb 02 '19 at 06:47
4

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();
        }
    }
}
  • Just learned the hard way that [ZipEntry#getSize() javadoc](https://docs.oracle.com/javase/8/docs/api/java/util/zip/ZipEntry.html#getSize--) ends with an ominous "...or -1 if size is not known" and that -1 pops up for every single real file in [my sample zip file](https://www-eu.apache.org/dist/maven/maven-3/3.6.0/binaries/apache-maven-3.6.0-bin.zip). I made a small edit to avoid the long parade of NegativeArraySizeExceptions. – Erik Ostermueller Apr 01 '19 at 04:49
3

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();
          }
        }

      }
    }
  }

}
Chamlini D
  • 31
  • 3
1

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);
            }
        }
    }
}
Lavandysh
  • 531
  • 1
  • 7
  • 15
BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
  • Hi, I've had some trouble using your code if the zip file contained more then just files. I had to adjust two things in my scenario. Folders were created as a textfile and then everything crashed. I've added this check before writefile: if(fileName.endsWith("/")){ newFile.mkdirs(); } else { writeFile(zipInputStream, newFile); } So that it can handle directories and added a charset to my constructor so that it could process files with special symbols. new ZipInputStream(new FileInputStream(sourceZipFile), Charset.forName("Cp437"))) May I add this to your code? – Lavandysh Apr 16 '19 at 09:51
  • Sure, please post the full code and I will edit it into my answer – BullyWiiPlaza Apr 16 '19 at 10:03
  • Hi, I've tried posting the code in a comment, but I'm afraid that's impossible, and I do not want to write it as a separate answer. I can edit your code directly, but I'd rather have your permission first. You can always undo the changes. – Lavandysh Apr 16 '19 at 12:04
  • Yeah, sure. I was thinking you could link to your code snippet on `Pastebin` etc. – BullyWiiPlaza Apr 16 '19 at 16:31
  • Next time I will use those, thanks for the hint. I've made the edits, you can review. – Lavandysh Apr 18 '19 at 16:40
1

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.

Nicholas DiPiazza
  • 10,029
  • 11
  • 83
  • 152
0

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.

svarog
  • 9,477
  • 4
  • 61
  • 77
Alex Stybaev
  • 4,623
  • 3
  • 30
  • 44
  • This is the part I actually need... I have access to my folder in ZIP and want to store it in sdcard/foldername with its contents from ZIP. How to do that? – Waypoint May 17 '12 at 10:14
  • 1
    well, I think you should try to write some code, have a look at some examples and if you fail or get stuck - come back here with your code. – Alex Stybaev May 17 '12 at 10:18
0

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())
        }
    }
}
K.H.
  • 1,383
  • 13
  • 33
0

An alternative of the very nice answer, still ZIP-slip vulnerability free, library-free, and Java 7+ variant but based on streams:

public static void unzip(File zipFile, Path targetDir) throws IOException {
    // Normalize the path to get rid parent folder accesses
    Path targetRoot = targetDir.normalize();
    // Open the zip file as a FileSystem
    try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + zipFile.toURI()), Map.of())) {
        for (Path root : fs.getRootDirectories()) {
            try (Stream<Path> walk = Files.walk(root)) {
                // For each path  in the zip
                walk.forEach(
                    source -> {
                        // Compute the target path of the file in the destination folder
                        Path target = targetRoot.resolve(root.relativize(source).toString()).normalize();
                        if (target.startsWith(targetRoot)) {
                            // Only copy safe files
                            // see: https://snyk.io/research/zip-slip-vulnerability
                            try {
                                if (Files.isDirectory(source)) {
                                    // Create folders
                                    Files.createDirectories(target);
                                } else {
                                    // Copy the file to the destination
                                    Files.copy(source, target);
                                }
                            } catch (IOException e) {
                                throw new UncheckedIOException(e);
                            }
                        }
                    });
            }
        }
    }
}
Nicolas Filotto
  • 43,537
  • 11
  • 94
  • 122