1. /*
  2. * @(#)ZipFile.java 1.67 04/05/05
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.util.zip;
  8. import java.io.InputStream;
  9. import java.io.IOException;
  10. import java.io.EOFException;
  11. import java.io.File;
  12. import java.util.Vector;
  13. import java.util.Enumeration;
  14. import java.util.NoSuchElementException;
  15. import java.security.AccessController;
  16. import java.security.PrivilegedAction;
  17. import java.nio.ByteBuffer;
  18. import java.nio.MappedByteBuffer;
  19. import sun.nio.ByteBuffered;
  20. import java.lang.reflect.*;
  21. /**
  22. * This class is used to read entries from a zip file.
  23. *
  24. * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
  25. * or method in this class will cause a {@link NullPointerException} to be
  26. * thrown.
  27. *
  28. * @version 1.67, 05/05/04
  29. * @author David Connelly
  30. */
  31. public
  32. class ZipFile implements ZipConstants {
  33. private long jzfile; // address of jzfile data
  34. private String name; // zip file name
  35. private int total; // total number of entries
  36. private MappedByteBuffer mappedBuffer; // if ZipFile.c uses file mapping.
  37. private ZipCloser closer; // cleans up after mappedBuffer.
  38. private boolean mbUsed; // if caller used mappedBuffer
  39. private boolean closeRequested;
  40. private static final int STORED = ZipEntry.STORED;
  41. private static final int DEFLATED = ZipEntry.DEFLATED;
  42. /**
  43. * Mode flag to open a zip file for reading.
  44. */
  45. public static final int OPEN_READ = 0x1;
  46. /**
  47. * Mode flag to open a zip file and mark it for deletion. The file will be
  48. * deleted some time between the moment that it is opened and the moment
  49. * that it is closed, but its contents will remain accessible via the
  50. * <tt>ZipFile</tt> object until either the close method is invoked or the
  51. * virtual machine exits.
  52. */
  53. public static final int OPEN_DELETE = 0x4;
  54. static {
  55. AccessController.doPrivileged(
  56. new sun.security.action.LoadLibraryAction("zip"));
  57. initIDs();
  58. }
  59. private static native void initIDs();
  60. /**
  61. * Opens a zip file for reading.
  62. *
  63. * <p>First, if there is a security
  64. * manager, its <code>checkRead</code> method
  65. * is called with the <code>name</code> argument
  66. * as its argument to ensure the read is allowed.
  67. *
  68. * @param name the name of the zip file
  69. * @throws ZipException if a ZIP format error has occurred
  70. * @throws IOException if an I/O error has occurred
  71. * @throws SecurityException if a security manager exists and its
  72. * <code>checkRead</code> method doesn't allow read access to the file.
  73. * @see SecurityManager#checkRead(java.lang.String)
  74. */
  75. public ZipFile(String name) throws IOException {
  76. this(new File(name), OPEN_READ);
  77. }
  78. /**
  79. * Handles cleanup after mappedBuffer is no longer referenced.
  80. *
  81. * The DirectByteBuffer code creates a phantom reference to mappedBuffer
  82. * that will call ZipCloser.run() when mappedBuffer is no longer
  83. * (strongly) referenced.
  84. * If it was safe to do so, ZipFile.close() (and finalize()) will have
  85. * already cleaned up.
  86. *
  87. * Note: since ZipFile references MappedByteBuffer, we can be sure that
  88. * the ZipFile has already been finalized by the time ZipCloser.run()
  89. * is called.
  90. */
  91. private static class ZipCloser
  92. implements Runnable
  93. {
  94. private long mappedFileID;
  95. private ZipCloser(long jzFile) {
  96. mappedFileID = jzFile;
  97. }
  98. public synchronized void setClosed() {
  99. mappedFileID = 0;
  100. }
  101. public synchronized void run() {
  102. if (mappedFileID != 0) {
  103. ZipFile.close(mappedFileID);
  104. mappedFileID = 0;
  105. }
  106. }
  107. } /* ZipCloser */
  108. private static Constructor directByteBufferConstructor = null;
  109. private static void initDBBConstructor() {
  110. AccessController.doPrivileged(new PrivilegedAction() {
  111. public Object run() {
  112. try {
  113. Class th = Class.forName("java.nio.DirectByteBuffer");
  114. directByteBufferConstructor
  115. = th.getDeclaredConstructor(
  116. new Class[] { int.class,
  117. long.class,
  118. Runnable.class });
  119. directByteBufferConstructor.setAccessible(true);
  120. } catch (ClassNotFoundException x) {
  121. throw new InternalError();
  122. } catch (NoSuchMethodException x) {
  123. throw new InternalError();
  124. } catch (IllegalArgumentException x) {
  125. throw new InternalError();
  126. } catch (ClassCastException x) {
  127. throw new InternalError();
  128. }
  129. return null;
  130. }});
  131. }
  132. private static MappedByteBuffer newMappedByteBuffer(int size, long addr,
  133. Runnable unmapper)
  134. {
  135. MappedByteBuffer dbb;
  136. if (directByteBufferConstructor == null)
  137. initDBBConstructor();
  138. try {
  139. dbb = (MappedByteBuffer)directByteBufferConstructor.newInstance(
  140. new Object[] { new Integer(size),
  141. new Long(addr),
  142. unmapper });
  143. } catch (InstantiationException e) {
  144. throw new InternalError();
  145. } catch (IllegalAccessException e) {
  146. throw new InternalError();
  147. } catch (InvocationTargetException e) {
  148. throw new InternalError();
  149. }
  150. return dbb;
  151. }
  152. /**
  153. * Opens a new <code>ZipFile</code> to read from the specified
  154. * <code>File</code> object in the specified mode. The mode argument
  155. * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
  156. *
  157. * <p>First, if there is a security manager, its <code>checkRead</code>
  158. * method is called with the <code>name</code> argument as its argument to
  159. * ensure the read is allowed.
  160. *
  161. * @param file the ZIP file to be opened for reading
  162. * @param mode the mode in which the file is to be opened
  163. * @throws ZipException if a ZIP format error has occurred
  164. * @throws IOException if an I/O error has occurred
  165. * @throws SecurityException if a security manager exists and
  166. * its <code>checkRead</code> method
  167. * doesn't allow read access to the file,
  168. * or its <code>checkDelete</code> method doesn't allow deleting
  169. * the file when the <tt>OPEN_DELETE</tt> flag is set.
  170. * @throws IllegalArgumentException if the <tt>mode</tt> argument is invalid
  171. * @see SecurityManager#checkRead(java.lang.String)
  172. */
  173. public ZipFile(File file, int mode) throws IOException {
  174. if (((mode & OPEN_READ) == 0) ||
  175. ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
  176. throw new IllegalArgumentException("Illegal mode: 0x"+
  177. Integer.toHexString(mode));
  178. }
  179. String name = file.getPath();
  180. SecurityManager sm = System.getSecurityManager();
  181. if (sm != null) {
  182. sm.checkRead(name);
  183. if ((mode & OPEN_DELETE) != 0) {
  184. sm.checkDelete(name);
  185. }
  186. }
  187. long jzfileCopy = open(name, mode, file.lastModified());
  188. this.name = name;
  189. this.total = getTotal(jzfileCopy);
  190. this.mbUsed = false;
  191. long mappedAddr = getMappedAddr(jzfileCopy);
  192. long len = getMappedLen(jzfileCopy);
  193. if (mappedAddr != 0 && len < Integer.MAX_VALUE) {
  194. // Zip's native code may be able to handle files up to 4GB, but
  195. // ByteBuffers can only handle 2GB. So fallback on Zip files >= 2GB.
  196. this.closer = new ZipCloser(jzfileCopy);
  197. this.mappedBuffer = newMappedByteBuffer((int)len, mappedAddr,
  198. this.closer);
  199. }
  200. jzfile = jzfileCopy;
  201. }
  202. private static native long open(String name, int mode, long lastModified);
  203. private static native int getTotal(long jzfile);
  204. private static native long getMappedAddr(long jzfile);
  205. private static native long getMappedLen(long jzfile);
  206. /**
  207. * Opens a ZIP file for reading given the specified File object.
  208. * @param file the ZIP file to be opened for reading
  209. * @throws ZipException if a ZIP error has occurred
  210. * @throws IOException if an I/O error has occurred
  211. */
  212. public ZipFile(File file) throws ZipException, IOException {
  213. this(file, OPEN_READ);
  214. }
  215. /**
  216. * Returns the zip file entry for the specified name, or null
  217. * if not found.
  218. *
  219. * @param name the name of the entry
  220. * @return the zip file entry, or null if not found
  221. * @throws IllegalStateException if the zip file has been closed
  222. */
  223. public ZipEntry getEntry(String name) {
  224. if (name == null) {
  225. throw new NullPointerException("name");
  226. }
  227. long jzentry = 0;
  228. synchronized (this) {
  229. ensureOpen();
  230. jzentry = getEntry(jzfile, name, true);
  231. if (jzentry != 0) {
  232. ZipEntry ze = new ZipEntry(name, jzentry);
  233. freeEntry(jzfile, jzentry);
  234. return ze;
  235. }
  236. }
  237. return null;
  238. }
  239. private static native long getEntry(long jzfile, String name,
  240. boolean addSlash);
  241. // freeEntry releases the C jzentry struct.
  242. private static native void freeEntry(long jzfile, long jzentry);
  243. /**
  244. * Returns an input stream for reading the contents of the specified
  245. * zip file entry.
  246. *
  247. * Returns an input stream for reading the contents of the specified
  248. * zip file entry.
  249. *
  250. * <p> Closing this ZIP file will, in turn, close all input
  251. * streams that have been returned by invocations of this method.
  252. *
  253. * @param entry the zip file entry
  254. * @return the input stream for reading the contents of the specified
  255. * zip file entry.
  256. * @throws ZipException if a ZIP format error has occurred
  257. * @throws IOException if an I/O error has occurred
  258. * @throws IllegalStateException if the zip file has been closed
  259. */
  260. public InputStream getInputStream(ZipEntry entry) throws IOException {
  261. return getInputStream(entry.name);
  262. }
  263. /**
  264. * Returns an input stream for reading the contents of the specified
  265. * entry, or null if the entry was not found.
  266. */
  267. private InputStream getInputStream(String name) throws IOException {
  268. if (name == null) {
  269. throw new NullPointerException("name");
  270. }
  271. long jzentry = 0;
  272. ZipFileInputStream in = null;
  273. synchronized (this) {
  274. ensureOpen();
  275. jzentry = getEntry(jzfile, name, false);
  276. if (jzentry == 0) {
  277. return null;
  278. }
  279. if (mappedBuffer != null) {
  280. in = new MappedZipFileInputStream(jzentry, name);
  281. } else {
  282. in = new ZipFileInputStream(jzentry);
  283. }
  284. }
  285. final ZipFileInputStream zfin = in;
  286. switch (getMethod(jzentry)) {
  287. case STORED:
  288. return zfin;
  289. case DEFLATED:
  290. // MORE: Compute good size for inflater stream:
  291. return new InflaterInputStream(zfin, getInflater()) {
  292. private boolean isClosed = false;
  293. public void close() throws IOException {
  294. if (!isClosed) {
  295. releaseInflater(inf);
  296. this.in.close();
  297. isClosed = true;
  298. }
  299. }
  300. // Override fill() method to provide an extra "dummy" byte
  301. // at the end of the input stream. This is required when
  302. // using the "nowrap" Inflater option.
  303. protected void fill() throws IOException {
  304. if (eof) {
  305. throw new EOFException(
  306. "Unexpected end of ZLIB input stream");
  307. }
  308. len = this.in.read(buf, 0, buf.length);
  309. if (len == -1) {
  310. buf[0] = 0;
  311. len = 1;
  312. eof = true;
  313. }
  314. inf.setInput(buf, 0, len);
  315. }
  316. private boolean eof;
  317. public int available() throws IOException {
  318. if (isClosed)
  319. return 0;
  320. long avail = zfin.size() - inf.getBytesWritten();
  321. return avail > (long) Integer.MAX_VALUE ?
  322. Integer.MAX_VALUE : (int) avail;
  323. }
  324. };
  325. default:
  326. throw new ZipException("invalid compression method");
  327. }
  328. }
  329. private static native int getMethod(long jzentry);
  330. /*
  331. * Gets an inflater from the list of available inflaters or allocates
  332. * a new one.
  333. */
  334. private Inflater getInflater() {
  335. synchronized (inflaters) {
  336. int size = inflaters.size();
  337. if (size > 0) {
  338. Inflater inf = (Inflater)inflaters.remove(size - 1);
  339. inf.reset();
  340. return inf;
  341. } else {
  342. return new Inflater(true);
  343. }
  344. }
  345. }
  346. /*
  347. * Releases the specified inflater to the list of available inflaters.
  348. */
  349. private void releaseInflater(Inflater inf) {
  350. synchronized (inflaters) {
  351. inflaters.add(inf);
  352. }
  353. }
  354. // List of available Inflater objects for decompression
  355. private Vector inflaters = new Vector();
  356. /**
  357. * Returns the path name of the ZIP file.
  358. * @return the path name of the ZIP file
  359. */
  360. public String getName() {
  361. return name;
  362. }
  363. /**
  364. * Returns an enumeration of the ZIP file entries.
  365. * @return an enumeration of the ZIP file entries
  366. * @throws IllegalStateException if the zip file has been closed
  367. */
  368. public Enumeration<? extends ZipEntry> entries() {
  369. ensureOpen();
  370. return new Enumeration<ZipEntry>() {
  371. private int i = 0;
  372. public boolean hasMoreElements() {
  373. synchronized (ZipFile.this) {
  374. ensureOpen();
  375. return i < total;
  376. }
  377. }
  378. public ZipEntry nextElement() throws NoSuchElementException {
  379. synchronized (ZipFile.this) {
  380. ensureOpen();
  381. if (i >= total) {
  382. throw new NoSuchElementException();
  383. }
  384. long jzentry = getNextEntry(jzfile, i++);
  385. if (jzentry == 0) {
  386. String message;
  387. if (closeRequested) {
  388. message = "ZipFile concurrently closed";
  389. } else {
  390. message = getZipMessage(ZipFile.this.jzfile);
  391. }
  392. throw new InternalError("jzentry == 0" +
  393. ",\n jzfile = " + ZipFile.this.jzfile +
  394. ",\n total = " + ZipFile.this.total +
  395. ",\n name = " + ZipFile.this.name +
  396. ",\n i = " + i +
  397. ",\n message = " + message
  398. );
  399. }
  400. ZipEntry ze = new ZipEntry(jzentry);
  401. freeEntry(jzfile, jzentry);
  402. return ze;
  403. }
  404. }
  405. };
  406. }
  407. private static native long getNextEntry(long jzfile, int i);
  408. /**
  409. * Returns the number of entries in the ZIP file.
  410. * @return the number of entries in the ZIP file
  411. * @throws IllegalStateException if the zip file has been closed
  412. */
  413. public int size() {
  414. ensureOpen();
  415. return total;
  416. }
  417. /**
  418. * Closes the ZIP file.
  419. * <p> Closing this ZIP file will close all of the input streams
  420. * previously returned by invocations of the {@link #getInputStream
  421. * getInputStream} method.
  422. *
  423. * @throws IOException if an I/O error has occurred
  424. */
  425. public void close() throws IOException {
  426. synchronized (this) {
  427. closeRequested = true;
  428. if (jzfile != 0) {
  429. // Close the zip file
  430. long zf = this.jzfile;
  431. jzfile = 0;
  432. if (closer != null) {
  433. if (!mbUsed) { // no one is looking; we can close early
  434. closer.setClosed(); // tell closer not to bother
  435. close(zf);
  436. }
  437. // Some caller may have ref to MappedByteBuffer,
  438. // so let phantom processing (ZipCloser) close the ZipFile.
  439. } else {
  440. close(zf);
  441. }
  442. // Release inflaters
  443. synchronized (inflaters) {
  444. int size = inflaters.size();
  445. for (int i = 0; i < size; i++) {
  446. Inflater inf = (Inflater)inflaters.get(i);
  447. inf.end();
  448. }
  449. }
  450. }
  451. }
  452. }
  453. /**
  454. * Ensures that the <code>close</code> method of this ZIP file is
  455. * called when there are no more references to it.
  456. *
  457. * <p>
  458. * Since the time when GC would invoke this method is undetermined,
  459. * it is strongly recommended that applications invoke the <code>close</code>
  460. * method as soon they have finished accessing this <code>ZipFile</code>.
  461. * This will prevent holding up system resources for an undetermined
  462. * length of time.
  463. *
  464. * @throws IOException if an I/O error has occurred
  465. * @see java.util.zip.ZipFile#close()
  466. */
  467. protected void finalize() throws IOException {
  468. close();
  469. }
  470. private static native void close(long jzfile);
  471. private void ensureOpen() {
  472. if (closeRequested) {
  473. throw new IllegalStateException("zip file closed");
  474. }
  475. }
  476. private void ensureOpenOrZipException() throws IOException {
  477. if (closeRequested) {
  478. throw new ZipException("ZipFile closed");
  479. }
  480. }
  481. /*
  482. * Inner class implementing the input stream used to read a
  483. * (possibly compressed) zip file entry.
  484. */
  485. private class ZipFileInputStream extends InputStream {
  486. protected long jzentry; // address of jzentry data
  487. private long pos; // current position within entry data
  488. protected long rem; // number of remaining bytes within entry
  489. protected long size; // uncompressed size of this entry
  490. ZipFileInputStream(long jzentry) {
  491. pos = 0;
  492. rem = getCSize(jzentry);
  493. size = getSize(jzentry);
  494. this.jzentry = jzentry;
  495. }
  496. public int read(byte b[], int off, int len) throws IOException {
  497. if (rem == 0) {
  498. return -1;
  499. }
  500. if (len <= 0) {
  501. return 0;
  502. }
  503. if (len > rem) {
  504. len = (int) rem;
  505. }
  506. synchronized (ZipFile.this) {
  507. ensureOpenOrZipException();
  508. len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b,
  509. off, len);
  510. }
  511. if (len > 0) {
  512. pos += len;
  513. rem -= len;
  514. }
  515. if (rem == 0) {
  516. close();
  517. }
  518. return len;
  519. }
  520. public int read() throws IOException {
  521. byte[] b = new byte[1];
  522. if (read(b, 0, 1) == 1) {
  523. return b[0] & 0xff;
  524. } else {
  525. return -1;
  526. }
  527. }
  528. public long skip(long n) {
  529. if (n > rem)
  530. n = rem;
  531. pos += n;
  532. rem -= n;
  533. if (rem == 0) {
  534. close();
  535. }
  536. return n;
  537. }
  538. public int available() {
  539. return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
  540. }
  541. public long size() {
  542. return size;
  543. }
  544. public void close() {
  545. rem = 0;
  546. synchronized (ZipFile.this) {
  547. if (jzentry != 0 && ZipFile.this.jzfile != 0) {
  548. freeEntry(ZipFile.this.jzfile, jzentry);
  549. jzentry = 0;
  550. }
  551. }
  552. }
  553. }
  554. /*
  555. * Inner class implementing the input stream used to read a
  556. * mapped (possibly compressed) zip file entry. Overrides
  557. * all methods of ZipFileInputStream.
  558. */
  559. private class MappedZipFileInputStream extends ZipFileInputStream
  560. implements ByteBuffered {
  561. private ByteBuffer directBuffer = null;
  562. private String name;
  563. MappedZipFileInputStream(long jzentry, String name) {
  564. super(jzentry);
  565. this.name = name;
  566. int offset = (int)getEntryOffset(jzentry);
  567. MappedByteBuffer bb = ZipFile.this.mappedBuffer;
  568. synchronized (bb) {
  569. bb.position(offset);
  570. bb.limit((int)(offset + rem)); // won't use this code if file > 2GB
  571. this.directBuffer = bb.slice();
  572. bb.position(0); // reset, but doesn't matter
  573. bb.limit(bb.capacity()); // reset limit
  574. }
  575. }
  576. /* getByteBuffer returns a ByteBuffer if the jar file has been mapped in.
  577. If this method is called, the zip code won't close the ZipFile until
  578. the last reference to file's mapped buffer is collected. */
  579. public ByteBuffer getByteBuffer() throws IOException {
  580. synchronized (ZipFile.this) {
  581. ensureOpenOrZipException();
  582. // have to defer ZipFile.close() until all the buffers are garbage
  583. ZipFile.this.mbUsed = true;
  584. return directBuffer;
  585. }
  586. }
  587. public int read(byte b[], int off, int len) throws IOException {
  588. int rem = directBuffer.remaining();
  589. if (rem == 0) {
  590. return -1;
  591. }
  592. if (len <= 0) {
  593. return 0;
  594. }
  595. if (len > rem) {
  596. len = rem;
  597. }
  598. synchronized (ZipFile.this) {
  599. ensureOpenOrZipException();
  600. directBuffer.get(b, off, len);
  601. }
  602. if (len == rem) {
  603. close();
  604. }
  605. return len;
  606. }
  607. public int read() throws IOException {
  608. synchronized (ZipFile.this) {
  609. ensureOpenOrZipException();
  610. if (directBuffer.remaining() == 0) {
  611. return -1;
  612. } else {
  613. return directBuffer.get() & 0xff;
  614. }
  615. }
  616. }
  617. public long skip(long n) {
  618. int rem = directBuffer.remaining();
  619. int len = n > rem ? rem : (int)n;
  620. directBuffer.position(directBuffer.position() + len);
  621. if (len == rem) {
  622. close();
  623. }
  624. return len;
  625. }
  626. public int available() {
  627. return directBuffer.remaining();
  628. }
  629. public long size() {
  630. return size;
  631. }
  632. public void close() {
  633. directBuffer.position(directBuffer.limit());
  634. synchronized (ZipFile.this) {
  635. if (jzentry != 0 && ZipFile.this.jzfile != 0) {
  636. freeEntry(ZipFile.this.jzfile, jzentry);
  637. jzentry = 0;
  638. }
  639. }
  640. }
  641. } /* MappedZipFileInputStream */
  642. private static native int read(long jzfile, long jzentry,
  643. long pos, byte[] b, int off, int len);
  644. private static native long getCSize(long jzentry);
  645. private static native long getSize(long jzentry);
  646. /* If the zip file is mapped, return the offset from the beginning of the zip
  647. file to this entry. Return 0 otherwise. */
  648. private static native long getEntryOffset(long jzentry);
  649. // Temporary add on for bug troubleshooting
  650. private static native String getZipMessage(long jzfile);
  651. }