GithubHelp home page GithubHelp logo

srikanth-lingala / zip4j Goto Github PK

View Code? Open in Web Editor NEW
2.0K 45.0 301.0 5.16 MB

A Java library for zip files and streams

License: Apache License 2.0

Java 99.96% Shell 0.04%
zip4j zip-encryption java-zip split-zip

zip4j's Introduction

javadoc Maven Central

Build Status Android Build Status Known Vulnerabilities

Zip4j - A Java library for zip files / streams

Thank you

for rating Zip4j as the best Java library for zip files [1, 2, 3, 4]. It has encouraged me to bring this project to life again after a gap of several years. I tried to add some of the important features that were requested over this time, and also made the API much more neater. The newer version (> 2.0.0) now supports streams, which was understandably, one of the most requested feature. If you have any feedback, bugs to report, feature requests, etc, please open an issue here on GitHub. I will try to address them as soon as I can. I also monitor the tag zip4j on Stack Overflow.

About

Zip4j is the most comprehensive Java library for zip files or streams. As of this writing, it is the only Java library which has support for zip encryption, apart from several other features. It tries to make handling zip files/streams a lot more easier. No more clunky boiler plate code with input streams and output streams. As you can see in the usage section below, working with zip files can now even be a single line of code, compared to this. I mean no offense to the Java's built-in zip support. In fact, this library depends on Java's built-in zip code and it would have been significantly more complicated challenging if I had to write compression logic as well. But lets be honest, working with zip files or streams can be a lot of boiler plate code. The main goal of this library is to provide a simple API for all usual actions of a zip file or streams by doing the heavy lifting within the library and not have developers worry about having to deal with streams, etc. Apart from usability, another important goal of this library is to provide support for as many zip features as possible, which brings me to:

Features

  • Create, Add, Extract, Update, Remove files from a zip file
  • Support for streams (ZipInputStream and ZipOutputStream)
  • Read/Write password protected zip files and streams
  • Support for both AES and zip standard encryption methods
  • Support for Zip64 format
  • Store (No Compression) and Deflate compression method
  • Create or extract files from split zip files (Ex: z01, z02,...zip)
  • Support for Unicode file names and comments in zip
  • Progress Monitor - for integration into apps and user facing applications

Background

Zip4j was started by me (Srikanth Reddy Lingala) back in 2008/2009, when I realized the lack of support for majority of zip format features in Java. And also working with zip files was, as mentioned several times above, a lot of boiler plate code, having to deal with streams (worse still, it was back in the days when there was no try-with-resources in Java). There was also no comprehensive library which supports zip features. So, I decided to write one, and approximately after a year, the first version was out. The response was truly overwhelming, and I got a lot of support right from the next day of release. It was not put on GitHub as git/GitHub was not as popular as it is now. Code was hosted on my website, as, guess what, a zip file :). And unfortunately, after a year or two after the initial release, life got busy and I was not able to support Zip4j as much as I wanted to. But the overwhelming encouragement I got over the years made me start working on Zip4j once again, and makes me support Zip4j as much as I can.

Requirements

JDK 7 or later*

* Zip4j is written on JDK 8, as some of the features (NIO) that Zip4j supports requires features available only in JDK 8. However, considering the fact that Zip4j is widely used in Android, and to support older versions of Android, Zip4j supports JDK 7 as well. In cases where the feature/class from JDK 8 is missing, Zip4j falls back to the features available in JDK 7. In other words, when running on JDK 7, not all features will be supported.

Maven

<dependency>
    <groupId>net.lingala.zip4j</groupId>
    <artifactId>zip4j</artifactId>
    <version>2.11.5</version>
</dependency>

Please check the latest version number on Maven Central.

Usage

Creating a zip file with single file in it / Adding single file to an existing zip

new ZipFile("filename.zip").addFile("filename.ext");

   Or

new ZipFile("filename.zip").addFile(new File("filename.ext"));

Creating a zip file with multiple files / Adding multiple files to an existing zip

new ZipFile("filename.zip").addFiles(Arrays.asList(new File("first_file"), new File("second_file")));

Creating a zip file by adding a folder to it / Adding a folder to an existing zip

new ZipFile("filename.zip").addFolder(new File("/users/some_user/folder_to_add"));

Since v2.6, it is possible to exclude certain files when adding a folder to zip by using an ExcludeFileFilter

ExcludeFileFilter excludeFileFilter = filesToExclude::contains;
ZipParameters zipParameters = new ZipParameters();
zipParameters.setExcludeFileFilter(excludeFileFilter);
new ZipFile("filename.zip").addFolder(new File("/users/some_user/folder_to_add"), zipParameters);

Creating a zip file from stream / Adding a stream to an existing zip

new ZipFile("filename.zip").addStream(inputStream, new ZipParameters());

Passing in new ZipParameters(), as in the above example, will make Zip4j use default zip parameters. Please look at ZipParameters to see the default configuration.

Creating a zip file of compression method STORE / Adding entries to zip file of compression method STORE

By default Zip4j uses Deflate compression algorithm to compress files. However, if you would like to not use any compression (called STORE compression), you can do so as shown in the example below:

ZipParameters zipParameters = new ZipParameters();
zipParameters.setCompressionMethod(CompressionMethod.STORE);

new ZipFile("filename.zip").addFile("fileToAdd", zipParameters);

You can similarly pass in zip parameters to all the other examples to create a zip file of STORE compression.

Creating a password protected zip file / Adding files to an existing zip with password protection

AES encryption
ZipParameters zipParameters = new ZipParameters();
zipParameters.setEncryptFiles(true);
zipParameters.setEncryptionMethod(EncryptionMethod.AES);
// Below line is optional. AES 256 is used by default. You can override it to use AES 128. AES 192 is supported only for extracting.
zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256); 

List<File> filesToAdd = Arrays.asList(
    new File("somefile"), 
    new File("someotherfile")
);

ZipFile zipFile = new ZipFile("filename.zip", "password".toCharArray());
zipFile.addFiles(filesToAdd, zipParameters);
Zip standard encryption

Instead of AES, replace zipParameters.setEncryptionMethod(EncryptionMethod.AES); with zipParameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);. You can omit the line to set AES key strength. As the name suggests, this is only applicable for AES encryption.

In all the above examples, you can similarly pass in zip parameters with appropriate password configuration to create a password protected zip file.

Creating a split zip file

If you want to split the zip file over several files when the size exceeds a particular limit, you can do so like this:

List<File> filesToAdd = Arrays.asList(
    new File("somefile"), 
    new File("someotherfile")
);

ZipFile zipFile = new ZipFile("filename.zip");
zipFile.createSplitZipFile(filesToAdd, new ZipParameters(), true, 10485760); // using 10MB in this example

Passing in new ZipParameters(), as in the above example, will make Zip4j use default zip parameters. Please look at ZipParameters to see the default configuration.

Zip file format specifies a minimum of 65536 bytes (64KB) as a minimum length for split files. Zip4j will throw an exception if anything less than this value is specified.

To create a split zip with password protection, pass in appropriate ZipParameters as shown in the example below:

ZipParameters zipParameters = new ZipParameters();
zipParameters.setEncryptFiles(true);
zipParameters.setEncryptionMethod(EncryptionMethod.AES);

List<File> filesToAdd = Arrays.asList(
    new File("somefile"), 
    new File("someotherfile")
);

ZipFile zipFile = new ZipFile("filename.zip", "password".toCharArray());
zipFile.createSplitZipFile(filesToAdd, zipParameters, true, 10485760); // using 10MB in this example

Zip64 format

Zip64 is a zip feature which allows support for zip files when the size of the zip file exceeds the maximum that can be stored in 4 bytes (i.e., greater than 4,294,967,295 bytes). Traditionally, zip headers have a provision of 4 bytes to store for file sizes. But with growing file sizes compared to a few decades back, zip file format extended support of file sizes which extends 4 bytes by adding additional headers which uses 8 bytes for file sizes (compressed and uncompressed file sizes). This feature is known as Zip64.

Zip4j will automatically make a zip file with Zip64 format and add appropriate headers, when it detects the zip file to be crossing this file size limit. You do not have to explicitly specify any flag for Zip4j to use this feature.

