1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.compress.archivers.sevenz;
18
19 import static java.nio.charset.StandardCharsets.UTF_16LE;
20
21 import java.io.BufferedInputStream;
22 import java.io.ByteArrayInputStream;
23 import java.io.Closeable;
24 import java.io.DataInputStream;
25 import java.io.EOFException;
26 import java.io.File;
27 import java.io.FilterInputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.nio.ByteBuffer;
31 import java.nio.ByteOrder;
32 import java.nio.channels.Channels;
33 import java.nio.channels.SeekableByteChannel;
34 import java.nio.file.Files;
35 import java.nio.file.OpenOption;
36 import java.nio.file.Path;
37 import java.nio.file.StandardOpenOption;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.BitSet;
41 import java.util.EnumSet;
42 import java.util.LinkedHashMap;
43 import java.util.LinkedList;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Objects;
47 import java.util.zip.CRC32;
48 import java.util.zip.CheckedInputStream;
49
50 import org.apache.commons.compress.MemoryLimitException;
51 import org.apache.commons.compress.utils.ByteUtils;
52 import org.apache.commons.compress.utils.IOUtils;
53 import org.apache.commons.compress.utils.InputStreamStatistics;
54 import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
55 import org.apache.commons.io.build.AbstractOrigin.ByteArrayOrigin;
56 import org.apache.commons.io.build.AbstractStreamBuilder;
57 import org.apache.commons.io.input.BoundedInputStream;
58 import org.apache.commons.io.input.ChecksumInputStream;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 public class SevenZFile implements Closeable {
84
85 private static final class ArchiveStatistics {
86 private int numberOfPackedStreams;
87 private long numberOfCoders;
88 private long numberOfOutStreams;
89 private long numberOfInStreams;
90 private long numberOfUnpackSubStreams;
91 private int numberOfFolders;
92 private BitSet folderHasCrc;
93 private int numberOfEntries;
94 private int numberOfEntriesWithStream;
95
96 void assertValidity(final int maxMemoryLimitInKb) throws IOException {
97 if (numberOfEntriesWithStream > 0 && numberOfFolders == 0) {
98 throw new IOException("archive with entries but no folders");
99 }
100 if (numberOfEntriesWithStream > numberOfUnpackSubStreams) {
101 throw new IOException("archive doesn't contain enough substreams for entries");
102 }
103
104 final long memoryNeededInKb = estimateSize() / 1024;
105 if (maxMemoryLimitInKb < memoryNeededInKb) {
106 throw new MemoryLimitException(memoryNeededInKb, maxMemoryLimitInKb);
107 }
108 }
109
110 private long bindPairSize() {
111 return 16;
112 }
113
114 private long coderSize() {
115 return 2
116 + 16 + 4
117 ;
118 }
119
120 private long entrySize() {
121 return 100;
122 }
123
124 long estimateSize() {
125 final long lowerBound = 16L * numberOfPackedStreams
126 + numberOfPackedStreams / 8
127 + numberOfFolders * folderSize()
128 + numberOfCoders * coderSize()
129 + (numberOfOutStreams - numberOfFolders) * bindPairSize()
130 + 8L * (numberOfInStreams - numberOfOutStreams + numberOfFolders)
131 + 8L * numberOfOutStreams
132 + numberOfEntries * entrySize()
133 + streamMapSize();
134 return 2 * lowerBound ;
135 }
136
137 private long folderSize() {
138 return 30;
139 }
140
141 private long streamMapSize() {
142 return 8 * numberOfFolders
143 + 8 * numberOfPackedStreams
144 + 4 * numberOfEntries
145 ;
146 }
147
148 @Override
149 public String toString() {
150 return "Archive with " + numberOfEntries + " entries in " + numberOfFolders + " folders. Estimated size " + estimateSize() / 1024L + " kB.";
151 }
152 }
153
154
155
156
157
158
159 public static class Builder extends AbstractStreamBuilder<SevenZFile, Builder> {
160
161 static final int MEMORY_LIMIT_IN_KB = Integer.MAX_VALUE;
162 static final boolean USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES = false;
163 static final boolean TRY_TO_RECOVER_BROKEN_ARCHIVES = false;
164
165 private SeekableByteChannel seekableByteChannel;
166 private String defaultName = DEFAULT_FILE_NAME;
167 private byte[] password;
168 private int maxMemoryLimitKb = MEMORY_LIMIT_IN_KB;
169 private boolean useDefaultNameForUnnamedEntries = USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES;
170 private boolean tryToRecoverBrokenArchives = TRY_TO_RECOVER_BROKEN_ARCHIVES;
171
172 @SuppressWarnings("resource")
173 @Override
174 public SevenZFile get() throws IOException {
175 final SeekableByteChannel actualChannel;
176 final String actualDescription;
177 if (seekableByteChannel != null) {
178 actualChannel = seekableByteChannel;
179 actualDescription = defaultName;
180 } else if (checkOrigin() instanceof ByteArrayOrigin) {
181 actualChannel = new SeekableInMemoryByteChannel(checkOrigin().getByteArray());
182 actualDescription = defaultName;
183 } else {
184 OpenOption[] openOptions = getOpenOptions();
185 if (openOptions.length == 0) {
186 openOptions = new OpenOption[] { StandardOpenOption.READ };
187 }
188 final Path path = getPath();
189 actualChannel = Files.newByteChannel(path, openOptions);
190 actualDescription = path.toAbsolutePath().toString();
191 }
192 final boolean closeOnError = seekableByteChannel != null;
193 return new SevenZFile(actualChannel, actualDescription, password, closeOnError, maxMemoryLimitKb, useDefaultNameForUnnamedEntries,
194 tryToRecoverBrokenArchives);
195 }
196
197
198
199
200
201
202
203 public Builder setDefaultName(final String defaultName) {
204 this.defaultName = defaultName;
205 return this;
206 }
207
208
209
210
211
212
213
214
215
216
217 public Builder setMaxMemoryLimitKb(final int maxMemoryLimitKb) {
218 this.maxMemoryLimitKb = maxMemoryLimitKb;
219 return this;
220 }
221
222
223
224
225
226
227
228 public Builder setPassword(final byte[] password) {
229 this.password = password != null ? password.clone() : null;
230 return this;
231 }
232
233
234
235
236
237
238
239 public Builder setPassword(final char[] password) {
240 this.password = password != null ? AES256SHA256Decoder.utf16Decode(password.clone()) : null;
241 return this;
242 }
243
244
245
246
247
248
249
250 public Builder setPassword(final String password) {
251 this.password = password != null ? AES256SHA256Decoder.utf16Decode(password.toCharArray()) : null;
252 return this;
253 }
254
255
256
257
258
259
260
261 public Builder setSeekableByteChannel(final SeekableByteChannel seekableByteChannel) {
262 this.seekableByteChannel = seekableByteChannel;
263 return this;
264 }
265
266
267
268
269
270
271
272
273
274
275
276
277 public Builder setTryToRecoverBrokenArchives(final boolean tryToRecoverBrokenArchives) {
278 this.tryToRecoverBrokenArchives = tryToRecoverBrokenArchives;
279 return this;
280 }
281
282
283
284
285
286
287
288 public Builder setUseDefaultNameForUnnamedEntries(final boolean useDefaultNameForUnnamedEntries) {
289 this.useDefaultNameForUnnamedEntries = useDefaultNameForUnnamedEntries;
290 return this;
291 }
292
293 }
294
295 static final int SIGNATURE_HEADER_SIZE = 32;
296
297 private static final String DEFAULT_FILE_NAME = "unknown archive";
298
299
300 static final byte[] sevenZSignature = {
301 (byte) '7', (byte) 'z', (byte) 0xBC, (byte) 0xAF, (byte) 0x27, (byte) 0x1C };
302
303 private static int assertFitsIntoNonNegativeInt(final String what, final long value) throws IOException {
304 if (value > Integer.MAX_VALUE || value < 0) {
305 throw new IOException(String.format("Cannot handle % %,d", what, value));
306 }
307 return (int) value;
308 }
309
310
311
312
313
314
315
316 public static Builder builder() {
317 return new Builder();
318 }
319
320 private static ByteBuffer checkEndOfFile(final ByteBuffer buf, final int expectRemaining) throws EOFException {
321 final int remaining = buf.remaining();
322 if (remaining < expectRemaining) {
323 throw new EOFException(String.format("remaining %,d < expectRemaining %,d", remaining, expectRemaining));
324 }
325 return buf;
326 }
327
328 private static void get(final ByteBuffer buf, final byte[] to) throws EOFException {
329 checkEndOfFile(buf, to.length).get(to);
330 }
331
332 private static char getChar(final ByteBuffer buf) throws EOFException {
333 return checkEndOfFile(buf, Character.BYTES).getChar();
334 }
335
336 private static int getInt(final ByteBuffer buf) throws EOFException {
337 return checkEndOfFile(buf, Integer.BYTES).getInt();
338 }
339
340 private static long getLong(final ByteBuffer buf) throws EOFException {
341 return checkEndOfFile(buf, Long.BYTES).getLong();
342 }
343
344 private static int getUnsignedByte(final ByteBuffer buf) throws EOFException {
345 if (!buf.hasRemaining()) {
346 throw new EOFException();
347 }
348 return buf.get() & 0xff;
349 }
350
351
352
353
354
355
356
357
358
359 public static boolean matches(final byte[] signature, final int length) {
360 if (length < sevenZSignature.length) {
361 return false;
362 }
363 for (int i = 0; i < sevenZSignature.length; i++) {
364 if (signature[i] != sevenZSignature[i]) {
365 return false;
366 }
367 }
368 return true;
369 }
370
371 private static SeekableByteChannel newByteChannel(final File file) throws IOException {
372 return Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ));
373 }
374
375 private static long readUint64(final ByteBuffer in) throws IOException {
376
377 final long firstByte = getUnsignedByte(in);
378 int mask = 0x80;
379 long value = 0;
380 for (int i = 0; i < 8; i++) {
381 if ((firstByte & mask) == 0) {
382 return value | (firstByte & mask - 1) << 8 * i;
383 }
384 final long nextByte = getUnsignedByte(in);
385 value |= nextByte << 8 * i;
386 mask >>>= 1;
387 }
388 return value;
389 }
390
391 private static long skipBytesFully(final ByteBuffer input, long bytesToSkip) {
392 if (bytesToSkip < 1) {
393 return 0;
394 }
395 final int current = input.position();
396 final int maxSkip = input.remaining();
397 if (maxSkip < bytesToSkip) {
398 bytesToSkip = maxSkip;
399 }
400 input.position(current + (int) bytesToSkip);
401 return bytesToSkip;
402 }
403
404 private final String fileName;
405 private SeekableByteChannel channel;
406 private final Archive archive;
407 private int currentEntryIndex = -1;
408 private int currentFolderIndex = -1;
409 private InputStream currentFolderInputStream;
410 private byte[] password;
411 private long compressedBytesReadFromCurrentEntry;
412 private long uncompressedBytesReadFromCurrentEntry;
413 private final ArrayList<InputStream> deferredBlockStreams = new ArrayList<>();
414 private final int maxMemoryLimitKb;
415 private final boolean useDefaultNameForUnnamedEntries;
416
417 private final boolean tryToRecoverBrokenArchives;
418
419
420
421
422
423
424
425
426 @Deprecated
427 public SevenZFile(final File fileName) throws IOException {
428 this(fileName, SevenZFileOptions.DEFAULT);
429 }
430
431
432
433
434
435
436
437
438
439 @SuppressWarnings("resource")
440 @Deprecated
441 public SevenZFile(final File file, final byte[] password) throws IOException {
442 this(newByteChannel(file), file.getAbsolutePath(), password, true, SevenZFileOptions.DEFAULT);
443 }
444
445
446
447
448
449
450
451
452
453
454 @Deprecated
455 public SevenZFile(final File file, final char[] password) throws IOException {
456 this(file, password, SevenZFileOptions.DEFAULT);
457 }
458
459
460
461
462
463
464
465
466
467
468
469 @SuppressWarnings("resource")
470 @Deprecated
471 public SevenZFile(final File file, final char[] password, final SevenZFileOptions options) throws IOException {
472 this(newByteChannel(file),
473 file.getAbsolutePath(), AES256SHA256Decoder.utf16Decode(password), true, options);
474 }
475
476
477
478
479
480
481
482
483
484
485 @Deprecated
486 public SevenZFile(final File file, final SevenZFileOptions options) throws IOException {
487 this(file, null, options);
488 }
489
490
491
492
493
494
495
496
497
498
499
500
501 @Deprecated
502 public SevenZFile(final SeekableByteChannel channel) throws IOException {
503 this(channel, SevenZFileOptions.DEFAULT);
504 }
505
506
507
508
509
510
511
512
513
514
515
516
517
518 @Deprecated
519 public SevenZFile(final SeekableByteChannel channel, final byte[] password) throws IOException {
520 this(channel, DEFAULT_FILE_NAME, password);
521 }
522
523
524
525
526
527
528
529
530
531
532
533
534
535 @Deprecated
536 public SevenZFile(final SeekableByteChannel channel, final char[] password) throws IOException {
537 this(channel, password, SevenZFileOptions.DEFAULT);
538 }
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553 @Deprecated
554 public SevenZFile(final SeekableByteChannel channel, final char[] password, final SevenZFileOptions options) throws IOException {
555 this(channel, DEFAULT_FILE_NAME, password, options);
556 }
557
558
559
560
561
562
563
564
565
566
567
568
569
570 @Deprecated
571 public SevenZFile(final SeekableByteChannel channel, final SevenZFileOptions options) throws IOException {
572 this(channel, DEFAULT_FILE_NAME, null, options);
573 }
574
575
576
577
578
579
580
581
582
583
584
585
586
587 @Deprecated
588 public SevenZFile(final SeekableByteChannel channel, final String fileName) throws IOException {
589 this(channel, fileName, SevenZFileOptions.DEFAULT);
590 }
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605 @Deprecated
606 public SevenZFile(final SeekableByteChannel channel, final String fileName, final byte[] password) throws IOException {
607 this(channel, fileName, password, false, SevenZFileOptions.DEFAULT);
608 }
609
610 private SevenZFile(final SeekableByteChannel channel, final String fileName, final byte[] password, final boolean closeOnError, final int maxMemoryLimitKb,
611 final boolean useDefaultNameForUnnamedEntries, final boolean tryToRecoverBrokenArchives) throws IOException {
612 boolean succeeded = false;
613 this.channel = channel;
614 this.fileName = fileName;
615 this.maxMemoryLimitKb = maxMemoryLimitKb;
616 this.useDefaultNameForUnnamedEntries = useDefaultNameForUnnamedEntries;
617 this.tryToRecoverBrokenArchives = tryToRecoverBrokenArchives;
618 try {
619 archive = readHeaders(password);
620 if (password != null) {
621 this.password = Arrays.copyOf(password, password.length);
622 } else {
623 this.password = null;
624 }
625 succeeded = true;
626 } finally {
627 if (!succeeded && closeOnError) {
628 this.channel.close();
629 }
630 }
631 }
632
633
634
635
636
637
638
639
640
641
642
643
644 @Deprecated
645 private SevenZFile(final SeekableByteChannel channel, final String fileName, final byte[] password, final boolean closeOnError,
646 final SevenZFileOptions options) throws IOException {
647 this(channel, fileName, password, closeOnError, options.getMaxMemoryLimitInKb(), options.getUseDefaultNameForUnnamedEntries(),
648 options.getTryToRecoverBrokenArchives());
649 }
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664 @Deprecated
665 public SevenZFile(final SeekableByteChannel channel, final String fileName, final char[] password) throws IOException {
666 this(channel, fileName, password, SevenZFileOptions.DEFAULT);
667 }
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683 @Deprecated
684 public SevenZFile(final SeekableByteChannel channel, final String fileName, final char[] password, final SevenZFileOptions options) throws IOException {
685 this(channel, fileName, AES256SHA256Decoder.utf16Decode(password), false, options);
686 }
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701 @Deprecated
702 public SevenZFile(final SeekableByteChannel channel, final String fileName, final SevenZFileOptions options) throws IOException {
703 this(channel, fileName, null, false, options);
704 }
705
706 private InputStream buildDecoderStack(final Folder folder, final long folderOffset, final int firstPackStreamIndex, final SevenZArchiveEntry entry)
707 throws IOException {
708 channel.position(folderOffset);
709 InputStream inputStreamStack = new FilterInputStream(
710 new BufferedInputStream(new BoundedSeekableByteChannelInputStream(channel, archive.packSizes[firstPackStreamIndex]))) {
711 private void count(final int c) {
712 compressedBytesReadFromCurrentEntry += c;
713 }
714
715 @Override
716 public int read() throws IOException {
717 final int r = in.read();
718 if (r >= 0) {
719 count(1);
720 }
721 return r;
722 }
723
724 @Override
725 public int read(final byte[] b) throws IOException {
726 return read(b, 0, b.length);
727 }
728
729 @Override
730 public int read(final byte[] b, final int off, final int len) throws IOException {
731 if (len == 0) {
732 return 0;
733 }
734 final int r = in.read(b, off, len);
735 if (r >= 0) {
736 count(r);
737 }
738 return r;
739 }
740 };
741 final LinkedList<SevenZMethodConfiguration> methods = new LinkedList<>();
742 for (final Coder coder : folder.getOrderedCoders()) {
743 if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
744 throw new IOException("Multi input/output stream coders are not yet supported");
745 }
746 final SevenZMethod method = SevenZMethod.byId(coder.decompressionMethodId);
747 inputStreamStack = Coders.addDecoder(fileName, inputStreamStack, folder.getUnpackSizeForCoder(coder), coder, password, maxMemoryLimitKb);
748 methods.addFirst(new SevenZMethodConfiguration(method, Coders.findByMethod(method).getOptionsFromCoder(coder, inputStreamStack)));
749 }
750 entry.setContentMethods(methods);
751 if (folder.hasCrc) {
752
753 return ChecksumInputStream.builder()
754 .setChecksum(new CRC32())
755 .setInputStream(inputStreamStack)
756 .setCountThreshold(folder.getUnpackSize())
757 .setExpectedChecksumValue(folder.crc)
758 .get();
759
760 }
761 return inputStreamStack;
762 }
763
764
765
766
767
768
769
770
771
772
773 private void buildDecodingStream(final int entryIndex, final boolean isRandomAccess) throws IOException {
774 if (archive.streamMap == null) {
775 throw new IOException("Archive doesn't contain stream information to read entries");
776 }
777 final int folderIndex = archive.streamMap.fileFolderIndex[entryIndex];
778 if (folderIndex < 0) {
779 deferredBlockStreams.clear();
780
781
782 return;
783 }
784 final SevenZArchiveEntry file = archive.files[entryIndex];
785 boolean isInSameFolder = false;
786 if (currentFolderIndex == folderIndex) {
787
788
789
790
791
792 if (entryIndex > 0) {
793 file.setContentMethods(archive.files[entryIndex - 1].getContentMethods());
794 }
795
796
797
798
799 if (isRandomAccess && file.getContentMethods() == null) {
800 final int folderFirstFileIndex = archive.streamMap.folderFirstFileIndex[folderIndex];
801 final SevenZArchiveEntry folderFirstFile = archive.files[folderFirstFileIndex];
802 file.setContentMethods(folderFirstFile.getContentMethods());
803 }
804 isInSameFolder = true;
805 } else {
806 currentFolderIndex = folderIndex;
807
808 reopenFolderInputStream(folderIndex, file);
809 }
810
811 boolean haveSkippedEntries = false;
812 if (isRandomAccess) {
813
814 haveSkippedEntries = skipEntriesWhenNeeded(entryIndex, isInSameFolder, folderIndex);
815 }
816
817 if (isRandomAccess && currentEntryIndex == entryIndex && !haveSkippedEntries) {
818
819
820
821
822
823 return;
824 }
825
826 InputStream fileStream = BoundedInputStream.builder()
827 .setInputStream(currentFolderInputStream)
828 .setMaxCount(file.getSize())
829 .setPropagateClose(false)
830 .get();
831 if (file.getHasCrc()) {
832
833 fileStream = ChecksumInputStream.builder()
834 .setChecksum(new CRC32())
835 .setInputStream(fileStream)
836 .setExpectedChecksumValue(file.getCrcValue())
837 .get();
838
839 }
840
841 deferredBlockStreams.add(fileStream);
842 }
843
844 private void calculateStreamMap(final Archive archive) throws IOException {
845 int nextFolderPackStreamIndex = 0;
846 final int numFolders = archive.folders != null ? archive.folders.length : 0;
847 final int[] folderFirstPackStreamIndex = new int[numFolders];
848 for (int i = 0; i < numFolders; i++) {
849 folderFirstPackStreamIndex[i] = nextFolderPackStreamIndex;
850 nextFolderPackStreamIndex += archive.folders[i].packedStreams.length;
851 }
852 long nextPackStreamOffset = 0;
853 final int numPackSizes = archive.packSizes.length;
854 final long[] packStreamOffsets = new long[numPackSizes];
855 for (int i = 0; i < numPackSizes; i++) {
856 packStreamOffsets[i] = nextPackStreamOffset;
857 nextPackStreamOffset += archive.packSizes[i];
858 }
859 final int[] folderFirstFileIndex = new int[numFolders];
860 final int[] fileFolderIndex = new int[archive.files.length];
861 int nextFolderIndex = 0;
862 int nextFolderUnpackStreamIndex = 0;
863 for (int i = 0; i < archive.files.length; i++) {
864 if (!archive.files[i].hasStream() && nextFolderUnpackStreamIndex == 0) {
865 fileFolderIndex[i] = -1;
866 continue;
867 }
868 if (nextFolderUnpackStreamIndex == 0) {
869 for (; nextFolderIndex < archive.folders.length; ++nextFolderIndex) {
870 folderFirstFileIndex[nextFolderIndex] = i;
871 if (archive.folders[nextFolderIndex].numUnpackSubStreams > 0) {
872 break;
873 }
874 }
875 if (nextFolderIndex >= archive.folders.length) {
876 throw new IOException("Too few folders in archive");
877 }
878 }
879 fileFolderIndex[i] = nextFolderIndex;
880 if (!archive.files[i].hasStream()) {
881 continue;
882 }
883 ++nextFolderUnpackStreamIndex;
884 if (nextFolderUnpackStreamIndex >= archive.folders[nextFolderIndex].numUnpackSubStreams) {
885 ++nextFolderIndex;
886 nextFolderUnpackStreamIndex = 0;
887 }
888 }
889 archive.streamMap = new StreamMap(folderFirstPackStreamIndex, packStreamOffsets, folderFirstFileIndex, fileFolderIndex);
890 }
891
892 private void checkEntryIsInitialized(final Map<Integer, SevenZArchiveEntry> archiveEntries, final int index) {
893 archiveEntries.computeIfAbsent(index, i -> new SevenZArchiveEntry());
894 }
895
896
897
898
899
900
901 @Override
902 public void close() throws IOException {
903 if (channel != null) {
904 try {
905 channel.close();
906 } finally {
907 channel = null;
908 if (password != null) {
909 Arrays.fill(password, (byte) 0);
910 }
911 password = null;
912 }
913 }
914 }
915
916 private InputStream getCurrentStream() throws IOException {
917 if (archive.files[currentEntryIndex].getSize() == 0) {
918 return new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY);
919 }
920 if (deferredBlockStreams.isEmpty()) {
921 throw new IllegalStateException("No current 7z entry (call getNextEntry() first).");
922 }
923 while (deferredBlockStreams.size() > 1) {
924
925
926
927 try (InputStream stream = deferredBlockStreams.remove(0)) {
928 org.apache.commons.io.IOUtils.skip(stream, Long.MAX_VALUE, org.apache.commons.io.IOUtils::byteArray);
929 }
930 compressedBytesReadFromCurrentEntry = 0;
931 }
932 return deferredBlockStreams.get(0);
933 }
934
935
936
937
938
939
940
941
942
943
944
945
946 public String getDefaultName() {
947 if (DEFAULT_FILE_NAME.equals(fileName) || fileName == null) {
948 return null;
949 }
950
951 final String lastSegment = new File(fileName).getName();
952 final int dotPos = lastSegment.lastIndexOf(".");
953 if (dotPos > 0) {
954 return lastSegment.substring(0, dotPos);
955 }
956 return lastSegment + "~";
957 }
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972 public Iterable<SevenZArchiveEntry> getEntries() {
973 return new ArrayList<>(Arrays.asList(archive.files));
974 }
975
976
977
978
979
980
981
982
983
984
985
986
987 public InputStream getInputStream(final SevenZArchiveEntry entry) throws IOException {
988 int entryIndex = -1;
989 for (int i = 0; i < archive.files.length; i++) {
990 if (entry == archive.files[i]) {
991 entryIndex = i;
992 break;
993 }
994 }
995
996 if (entryIndex < 0) {
997 throw new IllegalArgumentException("Can not find " + entry.getName() + " in " + fileName);
998 }
999
1000 buildDecodingStream(entryIndex, true);
1001 currentEntryIndex = entryIndex;
1002 currentFolderIndex = archive.streamMap.fileFolderIndex[entryIndex];
1003 return getCurrentStream();
1004 }
1005
1006
1007
1008
1009
1010
1011
1012 public SevenZArchiveEntry getNextEntry() throws IOException {
1013 if (currentEntryIndex >= archive.files.length - 1) {
1014 return null;
1015 }
1016 ++currentEntryIndex;
1017 final SevenZArchiveEntry entry = archive.files[currentEntryIndex];
1018 if (entry.getName() == null && useDefaultNameForUnnamedEntries) {
1019 entry.setName(getDefaultName());
1020 }
1021 buildDecodingStream(currentEntryIndex, false);
1022 uncompressedBytesReadFromCurrentEntry = compressedBytesReadFromCurrentEntry = 0;
1023 return entry;
1024 }
1025
1026
1027
1028
1029
1030
1031
1032 public InputStreamStatistics getStatisticsForCurrentEntry() {
1033 return new InputStreamStatistics() {
1034 @Override
1035 public long getCompressedCount() {
1036 return compressedBytesReadFromCurrentEntry;
1037 }
1038
1039 @Override
1040 public long getUncompressedCount() {
1041 return uncompressedBytesReadFromCurrentEntry;
1042 }
1043 };
1044 }
1045
1046
1047
1048
1049
1050
1051
1052 private boolean hasCurrentEntryBeenRead() {
1053 boolean hasCurrentEntryBeenRead = false;
1054 if (!deferredBlockStreams.isEmpty()) {
1055 final InputStream currentEntryInputStream = deferredBlockStreams.get(deferredBlockStreams.size() - 1);
1056
1057
1058 if (currentEntryInputStream instanceof ChecksumInputStream) {
1059 hasCurrentEntryBeenRead = ((ChecksumInputStream) currentEntryInputStream).getRemaining() != archive.files[currentEntryIndex].getSize();
1060 } else if (currentEntryInputStream instanceof BoundedInputStream) {
1061 hasCurrentEntryBeenRead = ((BoundedInputStream) currentEntryInputStream).getRemaining() != archive.files[currentEntryIndex].getSize();
1062 }
1063 }
1064 return hasCurrentEntryBeenRead;
1065 }
1066
1067 private Archive initializeArchive(final StartHeader startHeader, final byte[] password, final boolean verifyCrc) throws IOException {
1068 assertFitsIntoNonNegativeInt("nextHeaderSize", startHeader.nextHeaderSize);
1069 final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize;
1070 channel.position(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset);
1071 if (verifyCrc) {
1072 final long position = channel.position();
1073 final CheckedInputStream cis = new CheckedInputStream(Channels.newInputStream(channel), new CRC32());
1074 if (cis.skip(nextHeaderSizeInt) != nextHeaderSizeInt) {
1075 throw new IOException("Problem computing NextHeader CRC-32");
1076 }
1077 if (startHeader.nextHeaderCrc != cis.getChecksum().getValue()) {
1078 throw new IOException("NextHeader CRC-32 mismatch");
1079 }
1080 channel.position(position);
1081 }
1082 Archive archive = new Archive();
1083 ByteBuffer buf = ByteBuffer.allocate(nextHeaderSizeInt).order(ByteOrder.LITTLE_ENDIAN);
1084 readFully(buf);
1085 int nid = getUnsignedByte(buf);
1086 if (nid == NID.kEncodedHeader) {
1087 buf = readEncodedHeader(buf, archive, password);
1088
1089 archive = new Archive();
1090 nid = getUnsignedByte(buf);
1091 }
1092 if (nid != NID.kHeader) {
1093 throw new IOException("Broken or unsupported archive: no Header");
1094 }
1095 readHeader(buf, archive);
1096 archive.subStreamsInfo = null;
1097 return archive;
1098 }
1099
1100
1101
1102
1103
1104
1105
1106 public int read() throws IOException {
1107 final int b = getCurrentStream().read();
1108 if (b >= 0) {
1109 uncompressedBytesReadFromCurrentEntry++;
1110 }
1111 return b;
1112 }
1113
1114
1115
1116
1117
1118
1119
1120
1121 public int read(final byte[] b) throws IOException {
1122 return read(b, 0, b.length);
1123 }
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134 public int read(final byte[] b, final int off, final int len) throws IOException {
1135 if (len == 0) {
1136 return 0;
1137 }
1138 final int cnt = getCurrentStream().read(b, off, len);
1139 if (cnt > 0) {
1140 uncompressedBytesReadFromCurrentEntry += cnt;
1141 }
1142 return cnt;
1143 }
1144
1145 private BitSet readAllOrBits(final ByteBuffer header, final int size) throws IOException {
1146 final int areAllDefined = getUnsignedByte(header);
1147 final BitSet bits;
1148 if (areAllDefined != 0) {
1149 bits = new BitSet(size);
1150 for (int i = 0; i < size; i++) {
1151 bits.set(i, true);
1152 }
1153 } else {
1154 bits = readBits(header, size);
1155 }
1156 return bits;
1157 }
1158
1159 private void readArchiveProperties(final ByteBuffer input) throws IOException {
1160
1161 long nid = readUint64(input);
1162 while (nid != NID.kEnd) {
1163 final long propertySize = readUint64(input);
1164 final byte[] property = new byte[(int) propertySize];
1165 get(input, property);
1166 nid = readUint64(input);
1167 }
1168 }
1169
1170 private BitSet readBits(final ByteBuffer header, final int size) throws IOException {
1171 final BitSet bits = new BitSet(size);
1172 int mask = 0;
1173 int cache = 0;
1174 for (int i = 0; i < size; i++) {
1175 if (mask == 0) {
1176 mask = 0x80;
1177 cache = getUnsignedByte(header);
1178 }
1179 bits.set(i, (cache & mask) != 0);
1180 mask >>>= 1;
1181 }
1182 return bits;
1183 }
1184
1185 private ByteBuffer readEncodedHeader(final ByteBuffer header, final Archive archive, final byte[] password) throws IOException {
1186 final int pos = header.position();
1187 final ArchiveStatistics stats = new ArchiveStatistics();
1188 sanityCheckStreamsInfo(header, stats);
1189 stats.assertValidity(maxMemoryLimitKb);
1190 header.position(pos);
1191
1192 readStreamsInfo(header, archive);
1193
1194 if (archive.folders == null || archive.folders.length == 0) {
1195 throw new IOException("no folders, can't read encoded header");
1196 }
1197 if (archive.packSizes == null || archive.packSizes.length == 0) {
1198 throw new IOException("no packed streams, can't read encoded header");
1199 }
1200
1201
1202 final Folder folder = archive.folders[0];
1203 final int firstPackStreamIndex = 0;
1204 final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos + 0;
1205
1206 channel.position(folderOffset);
1207 InputStream inputStreamStack = new BoundedSeekableByteChannelInputStream(channel, archive.packSizes[firstPackStreamIndex]);
1208 for (final Coder coder : folder.getOrderedCoders()) {
1209 if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
1210 throw new IOException("Multi input/output stream coders are not yet supported");
1211 }
1212 inputStreamStack = Coders.addDecoder(fileName, inputStreamStack,
1213 folder.getUnpackSizeForCoder(coder), coder, password, maxMemoryLimitKb);
1214 }
1215 if (folder.hasCrc) {
1216
1217 inputStreamStack = ChecksumInputStream.builder()
1218 .setChecksum(new CRC32())
1219 .setInputStream(inputStreamStack)
1220 .setCountThreshold(folder.getUnpackSize())
1221 .setExpectedChecksumValue(folder.crc)
1222 .get();
1223
1224 }
1225 final int unpackSize = assertFitsIntoNonNegativeInt("unpackSize", folder.getUnpackSize());
1226 final byte[] nextHeader = IOUtils.readRange(inputStreamStack, unpackSize);
1227 if (nextHeader.length < unpackSize) {
1228 throw new IOException("premature end of stream");
1229 }
1230 inputStreamStack.close();
1231 return ByteBuffer.wrap(nextHeader).order(ByteOrder.LITTLE_ENDIAN);
1232 }
1233
1234 private void readFilesInfo(final ByteBuffer header, final Archive archive) throws IOException {
1235 final int numFilesInt = (int) readUint64(header);
1236 final Map<Integer, SevenZArchiveEntry> fileMap = new LinkedHashMap<>();
1237 BitSet isEmptyStream = null;
1238 BitSet isEmptyFile = null;
1239 BitSet isAnti = null;
1240 while (true) {
1241 final int propertyType = getUnsignedByte(header);
1242 if (propertyType == 0) {
1243 break;
1244 }
1245 final long size = readUint64(header);
1246 switch (propertyType) {
1247 case NID.kEmptyStream: {
1248 isEmptyStream = readBits(header, numFilesInt);
1249 break;
1250 }
1251 case NID.kEmptyFile: {
1252 isEmptyFile = readBits(header, isEmptyStream.cardinality());
1253 break;
1254 }
1255 case NID.kAnti: {
1256 isAnti = readBits(header, isEmptyStream.cardinality());
1257 break;
1258 }
1259 case NID.kName: {
1260 getUnsignedByte(header);
1261 final byte[] names = new byte[(int) (size - 1)];
1262 final int namesLength = names.length;
1263 get(header, names);
1264 int nextFile = 0;
1265 int nextName = 0;
1266 for (int i = 0; i < namesLength; i += 2) {
1267 if (names[i] == 0 && names[i + 1] == 0) {
1268 checkEntryIsInitialized(fileMap, nextFile);
1269 fileMap.get(nextFile).setName(new String(names, nextName, i - nextName, UTF_16LE));
1270 nextName = i + 2;
1271 nextFile++;
1272 }
1273 }
1274 if (nextName != namesLength || nextFile != numFilesInt) {
1275 throw new IOException("Error parsing file names");
1276 }
1277 break;
1278 }
1279 case NID.kCTime: {
1280 final BitSet timesDefined = readAllOrBits(header, numFilesInt);
1281 getUnsignedByte(header);
1282 for (int i = 0; i < numFilesInt; i++) {
1283 checkEntryIsInitialized(fileMap, i);
1284 final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
1285 entryAtIndex.setHasCreationDate(timesDefined.get(i));
1286 if (entryAtIndex.getHasCreationDate()) {
1287 entryAtIndex.setCreationDate(getLong(header));
1288 }
1289 }
1290 break;
1291 }
1292 case NID.kATime: {
1293 final BitSet timesDefined = readAllOrBits(header, numFilesInt);
1294 getUnsignedByte(header);
1295 for (int i = 0; i < numFilesInt; i++) {
1296 checkEntryIsInitialized(fileMap, i);
1297 final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
1298 entryAtIndex.setHasAccessDate(timesDefined.get(i));
1299 if (entryAtIndex.getHasAccessDate()) {
1300 entryAtIndex.setAccessDate(getLong(header));
1301 }
1302 }
1303 break;
1304 }
1305 case NID.kMTime: {
1306 final BitSet timesDefined = readAllOrBits(header, numFilesInt);
1307 getUnsignedByte(header);
1308 for (int i = 0; i < numFilesInt; i++) {
1309 checkEntryIsInitialized(fileMap, i);
1310 final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
1311 entryAtIndex.setHasLastModifiedDate(timesDefined.get(i));
1312 if (entryAtIndex.getHasLastModifiedDate()) {
1313 entryAtIndex.setLastModifiedDate(getLong(header));
1314 }
1315 }
1316 break;
1317 }
1318 case NID.kWinAttributes: {
1319 final BitSet attributesDefined = readAllOrBits(header, numFilesInt);
1320 getUnsignedByte(header);
1321 for (int i = 0; i < numFilesInt; i++) {
1322 checkEntryIsInitialized(fileMap, i);
1323 final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
1324 entryAtIndex.setHasWindowsAttributes(attributesDefined.get(i));
1325 if (entryAtIndex.getHasWindowsAttributes()) {
1326 entryAtIndex.setWindowsAttributes(getInt(header));
1327 }
1328 }
1329 break;
1330 }
1331 case NID.kDummy: {
1332
1333
1334
1335 skipBytesFully(header, size);
1336 break;
1337 }
1338
1339 default: {
1340
1341 skipBytesFully(header, size);
1342 break;
1343 }
1344 }
1345 }
1346 int nonEmptyFileCounter = 0;
1347 int emptyFileCounter = 0;
1348 for (int i = 0; i < numFilesInt; i++) {
1349 final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
1350 if (entryAtIndex == null) {
1351 continue;
1352 }
1353 entryAtIndex.setHasStream(isEmptyStream == null || !isEmptyStream.get(i));
1354 if (entryAtIndex.hasStream()) {
1355 if (archive.subStreamsInfo == null) {
1356 throw new IOException("Archive contains file with streams but no subStreamsInfo");
1357 }
1358 entryAtIndex.setDirectory(false);
1359 entryAtIndex.setAntiItem(false);
1360 entryAtIndex.setHasCrc(archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter));
1361 entryAtIndex.setCrcValue(archive.subStreamsInfo.crcs[nonEmptyFileCounter]);
1362 entryAtIndex.setSize(archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter]);
1363 if (entryAtIndex.getSize() < 0) {
1364 throw new IOException("broken archive, entry with negative size");
1365 }
1366 ++nonEmptyFileCounter;
1367 } else {
1368 entryAtIndex.setDirectory(isEmptyFile == null || !isEmptyFile.get(emptyFileCounter));
1369 entryAtIndex.setAntiItem(isAnti != null && isAnti.get(emptyFileCounter));
1370 entryAtIndex.setHasCrc(false);
1371 entryAtIndex.setSize(0);
1372 ++emptyFileCounter;
1373 }
1374 }
1375 archive.files = fileMap.values().stream().filter(Objects::nonNull).toArray(SevenZArchiveEntry[]::new);
1376 calculateStreamMap(archive);
1377 }
1378
1379 private Folder readFolder(final ByteBuffer header) throws IOException {
1380 final Folder folder = new Folder();
1381
1382 final long numCoders = readUint64(header);
1383 final Coder[] coders = new Coder[(int) numCoders];
1384 long totalInStreams = 0;
1385 long totalOutStreams = 0;
1386 for (int i = 0; i < coders.length; i++) {
1387 final int bits = getUnsignedByte(header);
1388 final int idSize = bits & 0xf;
1389 final boolean isSimple = (bits & 0x10) == 0;
1390 final boolean hasAttributes = (bits & 0x20) != 0;
1391 final boolean moreAlternativeMethods = (bits & 0x80) != 0;
1392
1393 final byte[] decompressionMethodId = new byte[idSize];
1394 get(header, decompressionMethodId);
1395 final long numInStreams;
1396 final long numOutStreams;
1397 if (isSimple) {
1398 numInStreams = 1;
1399 numOutStreams = 1;
1400 } else {
1401 numInStreams = readUint64(header);
1402 numOutStreams = readUint64(header);
1403 }
1404 totalInStreams += numInStreams;
1405 totalOutStreams += numOutStreams;
1406 byte[] properties = null;
1407 if (hasAttributes) {
1408 final long propertiesSize = readUint64(header);
1409 properties = new byte[(int) propertiesSize];
1410 get(header, properties);
1411 }
1412
1413 if (moreAlternativeMethods) {
1414 throw new IOException("Alternative methods are unsupported, please report. " +
1415 "The reference implementation doesn't support them either.");
1416 }
1417 coders[i] = new Coder(decompressionMethodId, numInStreams, numOutStreams, properties);
1418 }
1419 folder.coders = coders;
1420 folder.totalInputStreams = totalInStreams;
1421 folder.totalOutputStreams = totalOutStreams;
1422
1423 final long numBindPairs = totalOutStreams - 1;
1424 final BindPair[] bindPairs = new BindPair[(int) numBindPairs];
1425 for (int i = 0; i < bindPairs.length; i++) {
1426 bindPairs[i] = new BindPair(readUint64(header), readUint64(header));
1427 }
1428 folder.bindPairs = bindPairs;
1429
1430 final long numPackedStreams = totalInStreams - numBindPairs;
1431 final long[] packedStreams = new long[(int) numPackedStreams];
1432 if (numPackedStreams == 1) {
1433 int i;
1434 for (i = 0; i < (int) totalInStreams; i++) {
1435 if (folder.findBindPairForInStream(i) < 0) {
1436 break;
1437 }
1438 }
1439 packedStreams[0] = i;
1440 } else {
1441 for (int i = 0; i < (int) numPackedStreams; i++) {
1442 packedStreams[i] = readUint64(header);
1443 }
1444 }
1445 folder.packedStreams = packedStreams;
1446
1447 return folder;
1448 }
1449
1450 private void readFully(final ByteBuffer buf) throws IOException {
1451 buf.rewind();
1452 IOUtils.readFully(channel, buf);
1453 buf.flip();
1454 }
1455
1456 private void readHeader(final ByteBuffer header, final Archive archive) throws IOException {
1457 final int pos = header.position();
1458 final ArchiveStatistics stats = sanityCheckAndCollectStatistics(header);
1459 stats.assertValidity(maxMemoryLimitKb);
1460 header.position(pos);
1461
1462 int nid = getUnsignedByte(header);
1463
1464 if (nid == NID.kArchiveProperties) {
1465 readArchiveProperties(header);
1466 nid = getUnsignedByte(header);
1467 }
1468
1469 if (nid == NID.kAdditionalStreamsInfo) {
1470 throw new IOException("Additional streams unsupported");
1471
1472 }
1473
1474 if (nid == NID.kMainStreamsInfo) {
1475 readStreamsInfo(header, archive);
1476 nid = getUnsignedByte(header);
1477 }
1478
1479 if (nid == NID.kFilesInfo) {
1480 readFilesInfo(header, archive);
1481 nid = getUnsignedByte(header);
1482 }
1483 }
1484
1485 private Archive readHeaders(final byte[] password) throws IOException {
1486 final ByteBuffer buf = ByteBuffer.allocate(12 ).order(ByteOrder.LITTLE_ENDIAN);
1487 readFully(buf);
1488 final byte[] signature = new byte[6];
1489 buf.get(signature);
1490 if (!Arrays.equals(signature, sevenZSignature)) {
1491 throw new IOException("Bad 7z signature");
1492 }
1493
1494 final byte archiveVersionMajor = buf.get();
1495 final byte archiveVersionMinor = buf.get();
1496 if (archiveVersionMajor != 0) {
1497 throw new IOException(String.format("Unsupported 7z version (%d,%d)", archiveVersionMajor, archiveVersionMinor));
1498 }
1499
1500 boolean headerLooksValid = false;
1501 final long startHeaderCrc = 0xffffFFFFL & buf.getInt();
1502 if (startHeaderCrc == 0) {
1503
1504 final long currentPosition = channel.position();
1505 final ByteBuffer peekBuf = ByteBuffer.allocate(20);
1506 readFully(peekBuf);
1507 channel.position(currentPosition);
1508
1509 while (peekBuf.hasRemaining()) {
1510 if (peekBuf.get() != 0) {
1511 headerLooksValid = true;
1512 break;
1513 }
1514 }
1515 } else {
1516 headerLooksValid = true;
1517 }
1518
1519 if (headerLooksValid) {
1520 return initializeArchive(readStartHeader(startHeaderCrc), password, true);
1521 }
1522
1523 if (tryToRecoverBrokenArchives) {
1524 return tryToLocateEndHeader(password);
1525 }
1526 throw new IOException("archive seems to be invalid.\nYou may want to retry and enable the"
1527 + " tryToRecoverBrokenArchives if the archive could be a multi volume archive that has been closed" + " prematurely.");
1528 }
1529
1530 private void readPackInfo(final ByteBuffer header, final Archive archive) throws IOException {
1531 archive.packPos = readUint64(header);
1532 final int numPackStreamsInt = (int) readUint64(header);
1533 int nid = getUnsignedByte(header);
1534 if (nid == NID.kSize) {
1535 archive.packSizes = new long[numPackStreamsInt];
1536 for (int i = 0; i < archive.packSizes.length; i++) {
1537 archive.packSizes[i] = readUint64(header);
1538 }
1539 nid = getUnsignedByte(header);
1540 }
1541
1542 if (nid == NID.kCRC) {
1543 archive.packCrcsDefined = readAllOrBits(header, numPackStreamsInt);
1544 archive.packCrcs = new long[numPackStreamsInt];
1545 for (int i = 0; i < numPackStreamsInt; i++) {
1546 if (archive.packCrcsDefined.get(i)) {
1547 archive.packCrcs[i] = 0xffffFFFFL & getInt(header);
1548 }
1549 }
1550
1551 getUnsignedByte(header);
1552 }
1553 }
1554
1555 private StartHeader readStartHeader(final long startHeaderCrc) throws IOException {
1556
1557 try (DataInputStream dataInputStream = new DataInputStream(ChecksumInputStream.builder()
1558
1559 .setChecksum(new CRC32())
1560 .setInputStream(new BoundedSeekableByteChannelInputStream(channel, 20))
1561 .setCountThreshold(20L)
1562 .setExpectedChecksumValue(startHeaderCrc)
1563 .get())) {
1564
1565 final long nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong());
1566 if (nextHeaderOffset < 0 || nextHeaderOffset + SIGNATURE_HEADER_SIZE > channel.size()) {
1567 throw new IOException("nextHeaderOffset is out of bounds");
1568 }
1569 final long nextHeaderSize = Long.reverseBytes(dataInputStream.readLong());
1570 final long nextHeaderEnd = nextHeaderOffset + nextHeaderSize;
1571 if (nextHeaderEnd < nextHeaderOffset || nextHeaderEnd + SIGNATURE_HEADER_SIZE > channel.size()) {
1572 throw new IOException("nextHeaderSize is out of bounds");
1573 }
1574 final long nextHeaderCrc = 0xffffFFFFL & Integer.reverseBytes(dataInputStream.readInt());
1575 return new StartHeader(nextHeaderOffset, nextHeaderSize, nextHeaderCrc);
1576 }
1577 }
1578
1579 private void readStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException {
1580 int nid = getUnsignedByte(header);
1581
1582 if (nid == NID.kPackInfo) {
1583 readPackInfo(header, archive);
1584 nid = getUnsignedByte(header);
1585 }
1586
1587 if (nid == NID.kUnpackInfo) {
1588 readUnpackInfo(header, archive);
1589 nid = getUnsignedByte(header);
1590 } else {
1591
1592 archive.folders = Folder.EMPTY_FOLDER_ARRAY;
1593 }
1594
1595 if (nid == NID.kSubStreamsInfo) {
1596 readSubStreamsInfo(header, archive);
1597 nid = getUnsignedByte(header);
1598 }
1599 }
1600
1601 private void readSubStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException {
1602 for (final Folder folder : archive.folders) {
1603 folder.numUnpackSubStreams = 1;
1604 }
1605 long unpackStreamsCount = archive.folders.length;
1606
1607 int nid = getUnsignedByte(header);
1608 if (nid == NID.kNumUnpackStream) {
1609 unpackStreamsCount = 0;
1610 for (final Folder folder : archive.folders) {
1611 final long numStreams = readUint64(header);
1612 folder.numUnpackSubStreams = (int) numStreams;
1613 unpackStreamsCount += numStreams;
1614 }
1615 nid = getUnsignedByte(header);
1616 }
1617
1618 final int totalUnpackStreams = (int) unpackStreamsCount;
1619 final SubStreamsInfo subStreamsInfo = new SubStreamsInfo(totalUnpackStreams);
1620 int nextUnpackStream = 0;
1621 for (final Folder folder : archive.folders) {
1622 if (folder.numUnpackSubStreams == 0) {
1623 continue;
1624 }
1625 long sum = 0;
1626 if (nid == NID.kSize) {
1627 for (int i = 0; i < folder.numUnpackSubStreams - 1; i++) {
1628 final long size = readUint64(header);
1629 subStreamsInfo.unpackSizes[nextUnpackStream++] = size;
1630 sum += size;
1631 }
1632 }
1633 if (sum > folder.getUnpackSize()) {
1634 throw new IOException("sum of unpack sizes of folder exceeds total unpack size");
1635 }
1636 subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum;
1637 }
1638 if (nid == NID.kSize) {
1639 nid = getUnsignedByte(header);
1640 }
1641
1642 int numDigests = 0;
1643 for (final Folder folder : archive.folders) {
1644 if (folder.numUnpackSubStreams != 1 || !folder.hasCrc) {
1645 numDigests += folder.numUnpackSubStreams;
1646 }
1647 }
1648
1649 if (nid == NID.kCRC) {
1650 final BitSet hasMissingCrc = readAllOrBits(header, numDigests);
1651 final long[] missingCrcs = new long[numDigests];
1652 for (int i = 0; i < numDigests; i++) {
1653 if (hasMissingCrc.get(i)) {
1654 missingCrcs[i] = 0xffffFFFFL & getInt(header);
1655 }
1656 }
1657 int nextCrc = 0;
1658 int nextMissingCrc = 0;
1659 for (final Folder folder : archive.folders) {
1660 if (folder.numUnpackSubStreams == 1 && folder.hasCrc) {
1661 subStreamsInfo.hasCrc.set(nextCrc, true);
1662 subStreamsInfo.crcs[nextCrc] = folder.crc;
1663 ++nextCrc;
1664 } else {
1665 for (int i = 0; i < folder.numUnpackSubStreams; i++) {
1666 subStreamsInfo.hasCrc.set(nextCrc, hasMissingCrc.get(nextMissingCrc));
1667 subStreamsInfo.crcs[nextCrc] = missingCrcs[nextMissingCrc];
1668 ++nextCrc;
1669 ++nextMissingCrc;
1670 }
1671 }
1672 }
1673
1674 nid = getUnsignedByte(header);
1675 }
1676
1677 archive.subStreamsInfo = subStreamsInfo;
1678 }
1679
1680 private void readUnpackInfo(final ByteBuffer header, final Archive archive) throws IOException {
1681 int nid = getUnsignedByte(header);
1682 final int numFoldersInt = (int) readUint64(header);
1683 final Folder[] folders = new Folder[numFoldersInt];
1684 archive.folders = folders;
1685 getUnsignedByte(header);
1686 for (int i = 0; i < numFoldersInt; i++) {
1687 folders[i] = readFolder(header);
1688 }
1689
1690 nid = getUnsignedByte(header);
1691 for (final Folder folder : folders) {
1692 assertFitsIntoNonNegativeInt("totalOutputStreams", folder.totalOutputStreams);
1693 folder.unpackSizes = new long[(int) folder.totalOutputStreams];
1694 for (int i = 0; i < folder.totalOutputStreams; i++) {
1695 folder.unpackSizes[i] = readUint64(header);
1696 }
1697 }
1698
1699 nid = getUnsignedByte(header);
1700 if (nid == NID.kCRC) {
1701 final BitSet crcsDefined = readAllOrBits(header, numFoldersInt);
1702 for (int i = 0; i < numFoldersInt; i++) {
1703 if (crcsDefined.get(i)) {
1704 folders[i].hasCrc = true;
1705 folders[i].crc = 0xffffFFFFL & getInt(header);
1706 } else {
1707 folders[i].hasCrc = false;
1708 }
1709 }
1710
1711 nid = getUnsignedByte(header);
1712 }
1713 }
1714
1715
1716
1717
1718
1719
1720
1721
1722 private void reopenFolderInputStream(final int folderIndex, final SevenZArchiveEntry file) throws IOException {
1723 deferredBlockStreams.clear();
1724 if (currentFolderInputStream != null) {
1725 currentFolderInputStream.close();
1726 currentFolderInputStream = null;
1727 }
1728 final Folder folder = archive.folders[folderIndex];
1729 final int firstPackStreamIndex = archive.streamMap.folderFirstPackStreamIndex[folderIndex];
1730 final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos + archive.streamMap.packStreamOffsets[firstPackStreamIndex];
1731
1732 currentFolderInputStream = buildDecoderStack(folder, folderOffset, firstPackStreamIndex, file);
1733 }
1734
1735 private ArchiveStatistics sanityCheckAndCollectStatistics(final ByteBuffer header) throws IOException {
1736 final ArchiveStatistics stats = new ArchiveStatistics();
1737
1738 int nid = getUnsignedByte(header);
1739
1740 if (nid == NID.kArchiveProperties) {
1741 sanityCheckArchiveProperties(header);
1742 nid = getUnsignedByte(header);
1743 }
1744
1745 if (nid == NID.kAdditionalStreamsInfo) {
1746 throw new IOException("Additional streams unsupported");
1747
1748 }
1749
1750 if (nid == NID.kMainStreamsInfo) {
1751 sanityCheckStreamsInfo(header, stats);
1752 nid = getUnsignedByte(header);
1753 }
1754
1755 if (nid == NID.kFilesInfo) {
1756 sanityCheckFilesInfo(header, stats);
1757 nid = getUnsignedByte(header);
1758 }
1759
1760 if (nid != NID.kEnd) {
1761 throw new IOException("Badly terminated header, found " + nid);
1762 }
1763
1764 return stats;
1765 }
1766
1767 private void sanityCheckArchiveProperties(final ByteBuffer header) throws IOException {
1768 long nid = readUint64(header);
1769 while (nid != NID.kEnd) {
1770 final int propertySize = assertFitsIntoNonNegativeInt("propertySize", readUint64(header));
1771 if (skipBytesFully(header, propertySize) < propertySize) {
1772 throw new IOException("invalid property size");
1773 }
1774 nid = readUint64(header);
1775 }
1776 }
1777
1778 private void sanityCheckFilesInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
1779 stats.numberOfEntries = assertFitsIntoNonNegativeInt("numFiles", readUint64(header));
1780
1781 int emptyStreams = -1;
1782 while (true) {
1783 final int propertyType = getUnsignedByte(header);
1784 if (propertyType == 0) {
1785 break;
1786 }
1787 final long size = readUint64(header);
1788 switch (propertyType) {
1789 case NID.kEmptyStream: {
1790 emptyStreams = readBits(header, stats.numberOfEntries).cardinality();
1791 break;
1792 }
1793 case NID.kEmptyFile: {
1794 if (emptyStreams == -1) {
1795 throw new IOException("Header format error: kEmptyStream must appear before kEmptyFile");
1796 }
1797 readBits(header, emptyStreams);
1798 break;
1799 }
1800 case NID.kAnti: {
1801 if (emptyStreams == -1) {
1802 throw new IOException("Header format error: kEmptyStream must appear before kAnti");
1803 }
1804 readBits(header, emptyStreams);
1805 break;
1806 }
1807 case NID.kName: {
1808 final int external = getUnsignedByte(header);
1809 if (external != 0) {
1810 throw new IOException("Not implemented");
1811 }
1812 final int namesLength = assertFitsIntoNonNegativeInt("file names length", size - 1);
1813 if ((namesLength & 1) != 0) {
1814 throw new IOException("File names length invalid");
1815 }
1816
1817 int filesSeen = 0;
1818 for (int i = 0; i < namesLength; i += 2) {
1819 final char c = getChar(header);
1820 if (c == 0) {
1821 filesSeen++;
1822 }
1823 }
1824 if (filesSeen != stats.numberOfEntries) {
1825 throw new IOException("Invalid number of file names (" + filesSeen + " instead of " + stats.numberOfEntries + ")");
1826 }
1827 break;
1828 }
1829 case NID.kCTime: {
1830 final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
1831 final int external = getUnsignedByte(header);
1832 if (external != 0) {
1833 throw new IOException("Not implemented");
1834 }
1835 if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
1836 throw new IOException("invalid creation dates size");
1837 }
1838 break;
1839 }
1840 case NID.kATime: {
1841 final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
1842 final int external = getUnsignedByte(header);
1843 if (external != 0) {
1844 throw new IOException("Not implemented");
1845 }
1846 if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
1847 throw new IOException("invalid access dates size");
1848 }
1849 break;
1850 }
1851 case NID.kMTime: {
1852 final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
1853 final int external = getUnsignedByte(header);
1854 if (external != 0) {
1855 throw new IOException("Not implemented");
1856 }
1857 if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
1858 throw new IOException("invalid modification dates size");
1859 }
1860 break;
1861 }
1862 case NID.kWinAttributes: {
1863 final int attributesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
1864 final int external = getUnsignedByte(header);
1865 if (external != 0) {
1866 throw new IOException("Not implemented");
1867 }
1868 if (skipBytesFully(header, 4 * attributesDefined) < 4 * attributesDefined) {
1869 throw new IOException("invalid windows attributes size");
1870 }
1871 break;
1872 }
1873 case NID.kStartPos: {
1874 throw new IOException("kStartPos is unsupported, please report");
1875 }
1876 case NID.kDummy: {
1877
1878
1879
1880 if (skipBytesFully(header, size) < size) {
1881 throw new IOException("Incomplete kDummy property");
1882 }
1883 break;
1884 }
1885
1886 default: {
1887
1888 if (skipBytesFully(header, size) < size) {
1889 throw new IOException("Incomplete property of type " + propertyType);
1890 }
1891 break;
1892 }
1893 }
1894 }
1895 stats.numberOfEntriesWithStream = stats.numberOfEntries - Math.max(emptyStreams, 0);
1896 }
1897
1898 private int sanityCheckFolder(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
1899
1900 final int numCoders = assertFitsIntoNonNegativeInt("numCoders", readUint64(header));
1901 if (numCoders == 0) {
1902 throw new IOException("Folder without coders");
1903 }
1904 stats.numberOfCoders += numCoders;
1905
1906 long totalOutStreams = 0;
1907 long totalInStreams = 0;
1908 for (int i = 0; i < numCoders; i++) {
1909 final int bits = getUnsignedByte(header);
1910 final int idSize = bits & 0xf;
1911 get(header, new byte[idSize]);
1912
1913 final boolean isSimple = (bits & 0x10) == 0;
1914 final boolean hasAttributes = (bits & 0x20) != 0;
1915 final boolean moreAlternativeMethods = (bits & 0x80) != 0;
1916 if (moreAlternativeMethods) {
1917 throw new IOException("Alternative methods are unsupported, please report. " +
1918 "The reference implementation doesn't support them either.");
1919 }
1920
1921 if (isSimple) {
1922 totalInStreams++;
1923 totalOutStreams++;
1924 } else {
1925 totalInStreams += assertFitsIntoNonNegativeInt("numInStreams", readUint64(header));
1926 totalOutStreams += assertFitsIntoNonNegativeInt("numOutStreams", readUint64(header));
1927 }
1928
1929 if (hasAttributes) {
1930 final int propertiesSize = assertFitsIntoNonNegativeInt("propertiesSize", readUint64(header));
1931 if (skipBytesFully(header, propertiesSize) < propertiesSize) {
1932 throw new IOException("invalid propertiesSize in folder");
1933 }
1934 }
1935 }
1936 assertFitsIntoNonNegativeInt("totalInStreams", totalInStreams);
1937 assertFitsIntoNonNegativeInt("totalOutStreams", totalOutStreams);
1938 stats.numberOfOutStreams += totalOutStreams;
1939 stats.numberOfInStreams += totalInStreams;
1940
1941 if (totalOutStreams == 0) {
1942 throw new IOException("Total output streams can't be 0");
1943 }
1944
1945 final int numBindPairs = assertFitsIntoNonNegativeInt("numBindPairs", totalOutStreams - 1);
1946 if (totalInStreams < numBindPairs) {
1947 throw new IOException("Total input streams can't be less than the number of bind pairs");
1948 }
1949 final BitSet inStreamsBound = new BitSet((int) totalInStreams);
1950 for (int i = 0; i < numBindPairs; i++) {
1951 final int inIndex = assertFitsIntoNonNegativeInt("inIndex", readUint64(header));
1952 if (totalInStreams <= inIndex) {
1953 throw new IOException("inIndex is bigger than number of inStreams");
1954 }
1955 inStreamsBound.set(inIndex);
1956 final int outIndex = assertFitsIntoNonNegativeInt("outIndex", readUint64(header));
1957 if (totalOutStreams <= outIndex) {
1958 throw new IOException("outIndex is bigger than number of outStreams");
1959 }
1960 }
1961
1962 final int numPackedStreams = assertFitsIntoNonNegativeInt("numPackedStreams", totalInStreams - numBindPairs);
1963
1964 if (numPackedStreams == 1) {
1965 if (inStreamsBound.nextClearBit(0) == -1) {
1966 throw new IOException("Couldn't find stream's bind pair index");
1967 }
1968 } else {
1969 for (int i = 0; i < numPackedStreams; i++) {
1970 final int packedStreamIndex = assertFitsIntoNonNegativeInt("packedStreamIndex", readUint64(header));
1971 if (packedStreamIndex >= totalInStreams) {
1972 throw new IOException("packedStreamIndex is bigger than number of totalInStreams");
1973 }
1974 }
1975 }
1976
1977 return (int) totalOutStreams;
1978 }
1979
1980 private void sanityCheckPackInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
1981 final long packPos = readUint64(header);
1982 if (packPos < 0 || SIGNATURE_HEADER_SIZE + packPos > channel.size() || SIGNATURE_HEADER_SIZE + packPos < 0) {
1983 throw new IOException("packPos (" + packPos + ") is out of range");
1984 }
1985 final long numPackStreams = readUint64(header);
1986 stats.numberOfPackedStreams = assertFitsIntoNonNegativeInt("numPackStreams", numPackStreams);
1987 int nid = getUnsignedByte(header);
1988 if (nid == NID.kSize) {
1989 long totalPackSizes = 0;
1990 for (int i = 0; i < stats.numberOfPackedStreams; i++) {
1991 final long packSize = readUint64(header);
1992 totalPackSizes += packSize;
1993 final long endOfPackStreams = SIGNATURE_HEADER_SIZE + packPos + totalPackSizes;
1994 if (packSize < 0 || endOfPackStreams > channel.size() || endOfPackStreams < packPos) {
1995 throw new IOException("packSize (" + packSize + ") is out of range");
1996 }
1997 }
1998 nid = getUnsignedByte(header);
1999 }
2000
2001 if (nid == NID.kCRC) {
2002 final int crcsDefined = readAllOrBits(header, stats.numberOfPackedStreams).cardinality();
2003 if (skipBytesFully(header, 4 * crcsDefined) < 4 * crcsDefined) {
2004 throw new IOException("invalid number of CRCs in PackInfo");
2005 }
2006 nid = getUnsignedByte(header);
2007 }
2008
2009 if (nid != NID.kEnd) {
2010 throw new IOException("Badly terminated PackInfo (" + nid + ")");
2011 }
2012 }
2013
2014 private void sanityCheckStreamsInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
2015 int nid = getUnsignedByte(header);
2016
2017 if (nid == NID.kPackInfo) {
2018 sanityCheckPackInfo(header, stats);
2019 nid = getUnsignedByte(header);
2020 }
2021
2022 if (nid == NID.kUnpackInfo) {
2023 sanityCheckUnpackInfo(header, stats);
2024 nid = getUnsignedByte(header);
2025 }
2026
2027 if (nid == NID.kSubStreamsInfo) {
2028 sanityCheckSubStreamsInfo(header, stats);
2029 nid = getUnsignedByte(header);
2030 }
2031
2032 if (nid != NID.kEnd) {
2033 throw new IOException("Badly terminated StreamsInfo");
2034 }
2035 }
2036
2037 private void sanityCheckSubStreamsInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
2038
2039 int nid = getUnsignedByte(header);
2040 final List<Integer> numUnpackSubStreamsPerFolder = new LinkedList<>();
2041 if (nid == NID.kNumUnpackStream) {
2042 for (int i = 0; i < stats.numberOfFolders; i++) {
2043 numUnpackSubStreamsPerFolder.add(assertFitsIntoNonNegativeInt("numStreams", readUint64(header)));
2044 }
2045 stats.numberOfUnpackSubStreams = numUnpackSubStreamsPerFolder.stream().mapToLong(Integer::longValue).sum();
2046 nid = getUnsignedByte(header);
2047 } else {
2048 stats.numberOfUnpackSubStreams = stats.numberOfFolders;
2049 }
2050
2051 assertFitsIntoNonNegativeInt("totalUnpackStreams", stats.numberOfUnpackSubStreams);
2052
2053 if (nid == NID.kSize) {
2054 for (final int numUnpackSubStreams : numUnpackSubStreamsPerFolder) {
2055 if (numUnpackSubStreams == 0) {
2056 continue;
2057 }
2058 for (int i = 0; i < numUnpackSubStreams - 1; i++) {
2059 final long size = readUint64(header);
2060 if (size < 0) {
2061 throw new IOException("negative unpackSize");
2062 }
2063 }
2064 }
2065 nid = getUnsignedByte(header);
2066 }
2067
2068 int numDigests = 0;
2069 if (numUnpackSubStreamsPerFolder.isEmpty()) {
2070 numDigests = stats.folderHasCrc == null ? stats.numberOfFolders : stats.numberOfFolders - stats.folderHasCrc.cardinality();
2071 } else {
2072 int folderIdx = 0;
2073 for (final int numUnpackSubStreams : numUnpackSubStreamsPerFolder) {
2074 if (numUnpackSubStreams != 1 || stats.folderHasCrc == null || !stats.folderHasCrc.get(folderIdx++)) {
2075 numDigests += numUnpackSubStreams;
2076 }
2077 }
2078 }
2079
2080 if (nid == NID.kCRC) {
2081 assertFitsIntoNonNegativeInt("numDigests", numDigests);
2082 final int missingCrcs = readAllOrBits(header, numDigests).cardinality();
2083 if (skipBytesFully(header, 4 * missingCrcs) < 4 * missingCrcs) {
2084 throw new IOException("invalid number of missing CRCs in SubStreamInfo");
2085 }
2086 nid = getUnsignedByte(header);
2087 }
2088
2089 if (nid != NID.kEnd) {
2090 throw new IOException("Badly terminated SubStreamsInfo");
2091 }
2092 }
2093
2094 private void sanityCheckUnpackInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
2095 int nid = getUnsignedByte(header);
2096 if (nid != NID.kFolder) {
2097 throw new IOException("Expected kFolder, got " + nid);
2098 }
2099 final long numFolders = readUint64(header);
2100 stats.numberOfFolders = assertFitsIntoNonNegativeInt("numFolders", numFolders);
2101 final int external = getUnsignedByte(header);
2102 if (external != 0) {
2103 throw new IOException("External unsupported");
2104 }
2105
2106 final List<Integer> numberOfOutputStreamsPerFolder = new LinkedList<>();
2107 for (int i = 0; i < stats.numberOfFolders; i++) {
2108 numberOfOutputStreamsPerFolder.add(sanityCheckFolder(header, stats));
2109 }
2110
2111 final long totalNumberOfBindPairs = stats.numberOfOutStreams - stats.numberOfFolders;
2112 final long packedStreamsRequiredByFolders = stats.numberOfInStreams - totalNumberOfBindPairs;
2113 if (packedStreamsRequiredByFolders < stats.numberOfPackedStreams) {
2114 throw new IOException("archive doesn't contain enough packed streams");
2115 }
2116
2117 nid = getUnsignedByte(header);
2118 if (nid != NID.kCodersUnpackSize) {
2119 throw new IOException("Expected kCodersUnpackSize, got " + nid);
2120 }
2121
2122 for (final int numberOfOutputStreams : numberOfOutputStreamsPerFolder) {
2123 for (int i = 0; i < numberOfOutputStreams; i++) {
2124 final long unpackSize = readUint64(header);
2125 if (unpackSize < 0) {
2126 throw new IllegalArgumentException("negative unpackSize");
2127 }
2128 }
2129 }
2130
2131 nid = getUnsignedByte(header);
2132 if (nid == NID.kCRC) {
2133 stats.folderHasCrc = readAllOrBits(header, stats.numberOfFolders);
2134 final int crcsDefined = stats.folderHasCrc.cardinality();
2135 if (skipBytesFully(header, 4 * crcsDefined) < 4 * crcsDefined) {
2136 throw new IOException("invalid number of CRCs in UnpackInfo");
2137 }
2138 nid = getUnsignedByte(header);
2139 }
2140
2141 if (nid != NID.kEnd) {
2142 throw new IOException("Badly terminated UnpackInfo");
2143 }
2144 }
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167 private boolean skipEntriesWhenNeeded(final int entryIndex, final boolean isInSameFolder, final int folderIndex) throws IOException {
2168 final SevenZArchiveEntry file = archive.files[entryIndex];
2169
2170
2171 if (currentEntryIndex == entryIndex && !hasCurrentEntryBeenRead()) {
2172 return false;
2173 }
2174
2175
2176
2177
2178
2179
2180 int filesToSkipStartIndex = archive.streamMap.folderFirstFileIndex[currentFolderIndex];
2181 if (isInSameFolder) {
2182 if (currentEntryIndex < entryIndex) {
2183
2184 filesToSkipStartIndex = currentEntryIndex + 1;
2185 } else {
2186
2187
2188
2189 reopenFolderInputStream(folderIndex, file);
2190 }
2191 }
2192
2193 for (int i = filesToSkipStartIndex; i < entryIndex; i++) {
2194 final SevenZArchiveEntry fileToSkip = archive.files[i];
2195 InputStream fileStreamToSkip = BoundedInputStream.builder()
2196 .setInputStream(currentFolderInputStream)
2197 .setMaxCount(fileToSkip.getSize())
2198 .setPropagateClose(false)
2199 .get();
2200 if (fileToSkip.getHasCrc()) {
2201
2202 fileStreamToSkip = ChecksumInputStream.builder()
2203 .setChecksum(new CRC32())
2204 .setInputStream(fileStreamToSkip)
2205 .setCountThreshold(fileToSkip.getSize())
2206 .setExpectedChecksumValue(fileToSkip.getCrcValue())
2207 .get();
2208
2209 }
2210 deferredBlockStreams.add(fileStreamToSkip);
2211
2212
2213 fileToSkip.setContentMethods(file.getContentMethods());
2214 }
2215 return true;
2216 }
2217
2218 @Override
2219 public String toString() {
2220 return archive.toString();
2221 }
2222
2223 private Archive tryToLocateEndHeader(final byte[] password) throws IOException {
2224 final ByteBuffer nidBuf = ByteBuffer.allocate(1);
2225 final long searchLimit = 1024L * 1024 * 1;
2226
2227 final long previousDataSize = channel.position() + 20;
2228 final long minPos;
2229
2230 if (channel.position() + searchLimit > channel.size()) {
2231 minPos = channel.position();
2232 } else {
2233 minPos = channel.size() - searchLimit;
2234 }
2235 long pos = channel.size() - 1;
2236
2237 while (pos > minPos) {
2238 pos--;
2239 channel.position(pos);
2240 nidBuf.rewind();
2241 if (channel.read(nidBuf) < 1) {
2242 throw new EOFException();
2243 }
2244 final int nid = nidBuf.array()[0];
2245
2246 if (nid == NID.kEncodedHeader || nid == NID.kHeader) {
2247 try {
2248
2249 final long nextHeaderOffset = pos - previousDataSize;
2250 final long nextHeaderSize = channel.size() - pos;
2251 final StartHeader startHeader = new StartHeader(nextHeaderOffset, nextHeaderSize, 0);
2252 final Archive result = initializeArchive(startHeader, password, false);
2253
2254 if (result.packSizes.length > 0 && result.files.length > 0) {
2255 return result;
2256 }
2257 } catch (final Exception ignored) {
2258
2259 }
2260 }
2261 }
2262 throw new IOException("Start header corrupt and unable to guess end header");
2263 }
2264 }