1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.compress.archivers.zip;
18
19 import java.io.BufferedInputStream;
20 import java.io.ByteArrayInputStream;
21 import java.io.Closeable;
22 import java.io.EOFException;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.SequenceInputStream;
27 import java.nio.ByteBuffer;
28 import java.nio.ByteOrder;
29 import java.nio.channels.FileChannel;
30 import java.nio.channels.SeekableByteChannel;
31 import java.nio.charset.Charset;
32 import java.nio.charset.StandardCharsets;
33 import java.nio.file.Files;
34 import java.nio.file.OpenOption;
35 import java.nio.file.Path;
36 import java.nio.file.StandardOpenOption;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.Comparator;
41 import java.util.EnumSet;
42 import java.util.Enumeration;
43 import java.util.HashMap;
44 import java.util.LinkedList;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Objects;
48 import java.util.stream.Collectors;
49 import java.util.stream.IntStream;
50 import java.util.zip.Inflater;
51 import java.util.zip.ZipException;
52
53 import org.apache.commons.compress.archivers.EntryStreamOffsets;
54 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
55 import org.apache.commons.compress.compressors.deflate64.Deflate64CompressorInputStream;
56 import org.apache.commons.compress.utils.BoundedArchiveInputStream;
57 import org.apache.commons.compress.utils.BoundedSeekableByteChannelInputStream;
58 import org.apache.commons.compress.utils.IOUtils;
59 import org.apache.commons.compress.utils.InputStreamStatistics;
60 import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
61 import org.apache.commons.io.Charsets;
62 import org.apache.commons.io.FilenameUtils;
63 import org.apache.commons.io.build.AbstractOrigin.ByteArrayOrigin;
64 import org.apache.commons.io.build.AbstractStreamBuilder;
65 import org.apache.commons.io.input.BoundedInputStream;
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 public class ZipFile implements Closeable {
90
91
92
93
94
95 private static class BoundedFileChannelInputStream extends BoundedArchiveInputStream {
96 private final FileChannel archive;
97
98 BoundedFileChannelInputStream(final long start, final long remaining, final FileChannel archive) {
99 super(start, remaining);
100 this.archive = archive;
101 }
102
103 @Override
104 protected int read(final long pos, final ByteBuffer buf) throws IOException {
105 final int read = archive.read(buf, pos);
106 buf.flip();
107 return read;
108 }
109 }
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129 public static class Builder extends AbstractStreamBuilder<ZipFile, Builder> {
130
131 static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
132
133 private SeekableByteChannel seekableByteChannel;
134 private boolean useUnicodeExtraFields = true;
135 private boolean ignoreLocalFileHeader;
136 private long maxNumberOfDisks = 1;
137
138 public Builder() {
139 setCharset(DEFAULT_CHARSET);
140 setCharsetDefault(DEFAULT_CHARSET);
141 }
142
143 @Override
144 public ZipFile get() throws IOException {
145 final SeekableByteChannel actualChannel;
146 final String actualDescription;
147 if (seekableByteChannel != null) {
148 actualChannel = seekableByteChannel;
149 actualDescription = actualChannel.getClass().getSimpleName();
150 } else if (checkOrigin() instanceof ByteArrayOrigin) {
151 actualChannel = new SeekableInMemoryByteChannel(checkOrigin().getByteArray());
152 actualDescription = actualChannel.getClass().getSimpleName();
153 } else {
154 OpenOption[] openOptions = getOpenOptions();
155 if (openOptions.length == 0) {
156 openOptions = new OpenOption[] { StandardOpenOption.READ };
157 }
158 final Path path = getPath();
159 actualChannel = openZipChannel(path, maxNumberOfDisks, openOptions);
160 actualDescription = path.toString();
161 }
162 final boolean closeOnError = seekableByteChannel != null;
163 return new ZipFile(actualChannel, actualDescription, getCharset(), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader);
164 }
165
166
167
168
169
170
171
172 public Builder setIgnoreLocalFileHeader(final boolean ignoreLocalFileHeader) {
173 this.ignoreLocalFileHeader = ignoreLocalFileHeader;
174 return this;
175 }
176
177
178
179
180
181
182
183
184 public Builder setMaxNumberOfDisks(final long maxNumberOfDisks) {
185 this.maxNumberOfDisks = maxNumberOfDisks;
186 return this;
187 }
188
189
190
191
192
193
194
195 public Builder setSeekableByteChannel(final SeekableByteChannel seekableByteChannel) {
196 this.seekableByteChannel = seekableByteChannel;
197 return this;
198 }
199
200
201
202
203
204
205
206 public Builder setUseUnicodeExtraFields(final boolean useUnicodeExtraFields) {
207 this.useUnicodeExtraFields = useUnicodeExtraFields;
208 return this;
209 }
210
211 }
212
213
214
215
216 private static final class Entry extends ZipArchiveEntry {
217
218 @Override
219 public boolean equals(final Object other) {
220 if (super.equals(other)) {
221
222 final Entry otherEntry = (Entry) other;
223 return getLocalHeaderOffset() == otherEntry.getLocalHeaderOffset() && super.getDataOffset() == otherEntry.getDataOffset()
224 && super.getDiskNumberStart() == otherEntry.getDiskNumberStart();
225 }
226 return false;
227 }
228
229 @Override
230 public int hashCode() {
231 return 3 * super.hashCode() + (int) getLocalHeaderOffset() + (int) (getLocalHeaderOffset() >> 32);
232 }
233 }
234
235 private static final class NameAndComment {
236 private final byte[] name;
237 private final byte[] comment;
238
239 private NameAndComment(final byte[] name, final byte[] comment) {
240 this.name = name;
241 this.comment = comment;
242 }
243 }
244
245 private static final class StoredStatisticsStream extends BoundedInputStream implements InputStreamStatistics {
246 StoredStatisticsStream(final InputStream in) {
247 super(in);
248 }
249
250 @Override
251 public long getCompressedCount() {
252 return super.getCount();
253 }
254
255 @Override
256 public long getUncompressedCount() {
257 return getCompressedCount();
258 }
259 }
260
261 private static final String DEFAULT_CHARSET_NAME = StandardCharsets.UTF_8.name();
262
263 private static final EnumSet<StandardOpenOption> READ = EnumSet.of(StandardOpenOption.READ);
264
265 private static final int HASH_SIZE = 509;
266 static final int NIBLET_MASK = 0x0f;
267 static final int BYTE_SHIFT = 8;
268 private static final int POS_0 = 0;
269 private static final int POS_1 = 1;
270 private static final int POS_2 = 2;
271 private static final int POS_3 = 3;
272 private static final byte[] ONE_ZERO_BYTE = new byte[1];
273
274
275
276
277 private static final int CFH_LEN =
278
279 ZipConstants.SHORT
280 + ZipConstants.SHORT
281 + ZipConstants.SHORT
282 + ZipConstants.SHORT
283 + ZipConstants.SHORT
284 + ZipConstants.SHORT
285 + ZipConstants.WORD
286 + ZipConstants.WORD
287 + ZipConstants.WORD
288 + ZipConstants. SHORT
289 + ZipConstants.SHORT
290 + ZipConstants.SHORT
291 + ZipConstants.SHORT
292 + ZipConstants.SHORT
293 + ZipConstants.WORD
294 + ZipConstants.WORD;
295
296
297 private static final long CFH_SIG = ZipLong.getValue(ZipArchiveOutputStream.CFH_SIG);
298
299
300
301
302 static final int MIN_EOCD_SIZE =
303
304 ZipConstants.WORD
305 + ZipConstants.SHORT
306
307 + ZipConstants.SHORT
308
309 + ZipConstants.SHORT
310
311 + ZipConstants.SHORT
312 + ZipConstants.WORD
313
314
315 + ZipConstants.WORD
316 + ZipConstants.SHORT;
317
318
319
320
321
322 private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
323
324 + ZipConstants.ZIP64_MAGIC_SHORT;
325
326
327
328
329
330
331 private static final int CFD_LENGTH_OFFSET =
332
333 ZipConstants.WORD
334 + ZipConstants.SHORT
335
336 + ZipConstants.SHORT
337
338 + ZipConstants.SHORT
339
340 + ZipConstants.SHORT;
341
342
343
344
345
346
347 private static final int CFD_DISK_OFFSET =
348
349 ZipConstants.WORD
350 + ZipConstants.SHORT;
351
352
353
354
355
356
357 private static final int CFD_LOCATOR_RELATIVE_OFFSET =
358
359
360 + ZipConstants.SHORT
361
362 + ZipConstants.SHORT
363 + ZipConstants.WORD;
364
365
366
367
368
369
370 private static final int ZIP64_EOCDL_LENGTH =
371
372 ZipConstants.WORD
373
374
375 + ZipConstants.WORD
376
377 + ZipConstants.DWORD
378 + ZipConstants.WORD;
379
380
381
382
383
384
385 private static final int ZIP64_EOCDL_LOCATOR_OFFSET =
386
387 ZipConstants.WORD
388
389
390 + ZipConstants.WORD;
391
392
393
394
395
396
397 private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET =
398
399
400 ZipConstants.WORD
401
402 + ZipConstants.DWORD
403 + ZipConstants.SHORT
404 + ZipConstants.SHORT
405 + ZipConstants.WORD
406
407 + ZipConstants.WORD
408
409 + ZipConstants.DWORD
410
411 + ZipConstants.DWORD
412 + ZipConstants.DWORD;
413
414
415
416
417
418
419 private static final int ZIP64_EOCD_CFD_DISK_OFFSET =
420
421
422 ZipConstants.WORD
423
424 + ZipConstants.DWORD
425 + ZipConstants.SHORT
426 + ZipConstants.SHORT
427 + ZipConstants.WORD;
428
429
430
431
432
433
434 private static final int ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET =
435
436
437 ZipConstants.DWORD
438
439 + ZipConstants.DWORD
440 + ZipConstants.DWORD;
441
442
443
444
445
446 private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
447
448 ZipConstants.WORD
449 + ZipConstants.SHORT
450 + ZipConstants.SHORT
451 + ZipConstants.SHORT
452 + ZipConstants.SHORT
453 + ZipConstants.SHORT
454 + ZipConstants.WORD
455 + ZipConstants.WORD
456 + (long) ZipConstants.WORD;
457
458
459
460
461
462
463
464
465
466
467 private static final Comparator<ZipArchiveEntry> offsetComparator = Comparator.comparingLong(ZipArchiveEntry::getDiskNumberStart)
468 .thenComparingLong(ZipArchiveEntry::getLocalHeaderOffset);
469
470
471
472
473
474
475
476 public static Builder builder() {
477 return new Builder();
478 }
479
480
481
482
483
484
485 public static void closeQuietly(final ZipFile zipFile) {
486 org.apache.commons.io.IOUtils.closeQuietly(zipFile);
487 }
488
489
490
491
492
493
494
495
496 private static SeekableByteChannel newReadByteChannel(final Path path) throws IOException {
497 return Files.newByteChannel(path, READ);
498 }
499
500 private static SeekableByteChannel openZipChannel(final Path path, final long maxNumberOfDisks, final OpenOption[] openOptions) throws IOException {
501 final FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
502 final List<FileChannel> channels = new ArrayList<>();
503 try {
504 final boolean is64 = positionAtEndOfCentralDirectoryRecord(channel);
505 long numberOfDisks;
506 if (is64) {
507 channel.position(channel.position() + ZipConstants.WORD + ZipConstants.WORD + ZipConstants.DWORD);
508 final ByteBuffer buf = ByteBuffer.allocate(ZipConstants.WORD);
509 buf.order(ByteOrder.LITTLE_ENDIAN);
510 IOUtils.readFully(channel, buf);
511 buf.flip();
512 numberOfDisks = buf.getInt() & 0xffffffffL;
513 } else {
514 channel.position(channel.position() + ZipConstants.WORD);
515 final ByteBuffer buf = ByteBuffer.allocate(ZipConstants.SHORT);
516 buf.order(ByteOrder.LITTLE_ENDIAN);
517 IOUtils.readFully(channel, buf);
518 buf.flip();
519 numberOfDisks = (buf.getShort() & 0xffff) + 1;
520 }
521 if (numberOfDisks > Math.min(maxNumberOfDisks, Integer.MAX_VALUE)) {
522 throw new IOException("Too many disks for zip archive, max=" + Math.min(maxNumberOfDisks, Integer.MAX_VALUE) + " actual=" + numberOfDisks);
523 }
524
525 if (numberOfDisks <= 1) {
526 return channel;
527 }
528 channel.close();
529
530 final Path parent = path.getParent();
531 final String basename = FilenameUtils.removeExtension(Objects.toString(path.getFileName(), null));
532
533 return ZipSplitReadOnlySeekableByteChannel.forPaths(IntStream.range(0, (int) numberOfDisks).mapToObj(i -> {
534 if (i == numberOfDisks - 1) {
535 return path;
536 }
537 final Path lowercase = parent.resolve(String.format("%s.z%02d", basename, i + 1));
538 if (Files.exists(lowercase)) {
539 return lowercase;
540 }
541 final Path uppercase = parent.resolve(String.format("%s.Z%02d", basename, i + 1));
542 if (Files.exists(uppercase)) {
543 return uppercase;
544 }
545 return lowercase;
546 }).collect(Collectors.toList()), openOptions);
547 } catch (final Throwable ex) {
548 org.apache.commons.io.IOUtils.closeQuietly(channel);
549 channels.forEach(org.apache.commons.io.IOUtils::closeQuietly);
550 throw ex;
551 }
552 }
553
554
555
556
557
558
559 private static boolean positionAtEndOfCentralDirectoryRecord(final SeekableByteChannel channel) throws IOException {
560 final boolean found = tryToLocateSignature(channel, MIN_EOCD_SIZE, MAX_EOCD_SIZE, ZipArchiveOutputStream.EOCD_SIG);
561 if (!found) {
562 throw new ZipException("Archive is not a ZIP archive");
563 }
564 boolean found64 = false;
565 final long position = channel.position();
566 if (position > ZIP64_EOCDL_LENGTH) {
567 final ByteBuffer wordBuf = ByteBuffer.allocate(4);
568 channel.position(channel.position() - ZIP64_EOCDL_LENGTH);
569 wordBuf.rewind();
570 IOUtils.readFully(channel, wordBuf);
571 wordBuf.flip();
572 found64 = wordBuf.equals(ByteBuffer.wrap(ZipArchiveOutputStream.ZIP64_EOCD_LOC_SIG));
573 if (!found64) {
574 channel.position(position);
575 } else {
576 channel.position(channel.position() - ZipConstants.WORD);
577 }
578 }
579
580 return found64;
581 }
582
583
584
585
586
587 private static boolean tryToLocateSignature(final SeekableByteChannel channel, final long minDistanceFromEnd, final long maxDistanceFromEnd,
588 final byte[] sig) throws IOException {
589 final ByteBuffer wordBuf = ByteBuffer.allocate(ZipConstants.WORD);
590 boolean found = false;
591 long off = channel.size() - minDistanceFromEnd;
592 final long stopSearching = Math.max(0L, channel.size() - maxDistanceFromEnd);
593 if (off >= 0) {
594 for (; off >= stopSearching; off--) {
595 channel.position(off);
596 try {
597 wordBuf.rewind();
598 IOUtils.readFully(channel, wordBuf);
599 wordBuf.flip();
600 } catch (final EOFException ex) {
601 break;
602 }
603 int curr = wordBuf.get();
604 if (curr == sig[POS_0]) {
605 curr = wordBuf.get();
606 if (curr == sig[POS_1]) {
607 curr = wordBuf.get();
608 if (curr == sig[POS_2]) {
609 curr = wordBuf.get();
610 if (curr == sig[POS_3]) {
611 found = true;
612 break;
613 }
614 }
615 }
616 }
617 }
618 }
619 if (found) {
620 channel.position(off);
621 }
622 return found;
623 }
624
625
626
627
628 private final List<ZipArchiveEntry> entries = new LinkedList<>();
629
630
631
632
633 private final Map<String, LinkedList<ZipArchiveEntry>> nameMap = new HashMap<>(HASH_SIZE);
634
635
636
637
638
639
640
641
642 private final Charset encoding;
643
644
645
646
647 private final ZipEncoding zipEncoding;
648
649
650
651
652 private final SeekableByteChannel archive;
653
654
655
656
657 private final boolean useUnicodeExtraFields;
658
659
660
661
662 private volatile boolean closed = true;
663
664
665
666
667 private final boolean isSplitZipArchive;
668
669
670 private final byte[] dwordBuf = new byte[ZipConstants.DWORD];
671
672 private final byte[] wordBuf = new byte[ZipConstants.WORD];
673
674 private final byte[] cfhBuf = new byte[CFH_LEN];
675
676 private final byte[] shortBuf = new byte[ZipConstants.SHORT];
677
678 private final ByteBuffer dwordBbuf = ByteBuffer.wrap(dwordBuf);
679
680 private final ByteBuffer wordBbuf = ByteBuffer.wrap(wordBuf);
681
682 private final ByteBuffer cfhBbuf = ByteBuffer.wrap(cfhBuf);
683
684 private final ByteBuffer shortBbuf = ByteBuffer.wrap(shortBuf);
685
686 private long centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset;
687
688 private long centralDirectoryStartOffset;
689
690 private long firstLocalFileHeaderOffset;
691
692
693
694
695
696
697
698
699
700 @Deprecated
701 public ZipFile(final File file) throws IOException {
702 this(file, DEFAULT_CHARSET_NAME);
703 }
704
705
706
707
708
709
710
711
712
713 @Deprecated
714 public ZipFile(final File file, final String encoding) throws IOException {
715 this(file.toPath(), encoding, true);
716 }
717
718
719
720
721
722
723
724
725
726
727 @Deprecated
728 public ZipFile(final File file, final String encoding, final boolean useUnicodeExtraFields) throws IOException {
729 this(file.toPath(), encoding, useUnicodeExtraFields, false);
730 }
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750 @Deprecated
751 @SuppressWarnings("resource")
752 public ZipFile(final File file, final String encoding, final boolean useUnicodeExtraFields, final boolean ignoreLocalFileHeader) throws IOException {
753 this(newReadByteChannel(file.toPath()), file.getAbsolutePath(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
754 }
755
756
757
758
759
760
761
762
763
764 @Deprecated
765 public ZipFile(final Path path) throws IOException {
766 this(path, DEFAULT_CHARSET_NAME);
767 }
768
769
770
771
772
773
774
775
776
777
778 @Deprecated
779 public ZipFile(final Path path, final String encoding) throws IOException {
780 this(path, encoding, true);
781 }
782
783
784
785
786
787
788
789
790
791
792
793 @Deprecated
794 public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields) throws IOException {
795 this(path, encoding, useUnicodeExtraFields, false);
796 }
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816 @SuppressWarnings("resource")
817 @Deprecated
818 public ZipFile(final Path path, final String encoding, final boolean useUnicodeExtraFields, final boolean ignoreLocalFileHeader) throws IOException {
819 this(newReadByteChannel(path), path.toAbsolutePath().toString(), encoding, useUnicodeExtraFields, true, ignoreLocalFileHeader);
820 }
821
822
823
824
825
826
827
828
829
830
831
832
833
834 @Deprecated
835 public ZipFile(final SeekableByteChannel channel) throws IOException {
836 this(channel, "a SeekableByteChannel", DEFAULT_CHARSET_NAME, true);
837 }
838
839
840
841
842
843
844
845
846
847
848
849
850
851 @Deprecated
852 public ZipFile(final SeekableByteChannel channel, final String encoding) throws IOException {
853 this(channel, "a SeekableByteChannel", encoding, true);
854 }
855
856 private ZipFile(final SeekableByteChannel channel, final String channelDescription, final Charset encoding, final boolean useUnicodeExtraFields,
857 final boolean closeOnError, final boolean ignoreLocalFileHeader) throws IOException {
858 this.isSplitZipArchive = channel instanceof ZipSplitReadOnlySeekableByteChannel;
859 this.encoding = Charsets.toCharset(encoding, Builder.DEFAULT_CHARSET);
860 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
861 this.useUnicodeExtraFields = useUnicodeExtraFields;
862 this.archive = channel;
863 boolean success = false;
864 try {
865 final Map<ZipArchiveEntry, NameAndComment> entriesWithoutUTF8Flag = populateFromCentralDirectory();
866 if (!ignoreLocalFileHeader) {
867 resolveLocalFileHeaderData(entriesWithoutUTF8Flag);
868 }
869 fillNameMap();
870 success = true;
871 } catch (final IOException e) {
872 throw new IOException("Error reading Zip content from " + channelDescription, e);
873 } finally {
874 this.closed = !success;
875 if (!success && closeOnError) {
876 org.apache.commons.io.IOUtils.closeQuietly(archive);
877 }
878 }
879 }
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895 @Deprecated
896 public ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields)
897 throws IOException {
898 this(channel, channelDescription, encoding, useUnicodeExtraFields, false, false);
899 }
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923 @Deprecated
924 public ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields,
925 final boolean ignoreLocalFileHeader) throws IOException {
926 this(channel, channelDescription, encoding, useUnicodeExtraFields, false, ignoreLocalFileHeader);
927 }
928
929 private ZipFile(final SeekableByteChannel channel, final String channelDescription, final String encoding, final boolean useUnicodeExtraFields,
930 final boolean closeOnError, final boolean ignoreLocalFileHeader) throws IOException {
931 this(channel, channelDescription, Charsets.toCharset(encoding), useUnicodeExtraFields, closeOnError, ignoreLocalFileHeader);
932 }
933
934
935
936
937
938
939
940
941 @Deprecated
942 public ZipFile(final String name) throws IOException {
943 this(new File(name).toPath(), DEFAULT_CHARSET_NAME);
944 }
945
946
947
948
949
950
951
952
953
954 @Deprecated
955 public ZipFile(final String name, final String encoding) throws IOException {
956 this(new File(name).toPath(), encoding, true);
957 }
958
959
960
961
962
963
964
965
966
967
968
969 public boolean canReadEntryData(final ZipArchiveEntry entry) {
970 return ZipUtil.canHandleEntryData(entry);
971 }
972
973
974
975
976
977
978 @Override
979 public void close() throws IOException {
980
981
982
983 closed = true;
984 archive.close();
985 }
986
987
988
989
990
991
992
993
994
995
996
997 public void copyRawEntries(final ZipArchiveOutputStream target, final ZipArchiveEntryPredicate predicate) throws IOException {
998 final Enumeration<ZipArchiveEntry> src = getEntriesInPhysicalOrder();
999 while (src.hasMoreElements()) {
1000 final ZipArchiveEntry entry = src.nextElement();
1001 if (predicate.test(entry)) {
1002 target.addRawArchiveEntry(entry, getRawInputStream(entry));
1003 }
1004 }
1005 }
1006
1007
1008
1009
1010 private BoundedArchiveInputStream createBoundedInputStream(final long start, final long remaining) {
1011 if (start < 0 || remaining < 0 || start + remaining < start) {
1012 throw new IllegalArgumentException("Corrupted archive, stream boundaries" + " are out of range");
1013 }
1014 return archive instanceof FileChannel ? new BoundedFileChannelInputStream(start, remaining, (FileChannel) archive)
1015 : new BoundedSeekableByteChannelInputStream(start, remaining, archive);
1016 }
1017
1018 private void fillNameMap() {
1019 entries.forEach(ze -> {
1020
1021
1022 final String name = ze.getName();
1023 final LinkedList<ZipArchiveEntry> entriesOfThatName = nameMap.computeIfAbsent(name, k -> new LinkedList<>());
1024 entriesOfThatName.addLast(ze);
1025 });
1026 }
1027
1028
1029
1030
1031
1032
1033 @Override
1034 protected void finalize() throws Throwable {
1035 try {
1036 if (!closed) {
1037 close();
1038 }
1039 } finally {
1040 super.finalize();
1041 }
1042 }
1043
1044
1045
1046
1047
1048
1049
1050
1051 public InputStream getContentBeforeFirstLocalFileHeader() {
1052 return firstLocalFileHeaderOffset == 0 ? null : createBoundedInputStream(0, firstLocalFileHeaderOffset);
1053 }
1054
1055 private long getDataOffset(final ZipArchiveEntry ze) throws IOException {
1056 final long s = ze.getDataOffset();
1057 if (s == EntryStreamOffsets.OFFSET_UNKNOWN) {
1058 setDataOffset(ze);
1059 return ze.getDataOffset();
1060 }
1061 return s;
1062 }
1063
1064
1065
1066
1067
1068
1069 public String getEncoding() {
1070 return encoding.name();
1071 }
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081 public Enumeration<ZipArchiveEntry> getEntries() {
1082 return Collections.enumeration(entries);
1083 }
1084
1085
1086
1087
1088
1089
1090
1091
1092 public Iterable<ZipArchiveEntry> getEntries(final String name) {
1093 return nameMap.getOrDefault(name, ZipArchiveEntry.EMPTY_LINKED_LIST);
1094 }
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106 public Enumeration<ZipArchiveEntry> getEntriesInPhysicalOrder() {
1107 final ZipArchiveEntry[] allEntries = entries.toArray(ZipArchiveEntry.EMPTY_ARRAY);
1108 return Collections.enumeration(Arrays.asList(sortByOffset(allEntries)));
1109 }
1110
1111
1112
1113
1114
1115
1116
1117
1118 public Iterable<ZipArchiveEntry> getEntriesInPhysicalOrder(final String name) {
1119 final LinkedList<ZipArchiveEntry> linkedList = nameMap.getOrDefault(name, ZipArchiveEntry.EMPTY_LINKED_LIST);
1120 return Arrays.asList(sortByOffset(linkedList.toArray(ZipArchiveEntry.EMPTY_ARRAY)));
1121 }
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132 public ZipArchiveEntry getEntry(final String name) {
1133 final LinkedList<ZipArchiveEntry> entries = nameMap.get(name);
1134 return entries != null ? entries.getFirst() : null;
1135 }
1136
1137
1138
1139
1140
1141
1142
1143 public long getFirstLocalFileHeaderOffset() {
1144 return firstLocalFileHeaderOffset;
1145 }
1146
1147
1148
1149
1150
1151
1152
1153
1154 public InputStream getInputStream(final ZipArchiveEntry entry) throws IOException {
1155 if (!(entry instanceof Entry)) {
1156 return null;
1157 }
1158
1159 ZipUtil.checkRequestedFeatures(entry);
1160
1161
1162
1163
1164 final InputStream is = new BufferedInputStream(getRawInputStream(entry));
1165 switch (ZipMethod.getMethodByCode(entry.getMethod())) {
1166 case STORED:
1167 return new StoredStatisticsStream(is);
1168 case UNSHRINKING:
1169 return new UnshrinkingInputStream(is);
1170 case IMPLODING:
1171 try {
1172 return new ExplodingInputStream(entry.getGeneralPurposeBit().getSlidingDictionarySize(),
1173 entry.getGeneralPurposeBit().getNumberOfShannonFanoTrees(), is);
1174 } catch (final IllegalArgumentException ex) {
1175 throw new IOException("bad IMPLODE data", ex);
1176 }
1177 case DEFLATED:
1178 final Inflater inflater = new Inflater(true);
1179
1180
1181
1182
1183
1184 return new InflaterInputStreamWithStatistics(new SequenceInputStream(is, new ByteArrayInputStream(ONE_ZERO_BYTE)), inflater) {
1185 @Override
1186 public void close() throws IOException {
1187 try {
1188 super.close();
1189 } finally {
1190 inflater.end();
1191 }
1192 }
1193 };
1194 case BZIP2:
1195 return new BZip2CompressorInputStream(is);
1196 case ENHANCED_DEFLATED:
1197 return new Deflate64CompressorInputStream(is);
1198 case AES_ENCRYPTED:
1199 case EXPANDING_LEVEL_1:
1200 case EXPANDING_LEVEL_2:
1201 case EXPANDING_LEVEL_3:
1202 case EXPANDING_LEVEL_4:
1203 case JPEG:
1204 case LZMA:
1205 case PKWARE_IMPLODING:
1206 case PPMD:
1207 case TOKENIZATION:
1208 case UNKNOWN:
1209 case WAVPACK:
1210 case XZ:
1211 default:
1212 throw new UnsupportedZipFeatureException(ZipMethod.getMethodByCode(entry.getMethod()), entry);
1213 }
1214 }
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231 public InputStream getRawInputStream(final ZipArchiveEntry entry) throws IOException {
1232 if (!(entry instanceof Entry)) {
1233 return null;
1234 }
1235 final long start = getDataOffset(entry);
1236 if (start == EntryStreamOffsets.OFFSET_UNKNOWN) {
1237 return null;
1238 }
1239 return createBoundedInputStream(start, entry.getCompressedSize());
1240 }
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253 public String getUnixSymlink(final ZipArchiveEntry entry) throws IOException {
1254 if (entry != null && entry.isUnixSymlink()) {
1255 try (InputStream in = getInputStream(entry)) {
1256 return zipEncoding.decode(org.apache.commons.io.IOUtils.toByteArray(in));
1257 }
1258 }
1259 return null;
1260 }
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271 private Map<ZipArchiveEntry, NameAndComment> populateFromCentralDirectory() throws IOException {
1272 final HashMap<ZipArchiveEntry, NameAndComment> noUTF8Flag = new HashMap<>();
1273
1274 positionAtCentralDirectory();
1275 centralDirectoryStartOffset = archive.position();
1276
1277 wordBbuf.rewind();
1278 IOUtils.readFully(archive, wordBbuf);
1279 long sig = ZipLong.getValue(wordBuf);
1280
1281 if (sig != CFH_SIG && startsWithLocalFileHeader()) {
1282 throw new IOException("Central directory is empty, can't expand" + " corrupt archive.");
1283 }
1284
1285 while (sig == CFH_SIG) {
1286 readCentralDirectoryEntry(noUTF8Flag);
1287 wordBbuf.rewind();
1288 IOUtils.readFully(archive, wordBbuf);
1289 sig = ZipLong.getValue(wordBuf);
1290 }
1291 return noUTF8Flag;
1292 }
1293
1294
1295
1296
1297
1298 private void positionAtCentralDirectory() throws IOException {
1299 final boolean is64 = positionAtEndOfCentralDirectoryRecord(archive);
1300 if (!is64) {
1301 positionAtCentralDirectory32();
1302 } else {
1303 positionAtCentralDirectory64();
1304 }
1305 }
1306
1307
1308
1309
1310
1311
1312 private void positionAtCentralDirectory32() throws IOException {
1313 final long endOfCentralDirectoryRecordOffset = archive.position();
1314 if (isSplitZipArchive) {
1315 skipBytes(CFD_DISK_OFFSET);
1316 shortBbuf.rewind();
1317 IOUtils.readFully(archive, shortBbuf);
1318 centralDirectoryStartDiskNumber = ZipShort.getValue(shortBuf);
1319
1320 skipBytes(CFD_LOCATOR_RELATIVE_OFFSET);
1321
1322 wordBbuf.rewind();
1323 IOUtils.readFully(archive, wordBbuf);
1324 centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
1325 ((ZipSplitReadOnlySeekableByteChannel) archive).position(centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset);
1326 } else {
1327 skipBytes(CFD_LENGTH_OFFSET);
1328 wordBbuf.rewind();
1329 IOUtils.readFully(archive, wordBbuf);
1330 final long centralDirectoryLength = ZipLong.getValue(wordBuf);
1331
1332 wordBbuf.rewind();
1333 IOUtils.readFully(archive, wordBbuf);
1334 centralDirectoryStartDiskNumber = 0;
1335 centralDirectoryStartRelativeOffset = ZipLong.getValue(wordBuf);
1336
1337 firstLocalFileHeaderOffset = Long.max(endOfCentralDirectoryRecordOffset - centralDirectoryLength - centralDirectoryStartRelativeOffset, 0L);
1338 archive.position(centralDirectoryStartRelativeOffset + firstLocalFileHeaderOffset);
1339 }
1340 }
1341
1342
1343
1344
1345
1346
1347
1348 private void positionAtCentralDirectory64() throws IOException {
1349 skipBytes(ZipConstants.WORD);
1350 if (isSplitZipArchive) {
1351 wordBbuf.rewind();
1352 IOUtils.readFully(archive, wordBbuf);
1353 final long diskNumberOfEOCD = ZipLong.getValue(wordBuf);
1354
1355 dwordBbuf.rewind();
1356 IOUtils.readFully(archive, dwordBbuf);
1357 final long relativeOffsetOfEOCD = ZipEightByteInteger.getLongValue(dwordBuf);
1358 ((ZipSplitReadOnlySeekableByteChannel) archive).position(diskNumberOfEOCD, relativeOffsetOfEOCD);
1359 } else {
1360 skipBytes(ZIP64_EOCDL_LOCATOR_OFFSET - ZipConstants.WORD );
1361 dwordBbuf.rewind();
1362 IOUtils.readFully(archive, dwordBbuf);
1363 archive.position(ZipEightByteInteger.getLongValue(dwordBuf));
1364 }
1365
1366 wordBbuf.rewind();
1367 IOUtils.readFully(archive, wordBbuf);
1368 if (!Arrays.equals(wordBuf, ZipArchiveOutputStream.ZIP64_EOCD_SIG)) {
1369 throw new ZipException("Archive's ZIP64 end of central directory locator is corrupt.");
1370 }
1371
1372 if (isSplitZipArchive) {
1373 skipBytes(ZIP64_EOCD_CFD_DISK_OFFSET - ZipConstants.WORD );
1374 wordBbuf.rewind();
1375 IOUtils.readFully(archive, wordBbuf);
1376 centralDirectoryStartDiskNumber = ZipLong.getValue(wordBuf);
1377
1378 skipBytes(ZIP64_EOCD_CFD_LOCATOR_RELATIVE_OFFSET);
1379
1380 dwordBbuf.rewind();
1381 IOUtils.readFully(archive, dwordBbuf);
1382 centralDirectoryStartRelativeOffset = ZipEightByteInteger.getLongValue(dwordBuf);
1383 ((ZipSplitReadOnlySeekableByteChannel) archive).position(centralDirectoryStartDiskNumber, centralDirectoryStartRelativeOffset);
1384 } else {
1385 skipBytes(ZIP64_EOCD_CFD_LOCATOR_OFFSET - ZipConstants.WORD );
1386 dwordBbuf.rewind();
1387 IOUtils.readFully(archive, dwordBbuf);
1388 centralDirectoryStartDiskNumber = 0;
1389 centralDirectoryStartRelativeOffset = ZipEightByteInteger.getLongValue(dwordBuf);
1390 archive.position(centralDirectoryStartRelativeOffset);
1391 }
1392 }
1393
1394
1395
1396
1397
1398
1399
1400 private void readCentralDirectoryEntry(final Map<ZipArchiveEntry, NameAndComment> noUTF8Flag) throws IOException {
1401 cfhBbuf.rewind();
1402 IOUtils.readFully(archive, cfhBbuf);
1403 int off = 0;
1404 final Entry ze = new Entry();
1405
1406 final int versionMadeBy = ZipShort.getValue(cfhBuf, off);
1407 off += ZipConstants.SHORT;
1408 ze.setVersionMadeBy(versionMadeBy);
1409 ze.setPlatform(versionMadeBy >> BYTE_SHIFT & NIBLET_MASK);
1410
1411 ze.setVersionRequired(ZipShort.getValue(cfhBuf, off));
1412 off += ZipConstants.SHORT;
1413
1414 final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(cfhBuf, off);
1415 final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames();
1416 final ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding;
1417 if (hasUTF8Flag) {
1418 ze.setNameSource(ZipArchiveEntry.NameSource.NAME_WITH_EFS_FLAG);
1419 }
1420 ze.setGeneralPurposeBit(gpFlag);
1421 ze.setRawFlag(ZipShort.getValue(cfhBuf, off));
1422
1423 off += ZipConstants.SHORT;
1424
1425
1426 ze.setMethod(ZipShort.getValue(cfhBuf, off));
1427 off += ZipConstants.SHORT;
1428
1429 final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfhBuf, off));
1430 ze.setTime(time);
1431 off += ZipConstants.WORD;
1432
1433 ze.setCrc(ZipLong.getValue(cfhBuf, off));
1434 off += ZipConstants.WORD;
1435
1436 long size = ZipLong.getValue(cfhBuf, off);
1437 if (size < 0) {
1438 throw new IOException("broken archive, entry with negative compressed size");
1439 }
1440 ze.setCompressedSize(size);
1441 off += ZipConstants.WORD;
1442
1443 size = ZipLong.getValue(cfhBuf, off);
1444 if (size < 0) {
1445 throw new IOException("broken archive, entry with negative size");
1446 }
1447 ze.setSize(size);
1448 off += ZipConstants.WORD;
1449
1450 final int fileNameLen = ZipShort.getValue(cfhBuf, off);
1451 off += ZipConstants.SHORT;
1452 if (fileNameLen < 0) {
1453 throw new IOException("broken archive, entry with negative fileNameLen");
1454 }
1455
1456 final int extraLen = ZipShort.getValue(cfhBuf, off);
1457 off += ZipConstants.SHORT;
1458 if (extraLen < 0) {
1459 throw new IOException("broken archive, entry with negative extraLen");
1460 }
1461
1462 final int commentLen = ZipShort.getValue(cfhBuf, off);
1463 off += ZipConstants.SHORT;
1464 if (commentLen < 0) {
1465 throw new IOException("broken archive, entry with negative commentLen");
1466 }
1467
1468 ze.setDiskNumberStart(ZipShort.getValue(cfhBuf, off));
1469 off += ZipConstants.SHORT;
1470
1471 ze.setInternalAttributes(ZipShort.getValue(cfhBuf, off));
1472 off += ZipConstants.SHORT;
1473
1474 ze.setExternalAttributes(ZipLong.getValue(cfhBuf, off));
1475 off += ZipConstants.WORD;
1476
1477 final byte[] fileName = IOUtils.readRange(archive, fileNameLen);
1478 if (fileName.length < fileNameLen) {
1479 throw new EOFException();
1480 }
1481 ze.setName(entryEncoding.decode(fileName), fileName);
1482
1483
1484 ze.setLocalHeaderOffset(ZipLong.getValue(cfhBuf, off) + firstLocalFileHeaderOffset);
1485
1486 entries.add(ze);
1487
1488 final byte[] cdExtraData = IOUtils.readRange(archive, extraLen);
1489 if (cdExtraData.length < extraLen) {
1490 throw new EOFException();
1491 }
1492 try {
1493 ze.setCentralDirectoryExtra(cdExtraData);
1494 } catch (final RuntimeException e) {
1495 final ZipException z = new ZipException("Invalid extra data in entry " + ze.getName());
1496 z.initCause(e);
1497 throw z;
1498 }
1499
1500 setSizesAndOffsetFromZip64Extra(ze);
1501 sanityCheckLFHOffset(ze);
1502
1503 final byte[] comment = IOUtils.readRange(archive, commentLen);
1504 if (comment.length < commentLen) {
1505 throw new EOFException();
1506 }
1507 ze.setComment(entryEncoding.decode(comment));
1508
1509 if (!hasUTF8Flag && useUnicodeExtraFields) {
1510 noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
1511 }
1512
1513 ze.setStreamContiguous(true);
1514 }
1515
1516
1517
1518
1519
1520
1521
1522 private void resolveLocalFileHeaderData(final Map<ZipArchiveEntry, NameAndComment> entriesWithoutUTF8Flag) throws IOException {
1523 for (final ZipArchiveEntry zipArchiveEntry : entries) {
1524
1525 final Entry ze = (Entry) zipArchiveEntry;
1526 final int[] lens = setDataOffset(ze);
1527 final int fileNameLen = lens[0];
1528 final int extraFieldLen = lens[1];
1529 skipBytes(fileNameLen);
1530 final byte[] localExtraData = IOUtils.readRange(archive, extraFieldLen);
1531 if (localExtraData.length < extraFieldLen) {
1532 throw new EOFException();
1533 }
1534 try {
1535 ze.setExtra(localExtraData);
1536 } catch (final RuntimeException e) {
1537 final ZipException z = new ZipException("Invalid extra data in entry " + ze.getName());
1538 z.initCause(e);
1539 throw z;
1540 }
1541
1542 if (entriesWithoutUTF8Flag.containsKey(ze)) {
1543 final NameAndComment nc = entriesWithoutUTF8Flag.get(ze);
1544 ZipUtil.setNameAndCommentFromExtraFields(ze, nc.name, nc.comment);
1545 }
1546 }
1547 }
1548
1549 private void sanityCheckLFHOffset(final ZipArchiveEntry entry) throws IOException {
1550 if (entry.getDiskNumberStart() < 0) {
1551 throw new IOException("broken archive, entry with negative disk number");
1552 }
1553 if (entry.getLocalHeaderOffset() < 0) {
1554 throw new IOException("broken archive, entry with negative local file header offset");
1555 }
1556 if (isSplitZipArchive) {
1557 if (entry.getDiskNumberStart() > centralDirectoryStartDiskNumber) {
1558 throw new IOException("local file header for " + entry.getName() + " starts on a later disk than central directory");
1559 }
1560 if (entry.getDiskNumberStart() == centralDirectoryStartDiskNumber && entry.getLocalHeaderOffset() > centralDirectoryStartRelativeOffset) {
1561 throw new IOException("local file header for " + entry.getName() + " starts after central directory");
1562 }
1563 } else if (entry.getLocalHeaderOffset() > centralDirectoryStartOffset) {
1564 throw new IOException("local file header for " + entry.getName() + " starts after central directory");
1565 }
1566 }
1567
1568 private int[] setDataOffset(final ZipArchiveEntry entry) throws IOException {
1569 long offset = entry.getLocalHeaderOffset();
1570 if (isSplitZipArchive) {
1571 ((ZipSplitReadOnlySeekableByteChannel) archive).position(entry.getDiskNumberStart(), offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
1572
1573 offset = archive.position() - LFH_OFFSET_FOR_FILENAME_LENGTH;
1574 } else {
1575 archive.position(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
1576 }
1577 wordBbuf.rewind();
1578 IOUtils.readFully(archive, wordBbuf);
1579 wordBbuf.flip();
1580 wordBbuf.get(shortBuf);
1581 final int fileNameLen = ZipShort.getValue(shortBuf);
1582 wordBbuf.get(shortBuf);
1583 final int extraFieldLen = ZipShort.getValue(shortBuf);
1584 entry.setDataOffset(offset + LFH_OFFSET_FOR_FILENAME_LENGTH + ZipConstants.SHORT + ZipConstants.SHORT + fileNameLen + extraFieldLen);
1585 if (entry.getDataOffset() + entry.getCompressedSize() > centralDirectoryStartOffset) {
1586 throw new IOException("data for " + entry.getName() + " overlaps with central directory.");
1587 }
1588 return new int[] { fileNameLen, extraFieldLen };
1589 }
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599 private void setSizesAndOffsetFromZip64Extra(final ZipArchiveEntry entry) throws IOException {
1600 final ZipExtraField extra = entry.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
1601 if (extra != null && !(extra instanceof Zip64ExtendedInformationExtraField)) {
1602 throw new ZipException("archive contains unparseable zip64 extra field");
1603 }
1604 final Zip64ExtendedInformationExtraField z64 = (Zip64ExtendedInformationExtraField) extra;
1605 if (z64 != null) {
1606 final boolean hasUncompressedSize = entry.getSize() == ZipConstants.ZIP64_MAGIC;
1607 final boolean hasCompressedSize = entry.getCompressedSize() == ZipConstants.ZIP64_MAGIC;
1608 final boolean hasRelativeHeaderOffset = entry.getLocalHeaderOffset() == ZipConstants.ZIP64_MAGIC;
1609 final boolean hasDiskStart = entry.getDiskNumberStart() == ZipConstants.ZIP64_MAGIC_SHORT;
1610 z64.reparseCentralDirectoryData(hasUncompressedSize, hasCompressedSize, hasRelativeHeaderOffset, hasDiskStart);
1611
1612 if (hasUncompressedSize) {
1613 final long size = z64.getSize().getLongValue();
1614 if (size < 0) {
1615 throw new IOException("broken archive, entry with negative size");
1616 }
1617 entry.setSize(size);
1618 } else if (hasCompressedSize) {
1619 z64.setSize(new ZipEightByteInteger(entry.getSize()));
1620 }
1621
1622 if (hasCompressedSize) {
1623 final long size = z64.getCompressedSize().getLongValue();
1624 if (size < 0) {
1625 throw new IOException("broken archive, entry with negative compressed size");
1626 }
1627 entry.setCompressedSize(size);
1628 } else if (hasUncompressedSize) {
1629 z64.setCompressedSize(new ZipEightByteInteger(entry.getCompressedSize()));
1630 }
1631
1632 if (hasRelativeHeaderOffset) {
1633 entry.setLocalHeaderOffset(z64.getRelativeHeaderOffset().getLongValue());
1634 }
1635
1636 if (hasDiskStart) {
1637 entry.setDiskNumberStart(z64.getDiskStartNumber().getValue());
1638 }
1639 }
1640 }
1641
1642
1643
1644
1645 private void skipBytes(final int count) throws IOException {
1646 final long currentPosition = archive.position();
1647 final long newPosition = currentPosition + count;
1648 if (newPosition > archive.size()) {
1649 throw new EOFException();
1650 }
1651 archive.position(newPosition);
1652 }
1653
1654
1655
1656
1657
1658
1659
1660 private ZipArchiveEntry[] sortByOffset(final ZipArchiveEntry[] allEntries) {
1661 Arrays.sort(allEntries, offsetComparator);
1662 return allEntries;
1663 }
1664
1665
1666
1667
1668 private boolean startsWithLocalFileHeader() throws IOException {
1669 archive.position(firstLocalFileHeaderOffset);
1670 wordBbuf.rewind();
1671 IOUtils.readFully(archive, wordBbuf);
1672 return Arrays.equals(wordBuf, ZipArchiveOutputStream.LFH_SIG);
1673 }
1674 }