Extracting all files from a zip

new ZipFile("filename.zip").extractAll("/destination_directory");

Extracting all files from a password protected zip

new ZipFile("filename.zip", "password".toCharArray()).extractAll("/destination_directory");

Extracting a single file from zip

new ZipFile("filename.zip").extractFile("fileNameInZip.txt", "/destination_directory");

Extracting a folder from zip (since v2.6.0)

new ZipFile("filename.zip").extractFile("folderNameInZip/", "/destination_directory");

Extracting a single file from zip which is password protected

new ZipFile("filename.zip", "password".toCharArray()).extractFile("fileNameInZip.txt", "/destination_directory");

Since v2.6.0: If the file name represents a directory, Zip4j will extract all files in the zip that are part of this directory.

Extracting a single file from zip and giving it a new file name

Below example will extract the file fileNameInZip.txt from the zip file to the output directory /destination_directory and will give the file the name newfileName.txt. Without the third parameter of the new file name, the same name as the file in the zip will be used, which in this case is fileNameInZip.txt. If the file being extracted is a directory, newFileName parameter will be used as the directory name.

new ZipFile("filename.zip", "password".toCharArray()).extractFile("fileNameInZip.txt", "/destination_directory", "newfileName.txt");

Get an input stream for an entry in a zip file

ZipFile zipFile = new ZipFile("filename.zip");
FileHeader fileHeader = zipFile.getFileHeader("entry_name_in_zip.txt");
InputStream inputStream = zipFile.getInputStream(fileHeader);

You can now use this input stream to read content from it/write content to an output stream. Please note that the entry/file name is relative to the directory it is in. If entry_name_in_zip.txt is in a folder called "root_folder" in the zip, then you have to use zipFile.getFileHeader("root_folder/entry_name_in_zip.txt");.

Remove a file/entry from a zip file

new ZipFile("filename.zip").removeFile("fileNameInZipToRemove");

If fileNameInZipToRemove represents a folder all the files and folders under this folder will be removed as well (this is valid since v2.5.0 of Zip4j. All prior versions remove just the single entry even if it is a folder).

Please note that the file name is relative the root folder in zip. That is, if the file you want to remove exists in a folder called "folder1", which in-turn exists in a folder called "root-folder", removing this file from zip has to be done as below:

new ZipFile("filename.zip").removeFile("root-folder/folder1/fileNameInZipToRemove");

If you want to be sure that the file you want to remove exists in zip file or if you don't want to deal with file names as string when using the removeFile API, you can use the other overloaded method which takes in a FileHeader:

ZipFile zipFile = new ZipFile("someZip.zip");
FileHeader fileHeader = zipFile.getFileHeader("fileNameInZipToRemove");

if (fileHeader == null) {
  // file does not exist
}

zipFile.removeFile(fileHeader);

Since v2.5.0 of Zip4j, it is possible to remove multiple files and folders from a zip file. You can now pass in a list as shown in the code below:

ZipFile zipFile = new ZipFile("someZip.zip");
List<String> filesToRemove = Arrays.asList("file1.txt", "file2.txt", "some-folder/", "some-new-folder-1/somefile.pdf");

zipFile.removeFiles(filesToRemove);

The above code will remove file1.txt, file2.txt, all files and folders under some-folder (including some-folder) and just the entry somefile.pdf in folder some-new-folder-1. All other files and folders are kept intact in the zip file.

Rename entries in the zip file

There are three ways to rename an entry in a zip file with Zip4j. One way is to pass in a file header and the new file name:

ZipFile zipFile = new ZipFile("sample.zip");
FileHeader fileHeader = zipFile.getFileHeader("entry-to-be-changed.pdf");
zipFile.renameFile(fileHeader, "new-file-name.pdf");

Second way is to pass in just the file name to be changed (instead of the file header), and the new file name.

new ZipFile("filename.zip").renameFile("entry-to-be-changed.pdf", "new-file-name.pdf");

It is also possible to change multiple file names at once. In this case you have to use a map, with the key of the entry in the map being the entry to be changed, and the value of the map being the new file name:

Map<String, String> fileNamesMap = new HashMap<>();
fileNamesMap.put("firstFile.txt", "newFileFirst.txt");
fileNamesMap.put("secondFile.pdf", "newSecondFile.pdf");
fileNamesMap.put("some-folder/thirdFile.bin", "some-folder/newThirdFile.bin");
new ZipFile("filename.zip").renameFiles(fileNamesMap);

To modify an entry name which is inside a folder, the new file name should contain the complete parent path as well. For example, if an entry by the name some-entry.pdf is in the folder some-folder/some-sub-folder/, to modify this entry name to some-new-entry.pdf:

new ZipFile("filename.zip").renameFile("some-folder/some-sub-folder/some-entry.pdf", "some-folder/some-sub-folder/new-entry.pdf");

If the parent path is missing, then the file will be put at the root of the zip file. In the below example, after the file is renamed, some-new-entry.pdf will exist at the root of the zip file instead of at some-folder/some-sub-folder/:

new ZipFile("filename.zip").renameFile("some-folder/some-sub-folder/some-entry.pdf", "some-new-entry.pdf");

This also gives the flexibility to "move" the entry to a different folder. The below example will move the some-entry.pdf from some-folder/some-sub-folder/ to folder-to-be-moved-to/sub-folder/ and the file will also be renamed to new-entry.pdf. To just move the file, use the same file name instead of a new file name.

new ZipFile("filename.zip").renameFile("some-folder/some-sub-folder/some-entry.pdf", "folder-to-be-moved-to/sub-folder/new-entry.pdf");

If the entry being modified is a directory, all entries that are part of that directory will be renamed so that all of them have the new folder name as parent. In zip format, all entry names under a directory will contain the full name as their file name. For example if there is an entry by the name filename.txt inside a directory directoryName, the file name for the entry will be directoryName/filename.txt. And if the name of the directory has now been changed to newDirectoryName, the entry under it will also be changed to newDirectoryName/filename.txt, so when the zip file is extracted, filename.txt will be under newDirectoryName.

Zip file format does not allow modifying split zip files, and Zip4j will throw an exception if an attempt is made to rename files in a split zip file.

Merging split zip files into a single zip

This is the reverse of creating a split zip file, that is, this feature will merge a zip file which is split across several files into a single zip file:

new ZipFile("split_zip_file.zip").mergeSplitFiles(new File("merged_zip_file.zip"));

This method will throw an exception if the split zip file (in this case split_zip_file.zip) is not a split zip file.

List all files in a zip

List<FileHeader> fileHeaders = new ZipFile("zipfile.zip").getFileHeaders();
fileHeaders.stream().forEach(fileHeader -> System.out.println(fileHeader.getFileName()));

You can get all other information from the FileHeader object corresponding to each file/entry in the zip.

Check if a zip file is password protected

new ZipFile("encrypted_zip_file.zip").isEncrypted();

Check if a zip file is a split zip file

new ZipFile("split_zip_file.zip").isSplitArchive();

Set comment for a zip file

new ZipFile("some_zip_file.zip").setComment("Some comment");

Remove comment of a zip file

new ZipFile("some_zip_file.zip").setComment("");

Get comment of a zip file

new ZipFile("some_zip_file.zip").getComment();

Check if a zip file is valid

Note: This will only check for the validity of the headers and not the validity of each entry in the zip file.

new ZipFile("valid_zip_file.zip").isValidZipFile();

Working with streams

Adding entries with ZipOutputStream

