srikanth-lingala / zip4j Goto Github PK
View Code? Open in Web Editor NEWA Java library for zip files and streams
License: Apache License 2.0
A Java library for zip files and streams
License: Apache License 2.0
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?
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.
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.
I have a unit test asserting that a file is password protected. As an assertion on that, I check that opening the zip file with a null password fails. However when I do that, the file being asserted upon becomes undeletable due to the unclosed input streams this method creates.
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.
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:
@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
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)
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.
}
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
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
? :)
The method ZipFile.getFileHeaders() states in the Javadoc that it will throw an Exception in case of a non-existing file, but doesn't. Either the Javadoc should be changed accordingly or the method should really throw an Exception.
On Windows system platform, the extracted file name will be garbled
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)
Hello,
How about add character set on ZipInputStream
like java.util.zip.ZipInputStream
so that we can handle other character set rather than only UTF8?
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.
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)
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
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;
}
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
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());
}
When adding a file to compression, a file with the same name exists in the archive. an override option should be provided.
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.
In case of AES v1, crc should be included. And in case of v2, crc should be set to 0.
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());
}
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)
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: ");
}
}
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.
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.
ZipInputStream:read()
: In some cases, the following piece of code produced a nullpointer exception:// localFileHeader may be null!!!
if (isZipEntryDirectory(localFileHeader.getFileName())) {
return -1;
}
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!!!
}
.....
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).
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.
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!
Spawned from #11
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.
Issue spawned from #11
It looks like that the method decrementBytesWrittenForThisEntry is not used by any methods.
Is this a deprecated method? If so, maybe it should be deleted,or a @deprecated annotation should be added.
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!
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.
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)'
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.
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.
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?
RemoveFileFromZipFile.javaThis will remove all files in the compressed file, not the specified file
This uses the compressed size as totalWork
in the progressMonitor
.
But this updates the workCompleted
in the progressMonitor
with the uncompressed size.
This results in
fff - ╕▒▒╛.txt
correct name is
fff - 副本.txt
How can i resolve it?
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.
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.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.