SevenZFile.java

  1. /*
  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
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  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.
  16.  */
  17. package org.apache.commons.compress.archivers.sevenz;

  18. import static java.nio.charset.StandardCharsets.UTF_16LE;

  19. import java.io.BufferedInputStream;
  20. import java.io.ByteArrayInputStream;
  21. import java.io.Closeable;
  22. import java.io.DataInputStream;
  23. import java.io.EOFException;
  24. import java.io.File;
  25. import java.io.FilterInputStream;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.nio.ByteBuffer;
  29. import java.nio.ByteOrder;
  30. import java.nio.channels.Channels;
  31. import java.nio.channels.SeekableByteChannel;
  32. import java.nio.file.Files;
  33. import java.nio.file.OpenOption;
  34. import java.nio.file.Path;
  35. import java.nio.file.StandardOpenOption;
  36. import java.util.ArrayList;
  37. import java.util.Arrays;
  38. import java.util.BitSet;
  39. import java.util.EnumSet;
  40. import java.util.LinkedHashMap;
  41. import java.util.LinkedList;
  42. import java.util.List;
  43. import java.util.Map;
  44. import java.util.Objects;
  45. import java.util.zip.CRC32;
  46. import java.util.zip.CheckedInputStream;

  47. import org.apache.commons.compress.MemoryLimitException;
  48. import org.apache.commons.compress.utils.ByteUtils;
  49. import org.apache.commons.compress.utils.IOUtils;
  50. import org.apache.commons.compress.utils.InputStreamStatistics;
  51. import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
  52. import org.apache.commons.io.build.AbstractOrigin.ByteArrayOrigin;
  53. import org.apache.commons.io.build.AbstractStreamBuilder;
  54. import org.apache.commons.io.input.BoundedInputStream;
  55. import org.apache.commons.io.input.ChecksumInputStream;

  56. /**
  57.  * Reads a 7z file, using SeekableByteChannel under the covers.
  58.  * <p>
  59.  * The 7z file format is a flexible container that can contain many compression and encryption types, but at the moment only only Copy, LZMA, LZMA2, BZIP2,
  60.  * Deflate and AES-256 + SHA-256 are supported.
  61.  * </p>
  62.  * <p>
  63.  * 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
  64.  * timestamps (100 nanosecond units since 1 January 1601). Hence the official tools recommend against using it for backup purposes on *nix, and recommend
  65.  * .tar.7z or .tar.lzma or .tar.xz instead.
  66.  * </p>
  67.  * <p>
  68.  * 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
  69.  * encryption isn't plausibly deniable.
  70.  * </p>
  71.  * <p>
  72.  * Multi volume archives can be read by concatenating the parts in correct order - either manually or by using {link
  73.  * org.apache.commons.compress.utils.MultiReadOnlySeekableByteChannel} for example.
  74.  * </p>
  75.  *
  76.  * @NotThreadSafe
  77.  * @since 1.6
  78.  */
  79. public class SevenZFile implements Closeable {

  80.     private static final class ArchiveStatistics {
  81.         private int numberOfPackedStreams;
  82.         private long numberOfCoders;
  83.         private long numberOfOutStreams;
  84.         private long numberOfInStreams;
  85.         private long numberOfUnpackSubStreams;
  86.         private int numberOfFolders;
  87.         private BitSet folderHasCrc;
  88.         private int numberOfEntries;
  89.         private int numberOfEntriesWithStream;

  90.         void assertValidity(final int maxMemoryLimitInKb) throws IOException {
  91.             if (numberOfEntriesWithStream > 0 && numberOfFolders == 0) {
  92.                 throw new IOException("archive with entries but no folders");
  93.             }
  94.             if (numberOfEntriesWithStream > numberOfUnpackSubStreams) {
  95.                 throw new IOException("archive doesn't contain enough substreams for entries");
  96.             }

  97.             final long memoryNeededInKb = estimateSize() / 1024;
  98.             if (maxMemoryLimitInKb < memoryNeededInKb) {
  99.                 throw new MemoryLimitException(memoryNeededInKb, maxMemoryLimitInKb);
  100.             }
  101.         }

  102.         private long bindPairSize() {
  103.             return 16;
  104.         }

  105.         private long coderSize() {
  106.             return 2 /* methodId is between 1 and four bytes currently, COPY and LZMA2 are the most common with 1 */
  107.                     + 16 + 4 /* properties, guess */
  108.             ;
  109.         }

  110.         private long entrySize() {
  111.             return 100; /* real size depends on name length, everything without name is about 70 bytes */
  112.         }

  113.         long estimateSize() {
  114.             final long lowerBound = 16L * numberOfPackedStreams /* packSizes, packCrcs in Archive */
  115.                     + numberOfPackedStreams / 8 /* packCrcsDefined in Archive */
  116.                     + numberOfFolders * folderSize() /* folders in Archive */
  117.                     + numberOfCoders * coderSize() /* coders in Folder */
  118.                     + (numberOfOutStreams - numberOfFolders) * bindPairSize() /* bindPairs in Folder */
  119.                     + 8L * (numberOfInStreams - numberOfOutStreams + numberOfFolders) /* packedStreams in Folder */
  120.                     + 8L * numberOfOutStreams /* unpackSizes in Folder */
  121.                     + numberOfEntries * entrySize() /* files in Archive */
  122.                     + streamMapSize();
  123.             return 2 * lowerBound /* conservative guess */;
  124.         }

  125.         private long folderSize() {
  126.             return 30; /* nested arrays are accounted for separately */
  127.         }

  128.         private long streamMapSize() {
  129.             return 8 * numberOfFolders /* folderFirstPackStreamIndex, folderFirstFileIndex */
  130.                     + 8 * numberOfPackedStreams /* packStreamOffsets */
  131.                     + 4 * numberOfEntries /* fileFolderIndex */
  132.             ;
  133.         }

  134.         @Override
  135.         public String toString() {
  136.             return "Archive with " + numberOfEntries + " entries in " + numberOfFolders + " folders. Estimated size " + estimateSize() / 1024L + " kB.";
  137.         }
  138.     }

  139.     /**
  140.      * Builds new instances of {@link SevenZFile}.
  141.      *
  142.      * @since 1.26.0
  143.      */
  144.     public static class Builder extends AbstractStreamBuilder<SevenZFile, Builder> {

  145.         static final int MEMORY_LIMIT_IN_KB = Integer.MAX_VALUE;
  146.         static final boolean USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES = false;
  147.         static final boolean TRY_TO_RECOVER_BROKEN_ARCHIVES = false;

  148.         private SeekableByteChannel seekableByteChannel;
  149.         private String defaultName = DEFAULT_FILE_NAME;
  150.         private byte[] password;
  151.         private int maxMemoryLimitKb = MEMORY_LIMIT_IN_KB;
  152.         private boolean useDefaultNameForUnnamedEntries = USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES;
  153.         private boolean tryToRecoverBrokenArchives = TRY_TO_RECOVER_BROKEN_ARCHIVES;

  154.         @SuppressWarnings("resource") // Caller closes
  155.         @Override
  156.         public SevenZFile get() throws IOException {
  157.             final SeekableByteChannel actualChannel;
  158.             final String actualDescription;
  159.             if (seekableByteChannel != null) {
  160.                 actualChannel = seekableByteChannel;
  161.                 actualDescription = defaultName;
  162.             } else if (checkOrigin() instanceof ByteArrayOrigin) {
  163.                 actualChannel = new SeekableInMemoryByteChannel(checkOrigin().getByteArray());
  164.                 actualDescription = defaultName;
  165.             } else {
  166.                 OpenOption[] openOptions = getOpenOptions();
  167.                 if (openOptions.length == 0) {
  168.                     openOptions = new OpenOption[] { StandardOpenOption.READ };
  169.                 }
  170.                 final Path path = getPath();
  171.                 actualChannel = Files.newByteChannel(path, openOptions);
  172.                 actualDescription = path.toAbsolutePath().toString();
  173.             }
  174.             final boolean closeOnError = seekableByteChannel != null;
  175.             return new SevenZFile(actualChannel, actualDescription, password, closeOnError, maxMemoryLimitKb, useDefaultNameForUnnamedEntries,
  176.                     tryToRecoverBrokenArchives);
  177.         }

  178.         /**
  179.          * Sets the default name.
  180.          *
  181.          * @param defaultName the default name.
  182.          * @return {@code this} instance.
  183.          */
  184.         public Builder setDefaultName(final String defaultName) {
  185.             this.defaultName = defaultName;
  186.             return this;
  187.         }

  188.         /**
  189.          * Sets the maximum amount of memory in kilobytes to use for parsing the archive and during extraction.
  190.          * <p>
  191.          * Not all codecs honor this setting. Currently only LZMA and LZMA2 are supported.
  192.          * </p>
  193.          *
  194.          * @param maxMemoryLimitKb the max memory limit in kilobytes.
  195.          * @return {@code this} instance.
  196.          */
  197.         public Builder setMaxMemoryLimitKb(final int maxMemoryLimitKb) {
  198.             this.maxMemoryLimitKb = maxMemoryLimitKb;
  199.             return this;
  200.         }

  201.         /**
  202.          * Sets the password.
  203.          *
  204.          * @param password the password.
  205.          * @return {@code this} instance.
  206.          */
  207.         public Builder setPassword(final byte[] password) {
  208.             this.password = password != null ? password.clone() : null;
  209.             return this;
  210.         }

  211.         /**
  212.          * Sets the password.
  213.          *
  214.          * @param password the password.
  215.          * @return {@code this} instance.
  216.          */
  217.         public Builder setPassword(final char[] password) {
  218.             this.password = password != null ? AES256SHA256Decoder.utf16Decode(password.clone()) : null;
  219.             return this;
  220.         }

  221.         /**
  222.          * Sets the password.
  223.          *
  224.          * @param password the password.
  225.          * @return {@code this} instance.
  226.          */
  227.         public Builder setPassword(final String password) {
  228.             this.password = password != null ? AES256SHA256Decoder.utf16Decode(password.toCharArray()) : null;
  229.             return this;
  230.         }

  231.         /**
  232.          * Sets the input channel.
  233.          *
  234.          * @param seekableByteChannel the input channel.
  235.          * @return {@code this} instance.
  236.          */
  237.         public Builder setSeekableByteChannel(final SeekableByteChannel seekableByteChannel) {
  238.             this.seekableByteChannel = seekableByteChannel;
  239.             return this;
  240.         }

  241.         /**
  242.          * Sets whether {@link SevenZFile} will try to recover broken archives where the CRC of the file's metadata is 0.
  243.          * <p>
  244.          * This special kind of broken archive is encountered when mutli volume archives are closed prematurely. If you enable this option SevenZFile will trust
  245.          * data that looks as if it could contain metadata of an archive and allocate big amounts of memory. It is strongly recommended to not enable this
  246.          * option without setting {@link #setMaxMemoryLimitKb(int)} at the same time.
  247.          * </p>
  248.          *
  249.          * @param tryToRecoverBrokenArchives whether {@link SevenZFile} will try to recover broken archives where the CRC of the file's metadata is 0.
  250.          * @return {@code this} instance.
  251.          */
  252.         public Builder setTryToRecoverBrokenArchives(final boolean tryToRecoverBrokenArchives) {
  253.             this.tryToRecoverBrokenArchives = tryToRecoverBrokenArchives;
  254.             return this;
  255.         }

  256.         /**
  257.          * Sets whether entries without a name should get their names set to the archive's default file name.
  258.          *
  259.          * @param useDefaultNameForUnnamedEntries whether entries without a name should get their names set to the archive's default file name.
  260.          * @return {@code this} instance.
  261.          */
  262.         public Builder setUseDefaultNameForUnnamedEntries(final boolean useDefaultNameForUnnamedEntries) {
  263.             this.useDefaultNameForUnnamedEntries = useDefaultNameForUnnamedEntries;
  264.             return this;
  265.         }

  266.     }

  267.     static final int SIGNATURE_HEADER_SIZE = 32;

  268.     private static final String DEFAULT_FILE_NAME = "unknown archive";

  269.     /** Shared with SevenZOutputFile and tests, neither mutates it. */
  270.     static final byte[] sevenZSignature = { // NOSONAR
  271.             (byte) '7', (byte) 'z', (byte) 0xBC, (byte) 0xAF, (byte) 0x27, (byte) 0x1C };

  272.     private static int assertFitsIntoNonNegativeInt(final String what, final long value) throws IOException {
  273.         if (value > Integer.MAX_VALUE || value < 0) {
  274.             throw new IOException(String.format("Cannot handle % %,d", what, value));
  275.         }
  276.         return (int) value;
  277.     }

  278.     /**
  279.      * Creates a new Builder.
  280.      *
  281.      * @return a new Builder.
  282.      * @since 1.26.0
  283.      */
  284.     public static Builder builder() {
  285.         return new Builder();
  286.     }

  287.     private static ByteBuffer checkEndOfFile(final ByteBuffer buf, final int expectRemaining) throws EOFException {
  288.         final int remaining = buf.remaining();
  289.         if (remaining < expectRemaining) {
  290.             throw new EOFException(String.format("remaining %,d < expectRemaining %,d", remaining, expectRemaining));
  291.         }
  292.         return buf;
  293.     }

  294.     private static void get(final ByteBuffer buf, final byte[] to) throws EOFException {
  295.         checkEndOfFile(buf, to.length).get(to);
  296.     }

  297.     private static char getChar(final ByteBuffer buf) throws EOFException {
  298.         return checkEndOfFile(buf, Character.BYTES).getChar();
  299.     }

  300.     private static int getInt(final ByteBuffer buf) throws EOFException {
  301.         return checkEndOfFile(buf, Integer.BYTES).getInt();
  302.     }

  303.     private static long getLong(final ByteBuffer buf) throws EOFException {
  304.         return checkEndOfFile(buf, Long.BYTES).getLong();
  305.     }

  306.     private static int getUnsignedByte(final ByteBuffer buf) throws EOFException {
  307.         if (!buf.hasRemaining()) {
  308.             throw new EOFException();
  309.         }
  310.         return buf.get() & 0xff;
  311.     }

  312.     /**
  313.      * Checks if the signature matches what is expected for a 7z file.
  314.      *
  315.      * @param signature the bytes to check
  316.      * @param length    the number of bytes to check
  317.      * @return true, if this is the signature of a 7z archive.
  318.      * @since 1.8
  319.      */
  320.     public static boolean matches(final byte[] signature, final int length) {
  321.         if (length < sevenZSignature.length) {
  322.             return false;
  323.         }
  324.         for (int i = 0; i < sevenZSignature.length; i++) {
  325.             if (signature[i] != sevenZSignature[i]) {
  326.                 return false;
  327.             }
  328.         }
  329.         return true;
  330.     }

  331.     private static SeekableByteChannel newByteChannel(final File file) throws IOException {
  332.         return Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ));
  333.     }

  334.     private static long readUint64(final ByteBuffer in) throws IOException {
  335.         // long rather than int as it might get shifted beyond the range of an int
  336.         final long firstByte = getUnsignedByte(in);
  337.         int mask = 0x80;
  338.         long value = 0;
  339.         for (int i = 0; i < 8; i++) {
  340.             if ((firstByte & mask) == 0) {
  341.                 return value | (firstByte & mask - 1) << 8 * i;
  342.             }
  343.             final long nextByte = getUnsignedByte(in);
  344.             value |= nextByte << 8 * i;
  345.             mask >>>= 1;
  346.         }
  347.         return value;
  348.     }

  349.     private static long skipBytesFully(final ByteBuffer input, long bytesToSkip) {
  350.         if (bytesToSkip < 1) {
  351.             return 0;
  352.         }
  353.         final int current = input.position();
  354.         final int maxSkip = input.remaining();
  355.         if (maxSkip < bytesToSkip) {
  356.             bytesToSkip = maxSkip;
  357.         }
  358.         input.position(current + (int) bytesToSkip);
  359.         return bytesToSkip;
  360.     }

  361.     private final String fileName;
  362.     private SeekableByteChannel channel;
  363.     private final Archive archive;
  364.     private int currentEntryIndex = -1;
  365.     private int currentFolderIndex = -1;
  366.     private InputStream currentFolderInputStream;
  367.     private byte[] password;
  368.     private long compressedBytesReadFromCurrentEntry;
  369.     private long uncompressedBytesReadFromCurrentEntry;
  370.     private final ArrayList<InputStream> deferredBlockStreams = new ArrayList<>();
  371.     private final int maxMemoryLimitKb;
  372.     private final boolean useDefaultNameForUnnamedEntries;

  373.     private final boolean tryToRecoverBrokenArchives;

  374.     /**
  375.      * Reads a file as unencrypted 7z archive.
  376.      *
  377.      * @param fileName the file to read.
  378.      * @throws IOException if reading the archive fails.
  379.      * @deprecated Use {@link Builder#get()}.
  380.      */
  381.     @Deprecated
  382.     public SevenZFile(final File fileName) throws IOException {
  383.         this(fileName, SevenZFileOptions.DEFAULT);
  384.     }

  385.     /**
  386.      * Reads a file as 7z archive
  387.      *
  388.      * @param file     the file to read
  389.      * @param password optional password if the archive is encrypted - the byte array is supposed to be the UTF16-LE encoded representation of the password.
  390.      * @throws IOException if reading the archive fails
  391.      * @deprecated Use {@link Builder#get()}.
  392.      */
  393.     @SuppressWarnings("resource") // caller closes
  394.     @Deprecated
  395.     public SevenZFile(final File file, final byte[] password) throws IOException {
  396.         this(newByteChannel(file), file.getAbsolutePath(), password, true, SevenZFileOptions.DEFAULT);
  397.     }

  398.     /**
  399.      * Reads a file as 7z archive
  400.      *
  401.      * @param file     the file to read
  402.      * @param password optional password if the archive is encrypted
  403.      * @throws IOException if reading the archive fails
  404.      * @since 1.17
  405.      * @deprecated Use {@link Builder#get()}.
  406.      */
  407.     @Deprecated
  408.     public SevenZFile(final File file, final char[] password) throws IOException {
  409.         this(file, password, SevenZFileOptions.DEFAULT);
  410.     }

  411.     /**
  412.      * Reads a file as 7z archive with additional options.
  413.      *
  414.      * @param file     the file to read
  415.      * @param password optional password if the archive is encrypted
  416.      * @param options  the options to apply
  417.      * @throws IOException if reading the archive fails or the memory limit (if set) is too small
  418.      * @since 1.19
  419.      * @deprecated Use {@link Builder#get()}.
  420.      */
  421.     @SuppressWarnings("resource") // caller closes
  422.     @Deprecated
  423.     public SevenZFile(final File file, final char[] password, final SevenZFileOptions options) throws IOException {
  424.         this(newByteChannel(file), // NOSONAR
  425.                 file.getAbsolutePath(), AES256SHA256Decoder.utf16Decode(password), true, options);
  426.     }

  427.     /**
  428.      * Reads a file as unencrypted 7z archive
  429.      *
  430.      * @param file    the file to read
  431.      * @param options the options to apply
  432.      * @throws IOException if reading the archive fails or the memory limit (if set) is too small
  433.      * @since 1.19
  434.      * @deprecated Use {@link Builder#get()}.
  435.      */
  436.     @Deprecated
  437.     public SevenZFile(final File file, final SevenZFileOptions options) throws IOException {
  438.         this(file, null, options);
  439.     }

  440.     /**
  441.      * Reads a SeekableByteChannel as 7z archive
  442.      * <p>
  443.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  444.      * </p>
  445.      *
  446.      * @param channel the channel to read
  447.      * @throws IOException if reading the archive fails
  448.      * @since 1.13
  449.      * @deprecated Use {@link Builder#get()}.
  450.      */
  451.     @Deprecated
  452.     public SevenZFile(final SeekableByteChannel channel) throws IOException {
  453.         this(channel, SevenZFileOptions.DEFAULT);
  454.     }

  455.     /**
  456.      * Reads a SeekableByteChannel as 7z archive
  457.      * <p>
  458.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  459.      * </p>
  460.      *
  461.      * @param channel  the channel to read
  462.      * @param password optional password if the archive is encrypted - the byte array is supposed to be the UTF16-LE encoded representation of the password.
  463.      * @throws IOException if reading the archive fails
  464.      * @since 1.13
  465.      * @deprecated Use {@link Builder#get()}.
  466.      */
  467.     @Deprecated
  468.     public SevenZFile(final SeekableByteChannel channel, final byte[] password) throws IOException {
  469.         this(channel, DEFAULT_FILE_NAME, password);
  470.     }

  471.     /**
  472.      * Reads a SeekableByteChannel as 7z archive
  473.      * <p>
  474.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  475.      * </p>
  476.      *
  477.      * @param channel  the channel to read
  478.      * @param password optional password if the archive is encrypted
  479.      * @throws IOException if reading the archive fails
  480.      * @since 1.17
  481.      * @deprecated Use {@link Builder#get()}.
  482.      */
  483.     @Deprecated
  484.     public SevenZFile(final SeekableByteChannel channel, final char[] password) throws IOException {
  485.         this(channel, password, SevenZFileOptions.DEFAULT);
  486.     }

  487.     /**
  488.      * Reads a SeekableByteChannel as 7z archive with additional options.
  489.      * <p>
  490.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  491.      * </p>
  492.      *
  493.      * @param channel  the channel to read
  494.      * @param password optional password if the archive is encrypted
  495.      * @param options  the options to apply
  496.      * @throws IOException if reading the archive fails or the memory limit (if set) is too small
  497.      * @since 1.19
  498.      * @deprecated Use {@link Builder#get()}.
  499.      */
  500.     @Deprecated
  501.     public SevenZFile(final SeekableByteChannel channel, final char[] password, final SevenZFileOptions options) throws IOException {
  502.         this(channel, DEFAULT_FILE_NAME, password, options);
  503.     }

  504.     /**
  505.      * Reads a SeekableByteChannel as 7z archive with additional options.
  506.      * <p>
  507.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  508.      * </p>
  509.      *
  510.      * @param channel the channel to read
  511.      * @param options the options to apply
  512.      * @throws IOException if reading the archive fails or the memory limit (if set) is too small
  513.      * @since 1.19
  514.      * @deprecated Use {@link Builder#get()}.
  515.      */
  516.     @Deprecated
  517.     public SevenZFile(final SeekableByteChannel channel, final SevenZFileOptions options) throws IOException {
  518.         this(channel, DEFAULT_FILE_NAME, null, options);
  519.     }

  520.     /**
  521.      * Reads a SeekableByteChannel as 7z archive
  522.      * <p>
  523.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  524.      * </p>
  525.      *
  526.      * @param channel  the channel to read
  527.      * @param fileName name of the archive - only used for error reporting
  528.      * @throws IOException if reading the archive fails
  529.      * @since 1.17
  530.      * @deprecated Use {@link Builder#get()}.
  531.      */
  532.     @Deprecated
  533.     public SevenZFile(final SeekableByteChannel channel, final String fileName) throws IOException {
  534.         this(channel, fileName, SevenZFileOptions.DEFAULT);
  535.     }

  536.     /**
  537.      * Reads a SeekableByteChannel as 7z archive
  538.      * <p>
  539.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  540.      * </p>
  541.      *
  542.      * @param channel  the channel to read
  543.      * @param fileName name of the archive - only used for error reporting
  544.      * @param password optional password if the archive is encrypted - the byte array is supposed to be the UTF16-LE encoded representation of the password.
  545.      * @throws IOException if reading the archive fails
  546.      * @since 1.13
  547.      * @deprecated Use {@link Builder#get()}.
  548.      */
  549.     @Deprecated
  550.     public SevenZFile(final SeekableByteChannel channel, final String fileName, final byte[] password) throws IOException {
  551.         this(channel, fileName, password, false, SevenZFileOptions.DEFAULT);
  552.     }

  553.     private SevenZFile(final SeekableByteChannel channel, final String fileName, final byte[] password, final boolean closeOnError, final int maxMemoryLimitKb,
  554.             final boolean useDefaultNameForUnnamedEntries, final boolean tryToRecoverBrokenArchives) throws IOException {
  555.         boolean succeeded = false;
  556.         this.channel = channel;
  557.         this.fileName = fileName;
  558.         this.maxMemoryLimitKb = maxMemoryLimitKb;
  559.         this.useDefaultNameForUnnamedEntries = useDefaultNameForUnnamedEntries;
  560.         this.tryToRecoverBrokenArchives = tryToRecoverBrokenArchives;
  561.         try {
  562.             archive = readHeaders(password);
  563.             if (password != null) {
  564.                 this.password = Arrays.copyOf(password, password.length);
  565.             } else {
  566.                 this.password = null;
  567.             }
  568.             succeeded = true;
  569.         } finally {
  570.             if (!succeeded && closeOnError) {
  571.                 this.channel.close();
  572.             }
  573.         }
  574.     }

  575.     /**
  576.      * Constructs a new instance.
  577.      *
  578.      * @param channel      the channel to read.
  579.      * @param fileName     name of the archive - only used for error reporting.
  580.      * @param password     optional password if the archive is encrypted.
  581.      * @param closeOnError closes the channel on error.
  582.      * @param options      options.
  583.      * @throws IOException if reading the archive fails
  584.      * @deprecated Use {@link Builder#get()}.
  585.      */
  586.     @Deprecated
  587.     private SevenZFile(final SeekableByteChannel channel, final String fileName, final byte[] password, final boolean closeOnError,
  588.             final SevenZFileOptions options) throws IOException {
  589.         this(channel, fileName, password, closeOnError, options.getMaxMemoryLimitInKb(), options.getUseDefaultNameForUnnamedEntries(),
  590.                 options.getTryToRecoverBrokenArchives());
  591.     }

  592.     /**
  593.      * Reads a SeekableByteChannel as 7z archive
  594.      * <p>
  595.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  596.      * </p>
  597.      *
  598.      * @param channel  the channel to read
  599.      * @param fileName name of the archive - only used for error reporting
  600.      * @param password optional password if the archive is encrypted
  601.      * @throws IOException if reading the archive fails
  602.      * @since 1.17
  603.      * @deprecated Use {@link Builder#get()}.
  604.      */
  605.     @Deprecated
  606.     public SevenZFile(final SeekableByteChannel channel, final String fileName, final char[] password) throws IOException {
  607.         this(channel, fileName, password, SevenZFileOptions.DEFAULT);
  608.     }

  609.     /**
  610.      * Reads a SeekableByteChannel as 7z archive with additional options.
  611.      * <p>
  612.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  613.      * </p>
  614.      *
  615.      * @param channel  the channel to read
  616.      * @param fileName name of the archive - only used for error reporting
  617.      * @param password optional password if the archive is encrypted
  618.      * @param options  the options to apply
  619.      * @throws IOException if reading the archive fails or the memory limit (if set) is too small
  620.      * @since 1.19
  621.      * @deprecated Use {@link Builder#get()}.
  622.      */
  623.     @Deprecated
  624.     public SevenZFile(final SeekableByteChannel channel, final String fileName, final char[] password, final SevenZFileOptions options) throws IOException {
  625.         this(channel, fileName, AES256SHA256Decoder.utf16Decode(password), false, options);
  626.     }

  627.     /**
  628.      * Reads a SeekableByteChannel as 7z archive with additional options.
  629.      * <p>
  630.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read from an in-memory archive.
  631.      * </p>
  632.      *
  633.      * @param channel  the channel to read
  634.      * @param fileName name of the archive - only used for error reporting
  635.      * @param options  the options to apply
  636.      * @throws IOException if reading the archive fails or the memory limit (if set) is too small
  637.      * @since 1.19
  638.      * @deprecated Use {@link Builder#get()}.
  639.      */
  640.     @Deprecated
  641.     public SevenZFile(final SeekableByteChannel channel, final String fileName, final SevenZFileOptions options) throws IOException {
  642.         this(channel, fileName, null, false, options);
  643.     }

  644.     private InputStream buildDecoderStack(final Folder folder, final long folderOffset, final int firstPackStreamIndex, final SevenZArchiveEntry entry)
  645.             throws IOException {
  646.         channel.position(folderOffset);
  647.         InputStream inputStreamStack = new FilterInputStream(
  648.                 new BufferedInputStream(new BoundedSeekableByteChannelInputStream(channel, archive.packSizes[firstPackStreamIndex]))) {
  649.             private void count(final int c) {
  650.                 compressedBytesReadFromCurrentEntry += c;
  651.             }

  652.             @Override
  653.             public int read() throws IOException {
  654.                 final int r = in.read();
  655.                 if (r >= 0) {
  656.                     count(1);
  657.                 }
  658.                 return r;
  659.             }

  660.             @Override
  661.             public int read(final byte[] b) throws IOException {
  662.                 return read(b, 0, b.length);
  663.             }

  664.             @Override
  665.             public int read(final byte[] b, final int off, final int len) throws IOException {
  666.                 if (len == 0) {
  667.                     return 0;
  668.                 }
  669.                 final int r = in.read(b, off, len);
  670.                 if (r >= 0) {
  671.                     count(r);
  672.                 }
  673.                 return r;
  674.             }
  675.         };
  676.         final LinkedList<SevenZMethodConfiguration> methods = new LinkedList<>();
  677.         for (final Coder coder : folder.getOrderedCoders()) {
  678.             if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
  679.                 throw new IOException("Multi input/output stream coders are not yet supported");
  680.             }
  681.             final SevenZMethod method = SevenZMethod.byId(coder.decompressionMethodId);
  682.             inputStreamStack = Coders.addDecoder(fileName, inputStreamStack, folder.getUnpackSizeForCoder(coder), coder, password, maxMemoryLimitKb);
  683.             methods.addFirst(new SevenZMethodConfiguration(method, Coders.findByMethod(method).getOptionsFromCoder(coder, inputStreamStack)));
  684.         }
  685.         entry.setContentMethods(methods);
  686.         if (folder.hasCrc) {
  687.             // @formatter:off
  688.             return ChecksumInputStream.builder()
  689.                     .setChecksum(new CRC32())
  690.                     .setInputStream(inputStreamStack)
  691.                     .setCountThreshold(folder.getUnpackSize())
  692.                     .setExpectedChecksumValue(folder.crc)
  693.                     .get();
  694.             // @formatter:on
  695.         }
  696.         return inputStreamStack;
  697.     }

  698.     /**
  699.      * Builds the decoding stream for the entry to be read. This method may be called from a random access(getInputStream) or sequential access(getNextEntry).
  700.      * If this method is called from a random access, some entries may need to be skipped(we put them to the deferredBlockStreams and skip them when actually
  701.      * needed to improve the performance)
  702.      *
  703.      * @param entryIndex     the index of the entry to be read
  704.      * @param isRandomAccess is this called in a random access
  705.      * @throws IOException if there are exceptions when reading the file
  706.      */
  707.     private void buildDecodingStream(final int entryIndex, final boolean isRandomAccess) throws IOException {
  708.         if (archive.streamMap == null) {
  709.             throw new IOException("Archive doesn't contain stream information to read entries");
  710.         }
  711.         final int folderIndex = archive.streamMap.fileFolderIndex[entryIndex];
  712.         if (folderIndex < 0) {
  713.             deferredBlockStreams.clear();
  714.             // TODO: previously it'd return an empty stream?
  715.             // new BoundedInputStream(new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY), 0);
  716.             return;
  717.         }
  718.         final SevenZArchiveEntry file = archive.files[entryIndex];
  719.         boolean isInSameFolder = false;
  720.         if (currentFolderIndex == folderIndex) {
  721.             // (COMPRESS-320).
  722.             // The current entry is within the same (potentially opened) folder. The
  723.             // previous stream has to be fully decoded before we can start reading
  724.             // but don't do it eagerly -- if the user skips over the entire folder nothing
  725.             // is effectively decompressed.
  726.             if (entryIndex > 0) {
  727.                 file.setContentMethods(archive.files[entryIndex - 1].getContentMethods());
  728.             }

  729.             // if this is called in a random access, then the content methods of previous entry may be null
  730.             // the content methods should be set to methods of the first entry as it must not be null,
  731.             // and the content methods would only be set if the content methods was not set
  732.             if (isRandomAccess && file.getContentMethods() == null) {
  733.                 final int folderFirstFileIndex = archive.streamMap.folderFirstFileIndex[folderIndex];
  734.                 final SevenZArchiveEntry folderFirstFile = archive.files[folderFirstFileIndex];
  735.                 file.setContentMethods(folderFirstFile.getContentMethods());
  736.             }
  737.             isInSameFolder = true;
  738.         } else {
  739.             currentFolderIndex = folderIndex;
  740.             // We're opening a new folder. Discard any queued streams/ folder stream.
  741.             reopenFolderInputStream(folderIndex, file);
  742.         }

  743.         boolean haveSkippedEntries = false;
  744.         if (isRandomAccess) {
  745.             // entries will only need to be skipped if it's a random access
  746.             haveSkippedEntries = skipEntriesWhenNeeded(entryIndex, isInSameFolder, folderIndex);
  747.         }

  748.         if (isRandomAccess && currentEntryIndex == entryIndex && !haveSkippedEntries) {
  749.             // we don't need to add another entry to the deferredBlockStreams when :
  750.             // 1. If this method is called in a random access and the entry index
  751.             // to be read equals to the current entry index, the input stream
  752.             // has already been put in the deferredBlockStreams
  753.             // 2. If this entry has not been read(which means no entries are skipped)
  754.             return;
  755.         }

  756.         InputStream fileStream = BoundedInputStream.builder()
  757.                 .setInputStream(currentFolderInputStream)
  758.                 .setMaxCount(file.getSize())
  759.                 .setPropagateClose(false)
  760.                 .get();
  761.         if (file.getHasCrc()) {
  762.             // @formatter:off
  763.             fileStream = ChecksumInputStream.builder()
  764.                     .setChecksum(new CRC32())
  765.                     .setInputStream(fileStream)
  766.                     .setExpectedChecksumValue(file.getCrcValue())
  767.                     .get();
  768.             // @formatter:on
  769.         }

  770.         deferredBlockStreams.add(fileStream);
  771.     }

  772.     private void calculateStreamMap(final Archive archive) throws IOException {
  773.         int nextFolderPackStreamIndex = 0;
  774.         final int numFolders = archive.folders != null ? archive.folders.length : 0;
  775.         final int[] folderFirstPackStreamIndex = new int[numFolders];
  776.         for (int i = 0; i < numFolders; i++) {
  777.             folderFirstPackStreamIndex[i] = nextFolderPackStreamIndex;
  778.             nextFolderPackStreamIndex += archive.folders[i].packedStreams.length;
  779.         }
  780.         long nextPackStreamOffset = 0;
  781.         final int numPackSizes = archive.packSizes.length;
  782.         final long[] packStreamOffsets = new long[numPackSizes];
  783.         for (int i = 0; i < numPackSizes; i++) {
  784.             packStreamOffsets[i] = nextPackStreamOffset;
  785.             nextPackStreamOffset += archive.packSizes[i];
  786.         }
  787.         final int[] folderFirstFileIndex = new int[numFolders];
  788.         final int[] fileFolderIndex = new int[archive.files.length];
  789.         int nextFolderIndex = 0;
  790.         int nextFolderUnpackStreamIndex = 0;
  791.         for (int i = 0; i < archive.files.length; i++) {
  792.             if (!archive.files[i].hasStream() && nextFolderUnpackStreamIndex == 0) {
  793.                 fileFolderIndex[i] = -1;
  794.                 continue;
  795.             }
  796.             if (nextFolderUnpackStreamIndex == 0) {
  797.                 for (; nextFolderIndex < archive.folders.length; ++nextFolderIndex) {
  798.                     folderFirstFileIndex[nextFolderIndex] = i;
  799.                     if (archive.folders[nextFolderIndex].numUnpackSubStreams > 0) {
  800.                         break;
  801.                     }
  802.                 }
  803.                 if (nextFolderIndex >= archive.folders.length) {
  804.                     throw new IOException("Too few folders in archive");
  805.                 }
  806.             }
  807.             fileFolderIndex[i] = nextFolderIndex;
  808.             if (!archive.files[i].hasStream()) {
  809.                 continue;
  810.             }
  811.             ++nextFolderUnpackStreamIndex;
  812.             if (nextFolderUnpackStreamIndex >= archive.folders[nextFolderIndex].numUnpackSubStreams) {
  813.                 ++nextFolderIndex;
  814.                 nextFolderUnpackStreamIndex = 0;
  815.             }
  816.         }
  817.         archive.streamMap = new StreamMap(folderFirstPackStreamIndex, packStreamOffsets, folderFirstFileIndex, fileFolderIndex);
  818.     }

  819.     private void checkEntryIsInitialized(final Map<Integer, SevenZArchiveEntry> archiveEntries, final int index) {
  820.         archiveEntries.computeIfAbsent(index, i -> new SevenZArchiveEntry());
  821.     }

  822.     /**
  823.      * Closes the archive.
  824.      *
  825.      * @throws IOException if closing the file fails
  826.      */
  827.     @Override
  828.     public void close() throws IOException {
  829.         if (channel != null) {
  830.             try {
  831.                 channel.close();
  832.             } finally {
  833.                 channel = null;
  834.                 if (password != null) {
  835.                     Arrays.fill(password, (byte) 0);
  836.                 }
  837.                 password = null;
  838.             }
  839.         }
  840.     }

  841.     private InputStream getCurrentStream() throws IOException {
  842.         if (archive.files[currentEntryIndex].getSize() == 0) {
  843.             return new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY);
  844.         }
  845.         if (deferredBlockStreams.isEmpty()) {
  846.             throw new IllegalStateException("No current 7z entry (call getNextEntry() first).");
  847.         }
  848.         while (deferredBlockStreams.size() > 1) {
  849.             // In solid compression mode we need to decompress all leading folder'
  850.             // streams to get access to an entry. We defer this until really needed
  851.             // so that entire blocks can be skipped without wasting time for decompression.
  852.             try (InputStream stream = deferredBlockStreams.remove(0)) {
  853.                 org.apache.commons.io.IOUtils.skip(stream, Long.MAX_VALUE, org.apache.commons.io.IOUtils::byteArray);
  854.             }
  855.             compressedBytesReadFromCurrentEntry = 0;
  856.         }
  857.         return deferredBlockStreams.get(0);
  858.     }

  859.     /**
  860.      * Gets a default file name from the archive name - if known.
  861.      * <p>
  862.      * This implements the same heuristics the 7z tools use. In 7z's case if an archive contains entries without a name - i.e.
  863.      * {@link SevenZArchiveEntry#getName} returns {@code null} - then its command line and GUI tools will use this default name when extracting the entries.
  864.      * </p>
  865.      *
  866.      * @return null if the name of the archive is unknown. Otherwise, if the name of the archive has got any extension, it is stripped and the remainder
  867.      *         returned. Finally, if the name of the archive hasn't got any extension, then a {@code ~} character is appended to the archive name.
  868.      * @since 1.19
  869.      */
  870.     public String getDefaultName() {
  871.         if (DEFAULT_FILE_NAME.equals(fileName) || fileName == null) {
  872.             return null;
  873.         }

  874.         final String lastSegment = new File(fileName).getName();
  875.         final int dotPos = lastSegment.lastIndexOf(".");
  876.         if (dotPos > 0) { // if the file starts with a dot then this is not an extension
  877.             return lastSegment.substring(0, dotPos);
  878.         }
  879.         return lastSegment + "~";
  880.     }

  881.     /**
  882.      * Gets a copy of meta-data of all archive entries.
  883.      * <p>
  884.      * This method only provides meta-data, the entries can not be used to read the contents, you still need to process all entries in order using
  885.      * {@link #getNextEntry} for that.
  886.      * </p>
  887.      * <p>
  888.      * The content methods are only available for entries that have already been reached via {@link #getNextEntry}.
  889.      * </p>
  890.      *
  891.      * @return a copy of meta-data of all archive entries.
  892.      * @since 1.11
  893.      */
  894.     public Iterable<SevenZArchiveEntry> getEntries() {
  895.         return new ArrayList<>(Arrays.asList(archive.files));
  896.     }

  897.     /**
  898.      * Gets an InputStream for reading the contents of the given entry.
  899.      * <p>
  900.      * For archives using solid compression randomly accessing entries will be significantly slower than reading the archive sequentially.
  901.      * </p>
  902.      *
  903.      * @param entry the entry to get the stream for.
  904.      * @return a stream to read the entry from.
  905.      * @throws IOException if unable to create an input stream from the entry
  906.      * @since 1.20
  907.      */
  908.     public InputStream getInputStream(final SevenZArchiveEntry entry) throws IOException {
  909.         int entryIndex = -1;
  910.         for (int i = 0; i < archive.files.length; i++) {
  911.             if (entry == archive.files[i]) {
  912.                 entryIndex = i;
  913.                 break;
  914.             }
  915.         }

  916.         if (entryIndex < 0) {
  917.             throw new IllegalArgumentException("Can not find " + entry.getName() + " in " + fileName);
  918.         }

  919.         buildDecodingStream(entryIndex, true);
  920.         currentEntryIndex = entryIndex;
  921.         currentFolderIndex = archive.streamMap.fileFolderIndex[entryIndex];
  922.         return getCurrentStream();
  923.     }

  924.     /**
  925.      * Gets the next Archive Entry in this archive.
  926.      *
  927.      * @return the next entry, or {@code null} if there are no more entries
  928.      * @throws IOException if the next entry could not be read
  929.      */
  930.     public SevenZArchiveEntry getNextEntry() throws IOException {
  931.         if (currentEntryIndex >= archive.files.length - 1) {
  932.             return null;
  933.         }
  934.         ++currentEntryIndex;
  935.         final SevenZArchiveEntry entry = archive.files[currentEntryIndex];
  936.         if (entry.getName() == null && useDefaultNameForUnnamedEntries) {
  937.             entry.setName(getDefaultName());
  938.         }
  939.         buildDecodingStream(currentEntryIndex, false);
  940.         uncompressedBytesReadFromCurrentEntry = compressedBytesReadFromCurrentEntry = 0;
  941.         return entry;
  942.     }

  943.     /**
  944.      * Gets statistics for bytes read from the current entry.
  945.      *
  946.      * @return statistics for bytes read from the current entry
  947.      * @since 1.17
  948.      */
  949.     public InputStreamStatistics getStatisticsForCurrentEntry() {
  950.         return new InputStreamStatistics() {
  951.             @Override
  952.             public long getCompressedCount() {
  953.                 return compressedBytesReadFromCurrentEntry;
  954.             }

  955.             @Override
  956.             public long getUncompressedCount() {
  957.                 return uncompressedBytesReadFromCurrentEntry;
  958.             }
  959.         };
  960.     }

  961.     /**
  962.      * Tests if any data of current entry has been read or not. This is achieved by comparing the bytes remaining to read and the size of the file.
  963.      *
  964.      * @return true if any data of current entry has been read
  965.      * @since 1.21
  966.      */
  967.     private boolean hasCurrentEntryBeenRead() {
  968.         boolean hasCurrentEntryBeenRead = false;
  969.         if (!deferredBlockStreams.isEmpty()) {
  970.             final InputStream currentEntryInputStream = deferredBlockStreams.get(deferredBlockStreams.size() - 1);
  971.             // get the bytes remaining to read, and compare it with the size of
  972.             // the file to figure out if the file has been read
  973.             if (currentEntryInputStream instanceof ChecksumInputStream) {
  974.                 hasCurrentEntryBeenRead = ((ChecksumInputStream) currentEntryInputStream).getRemaining() != archive.files[currentEntryIndex].getSize();
  975.             } else if (currentEntryInputStream instanceof BoundedInputStream) {
  976.                 hasCurrentEntryBeenRead = ((BoundedInputStream) currentEntryInputStream).getRemaining() != archive.files[currentEntryIndex].getSize();
  977.             }
  978.         }
  979.         return hasCurrentEntryBeenRead;
  980.     }

  981.     private Archive initializeArchive(final StartHeader startHeader, final byte[] password, final boolean verifyCrc) throws IOException {
  982.         assertFitsIntoNonNegativeInt("nextHeaderSize", startHeader.nextHeaderSize);
  983.         final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize;
  984.         channel.position(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset);
  985.         if (verifyCrc) {
  986.             final long position = channel.position();
  987.             final CheckedInputStream cis = new CheckedInputStream(Channels.newInputStream(channel), new CRC32());
  988.             if (cis.skip(nextHeaderSizeInt) != nextHeaderSizeInt) {
  989.                 throw new IOException("Problem computing NextHeader CRC-32");
  990.             }
  991.             if (startHeader.nextHeaderCrc != cis.getChecksum().getValue()) {
  992.                 throw new IOException("NextHeader CRC-32 mismatch");
  993.             }
  994.             channel.position(position);
  995.         }
  996.         Archive archive = new Archive();
  997.         ByteBuffer buf = ByteBuffer.allocate(nextHeaderSizeInt).order(ByteOrder.LITTLE_ENDIAN);
  998.         readFully(buf);
  999.         int nid = getUnsignedByte(buf);
  1000.         if (nid == NID.kEncodedHeader) {
  1001.             buf = readEncodedHeader(buf, archive, password);
  1002.             // Archive gets rebuilt with the new header
  1003.             archive = new Archive();
  1004.             nid = getUnsignedByte(buf);
  1005.         }
  1006.         if (nid != NID.kHeader) {
  1007.             throw new IOException("Broken or unsupported archive: no Header");
  1008.         }
  1009.         readHeader(buf, archive);
  1010.         archive.subStreamsInfo = null;
  1011.         return archive;
  1012.     }

  1013.     /**
  1014.      * Reads a byte of data.
  1015.      *
  1016.      * @return the byte read, or -1 if end of input is reached
  1017.      * @throws IOException if an I/O error has occurred
  1018.      */
  1019.     public int read() throws IOException {
  1020.         final int b = getCurrentStream().read();
  1021.         if (b >= 0) {
  1022.             uncompressedBytesReadFromCurrentEntry++;
  1023.         }
  1024.         return b;
  1025.     }

  1026.     /**
  1027.      * Reads data into an array of bytes.
  1028.      *
  1029.      * @param b the array to write data to
  1030.      * @return the number of bytes read, or -1 if end of input is reached
  1031.      * @throws IOException if an I/O error has occurred
  1032.      */
  1033.     public int read(final byte[] b) throws IOException {
  1034.         return read(b, 0, b.length);
  1035.     }

  1036.     /**
  1037.      * Reads data into an array of bytes.
  1038.      *
  1039.      * @param b   the array to write data to
  1040.      * @param off offset into the buffer to start filling at
  1041.      * @param len of bytes to read
  1042.      * @return the number of bytes read, or -1 if end of input is reached
  1043.      * @throws IOException if an I/O error has occurred
  1044.      */
  1045.     public int read(final byte[] b, final int off, final int len) throws IOException {
  1046.         if (len == 0) {
  1047.             return 0;
  1048.         }
  1049.         final int cnt = getCurrentStream().read(b, off, len);
  1050.         if (cnt > 0) {
  1051.             uncompressedBytesReadFromCurrentEntry += cnt;
  1052.         }
  1053.         return cnt;
  1054.     }

  1055.     private BitSet readAllOrBits(final ByteBuffer header, final int size) throws IOException {
  1056.         final int areAllDefined = getUnsignedByte(header);
  1057.         final BitSet bits;
  1058.         if (areAllDefined != 0) {
  1059.             bits = new BitSet(size);
  1060.             for (int i = 0; i < size; i++) {
  1061.                 bits.set(i, true);
  1062.             }
  1063.         } else {
  1064.             bits = readBits(header, size);
  1065.         }
  1066.         return bits;
  1067.     }

  1068.     private void readArchiveProperties(final ByteBuffer input) throws IOException {
  1069.         // FIXME: the reference implementation just throws them away?
  1070.         long nid = readUint64(input);
  1071.         while (nid != NID.kEnd) {
  1072.             final long propertySize = readUint64(input);
  1073.             final byte[] property = new byte[(int) propertySize];
  1074.             get(input, property);
  1075.             nid = readUint64(input);
  1076.         }
  1077.     }

  1078.     private BitSet readBits(final ByteBuffer header, final int size) throws IOException {
  1079.         final BitSet bits = new BitSet(size);
  1080.         int mask = 0;
  1081.         int cache = 0;
  1082.         for (int i = 0; i < size; i++) {
  1083.             if (mask == 0) {
  1084.                 mask = 0x80;
  1085.                 cache = getUnsignedByte(header);
  1086.             }
  1087.             bits.set(i, (cache & mask) != 0);
  1088.             mask >>>= 1;
  1089.         }
  1090.         return bits;
  1091.     }

  1092.     private ByteBuffer readEncodedHeader(final ByteBuffer header, final Archive archive, final byte[] password) throws IOException {
  1093.         final int pos = header.position();
  1094.         final ArchiveStatistics stats = new ArchiveStatistics();
  1095.         sanityCheckStreamsInfo(header, stats);
  1096.         stats.assertValidity(maxMemoryLimitKb);
  1097.         header.position(pos);

  1098.         readStreamsInfo(header, archive);

  1099.         if (archive.folders == null || archive.folders.length == 0) {
  1100.             throw new IOException("no folders, can't read encoded header");
  1101.         }
  1102.         if (archive.packSizes == null || archive.packSizes.length == 0) {
  1103.             throw new IOException("no packed streams, can't read encoded header");
  1104.         }

  1105.         // FIXME: merge with buildDecodingStream()/buildDecoderStack() at some stage?
  1106.         final Folder folder = archive.folders[0];
  1107.         final int firstPackStreamIndex = 0;
  1108.         final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos + 0;

  1109.         channel.position(folderOffset);
  1110.         InputStream inputStreamStack = new BoundedSeekableByteChannelInputStream(channel, archive.packSizes[firstPackStreamIndex]);
  1111.         for (final Coder coder : folder.getOrderedCoders()) {
  1112.             if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
  1113.                 throw new IOException("Multi input/output stream coders are not yet supported");
  1114.             }
  1115.             inputStreamStack = Coders.addDecoder(fileName, inputStreamStack, // NOSONAR
  1116.                     folder.getUnpackSizeForCoder(coder), coder, password, maxMemoryLimitKb);
  1117.         }
  1118.         if (folder.hasCrc) {
  1119.             // @formatter:off
  1120.             inputStreamStack = ChecksumInputStream.builder()
  1121.                     .setChecksum(new CRC32())
  1122.                     .setInputStream(inputStreamStack)
  1123.                     .setCountThreshold(folder.getUnpackSize())
  1124.                     .setExpectedChecksumValue(folder.crc)
  1125.                     .get();
  1126.             // @formatter:on
  1127.         }
  1128.         final int unpackSize = assertFitsIntoNonNegativeInt("unpackSize", folder.getUnpackSize());
  1129.         final byte[] nextHeader = IOUtils.readRange(inputStreamStack, unpackSize);
  1130.         if (nextHeader.length < unpackSize) {
  1131.             throw new IOException("premature end of stream");
  1132.         }
  1133.         inputStreamStack.close();
  1134.         return ByteBuffer.wrap(nextHeader).order(ByteOrder.LITTLE_ENDIAN);
  1135.     }

  1136.     private void readFilesInfo(final ByteBuffer header, final Archive archive) throws IOException {
  1137.         final int numFilesInt = (int) readUint64(header);
  1138.         final Map<Integer, SevenZArchiveEntry> fileMap = new LinkedHashMap<>();
  1139.         BitSet isEmptyStream = null;
  1140.         BitSet isEmptyFile = null;
  1141.         BitSet isAnti = null;
  1142.         while (true) {
  1143.             final int propertyType = getUnsignedByte(header);
  1144.             if (propertyType == 0) {
  1145.                 break;
  1146.             }
  1147.             final long size = readUint64(header);
  1148.             switch (propertyType) {
  1149.             case NID.kEmptyStream: {
  1150.                 isEmptyStream = readBits(header, numFilesInt);
  1151.                 break;
  1152.             }
  1153.             case NID.kEmptyFile: {
  1154.                 isEmptyFile = readBits(header, isEmptyStream.cardinality());
  1155.                 break;
  1156.             }
  1157.             case NID.kAnti: {
  1158.                 isAnti = readBits(header, isEmptyStream.cardinality());
  1159.                 break;
  1160.             }
  1161.             case NID.kName: {
  1162.                 /* final int external = */ getUnsignedByte(header);
  1163.                 final byte[] names = new byte[(int) (size - 1)];
  1164.                 final int namesLength = names.length;
  1165.                 get(header, names);
  1166.                 int nextFile = 0;
  1167.                 int nextName = 0;
  1168.                 for (int i = 0; i < namesLength; i += 2) {
  1169.                     if (names[i] == 0 && names[i + 1] == 0) {
  1170.                         checkEntryIsInitialized(fileMap, nextFile);
  1171.                         fileMap.get(nextFile).setName(new String(names, nextName, i - nextName, UTF_16LE));
  1172.                         nextName = i + 2;
  1173.                         nextFile++;
  1174.                     }
  1175.                 }
  1176.                 if (nextName != namesLength || nextFile != numFilesInt) {
  1177.                     throw new IOException("Error parsing file names");
  1178.                 }
  1179.                 break;
  1180.             }
  1181.             case NID.kCTime: {
  1182.                 final BitSet timesDefined = readAllOrBits(header, numFilesInt);
  1183.                 /* final int external = */ getUnsignedByte(header);
  1184.                 for (int i = 0; i < numFilesInt; i++) {
  1185.                     checkEntryIsInitialized(fileMap, i);
  1186.                     final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
  1187.                     entryAtIndex.setHasCreationDate(timesDefined.get(i));
  1188.                     if (entryAtIndex.getHasCreationDate()) {
  1189.                         entryAtIndex.setCreationDate(getLong(header));
  1190.                     }
  1191.                 }
  1192.                 break;
  1193.             }
  1194.             case NID.kATime: {
  1195.                 final BitSet timesDefined = readAllOrBits(header, numFilesInt);
  1196.                 /* final int external = */ getUnsignedByte(header);
  1197.                 for (int i = 0; i < numFilesInt; i++) {
  1198.                     checkEntryIsInitialized(fileMap, i);
  1199.                     final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
  1200.                     entryAtIndex.setHasAccessDate(timesDefined.get(i));
  1201.                     if (entryAtIndex.getHasAccessDate()) {
  1202.                         entryAtIndex.setAccessDate(getLong(header));
  1203.                     }
  1204.                 }
  1205.                 break;
  1206.             }
  1207.             case NID.kMTime: {
  1208.                 final BitSet timesDefined = readAllOrBits(header, numFilesInt);
  1209.                 /* final int external = */ getUnsignedByte(header);
  1210.                 for (int i = 0; i < numFilesInt; i++) {
  1211.                     checkEntryIsInitialized(fileMap, i);
  1212.                     final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
  1213.                     entryAtIndex.setHasLastModifiedDate(timesDefined.get(i));
  1214.                     if (entryAtIndex.getHasLastModifiedDate()) {
  1215.                         entryAtIndex.setLastModifiedDate(getLong(header));
  1216.                     }
  1217.                 }
  1218.                 break;
  1219.             }
  1220.             case NID.kWinAttributes: {
  1221.                 final BitSet attributesDefined = readAllOrBits(header, numFilesInt);
  1222.                 /* final int external = */ getUnsignedByte(header);
  1223.                 for (int i = 0; i < numFilesInt; i++) {
  1224.                     checkEntryIsInitialized(fileMap, i);
  1225.                     final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
  1226.                     entryAtIndex.setHasWindowsAttributes(attributesDefined.get(i));
  1227.                     if (entryAtIndex.getHasWindowsAttributes()) {
  1228.                         entryAtIndex.setWindowsAttributes(getInt(header));
  1229.                     }
  1230.                 }
  1231.                 break;
  1232.             }
  1233.             case NID.kDummy: {
  1234.                 // 7z 9.20 asserts the content is all zeros and ignores the property
  1235.                 // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287

  1236.                 skipBytesFully(header, size);
  1237.                 break;
  1238.             }

  1239.             default: {
  1240.                 // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287
  1241.                 skipBytesFully(header, size);
  1242.                 break;
  1243.             }
  1244.             }
  1245.         }
  1246.         int nonEmptyFileCounter = 0;
  1247.         int emptyFileCounter = 0;
  1248.         for (int i = 0; i < numFilesInt; i++) {
  1249.             final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
  1250.             if (entryAtIndex == null) {
  1251.                 continue;
  1252.             }
  1253.             entryAtIndex.setHasStream(isEmptyStream == null || !isEmptyStream.get(i));
  1254.             if (entryAtIndex.hasStream()) {
  1255.                 if (archive.subStreamsInfo == null) {
  1256.                     throw new IOException("Archive contains file with streams but no subStreamsInfo");
  1257.                 }
  1258.                 entryAtIndex.setDirectory(false);
  1259.                 entryAtIndex.setAntiItem(false);
  1260.                 entryAtIndex.setHasCrc(archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter));
  1261.                 entryAtIndex.setCrcValue(archive.subStreamsInfo.crcs[nonEmptyFileCounter]);
  1262.                 entryAtIndex.setSize(archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter]);
  1263.                 if (entryAtIndex.getSize() < 0) {
  1264.                     throw new IOException("broken archive, entry with negative size");
  1265.                 }
  1266.                 ++nonEmptyFileCounter;
  1267.             } else {
  1268.                 entryAtIndex.setDirectory(isEmptyFile == null || !isEmptyFile.get(emptyFileCounter));
  1269.                 entryAtIndex.setAntiItem(isAnti != null && isAnti.get(emptyFileCounter));
  1270.                 entryAtIndex.setHasCrc(false);
  1271.                 entryAtIndex.setSize(0);
  1272.                 ++emptyFileCounter;
  1273.             }
  1274.         }
  1275.         archive.files = fileMap.values().stream().filter(Objects::nonNull).toArray(SevenZArchiveEntry[]::new);
  1276.         calculateStreamMap(archive);
  1277.     }

  1278.     private Folder readFolder(final ByteBuffer header) throws IOException {
  1279.         final Folder folder = new Folder();

  1280.         final long numCoders = readUint64(header);
  1281.         final Coder[] coders = new Coder[(int) numCoders];
  1282.         long totalInStreams = 0;
  1283.         long totalOutStreams = 0;
  1284.         for (int i = 0; i < coders.length; i++) {
  1285.             final int bits = getUnsignedByte(header);
  1286.             final int idSize = bits & 0xf;
  1287.             final boolean isSimple = (bits & 0x10) == 0;
  1288.             final boolean hasAttributes = (bits & 0x20) != 0;
  1289.             final boolean moreAlternativeMethods = (bits & 0x80) != 0;

  1290.             final byte[] decompressionMethodId = new byte[idSize];
  1291.             get(header, decompressionMethodId);
  1292.             final long numInStreams;
  1293.             final long numOutStreams;
  1294.             if (isSimple) {
  1295.                 numInStreams = 1;
  1296.                 numOutStreams = 1;
  1297.             } else {
  1298.                 numInStreams = readUint64(header);
  1299.                 numOutStreams = readUint64(header);
  1300.             }
  1301.             totalInStreams += numInStreams;
  1302.             totalOutStreams += numOutStreams;
  1303.             byte[] properties = null;
  1304.             if (hasAttributes) {
  1305.                 final long propertiesSize = readUint64(header);
  1306.                 properties = new byte[(int) propertiesSize];
  1307.                 get(header, properties);
  1308.             }
  1309.             // would need to keep looping as above:
  1310.             if (moreAlternativeMethods) {
  1311.                 throw new IOException("Alternative methods are unsupported, please report. " + // NOSONAR
  1312.                         "The reference implementation doesn't support them either.");
  1313.             }
  1314.             coders[i] = new Coder(decompressionMethodId, numInStreams, numOutStreams, properties);
  1315.         }
  1316.         folder.coders = coders;
  1317.         folder.totalInputStreams = totalInStreams;
  1318.         folder.totalOutputStreams = totalOutStreams;

  1319.         final long numBindPairs = totalOutStreams - 1;
  1320.         final BindPair[] bindPairs = new BindPair[(int) numBindPairs];
  1321.         for (int i = 0; i < bindPairs.length; i++) {
  1322.             bindPairs[i] = new BindPair(readUint64(header), readUint64(header));
  1323.         }
  1324.         folder.bindPairs = bindPairs;

  1325.         final long numPackedStreams = totalInStreams - numBindPairs;
  1326.         final long[] packedStreams = new long[(int) numPackedStreams];
  1327.         if (numPackedStreams == 1) {
  1328.             int i;
  1329.             for (i = 0; i < (int) totalInStreams; i++) {
  1330.                 if (folder.findBindPairForInStream(i) < 0) {
  1331.                     break;
  1332.                 }
  1333.             }
  1334.             packedStreams[0] = i;
  1335.         } else {
  1336.             for (int i = 0; i < (int) numPackedStreams; i++) {
  1337.                 packedStreams[i] = readUint64(header);
  1338.             }
  1339.         }
  1340.         folder.packedStreams = packedStreams;

  1341.         return folder;
  1342.     }

  1343.     private void readFully(final ByteBuffer buf) throws IOException {
  1344.         buf.rewind();
  1345.         IOUtils.readFully(channel, buf);
  1346.         buf.flip();
  1347.     }

  1348.     private void readHeader(final ByteBuffer header, final Archive archive) throws IOException {
  1349.         final int pos = header.position();
  1350.         final ArchiveStatistics stats = sanityCheckAndCollectStatistics(header);
  1351.         stats.assertValidity(maxMemoryLimitKb);
  1352.         header.position(pos);

  1353.         int nid = getUnsignedByte(header);

  1354.         if (nid == NID.kArchiveProperties) {
  1355.             readArchiveProperties(header);
  1356.             nid = getUnsignedByte(header);
  1357.         }

  1358.         if (nid == NID.kAdditionalStreamsInfo) {
  1359.             throw new IOException("Additional streams unsupported");
  1360.             // nid = getUnsignedByte(header);
  1361.         }

  1362.         if (nid == NID.kMainStreamsInfo) {
  1363.             readStreamsInfo(header, archive);
  1364.             nid = getUnsignedByte(header);
  1365.         }

  1366.         if (nid == NID.kFilesInfo) {
  1367.             readFilesInfo(header, archive);
  1368.             nid = getUnsignedByte(header);
  1369.         }
  1370.     }

  1371.     private Archive readHeaders(final byte[] password) throws IOException {
  1372.         final ByteBuffer buf = ByteBuffer.allocate(12 /* signature + 2 bytes version + 4 bytes CRC */).order(ByteOrder.LITTLE_ENDIAN);
  1373.         readFully(buf);
  1374.         final byte[] signature = new byte[6];
  1375.         buf.get(signature);
  1376.         if (!Arrays.equals(signature, sevenZSignature)) {
  1377.             throw new IOException("Bad 7z signature");
  1378.         }
  1379.         // 7zFormat.txt has it wrong - it's first major then minor
  1380.         final byte archiveVersionMajor = buf.get();
  1381.         final byte archiveVersionMinor = buf.get();
  1382.         if (archiveVersionMajor != 0) {
  1383.             throw new IOException(String.format("Unsupported 7z version (%d,%d)", archiveVersionMajor, archiveVersionMinor));
  1384.         }

  1385.         boolean headerLooksValid = false; // See https://www.7-zip.org/recover.html - "There is no correct End Header at the end of archive"
  1386.         final long startHeaderCrc = 0xffffFFFFL & buf.getInt();
  1387.         if (startHeaderCrc == 0) {
  1388.             // This is an indication of a corrupt header - peek the next 20 bytes
  1389.             final long currentPosition = channel.position();
  1390.             final ByteBuffer peekBuf = ByteBuffer.allocate(20);
  1391.             readFully(peekBuf);
  1392.             channel.position(currentPosition);
  1393.             // Header invalid if all data is 0
  1394.             while (peekBuf.hasRemaining()) {
  1395.                 if (peekBuf.get() != 0) {
  1396.                     headerLooksValid = true;
  1397.                     break;
  1398.                 }
  1399.             }
  1400.         } else {
  1401.             headerLooksValid = true;
  1402.         }

  1403.         if (headerLooksValid) {
  1404.             return initializeArchive(readStartHeader(startHeaderCrc), password, true);
  1405.         }
  1406.         // No valid header found - probably first file of multipart archive was removed too early. Scan for end header.
  1407.         if (tryToRecoverBrokenArchives) {
  1408.             return tryToLocateEndHeader(password);
  1409.         }
  1410.         throw new IOException("archive seems to be invalid.\nYou may want to retry and enable the"
  1411.                 + " tryToRecoverBrokenArchives if the archive could be a multi volume archive that has been closed" + " prematurely.");
  1412.     }

  1413.     private void readPackInfo(final ByteBuffer header, final Archive archive) throws IOException {
  1414.         archive.packPos = readUint64(header);
  1415.         final int numPackStreamsInt = (int) readUint64(header);
  1416.         int nid = getUnsignedByte(header);
  1417.         if (nid == NID.kSize) {
  1418.             archive.packSizes = new long[numPackStreamsInt];
  1419.             for (int i = 0; i < archive.packSizes.length; i++) {
  1420.                 archive.packSizes[i] = readUint64(header);
  1421.             }
  1422.             nid = getUnsignedByte(header);
  1423.         }

  1424.         if (nid == NID.kCRC) {
  1425.             archive.packCrcsDefined = readAllOrBits(header, numPackStreamsInt);
  1426.             archive.packCrcs = new long[numPackStreamsInt];
  1427.             for (int i = 0; i < numPackStreamsInt; i++) {
  1428.                 if (archive.packCrcsDefined.get(i)) {
  1429.                     archive.packCrcs[i] = 0xffffFFFFL & getInt(header);
  1430.                 }
  1431.             }
  1432.             // read one more
  1433.             getUnsignedByte(header);
  1434.         }
  1435.     }

  1436.     private StartHeader readStartHeader(final long startHeaderCrc) throws IOException {
  1437.         // using Stream rather than ByteBuffer for the benefit of the built-in CRC check
  1438.         try (DataInputStream dataInputStream = new DataInputStream(ChecksumInputStream.builder()
  1439.                 // @formatter:off
  1440.                 .setChecksum(new CRC32())
  1441.                 .setInputStream(new BoundedSeekableByteChannelInputStream(channel, 20))
  1442.                 .setCountThreshold(20L)
  1443.                 .setExpectedChecksumValue(startHeaderCrc)
  1444.                 .get())) {
  1445.                 // @formatter:on
  1446.             final long nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong());
  1447.             if (nextHeaderOffset < 0 || nextHeaderOffset + SIGNATURE_HEADER_SIZE > channel.size()) {
  1448.                 throw new IOException("nextHeaderOffset is out of bounds");
  1449.             }
  1450.             final long nextHeaderSize = Long.reverseBytes(dataInputStream.readLong());
  1451.             final long nextHeaderEnd = nextHeaderOffset + nextHeaderSize;
  1452.             if (nextHeaderEnd < nextHeaderOffset || nextHeaderEnd + SIGNATURE_HEADER_SIZE > channel.size()) {
  1453.                 throw new IOException("nextHeaderSize is out of bounds");
  1454.             }
  1455.             final long nextHeaderCrc = 0xffffFFFFL & Integer.reverseBytes(dataInputStream.readInt());
  1456.             return new StartHeader(nextHeaderOffset, nextHeaderSize, nextHeaderCrc);
  1457.         }
  1458.     }

  1459.     private void readStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException {
  1460.         int nid = getUnsignedByte(header);

  1461.         if (nid == NID.kPackInfo) {
  1462.             readPackInfo(header, archive);
  1463.             nid = getUnsignedByte(header);
  1464.         }

  1465.         if (nid == NID.kUnpackInfo) {
  1466.             readUnpackInfo(header, archive);
  1467.             nid = getUnsignedByte(header);
  1468.         } else {
  1469.             // archive without unpack/coders info
  1470.             archive.folders = Folder.EMPTY_FOLDER_ARRAY;
  1471.         }

  1472.         if (nid == NID.kSubStreamsInfo) {
  1473.             readSubStreamsInfo(header, archive);
  1474.             nid = getUnsignedByte(header);
  1475.         }
  1476.     }

  1477.     private void readSubStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException {
  1478.         for (final Folder folder : archive.folders) {
  1479.             folder.numUnpackSubStreams = 1;
  1480.         }
  1481.         long unpackStreamsCount = archive.folders.length;

  1482.         int nid = getUnsignedByte(header);
  1483.         if (nid == NID.kNumUnpackStream) {
  1484.             unpackStreamsCount = 0;
  1485.             for (final Folder folder : archive.folders) {
  1486.                 final long numStreams = readUint64(header);
  1487.                 folder.numUnpackSubStreams = (int) numStreams;
  1488.                 unpackStreamsCount += numStreams;
  1489.             }
  1490.             nid = getUnsignedByte(header);
  1491.         }

  1492.         final int totalUnpackStreams = (int) unpackStreamsCount;
  1493.         final SubStreamsInfo subStreamsInfo = new SubStreamsInfo(totalUnpackStreams);
  1494.         int nextUnpackStream = 0;
  1495.         for (final Folder folder : archive.folders) {
  1496.             if (folder.numUnpackSubStreams == 0) {
  1497.                 continue;
  1498.             }
  1499.             long sum = 0;
  1500.             if (nid == NID.kSize) {
  1501.                 for (int i = 0; i < folder.numUnpackSubStreams - 1; i++) {
  1502.                     final long size = readUint64(header);
  1503.                     subStreamsInfo.unpackSizes[nextUnpackStream++] = size;
  1504.                     sum += size;
  1505.                 }
  1506.             }
  1507.             if (sum > folder.getUnpackSize()) {
  1508.                 throw new IOException("sum of unpack sizes of folder exceeds total unpack size");
  1509.             }
  1510.             subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum;
  1511.         }
  1512.         if (nid == NID.kSize) {
  1513.             nid = getUnsignedByte(header);
  1514.         }

  1515.         int numDigests = 0;
  1516.         for (final Folder folder : archive.folders) {
  1517.             if (folder.numUnpackSubStreams != 1 || !folder.hasCrc) {
  1518.                 numDigests += folder.numUnpackSubStreams;
  1519.             }
  1520.         }

  1521.         if (nid == NID.kCRC) {
  1522.             final BitSet hasMissingCrc = readAllOrBits(header, numDigests);
  1523.             final long[] missingCrcs = new long[numDigests];
  1524.             for (int i = 0; i < numDigests; i++) {
  1525.                 if (hasMissingCrc.get(i)) {
  1526.                     missingCrcs[i] = 0xffffFFFFL & getInt(header);
  1527.                 }
  1528.             }
  1529.             int nextCrc = 0;
  1530.             int nextMissingCrc = 0;
  1531.             for (final Folder folder : archive.folders) {
  1532.                 if (folder.numUnpackSubStreams == 1 && folder.hasCrc) {
  1533.                     subStreamsInfo.hasCrc.set(nextCrc, true);
  1534.                     subStreamsInfo.crcs[nextCrc] = folder.crc;
  1535.                     ++nextCrc;
  1536.                 } else {
  1537.                     for (int i = 0; i < folder.numUnpackSubStreams; i++) {
  1538.                         subStreamsInfo.hasCrc.set(nextCrc, hasMissingCrc.get(nextMissingCrc));
  1539.                         subStreamsInfo.crcs[nextCrc] = missingCrcs[nextMissingCrc];
  1540.                         ++nextCrc;
  1541.                         ++nextMissingCrc;
  1542.                     }
  1543.                 }
  1544.             }

  1545.             nid = getUnsignedByte(header);
  1546.         }

  1547.         archive.subStreamsInfo = subStreamsInfo;
  1548.     }

  1549.     private void readUnpackInfo(final ByteBuffer header, final Archive archive) throws IOException {
  1550.         int nid = getUnsignedByte(header);
  1551.         final int numFoldersInt = (int) readUint64(header);
  1552.         final Folder[] folders = new Folder[numFoldersInt];
  1553.         archive.folders = folders;
  1554.         /* final int external = */ getUnsignedByte(header);
  1555.         for (int i = 0; i < numFoldersInt; i++) {
  1556.             folders[i] = readFolder(header);
  1557.         }

  1558.         nid = getUnsignedByte(header);
  1559.         for (final Folder folder : folders) {
  1560.             assertFitsIntoNonNegativeInt("totalOutputStreams", folder.totalOutputStreams);
  1561.             folder.unpackSizes = new long[(int) folder.totalOutputStreams];
  1562.             for (int i = 0; i < folder.totalOutputStreams; i++) {
  1563.                 folder.unpackSizes[i] = readUint64(header);
  1564.             }
  1565.         }

  1566.         nid = getUnsignedByte(header);
  1567.         if (nid == NID.kCRC) {
  1568.             final BitSet crcsDefined = readAllOrBits(header, numFoldersInt);
  1569.             for (int i = 0; i < numFoldersInt; i++) {
  1570.                 if (crcsDefined.get(i)) {
  1571.                     folders[i].hasCrc = true;
  1572.                     folders[i].crc = 0xffffFFFFL & getInt(header);
  1573.                 } else {
  1574.                     folders[i].hasCrc = false;
  1575.                 }
  1576.             }

  1577.             nid = getUnsignedByte(header);
  1578.         }
  1579.     }

  1580.     /**
  1581.      * Discard any queued streams/ folder stream, and reopen the current folder input stream.
  1582.      *
  1583.      * @param folderIndex the index of the folder to reopen
  1584.      * @param file        the 7z entry to read
  1585.      * @throws IOException if exceptions occur when reading the 7z file
  1586.      */
  1587.     private void reopenFolderInputStream(final int folderIndex, final SevenZArchiveEntry file) throws IOException {
  1588.         deferredBlockStreams.clear();
  1589.         if (currentFolderInputStream != null) {
  1590.             currentFolderInputStream.close();
  1591.             currentFolderInputStream = null;
  1592.         }
  1593.         final Folder folder = archive.folders[folderIndex];
  1594.         final int firstPackStreamIndex = archive.streamMap.folderFirstPackStreamIndex[folderIndex];
  1595.         final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos + archive.streamMap.packStreamOffsets[firstPackStreamIndex];

  1596.         currentFolderInputStream = buildDecoderStack(folder, folderOffset, firstPackStreamIndex, file);
  1597.     }

  1598.     private ArchiveStatistics sanityCheckAndCollectStatistics(final ByteBuffer header) throws IOException {
  1599.         final ArchiveStatistics stats = new ArchiveStatistics();

  1600.         int nid = getUnsignedByte(header);

  1601.         if (nid == NID.kArchiveProperties) {
  1602.             sanityCheckArchiveProperties(header);
  1603.             nid = getUnsignedByte(header);
  1604.         }

  1605.         if (nid == NID.kAdditionalStreamsInfo) {
  1606.             throw new IOException("Additional streams unsupported");
  1607.             // nid = getUnsignedByte(header);
  1608.         }

  1609.         if (nid == NID.kMainStreamsInfo) {
  1610.             sanityCheckStreamsInfo(header, stats);
  1611.             nid = getUnsignedByte(header);
  1612.         }

  1613.         if (nid == NID.kFilesInfo) {
  1614.             sanityCheckFilesInfo(header, stats);
  1615.             nid = getUnsignedByte(header);
  1616.         }

  1617.         if (nid != NID.kEnd) {
  1618.             throw new IOException("Badly terminated header, found " + nid);
  1619.         }

  1620.         return stats;
  1621.     }

  1622.     private void sanityCheckArchiveProperties(final ByteBuffer header) throws IOException {
  1623.         long nid = readUint64(header);
  1624.         while (nid != NID.kEnd) {
  1625.             final int propertySize = assertFitsIntoNonNegativeInt("propertySize", readUint64(header));
  1626.             if (skipBytesFully(header, propertySize) < propertySize) {
  1627.                 throw new IOException("invalid property size");
  1628.             }
  1629.             nid = readUint64(header);
  1630.         }
  1631.     }

  1632.     private void sanityCheckFilesInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
  1633.         stats.numberOfEntries = assertFitsIntoNonNegativeInt("numFiles", readUint64(header));

  1634.         int emptyStreams = -1;
  1635.         while (true) {
  1636.             final int propertyType = getUnsignedByte(header);
  1637.             if (propertyType == 0) {
  1638.                 break;
  1639.             }
  1640.             final long size = readUint64(header);
  1641.             switch (propertyType) {
  1642.             case NID.kEmptyStream: {
  1643.                 emptyStreams = readBits(header, stats.numberOfEntries).cardinality();
  1644.                 break;
  1645.             }
  1646.             case NID.kEmptyFile: {
  1647.                 if (emptyStreams == -1) {
  1648.                     throw new IOException("Header format error: kEmptyStream must appear before kEmptyFile");
  1649.                 }
  1650.                 readBits(header, emptyStreams);
  1651.                 break;
  1652.             }
  1653.             case NID.kAnti: {
  1654.                 if (emptyStreams == -1) {
  1655.                     throw new IOException("Header format error: kEmptyStream must appear before kAnti");
  1656.                 }
  1657.                 readBits(header, emptyStreams);
  1658.                 break;
  1659.             }
  1660.             case NID.kName: {
  1661.                 final int external = getUnsignedByte(header);
  1662.                 if (external != 0) {
  1663.                     throw new IOException("Not implemented");
  1664.                 }
  1665.                 final int namesLength = assertFitsIntoNonNegativeInt("file names length", size - 1);
  1666.                 if ((namesLength & 1) != 0) {
  1667.                     throw new IOException("File names length invalid");
  1668.                 }

  1669.                 int filesSeen = 0;
  1670.                 for (int i = 0; i < namesLength; i += 2) {
  1671.                     final char c = getChar(header);
  1672.                     if (c == 0) {
  1673.                         filesSeen++;
  1674.                     }
  1675.                 }
  1676.                 if (filesSeen != stats.numberOfEntries) {
  1677.                     throw new IOException("Invalid number of file names (" + filesSeen + " instead of " + stats.numberOfEntries + ")");
  1678.                 }
  1679.                 break;
  1680.             }
  1681.             case NID.kCTime: {
  1682.                 final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
  1683.                 final int external = getUnsignedByte(header);
  1684.                 if (external != 0) {
  1685.                     throw new IOException("Not implemented");
  1686.                 }
  1687.                 if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
  1688.                     throw new IOException("invalid creation dates size");
  1689.                 }
  1690.                 break;
  1691.             }
  1692.             case NID.kATime: {
  1693.                 final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
  1694.                 final int external = getUnsignedByte(header);
  1695.                 if (external != 0) {
  1696.                     throw new IOException("Not implemented");
  1697.                 }
  1698.                 if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
  1699.                     throw new IOException("invalid access dates size");
  1700.                 }
  1701.                 break;
  1702.             }
  1703.             case NID.kMTime: {
  1704.                 final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
  1705.                 final int external = getUnsignedByte(header);
  1706.                 if (external != 0) {
  1707.                     throw new IOException("Not implemented");
  1708.                 }
  1709.                 if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
  1710.                     throw new IOException("invalid modification dates size");
  1711.                 }
  1712.                 break;
  1713.             }
  1714.             case NID.kWinAttributes: {
  1715.                 final int attributesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
  1716.                 final int external = getUnsignedByte(header);
  1717.                 if (external != 0) {
  1718.                     throw new IOException("Not implemented");
  1719.                 }
  1720.                 if (skipBytesFully(header, 4 * attributesDefined) < 4 * attributesDefined) {
  1721.                     throw new IOException("invalid windows attributes size");
  1722.                 }
  1723.                 break;
  1724.             }
  1725.             case NID.kStartPos: {
  1726.                 throw new IOException("kStartPos is unsupported, please report");
  1727.             }
  1728.             case NID.kDummy: {
  1729.                 // 7z 9.20 asserts the content is all zeros and ignores the property
  1730.                 // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287

  1731.                 if (skipBytesFully(header, size) < size) {
  1732.                     throw new IOException("Incomplete kDummy property");
  1733.                 }
  1734.                 break;
  1735.             }

  1736.             default: {
  1737.                 // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287
  1738.                 if (skipBytesFully(header, size) < size) {
  1739.                     throw new IOException("Incomplete property of type " + propertyType);
  1740.                 }
  1741.                 break;
  1742.             }
  1743.             }
  1744.         }
  1745.         stats.numberOfEntriesWithStream = stats.numberOfEntries - Math.max(emptyStreams, 0);
  1746.     }

  1747.     private int sanityCheckFolder(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {

  1748.         final int numCoders = assertFitsIntoNonNegativeInt("numCoders", readUint64(header));
  1749.         if (numCoders == 0) {
  1750.             throw new IOException("Folder without coders");
  1751.         }
  1752.         stats.numberOfCoders += numCoders;

  1753.         long totalOutStreams = 0;
  1754.         long totalInStreams = 0;
  1755.         for (int i = 0; i < numCoders; i++) {
  1756.             final int bits = getUnsignedByte(header);
  1757.             final int idSize = bits & 0xf;
  1758.             get(header, new byte[idSize]);

  1759.             final boolean isSimple = (bits & 0x10) == 0;
  1760.             final boolean hasAttributes = (bits & 0x20) != 0;
  1761.             final boolean moreAlternativeMethods = (bits & 0x80) != 0;
  1762.             if (moreAlternativeMethods) {
  1763.                 throw new IOException("Alternative methods are unsupported, please report. " + // NOSONAR
  1764.                         "The reference implementation doesn't support them either.");
  1765.             }

  1766.             if (isSimple) {
  1767.                 totalInStreams++;
  1768.                 totalOutStreams++;
  1769.             } else {
  1770.                 totalInStreams += assertFitsIntoNonNegativeInt("numInStreams", readUint64(header));
  1771.                 totalOutStreams += assertFitsIntoNonNegativeInt("numOutStreams", readUint64(header));
  1772.             }

  1773.             if (hasAttributes) {
  1774.                 final int propertiesSize = assertFitsIntoNonNegativeInt("propertiesSize", readUint64(header));
  1775.                 if (skipBytesFully(header, propertiesSize) < propertiesSize) {
  1776.                     throw new IOException("invalid propertiesSize in folder");
  1777.                 }
  1778.             }
  1779.         }
  1780.         assertFitsIntoNonNegativeInt("totalInStreams", totalInStreams);
  1781.         assertFitsIntoNonNegativeInt("totalOutStreams", totalOutStreams);
  1782.         stats.numberOfOutStreams += totalOutStreams;
  1783.         stats.numberOfInStreams += totalInStreams;

  1784.         if (totalOutStreams == 0) {
  1785.             throw new IOException("Total output streams can't be 0");
  1786.         }

  1787.         final int numBindPairs = assertFitsIntoNonNegativeInt("numBindPairs", totalOutStreams - 1);
  1788.         if (totalInStreams < numBindPairs) {
  1789.             throw new IOException("Total input streams can't be less than the number of bind pairs");
  1790.         }
  1791.         final BitSet inStreamsBound = new BitSet((int) totalInStreams);
  1792.         for (int i = 0; i < numBindPairs; i++) {
  1793.             final int inIndex = assertFitsIntoNonNegativeInt("inIndex", readUint64(header));
  1794.             if (totalInStreams <= inIndex) {
  1795.                 throw new IOException("inIndex is bigger than number of inStreams");
  1796.             }
  1797.             inStreamsBound.set(inIndex);
  1798.             final int outIndex = assertFitsIntoNonNegativeInt("outIndex", readUint64(header));
  1799.             if (totalOutStreams <= outIndex) {
  1800.                 throw new IOException("outIndex is bigger than number of outStreams");
  1801.             }
  1802.         }

  1803.         final int numPackedStreams = assertFitsIntoNonNegativeInt("numPackedStreams", totalInStreams - numBindPairs);

  1804.         if (numPackedStreams == 1) {
  1805.             if (inStreamsBound.nextClearBit(0) == -1) {
  1806.                 throw new IOException("Couldn't find stream's bind pair index");
  1807.             }
  1808.         } else {
  1809.             for (int i = 0; i < numPackedStreams; i++) {
  1810.                 final int packedStreamIndex = assertFitsIntoNonNegativeInt("packedStreamIndex", readUint64(header));
  1811.                 if (packedStreamIndex >= totalInStreams) {
  1812.                     throw new IOException("packedStreamIndex is bigger than number of totalInStreams");
  1813.                 }
  1814.             }
  1815.         }

  1816.         return (int) totalOutStreams;
  1817.     }

  1818.     private void sanityCheckPackInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
  1819.         final long packPos = readUint64(header);
  1820.         if (packPos < 0 || SIGNATURE_HEADER_SIZE + packPos > channel.size() || SIGNATURE_HEADER_SIZE + packPos < 0) {
  1821.             throw new IOException("packPos (" + packPos + ") is out of range");
  1822.         }
  1823.         final long numPackStreams = readUint64(header);
  1824.         stats.numberOfPackedStreams = assertFitsIntoNonNegativeInt("numPackStreams", numPackStreams);
  1825.         int nid = getUnsignedByte(header);
  1826.         if (nid == NID.kSize) {
  1827.             long totalPackSizes = 0;
  1828.             for (int i = 0; i < stats.numberOfPackedStreams; i++) {
  1829.                 final long packSize = readUint64(header);
  1830.                 totalPackSizes += packSize;
  1831.                 final long endOfPackStreams = SIGNATURE_HEADER_SIZE + packPos + totalPackSizes;
  1832.                 if (packSize < 0 || endOfPackStreams > channel.size() || endOfPackStreams < packPos) {
  1833.                     throw new IOException("packSize (" + packSize + ") is out of range");
  1834.                 }
  1835.             }
  1836.             nid = getUnsignedByte(header);
  1837.         }

  1838.         if (nid == NID.kCRC) {
  1839.             final int crcsDefined = readAllOrBits(header, stats.numberOfPackedStreams).cardinality();
  1840.             if (skipBytesFully(header, 4 * crcsDefined) < 4 * crcsDefined) {
  1841.                 throw new IOException("invalid number of CRCs in PackInfo");
  1842.             }
  1843.             nid = getUnsignedByte(header);
  1844.         }

  1845.         if (nid != NID.kEnd) {
  1846.             throw new IOException("Badly terminated PackInfo (" + nid + ")");
  1847.         }
  1848.     }

  1849.     private void sanityCheckStreamsInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
  1850.         int nid = getUnsignedByte(header);

  1851.         if (nid == NID.kPackInfo) {
  1852.             sanityCheckPackInfo(header, stats);
  1853.             nid = getUnsignedByte(header);
  1854.         }

  1855.         if (nid == NID.kUnpackInfo) {
  1856.             sanityCheckUnpackInfo(header, stats);
  1857.             nid = getUnsignedByte(header);
  1858.         }

  1859.         if (nid == NID.kSubStreamsInfo) {
  1860.             sanityCheckSubStreamsInfo(header, stats);
  1861.             nid = getUnsignedByte(header);
  1862.         }

  1863.         if (nid != NID.kEnd) {
  1864.             throw new IOException("Badly terminated StreamsInfo");
  1865.         }
  1866.     }

  1867.     private void sanityCheckSubStreamsInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {

  1868.         int nid = getUnsignedByte(header);
  1869.         final List<Integer> numUnpackSubStreamsPerFolder = new LinkedList<>();
  1870.         if (nid == NID.kNumUnpackStream) {
  1871.             for (int i = 0; i < stats.numberOfFolders; i++) {
  1872.                 numUnpackSubStreamsPerFolder.add(assertFitsIntoNonNegativeInt("numStreams", readUint64(header)));
  1873.             }
  1874.             stats.numberOfUnpackSubStreams = numUnpackSubStreamsPerFolder.stream().mapToLong(Integer::longValue).sum();
  1875.             nid = getUnsignedByte(header);
  1876.         } else {
  1877.             stats.numberOfUnpackSubStreams = stats.numberOfFolders;
  1878.         }

  1879.         assertFitsIntoNonNegativeInt("totalUnpackStreams", stats.numberOfUnpackSubStreams);

  1880.         if (nid == NID.kSize) {
  1881.             for (final int numUnpackSubStreams : numUnpackSubStreamsPerFolder) {
  1882.                 if (numUnpackSubStreams == 0) {
  1883.                     continue;
  1884.                 }
  1885.                 for (int i = 0; i < numUnpackSubStreams - 1; i++) {
  1886.                     final long size = readUint64(header);
  1887.                     if (size < 0) {
  1888.                         throw new IOException("negative unpackSize");
  1889.                     }
  1890.                 }
  1891.             }
  1892.             nid = getUnsignedByte(header);
  1893.         }

  1894.         int numDigests = 0;
  1895.         if (numUnpackSubStreamsPerFolder.isEmpty()) {
  1896.             numDigests = stats.folderHasCrc == null ? stats.numberOfFolders : stats.numberOfFolders - stats.folderHasCrc.cardinality();
  1897.         } else {
  1898.             int folderIdx = 0;
  1899.             for (final int numUnpackSubStreams : numUnpackSubStreamsPerFolder) {
  1900.                 if (numUnpackSubStreams != 1 || stats.folderHasCrc == null || !stats.folderHasCrc.get(folderIdx++)) {
  1901.                     numDigests += numUnpackSubStreams;
  1902.                 }
  1903.             }
  1904.         }

  1905.         if (nid == NID.kCRC) {
  1906.             assertFitsIntoNonNegativeInt("numDigests", numDigests);
  1907.             final int missingCrcs = readAllOrBits(header, numDigests).cardinality();
  1908.             if (skipBytesFully(header, 4 * missingCrcs) < 4 * missingCrcs) {
  1909.                 throw new IOException("invalid number of missing CRCs in SubStreamInfo");
  1910.             }
  1911.             nid = getUnsignedByte(header);
  1912.         }

  1913.         if (nid != NID.kEnd) {
  1914.             throw new IOException("Badly terminated SubStreamsInfo");
  1915.         }
  1916.     }

  1917.     private void sanityCheckUnpackInfo(final ByteBuffer header, final ArchiveStatistics stats) throws IOException {
  1918.         int nid = getUnsignedByte(header);
  1919.         if (nid != NID.kFolder) {
  1920.             throw new IOException("Expected kFolder, got " + nid);
  1921.         }
  1922.         final long numFolders = readUint64(header);
  1923.         stats.numberOfFolders = assertFitsIntoNonNegativeInt("numFolders", numFolders);
  1924.         final int external = getUnsignedByte(header);
  1925.         if (external != 0) {
  1926.             throw new IOException("External unsupported");
  1927.         }

  1928.         final List<Integer> numberOfOutputStreamsPerFolder = new LinkedList<>();
  1929.         for (int i = 0; i < stats.numberOfFolders; i++) {
  1930.             numberOfOutputStreamsPerFolder.add(sanityCheckFolder(header, stats));
  1931.         }

  1932.         final long totalNumberOfBindPairs = stats.numberOfOutStreams - stats.numberOfFolders;
  1933.         final long packedStreamsRequiredByFolders = stats.numberOfInStreams - totalNumberOfBindPairs;
  1934.         if (packedStreamsRequiredByFolders < stats.numberOfPackedStreams) {
  1935.             throw new IOException("archive doesn't contain enough packed streams");
  1936.         }

  1937.         nid = getUnsignedByte(header);
  1938.         if (nid != NID.kCodersUnpackSize) {
  1939.             throw new IOException("Expected kCodersUnpackSize, got " + nid);
  1940.         }

  1941.         for (final int numberOfOutputStreams : numberOfOutputStreamsPerFolder) {
  1942.             for (int i = 0; i < numberOfOutputStreams; i++) {
  1943.                 final long unpackSize = readUint64(header);
  1944.                 if (unpackSize < 0) {
  1945.                     throw new IllegalArgumentException("negative unpackSize");
  1946.                 }
  1947.             }
  1948.         }

  1949.         nid = getUnsignedByte(header);
  1950.         if (nid == NID.kCRC) {
  1951.             stats.folderHasCrc = readAllOrBits(header, stats.numberOfFolders);
  1952.             final int crcsDefined = stats.folderHasCrc.cardinality();
  1953.             if (skipBytesFully(header, 4 * crcsDefined) < 4 * crcsDefined) {
  1954.                 throw new IOException("invalid number of CRCs in UnpackInfo");
  1955.             }
  1956.             nid = getUnsignedByte(header);
  1957.         }

  1958.         if (nid != NID.kEnd) {
  1959.             throw new IOException("Badly terminated UnpackInfo");
  1960.         }
  1961.     }

  1962.     /**
  1963.      * Skips all the entries if needed. Entries need to be skipped when:
  1964.      * <p>
  1965.      * 1. it's a random access 2. one of these 2 condition is meet :
  1966.      * </p>
  1967.      * <p>
  1968.      * 2.1 currentEntryIndex != entryIndex : this means there are some entries to be skipped(currentEntryIndex < entryIndex) or the entry has already been
  1969.      * read(currentEntryIndex > entryIndex)
  1970.      * </p>
  1971.      * <p>
  1972.      * 2.2 currentEntryIndex == entryIndex && !hasCurrentEntryBeenRead: if the entry to be read is the current entry, but some data of it has been read before,
  1973.      * then we need to reopen the stream of the folder and skip all the entries before the current entries
  1974.      * </p>
  1975.      *
  1976.      * @param entryIndex     the entry to be read
  1977.      * @param isInSameFolder are the entry to be read and the current entry in the same folder
  1978.      * @param folderIndex    the index of the folder which contains the entry
  1979.      * @return true if there are entries actually skipped
  1980.      * @throws IOException there are exceptions when skipping entries
  1981.      * @since 1.21
  1982.      */
  1983.     private boolean skipEntriesWhenNeeded(final int entryIndex, final boolean isInSameFolder, final int folderIndex) throws IOException {
  1984.         final SevenZArchiveEntry file = archive.files[entryIndex];
  1985.         // if the entry to be read is the current entry, and the entry has not
  1986.         // been read yet, then there's nothing we need to do
  1987.         if (currentEntryIndex == entryIndex && !hasCurrentEntryBeenRead()) {
  1988.             return false;
  1989.         }

  1990.         // 1. if currentEntryIndex < entryIndex :
  1991.         // this means there are some entries to be skipped(currentEntryIndex < entryIndex)
  1992.         // 2. if currentEntryIndex > entryIndex || (currentEntryIndex == entryIndex && hasCurrentEntryBeenRead) :
  1993.         // this means the entry has already been read before, and we need to reopen the
  1994.         // stream of the folder and skip all the entries before the current entries
  1995.         int filesToSkipStartIndex = archive.streamMap.folderFirstFileIndex[currentFolderIndex];
  1996.         if (isInSameFolder) {
  1997.             if (currentEntryIndex < entryIndex) {
  1998.                 // the entries between filesToSkipStartIndex and currentEntryIndex had already been skipped
  1999.                 filesToSkipStartIndex = currentEntryIndex + 1;
  2000.             } else {
  2001.                 // the entry is in the same folder of current entry, but it has already been read before, we need to reset
  2002.                 // the position of the currentFolderInputStream to the beginning of folder, and then skip the files
  2003.                 // from the start entry of the folder again
  2004.                 reopenFolderInputStream(folderIndex, file);
  2005.             }
  2006.         }

  2007.         for (int i = filesToSkipStartIndex; i < entryIndex; i++) {
  2008.             final SevenZArchiveEntry fileToSkip = archive.files[i];
  2009.             InputStream fileStreamToSkip = BoundedInputStream.builder()
  2010.                     .setInputStream(currentFolderInputStream)
  2011.                     .setMaxCount(fileToSkip.getSize())
  2012.                     .setPropagateClose(false)
  2013.                     .get();
  2014.             if (fileToSkip.getHasCrc()) {
  2015.                 // @formatter:off
  2016.                 fileStreamToSkip = ChecksumInputStream.builder()
  2017.                         .setChecksum(new CRC32())
  2018.                         .setInputStream(fileStreamToSkip)
  2019.                         .setCountThreshold(fileToSkip.getSize())
  2020.                         .setExpectedChecksumValue(fileToSkip.getCrcValue())
  2021.                         .get();
  2022.                 // @formatter:on
  2023.             }
  2024.             deferredBlockStreams.add(fileStreamToSkip);

  2025.             // set the content methods as well, it equals to file.getContentMethods() because they are in same folder
  2026.             fileToSkip.setContentMethods(file.getContentMethods());
  2027.         }
  2028.         return true;
  2029.     }

  2030.     @Override
  2031.     public String toString() {
  2032.         return archive.toString();
  2033.     }

  2034.     private Archive tryToLocateEndHeader(final byte[] password) throws IOException {
  2035.         final ByteBuffer nidBuf = ByteBuffer.allocate(1);
  2036.         final long searchLimit = 1024L * 1024 * 1;
  2037.         // Main header, plus bytes that readStartHeader would read
  2038.         final long previousDataSize = channel.position() + 20;
  2039.         final long minPos;
  2040.         // Determine minimal position - can't start before current position
  2041.         if (channel.position() + searchLimit > channel.size()) {
  2042.             minPos = channel.position();
  2043.         } else {
  2044.             minPos = channel.size() - searchLimit;
  2045.         }
  2046.         long pos = channel.size() - 1;
  2047.         // Loop: Try from end of archive
  2048.         while (pos > minPos) {
  2049.             pos--;
  2050.             channel.position(pos);
  2051.             nidBuf.rewind();
  2052.             if (channel.read(nidBuf) < 1) {
  2053.                 throw new EOFException();
  2054.             }
  2055.             final int nid = nidBuf.array()[0];
  2056.             // First indicator: Byte equals one of these header identifiers
  2057.             if (nid == NID.kEncodedHeader || nid == NID.kHeader) {
  2058.                 try {
  2059.                     // Try to initialize Archive structure from here
  2060.                     final long nextHeaderOffset = pos - previousDataSize;
  2061.                     final long nextHeaderSize = channel.size() - pos;
  2062.                     final StartHeader startHeader = new StartHeader(nextHeaderOffset, nextHeaderSize, 0);
  2063.                     final Archive result = initializeArchive(startHeader, password, false);
  2064.                     // Sanity check: There must be some data...
  2065.                     if (result.packSizes.length > 0 && result.files.length > 0) {
  2066.                         return result;
  2067.                     }
  2068.                 } catch (final Exception ignored) {
  2069.                     // Wrong guess...
  2070.                 }
  2071.             }
  2072.         }
  2073.         throw new IOException("Start header corrupt and unable to guess end header");
  2074.     }
  2075. }