< 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 >