Android ZipInputStream: only DEFLATED entries can have EXT descriptor

徘徊边缘 提交于 2020-05-16 02:26:09

问题


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.


回答1:


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:

                    ByteArrayOutputStream().use { byteArrayOutputStream ->
                        inputStream.copyTo(byteArrayOutputStream)
                        ZipFile(SeekableInMemoryByteChannel(byteArrayOutputStream.toByteArray())).use {
                            for (entry in it.entries) {
                                 it.getInputStream(entry).copyTo(someOutputStream)
                            }
                        }
                    }



回答2:


Try using commons-compress

<dependency>
   <groupId>org.apache.commons</groupId>
    <artifactId>commons-compress</artifactId>
    <version>1.17</version>
</dependency>

Here is a snippet

public void unzip(File zipFile, File destDir) throws IOException, ArchiveException {
       String destDirectory = destDir.getAbsolutePath();

       try (ArchiveInputStream i = new ZipArchiveInputStream(new 
          FileInputStream(zipFile), "UTF-8", false, true)) {
          ArchiveEntry entry = null;
          while ((entry = i.getNextEntry()) != null) {
             if (!i.canReadEntryData(entry)) {
                System.out.println("Can't read entry: " + entry);
                continue;
             }
             String name = destDirectory + File.separator + entry.getName();
             File f = new File(name);
             if (entry.isDirectory()) {
                if (!f.isDirectory() && !f.mkdirs()) {
                   throw new IOException("failed to create directory " + f);
                }
             } else {
                File parent = f.getParentFile();
                if (!parent.isDirectory() && !parent.mkdirs()) {
                   throw new IOException("failed to create directory " + parent);
                }
                try (OutputStream o = Files.newOutputStream(f.toPath())) {
                   IOUtils.copy(i, o);
                }
             }
          }
       }
    }


来源:https://stackoverflow.com/questions/47208272/android-zipinputstream-only-deflated-entries-can-have-ext-descriptor

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!