Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.commons.compress.archivers.sevenz; import java.io.ByteArrayInputStream; import java.io.DataInput; import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.util.Arrays; import java.util.BitSet; import java.util.zip.CRC32; import org.apache.commons.compress.utils.BoundedInputStream; import org.apache.commons.compress.utils.CRC32VerifyingInputStream; import org.apache.commons.compress.utils.CharsetNames; /** * Reads a 7z file, using RandomAccessFile under * the covers. * <p> * The 7z file format is a flexible container * that can contain many compression and * encryption types, but at the moment only * only Copy, LZMA2, BZIP2, and AES-256 + SHA-256 * are supported, and archive header compression * (when it uses the unsupported LZMA * compression) isn't. So the only archives * that can be read are the following: * <pre> * 7z a -mhc=off [-mhe=on] -mx=0 [-ppassword] archive.7z files * 7z a -mhc=off [-mhe=on] -m0=LZMA2 [-ppassword] archive.7z files * 7z a -mhc=off [-mhe=on] -m0=BZIP2 [-ppassword] archive.7z files * </pre> * <p> * The format is very Windows/Intel specific, * so it uses little-endian byte order, * doesn't store user/group or permission bits, * and represents times using NTFS timestamps * (100 nanosecond units since 1 January 1601). * Hence the official tools recommend against * using it for backup purposes on *nix, and * recommend .tar.7z or .tar.lzma or .tar.xz * instead. * <p> * Both the header and file contents may be * compressed and/or encrypted. With both * encrypted, neither file names nor file * contents can be read, but the use of * encryption isn't plausibly deniable. * * @NotThreadSafe */ public class SevenZFileGiveStream { private static final boolean DEBUG = false; static final int SIGNATURE_HEADER_SIZE = 32; private RandomAccessFile file; private final Archive archive; private int currentEntryIndex = -1; private int currentFolderIndex = -1; private InputStream currentFolderInputStream = null; private InputStream currentEntryInputStream = null; private String password; static final byte[] sevenZSignature = { (byte) '7', (byte) 'z', (byte) 0xBC, (byte) 0xAF, (byte) 0x27, (byte) 0x1C }; public SevenZFileGiveStream(final File filename, final String password) throws IOException { boolean succeeded = false; this.password = password; this.file = new RandomAccessFile(filename, "r"); try { archive = readHeaders(); succeeded = true; } finally { if (!succeeded) { this.file.close(); } } } public SevenZFileGiveStream(final File filename) throws IOException { this(filename, null); } public void close() { if (file != null) { try { file.close(); } catch (IOException ignored) { // NOPMD } file = null; } } private static void debug(String str) { if (DEBUG) { System.out.println(str); } } private static void debug(String fmt, Object... args) { if (DEBUG) { System.out.format(fmt, args); } } public SevenZArchiveEntry getNextEntry() throws IOException { if (currentEntryIndex >= (archive.files.length - 1)) { return null; } ++currentEntryIndex; final SevenZArchiveEntry entry = archive.files[currentEntryIndex]; buildDecodingStream(); return entry; } private Archive readHeaders() throws IOException { debug("SignatureHeader"); final byte[] signature = new byte[6]; file.readFully(signature); if (!Arrays.equals(signature, sevenZSignature)) { throw new IOException("Bad 7z signature"); } // 7zFormat.txt has it wrong - it's first major then minor final byte archiveVersionMajor = file.readByte(); final byte archiveVersionMinor = file.readByte(); debug(" archiveVersion major=%d, minor=%d\n", archiveVersionMajor, archiveVersionMinor); if (archiveVersionMajor != 0) { throw new IOException( String.format("Unsupported 7z version (%d,%d)", archiveVersionMajor, archiveVersionMinor)); } final int startHeaderCrc = Integer.reverseBytes(file.readInt()); final StartHeader startHeader = readStartHeader(startHeaderCrc); final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize; if (nextHeaderSizeInt != startHeader.nextHeaderSize) { throw new IOException("cannot handle nextHeaderSize " + startHeader.nextHeaderSize); } file.seek(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset); final byte[] nextHeader = new byte[nextHeaderSizeInt]; file.readFully(nextHeader); final CRC32 crc = new CRC32(); crc.update(nextHeader); if (startHeader.nextHeaderCrc != (int) crc.getValue()) { throw new IOException("NextHeader CRC mismatch"); } final ByteArrayInputStream byteStream = new ByteArrayInputStream(nextHeader); DataInputStream nextHeaderInputStream = new DataInputStream(byteStream); Archive archive = new Archive(); int nid = nextHeaderInputStream.readUnsignedByte(); if (nid == NID.kEncodedHeader) { nextHeaderInputStream = readEncodedHeader(nextHeaderInputStream, archive); // Archive gets rebuilt with the new header archive = new Archive(); nid = nextHeaderInputStream.readUnsignedByte(); } if (nid == NID.kHeader) { readHeader(nextHeaderInputStream, archive); } else { throw new IOException("Broken or unsupported archive: no Header"); } return archive; } private StartHeader readStartHeader(final int startHeaderCrc) throws IOException { final StartHeader startHeader = new StartHeader(); DataInputStream dataInputStream = null; try { dataInputStream = new DataInputStream(new CRC32VerifyingInputStream( new BoundedRandomAccessFileInputStream(file, 20), 20, startHeaderCrc)); startHeader.nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong()); startHeader.nextHeaderSize = Long.reverseBytes(dataInputStream.readLong()); startHeader.nextHeaderCrc = Integer.reverseBytes(dataInputStream.readInt()); return startHeader; } finally { if (dataInputStream != null) { dataInputStream.close(); } } } private void readHeader(final DataInput header, final Archive archive) throws IOException { debug("Header"); int nid = header.readUnsignedByte(); if (nid == NID.kArchiveProperties) { readArchiveProperties(header); nid = header.readUnsignedByte(); } if (nid == NID.kAdditionalStreamsInfo) { throw new IOException("Additional streams unsupported"); //nid = header.readUnsignedByte(); } if (nid == NID.kMainStreamsInfo) { readStreamsInfo(header, archive); nid = header.readUnsignedByte(); } if (nid == NID.kFilesInfo) { readFilesInfo(header, archive); nid = header.readUnsignedByte(); } if (nid != NID.kEnd) { throw new IOException("Badly terminated header"); } } private void readArchiveProperties(final DataInput input) throws IOException { // FIXME: the reference implementation just throws them away? debug("ArchiveProperties"); int nid = input.readUnsignedByte(); while (nid != NID.kEnd) { final long propertySize = readUint64(input); final byte[] property = new byte[(int) propertySize]; input.readFully(property); nid = input.readUnsignedByte(); } } private DataInputStream readEncodedHeader(final DataInputStream header, final Archive archive) throws IOException { debug("EncodedHeader"); readStreamsInfo(header, archive); // FIXME: merge with buildDecodingStream()/buildDecoderStack() at some stage? final Folder folder = archive.folders[0]; final int firstPackStreamIndex = 0; final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos + 0; file.seek(folderOffset); InputStream inputStreamStack = new BoundedRandomAccessFileInputStream(file, archive.packSizes[firstPackStreamIndex]); for (final Coder coder : folder.coders) { if (coder.numInStreams != 1 || coder.numOutStreams != 1) { throw new IOException("Multi input/output stream coders are not yet supported"); } inputStreamStack = Coders.addDecoder(inputStreamStack, coder, password); } if (folder.hasCrc) { inputStreamStack = new CRC32VerifyingInputStream(inputStreamStack, folder.getUnpackSize(), folder.crc); } final byte[] nextHeader = new byte[(int) folder.getUnpackSize()]; final DataInputStream nextHeaderInputStream = new DataInputStream(inputStreamStack); try { nextHeaderInputStream.readFully(nextHeader); } finally { nextHeaderInputStream.close(); } return new DataInputStream(new ByteArrayInputStream(nextHeader)); //throw new IOException("LZMA compression unsupported, so files with compressed header cannot be read"); // FIXME: this extracts the header to an LZMA file which can then be // manually decompressed. // long offset = SIGNATURE_HEADER_SIZE + archive.packPos; // file.seek(offset); // long unpackSize = archive.folders[0].getUnpackSize(); // byte[] packed = new byte[(int)archive.packSizes[0]]; // file.readFully(packed); // // FileOutputStream fos = new FileOutputStream(new File("/tmp/encodedHeader.7z")); // fos.write(archive.folders[0].coders[0].properties); // // size - assuming < 256 // fos.write((int)(unpackSize & 0xff)); // fos.write(0); // fos.write(0); // fos.write(0); // fos.write(0); // fos.write(0); // fos.write(0); // fos.write(0); // fos.write(packed); // fos.close(); } private void readStreamsInfo(final DataInput header, final Archive archive) throws IOException { debug("StreamsInfo"); int nid = header.readUnsignedByte(); if (nid == NID.kPackInfo) { readPackInfo(header, archive); nid = header.readUnsignedByte(); } if (nid == NID.kUnpackInfo) { readUnpackInfo(header, archive); nid = header.readUnsignedByte(); } if (nid == NID.kSubStreamsInfo) { readSubStreamsInfo(header, archive); nid = header.readUnsignedByte(); } if (nid != NID.kEnd) { throw new IOException("Badly terminated StreamsInfo"); } } private void readPackInfo(final DataInput header, final Archive archive) throws IOException { debug("PackInfo"); archive.packPos = readUint64(header); final long numPackStreams = readUint64(header); debug(" " + numPackStreams + " pack streams"); int nid = header.readUnsignedByte(); if (nid == NID.kSize) { archive.packSizes = new long[(int) numPackStreams]; for (int i = 0; i < archive.packSizes.length; i++) { archive.packSizes[i] = readUint64(header); debug(" pack size %d is %d\n", i, archive.packSizes[i]); } nid = header.readUnsignedByte(); } if (nid == NID.kCRC) { archive.packCrcsDefined = readAllOrBits(header, (int) numPackStreams); archive.packCrcs = new int[(int) numPackStreams]; for (int i = 0; i < (int) numPackStreams; i++) { if (archive.packCrcsDefined.get(i)) { archive.packCrcs[i] = Integer.reverseBytes(header.readInt()); } } nid = header.readUnsignedByte(); } if (nid != NID.kEnd) { throw new IOException("Badly terminated PackInfo (" + nid + ")"); } } private void readUnpackInfo(final DataInput header, final Archive archive) throws IOException { debug("UnpackInfo"); int nid = header.readUnsignedByte(); if (nid != NID.kFolder) { throw new IOException("Expected kFolder, got " + nid); } final long numFolders = readUint64(header); debug(" " + numFolders + " folders"); final Folder[] folders = new Folder[(int) numFolders]; archive.folders = folders; final int external = header.readUnsignedByte(); if (external != 0) { throw new IOException("External unsupported"); } else { for (int i = 0; i < (int) numFolders; i++) { folders[i] = readFolder(header); } } nid = header.readUnsignedByte(); if (nid != NID.kCodersUnpackSize) { throw new IOException("Expected kCodersUnpackSize, got " + nid); } for (final Folder folder : folders) { folder.unpackSizes = new long[(int) folder.totalOutputStreams]; for (int i = 0; i < folder.totalOutputStreams; i++) { folder.unpackSizes[i] = readUint64(header); } } nid = header.readUnsignedByte(); if (nid == NID.kCRC) { final BitSet crcsDefined = readAllOrBits(header, (int) numFolders); for (int i = 0; i < (int) numFolders; i++) { if (crcsDefined.get(i)) { folders[i].hasCrc = true; folders[i].crc = Integer.reverseBytes(header.readInt()); } else { folders[i].hasCrc = false; } } nid = header.readUnsignedByte(); } if (nid != NID.kEnd) { throw new IOException("Badly terminated UnpackInfo"); } } private void readSubStreamsInfo(final DataInput header, final Archive archive) throws IOException { debug("SubStreamsInfo"); for (final Folder folder : archive.folders) { folder.numUnpackSubStreams = 1; } int totalUnpackStreams = archive.folders.length; int nid = header.readUnsignedByte(); if (nid == NID.kNumUnpackStream) { totalUnpackStreams = 0; for (final Folder folder : archive.folders) { final long numStreams = readUint64(header); folder.numUnpackSubStreams = (int) numStreams; totalUnpackStreams += numStreams; } nid = header.readUnsignedByte(); } final SubStreamsInfo subStreamsInfo = new SubStreamsInfo(); subStreamsInfo.unpackSizes = new long[totalUnpackStreams]; subStreamsInfo.hasCrc = new BitSet(totalUnpackStreams); subStreamsInfo.crcs = new int[totalUnpackStreams]; int nextUnpackStream = 0; for (final Folder folder : archive.folders) { if (folder.numUnpackSubStreams == 0) { continue; } long sum = 0; if (nid == NID.kSize) { for (int i = 0; i < (folder.numUnpackSubStreams - 1); i++) { final long size = readUint64(header); subStreamsInfo.unpackSizes[nextUnpackStream++] = size; sum += size; } } subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum; } if (nid == NID.kSize) { nid = header.readUnsignedByte(); } int numDigests = 0; for (final Folder folder : archive.folders) { if (folder.numUnpackSubStreams != 1 || !folder.hasCrc) { numDigests += folder.numUnpackSubStreams; } } if (nid == NID.kCRC) { final BitSet hasMissingCrc = readAllOrBits(header, numDigests); final int[] missingCrcs = new int[numDigests]; for (int i = 0; i < numDigests; i++) { if (hasMissingCrc.get(i)) { missingCrcs[i] = Integer.reverseBytes(header.readInt()); } } int nextCrc = 0; int nextMissingCrc = 0; for (final Folder folder : archive.folders) { if (folder.numUnpackSubStreams == 1 && folder.hasCrc) { subStreamsInfo.hasCrc.set(nextCrc, true); subStreamsInfo.crcs[nextCrc] = folder.crc; ++nextCrc; } else { for (int i = 0; i < folder.numUnpackSubStreams; i++) { subStreamsInfo.hasCrc.set(nextCrc, hasMissingCrc.get(nextMissingCrc)); subStreamsInfo.crcs[nextCrc] = missingCrcs[nextMissingCrc]; ++nextCrc; ++nextMissingCrc; } } } nid = header.readUnsignedByte(); } if (nid != NID.kEnd) { throw new IOException("Badly terminated SubStreamsInfo"); } archive.subStreamsInfo = subStreamsInfo; } private Folder readFolder(final DataInput header) throws IOException { final Folder folder = new Folder(); final long numCoders = readUint64(header); final Coder[] coders = new Coder[(int) numCoders]; long totalInStreams = 0; long totalOutStreams = 0; for (int i = 0; i < coders.length; i++) { coders[i] = new Coder(); int bits = header.readUnsignedByte(); final int idSize = bits & 0xf; final boolean isSimple = ((bits & 0x10) == 0); final boolean hasAttributes = ((bits & 0x20) != 0); final boolean moreAlternativeMethods = ((bits & 0x80) != 0); coders[i].decompressionMethodId = new byte[idSize]; header.readFully(coders[i].decompressionMethodId); if (isSimple) { coders[i].numInStreams = 1; coders[i].numOutStreams = 1; } else { coders[i].numInStreams = readUint64(header); coders[i].numOutStreams = readUint64(header); } totalInStreams += coders[i].numInStreams; totalOutStreams += coders[i].numOutStreams; if (hasAttributes) { final long propertiesSize = readUint64(header); coders[i].properties = new byte[(int) propertiesSize]; header.readFully(coders[i].properties); } if (DEBUG) { final StringBuilder methodStr = new StringBuilder(); for (final byte b : coders[i].decompressionMethodId) { methodStr.append(String.format("%02X", 0xff & b)); } debug(" coder entry %d numInStreams=%d, numOutStreams=%d, method=%s, properties=%s\n", i, coders[i].numInStreams, coders[i].numOutStreams, methodStr.toString(), Arrays.toString(coders[i].properties)); } // would need to keep looping as above: while (moreAlternativeMethods) { throw new IOException("Alternative methods are unsupported, please report. " + "The reference implementation doesn't support them either."); } } folder.coders = coders; folder.totalInputStreams = totalInStreams; folder.totalOutputStreams = totalOutStreams; if (totalOutStreams == 0) { throw new IOException("Total output streams can't be 0"); } final long numBindPairs = totalOutStreams - 1; final BindPair[] bindPairs = new BindPair[(int) numBindPairs]; for (int i = 0; i < bindPairs.length; i++) { bindPairs[i] = new BindPair(); bindPairs[i].inIndex = readUint64(header); bindPairs[i].outIndex = readUint64(header); debug(" bind pair in=%d out=%d\n", bindPairs[i].inIndex, bindPairs[i].outIndex); } folder.bindPairs = bindPairs; if (totalInStreams < numBindPairs) { throw new IOException("Total input streams can't be less than the number of bind pairs"); } final long numPackedStreams = totalInStreams - numBindPairs; final long packedStreams[] = new long[(int) numPackedStreams]; if (numPackedStreams == 1) { int i; for (i = 0; i < (int) totalInStreams; i++) { if (folder.findBindPairForInStream(i) < 0) { break; } } if (i == (int) totalInStreams) { throw new IOException("Couldn't find stream's bind pair index"); } packedStreams[0] = i; } else { for (int i = 0; i < (int) numPackedStreams; i++) { packedStreams[i] = readUint64(header); } } folder.packedStreams = packedStreams; return folder; } private BitSet readAllOrBits(final DataInput header, final int size) throws IOException { final int areAllDefined = header.readUnsignedByte(); final BitSet bits; if (areAllDefined != 0) { bits = new BitSet(size); for (int i = 0; i < size; i++) { bits.set(i, true); } } else { bits = readBits(header, size); } return bits; } private BitSet readBits(final DataInput header, final int size) throws IOException { final BitSet bits = new BitSet(size); int mask = 0; int cache = 0; for (int i = 0; i < size; i++) { if (mask == 0) { mask = 0x80; cache = header.readUnsignedByte(); } bits.set(i, (cache & mask) != 0); mask >>>= 1; } return bits; } private void readFilesInfo(final DataInput header, final Archive archive) throws IOException { debug("FilesInfo"); final long numFiles = readUint64(header); final SevenZArchiveEntry[] files = new SevenZArchiveEntry[(int) numFiles]; for (int i = 0; i < files.length; i++) { files[i] = new SevenZArchiveEntry(); } BitSet isEmptyStream = null; BitSet isEmptyFile = null; BitSet isAnti = null; while (true) { final int propertyType = header.readUnsignedByte(); if (propertyType == 0) { break; } long size = readUint64(header); switch (propertyType) { case NID.kEmptyStream: { debug(" kEmptyStream"); isEmptyStream = readBits(header, files.length); break; } case NID.kEmptyFile: { debug(" kEmptyFile"); if (isEmptyStream == null) { // protect against NPE throw new IOException("Header format error: kEmptyStream must appear before kEmptyFile"); } isEmptyFile = readBits(header, isEmptyStream.cardinality()); break; } case NID.kAnti: { debug(" kAnti"); if (isEmptyStream == null) { // protect against NPE throw new IOException("Header format error: kEmptyStream must appear before kAnti"); } isAnti = readBits(header, isEmptyStream.cardinality()); break; } case NID.kName: { debug(" kNames"); final int external = header.readUnsignedByte(); if (external != 0) { throw new IOException("Not implemented"); } else { if (((size - 1) & 1) != 0) { throw new IOException("File names length invalid"); } final byte[] names = new byte[(int) (size - 1)]; header.readFully(names); int nextFile = 0; int nextName = 0; for (int i = 0; i < names.length; i += 2) { if (names[i] == 0 && names[i + 1] == 0) { files[nextFile++] .setName(new String(names, nextName, i - nextName, CharsetNames.UTF_16LE)); nextName = i + 2; } } if (nextName != names.length || nextFile != files.length) { throw new IOException("Error parsing file names"); } } break; } case NID.kCTime: { debug(" kCreationTime"); final BitSet timesDefined = readAllOrBits(header, files.length); final int external = header.readUnsignedByte(); if (external != 0) { throw new IOException("Unimplemented"); } else { for (int i = 0; i < files.length; i++) { files[i].setHasCreationDate(timesDefined.get(i)); if (files[i].getHasCreationDate()) { files[i].setCreationDate(Long.reverseBytes(header.readLong())); } } } break; } case NID.kATime: { debug(" kLastAccessTime"); final BitSet timesDefined = readAllOrBits(header, files.length); final int external = header.readUnsignedByte(); if (external != 0) { throw new IOException("Unimplemented"); } else { for (int i = 0; i < files.length; i++) { files[i].setHasAccessDate(timesDefined.get(i)); if (files[i].getHasAccessDate()) { files[i].setAccessDate(Long.reverseBytes(header.readLong())); } } } break; } case NID.kMTime: { debug(" kLastWriteTime"); final BitSet timesDefined = readAllOrBits(header, files.length); final int external = header.readUnsignedByte(); if (external != 0) { throw new IOException("Unimplemented"); } else { for (int i = 0; i < files.length; i++) { files[i].setHasLastModifiedDate(timesDefined.get(i)); if (files[i].getHasLastModifiedDate()) { files[i].setLastModifiedDate(Long.reverseBytes(header.readLong())); } } } break; } case NID.kWinAttributes: { debug(" kWinAttributes"); final BitSet attributesDefined = readAllOrBits(header, files.length); final int external = header.readUnsignedByte(); if (external != 0) { throw new IOException("Unimplemented"); } else { for (int i = 0; i < files.length; i++) { files[i].setHasWindowsAttributes(attributesDefined.get(i)); if (files[i].getHasWindowsAttributes()) { files[i].setWindowsAttributes(Integer.reverseBytes(header.readInt())); } } } break; } case NID.kStartPos: { debug(" kStartPos"); throw new IOException("kStartPos is unsupported, please report"); } case NID.kDummy: { debug(" kDummy"); throw new IOException("kDummy is unsupported, please report"); } default: { throw new IOException("Unknown property " + propertyType); // FIXME: Should actually: //header.skipBytes((int)size); } } } int nonEmptyFileCounter = 0; int emptyFileCounter = 0; for (int i = 0; i < files.length; i++) { files[i].setHasStream((isEmptyStream == null) ? true : !isEmptyStream.get(i)); if (files[i].hasStream()) { files[i].setDirectory(false); files[i].setAntiItem(false); files[i].setHasCrc(archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter)); files[i].setCrc(archive.subStreamsInfo.crcs[nonEmptyFileCounter]); files[i].setSize(archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter]); ++nonEmptyFileCounter; } else { files[i].setDirectory((isEmptyFile == null) ? true : !isEmptyFile.get(emptyFileCounter)); files[i].setAntiItem((isAnti == null) ? false : isAnti.get(emptyFileCounter)); files[i].setHasCrc(false); files[i].setSize(0); ++emptyFileCounter; } } archive.files = files; calculateStreamMap(archive); } private void calculateStreamMap(final Archive archive) throws IOException { final StreamMap streamMap = new StreamMap(); int nextFolderPackStreamIndex = 0; final int numFolders = (archive.folders != null) ? archive.folders.length : 0; streamMap.folderFirstPackStreamIndex = new int[numFolders]; for (int i = 0; i < numFolders; i++) { streamMap.folderFirstPackStreamIndex[i] = nextFolderPackStreamIndex; nextFolderPackStreamIndex += archive.folders[i].packedStreams.length; } long nextPackStreamOffset = 0; final int numPackSizes = (archive.packSizes != null) ? archive.packSizes.length : 0; streamMap.packStreamOffsets = new long[numPackSizes]; for (int i = 0; i < numPackSizes; i++) { streamMap.packStreamOffsets[i] = nextPackStreamOffset; nextPackStreamOffset += archive.packSizes[i]; } streamMap.folderFirstFileIndex = new int[numFolders]; streamMap.fileFolderIndex = new int[archive.files.length]; int nextFolderIndex = 0; int nextFolderUnpackStreamIndex = 0; for (int i = 0; i < archive.files.length; i++) { if (!archive.files[i].hasStream() && nextFolderUnpackStreamIndex == 0) { streamMap.fileFolderIndex[i] = -1; continue; } if (nextFolderUnpackStreamIndex == 0) { for (; nextFolderIndex < archive.folders.length; ++nextFolderIndex) { streamMap.folderFirstFileIndex[nextFolderIndex] = i; if (archive.folders[nextFolderIndex].numUnpackSubStreams > 0) { break; } } if (nextFolderIndex >= archive.folders.length) { throw new IOException("Too few folders in archive"); } } streamMap.fileFolderIndex[i] = nextFolderIndex; if (!archive.files[i].hasStream()) { continue; } ++nextFolderUnpackStreamIndex; if (nextFolderUnpackStreamIndex >= archive.folders[nextFolderIndex].numUnpackSubStreams) { ++nextFolderIndex; nextFolderUnpackStreamIndex = 0; } } archive.streamMap = streamMap; } private void buildDecodingStream() throws IOException { final int folderIndex = archive.streamMap.fileFolderIndex[currentEntryIndex]; if (folderIndex < 0) { currentEntryInputStream = new BoundedInputStream(new ByteArrayInputStream(new byte[0]), 0); return; } if (currentFolderIndex == folderIndex) { // need to advance the folder input stream past the current file drainPreviousEntry(); } else { currentFolderIndex = folderIndex; if (currentFolderInputStream != null) { currentFolderInputStream.close(); currentFolderInputStream = null; } final Folder folder = archive.folders[folderIndex]; final int firstPackStreamIndex = archive.streamMap.folderFirstPackStreamIndex[folderIndex]; final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos + archive.streamMap.packStreamOffsets[firstPackStreamIndex]; currentFolderInputStream = buildDecoderStack(folder, folderOffset, firstPackStreamIndex); } final SevenZArchiveEntry file = archive.files[currentEntryIndex]; final InputStream fileStream = new BoundedInputStream(currentFolderInputStream, file.getSize()); if (file.getHasCrc()) { currentEntryInputStream = new CRC32VerifyingInputStream(fileStream, file.getSize(), file.getCrc()); } else { currentEntryInputStream = fileStream; } } private void drainPreviousEntry() throws IOException { if (currentEntryInputStream != null) { final byte[] buffer = new byte[64 * 1024]; while (currentEntryInputStream.read(buffer) >= 0) { // NOPMD } currentEntryInputStream.close(); currentEntryInputStream = null; } } private InputStream buildDecoderStack(final Folder folder, final long folderOffset, final int firstPackStreamIndex) throws IOException { file.seek(folderOffset); InputStream inputStreamStack = new BoundedRandomAccessFileInputStream(file, archive.packSizes[firstPackStreamIndex]); for (final Coder coder : folder.coders) { if (coder.numInStreams != 1 || coder.numOutStreams != 1) { throw new IOException("Multi input/output stream coders are not yet supported"); } inputStreamStack = Coders.addDecoder(inputStreamStack, coder, password); } if (folder.hasCrc) { return new CRC32VerifyingInputStream(inputStreamStack, folder.getUnpackSize(), folder.crc); } else { return inputStreamStack; } } public InputStream getCurrentEntryInputStream() { return currentEntryInputStream; } public int read() throws IOException { return currentEntryInputStream.read(); } public int read(byte[] b) throws IOException { return read(b, 0, b.length); } public int read(byte[] b, int off, int len) throws IOException { return currentEntryInputStream.read(b, off, len); } private static long readUint64(final DataInput in) throws IOException { int firstByte = in.readUnsignedByte(); int mask = 0x80; long value = 0; for (int i = 0; i < 8; i++) { if ((firstByte & mask) == 0) { return value | ((firstByte & (mask - 1)) << (8 * i)); } long nextByte = in.readUnsignedByte(); value |= (nextByte << (8 * i)); mask >>>= 1; } return value; } }