diff --git a/pom.xml b/pom.xml index d8d6bd35..324a5fee 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,11 @@ + + org.apache.commons + commons-compress + 1.25.0 + junit junit diff --git a/src/main/java/net/lingala/zip4j/headers/FileHeaderFactory.java b/src/main/java/net/lingala/zip4j/headers/FileHeaderFactory.java index b26dceaf..0d7035a3 100644 --- a/src/main/java/net/lingala/zip4j/headers/FileHeaderFactory.java +++ b/src/main/java/net/lingala/zip4j/headers/FileHeaderFactory.java @@ -114,20 +114,27 @@ private byte generateFirstGeneralPurposeByte(boolean isEncrypted, ZipParameters firstByte = setBit(firstByte, 0); } - if (CompressionMethod.DEFLATE.equals(zipParameters.getCompressionMethod())) { - if (CompressionLevel.NORMAL.equals(zipParameters.getCompressionLevel())) { - firstByte = unsetBit(firstByte, 1); - firstByte = unsetBit(firstByte, 2); - } else if (CompressionLevel.MAXIMUM.equals(zipParameters.getCompressionLevel())) { - firstByte = setBit(firstByte, 1); - firstByte = unsetBit(firstByte, 2); - } else if (CompressionLevel.FAST.equals(zipParameters.getCompressionLevel())) { - firstByte = unsetBit(firstByte, 1); - firstByte = setBit(firstByte, 2); - } else if (CompressionLevel.FASTEST.equals(zipParameters.getCompressionLevel()) - || CompressionLevel.ULTRA.equals(zipParameters.getCompressionLevel())) { - firstByte = setBit(firstByte, 1); - firstByte = setBit(firstByte, 2); + if (zipParameters.getCompressionMethod() != null) { + switch (zipParameters.getCompressionMethod()) { + case DEFLATE: + case DEFLATE64: + if (CompressionLevel.NORMAL.equals(zipParameters.getCompressionLevel())) { + firstByte = unsetBit(firstByte, 1); + firstByte = unsetBit(firstByte, 2); + } else if (CompressionLevel.MAXIMUM.equals(zipParameters.getCompressionLevel())) { + firstByte = setBit(firstByte, 1); + firstByte = unsetBit(firstByte, 2); + } else if (CompressionLevel.FAST.equals(zipParameters.getCompressionLevel())) { + firstByte = unsetBit(firstByte, 1); + firstByte = setBit(firstByte, 2); + } else if (CompressionLevel.FASTEST.equals(zipParameters.getCompressionLevel()) + || CompressionLevel.ULTRA.equals(zipParameters.getCompressionLevel())) { + firstByte = setBit(firstByte, 1); + firstByte = setBit(firstByte, 2); + } + break; + default: + // do nothing } } diff --git a/src/main/java/net/lingala/zip4j/io/inputstream/CipherInputStream.java b/src/main/java/net/lingala/zip4j/io/inputstream/CipherInputStream.java index 5023a94e..987edf4f 100644 --- a/src/main/java/net/lingala/zip4j/io/inputstream/CipherInputStream.java +++ b/src/main/java/net/lingala/zip4j/io/inputstream/CipherInputStream.java @@ -2,7 +2,6 @@ import net.lingala.zip4j.crypto.Decrypter; import net.lingala.zip4j.model.LocalFileHeader; -import net.lingala.zip4j.model.enums.CompressionMethod; import net.lingala.zip4j.util.Zip4jUtil; import java.io.IOException; @@ -24,8 +23,12 @@ public CipherInputStream(ZipEntryInputStream zipEntryInputStream, LocalFileHeade this.decrypter = initializeDecrypter(localFileHeader, password, useUtf8ForPassword); this.localFileHeader = localFileHeader; - if (Zip4jUtil.getCompressionMethod(localFileHeader).equals(CompressionMethod.DEFLATE)) { - lastReadRawDataCache = new byte[bufferSize]; + switch (Zip4jUtil.getCompressionMethod(localFileHeader)) { + case DEFLATE: + case DEFLATE64: + lastReadRawDataCache = new byte[bufferSize]; + break; + default: } } diff --git a/src/main/java/net/lingala/zip4j/io/inputstream/Inflater64InputStream.java b/src/main/java/net/lingala/zip4j/io/inputstream/Inflater64InputStream.java new file mode 100644 index 00000000..79a76d43 --- /dev/null +++ b/src/main/java/net/lingala/zip4j/io/inputstream/Inflater64InputStream.java @@ -0,0 +1,61 @@ +package net.lingala.zip4j.io.inputstream; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; + +import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream; + +public class Inflater64InputStream extends DecompressedInputStream { + + private Deflate64CompressorInputStream inflater; + private BufferedInputStream buffer; + + public Inflater64InputStream(CipherInputStream cipherInputStream, int bufferSize) { + super(cipherInputStream); + this.inflater = new Deflate64CompressorInputStream(cipherInputStream); + this.buffer = new BufferedInputStream(this.inflater, bufferSize); + } + + @Override + public int read() throws IOException { + return this.buffer.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return this.buffer.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return this.buffer.read(b, off, len); + } + + @Override + public void endOfEntryReached(InputStream inputStream, int numberOfBytesPushedBack) throws IOException { + if (inflater != null) { + buffer.close(); + inflater.close(); // this is redundant, but leaves no doubt for readability + inflater = null; + buffer = null; + } + super.endOfEntryReached(inputStream, numberOfBytesPushedBack); + } + + @Override + public int pushBackInputStreamIfNecessary(PushbackInputStream pushbackInputStream) throws IOException { + // Deflate64CompressorInputStream should not overread + return 0; + } + + @Override + public void close() throws IOException { + if (inflater != null) { + buffer.close(); + inflater.close(); // this is redundant, but leaves no doubt for readability + } + super.close(); + } +} diff --git a/src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java b/src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java index 46acc8cf..dc1020e1 100755 --- a/src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java +++ b/src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java @@ -270,8 +270,15 @@ private DecompressedInputStream initializeDecompressorForThisEntry(CipherInputSt LocalFileHeader localFileHeader) throws ZipException { CompressionMethod compressionMethod = getCompressionMethod(localFileHeader); - if (compressionMethod == CompressionMethod.DEFLATE) { - return new InflaterInputStream(cipherInputStream, zip4jConfig.getBufferSize()); + if (compressionMethod != null) { + switch (compressionMethod) { + case DEFLATE: + return new InflaterInputStream(cipherInputStream, zip4jConfig.getBufferSize()); + case DEFLATE64: + return new Inflater64InputStream(cipherInputStream, zip4jConfig.getBufferSize()); + default: + // do nothing + } } return new StoreInputStream(cipherInputStream); diff --git a/src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java b/src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java index bf585b0f..040d67ba 100755 --- a/src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java +++ b/src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java @@ -216,9 +216,16 @@ private CipherOutputStream initializeCipherOutputStream(ZipEntryOutputStream } private CompressedOutputStream initializeCompressedOutputStream(CipherOutputStream cipherOutputStream, - ZipParameters zipParameters) { - if (zipParameters.getCompressionMethod() == CompressionMethod.DEFLATE) { - return new DeflaterOutputStream(cipherOutputStream, zipParameters.getCompressionLevel(), zip4jConfig.getBufferSize()); + ZipParameters zipParameters) throws ZipException { + if (zipParameters.getCompressionMethod() != null) { + switch (zipParameters.getCompressionMethod()) { + case DEFLATE: + return new DeflaterOutputStream(cipherOutputStream, zipParameters.getCompressionLevel(), zip4jConfig.getBufferSize()); + case DEFLATE64: + throw new ZipException("Deflate64 not supported for compression"); + default: + // do nothing + } } return new StoreOutputStream(cipherOutputStream); diff --git a/src/main/java/net/lingala/zip4j/model/enums/CompressionMethod.java b/src/main/java/net/lingala/zip4j/model/enums/CompressionMethod.java index 7216618a..5fb3c543 100644 --- a/src/main/java/net/lingala/zip4j/model/enums/CompressionMethod.java +++ b/src/main/java/net/lingala/zip4j/model/enums/CompressionMethod.java @@ -17,6 +17,11 @@ public enum CompressionMethod { * @see java.util.zip.Deflater */ DEFLATE(8), + /** + * The Deflate enhanced or deflate64 compression is used. + * @see org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream + */ + DEFLATE64(9), /** * For internal use in Zip4J */ diff --git a/src/main/java/net/lingala/zip4j/tasks/AbstractAddFileToZipTask.java b/src/main/java/net/lingala/zip4j/tasks/AbstractAddFileToZipTask.java index 03d5ffc7..ad189342 100644 --- a/src/main/java/net/lingala/zip4j/tasks/AbstractAddFileToZipTask.java +++ b/src/main/java/net/lingala/zip4j/tasks/AbstractAddFileToZipTask.java @@ -29,7 +29,7 @@ import static net.lingala.zip4j.headers.HeaderUtil.getFileHeader; import static net.lingala.zip4j.model.ZipParameters.SymbolicLinkAction.INCLUDE_LINK_AND_LINKED_FILE; import static net.lingala.zip4j.model.ZipParameters.SymbolicLinkAction.INCLUDE_LINK_ONLY; -import static net.lingala.zip4j.model.enums.CompressionMethod.DEFLATE; +import static net.lingala.zip4j.model.enums.CompressionMethod.AES_INTERNAL_ONLY; import static net.lingala.zip4j.model.enums.CompressionMethod.STORE; import static net.lingala.zip4j.model.enums.EncryptionMethod.NONE; import static net.lingala.zip4j.model.enums.EncryptionMethod.ZIP_STANDARD; @@ -175,7 +175,7 @@ void verifyZipParameters(ZipParameters parameters) throws ZipException { throw new ZipException("cannot validate zip parameters"); } - if (parameters.getCompressionMethod() != STORE && parameters.getCompressionMethod() != DEFLATE) { + if (parameters.getCompressionMethod() == null || parameters.getCompressionMethod() == AES_INTERNAL_ONLY) { throw new ZipException("unsupported compression type"); } diff --git a/src/main/java/net/lingala/zip4j/util/ZipVersionUtils.java b/src/main/java/net/lingala/zip4j/util/ZipVersionUtils.java index 4e24a680..b04e4670 100644 --- a/src/main/java/net/lingala/zip4j/util/ZipVersionUtils.java +++ b/src/main/java/net/lingala/zip4j/util/ZipVersionUtils.java @@ -22,7 +22,8 @@ public static int determineVersionMadeBy(ZipParameters zipParameters, RawIO rawI public static VersionNeededToExtract determineVersionNeededToExtract(ZipParameters zipParameters) { VersionNeededToExtract versionRequired = VersionNeededToExtract.DEFAULT; - if (zipParameters.getCompressionMethod() == CompressionMethod.DEFLATE) { + if (zipParameters.getCompressionMethod() == CompressionMethod.DEFLATE || + zipParameters.getCompressionMethod() == CompressionMethod.DEFLATE64) { versionRequired = VersionNeededToExtract.DEFLATE_COMPRESSED; } diff --git a/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java b/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java index d37faf8f..b65bcd5e 100644 --- a/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java +++ b/src/test/java/net/lingala/zip4j/ExtractZipFileIT.java @@ -530,6 +530,14 @@ public void testExtractZipFileCRCError() throws IOException { new File(outputFolder, "images")); } + @Test + public void testExtractZipFileDeflate64() throws IOException { + ZipFile zipFile = new ZipFile(getTestArchiveFromResources("archive_deflate64_1_file.zip")); + zipFile.extractAll(outputFolder.getPath()); + assertThat(outputFolder.listFiles()).contains( + new File(outputFolder, "test-simple.pdf")); + } + @Test public void testExtractZipFileOf7ZipFormatSplitWithoutEncryption() throws IOException { List filesToAddToZip = new ArrayList<>(FILES_TO_ADD); diff --git a/src/test/resources/test-archives/archive_deflate64_1_file.zip b/src/test/resources/test-archives/archive_deflate64_1_file.zip new file mode 100644 index 00000000..5d794584 Binary files /dev/null and b/src/test/resources/test-archives/archive_deflate64_1_file.zip differ