[Git][java-team/zip4j][upstream] New upstream version 2.9.1

Andrius Merkys (@merkys) gitlab at salsa.debian.org
Wed Nov 24 06:51:57 GMT 2021



Andrius Merkys pushed to branch upstream at Debian Java Maintainers / zip4j


Commits:
bd9bc700 by Andrius Merkys at 2021-11-24T01:15:06-05:00
New upstream version 2.9.1
- - - - -


23 changed files:

- .travis.yml
- src/main/java/net/lingala/zip4j/ZipFile.java
- src/main/java/net/lingala/zip4j/crypto/AESDecrypter.java
- src/main/java/net/lingala/zip4j/headers/HeaderReader.java
- src/main/java/net/lingala/zip4j/headers/HeaderUtil.java
- src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java
- src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java
- src/main/java/net/lingala/zip4j/model/FileHeader.java
- src/main/java/net/lingala/zip4j/tasks/AbstractExtractFileTask.java
- src/main/java/net/lingala/zip4j/tasks/ExtractFileTask.java
- + src/main/java/net/lingala/zip4j/util/PasswordCallback.java
- src/main/java/net/lingala/zip4j/util/UnzipUtil.java
- src/test/java/net/lingala/zip4j/ExtractZipFileIT.java
- src/test/java/net/lingala/zip4j/RemoveFilesFromZipIT.java
- src/test/java/net/lingala/zip4j/ZipFileTest.java
- src/test/java/net/lingala/zip4j/headers/HeaderReaderIT.java
- src/test/java/net/lingala/zip4j/headers/HeaderUtilTest.java
- src/test/java/net/lingala/zip4j/io/inputstream/ZipInputStreamIT.java
- src/test/java/net/lingala/zip4j/io/outputstream/ZipOutputStreamIT.java
- src/test/java/net/lingala/zip4j/testutils/TestUtils.java
- + src/test/resources/test-archives/archive-with-no-dir-entries.zip
- + src/test/resources/test-archives/dirs_with_extended_local_file_headers.zip
- + src/test/resources/test-archives/zip_with_duplicate_entries.zip


Changes:

=====================================
.travis.yml
=====================================
@@ -5,3 +5,7 @@ before_script:
 
 script:
   - travis_wait 45 mvn clean verify
+
+cache:
+  directories:
+  - $HOME/.m2


=====================================
src/main/java/net/lingala/zip4j/ZipFile.java
=====================================
@@ -500,6 +500,34 @@ public class ZipFile implements Closeable {
     extractFile(fileHeader, destinationPath, null, unzipParameters);
   }
 