import net.lingala.zip4j.io.outputstream.ZipOutputStream;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class ZipOutputStreamExample {

  public void zipOutputStreamExample(File outputZipFile, List<File> filesToAdd, char[] password,  
                                     CompressionMethod compressionMethod, boolean encrypt,
                                     EncryptionMethod encryptionMethod, AesKeyStrength aesKeyStrength)
      throws IOException {

    ZipParameters zipParameters = buildZipParameters(compressionMethod, encrypt, encryptionMethod, aesKeyStrength);
    byte[] buff = new byte[4096];
    int readLen;

    try(ZipOutputStream zos = initializeZipOutputStream(outputZipFile, encrypt, password)) {
      for (File fileToAdd : filesToAdd) {

        // Entry size has to be set if you want to add entries of STORE compression method (no compression)
        // This is not required for deflate compression
        if (zipParameters.getCompressionMethod() == CompressionMethod.STORE) {
          zipParameters.setEntrySize(fileToAdd.length());
        }

        zipParameters.setFileNameInZip(fileToAdd.getName());
        zos.putNextEntry(zipParameters);

        try(InputStream inputStream = new FileInputStream(fileToAdd)) {
          while ((readLen = inputStream.read(buff)) != -1) {
            zos.write(buff, 0, readLen);
          }
        }
        zos.closeEntry();
      }
    }
  }

  private ZipOutputStream initializeZipOutputStream(File outputZipFile, boolean encrypt, char[] password) 
      throws IOException {
    
    FileOutputStream fos = new FileOutputStream(outputZipFile);

    if (encrypt) {
      return new ZipOutputStream(fos, password);
    }

    return new ZipOutputStream(fos);
  }

  private ZipParameters buildZipParameters(CompressionMethod compressionMethod, boolean encrypt,
                                           EncryptionMethod encryptionMethod, AesKeyStrength aesKeyStrength) {
    ZipParameters zipParameters = new ZipParameters();
    zipParameters.setCompressionMethod(compressionMethod);
    zipParameters.setEncryptionMethod(encryptionMethod);
    zipParameters.setAesKeyStrength(aesKeyStrength);
    zipParameters.setEncryptFiles(encrypt);
    return zipParameters;
  }
}

Extract files with ZipInputStream

import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.model.LocalFileHeader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class ZipInputStreamExample {
  
  public void extractWithZipInputStream(File zipFile, char[] password) throws IOException {
    LocalFileHeader localFileHeader;
    int readLen;
    byte[] readBuffer = new byte[4096];

    InputStream inputStream = new FileInputStream(zipFile);
    try (ZipInputStream zipInputStream = new ZipInputStream(inputStream, password)) {
      while ((localFileHeader = zipInputStream.getNextEntry()) != null) {
        File extractedFile = new File(localFileHeader.getFileName());
        try (OutputStream outputStream = new FileOutputStream(extractedFile)) {
          while ((readLen = zipInputStream.read(readBuffer)) != -1) {
            outputStream.write(readBuffer, 0, readLen);
          }
        }
      }
    }
  }
}

Working with Progress Monitor

ProgressMonitor makes it easier for applications (especially user facing) to integrate Zip4j. It is useful to show progress (example: updating a progress bar, displaying the current action, show file name being worked on, etc.). To use ProgressMonitor, you have to set ZipFile.setRunInThread(true). This will make any actions being done on the zip file to run in a background thread. You can then access ProgressMonitor Zipfile.getProgressMonitor() and get details of the current action being done along with the percentage work done, etc. Below is an example:

ZipFile zipFile = new ZipFile(generatedZipFile, PASSWORD);
ProgressMonitor progressMonitor = zipFile.getProgressMonitor();

zipFile.setRunInThread(true);
zipFile.addFolder(new File("/some/folder"));

while (!progressMonitor.getState().equals(ProgressMonitor.State.READY)) {
  System.out.println("Percentage done: " + progressMonitor.getPercentDone());
  System.out.println("Current file: " + progressMonitor.getFileName());
  System.out.println("Current task: " + progressMonitor.getCurrentTask());

  Thread.sleep(100);
}

if (progressMonitor.getResult().equals(ProgressMonitor.Result.SUCCESS)) {
  System.out.println("Successfully added folder to zip");
} else if (progressMonitor.getResult().equals(ProgressMonitor.Result.ERROR)) {
  System.out.println("Error occurred. Error message: " + progressMonitor.getException().getMessage());
} else if (progressMonitor.getResult().equals(ProgressMonitor.Result.CANCELLED)) {
  System.out.println("Task cancelled");
}

Note that in the above example, addFolder() will almost immediately return back the control to the caller. The client code can then perform a loop until the state gets back to "Ready" as shown in the above example.

Similarly, ProgressMonitor can be used with other actions like, addFiles, removeFiles and extractFiles.

Contribution

It is hard to find as much free time as I used to have when I first started Zip4j back in 2009. I would highly appreciate any support I can get for this project. You can fork this project, and send me pull requests for any bug fixes, issues mentioned here or new features. If you need any support in understanding the code or zip specification, just drop me a mail and I will help you as best as I can. (See FAQ for my email address.)

