8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package java.util.zip;
27
28 import java.io.Closeable;
29 import java.io.InputStream;
30 import java.io.IOException;
31 import java.io.EOFException;
32 import java.io.File;
33 import java.nio.charset.Charset;
34 import java.nio.charset.StandardCharsets;
35 import java.util.ArrayDeque;
36 import java.util.Deque;
37 import java.util.Enumeration;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.Map;
41 import java.util.NoSuchElementException;
42 import java.util.Spliterator;
43 import java.util.Spliterators;
44 import java.util.WeakHashMap;
45 import java.util.stream.Stream;
46 import java.util.stream.StreamSupport;
47
50 /**
51 * This class is used to read entries from a zip file.
52 *
53 * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
54 * or method in this class will cause a {@link NullPointerException} to be
55 * thrown.
56 *
57 * @author David Connelly
58 */
59 public
60 class ZipFile implements ZipConstants, Closeable {
61 private long jzfile; // address of jzfile data
62 private final String name; // zip file name
63 private final int total; // total number of entries
64 private final boolean locsig; // if zip file starts with LOCSIG (usually true)
65 private volatile boolean closeRequested = false;
66
67 private static final int STORED = ZipEntry.STORED;
68 private static final int DEFLATED = ZipEntry.DEFLATED;
69
70 /**
71 * Mode flag to open a zip file for reading.
72 */
73 public static final int OPEN_READ = 0x1;
74
75 /**
76 * Mode flag to open a zip file and mark it for deletion. The file will be
77 * deleted some time between the moment that it is opened and the moment
78 * that it is closed, but its contents will remain accessible via the
79 * <tt>ZipFile</tt> object until either the close method is invoked or the
80 * virtual machine exits.
81 */
82 public static final int OPEN_DELETE = 0x4;
83
84 static {
85 /* Zip library is loaded from System.initializeSystemClass */
86 initIDs();
87 }
88
89 private static native void initIDs();
328 private final Map<InputStream, Inflater> streams = new WeakHashMap<>();
329
330 /**
331 * Returns an input stream for reading the contents of the specified
332 * zip file entry.
333 *
334 * <p> Closing this ZIP file will, in turn, close all input
335 * streams that have been returned by invocations of this method.
336 *
337 * @param entry the zip file entry
338 * @return the input stream for reading the contents of the specified
339 * zip file entry.
340 * @throws ZipException if a ZIP format error has occurred
341 * @throws IOException if an I/O error has occurred
342 * @throws IllegalStateException if the zip file has been closed
343 */
344 public InputStream getInputStream(ZipEntry entry) throws IOException {
345 if (entry == null) {
346 throw new NullPointerException("entry");
347 }
348 long jzentry = 0;
349 ZipFileInputStream in = null;
350 synchronized (this) {
351 ensureOpen();
352 if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
353 jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false);
354 } else {
355 jzentry = getEntry(jzfile, zc.getBytes(entry.name), false);
356 }
357 if (jzentry == 0) {
358 return null;
359 }
360 in = new ZipFileInputStream(jzentry);
361
362 switch (getEntryMethod(jzentry)) {
363 case STORED:
364 synchronized (streams) {
365 streams.put(in, null);
366 }
367 return in;
368 case DEFLATED:
369 // MORE: Compute good size for inflater stream:
370 long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack
371 if (size > 65536) size = 8192;
372 if (size <= 0) size = 4096;
373 Inflater inf = getInflater();
374 InputStream is =
375 new ZipFileInflaterInputStream(in, inf, (int)size);
376 synchronized (streams) {
377 streams.put(is, inf);
378 }
379 return is;
380 default:
381 throw new ZipException("invalid compression method");
382 }
383 }
384 }
385
386 private class ZipFileInflaterInputStream extends InflaterInputStream {
387 private volatile boolean closeRequested = false;
388 private boolean eof = false;
389 private final ZipFileInputStream zfin;
390
391 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf,
392 int size) {
393 super(zfin, inf, size);
394 this.zfin = zfin;
395 }
396
397 public void close() throws IOException {
398 if (closeRequested)
399 return;
400 closeRequested = true;
401
402 super.close();
403 Inflater inf;
404 synchronized (streams) {
452 }
453 }
454 return new Inflater(true);
455 }
456
457 /*
458 * Releases the specified inflater to the list of available inflaters.
459 */
460 private void releaseInflater(Inflater inf) {
461 if (false == inf.ended()) {
462 inf.reset();
463 synchronized (inflaterCache) {
464 inflaterCache.add(inf);
465 }
466 }
467 }
468
469 // List of available Inflater objects for decompression
470 private Deque<Inflater> inflaterCache = new ArrayDeque<>();
471
472 /**
473 * Returns the path name of the ZIP file.
474 * @return the path name of the ZIP file
475 */
476 public String getName() {
477 return name;
478 }
479
480 private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> {
481 private int i = 0;
482
483 public ZipEntryIterator() {
484 ensureOpen();
485 }
486
487 public boolean hasMoreElements() {
488 return hasNext();
489 }
490
491 public boolean hasNext() {
674 }
675 }
676
677 private void ensureOpenOrZipException() throws IOException {
678 if (closeRequested) {
679 throw new ZipException("ZipFile closed");
680 }
681 }
682
683 /*
684 * Inner class implementing the input stream used to read a
685 * (possibly compressed) zip file entry.
686 */
687 private class ZipFileInputStream extends InputStream {
688 private volatile boolean closeRequested = false;
689 protected long jzentry; // address of jzentry data
690 private long pos; // current position within entry data
691 protected long rem; // number of remaining bytes within entry
692 protected long size; // uncompressed size of this entry
693
694 ZipFileInputStream(long jzentry) {
695 pos = 0;
696 rem = getEntryCSize(jzentry);
697 size = getEntrySize(jzentry);
698 this.jzentry = jzentry;
699 }
700
701 public int read(byte b[], int off, int len) throws IOException {
702 synchronized (ZipFile.this) {
703 long rem = this.rem;
704 long pos = this.pos;
705 if (rem == 0) {
706 return -1;
707 }
708 if (len <= 0) {
709 return 0;
710 }
711 if (len > rem) {
712 len = (int) rem;
713 }
714
715 ensureOpenOrZipException();
716 len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b,
717 off, len);
762 rem = 0;
763 synchronized (ZipFile.this) {
764 if (jzentry != 0 && ZipFile.this.jzfile != 0) {
765 freeEntry(ZipFile.this.jzfile, jzentry);
766 jzentry = 0;
767 }
768 }
769 synchronized (streams) {
770 streams.remove(this);
771 }
772 }
773
774 protected void finalize() {
775 close();
776 }
777 }
778
779 static {
780 sun.misc.SharedSecrets.setJavaUtilZipFileAccess(
781 new sun.misc.JavaUtilZipFileAccess() {
782 public boolean startsWithLocHeader(ZipFile zip) {
783 return zip.startsWithLocHeader();
784 }
785 }
786 );
787 }
788
789 /**
790 * Returns {@code true} if, and only if, the zip file begins with {@code
791 * LOCSIG}.
792 */
793 private boolean startsWithLocHeader() {
794 return locsig;
795 }
796
797 private static native long open(String name, int mode, long lastModified,
798 boolean usemmap) throws IOException;
799 private static native int getTotal(long jzfile);
800 private static native boolean startsWithLOC(long jzfile);
801 private static native int read(long jzfile, long jzentry,
802 long pos, byte[] b, int off, int len);
803
|
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package java.util.zip;
27
28 import java.io.ByteArrayInputStream;
29 import java.io.Closeable;
30 import java.io.InputStream;
31 import java.io.IOException;
32 import java.io.EOFException;
33 import java.io.File;
34 import java.nio.charset.Charset;
35 import java.nio.charset.StandardCharsets;
36 import java.util.ArrayDeque;
37 import java.util.Deque;
38 import java.util.Enumeration;
39 import java.util.HashMap;
40 import java.util.Iterator;
41 import java.util.Map;
42 import java.util.NoSuchElementException;
43 import java.util.Spliterator;
44 import java.util.Spliterators;
45 import java.util.WeakHashMap;
46 import java.util.stream.Stream;
47 import java.util.stream.StreamSupport;
48
51 /**
52 * This class is used to read entries from a zip file.
53 *
54 * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
55 * or method in this class will cause a {@link NullPointerException} to be
56 * thrown.
57 *
58 * @author David Connelly
59 */
60 public
61 class ZipFile implements ZipConstants, Closeable {
62 private long jzfile; // address of jzfile data
63 private final String name; // zip file name
64 private final int total; // total number of entries
65 private final boolean locsig; // if zip file starts with LOCSIG (usually true)
66 private volatile boolean closeRequested = false;
67
68 private static final int STORED = ZipEntry.STORED;
69 private static final int DEFLATED = ZipEntry.DEFLATED;
70
71 // Max buffer size when returning bytebuffers directly
72 private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
73
74 /**
75 * Mode flag to open a zip file for reading.
76 */
77 public static final int OPEN_READ = 0x1;
78
79 /**
80 * Mode flag to open a zip file and mark it for deletion. The file will be
81 * deleted some time between the moment that it is opened and the moment
82 * that it is closed, but its contents will remain accessible via the
83 * <tt>ZipFile</tt> object until either the close method is invoked or the
84 * virtual machine exits.
85 */
86 public static final int OPEN_DELETE = 0x4;
87
88 static {
89 /* Zip library is loaded from System.initializeSystemClass */
90 initIDs();
91 }
92
93 private static native void initIDs();
332 private final Map<InputStream, Inflater> streams = new WeakHashMap<>();
333
334 /**
335 * Returns an input stream for reading the contents of the specified
336 * zip file entry.
337 *
338 * <p> Closing this ZIP file will, in turn, close all input
339 * streams that have been returned by invocations of this method.
340 *
341 * @param entry the zip file entry
342 * @return the input stream for reading the contents of the specified
343 * zip file entry.
344 * @throws ZipException if a ZIP format error has occurred
345 * @throws IOException if an I/O error has occurred
346 * @throws IllegalStateException if the zip file has been closed
347 */
348 public InputStream getInputStream(ZipEntry entry) throws IOException {
349 if (entry == null) {
350 throw new NullPointerException("entry");
351 }
352
353 long jzentry, csize, size;
354 int cmethod;
355 synchronized (this) {
356 ensureOpen();
357 if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
358 jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false);
359 } else {
360 jzentry = getEntry(jzfile, zc.getBytes(entry.name), false);
361 }
362 if (jzentry == 0) {
363 return null;
364 }
365 size = getEntrySize(jzentry);
366 csize = getEntryCSize(jzentry);
367 cmethod = getEntryMethod(jzentry);
368 }
369
370 if (csize >= 0 && size > 0 && size < 128 * 1024) {
371 try {
372 return new ByteArrayInputStream(getBytes(jzentry, csize, size, cmethod));
373 } finally {
374 synchronized (this) {
375 if (jzfile != 0) {
376 freeEntry(jzfile, jzentry);
377 }
378 }
379 }
380 }
381
382 ZipFileInputStream in = new ZipFileInputStream(jzentry, csize, size);
383
384 switch (cmethod) {
385 case STORED:
386 synchronized (streams) {
387 streams.put(in, null);
388 }
389 return in;
390 case DEFLATED:
391 // MORE: Compute good size for inflater stream:
392 size += 2; // Inflater likes a bit of slack
393 if (size > 65536) size = 8192;
394 if (size <= 0) size = 4096;
395 Inflater inf = getInflater();
396 InputStream is =
397 new ZipFileInflaterInputStream(in, inf, (int)size);
398 synchronized (streams) {
399 streams.put(is, inf);
400 }
401 return is;
402 default:
403 throw new ZipException("invalid compression method");
404 }
405 }
406
407 private class ZipFileInflaterInputStream extends InflaterInputStream {
408 private volatile boolean closeRequested = false;
409 private boolean eof = false;
410 private final ZipFileInputStream zfin;
411
412 ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf,
413 int size) {
414 super(zfin, inf, size);
415 this.zfin = zfin;
416 }
417
418 public void close() throws IOException {
419 if (closeRequested)
420 return;
421 closeRequested = true;
422
423 super.close();
424 Inflater inf;
425 synchronized (streams) {
473 }
474 }
475 return new Inflater(true);
476 }
477
478 /*
479 * Releases the specified inflater to the list of available inflaters.
480 */
481 private void releaseInflater(Inflater inf) {
482 if (false == inf.ended()) {
483 inf.reset();
484 synchronized (inflaterCache) {
485 inflaterCache.add(inf);
486 }
487 }
488 }
489
490 // List of available Inflater objects for decompression
491 private Deque<Inflater> inflaterCache = new ArrayDeque<>();
492
493 /*
494 * Uncompress the zip entry into a new byte[].
495 *
496 * This method can only read entries smaller than 2GB, for larger entries
497 * use getInputStream.
498 *
499 * @param entry the zip file entry
500 *
501 * @return the byte[] with the deflated contents of the specified zip file entry.
502 *
503 * @throws ZipException if a ZIP format error has occurred
504 * @throws IOException if an I/O error has occurred
505 * @throws IllegalStateException if the zip file has been closed
506 * @throws OutOfMemory if the zip entry is larger than 2GB
507 */
508 private byte[] getBytes(ZipEntry entry) throws IOException {
509 if (entry == null) {
510 throw new NullPointerException("entry");
511 }
512
513 long jzentry, csize, size;
514 int cmethod;
515 synchronized (this) {
516 ensureOpen();
517 if (!zc.isUTF8() && (entry.flag & EFS) != 0) {
518 jzentry = getEntry(jzfile, zc.getBytesUTF8(entry.name), false);
519 } else {
520 jzentry = getEntry(jzfile, zc.getBytes(entry.name), false);
521 }
522 if (jzentry == 0) {
523 return null;
524 }
525
526 csize = getEntryCSize(jzentry);
527 size = getEntrySize(jzentry);
528 cmethod = getEntryMethod(jzentry);
529 }
530
531 try {
532 return getBytes(jzentry, csize, size, cmethod);
533 } finally {
534 synchronized (this) {
535 if (jzfile != 0) {
536 freeEntry(jzfile, jzentry);
537 }
538 }
539 }
540 }
541
542 private byte[] getBytes(long jzentry, long csize, long size, int cmethod)
543 throws IOException {
544
545 if (csize < 0 || size < 0) {
546 throw new ZipException("Unknown size of ZipEntry");
547 }
548 if (csize > MAX_BUFFER_SIZE || size > MAX_BUFFER_SIZE) {
549 throw new OutOfMemoryError("ZipEntry too large");
550 }
551
552 byte[] cbytes = new byte[(int) csize];
553 readCompressedBytes(jzentry, (int) csize, cbytes);
554 switch (cmethod) {
555 case STORED:
556 return cbytes;
557 case DEFLATED:
558 byte[] bytes = new byte[(int) size];
559 Inflater inf = getInflater();
560 try {
561 inf.setInput(cbytes);
562 inf.inflate(bytes);
563 } catch (DataFormatException e) {
564 String s = e.getMessage();
565 throw new ZipException(s != null ? s : "Invalid ZLIB data format");
566 } finally {
567 releaseInflater(inf);
568 }
569 return bytes;
570 default:
571 throw new ZipException("invalid compression method");
572 }
573 }
574
575 private int readCompressedBytes(long jzentry, int csize, byte[] buf)
576 throws IOException {
577 assert csize == buf.length;
578 synchronized (this) {
579 ensureOpenOrZipException();
580 int n = 0;
581 int nread = 0;
582 while (nread < csize
583 && (n = read(jzfile, jzentry, nread, buf, nread, csize - nread)) > 0) {
584 nread += n;
585 }
586 return nread;
587 }
588 }
589
590 /**
591 * Returns the path name of the ZIP file.
592 * @return the path name of the ZIP file
593 */
594 public String getName() {
595 return name;
596 }
597
598 private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> {
599 private int i = 0;
600
601 public ZipEntryIterator() {
602 ensureOpen();
603 }
604
605 public boolean hasMoreElements() {
606 return hasNext();
607 }
608
609 public boolean hasNext() {
792 }
793 }
794
795 private void ensureOpenOrZipException() throws IOException {
796 if (closeRequested) {
797 throw new ZipException("ZipFile closed");
798 }
799 }
800
801 /*
802 * Inner class implementing the input stream used to read a
803 * (possibly compressed) zip file entry.
804 */
805 private class ZipFileInputStream extends InputStream {
806 private volatile boolean closeRequested = false;
807 protected long jzentry; // address of jzentry data
808 private long pos; // current position within entry data
809 protected long rem; // number of remaining bytes within entry
810 protected long size; // uncompressed size of this entry
811
812 ZipFileInputStream(long jzentry, long csize, long size) {
813 pos = 0;
814 rem = csize;
815 this.size = size;
816 this.jzentry = jzentry;
817 }
818
819 public int read(byte b[], int off, int len) throws IOException {
820 synchronized (ZipFile.this) {
821 long rem = this.rem;
822 long pos = this.pos;
823 if (rem == 0) {
824 return -1;
825 }
826 if (len <= 0) {
827 return 0;
828 }
829 if (len > rem) {
830 len = (int) rem;
831 }
832
833 ensureOpenOrZipException();
834 len = ZipFile.read(ZipFile.this.jzfile, jzentry, pos, b,
835 off, len);
880 rem = 0;
881 synchronized (ZipFile.this) {
882 if (jzentry != 0 && ZipFile.this.jzfile != 0) {
883 freeEntry(ZipFile.this.jzfile, jzentry);
884 jzentry = 0;
885 }
886 }
887 synchronized (streams) {
888 streams.remove(this);
889 }
890 }
891
892 protected void finalize() {
893 close();
894 }
895 }
896
897 static {
898 sun.misc.SharedSecrets.setJavaUtilZipFileAccess(
899 new sun.misc.JavaUtilZipFileAccess() {
900 @Override
901 public boolean startsWithLocHeader(ZipFile zip) {
902 return zip.startsWithLocHeader();
903 }
904
905 @Override
906 public byte[] getBytes(ZipFile zip, ZipEntry entry)
907 throws IOException {
908 return zip.getBytes(entry);
909 }
910 }
911 );
912 }
913
914 /**
915 * Returns {@code true} if, and only if, the zip file begins with {@code
916 * LOCSIG}.
917 */
918 private boolean startsWithLocHeader() {
919 return locsig;
920 }
921
922 private static native long open(String name, int mode, long lastModified,
923 boolean usemmap) throws IOException;
924 private static native int getTotal(long jzfile);
925 private static native boolean startsWithLOC(long jzfile);
926 private static native int read(long jzfile, long jzentry,
927 long pos, byte[] b, int off, int len);
928
|