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