FAQ

  1. Why do I have to pass in password as char array and not as a string?

    That's why

  2. How can I contact you?

    [email protected]

  3. Are unicode file names supported?

    Yes, unicode file names (UTF-8) are supported as specified by the zip format specification. Zip4j will use UTF-8 file name and file comment encoding when creating a zip file. When extracting a zip file, Zip4j will only use UTF-8 encoding, only if the appropriate header flag is set as specified by zip file format specification. If this flag is not set, Zip4j will use CP437 encoding which only supports extended ASCII characters.

  4. Where can I find zip file format specification?

    Here

  5. Why are there so many changes in version 2.x compared to 1.x?

    Because when version 1.x was written back in 2009, Zip4j was badly in need of a face-lift and code modernization. Also, my coding standards have improved over the years (or at least that's what I like to think). Although I am proud of the work I did with Zip4j back in 2009, some parts of the code make me feel like hiding my face in shame. One such example is the usage of ArrayList instead of List. API and code should look much neater now. And also, Zip4j now supports a minimum of JRE 8, as compared to JRE 5 with 1.x, which obviously will bring some nice features that I can make use of. (For example: no more explicitly closing the streams all over the code). If you still feel like something can be improved (and I am pretty sure that there are things to be improved), please let me know by opening an issue here or writing to me (my email address is in point #2 above).

  6. What are the licensing conditions for older releases of Zip4j?

    All releases of Zip4j, from version 1.0, are licensed under Apache License 2.0

zip4j's People

Contributors

aljchristensen avatar asbachb avatar bio007 avatar chenzhang22 avatar danbodoh avatar dependabot[bot] avatar dunemaster avatar enoyhs avatar exceptionfactory avatar fpavageau avatar gr33nbl00d avatar huddeldaddel avatar huyngo1407 avatar iinegve avatar j05u3 avatar jarfiles avatar jlleitschuh avatar joorei avatar leeyoung624 avatar marcono1234 avatar mrpans avatar playera avatar salphaon avatar shannah avatar slp091020 avatar srikanth-lingala avatar thomasperkins1123 avatar yarix avatar yausername avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zip4j's Issues

zip4j 2.0.3: when execute ZipFile.getInputStream(FileHeader), java.lang.ArrayIndexOutOfBoundsException happened.

Code:

public static void main(String[] args)
{
	try
	{
		String zipFilePath = "D:\\temp\\GoogleInstaller_3.0.zip";
		String filePathInZip = "classes.dex";
		ZipFile zipFile = new ZipFile(zipFilePath);				
		FileHeader fileHeader = zipFile.getFileHeader(filePathInZip);
		
		if(null != fileHeader && !fileHeader.isDirectory())
		{
			zipFile.getInputStream(fileHeader);
		}
	}
	catch(Exception e)
	{
		e.printStackTrace();
	}
}

Exception:
java.lang.ArrayIndexOutOfBoundsException: 3
at net.lingala.zip4j.util.RawIO.readShortLittleEndian(RawIO.java:107)
at net.lingala.zip4j.headers.HeaderReader.parseExtraDataRecords(HeaderReader.java:303)
at net.lingala.zip4j.headers.HeaderReader.readExtraDataRecords(HeaderReader.java:279)
at net.lingala.zip4j.headers.HeaderReader.readExtraDataRecords(HeaderReader.java:256)
at net.lingala.zip4j.headers.HeaderReader.readCentralDirectory(HeaderReader.java:208)
at net.lingala.zip4j.headers.HeaderReader.readAllHeaders(HeaderReader.java:75)
at net.lingala.zip4j.ZipFile.readZipInfo(ZipFile.java:831)
at net.lingala.zip4j.ZipFile.getFileHeader(ZipFile.java:565)
at com.dancen.util.filecompressor.MyZipUtil.main(MyZipUtil.java:37)

Usually, I can execute ZipFile.getInputStream(FileHeader) successfull, but for the file "GoogleInstaller_3.0.zip", ArrayIndexOutOfBoundsException throwed. I have attached the file.

GoogleInstaller_3.0.zip

AES encrypted files sometimes produce wrong checksums

Hi,

when I create a password protected zip file I get the following error message during uncompress:

net.lingala.zip4j.exception.ZipException: java.io.IOException: Reached end of data for this entry, but aes verification failed
	at net.lingala.zip4j.tasks.AsyncZipTask.performTaskWithErrorHandling(AsyncZipTask.java:48)
	at net.lingala.zip4j.tasks.AsyncZipTask.execute(AsyncZipTask.java:35)
	at net.lingala.zip4j.ZipFile.extractAll(ZipFile.java:431)

when compressing files with a specific compressed size. See
zip4j-failing-compress.zip for an example (Maven project with failing unit test).

I guess the issue occurs when net.lingala.zip4j.io.outputstream.CompressedOutputStream.decrementBytesWrittenForThisEntry(int) is called. Maybe the length is calculated correct but the checksum is using a wrong range.

The issue is reproducible when the stacktrace runs through net.lingala.zip4j.io.outputstream.DeflaterOutputStream.deflate() and the len is in between 1..3

The resulting zip file can be decompressed, but throws an error when using zip4j, 7z and winrar.

2.1.0 closable leak when extracting zip file

when calling ZipFile.extractAll with StrictMode on there is a crash due to a unclosed resource.

Similar to issue #3

'mainapp E/StrictMode: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
java.lang.Throwable: Explicit termination method 'close' not called
at dalvik.system.CloseGuard.open(CloseGuard.java:223)
at java.io.RandomAccessFile.(RandomAccessFile.java:282)
at net.lingala.zip4j.io.inputstream.SplitInputStream.(SplitInputStream.java:22)
at net.lingala.zip4j.tasks.ExtractAllFilesTask.prepareZipInputStream(ExtractAllFilesTask.java:59)
at net.lingala.zip4j.tasks.ExtractAllFilesTask.executeTask(ExtractAllFilesTask.java:26)
at net.lingala.zip4j.tasks.ExtractAllFilesTask.executeTask(ExtractAllFilesTask.java:13)
at net.lingala.zip4j.tasks.AsyncZipTask.performTaskWithErrorHandling(AsyncZipTask.java:41)
at net.lingala.zip4j.tasks.AsyncZipTask.execute(AsyncZipTask.java:35)
at net.lingala.zip4j.ZipFile.extractAll(ZipFile.java:431)'

Question about coding

Hello, I tried to open a compressed package compressed under Windows with zip4j, which contains the Chinese file name. The previous 1.x version has a "setFileNameCharset" method, but now it seems to be gone?

Dynamically set password after ZipFile creation

Hey @srikanth-lingala,

thanks for maintaining such a top notch library. I wanted to upgrade from 1.3.2 to 2.1.2, but have a question concerning the API change.

Use case:
I have to process incoming zip files. They can be encrypyed or not, I do not know upfront. If they are encrypted i know how to generate the password from the file name.

With the old API, I could create a ZipFile and check its encryption status, and if true I could set the password:

Zipfile zip = new ZipFile(filename);
if( zip.isEncrypted() ){
  String password = generatePassword(filename);
  zip.setPassword(password);
}
// ... process zip

AFAIK, with the new API, I can only set the password when creating the ZipFile. I cannot set it afterwards. Also not via the ZipParameters, which was also possible with the old API.

Question:
How can i handle this use case with the new API?
Or in other term, what happens when I proactively/preventively supply a password, but the zip is not actually encrypted. Does extraction still work correctly?

String meaninglessPassword = generatePassword(nonEncryptedFilename);
Zipfile zip = new ZipFile(nonEncryptedFilename, meaninglessPassword);
// ... process zip correctly?

Thanks for looking into this.

Maybe you should add an api that unpacks all the files with keywords?

And, I added some extensions for the zip4j kotlin version and added an api for him, like this:

file.extractTo(destination, "keyword1/fileName", "keyword2/fileName")

Actually in java it should be:

ZipFile().extractFile(String, String...)

maybe you should add it in zip4j, thank you very much, zip4j is a very great library, It is the best library I have used. The imperfection is that its api is not perfect.

CompressionMethod.STORE requires ZipParameters EntrySize to be set in advance

ZipOutputStream.java contains the following piece of code, which I do not understand:

private void verifyZipParameters(ZipParameters zipParameters) {
    if (zipParameters.getCompressionMethod() == CompressionMethod.STORE
        && zipParameters.getEntrySize() < 0
        && !isEntryDirectory(zipParameters.getFileNameInZip())) {
      throw new IllegalArgumentException("uncompressed size should be set for zip entries of compression type store");
    }
  }

This IllegalArgumentException is thrown when I call zipFile.addStream(myInputStream, myParameters);

Note that the default entry size is set to -1 (hence the above exception is thrown).
I can avoid this exception by calling myParameters.setEntrySize(?) with a nonnegative value.

My question is: Why do I need to set the entry size ahead of time? Why only for CompressionMethod.STORE? This was not necessary for 1.X versions of Zip4j.
Is this a bug?
If it is not a bug: Please document this change.
It seems strange to set the entry size even before adding the input stream to the zip file.

I would expect that the entry size is determined automatically once the InputStream has reached its end.

Getting "Incorrect password" in version 2

I'm migrating to version 2 and having issues getting encryption to work. I'm just trying the standard zip method for now and it seems to create the zip fine, but both gnu unzip and 7z both say I have the wrong password when I try to decrypt it. I have the same issue using Store instead of Deflate.

Simple test program:

import net.lingala.zip4j.io.outputstream.ZipOutputStream;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.CompressionLevel;
import net.lingala.zip4j.model.enums.CompressionMethod;
import net.lingala.zip4j.model.enums.EncryptionMethod;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class ztest {
    public static void main(final String[] args) throws Exception {
        ByteArrayOutputStream bao = new ByteArrayOutputStream(1024 * 64);

        try (
              ZipOutputStream zf = new ZipOutputStream(bao, "t".toCharArray())
        ) {
            ZipParameters zparam = new ZipParameters();
            zparam.setCompressionLevel(CompressionLevel.NORMAL);
            zparam.setCompressionMethod(CompressionMethod.DEFLATE);
            zparam.setEncryptFiles(true);
            zparam.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);

            zparam.setFileNameInZip("test1.txt");
            zf.putNextEntry(zparam);
            zf.write("test contents 1".getBytes());
            zf.closeEntry();
        }

        Files.copy(
              new ByteArrayInputStream(bao.toByteArray()),
              Paths.get("test.zip"),
              StandardCopyOption.REPLACE_EXISTING
        );
    }
}

Trying to extract the file:

$ unzip test.zip
Archive:  test.zip
[test.zip] test1.txt password:
password incorrect--reenter:
$ 7z -pt x test.zip

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=utf8,Utf16=on,HugeFiles=on,64 bits,12 CPUs x64)

Scanning the drive for archives:
1 file, 159 bytes (1 KiB)

Extracting archive: test.zip
--
Path = test.zip
Type = zip
Physical Size = 159

ERROR: Wrong password : test1.txt

Sub items Errors: 1

Archives with Errors: 1

Sub items Errors: 1

java.lang.NoClassDefFoundError: java.nio.charset.StandardCharsets

java.lang.NoClassDefFoundError: java.nio.charset.StandardCharsets
at net.lingala.zip4j.headers.HeaderUtil.decodeStringWithCharset(HeaderUtil.java:67)
at net.lingala.zip4j.headers.HeaderReader.readCentralDirectory(HeaderReader.java:196)
at net.lingala.zip4j.headers.HeaderReader.readAllHeaders(HeaderReader.java:75)
at net.lingala.zip4j.ZipFile.readZipInfo(ZipFile.java:831)
at net.lingala.zip4j.ZipFile.isValidZipFile(ZipFile.java:791)

Unable to decrypt zip file that was created with StandardZipEncryption

A zip file created with Zip4j which is encrypted with StandardZipEncryption cannot be extracted with other tools (7Zip, The Unarchiver, Keka, etc). It works if the zip file is created using Outputstreams but not with ZipFIle api.

