2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
18
package org.apache.commons.compress.archivers.sevenz;
20
import java.io.ByteArrayInputStream;
21
import java.io.DataInput;
22
import java.io.DataInputStream;
24
import java.io.IOException;
25
import java.io.InputStream;
26
import java.io.RandomAccessFile;
27
import java.util.Arrays;
28
import java.util.BitSet;
29
import java.util.zip.CRC32;
31
import org.apache.commons.compress.utils.BoundedInputStream;
32
import org.apache.commons.compress.utils.CRC32VerifyingInputStream;
33
import org.apache.commons.compress.utils.CharsetNames;
36
* Reads a 7z file, using RandomAccessFile under
39
* The 7z file format is a flexible container
40
* that can contain many compression and
41
* encryption types, but at the moment only
42
* only Copy, LZMA, LZMA2, BZIP2, Deflate and AES-256 + SHA-256
45
* The format is very Windows/Intel specific,
46
* so it uses little-endian byte order,
47
* doesn't store user/group or permission bits,
48
* and represents times using NTFS timestamps
49
* (100 nanosecond units since 1 January 1601).
50
* Hence the official tools recommend against
51
* using it for backup purposes on *nix, and
52
* recommend .tar.7z or .tar.lzma or .tar.xz
55
* Both the header and file contents may be
56
* compressed and/or encrypted. With both
57
* encrypted, neither file names nor file
58
* contents can be read, but the use of
59
* encryption isn't plausibly deniable.
64
public class SevenZFile {
65
static final int SIGNATURE_HEADER_SIZE = 32;
67
private static final int DRAIN_BUF_SIZE = 64 * 1024;
69
private RandomAccessFile file;
70
private final Archive archive;
71
private int currentEntryIndex = -1;
72
private int currentFolderIndex = -1;
73
private InputStream currentFolderInputStream = null;
74
private InputStream currentEntryInputStream = null;
75
private byte[] password;
77
static final byte[] sevenZSignature = {
78
(byte)'7', (byte)'z', (byte)0xBC, (byte)0xAF, (byte)0x27, (byte)0x1C
82
* Reads a file as 7z archive
84
* @param filename the file to read
85
* @param password optional password if the archive is encrypted -
86
* the byte array is supposed to be the UTF16-LE encoded
87
* representation of the password.
88
* @throws IOException if reading the archive fails
90
public SevenZFile(final File filename, final byte[] password) throws IOException {
91
boolean succeeded = false;
92
this.file = new RandomAccessFile(filename, "r");
94
archive = readHeaders(password);
95
if (password != null) {
96
this.password = new byte[password.length];
97
System.arraycopy(password, 0, this.password, 0, password.length);
110
* Reads a file as unecrypted 7z archive
112
* @param filename the file to read
113
* @throws IOException if reading the archive fails
115
public SevenZFile(final File filename) throws IOException {
116
this(filename, null);
120
* Closes the archive.
121
* @throws IOException if closing the file fails
123
public void close() throws IOException {
129
if (password != null) {
130
Arrays.fill(password, (byte) 0);
138
* Returns the next Archive Entry in this archive.
140
* @return the next entry,
141
* or {@code null} if there are no more entries
142
* @throws IOException if the next entry could not be read
144
public SevenZArchiveEntry getNextEntry() throws IOException {
145
if (currentEntryIndex >= (archive.files.length - 1)) {
149
final SevenZArchiveEntry entry = archive.files[currentEntryIndex];
150
buildDecodingStream();
154
private Archive readHeaders(byte[] password) throws IOException {
155
final byte[] signature = new byte[6];
156
file.readFully(signature);
157
if (!Arrays.equals(signature, sevenZSignature)) {
158
throw new IOException("Bad 7z signature");
160
// 7zFormat.txt has it wrong - it's first major then minor
161
final byte archiveVersionMajor = file.readByte();
162
final byte archiveVersionMinor = file.readByte();
163
if (archiveVersionMajor != 0) {
164
throw new IOException(String.format("Unsupported 7z version (%d,%d)",
165
archiveVersionMajor, archiveVersionMinor));
168
final int startHeaderCrc = Integer.reverseBytes(file.readInt());
169
final StartHeader startHeader = readStartHeader(startHeaderCrc);
171
final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize;
172
if (nextHeaderSizeInt != startHeader.nextHeaderSize) {
173
throw new IOException("cannot handle nextHeaderSize " + startHeader.nextHeaderSize);
175
file.seek(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset);
176
final byte[] nextHeader = new byte[nextHeaderSizeInt];
177
file.readFully(nextHeader);
178
final CRC32 crc = new CRC32();
179
crc.update(nextHeader);
180
if (startHeader.nextHeaderCrc != (int) crc.getValue()) {
181
throw new IOException("NextHeader CRC mismatch");
184
final ByteArrayInputStream byteStream = new ByteArrayInputStream(nextHeader);
185
DataInputStream nextHeaderInputStream = new DataInputStream(
187
Archive archive = new Archive();
188
int nid = nextHeaderInputStream.readUnsignedByte();
189
if (nid == NID.kEncodedHeader) {
190
nextHeaderInputStream =
191
readEncodedHeader(nextHeaderInputStream, archive, password);
192
// Archive gets rebuilt with the new header
193
archive = new Archive();
194
nid = nextHeaderInputStream.readUnsignedByte();
196
if (nid == NID.kHeader) {
197
readHeader(nextHeaderInputStream, archive);
198
nextHeaderInputStream.close();
200
throw new IOException("Broken or unsupported archive: no Header");
205
private StartHeader readStartHeader(final int startHeaderCrc) throws IOException {
206
final StartHeader startHeader = new StartHeader();
207
DataInputStream dataInputStream = null;
209
dataInputStream = new DataInputStream(new CRC32VerifyingInputStream(
210
new BoundedRandomAccessFileInputStream(file, 20), 20, startHeaderCrc));
211
startHeader.nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong());
212
startHeader.nextHeaderSize = Long.reverseBytes(dataInputStream.readLong());
213
startHeader.nextHeaderCrc = Integer.reverseBytes(dataInputStream.readInt());
216
if (dataInputStream != null) {
217
dataInputStream.close();
222
private void readHeader(final DataInput header, final Archive archive) throws IOException {
223
int nid = header.readUnsignedByte();
225
if (nid == NID.kArchiveProperties) {
226
readArchiveProperties(header);
227
nid = header.readUnsignedByte();
230
if (nid == NID.kAdditionalStreamsInfo) {
231
throw new IOException("Additional streams unsupported");
232
//nid = header.readUnsignedByte();
235
if (nid == NID.kMainStreamsInfo) {
236
readStreamsInfo(header, archive);
237
nid = header.readUnsignedByte();
240
if (nid == NID.kFilesInfo) {
241
readFilesInfo(header, archive);
242
nid = header.readUnsignedByte();
245
if (nid != NID.kEnd) {
246
throw new IOException("Badly terminated header");
250
private void readArchiveProperties(final DataInput input) throws IOException {
251
// FIXME: the reference implementation just throws them away?
252
int nid = input.readUnsignedByte();
253
while (nid != NID.kEnd) {
254
final long propertySize = readUint64(input);
255
final byte[] property = new byte[(int)propertySize];
256
input.readFully(property);
257
nid = input.readUnsignedByte();
261
private DataInputStream readEncodedHeader(final DataInputStream header, final Archive archive,
262
byte[] password) throws IOException {
263
readStreamsInfo(header, archive);
265
// FIXME: merge with buildDecodingStream()/buildDecoderStack() at some stage?
266
final Folder folder = archive.folders[0];
267
final int firstPackStreamIndex = 0;
268
final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos +
271
file.seek(folderOffset);
272
InputStream inputStreamStack = new BoundedRandomAccessFileInputStream(file,
273
archive.packSizes[firstPackStreamIndex]);
274
for (final Coder coder : folder.coders) {
275
if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
276
throw new IOException("Multi input/output stream coders are not yet supported");
278
inputStreamStack = Coders.addDecoder(inputStreamStack, coder, password);
281
inputStreamStack = new CRC32VerifyingInputStream(inputStreamStack,
282
folder.getUnpackSize(), folder.crc);
284
final byte[] nextHeader = new byte[(int)folder.getUnpackSize()];
285
final DataInputStream nextHeaderInputStream = new DataInputStream(inputStreamStack);
287
nextHeaderInputStream.readFully(nextHeader);
289
nextHeaderInputStream.close();
291
return new DataInputStream(new ByteArrayInputStream(nextHeader));
294
private void readStreamsInfo(final DataInput header, final Archive archive) throws IOException {
295
int nid = header.readUnsignedByte();
297
if (nid == NID.kPackInfo) {
298
readPackInfo(header, archive);
299
nid = header.readUnsignedByte();
302
if (nid == NID.kUnpackInfo) {
303
readUnpackInfo(header, archive);
304
nid = header.readUnsignedByte();
306
// archive without unpack/coders info
307
archive.folders = new Folder[0];
310
if (nid == NID.kSubStreamsInfo) {
311
readSubStreamsInfo(header, archive);
312
nid = header.readUnsignedByte();
315
if (nid != NID.kEnd) {
316
throw new IOException("Badly terminated StreamsInfo");
320
private void readPackInfo(final DataInput header, final Archive archive) throws IOException {
321
archive.packPos = readUint64(header);
322
final long numPackStreams = readUint64(header);
323
int nid = header.readUnsignedByte();
324
if (nid == NID.kSize) {
325
archive.packSizes = new long[(int)numPackStreams];
326
for (int i = 0; i < archive.packSizes.length; i++) {
327
archive.packSizes[i] = readUint64(header);
329
nid = header.readUnsignedByte();
332
if (nid == NID.kCRC) {
333
archive.packCrcsDefined = readAllOrBits(header, (int)numPackStreams);
334
archive.packCrcs = new int[(int)numPackStreams];
335
for (int i = 0; i < (int)numPackStreams; i++) {
336
if (archive.packCrcsDefined.get(i)) {
337
archive.packCrcs[i] = Integer.reverseBytes(header.readInt());
341
nid = header.readUnsignedByte();
344
if (nid != NID.kEnd) {
345
throw new IOException("Badly terminated PackInfo (" + nid + ")");
349
private void readUnpackInfo(final DataInput header, final Archive archive) throws IOException {
350
int nid = header.readUnsignedByte();
351
if (nid != NID.kFolder) {
352
throw new IOException("Expected kFolder, got " + nid);
354
final long numFolders = readUint64(header);
355
final Folder[] folders = new Folder[(int)numFolders];
356
archive.folders = folders;
357
final int external = header.readUnsignedByte();
359
throw new IOException("External unsupported");
361
for (int i = 0; i < (int)numFolders; i++) {
362
folders[i] = readFolder(header);
366
nid = header.readUnsignedByte();
367
if (nid != NID.kCodersUnpackSize) {
368
throw new IOException("Expected kCodersUnpackSize, got " + nid);
370
for (final Folder folder : folders) {
371
folder.unpackSizes = new long[(int)folder.totalOutputStreams];
372
for (int i = 0; i < folder.totalOutputStreams; i++) {
373
folder.unpackSizes[i] = readUint64(header);
377
nid = header.readUnsignedByte();
378
if (nid == NID.kCRC) {
379
final BitSet crcsDefined = readAllOrBits(header, (int)numFolders);
380
for (int i = 0; i < (int)numFolders; i++) {
381
if (crcsDefined.get(i)) {
382
folders[i].hasCrc = true;
383
folders[i].crc = Integer.reverseBytes(header.readInt());
385
folders[i].hasCrc = false;
389
nid = header.readUnsignedByte();
392
if (nid != NID.kEnd) {
393
throw new IOException("Badly terminated UnpackInfo");
397
private void readSubStreamsInfo(final DataInput header, final Archive archive) throws IOException {
398
for (final Folder folder : archive.folders) {
399
folder.numUnpackSubStreams = 1;
401
int totalUnpackStreams = archive.folders.length;
403
int nid = header.readUnsignedByte();
404
if (nid == NID.kNumUnpackStream) {
405
totalUnpackStreams = 0;
406
for (final Folder folder : archive.folders) {
407
final long numStreams = readUint64(header);
408
folder.numUnpackSubStreams = (int)numStreams;
409
totalUnpackStreams += numStreams;
411
nid = header.readUnsignedByte();
414
final SubStreamsInfo subStreamsInfo = new SubStreamsInfo();
415
subStreamsInfo.unpackSizes = new long[totalUnpackStreams];
416
subStreamsInfo.hasCrc = new BitSet(totalUnpackStreams);
417
subStreamsInfo.crcs = new int[totalUnpackStreams];
419
int nextUnpackStream = 0;
420
for (final Folder folder : archive.folders) {
421
if (folder.numUnpackSubStreams == 0) {
425
if (nid == NID.kSize) {
426
for (int i = 0; i < (folder.numUnpackSubStreams - 1); i++) {
427
final long size = readUint64(header);
428
subStreamsInfo.unpackSizes[nextUnpackStream++] = size;
432
subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum;
434
if (nid == NID.kSize) {
435
nid = header.readUnsignedByte();
439
for (final Folder folder : archive.folders) {
440
if (folder.numUnpackSubStreams != 1 || !folder.hasCrc) {
441
numDigests += folder.numUnpackSubStreams;
445
if (nid == NID.kCRC) {
446
final BitSet hasMissingCrc = readAllOrBits(header, numDigests);
447
final int[] missingCrcs = new int[numDigests];
448
for (int i = 0; i < numDigests; i++) {
449
if (hasMissingCrc.get(i)) {
450
missingCrcs[i] = Integer.reverseBytes(header.readInt());
454
int nextMissingCrc = 0;
455
for (final Folder folder: archive.folders) {
456
if (folder.numUnpackSubStreams == 1 && folder.hasCrc) {
457
subStreamsInfo.hasCrc.set(nextCrc, true);
458
subStreamsInfo.crcs[nextCrc] = folder.crc;
461
for (int i = 0; i < folder.numUnpackSubStreams; i++) {
462
subStreamsInfo.hasCrc.set(nextCrc, hasMissingCrc.get(nextMissingCrc));
463
subStreamsInfo.crcs[nextCrc] = missingCrcs[nextMissingCrc];
470
nid = header.readUnsignedByte();
473
if (nid != NID.kEnd) {
474
throw new IOException("Badly terminated SubStreamsInfo");
477
archive.subStreamsInfo = subStreamsInfo;
480
private Folder readFolder(final DataInput header) throws IOException {
481
final Folder folder = new Folder();
483
final long numCoders = readUint64(header);
484
final Coder[] coders = new Coder[(int)numCoders];
485
long totalInStreams = 0;
486
long totalOutStreams = 0;
487
for (int i = 0; i < coders.length; i++) {
488
coders[i] = new Coder();
489
int bits = header.readUnsignedByte();
490
final int idSize = bits & 0xf;
491
final boolean isSimple = ((bits & 0x10) == 0);
492
final boolean hasAttributes = ((bits & 0x20) != 0);
493
final boolean moreAlternativeMethods = ((bits & 0x80) != 0);
495
coders[i].decompressionMethodId = new byte[idSize];
496
header.readFully(coders[i].decompressionMethodId);
498
coders[i].numInStreams = 1;
499
coders[i].numOutStreams = 1;
501
coders[i].numInStreams = readUint64(header);
502
coders[i].numOutStreams = readUint64(header);
504
totalInStreams += coders[i].numInStreams;
505
totalOutStreams += coders[i].numOutStreams;
507
final long propertiesSize = readUint64(header);
508
coders[i].properties = new byte[(int)propertiesSize];
509
header.readFully(coders[i].properties);
511
// would need to keep looping as above:
512
while (moreAlternativeMethods) {
513
throw new IOException("Alternative methods are unsupported, please report. " +
514
"The reference implementation doesn't support them either.");
517
folder.coders = coders;
518
folder.totalInputStreams = totalInStreams;
519
folder.totalOutputStreams = totalOutStreams;
521
if (totalOutStreams == 0) {
522
throw new IOException("Total output streams can't be 0");
524
final long numBindPairs = totalOutStreams - 1;
525
final BindPair[] bindPairs = new BindPair[(int)numBindPairs];
526
for (int i = 0; i < bindPairs.length; i++) {
527
bindPairs[i] = new BindPair();
528
bindPairs[i].inIndex = readUint64(header);
529
bindPairs[i].outIndex = readUint64(header);
531
folder.bindPairs = bindPairs;
533
if (totalInStreams < numBindPairs) {
534
throw new IOException("Total input streams can't be less than the number of bind pairs");
536
final long numPackedStreams = totalInStreams - numBindPairs;
537
final long packedStreams[] = new long[(int)numPackedStreams];
538
if (numPackedStreams == 1) {
540
for (i = 0; i < (int)totalInStreams; i++) {
541
if (folder.findBindPairForInStream(i) < 0) {
545
if (i == (int)totalInStreams) {
546
throw new IOException("Couldn't find stream's bind pair index");
548
packedStreams[0] = i;
550
for (int i = 0; i < (int)numPackedStreams; i++) {
551
packedStreams[i] = readUint64(header);
554
folder.packedStreams = packedStreams;
559
private BitSet readAllOrBits(final DataInput header, final int size) throws IOException {
560
final int areAllDefined = header.readUnsignedByte();
562
if (areAllDefined != 0) {
563
bits = new BitSet(size);
564
for (int i = 0; i < size; i++) {
568
bits = readBits(header, size);
573
private BitSet readBits(final DataInput header, final int size) throws IOException {
574
final BitSet bits = new BitSet(size);
577
for (int i = 0; i < size; i++) {
580
cache = header.readUnsignedByte();
582
bits.set(i, (cache & mask) != 0);
588
private void readFilesInfo(final DataInput header, final Archive archive) throws IOException {
589
final long numFiles = readUint64(header);
590
final SevenZArchiveEntry[] files = new SevenZArchiveEntry[(int)numFiles];
591
for (int i = 0; i < files.length; i++) {
592
files[i] = new SevenZArchiveEntry();
594
BitSet isEmptyStream = null;
595
BitSet isEmptyFile = null;
596
BitSet isAnti = null;
598
final int propertyType = header.readUnsignedByte();
599
if (propertyType == 0) {
602
long size = readUint64(header);
603
switch (propertyType) {
604
case NID.kEmptyStream: {
605
isEmptyStream = readBits(header, files.length);
608
case NID.kEmptyFile: {
609
if (isEmptyStream == null) { // protect against NPE
610
throw new IOException("Header format error: kEmptyStream must appear before kEmptyFile");
612
isEmptyFile = readBits(header, isEmptyStream.cardinality());
616
if (isEmptyStream == null) { // protect against NPE
617
throw new IOException("Header format error: kEmptyStream must appear before kAnti");
619
isAnti = readBits(header, isEmptyStream.cardinality());
623
final int external = header.readUnsignedByte();
625
throw new IOException("Not implemented");
627
if (((size - 1) & 1) != 0) {
628
throw new IOException("File names length invalid");
630
final byte[] names = new byte[(int)(size - 1)];
631
header.readFully(names);
634
for (int i = 0; i < names.length; i += 2) {
635
if (names[i] == 0 && names[i+1] == 0) {
636
files[nextFile++].setName(new String(names, nextName, i-nextName, CharsetNames.UTF_16LE));
640
if (nextName != names.length || nextFile != files.length) {
641
throw new IOException("Error parsing file names");
647
final BitSet timesDefined = readAllOrBits(header, files.length);
648
final int external = header.readUnsignedByte();
650
throw new IOException("Unimplemented");
652
for (int i = 0; i < files.length; i++) {
653
files[i].setHasCreationDate(timesDefined.get(i));
654
if (files[i].getHasCreationDate()) {
655
files[i].setCreationDate(Long.reverseBytes(header.readLong()));
662
final BitSet timesDefined = readAllOrBits(header, files.length);
663
final int external = header.readUnsignedByte();
665
throw new IOException("Unimplemented");
667
for (int i = 0; i < files.length; i++) {
668
files[i].setHasAccessDate(timesDefined.get(i));
669
if (files[i].getHasAccessDate()) {
670
files[i].setAccessDate(Long.reverseBytes(header.readLong()));
677
final BitSet timesDefined = readAllOrBits(header, files.length);
678
final int external = header.readUnsignedByte();
680
throw new IOException("Unimplemented");
682
for (int i = 0; i < files.length; i++) {
683
files[i].setHasLastModifiedDate(timesDefined.get(i));
684
if (files[i].getHasLastModifiedDate()) {
685
files[i].setLastModifiedDate(Long.reverseBytes(header.readLong()));
691
case NID.kWinAttributes: {
692
final BitSet attributesDefined = readAllOrBits(header, files.length);
693
final int external = header.readUnsignedByte();
695
throw new IOException("Unimplemented");
697
for (int i = 0; i < files.length; i++) {
698
files[i].setHasWindowsAttributes(attributesDefined.get(i));
699
if (files[i].getHasWindowsAttributes()) {
700
files[i].setWindowsAttributes(Integer.reverseBytes(header.readInt()));
706
case NID.kStartPos: {
707
throw new IOException("kStartPos is unsupported, please report");
710
throw new IOException("kDummy is unsupported, please report");
714
throw new IOException("Unknown property " + propertyType);
715
// FIXME: Should actually:
716
//header.skipBytes((int)size);
720
int nonEmptyFileCounter = 0;
721
int emptyFileCounter = 0;
722
for (int i = 0; i < files.length; i++) {
723
files[i].setHasStream((isEmptyStream == null) ? true : !isEmptyStream.get(i));
724
if (files[i].hasStream()) {
725
files[i].setDirectory(false);
726
files[i].setAntiItem(false);
727
files[i].setHasCrc(archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter));
728
files[i].setCrc(archive.subStreamsInfo.crcs[nonEmptyFileCounter]);
729
files[i].setSize(archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter]);
730
++nonEmptyFileCounter;
732
files[i].setDirectory((isEmptyFile == null) ? true : !isEmptyFile.get(emptyFileCounter));
733
files[i].setAntiItem((isAnti == null) ? false : isAnti.get(emptyFileCounter));
734
files[i].setHasCrc(false);
739
archive.files = files;
740
calculateStreamMap(archive);
743
private void calculateStreamMap(final Archive archive) throws IOException {
744
final StreamMap streamMap = new StreamMap();
746
int nextFolderPackStreamIndex = 0;
747
final int numFolders = (archive.folders != null) ? archive.folders.length : 0;
748
streamMap.folderFirstPackStreamIndex = new int[numFolders];
749
for (int i = 0; i < numFolders; i++) {
750
streamMap.folderFirstPackStreamIndex[i] = nextFolderPackStreamIndex;
751
nextFolderPackStreamIndex += archive.folders[i].packedStreams.length;
754
long nextPackStreamOffset = 0;
755
final int numPackSizes = (archive.packSizes != null) ? archive.packSizes.length : 0;
756
streamMap.packStreamOffsets = new long[numPackSizes];
757
for (int i = 0; i < numPackSizes; i++) {
758
streamMap.packStreamOffsets[i] = nextPackStreamOffset;
759
nextPackStreamOffset += archive.packSizes[i];
762
streamMap.folderFirstFileIndex = new int[numFolders];
763
streamMap.fileFolderIndex = new int[archive.files.length];
764
int nextFolderIndex = 0;
765
int nextFolderUnpackStreamIndex = 0;
766
for (int i = 0; i < archive.files.length; i++) {
767
if (!archive.files[i].hasStream() && nextFolderUnpackStreamIndex == 0) {
768
streamMap.fileFolderIndex[i] = -1;
771
if (nextFolderUnpackStreamIndex == 0) {
772
for (; nextFolderIndex < archive.folders.length; ++nextFolderIndex) {
773
streamMap.folderFirstFileIndex[nextFolderIndex] = i;
774
if (archive.folders[nextFolderIndex].numUnpackSubStreams > 0) {
778
if (nextFolderIndex >= archive.folders.length) {
779
throw new IOException("Too few folders in archive");
782
streamMap.fileFolderIndex[i] = nextFolderIndex;
783
if (!archive.files[i].hasStream()) {
786
++nextFolderUnpackStreamIndex;
787
if (nextFolderUnpackStreamIndex >= archive.folders[nextFolderIndex].numUnpackSubStreams) {
789
nextFolderUnpackStreamIndex = 0;
793
archive.streamMap = streamMap;
796
private void buildDecodingStream() throws IOException {
797
final int folderIndex = archive.streamMap.fileFolderIndex[currentEntryIndex];
798
if (folderIndex < 0) {
799
currentEntryInputStream = new BoundedInputStream(
800
new ByteArrayInputStream(new byte[0]), 0);
803
if (currentFolderIndex == folderIndex) {
804
// need to advance the folder input stream past the current file
805
drainPreviousEntry();
807
currentFolderIndex = folderIndex;
808
if (currentFolderInputStream != null) {
809
currentFolderInputStream.close();
810
currentFolderInputStream = null;
813
final Folder folder = archive.folders[folderIndex];
814
final int firstPackStreamIndex = archive.streamMap.folderFirstPackStreamIndex[folderIndex];
815
final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos +
816
archive.streamMap.packStreamOffsets[firstPackStreamIndex];
817
currentFolderInputStream = buildDecoderStack(folder, folderOffset, firstPackStreamIndex);
819
final SevenZArchiveEntry file = archive.files[currentEntryIndex];
820
final InputStream fileStream = new BoundedInputStream(
821
currentFolderInputStream, file.getSize());
822
if (file.getHasCrc()) {
823
currentEntryInputStream = new CRC32VerifyingInputStream(
824
fileStream, file.getSize(), file.getCrc());
826
currentEntryInputStream = fileStream;
831
private void drainPreviousEntry() throws IOException {
832
if (currentEntryInputStream != null) {
833
final byte[] buffer = new byte[DRAIN_BUF_SIZE];
834
while (currentEntryInputStream.read(buffer) >= 0) { // NOPMD
836
currentEntryInputStream.close();
837
currentEntryInputStream = null;
841
private InputStream buildDecoderStack(final Folder folder, final long folderOffset,
842
final int firstPackStreamIndex) throws IOException {
843
file.seek(folderOffset);
844
InputStream inputStreamStack = new BoundedRandomAccessFileInputStream(file,
845
archive.packSizes[firstPackStreamIndex]);
846
for (final Coder coder : folder.coders) {
847
if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
848
throw new IOException("Multi input/output stream coders are not yet supported");
850
inputStreamStack = Coders.addDecoder(inputStreamStack, coder, password);
853
return new CRC32VerifyingInputStream(inputStreamStack,
854
folder.getUnpackSize(), folder.crc);
856
return inputStreamStack;
861
* Reads a byte of data.
863
* @return the byte read, or -1 if end of input is reached
864
* @throws IOException
865
* if an I/O error has occurred
867
public int read() throws IOException {
868
return currentEntryInputStream.read();
872
* Reads data into an array of bytes.
874
* @param b the array to write data to
875
* @return the number of bytes read, or -1 if end of input is reached
876
* @throws IOException
877
* if an I/O error has occurred
879
public int read(byte[] b) throws IOException {
880
return read(b, 0, b.length);
884
* Reads data into an array of bytes.
886
* @param b the array to write data to
887
* @param off offset into the buffer to start filling at
888
* @param len of bytes to read
889
* @return the number of bytes read, or -1 if end of input is reached
890
* @throws IOException
891
* if an I/O error has occurred
893
public int read(byte[] b, int off, int len) throws IOException {
894
return currentEntryInputStream.read(b, off, len);
897
private static long readUint64(final DataInput in) throws IOException {
898
int firstByte = in.readUnsignedByte();
901
for (int i = 0; i < 8; i++) {
902
if ((firstByte & mask) == 0) {
903
return value | ((firstByte & (mask - 1)) << (8 * i));
905
long nextByte = in.readUnsignedByte();
906
value |= (nextByte << (8 * i));