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