Difference between them is that when outputstreams are used, lastmodifiedfiletime is used as key for encryption, and when ZipFIle api is used, crc has to be used as key for encryption.

Incorrect exception message shown for wrong password error

Hi,
I'm using latest version v2.0.3 and I am getting incorrect exception messages when trying to unzip password-protected zip files created with 7zip using ZipCrypto enctyption method and providing wrong password.
Steps to reproduce:

  1. Create a zip file test.txt using 7Zip, provide a password and choose Archive Format: zip, Encryption method: ZipCrypto.
  2. Run sample code:
@Test
public void test() throws Exception {
  ZipFile zipFile = new ZipFile("test-ZipCrypto.zip", "blah".toCharArray());
  for (FileHeader h:zipFile.getFileHeaders()) {
   try {
       zipFile.extractFile(h, "d:\\temp");
   }
   catch (Exception e) {
       System.out.println(e.getMessage());
   }
  }
}

The sample code above prints:
java.io.IOException: Reached end of entry, but crc verification failed for test.txt

ProgressMonitor resets itself when a task successfully or unsuccessfully finishes

The ProgressMonitor resets itself when a task successfully or unsuccessfully finishes. This purges valuable data needed after the task is done.


The ProgressManager should not automatically reset when a task is ended but only when a new task is started.

ArrayIndexOutofBoundsException when read directory from zipInputStream

Hi, Author. I found out this bug that when I tried to read encrypted zip files from ZipInputStream, if it meet some directories, it would throw this exception:
java.lang.ArrayIndexOutOfBoundsException: 11
at net.lingala.zip4j.util.RawIO.readShortLittleEndian(RawIO.java:109)
at net.lingala.zip4j.headers.HeaderReader.parseExtraDataRecords(HeaderReader.java:309)
at net.lingala.zip4j.headers.HeaderReader.readExtraDataRecords(HeaderReader.java:297)
at net.lingala.zip4j.headers.HeaderReader.readExtraDataRecords(HeaderReader.java:264)
at net.lingala.zip4j.headers.HeaderReader.readLocalFileHeader(HeaderReader.java:553)
at net.lingala.zip4j.io.inputstream.ZipInputStream.getNextEntry(ZipInputStream.java:66)
at net.lingala.zip4j.io.inputstream.ZipInputStream.getNextEntry(ZipInputStream.java:62)

This error only happens on reading directories from ZipInputStream.
Please help me fix this bug. I am in urgent need.

Thank you in advance!

NullPointer

ZipFile zipFile = new ZipFile("filename.zip");
FileHeader fileHeader = zipFile.getFileHeader("entry_name_in_zip.txt");
InputStream inputStream = zipFile.getInputStream(fileHeader);

Here's the error message

2019-07-12 17:08:55.617 3397-3555/xxxxxxE/CrashHandler: In thread: Thread[TaskSchedulerFo,5,main]
UncaughtException detected: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean net.lingala.zip4j.model.AbstractFileHeader.isDirectory()' on a null object reference
at net.lingala.zip4j.io.inputstream.ZipInputStream.read(ZipInputStream.java:113)
at org.chromium.android_webview.InputStreamUtil.read(Unknown Source)
2019-07-12 17:08:55.617 3397-3555/com.lzj.shanyi E/AndroidRuntime: FATAL EXCEPTION: TaskSchedulerFo
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean net.lingala.zip4j.model.AbstractFileHeader.isDirectory()' on a null object reference
at net.lingala.zip4j.io.inputstream.ZipInputStream.read(ZipInputStream.java:113)
at org.chromium.android_webview.InputStreamUtil.read(Unknown Source)

1.3.3 leaks Deflate resource on Android

Calling zipFile.addStream(inputStream, zipParameters) calls zipEngine.addStreamToZip(...) which creates a ZipOutputStream which is a DeflaterOutputStream that has a Deflate member. This Deflate member should be closed using the end() method which is never called.

unzip is failed - for getting inputStream for a file

Caused by: java.lang.NoSuchMethodError: net.lingala.zip4j.model.ZipModel.setEndOfCentralDirectoryRecord(Lnet/lingala/zip4j/model/EndOfCentralDirectoryRecord;)V
at net.lingala.zip4j.headers.HeaderReader.readAllHeaders(HeaderReader.java:63)
at net.lingala.zip4j.ZipFile.readZipInfo(ZipFile.java:831)
at net.lingala.zip4j.ZipFile.getFileHeader(ZipFile.java:565)

ZipFile API methods return this

Hey @srikanth-lingala , I was looking for a zip library and I recently find zip4j. I think this is a great lib, the code is clear and is well documented. Great job!
I have some suggestions : would you consider returning this for the APIs in ZipFile? Here is my use case : I have some files and directories to be zipped, and I want them to be zipped in a single line of code like this :
new ZipFile("filename.zip").addFile("filename.ext").addFolder(new File("/user/myuser/folder_to_add"));
I think the API methods returning this would work for this use case, and it would make the code more clear.

BTW, I like zip4j and I'd like to contribute to zip4j. Do you have any ideas about what I can do for zip4j? :)

zip64 extra data filed parse

As the issue#65 has mentioned, zip4j has some parsing errors in zip64 extra field.
The fix in this issue is here, but I think there are some problems with this fix.

The zip specification said about extra data :

4.5.3 -Zip64 Extended Information Extra Field (0x0001):
The following is the layout of the zip64 extended information "extra" block. If one of the size or offset fields in the Local or Central directory record is too small to hold the required data, a Zip64 extended information record is created. The order of the fields in the zip64 extended information record is fixed, but the fields MUST only appear if the corresponding Local or Central directory record field is set to 0xFFFF or 0xFFFFFFFF.

As the specification said, the extra field should only be valid when the corresponding original field is 0xFFFF or 0xFFFFFFFF. The fix in commit Do not override from Zip64 record if value not present is here:

    if (zip64ExtendedInfo.getUncompressedSize() != -1) {
      fileHeader.setUncompressedSize(zip64ExtendedInfo.getUncompressedSize());
    }

    if (zip64ExtendedInfo.getCompressedSize() != -1) {
      fileHeader.setCompressedSize(zip64ExtendedInfo.getCompressedSize());
    }

    if (zip64ExtendedInfo.getOffsetLocalHeader() != -1) {
      fileHeader.setOffsetLocalHeader(zip64ExtendedInfo.getOffsetLocalHeader());
    }

    if (zip64ExtendedInfo.getDiskNumberStart() != -1) {
      fileHeader.setDiskNumberStart(zip64ExtendedInfo.getDiskNumberStart());
    }

I think it should be like this:

    if (fileHeader.getUncompressedSize() != 0xFFFFFFFFL) {
      fileHeader.setUncompressedSize(zip64ExtendedInfo.getUncompressedSize());
    }

    if (fileHeader.getCompressedSize() != 0xFFFFFFFFL) {
      fileHeader.setCompressedSize(zip64ExtendedInfo.getCompressedSize());
    }

    if (fileHeader.getOffsetLocalHeader() != 0xFFFFFFFFL) {
      fileHeader.setOffsetLocalHeader(zip64ExtendedInfo.getOffsetLocalHeader());
    }

    if (fileHeader.getDiskNumberStart() != 0xFFFFL) {
      fileHeader.setDiskNumberStart(zip64ExtendedInfo.getDiskNumberStart());
    }

Password protection not working?

Hello,

I'm trying to create a password protected ZIP file, but I keep getting that the password is not correct when I try to unzip the contents:

var params1 = new ZipParameters();
params1.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
params1.setCompressionLevel(CompressionLevel.NORMAL);
params1.setCompressionMethod(CompressionMethod.DEFLATE);
params1.setEncryptFiles(true);
params1.setFileNameInZip("file1.csv");
params1.setEncryptFiles(true);

var params2 = new ZipParameters();
params2.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
params2.setCompressionLevel(CompressionLevel.NORMAL);
params2.setCompressionMethod(CompressionMethod.DEFLATE);
params2.setEncryptFiles(true);
params2.setFileNameInZip("file2.csv");
params2.setEncryptFiles(true);

var zip = new ZipFile("MY_ZIP.zip", "hello".toCharArray());
zip.addStream(s1, params1);
zip.addStream(s2, params2);

