Java ZIP - how to unzip folder?
Asked Answered
G

11

29

Is there any sample code, how to practically 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?

Groh answered 17/5, 2012 at 10:5 Comment(0)
A
42

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.

Aveyron answered 12/4, 2013 at 11:10 Comment(0)
H
30

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.

Hang answered 3/1, 2020 at 16:21 Comment(3)
flawless answer @HangHeterolysis
great answer, especially the vulnerability hint!Polythene
This is good. Just note that it does not cover other kinds of zip vulnerabilities like zip bombs. So if you are accepting zip files from unknown/untrusted sources, make sure you read up on zip vulnerabilities and covering all the most common ones.Hawsehole
K
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();
        }
    }

}
Kakemono answered 17/5, 2012 at 11:6 Comment(1)
You should not swallow IOException.Scapular
R
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();
Rase answered 20/3, 2013 at 4:4 Comment(1)
Hello, the class org.apache.ant.compress.taskdefs.Unzip don't have the method setSrc, setDest or execute.Karame
U
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();
        }
    }
}
Unpleasantness answered 21/1, 2017 at 7:42 Comment(1)
Just learned the hard way that ZipEntry#getSize() javadoc 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. I made a small edit to avoid the long parade of NegativeArraySizeExceptions.Voluminous
C
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();
          }
        }

      }
    }
  }

}
Coronado answered 17/6, 2020 at 9:23 Comment(0)
J
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);
            }
        }
    }
}
Jewelfish answered 12/2, 2018 at 15:26 Comment(5)
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?Climax
Sure, please post the full code and I will edit it into my answerJewelfish
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.Climax
Yeah, sure. I was thinking you could link to your code snippet on Pastebin etc.Jewelfish
Next time I will use those, thanks for the hint. I've made the edits, you can review.Climax
R
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.

Romola answered 19/4, 2022 at 1:16 Comment(0)
M
1

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);
                            }
                        }
                    });
            }
        }
    }
}
Municipal answered 3/6, 2023 at 9:53 Comment(0)
H
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.

Hemispheroid answered 17/5, 2012 at 10:13 Comment(2)
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?Groh
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.Hemispheroid
S
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())
        }
    }
}
Shae answered 1/7, 2022 at 8:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.