    001/*
    002 *  Licensed to the Apache Software Foundation (ASF) under one or more
    003 *  contributor license agreements.  See the NOTICE file distributed with
    004 *  this work for additional information regarding copyright ownership.
    005 *  The ASF licenses this file to You under the Apache License, Version 2.0
    006 *  (the "License"); you may not use this file except in compliance with
    007 *  the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017package org.apache.commons.compress.archivers.tar;
    018
    019import java.io.ByteArrayOutputStream;
    020import java.io.Closeable;
    021import java.io.File;
    022import java.io.IOException;
    023import java.io.InputStream;
    024import java.nio.ByteBuffer;
    025import java.nio.channels.SeekableByteChannel;
    026import java.nio.file.Files;
    027import java.nio.file.Path;
    028import java.util.ArrayList;
    029import java.util.Arrays;
    030import java.util.HashMap;
    031import java.util.LinkedList;
    032import java.util.List;
    033import java.util.Map;
    034
    035import org.apache.commons.compress.archivers.zip.ZipEncoding;
    036import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
    037import org.apache.commons.compress.utils.ArchiveUtils;
    038import org.apache.commons.compress.utils.BoundedArchiveInputStream;
    039import org.apache.commons.compress.utils.BoundedInputStream;
    040import org.apache.commons.compress.utils.BoundedSeekableByteChannelInputStream;
    041import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
    042
    043/**
    044 * Provides random access to UNIX archives.
    045 *
    046 * @since 1.21
    047 */
    048public class TarFile implements Closeable {
    049
    050    private final class BoundedTarEntryInputStream extends BoundedArchiveInputStream {
    051
    052        private final SeekableByteChannel channel;
    053
    054        private final TarArchiveEntry entry;
    055
    056        private long entryOffset;
    057
    058        private int currentSparseInputStreamIndex;
    059
    060        BoundedTarEntryInputStream(final TarArchiveEntry entry, final SeekableByteChannel channel) throws IOException {
    061            super(entry.getDataOffset(), entry.getRealSize());
    062            if (channel.size() - entry.getSize() < entry.getDataOffset()) {
    063                throw new IOException("entry size exceeds archive size");
    064            }
    065            this.entry = entry;
    066            this.channel = channel;
    067        }
    068
    069        @Override
    070        protected int read(final long pos, final ByteBuffer buf) throws IOException {
    071            if (entryOffset >= entry.getRealSize()) {
    072                return -1;
    073            }
    074
    075            final int totalRead;
    076            if (entry.isSparse()) {
    077                totalRead = readSparse(entryOffset, buf, buf.limit());
    078            } else {
    079                totalRead = readArchive(pos, buf);
    080            }
    081
    082            if (totalRead == -1) {
    083                if (buf.array().length > 0) {
    084                    throw new IOException("Truncated TAR archive");
    085                }
    086                setAtEOF(true);
    087            } else {
    088                entryOffset += totalRead;
    089                buf.flip();
    090            }
    091            return totalRead;
    092        }
    093
    094        private int readArchive(final long pos, final ByteBuffer buf) throws IOException {
    095            channel.position(pos);
    096            return channel.read(buf);
    097        }
    098
    099        private int readSparse(final long pos, final ByteBuffer buf, final int numToRead) throws IOException {
    100            // if there are no actual input streams, just read from the original archive
    101            final List<InputStream> entrySparseInputStreams = sparseInputStreams.get(entry.getName());
    102            if (entrySparseInputStreams == null || entrySparseInputStreams.isEmpty()) {
    103                return readArchive(entry.getDataOffset() + pos, buf);
    104            }
    105
    106            if (currentSparseInputStreamIndex >= entrySparseInputStreams.size()) {
    107                return -1;
    108            }
    109
    110            final InputStream currentInputStream = entrySparseInputStreams.get(currentSparseInputStreamIndex);
    111            final byte[] bufArray = new byte[numToRead];
    112            final int readLen = currentInputStream.read(bufArray);
    113            if (readLen != -1) {
    114                buf.put(bufArray, 0, readLen);
    115            }
    116
    117            // if the current input stream is the last input stream,
    118            // just return the number of bytes read from current input stream
    119            if (currentSparseInputStreamIndex == entrySparseInputStreams.size() - 1) {
    120                return readLen;
    121            }
    122
    123            // if EOF of current input stream is meet, open a new input stream and recursively call read
    124            if (readLen == -1) {
    125                currentSparseInputStreamIndex++;
    126                return readSparse(pos, buf, numToRead);
    127            }
    128
    129            // if the rest data of current input stream is not long enough, open a new input stream
    130            // and recursively call read
    131            if (readLen < numToRead) {
    132                currentSparseInputStreamIndex++;
    133                final int readLenOfNext = readSparse(pos + readLen, buf, numToRead - readLen);
    134                if (readLenOfNext == -1) {
    135                    return readLen;
    136                }
    137
    138                return readLen + readLenOfNext;
    139            }
    140
    141            // if the rest data of current input stream is enough(which means readLen == len), just return readLen
    142            return readLen;
    143        }
    144    }
    145
    146    private static final int SMALL_BUFFER_SIZE = 256;
    147
    148    private final byte[] smallBuf = new byte[SMALL_BUFFER_SIZE];
    149
    150    private final SeekableByteChannel archive;
    151
    152    /**
    153     * The encoding of the tar file
    154     */
    155    private final ZipEncoding zipEncoding;
    156
    157    private final LinkedList<TarArchiveEntry> entries = new LinkedList<>();
    158
    159    private final int blockSize;
    160
    161    private final boolean lenient;
    162
    163    private final int recordSize;
    164
    165    private final ByteBuffer recordBuffer;
    166
    167    // the global sparse headers, this is only used in PAX Format 0.X
    168    private final List<TarArchiveStructSparse> globalSparseHeaders = new ArrayList<>();
    169
    170    private boolean hasHitEOF;
    171
    172    /**
    173     * The meta-data about the current entry
    174     */
    175    private TarArchiveEntry currEntry;
    176
    177    // the global PAX header
    178    private Map<String, String> globalPaxHeaders = new HashMap<>();
    179
    180    private final Map<String, List<InputStream>> sparseInputStreams = new HashMap<>();
    181
    182    /**
    183     * Constructor for TarFile.
    184     *
    185     * @param content the content to use
    186     * @throws IOException when reading the tar archive fails
    187     */
    188    public TarFile(final byte[] content) throws IOException {
    189        this(new SeekableInMemoryByteChannel(content));
    190    }
    191
    192    /**
    193     * Constructor for TarFile.
    194     *
    195     * @param content the content to use
    196     * @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    197     *                {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead.
    198     * @throws IOException when reading the tar archive fails
    199     */
    200    public TarFile(final byte[] content, final boolean lenient) throws IOException {
    201        this(new SeekableInMemoryByteChannel(content), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, lenient);
    202    }
    203
    204    /**
    205     * Constructor for TarFile.
    206     *
    207     * @param content  the content to use
    208     * @param encoding the encoding to use
    209     * @throws IOException when reading the tar archive fails
    210     */
    211    public TarFile(final byte[] content, final String encoding) throws IOException {
    212        this(new SeekableInMemoryByteChannel(content), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, encoding, false);
    213    }
    214
    215    /**
    216     * Constructor for TarFile.
    217     *
    218     * @param archive the file of the archive to use
    219     * @throws IOException when reading the tar archive fails
    220     */
    221    public TarFile(final File archive) throws IOException {
    222        this(archive.toPath());
    223    }
    224
    225    /**
    226     * Constructor for TarFile.
    227     *
    228     * @param archive the file of the archive to use
    229     * @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    230     *                {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead.
    231     * @throws IOException when reading the tar archive fails
    232     */
    233    public TarFile(final File archive, final boolean lenient) throws IOException {
    234        this(archive.toPath(), lenient);
    235    }
    236
    237    /**
    238     * Constructor for TarFile.
    239     *
    240     * @param archive  the file of the archive to use
    241     * @param encoding the encoding to use
    242     * @throws IOException when reading the tar archive fails
    243     */
    244    public TarFile(final File archive, final String encoding) throws IOException {
    245        this(archive.toPath(), encoding);
    246    }
    247
    248    /**
    249     * Constructor for TarFile.
    250     *
    251     * @param archivePath the path of the archive to use
    252     * @throws IOException when reading the tar archive fails
    253     */
    254    public TarFile(final Path archivePath) throws IOException {
    255        this(Files.newByteChannel(archivePath), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, false);
    256    }
    257
    258    /**
    259     * Constructor for TarFile.
    260     *
    261     * @param archivePath the path of the archive to use
    262     * @param lenient     when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    263     *                    {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead.
    264     * @throws IOException when reading the tar archive fails
    265     */
    266    public TarFile(final Path archivePath, final boolean lenient) throws IOException {
    267        this(Files.newByteChannel(archivePath), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, lenient);
    268    }
    269
    270    /**
    271     * Constructor for TarFile.
    272     *
    273     * @param archivePath the path of the archive to use
    274     * @param encoding    the encoding to use
    275     * @throws IOException when reading the tar archive fails
    276     */
    277    public TarFile(final Path archivePath, final String encoding) throws IOException {
    278        this(Files.newByteChannel(archivePath), TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, encoding, false);
    279    }
    280
    281    /**
    282     * Constructor for TarFile.
    283     *
    284     * @param content the content to use
    285     * @throws IOException when reading the tar archive fails
    286     */
    287    public TarFile(final SeekableByteChannel content) throws IOException {
    288        this(content, TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, null, false);
    289    }
    290
    291    /**
    292     * Constructor for TarFile.
    293     *
    294     * @param archive    the seekable byte channel to use
    295     * @param blockSize  the blocks size to use
    296     * @param recordSize the record size to use
    297     * @param encoding   the encoding to use
    298     * @param lenient    when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    299     *                   {@link TarArchiveEntry#UNKNOWN}. When set to false such illegal fields cause an exception instead.
    300     * @throws IOException when reading the tar archive fails
    301     */
    302    public TarFile(final SeekableByteChannel archive, final int blockSize, final int recordSize, final String encoding, final boolean lenient)
    303            throws IOException {
    304        this.archive = archive;
    305        this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
    306        this.recordSize = recordSize;
    307        this.recordBuffer = ByteBuffer.allocate(this.recordSize);
    308        this.blockSize = blockSize;
    309        this.lenient = lenient;
    310
    311        TarArchiveEntry entry;
    312        while ((entry = getNextTarEntry()) != null) {
    313            entries.add(entry);
    314        }
    315    }
    316
    317    /**
    318     * Update the current entry with the read pax headers
    319     *
    320     * @param headers       Headers read from the pax header
    321     * @param sparseHeaders Sparse headers read from pax header
    322     */
    323    private void applyPaxHeadersToCurrentEntry(final Map<String, String> headers, final List<TarArchiveStructSparse> sparseHeaders) throws IOException {
    324        currEntry.updateEntryFromPaxHeaders(headers);
    325        currEntry.setSparseHeaders(sparseHeaders);
    326    }
    327
    328    /**
    329     * Build the input streams consisting of all-zero input streams and non-zero input streams. When reading from the non-zero input streams, the data is
    330     * actually read from the original input stream. The size of each input stream is introduced by the sparse headers.
    331     *
    332     * @implNote Some all-zero input streams and non-zero input streams have the size of 0. We DO NOT store the 0 size input streams because they are
    333     *           meaningless.
    334     */
    335    private void buildSparseInputStreams() throws IOException {
    336        final List<InputStream> streams = new ArrayList<>();
    337
    338        final List<TarArchiveStructSparse> sparseHeaders = currEntry.getOrderedSparseHeaders();
    339
    340        // Stream doesn't need to be closed at all as it doesn't use any resources
    341        final InputStream zeroInputStream = new TarArchiveSparseZeroInputStream(); // NOSONAR
    342        // logical offset into the extracted entry
    343        long offset = 0;
    344        long numberOfZeroBytesInSparseEntry = 0;
    345        for (final TarArchiveStructSparse sparseHeader : sparseHeaders) {
    346            final long zeroBlockSize = sparseHeader.getOffset() - offset;
    347            if (zeroBlockSize < 0) {
    348                // sparse header says to move backwards inside the extracted entry
    349                throw new IOException("Corrupted struct sparse detected");
    350            }
    351
    352            // only store the zero block if it is not empty
    353            if (zeroBlockSize > 0) {
    354                streams.add(new BoundedInputStream(zeroInputStream, zeroBlockSize));
    355                numberOfZeroBytesInSparseEntry += zeroBlockSize;
    356            }
    357
    358            // only store the input streams with non-zero size
    359            if (sparseHeader.getNumbytes() > 0) {
    360                final long start = currEntry.getDataOffset() + sparseHeader.getOffset() - numberOfZeroBytesInSparseEntry;
    361                if (start + sparseHeader.getNumbytes() < start) {
    362                    // possible integer overflow
    363                    throw new IOException("Unreadable TAR archive, sparse block offset or length too big");
    364                }
    365                streams.add(new BoundedSeekableByteChannelInputStream(start, sparseHeader.getNumbytes(), archive));
    366            }
    367
    368            offset = sparseHeader.getOffset() + sparseHeader.getNumbytes();
    369        }
    370
    371        sparseInputStreams.put(currEntry.getName(), streams);
    372    }
    373
    374    @Override
    375    public void close() throws IOException {
    376        archive.close();
    377    }
    378
    379    /**
    380     * This method is invoked once the end of the archive is hit, it tries to consume the remaining bytes under the assumption that the tool creating this
    381     * archive has padded the last block.
    382     */
    383    private void consumeRemainderOfLastBlock() throws IOException {
    384        final long bytesReadOfLastBlock = archive.position() % blockSize;
    385        if (bytesReadOfLastBlock > 0) {
    386            repositionForwardBy(blockSize - bytesReadOfLastBlock);
    387        }
    388    }
    389
    390    /**
    391     * Gets all TAR Archive Entries from the TarFile
    392     *
    393     * @return All entries from the tar file
    394     */
    395    public List<TarArchiveEntry> getEntries() {
    396        return new ArrayList<>(entries);
    397    }
    398
    399    /**
    400     * Gets the input stream for the provided Tar Archive Entry.
    401     *
    402     * @param entry Entry to get the input stream from
    403     * @return Input stream of the provided entry
    404     * @throws IOException Corrupted TAR archive. Can't read entry.
    405     */
    406    public InputStream getInputStream(final TarArchiveEntry entry) throws IOException {
    407        try {
    408            return new BoundedTarEntryInputStream(entry, archive);
    409        } catch (final RuntimeException ex) {
    410            throw new IOException("Corrupted TAR archive. Can't read entry", ex);
    411        }
    412    }
    413
    414    /**
    415     * Gets the next entry in this tar archive as long name data.
    416     *
    417     * @return The next entry in the archive as long name data, or null.
    418     * @throws IOException on error
    419     */
    420    private byte[] getLongNameData() throws IOException {
    421        final ByteArrayOutputStream longName = new ByteArrayOutputStream();
    422        int length;
    423        try (InputStream in = getInputStream(currEntry)) {
    424            while ((length = in.read(smallBuf)) >= 0) {
    425                longName.write(smallBuf, 0, length);
    426            }
    427        }
    428        getNextTarEntry();
    429        if (currEntry == null) {
    430            // Bugzilla: 40334
    431            // Malformed tar file - long entry name not followed by entry
    432            return null;
    433        }
    434        byte[] longNameData = longName.toByteArray();
    435        // remove trailing null terminator(s)
    436        length = longNameData.length;
    437        while (length > 0 && longNameData[length - 1] == 0) {
    438            --length;
    439        }
    440        if (length != longNameData.length) {
    441            longNameData = Arrays.copyOf(longNameData, length);
    442        }
    443        return longNameData;
    444    }
    445
    446    /**
    447     * Gets the next entry in this tar archive. This will skip to the end of the current entry, if there is one, and place the position of the channel at the
    448     * header of the next entry, and read the header and instantiate a new TarEntry from the header bytes and return that entry. If there are no more entries in
    449     * the archive, null will be returned to indicate that the end of the archive has been reached.
    450     *
    451     * @return The next TarEntry in the archive, or null if there is no next entry.
    452     * @throws IOException when reading the next TarEntry fails
    453     */
    454    private TarArchiveEntry getNextTarEntry() throws IOException {
    455        if (isAtEOF()) {
    456            return null;
    457        }
    458
    459        if (currEntry != null) {
    460            // Skip to the end of the entry
    461            repositionForwardTo(currEntry.getDataOffset() + currEntry.getSize());
    462            throwExceptionIfPositionIsNotInArchive();
    463            skipRecordPadding();
    464        }
    465
    466        final ByteBuffer headerBuf = getRecord();
    467        if (null == headerBuf) {
    468            /* hit EOF */
    469            currEntry = null;
    470            return null;
    471        }
    472
    473        try {
    474            final long position = archive.position();
    475            currEntry = new TarArchiveEntry(globalPaxHeaders, headerBuf.array(), zipEncoding, lenient, position);
    476        } catch (final IllegalArgumentException e) {
    477            throw new IOException("Error detected parsing the header", e);
    478        }
    479
    480        if (currEntry.isGNULongLinkEntry()) {
    481            final byte[] longLinkData = getLongNameData();
    482            if (longLinkData == null) {
    483                // Bugzilla: 40334
    484                // Malformed tar file - long link entry name not followed by
    485                // entry
    486                return null;
    487            }
    488            currEntry.setLinkName(zipEncoding.decode(longLinkData));
    489        }
    490
    491        if (currEntry.isGNULongNameEntry()) {
    492            final byte[] longNameData = getLongNameData();
    493            if (longNameData == null) {
    494                // Bugzilla: 40334
    495                // Malformed tar file - long entry name not followed by
    496                // entry
    497                return null;
    498            }
    499
    500            // COMPRESS-509 : the name of directories should end with '/'
    501            final String name = zipEncoding.decode(longNameData);
    502            currEntry.setName(name);
    503            if (currEntry.isDirectory() && !name.endsWith("/")) {
    504                currEntry.setName(name + "/");
    505            }
    506        }
    507
    508        if (currEntry.isGlobalPaxHeader()) { // Process Global Pax headers
    509            readGlobalPaxHeaders();
    510        }
    511
    512        try {
    513            if (currEntry.isPaxHeader()) { // Process Pax headers
    514                paxHeaders();
    515            } else if (!globalPaxHeaders.isEmpty()) {
    516                applyPaxHeadersToCurrentEntry(globalPaxHeaders, globalSparseHeaders);
    517            }
    518        } catch (final NumberFormatException e) {
    519            throw new IOException("Error detected parsing the pax header", e);
    520        }
    521
    522        if (currEntry.isOldGNUSparse()) { // Process sparse files
    523            readOldGNUSparse();
    524        }
    525
    526        return currEntry;
    527    }
    528
    529    /**
    530     * Gets the next record in this tar archive. This will skip over any remaining data in the current entry, if there is one, and place the input stream at the
    531     * header of the next entry.
    532     *
    533     * <p>
    534     * If there are no more entries in the archive, null will be returned to indicate that the end of the archive has been reached. At the same time the
    535     * {@code hasHitEOF} marker will be set to true.
    536     * </p>
    537     *
    538     * @return The next TarEntry in the archive, or null if there is no next entry.
    539     * @throws IOException when reading the next TarEntry fails
    540     */
    541    private ByteBuffer getRecord() throws IOException {
    542        ByteBuffer headerBuf = readRecord();
    543        setAtEOF(isEOFRecord(headerBuf));
    544        if (isAtEOF() && headerBuf != null) {
    545            // Consume rest
    546            tryToConsumeSecondEOFRecord();
    547            consumeRemainderOfLastBlock();
    548            headerBuf = null;
    549        }
    550        return headerBuf;
    551    }
    552
    553    protected final boolean isAtEOF() {
    554        return hasHitEOF;
    555    }
    556
    557    private boolean isDirectory() {
    558        return currEntry != null && currEntry.isDirectory();
    559    }
    560
    561    private boolean isEOFRecord(final ByteBuffer headerBuf) {
    562        return headerBuf == null || ArchiveUtils.isArrayZero(headerBuf.array(), recordSize);
    563    }
    564
    565    /**
    566     * <p>
    567     * For PAX Format 0.0, the sparse headers(GNU.sparse.offset and GNU.sparse.numbytes) may appear multi times, and they look like:
    568     *
    569     * <pre>
    570     * GNU.sparse.size=size
    571     * GNU.sparse.numblocks=numblocks
    572     * repeat numblocks times
    573     *   GNU.sparse.offset=offset
    574     *   GNU.sparse.numbytes=numbytes
    575     * end repeat
    576     * </pre>
    577     *
    578     * <p>
    579     * For PAX Format 0.1, the sparse headers are stored in a single variable : GNU.sparse.map
    580     *
    581     * <pre>
    582     * GNU.sparse.map
    583     *    Map of non-null data chunks. It is a string consisting of comma-separated values "offset,size[,offset-1,size-1...]"
    584     * </pre>
    585     *
    586     * <p>
    587     * For PAX Format 1.X: <br>
    588     * The sparse map itself is stored in the file data block, preceding the actual file data. It consists of a series of decimal numbers delimited by newlines.
    589     * The map is padded with nulls to the nearest block boundary. The first number gives the number of entries in the map. Following are map entries, each one
    590     * consisting of two numbers giving the offset and size of the data block it describes.
    591     *
    592     * @throws IOException
    593     */
    594    private void paxHeaders() throws IOException {
    595        List<TarArchiveStructSparse> sparseHeaders = new ArrayList<>();
    596        final Map<String, String> headers;
    597        try (InputStream input = getInputStream(currEntry)) {
    598            headers = TarUtils.parsePaxHeaders(input, sparseHeaders, globalPaxHeaders, currEntry.getSize());
    599        }
    600
    601        // for 0.1 PAX Headers
    602        if (headers.containsKey(TarGnuSparseKeys.MAP)) {
    603            sparseHeaders = new ArrayList<>(TarUtils.parseFromPAX01SparseHeaders(headers.get(TarGnuSparseKeys.MAP)));
    604        }
    605        getNextTarEntry(); // Get the actual file entry
    606        if (currEntry == null) {
    607            throw new IOException("premature end of tar archive. Didn't find any entry after PAX header.");
    608        }
    609        applyPaxHeadersToCurrentEntry(headers, sparseHeaders);
    610
    611        // for 1.0 PAX Format, the sparse map is stored in the file data block
    612        if (currEntry.isPaxGNU1XSparse()) {
    613            try (InputStream input = getInputStream(currEntry)) {
    614                sparseHeaders = TarUtils.parsePAX1XSparseHeaders(input, recordSize);
    615            }
    616            currEntry.setSparseHeaders(sparseHeaders);
    617            // data of the entry is after the pax gnu entry. So we need to update the data position once again
    618            currEntry.setDataOffset(currEntry.getDataOffset() + recordSize);
    619        }
    620
    621        // sparse headers are all done reading, we need to build
    622        // sparse input streams using these sparse headers
    623        buildSparseInputStreams();
    624    }
    625
    626    private void readGlobalPaxHeaders() throws IOException {
    627        try (InputStream input = getInputStream(currEntry)) {
    628            globalPaxHeaders = TarUtils.parsePaxHeaders(input, globalSparseHeaders, globalPaxHeaders, currEntry.getSize());
    629        }
    630        getNextTarEntry(); // Get the actual file entry
    631
    632        if (currEntry == null) {
    633            throw new IOException("Error detected parsing the pax header");
    634        }
    635    }
    636
    637    /**
    638     * Adds the sparse chunks from the current entry to the sparse chunks, including any additional sparse entries following the current entry.
    639     *
    640     * @throws IOException when reading the sparse entry fails
    641     */
    642    private void readOldGNUSparse() throws IOException {
    643        if (currEntry.isExtended()) {
    644            TarArchiveSparseEntry entry;
    645            do {
    646                final ByteBuffer headerBuf = getRecord();
    647                if (headerBuf == null) {
    648                    throw new IOException("premature end of tar archive. Didn't find extended_header after header with extended flag.");
    649                }
    650                entry = new TarArchiveSparseEntry(headerBuf.array());
    651                currEntry.getSparseHeaders().addAll(entry.getSparseHeaders());
    652                currEntry.setDataOffset(currEntry.getDataOffset() + recordSize);
    653            } while (entry.isExtended());
    654        }
    655
    656        // sparse headers are all done reading, we need to build
    657        // sparse input streams using these sparse headers
    658        buildSparseInputStreams();
    659    }
    660
    661    /**
    662     * Read a record from the input stream and return the data.
    663     *
    664     * @return The record data or null if EOF has been hit.
    665     * @throws IOException if reading from the archive fails
    666     */
    667    private ByteBuffer readRecord() throws IOException {
    668        recordBuffer.rewind();
    669        final int readNow = archive.read(recordBuffer);
    670        if (readNow != recordSize) {
    671            return null;
    672        }
    673        return recordBuffer;
    674    }
    675
    676    private void repositionForwardBy(final long offset) throws IOException {
    677        repositionForwardTo(archive.position() + offset);
    678    }
    679
    680    private void repositionForwardTo(final long newPosition) throws IOException {
    681        final long currPosition = archive.position();
    682        if (newPosition < currPosition) {
    683            throw new IOException("trying to move backwards inside of the archive");
    684        }
    685        archive.position(newPosition);
    686    }
    687
    688    protected final void setAtEOF(final boolean b) {
    689        hasHitEOF = b;
    690    }
    691
    692    /**
    693     * The last record block should be written at the full size, so skip any additional space used to fill a record after an entry
    694     *
    695     * @throws IOException when skipping the padding of the record fails
    696     */
    697    private void skipRecordPadding() throws IOException {
    698        if (!isDirectory() && currEntry.getSize() > 0 && currEntry.getSize() % recordSize != 0) {
    699            final long numRecords = currEntry.getSize() / recordSize + 1;
    700            final long padding = numRecords * recordSize - currEntry.getSize();
    701            repositionForwardBy(padding);
    702            throwExceptionIfPositionIsNotInArchive();
    703        }
    704    }
    705
    706    /**
    707     * Checks if the current position of the SeekableByteChannel is in the archive.
    708     *
    709     * @throws IOException If the position is not in the archive
    710     */
    711    private void throwExceptionIfPositionIsNotInArchive() throws IOException {
    712        if (archive.size() < archive.position()) {
    713            throw new IOException("Truncated TAR archive");
    714        }
    715    }
    716
    717    /**
    718     * Tries to read the next record resetting the position in the archive if it is not an EOF record.
    719     *
    720     * <p>
    721     * This is meant to protect against cases where a tar implementation has written only one EOF record when two are expected. Actually this won't help since a
    722     * non-conforming implementation likely won't fill full blocks consisting of - by default - ten records either so we probably have already read beyond the
    723     * archive anyway.
    724     * </p>
    725     *
    726     * @throws IOException if reading the record of resetting the position in the archive fails
    727     */
    728    private void tryToConsumeSecondEOFRecord() throws IOException {
    729        boolean shouldReset = true;
    730        try {
    731            shouldReset = !isEOFRecord(readRecord());
    732        } finally {
    733            if (shouldReset) {
    734                archive.position(archive.position() - recordSize);
    735            }
    736        }
    737    }
    738}