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