javazipzip4j

Getting "negative time" exception when unpacking zip file with zip4j


I'm using zip4j to extract zip files. For many users this works fine but a Windows 8 user is getting the following exception:

net.lingala.zip4j.exception.ZipException: net.lingala.zip4j.exception.ZipException: java.lang.IllegalArgumentException: Negative time
    at net.lingala.zip4j.unzip.Unzip.initExtractFile(Unzip.java:163)
    at net.lingala.zip4j.unzip.Unzip.initExtractAll(Unzip.java:83)
    at net.lingala.zip4j.unzip.Unzip.extractAll(Unzip.java:73)
    at net.lingala.zip4j.core.ZipFile.extractAll(ZipFile.java:488)
    at net.lingala.zip4j.core.ZipFile.extractAll(ZipFile.java:451)
    ...

Negative time seems to be caused by a file on the file system having a negative time and/or by a JVM bug. Does anyone know how to fix this problem since this is quite odd and not related to my usage of the API I assume.

zip4j hasn't been maintained since 2013 so I wouldn't be surprised if it has some bugs but there just isn't another more capable zip library without boilerplate besides the JDK one. However, I need password protected zip file support and that isn't supported by the JDK.

Installing JDK 11 and using it to run the application does not fix the problem but it was worth a try.


Solution

  • After doing some more research, I found 7-Zip-JBinding:

    <dependency>
        <groupId>net.sf.sevenzipjbinding</groupId>
        <artifactId>sevenzipjbinding</artifactId>
        <version>LATEST</version>
    </dependency>
    <dependency>
        <groupId>net.sf.sevenzipjbinding</groupId>
        <artifactId>sevenzipjbinding-all-platforms</artifactId>
        <version>LATEST</version>
    </dependency>
    

    The following code can be used to extract a password protected zip file:

    public static void unzipUsing7Zip(String zipFilePath,
                                       String destinationDirectory,
                                       String password) throws IOException
    {
        try (val randomAccessFile = new RandomAccessFile(zipFilePath, "r");
             val randomAccessFileInStream = new RandomAccessFileInStream(randomAccessFile);
             val inArchive = openInArchive(null, randomAccessFileInStream))
        {
            val simpleInArchive = inArchive.getSimpleInterface();
            val archiveItems = simpleInArchive.getArchiveItems();
    
            for (val archiveItem : archiveItems)
            {
                if (!archiveItem.isFolder())
                {
                    val archiveItemPath = archiveItem.getPath();
                    val targetFilePath = destinationDirectory + separator + archiveItemPath;
    
                    try (val fileOutputStream = new FileOutputStream(targetFilePath))
                    {
                        archiveItem.extractSlow(data ->
                        {
                            try
                            {
                                if (archiveItemPath.indexOf(separator) > 0)
                                {
                                    // Create parent folder(s)
                                    val lastSeparatorIndex = archiveItemPath.lastIndexOf(separator);
                                    val path = destinationDirectory + separator + archiveItemPath.substring(0, lastSeparatorIndex);
                                    createDirectories(Paths.get(path));
                                }
    
                                fileOutputStream.write(data);
                            } catch (Exception exception)
                            {
                                exception.printStackTrace();
                            }
    
                            return data.length;
                        }, password);
                    }
                }
            }
        }
    }
    

    Based on here but cleaned up and real file extraction code via FileOutputStream added.

    Additional note: val comes from lombok of course.

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>LATEST</version>
        <scope>provided</scope>
    </dependency>