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

Andrius Merkys gitlab at salsa.debian.org
Mon May 25 05:34:29 BST 2020



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


Commits:
a7171185 by Andrius Merkys at 2020-05-25T00:12:12-04:00
New upstream version 2.6.0
- - - - -


30 changed files:

- .travis.yml
- README.md
- pom.xml
- src/main/java/net/lingala/zip4j/ZipFile.java
- src/main/java/net/lingala/zip4j/headers/FileHeaderFactory.java
- src/main/java/net/lingala/zip4j/headers/HeaderUtil.java
- + src/main/java/net/lingala/zip4j/headers/VersionMadeBy.java
- + src/main/java/net/lingala/zip4j/headers/VersionNeededToExtract.java
- src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java
- + src/main/java/net/lingala/zip4j/model/ExcludeFileFilter.java
- src/main/java/net/lingala/zip4j/model/ZipParameters.java
- src/main/java/net/lingala/zip4j/model/enums/AesKeyStrength.java
- src/main/java/net/lingala/zip4j/model/enums/AesVersion.java
- src/main/java/net/lingala/zip4j/model/enums/CompressionLevel.java
- src/main/java/net/lingala/zip4j/model/enums/CompressionMethod.java
- src/main/java/net/lingala/zip4j/model/enums/EncryptionMethod.java
- src/main/java/net/lingala/zip4j/tasks/AddFolderToZipTask.java
- src/main/java/net/lingala/zip4j/tasks/ExtractAllFilesTask.java
- src/main/java/net/lingala/zip4j/tasks/ExtractFileTask.java
- src/main/java/net/lingala/zip4j/util/FileUtils.java
- + src/main/java/net/lingala/zip4j/util/ZipVersionUtils.java
- src/test/java/net/lingala/zip4j/AddFilesToZipIT.java
- src/test/java/net/lingala/zip4j/ExtractZipFileIT.java
- src/test/java/net/lingala/zip4j/headers/FileHeaderFactoryTest.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/util/FileUtilsIT.java
- src/test/java/net/lingala/zip4j/util/FileUtilsTestLinuxAndMac.java
- src/test/java/net/lingala/zip4j/util/FileUtilsTestWindows.java
- + src/test/java/net/lingala/zip4j/util/ZipVersionUtilsTest.java


Changes:

=====================================
.travis.yml
=====================================
@@ -1,7 +1,7 @@
 language: java
 
 before_script:
-  - bash trigger-android-build.sh ${CIRCLE_CI_TOKEN} || travis_terminate 1;
+  - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash trigger-android-build.sh ${CIRCLE_CI_TOKEN} || travis_terminate 1; fi'
 
 script:
   - travis_wait 30 mvn clean verify


=====================================
README.md
=====================================
@@ -62,7 +62,7 @@ once again, and makes me support Zip4j as much as I can..
 <dependency>
     <groupId>net.lingala.zip4j</groupId>
     <artifactId>zip4j</artifactId>
-    <version>2.5.2</version>
+    <version>2.6.0</version>
 </dependency>
 ~~~~
 
