1 /*
   2  * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util.zip;
  27 
  28 import java.io.Closeable;
  29 import java.io.InputStream;
  30 import java.io.IOException;
  31 import java.io.EOFException;
  32 import java.io.File;
  33 import java.nio.BufferOverflowException;
  34 import java.nio.ByteBuffer;
  35 import java.nio.ReadOnlyBufferException;
  36 import java.nio.charset.Charset;
  37 import java.nio.charset.StandardCharsets;
  38 import java.util.ArrayDeque;
  39 import java.util.Arrays;
  40 import java.util.Deque;
  41 import java.util.Enumeration;
  42 import java.util.HashMap;
  43 import java.util.Iterator;
  44 import java.util.Map;
  45 import java.util.NoSuchElementException;
  46 import java.util.Spliterator;
  47 import java.util.Spliterators;
  48 import java.util.WeakHashMap;
  49 import java.util.stream.Stream;
  50 import java.util.stream.StreamSupport;
  51 
  52 import static java.util.zip.ZipConstants64.*;
  53 
  54 /**
  55  * This class is used to read entries from a zip file.
  56  *
  57  * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
  58  * or method in this class will cause a {@link NullPointerException} to be
  59  * thrown.
  60  *
  61  * @author      David Connelly
  62  */
  63 public
  64 class ZipFile implements ZipConstants, Closeable {
  65     private long jzfile;           // address of jzfile data
  66     private final String name;     // zip file name
  67     private final int total;       // total number of entries
  68     private final boolean locsig;  // if zip file starts with LOCSIG (usually true)
  69     private volatile boolean closeRequested = false;
  70 
  71     private static final int STORED = ZipEntry.STORED;
  72     private static final int DEFLATED = ZipEntry.DEFLATED;
  73 
  74     // Max buffer size when returning bytebuffers directly
  75     private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
  76 
  77     /**
  78      * Mode flag to open a zip file for reading.
  79      */
  80     public static final int OPEN_READ = 0x1;
  81 
  82     /**
  83      * Mode flag to open a zip file and mark it for deletion.  The file will be
  84      * deleted some time between the moment that it is opened and the moment
  85      * that it is closed, but its contents will remain accessible via the
  86      * <tt>ZipFile</tt> object until either the close method is invoked or the
  87      * virtual machine exits.
  88      */
  89     public static final int OPEN_DELETE = 0x4;
  90 
  91     static {
  92         /* Zip library is loaded from System.initializeSystemClass */
  93         initIDs();
  94     }
  95 
  96     private static native void initIDs();
  97 
  98     private static final boolean usemmap;
  99 
 100     static {
 101         // A system prpperty to disable mmap use to avoid vm crash when
 102         // in-use zip file is accidently overwritten by others.
 103         String prop = sun.misc.VM.getSavedProperty("sun.zip.disableMemoryMapping");
 104         usemmap = (prop == null ||
 105                    !(prop.length() == 0 || prop.equalsIgnoreCase("true")));
 106     }
 107 
 108     /**
 109      * Opens a zip file for reading.
 110      *
 111      * <p>First, if there is a security manager, its <code>checkRead</code>
 112      * method is called with the <code>name</code> argument as its argument
 113      * to ensure the read is allowed.
 114      *
 115      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
 116      * decode the entry names and comments.
 117      *
 118      * @param name the name of the zip file
 119      * @throws ZipException if a ZIP format error has occurred
 120      * @throws IOException if an I/O error has occurred
 121      * @throws SecurityException if a security manager exists and its
 122      *         <code>checkRead</code> method doesn't allow read access to the file.
 123      *
 124      * @see SecurityManager#checkRead(java.lang.String)
 125      */
 126     public ZipFile(String name) throws IOException {
 127         this(new File(name), OPEN_READ);
 128     }
 129 
 130     /**
 131      * Opens a new <code>ZipFile</code> to read from the specified
 132      * <code>File</code> object in the specified mode.  The mode argument
 133      * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
 134      *
 135      * <p>First, if there is a security manager, its <code>checkRead</code>
 136      * method is called with the <code>name</code> argument as its argument to
 137      * ensure the read is allowed.
 138      *
 139      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
 140      * decode the entry names and comments
 141      *
 142      * @param file the ZIP file to be opened for reading
 143      * @param mode the mode in which the file is to be opened
 144      * @throws ZipException if a ZIP format error has occurred
 145      * @throws IOException if an I/O error has occurred
 146      * @throws SecurityException if a security manager exists and
 147      *         its <code>checkRead</code> method
 148      *         doesn't allow read access to the file,
 149      *         or its <code>checkDelete</code> method doesn't allow deleting
 150      *         the file when the <tt>OPEN_DELETE</tt> flag is set.
 151      * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid
 152      * @see SecurityManager#checkRead(java.lang.String)
 153      * @since 1.3
 154      */
 155     public ZipFile(File file, int mode) throws IOException {
 156         this(file, mode, StandardCharsets.UTF_8);
 157     }
 158 
 159     /**
 160      * Opens a ZIP file for reading given the specified File object.
 161      *
 162      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used to
 163      * decode the entry names and comments.
 164      *
 165      * @param file the ZIP file to be opened for reading
 166      * @throws ZipException if a ZIP format error has occurred
 167      * @throws IOException if an I/O error has occurred
 168      */
 169     public ZipFile(File file) throws ZipException, IOException {
 170         this(file, OPEN_READ);
 171     }
 172 
 173     private ZipCoder zc;
 174 
 175     /**
 176      * Opens a new <code>ZipFile</code> to read from the specified
 177      * <code>File</code> object in the specified mode.  The mode argument
 178      * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
 179      *
 180      * <p>First, if there is a security manager, its <code>checkRead</code>
 181      * method is called with the <code>name</code> argument as its argument to
 182      * ensure the read is allowed.
 183      *
 184      * @param file the ZIP file to be opened for reading
 185      * @param mode the mode in which the file is to be opened
 186      * @param charset
 187      *        the {@linkplain java.nio.charset.Charset charset} to
 188      *        be used to decode the ZIP entry name and comment that are not
 189      *        encoded by using UTF-8 encoding (indicated by entry's general
 190      *        purpose flag).
 191      *
 192      * @throws ZipException if a ZIP format error has occurred
 193      * @throws IOException if an I/O error has occurred
 194      *
 195      * @throws SecurityException
 196      *         if a security manager exists and its <code>checkRead</code>
 197      *         method doesn't allow read access to the file,or its
 198      *         <code>checkDelete</code> method doesn't allow deleting the
 199      *         file when the <tt>OPEN_DELETE</tt> flag is set
 200      *
 201      * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid
 202      *
 203      * @see SecurityManager#checkRead(java.lang.String)
 204      *
 205      * @since 1.7
 206      */
 207     public ZipFile(File file, int mode, Charset charset) throws IOException
 208     {
 209         if (((mode & OPEN_READ) == 0) ||
 210             ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
 211             throw new IllegalArgumentException("Illegal mode: 0x"+
 212                                                Integer.toHexString(mode));
 213         }
 214         String name = file.getPath();
 215         SecurityManager sm = System.getSecurityManager();
 216         if (sm != null) {
 217             sm.checkRead(name);
 218             if ((mode & OPEN_DELETE) != 0) {
 219                 sm.checkDelete(name);
 220             }
 221         }
 222         if (charset == null)
 223             throw new NullPointerException("charset is null");
 224         this.zc = ZipCoder.get(charset);
 225         long t0 = System.nanoTime();
 226         jzfile = open(name, mode, file.lastModified(), usemmap);
 227         sun.misc.PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0);
 228         sun.misc.PerfCounter.getZipFileCount().increment();
 229         this.name = name;
 230         this.total = getTotal(jzfile);
 231         this.locsig = startsWithLOC(jzfile);
 232     }
 233 
 234     /**
 235      * Opens a zip file for reading.
 236      *
 237      * <p>First, if there is a security manager, its <code>checkRead</code>
 238      * method is called with the <code>name</code> argument as its argument
 239      * to ensure the read is allowed.
 240      *
 241      * @param name the name of the zip file
 242      * @param charset
 243      *        the {@linkplain java.nio.charset.Charset charset} to
 244      *        be used to decode the ZIP entry name and comment that are not
 245      *        encoded by using UTF-8 encoding (indicated by entry's general
 246      *        purpose flag).
 247      *
 248      * @throws ZipException if a ZIP format error has occurred
 249      * @throws IOException if an I/O error has occurred
 250      * @throws SecurityException
 251      *         if a security manager exists and its <code>checkRead</code>
 252      *         method doesn't allow read access to the file
 253      *
 254      * @see SecurityManager#checkRead(java.lang.String)
 255      *
 256      * @since 1.7
 257      */
 258     public ZipFile(String name, Charset charset) throws IOException
 259     {
 260         this(new File(name), OPEN_READ, charset);
 261     }
 262 
 263     /**
 264      * Opens a ZIP file for reading given the specified File object.
 265      * @param file the ZIP file to be opened for reading
 266      * @param charset
 267      *        The {@linkplain java.nio.charset.Charset charset} to be
 268      *        used to decode the ZIP entry name and comment (ignored if
 269      *        the <a href="package-summary.html#lang_encoding"> language
 270      *        encoding bit</a> of the ZIP entry's general purpose bit
 271      *        flag is set).
 272      *
 273      * @throws ZipException if a ZIP format error has occurred
 274      * @throws IOException if an I/O error has occurred
 275      *
 276      * @since 1.7
 277      */
 278     public ZipFile(File file, Charset charset) throws IOException
 279     {
 280         this(file, OPEN_READ, charset);
 281     }
 282 
 283     /**
 284      * Returns the zip file comment, or null if none.
 285      *
 286      * @return the comment string for the zip file, or null if none
 287      *
 288      * @throws IllegalStateException if the zip file has been closed
 289      *
 290      * Since 1.7
 291      */
 292     public String getComment() {
 293         synchronized (this) {
 294             ensureOpen();
 295             byte[] bcomm = getCommentBytes(jzfile);
 296             if (bcomm == null)
 297                 return null;
 298             return zc.toString(bcomm, bcomm.length);
 299         }
 300     }
 301 
 302     /**
 303      * Returns the zip file entry for the specified name, or null
 304      * if not found.
 305      *
 306      * @param name the name of the entry
 307      * @return the zip file entry, or null if not found
 308      * @throws IllegalStateException if the zip file has been closed
 309      */
 310     public ZipEntry getEntry(String name) {
 311         if (name == null) {
 312             throw new NullPointerException("name");
 313         }
 314         long jzentry = 0;
 315         synchronized (this) {
 316             ensureOpen();
 317             jzentry = getEntry(jzfile, zc.getBytes(name), true);
 318             if (jzentry != 0) {
 319                 ZipEntry ze = getZipEntry(name, jzentry);
 320                 freeEntry(jzfile, jzentry);
 321                 return ze;
 322             }
 323         }
 324         return null;
 325     }
 326 
 327     private static native long getEntry(long jzfile, byte[] name,
 328                                         boolean addSlash);
 329 
 330     // freeEntry releases the C jzentry struct.
 331     private static native void freeEntry(long jzfile, long jzentry);
 332 
 333     // the outstanding inputstreams that need to be closed,
 334     // mapped to the inflater objects they use.
 335     private final Map<InputStream, Inflater> streams = new WeakHashMap<>();
 336 
 337     /**
 338      * Returns an input stream for reading the contents of the specified
 339      * zip file entry.
 340      *
 341      * <p> Closing this ZIP file will, in turn, close all input
 342      * streams that have been returned by invocations of this method.
 343      *
 344      * @param entry the zip file entry
 345      * @return the input stream for reading the contents of the specified
 346      * zip file entry.
 347      * @throws ZipException if a ZIP format error has occurred
 348      * @throws IOException if an I/O error has occurred
 349      * @throws IllegalStateException if the zip file has been closed
 350      */
 351     public InputStream getInputStream(ZipEntry entry) throws IOException {
 352         if (entry == null) {
 353             throw new NullPointerException("entry");
 354         }
 355         long jzentry = 0;
 356         ZipFileInputStream in = null;
 357         synchronized (this) {
 358             ensureOpen();
 359             if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
 360                 jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false);
 361             } else {
 362                 jzentry = getEntry(jzfile, zc.getBytes(entry.name), false);
 363             }
 364             if (jzentry == 0) {
 365                 return null;
 366             }
 367             in = new ZipFileInputStream(jzentry);
 368 
 369             switch (getEntryMethod(jzentry)) {
 370             case STORED:
 371                 synchronized (streams) {
 372                     streams.put(in, null);
 373                 }
 374                 return in;
 375             case DEFLATED:
 376                 // MORE: Compute good size for inflater stream:
 377                 long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack
 378                 if (size > 65536) size = 8192;
 379                 if (size <= 0) size = 4096;
 380                 Inflater inf = getInflater();
 381                 InputStream is =
 382                     new ZipFileInflaterInputStream(in, inf, (int)size);
 383                 synchronized (streams) {
 384                     streams.put(is, inf);
 385                 }
 386                 return is;
 387             default:
 388                 throw new ZipException("invalid compression method");
 389             }
 390         }
 391     }
 392 
 393     private class ZipFileInflaterInputStream extends InflaterInputStream {
 394         private volatile boolean closeRequested = false;
 395         private boolean eof = false;
 396         private final ZipFileInputStream zfin;
 397 
 398         ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf,
 399                 int size) {
 400             super(zfin, inf, size);
 401             this.zfin = zfin;
 402         }
 403 
 404         public void close() throws IOException {
 405             if (closeRequested)
 406                 return;
 407             closeRequested = true;
 408 
 409             super.close();
 410             Inflater inf;
 411             synchronized (streams) {
 412                 inf = streams.remove(this);
 413             }
 414             if (inf != null) {
 415                 releaseInflater(inf);
 416             }
 417         }
 418 
 419         // Override fill() method to provide an extra "dummy" byte
 420         // at the end of the input stream. This is required when
 421         // using the "nowrap" Inflater option.
 422         protected void fill() throws IOException {
 423             if (eof) {
 424                 throw new EOFException("Unexpected end of ZLIB input stream");
 425             }
 426             len = in.read(buf, 0, buf.length);
 427             if (len == -1) {
 428                 buf[0] = 0;
 429                 len = 1;
 430                 eof = true;
 431             }
 432             inf.setInput(buf, 0, len);
 433         }
 434 
 435         public int available() throws IOException {
 436             if (closeRequested)
 437                 return 0;
 438             long avail = zfin.size() - inf.getBytesWritten();
 439             return (avail > (long) Integer.MAX_VALUE ?
 440                     Integer.MAX_VALUE : (int) avail);
 441         }
 442 
 443         protected void finalize() throws Throwable {
 444             close();
 445         }
 446     }
 447 
 448     /*
 449      * Gets an inflater from the list of available inflaters or allocates
 450      * a new one.
 451      */
 452     private Inflater getInflater() {
 453         Inflater inf;
 454         synchronized (inflaterCache) {
 455             while (null != (inf = inflaterCache.poll())) {
 456                 if (false == inf.ended()) {
 457                     return inf;
 458                 }
 459             }
 460         }
 461         return new Inflater(true);
 462     }
 463 
 464     /*
 465      * Releases the specified inflater to the list of available inflaters.
 466      */
 467     private void releaseInflater(Inflater inf) {
 468         if (false == inf.ended()) {
 469             inf.reset();
 470             synchronized (inflaterCache) {
 471                 inflaterCache.add(inf);
 472             }
 473         }
 474     }
 475 
 476     // List of available Inflater objects for decompression
 477     private Deque<Inflater> inflaterCache = new ArrayDeque<>();
 478 
 479     /**
 480      * Uncompress the zip entry into a new ByteBuffer.
 481      *
 482      * The returned ByteBuffer will have position 0 and limit set to the length
 483      * of the uncompressed bytes of the zip entry.
 484      *
 485      * This method can only read entries smaller than 2GB, for larger entries
 486      * use getInputStream.
 487      *
 488      * @param entry the zip file entry
 489      *
 490      * @return the ByteBuffer with the contents of the specified zip file entry.
 491      *
 492      * @throws ZipException if a ZIP format error has occurred
 493      * @throws IOException if an I/O error has occurred
 494      * @throws IllegalStateException if the zip file has been closed
 495      * @throws BufferOverflowException if the zip entry is larger than the
 496      * supplied buffer
 497      * @throws ReadOnlyBufferException if the supplied buffer is read-only
 498      */
 499     private ByteBuffer getByteBuffer(ZipEntry entry) throws IOException {
 500         if (entry == null) {
 501             throw new NullPointerException("entry");
 502         }
 503 
 504         long size = entry.getSize();
 505 
 506         if (size < 0) {
 507             // Uknown size of zip entry (-1)
 508             return getByteBufferUnknownSize(entry);
 509         }
 510 
 511         if (size > MAX_BUFFER_SIZE) {
 512             throw new BufferOverflowException();
 513         }
 514 
 515         return fillByteBuffer(entry, ByteBuffer.allocate((int) size)).flip();
 516     }
 517 
 518     /**
 519      * Fill a ByteBuffer with the uncompressed contents of the specified zip
 520      * file entry.
 521      *
 522      * This method transfers the zip entry bytes into this buffer. If there are
 523      * more bytes to be written than remain in this buffer, that is, if
 524      * ZipEntry.getSize() > remaining(), then no bytes are transferred and a
 525      * BufferOverflowException is thrown.
 526      *
 527      * Otherwise, this method copies length bytes from the given array into this
 528      * buffer, starting at the given offset in the array and at the current
 529      * position of this buffer. The position of this buffer is then incremented
 530      * by length.
 531      *
 532      * This method can only read entries smaller than 2GB, for larger entries
 533      * use getInputStream.
 534      *
 535      * @param entry the zip file entry
 536      * @param buffer ByteBuffer to write the zip entry to
 537      *
 538      * @return the ByteBuffer with the contents of the specified zip file entry.
 539      *
 540      * @throws ZipException if a ZIP format error has occurred
 541      * @throws IOException if an I/O error has occurred
 542      * @throws IllegalStateException if the zip file has been closed
 543      * @throws BufferOverflowException if the zip entry is larger than the
 544      * supplied buffer
 545      * @throws ReadOnlyBufferException if the supplied buffer is read-only
 546      */
 547     private ByteBuffer fillByteBuffer(ZipEntry entry, ByteBuffer buffer) throws IOException {
 548         if (entry == null) {
 549             throw new NullPointerException("entry");
 550         }
 551         if (buffer.isReadOnly()) {
 552             throw new ReadOnlyBufferException();
 553         }
 554 
 555         long jzentry = 0;
 556         try {
 557             long csize;
 558             long size;
 559             int cmethod;
 560 
 561             synchronized (this) {
 562                 ensureOpen();
 563                 if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
 564                     jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false);
 565                 } else {
 566                     jzentry = getEntry(jzfile, zc.getBytes(entry.name), false);
 567                 }
 568                 if (jzentry == 0) {
 569                     return null;
 570                 }
 571 
 572                 csize = getEntryCSize(jzentry);
 573                 size = getEntrySize(jzentry);
 574                 cmethod = getEntryMethod(jzentry);
 575             }
 576 
 577             if (csize < 0 || size < 0) {
 578                 // Uknown size of zip entry (-1)
 579                 ByteBuffer tempBuffer = getByteBufferUnknownSize(entry);
 580                 buffer.put(tempBuffer); // Throws BufferOverflowException if too large
 581                 return buffer;
 582             }
 583             if (csize > MAX_BUFFER_SIZE || size > MAX_BUFFER_SIZE) {
 584                 throw new BufferOverflowException();
 585             }
 586 
 587             switch (cmethod) {
 588                 case STORED:
 589                     if (csize > buffer.remaining()) {
 590                         throw new BufferOverflowException();
 591                     }
 592                     return getByteBufferStored(jzentry, (int) csize, buffer);
 593                 case DEFLATED:
 594                     if (size > buffer.remaining()) {
 595                         throw new BufferOverflowException();
 596                     }
 597 
 598                     byte[] cbytes = new byte[(int) csize];
 599                     readCompressedBytes(jzentry, (int) csize, cbytes, 0);
 600 
 601                     Inflater inf = getInflater();
 602                     try {
 603                         inf.setInput(cbytes);
 604                         if (buffer.hasArray()) {
 605                             inf.inflate(buffer.array(), buffer.arrayOffset(), buffer.remaining());
 606                             buffer.position(buffer.position() + (int) size);
 607                         } else {
 608                             byte[] bytes = new byte[(int) size];
 609                             inf.inflate(bytes);
 610                             buffer.put(bytes);
 611                         }
 612                     } catch (DataFormatException e) {
 613                         String s = e.getMessage();
 614                         throw new ZipException(s != null ? s : "Invalid ZLIB data format");
 615                     } finally {
 616                         releaseInflater(inf);
 617                     }
 618                     return buffer;
 619                 default:
 620                     throw new ZipException("invalid compression method");
 621 
 622             }
 623         } finally {
 624             if (jzentry != 0) {
 625                 synchronized (this) {
 626                     if (jzfile != 0) {
 627                         freeEntry(jzfile, jzentry);
 628                     }
 629                 }
 630             }
 631         }
 632     }
 633 
 634     private ByteBuffer getByteBufferStored(long jzentry, int csize, ByteBuffer buffer)
 635             throws IOException {
 636         assert csize >= buffer.remaining();
 637         if (buffer.hasArray()) {
 638             int nread = readCompressedBytes(jzentry, csize, buffer.array(),
 639                                             buffer.arrayOffset());
 640             buffer.position(buffer.position() + nread);
 641         } else {
 642             byte[] cbytes = new byte[csize];
 643             readCompressedBytes(jzentry, csize, cbytes, 0);
 644             buffer.put(cbytes);
 645         }
 646         return buffer;
 647     }
 648 
 649     private int readCompressedBytes(long jzentry, int csize, byte[] buf, int off)
 650             throws IOException {
 651         assert csize >= buf.length - off;
 652         synchronized (this) {
 653             ensureOpenOrZipException();
 654             int n = 0;
 655             int nread = 0;
 656             while (nread < csize
 657                    && (n = read(jzfile, jzentry, nread, buf, off + nread, csize - nread)) > 0) {
 658                 nread += n;
 659             }
 660             return nread;
 661         }
 662     }
 663 
 664     private ByteBuffer getByteBufferUnknownSize(ZipEntry entry) throws IOException {
 665         final int BUFFER_SIZE = 8192;
 666         InputStream in = getInputStream(entry);
 667 
 668         if (in == null) {
 669             // not found
 670             return null;
 671         }
 672 
 673         try (in) {
 674             int capacity = in.available();
 675             if (capacity == 0) {
 676                 capacity = BUFFER_SIZE;
 677             }
 678 
 679             byte[] buf = new byte[capacity];
 680             int nread = 0;
 681             int n;
 682             for (;;) {
 683                 // read to EOF
 684                 while ((n = in.read(buf, nread, capacity - nread)) > 0) {
 685                     nread += n;
 686                 }
 687 
 688                 // if last call to source.read() returned -1, we are done
 689                 // otherwise, try to read one more byte; if that failed we're done too
 690                 if (n < 0 || (n = in.read()) < 0) {
 691                     break;
 692                 }
 693 
 694                 // one more byte was read; need to allocate a larger buffer
 695                 if (capacity < MAX_BUFFER_SIZE) {
 696                     capacity = Math.min(capacity << 1, MAX_BUFFER_SIZE);
 697                 } else {
 698                     throw new BufferOverflowException();
 699                 }
 700                 buf = Arrays.copyOf(buf, capacity);
 701                 buf[nread++] = (byte) n;
 702             }
 703 
 704             return ByteBuffer.wrap(buf, 0, nread);
 705         }
 706     }
 707 
 708     /**
 709      * Returns the path name of the ZIP file.
 710      * @return the path name of the ZIP file
 711      */
 712     public String getName() {
 713         return name;
 714     }
 715 
 716     private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> {
 717         private int i = 0;
 718 
 719         public ZipEntryIterator() {
 720             ensureOpen();
 721         }
 722 
 723         public boolean hasMoreElements() {
 724             return hasNext();
 725         }
 726 
 727         public boolean hasNext() {
 728             synchronized (ZipFile.this) {
 729                 ensureOpen();
 730                 return i < total;
 731             }
 732         }
 733 
 734         public ZipEntry nextElement() {
 735             return next();
 736         }
 737 
 738         public ZipEntry next() {
 739             synchronized (ZipFile.this) {
 740                 ensureOpen();
 741                 if (i >= total) {
 742                     throw new NoSuchElementException();
 743                 }
 744                 long jzentry = getNextEntry(jzfile, i++);
 745                 if (jzentry == 0) {
 746                     String message;
 747                     if (closeRequested) {
 748                         message = "ZipFile concurrently closed";
 749                     } else {
 750                         message = getZipMessage(ZipFile.this.jzfile);
 751                     }
 752                     throw new ZipError("jzentry == 0" +
 753                                        ",\n jzfile = " + ZipFile.this.jzfile +
 754                                        ",\n total = " + ZipFile.this.total +
 755                                        ",\n name = " + ZipFile.this.name +
 756                                        ",\n i = " + i +
 757                                        ",\n message = " + message
 758                         );
 759                 }
 760                 ZipEntry ze = getZipEntry(null, jzentry);
 761                 freeEntry(jzfile, jzentry);
 762                 return ze;
 763             }
 764         }
 765     }
 766 
 767     /**
 768      * Returns an enumeration of the ZIP file entries.
 769      * @return an enumeration of the ZIP file entries
 770      * @throws IllegalStateException if the zip file has been closed
 771      */
 772     public Enumeration<? extends ZipEntry> entries() {
 773         return new ZipEntryIterator();
 774     }
 775 
 776     /**
 777      * Return an ordered {@code Stream} over the ZIP file entries.
 778      * Entries appear in the {@code Stream} in the order they appear in
 779      * the central directory of the ZIP file.
 780      *
 781      * @return an ordered {@code Stream} of entries in this ZIP file
 782      * @throws IllegalStateException if the zip file has been closed
 783      * @since 1.8
 784      */
 785     public Stream<? extends ZipEntry> stream() {
 786         return StreamSupport.stream(Spliterators.spliterator(
 787                 new ZipEntryIterator(), size(),
 788                 Spliterator.ORDERED | Spliterator.DISTINCT |
 789                         Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
 790     }
 791 
 792     private ZipEntry getZipEntry(String name, long jzentry) {
 793         ZipEntry e = new ZipEntry();
 794         e.flag = getEntryFlag(jzentry);  // get the flag first
 795         if (name != null) {
 796             e.name = name;
 797         } else {
 798             byte[] bname = getEntryBytes(jzentry, JZENTRY_NAME);
 799             if (!zc.isUTF8() && (e.flag & EFS) != 0) {
 800                 e.name = zc.toStringUTF8(bname, bname.length);
 801             } else {
 802                 e.name = zc.toString(bname, bname.length);
 803             }
 804         }
 805         e.xdostime = getEntryTime(jzentry);
 806         e.crc = getEntryCrc(jzentry);
 807         e.size = getEntrySize(jzentry);
 808         e.csize = getEntryCSize(jzentry);
 809         e.method = getEntryMethod(jzentry);
 810         e.setExtra0(getEntryBytes(jzentry, JZENTRY_EXTRA), false);
 811         byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT);
 812         if (bcomm == null) {
 813             e.comment = null;
 814         } else {
 815             if (!zc.isUTF8() && (e.flag & EFS) != 0) {
 816                 e.comment = zc.toStringUTF8(bcomm, bcomm.length);
 817             } else {
 818                 e.comment = zc.toString(bcomm, bcomm.length);
 819             }
 820         }
 821         return e;
 822     }
 823 
 824     private static native long getNextEntry(long jzfile, int i);
 825 
 826     /**
 827      * Returns the number of entries in the ZIP file.
 828      * @return the number of entries in the ZIP file
 829      * @throws IllegalStateException if the zip file has been closed
 830      */
 831     public int size() {
 832         ensureOpen();
 833         return total;
 834     }
 835 
 836     /**
 837      * Closes the ZIP file.
 838      * <p> Closing this ZIP file will close all of the input streams
 839      * previously returned by invocations of the {@link #getInputStream
 840      * getInputStream} method.
 841      *
 842      * @throws IOException if an I/O error has occurred
 843      */
 844     public void close() throws IOException {
 845         if (closeRequested)
 846             return;
 847         closeRequested = true;
 848 
 849         synchronized (this) {
 850             // Close streams, release their inflaters
 851             synchronized (streams) {
 852                 if (false == streams.isEmpty()) {
 853                     Map<InputStream, Inflater> copy = new HashMap<>(streams);
 854                     streams.clear();
 855                     for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) {
 856                         e.getKey().close();
 857                         Inflater inf = e.getValue();
 858                         if (inf != null) {
 859                             inf.end();
 860                         }
 861                     }
 862                 }
 863             }
 864 
 865             // Release cached inflaters
 866             Inflater inf;
 867             synchronized (inflaterCache) {
 868                 while (null != (inf = inflaterCache.poll())) {
 869                     inf.end();
 870                 }
 871             }
 872 
 873             if (jzfile != 0) {
 874                 // Close the zip file
 875                 long zf = this.jzfile;
 876                 jzfile = 0;
 877 
 878                 close(zf);
 879             }
 880         }
 881     }
 882 
 883     /**
 884      * Ensures that the system resources held by this ZipFile object are
 885      * released when there are no more references to it.
 886      *
 887      * <p>
 888      * Since the time when GC would invoke this method is undetermined,
 889      * it is strongly recommended that applications invoke the <code>close</code>
 890      * method as soon they have finished accessing this <code>ZipFile</code>.
 891      * This will prevent holding up system resources for an undetermined
 892      * length of time.
 893      *
 894      * @throws IOException if an I/O error has occurred
 895      * @see    java.util.zip.ZipFile#close()
 896      */
 897     protected void finalize() throws IOException {
 898         close();
 899     }
 900 
 901     private static native void close(long jzfile);
 902 
 903     private void ensureOpen() {
 904         if (closeRequested) {
 905             throw new IllegalStateException("zip file closed");
 906         }
 907 
 908         if (jzfile == 0) {
 909             throw new IllegalStateException("The object is not initialized.");
 910         }
 911     }
 912 
 913     private void ensureOpenOrZipException() throws IOException {
 914         if (closeRequested) {
 915             throw new ZipException("ZipFile closed");
 916         }
 917     }
 918 
 919     /*
 920      * Inner class implementing the input stream used to read a
 921      * (possibly compressed) zip file entry.
 922      */
 923    private class ZipFileInputStream extends InputStream {
 924         private volatile boolean closeRequested = false;
 925         protected long jzentry; // address of jzentry data
 926         private   long pos;     // current position within entry data
 927         protected long rem;     // number of remaining bytes within entry
 928         protected long size;    // uncompressed size of this entry
 929 
 930         ZipFileInputStream(long jzentry) {
 931             pos = 0;
 932             rem = getEntryCSize(jzentry);
 933             size = getEntrySize(jzentry);
 934             this.jzentry = jzentry;
 935         }
 936 
 937         public int read(byte b[], int off, int len) throws IOException {
 938             synchronized (ZipFile.this) {
 939                 long rem = this.rem;
 940                 long pos = this.pos;
 941                 if (rem == 0) {
 942                     return -1;
 943                 }
 944                 if (len <= 0) {
 945                     return 0;
 946                 }
 947                 if (len > rem) {
 948                     len = (int) rem;
 949                 }
 950 
 951                 ensureOpenOrZipException();
 952                 len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b,
 953                                    off, len);
 954                 if (len > 0) {
 955                     this.pos = (pos + len);
 956                     this.rem = (rem - len);
 957                 }
 958             }
 959             if (rem == 0) {
 960                 close();
 961             }
 962             return len;
 963         }
 964 
 965         public int read() throws IOException {
 966             byte[] b = new byte[1];
 967             if (read(b, 0, 1) == 1) {
 968                 return b[0] & 0xff;
 969             } else {
 970                 return -1;
 971             }
 972         }
 973 
 974         public long skip(long n) {
 975             if (n > rem)
 976                 n = rem;
 977             pos += n;
 978             rem -= n;
 979             if (rem == 0) {
 980                 close();
 981             }
 982             return n;
 983         }
 984 
 985         public int available() {
 986             return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
 987         }
 988 
 989         public long size() {
 990             return size;
 991         }
 992 
 993         public void close() {
 994             if (closeRequested)
 995                 return;
 996             closeRequested = true;
 997 
 998             rem = 0;
 999             synchronized (ZipFile.this) {
1000                 if (jzentry != 0 && ZipFile.this.jzfile != 0) {
1001                     freeEntry(ZipFile.this.jzfile, jzentry);
1002                     jzentry = 0;
1003                 }
1004             }
1005             synchronized (streams) {
1006                 streams.remove(this);
1007             }
1008         }
1009 
1010         protected void finalize() {
1011             close();
1012         }
1013     }
1014 
1015     static {
1016         sun.misc.SharedSecrets.setJavaUtilZipFileAccess(
1017             new sun.misc.JavaUtilZipFileAccess() {
1018                 @Override
1019                 public boolean startsWithLocHeader(ZipFile zip) {
1020                     return zip.startsWithLocHeader();
1021                 }
1022 
1023                 @Override
1024                 public ByteBuffer getByteBuffer(ZipFile zip, ZipEntry entry)
1025                         throws IOException {
1026                     return zip.getByteBuffer(entry);
1027                 }
1028 
1029                 @Override
1030                 public ByteBuffer fillByteBuffer(ZipFile zip, ZipEntry entry,
1031                                                  ByteBuffer buffer) throws IOException {
1032                     return zip.fillByteBuffer(entry, buffer);
1033                 }
1034              }
1035         );
1036     }
1037 
1038     /**
1039      * Returns {@code true} if, and only if, the zip file begins with {@code
1040      * LOCSIG}.
1041      */
1042     private boolean startsWithLocHeader() {
1043         return locsig;
1044     }
1045 
1046     private static native long open(String name, int mode, long lastModified,
1047                                     boolean usemmap) throws IOException;
1048     private static native int getTotal(long jzfile);
1049     private static native boolean startsWithLOC(long jzfile);
1050     private static native int read(long jzfile, long jzentry,
1051                                    long pos, byte[] b, int off, int len);
1052 
1053     // access to the native zentry object
1054     private static native long getEntryTime(long jzentry);
1055     private static native long getEntryCrc(long jzentry);
1056     private static native long getEntryCSize(long jzentry);
1057     private static native long getEntrySize(long jzentry);
1058     private static native int getEntryMethod(long jzentry);
1059     private static native int getEntryFlag(long jzentry);
1060     private static native byte[] getCommentBytes(long jzfile);
1061 
1062     private static final int JZENTRY_NAME = 0;
1063     private static final int JZENTRY_EXTRA = 1;
1064     private static final int JZENTRY_COMMENT = 2;
1065     private static native byte[] getEntryBytes(long jzentry, int type);
1066 
1067     private static native String getZipMessage(long jzfile);
1068 }