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