@@ -94,6 +94,14 @@ new ZipFile("filename.zip").addFiles(Arrays.asList(new File("first_file"), new F
 new ZipFile("filename.zip").addFolder(new File("/user/myuser/folder_to_add"));
 ~~~~
 
+Since v2.6, it is possible to exclude certain files when adding a folder to zip by using an ExcludeFileFilter
+
+~~~~
+List<File> filesToExclude = Arrays.asList(new File("sample.pdf"), new File("sample_2.txt"));
+ExcludeFileFilter excludeFileFilter = filesToExclude::contains;
+new ZipFile("filename.zip").addFolder(new File("/user/myuser/folder_to_add"), new ZipParameters, excludeFileFilter);
+~~~~
+
 ### Creating a zip file from stream / Adding a stream to an existing zip
 
 ~~~~
@@ -211,17 +219,26 @@ new ZipFile("filename.zip", "password".toCharArray()).extractAll("/destination_d
 new ZipFile("filename.zip").extractFile("fileNameInZip.txt", "/destination_directory");
 ~~~~
 
+### Extracting a folder from zip (since v2.6.0)
+
+~~~~
+new ZipFile("filename.zip").extractFile("folderNameInZip/", "/destination_directory");
+~~~~
+
 ### Extracting a single file from zip which is password protected
 
 ~~~~
 new ZipFile("filename.zip", "password".toCharArray()).extractFile("fileNameInZip.txt", "/destination_directory");
 ~~~~
 
+Since v2.6.0: If the file name represents a directory, zip4j will extract all files in the zip that are part of this directory. 
+
 ### Extracting a single file from zip and giving it a new file name
 
 Below example will extract the file `fileNameInZip.txt` from the zip file to the output directory `/destination_directory` 
 and will give the file a name `newfileName.txt`. Without the third parameter of the new file name, the same name as the
-file in the zip will be used, which in this case is `fileNameInZip.txt`
+file in the zip will be used, which in this case is `fileNameInZip.txt`. If the file being extracted is a directory,
+`newFileName` parameter will be used as the directory name. 
 
 ~~~~
 new ZipFile("filename.zip", "password".toCharArray()).extractFile("fileNameInZip.txt", "/destination_directory", "newfileName.txt");


=====================================
pom.xml
=====================================
@@ -6,7 +6,7 @@
 
     <groupId>net.lingala.zip4j</groupId>
     <artifactId>zip4j</artifactId>
-    <version>2.5.3-SNAPSHOT</version>
+    <version>2.6.1-SNAPSHOT</version>
 
     <name>Zip4j</name>
     <description>Zip4j - A Java library for zip files and streams</description>


=====================================
src/main/java/net/lingala/zip4j/ZipFile.java
=====================================
@@ -181,7 +181,6 @@ public class ZipFile {
    */
   public void createSplitZipFileFromFolder(File folderToAdd, ZipParameters parameters, boolean splitArchive,
                                       long splitLength) throws ZipException {
-
     if (folderToAdd == null) {
       throw new ZipException("folderToAdd is null, cannot create zip file from folder");
     }
@@ -454,9 +453,7 @@ public class ZipFile {
    * 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 fileHeader is a directory, this method extracts all files under this directory
    *
    * @param fileHeader
    * @param destinationPath
@@ -469,6 +466,13 @@ public class ZipFile {
   /**
    * 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.
    *
    * @param fileHeader
    * @param destinationPath
@@ -503,6 +507,8 @@ public class ZipFile {
    * example is if there is a file "b.txt" in a folder "abc" in the zip file, then the
    * input file name has to be abc/b.txt
    * <br><br>
+   * If fileHeader is a directory, this method extracts all files under this directory.
+   * <br><br>
    * Throws an exception of type {@link ZipException.Type#FILE_NOT_FOUND} if file header could not be found for the given file name.
    * Throws an exception if the destination path is invalid.
    *
@@ -525,7 +531,10 @@ public class ZipFile {
    * <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
+   * 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>
    * Throws an exception of type {@link ZipException.Type#FILE_NOT_FOUND} if file header could not be found for the given file name.
    * Throws an exception if the destination path is invalid.


=====================================
src/main/java/net/lingala/zip4j/headers/FileHeaderFactory.java
=====================================
@@ -10,6 +10,7 @@ import net.lingala.zip4j.model.enums.CompressionLevel;
 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.RawIO;
 import net.lingala.zip4j.util.Zip4jUtil;
 
 import java.nio.charset.Charset;
@@ -17,16 +18,19 @@ import java.nio.charset.Charset;
 import static net.lingala.zip4j.util.BitUtils.setBit;
 import static net.lingala.zip4j.util.BitUtils.unsetBit;
 import static net.lingala.zip4j.util.FileUtils.isZipEntryDirectory;
+import static net.lingala.zip4j.util.ZipVersionUtils.determineVersionMadeBy;
+import static net.lingala.zip4j.util.ZipVersionUtils.determineVersionNeededToExtract;
 
 public class FileHeaderFactory {
 
-  public FileHeader generateFileHeader(ZipParameters zipParameters, boolean isSplitZip, int currentDiskNumberStart, Charset charset)
+  public FileHeader generateFileHeader(ZipParameters zipParameters, boolean isSplitZip, int currentDiskNumberStart,
+                                       Charset charset, RawIO rawIO)
       throws ZipException {
 
     FileHeader fileHeader = new FileHeader();
     fileHeader.setSignature(HeaderSignature.CENTRAL_DIRECTORY);
-    fileHeader.setVersionMadeBy(20);
-    fileHeader.setVersionNeededToExtract(20);
+    fileHeader.setVersionMadeBy(determineVersionMadeBy(zipParameters, rawIO));
+    fileHeader.setVersionNeededToExtract(determineVersionNeededToExtract(zipParameters).getCode());
 
     if (zipParameters.isEncryptFiles() && zipParameters.getEncryptionMethod() == EncryptionMethod.AES) {
       fileHeader.setCompressionMethod(CompressionMethod.AES_INTERNAL_ONLY);


=====================================
src/main/java/net/lingala/zip4j/headers/HeaderUtil.java
=====================================
@@ -7,7 +7,9 @@ import net.lingala.zip4j.util.InternalZipConstants;
 
 import java.io.UnsupportedEncodingException;
 import java.nio.charset.Charset;
+import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import static net.lingala.zip4j.util.InternalZipConstants.ZIP_STANDARD_CHARSET;
 import static net.lingala.zip4j.util.Zip4jUtil.isStringNotNullAndNotEmpty;
@@ -91,6 +93,27 @@ public class HeaderUtil {
     }
   }
 
+  public static List<FileHeader> getFileHeadersUnderDirectory(List<FileHeader> allFileHeaders, FileHeader rootFileHeader) {
+    if (!rootFileHeader.isDirectory()) {
+      return Collections.emptyList();
+    }
+
+    return allFileHeaders.stream().filter(e -> e.getFileName().startsWith(rootFileHeader.getFileName())).collect(Collectors.toList());
+  }
+
+  public static long getTotalUncompressedSizeOfAllFileHeaders(List<FileHeader> fileHeaders) {
+    long totalUncompressedSize = 0;
+    for (FileHeader fileHeader : fileHeaders) {
+      if (fileHeader.getZip64ExtendedInfo() != null &&
+          fileHeader.getZip64ExtendedInfo().getUncompressedSize() > 0) {
+        totalUncompressedSize += fileHeader.getZip64ExtendedInfo().getUncompressedSize();
+      } else {
+        totalUncompressedSize += fileHeader.getUncompressedSize();
+      }
+    }
+    return totalUncompressedSize;
+  }
+
   private static long getOffsetOfEndOfCentralDirectory(ZipModel zipModel) {
     if (zipModel.isZip64Format()) {
       return zipModel.getZip64EndOfCentralDirectoryRecord().getOffsetStartCentralDirectoryWRTStartDiskNumber();


=====================================
src/main/java/net/lingala/zip4j/headers/VersionMadeBy.java
=====================================
@@ -0,0 +1,18 @@
+package net.lingala.zip4j.headers;
+
+public enum VersionMadeBy {
+
+  SPECIFICATION_VERSION((byte) 51),
+  WINDOWS((byte) 0),
+  UNIX((byte) 3);
+
+  private byte code;
+
+  VersionMadeBy(byte code) {
+    this.code = code;
+  }
+
+  public byte getCode() {
+    return code;
+  }
+}


=====================================
src/main/java/net/lingala/zip4j/headers/VersionNeededToExtract.java
=====================================
@@ -0,0 +1,19 @@
+package net.lingala.zip4j.headers;
+
+public enum VersionNeededToExtract {
+
+  DEFAULT(10),
+  DEFLATE_COMPRESSED(20),
+  ZIP_64_FORMAT(45),
+  AES_ENCRYPTED(51);
+
+  private int code;
+
+  VersionNeededToExtract(int code) {
+    this.code = code;
+  }
+
+  public int getCode() {
+    return code;
+  }
+}


=====================================
src/main/java/net/lingala/zip4j/io/outputstream/ZipOutputStream.java
=====================================
@@ -148,7 +148,7 @@ public class ZipOutputStream extends OutputStream {
 
   private void initializeAndWriteFileHeader(ZipParameters zipParameters) throws IOException {
     fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, countingOutputStream.isSplitZipFile(),
-        countingOutputStream.getCurrentSplitFileCounter(), charset);
+        countingOutputStream.getCurrentSplitFileCounter(), charset, rawIO);
     fileHeader.setOffsetLocalHeader(countingOutputStream.getOffsetForNextEntry());
 
     localFileHeader = fileHeaderFactory.generateLocalFileHeader(fileHeader);


=====================================
src/main/java/net/lingala/zip4j/model/ExcludeFileFilter.java
=====================================
@@ -0,0 +1,9 @@
+package net.lingala.zip4j.model;
+
+import java.io.File;
+
+public interface ExcludeFileFilter {
+
+    boolean isExcluded(File file);
+
+}


=====================================
src/main/java/net/lingala/zip4j/model/ZipParameters.java
=====================================
@@ -22,9 +22,28 @@ import net.lingala.zip4j.model.enums.CompressionLevel;
 import net.lingala.zip4j.model.enums.CompressionMethod;
 import net.lingala.zip4j.model.enums.EncryptionMethod;
 
+/**
+ * Encapsulates the parameters that that control how Zip4J encodes data
+ */
 public class ZipParameters {
 
-  public enum SymbolicLinkAction {INCLUDE_LINK_ONLY, INCLUDE_LINKED_FILE_ONLY, INCLUDE_LINK_AND_LINKED_FILE};
+  /**
+   * Indicates the action to take when a symbolic link is added to the ZIP file
+   */
+  public enum SymbolicLinkAction {
+    /**
+     * Add only the symbolic link itself, not the target file or its contents
+     */
+    INCLUDE_LINK_ONLY, 
+    /**
+     * Add only the target file and its contents, using the filename of the symbolic link
+     */
+    INCLUDE_LINKED_FILE_ONLY, 
+    /**
+     * Add the symbolic link itself and the target file with its original filename and its contents
+     */
+    INCLUDE_LINK_AND_LINKED_FILE
+  };
 
   private CompressionMethod compressionMethod = CompressionMethod.DEFLATE;
   private CompressionLevel compressionLevel = CompressionLevel.NORMAL;
@@ -45,10 +64,23 @@ public class ZipParameters {
   private String rootFolderNameInZip;
   private String fileComment;
   private SymbolicLinkAction symbolicLinkAction = SymbolicLinkAction.INCLUDE_LINKED_FILE_ONLY;
-
+  private ExcludeFileFilter excludeFileFilter;
+  private boolean unixMode;
+
+  /**
+   * Create a ZipParameters instance with default values;
+   * CompressionMethod.DEFLATE, CompressionLevel.NORMAL, EncryptionMethod.NONE,
+   * AesKeyStrength.KEY_STRENGTH_256, AesVerson.Two, SymbolicLinkAction.INCLUDE_LINKED_FILE_ONLY,
+   * readHiddenFiles is true, readHiddenFolders is true, includeRootInFolder is true,
+   * writeExtendedLocalFileHeader is true, overrideExistingFilesInZip is true 
+   */
   public ZipParameters() {
   }
 
+  /**
+   * Create a clone of given ZipParameters instance
+   * @param zipParameters the ZipParameters instance to clone
+   */
   public ZipParameters(ZipParameters zipParameters) {
     this.compressionMethod = zipParameters.getCompressionMethod();
     this.compressionLevel = zipParameters.getCompressionLevel();
@@ -71,50 +103,101 @@ public class ZipParameters {
     this.symbolicLinkAction = zipParameters.getSymbolicLinkAction();
   }
 
+  /**
+   * Get the compression method specified in this ZipParameters
+   * @return the ZIP compression method
+   */
   public CompressionMethod getCompressionMethod() {
     return compressionMethod;
   }
 
+  /** 
+   * Set the ZIP compression method
+   * @param compressionMethod the ZIP compression method
+   */
   public void setCompressionMethod(CompressionMethod compressionMethod) {
     this.compressionMethod = compressionMethod;
   }
 
+  /**
+   * Test if files files are to be encrypted
+   * @return true if files are to be encrypted
+   */
   public boolean isEncryptFiles() {
     return encryptFiles;
   }
 
-  public void setEncryptFiles(boolean encryptFiles) {
+  /**
+   * Set the flag indicating that files are to be encrypted
+   * @param encryptFiles if true, files will be encrypted
+   */
+public void setEncryptFiles(boolean encryptFiles) {
     this.encryptFiles = encryptFiles;
   }
 
+  /**
+   * Get the encryption method used to encrypt files
+   * @return the encryption method
+   */
   public EncryptionMethod getEncryptionMethod() {
     return encryptionMethod;
   }
 
+  /**
+   * Set the encryption method used to encrypt files
+   * @param encryptionMethod the encryption method to be used
+   */
   public void setEncryptionMethod(EncryptionMethod encryptionMethod) {
     this.encryptionMethod = encryptionMethod;
   }
 
+  /**
+   * Get the compression level used to compress files
+   * @return the compression level used to compress files
+   */
   public CompressionLevel getCompressionLevel() {
     return compressionLevel;
   }
 
+  /**
+   * Set the compression level used to compress files
+   * @param compressionLevel the compression level used to compress files
+   */
   public void setCompressionLevel(CompressionLevel compressionLevel) {
     this.compressionLevel = compressionLevel;
   }
 
+  /**
+   * Test if hidden files will be included during folder recursion
+   * 
+   * @return true if hidden files will be included when adding folders to the zip
+   */
   public boolean isReadHiddenFiles() {
     return readHiddenFiles;
   }
-
+  
+  /**
+   * Indicate if hidden files will be included during folder recursion
+   * 
+   * @param readHiddenFiles if true, hidden files will be included when adding folders to the zip
+   */
   public void setReadHiddenFiles(boolean readHiddenFiles) {
     this.readHiddenFiles = readHiddenFiles;
   }
-
+  
+  /**
+   * Test if hidden folders will be included during folder recursion
+   * 
+   * @return true if hidden folders will be included when adding folders to the zip
+   */
   public boolean isReadHiddenFolders() {
     return readHiddenFolders;
   }
-
+  
+  /**
+   * Indicate if hidden folders will be included during folder recursion
+   * @param readHiddenFolders if true, hidden folders will be included when added folders to the zip
+   */
   public void setReadHiddenFolders(boolean readHiddenFolders) {
     this.readHiddenFolders = readHiddenFolders;
   }
@@ -123,26 +206,50 @@ public class ZipParameters {
     return super.clone();
   }
 
+  /**
+   * Get the key strength of the AES encryption key
+   * @return the key strength of the AES encryption key
+   */
   public AesKeyStrength getAesKeyStrength() {
     return aesKeyStrength;
   }
 
+  /**
+   * Set the key strength of the AES encryption key 
+   * @param aesKeyStrength the key strength of the AES encryption key
+   */
   public void setAesKeyStrength(AesKeyStrength aesKeyStrength) {
     this.aesKeyStrength = aesKeyStrength;
   }
 
+  /**
+   * Get the AES format version used for encryption
+   * @return the AES format version used for encryption
+   */
   public AesVersion getAesVersion() {
     return aesVersion;
   }
 
+  /**
+   * Set the AES format version to use for encryption
+   * @param aesVersion the AES format version to use
+   */
   public void setAesVersion(AesVersion aesVersion) {
     this.aesVersion = aesVersion;
   }
 
+  /**
+   * Test if the parent folder of the added files will be included in the ZIP
+   * @return true if the parent folder of the added files will be included into the zip
+   */
   public boolean isIncludeRootFolder() {
     return includeRootFolder;
   }
 
+  /**
+   * Set the flag to indicate if the parent folder of added files will be included in the ZIP
+   * @param includeRootFolder if true, the parent folder of added files will be included in the ZIP
+   */
   public void setIncludeRootFolder(boolean includeRootFolder) {
     this.includeRootFolder = includeRootFolder;
   }
@@ -167,14 +274,32 @@ public class ZipParameters {
     return fileNameInZip;
   }
 
-  public void setFileNameInZip(String fileNameInZip) {
+  /**
+   * Set the filename that will be used to include a file into the ZIP file to a different name
+   * that given by the source filename added to the ZIP file.  The filenameInZip must
+   * adhere to the ZIP filename specification, including the use of forward slash '/' as the 
+   * directory separator, and it must also be a relative file.  If the filenameInZip given is not null and 
+   * not empty, the value specified by setRootFolderNameInZip() will be ignored.  
+   * 
+   * @param fileNameInZip the filename to set in the ZIP. Use null or an empty String to set the default behavior
+   */
+   public void setFileNameInZip(String fileNameInZip) {
     this.fileNameInZip = fileNameInZip;
   }
 
+  /**
+   * Get the last modified time to be used for files written to the ZIP 
+   * @return the last modified time in milliseconds since the epoch
+   */
   public long getLastModifiedFileTime() {
     return lastModifiedFileTime;
   }
 
+  /**
+   * Set the last modified time recorded in the ZIP file for the added files.  If less than 0,
+   * the last modified time is cleared and the current time is used
+   * @param lastModifiedFileTime the last modified time in milliseconds since the epoch
+   */
   public void setLastModifiedFileTime(long lastModifiedFileTime) {
     if (lastModifiedFileTime <= 0) {
       return;
@@ -203,6 +328,10 @@ public class ZipParameters {
     return overrideExistingFilesInZip;
   }
 
+  /**
+   * Set the behavior if a file is added that already exists in the ZIP.
+   * @param overrideExistingFilesInZip if true, remove the existing file in the ZIP; if false do not add the new file
+   */
   public void setOverrideExistingFilesInZip(boolean overrideExistingFilesInZip) {
     this.overrideExistingFilesInZip = overrideExistingFilesInZip;
   }
@@ -211,23 +340,78 @@ public class ZipParameters {
     return rootFolderNameInZip;
   }
 
+  /**
+   * Set the folder name that will be prepended to the filename in the ZIP.  This value is ignored
+   * if setFileNameInZip() is specified with a non-null, non-empty string.
+   * 
+   * @param rootFolderNameInZip the name of the folder to be prepended to the filename
+   * in the ZIP archive
+   */
   public void setRootFolderNameInZip(String rootFolderNameInZip) {
     this.rootFolderNameInZip = rootFolderNameInZip;
   }
 
+  /**
+   * Get the file comment
+   * @return the file comment
+   */
   public String getFileComment() {
     return fileComment;
   }
 
+  /**
+   * Set the file comment
+   * @param fileComment the file comment
+   */
   public void setFileComment(String fileComment) {
     this.fileComment = fileComment;
   }
 
+  /**
+   * Get the behavior when adding a symbolic link
+   * @return the behavior when adding a symbolic link
+   */
   public SymbolicLinkAction getSymbolicLinkAction() {
     return symbolicLinkAction;
   }
 
+  /**
+   * Set the behavior when adding a symbolic link
+   * @param symbolicLinkAction the behavior when adding a symbolic link
+   */
   public void setSymbolicLinkAction(SymbolicLinkAction symbolicLinkAction) {
     this.symbolicLinkAction = symbolicLinkAction;
   }
+
+  /**
+   * Returns the file exclusion filter that is currently being used when adding files/folders to zip file
+   * @return ExcludeFileFilter
+   */
+  public ExcludeFileFilter getExcludeFileFilter() {
+    return excludeFileFilter;
+  }
+
+  /**
+   * Set a filter to exclude any files from the list of files being added to zip. Mostly used when adding a folder
+   * to a zip, and if certain files have to be excluded from adding to the zip file.
+   */
+  public void setExcludeFileFilter(ExcludeFileFilter excludeFileFilter) {
+    this.excludeFileFilter = excludeFileFilter;
+  }
+
+  /**
+   * Returns true if zip4j is using unix mode as default. Returns False otherwise.
+   * @return true if zip4j is using unix mode as default, false otherwise
+   */
+  public boolean isUnixMode() {
+    return unixMode;
+  }
+
+  /**
+   * When set to true, zip4j uses unix mode as default when generating file headers.
+   * @param unixMode
+   */
+  public void setUnixMode(boolean unixMode) {
+    this.unixMode = unixMode;
+  }
 }


=====================================
src/main/java/net/lingala/zip4j/model/enums/AesKeyStrength.java
=====================================
@@ -1,9 +1,22 @@
 package net.lingala.zip4j.model.enums;
 
+/**
+ * Indicates the AES encryption key length 
+ *
+ */
 public enum AesKeyStrength {
 
+  /**
+   * 128-bit AES key length 
+   */
   KEY_STRENGTH_128(1, 8, 16, 16),
+  /**
+   * 192-bit AES key length 
+   */
   KEY_STRENGTH_192(2, 12, 24, 24),
+  /**
+   * 256-bit AES key length 
+   */
   KEY_STRENGTH_256(3, 16, 32, 32);
 
   private int rawCode;
@@ -18,7 +31,11 @@ public enum AesKeyStrength {
     this.keyLength = keyLength;
   }
 
-  public int getRawCode() {
+  /**
+   * Get the code written to the ZIP file
+   * @return the code written the ZIP file
+   */
+   public int getRawCode() {
     return rawCode;
   }
 
@@ -29,11 +46,19 @@ public enum AesKeyStrength {
   public int getMacLength() {
     return macLength;
   }
-
+  /**
+   * Get the key length in bytes that this AesKeyStrength represents
+   * @return the key length in bytes
+   */
   public int getKeyLength() {
     return keyLength;
   }
 
+  /**
+   * Get a AesKeyStrength given a code from the ZIP file
+   * @param code the code from the ZIP file
+   * @return the AesKeyStrength that represents the given code, or null if the code does not match
+   */
   public static AesKeyStrength getAesKeyStrengthFromRawCode(int code) {
     for (AesKeyStrength aesKeyStrength : values()) {
       if (aesKeyStrength.getRawCode() == code) {


=====================================
src/main/java/net/lingala/zip4j/model/enums/AesVersion.java
=====================================
@@ -1,8 +1,17 @@
 package net.lingala.zip4j.model.enums;
 
+/**
+ * Indicates the AES format used
+ */
 public enum AesVersion {
 
+  /**
+   * Version 1 of the AES format 
+   */
   ONE(1),
+  /**
+   * Version 2 of the AES format 
+   */
   TWO(2);
 
   private int versionNumber;
@@ -11,10 +20,18 @@ public enum AesVersion {
     this.versionNumber = versionNumber;
   }
 
+  /**
+   * Get the AES version number as an integer
+   * @return the AES version number
+   */
   public int getVersionNumber() {
     return versionNumber;
   }
-
+  /**
+   * Get the AESVersion instance from an integer AES version number
+   * @return the AESVersion instance for a given version
+   * @throws IllegalArgumentException if an unsupported version is given
+   */
   public static AesVersion getFromVersionNumber(int versionNumber) {
     for (AesVersion aesVersion : values()) {
       if (aesVersion.versionNumber == versionNumber) {


=====================================
src/main/java/net/lingala/zip4j/model/enums/CompressionLevel.java
=====================================
@@ -1,11 +1,35 @@
 package net.lingala.zip4j.model.enums;
 
+/**
+ * Indicates the level of compression for the DEFLATE compression method
+ *
+ */
 public enum CompressionLevel {
 
+  /**
+   * Level 1 Deflate compression
+   * @see java.util.zip.Deflater#BEST_SPEED
+   */
   FASTEST(1),
+  /**
+   * Level 3 Deflate compression
+   * @see java.util.zip.Deflater
+   */
   FAST(3),
+  /**
+   * Level 5 Deflate compression
+   * @see java.util.zip.Deflater
+   */
   NORMAL(5),
+  /**
+   * Level 7 Deflate compression
+   * @see java.util.zip.Deflater
+   */
   MAXIMUM(7),
+  /**
+   * Level 9 Deflate compression. Not part of the original ZIP format specification.
+   * @see java.util.zip.Deflater#BEST_COMPRESSION
+   */
   ULTRA(9);
 
   private int level;
@@ -14,6 +38,10 @@ public enum CompressionLevel {
     this.level = level;
   }
 
+  /**
+   * Get the Deflate compression level (0-9) for this CompressionLevel 
+   * @return the deflate compression level
+   */
   public int getLevel() {
     return level;
   }


=====================================
src/main/java/net/lingala/zip4j/model/enums/CompressionMethod.java
=====================================
@@ -2,10 +2,24 @@ package net.lingala.zip4j.model.enums;
 
 import net.lingala.zip4j.exception.ZipException;
 
+/**
+ * Indicates the algorithm used for compression
+ *
+ */
 public enum CompressionMethod {
 
+  /**
+   * No compression is performed 
+   */
   STORE(0),
+  /**
+   * The Deflate compression is used.
+   * @see java.util.zip.Deflater 
+   */
   DEFLATE(8),
+  /**
+   * For internal use in Zip4J
+   */
   AES_INTERNAL_ONLY(99);
 
   private int code;
@@ -14,10 +28,20 @@ public enum CompressionMethod {
     this.code = code;
   }
 
+  /**
+   * Get the code used in the ZIP file for this CompressionMethod
+   * @return the code used in the ZIP file
+   */
   public int getCode() {
     return code;
   }
 
+  /**
+   * Get the CompressionMethod for a given ZIP file code
+   * @param code the code for a compression method
+   * @return the CompressionMethod related to the given code
+   * @throws ZipException on unknown code
+   */
   public static CompressionMethod getCompressionMethodFromCode(int code) throws ZipException {
     for (CompressionMethod compressionMethod : values()) {
       if (compressionMethod.getCode() == code) {


=====================================
src/main/java/net/lingala/zip4j/model/enums/EncryptionMethod.java
=====================================
@@ -1,10 +1,27 @@
 package net.lingala.zip4j.model.enums;
 
+/**
+ * Indicates the encryption method used in the ZIP file
+ *
+ */
 public enum EncryptionMethod {
 
+  /**
+   * No encryption is performed 
+   */
   NONE,
+  /**
+   * Encrypted with the weak ZIP standard algorithm 
+   */
   ZIP_STANDARD,
+  /**
+   * Encrypted with the stronger ZIP standard algorithm 
+   */
   ZIP_STANDARD_VARIANT_STRONG,
+  /**
+   * Encrypted with AES, the strongest choice but currently
+   * cannot be expanded in Windows Explorer 
+   */
   AES
 
 }


=====================================
src/main/java/net/lingala/zip4j/tasks/AddFolderToZipTask.java
=====================================
@@ -32,7 +32,8 @@ public class AddFolderToZipTask extends AbstractAddFileToZipTask<AddFolderToZipT
   protected long calculateTotalWork(AddFolderToZipTaskParameters taskParameters) throws ZipException {
     List<File> filesToAdd = getFilesInDirectoryRecursive(taskParameters.folderToAdd,
         taskParameters.zipParameters.isReadHiddenFiles(),
-        taskParameters.zipParameters.isReadHiddenFolders());
+        taskParameters.zipParameters.isReadHiddenFolders(),
+        taskParameters.zipParameters.getExcludeFileFilter());
 
     if (taskParameters.zipParameters.isIncludeRootFolder()) {
       filesToAdd.add(taskParameters.folderToAdd);
@@ -61,7 +62,8 @@ public class AddFolderToZipTask extends AbstractAddFileToZipTask<AddFolderToZipT
   private List<File> getFilesToAdd(AddFolderToZipTaskParameters taskParameters) throws ZipException {
     List<File> filesToAdd = getFilesInDirectoryRecursive(taskParameters.folderToAdd,
         taskParameters.zipParameters.isReadHiddenFiles(),
-        taskParameters.zipParameters.isReadHiddenFolders());
+        taskParameters.zipParameters.isReadHiddenFolders(),
+        taskParameters.zipParameters.getExcludeFileFilter());
 
     if (taskParameters.zipParameters.isIncludeRootFolder()) {
       filesToAdd.add(taskParameters.folderToAdd);


=====================================
src/main/java/net/lingala/zip4j/tasks/ExtractAllFilesTask.java
=====================================
@@ -11,6 +11,8 @@ import net.lingala.zip4j.util.UnzipUtil;
 import java.io.IOException;
 import java.nio.charset.Charset;
 
+import static net.lingala.zip4j.headers.HeaderUtil.getTotalUncompressedSizeOfAllFileHeaders;
+
 public class ExtractAllFilesTask extends AbstractExtractFileTask<ExtractAllFilesTaskParameters> {
 
   private char[] password;
@@ -45,18 +47,7 @@ public class ExtractAllFilesTask extends AbstractExtractFileTask<ExtractAllFiles
 
   @Override
   protected long calculateTotalWork(ExtractAllFilesTaskParameters taskParameters) {
-    long totalWork = 0;
-
-    for (FileHeader fileHeader : getZipModel().getCentralDirectory().getFileHeaders()) {
-      if (fileHeader.getZip64ExtendedInfo() != null &&
-          fileHeader.getZip64ExtendedInfo().getUncompressedSize() > 0) {
-        totalWork += fileHeader.getZip64ExtendedInfo().getUncompressedSize();
-      } else {
-        totalWork += fileHeader.getUncompressedSize();
-      }
-    }
-
-    return totalWork;
+    return getTotalUncompressedSizeOfAllFileHeaders(getZipModel().getCentralDirectory().getFileHeaders());
   }
 
   private ZipInputStream prepareZipInputStream(Charset charset) throws IOException {


=====================================
src/main/java/net/lingala/zip4j/tasks/ExtractFileTask.java
=====================================
@@ -6,10 +6,17 @@ import net.lingala.zip4j.model.FileHeader;
 import net.lingala.zip4j.model.ZipModel;
 import net.lingala.zip4j.progress.ProgressMonitor;
 import net.lingala.zip4j.tasks.ExtractFileTask.ExtractFileTaskParameters;
+import net.lingala.zip4j.util.InternalZipConstants;
 import net.lingala.zip4j.util.UnzipUtil;
+import net.lingala.zip4j.util.Zip4jUtil;
 
 import java.io.IOException;
 import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.List;
+
+import static net.lingala.zip4j.headers.HeaderUtil.getFileHeadersUnderDirectory;
+import static net.lingala.zip4j.headers.HeaderUtil.getTotalUncompressedSizeOfAllFileHeaders;
 
 public class ExtractFileTask extends AbstractExtractFileTask<ExtractFileTaskParameters> {
 
@@ -24,9 +31,13 @@ public class ExtractFileTask extends AbstractExtractFileTask<ExtractFileTaskPara
   @Override
   protected void executeTask(ExtractFileTaskParameters taskParameters, ProgressMonitor progressMonitor)
       throws IOException {
+
     try(ZipInputStream zipInputStream = createZipInputStream(taskParameters.fileHeader, taskParameters.charset)) {
-      extractFile(zipInputStream, taskParameters.fileHeader, taskParameters.outputPath, taskParameters.newFileName,
-          progressMonitor);
+      List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileHeader);
+      for (FileHeader fileHeader : fileHeadersUnderDirectory) {
+        String newFileName = determineNewFileName(taskParameters.newFileName, taskParameters.fileHeader, fileHeader);
+        extractFile(zipInputStream, fileHeader, taskParameters.outputPath, newFileName, progressMonitor);
+      }
     } finally {
       if (splitInputStream != null) {
         splitInputStream.close();
@@ -36,15 +47,43 @@ public class ExtractFileTask extends AbstractExtractFileTask<ExtractFileTaskPara
 
   @Override
   protected long calculateTotalWork(ExtractFileTaskParameters taskParameters) {
-    return taskParameters.fileHeader.getUncompressedSize();
+    List<FileHeader> fileHeadersUnderDirectory = getFileHeadersToExtract(taskParameters.fileHeader);
+    return getTotalUncompressedSizeOfAllFileHeaders(fileHeadersUnderDirectory);
+  }
+
+  private List<FileHeader> getFileHeadersToExtract(FileHeader rootFileHeader) {
+    if (!rootFileHeader.isDirectory()) {
+      return Collections.singletonList(rootFileHeader);
+    }
+
+    return getFileHeadersUnderDirectory(
+        getZipModel().getCentralDirectory().getFileHeaders(), rootFileHeader);
   }
 
-  protected ZipInputStream createZipInputStream(FileHeader fileHeader, Charset charset) throws IOException {
+  private ZipInputStream createZipInputStream(FileHeader fileHeader, Charset charset) throws IOException {
     splitInputStream = UnzipUtil.createSplitInputStream(getZipModel());
     splitInputStream.prepareExtractionForFileHeader(fileHeader);
     return new ZipInputStream(splitInputStream, password, charset);
   }
 
+  private String determineNewFileName(String newFileName, FileHeader fileHeaderToExtract, FileHeader fileHeaderBeingExtracted) {
+    if (!Zip4jUtil.isStringNotNullAndNotEmpty(newFileName)) {
+      return newFileName;
+    }
+
+    if (!fileHeaderToExtract.isDirectory()) {
+      return newFileName;
+    }
+
+    String fileSeparator = InternalZipConstants.ZIP_FILE_SEPARATOR;
+    if (newFileName.endsWith(InternalZipConstants.ZIP_FILE_SEPARATOR)) {
+      fileSeparator = "";
+    }
+
+    return fileHeaderBeingExtracted.getFileName().replaceFirst(fileHeaderToExtract.getFileName(),
+        newFileName + fileSeparator);
+  }
+
   public static class ExtractFileTaskParameters extends AbstractZipTaskParameters {
     private String outputPath;
     private FileHeader fileHeader;


=====================================
src/main/java/net/lingala/zip4j/util/FileUtils.java
=====================================
@@ -1,6 +1,7 @@
 package net.lingala.zip4j.util;
 
 import net.lingala.zip4j.exception.ZipException;
+import net.lingala.zip4j.model.ExcludeFileFilter;
 import net.lingala.zip4j.model.ZipModel;
 import net.lingala.zip4j.model.ZipParameters;
 import net.lingala.zip4j.progress.ProgressMonitor;
@@ -89,7 +90,11 @@ public class FileUtils {
     }
   }
 
-  public static List<File> getFilesInDirectoryRecursive(File path, boolean readHiddenFiles, boolean readHiddenFolders)
+  public static List<File> getFilesInDirectoryRecursive(File path, boolean readHiddenFiles, boolean readHiddenFolders) throws ZipException {
+    return getFilesInDirectoryRecursive(path, readHiddenFiles, readHiddenFolders, null);
+  }
+
+  public static List<File> getFilesInDirectoryRecursive(File path, boolean readHiddenFiles, boolean readHiddenFolders, ExcludeFileFilter excludedFiles)
       throws ZipException {
 
     if (path == null) {
@@ -104,6 +109,10 @@ public class FileUtils {
     }
 
     for (File file : filesAndDirs) {
+      if (excludedFiles != null && excludedFiles.isExcluded(file)) {
+        continue;
+      }
+
       if (file.isHidden()) {
         if (file.isDirectory()) {
           if (!readHiddenFolders) {
@@ -115,7 +124,7 @@ public class FileUtils {
       }
       result.add(file);
       if (file.isDirectory()) {
-        result.addAll(getFilesInDirectoryRecursive(file, readHiddenFiles, readHiddenFolders));
+        result.addAll(getFilesInDirectoryRecursive(file, readHiddenFiles, readHiddenFolders, excludedFiles));
       }
     }
 
@@ -483,6 +492,11 @@ public class FileUtils {
     }
   }
 
+  public static boolean isWindows() {
+    String os = System.getProperty("os.name").toLowerCase();
+    return isWindows(os);
+  }
+
   private static boolean isWindows(String os) {
     return (os.contains("win"));
   }


=====================================
src/main/java/net/lingala/zip4j/util/ZipVersionUtils.java
=====================================
@@ -0,0 +1,39 @@
+package net.lingala.zip4j.util;
+
+import net.lingala.zip4j.headers.VersionMadeBy;
+import net.lingala.zip4j.headers.VersionNeededToExtract;
+import net.lingala.zip4j.model.ZipParameters;
+import net.lingala.zip4j.model.enums.CompressionMethod;
+import net.lingala.zip4j.model.enums.EncryptionMethod;
+
+public class ZipVersionUtils {
+
+  public static int determineVersionMadeBy(ZipParameters zipParameters, RawIO rawIO) {
+    byte[] versionMadeBy = new byte[2];
+    versionMadeBy[0] = VersionMadeBy.SPECIFICATION_VERSION.getCode();
+    versionMadeBy[1] = VersionMadeBy.UNIX.getCode();
+    if (FileUtils.isWindows() && !zipParameters.isUnixMode()) {  // skip setting windows mode if unix mode is forced
+      versionMadeBy[1] = VersionMadeBy.WINDOWS.getCode();
+    }
+
+    return rawIO.readShortLittleEndian(versionMadeBy, 0);
+  }
+
+  public static VersionNeededToExtract determineVersionNeededToExtract(ZipParameters zipParameters) {
+    VersionNeededToExtract versionRequired = VersionNeededToExtract.DEFAULT;
+
+    if (zipParameters.getCompressionMethod() == CompressionMethod.DEFLATE) {
+      versionRequired = VersionNeededToExtract.DEFLATE_COMPRESSED;
+    }
+
+    if (zipParameters.getEntrySize() > InternalZipConstants.ZIP_64_SIZE_LIMIT) {
+      versionRequired = VersionNeededToExtract.ZIP_64_FORMAT;
+    }
+
+    if (zipParameters.isEncryptFiles() && zipParameters.getEncryptionMethod().equals(EncryptionMethod.AES)) {
+      versionRequired = VersionNeededToExtract.AES_ENCRYPTED;
+    }
+
+    return versionRequired;
+  }
+}


=====================================
src/test/java/net/lingala/zip4j/AddFilesToZipIT.java
=====================================
@@ -16,6 +16,8 @@ import net.lingala.zip4j.testutils.TestUtils;
 import net.lingala.zip4j.testutils.ZipFileVerifier;
 import net.lingala.zip4j.util.BitUtils;
 import net.lingala.zip4j.util.InternalZipConstants;
+import net.lingala.zip4j.util.RawIO;
+import net.lingala.zip4j.util.ZipVersionUtils;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -25,6 +27,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -36,6 +39,8 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 public class AddFilesToZipIT extends AbstractIT {
 
+  private RawIO rawIO = new RawIO();
+
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
@@ -55,6 +60,7 @@ public class AddFilesToZipIT extends AbstractIT {
 
     ZipFileVerifier.verifyZipFileByExtractingAllFiles(generatedZipFile, outputFolder, 1);
     verifyZipFileContainsFiles(generatedZipFile, singletonList("sample.pdf"), CompressionMethod.DEFLATE, null, null);
+    verifyZipVersions(zipFile.getFileHeaders().get(0), new ZipParameters());
   }
 
   @Test
@@ -82,6 +88,7 @@ public class AddFilesToZipIT extends AbstractIT {
 
     ZipFileVerifier.verifyZipFileByExtractingAllFiles(generatedZipFile, PASSWORD, outputFolder, 1, true, CHARSET_CP_949);
     assertThat(zipFile.getFileHeaders().get(0).getFileName()).isEqualTo(koreanFileName);
+    verifyZipVersions(zipFile.getFileHeaders().get(0), zipParameters);
   }
 
   @Test
@@ -138,6 +145,7 @@ public class AddFilesToZipIT extends AbstractIT {
     ZipFileVerifier.verifyZipFileByExtractingAllFiles(generatedZipFile, PASSWORD, outputFolder, 1);
     verifyZipFileContainsFiles(generatedZipFile, singletonList("sample_text_large.txt"), CompressionMethod.STORE,
         EncryptionMethod.AES, AesKeyStrength.KEY_STRENGTH_128);
+    verifyZipVersions(zipFile.getFileHeaders().get(0), zipParameters);
   }
 
   @Test
@@ -620,6 +628,23 @@ public class AddFilesToZipIT extends AbstractIT {
     ZipFileVerifier.verifyZipFileByExtractingAllFiles(generatedZipFile, outputFolder, 13);
   }
 
+  @Test
+  public void testAddFolderWithExcludeFileFilter() throws IOException {
+    ZipFile zipFile = new ZipFile(generatedZipFile);
+    List<File> filesToExclude = Arrays.asList(
+        TestUtils.getTestFileFromResources("sample.pdf"),
+        TestUtils.getTestFileFromResources("sample_directory/favicon.ico")
+    );
+    ZipParameters zipParameters = new ZipParameters();
+    zipParameters.setIncludeRootFolder(false);
+    zipParameters.setExcludeFileFilter(filesToExclude::contains);
+
+    zipFile.addFolder(TestUtils.getTestFileFromResources(""), zipParameters);
+
+    ZipFileVerifier.verifyZipFileByExtractingAllFiles(generatedZipFile, outputFolder, 10);
+    verifyZipFileDoesNotContainFiles(generatedZipFile, Arrays.asList("sample.pdf", "sample_directory/favicon.ico"));
+  }
+
   @Test
   public void testAddStreamToZipThrowsExceptionWhenFileNameIsNull() throws IOException {
     ZipFile zipFile = new ZipFile(generatedZipFile);
@@ -849,6 +874,17 @@ public class AddFilesToZipIT extends AbstractIT {
     verifyAllFilesAreOf(fileHeaders, compressionMethod, encryptionMethod, aesKeyStrength, aesVersion);
   }
 
+  private void verifyZipFileDoesNotContainFiles(File generatedZipFile, List<String> fileNamesNotInZip) throws ZipException {
+    ZipFile zipFile = new ZipFile(generatedZipFile);
+    for (FileHeader fileHeader : zipFile.getFileHeaders()) {
+      for (String fileNameNotInZip : fileNamesNotInZip) {
+        assertThat(fileHeader.getFileName())
+            .withFailMessage("Expected file " + fileNameNotInZip + " to not be present in zip file")
+            .isNotEqualTo(fileNameNotInZip);
+      }
+    }
+  }
+
   private void verifyFoldersInZip(List<FileHeader> fileHeaders, File generatedZipFile, char[] password)
       throws IOException {
     verifyFoldersInFileHeaders(fileHeaders);
@@ -973,4 +1009,12 @@ public class AddFilesToZipIT extends AbstractIT {
 
     return compressionMethod;
   }
+
+  private void verifyZipVersions(FileHeader fileHeader, ZipParameters zipParameters) {
+    int versionMadeBy = ZipVersionUtils.determineVersionMadeBy(zipParameters, rawIO);
+    int versionNeededToExtract = ZipVersionUtils.determineVersionNeededToExtract(zipParameters).getCode();
+
+    assertThat(fileHeader.getVersionMadeBy()).isEqualTo(versionMadeBy);
+    assertThat(fileHeader.getVersionNeededToExtract()).isEqualTo(versionNeededToExtract);
+  }
 }


=====================================
src/test/java/net/lingala/zip4j/ExtractZipFileIT.java
=====================================
@@ -18,6 +18,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -448,6 +449,32 @@ public class ExtractZipFileIT extends AbstractIT {
     assertThat(zipFile.getFileHeaders()).hasSize(19);
   }
 
+  @Test
+  public void testExtractFileHeaderExtractAllFilesIfFileHeaderIsDirectory() throws IOException {
+    ZipFile zipFile = new ZipFile(generatedZipFile);
+    ZipParameters zipParameters = new ZipParameters();
+    zipParameters.setIncludeRootFolder(false);
+    zipFile.addFolder(TestUtils.getTestFileFromResources(""), zipParameters);
+
+    zipFile.extractFile(zipFile.getFileHeader("öüäöäö/"), outputFolder.getPath());
+    File outputFile = Paths.get(outputFolder.getPath(), "öüäöäö", "asöäööl").toFile();
+    assertThat(outputFile).exists();
+    ZipFileVerifier.verifyFileContent(TestUtils.getTestFileFromResources("öüäöäö/asöäööl"), outputFile);
+  }
+
+  @Test
+  public void testExtractFileHeaderExtractAllFilesIfFileHeaderIsDirectoryAndRenameFile() throws IOException {
+    ZipFile zipFile = new ZipFile(generatedZipFile);
+    ZipParameters zipParameters = new ZipParameters();
+    zipParameters.setIncludeRootFolder(false);
+    zipFile.addFolder(TestUtils.getTestFileFromResources(""), zipParameters);
+
+    zipFile.extractFile(zipFile.getFileHeader("öüäöäö/"), outputFolder.getPath(), "new_folder_name/");
+    File outputFile = Paths.get(outputFolder.getPath(), "new_folder_name", "asöäööl").toFile();
+    assertThat(outputFile).exists();
+    ZipFileVerifier.verifyFileContent(TestUtils.getTestFileFromResources("öüäöäö/asöäööl"), outputFile);
+  }
+
   private void addFileToZip(ZipFile zipFile, String fileName, EncryptionMethod encryptionMethod, String password) throws ZipException {
     ZipParameters zipParameters = new ZipParameters();
     zipParameters.setEncryptFiles(encryptionMethod != null);


=====================================
src/test/java/net/lingala/zip4j/headers/FileHeaderFactoryTest.java
=====================================
@@ -11,6 +11,9 @@ import net.lingala.zip4j.model.enums.CompressionLevel;
 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.RawIO;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -23,25 +26,37 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 public class FileHeaderFactoryTest {
 
+  private static final String ACTUAL_OS = System.getProperty("os.name");
   private static final String FILE_NAME_IN_ZIP = "filename.txt";
   private static final long ENTRY_CRC = 2323L;
 
   private FileHeaderFactory fileHeaderFactory = new FileHeaderFactory();
+  private RawIO rawIO = new RawIO();
 
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
+  @Before
+  public void setup() {
+    System.setProperty("os.name", "linux");
+  }
+
+  @After
+  public void cleanup() {
+    System.setProperty("os.name", ACTUAL_OS);
+  }
+
   @Test
   public void testGenerateFileHeaderWithoutFileNameThrowsException() throws ZipException {
     expectedException.expect(ZipException.class);
     expectedException.expectMessage("fileNameInZip is null or empty");
 
-    fileHeaderFactory.generateFileHeader(new ZipParameters(), false, 0, InternalZipConstants.CHARSET_UTF_8);
+    fileHeaderFactory.generateFileHeader(new ZipParameters(), false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
   }
 
   @Test
   public void testGenerateFileHeaderDefaults() throws ZipException {
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(generateZipParameters(), false, 0, InternalZipConstants.CHARSET_UTF_8);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(generateZipParameters(), false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
 
     assertThat(fileHeader).isNotNull();
     assertThat(fileHeader.getCompressionMethod()).isEqualTo(CompressionMethod.DEFLATE);
@@ -60,8 +75,8 @@ public class FileHeaderFactoryTest {
     ZipParameters zipParameters = generateZipParameters();
     zipParameters.setCompressionMethod(CompressionMethod.STORE);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
-    verifyFileHeader(fileHeader, zipParameters, false, 0, false);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
+    verifyFileHeader(fileHeader, zipParameters, false, 0, 10, false);
   }
 
   @Test
@@ -72,7 +87,7 @@ public class FileHeaderFactoryTest {
     ZipParameters zipParameters = generateZipParameters();
     zipParameters.setEncryptFiles(true);
 
-    fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
+    fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
   }
 
   @Test
@@ -81,8 +96,8 @@ public class FileHeaderFactoryTest {
     zipParameters.setEncryptFiles(true);
     zipParameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
-    verifyFileHeader(fileHeader, zipParameters, false, 0, false);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
+    verifyFileHeader(fileHeader, zipParameters, false, 0, 20, false);
   }
 
   @Test
@@ -95,7 +110,7 @@ public class FileHeaderFactoryTest {
     zipParameters.setEncryptionMethod(EncryptionMethod.AES);
     zipParameters.setAesKeyStrength(null);
 
-    fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
+    fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
   }
 
   @Test
@@ -104,8 +119,8 @@ public class FileHeaderFactoryTest {
     zipParameters.setEncryptFiles(true);
     zipParameters.setEncryptionMethod(EncryptionMethod.AES);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
-    verifyFileHeader(fileHeader, zipParameters, false, 0, true);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
+    verifyFileHeader(fileHeader, zipParameters, false, 0, 51, true);
     verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_256,
         CompressionMethod.DEFLATE, AesVersion.TWO);
   }
@@ -117,8 +132,8 @@ public class FileHeaderFactoryTest {
     zipParameters.setEncryptionMethod(EncryptionMethod.AES);
     zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_128);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
-    verifyFileHeader(fileHeader, zipParameters, false, 0, true);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
+    verifyFileHeader(fileHeader, zipParameters, false, 0, 51, true);
     verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_128,
         CompressionMethod.DEFLATE, AesVersion.TWO);
   }
@@ -130,8 +145,8 @@ public class FileHeaderFactoryTest {
     zipParameters.setEncryptionMethod(EncryptionMethod.AES);
     zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_192);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
-    verifyFileHeader(fileHeader, zipParameters, false, 0, true);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
+    verifyFileHeader(fileHeader, zipParameters, false, 0, 51, true);
     verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_192,
         CompressionMethod.DEFLATE, AesVersion.TWO);
   }
@@ -143,8 +158,8 @@ public class FileHeaderFactoryTest {
     zipParameters.setEncryptionMethod(EncryptionMethod.AES);
     zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
-    verifyFileHeader(fileHeader, zipParameters, false, 0, true);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
+    verifyFileHeader(fileHeader, zipParameters, false, 0, 51, true);
     verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_256,
         CompressionMethod.DEFLATE, AesVersion.TWO);
   }
@@ -157,8 +172,8 @@ public class FileHeaderFactoryTest {
     zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);
     zipParameters.setAesVersion(AesVersion.ONE);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
-    verifyFileHeader(fileHeader, zipParameters, false, 0, true);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
+    verifyFileHeader(fileHeader, zipParameters, false, 0, 51, true);
     verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_256,
         CompressionMethod.DEFLATE, AesVersion.ONE);
   }
@@ -171,8 +186,8 @@ public class FileHeaderFactoryTest {
     zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);
     zipParameters.setAesVersion(null);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
-    verifyFileHeader(fileHeader, zipParameters, false, 0, true);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
+    verifyFileHeader(fileHeader, zipParameters, false, 0, 51, true);
     verifyAesExtraDataRecord(fileHeader.getAesExtraDataRecord(), AesKeyStrength.KEY_STRENGTH_256,
         CompressionMethod.DEFLATE, AesVersion.TWO);
   }
@@ -183,7 +198,7 @@ public class FileHeaderFactoryTest {
     ZipParameters zipParameters = generateZipParameters();
     zipParameters.setLastModifiedFileTime(lastModifiedFileTime);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
 
     assertThat(fileHeader.getLastModifiedTime()).isEqualTo(javaToDosTime(zipParameters.getLastModifiedFileTime()));
   }
@@ -193,7 +208,7 @@ public class FileHeaderFactoryTest {
     ZipParameters zipParameters = generateZipParameters();
     zipParameters.setCompressionLevel(CompressionLevel.ULTRA);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
 
     verifyCompressionLevelGridForDeflate(CompressionLevel.ULTRA, fileHeader.getGeneralPurposeFlag()[0]);
   }
@@ -203,7 +218,7 @@ public class FileHeaderFactoryTest {
     ZipParameters zipParameters = generateZipParameters();
     zipParameters.setCompressionLevel(CompressionLevel.MAXIMUM);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
 
     verifyCompressionLevelGridForDeflate(CompressionLevel.MAXIMUM, fileHeader.getGeneralPurposeFlag()[0]);
   }
@@ -213,7 +228,7 @@ public class FileHeaderFactoryTest {
     ZipParameters zipParameters = generateZipParameters();
     zipParameters.setCompressionLevel(CompressionLevel.FAST);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
 
     verifyCompressionLevelGridForDeflate(CompressionLevel.FAST, fileHeader.getGeneralPurposeFlag()[0]);
   }
@@ -223,20 +238,20 @@ public class FileHeaderFactoryTest {
     ZipParameters zipParameters = generateZipParameters();
     zipParameters.setCompressionLevel(CompressionLevel.FASTEST);
 
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
 
     verifyCompressionLevelGridForDeflate(CompressionLevel.FASTEST, fileHeader.getGeneralPurposeFlag()[0]);
   }
 
   @Test
   public void testGenerateFileHeaderWithCorrectCharset() throws ZipException {
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(generateZipParameters(), false, 0, Charset.forName("Cp949"));
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(generateZipParameters(), false, 0, Charset.forName("Cp949"), rawIO);
     assertThat(isBitSet(fileHeader.getGeneralPurposeFlag()[1], 3)).isFalse();
   }
 
   @Test
   public void testGenerateFileHeaderWithUTF8Charset() throws ZipException {
-    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(generateZipParameters(), false, 0, InternalZipConstants.CHARSET_UTF_8);
+    FileHeader fileHeader = fileHeaderFactory.generateFileHeader(generateZipParameters(), false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
     assertThat(isBitSet(fileHeader.getGeneralPurposeFlag()[1], 3)).isTrue();
   }
 
@@ -250,6 +265,41 @@ public class FileHeaderFactoryTest {
     verifyLocalFileHeader(localFileHeader, lastModifiedFileTime);
   }
 
+  @Test
+  public void testVersionMadeByWindowsWithUnixModeOff() throws ZipException {
+    changeOsSystemPropertyToWindows();
+    testVersionMadeBy(generateZipParameters(), 51);
+  }
+
+  @Test
+  public void testVersionMadeByWindowsWithUnixModeOn() throws ZipException {
+    changeOsSystemPropertyToWindows();
+    ZipParameters zipParameters = generateZipParameters();
+    zipParameters.setUnixMode(true);
+    testVersionMadeBy(zipParameters, 819);
+  }
+
+  @Test
+  public void testVersionMadeByUnix() throws ZipException {
+    changeOsSystemPropertyToUnix();
+    testVersionMadeBy(generateZipParameters(), 819);
+  }
+
+  @Test
+  public void testVersionMadeByMac() throws ZipException {
+    changeOsSystemPropertyToMac();
+    testVersionMadeBy(generateZipParameters(), 819);
+  }
+
+  private void testVersionMadeBy(ZipParameters zipParameters, int expectedVersionMadeBy) {
+    try {
+      FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
+      assertThat(fileHeader.getVersionMadeBy()).isEqualTo(expectedVersionMadeBy);
+    } catch (Exception e) {
+      restoreOsSystemProperty();
+    }
+  }
+
   private ZipParameters generateZipParameters() {
     ZipParameters zipParameters = new ZipParameters();
     zipParameters.setFileNameInZip(FILE_NAME_IN_ZIP);
@@ -276,11 +326,11 @@ public class FileHeaderFactoryTest {
   }
 
   private void verifyFileHeader(FileHeader fileHeader, ZipParameters zipParameters, boolean isSplitZip,
-                                int diskNumberStart, boolean aesExtraDataRecordPresent) {
+                                int diskNumberStart, int versionNeededToExtract, boolean aesExtraDataRecordPresent) {
     assertThat(fileHeader).isNotNull();
     assertThat(fileHeader.getSignature()).isEqualTo(HeaderSignature.CENTRAL_DIRECTORY);
-    assertThat(fileHeader.getVersionMadeBy()).isEqualTo(20);
-    assertThat(fileHeader.getVersionNeededToExtract()).isEqualTo(20);
+    assertThat(fileHeader.getVersionMadeBy()).isEqualTo(819);
+    assertThat(fileHeader.getVersionNeededToExtract()).isEqualTo(versionNeededToExtract);
     verifyCompressionMethod(fileHeader, zipParameters);
     assertThat(fileHeader.isEncrypted()).isEqualTo(zipParameters.isEncryptFiles());
     assertThat(fileHeader.getEncryptionMethod()).isEqualTo(zipParameters.isEncryptFiles()
@@ -393,4 +443,19 @@ public class FileHeaderFactoryTest {
     assertThat(aesExtraDataRecord.getAesKeyStrength()).isEqualTo(aesKeyStrength);
   }
 
+  private void changeOsSystemPropertyToWindows() {
+    System.setProperty("os.name", "windows");
+  }
+
+  private void changeOsSystemPropertyToUnix() {
+    System.setProperty("os.name", "nux");
+  }
+
+  private void changeOsSystemPropertyToMac() {
+    System.setProperty("os.name", "mac");
+  }
+
+  private void restoreOsSystemProperty() {
+    System.setProperty("os.name", ACTUAL_OS);
+  }
 }
\ No newline at end of file


=====================================
src/test/java/net/lingala/zip4j/headers/HeaderReaderIT.java
=====================================
@@ -14,6 +14,7 @@ import net.lingala.zip4j.model.enums.EncryptionMethod;
 import net.lingala.zip4j.model.enums.RandomAccessFileMode;
 import net.lingala.zip4j.util.BitUtils;
 import net.lingala.zip4j.util.InternalZipConstants;
+import net.lingala.zip4j.util.RawIO;
 import org.junit.Test;
 
 import java.io.File;
@@ -38,6 +39,7 @@ public class HeaderReaderIT extends AbstractIT {
   private FileHeaderFactory fileHeaderFactory = new FileHeaderFactory();
   private HeaderReader headerReader = new HeaderReader();
   private HeaderWriter headerWriter = new HeaderWriter();
+  private RawIO rawIO = new RawIO();
 
   @Test
   public void testReadAllHeadersWith10Entries() throws IOException {
@@ -367,7 +369,7 @@ public class HeaderReaderIT extends AbstractIT {
     List<FileHeader> fileHeaders = new ArrayList<>();
     for (int i = 0; i < numberOfEntries; i++) {
       zipParameters.setFileNameInZip(FILE_NAME_PREFIX + i);
-      FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8);
+      FileHeader fileHeader = fileHeaderFactory.generateFileHeader(zipParameters, false, 0, InternalZipConstants.CHARSET_UTF_8, rawIO);
       fileHeaders.add(fileHeader);
     }
     return fileHeaders;


=====================================
src/test/java/net/lingala/zip4j/headers/HeaderUtilTest.java
=====================================
@@ -3,6 +3,7 @@ package net.lingala.zip4j.headers;
 import net.lingala.zip4j.exception.ZipException;
 import net.lingala.zip4j.model.CentralDirectory;
 import net.lingala.zip4j.model.FileHeader;
+import net.lingala.zip4j.model.Zip64ExtendedInfo;
 import net.lingala.zip4j.model.ZipModel;
 import net.lingala.zip4j.util.InternalZipConstants;
 import org.junit.Rule;
@@ -200,7 +201,7 @@ public class HeaderUtilTest {
   public void testGetIndexOfFileHeaderGetsIndexSuccessfully() throws ZipException {
     String fileNamePrefix = "FILE_NAME_";
     int numberOfEntriesToAdd = 10;
-    List<FileHeader> fileHeadersInZipModel = generateFileHeaderWithFileNames(fileNamePrefix, numberOfEntriesToAdd);
+    List<FileHeader> fileHeadersInZipModel = generateFileHeaderWithFileNamesWithEmptyAndNullFileNames(fileNamePrefix, numberOfEntriesToAdd);
     ZipModel zipModel = new ZipModel();
     zipModel.getCentralDirectory().setFileHeaders(fileHeadersInZipModel);
 
@@ -265,13 +266,60 @@ public class HeaderUtilTest {
     assertThat(HeaderUtil.decodeStringWithCharset(plainEncodedBytes, false, null)).isEqualTo(englishString);
   }
 
+  @Test
+  public void testGetFileHeadersUnderDirectoryWhenNotDirectoryReturnsEmptyList() {
+    List<FileHeader> allFileHeaders = generateFileHeaderWithFileNames("header", 5);
+    FileHeader rootFileHeader = generateFileHeader("some_name");
+    rootFileHeader.setDirectory(false);
+
+    assertThat(HeaderUtil.getFileHeadersUnderDirectory(allFileHeaders, rootFileHeader)).isEmpty();
+  }
+
+  @Test
+  public void testGetFileHeadersUnderDirectoryReturnsFileHeadersUnderDirectory() {
+    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);
+    assertThat(filHeadersUnderDirectory).hasSize(6);
+    for (FileHeader fileHeader : filHeadersUnderDirectory) {
+      assertThat(fileHeader)
+          .withFailMessage("file header with name some_other_name.txt should not exist")
+          .isNotEqualTo("some_other_name.txt");
+    }
+  }
+
+  @Test
+  public void testGetUncompressedSizeOfAllFileHeaders() {
+    FileHeader fileHeader1 = generateFileHeader("1");
+    fileHeader1.setUncompressedSize(1000);
+    FileHeader fileHeader2 = generateFileHeader("2");
+    fileHeader2.setUncompressedSize(2000);
+    FileHeader fileHeader3 = generateFileHeader("3");
+    Zip64ExtendedInfo zip64ExtendedInfo = new Zip64ExtendedInfo();
+    zip64ExtendedInfo.setUncompressedSize(3000);
+    fileHeader3.setZip64ExtendedInfo(zip64ExtendedInfo);
+    fileHeader3.setUncompressedSize(0);
+    List<FileHeader> fileHeaders = Arrays.asList(fileHeader1, fileHeader2, fileHeader3);
+
+    assertThat(HeaderUtil.getTotalUncompressedSizeOfAllFileHeaders(fileHeaders)).isEqualTo(6000);
+  }
+
+  private List<FileHeader> generateFileHeaderWithFileNamesWithEmptyAndNullFileNames(String fileNamePrefix, int numberOfEntriesToAdd) {
+    List<FileHeader> fileHeaders = generateFileHeaderWithFileNames(fileNamePrefix, numberOfEntriesToAdd);
+    fileHeaders.add(generateFileHeader(""));
+    fileHeaders.add(generateFileHeader(null));
+    return fileHeaders;
+  }
+
   private List<FileHeader> generateFileHeaderWithFileNames(String fileNamePrefix, int numberOfEntriesToAdd) {
     List<FileHeader> fileHeaders = new ArrayList<>();
     for (int i = 0; i < numberOfEntriesToAdd; i++) {
       fileHeaders.add(generateFileHeader(fileNamePrefix + i));
     }
-    fileHeaders.add(generateFileHeader(""));
-    fileHeaders.add(generateFileHeader(null));
     return fileHeaders;
   }
 


=====================================
src/test/java/net/lingala/zip4j/util/FileUtilsIT.java
=====================================
@@ -17,6 +17,8 @@ import java.io.RandomAccessFile;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -114,6 +116,21 @@ public class FileUtilsIT extends AbstractIT {
     assertThat(FileUtils.isSymbolicLink(linkFile.toFile()));
   }
 
+  @Test
+  public void testGetFilesInDirectoryRecursiveWithExcludeFileFilter() throws IOException {
+    File rootFolder = TestUtils.getTestFileFromResources("");
+    List<File> filesToExclude = Arrays.asList(
+        TestUtils.getTestFileFromResources("бореиская.txt"),
+        TestUtils.getTestFileFromResources("sample_directory/favicon.ico")
+    );
+    List<File> allFiles = FileUtils.getFilesInDirectoryRecursive(rootFolder, true, true, filesToExclude::contains);
+
+    assertThat(allFiles).hasSize(10);
+    for (File file : allFiles) {
+      assertThat(filesToExclude).doesNotContain(file);
+    }
+  }
+
   private void testInvalidOffsetsScenario(int start, int offset) throws IOException {
     expectedException.expectMessage("invalid offsets");
     expectedException.expect(ZipException.class);


=====================================
src/test/java/net/lingala/zip4j/util/FileUtilsTestLinuxAndMac.java
=====================================
@@ -90,6 +90,11 @@ public class FileUtilsTestLinuxAndMac {
     testGetFileAttributesGetsAsDefined(true);
   }
 
+  @Test
+  public void testIsWindowsReturnsFalse() {
+    assertThat(FileUtils.isWindows()).isFalse();
+  }
+
   private void testGetFileAttributesGetsAsDefined(boolean isDirectory) throws IOException {
     File file = mock(File.class);
     Path path = mock(Path.class);


=====================================
src/test/java/net/lingala/zip4j/util/FileUtilsTestWindows.java
=====================================
@@ -89,6 +89,11 @@ public class FileUtilsTestWindows {
     assertThat(attributes).contains(0, 0, 0, 0);
   }
 
+  @Test
+  public void testIsWindowsReturnsTrue() {
+    assertThat(FileUtils.isWindows()).isTrue();
+  }
+
   @Test
   public void testGetFileAttributesReturnsAttributesAsDefined() throws IOException {
     File file = mock(File.class);


=====================================
src/test/java/net/lingala/zip4j/util/ZipVersionUtilsTest.java
=====================================
@@ -0,0 +1,79 @@
+package net.lingala.zip4j.util;
+
+import net.lingala.zip4j.headers.VersionNeededToExtract;
+import net.lingala.zip4j.model.ZipParameters;
+import net.lingala.zip4j.model.enums.CompressionMethod;
+import net.lingala.zip4j.model.enums.EncryptionMethod;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ZipVersionUtilsTest {
+
+  private static final String ACTUAL_OS = System.getProperty("os.name");
+
+  private RawIO rawIO = new RawIO();
+
+  @Before
+  public void setup() {
+    System.setProperty("os.name", "linux");
+  }
+
+  @After
+  public void cleanup() {
+    System.setProperty("os.name", ACTUAL_OS);
+  }
+
+  @Test
+  public void testDetermineVersionMadeByUnix() {
+    assertThat(ZipVersionUtils.determineVersionMadeBy(new ZipParameters(), rawIO)).isEqualTo(819);
+  }
+
+  @Test
+  public void testDetermineVersionMadeByWindows() {
+    changeOsSystemPropertyToWindows();
+    assertThat(ZipVersionUtils.determineVersionMadeBy(new ZipParameters(), rawIO)).isEqualTo(51);
+  }
+
+  @Test
+  public void testDetermineVersionMadeByWindowsAndUnixModeOn() {
+    ZipParameters zipParameters = new ZipParameters();
+    zipParameters.setUnixMode(true);
+    assertThat(ZipVersionUtils.determineVersionMadeBy(zipParameters, rawIO)).isEqualTo(819);
+  }
+
+  @Test
+  public void testDetermineVersionNeededToExtractDefault() {
+    ZipParameters zipParameters = new ZipParameters();
+    zipParameters.setCompressionMethod(CompressionMethod.STORE);
+    assertThat(ZipVersionUtils.determineVersionNeededToExtract(zipParameters)).isEqualTo(VersionNeededToExtract.DEFAULT);
+  }
+
+  @Test
+  public void testDetermineVersionNeededToExtractDefalte() {
+    ZipParameters zipParameters = new ZipParameters();
+    zipParameters.setCompressionMethod(CompressionMethod.DEFLATE);
+    assertThat(ZipVersionUtils.determineVersionNeededToExtract(zipParameters)).isEqualTo(VersionNeededToExtract.DEFLATE_COMPRESSED);
+  }
+
+  @Test
+  public void testDetermineVersionNeededToExtractZip64() {
+    ZipParameters zipParameters = new ZipParameters();
+    zipParameters.setEntrySize(InternalZipConstants.ZIP_64_SIZE_LIMIT + 10);
+    assertThat(ZipVersionUtils.determineVersionNeededToExtract(zipParameters)).isEqualTo(VersionNeededToExtract.ZIP_64_FORMAT);
+  }
+
+  @Test
+  public void testDetermineVersionNeededToExtractAES() {
+    ZipParameters zipParameters = new ZipParameters();
+    zipParameters.setEncryptFiles(true);
+    zipParameters.setEncryptionMethod(EncryptionMethod.AES);
+    assertThat(ZipVersionUtils.determineVersionNeededToExtract(zipParameters)).isEqualTo(VersionNeededToExtract.AES_ENCRYPTED);
+  }
+
+  private void changeOsSystemPropertyToWindows() {
+    System.setProperty("os.name", "windows");
+  }
+}
\ No newline at end of file



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

-- 
View it on GitLab: https://salsa.debian.org/java-team/zip4j/-/commit/a71711856e15238e039454193b9c9634c67fa703
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/20200525/46645a66/attachment.html>


More information about the pkg-java-commits mailing list