    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 * http://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.archivers.tar;
    020
    021import java.io.File;
    022import java.io.IOException;
    023import java.io.UncheckedIOException;
    024import java.math.BigDecimal;
    025import java.nio.file.DirectoryStream;
    026import java.nio.file.Files;
    027import java.nio.file.LinkOption;
    028import java.nio.file.Path;
    029import java.nio.file.attribute.BasicFileAttributes;
    030import java.nio.file.attribute.DosFileAttributes;
    031import java.nio.file.attribute.FileTime;
    032import java.nio.file.attribute.PosixFileAttributes;
    033import java.time.DateTimeException;
    034import java.time.Instant;
    035import java.util.ArrayList;
    036import java.util.Collections;
    037import java.util.Comparator;
    038import java.util.Date;
    039import java.util.HashMap;
    040import java.util.List;
    041import java.util.Locale;
    042import java.util.Map;
    043import java.util.Objects;
    044import java.util.Set;
    045import java.util.regex.Pattern;
    046import java.util.stream.Collectors;
    047
    048import org.apache.commons.compress.archivers.ArchiveEntry;
    049import org.apache.commons.compress.archivers.EntryStreamOffsets;
    050import org.apache.commons.compress.archivers.zip.ZipEncoding;
    051import org.apache.commons.compress.utils.ArchiveUtils;
    052import org.apache.commons.compress.utils.IOUtils;
    053import org.apache.commons.compress.utils.ParsingUtils;
    054import org.apache.commons.compress.utils.TimeUtils;
    055import org.apache.commons.io.file.attribute.FileTimes;
    056
    057/**
    058 * This class represents an entry in a Tar archive. It consists of the entry's header, as well as the entry's File. Entries can be instantiated in one of three
    059 * ways, depending on how they are to be used.
    060 * <p>
    061 * TarEntries that are created from the header bytes read from an archive are instantiated with the {@link TarArchiveEntry#TarArchiveEntry(byte[])} constructor.
    062 * These entries will be used when extracting from or listing the contents of an archive. These entries have their header filled in using the header bytes. They
    063 * also set the File to null, since they reference an archive entry not a file.
    064 * </p>
    065 * <p>
    066 * TarEntries that are created from Files that are to be written into an archive are instantiated with the {@link TarArchiveEntry#TarArchiveEntry(File)} or
    067 * {@link TarArchiveEntry#TarArchiveEntry(Path)} constructor. These entries have their header filled in using the File's information. They also keep a reference
    068 * to the File for convenience when writing entries.
    069 * </p>
    070 * <p>
    071 * Finally, TarEntries can be constructed from nothing but a name. This allows the programmer to construct the entry by hand, for instance when only an
    072 * InputStream is available for writing to the archive, and the header information is constructed from other information. In this case the header fields are set
    073 * to defaults and the File is set to null.
    074 * </p>
    075 * <p>
    076 * The C structure for a Tar Entry's header is:
    077 * </p>
    078 * <pre>
    079 * struct header {
    080 *   char name[100];     // TarConstants.NAMELEN    - offset   0
    081 *   char mode[8];       // TarConstants.MODELEN    - offset 100
    082 *   char uid[8];        // TarConstants.UIDLEN     - offset 108
    083 *   char gid[8];        // TarConstants.GIDLEN     - offset 116
    084 *   char size[12];      // TarConstants.SIZELEN    - offset 124
    085 *   char mtime[12];     // TarConstants.MODTIMELEN - offset 136
    086 *   char chksum[8];     // TarConstants.CHKSUMLEN  - offset 148
    087 *   char linkflag[1];   //                         - offset 156
    088 *   char linkname[100]; // TarConstants.NAMELEN    - offset 157
    089 *   // The following fields are only present in new-style POSIX tar archives:
    090 *   char magic[6];      // TarConstants.MAGICLEN   - offset 257
    091 *   char version[2];    // TarConstants.VERSIONLEN - offset 263
    092 *   char uname[32];     // TarConstants.UNAMELEN   - offset 265
    093 *   char gname[32];     // TarConstants.GNAMELEN   - offset 297
    094 *   char devmajor[8];   // TarConstants.DEVLEN     - offset 329
    095 *   char devminor[8];   // TarConstants.DEVLEN     - offset 337
    096 *   char prefix[155];   // TarConstants.PREFIXLEN  - offset 345
    097 *   // Used if "name" field is not long enough to hold the path
    098 *   char pad[12];       // NULs                    - offset 500
    099 * } header;
    100 * </pre>
    101 * <p>
    102 * All unused bytes are set to null. New-style GNU tar files are slightly different from the above. For values of size larger than 077777777777L (11 7s) or uid
    103 * and gid larger than 07777777L (7 7s) the sign bit of the first byte is set, and the rest of the field is the binary representation of the number. See
    104 * {@link TarUtils#parseOctalOrBinary(byte[], int, int)}.
    105 * <p>
    106 * The C structure for a old GNU Tar Entry's header is:
    107 * </p>
    108 * <pre>
    109 * struct oldgnu_header {
    110 *   char unused_pad1[345]; // TarConstants.PAD1LEN_GNU       - offset 0
    111 *   char atime[12];        // TarConstants.ATIMELEN_GNU      - offset 345
    112 *   char ctime[12];        // TarConstants.CTIMELEN_GNU      - offset 357
    113 *   char offset[12];       // TarConstants.OFFSETLEN_GNU     - offset 369
    114 *   char longnames[4];     // TarConstants.LONGNAMESLEN_GNU  - offset 381
    115 *   char unused_pad2;      // TarConstants.PAD2LEN_GNU       - offset 385
    116 *   struct sparse sp[4];   // TarConstants.SPARSELEN_GNU     - offset 386
    117 *   char isextended;       // TarConstants.ISEXTENDEDLEN_GNU - offset 482
    118 *   char realsize[12];     // TarConstants.REALSIZELEN_GNU   - offset 483
    119 *   char unused_pad[17];   // TarConstants.PAD3LEN_GNU       - offset 495
    120 * };
    121 * </pre>
    122 * <p>
    123 * Whereas, "struct sparse" is:
    124 * </p>
    125 * <pre>
    126 * struct sparse {
    127 *   char offset[12];   // offset 0
    128 *   char numbytes[12]; // offset 12
    129 * };
    130 * </pre>
    131 * <p>
    132 * The C structure for a xstar (Jörg Schilling star) Tar Entry's header is:
    133 * </p>
    134 * <pre>
    135 * struct star_header {
    136 *   char name[100];     // offset   0
    137 *   char mode[8];       // offset 100
    138 *   char uid[8];        // offset 108
    139 *   char gid[8];        // offset 116
    140 *   char size[12];      // offset 124
    141 *   char mtime[12];     // offset 136
    142 *   char chksum[8];     // offset 148
    143 *   char typeflag;      // offset 156
    144 *   char linkname[100]; // offset 157
    145 *   char magic[6];      // offset 257
    146 *   char version[2];    // offset 263
    147 *   char uname[32];     // offset 265
    148 *   char gname[32];     // offset 297
    149 *   char devmajor[8];   // offset 329
    150 *   char devminor[8];   // offset 337
    151 *   char prefix[131];   // offset 345
    152 *   char atime[12];     // offset 476
    153 *   char ctime[12];     // offset 488
    154 *   char mfill[8];      // offset 500
    155 *   char xmagic[4];     // offset 508  "tar\0"
    156 * };
    157 * </pre>
    158 * <p>
    159 * which is identical to new-style POSIX up to the first 130 bytes of the prefix.
    160 * </p>
    161 * <p>
    162 * The C structure for the xstar-specific parts of a xstar Tar Entry's header is:
    163 * </p>
    164 * <pre>
    165 * struct xstar_in_header {
    166 *   char fill[345];         // offset 0     Everything before t_prefix
    167 *   char prefix[1];         // offset 345   Prefix for t_name
    168 *   char fill2;             // offset 346
    169 *   char fill3[8];          // offset 347
    170 *   char isextended;        // offset 355
    171 *   struct sparse sp[SIH];  // offset 356   8 x 12
    172 *   char realsize[12];      // offset 452   Real size for sparse data
    173 *   char offset[12];        // offset 464   Offset for multivolume data
    174 *   char atime[12];         // offset 476
    175 *   char ctime[12];         // offset 488
    176 *   char mfill[8];          // offset 500
    177 *   char xmagic[4];         // offset 508   "tar\0"
    178 * };
    179 * </pre>
    180 *
    181 * @NotThreadSafe
    182 */
    183public class TarArchiveEntry implements ArchiveEntry, TarConstants, EntryStreamOffsets {
    184
    185    private static final TarArchiveEntry[] EMPTY_TAR_ARCHIVE_ENTRY_ARRAY = {};
    186
    187    /**
    188     * Value used to indicate unknown mode, user/groupids, device numbers and modTime when parsing a file in lenient mode and the archive contains illegal
    189     * fields.
    190     *
    191     * @since 1.19
    192     */
    193    public static final long UNKNOWN = -1L;
    194
    195    /** Maximum length of a user's name in the tar file */
    196    public static final int MAX_NAMELEN = 31;
    197
    198    /** Default permissions bits for directories */
    199    public static final int DEFAULT_DIR_MODE = 040755;
    200
    201    /** Default permissions bits for files */
    202    public static final int DEFAULT_FILE_MODE = 0100644;
    203
    204    /**
    205     * Convert millis to seconds
    206     *
    207     * @deprecated Unused.
    208     */
    209    @Deprecated
    210    public static final int MILLIS_PER_SECOND = 1000;
    211
    212    /**
    213     * Regular expression pattern for validating values in pax extended header file time fields. These fields contain two numeric values (seconds and sub-second
    214     * values) as per this definition: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_05
    215     * <p>
    216     * Since they are parsed into long values, maximum length of each is the same as Long.MAX_VALUE which is 19 digits.
    217     * </p>
    218     */
    219    private static final Pattern PAX_EXTENDED_HEADER_FILE_TIMES_PATTERN = Pattern.compile("-?\\d{1,19}(?:\\.\\d{1,19})?");
    220
    221    private static FileTime fileTimeFromOptionalSeconds(final long seconds) {
    222        if (seconds <= 0) {
    223            return null;
    224        }
    225        return TimeUtils.unixTimeToFileTime(seconds);
    226    }
    227
    228    /**
    229     * Strips Windows' drive letter as well as any leading slashes, turns path separators into forward slashes.
    230     */
    231    private static String normalizeFileName(String fileName, final boolean preserveAbsolutePath) {
    232        if (!preserveAbsolutePath) {
    233            final String property = System.getProperty("os.name");
    234            if (property != null) {
    235                final String osName = property.toLowerCase(Locale.ROOT);
    236
    237                // Strip off drive letters!
    238                // REVIEW Would a better check be "(File.separator == '\')"?
    239
    240                if (osName.startsWith("windows")) {
    241                    if (fileName.length() > 2) {
    242                        final char ch1 = fileName.charAt(0);
    243                        final char ch2 = fileName.charAt(1);
    244
    245                        if (ch2 == ':' && (ch1 >= 'a' && ch1 <= 'z' || ch1 >= 'A' && ch1 <= 'Z')) {
    246                            fileName = fileName.substring(2);
    247                        }
    248                    }
    249                } else if (osName.contains("netware")) {
    250                    final int colon = fileName.indexOf(':');
    251                    if (colon != -1) {
    252                        fileName = fileName.substring(colon + 1);
    253                    }
    254                }
    255            }
    256        }
    257
    258        fileName = fileName.replace(File.separatorChar, '/');
    259
    260        // No absolute pathnames
    261        // Windows (and Posix?) paths can start with "\\NetworkDrive\",
    262        // so we loop on starting /'s.
    263        while (!preserveAbsolutePath && fileName.startsWith("/")) {
    264            fileName = fileName.substring(1);
    265        }
    266        return fileName;
    267    }
    268
    269    private static Instant parseInstantFromDecimalSeconds(final String value) throws IOException {
    270        // Validate field values to prevent denial of service attacks with BigDecimal values (see JDK-6560193)
    271        if (!TarArchiveEntry.PAX_EXTENDED_HEADER_FILE_TIMES_PATTERN.matcher(value).matches()) {
    272            throw new IOException("Corrupted PAX header. Time field value is invalid '" + value + "'");
    273        }
    274
    275        final BigDecimal epochSeconds = new BigDecimal(value);
    276        final long seconds = epochSeconds.longValue();
    277        final long nanos = epochSeconds.remainder(BigDecimal.ONE).movePointRight(9).longValue();
    278        try {
    279            return Instant.ofEpochSecond(seconds, nanos);
    280        } catch (DateTimeException | ArithmeticException e) {
    281            // DateTimeException: Thrown if the instant exceeds the maximum or minimum instant.
    282            // ArithmeticException: Thrown if numeric overflow occurs.
    283            throw new IOException("Corrupted PAX header. Time field value is invalid '" + value + "'", e);
    284        }
    285    }
    286
    287    /** The entry's name. */
    288    private String name = "";
    289
    290    /** Whether to allow leading slashes or drive names inside the name */
    291    private final boolean preserveAbsolutePath;
    292
    293    /** The entry's permission mode. */
    294    private int mode;
    295
    296    /** The entry's user id. */
    297    private long userId;
    298
    299    /** The entry's group id. */
    300    private long groupId;
    301
    302    /** The entry's size. */
    303    private long size;
    304
    305    /**
    306     * The entry's modification time. Corresponds to the POSIX {@code mtime} attribute.
    307     */
    308    private FileTime mTime;
    309
    310    /**
    311     * The entry's status change time. Corresponds to the POSIX {@code ctime} attribute.
    312     *
    313     * @since 1.22
    314     */
    315    private FileTime cTime;
    316
    317    /**
    318     * The entry's last access time. Corresponds to the POSIX {@code atime} attribute.
    319     *
    320     * @since 1.22
    321     */
    322    private FileTime aTime;
    323
    324    /**
    325     * The entry's creation time. Corresponds to the POSIX {@code birthtime} attribute.
    326     *
    327     * @since 1.22
    328     */
    329    private FileTime birthTime;
    330
    331    /** If the header checksum is reasonably correct. */
    332    private boolean checkSumOK;
    333
    334    /** The entry's link flag. */
    335    private byte linkFlag;
    336
    337    /** The entry's link name. */
    338    private String linkName = "";
    339
    340    /** The entry's magic tag. */
    341    private String magic = MAGIC_POSIX;
    342
    343    /** The version of the format */
    344    private String version = VERSION_POSIX;
    345
    346    /** The entry's user name. */
    347    private String userName;
    348
    349    /** The entry's group name. */
    350    private String groupName = "";
    351
    352    /** The entry's major device number. */
    353    private int devMajor;
    354
    355    /** The entry's minor device number. */
    356    private int devMinor;
    357
    358    /** The sparse headers in tar */
    359    private List<TarArchiveStructSparse> sparseHeaders;
    360
    361    /** If an extension sparse header follows. */
    362    private boolean isExtended;
    363
    364    /** The entry's real size in case of a sparse file. */
    365    private long realSize;
    366
    367    /** Is this entry a GNU sparse entry using one of the PAX formats? */
    368    private boolean paxGNUSparse;
    369
    370    /**
    371     * is this entry a GNU sparse entry using 1.X PAX formats? the sparse headers of 1.x PAX Format is stored in file data block
    372     */
    373    private boolean paxGNU1XSparse;
    374
    375    /** Is this entry a star sparse entry using the PAX header? */
    376    private boolean starSparse;
    377
    378    /** The entry's file reference */
    379    private final Path file;
    380
    381    /** The entry's file linkOptions */
    382    private final LinkOption[] linkOptions;
    383
    384    /** Extra, user supplied pax headers */
    385    private final Map<String, String> extraPaxHeaders = new HashMap<>();
    386
    387    private long dataOffset = EntryStreamOffsets.OFFSET_UNKNOWN;
    388
    389    /**
    390     * Constructs an empty entry and prepares the header values.
    391     */
    392    private TarArchiveEntry(final boolean preserveAbsolutePath) {
    393        String user = System.getProperty("user.name", "");
    394        if (user.length() > MAX_NAMELEN) {
    395            user = user.substring(0, MAX_NAMELEN);
    396        }
    397        this.userName = user;
    398        this.file = null;
    399        this.linkOptions = IOUtils.EMPTY_LINK_OPTIONS;
    400        this.preserveAbsolutePath = preserveAbsolutePath;
    401    }
    402
    403    /**
    404     * Constructs an entry from an archive's header bytes. File is set to null.
    405     *
    406     * @param headerBuf The header bytes from a tar archive entry.
    407     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    408     */
    409    public TarArchiveEntry(final byte[] headerBuf) {
    410        this(false);
    411        parseTarHeader(headerBuf);
    412    }
    413
    414    /**
    415     * Constructs an entry from an archive's header bytes. File is set to null.
    416     *
    417     * @param headerBuf The header bytes from a tar archive entry.
    418     * @param encoding  encoding to use for file names
    419     * @since 1.4
    420     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    421     * @throws IOException              on error
    422     */
    423    public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding) throws IOException {
    424        this(headerBuf, encoding, false);
    425    }
    426
    427    /**
    428     * Constructs an entry from an archive's header bytes. File is set to null.
    429     *
    430     * @param headerBuf The header bytes from a tar archive entry.
    431     * @param encoding  encoding to use for file names
    432     * @param lenient   when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    433     *                  {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
    434     * @since 1.19
    435     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    436     * @throws IOException              on error
    437     */
    438    public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient) throws IOException {
    439        this(Collections.emptyMap(), headerBuf, encoding, lenient);
    440    }
    441
    442    /**
    443     * Constructs an entry from an archive's header bytes for random access tar. File is set to null.
    444     *
    445     * @param headerBuf  the header bytes from a tar archive entry.
    446     * @param encoding   encoding to use for file names.
    447     * @param lenient    when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    448     *                   {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
    449     * @param dataOffset position of the entry data in the random access file.
    450     * @since 1.21
    451     * @throws IllegalArgumentException if any of the numeric fields have an invalid format.
    452     * @throws IOException              on error.
    453     */
    454    public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient, final long dataOffset) throws IOException {
    455        this(headerBuf, encoding, lenient);
    456        setDataOffset(dataOffset);
    457    }
    458
    459    /**
    460     * Constructs an entry for a file. File is set to file, and the header is constructed from information from the file. The name is set from the normalized
    461     * file path.
    462     * <p>
    463     * The entry's name will be the value of the {@code file}'s path with all file separators replaced by forward slashes and leading slashes as well as Windows
    464     * drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
    465     * </p>
    466     * <p>
    467     * Note: Since 1.21 this internally uses the same code as the TarArchiveEntry constructors with a {@link Path} as parameter. But all thrown exceptions are
    468     * ignored. If handling those exceptions is needed consider switching to the path constructors.
    469     * </p>
    470     *
    471     * @param file The file that the entry represents.
    472     */
    473    public TarArchiveEntry(final File file) {
    474        this(file, file.getPath());
    475    }
    476
    477    /**
    478     * Constructs an entry for a file. File is set to file, and the header is constructed from information from the file.
    479     * <p>
    480     * The entry's name will be the value of the {@code fileName} argument with all file separators replaced by forward slashes and leading slashes as well as
    481     * Windows drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
    482     * </p>
    483     * <p>
    484     * Note: Since 1.21 this internally uses the same code as the TarArchiveEntry constructors with a {@link Path} as parameter. But all thrown exceptions are
    485     * ignored. If handling those exceptions is needed consider switching to the path constructors.
    486     * </p>
    487     *
    488     * @param file     The file that the entry represents.
    489     * @param fileName the name to be used for the entry.
    490     */
    491    public TarArchiveEntry(final File file, final String fileName) {
    492        final String normalizedName = normalizeFileName(fileName, false);
    493        this.file = file.toPath();
    494        this.linkOptions = IOUtils.EMPTY_LINK_OPTIONS;
    495        try {
    496            readFileMode(this.file, normalizedName);
    497        } catch (final IOException e) {
    498            // Ignore exceptions from NIO for backwards compatibility
    499            // Fallback to get size of file if it's no directory to the old file api
    500            if (!file.isDirectory()) {
    501                this.size = file.length();
    502            }
    503        }
    504        this.userName = "";
    505        try {
    506            readOsSpecificProperties(this.file);
    507        } catch (final IOException e) {
    508            // Ignore exceptions from NIO for backwards compatibility
    509            // Fallback to get the last modified date of the file from the old file api
    510            this.mTime = FileTime.fromMillis(file.lastModified());
    511        }
    512        preserveAbsolutePath = false;
    513    }
    514
    515    /**
    516     * Constructs an entry from an archive's header bytes. File is set to null.
    517     *
    518     * @param globalPaxHeaders the parsed global PAX headers, or null if this is the first one.
    519     * @param headerBuf        The header bytes from a tar archive entry.
    520     * @param encoding         encoding to use for file names
    521     * @param lenient          when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    522     *                         {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
    523     * @since 1.22
    524     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    525     * @throws IOException              on error
    526     */
    527    public TarArchiveEntry(final Map<String, String> globalPaxHeaders, final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient)
    528            throws IOException {
    529        this(false);
    530        parseTarHeader(globalPaxHeaders, headerBuf, encoding, false, lenient);
    531    }
    532
    533    /**
    534     * Constructs an entry from an archive's header bytes for random access tar. File is set to null.
    535     *
    536     * @param globalPaxHeaders the parsed global PAX headers, or null if this is the first one.
    537     * @param headerBuf        the header bytes from a tar archive entry.
    538     * @param encoding         encoding to use for file names.
    539     * @param lenient          when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    540     *                         {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
    541     * @param dataOffset       position of the entry data in the random access file.
    542     * @since 1.22
    543     * @throws IllegalArgumentException if any of the numeric fields have an invalid format.
    544     * @throws IOException              on error.
    545     */
    546    public TarArchiveEntry(final Map<String, String> globalPaxHeaders, final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient,
    547            final long dataOffset) throws IOException {
    548        this(globalPaxHeaders, headerBuf, encoding, lenient);
    549        setDataOffset(dataOffset);
    550    }
    551
    552    /**
    553     * Constructs an entry for a file. File is set to file, and the header is constructed from information from the file. The name is set from the normalized
    554     * file path.
    555     * <p>
    556     * The entry's name will be the value of the {@code file}'s path with all file separators replaced by forward slashes and leading slashes as well as Windows
    557     * drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
    558     * </p>
    559     *
    560     * @param file The file that the entry represents.
    561     * @throws IOException if an I/O error occurs
    562     * @since 1.21
    563     */
    564    public TarArchiveEntry(final Path file) throws IOException {
    565        this(file, file.toString());
    566    }
    567
    568    /**
    569     * Constructs an entry for a file. File is set to file, and the header is constructed from information from the file.
    570     * <p>
    571     * The entry's name will be the value of the {@code fileName} argument with all file separators replaced by forward slashes and leading slashes as well as
    572     * Windows drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
    573     * </p>
    574     *
    575     * @param file        The file that the entry represents.
    576     * @param fileName    the name to be used for the entry.
    577     * @param linkOptions options indicating how symbolic links are handled.
    578     * @throws IOException if an I/O error occurs
    579     * @since 1.21
    580     */
    581    public TarArchiveEntry(final Path file, final String fileName, final LinkOption... linkOptions) throws IOException {
    582        final String normalizedName = normalizeFileName(fileName, false);
    583        this.file = file;
    584        this.linkOptions = linkOptions == null ? IOUtils.EMPTY_LINK_OPTIONS : linkOptions;
    585        readFileMode(file, normalizedName, linkOptions);
    586        this.userName = "";
    587        readOsSpecificProperties(file);
    588        preserveAbsolutePath = false;
    589    }
    590
    591    /**
    592     * Constructs an entry with only a name. This allows the programmer to construct the entry's header "by hand". File is set to null.
    593     * <p>
    594     * The entry's name will be the value of the {@code name} argument with all file separators replaced by forward slashes and leading slashes as well as
    595     * Windows drive letters stripped.
    596     * </p>
    597     *
    598     * @param name the entry name
    599     */
    600    public TarArchiveEntry(final String name) {
    601        this(name, false);
    602    }
    603
    604    /**
    605     * Constructs an entry with only a name. This allows the programmer to construct the entry's header "by hand". File is set to null.
    606     * <p>
    607     * The entry's name will be the value of the {@code name} argument with all file separators replaced by forward slashes. Leading slashes and Windows drive
    608     * letters are stripped if {@code preserveAbsolutePath} is {@code false}.
    609     * </p>
    610     *
    611     * @param name                 the entry name
    612     * @param preserveAbsolutePath whether to allow leading slashes or drive letters in the name.
    613     *
    614     * @since 1.1
    615     */
    616    public TarArchiveEntry(String name, final boolean preserveAbsolutePath) {
    617        this(preserveAbsolutePath);
    618        name = normalizeFileName(name, preserveAbsolutePath);
    619        final boolean isDir = name.endsWith("/");
    620        this.name = name;
    621        this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
    622        this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
    623        this.mTime = FileTime.from(Instant.now());
    624        this.userName = "";
    625    }
    626
    627    /**
    628     * Constructs an entry with a name and a link flag.
    629     * <p>
    630     * The entry's name will be the value of the {@code name} argument with all file separators replaced by forward slashes and leading slashes as well as
    631     * Windows drive letters stripped.
    632     * </p>
    633     *
    634     * @param name     the entry name
    635     * @param linkFlag the entry link flag.
    636     */
    637    public TarArchiveEntry(final String name, final byte linkFlag) {
    638        this(name, linkFlag, false);
    639    }
    640
    641    /**
    642     * Constructs an entry with a name and a link flag.
    643     * <p>
    644     * The entry's name will be the value of the {@code name} argument with all file separators replaced by forward slashes. Leading slashes and Windows drive
    645     * letters are stripped if {@code preserveAbsolutePath} is {@code false}.
    646     * </p>
    647     *
    648     * @param name                 the entry name
    649     * @param linkFlag             the entry link flag.
    650     * @param preserveAbsolutePath whether to allow leading slashes or drive letters in the name.
    651     *
    652     * @since 1.5
    653     */
    654    public TarArchiveEntry(final String name, final byte linkFlag, final boolean preserveAbsolutePath) {
    655        this(name, preserveAbsolutePath);
    656        this.linkFlag = linkFlag;
    657        if (linkFlag == LF_GNUTYPE_LONGNAME) {
    658            magic = MAGIC_GNU;
    659            version = VERSION_GNU_SPACE;
    660        }
    661    }
    662
    663    /**
    664     * Adds a PAX header to this entry. If the header corresponds to an existing field in the entry, that field will be set; otherwise the header will be added
    665     * to the extraPaxHeaders Map
    666     *
    667     * @param name  The full name of the header to set.
    668     * @param value value of header.
    669     * @since 1.15
    670     */
    671    public void addPaxHeader(final String name, final String value) {
    672        try {
    673            processPaxHeader(name, value);
    674        } catch (final IOException ex) {
    675            throw new IllegalArgumentException("Invalid input", ex);
    676        }
    677    }
    678
    679    /**
    680     * Clears all extra PAX headers.
    681     *
    682     * @since 1.15
    683     */
    684    public void clearExtraPaxHeaders() {
    685        extraPaxHeaders.clear();
    686    }
    687
    688    /**
    689     * Determine if the two entries are equal. Equality is determined by the header names being equal.
    690     *
    691     * @param it Entry to be checked for equality.
    692     * @return True if the entries are equal.
    693     */
    694    @Override
    695    public boolean equals(final Object it) {
    696        if (it == null || getClass() != it.getClass()) {
    697            return false;
    698        }
    699        return equals((TarArchiveEntry) it);
    700    }
    701
    702    /**
    703     * Determine if the two entries are equal. Equality is determined by the header names being equal.
    704     *
    705     * @param it Entry to be checked for equality.
    706     * @return True if the entries are equal.
    707     */
    708    public boolean equals(final TarArchiveEntry it) {
    709        return it != null && getName().equals(it.getName());
    710    }
    711
    712    /**
    713     * Evaluates an entry's header format from a header buffer.
    714     *
    715     * @param header The tar entry header buffer to evaluate the format for.
    716     * @return format type
    717     */
    718    private int evaluateType(final Map<String, String> globalPaxHeaders, final byte[] header) {
    719        if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) {
    720            return FORMAT_OLDGNU;
    721        }
    722        if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) {
    723            if (isXstar(globalPaxHeaders, header)) {
    724                return FORMAT_XSTAR;
    725            }
    726            return FORMAT_POSIX;
    727        }
    728        return 0;
    729    }
    730
    731    private int fill(final byte value, final int offset, final byte[] outbuf, final int length) {
    732        for (int i = 0; i < length; i++) {
    733            outbuf[offset + i] = value;
    734        }
    735        return offset + length;
    736    }
    737
    738    private int fill(final int value, final int offset, final byte[] outbuf, final int length) {
    739        return fill((byte) value, offset, outbuf, length);
    740    }
    741
    742    void fillGNUSparse0xData(final Map<String, String> headers) throws IOException {
    743        paxGNUSparse = true;
    744        realSize = ParsingUtils.parseIntValue(headers.get(TarGnuSparseKeys.SIZE));
    745        if (headers.containsKey(TarGnuSparseKeys.NAME)) {
    746            // version 0.1
    747            name = headers.get(TarGnuSparseKeys.NAME);
    748        }
    749    }
    750
    751    void fillGNUSparse1xData(final Map<String, String> headers) throws IOException {
    752        paxGNUSparse = true;
    753        paxGNU1XSparse = true;
    754        if (headers.containsKey(TarGnuSparseKeys.NAME)) {
    755            name = headers.get(TarGnuSparseKeys.NAME);
    756        }
    757        if (headers.containsKey(TarGnuSparseKeys.REALSIZE)) {
    758            realSize = ParsingUtils.parseIntValue(headers.get(TarGnuSparseKeys.REALSIZE));
    759        }
    760    }
    761
    762    void fillStarSparseData(final Map<String, String> headers) throws IOException {
    763        starSparse = true;
    764        if (headers.containsKey("SCHILY.realsize")) {
    765            realSize = ParsingUtils.parseLongValue(headers.get("SCHILY.realsize"));
    766        }
    767    }
    768
    769    /**
    770     * Gets this entry's creation time.
    771     *
    772     * @since 1.22
    773     * @return This entry's computed creation time.
    774     */
    775    public FileTime getCreationTime() {
    776        return birthTime;
    777    }
    778
    779    /**
    780     * {@inheritDoc}
    781     *
    782     * @since 1.21
    783     */
    784    @Override
    785    public long getDataOffset() {
    786        return dataOffset;
    787    }
    788
    789    /**
    790     * Gets this entry's major device number.
    791     *
    792     * @return This entry's major device number.
    793     * @since 1.4
    794     */
    795    public int getDevMajor() {
    796        return devMajor;
    797    }
    798
    799    /**
    800     * Gets this entry's minor device number.
    801     *
    802     * @return This entry's minor device number.
    803     * @since 1.4
    804     */
    805    public int getDevMinor() {
    806        return devMinor;
    807    }
    808
    809    /**
    810     * If this entry represents a file, and the file is a directory, return an array of TarEntries for this entry's children.
    811     * <p>
    812     * This method is only useful for entries created from a {@code
    813     * File} or {@code Path} but not for entries read from an archive.
    814     * </p>
    815     *
    816     * @return An array of TarEntry's for this entry's children.
    817     */
    818    public TarArchiveEntry[] getDirectoryEntries() {
    819        if (file == null || !isDirectory()) {
    820            return EMPTY_TAR_ARCHIVE_ENTRY_ARRAY;
    821        }
    822        final List<TarArchiveEntry> entries = new ArrayList<>();
    823        try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(file)) {
    824            for (final Path p : dirStream) {
    825                entries.add(new TarArchiveEntry(p));
    826            }
    827        } catch (final IOException e) {
    828            return EMPTY_TAR_ARCHIVE_ENTRY_ARRAY;
    829        }
    830        return entries.toArray(EMPTY_TAR_ARCHIVE_ENTRY_ARRAY);
    831    }
    832
    833    /**
    834     * Gets named extra PAX header
    835     *
    836     * @param name The full name of an extended PAX header to retrieve
    837     * @return The value of the header, if any.
    838     * @since 1.15
    839     */
    840    public String getExtraPaxHeader(final String name) {
    841        return extraPaxHeaders.get(name);
    842    }
    843
    844    /**
    845     * Gets extra PAX Headers
    846     *
    847     * @return read-only map containing any extra PAX Headers
    848     * @since 1.15
    849     */
    850    public Map<String, String> getExtraPaxHeaders() {
    851        return Collections.unmodifiableMap(extraPaxHeaders);
    852    }
    853
    854    /**
    855     * Gets this entry's file.
    856     * <p>
    857     * This method is only useful for entries created from a {@code
    858     * File} or {@code Path} but not for entries read from an archive.
    859     * </p>
    860     *
    861     * @return this entry's file or null if the entry was not created from a file.
    862     */
    863    public File getFile() {
    864        if (file == null) {
    865            return null;
    866        }
    867        return file.toFile();
    868    }
    869
    870    /**
    871     * Gets this entry's group id.
    872     *
    873     * @return This entry's group id.
    874     * @deprecated use #getLongGroupId instead as group ids can be bigger than {@link Integer#MAX_VALUE}
    875     */
    876    @Deprecated
    877    public int getGroupId() {
    878        return (int) (groupId & 0xffffffff);
    879    }
    880
    881    /**
    882     * Gets this entry's group name.
    883     *
    884     * @return This entry's group name.
    885     */
    886    public String getGroupName() {
    887        return groupName;
    888    }
    889
    890    /**
    891     * Gets this entry's last access time.
    892     *
    893     * @since 1.22
    894     * @return This entry's last access time.
    895     */
    896    public FileTime getLastAccessTime() {
    897        return aTime;
    898    }
    899
    900    /**
    901     * Gets this entry's modification time. This is equivalent to {@link TarArchiveEntry#getLastModifiedTime()}, but precision is truncated to milliseconds.
    902     *
    903     * @return This entry's modification time.
    904     * @see TarArchiveEntry#getLastModifiedTime()
    905     */
    906    @Override
    907    public Date getLastModifiedDate() {
    908        return getModTime();
    909    }
    910
    911    /**
    912     * Gets this entry's modification time.
    913     *
    914     * @since 1.22
    915     * @return This entry's modification time.
    916     */
    917    public FileTime getLastModifiedTime() {
    918        return mTime;
    919    }
    920
    921    /**
    922     * Gets this entry's link flag.
    923     *
    924     * @return this entry's link flag.
    925     * @since 1.23
    926     */
    927    public byte getLinkFlag() {
    928        return this.linkFlag;
    929    }
    930
    931    /**
    932     * Gets this entry's link name.
    933     *
    934     * @return This entry's link name.
    935     */
    936    public String getLinkName() {
    937        return linkName;
    938    }
    939
    940    /**
    941     * Gets this entry's group id.
    942     *
    943     * @since 1.10
    944     * @return This entry's group id.
    945     */
    946    public long getLongGroupId() {
    947        return groupId;
    948    }
    949
    950    /**
    951     * Gets this entry's user id.
    952     *
    953     * @return This entry's user id.
    954     * @since 1.10
    955     */
    956    public long getLongUserId() {
    957        return userId;
    958    }
    959
    960    /**
    961     * Gets this entry's mode.
    962     *
    963     * @return This entry's mode.
    964     */
    965    public int getMode() {
    966        return mode;
    967    }
    968
    969    /**
    970     * Gets this entry's modification time. This is equivalent to {@link TarArchiveEntry#getLastModifiedTime()}, but precision is truncated to milliseconds.
    971     *
    972     * @return This entry's modification time.
    973     * @see TarArchiveEntry#getLastModifiedTime()
    974     */
    975    public Date getModTime() {
    976        final FileTime fileTime = mTime;
    977        return FileTimes.toDate(fileTime);
    978    }
    979
    980    /**
    981     * Gets this entry's name.
    982     * <p>
    983     * This method returns the raw name as it is stored inside of the archive.
    984     * </p>
    985     *
    986     * @return This entry's name.
    987     */
    988    @Override
    989    public String getName() {
    990        return name;
    991    }
    992
    993    /**
    994     * Gets this entry's sparse headers ordered by offset with all empty sparse sections at the start filtered out.
    995     *
    996     * @return immutable list of this entry's sparse headers, never null
    997     * @since 1.21
    998     * @throws IOException if the list of sparse headers contains blocks that overlap
    999     */
    1000    public List<TarArchiveStructSparse> getOrderedSparseHeaders() throws IOException {
    1001        if (sparseHeaders == null || sparseHeaders.isEmpty()) {
    1002            return Collections.emptyList();
    1003        }
    1004        final List<TarArchiveStructSparse> orderedAndFiltered = sparseHeaders.stream().filter(s -> s.getOffset() > 0 || s.getNumbytes() > 0)
    1005                .sorted(Comparator.comparingLong(TarArchiveStructSparse::getOffset)).collect(Collectors.toList());
    1006        final int numberOfHeaders = orderedAndFiltered.size();
    1007        for (int i = 0; i < numberOfHeaders; i++) {
    1008            final TarArchiveStructSparse str = orderedAndFiltered.get(i);
    1009            if (i + 1 < numberOfHeaders && str.getOffset() + str.getNumbytes() > orderedAndFiltered.get(i + 1).getOffset()) {
    1010                throw new IOException("Corrupted TAR archive. Sparse blocks for " + getName() + " overlap each other.");
    1011            }
    1012            if (str.getOffset() + str.getNumbytes() < 0) {
    1013                // integer overflow?
    1014                throw new IOException("Unreadable TAR archive. Offset and numbytes for sparse block in " + getName() + " too large.");
    1015            }
    1016        }
    1017        if (!orderedAndFiltered.isEmpty()) {
    1018            final TarArchiveStructSparse last = orderedAndFiltered.get(numberOfHeaders - 1);
    1019            if (last.getOffset() + last.getNumbytes() > getRealSize()) {
    1020                throw new IOException("Corrupted TAR archive. Sparse block extends beyond real size of the entry");
    1021            }
    1022        }
    1023        return orderedAndFiltered;
    1024    }
    1025
    1026    /**
    1027     * Gets this entry's file.
    1028     * <p>
    1029     * This method is only useful for entries created from a {@code
    1030     * File} or {@code Path} but not for entries read from an archive.
    1031     * </p>
    1032     *
    1033     * @return this entry's file or null if the entry was not created from a file.
    1034     * @since 1.21
    1035     */
    1036    public Path getPath() {
    1037        return file;
    1038    }
    1039
    1040    /**
    1041     * Gets this entry's real file size in case of a sparse file.
    1042     * <p>
    1043     * This is the size a file would take on disk if the entry was expanded.
    1044     * </p>
    1045     * <p>
    1046     * If the file is not a sparse file, return size instead of realSize.
    1047     * </p>
    1048     *
    1049     * @return This entry's real file size, if the file is not a sparse file, return size instead of realSize.
    1050     */
    1051    public long getRealSize() {
    1052        if (!isSparse()) {
    1053            return getSize();
    1054        }
    1055        return realSize;
    1056    }
    1057
    1058    /**
    1059     * Gets this entry's file size.
    1060     * <p>
    1061     * This is the size the entry's data uses inside the archive. Usually this is the same as {@link #getRealSize}, but it doesn't take the "holes" into account
    1062     * when the entry represents a sparse file.
    1063     * </p>
    1064     *
    1065     * @return This entry's file size.
    1066     */
    1067    @Override
    1068    public long getSize() {
    1069        return size;
    1070    }
    1071
    1072    /**
    1073     * Gets this entry's sparse headers
    1074     *
    1075     * @return This entry's sparse headers
    1076     * @since 1.20
    1077     */
    1078    public List<TarArchiveStructSparse> getSparseHeaders() {
    1079        return sparseHeaders;
    1080    }
    1081
    1082    /**
    1083     * Gets this entry's status change time.
    1084     *
    1085     * @since 1.22
    1086     * @return This entry's status change time.
    1087     */
    1088    public FileTime getStatusChangeTime() {
    1089        return cTime;
    1090    }
    1091
    1092    /**
    1093     * Gets this entry's user id.
    1094     *
    1095     * @return This entry's user id.
    1096     * @deprecated use #getLongUserId instead as user ids can be bigger than {@link Integer#MAX_VALUE}
    1097     */
    1098    @Deprecated
    1099    public int getUserId() {
    1100        return (int) (userId & 0xffffffff);
    1101    }
    1102
    1103    /**
    1104     * Gets this entry's user name.
    1105     *
    1106     * @return This entry's user name.
    1107     */
    1108    public String getUserName() {
    1109        return userName;
    1110    }
    1111
    1112    /**
    1113     * Hash codes are based on entry names.
    1114     *
    1115     * @return the entry hash code
    1116     */
    1117    @Override
    1118    public int hashCode() {
    1119        return getName().hashCode();
    1120    }
    1121
    1122    /**
    1123     * Tests whether this is a block device entry.
    1124     *
    1125     * @since 1.2
    1126     * @return whether this is a block device
    1127     */
    1128    public boolean isBlockDevice() {
    1129        return linkFlag == LF_BLK;
    1130    }
    1131
    1132    /**
    1133     * Tests whether this is a character device entry.
    1134     *
    1135     * @since 1.2
    1136     * @return whether this is a character device
    1137     */
    1138    public boolean isCharacterDevice() {
    1139        return linkFlag == LF_CHR;
    1140    }
    1141
    1142    /**
    1143     * Tests whether this entry's checksum status.
    1144     *
    1145     * @return if the header checksum is reasonably correct
    1146     * @see TarUtils#verifyCheckSum(byte[])
    1147     * @since 1.5
    1148     */
    1149    public boolean isCheckSumOK() {
    1150        return checkSumOK;
    1151    }
    1152
    1153    /**
    1154     * Tests whether the given entry is a descendant of this entry. Descendancy is determined by the name of the descendant starting with this entry's name.
    1155     *
    1156     * @param desc Entry to be checked as a descendent of this.
    1157     * @return True if entry is a descendant of this.
    1158     */
    1159    public boolean isDescendent(final TarArchiveEntry desc) {
    1160        return desc.getName().startsWith(getName());
    1161    }
    1162
    1163    /**
    1164     * Tests whether or not this entry represents a directory.
    1165     *
    1166     * @return True if this entry is a directory.
    1167     */
    1168    @Override
    1169    public boolean isDirectory() {
    1170        if (file != null) {
    1171            return Files.isDirectory(file, linkOptions);
    1172        }
    1173        if (linkFlag == LF_DIR) {
    1174            return true;
    1175        }
    1176        return !isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/");
    1177    }
    1178
    1179    /**
    1180     * Tests whether in case of an oldgnu sparse file if an extension sparse header follows.
    1181     *
    1182     * @return true if an extension oldgnu sparse header follows.
    1183     */
    1184    public boolean isExtended() {
    1185        return isExtended;
    1186    }
    1187
    1188    /**
    1189     * Tests whether this is a FIFO (pipe) entry.
    1190     *
    1191     * @since 1.2
    1192     * @return whether this is a FIFO entry
    1193     */
    1194    public boolean isFIFO() {
    1195        return linkFlag == LF_FIFO;
    1196    }
    1197
    1198    /**
    1199     * Tests whether this is a "normal file"
    1200     *
    1201     * @since 1.2
    1202     * @return whether this is a "normal file"
    1203     */
    1204    public boolean isFile() {
    1205        if (file != null) {
    1206            return Files.isRegularFile(file, linkOptions);
    1207        }
    1208        if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
    1209            return true;
    1210        }
    1211        return linkFlag != LF_DIR && !getName().endsWith("/");
    1212    }
    1213
    1214    /**
    1215     * Tests whether this is a Pax header.
    1216     *
    1217     * @return {@code true} if this is a Pax header.
    1218     *
    1219     * @since 1.1
    1220     */
    1221    public boolean isGlobalPaxHeader() {
    1222        return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
    1223    }
    1224
    1225    /**
    1226     * Tests whether this entry is a GNU long linkname block
    1227     *
    1228     * @return true if this is a long name extension provided by GNU tar
    1229     */
    1230    public boolean isGNULongLinkEntry() {
    1231        return linkFlag == LF_GNUTYPE_LONGLINK;
    1232    }
    1233
    1234    /**
    1235     * Tests whether this entry is a GNU long name block
    1236     *
    1237     * @return true if this is a long name extension provided by GNU tar
    1238     */
    1239    public boolean isGNULongNameEntry() {
    1240        return linkFlag == LF_GNUTYPE_LONGNAME;
    1241    }
    1242
    1243    /**
    1244     * Tests whether this entry is a GNU sparse block.
    1245     *
    1246     * @return true if this is a sparse extension provided by GNU tar
    1247     */
    1248    public boolean isGNUSparse() {
    1249        return isOldGNUSparse() || isPaxGNUSparse();
    1250    }
    1251
    1252    private boolean isInvalidPrefix(final byte[] header) {
    1253        // prefix[130] is guaranteed to be '\0' with XSTAR/XUSTAR
    1254        if (header[XSTAR_PREFIX_OFFSET + 130] != 0) {
    1255            // except when typeflag is 'M'
    1256            if (header[LF_OFFSET] != LF_MULTIVOLUME) {
    1257                return true;
    1258            }
    1259            // We come only here if we try to read in a GNU/xstar/xustar multivolume archive starting past volume #0
    1260            // As of 1.22, commons-compress does not support multivolume tar archives.
    1261            // If/when it does, this should work as intended.
    1262            if ((header[XSTAR_MULTIVOLUME_OFFSET] & 0x80) == 0 && header[XSTAR_MULTIVOLUME_OFFSET + 11] != ' ') {
    1263                return true;
    1264            }
    1265        }
    1266        return false;
    1267    }
    1268
    1269    private boolean isInvalidXtarTime(final byte[] buffer, final int offset, final int length) {
    1270        // If atime[0]...atime[10] or ctime[0]...ctime[10] is not a POSIX octal number it cannot be 'xstar'.
    1271        if ((buffer[offset] & 0x80) == 0) {
    1272            final int lastIndex = length - 1;
    1273            for (int i = 0; i < lastIndex; i++) {
    1274                final byte b = buffer[offset + i];
    1275                if (b < '0' || b > '7') {
    1276                    return true;
    1277                }
    1278            }
    1279            // Check for both POSIX compliant end of number characters if not using base 256
    1280            final byte b = buffer[offset + lastIndex];
    1281            if (b != ' ' && b != 0) {
    1282                return true;
    1283            }
    1284        }
    1285        return false;
    1286    }
    1287
    1288    /**
    1289     * Tests whether this is a link entry.
    1290     *
    1291     * @since 1.2
    1292     * @return whether this is a link entry
    1293     */
    1294    public boolean isLink() {
    1295        return linkFlag == LF_LINK;
    1296    }
    1297
    1298    /**
    1299     * Tests whether this entry is a GNU or star sparse block using the oldgnu format.
    1300     *
    1301     * @return true if this is a sparse extension provided by GNU tar or star
    1302     * @since 1.11
    1303     */
    1304    public boolean isOldGNUSparse() {
    1305        return linkFlag == LF_GNUTYPE_SPARSE;
    1306    }
    1307
    1308    /**
    1309     * Tests whether this entry is a sparse file with 1.X PAX Format or not
    1310     *
    1311     * @return True if this entry is a sparse file with 1.X PAX Format
    1312     * @since 1.20
    1313     */
    1314    public boolean isPaxGNU1XSparse() {
    1315        return paxGNU1XSparse;
    1316    }
    1317
    1318    /**
    1319     * Tests whether this entry is a GNU sparse block using one of the PAX formats.
    1320     *
    1321     * @return true if this is a sparse extension provided by GNU tar
    1322     * @since 1.11
    1323     */
    1324    public boolean isPaxGNUSparse() {
    1325        return paxGNUSparse;
    1326    }
    1327
    1328    /**
    1329     * Tests whether this is a Pax header.
    1330     *
    1331     * @return {@code true} if this is a Pax header.
    1332     *
    1333     * @since 1.1
    1334     */
    1335    public boolean isPaxHeader() {
    1336        return linkFlag == LF_PAX_EXTENDED_HEADER_LC || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
    1337    }
    1338
    1339    /**
    1340     * Tests whether this is a sparse entry.
    1341     *
    1342     * @return whether this is a sparse entry
    1343     * @since 1.11
    1344     */
    1345    public boolean isSparse() {
    1346        return isGNUSparse() || isStarSparse();
    1347    }
    1348
    1349    /**
    1350     * Tests whether this entry is a star sparse block using PAX headers.
    1351     *
    1352     * @return true if this is a sparse extension provided by star
    1353     * @since 1.11
    1354     */
    1355    public boolean isStarSparse() {
    1356        return starSparse;
    1357    }
    1358
    1359    /**
    1360     * {@inheritDoc}
    1361     *
    1362     * @since 1.21
    1363     */
    1364    @Override
    1365    public boolean isStreamContiguous() {
    1366        return true;
    1367    }
    1368
    1369    /**
    1370     * Tests whether this is a symbolic link entry.
    1371     *
    1372     * @since 1.2
    1373     * @return whether this is a symbolic link
    1374     */
    1375    public boolean isSymbolicLink() {
    1376        return linkFlag == LF_SYMLINK;
    1377    }
    1378
    1379    /**
    1380     * Tests whether the given header is in XSTAR / XUSTAR format.
    1381     *
    1382     * Use the same logic found in star version 1.6 in {@code header.c}, function {@code isxmagic(TCB *ptb)}.
    1383     */
    1384    private boolean isXstar(final Map<String, String> globalPaxHeaders, final byte[] header) {
    1385        // Check if this is XSTAR
    1386        if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET, XSTAR_MAGIC_LEN)) {
    1387            return true;
    1388        }
    1389        //
    1390        // If SCHILY.archtype is present in the global PAX header, we can use it to identify the type of archive.
    1391        //
    1392        // Possible values for XSTAR: - xustar: 'xstar' format without "tar" signature at header offset 508. - exustar: 'xustar' format variant that always
    1393        // includes x-headers and g-headers.
    1394        //
    1395        final String archType = globalPaxHeaders.get("SCHILY.archtype");
    1396        if (archType != null) {
    1397            return "xustar".equals(archType) || "exustar".equals(archType);
    1398        }
    1399        // Check if this is XUSTAR
    1400        if (isInvalidPrefix(header)) {
    1401            return false;
    1402        }
    1403        if (isInvalidXtarTime(header, XSTAR_ATIME_OFFSET, ATIMELEN_XSTAR)) {
    1404            return false;
    1405        }
    1406        if (isInvalidXtarTime(header, XSTAR_CTIME_OFFSET, CTIMELEN_XSTAR)) {
    1407            return false;
    1408        }
    1409        return true;
    1410    }
    1411
    1412    private long parseOctalOrBinary(final byte[] header, final int offset, final int length, final boolean lenient) {
    1413        if (lenient) {
    1414            try {
    1415                return TarUtils.parseOctalOrBinary(header, offset, length);
    1416            } catch (final IllegalArgumentException ex) { // NOSONAR
    1417                return UNKNOWN;
    1418            }
    1419        }
    1420        return TarUtils.parseOctalOrBinary(header, offset, length);
    1421    }
    1422
    1423    /**
    1424     * Parses an entry's header information from a header buffer.
    1425     *
    1426     * @param header The tar entry header buffer to get information from.
    1427     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    1428     */
    1429    public void parseTarHeader(final byte[] header) {
    1430        try {
    1431            parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
    1432        } catch (final IOException ex) { // NOSONAR
    1433            try {
    1434                parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true, false);
    1435            } catch (final IOException ex2) {
    1436                // not really possible
    1437                throw new UncheckedIOException(ex2); // NOSONAR
    1438            }
    1439        }
    1440    }
    1441
    1442    /**
    1443     * Parse an entry's header information from a header buffer.
    1444     *
    1445     * @param header   The tar entry header buffer to get information from.
    1446     * @param encoding encoding to use for file names
    1447     * @since 1.4
    1448     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    1449     * @throws IOException              on error
    1450     */
    1451    public void parseTarHeader(final byte[] header, final ZipEncoding encoding) throws IOException {
    1452        parseTarHeader(header, encoding, false, false);
    1453    }
    1454
    1455    private void parseTarHeader(final byte[] header, final ZipEncoding encoding, final boolean oldStyle, final boolean lenient) throws IOException {
    1456        parseTarHeader(Collections.emptyMap(), header, encoding, oldStyle, lenient);
    1457    }
    1458
    1459    private void parseTarHeader(final Map<String, String> globalPaxHeaders, final byte[] header, final ZipEncoding encoding, final boolean oldStyle,
    1460            final boolean lenient) throws IOException {
    1461        try {
    1462            parseTarHeaderUnwrapped(globalPaxHeaders, header, encoding, oldStyle, lenient);
    1463        } catch (final IllegalArgumentException ex) {
    1464            throw new IOException("Corrupted TAR archive.", ex);
    1465        }
    1466    }
    1467
    1468    private void parseTarHeaderUnwrapped(final Map<String, String> globalPaxHeaders, final byte[] header, final ZipEncoding encoding, final boolean oldStyle,
    1469            final boolean lenient) throws IOException {
    1470        int offset = 0;
    1471        name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) : TarUtils.parseName(header, offset, NAMELEN, encoding);
    1472        offset += NAMELEN;
    1473        mode = (int) parseOctalOrBinary(header, offset, MODELEN, lenient);
    1474        offset += MODELEN;
    1475        userId = (int) parseOctalOrBinary(header, offset, UIDLEN, lenient);
    1476        offset += UIDLEN;
    1477        groupId = (int) parseOctalOrBinary(header, offset, GIDLEN, lenient);
    1478        offset += GIDLEN;
    1479        size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
    1480        if (size < 0) {
    1481            throw new IOException("broken archive, entry with negative size");
    1482        }
    1483        offset += SIZELEN;
    1484        mTime = TimeUtils.unixTimeToFileTime(parseOctalOrBinary(header, offset, MODTIMELEN, lenient));
    1485        offset += MODTIMELEN;
    1486        checkSumOK = TarUtils.verifyCheckSum(header);
    1487        offset += CHKSUMLEN;
    1488        linkFlag = header[offset++];
    1489        linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) : TarUtils.parseName(header, offset, NAMELEN, encoding);
    1490        offset += NAMELEN;
    1491        magic = TarUtils.parseName(header, offset, MAGICLEN);
    1492        offset += MAGICLEN;
    1493        version = TarUtils.parseName(header, offset, VERSIONLEN);
    1494        offset += VERSIONLEN;
    1495        userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN) : TarUtils.parseName(header, offset, UNAMELEN, encoding);
    1496        offset += UNAMELEN;
    1497        groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN) : TarUtils.parseName(header, offset, GNAMELEN, encoding);
    1498        offset += GNAMELEN;
    1499        if (linkFlag == LF_CHR || linkFlag == LF_BLK) {
    1500            devMajor = (int) parseOctalOrBinary(header, offset, DEVLEN, lenient);
    1501            offset += DEVLEN;
    1502            devMinor = (int) parseOctalOrBinary(header, offset, DEVLEN, lenient);
    1503            offset += DEVLEN;
    1504        } else {
    1505            offset += 2 * DEVLEN;
    1506        }
    1507        final int type = evaluateType(globalPaxHeaders, header);
    1508        switch (type) {
    1509        case FORMAT_OLDGNU: {
    1510            aTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, ATIMELEN_GNU, lenient));
    1511            offset += ATIMELEN_GNU;
    1512            cTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, CTIMELEN_GNU, lenient));
    1513            offset += CTIMELEN_GNU;
    1514            offset += OFFSETLEN_GNU;
    1515            offset += LONGNAMESLEN_GNU;
    1516            offset += PAD2LEN_GNU;
    1517            sparseHeaders = new ArrayList<>(TarUtils.readSparseStructs(header, offset, SPARSE_HEADERS_IN_OLDGNU_HEADER));
    1518            offset += SPARSELEN_GNU;
    1519            isExtended = TarUtils.parseBoolean(header, offset);
    1520            offset += ISEXTENDEDLEN_GNU;
    1521            realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
    1522            offset += REALSIZELEN_GNU; // NOSONAR - assignment as documentation
    1523            break;
    1524        }
    1525        case FORMAT_XSTAR: {
    1526            final String xstarPrefix = oldStyle ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR)
    1527                    : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding);
    1528            offset += PREFIXLEN_XSTAR;
    1529            if (!xstarPrefix.isEmpty()) {
    1530                name = xstarPrefix + "/" + name;
    1531            }
    1532            aTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, ATIMELEN_XSTAR, lenient));
    1533            offset += ATIMELEN_XSTAR;
    1534            cTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, CTIMELEN_XSTAR, lenient));
    1535            offset += CTIMELEN_XSTAR; // NOSONAR - assignment as documentation
    1536            break;
    1537        }
    1538        case FORMAT_POSIX:
    1539        default: {
    1540            final String prefix = oldStyle ? TarUtils.parseName(header, offset, PREFIXLEN) : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
    1541            offset += PREFIXLEN; // NOSONAR - assignment as documentation
    1542            // SunOS tar -E does not add / to directory names, so fix
    1543            // up to be consistent
    1544            if (isDirectory() && !name.endsWith("/")) {
    1545                name += "/";
    1546            }
    1547            if (!prefix.isEmpty()) {
    1548                name = prefix + "/" + name;
    1549            }
    1550        }
    1551        }
    1552    }
    1553
    1554    /**
    1555     * Processes one pax header, using the entries extraPaxHeaders map as source for extra headers used when handling entries for sparse files.
    1556     *
    1557     * @param key
    1558     * @param val
    1559     * @since 1.15
    1560     */
    1561    private void processPaxHeader(final String key, final String val) throws IOException {
    1562        processPaxHeader(key, val, extraPaxHeaders);
    1563    }
    1564
    1565    /**
    1566     * Processes one pax header, using the supplied map as source for extra headers to be used when handling entries for sparse files
    1567     *
    1568     * @param key     the header name.
    1569     * @param val     the header value.
    1570     * @param headers map of headers used for dealing with sparse file.
    1571     * @throws NumberFormatException if encountered errors when parsing the numbers
    1572     * @since 1.15
    1573     */
    1574    private void processPaxHeader(final String key, final String val, final Map<String, String> headers) throws IOException {
    1575        /*
    1576         * The following headers are defined for Pax. charset: cannot use these without changing TarArchiveEntry fields mtime atime ctime
    1577         * LIBARCHIVE.creationtime comment gid, gname linkpath size uid,uname SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those
    1578         *
    1579         * GNU sparse files use additional members, we use GNU.sparse.size to detect the 0.0 and 0.1 versions and GNU.sparse.realsize for 1.0.
    1580         *
    1581         * star files use additional members of which we use SCHILY.filetype in order to detect star sparse files.
    1582         *
    1583         * If called from addExtraPaxHeader, these additional headers must be already present .
    1584         */
    1585        switch (key) {
    1586        case "path":
    1587            setName(val);
    1588            break;
    1589        case "linkpath":
    1590            setLinkName(val);
    1591            break;
    1592        case "gid":
    1593            setGroupId(ParsingUtils.parseLongValue(val));
    1594            break;
    1595        case "gname":
    1596            setGroupName(val);
    1597            break;
    1598        case "uid":
    1599            setUserId(ParsingUtils.parseLongValue(val));
    1600            break;
    1601        case "uname":
    1602            setUserName(val);
    1603            break;
    1604        case "size":
    1605            final long size = ParsingUtils.parseLongValue(val);
    1606            if (size < 0) {
    1607                throw new IOException("Corrupted TAR archive. Entry size is negative");
    1608            }
    1609            setSize(size);
    1610            break;
    1611        case "mtime":
    1612            setLastModifiedTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
    1613            break;
    1614        case "atime":
    1615            setLastAccessTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
    1616            break;
    1617        case "ctime":
    1618            setStatusChangeTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
    1619            break;
    1620        case "LIBARCHIVE.creationtime":
    1621            setCreationTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
    1622            break;
    1623        case "SCHILY.devminor":
    1624            final int devMinor = ParsingUtils.parseIntValue(val);
    1625            if (devMinor < 0) {
    1626                throw new IOException("Corrupted TAR archive. Dev-Minor is negative");
    1627            }
    1628            setDevMinor(devMinor);
    1629            break;
    1630        case "SCHILY.devmajor":
    1631            final int devMajor = ParsingUtils.parseIntValue(val);
    1632            if (devMajor < 0) {
    1633                throw new IOException("Corrupted TAR archive. Dev-Major is negative");
    1634            }
    1635            setDevMajor(devMajor);
    1636            break;
    1637        case TarGnuSparseKeys.SIZE:
    1638            fillGNUSparse0xData(headers);
    1639            break;
    1640        case TarGnuSparseKeys.REALSIZE:
    1641            fillGNUSparse1xData(headers);
    1642            break;
    1643        case "SCHILY.filetype":
    1644            if ("sparse".equals(val)) {
    1645                fillStarSparseData(headers);
    1646            }
    1647            break;
    1648        default:
    1649            extraPaxHeaders.put(key, val);
    1650        }
    1651    }
    1652
    1653    private void readFileMode(final Path file, final String normalizedName, final LinkOption... options) throws IOException {
    1654        if (Files.isDirectory(file, options)) {
    1655            this.mode = DEFAULT_DIR_MODE;
    1656            this.linkFlag = LF_DIR;
    1657
    1658            final int nameLength = normalizedName.length();
    1659            if (nameLength == 0 || normalizedName.charAt(nameLength - 1) != '/') {
    1660                this.name = normalizedName + "/";
    1661            } else {
    1662                this.name = normalizedName;
    1663            }
    1664        } else {
    1665            this.mode = DEFAULT_FILE_MODE;
    1666            this.linkFlag = LF_NORMAL;
    1667            this.name = normalizedName;
    1668            this.size = Files.size(file);
    1669        }
    1670    }
    1671
    1672    private void readOsSpecificProperties(final Path file, final LinkOption... options) throws IOException {
    1673        final Set<String> availableAttributeViews = file.getFileSystem().supportedFileAttributeViews();
    1674        if (availableAttributeViews.contains("posix")) {
    1675            final PosixFileAttributes posixFileAttributes = Files.readAttributes(file, PosixFileAttributes.class, options);
    1676            setLastModifiedTime(posixFileAttributes.lastModifiedTime());
    1677            setCreationTime(posixFileAttributes.creationTime());
    1678            setLastAccessTime(posixFileAttributes.lastAccessTime());
    1679            this.userName = posixFileAttributes.owner().getName();
    1680            this.groupName = posixFileAttributes.group().getName();
    1681            if (availableAttributeViews.contains("unix")) {
    1682                this.userId = ((Number) Files.getAttribute(file, "unix:uid", options)).longValue();
    1683                this.groupId = ((Number) Files.getAttribute(file, "unix:gid", options)).longValue();
    1684                try {
    1685                    setStatusChangeTime((FileTime) Files.getAttribute(file, "unix:ctime", options));
    1686                } catch (final IllegalArgumentException ex) { // NOSONAR
    1687                    // ctime is not supported
    1688                }
    1689            }
    1690        } else {
    1691            if (availableAttributeViews.contains("dos")) {
    1692                final DosFileAttributes dosFileAttributes = Files.readAttributes(file, DosFileAttributes.class, options);
    1693                setLastModifiedTime(dosFileAttributes.lastModifiedTime());
    1694                setCreationTime(dosFileAttributes.creationTime());
    1695                setLastAccessTime(dosFileAttributes.lastAccessTime());
    1696            } else {
    1697                final BasicFileAttributes basicFileAttributes = Files.readAttributes(file, BasicFileAttributes.class, options);
    1698                setLastModifiedTime(basicFileAttributes.lastModifiedTime());
    1699                setCreationTime(basicFileAttributes.creationTime());
    1700                setLastAccessTime(basicFileAttributes.lastAccessTime());
    1701            }
    1702            this.userName = Files.getOwner(file, options).getName();
    1703        }
    1704    }
    1705
    1706    /**
    1707     * Sets this entry's creation time.
    1708     *
    1709     * @param time This entry's new creation time.
    1710     * @since 1.22
    1711     */
    1712    public void setCreationTime(final FileTime time) {
    1713        birthTime = time;
    1714    }
    1715
    1716    /**
    1717     * Sets the offset of the data for the tar entry.
    1718     *
    1719     * @param dataOffset the position of the data in the tar.
    1720     * @since 1.21
    1721     */
    1722    public void setDataOffset(final long dataOffset) {
    1723        if (dataOffset < 0) {
    1724            throw new IllegalArgumentException("The offset can not be smaller than 0");
    1725        }
    1726        this.dataOffset = dataOffset;
    1727    }
    1728
    1729    /**
    1730     * Sets this entry's major device number.
    1731     *
    1732     * @param devNo This entry's major device number.
    1733     * @throws IllegalArgumentException if the devNo is &lt; 0.
    1734     * @since 1.4
    1735     */
    1736    public void setDevMajor(final int devNo) {
    1737        if (devNo < 0) {
    1738            throw new IllegalArgumentException("Major device number is out of " + "range: " + devNo);
    1739        }
    1740        this.devMajor = devNo;
    1741    }
    1742
    1743    /**
    1744     * Sets this entry's minor device number.
    1745     *
    1746     * @param devNo This entry's minor device number.
    1747     * @throws IllegalArgumentException if the devNo is &lt; 0.
    1748     * @since 1.4
    1749     */
    1750    public void setDevMinor(final int devNo) {
    1751        if (devNo < 0) {
    1752            throw new IllegalArgumentException("Minor device number is out of " + "range: " + devNo);
    1753        }
    1754        this.devMinor = devNo;
    1755    }
    1756
    1757    /**
    1758     * Sets this entry's group id.
    1759     *
    1760     * @param groupId This entry's new group id.
    1761     */
    1762    public void setGroupId(final int groupId) {
    1763        setGroupId((long) groupId);
    1764    }
    1765
    1766    /**
    1767     * Sets this entry's group id.
    1768     *
    1769     * @since 1.10
    1770     * @param groupId This entry's new group id.
    1771     */
    1772    public void setGroupId(final long groupId) {
    1773        this.groupId = groupId;
    1774    }
    1775
    1776    /**
    1777     * Sets this entry's group name.
    1778     *
    1779     * @param groupName This entry's new group name.
    1780     */
    1781    public void setGroupName(final String groupName) {
    1782        this.groupName = groupName;
    1783    }
    1784
    1785    /**
    1786     * Convenience method to set this entry's group and user ids.
    1787     *
    1788     * @param userId  This entry's new user id.
    1789     * @param groupId This entry's new group id.
    1790     */
    1791    public void setIds(final int userId, final int groupId) {
    1792        setUserId(userId);
    1793        setGroupId(groupId);
    1794    }
    1795
    1796    /**
    1797     * Sets this entry's last access time.
    1798     *
    1799     * @param time This entry's new last access time.
    1800     * @since 1.22
    1801     */
    1802    public void setLastAccessTime(final FileTime time) {
    1803        aTime = time;
    1804    }
    1805
    1806    /**
    1807     * Sets this entry's modification time.
    1808     *
    1809     * @param time This entry's new modification time.
    1810     * @since 1.22
    1811     */
    1812    public void setLastModifiedTime(final FileTime time) {
    1813        mTime = Objects.requireNonNull(time, "Time must not be null");
    1814    }
    1815
    1816    /**
    1817     * Sets this entry's link name.
    1818     *
    1819     * @param link the link name to use.
    1820     *
    1821     * @since 1.1
    1822     */
    1823    public void setLinkName(final String link) {
    1824        this.linkName = link;
    1825    }
    1826
    1827    /**
    1828     * Sets the mode for this entry
    1829     *
    1830     * @param mode the mode for this entry
    1831     */
    1832    public void setMode(final int mode) {
    1833        this.mode = mode;
    1834    }
    1835
    1836    /**
    1837     * Sets this entry's modification time.
    1838     *
    1839     * @param time This entry's new modification time.
    1840     * @see TarArchiveEntry#setLastModifiedTime(FileTime)
    1841     */
    1842    public void setModTime(final Date time) {
    1843        setLastModifiedTime(FileTimes.toFileTime(time));
    1844    }
    1845
    1846    /**
    1847     * Sets this entry's modification time.
    1848     *
    1849     * @param time This entry's new modification time.
    1850     * @since 1.21
    1851     * @see TarArchiveEntry#setLastModifiedTime(FileTime)
    1852     */
    1853    public void setModTime(final FileTime time) {
    1854        setLastModifiedTime(time);
    1855    }
    1856
    1857    /**
    1858     * Sets this entry's modification time. The parameter passed to this method is in "Java time".
    1859     *
    1860     * @param time This entry's new modification time.
    1861     * @see TarArchiveEntry#setLastModifiedTime(FileTime)
    1862     */
    1863    public void setModTime(final long time) {
    1864        setLastModifiedTime(FileTime.fromMillis(time));
    1865    }
    1866
    1867    /**
    1868     * Sets this entry's name.
    1869     *
    1870     * @param name This entry's new name.
    1871     */
    1872    public void setName(final String name) {
    1873        this.name = normalizeFileName(name, this.preserveAbsolutePath);
    1874    }
    1875
    1876    /**
    1877     * Convenience method to set this entry's group and user names.
    1878     *
    1879     * @param userName  This entry's new user name.
    1880     * @param groupName This entry's new group name.
    1881     */
    1882    public void setNames(final String userName, final String groupName) {
    1883        setUserName(userName);
    1884        setGroupName(groupName);
    1885    }
    1886
    1887    /**
    1888     * Sets this entry's file size.
    1889     *
    1890     * @param size This entry's new file size.
    1891     * @throws IllegalArgumentException if the size is &lt; 0.
    1892     */
    1893    public void setSize(final long size) {
    1894        if (size < 0) {
    1895            throw new IllegalArgumentException("Size is out of range: " + size);
    1896        }
    1897        this.size = size;
    1898    }
    1899
    1900    /**
    1901     * Sets this entry's sparse headers
    1902     *
    1903     * @param sparseHeaders The new sparse headers
    1904     * @since 1.20
    1905     */
    1906    public void setSparseHeaders(final List<TarArchiveStructSparse> sparseHeaders) {
    1907        this.sparseHeaders = sparseHeaders;
    1908    }
    1909
    1910    /**
    1911     * Sets this entry's status change time.
    1912     *
    1913     * @param time This entry's new status change time.
    1914     * @since 1.22
    1915     */
    1916    public void setStatusChangeTime(final FileTime time) {
    1917        cTime = time;
    1918    }
    1919
    1920    /**
    1921     * Sets this entry's user id.
    1922     *
    1923     * @param userId This entry's new user id.
    1924     */
    1925    public void setUserId(final int userId) {
    1926        setUserId((long) userId);
    1927    }
    1928
    1929    /**
    1930     * Sets this entry's user id.
    1931     *
    1932     * @param userId This entry's new user id.
    1933     * @since 1.10
    1934     */
    1935    public void setUserId(final long userId) {
    1936        this.userId = userId;
    1937    }
    1938
    1939    /**
    1940     * Sets this entry's user name.
    1941     *
    1942     * @param userName This entry's new user name.
    1943     */
    1944    public void setUserName(final String userName) {
    1945        this.userName = userName;
    1946    }
    1947
    1948    /**
    1949     * Update the entry using a map of pax headers.
    1950     *
    1951     * @param headers
    1952     * @since 1.15
    1953     */
    1954    void updateEntryFromPaxHeaders(final Map<String, String> headers) throws IOException {
    1955        for (final Map.Entry<String, String> ent : headers.entrySet()) {
    1956            processPaxHeader(ent.getKey(), ent.getValue(), headers);
    1957        }
    1958    }
    1959
    1960    /**
    1961     * Writes an entry's header information to a header buffer.
    1962     * <p>
    1963     * This method does not use the star/GNU tar/BSD tar extensions.
    1964     * </p>
    1965     *
    1966     * @param outbuf The tar entry header buffer to fill in.
    1967     */
    1968    public void writeEntryHeader(final byte[] outbuf) {
    1969        try {
    1970            writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
    1971        } catch (final IOException ex) { // NOSONAR
    1972            try {
    1973                writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
    1974            } catch (final IOException ex2) {
    1975                // impossible
    1976                throw new UncheckedIOException(ex2); // NOSONAR
    1977            }
    1978        }
    1979    }
    1980
    1981    /**
    1982     * Writes an entry's header information to a header buffer.
    1983     *
    1984     * @param outbuf   The tar entry header buffer to fill in.
    1985     * @param encoding encoding to use when writing the file name.
    1986     * @param starMode whether to use the star/GNU tar/BSD tar extension for numeric fields if their value doesn't fit in the maximum size of standard tar
    1987     *                 archives
    1988     * @since 1.4
    1989     * @throws IOException on error
    1990     */
    1991    public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding, final boolean starMode) throws IOException {
    1992        int offset = 0;
    1993        offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN, encoding);
    1994        offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
    1995        offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN, starMode);
    1996        offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN, starMode);
    1997        offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
    1998        offset = writeEntryHeaderField(TimeUtils.toUnixTime(mTime), outbuf, offset, MODTIMELEN, starMode);
    1999        final int csOffset = offset;
    2000        offset = fill((byte) ' ', offset, outbuf, CHKSUMLEN);
    2001        outbuf[offset++] = linkFlag;
    2002        offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN, encoding);
    2003        offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
    2004        offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
    2005        offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN, encoding);
    2006        offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN, encoding);
    2007        offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN, starMode);
    2008        offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN, starMode);
    2009        if (starMode) {
    2010            // skip prefix
    2011            offset = fill(0, offset, outbuf, PREFIXLEN_XSTAR);
    2012            offset = writeEntryHeaderOptionalTimeField(aTime, offset, outbuf, ATIMELEN_XSTAR);
    2013            offset = writeEntryHeaderOptionalTimeField(cTime, offset, outbuf, CTIMELEN_XSTAR);
    2014            // 8-byte fill
    2015            offset = fill(0, offset, outbuf, 8);
    2016            // Do not write MAGIC_XSTAR because it causes issues with some TAR tools
    2017            // This makes it effectively XUSTAR, which guarantees compatibility with USTAR
    2018            offset = fill(0, offset, outbuf, XSTAR_MAGIC_LEN);
    2019        }
    2020        offset = fill(0, offset, outbuf, outbuf.length - offset); // NOSONAR - assignment as documentation
    2021        final long chk = TarUtils.computeCheckSum(outbuf);
    2022        TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
    2023    }
    2024
    2025    private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset, final int length, final boolean starMode) {
    2026        if (!starMode && (value < 0 || value >= 1L << 3 * (length - 1))) {
    2027            // value doesn't fit into field when written as octal
    2028            // number, will be written to PAX header or causes an
    2029            // error
    2030            return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
    2031        }
    2032        return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset, length);
    2033    }
    2034
    2035    private int writeEntryHeaderOptionalTimeField(final FileTime time, int offset, final byte[] outbuf, final int fieldLength) {
    2036        if (time != null) {
    2037            offset = writeEntryHeaderField(TimeUtils.toUnixTime(time), outbuf, offset, fieldLength, true);
    2038        } else {
    2039            offset = fill(0, offset, outbuf, fieldLength);
    2040        }
    2041        return offset;
    2042    }
    2043
    2044}