zip.getFile();
// Check the file...

This issue seems related: #6, but I'm using version 2.1.0, so it should be fixed now?

I'll report if I find anything else.

Thank you.

ZipInputStream.getNextEntry return only 1 entry, but next null

I'm use your library in Android app, and want extract/compress with Storage access framework, I see one way - streams, but ZipInputStream.getNextEntry in the second and next iteration return null, I'm try different zip archives, the same problem.
And second question, how with input stream get total files count and size before unpack (for progress bar) in zip?

ZipException while extracting, despite unzip, 7z and Windows Explorer extracts well

Hi!

I use your library and just stumbled a problem archive, which causes exception during extracting, despite other archivers works with it normally and even without any warnings.

Stacktrace:

net.lingala.zip4j.exception.ZipException: Could not read corresponding local file header for file header: Some folder/lib/

	at net.lingala.zip4j.tasks.AbstractExtractFileTask.verifyNextEntry(AbstractExtractFileTask.java:85)
	at net.lingala.zip4j.tasks.AbstractExtractFileTask.extractFile(AbstractExtractFileTask.java:47)
	at net.lingala.zip4j.tasks.ExtractAllFilesTask.executeTask(ExtractAllFilesTask.java:35)
	at net.lingala.zip4j.tasks.ExtractAllFilesTask.executeTask(ExtractAllFilesTask.java:13)
	at net.lingala.zip4j.tasks.AsyncZipTask.performTaskWithErrorHandling(AsyncZipTask.java:41)
	at net.lingala.zip4j.tasks.AsyncZipTask.execute(AsyncZipTask.java:35)
	at net.lingala.zip4j.ZipFile.extractAll(ZipFile.java:431)

Original archive contained some files, but I modified it via Windows Explorer and 7z and got almost empty sample (empty1.zip), which causes same behavior.

InputStream cannot be get from a zip file

I want to get inputStream from a zip file, but it is always empty.
This means that InputStream can not be used properly and can not get the bytes inside.
Because the length of InputStream is 0.

//Case 1: I used failure cases.

public InputStream getInputStream() {
	// 1. get the inputStream, it size( or length) is empty.
	ZipFile zipFile = new ZipFile("filename.zip");
	FileHeader fileHeader = zipFile.getFileHeader("entry_name_in_zip.txt");
	InputStream inputStream = zipFile.getInputStream(fileHeader);
	return inputStream;
}

//1.2 get the inputStream, it size( or length)  is empty. 
public void extractWithZipInputStream(File zipFile, char[] password) throws IOException {
	LocalFileHeader localFileHeader;
	int readLen;
	byte[] readBuffer = new byte[4096];
	
	try (FileInputStream fileInputStream = new FileInputStream(zipFile); ZipInputStream zipInputStream = new ZipInputStream(fileInputStream, password)) {
		while ((localFileHeader = zipInputStream.getNextEntry()) != null) {
			File extractedFile = new File(localFileHeader.getFileName());
			try (OutputStream outputStream = new FileOutputStream(extractedFile)) {
				while ((readLen = zipInputStream.read(readBuffer)) != -1) {
					outputStream.write(readBuffer, 0, readLen);
				}
			}
		}
	}
}

//Case 2: I get inputStream success, but it's not good.
//when zipInputStream is empty, i use api( zipFile.extractFile(xx, xx)), get unzip file,
// then i can new inputStream success.
// int fact, i don't want get the unzip file.

private static InputStream getCerInputStream(ZipFile zipFile, Context context) throws Exception {
	List<FileHeader> fileHeaders = zipFile.getFileHeaders();
	int size = fileHeaders.size();
	SugrLog.i(TAG, "size = " + size);
	
	for (int i = 0; i < size; i++) {
		FileHeader fileHeader = fileHeaders.get(i);
		
		SugrLog.i(TAG, "name = " + fileHeader.getFileName());
		ZipInputStream zipInputStream = zipFile.getInputStream(fileHeader);
		SugrLog.i(TAG, " zipInputStream = " + (zipInputStream == null));
		
		if (zipInputStream != null){
			// TODO: size = 0 , There's a problem. zipInputStream shouldn't be zero.
			SugrLog.i(TAG, " zipInputStream size= " + zipInputStream.available());
			
			if ( zipInputStream.available() > 0){
				return zipInputStream;
			}
			zipInputStream.close();
		}
		
		//unzip test
		String cachePath = context.getFilesDir().getPath();
		zipFile.extractFile(fileHeader.getFileName(), cachePath);
		String cachePath33 = context.getFilesDir().getPath() + File.separator + fileHeader.getFileName();
		FileInputStream inputStream = new FileInputStream(cachePath33);
		
		File file = new File(cachePath33);
		if (file.exists()){
			file.delete();
		}
		
		SugrLog.i(TAG, "inputStream size = " + inputStream.available());
		return inputStream;
	}

	return null;
}

readLine from zip file,throw NPE when file contains blank lines

here is my test Code

 public static void main(String[] args) throws IOException {
        ZipFile zipFile = new ZipFile("/Users/lihui/IdeaProjects/nettydemo/src/main/resources/a.txt.zip");
        List<FileHeader> fileHeaders = zipFile.getFileHeaders();
        FileHeader fileHeader = fileHeaders.get(0);
        ZipInputStream inputStream = zipFile.getInputStream(fileHeader);

        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

        String line;
        while ( (line=bufferedReader.readLine())!=null  ){
            System.out.println(line);
        }

    }

and a.txt.zip is Zip file of this

1234
12124

5678
123123

Note that there have a blank line in a.txt

Then run this code , program will throw a NPE after print all lines.
and this code work fine when file contains no blank line.

ZipException while extracting in version 2.1.x, but works fine in 1.x

Hello,

I´m getting the following exception while extracting using 2.1.2. The same file works fine in 1.3.3. I also checked the file with unzip -t and with WinZIP and 7z -> no problems there.

 Caused by: net.lingala.zip4j.exception.ZipException: Reached end of entry, but crc verification failed for {--file--}
 	at net.lingala.zip4j.io.inputstream.ZipInputStream.verifyCrc(ZipInputStream.java:240) ~[zip4j-2.1.1.jar!/:na]
 	at net.lingala.zip4j.io.inputstream.ZipInputStream.endOfCompressedDataReached(ZipInputStream.java:154) ~[zip4j-2.1.1.jar!/:na]
 	at net.lingala.zip4j.io.inputstream.ZipInputStream.read(ZipInputStream.java:122) ~[zip4j-2.1.1.jar!/:na]
 	at java.base/sun.nio.cs.StreamDecoder.readBytes(Unknown Source) ~[na:na]
 	at java.base/sun.nio.cs.StreamDecoder.implRead(Unknown Source) ~[na:na]
 	at java.base/sun.nio.cs.StreamDecoder.read(Unknown Source) ~[na:na]
 	at java.base/java.io.InputStreamReader.read(Unknown Source) ~[na:na]
 	at java.base/java.io.Reader.read(Unknown Source) ~[na:na]
 	at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:2001) ~[commons-io-2.4.jar!/:2.4]
 	at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1980) ~[commons-io-2.4.jar!/:2.4]
 	at org.apache.commons.io.IOUtils.copy(IOUtils.java:1957) ~[commons-io-2.4.jar!/:2.4]
 	at org.apache.commons.io.IOUtils.copy(IOUtils.java:1907) ~[commons-io-2.4.jar!/:2.4]
 	at org.apache.commons.io.IOUtils.toString(IOUtils.java:778) ~[commons-io-2.4.jar!/:2.4]
 	at de.xxx.xxx.helper.ExtractImport.content(ExtractImport.java:90) ~[classes!/:1.5.0-SNAPSHOT]
 	... 14 common frames omitted
final Map<String, String> data = new HashMap<>();
final Charset charset = Charset.forName("Cp1252");

tempFile = createTempFile(bin);
ZipFile zipFile = new ZipFile(tempFile, password.toCharArray());
ZipInputStream zis;

final Map<String,FileHeader> fileheaders = zipFile.getFileHeaders()
        .stream()
        .collect(toMap(FileHeader::getFileName, Function.identity()));
