< prev index next >

src/java.base/share/classes/java/util/zip/ZipFile.java

Print this page

        

@@ -28,13 +28,17 @@
 import java.io.Closeable;
 import java.io.InputStream;
 import java.io.IOException;
 import java.io.EOFException;
 import java.io.File;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
+import java.util.Arrays;
 import java.util.Deque;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;

@@ -65,10 +69,13 @@
     private volatile boolean closeRequested = false;
 
     private static final int STORED = ZipEntry.STORED;
     private static final int DEFLATED = ZipEntry.DEFLATED;
 
+    // Max buffer size when returning bytebuffers directly
+    private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
+
     /**
      * Mode flag to open a zip file for reading.
      */
     public static final int OPEN_READ = 0x1;
 

@@ -468,10 +475,239 @@
 
     // List of available Inflater objects for decompression
     private Deque<Inflater> inflaterCache = new ArrayDeque<>();
 
     /**
+     * Uncompress the zip entry into a new ByteBuffer.
+     *
+     * The returned ByteBuffer will have position 0 and limit set to the length
+     * of the uncompressed bytes of the zip entry.
+     *
+     * This method can only read entries smaller than 2GB, for larger entries
+     * use getInputStream.
+     *
+     * @param entry the zip file entry
+     *
+     * @return the ByteBuffer with the contents of the specified zip file entry.
+     *
+     * @throws ZipException if a ZIP format error has occurred
+     * @throws IOException if an I/O error has occurred
+     * @throws IllegalStateException if the zip file has been closed
+     * @throws BufferOverflowException if the zip entry is larger than the
+     * supplied buffer
+     * @throws ReadOnlyBufferException if the supplied buffer is read-only
+     */
+    private ByteBuffer getByteBuffer(ZipEntry entry) throws IOException {
+        if (entry == null) {
+            throw new NullPointerException("entry");
+        }
+
+        long size = entry.getSize();
+
+        if (size < 0) {
+            // Uknown size of zip entry (-1)
+            return getByteBufferUnknownSize(entry);
+        }
+
+        if (size > MAX_BUFFER_SIZE) {
+            throw new BufferOverflowException();
+        }
+
+        return fillByteBuffer(entry, ByteBuffer.allocate((int) size)).flip();
+    }
+
+    /**
+     * Fill a ByteBuffer with the uncompressed contents of the specified zip
+     * file entry.
+     *
+     * This method transfers the zip entry bytes into this buffer. If there are
+     * more bytes to be written than remain in this buffer, that is, if
+     * ZipEntry.getSize() > remaining(), then no bytes are transferred and a
+     * BufferOverflowException is thrown.
+     *
+     * Otherwise, this method copies length bytes from the given array into this
+     * buffer, starting at the given offset in the array and at the current
+     * position of this buffer. The position of this buffer is then incremented
+     * by length.
+     *
+     * This method can only read entries smaller than 2GB, for larger entries
+     * use getInputStream.
+     *
+     * @param entry the zip file entry
+     * @param buffer ByteBuffer to write the zip entry to
+     *
+     * @return the ByteBuffer with the contents of the specified zip file entry.
+     *
+     * @throws ZipException if a ZIP format error has occurred
+     * @throws IOException if an I/O error has occurred
+     * @throws IllegalStateException if the zip file has been closed
+     * @throws BufferOverflowException if the zip entry is larger than the
+     * supplied buffer
+     * @throws ReadOnlyBufferException if the supplied buffer is read-only
+     */
+    private ByteBuffer fillByteBuffer(ZipEntry entry, ByteBuffer buffer) throws IOException {
+        if (entry == null) {
+            throw new NullPointerException("entry");
+        }
+        if (buffer.isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
+
+        long jzentry = 0;
+        try {
+            long csize;
+            long size;
+            int cmethod;
+
+            synchronized (this) {
+                ensureOpen();
+                if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
+                    jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false);
+                } else {
+                    jzentry = getEntry(jzfile, zc.getBytes(entry.name), false);
+                }
+                if (jzentry == 0) {
+                    return null;
+                }
+
+                csize = getEntryCSize(jzentry);
+                size = getEntrySize(jzentry);
+                cmethod = getEntryMethod(jzentry);
+            }
+
+            if (csize < 0 || size < 0) {
+                // Uknown size of zip entry (-1)
+                ByteBuffer tempBuffer = getByteBufferUnknownSize(entry);
+                buffer.put(tempBuffer); // Throws BufferOverflowException if too large
+                return buffer;
+            }
+            if (csize > MAX_BUFFER_SIZE || size > MAX_BUFFER_SIZE) {
+                throw new BufferOverflowException();
+            }
+
+            switch (cmethod) {
+                case STORED:
+                    if (csize > buffer.remaining()) {
+                        throw new BufferOverflowException();
+                    }
+                    return getByteBufferStored(jzentry, (int) csize, buffer);
+                case DEFLATED:
+                    if (size > buffer.remaining()) {
+                        throw new BufferOverflowException();
+                    }
+
+                    byte[] cbytes = new byte[(int) csize];
+                    readCompressedBytes(jzentry, (int) csize, cbytes, 0);
+
+                    Inflater inf = getInflater();
+                    try {
+                        inf.setInput(cbytes);
+                        if (buffer.hasArray()) {
+                            inf.inflate(buffer.array(), buffer.arrayOffset(), buffer.remaining());
+                            buffer.position(buffer.position() + (int) size);
+                        } else {
+                            byte[] bytes = new byte[(int) size];
+                            inf.inflate(bytes);
+                            buffer.put(bytes);
+                        }
+                    } catch (DataFormatException e) {
+                        String s = e.getMessage();
+                        throw new ZipException(s != null ? s : "Invalid ZLIB data format");
+                    } finally {
+                        releaseInflater(inf);
+                    }
+                    return buffer;
+                default:
+                    throw new ZipException("invalid compression method");
+
+            }
+        } finally {
+            if (jzentry != 0) {
+                synchronized (this) {
+                    if (jzfile != 0) {
+                        freeEntry(jzfile, jzentry);
+                    }
+                }
+            }
+        }
+    }
+
+    private ByteBuffer getByteBufferStored(long jzentry, int csize, ByteBuffer buffer)
+            throws IOException {
+        assert csize >= buffer.remaining();
+        if (buffer.hasArray()) {
+            int nread = readCompressedBytes(jzentry, csize, buffer.array(),
+                                            buffer.arrayOffset());
+            buffer.position(buffer.position() + nread);
+        } else {
+            byte[] cbytes = new byte[csize];
+            readCompressedBytes(jzentry, csize, cbytes, 0);
+            buffer.put(cbytes);
+        }
+        return buffer;
+    }
+
+    private int readCompressedBytes(long jzentry, int csize, byte[] buf, int off)
+            throws IOException {
+        assert csize >= buf.length - off;
+        synchronized (this) {
+            ensureOpenOrZipException();
+            int n = 0;
+            int nread = 0;
+            while (nread < csize
+                   && (n = read(jzfile, jzentry, nread, buf, off + nread, csize - nread)) > 0) {
+                nread += n;
+            }
+            return nread;
+        }
+    }
+
+    private ByteBuffer getByteBufferUnknownSize(ZipEntry entry) throws IOException {
+        final int BUFFER_SIZE = 8192;
+        InputStream in = getInputStream(entry);
+
+        if (in == null) {
+            // not found
+            return null;
+        }
+
+        try (in) {
+            int capacity = in.available();
+            if (capacity == 0) {
+                capacity = BUFFER_SIZE;
+            }
+
+            byte[] buf = new byte[capacity];
+            int nread = 0;
+            int n;
+            for (;;) {
+                // read to EOF
+                while ((n = in.read(buf, nread, capacity - nread)) > 0) {
+                    nread += n;
+                }
+
+                // if last call to source.read() returned -1, we are done
+                // otherwise, try to read one more byte; if that failed we're done too
+                if (n < 0 || (n = in.read()) < 0) {
+                    break;
+                }
+
+                // one more byte was read; need to allocate a larger buffer
+                if (capacity < MAX_BUFFER_SIZE) {
+                    capacity = Math.min(capacity << 1, MAX_BUFFER_SIZE);
+                } else {
+                    throw new BufferOverflowException();
+                }
+                buf = Arrays.copyOf(buf, capacity);
+                buf[nread++] = (byte) n;
+            }
+
+            return ByteBuffer.wrap(buf, 0, nread);
+        }
+    }
+
+    /**
      * Returns the path name of the ZIP file.
      * @return the path name of the ZIP file
      */
     public String getName() {
         return name;

@@ -777,13 +1013,26 @@
     }
 
     static {
         sun.misc.SharedSecrets.setJavaUtilZipFileAccess(
             new sun.misc.JavaUtilZipFileAccess() {
+                @Override
                 public boolean startsWithLocHeader(ZipFile zip) {
                     return zip.startsWithLocHeader();
                 }
+
+                @Override
+                public ByteBuffer getByteBuffer(ZipFile zip, ZipEntry entry)
+                        throws IOException {
+                    return zip.getByteBuffer(entry);
+                }
+
+                @Override
+                public ByteBuffer fillByteBuffer(ZipFile zip, ZipEntry entry,
+                                                 ByteBuffer buffer) throws IOException {
+                    return zip.fillByteBuffer(entry, buffer);
+                }
              }
         );
     }
 
     /**
< prev index next >