1   /*
2    * %W% %E%
3    *
4    * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
5    * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6    */
7   
8   package java.util.zip;
9   
10  import java.io.InputStream;
11  import java.io.IOException;
12  import java.io.EOFException;
13  import java.io.File;
14  import java.util.Vector;
15  import java.util.Enumeration;
16  import java.util.NoSuchElementException;
17  import java.security.AccessController;
18  import sun.security.action.GetPropertyAction;
19  
20  /**
21   * This class is used to read entries from a zip file.
22   *
23   * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
24   * or method in this class will cause a {@link NullPointerException} to be
25   * thrown.
26   *
27   * @version   1.78, 07/31/06
28   * @author  David Connelly
29   */
30  public
31  class ZipFile implements ZipConstants {
32      private long jzfile;  // address of jzfile data
33      private String name;  // zip file name
34      private int total;    // total number of entries
35      private boolean closeRequested;
36  
37      private static final int STORED = ZipEntry.STORED;
38      private static final int DEFLATED = ZipEntry.DEFLATED;
39  
40      /**
41       * Mode flag to open a zip file for reading.
42       */
43      public static final int OPEN_READ = 0x1;
44  
45      /**
46       * Mode flag to open a zip file and mark it for deletion.  The file will be
47       * deleted some time between the moment that it is opened and the moment
48       * that it is closed, but its contents will remain accessible via the
49       * <tt>ZipFile</tt> object until either the close method is invoked or the
50       * virtual machine exits.
51       */
52      public static final int OPEN_DELETE = 0x4;
53  
54      static {
55      /* Zip library is loaded from System.initializeSystemClass */
56      initIDs();
57      }
58  
59      private static native void initIDs();
60  
61      private static final boolean usemmap;
62  
63      static {
64          // A system property to disable mmap use to avoid vm crash when
65          // in-use zip file is accidently overwritten by others.
66          String prop = AccessController.doPrivileged(
67              new GetPropertyAction("sun.zip.disableMemoryMapping"));
68          usemmap = (prop == null ||
69                     !(prop.length() == 0 || prop.equalsIgnoreCase("true")));
70      }
71  
72      /**
73       * Opens a zip file for reading.
74       *
75       * <p>First, if there is a security
76       * manager, its <code>checkRead</code> method
77       * is called with the <code>name</code> argument
78       * as its argument to ensure the read is allowed.
79       *
80       * @param name the name of the zip file
81       * @throws ZipException if a ZIP format error has occurred
82       * @throws IOException if an I/O error has occurred
83       * @throws SecurityException if a security manager exists and its
84       *         <code>checkRead</code> method doesn't allow read access to the file.
85       * @see SecurityManager#checkRead(java.lang.String)
86       */
87      public ZipFile(String name) throws IOException {
88      this(new File(name), OPEN_READ);
89      }
90  
91      /**
92       * Opens a new <code>ZipFile</code> to read from the specified
93       * <code>File</code> object in the specified mode.  The mode argument
94       * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
95       *
96       * <p>First, if there is a security manager, its <code>checkRead</code>
97       * method is called with the <code>name</code> argument as its argument to
98       * ensure the read is allowed.
99       *
100      * @param file the ZIP file to be opened for reading
101      * @param mode the mode in which the file is to be opened
102      * @throws ZipException if a ZIP format error has occurred
103      * @throws IOException if an I/O error has occurred
104      * @throws SecurityException if a security manager exists and
105      *         its <code>checkRead</code> method
106      *         doesn't allow read access to the file,
107      *         or its <code>checkDelete</code> method doesn't allow deleting
108      *         the file when the <tt>OPEN_DELETE</tt> flag is set.
109      * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid
110      * @see SecurityManager#checkRead(java.lang.String)
111      * @since 1.3
112      */
113     public ZipFile(File file, int mode) throws IOException {
114         if (((mode & OPEN_READ) == 0) ||
115             ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
116             throw new IllegalArgumentException("Illegal mode: 0x"+
117                                                Integer.toHexString(mode));
118         }
119         String name = file.getPath();
120     SecurityManager sm = System.getSecurityManager();
121     if (sm != null) {
122         sm.checkRead(name);
123         if ((mode & OPEN_DELETE) != 0) {
124         sm.checkDelete(name);
125         }
126     }
127         jzfile = open(name, mode, file.lastModified(), usemmap);
128 
129     this.name = name;
130     this.total = getTotal(jzfile);
131     }
132 
133     private static native long open(String name, int mode, long lastModified);
134     private static native int getTotal(long jzfile);
135 
136 
137     /**
138      * Opens a ZIP file for reading given the specified File object.
139      * @param file the ZIP file to be opened for reading
140      * @throws ZipException if a ZIP error has occurred
141      * @throws IOException if an I/O error has occurred
142      */
143     public ZipFile(File file) throws ZipException, IOException {
144     this(file, OPEN_READ);
145     }
146 
147     /**
148      * Returns the zip file entry for the specified name, or null
149      * if not found.
150      *
151      * @param name the name of the entry
152      * @return the zip file entry, or null if not found
153      * @throws IllegalStateException if the zip file has been closed
154      */
155     public ZipEntry getEntry(String name) {
156         if (name == null) {
157             throw new NullPointerException("name");
158         }
159         long jzentry = 0;
160         synchronized (this) {
161             ensureOpen();
162             jzentry = getEntry(jzfile, name, true);
163             if (jzentry != 0) {
164                 ZipEntry ze = new ZipEntry(name, jzentry);
165                 freeEntry(jzfile, jzentry);
166                 return ze;
167             }
168         }
169         return null;
170     }
171 
172     private static native long getEntry(long jzfile, String name,
173                                         boolean addSlash);
174 
175     // freeEntry releases the C jzentry struct.
176     private static native void freeEntry(long jzfile, long jzentry);
177 
178     /**
179      * Returns an input stream for reading the contents of the specified
180      * zip file entry.
181      *
182      * <p> Closing this ZIP file will, in turn, close all input
183      * streams that have been returned by invocations of this method.
184      *
185      * @param entry the zip file entry
186      * @return the input stream for reading the contents of the specified
187      * zip file entry.
188      * @throws ZipException if a ZIP format error has occurred
189      * @throws IOException if an I/O error has occurred
190      * @throws IllegalStateException if the zip file has been closed
191      */
192     public InputStream getInputStream(ZipEntry entry) throws IOException {
193     return getInputStream(entry.name);
194     }
195 
196     /**
197      * Returns an input stream for reading the contents of the specified
198      * entry, or null if the entry was not found.
199      */
200     private InputStream getInputStream(String name) throws IOException {
201     if (name == null) {
202         throw new NullPointerException("name");
203     }
204         long jzentry = 0;
205         ZipFileInputStream in = null;
206         synchronized (this) {
207             ensureOpen();
208             jzentry = getEntry(jzfile, name, false);
209             if (jzentry == 0) {
210                 return null;
211             }
212 
213         in = new ZipFileInputStream(jzentry);
214 
215         }
216         final ZipFileInputStream zfin = in;
217     switch (getMethod(jzentry)) {
218     case STORED:
219         return zfin;
220     case DEFLATED:
221         // MORE: Compute good size for inflater stream:
222         long size = getSize(jzentry) + 2; // Inflater likes a bit of slack
223             if (size > 65536) size = 8192;
224             if (size <= 0) size = 4096;
225         return new InflaterInputStream(zfin, getInflater(), (int)size) {
226                 private boolean isClosed = false;
227 
228         public void close() throws IOException {
229                     if (!isClosed) {
230                          releaseInflater(inf);
231                         this.in.close();
232                         isClosed = true;
233                     }
234         }
235         // Override fill() method to provide an extra "dummy" byte
236         // at the end of the input stream. This is required when
237         // using the "nowrap" Inflater option.
238         protected void fill() throws IOException {
239             if (eof) {
240             throw new EOFException(
241                 "Unexpected end of ZLIB input stream");
242             }
243             len = this.in.read(buf, 0, buf.length);
244             if (len == -1) {
245             buf[0] = 0;
246             len = 1;
247             eof = true;
248             }
249             inf.setInput(buf, 0, len);
250         }
251         private boolean eof;
252 
253                 public int available() throws IOException {
254                     if (isClosed)
255                         return 0;
256             long avail = zfin.size() - inf.getBytesWritten();
257             return avail > (long) Integer.MAX_VALUE ?
258             Integer.MAX_VALUE : (int) avail;
259                 }
260         };
261     default:
262         throw new ZipException("invalid compression method");
263     }
264     }
265 
266     private static native int getMethod(long jzentry);
267 
268     /*
269      * Gets an inflater from the list of available inflaters or allocates
270      * a new one.
271      */
272     private Inflater getInflater() {
273     synchronized (inflaters) {
274         int size = inflaters.size();
275         if (size > 0) {
276         Inflater inf = (Inflater)inflaters.remove(size - 1);
277         return inf;
278         } else {
279         return new Inflater(true);
280         }
281     }
282     }
283 
284     /*
285      * Releases the specified inflater to the list of available inflaters.
286      */
287     private void releaseInflater(Inflater inf) {
288     synchronized (inflaters) {
289             inf.reset();
290         inflaters.add(inf);
291     }
292     }
293 
294     // List of available Inflater objects for decompression
295     private Vector inflaters = new Vector();
296 
297     /**
298      * Returns the path name of the ZIP file.
299      * @return the path name of the ZIP file
300      */
301     public String getName() {
302         return name;
303     }
304 
305     /**
306      * Returns an enumeration of the ZIP file entries.
307      * @return an enumeration of the ZIP file entries
308      * @throws IllegalStateException if the zip file has been closed
309      */
310     public Enumeration<? extends ZipEntry> entries() {
311         ensureOpen();
312         return new Enumeration<ZipEntry>() {
313                 private int i = 0;
314                 public boolean hasMoreElements() {
315                     synchronized (ZipFile.this) {
316                         ensureOpen();
317                         return i < total;
318                     }
319                 }
320                 public ZipEntry nextElement() throws NoSuchElementException {
321                     synchronized (ZipFile.this) {
322                         ensureOpen();
323                         if (i >= total) {
324                             throw new NoSuchElementException();
325                         }
326                         long jzentry = getNextEntry(jzfile, i++);
327                         if (jzentry == 0) {
328                             String message;
329                             if (closeRequested) {
330                                 message = "ZipFile concurrently closed";
331                             } else {
332                                 message = getZipMessage(ZipFile.this.jzfile);
333                             }
334                             throw new ZipError("jzentry == 0" +
335                                                ",\n jzfile = " + ZipFile.this.jzfile +
336                                                ",\n total = " + ZipFile.this.total +
337                                                ",\n name = " + ZipFile.this.name +
338                                                ",\n i = " + i +
339                                                ",\n message = " + message
340                                 );
341                         }
342                         ZipEntry ze = new ZipEntry(jzentry);
343                         freeEntry(jzfile, jzentry);
344                         return ze;
345                     }
346                 }
347             };
348     }
349 
350     private static native long getNextEntry(long jzfile, int i);
351 
352     /**
353      * Returns the number of entries in the ZIP file.
354      * @return the number of entries in the ZIP file
355      * @throws IllegalStateException if the zip file has been closed
356      */
357     public int size() {
358         ensureOpen();
359     return total;
360     }
361 
362     /**
363      * Closes the ZIP file.
364      * <p> Closing this ZIP file will close all of the input streams
365      * previously returned by invocations of the {@link #getInputStream
366      * getInputStream} method.
367      *
368      * @throws IOException if an I/O error has occurred
369      */
370     public void close() throws IOException {
371         synchronized (this) {
372         closeRequested = true;
373 
374         if (jzfile != 0) {
375         // Close the zip file
376         long zf = this.jzfile;
377         jzfile = 0;
378 
379         close(zf);
380 
381         // Release inflaters
382         synchronized (inflaters) {
383             int size = inflaters.size();
384             for (int i = 0; i < size; i++) {
385             Inflater inf = (Inflater)inflaters.get(i);
386             inf.end();
387             }
388         }
389         }
390         }
391     }
392 
393 
394     /**
395      * Ensures that the <code>close</code> method of this ZIP file is
396      * called when there are no more references to it.
397      *
398      * <p>
399      * Since the time when GC would invoke this method is undetermined,
400      * it is strongly recommended that applications invoke the <code>close</code>
401      * method as soon they have finished accessing this <code>ZipFile</code>.
402      * This will prevent holding up system resources for an undetermined
403      * length of time.
404      *
405      * @throws IOException if an I/O error has occurred
406      * @see    java.util.zip.ZipFile#close()
407      */
408     protected void finalize() throws IOException {
409         close();
410     }
411 
412     private static native void close(long jzfile);
413 
414     private void ensureOpen() {
415     if (closeRequested) {
416         throw new IllegalStateException("zip file closed");
417     }
418 
419     if (jzfile == 0) {
420         throw new IllegalStateException("The object is not initialized.");
421     }
422     }
423 
424     private void ensureOpenOrZipException() throws IOException {
425     if (closeRequested) {
426         throw new ZipException("ZipFile closed");
427     }
428     }
429 
430     /*
431      * Inner class implementing the input stream used to read a
432      * (possibly compressed) zip file entry.
433      */
434    private class ZipFileInputStream extends InputStream {
435     protected long jzentry; // address of jzentry data
436     private   long pos; // current position within entry data
437     protected long rem; // number of remaining bytes within entry
438         protected long size;    // uncompressed size of this entry
439 
440     ZipFileInputStream(long jzentry) {
441         pos = 0;
442         rem = getCSize(jzentry);
443             size = getSize(jzentry);
444         this.jzentry = jzentry;
445     }
446 
447     public int read(byte b[], int off, int len) throws IOException {
448         if (rem == 0) {
449         return -1;
450         }
451         if (len <= 0) {
452         return 0;
453         }
454         if (len > rem) {
455         len = (int) rem;
456         }
457             synchronized (ZipFile.this) {
458         ensureOpenOrZipException();
459 
460         len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b,
461                    off, len);
462             }
463         if (len > 0) {
464         pos += len;
465         rem -= len;
466         }
467         if (rem == 0) {
468         close();
469         }
470         return len;
471     }
472 
473     public int read() throws IOException {
474         byte[] b = new byte[1];
475         if (read(b, 0, 1) == 1) {
476         return b[0] & 0xff;
477         } else {
478         return -1;
479         }
480     }
481 
482     public long skip(long n) {
483         if (n > rem)
484         n = rem;
485         pos += n;
486         rem -= n;
487         if (rem == 0) {
488         close();
489         }
490         return n;
491     }
492 
493         public int available() {
494         return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
495         }
496 
497         public long size() {
498             return size;
499         }
500 
501         public void close() {
502             rem = 0;
503             synchronized (ZipFile.this) {
504                 if (jzentry != 0 && ZipFile.this.jzfile != 0) {
505                     freeEntry(ZipFile.this.jzfile, jzentry);
506                     jzentry = 0;
507                 }
508             }
509         }
510 
511     }
512 
513 
514     private static native long open(String name, int mode, long lastModified,
515                                     boolean usemmap) throws IOException;
516 
517     private static native int read(long jzfile, long jzentry,
518                    long pos, byte[] b, int off, int len);
519 
520     private static native long getCSize(long jzentry);
521 
522     private static native long getSize(long jzentry);
523 
524     // Temporary add on for bug troubleshooting
525     private static native String getZipMessage(long jzfile);
526 }
527