...
for(Map.Entry<String,IBaseModel> file: toParseFiles.entrySet()) {
    final String filename = file.getKey();
    final FileHeader fileHeader = fileheaders.get(filename);

    if(fileHeader == null) {
        ...
    } else {
        zis = zipFile.getInputStream(fileHeader);
        data.put(filename, IOUtils.toString(zis, charset));
        zis.close();
    }
}

and..Thanks for your great work!

Crash on Android Nougat (7.x) and below due to lack of java.nio.file APIs not added until Android 8.0 Oreo

Hi, thanks for your work!

java.nio.file APIs were added to Android with API 26 (8.0, Oreo). Thus, zip4j v2.0 will work only on Android 8.0 and up for Android devices. Is this API choice? Many wouldn't be able to use the new version on Android, since Android 6.0 Marshmallow and 7.x Nougat are still supported by many apps.

--------- beginning of crash 06-26 10:35:07.660 4977-5149/org.swiftapps.swiftbackup E/AndroidRuntime: FATAL EXCEPTION: IntentService[TaskService] Process: org.swiftapps.swiftbackup, PID: 4977 java.lang.NoSuchMethodError: No virtual method toPath()Ljava/nio/file/Path; in class Ljava/io/File; or its super classes (declaration of 'java.io.File' appears in /system/framework/core-libart.jar) at net.lingala.zip4j.tasks.AbstractAddFileToZipTask.addFilesToZip(AbstractAddFileToZipTask.java:78) at net.lingala.zip4j.tasks.AddFolderToZipTask.executeTask(AddFolderToZipTask.java:28) at net.lingala.zip4j.tasks.AddFolderToZipTask.executeTask(AddFolderToZipTask.java:16) at net.lingala.zip4j.tasks.AsyncZipTask.performTaskWithErrorHandling(AsyncZipTask.java:40) at net.lingala.zip4j.tasks.AsyncZipTask.execute(AsyncZipTask.java:34) at net.lingala.zip4j.ZipFile.addFolder(ZipFile.java:359) at net.lingala.zip4j.ZipFile.addFolder(ZipFile.java:334) at org.swiftapps.swiftbackup.common.Zipper.packFoldersZip4J(Zipper.kt:96) at org.swiftapps.swiftbackup.common.Zipper.packFolders(Zipper.kt:22) at org.swiftapps.swiftbackup.apptasks.AppBackupTask.packFoldersToZip(AppBackupTask.kt:508) at org.swiftapps.swiftbackup.apptasks.AppBackupTask.execute(AppBackupTask.kt:266) at org.swiftapps.swiftbackup.apptasks.AppBackupManager.processApp(AppBackupManager.kt:80) at org.swiftapps.swiftbackup.apptasks.AppBackupManager.execute(AppBackupManager.kt:65) at org.swiftapps.swiftbackup.tasks.stasks.AppsTask.startBackup(AppsTask.kt:117) at org.swiftapps.swiftbackup.tasks.stasks.AppsTask.executeTask(AppsTask.kt:80) at org.swiftapps.swiftbackup.tasks.stasks.STask.execute(STask.kt:83) at org.swiftapps.swiftbackup.tasks.TaskManager.performTasks(TaskManager.kt:104) at org.swiftapps.swiftbackup.tasks.TaskService.onHandleIntent(TaskService.kt:33) at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:66) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.os.HandlerThread.run(HandlerThread.java:61)

Unable to read InputStream (AES-encrypted, CompressionMethod.STORE)

Currently, Zip4J 2.X does not work at all for my AES encrypted files.
What I am trying to do is simple: Read a ZipInputStream from an AES-encrypted file entry like this:

final ZipInputStream is = zipFile.getInputStream(fileHeader);
do {
 n = is.read(buf, 0, buf_len);
 ... do some stuff with the buffer
} while (n != -1);

Note that I use CompressionMethod.STORE.
Depending on the file I am testing, the above snippet fails at one of multiple places.
My main test file has been created by Zip4j version 1.3.2.

  • Firstly, the following line in ZipInputStream:read() looks suspicious:
int readLen = decompressedInputStream.read(b, off, (len - len %16));

If len is smaller than 16, then the above line may lead to an infinite loop since len - len % 16 evaluates to zero and nothing is read at all.

  • Still staying within ZipInputStream:read(): In some cases, the following piece of code produced a nullpointer exception:
// localFileHeader may be null!!!
if (isZipEntryDirectory(localFileHeader.getFileName())) {
      return -1;
}
  • If we manage to get until DecompressedInputStream:read(), then the following failure may occur:
