Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android ZipInputStream: only DEFLATED entries can have EXT descriptor

On my android device, I need to extract a file (an xapk, that is a plain zip archive as far as I know) that I get from a content uri. I'm creating the ZipInputStream using this line of code:

ZipInputStream zis = new ZipInputStream(getContentResolver().openInputStream(zipUri));

And then I try to read the first entry of the archive with:

ZipEntry entry = zis.getNextEntry()

The problem is that I get this exception:

java.util.zip.ZipException: only DEFLATED entries can have EXT descriptor

I'm 100% sure that there is no 0bytes files in the archive, and I can extract the same archive with other utilities (RAR, unzip etc) in my device.

If I use a ZipFile with an hard coded path (so no content uri involved), I can extract the same archive without problems, so the issue is related to ZipInputStream with an uri. On the other hand, I can't use a ZipFile here because it doesn't support content uris.

like image 568
Luca D'Amico Avatar asked Nov 09 '17 17:11

Luca D'Amico


1 Answers

Unfortunately the only answer is currently:

Don't process ZIP files in streaming mode like ZipInputStream does. It seems like all currently available ZIP processing components like ZipInputStream from the JRE and ZipArchiveInputStream from Apache commons-compress can not handle such ZIP files.

There is a very good description of the problem on the apache commons-compress help page:

ZIP archives know a feature called the data descriptor which is a way to store an entry's length after the entry's data. This can only work reliably if the size information can be taken from the central directory or the data itself can signal it is complete, which is true for data that is compressed using the DEFLATED compression algorithm.

ZipFile has access to the central directory and can extract entries using the data descriptor reliably. The same is true for ZipArchiveInputStream as long as the entry is DEFLATED. For STORED entries ZipArchiveInputStream can try to read ahead until it finds the next entry, but this approach is not safe and has to be enabled by a constructor argument explicitly.

https://commons.apache.org/proper/commons-compress/zip.html

Solution

The only possibility to avoid this problem is to use ZipFile, however the ZipFile implementation for the JRE requires a real file, therefore you may have to save to data to a temporary file.

Or if you use instead ZipFile from Apache commons-compress and you already have the ZIP file completely in-memory you can avoid saving it to a temporary file using a SeekableInMemoryByteChannel instead.


EDIT: solution of using in-memory ZipFile of Apache (Kotlin):

ByteArrayOutputStream().use { byteArrayOutputStream ->
    inputStream.copyTo(byteArrayOutputStream)
    ZipFile(SeekableInMemoryByteChannel(byteArrayOutputStream.toByteArray())).use {
        for (entry in it.entries) {
            it.getInputStream(entry).copyTo(someOutputStream)
        }
    }
}
like image 85
Robert Avatar answered Nov 09 '22 09:11

Robert