+  /**
+   * Extracts a specific file from the zip file to the destination path.
+   * If destination path is invalid, then this method throws an exception.
+   * <br><br>
+   * If newFileName is not null or empty, newly created file name will be replaced by
+   * the value in newFileName. If this value is null, then the file name will be the
+   * value in FileHeader.getFileName. If file being extract is a directory, the directory name
+   * will be replaced with the newFileName
+   * <br><br>
+   * If fileHeader is a directory, this method extracts all files under this directory.
+   * <br/><br/>
+   * Any parameters that have to be considered during extraction can be passed in through unzipParameters
+   *
+   * @param fileHeader file header corresponding to the entry which has to be extracted
+   * @param destinationPath path to which the entries of the zip are to be extracted
+   * @param newFileName if not null, this will be the name given to the file upon extraction
+   * @param unzipParameters any parameters that have to be considered during extraction
+   * @throws ZipException when an issue occurs during extraction
+   */
+  public void extractFile(FileHeader fileHeader, String destinationPath, String newFileName,
+                          UnzipParameters unzipParameters) throws ZipException {
+    if (fileHeader == null) {
+      throw new ZipException("input file header is null, cannot extract file");
+    }
+
+    extractFile(fileHeader.getFileName(), destinationPath, newFileName, unzipParameters);
+  }
+
   /**
    * Extracts a specific file from the zip file to the destination path.
    * This method first finds the necessary file header from the input file name.
@@ -631,42 +659,6 @@ public class ZipFile implements Closeable {
       throw new ZipException("file to extract is null or empty, cannot extract file");
     }
 
-    readZipInfo();
-
-    FileHeader fileHeader = HeaderUtil.getFileHeader(zipModel, fileName);
-
-    if (fileHeader == null) {
-      throw new ZipException("No file found with name " + fileName + " in zip file", ZipException.Type.FILE_NOT_FOUND);
-    }
-
-    extractFile(fileHeader, destinationPath, newFileName, unzipParameters);
-  }
-
-  /**
-   * Extracts a specific file from the zip file to the destination path.
-   * If destination path is invalid, then this method throws an exception.
-   * <br><br>
-   * If newFileName is not null or empty, newly created file name will be replaced by
-   * the value in newFileName. If this value is null, then the file name will be the
-   * value in FileHeader.getFileName. If file being extract is a directory, the directory name
-   * will be replaced with the newFileName
-   * <br><br>
-   * If fileHeader is a directory, this method extracts all files under this directory.
-   * <br/><br/>
-   * Any parameters that have to be considered during extraction can be passed in through unzipParameters
-   *
-   * @param fileHeader file header corresponding to the entry which has to be extracted
-   * @param destinationPath path to which the entries of the zip are to be extracted
-   * @param newFileName if not null, this will be the name given to the file upon extraction
-   * @param unzipParameters any parameters that have to be considered during extraction
-   * @throws ZipException when an issue occurs during extraction
-   */
-  public void extractFile(FileHeader fileHeader, String destinationPath, String newFileName,
-                          UnzipParameters unzipParameters) throws ZipException {
-    if (fileHeader == null) {
-      throw new ZipException("input file header is null, cannot extract file");
-    }
-
     if (!isStringNotNullAndNotEmpty(destinationPath)) {
       throw new ZipException("destination path is empty or null, cannot extract file");
     }
@@ -678,7 +670,7 @@ public class ZipFile implements Closeable {
     readZipInfo();
 
     new ExtractFileTask(zipModel, password, unzipParameters, buildAsyncParameters()).execute(
-        new ExtractFileTaskParameters(destinationPath, fileHeader, newFileName, buildConfig()));
+        new ExtractFileTaskParameters(destinationPath, fileName, newFileName, buildConfig()));
   }
 
   /**


=====================================
src/main/java/net/lingala/zip4j/crypto/AESDecrypter.java
=====================================
@@ -25,6 +25,7 @@ import net.lingala.zip4j.model.enums.AesKeyStrength;
 import java.util.Arrays;
 
 import static net.lingala.zip4j.crypto.AesCipherUtil.prepareBuffAESIVBytes;
+import static net.lingala.zip4j.exception.ZipException.Type.WRONG_PASSWORD;
 import static net.lingala.zip4j.util.InternalZipConstants.AES_BLOCK_SIZE;
 
 /**
@@ -49,7 +50,7 @@ public class AESDecrypter implements Decrypter {
       throws ZipException {
 
     if (password == null || password.length <= 0) {
-      throw new ZipException("empty or null password provided for AES decryption");
+      throw new ZipException("empty or null password provided for AES decryption", WRONG_PASSWORD);
     }
 
     final AesKeyStrength aesKeyStrength = aesExtraDataRecord.getAesKeyStrength();


=====================================
src/main/java/net/lingala/zip4j/headers/HeaderReader.java
=====================================
@@ -191,11 +191,6 @@ public class HeaderReader {
         byte[] fileNameBuff = new byte[fileNameLength];
         zip4jRaf.readFully(fileNameBuff);
         String fileName = decodeStringWithCharset(fileNameBuff, fileHeader.isFileNameUTF8Encoded(), charset);
-
-        if (fileName.contains(":\\")) {
-          fileName = fileName.substring(fileName.indexOf(":\\") + 2);
-        }
-
         fileHeader.setFileName(fileName);
       } else {
         fileHeader.setFileName(null);
@@ -560,11 +555,6 @@ public class HeaderReader {
       readFully(inputStream, fileNameBuf);
 
       String fileName = decodeStringWithCharset(fileNameBuf, localFileHeader.isFileNameUTF8Encoded(), charset);
-
-      if (fileName.contains(":" + System.getProperty("file.separator"))) {
-        fileName = fileName.substring(fileName.indexOf(":" + System.getProperty("file.separator")) + 2);
-      }
-
       localFileHeader.setFileName(fileName);
       localFileHeader.setDirectory(fileName.endsWith("/") || fileName.endsWith("\\"));
     } else {


=====================================
src/main/java/net/lingala/zip4j/headers/HeaderUtil.java
=====================================
@@ -8,7 +8,6 @@ import net.lingala.zip4j.util.InternalZipConstants;
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 import static net.lingala.zip4j.util.InternalZipConstants.ZIP4J_DEFAULT_CHARSET;
@@ -65,14 +64,10 @@ public class HeaderUtil {
     return zipModel.getEndOfCentralDirectoryRecord().getOffsetOfStartOfCentralDirectory();
   }
 
-  public static List<FileHeader> getFileHeadersUnderDirectory(List<FileHeader> allFileHeaders, FileHeader rootFileHeader) {
-    if (!rootFileHeader.isDirectory()) {
-      return Collections.emptyList();
-    }
-
+  public static List<FileHeader> getFileHeadersUnderDirectory(List<FileHeader> allFileHeaders, String fileName) {
     List<FileHeader> fileHeadersUnderDirectory = new ArrayList<>();
     for (FileHeader fileHeader : allFileHeaders) {
-      if (fileHeader.getFileName().startsWith(rootFileHeader.getFileName())) {
+      if (fileHeader.getFileName().startsWith(fileName)) {
         fileHeadersUnderDirectory.add(fileHeader);
       }
     }


=====================================
src/main/java/net/lingala/zip4j/io/inputstream/ZipInputStream.java
=====================================
@@ -16,6 +16,9 @@
 
 package net.lingala.zip4j.io.inputstream;
 
+import static net.lingala.zip4j.util.InternalZipConstants.MIN_BUFF_SIZE;
+import static net.lingala.zip4j.util.Zip4jUtil.getCompressionMethod;
+
 import net.lingala.zip4j.exception.ZipException;
 import net.lingala.zip4j.headers.HeaderReader;
 import net.lingala.zip4j.headers.HeaderSignature;
@@ -28,6 +31,7 @@ import net.lingala.zip4j.model.enums.AesVersion;
 import net.lingala.zip4j.model.enums.CompressionMethod;
 import net.lingala.zip4j.model.enums.EncryptionMethod;
 import net.lingala.zip4j.util.InternalZipConstants;
+import net.lingala.zip4j.util.PasswordCallback;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -36,15 +40,13 @@ import java.nio.charset.Charset;
 import java.util.List;
 import java.util.zip.CRC32;
 
-import static net.lingala.zip4j.util.InternalZipConstants.MIN_BUFF_SIZE;
-import static net.lingala.zip4j.util.Zip4jUtil.getCompressionMethod;
-
 public class ZipInputStream extends InputStream {
 
   private PushbackInputStream inputStream;
   private DecompressedInputStream decompressedInputStream;
   private HeaderReader headerReader = new HeaderReader();
   private char[] password;
+  private PasswordCallback passwordCallback;
   private LocalFileHeader localFileHeader;
   private CRC32 crc32 = new CRC32();
   private byte[] endOfEntryBuffer;
@@ -54,37 +56,56 @@ public class ZipInputStream extends InputStream {
   private boolean entryEOFReached = false;
 
   public ZipInputStream(InputStream inputStream) {
-    this(inputStream, null, (Charset) null);
+    this(inputStream, (char[]) null, (Charset) null);
   }
 
   public ZipInputStream(InputStream inputStream, Charset charset) {
-    this(inputStream, null, charset);
+    this(inputStream, (char[]) null, charset);
   }
 
   public ZipInputStream(InputStream inputStream, char[] password) {
     this(inputStream, password, (Charset) null);
   }
 
+  public ZipInputStream(InputStream inputStream, PasswordCallback passwordCallback) {
+    this(inputStream, passwordCallback, (Charset) null);
+  }
+
   public ZipInputStream(InputStream inputStream, char[] password, Charset charset) {
     this(inputStream, password, new Zip4jConfig(charset, InternalZipConstants.BUFF_SIZE));
   }
 
+  public ZipInputStream(InputStream inputStream, PasswordCallback passwordCallback, Charset charset) {
+    this(inputStream, passwordCallback, new Zip4jConfig(charset, InternalZipConstants.BUFF_SIZE));
+  }
+
   public ZipInputStream(InputStream inputStream, char[] password, Zip4jConfig zip4jConfig) {
+    this(inputStream, password, null, zip4jConfig);
+  }
+
+  public ZipInputStream(InputStream inputStream, PasswordCallback passwordCallback, Zip4jConfig zip4jConfig) {
+    this(inputStream, null, passwordCallback, zip4jConfig);
+  }
+
+  private ZipInputStream(InputStream inputStream, char[] password, PasswordCallback passwordCallback, Zip4jConfig zip4jConfig) {
     if (zip4jConfig.getBufferSize() < InternalZipConstants.MIN_BUFF_SIZE) {
       throw new IllegalArgumentException("Buffer size cannot be less than " + MIN_BUFF_SIZE + " bytes");
     }
 
     this.inputStream = new PushbackInputStream(inputStream, zip4jConfig.getBufferSize());
     this.password = password;
+    this.passwordCallback = passwordCallback;
     this.zip4jConfig = zip4jConfig;
   }
 
   public LocalFileHeader getNextEntry() throws IOException {
-    return getNextEntry(null);
+    return getNextEntry(null, true);
   }
 
-  public LocalFileHeader getNextEntry(FileHeader fileHeader) throws IOException {
-    if (localFileHeader != null) {
+  public LocalFileHeader getNextEntry(FileHeader fileHeader, boolean readUntilEndOfCurrentEntryIfOpen)
+      throws IOException {
+
+    if (localFileHeader != null && readUntilEndOfCurrentEntryIfOpen) {
       readUntilEndOfEntry();
     }
 
@@ -94,6 +115,10 @@ public class ZipInputStream extends InputStream {
       return null;
     }
 
+    if (localFileHeader.isEncrypted() && password == null && passwordCallback != null) {
+      setPassword(passwordCallback.getPassword());
+    }
+
     verifyLocalFileHeader(localFileHeader);
     crc32.reset();
 
@@ -152,10 +177,6 @@ public class ZipInputStream extends InputStream {
       return -1;
     }
 
-    if (localFileHeader.isDirectory()) {
-      return -1;
-    }
-
     try {
       int readLen = decompressedInputStream.read(b, off, len);
 


=====================================
src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java
=====================================
@@ -20,6 +20,7 @@ import java.io.OutputStream;
 import java.nio.charset.Charset;
 import java.util.zip.CRC32;
 
+import static net.lingala.zip4j.util.FileUtils.isZipEntryDirectory;
 import static net.lingala.zip4j.util.InternalZipConstants.BUFF_SIZE;
 import static net.lingala.zip4j.util.InternalZipConstants.MIN_BUFF_SIZE;
 
@@ -72,12 +73,19 @@ public class ZipOutputStream extends OutputStream {
 
   public void putNextEntry(ZipParameters zipParameters) throws IOException {
     verifyZipParameters(zipParameters);
-    initializeAndWriteFileHeader(zipParameters);
+
+    ZipParameters clonedZipParameters = new ZipParameters(zipParameters);
+    if (isZipEntryDirectory(zipParameters.getFileNameInZip())) {
+      clonedZipParameters.setWriteExtendedLocalFileHeader(false);
+      clonedZipParameters.setCompressionMethod(CompressionMethod.STORE);
+      clonedZipParameters.setEncryptFiles(false);
+    }
+    initializeAndWriteFileHeader(clonedZipParameters);
 
     //Initialisation of below compressedOutputStream should happen after writing local file header
     //because local header data should be written first and then the encryption header data
     //and below initialisation writes encryption header data
-    compressedOutputStream = initializeCompressedOutputStream(zipParameters);
+    compressedOutputStream = initializeCompressedOutputStream(clonedZipParameters);
     this.entryClosed = false;
   }
 
@@ -220,7 +228,7 @@ public class ZipOutputStream extends OutputStream {
   private void verifyZipParameters(ZipParameters zipParameters) {
     if (zipParameters.getCompressionMethod() == CompressionMethod.STORE
         && zipParameters.getEntrySize() < 0
-        && !isEntryDirectory(zipParameters.getFileNameInZip())
+        && !isZipEntryDirectory(zipParameters.getFileNameInZip())
         && zipParameters.isWriteExtendedLocalFileHeader()) {
       throw new IllegalArgumentException("uncompressed size should be set for zip entries of compression type store");
     }
@@ -235,8 +243,4 @@ public class ZipOutputStream extends OutputStream {
 
     return fileHeader.getAesExtraDataRecord().getAesVersion().equals(AesVersion.ONE);
   }
-
-  private boolean isEntryDirectory(String entryName) {
-    return entryName.endsWith("/") || entryName.endsWith("\\");
-  }
 }


=====================================
src/main/java/net/lingala/zip4j/model/FileHeader.java
=====================================
@@ -18,6 +18,8 @@ package net.lingala.zip4j.model;
 
 import net.lingala.zip4j.headers.HeaderSignature;
 
+import java.util.Objects;
+
 public class FileHeader extends AbstractFileHeader {
 
   private int versionMadeBy;
@@ -92,4 +94,26 @@ public class FileHeader extends AbstractFileHeader {
   public String toString() {
     return getFileName();
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    if (!super.equals(o)) return false;
+    FileHeader that = (FileHeader) o;
+    return determineOffsetOfLocalFileHeader(this) == determineOffsetOfLocalFileHeader(that);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(getFileName(), determineOffsetOfLocalFileHeader(this));
+  }
+
+  private long determineOffsetOfLocalFileHeader(FileHeader fileHeader) {
+    if (fileHeader.getZip64ExtendedInfo() != null) {
+      return fileHeader.getZip64ExtendedInfo().getOffsetLocalHeader();
+    }
+
+    return fileHeader.getOffsetLocalHeader();
+  }
 }


=====================================
src/main/java/net/lingala/zip4j/tasks/AbstractExtractFileTask.java
=====================================
@@ -18,7 +18,6 @@ import java.io.OutputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.regex.Matcher;
 
 import static net.lingala.zip4j.util.InternalZipConstants.FILE_SEPARATOR;
 
@@ -140,7 +139,7 @@ public abstract class AbstractExtractFileTask<T> extends AsyncZipTask<T> {
           "Zip4j does not support Strong Encryption, as this is patented.");
     }
 
-    LocalFileHeader localFileHeader = zipInputStream.getNextEntry(fileHeader);
+    LocalFileHeader localFileHeader = zipInputStream.getNextEntry(fileHeader, false);
 
     if (localFileHeader == null) {
       throw new ZipException("Could not read corresponding local file header for file header: "
@@ -159,21 +158,13 @@ public abstract class AbstractExtractFileTask<T> extends AsyncZipTask<T> {
   }
 
   private File determineOutputFile(FileHeader fileHeader, String outputPath, String newFileName) {
-    String outputFileName;
+    String outputFileName = fileHeader.getFileName();
     if (Zip4jUtil.isStringNotNullAndNotEmpty(newFileName)) {
       outputFileName = newFileName;
-    } else {
-      // replace all slashes with file separator
-      outputFileName = getFileNameWithSystemFileSeparators(fileHeader.getFileName());
     }
-
     return new File(outputPath + FILE_SEPARATOR + outputFileName);
   }
 
-  private String getFileNameWithSystemFileSeparators(String fileNameToReplace) {
-    return fileNameToReplace.replaceAll("[/\\\\]", Matcher.quoteReplacement(FILE_SEPARATOR));
-  }
-
   @Override
   protected ProgressMonitor.Task getTask() {
     return ProgressMonitor.Task.EXTRACT_ENTRY;


=====================================
src/main/java/net/lingala/zip4j/tasks/ExtractFileTask.java
=====================================
@@ -1,5 +1,7 @@
 package net.lingala.zip4j.tasks;
 
+import net.lingala.zip4j.exception.ZipException;
+import net.lingala.zip4j.headers.HeaderUtil;
 import net.lingala.zip4j.io.inputstream.SplitInputStream;
 import net.lingala.zip4j.io.inputstream.ZipInputStream;
 import net.lingala.zip4j.model.FileHeader;
@@ -8,6 +10,7 @@ import net.lingala.zip4j.model.Zip4jConfig;
 import net.lingala.zip4j.model.ZipModel;
 import net.lingala.zip4j.progress.ProgressMonitor;
 import net.lingala.zip4j.tasks.ExtractFileTask.ExtractFileTaskParameters;
+import net.lingala.zip4j.util.FileUtils;
 import net.lingala.zip4j.util.InternalZipConstants;
 import net.lingala.zip4j.util.UnzipUtil;
 import net.lingala.zip4j.util.Zip4jUtil;
@@ -16,6 +19,7 @@ import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 
+import static net.lingala.zip4j.exception.ZipException.Type.FILE_NOT_FOUND;
 import static net.lingala.zip4j.headers.HeaderUtil.getFileHeadersUnderDirectory;
 import static net.lingala.zip4j.headers.HeaderUtil.getTotalUncompressedSizeOfAllFileHeaders;
 
@@ -34,12 +38,12 @@ public class ExtractFileTask extends AbstractExtractFileTask<ExtractFileTaskPara
   protected void executeTask(ExtractFileTaskParameters taskParameters, ProgressMonitor progressMonitor)
       throws IOException {
 
-    try(ZipInputStream zipInputStream =
-            createZipInputStream(taskParameters.fileHeader, taskParameters.zip4jConfig)) {
-      List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileHeader);
+    List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileToExtract);
+    try(ZipInputStream zipInputStream = createZipInputStream(taskParameters.zip4jConfig)) {
       byte[] readBuff = new byte[taskParameters.zip4jConfig.getBufferSize()];
       for (FileHeader fileHeader : fileHeadersUnderDirectory) {
-        String newFileName = determineNewFileName(taskParameters.newFileName, taskParameters.fileHeader, fileHeader);
+        splitInputStream.prepareExtractionForFileHeader(fileHeader);
+        String newFileName = determineNewFileName(taskParameters.newFileName, taskParameters.fileToExtract, fileHeader);
         extractFile(zipInputStream, fileHeader, taskParameters.outputPath, newFileName, progressMonitor, readBuff);
       }
     } finally {
@@ -50,33 +54,35 @@ public class ExtractFileTask extends AbstractExtractFileTask<ExtractFileTaskPara
   }
 
   @Override
-  protected long calculateTotalWork(ExtractFileTaskParameters taskParameters) {
-    List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileHeader);
+  protected long calculateTotalWork(ExtractFileTaskParameters taskParameters) throws ZipException {
+    List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileToExtract);
     return getTotalUncompressedSizeOfAllFileHeaders(fileHeadersUnderDirectory);
   }
 
-  private List<FileHeader> getFileHeadersToExtract(FileHeader rootFileHeader) {
-    if (!rootFileHeader.isDirectory()) {
-      return Collections.singletonList(rootFileHeader);
+  private List<FileHeader> getFileHeadersToExtract(String fileNameToExtract) throws ZipException {
+    if (!FileUtils.isZipEntryDirectory(fileNameToExtract)) {
+      FileHeader fileHeader = HeaderUtil.getFileHeader(getZipModel(), fileNameToExtract);
+      if (fileHeader == null) {
+        throw new ZipException("No file found with name " + fileNameToExtract + " in zip file", FILE_NOT_FOUND);
+      }
+      return Collections.singletonList(fileHeader);
     }
 
-    return getFileHeadersUnderDirectory(
-        getZipModel().getCentralDirectory().getFileHeaders(), rootFileHeader);
+    return getFileHeadersUnderDirectory(getZipModel().getCentralDirectory().getFileHeaders(), fileNameToExtract);
   }
 
-  private ZipInputStream createZipInputStream(FileHeader fileHeader, Zip4jConfig zip4jConfig) throws IOException {
+  private ZipInputStream createZipInputStream(Zip4jConfig zip4jConfig) throws IOException {
     splitInputStream = UnzipUtil.createSplitInputStream(getZipModel());
-    splitInputStream.prepareExtractionForFileHeader(fileHeader);
     return new ZipInputStream(splitInputStream, password, zip4jConfig);
   }
 
-  private String determineNewFileName(String newFileName, FileHeader fileHeaderToExtract,
+  private String determineNewFileName(String newFileName, String fileNameToExtract,
                                       FileHeader fileHeaderBeingExtracted) {
     if (!Zip4jUtil.isStringNotNullAndNotEmpty(newFileName)) {
       return newFileName;
     }
 
-    if (!fileHeaderToExtract.isDirectory()) {
+    if (!FileUtils.isZipEntryDirectory(fileNameToExtract)) {
       return newFileName;
     }
 
@@ -85,20 +91,19 @@ public class ExtractFileTask extends AbstractExtractFileTask<ExtractFileTaskPara
       fileSeparator = "";
     }
 
-    return fileHeaderBeingExtracted.getFileName().replaceFirst(fileHeaderToExtract.getFileName(),
-        newFileName + fileSeparator);
+    return fileHeaderBeingExtracted.getFileName().replaceFirst(fileNameToExtract, newFileName + fileSeparator);
   }
 
   public static class ExtractFileTaskParameters extends AbstractZipTaskParameters {
     private String outputPath;
-    private FileHeader fileHeader;
+    private String fileToExtract;
     private String newFileName;
 
-    public ExtractFileTaskParameters(String outputPath, FileHeader fileHeader, String newFileName,
+    public ExtractFileTaskParameters(String outputPath, String fileToExtract, String newFileName,
                                      Zip4jConfig zip4jConfig) {
       super(zip4jConfig);
       this.outputPath = outputPath;
-      this.fileHeader = fileHeader;
+      this.fileToExtract = fileToExtract;
       this.newFileName = newFileName;
     }
   }


=====================================
src/main/java/net/lingala/zip4j/util/PasswordCallback.java
=====================================
@@ -0,0 +1,6 @@
+package net.lingala.zip4j.util;
+
+public interface PasswordCallback {
+
+    char[] getPassword();
+}


=====================================
src/main/java/net/lingala/zip4j/util/UnzipUtil.java
=====================================
@@ -27,7 +27,7 @@ public class UnzipUtil {
       splitInputStream.prepareExtractionForFileHeader(fileHeader);
 
       ZipInputStream zipInputStream = new ZipInputStream(splitInputStream, password);
-      if (zipInputStream.getNextEntry(fileHeader) == null) {
+      if (zipInputStream.getNextEntry(fileHeader, false) == null) {
         throw new ZipException("Could not locate local file header for corresponding file header");
       }
 


=====================================
src/test/java/net/lingala/zip4j/ExtractZipFileIT.java
=====================================
@@ -587,6 +587,22 @@ public class ExtractZipFileIT extends AbstractIT {
     ZipFileVerifier.verifyFileContent(TestUtils.getTestFileFromResources("öüäöäö/asöäööl"), outputFile);
   }
 
+  @Test
+  public void testExtractFileWhichIsAFolderExtractsContentsEvenWhenFolderEntryIsNotInZip() throws IOException {
+    ZipFile zipFile = new ZipFile(getTestArchiveFromResources("archive-with-no-dir-entries.zip"));
+    String outputFolderPath = outputFolder.getPath();
+
+    zipFile.extractFile("items/", outputFolderPath);
+
+    List<File> extractedFiles = FileUtils.getFilesInDirectoryRecursive(outputFolder, false, false);
+    assertThat(extractedFiles).isNotEmpty();
+    assertThat(extractedFiles).hasSize(3);
+    assertThat(extractedFiles).contains(
+        Paths.get(outputFolderPath, "items").toFile(),
+        Paths.get(outputFolderPath, "items/subitems").toFile(),
+        Paths.get(outputFolderPath, "items/subitems/beta.txt").toFile());
+  }
+
   @Test
   public void testExtractJarFileWithFileHeaderCompressedSize2() throws IOException {
     extractFile(TestUtils.getTestArchiveFromResources("jar-dir-fh-entry-size-2.jar"));
@@ -641,6 +657,27 @@ public class ExtractZipFileIT extends AbstractIT {
     }
   }
 
+  @Test
+  public void testExtractZipFileByFileNameWhichTheDirectoryEntryAtTheEndOfCentralDirectoryExtractsSuccessfully()
+      throws  ZipException {
+    ZipFile zipFile = new ZipFile(generatedZipFile);
+    zipFile.addFolder(getTestFileFromResources("/"));
+
+    zipFile.extractFile("test-files/", outputFolder.getPath());
+    zipFile = new ZipFile(generatedZipFile);
+    zipFile.extractFile("test-files/", outputFolder.getPath());
+  }
+
+  @Test
+  public void testExtractZipFileThrowsExceptionOfTypeWrongPasswordForNullAesPassword() throws ZipException {
+    testExtractZipFileThrowsExceptionOfTypeWrongPasswordForNullOrEmptyAesPassword(null);
+  }
+
+  @Test
+  public void testExtractZipFileThrowsExceptionOfTypeWrongPasswordForEmptyAesPassword() throws ZipException {
+    testExtractZipFileThrowsExceptionOfTypeWrongPasswordForNullOrEmptyAesPassword("".toCharArray());
+  }
+
   private void addFileToZip(ZipFile zipFile, String fileName, EncryptionMethod encryptionMethod, String password) throws ZipException {
     ZipParameters zipParameters = new ZipParameters();
     zipParameters.setEncryptFiles(encryptionMethod != null);
@@ -768,4 +805,15 @@ public class ExtractZipFileIT extends AbstractIT {
     }
     return regularFiles;
   }
+
+  private void testExtractZipFileThrowsExceptionOfTypeWrongPasswordForNullOrEmptyAesPassword(char[] password) throws ZipException {
+    ZipFile zipFile = new ZipFile(generatedZipFile, PASSWORD);
+    addFileToZip(zipFile, "sample.pdf", EncryptionMethod.AES, new String(PASSWORD));
+
+    expectedException.expect(ZipException.class);
+    expectedException.expectMessage("empty or null password provided for AES decryption");
+
+    zipFile = new ZipFile(generatedZipFile, password);
+    zipFile.extractAll(outputFolder.getPath());
+  }
 }


=====================================
src/test/java/net/lingala/zip4j/RemoveFilesFromZipIT.java
=====================================
@@ -234,6 +234,18 @@ public class RemoveFilesFromZipIT extends AbstractIT {
     assertZipFileDoesNotContainsFileByName(new ZipFile(zipFileUnderTest), fileNameToRemove);
   }
 
+  @Test
+  public void testRemoveEntryFromAZipFileWithDuplicateEntriesRemovesSuccessfully() throws IOException {
+    TestUtils.copyFile(TestUtils.getTestArchiveFromResources("zip_with_duplicate_entries.zip"), generatedZipFile);
+    ZipFile zipFile = new ZipFile(generatedZipFile);
+    int actualNumberOfEntries = zipFile.getFileHeaders().size();
+    zipFile.removeFile("sample.pdf");
+
+    zipFile = new ZipFile(generatedZipFile);
+    assertThat(zipFile.getFileHeaders().size()).isEqualTo(actualNumberOfEntries - 1);
+    assertZipFileDoesNotContainsFileByName(zipFile, "sample.pdf");
+  }
+
   private void testRemoveEntryFromZipWhichHasCentralDirEntriesInDifferentOrderThanLocalEntries(
       String fileNameToRemove) throws IOException {
     TestUtils.copyFile(TestUtils.getTestArchiveFromResources("cen_dir_entries_diff_order_as_local_entries.zip"),


=====================================
src/test/java/net/lingala/zip4j/ZipFileTest.java
=====================================
@@ -378,7 +378,7 @@ public class ZipFileTest {
     expectedException.expectMessage("destination path is empty or null, cannot extract file");
     expectedException.expect(ZipException.class);
 
-    zipFile.extractFile(new FileHeader(), null);
+    zipFile.extractFile(createFileHeader("SOME_NAME"), null);
   }
 
   @Test
@@ -386,7 +386,7 @@ public class ZipFileTest {
     expectedException.expectMessage("destination path is empty or null, cannot extract file");
     expectedException.expect(ZipException.class);
 
-    zipFile.extractFile(new FileHeader(), "");
+    zipFile.extractFile(createFileHeader("SOME_NAME"), "");
   }
 
   @Test
@@ -397,7 +397,7 @@ public class ZipFileTest {
     expectedException.expectMessage("invalid operation - Zip4j is in busy state");
     expectedException.expect(ZipException.class);
 
-    zipFile.extractFile(new FileHeader(), "SOME_DESTINATION");
+    zipFile.extractFile(createFileHeader("SOME_NAME"), "SOME_DESTINATION");
   }
 
   @Test
@@ -649,4 +649,10 @@ public class ZipFileTest {
     when(folder.canRead()).thenReturn(true);
     return folder;
   }
+
+  private FileHeader createFileHeader(String fileName) {
+    FileHeader fileHeader = new FileHeader();
+    fileHeader.setFileName(fileName);
+    return fileHeader;
+  }
 }


=====================================
src/test/java/net/lingala/zip4j/headers/HeaderReaderIT.java
=====================================
@@ -116,22 +116,6 @@ public class HeaderReaderIT extends AbstractIT {
     }
   }
 
-  @Test
-  public void testReadAllWithFileNameContainsWindowsDriveExcludesIt() throws IOException {
-    String fileName = "C:\\test.txt";
-    ZipModel actualZipModel = generateZipModel(1);
-    actualZipModel.getCentralDirectory().getFileHeaders().get(0).setFileName(fileName);
-    File headersFile = writeZipHeaders(actualZipModel);
-    actualZipModel.setZipFile(headersFile);
-
-    try(RandomAccessFile randomAccessFile = new RandomAccessFile(actualZipModel.getZipFile(),
-        RandomAccessFileMode.READ.getValue())) {
-      ZipModel readZipModel = headerReader.readAllHeaders(randomAccessFile, buildDefaultConfig());
-      FileHeader fileHeader = readZipModel.getCentralDirectory().getFileHeaders().get(0);
-      assertThat(fileHeader.getFileName()).isEqualTo("test.txt");
-    }
-  }
-
   @Test
   public void testReadAllWithoutFileNameWritesNull() throws IOException {
     ZipModel actualZipModel = generateZipModel(1);
@@ -438,4 +422,4 @@ public class HeaderReaderIT extends AbstractIT {
       return headersFile;
     }
   }
-}
\ No newline at end of file
+}


=====================================
src/test/java/net/lingala/zip4j/headers/HeaderUtilTest.java
=====================================
@@ -194,10 +194,8 @@ public class HeaderUtilTest {
   @Test
   public void testGetFileHeadersUnderDirectoryWhenNotDirectoryReturnsEmptyList() {
     List<FileHeader> allFileHeaders = generateFileHeaderWithFileNames("header", 5);
-    FileHeader rootFileHeader = generateFileHeader("some_name");
-    rootFileHeader.setDirectory(false);
 
-    assertThat(HeaderUtil.getFileHeadersUnderDirectory(allFileHeaders, rootFileHeader)).isEmpty();
+    assertThat(HeaderUtil.getFileHeadersUnderDirectory(allFileHeaders, "some_name")).isEmpty();
   }
 
   @Test
@@ -205,10 +203,8 @@ public class HeaderUtilTest {
     List<FileHeader> allFileHeaders = generateFileHeaderWithFileNames("some_name/header", 5);
     allFileHeaders.add(generateFileHeader("some_name/"));
     allFileHeaders.add(generateFileHeader("some_other_name.txt"));
-    FileHeader rootFileHeader = generateFileHeader("some_name/");
-    rootFileHeader.setDirectory(true);
 
-    List<FileHeader> filHeadersUnderDirectory = HeaderUtil.getFileHeadersUnderDirectory(allFileHeaders, rootFileHeader);
+    List<FileHeader> filHeadersUnderDirectory = HeaderUtil.getFileHeadersUnderDirectory(allFileHeaders, "some_name/");
     assertThat(filHeadersUnderDirectory).hasSize(6);
     for (FileHeader fileHeader : filHeadersUnderDirectory) {
       assertThat(fileHeader)


=====================================
src/test/java/net/lingala/zip4j/io/inputstream/ZipInputStreamIT.java
=====================================
@@ -49,7 +49,7 @@ public class ZipInputStreamIT extends AbstractIT {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Buffer size cannot be less than " + MIN_BUFF_SIZE + " bytes");
 
-    new ZipInputStream(inputStream, null, zip4jConfig);
+    new ZipInputStream(inputStream, (char[]) null, zip4jConfig);
   }
 
   @Test
@@ -97,55 +97,55 @@ public class ZipInputStreamIT extends AbstractIT {
   @Test
   public void testExtractDeflateWithAesEncryption256AndV1() throws IOException {
     File createdZipFile = createZipFile(CompressionMethod.DEFLATE, true, EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_256, PASSWORD, AesVersion.ONE);
-    extractZipFileWithInputStreams(createdZipFile, PASSWORD, InternalZipConstants.BUFF_SIZE, AesVersion.ONE);
+    extractZipFileWithInputStreams(createdZipFile, PASSWORD);
   }
 
   @Test
   public void testExtractWithReadLengthLessThan16WithAesAndStoreCompression() throws IOException {
     File createZipFile = createZipFile(CompressionMethod.STORE, true, EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_256, PASSWORD);
-    extractZipFileWithInputStreams(createZipFile, PASSWORD, 15, AesVersion.TWO);
+    extractZipFileWithInputStreams(createZipFile, PASSWORD, 15);
   }
 
   @Test
   public void testExtractWithReadLengthLessThan16WithAesAndDeflateCompression() throws IOException {
     File createZipFile = createZipFile(CompressionMethod.DEFLATE, true, EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_256, PASSWORD);
-    extractZipFileWithInputStreams(createZipFile, PASSWORD, 15, AesVersion.TWO);
+    extractZipFileWithInputStreams(createZipFile, PASSWORD, 15);
   }
 
   @Test
   public void testExtractWithReadLengthLessThan16WithZipCryptoAndStoreCompression() throws IOException {
     File createZipFile = createZipFile(CompressionMethod.DEFLATE, true, EncryptionMethod.ZIP_STANDARD, null, PASSWORD);
-    extractZipFileWithInputStreams(createZipFile, PASSWORD, 12, null);
+    extractZipFileWithInputStreams(createZipFile, PASSWORD, 12);
   }
 
   @Test
   public void testExtractWithReadLengthLessThan16WithZipCryptoAndDeflateCompression() throws IOException {
     File createZipFile = createZipFile(CompressionMethod.DEFLATE, true, EncryptionMethod.ZIP_STANDARD, null, PASSWORD);
-    extractZipFileWithInputStreams(createZipFile, PASSWORD, 5, null);
+    extractZipFileWithInputStreams(createZipFile, PASSWORD, 5);
   }
 
   @Test
   public void testExtractWithReadLengthGreaterThanButNotMultipleOf16WithAesAndStoreCompression() throws IOException {
     File createZipFile = createZipFile(CompressionMethod.STORE, true, EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_256, PASSWORD);
-    extractZipFileWithInputStreams(createZipFile, PASSWORD, (16 * 4) + 1, AesVersion.TWO);
+    extractZipFileWithInputStreams(createZipFile, PASSWORD, (16 * 4) + 1);
   }
 
   @Test
   public void testExtractWithReadLengthGreaterThanButNotMultipleOf16WithAesAndDeflateCompression() throws IOException {
     File createZipFile = createZipFile(CompressionMethod.DEFLATE, true, EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_256, PASSWORD);
-    extractZipFileWithInputStreams(createZipFile, PASSWORD, (16 * 8) - 10, AesVersion.TWO);
+    extractZipFileWithInputStreams(createZipFile, PASSWORD, (16 * 8) - 10);
   }
 
   @Test
   public void testExtractWithReadLengthGreaterThanButNotMultipleOf16WithZipCryptoAndStoreCompression() throws IOException {
     File createZipFile = createZipFile(CompressionMethod.DEFLATE, true, EncryptionMethod.ZIP_STANDARD, null, PASSWORD);
-    extractZipFileWithInputStreams(createZipFile, PASSWORD, (16 * 2) - 6, null);
+    extractZipFileWithInputStreams(createZipFile, PASSWORD, (16 * 2) - 6);
   }
 
   @Test
   public void testExtractWithReadLengthGreaterThanButNotMultipleOf16WithZipCryptoAndDeflateCompression() throws IOException {
     File createZipFile = createZipFile(CompressionMethod.DEFLATE, true, EncryptionMethod.ZIP_STANDARD, null, PASSWORD);
-    extractZipFileWithInputStreams(createZipFile, PASSWORD, (16 * 10) - 11, null);
+    extractZipFileWithInputStreams(createZipFile, PASSWORD, (16 * 10) - 11);
   }
 
   @Test
@@ -295,12 +295,22 @@ public class ZipInputStreamIT extends AbstractIT {
     }
   }
 
+  @Test
+  public void testExtractZipFileWithDirectoriesContainingExtendedLocalFileHeader() throws IOException {
+    extractZipFileWithInputStreams(TestUtils.getTestArchiveFromResources("dirs_with_extended_local_file_headers.zip"),
+        null, InternalZipConstants.BUFF_SIZE, false, 2);
+  }
+
   private void extractZipFileWithInputStreams(File zipFile, char[] password) throws IOException {
-    extractZipFileWithInputStreams(zipFile, password, 4096, AesVersion.TWO);
+    extractZipFileWithInputStreams(zipFile, password, InternalZipConstants.BUFF_SIZE);
   }
 
-  private void extractZipFileWithInputStreams(File zipFile, char[] password, int bufferLength, AesVersion aesVersion)
-      throws IOException {
+  private void extractZipFileWithInputStreams(File zipFile, char[] password, int bufferLength) throws IOException {
+    extractZipFileWithInputStreams(zipFile, password, bufferLength, true, FILES_TO_ADD.size());
+  }
+
+  private void extractZipFileWithInputStreams(File zipFile, char[] password, int bufferLength,
+                                              boolean verifyFileContents, int numberOfEntriesExpected) throws IOException {
     LocalFileHeader localFileHeader;
     int readLen;
     byte[] readBuffer = new byte[bufferLength];
@@ -316,13 +326,15 @@ public class ZipInputStreamIT extends AbstractIT {
             }
           }
           verifyLocalFileHeader(localFileHeader);
-          verifyFileContent(getTestFileFromResources(localFileHeader.getFileName()), extractedFile);
+          if (verifyFileContents) {
+            verifyFileContent(getTestFileFromResources(localFileHeader.getFileName()), extractedFile);
+          }
           numberOfEntriesExtracted++;
         }
       }
     }
 
-    assertThat(numberOfEntriesExtracted).isEqualTo(FILES_TO_ADD.size());
+    assertThat(numberOfEntriesExtracted).isEqualTo(numberOfEntriesExpected);
   }
 
   private void verifyLocalFileHeader(LocalFileHeader localFileHeader) {


=====================================
src/test/java/net/lingala/zip4j/io/outputstream/ZipOutputStreamIT.java
=====================================
@@ -238,6 +238,21 @@ public class ZipOutputStreamIT extends AbstractIT {
     verifyZipFileByExtractingAllFiles(generatedZipFile, outputFolder, 1);
   }
 
+  @Test
+  public void testCreateZipFileWithDirectoriesAndExtendedLocalFileHeaderIsSuccessful() throws IOException {
+    try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(generatedZipFile))) {
+      putNextEntryAndCloseEntry(zos, "dir1/");
+      putNextEntryAndCloseEntry(zos, "dir2/");
+    }
+
+    ZipFile zipFile = new ZipFile(generatedZipFile);
+    List<FileHeader> fileHeaders = zipFile.getFileHeaders();
+    for (FileHeader fileHeader : fileHeaders) {
+      assertThat(fileHeader.isDataDescriptorExists()).isFalse();
+    }
+    verifyZipFileByExtractingAllFiles(generatedZipFile, outputFolder, 2);
+  }
+
   private void testZipOutputStream(CompressionMethod compressionMethod, boolean encrypt,
                                    EncryptionMethod encryptionMethod, AesKeyStrength aesKeyStrength,
                                    AesVersion aesVersion)
@@ -434,4 +449,11 @@ public class ZipOutputStreamIT extends AbstractIT {
 
     return zipFile;
   }
+
+  private void putNextEntryAndCloseEntry(ZipOutputStream zipOutputStream, String fileName) throws IOException {
+    ZipParameters zipParameters = new ZipParameters();
+    zipParameters.setFileNameInZip(fileName);
+    zipOutputStream.putNextEntry(zipParameters);
+    zipOutputStream.closeEntry();
+  }
 }


=====================================
src/test/java/net/lingala/zip4j/testutils/TestUtils.java
=====================================
@@ -13,6 +13,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+import java.net.URL;
 import java.net.URLDecoder;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -164,8 +165,11 @@ public class TestUtils {
   private static File getFileFromResources(String parentFolder, String fileName) {
     try {
       String path = "/" + parentFolder + "/" + fileName;
-      String utfDecodedFilePath = URLDecoder.decode(TestUtils.class.getResource(path).getFile(),
-          InternalZipConstants.CHARSET_UTF_8.toString());
+      URL fileUrl = TestUtils.class.getResource(path);
+      if (fileUrl == null) {
+        throw new RuntimeException("File not found " + path);
+      }
+      String utfDecodedFilePath = URLDecoder.decode(fileUrl.getFile(), InternalZipConstants.CHARSET_UTF_8.toString());
       return new File(utfDecodedFilePath);
     } catch (UnsupportedEncodingException e) {
       throw new RuntimeException(e);


=====================================
src/test/resources/test-archives/archive-with-no-dir-entries.zip
=====================================
Binary files /dev/null and b/src/test/resources/test-archives/archive-with-no-dir-entries.zip differ


=====================================
src/test/resources/test-archives/dirs_with_extended_local_file_headers.zip
=====================================
Binary files /dev/null and b/src/test/resources/test-archives/dirs_with_extended_local_file_headers.zip differ


=====================================
src/test/resources/test-archives/zip_with_duplicate_entries.zip
=====================================
Binary files /dev/null and b/src/test/resources/test-archives/zip_with_duplicate_entries.zip differ



View it on GitLab: https://salsa.debian.org/java-team/zip4j/-/commit/bd9bc700acea7482af924645ba21c6dcbe3dc40d

-- 
View it on GitLab: https://salsa.debian.org/java-team/zip4j/-/commit/bd9bc700acea7482af924645ba21c6dcbe3dc40d
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20211124/d2e36415/attachment.htm>


More information about the pkg-java-commits mailing list