@Override
public int read(byte[] b, int off, int len) throws IOException {
    if (compressedSize != -1) {
      if (bytesRead >= compressedSize) {
        return -1; // This may return as a failure because compressedSize is zero and bytesRead is also set to zero when trying to read the first time!!!
      }
      .....
  • Furthermore, I worry about the way that a "LocalFileHeader" is retrieved from a "FileHeader".
    In particular, the following method is called within zipFile.getInputStream(fileHeader);:
public static ZipInputStream createZipInputStream(ZipModel zipModel, FileHeader fileHeader, char[] password)
      throws ZipException {
    try {
      SplitInputStream splitInputStream = new SplitInputStream(zipModel.getZipFile(), zipModel.isSplitArchive(),
          zipModel.getEndOfCentralDirectoryRecord().getNumberOfThisDisk());
      splitInputStream.prepareExtractionForFileHeader(fileHeader);

      ZipInputStream zipInputStream = new ZipInputStream(splitInputStream, password);
      if (zipInputStream.getNextEntry() == null) {
        throw new ZipException("Could not locate local file header for corresponding file header");
      }
      return zipInputStream;
    } catch (IOException e) {
      throw new ZipException(e);
    }
  }

All FileHeader entries seem to be ignored except of the offset to the LocalFileHeader, which is then parsed. Note that many fields in the Zip format are redundant (fields being present in both the central directory file header and the corresponding local file header).
A debugging session with an old sample file revealed the following:

fileHeader.compressedSize = 31
fileHeader.uncompressedSize = 3
localFileHeader.compressedSize = 0
localFileHeader.uncompressedSize = 0

localFileHeader should not have zero values for compressedSize and uncompressedSize.
In fact, I would expect that these fields are equal to the "central directory fileHeader".
Instead, these zero values seem to mess up the decryption and input streaming code in various places.

I am sure that the above file headers correspond to the same file since most of the other fields are equal (timestamps, file name and other stuff).

Adding a Stream to a password-protected zip file requires manually setting entryCRC on ZipParameters

Maybe there is no other choice. But I had to dig pretty hard to discover that when I use net.lingala.zip4j.ZipFile.addStream(InputStream, ZipParameters), unless I do the following set of steps, I am unable to extract/open the file from within the password-protected zip file using the expected password.

  1. Write the inputStream to a file
  2. Use net.lingala.zip4j.util.CrcUtil.computeFileCrc(File, ProgressMonitor) to calculate the CRC value
  3. Take the result of the previous step and pass it to ZipParameters.setEntryCRC()

workCompleted gets bigger than totalWork in the ProgressMonitor

This uses the compressed size as totalWork in the progressMonitor.

return taskParameters.fileHeader.getCompressedSize();

But this updates the workCompleted in the progressMonitor with the uncompressed size.

progressMonitor.updateWorkCompleted(readLength);

This results in


getting bigger than

False-positive result while checking for slip zip if target path is not normalized

If i use non normalized target paths while unzipping, the check for slip zip ends with a false-positive result.
The issue is, that the path of the extracted file is compared with a non normalized target path in AbstractExtractFileTask#extractFile(...):

if (!new File(completePath).getCanonicalPath().startsWith(new File(outPath).getPath())) {

This line should be changed to

!new File(completePath).getCanonicalPath().startsWith(new File(outPath).getCanonicalPath())

Test case (JUnit5 + AssertJ) for verification:

@Test
public void testUnzipFileZipSlipWithNotNormalizedTarget(@TempDir File tempDir) throws Exception {
	final File goodFile = new File(tempDir, "good.txt");
	assertThat(goodFile.createNewFile()).isTrue();
	FileUtils.write(goodFile, "good", StandardCharsets.UTF_8);

	final ZipParameters zipParameters = new ZipParameters();
	zipParameters.setFileNameInZip("good.txt");

	final ZipFile zip = new ZipFile(new File(tempDir, "test.zip"));
	zip.addFile(goodFile, zipParameters);

	zip.extractAll(new File(tempDir, "../" + tempDir.getName() + "/unzipped").getAbsolutePath());
}

ZipException: cannot delete old zip file

Zip4J v2.1.2
JDK 11 (Oracle)
Windows 10

I'm getting the next error attempting to remove file from jar (zip) archive:

net.lingala.zip4j.exception.ZipException: cannot delete old zip file
	at net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.restoreFileName(RemoveEntryFromZipFileTask.java:171)
	at net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.cleanupFile(RemoveEntryFromZipFileTask.java:159)
	at net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.executeTask(RemoveEntryFromZipFileTask.java:70)
	at net.lingala.zip4j.tasks.RemoveEntryFromZipFileTask.executeTask(RemoveEntryFromZipFileTask.java:21)
	at net.lingala.zip4j.tasks.AsyncZipTask.performTaskWithErrorHandling(AsyncZipTask.java:42)
	at net.lingala.zip4j.tasks.AsyncZipTask.execute(AsyncZipTask.java:36)
	at net.lingala.zip4j.ZipFile.removeFile(ZipFile.java:674)
	at net.lingala.zip4j.ZipFile.removeFile(ZipFile.java:650)
	at ua.i0xhex.signer.Signer.sign(Signer.java:101)
	at ua.i0xhex.signer.Signer.runMainApp(Signer.java:33)
	at ua.i0xhex.signer.Signer.main(Signer.java:26)

What's going in my code (simplified to zip operations only):

ZipFile zipFile = new ZipFile(file);
for (FileHeader header : (List<FileHeader>) zipFile.getFileHeaders()) {
	if (header.getFileName().equals("signature.txt")) {
		ZipInputStream stream = zipFile.getInputStream(header);
		data = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
		stream.close();
		break;
	}
}
zipFile.removeFile("signature.txt"); // 101 line here

Is this a bug or my fault? Anyway to fix this?

This error appears more often (up to 100%) on larger files, for example this didn't happened on 18 626 bytes archive, but happened on 8 364 918 bytes archive. For some archives it didn't happen from Nth time. Magic? I can send this archives privately if needed (it's not for public).

Also this error also appears on 1.3.3.

Extraction on Android failing due to too many open files

Hi everyone
I'm experiencing a strange issue with a ZIP file both created and extracted with zip4j 2.1.1.
The file is created on the desktop and extracted on Android, leading to this exception and Android rebooting (!!!)

6847-8599/system_process E/art: ashmem_create_region failed for 'indirect ref table': Too many open files

Full exception when extracting on Android:

Process: com.xyz, PID: 6504
    java.lang.RuntimeException: Could not read input channel file descriptors from parcel.
        at android.view.InputChannel.nativeReadFromParcel(Native Method)
        at android.view.InputChannel.readFromParcel(InputChannel.java:148)
        at android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:841)
        at android.view.ViewRootImpl.setView(ViewRootImpl.java:640)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
        at android.app.Dialog.show(Dialog.java:322)

As a reference, the same extraction Android code works with a ZIP file created through Apache Commons Compress:

private boolean decompress() {
    final File targetFolder = file.getParentFile();
    final File existingMediaFolder = new File(targetFolder, "Media");
    cleanupExistingMediaFolder(existingMediaFolder);
    targetFolder.mkdirs();
    try {
        ZipFile zipFile = new ZipFile(file);
        zipFile.setRunInThread(true);
        ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
        zipFile.extractAll(targetFolder.getAbsolutePath());
        while (progressMonitor.getState() == ProgressMonitor.State.BUSY) {
            publishProgress(progressMonitor.getPercentDone());
        }
    } catch (Exception ex) {
        ex.printStackTrace();
        FLog.e(MainActivity.AppName, ex.getMessage());
        cleanupExistingMediaFolder(existingMediaFolder);
        return false;
    }
    return true;
}

This is the problematic compress code on Java desktop (as wrote, recently switched from Apache Commons Compress, which had no issue - I wanted to unify the compress/decompress through zip4j):

private void compressToZip() throws Exception {
    numFiles = countFiles(folder.toPath());
    App.LOG.info("Compressing " + numFiles + " files to " + targetZip.getAbsolutePath());
    updateProgress(0, 100);
    ZipParameters zipParameters = new ZipParameters();
    zipParameters.setCompressionMethod(CompressionMethod.STORE);
    ZipFile zipFile = new ZipFile(targetZip);
    ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
    zipFile.setRunInThread(true);
    zipFile.addFolder(folder, zipParameters);
    while (!progressMonitor.getState().equals(ProgressMonitor.State.READY)) {
        updateProgress(progressMonitor.getPercentDone(), 100);
        updateMessage(String.format(getString("compressing.d.out.of.d"), numFiles));
        if (isCancelled()) {
            return;
        }
    }
    App.LOG.info(String.format("Compressed successfully to %s (%s)", targetZip.getAbsolutePath(), HumanReadableSize.humanReadableByteCount(targetZip.length())));
}

Any idea on this?

thanks
nicola

zip4j 2.0: When execute ZipFile.getInputStream(FileHeader), do something and close the inputstream, the zipFile is still occupied.

zip4j 2.0: When execute ZipFile.getInputStream(FileHeader), do something and then close the inputstream, the zipFile is still occupied.

ZipFile zipFile = new ZipFile("filename.zip");
FileHeader fileHeader = zipFile.getFileHeader("filePathInZip");

if(null != fileHeader && !fileHeader.isDirectory())
{
InputStream inputStream = zipFile.getInputStream(fileHeader);
//TO DO SOMETHING
inputStream.close();
//after this, rename the zipFile will failed, the file is occupied.
}

unzip sucessfully in 1.x but failed in latest version

Hello,

I created the zip using c# ICSharpCode.SharpZipLib.Zip with password. And it could be unzipped when I extracted it using zip4j version 1.3.2. I upgrade it 2.x in order to support stream. And some exception when I extract the same zip file:

Code:

    try {
      ZipFile zipFile = new ZipFile(source);
      if (zipFile.isEncrypted()) {
        zipFile.setPassword(password);
      }
      zipFile.extractAll(destination);
    } catch (ZipException e) {
      e.printStackTrace();
    }

Exception:

net.lingala.zip4j.exception.ZipException: java.io.IOException: Negative seek offset
	at net.lingala.zip4j.tasks.AsyncZipTask.performTaskWithErrorHandling(AsyncZipTask.java:49)
	at net.lingala.zip4j.tasks.AsyncZipTask.execute(AsyncZipTask.java:36)
	at net.lingala.zip4j.ZipFile.extractAll(ZipFile.java:431)
	at app.util.ZipMaker.unzip(ZipMaker.java:108)

The sample zip hello.zip with password Shu1an@2019GTS

ZipSlip not fixed

According to tests and manual debugging, the library is still vulnerable against ZipSlip.

The fix seems to be a change in AbstractExtractFileTask#extractFile(...), namely:

if (!new File(completePath).getPath().startsWith(new File(outPath).getPath())) {

should be replaced with

if (!new File(completePath).getCanonicalPath().startsWith(new File(outPath).getPath())) {

Test case (JUnit5 + AspectJ) for verification:

@Test
public void testUnzipFileZipSlip(@TempDir File tempDir) throws Exception {
	final File badFile = new File(tempDir, "bad.txt");
	assertThat(badFile.createNewFile()).isTrue();
	FileUtils.write(badFile, "bad", StandardCharsets.UTF_8);

	final ZipParameters zipParameters = new ZipParameters();
	zipParameters.setFileNameInZip("../../bad.txt");

	final ZipFile zip = new ZipFile(new File(tempDir, "test.zip"));
	zip.addFile(badFile, zipParameters);

	try {
		zip.extractAll(new File(tempDir, "unzipped").getAbsolutePath());
		fail("zip4j is vulnerable for slip zip");
	}
	catch (ZipException e) {
		assertThat(e).hasMessageStartingWith("illegal file name that breaks out of the target directory: ");
	}
} 

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.