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.io.RandomAccessFile;
34 import java.nio.charset.Charset;
35 import java.nio.charset.StandardCharsets;
36 import java.nio.file.attribute.BasicFileAttributes;
37 import java.nio.file.Path;
38 import java.nio.file.Files;
39
40 import java.util.ArrayDeque;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Deque;
44 import java.util.Enumeration;
45 import java.util.HashMap;
46 import java.util.Iterator;
47 import java.util.Map;
48 import java.util.Objects;
49 import java.util.NoSuchElementException;
50 import java.util.Spliterator;
51 import java.util.Spliterators;
52 import java.util.WeakHashMap;
53
54 import java.util.function.Consumer;
55 import java.util.function.Function;
56 import java.util.function.IntFunction;
57 import java.util.jar.JarEntry;
58 import java.util.stream.Stream;
59 import java.util.stream.StreamSupport;
60 import jdk.internal.misc.JavaUtilZipFileAccess;
61 import jdk.internal.misc.SharedSecrets;
62 import jdk.internal.misc.VM;
63 import jdk.internal.perf.PerfCounter;
64
65 import static java.util.zip.ZipConstants.*;
66 import static java.util.zip.ZipConstants64.*;
67 import static java.util.zip.ZipUtils.*;
68
69 /**
70 * This class is used to read entries from a zip file.
71 *
72 * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
73 * or method in this class will cause a {@link NullPointerException} to be
74 * thrown.
75 *
76 * @author David Connelly
77 * @since 1.1
78 */
79 public
80 class ZipFile implements ZipConstants, Closeable {
81
82 private final String name; // zip file name
83 private volatile boolean closeRequested;
84 private Source zsrc;
85 private ZipCoder zc;
86
87 private static final int STORED = ZipEntry.STORED;
88 private static final int DEFLATED = ZipEntry.DEFLATED;
89
90 /**
91 * Mode flag to open a zip file for reading.
92 */
93 public static final int OPEN_READ = 0x1;
94
95 /**
96 * Mode flag to open a zip file and mark it for deletion. The file will be
97 * deleted some time between the moment that it is opened and the moment
98 * that it is closed, but its contents will remain accessible via the
99 * {@code ZipFile} object until either the close method is invoked or the
100 * virtual machine exits.
101 */
102 public static final int OPEN_DELETE = 0x4;
103
104 /**
105 * Opens a zip file for reading.
106 *
197 * @see SecurityManager#checkRead(java.lang.String)
198 *
199 * @since 1.7
200 */
201 public ZipFile(File file, int mode, Charset charset) throws IOException
202 {
203 if (((mode & OPEN_READ) == 0) ||
204 ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
205 throw new IllegalArgumentException("Illegal mode: 0x"+
206 Integer.toHexString(mode));
207 }
208 String name = file.getPath();
209 SecurityManager sm = System.getSecurityManager();
210 if (sm != null) {
211 sm.checkRead(name);
212 if ((mode & OPEN_DELETE) != 0) {
213 sm.checkDelete(name);
214 }
215 }
216 Objects.requireNonNull(charset, "charset");
217 this.zc = ZipCoder.get(charset);
218 this.name = name;
219 long t0 = System.nanoTime();
220 this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0);
221 PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0);
222 PerfCounter.getZipFileCount().increment();
223 }
224
225 /**
226 * Opens a zip file for reading.
227 *
228 * <p>First, if there is a security manager, its {@code checkRead}
229 * method is called with the {@code name} argument as its argument
230 * to ensure the read is allowed.
231 *
232 * @param name the name of the zip file
233 * @param charset
234 * the {@linkplain java.nio.charset.Charset charset} to
235 * be used to decode the ZIP entry name and comment that are not
236 * encoded by using UTF-8 encoding (indicated by entry's general
237 * purpose flag).
238 *
239 * @throws ZipException if a ZIP format error has occurred
240 * @throws IOException if an I/O error has occurred
267 *
268 * @since 1.7
269 */
270 public ZipFile(File file, Charset charset) throws IOException
271 {
272 this(file, OPEN_READ, charset);
273 }
274
275 /**
276 * Returns the zip file comment, or null if none.
277 *
278 * @return the comment string for the zip file, or null if none
279 *
280 * @throws IllegalStateException if the zip file has been closed
281 *
282 * @since 1.7
283 */
284 public String getComment() {
285 synchronized (this) {
286 ensureOpen();
287 if (zsrc.comment == null) {
288 return null;
289 }
290 return zc.toString(zsrc.comment);
291 }
292 }
293
294 /**
295 * Returns the zip file entry for the specified name, or null
296 * if not found.
297 *
298 * @param name the name of the entry
299 * @return the zip file entry, or null if not found
300 * @throws IllegalStateException if the zip file has been closed
301 */
302 public ZipEntry getEntry(String name) {
303 return getEntry(name, ZipEntry::new);
304 }
305
306 /*
307 * Returns the zip file entry for the specified name, or null
308 * if not found.
309 *
310 * @param name the name of the entry
311 * @param func the function that creates the returned entry
312 *
313 * @return the zip file entry, or null if not found
314 * @throws IllegalStateException if the zip file has been closed
315 */
316 private ZipEntry getEntry(String name, Function<String, ? extends ZipEntry> func) {
317 Objects.requireNonNull(name, "name");
318 synchronized (this) {
319 ensureOpen();
320 byte[] bname = zc.getBytes(name);
321 int pos = zsrc.getEntryPos(bname, true);
322 if (pos != -1) {
323 return getZipEntry(name, bname, pos, func);
324 }
325 }
326 return null;
327 }
328
329 // The outstanding inputstreams that need to be closed,
330 // mapped to the inflater objects they use.
331 private final Map<InputStream, Inflater> streams = new WeakHashMap<>();
332
333 /**
334 * Returns an input stream for reading the contents of the specified
335 * zip file entry.
336 * <p>
337 * Closing this ZIP file will, in turn, close all input streams that
338 * have been returned by invocations of this method.
339 *
340 * @param entry the zip file entry
341 * @return the input stream for reading the contents of the specified
342 * zip file entry.
343 * @throws ZipException if a ZIP format error has occurred
344 * @throws IOException if an I/O error has occurred
345 * @throws IllegalStateException if the zip file has been closed
346 */
347 public InputStream getInputStream(ZipEntry entry) throws IOException {
348 Objects.requireNonNull(entry, "entry");
349 int pos = -1;
350 ZipFileInputStream in = null;
351 synchronized (this) {
352 ensureOpen();
353 if (Objects.equals(lastEntryName, entry.name)) {
354 pos = lastEntryPos;
355 } else if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
356 pos = zsrc.getEntryPos(zc.getBytesUTF8(entry.name), false);
357 } else {
358 pos = zsrc.getEntryPos(zc.getBytes(entry.name), false);
359 }
360 if (pos == -1) {
361 return null;
362 }
363 in = new ZipFileInputStream(zsrc.cen, pos);
364 switch (CENHOW(zsrc.cen, pos)) {
365 case STORED:
366 synchronized (streams) {
367 streams.put(in, null);
368 }
369 return in;
370 case DEFLATED:
371 // Inflater likes a bit of slack
372 // MORE: Compute good size for inflater stream:
373 long size = CENLEN(zsrc.cen, pos) + 2;
374 if (size > 65536) {
375 size = 8192;
376 }
377 if (size <= 0) {
378 size = 4096;
379 }
380 Inflater inf = getInflater();
381 InputStream is = new ZipFileInflaterInputStream(in, inf, (int)size);
382 synchronized (streams) {
383 streams.put(is, inf);
384 }
385 return is;
386 default:
387 throw new ZipException("invalid compression method");
388 }
389 }
390 }
391
392 private class ZipFileInflaterInputStream extends InflaterInputStream {
393 private volatile boolean closeRequested;
394 private boolean eof = false;
395
396 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf,
397 int size) {
398 super(zfin, inf, size);
399 }
400
401 public void close() throws IOException {
402 if (closeRequested)
403 return;
404 closeRequested = true;
405
406 super.close();
407 Inflater inf;
408 synchronized (streams) {
409 inf = streams.remove(this);
410 }
411 if (inf != null) {
412 releaseInflater(inf);
413 }
414 }
415
416 // Override fill() method to provide an extra "dummy" byte
417 // at the end of the input stream. This is required when
418 // using the "nowrap" Inflater option.
419 protected void fill() throws IOException {
420 if (eof) {
421 throw new EOFException("Unexpected end of ZLIB input stream");
422 }
423 len = in.read(buf, 0, buf.length);
424 if (len == -1) {
425 buf[0] = 0;
426 len = 1;
427 eof = true;
428 }
429 inf.setInput(buf, 0, len);
430 }
431
432 public int available() throws IOException {
433 if (closeRequested)
434 return 0;
435 long avail = ((ZipFileInputStream)in).size() - inf.getBytesWritten();
436 return (avail > (long) Integer.MAX_VALUE ?
437 Integer.MAX_VALUE : (int) avail);
438 }
439
440 @SuppressWarnings("deprecation")
441 protected void finalize() throws Throwable {
442 close();
443 }
444 }
445
446 /*
447 * Gets an inflater from the list of available inflaters or allocates
448 * a new one.
449 */
450 private Inflater getInflater() {
451 Inflater inf;
452 synchronized (inflaterCache) {
453 while ((inf = inflaterCache.poll()) != null) {
454 if (!inf.ended()) {
455 return inf;
456 }
457 }
458 }
459 return new Inflater(true);
460 }
461
462 /*
463 * Releases the specified inflater to the list of available inflaters.
464 */
465 private void releaseInflater(Inflater inf) {
466 if (!inf.ended()) {
467 inf.reset();
468 synchronized (inflaterCache) {
469 inflaterCache.add(inf);
470 }
471 }
472 }
473
474 // List of available Inflater objects for decompression
475 private final Deque<Inflater> inflaterCache = new ArrayDeque<>();
476
477 /**
478 * Returns the path name of the ZIP file.
479 * @return the path name of the ZIP file
480 */
481 public String getName() {
482 return name;
483 }
484
485 private class ZipEntryIterator<T extends ZipEntry>
486 implements Enumeration<T>, Iterator<T> {
487
488 private int i = 0;
489 private final int entryCount;
490 private final Function<String, T> gen;
491
492 public ZipEntryIterator(int entryCount, Function<String, T> gen) {
493 this.entryCount = entryCount;
494 this.gen = gen;
495 }
496
501
502 @Override
503 public boolean hasNext() {
504 return i < entryCount;
505 }
506
507 @Override
508 public T nextElement() {
509 return next();
510 }
511
512 @Override
513 @SuppressWarnings("unchecked")
514 public T next() {
515 synchronized (ZipFile.this) {
516 ensureOpen();
517 if (!hasNext()) {
518 throw new NoSuchElementException();
519 }
520 // each "entry" has 3 ints in table entries
521 return (T)getZipEntry(null, null, zsrc.getEntryPos(i++ * 3), gen);
522 }
523 }
524
525 @Override
526 public Iterator<T> asIterator() {
527 return this;
528 }
529 }
530
531 /**
532 * Returns an enumeration of the ZIP file entries.
533 * @return an enumeration of the ZIP file entries
534 * @throws IllegalStateException if the zip file has been closed
535 */
536 public Enumeration<? extends ZipEntry> entries() {
537 synchronized (this) {
538 ensureOpen();
539 return new ZipEntryIterator<ZipEntry>(zsrc.total, ZipEntry::new);
540 }
541 }
542
543 private Enumeration<JarEntry> entries(Function<String, JarEntry> func) {
544 synchronized (this) {
545 ensureOpen();
546 return new ZipEntryIterator<JarEntry>(zsrc.total, func);
547 }
548 }
549
550 private class EntrySpliterator<T> extends Spliterators.AbstractSpliterator<T> {
551 private int index;
552 private final int fence;
553 private final IntFunction<T> gen;
554
555 EntrySpliterator(int index, int fence, IntFunction<T> gen) {
556 super((long)fence,
557 Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE |
558 Spliterator.NONNULL);
559 this.index = index;
560 this.fence = fence;
561 this.gen = gen;
562 }
563
564 @Override
565 public boolean tryAdvance(Consumer<? super T> action) {
566 if (action == null)
567 throw new NullPointerException();
568 if (index >= 0 && index < fence) {
569 synchronized (ZipFile.this) {
570 ensureOpen();
571 action.accept(gen.apply(zsrc.getEntryPos(index++ * 3)));
572 }
573 return true;
574 }
575 return false;
576 }
577 }
578
579 /**
580 * Returns an ordered {@code Stream} over the ZIP file entries.
581 *
582 * Entries appear in the {@code Stream} in the order they appear in
583 * the central directory of the ZIP file.
584 *
585 * @return an ordered {@code Stream} of entries in this ZIP file
586 * @throws IllegalStateException if the zip file has been closed
587 * @since 1.8
588 */
589 public Stream<? extends ZipEntry> stream() {
590 synchronized (this) {
591 ensureOpen();
592 return StreamSupport.stream(new EntrySpliterator<>(0, zsrc.total,
593 pos -> getZipEntry(null, null, pos, ZipEntry::new)), false);
594 }
595 }
596
597 private String getEntryName(int pos) {
598 byte[] cen = zsrc.cen;
599 int nlen = CENNAM(cen, pos);
600 int clen = CENCOM(cen, pos);
601 int flag = CENFLG(cen, pos);
602 if (!zc.isUTF8() && (flag & EFS) != 0) {
603 return zc.toStringUTF8(cen, pos + CENHDR, nlen);
604 } else {
605 return zc.toString(cen, pos + CENHDR, nlen);
606 }
607 }
608
609 /*
610 * Returns an ordered {@code Stream} over the zip file entry names.
611 *
612 * Entry names appear in the {@code Stream} in the order they appear in
613 * the central directory of the ZIP file.
614 *
615 * @return an ordered {@code Stream} of entry names in this zip file
616 * @throws IllegalStateException if the zip file has been closed
617 * @since 10
618 */
619 private Stream<String> entryNameStream() {
620 synchronized (this) {
621 ensureOpen();
622 return StreamSupport.stream(
623 new EntrySpliterator<>(0, zsrc.total, this::getEntryName), false);
624 }
625 }
626
627 /*
628 * Returns an ordered {@code Stream} over the zip file entries.
629 *
630 * Entries appear in the {@code Stream} in the order they appear in
631 * the central directory of the jar file.
632 *
633 * @param func the function that creates the returned entry
634 * @return an ordered {@code Stream} of entries in this zip file
635 * @throws IllegalStateException if the zip file has been closed
636 * @since 10
637 */
638 private Stream<JarEntry> stream(Function<String, JarEntry> func) {
639 synchronized (this) {
640 ensureOpen();
641 return StreamSupport.stream(new EntrySpliterator<>(0, zsrc.total,
642 pos -> (JarEntry)getZipEntry(null, null, pos, func)), false);
643 }
644 }
645
646 private String lastEntryName;
647 private int lastEntryPos;
648
649 /* Checks ensureOpen() before invoke this method */
650 private ZipEntry getZipEntry(String name, byte[] bname, int pos,
651 Function<String, ? extends ZipEntry> func) {
652 byte[] cen = zsrc.cen;
653 int nlen = CENNAM(cen, pos);
654 int elen = CENEXT(cen, pos);
655 int clen = CENCOM(cen, pos);
656 int flag = CENFLG(cen, pos);
657 if (name == null || bname.length != nlen) {
658 // to use the entry name stored in cen, if the passed in name is
659 // (1) null, invoked from iterator, or
660 // (2) not equal to the name stored, a slash is appended during
661 // getEntryPos() search.
662 if (!zc.isUTF8() && (flag & EFS) != 0) {
663 name = zc.toStringUTF8(cen, pos + CENHDR, nlen);
664 } else {
665 name = zc.toString(cen, pos + CENHDR, nlen);
666 }
667 }
668 ZipEntry e = func.apply(name); //ZipEntry e = new ZipEntry(name);
669 e.flag = flag;
670 e.xdostime = CENTIM(cen, pos);
671 e.crc = CENCRC(cen, pos);
672 e.size = CENLEN(cen, pos);
681 if (!zc.isUTF8() && (flag & EFS) != 0) {
682 e.comment = zc.toStringUTF8(cen, start, clen);
683 } else {
684 e.comment = zc.toString(cen, start, clen);
685 }
686 }
687 lastEntryName = e.name;
688 lastEntryPos = pos;
689 return e;
690 }
691
692 /**
693 * Returns the number of entries in the ZIP file.
694 *
695 * @return the number of entries in the ZIP file
696 * @throws IllegalStateException if the zip file has been closed
697 */
698 public int size() {
699 synchronized (this) {
700 ensureOpen();
701 return zsrc.total;
702 }
703 }
704
705 /**
706 * Closes the ZIP file.
707 * <p> Closing this ZIP file will close all of the input streams
708 * previously returned by invocations of the {@link #getInputStream
709 * getInputStream} method.
710 *
711 * @throws IOException if an I/O error has occurred
712 */
713 public void close() throws IOException {
714 if (closeRequested) {
715 return;
716 }
717 closeRequested = true;
718
719 synchronized (this) {
720 // Close streams, release their inflaters
721 synchronized (streams) {
722 if (!streams.isEmpty()) {
723 Map<InputStream, Inflater> copy = new HashMap<>(streams);
724 streams.clear();
725 for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) {
726 e.getKey().close();
727 Inflater inf = e.getValue();
728 if (inf != null) {
729 inf.end();
730 }
731 }
732 }
733 }
734 // Release cached inflaters
735 synchronized (inflaterCache) {
736 Inflater inf;
737 while ((inf = inflaterCache.poll()) != null) {
738 inf.end();
739 }
740 }
741 // Release zip src
742 if (zsrc != null) {
743 Source.close(zsrc);
744 zsrc = null;
745 }
746 }
747 }
748
749 /**
750 * Ensures that the system resources held by this ZipFile object are
751 * released when there are no more references to it.
752 *
753 * <p>
754 * Since the time when GC would invoke this method is undetermined,
755 * it is strongly recommended that applications invoke the {@code close}
756 * method as soon they have finished accessing this {@code ZipFile}.
757 * This will prevent holding up system resources for an undetermined
758 * length of time.
759 *
760 * @deprecated The {@code finalize} method has been deprecated.
761 * Subclasses that override {@code finalize} in order to perform cleanup
762 * should be modified to use alternative cleanup mechanisms and
763 * to remove the overriding {@code finalize} method.
764 * When overriding the {@code finalize} method, its implementation must explicitly
765 * ensure that {@code super.finalize()} is invoked as described in {@link Object#finalize}.
766 * See the specification for {@link Object#finalize()} for further
767 * information about migration options.
768 * @throws IOException if an I/O error has occurred
769 * @see java.util.zip.ZipFile#close()
770 */
771 @Deprecated(since="9")
772 protected void finalize() throws IOException {
773 close();
774 }
775
776 private void ensureOpen() {
777 if (closeRequested) {
778 throw new IllegalStateException("zip file closed");
779 }
780 if (zsrc == null) {
781 throw new IllegalStateException("The object is not initialized.");
782 }
783 }
784
785 private void ensureOpenOrZipException() throws IOException {
786 if (closeRequested) {
787 throw new ZipException("ZipFile closed");
788 }
789 }
790
791 /*
792 * Inner class implementing the input stream used to read a
793 * (possibly compressed) zip file entry.
794 */
795 private class ZipFileInputStream extends InputStream {
796 private volatile boolean closeRequested;
797 private long pos; // current position within entry data
798 protected long rem; // number of remaining bytes within entry
799 protected long size; // uncompressed size of this entry
800
801 ZipFileInputStream(byte[] cen, int cenpos) throws IOException {
802 rem = CENSIZ(cen, cenpos);
803 size = CENLEN(cen, cenpos);
804 pos = CENOFF(cen, cenpos);
805 // zip64
806 if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL ||
807 pos == ZIP64_MAGICVAL) {
808 checkZIP64(cen, cenpos);
809 }
810 // negative for lazy initialization, see getDataOffset();
811 pos = - (pos + ZipFile.this.zsrc.locpos);
812 }
813
814 private void checkZIP64(byte[] cen, int cenpos) throws IOException {
815 int off = cenpos + CENHDR + CENNAM(cen, cenpos);
816 int end = off + CENEXT(cen, cenpos);
817 while (off + 4 < end) {
818 int tag = get16(cen, off);
819 int sz = get16(cen, off + 2);
820 off += 4;
821 if (off + sz > end) // invalid data
822 break;
823 if (tag == EXTID_ZIP64) {
824 if (size == ZIP64_MAGICVAL) {
825 if (sz < 8 || (off + 8) > end)
826 break;
827 size = get64(cen, off);
828 sz -= 8;
829 off += 8;
830 }
831 if (rem == ZIP64_MAGICVAL) {
832 if (sz < 8 || (off + 8) > end)
833 break;
834 rem = get64(cen, off);
840 break;
841 pos = get64(cen, off);
842 sz -= 8;
843 off += 8;
844 }
845 break;
846 }
847 off += sz;
848 }
849 }
850
851 /* The Zip file spec explicitly allows the LOC extra data size to
852 * be different from the CEN extra data size. Since we cannot trust
853 * the CEN extra data size, we need to read the LOC to determine
854 * the entry data offset.
855 */
856 private long initDataOffset() throws IOException {
857 if (pos <= 0) {
858 byte[] loc = new byte[LOCHDR];
859 pos = -pos;
860 int len = ZipFile.this.zsrc.readFullyAt(loc, 0, loc.length, pos);
861 if (len != LOCHDR) {
862 throw new ZipException("ZipFile error reading zip file");
863 }
864 if (LOCSIG(loc) != LOCSIG) {
865 throw new ZipException("ZipFile invalid LOC header (bad signature)");
866 }
867 pos += LOCHDR + LOCNAM(loc) + LOCEXT(loc);
868 }
869 return pos;
870 }
871
872 public int read(byte b[], int off, int len) throws IOException {
873 synchronized (ZipFile.this) {
874 ensureOpenOrZipException();
875 initDataOffset();
876 if (rem == 0) {
877 return -1;
878 }
879 if (len > rem) {
880 len = (int) rem;
881 }
882 if (len <= 0) {
883 return 0;
884 }
885 len = ZipFile.this.zsrc.readAt(b, off, len, pos);
886 if (len > 0) {
887 pos += len;
888 rem -= len;
889 }
890 }
891 if (rem == 0) {
892 close();
893 }
894 return len;
895 }
896
897 public int read() throws IOException {
898 byte[] b = new byte[1];
899 if (read(b, 0, 1) == 1) {
900 return b[0] & 0xff;
901 } else {
902 return -1;
903 }
904 }
905
915 if (rem == 0) {
916 close();
917 }
918 return n;
919 }
920
921 public int available() {
922 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
923 }
924
925 public long size() {
926 return size;
927 }
928
929 public void close() {
930 if (closeRequested) {
931 return;
932 }
933 closeRequested = true;
934 rem = 0;
935 synchronized (streams) {
936 streams.remove(this);
937 }
938 }
939
940 @SuppressWarnings("deprecation")
941 protected void finalize() {
942 close();
943 }
944 }
945
946 /**
947 * Returns the names of all non-directory entries that begin with
948 * "META-INF/" (case ignored). This method is used in JarFile, via
949 * SharedSecrets, as an optimization when looking up manifest and
950 * signature file entries. Returns null if no entries were found.
951 */
952 private String[] getMetaInfEntryNames() {
953 synchronized (this) {
954 ensureOpen();
955 if (zsrc.metanames == null) {
956 return null;
957 }
958 String[] names = new String[zsrc.metanames.length];
959 byte[] cen = zsrc.cen;
960 for (int i = 0; i < names.length; i++) {
961 int pos = zsrc.metanames[i];
962 names[i] = new String(cen, pos + CENHDR, CENNAM(cen, pos),
963 StandardCharsets.UTF_8);
964 }
965 return names;
966 }
967 }
968
969 private static boolean isWindows;
970 static {
971 SharedSecrets.setJavaUtilZipFileAccess(
972 new JavaUtilZipFileAccess() {
973 @Override
974 public boolean startsWithLocHeader(ZipFile zip) {
975 return zip.zsrc.startsWithLoc;
976 }
977 @Override
978 public String[] getMetaInfEntryNames(ZipFile zip) {
979 return zip.getMetaInfEntryNames();
980 }
981 @Override
982 public JarEntry getEntry(ZipFile zip, String name,
983 Function<String, JarEntry> func) {
984 return (JarEntry)zip.getEntry(name, func);
985 }
986 @Override
987 public Enumeration<JarEntry> entries(ZipFile zip,
988 Function<String, JarEntry> func) {
989 return zip.entries(func);
990 }
991 @Override
992 public Stream<JarEntry> stream(ZipFile zip,
993 Function<String, JarEntry> func) {
994 return zip.stream(func);
995 }
1063
1064 public boolean equals(Object obj) {
1065 if (obj instanceof Key) {
1066 Key key = (Key)obj;
1067 if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) {
1068 return false;
1069 }
1070 Object fk = attrs.fileKey();
1071 if (fk != null) {
1072 return fk.equals(key.attrs.fileKey());
1073 } else {
1074 return file.equals(key.file);
1075 }
1076 }
1077 return false;
1078 }
1079 }
1080 private static final HashMap<Key, Source> files = new HashMap<>();
1081
1082
1083 public static Source get(File file, boolean toDelete) throws IOException {
1084 Key key = new Key(file,
1085 Files.readAttributes(file.toPath(), BasicFileAttributes.class));
1086 Source src = null;
1087 synchronized (files) {
1088 src = files.get(key);
1089 if (src != null) {
1090 src.refs++;
1091 return src;
1092 }
1093 }
1094 src = new Source(key, toDelete);
1095
1096 synchronized (files) {
1097 if (files.containsKey(key)) { // someone else put in first
1098 src.close(); // close the newly created one
1099 src = files.get(key);
1100 src.refs++;
1101 return src;
1102 }
1103 files.put(key, src);
1104 return src;
1105 }
1106 }
1107
1108 private static void close(Source src) throws IOException {
1109 synchronized (files) {
1110 if (--src.refs == 0) {
1111 files.remove(src.key);
1112 src.close();
1113 }
1114 }
1115 }
1116
1117 private Source(Key key, boolean toDelete) throws IOException {
1118 this.key = key;
1119 if (toDelete) {
1120 if (isWindows) {
1121 this.zfile = SharedSecrets.getJavaIORandomAccessFileAccess()
1122 .openAndDelete(key.file, "r");
1123 } else {
1124 this.zfile = new RandomAccessFile(key.file, "r");
1125 key.file.delete();
1126 }
1127 } else {
1128 this.zfile = new RandomAccessFile(key.file, "r");
1129 }
1130 try {
|
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.io.RandomAccessFile;
34 import java.io.UncheckedIOException;
35 import java.lang.ref.Cleaner.Cleanable;
36 import java.nio.charset.Charset;
37 import java.nio.charset.StandardCharsets;
38 import java.nio.file.attribute.BasicFileAttributes;
39 import java.nio.file.Files;
40
41 import java.util.ArrayDeque;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collections;
45 import java.util.Deque;
46 import java.util.Enumeration;
47 import java.util.HashMap;
48 import java.util.Iterator;
49 import java.util.Objects;
50 import java.util.NoSuchElementException;
51 import java.util.Set;
52 import java.util.Spliterator;
53 import java.util.Spliterators;
54 import java.util.WeakHashMap;
55
56 import java.util.function.Consumer;
57 import java.util.function.Function;
58 import java.util.function.IntFunction;
59 import java.util.jar.JarEntry;
60 import java.util.stream.Stream;
61 import java.util.stream.StreamSupport;
62 import jdk.internal.misc.JavaUtilZipFileAccess;
63 import jdk.internal.misc.SharedSecrets;
64 import jdk.internal.misc.VM;
65 import jdk.internal.perf.PerfCounter;
66 import jdk.internal.ref.CleanerFactory;
67
68 import static java.util.zip.ZipConstants64.*;
69 import static java.util.zip.ZipUtils.*;
70
71 /**
72 * This class is used to read entries from a zip file.
73 *
74 * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
75 * or method in this class will cause a {@link NullPointerException} to be
76 * thrown.
77 *
78 * @apiNote
79 * To release resources used by this {@code ZipFile}, the {@link #close()} method
80 * should be called explicitly or by try-with-resources. Subclasses are responsible
81 * for the cleanup of resources acquired by the subclass. Subclasses that override
82 * {@link #finalize()} in order to perform cleanup should be modified to use alternative
83 * cleanup mechanisms such as {@link java.lang.ref.Cleaner} and remove the overriding
84 * {@code finalize} method.
85 *
86 * @author David Connelly
87 * @since 1.1
88 */
89 public
90 class ZipFile implements ZipConstants, Closeable {
91
92 private final String name; // zip file name
93 private volatile boolean closeRequested;
94 private ZipCoder zc;
95
96 // The "resource" used by this zip file that needs to be
97 // cleaned after use.
98 // a) the input streams that need to be closed
99 // b) the list of cached Inflater objects
100 // c) the "native" source of this zip file.
101 private final CleanableResource res;
102
103 private static final int STORED = ZipEntry.STORED;
104 private static final int DEFLATED = ZipEntry.DEFLATED;
105
106 /**
107 * Mode flag to open a zip file for reading.
108 */
109 public static final int OPEN_READ = 0x1;
110
111 /**
112 * Mode flag to open a zip file and mark it for deletion. The file will be
113 * deleted some time between the moment that it is opened and the moment
114 * that it is closed, but its contents will remain accessible via the
115 * {@code ZipFile} object until either the close method is invoked or the
116 * virtual machine exits.
117 */
118 public static final int OPEN_DELETE = 0x4;
119
120 /**
121 * Opens a zip file for reading.
122 *
213 * @see SecurityManager#checkRead(java.lang.String)
214 *
215 * @since 1.7
216 */
217 public ZipFile(File file, int mode, Charset charset) throws IOException
218 {
219 if (((mode & OPEN_READ) == 0) ||
220 ((mode & ~(OPEN_READ | OPEN_DELETE)) != 0)) {
221 throw new IllegalArgumentException("Illegal mode: 0x"+
222 Integer.toHexString(mode));
223 }
224 String name = file.getPath();
225 SecurityManager sm = System.getSecurityManager();
226 if (sm != null) {
227 sm.checkRead(name);
228 if ((mode & OPEN_DELETE) != 0) {
229 sm.checkDelete(name);
230 }
231 }
232 Objects.requireNonNull(charset, "charset");
233
234 this.zc = ZipCoder.get(charset);
235 this.name = name;
236 long t0 = System.nanoTime();
237
238 this.res = CleanableResource.get(this, file, mode);
239
240 PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0);
241 PerfCounter.getZipFileCount().increment();
242 }
243
244 /**
245 * Opens a zip file for reading.
246 *
247 * <p>First, if there is a security manager, its {@code checkRead}
248 * method is called with the {@code name} argument as its argument
249 * to ensure the read is allowed.
250 *
251 * @param name the name of the zip file
252 * @param charset
253 * the {@linkplain java.nio.charset.Charset charset} to
254 * be used to decode the ZIP entry name and comment that are not
255 * encoded by using UTF-8 encoding (indicated by entry's general
256 * purpose flag).
257 *
258 * @throws ZipException if a ZIP format error has occurred
259 * @throws IOException if an I/O error has occurred
286 *
287 * @since 1.7
288 */
289 public ZipFile(File file, Charset charset) throws IOException
290 {
291 this(file, OPEN_READ, charset);
292 }
293
294 /**
295 * Returns the zip file comment, or null if none.
296 *
297 * @return the comment string for the zip file, or null if none
298 *
299 * @throws IllegalStateException if the zip file has been closed
300 *
301 * @since 1.7
302 */
303 public String getComment() {
304 synchronized (this) {
305 ensureOpen();
306 if (res.zsrc.comment == null) {
307 return null;
308 }
309 return zc.toString(res.zsrc.comment);
310 }
311 }
312
313 /**
314 * Returns the zip file entry for the specified name, or null
315 * if not found.
316 *
317 * @param name the name of the entry
318 * @return the zip file entry, or null if not found
319 * @throws IllegalStateException if the zip file has been closed
320 */
321 public ZipEntry getEntry(String name) {
322 return getEntry(name, ZipEntry::new);
323 }
324
325 /*
326 * Returns the zip file entry for the specified name, or null
327 * if not found.
328 *
329 * @param name the name of the entry
330 * @param func the function that creates the returned entry
331 *
332 * @return the zip file entry, or null if not found
333 * @throws IllegalStateException if the zip file has been closed
334 */
335 private ZipEntry getEntry(String name, Function<String, ? extends ZipEntry> func) {
336 Objects.requireNonNull(name, "name");
337 synchronized (this) {
338 ensureOpen();
339 byte[] bname = zc.getBytes(name);
340 int pos = res.zsrc.getEntryPos(bname, true);
341 if (pos != -1) {
342 return getZipEntry(name, bname, pos, func);
343 }
344 }
345 return null;
346 }
347
348 /**
349 * Returns an input stream for reading the contents of the specified
350 * zip file entry.
351 * <p>
352 * Closing this ZIP file will, in turn, close all input streams that
353 * have been returned by invocations of this method.
354 *
355 * @param entry the zip file entry
356 * @return the input stream for reading the contents of the specified
357 * zip file entry.
358 * @throws ZipException if a ZIP format error has occurred
359 * @throws IOException if an I/O error has occurred
360 * @throws IllegalStateException if the zip file has been closed
361 */
362 public InputStream getInputStream(ZipEntry entry) throws IOException {
363 Objects.requireNonNull(entry, "entry");
364 int pos = -1;
365 ZipFileInputStream in = null;
366 Source zsrc = res.zsrc;
367 Set<InputStream> istreams = res.istreams;
368 synchronized (this) {
369 ensureOpen();
370 if (Objects.equals(lastEntryName, entry.name)) {
371 pos = lastEntryPos;
372 } else if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
373 pos = zsrc.getEntryPos(zc.getBytesUTF8(entry.name), false);
374 } else {
375 pos = zsrc.getEntryPos(zc.getBytes(entry.name), false);
376 }
377 if (pos == -1) {
378 return null;
379 }
380 in = new ZipFileInputStream(zsrc.cen, pos);
381 switch (CENHOW(zsrc.cen, pos)) {
382 case STORED:
383 synchronized (istreams) {
384 istreams.add(in);
385 }
386 return in;
387 case DEFLATED:
388 // Inflater likes a bit of slack
389 // MORE: Compute good size for inflater stream:
390 long size = CENLEN(zsrc.cen, pos) + 2;
391 if (size > 65536) {
392 size = 8192;
393 }
394 if (size <= 0) {
395 size = 4096;
396 }
397 InputStream is = new ZipFileInflaterInputStream(in, res, (int)size);
398 synchronized (istreams) {
399 istreams.add(is);
400 }
401 return is;
402 default:
403 throw new ZipException("invalid compression method");
404 }
405 }
406 }
407
408 private class ZipFileInflaterInputStream extends InflaterInputStream {
409 private volatile boolean closeRequested;
410 private boolean eof = false;
411 private final Cleanable cleanable;
412
413 ZipFileInflaterInputStream(ZipFileInputStream zfin,
414 CleanableResource res, int size) {
415 this(zfin, res, res.getInflater(), size);
416 }
417
418 private ZipFileInflaterInputStream(ZipFileInputStream zfin,
419 CleanableResource res,
420 Inflater inf, int size) {
421 super(zfin, inf, size);
422 this.cleanable = CleanerFactory.cleaner().register(this,
423 () -> res.releaseInflater(inf));
424 }
425
426 public void close() throws IOException {
427 if (closeRequested)
428 return;
429 closeRequested = true;
430 super.close();
431 synchronized (res.istreams) {
432 res.istreams.remove(this);
433 }
434 cleanable.clean();
435 }
436
437 // Override fill() method to provide an extra "dummy" byte
438 // at the end of the input stream. This is required when
439 // using the "nowrap" Inflater option.
440 protected void fill() throws IOException {
441 if (eof) {
442 throw new EOFException("Unexpected end of ZLIB input stream");
443 }
444 len = in.read(buf, 0, buf.length);
445 if (len == -1) {
446 buf[0] = 0;
447 len = 1;
448 eof = true;
449 }
450 inf.setInput(buf, 0, len);
451 }
452
453 public int available() throws IOException {
454 if (closeRequested)
455 return 0;
456 long avail = ((ZipFileInputStream)in).size() - inf.getBytesWritten();
457 return (avail > (long) Integer.MAX_VALUE ?
458 Integer.MAX_VALUE : (int) avail);
459 }
460 }
461
462 /**
463 * Returns the path name of the ZIP file.
464 * @return the path name of the ZIP file
465 */
466 public String getName() {
467 return name;
468 }
469
470 private class ZipEntryIterator<T extends ZipEntry>
471 implements Enumeration<T>, Iterator<T> {
472
473 private int i = 0;
474 private final int entryCount;
475 private final Function<String, T> gen;
476
477 public ZipEntryIterator(int entryCount, Function<String, T> gen) {
478 this.entryCount = entryCount;
479 this.gen = gen;
480 }
481
486
487 @Override
488 public boolean hasNext() {
489 return i < entryCount;
490 }
491
492 @Override
493 public T nextElement() {
494 return next();
495 }
496
497 @Override
498 @SuppressWarnings("unchecked")
499 public T next() {
500 synchronized (ZipFile.this) {
501 ensureOpen();
502 if (!hasNext()) {
503 throw new NoSuchElementException();
504 }
505 // each "entry" has 3 ints in table entries
506 return (T)getZipEntry(null, null, res.zsrc.getEntryPos(i++ * 3), gen);
507 }
508 }
509
510 @Override
511 public Iterator<T> asIterator() {
512 return this;
513 }
514 }
515
516 /**
517 * Returns an enumeration of the ZIP file entries.
518 * @return an enumeration of the ZIP file entries
519 * @throws IllegalStateException if the zip file has been closed
520 */
521 public Enumeration<? extends ZipEntry> entries() {
522 synchronized (this) {
523 ensureOpen();
524 return new ZipEntryIterator<ZipEntry>(res.zsrc.total, ZipEntry::new);
525 }
526 }
527
528 private Enumeration<JarEntry> entries(Function<String, JarEntry> func) {
529 synchronized (this) {
530 ensureOpen();
531 return new ZipEntryIterator<JarEntry>(res.zsrc.total, func);
532 }
533 }
534
535 private class EntrySpliterator<T> extends Spliterators.AbstractSpliterator<T> {
536 private int index;
537 private final int fence;
538 private final IntFunction<T> gen;
539
540 EntrySpliterator(int index, int fence, IntFunction<T> gen) {
541 super((long)fence,
542 Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE |
543 Spliterator.NONNULL);
544 this.index = index;
545 this.fence = fence;
546 this.gen = gen;
547 }
548
549 @Override
550 public boolean tryAdvance(Consumer<? super T> action) {
551 if (action == null)
552 throw new NullPointerException();
553 if (index >= 0 && index < fence) {
554 synchronized (ZipFile.this) {
555 ensureOpen();
556 action.accept(gen.apply(res.zsrc.getEntryPos(index++ * 3)));
557 }
558 return true;
559 }
560 return false;
561 }
562 }
563
564 /**
565 * Returns an ordered {@code Stream} over the ZIP file entries.
566 *
567 * Entries appear in the {@code Stream} in the order they appear in
568 * the central directory of the ZIP file.
569 *
570 * @return an ordered {@code Stream} of entries in this ZIP file
571 * @throws IllegalStateException if the zip file has been closed
572 * @since 1.8
573 */
574 public Stream<? extends ZipEntry> stream() {
575 synchronized (this) {
576 ensureOpen();
577 return StreamSupport.stream(new EntrySpliterator<>(0, res.zsrc.total,
578 pos -> getZipEntry(null, null, pos, ZipEntry::new)), false);
579 }
580 }
581
582 private String getEntryName(int pos) {
583 byte[] cen = res.zsrc.cen;
584 int nlen = CENNAM(cen, pos);
585 int clen = CENCOM(cen, pos);
586 int flag = CENFLG(cen, pos);
587 if (!zc.isUTF8() && (flag & EFS) != 0) {
588 return zc.toStringUTF8(cen, pos + CENHDR, nlen);
589 } else {
590 return zc.toString(cen, pos + CENHDR, nlen);
591 }
592 }
593
594 /*
595 * Returns an ordered {@code Stream} over the zip file entry names.
596 *
597 * Entry names appear in the {@code Stream} in the order they appear in
598 * the central directory of the ZIP file.
599 *
600 * @return an ordered {@code Stream} of entry names in this zip file
601 * @throws IllegalStateException if the zip file has been closed
602 * @since 10
603 */
604 private Stream<String> entryNameStream() {
605 synchronized (this) {
606 ensureOpen();
607 return StreamSupport.stream(
608 new EntrySpliterator<>(0, res.zsrc.total, this::getEntryName), false);
609 }
610 }
611
612 /*
613 * Returns an ordered {@code Stream} over the zip file entries.
614 *
615 * Entries appear in the {@code Stream} in the order they appear in
616 * the central directory of the jar file.
617 *
618 * @param func the function that creates the returned entry
619 * @return an ordered {@code Stream} of entries in this zip file
620 * @throws IllegalStateException if the zip file has been closed
621 * @since 10
622 */
623 private Stream<JarEntry> stream(Function<String, JarEntry> func) {
624 synchronized (this) {
625 ensureOpen();
626 return StreamSupport.stream(new EntrySpliterator<>(0, res.zsrc.total,
627 pos -> (JarEntry)getZipEntry(null, null, pos, func)), false);
628 }
629 }
630
631 private String lastEntryName;
632 private int lastEntryPos;
633
634 /* Checks ensureOpen() before invoke this method */
635 private ZipEntry getZipEntry(String name, byte[] bname, int pos,
636 Function<String, ? extends ZipEntry> func) {
637 byte[] cen = res.zsrc.cen;
638 int nlen = CENNAM(cen, pos);
639 int elen = CENEXT(cen, pos);
640 int clen = CENCOM(cen, pos);
641 int flag = CENFLG(cen, pos);
642 if (name == null || bname.length != nlen) {
643 // to use the entry name stored in cen, if the passed in name is
644 // (1) null, invoked from iterator, or
645 // (2) not equal to the name stored, a slash is appended during
646 // getEntryPos() search.
647 if (!zc.isUTF8() && (flag & EFS) != 0) {
648 name = zc.toStringUTF8(cen, pos + CENHDR, nlen);
649 } else {
650 name = zc.toString(cen, pos + CENHDR, nlen);
651 }
652 }
653 ZipEntry e = func.apply(name); //ZipEntry e = new ZipEntry(name);
654 e.flag = flag;
655 e.xdostime = CENTIM(cen, pos);
656 e.crc = CENCRC(cen, pos);
657 e.size = CENLEN(cen, pos);
666 if (!zc.isUTF8() && (flag & EFS) != 0) {
667 e.comment = zc.toStringUTF8(cen, start, clen);
668 } else {
669 e.comment = zc.toString(cen, start, clen);
670 }
671 }
672 lastEntryName = e.name;
673 lastEntryPos = pos;
674 return e;
675 }
676
677 /**
678 * Returns the number of entries in the ZIP file.
679 *
680 * @return the number of entries in the ZIP file
681 * @throws IllegalStateException if the zip file has been closed
682 */
683 public int size() {
684 synchronized (this) {
685 ensureOpen();
686 return res.zsrc.total;
687 }
688 }
689
690 private static class CleanableResource implements Runnable {
691 // The outstanding inputstreams that need to be closed
692 final Set<InputStream> istreams;
693
694 // List of cached Inflater objects for decompression
695 Deque<Inflater> inflaterCache;
696
697 final Cleanable cleanable;
698
699 Source zsrc;
700
701 CleanableResource(ZipFile zf, File file, int mode) throws IOException {
702 this.cleanable = CleanerFactory.cleaner().register(zf, this);
703 this.istreams = Collections.newSetFromMap(new WeakHashMap<>());
704 this.inflaterCache = new ArrayDeque<>();
705 this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0);
706 }
707
708 void clean() {
709 cleanable.clean();
710 }
711
712 /*
713 * Gets an inflater from the list of available inflaters or allocates
714 * a new one.
715 */
716 Inflater getInflater() {
717 Inflater inf;
718 synchronized (inflaterCache) {
719 if ((inf = inflaterCache.poll()) != null) {
720 return inf;
721 }
722 }
723 return new Inflater(true);
724 }
725
726 /*
727 * Releases the specified inflater to the list of available inflaters.
728 */
729 void releaseInflater(Inflater inf) {
730 Deque<Inflater> inflaters = this.inflaterCache;
731 if (inflaters != null) {
732 synchronized (inflaters) {
733 // double checked!
734 if (inflaters == this.inflaterCache) {
735 inf.reset();
736 inflaters.add(inf);
737 return;
738 }
739 }
740 }
741 // inflaters cache already closed - just end it.
742 inf.end();
743 }
744
745 public void run() {
746 IOException ioe = null;
747
748 // Release cached inflaters and close the cache first
749 Deque<Inflater> inflaters = this.inflaterCache;
750 if (inflaters != null) {
751 synchronized (inflaters) {
752 // no need to double-check as only one thread gets a
753 // chance to execute run() (Cleaner guarantee)...
754 Inflater inf;
755 while ((inf = inflaters.poll()) != null) {
756 inf.end();
757 }
758 // close inflaters cache
759 this.inflaterCache = null;
760 }
761 }
762
763 // Close streams, release their inflaters
764 if (istreams != null) {
765 synchronized (istreams) {
766 if (!istreams.isEmpty()) {
767 InputStream[] copy = istreams.toArray(new InputStream[0]);
768 istreams.clear();
769 for (InputStream is : copy) {
770 try {
771 is.close();
772 } catch (IOException e) {
773 if (ioe == null) ioe = e;
774 else ioe.addSuppressed(e);
775 }
776 }
777 }
778 }
779 }
780
781 // Release zip src
782 if (zsrc != null) {
783 synchronized (zsrc) {
784 try {
785 Source.release(zsrc);
786 zsrc = null;
787 } catch (IOException e) {
788 if (ioe == null) ioe = e;
789 else ioe.addSuppressed(e);
790 }
791 }
792 }
793 if (ioe != null) {
794 throw new UncheckedIOException(ioe);
795 }
796 }
797
798 CleanableResource(File file, int mode)
799 throws IOException {
800 this.cleanable = null;
801 this.istreams = Collections.newSetFromMap(new WeakHashMap<>());
802 this.inflaterCache = new ArrayDeque<>();
803 this.zsrc = Source.get(file, (mode & OPEN_DELETE) != 0);
804 }
805
806 /*
807 * If {@code ZipFile} has been subclassed and the {@code close} method is
808 * overridden, uses the {@code finalizer} mechanism for resource cleanup.
809 * So {@code close} method can be called when the the {@code ZipFile} is
810 * unreachable. This mechanism will be removed when {@code finalize} method
811 * is removed from {@code ZipFile}.
812 */
813 static CleanableResource get(ZipFile zf, File file, int mode)
814 throws IOException {
815 Class<?> clz = zf.getClass();
816 while (clz != ZipFile.class) {
817 try {
818 clz.getDeclaredMethod("close");
819 return new FinalizableResource(zf, file, mode);
820 } catch (NoSuchMethodException nsme) {}
821 clz = clz.getSuperclass();
822 }
823 return new CleanableResource(zf, file, mode);
824 }
825
826 static class FinalizableResource extends CleanableResource {
827 ZipFile zf;
828 FinalizableResource(ZipFile zf, File file, int mode)
829 throws IOException {
830 super(file, mode);
831 this.zf = zf;
832 }
833
834 @Override
835 void clean() {
836 run();
837 }
838
839 @Override
840 @SuppressWarnings("deprecation")
841 protected void finalize() throws IOException {
842 zf.close();
843 }
844 }
845 }
846
847 /**
848 * Closes the ZIP file.
849 *
850 * <p> Closing this ZIP file will close all of the input streams
851 * previously returned by invocations of the {@link #getInputStream
852 * getInputStream} method.
853 *
854 * @throws IOException if an I/O error has occurred
855 */
856 public void close() throws IOException {
857 if (closeRequested) {
858 return;
859 }
860 closeRequested = true;
861
862 synchronized (this) {
863 // Close streams, release their inflaters, release cached inflaters
864 // and release zip source
865 try {
866 res.clean();
867 } catch (UncheckedIOException ioe) {
868 throw ioe.getCause();
869 }
870 }
871 }
872
873 /**
874 * Ensures that the system resources held by this ZipFile object are
875 * released when there are no more references to it.
876 *
877 * @implSpec
878 * If this {@code ZipFile} has been subclassed and the {@code close} method
879 * has been overridden, the {@code close} method will be called when the
880 * {@code ZipFile} is unreachable.
881 *
882 * @deprecated The {@code finalize} method has been deprecated and
883 * implemented as a no-op. Subclasses that override {@code finalize}
884 * in order to perform cleanup should be modified to use alternative
885 * cleanup mechanisms and to remove the overriding {@code finalize}
886 * method. The recommended cleanup for ZipFile object is to explicitly
887 * invoke {@code close} method when it is no longer in use, or use
888 * try-with-resources. If the {@code close} is not invoked explicitly
889 * the resources held by this object will be released when the instance
890 * becomes phantom-reachable.
891 *
892 * @throws IOException if an I/O error has occurred
893 */
894 @Deprecated(since="9", forRemoval=true)
895 protected void finalize() throws IOException {}
896
897 private void ensureOpen() {
898 if (closeRequested) {
899 throw new IllegalStateException("zip file closed");
900 }
901 if (res.zsrc == null) {
902 throw new IllegalStateException("The object is not initialized.");
903 }
904 }
905
906 private void ensureOpenOrZipException() throws IOException {
907 if (closeRequested) {
908 throw new ZipException("ZipFile closed");
909 }
910 }
911
912 /*
913 * Inner class implementing the input stream used to read a
914 * (possibly compressed) zip file entry.
915 */
916 private class ZipFileInputStream extends InputStream {
917 private volatile boolean closeRequested;
918 private long pos; // current position within entry data
919 protected long rem; // number of remaining bytes within entry
920 protected long size; // uncompressed size of this entry
921
922 ZipFileInputStream(byte[] cen, int cenpos) {
923 rem = CENSIZ(cen, cenpos);
924 size = CENLEN(cen, cenpos);
925 pos = CENOFF(cen, cenpos);
926 // zip64
927 if (rem == ZIP64_MAGICVAL || size == ZIP64_MAGICVAL ||
928 pos == ZIP64_MAGICVAL) {
929 checkZIP64(cen, cenpos);
930 }
931 // negative for lazy initialization, see getDataOffset();
932 pos = - (pos + ZipFile.this.res.zsrc.locpos);
933 }
934
935 private void checkZIP64(byte[] cen, int cenpos) {
936 int off = cenpos + CENHDR + CENNAM(cen, cenpos);
937 int end = off + CENEXT(cen, cenpos);
938 while (off + 4 < end) {
939 int tag = get16(cen, off);
940 int sz = get16(cen, off + 2);
941 off += 4;
942 if (off + sz > end) // invalid data
943 break;
944 if (tag == EXTID_ZIP64) {
945 if (size == ZIP64_MAGICVAL) {
946 if (sz < 8 || (off + 8) > end)
947 break;
948 size = get64(cen, off);
949 sz -= 8;
950 off += 8;
951 }
952 if (rem == ZIP64_MAGICVAL) {
953 if (sz < 8 || (off + 8) > end)
954 break;
955 rem = get64(cen, off);
961 break;
962 pos = get64(cen, off);
963 sz -= 8;
964 off += 8;
965 }
966 break;
967 }
968 off += sz;
969 }
970 }
971
972 /* The Zip file spec explicitly allows the LOC extra data size to
973 * be different from the CEN extra data size. Since we cannot trust
974 * the CEN extra data size, we need to read the LOC to determine
975 * the entry data offset.
976 */
977 private long initDataOffset() throws IOException {
978 if (pos <= 0) {
979 byte[] loc = new byte[LOCHDR];
980 pos = -pos;
981 int len = ZipFile.this.res.zsrc.readFullyAt(loc, 0, loc.length, pos);
982 if (len != LOCHDR) {
983 throw new ZipException("ZipFile error reading zip file");
984 }
985 if (LOCSIG(loc) != LOCSIG) {
986 throw new ZipException("ZipFile invalid LOC header (bad signature)");
987 }
988 pos += LOCHDR + LOCNAM(loc) + LOCEXT(loc);
989 }
990 return pos;
991 }
992
993 public int read(byte b[], int off, int len) throws IOException {
994 synchronized (ZipFile.this) {
995 ensureOpenOrZipException();
996 initDataOffset();
997 if (rem == 0) {
998 return -1;
999 }
1000 if (len > rem) {
1001 len = (int) rem;
1002 }
1003 if (len <= 0) {
1004 return 0;
1005 }
1006 len = ZipFile.this.res.zsrc.readAt(b, off, len, pos);
1007 if (len > 0) {
1008 pos += len;
1009 rem -= len;
1010 }
1011 }
1012 if (rem == 0) {
1013 close();
1014 }
1015 return len;
1016 }
1017
1018 public int read() throws IOException {
1019 byte[] b = new byte[1];
1020 if (read(b, 0, 1) == 1) {
1021 return b[0] & 0xff;
1022 } else {
1023 return -1;
1024 }
1025 }
1026
1036 if (rem == 0) {
1037 close();
1038 }
1039 return n;
1040 }
1041
1042 public int available() {
1043 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1044 }
1045
1046 public long size() {
1047 return size;
1048 }
1049
1050 public void close() {
1051 if (closeRequested) {
1052 return;
1053 }
1054 closeRequested = true;
1055 rem = 0;
1056 synchronized (res.istreams) {
1057 res.istreams.remove(this);
1058 }
1059 }
1060
1061 }
1062
1063 /**
1064 * Returns the names of all non-directory entries that begin with
1065 * "META-INF/" (case ignored). This method is used in JarFile, via
1066 * SharedSecrets, as an optimization when looking up manifest and
1067 * signature file entries. Returns null if no entries were found.
1068 */
1069 private String[] getMetaInfEntryNames() {
1070 synchronized (this) {
1071 ensureOpen();
1072 Source zsrc = res.zsrc;
1073 if (zsrc.metanames == null) {
1074 return null;
1075 }
1076 String[] names = new String[zsrc.metanames.length];
1077 byte[] cen = zsrc.cen;
1078 for (int i = 0; i < names.length; i++) {
1079 int pos = zsrc.metanames[i];
1080 names[i] = new String(cen, pos + CENHDR, CENNAM(cen, pos),
1081 StandardCharsets.UTF_8);
1082 }
1083 return names;
1084 }
1085 }
1086
1087 private static boolean isWindows;
1088 static {
1089 SharedSecrets.setJavaUtilZipFileAccess(
1090 new JavaUtilZipFileAccess() {
1091 @Override
1092 public boolean startsWithLocHeader(ZipFile zip) {
1093 return zip.res.zsrc.startsWithLoc;
1094 }
1095 @Override
1096 public String[] getMetaInfEntryNames(ZipFile zip) {
1097 return zip.getMetaInfEntryNames();
1098 }
1099 @Override
1100 public JarEntry getEntry(ZipFile zip, String name,
1101 Function<String, JarEntry> func) {
1102 return (JarEntry)zip.getEntry(name, func);
1103 }
1104 @Override
1105 public Enumeration<JarEntry> entries(ZipFile zip,
1106 Function<String, JarEntry> func) {
1107 return zip.entries(func);
1108 }
1109 @Override
1110 public Stream<JarEntry> stream(ZipFile zip,
1111 Function<String, JarEntry> func) {
1112 return zip.stream(func);
1113 }
1181
1182 public boolean equals(Object obj) {
1183 if (obj instanceof Key) {
1184 Key key = (Key)obj;
1185 if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) {
1186 return false;
1187 }
1188 Object fk = attrs.fileKey();
1189 if (fk != null) {
1190 return fk.equals(key.attrs.fileKey());
1191 } else {
1192 return file.equals(key.file);
1193 }
1194 }
1195 return false;
1196 }
1197 }
1198 private static final HashMap<Key, Source> files = new HashMap<>();
1199
1200
1201 static Source get(File file, boolean toDelete) throws IOException {
1202 Key key = new Key(file,
1203 Files.readAttributes(file.toPath(), BasicFileAttributes.class));
1204 Source src = null;
1205 synchronized (files) {
1206 src = files.get(key);
1207 if (src != null) {
1208 src.refs++;
1209 return src;
1210 }
1211 }
1212 src = new Source(key, toDelete);
1213
1214 synchronized (files) {
1215 if (files.containsKey(key)) { // someone else put in first
1216 src.close(); // close the newly created one
1217 src = files.get(key);
1218 src.refs++;
1219 return src;
1220 }
1221 files.put(key, src);
1222 return src;
1223 }
1224 }
1225
1226 static void release(Source src) throws IOException {
1227 synchronized (files) {
1228 if (src != null && --src.refs == 0) {
1229 files.remove(src.key);
1230 src.close();
1231 }
1232 }
1233 }
1234
1235 private Source(Key key, boolean toDelete) throws IOException {
1236 this.key = key;
1237 if (toDelete) {
1238 if (isWindows) {
1239 this.zfile = SharedSecrets.getJavaIORandomAccessFileAccess()
1240 .openAndDelete(key.file, "r");
1241 } else {
1242 this.zfile = new RandomAccessFile(key.file, "r");
1243 key.file.delete();
1244 }
1245 } else {
1246 this.zfile = new RandomAccessFile(key.file, "r");
1247 }
1248 try {
|