    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
    395        if (user.length() > MAX_NAMELEN) {
    396            user = user.substring(0, MAX_NAMELEN);
    397        }
    398
    399        this.userName = user;
    400        this.file = null;
    401        this.linkOptions = IOUtils.EMPTY_LINK_OPTIONS;
    402        this.preserveAbsolutePath = preserveAbsolutePath;
    403    }
    404
    405    /**
    406     * Constructs an entry from an archive's header bytes. File is set to null.
    407     *
    408     * @param headerBuf The header bytes from a tar archive entry.
    409     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    410     */
    411    public TarArchiveEntry(final byte[] headerBuf) {
    412        this(false);
    413        parseTarHeader(headerBuf);
    414    }
    415
    416    /**
    417     * Constructs an entry from an archive's header bytes. File is set to null.
    418     *
    419     * @param headerBuf The header bytes from a tar archive entry.
    420     * @param encoding  encoding to use for file names
    421     * @since 1.4
    422     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    423     * @throws IOException              on error
    424     */
    425    public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding) throws IOException {
    426        this(headerBuf, encoding, false);
    427    }
    428
    429    /**
    430     * Constructs an entry from an archive's header bytes. File is set to null.
    431     *
    432     * @param headerBuf The header bytes from a tar archive entry.
    433     * @param encoding  encoding to use for file names
    434     * @param lenient   when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    435     *                  {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
    436     * @since 1.19
    437     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    438     * @throws IOException              on error
    439     */
    440    public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient) throws IOException {
    441        this(Collections.emptyMap(), headerBuf, encoding, lenient);
    442    }
    443
    444    /**
    445     * Constructs an entry from an archive's header bytes for random access tar. File is set to null.
    446     *
    447     * @param headerBuf  the header bytes from a tar archive entry.
    448     * @param encoding   encoding to use for file names.
    449     * @param lenient    when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    450     *                   {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
    451     * @param dataOffset position of the entry data in the random access file.
    452     * @since 1.21
    453     * @throws IllegalArgumentException if any of the numeric fields have an invalid format.
    454     * @throws IOException              on error.
    455     */
    456    public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient, final long dataOffset) throws IOException {
    457        this(headerBuf, encoding, lenient);
    458        setDataOffset(dataOffset);
    459    }
    460
    461    /**
    462     * 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
    463     * file path.
    464     * <p>
    465     * 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
    466     * drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
    467     * </p>
    468     * <p>
    469     * 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
    470     * ignored. If handling those exceptions is needed consider switching to the path constructors.
    471     * </p>
    472     *
    473     * @param file The file that the entry represents.
    474     */
    475    public TarArchiveEntry(final File file) {
    476        this(file, file.getPath());
    477    }
    478
    479    /**
    480     * Constructs an entry for a file. File is set to file, and the header is constructed from information from the file.
    481     * <p>
    482     * 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
    483     * Windows drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
    484     * </p>
    485     * <p>
    486     * 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
    487     * ignored. If handling those exceptions is needed consider switching to the path constructors.
    488     * </p>
    489     *
    490     * @param file     The file that the entry represents.
    491     * @param fileName the name to be used for the entry.
    492     */
    493    public TarArchiveEntry(final File file, final String fileName) {
    494        final String normalizedName = normalizeFileName(fileName, false);
    495        this.file = file.toPath();
    496        this.linkOptions = IOUtils.EMPTY_LINK_OPTIONS;
    497
    498        try {
    499            readFileMode(this.file, normalizedName);
    500        } catch (final IOException e) {
    501            // Ignore exceptions from NIO for backwards compatibility
    502            // Fallback to get size of file if it's no directory to the old file api
    503            if (!file.isDirectory()) {
    504                this.size = file.length();
    505            }
    506        }
    507
    508        this.userName = "";
    509        try {
    510            readOsSpecificProperties(this.file);
    511        } catch (final IOException e) {
    512            // Ignore exceptions from NIO for backwards compatibility
    513            // Fallback to get the last modified date of the file from the old file api
    514            this.mTime = FileTime.fromMillis(file.lastModified());
    515        }
    516        preserveAbsolutePath = false;
    517    }
    518
    519    /**
    520     * Constructs an entry from an archive's header bytes. File is set to null.
    521     *
    522     * @param globalPaxHeaders the parsed global PAX headers, or null if this is the first one.
    523     * @param headerBuf        The header bytes from a tar archive entry.
    524     * @param encoding         encoding to use for file names
    525     * @param lenient          when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    526     *                         {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
    527     * @since 1.22
    528     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    529     * @throws IOException              on error
    530     */
    531    public TarArchiveEntry(final Map<String, String> globalPaxHeaders, final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient)
    532            throws IOException {
    533        this(false);
    534        parseTarHeader(globalPaxHeaders, headerBuf, encoding, false, lenient);
    535    }
    536
    537    /**
    538     * Constructs an entry from an archive's header bytes for random access tar. File is set to null.
    539     *
    540     * @param globalPaxHeaders the parsed global PAX headers, or null if this is the first one.
    541     * @param headerBuf        the header bytes from a tar archive entry.
    542     * @param encoding         encoding to use for file names.
    543     * @param lenient          when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
    544     *                         {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
    545     * @param dataOffset       position of the entry data in the random access file.
    546     * @since 1.22
    547     * @throws IllegalArgumentException if any of the numeric fields have an invalid format.
    548     * @throws IOException              on error.
    549     */
    550    public TarArchiveEntry(final Map<String, String> globalPaxHeaders, final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient,
    551            final long dataOffset) throws IOException {
    552        this(globalPaxHeaders, headerBuf, encoding, lenient);
    553        setDataOffset(dataOffset);
    554    }
    555
    556    /**
    557     * 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
    558     * file path.
    559     * <p>
    560     * 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
    561     * drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
    562     * </p>
    563     *
    564     * @param file The file that the entry represents.
    565     * @throws IOException if an I/O error occurs
    566     * @since 1.21
    567     */
    568    public TarArchiveEntry(final Path file) throws IOException {
    569        this(file, file.toString());
    570    }
    571
    572    /**
    573     * Constructs an entry for a file. File is set to file, and the header is constructed from information from the file.
    574     * <p>
    575     * 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
    576     * Windows drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
    577     * </p>
    578     *
    579     * @param file        The file that the entry represents.
    580     * @param fileName    the name to be used for the entry.
    581     * @param linkOptions options indicating how symbolic links are handled.
    582     * @throws IOException if an I/O error occurs
    583     * @since 1.21
    584     */
    585    public TarArchiveEntry(final Path file, final String fileName, final LinkOption... linkOptions) throws IOException {
    586        final String normalizedName = normalizeFileName(fileName, false);
    587        this.file = file;
    588        this.linkOptions = linkOptions == null ? IOUtils.EMPTY_LINK_OPTIONS : linkOptions;
    589
    590        readFileMode(file, normalizedName, linkOptions);
    591
    592        this.userName = "";
    593        readOsSpecificProperties(file);
    594        preserveAbsolutePath = false;
    595    }
    596
    597    /**
    598     * Constructs an entry with only a name. This allows the programmer to construct the entry's header "by hand". File is set to null.
    599     * <p>
    600     * 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
    601     * Windows drive letters stripped.
    602     * </p>
    603     *
    604     * @param name the entry name
    605     */
    606    public TarArchiveEntry(final String name) {
    607        this(name, false);
    608    }
    609
    610    /**
    611     * Constructs an entry with only a name. This allows the programmer to construct the entry's header "by hand". File is set to null.
    612     * <p>
    613     * 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
    614     * letters are stripped if {@code preserveAbsolutePath} is {@code false}.
    615     * </p>
    616     *
    617     * @param name                 the entry name
    618     * @param preserveAbsolutePath whether to allow leading slashes or drive letters in the name.
    619     *
    620     * @since 1.1
    621     */
    622    public TarArchiveEntry(String name, final boolean preserveAbsolutePath) {
    623        this(preserveAbsolutePath);
    624
    625        name = normalizeFileName(name, preserveAbsolutePath);
    626        final boolean isDir = name.endsWith("/");
    627
    628        this.name = name;
    629        this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
    630        this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
    631        this.mTime = FileTime.from(Instant.now());
    632        this.userName = "";
    633    }
    634
    635    /**
    636     * Constructs an entry with a name and a link flag.
    637     * <p>
    638     * 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
    639     * Windows drive letters stripped.
    640     * </p>
    641     *
    642     * @param name     the entry name
    643     * @param linkFlag the entry link flag.
    644     */
    645    public TarArchiveEntry(final String name, final byte linkFlag) {
    646        this(name, linkFlag, false);
    647    }
    648
    649    /**
    650     * Constructs an entry with a name and a link flag.
    651     * <p>
    652     * 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
    653     * letters are stripped if {@code preserveAbsolutePath} is {@code false}.
    654     * </p>
    655     *
    656     * @param name                 the entry name
    657     * @param linkFlag             the entry link flag.
    658     * @param preserveAbsolutePath whether to allow leading slashes or drive letters in the name.
    659     *
    660     * @since 1.5
    661     */
    662    public TarArchiveEntry(final String name, final byte linkFlag, final boolean preserveAbsolutePath) {
    663        this(name, preserveAbsolutePath);
    664        this.linkFlag = linkFlag;
    665        if (linkFlag == LF_GNUTYPE_LONGNAME) {
    666            magic = MAGIC_GNU;
    667            version = VERSION_GNU_SPACE;
    668        }
    669    }
    670
    671    /**
    672     * 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
    673     * to the extraPaxHeaders Map
    674     *
    675     * @param name  The full name of the header to set.
    676     * @param value value of header.
    677     * @since 1.15
    678     */
    679    public void addPaxHeader(final String name, final String value) {
    680        try {
    681            processPaxHeader(name, value);
    682        } catch (final IOException ex) {
    683            throw new IllegalArgumentException("Invalid input", ex);
    684        }
    685    }
    686
    687    /**
    688     * Clears all extra PAX headers.
    689     *
    690     * @since 1.15
    691     */
    692    public void clearExtraPaxHeaders() {
    693        extraPaxHeaders.clear();
    694    }
    695
    696    /**
    697     * Determine if the two entries are equal. Equality is determined by the header names being equal.
    698     *
    699     * @param it Entry to be checked for equality.
    700     * @return True if the entries are equal.
    701     */
    702    @Override
    703    public boolean equals(final Object it) {
    704        if (it == null || getClass() != it.getClass()) {
    705            return false;
    706        }
    707        return equals((TarArchiveEntry) it);
    708    }
    709
    710    /**
    711     * Determine if the two entries are equal. Equality is determined by the header names being equal.
    712     *
    713     * @param it Entry to be checked for equality.
    714     * @return True if the entries are equal.
    715     */
    716    public boolean equals(final TarArchiveEntry it) {
    717        return it != null && getName().equals(it.getName());
    718    }
    719
    720    /**
    721     * Evaluates an entry's header format from a header buffer.
    722     *
    723     * @param header The tar entry header buffer to evaluate the format for.
    724     * @return format type
    725     */
    726    private int evaluateType(final Map<String, String> globalPaxHeaders, final byte[] header) {
    727        if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) {
    728            return FORMAT_OLDGNU;
    729        }
    730        if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) {
    731            if (isXstar(globalPaxHeaders, header)) {
    732                return FORMAT_XSTAR;
    733            }
    734            return FORMAT_POSIX;
    735        }
    736        return 0;
    737    }
    738
    739    private int fill(final byte value, final int offset, final byte[] outbuf, final int length) {
    740        for (int i = 0; i < length; i++) {
    741            outbuf[offset + i] = value;
    742        }
    743        return offset + length;
    744    }
    745
    746    private int fill(final int value, final int offset, final byte[] outbuf, final int length) {
    747        return fill((byte) value, offset, outbuf, length);
    748    }
    749
    750    void fillGNUSparse0xData(final Map<String, String> headers) throws IOException {
    751        paxGNUSparse = true;
    752        realSize = ParsingUtils.parseIntValue(headers.get(TarGnuSparseKeys.SIZE));
    753        if (headers.containsKey(TarGnuSparseKeys.NAME)) {
    754            // version 0.1
    755            name = headers.get(TarGnuSparseKeys.NAME);
    756        }
    757    }
    758
    759    void fillGNUSparse1xData(final Map<String, String> headers) throws IOException {
    760        paxGNUSparse = true;
    761        paxGNU1XSparse = true;
    762        if (headers.containsKey(TarGnuSparseKeys.NAME)) {
    763            name = headers.get(TarGnuSparseKeys.NAME);
    764        }
    765        if (headers.containsKey(TarGnuSparseKeys.REALSIZE)) {
    766            realSize = ParsingUtils.parseIntValue(headers.get(TarGnuSparseKeys.REALSIZE));
    767        }
    768    }
    769
    770    void fillStarSparseData(final Map<String, String> headers) throws IOException {
    771        starSparse = true;
    772        if (headers.containsKey("SCHILY.realsize")) {
    773            realSize = ParsingUtils.parseLongValue(headers.get("SCHILY.realsize"));
    774        }
    775    }
    776
    777    /**
    778     * Gets this entry's creation time.
    779     *
    780     * @since 1.22
    781     * @return This entry's computed creation time.
    782     */
    783    public FileTime getCreationTime() {
    784        return birthTime;
    785    }
    786
    787    /**
    788     * {@inheritDoc}
    789     *
    790     * @since 1.21
    791     */
    792    @Override
    793    public long getDataOffset() {
    794        return dataOffset;
    795    }
    796
    797    /**
    798     * Gets this entry's major device number.
    799     *
    800     * @return This entry's major device number.
    801     * @since 1.4
    802     */
    803    public int getDevMajor() {
    804        return devMajor;
    805    }
    806
    807    /**
    808     * Gets this entry's minor device number.
    809     *
    810     * @return This entry's minor device number.
    811     * @since 1.4
    812     */
    813    public int getDevMinor() {
    814        return devMinor;
    815    }
    816
    817    /**
    818     * If this entry represents a file, and the file is a directory, return an array of TarEntries for this entry's children.
    819     * <p>
    820     * This method is only useful for entries created from a {@code
    821     * File} or {@code Path} but not for entries read from an archive.
    822     * </p>
    823     *
    824     * @return An array of TarEntry's for this entry's children.
    825     */
    826    public TarArchiveEntry[] getDirectoryEntries() {
    827        if (file == null || !isDirectory()) {
    828            return EMPTY_TAR_ARCHIVE_ENTRY_ARRAY;
    829        }
    830
    831        final List<TarArchiveEntry> entries = new ArrayList<>();
    832        try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(file)) {
    833            for (final Path p : dirStream) {
    834                entries.add(new TarArchiveEntry(p));
    835            }
    836        } catch (final IOException e) {
    837            return EMPTY_TAR_ARCHIVE_ENTRY_ARRAY;
    838        }
    839        return entries.toArray(EMPTY_TAR_ARCHIVE_ENTRY_ARRAY);
    840    }
    841
    842    /**
    843     * Gets named extra PAX header
    844     *
    845     * @param name The full name of an extended PAX header to retrieve
    846     * @return The value of the header, if any.
    847     * @since 1.15
    848     */
    849    public String getExtraPaxHeader(final String name) {
    850        return extraPaxHeaders.get(name);
    851    }
    852
    853    /**
    854     * Gets extra PAX Headers
    855     *
    856     * @return read-only map containing any extra PAX Headers
    857     * @since 1.15
    858     */
    859    public Map<String, String> getExtraPaxHeaders() {
    860        return Collections.unmodifiableMap(extraPaxHeaders);
    861    }
    862
    863    /**
    864     * Gets this entry's file.
    865     * <p>
    866     * This method is only useful for entries created from a {@code
    867     * File} or {@code Path} but not for entries read from an archive.
    868     * </p>
    869     *
    870     * @return this entry's file or null if the entry was not created from a file.
    871     */
    872    public File getFile() {
    873        if (file == null) {
    874            return null;
    875        }
    876        return file.toFile();
    877    }
    878
    879    /**
    880     * Gets this entry's group id.
    881     *
    882     * @return This entry's group id.
    883     * @deprecated use #getLongGroupId instead as group ids can be bigger than {@link Integer#MAX_VALUE}
    884     */
    885    @Deprecated
    886    public int getGroupId() {
    887        return (int) (groupId & 0xffffffff);
    888    }
    889
    890    /**
    891     * Gets this entry's group name.
    892     *
    893     * @return This entry's group name.
    894     */
    895    public String getGroupName() {
    896        return groupName;
    897    }
    898
    899    /**
    900     * Gets this entry's last access time.
    901     *
    902     * @since 1.22
    903     * @return This entry's last access time.
    904     */
    905    public FileTime getLastAccessTime() {
    906        return aTime;
    907    }
    908
    909    /**
    910     * Gets this entry's modification time. This is equivalent to {@link TarArchiveEntry#getLastModifiedTime()}, but precision is truncated to milliseconds.
    911     *
    912     * @return This entry's modification time.
    913     * @see TarArchiveEntry#getLastModifiedTime()
    914     */
    915    @Override
    916    public Date getLastModifiedDate() {
    917        return getModTime();
    918    }
    919
    920    /**
    921     * Gets this entry's modification time.
    922     *
    923     * @since 1.22
    924     * @return This entry's modification time.
    925     */
    926    public FileTime getLastModifiedTime() {
    927        return mTime;
    928    }
    929
    930    /**
    931     * Gets this entry's link flag.
    932     *
    933     * @return this entry's link flag.
    934     * @since 1.23
    935     */
    936    public byte getLinkFlag() {
    937        return this.linkFlag;
    938    }
    939
    940    /**
    941     * Gets this entry's link name.
    942     *
    943     * @return This entry's link name.
    944     */
    945    public String getLinkName() {
    946        return linkName;
    947    }
    948
    949    /**
    950     * Gets this entry's group id.
    951     *
    952     * @since 1.10
    953     * @return This entry's group id.
    954     */
    955    public long getLongGroupId() {
    956        return groupId;
    957    }
    958
    959    /**
    960     * Gets this entry's user id.
    961     *
    962     * @return This entry's user id.
    963     * @since 1.10
    964     */
    965    public long getLongUserId() {
    966        return userId;
    967    }
    968
    969    /**
    970     * Gets this entry's mode.
    971     *
    972     * @return This entry's mode.
    973     */
    974    public int getMode() {
    975        return mode;
    976    }
    977
    978    /**
    979     * Gets this entry's modification time. This is equivalent to {@link TarArchiveEntry#getLastModifiedTime()}, but precision is truncated to milliseconds.
    980     *
    981     * @return This entry's modification time.
    982     * @see TarArchiveEntry#getLastModifiedTime()
    983     */
    984    public Date getModTime() {
    985        final FileTime fileTime = mTime;
    986        return FileTimes.toDate(fileTime);
    987    }
    988
    989    /**
    990     * Gets this entry's name.
    991     * <p>
    992     * This method returns the raw name as it is stored inside of the archive.
    993     * </p>
    994     *
    995     * @return This entry's name.
    996     */
    997    @Override
    998    public String getName() {
    999        return name;
    1000    }
    1001
    1002    /**
    1003     * Gets this entry's sparse headers ordered by offset with all empty sparse sections at the start filtered out.
    1004     *
    1005     * @return immutable list of this entry's sparse headers, never null
    1006     * @since 1.21
    1007     * @throws IOException if the list of sparse headers contains blocks that overlap
    1008     */
    1009    public List<TarArchiveStructSparse> getOrderedSparseHeaders() throws IOException {
    1010        if (sparseHeaders == null || sparseHeaders.isEmpty()) {
    1011            return Collections.emptyList();
    1012        }
    1013        final List<TarArchiveStructSparse> orderedAndFiltered = sparseHeaders.stream().filter(s -> s.getOffset() > 0 || s.getNumbytes() > 0)
    1014                .sorted(Comparator.comparingLong(TarArchiveStructSparse::getOffset)).collect(Collectors.toList());
    1015
    1016        final int numberOfHeaders = orderedAndFiltered.size();
    1017        for (int i = 0; i < numberOfHeaders; i++) {
    1018            final TarArchiveStructSparse str = orderedAndFiltered.get(i);
    1019            if (i + 1 < numberOfHeaders && str.getOffset() + str.getNumbytes() > orderedAndFiltered.get(i + 1).getOffset()) {
    1020                throw new IOException("Corrupted TAR archive. Sparse blocks for " + getName() + " overlap each other.");
    1021            }
    1022            if (str.getOffset() + str.getNumbytes() < 0) {
    1023                // integer overflow?
    1024                throw new IOException("Unreadable TAR archive. Offset and numbytes for sparse block in " + getName() + " too large.");
    1025            }
    1026        }
    1027        if (!orderedAndFiltered.isEmpty()) {
    1028            final TarArchiveStructSparse last = orderedAndFiltered.get(numberOfHeaders - 1);
    1029            if (last.getOffset() + last.getNumbytes() > getRealSize()) {
    1030                throw new IOException("Corrupted TAR archive. Sparse block extends beyond real size of the entry");
    1031            }
    1032        }
    1033
    1034        return orderedAndFiltered;
    1035    }
    1036
    1037    /**
    1038     * Gets this entry's file.
    1039     * <p>
    1040     * This method is only useful for entries created from a {@code
    1041     * File} or {@code Path} but not for entries read from an archive.
    1042     * </p>
    1043     *
    1044     * @return this entry's file or null if the entry was not created from a file.
    1045     * @since 1.21
    1046     */
    1047    public Path getPath() {
    1048        return file;
    1049    }
    1050
    1051    /**
    1052     * Gets this entry's real file size in case of a sparse file.
    1053     * <p>
    1054     * This is the size a file would take on disk if the entry was expanded.
    1055     * </p>
    1056     * <p>
    1057     * If the file is not a sparse file, return size instead of realSize.
    1058     * </p>
    1059     *
    1060     * @return This entry's real file size, if the file is not a sparse file, return size instead of realSize.
    1061     */
    1062    public long getRealSize() {
    1063        if (!isSparse()) {
    1064            return getSize();
    1065        }
    1066        return realSize;
    1067    }
    1068
    1069    /**
    1070     * Gets this entry's file size.
    1071     * <p>
    1072     * 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
    1073     * when the entry represents a sparse file.
    1074     * </p>
    1075     *
    1076     * @return This entry's file size.
    1077     */
    1078    @Override
    1079    public long getSize() {
    1080        return size;
    1081    }
    1082
    1083    /**
    1084     * Gets this entry's sparse headers
    1085     *
    1086     * @return This entry's sparse headers
    1087     * @since 1.20
    1088     */
    1089    public List<TarArchiveStructSparse> getSparseHeaders() {
    1090        return sparseHeaders;
    1091    }
    1092
    1093    /**
    1094     * Gets this entry's status change time.
    1095     *
    1096     * @since 1.22
    1097     * @return This entry's status change time.
    1098     */
    1099    public FileTime getStatusChangeTime() {
    1100        return cTime;
    1101    }
    1102
    1103    /**
    1104     * Gets this entry's user id.
    1105     *
    1106     * @return This entry's user id.
    1107     * @deprecated use #getLongUserId instead as user ids can be bigger than {@link Integer#MAX_VALUE}
    1108     */
    1109    @Deprecated
    1110    public int getUserId() {
    1111        return (int) (userId & 0xffffffff);
    1112    }
    1113
    1114    /**
    1115     * Gets this entry's user name.
    1116     *
    1117     * @return This entry's user name.
    1118     */
    1119    public String getUserName() {
    1120        return userName;
    1121    }
    1122
    1123    /**
    1124     * Hash codes are based on entry names.
    1125     *
    1126     * @return the entry hash code
    1127     */
    1128    @Override
    1129    public int hashCode() {
    1130        return getName().hashCode();
    1131    }
    1132
    1133    /**
    1134     * Tests whether this is a block device entry.
    1135     *
    1136     * @since 1.2
    1137     * @return whether this is a block device
    1138     */
    1139    public boolean isBlockDevice() {
    1140        return linkFlag == LF_BLK;
    1141    }
    1142
    1143    /**
    1144     * Tests whether this is a character device entry.
    1145     *
    1146     * @since 1.2
    1147     * @return whether this is a character device
    1148     */
    1149    public boolean isCharacterDevice() {
    1150        return linkFlag == LF_CHR;
    1151    }
    1152
    1153    /**
    1154     * Tests whether this entry's checksum status.
    1155     *
    1156     * @return if the header checksum is reasonably correct
    1157     * @see TarUtils#verifyCheckSum(byte[])
    1158     * @since 1.5
    1159     */
    1160    public boolean isCheckSumOK() {
    1161        return checkSumOK;
    1162    }
    1163
    1164    /**
    1165     * 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.
    1166     *
    1167     * @param desc Entry to be checked as a descendent of this.
    1168     * @return True if entry is a descendant of this.
    1169     */
    1170    public boolean isDescendent(final TarArchiveEntry desc) {
    1171        return desc.getName().startsWith(getName());
    1172    }
    1173
    1174    /**
    1175     * Tests whether or not this entry represents a directory.
    1176     *
    1177     * @return True if this entry is a directory.
    1178     */
    1179    @Override
    1180    public boolean isDirectory() {
    1181        if (file != null) {
    1182            return Files.isDirectory(file, linkOptions);
    1183        }
    1184
    1185        if (linkFlag == LF_DIR) {
    1186            return true;
    1187        }
    1188
    1189        return !isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/");
    1190    }
    1191
    1192    /**
    1193     * Tests whether in case of an oldgnu sparse file if an extension sparse header follows.
    1194     *
    1195     * @return true if an extension oldgnu sparse header follows.
    1196     */
    1197    public boolean isExtended() {
    1198        return isExtended;
    1199    }
    1200
    1201    /**
    1202     * Tests whether this is a FIFO (pipe) entry.
    1203     *
    1204     * @since 1.2
    1205     * @return whether this is a FIFO entry
    1206     */
    1207    public boolean isFIFO() {
    1208        return linkFlag == LF_FIFO;
    1209    }
    1210
    1211    /**
    1212     * Tests whether this is a "normal file"
    1213     *
    1214     * @since 1.2
    1215     * @return whether this is a "normal file"
    1216     */
    1217    public boolean isFile() {
    1218        if (file != null) {
    1219            return Files.isRegularFile(file, linkOptions);
    1220        }
    1221        if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
    1222            return true;
    1223        }
    1224        return linkFlag != LF_DIR && !getName().endsWith("/");
    1225    }
    1226
    1227    /**
    1228     * Tests whether this is a Pax header.
    1229     *
    1230     * @return {@code true} if this is a Pax header.
    1231     *
    1232     * @since 1.1
    1233     */
    1234    public boolean isGlobalPaxHeader() {
    1235        return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
    1236    }
    1237
    1238    /**
    1239     * Tests whether this entry is a GNU long linkname block
    1240     *
    1241     * @return true if this is a long name extension provided by GNU tar
    1242     */
    1243    public boolean isGNULongLinkEntry() {
    1244        return linkFlag == LF_GNUTYPE_LONGLINK;
    1245    }
    1246
    1247    /**
    1248     * Tests whether this entry is a GNU long name block
    1249     *
    1250     * @return true if this is a long name extension provided by GNU tar
    1251     */
    1252    public boolean isGNULongNameEntry() {
    1253        return linkFlag == LF_GNUTYPE_LONGNAME;
    1254    }
    1255
    1256    /**
    1257     * Tests whether this entry is a GNU sparse block.
    1258     *
    1259     * @return true if this is a sparse extension provided by GNU tar
    1260     */
    1261    public boolean isGNUSparse() {
    1262        return isOldGNUSparse() || isPaxGNUSparse();
    1263    }
    1264
    1265    private boolean isInvalidPrefix(final byte[] header) {
    1266        // prefix[130] is guaranteed to be '\0' with XSTAR/XUSTAR
    1267        if (header[XSTAR_PREFIX_OFFSET + 130] != 0) {
    1268            // except when typeflag is 'M'
    1269            if (header[LF_OFFSET] != LF_MULTIVOLUME) {
    1270                return true;
    1271            }
    1272            // We come only here if we try to read in a GNU/xstar/xustar multivolume archive starting past volume #0
    1273            // As of 1.22, commons-compress does not support multivolume tar archives.
    1274            // If/when it does, this should work as intended.
    1275            if ((header[XSTAR_MULTIVOLUME_OFFSET] & 0x80) == 0 && header[XSTAR_MULTIVOLUME_OFFSET + 11] != ' ') {
    1276                return true;
    1277            }
    1278        }
    1279        return false;
    1280    }
    1281
    1282    private boolean isInvalidXtarTime(final byte[] buffer, final int offset, final int length) {
    1283        // If atime[0]...atime[10] or ctime[0]...ctime[10] is not a POSIX octal number it cannot be 'xstar'.
    1284        if ((buffer[offset] & 0x80) == 0) {
    1285            final int lastIndex = length - 1;
    1286            for (int i = 0; i < lastIndex; i++) {
    1287                final byte b = buffer[offset + i];
    1288                if (b < '0' || b > '7') {
    1289                    return true;
    1290                }
    1291            }
    1292            // Check for both POSIX compliant end of number characters if not using base 256
    1293            final byte b = buffer[offset + lastIndex];
    1294            if (b != ' ' && b != 0) {
    1295                return true;
    1296            }
    1297        }
    1298        return false;
    1299    }
    1300
    1301    /**
    1302     * Tests whether this is a link entry.
    1303     *
    1304     * @since 1.2
    1305     * @return whether this is a link entry
    1306     */
    1307    public boolean isLink() {
    1308        return linkFlag == LF_LINK;
    1309    }
    1310
    1311    /**
    1312     * Tests whether this entry is a GNU or star sparse block using the oldgnu format.
    1313     *
    1314     * @return true if this is a sparse extension provided by GNU tar or star
    1315     * @since 1.11
    1316     */
    1317    public boolean isOldGNUSparse() {
    1318        return linkFlag == LF_GNUTYPE_SPARSE;
    1319    }
    1320
    1321    /**
    1322     * Tests whether this entry is a sparse file with 1.X PAX Format or not
    1323     *
    1324     * @return True if this entry is a sparse file with 1.X PAX Format
    1325     * @since 1.20
    1326     */
    1327    public boolean isPaxGNU1XSparse() {
    1328        return paxGNU1XSparse;
    1329    }
    1330
    1331    /**
    1332     * Tests whether this entry is a GNU sparse block using one of the PAX formats.
    1333     *
    1334     * @return true if this is a sparse extension provided by GNU tar
    1335     * @since 1.11
    1336     */
    1337    public boolean isPaxGNUSparse() {
    1338        return paxGNUSparse;
    1339    }
    1340
    1341    /**
    1342     * Tests whether this is a Pax header.
    1343     *
    1344     * @return {@code true} if this is a Pax header.
    1345     *
    1346     * @since 1.1
    1347     */
    1348    public boolean isPaxHeader() {
    1349        return linkFlag == LF_PAX_EXTENDED_HEADER_LC || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
    1350    }
    1351
    1352    /**
    1353     * Tests whether this is a sparse entry.
    1354     *
    1355     * @return whether this is a sparse entry
    1356     * @since 1.11
    1357     */
    1358    public boolean isSparse() {
    1359        return isGNUSparse() || isStarSparse();
    1360    }
    1361
    1362    /**
    1363     * Tests whether this entry is a star sparse block using PAX headers.
    1364     *
    1365     * @return true if this is a sparse extension provided by star
    1366     * @since 1.11
    1367     */
    1368    public boolean isStarSparse() {
    1369        return starSparse;
    1370    }
    1371
    1372    /**
    1373     * {@inheritDoc}
    1374     *
    1375     * @since 1.21
    1376     */
    1377    @Override
    1378    public boolean isStreamContiguous() {
    1379        return true;
    1380    }
    1381
    1382    /**
    1383     * Tests whether this is a symbolic link entry.
    1384     *
    1385     * @since 1.2
    1386     * @return whether this is a symbolic link
    1387     */
    1388    public boolean isSymbolicLink() {
    1389        return linkFlag == LF_SYMLINK;
    1390    }
    1391
    1392    /**
    1393     * Tests whether the given header is in XSTAR / XUSTAR format.
    1394     *
    1395     * Use the same logic found in star version 1.6 in {@code header.c}, function {@code isxmagic(TCB *ptb)}.
    1396     */
    1397    private boolean isXstar(final Map<String, String> globalPaxHeaders, final byte[] header) {
    1398        // Check if this is XSTAR
    1399        if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET, XSTAR_MAGIC_LEN)) {
    1400            return true;
    1401        }
    1402
    1403        /*
    1404         * If SCHILY.archtype is present in the global PAX header, we can use it to identify the type of archive.
    1405         *
    1406         * Possible values for XSTAR: - xustar: 'xstar' format without "tar" signature at header offset 508. - exustar: 'xustar' format variant that always
    1407         * includes x-headers and g-headers.
    1408         */
    1409        final String archType = globalPaxHeaders.get("SCHILY.archtype");
    1410        if (archType != null) {
    1411            return "xustar".equals(archType) || "exustar".equals(archType);
    1412        }
    1413
    1414        // Check if this is XUSTAR
    1415        if (isInvalidPrefix(header)) {
    1416            return false;
    1417        }
    1418        if (isInvalidXtarTime(header, XSTAR_ATIME_OFFSET, ATIMELEN_XSTAR)) {
    1419            return false;
    1420        }
    1421        if (isInvalidXtarTime(header, XSTAR_CTIME_OFFSET, CTIMELEN_XSTAR)) {
    1422            return false;
    1423        }
    1424
    1425        return true;
    1426    }
    1427
    1428    private long parseOctalOrBinary(final byte[] header, final int offset, final int length, final boolean lenient) {
    1429        if (lenient) {
    1430            try {
    1431                return TarUtils.parseOctalOrBinary(header, offset, length);
    1432            } catch (final IllegalArgumentException ex) { // NOSONAR
    1433                return UNKNOWN;
    1434            }
    1435        }
    1436        return TarUtils.parseOctalOrBinary(header, offset, length);
    1437    }
    1438
    1439    /**
    1440     * Parses an entry's header information from a header buffer.
    1441     *
    1442     * @param header The tar entry header buffer to get information from.
    1443     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    1444     */
    1445    public void parseTarHeader(final byte[] header) {
    1446        try {
    1447            parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
    1448        } catch (final IOException ex) { // NOSONAR
    1449            try {
    1450                parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true, false);
    1451            } catch (final IOException ex2) {
    1452                // not really possible
    1453                throw new UncheckedIOException(ex2); // NOSONAR
    1454            }
    1455        }
    1456    }
    1457
    1458    /**
    1459     * Parse an entry's header information from a header buffer.
    1460     *
    1461     * @param header   The tar entry header buffer to get information from.
    1462     * @param encoding encoding to use for file names
    1463     * @since 1.4
    1464     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    1465     * @throws IOException              on error
    1466     */
    1467    public void parseTarHeader(final byte[] header, final ZipEncoding encoding) throws IOException {
    1468        parseTarHeader(header, encoding, false, false);
    1469    }
    1470
    1471    private void parseTarHeader(final byte[] header, final ZipEncoding encoding, final boolean oldStyle, final boolean lenient) throws IOException {
    1472        parseTarHeader(Collections.emptyMap(), header, encoding, oldStyle, lenient);
    1473    }
    1474
    1475    private void parseTarHeader(final Map<String, String> globalPaxHeaders, final byte[] header, final ZipEncoding encoding, final boolean oldStyle,
    1476            final boolean lenient) throws IOException {
    1477        try {
    1478            parseTarHeaderUnwrapped(globalPaxHeaders, header, encoding, oldStyle, lenient);
    1479        } catch (final IllegalArgumentException ex) {
    1480            throw new IOException("Corrupted TAR archive.", ex);
    1481        }
    1482    }
    1483
    1484    private void parseTarHeaderUnwrapped(final Map<String, String> globalPaxHeaders, final byte[] header, final ZipEncoding encoding, final boolean oldStyle,
    1485            final boolean lenient) throws IOException {
    1486        int offset = 0;
    1487
    1488        name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) : TarUtils.parseName(header, offset, NAMELEN, encoding);
    1489        offset += NAMELEN;
    1490        mode = (int) parseOctalOrBinary(header, offset, MODELEN, lenient);
    1491        offset += MODELEN;
    1492        userId = (int) parseOctalOrBinary(header, offset, UIDLEN, lenient);
    1493        offset += UIDLEN;
    1494        groupId = (int) parseOctalOrBinary(header, offset, GIDLEN, lenient);
    1495        offset += GIDLEN;
    1496        size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
    1497        if (size < 0) {
    1498            throw new IOException("broken archive, entry with negative size");
    1499        }
    1500        offset += SIZELEN;
    1501        mTime = TimeUtils.unixTimeToFileTime(parseOctalOrBinary(header, offset, MODTIMELEN, lenient));
    1502        offset += MODTIMELEN;
    1503        checkSumOK = TarUtils.verifyCheckSum(header);
    1504        offset += CHKSUMLEN;
    1505        linkFlag = header[offset++];
    1506        linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) : TarUtils.parseName(header, offset, NAMELEN, encoding);
    1507        offset += NAMELEN;
    1508        magic = TarUtils.parseName(header, offset, MAGICLEN);
    1509        offset += MAGICLEN;
    1510        version = TarUtils.parseName(header, offset, VERSIONLEN);
    1511        offset += VERSIONLEN;
    1512        userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN) : TarUtils.parseName(header, offset, UNAMELEN, encoding);
    1513        offset += UNAMELEN;
    1514        groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN) : TarUtils.parseName(header, offset, GNAMELEN, encoding);
    1515        offset += GNAMELEN;
    1516        if (linkFlag == LF_CHR || linkFlag == LF_BLK) {
    1517            devMajor = (int) parseOctalOrBinary(header, offset, DEVLEN, lenient);
    1518            offset += DEVLEN;
    1519            devMinor = (int) parseOctalOrBinary(header, offset, DEVLEN, lenient);
    1520            offset += DEVLEN;
    1521        } else {
    1522            offset += 2 * DEVLEN;
    1523        }
    1524
    1525        final int type = evaluateType(globalPaxHeaders, header);
    1526        switch (type) {
    1527        case FORMAT_OLDGNU: {
    1528            aTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, ATIMELEN_GNU, lenient));
    1529            offset += ATIMELEN_GNU;
    1530            cTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, CTIMELEN_GNU, lenient));
    1531            offset += CTIMELEN_GNU;
    1532            offset += OFFSETLEN_GNU;
    1533            offset += LONGNAMESLEN_GNU;
    1534            offset += PAD2LEN_GNU;
    1535            sparseHeaders = new ArrayList<>(TarUtils.readSparseStructs(header, offset, SPARSE_HEADERS_IN_OLDGNU_HEADER));
    1536            offset += SPARSELEN_GNU;
    1537            isExtended = TarUtils.parseBoolean(header, offset);
    1538            offset += ISEXTENDEDLEN_GNU;
    1539            realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
    1540            offset += REALSIZELEN_GNU; // NOSONAR - assignment as documentation
    1541            break;
    1542        }
    1543        case FORMAT_XSTAR: {
    1544            final String xstarPrefix = oldStyle ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR)
    1545                    : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding);
    1546            offset += PREFIXLEN_XSTAR;
    1547            if (!xstarPrefix.isEmpty()) {
    1548                name = xstarPrefix + "/" + name;
    1549            }
    1550            aTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, ATIMELEN_XSTAR, lenient));
    1551            offset += ATIMELEN_XSTAR;
    1552            cTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, CTIMELEN_XSTAR, lenient));
    1553            offset += CTIMELEN_XSTAR; // NOSONAR - assignment as documentation
    1554            break;
    1555        }
    1556        case FORMAT_POSIX:
    1557        default: {
    1558            final String prefix = oldStyle ? TarUtils.parseName(header, offset, PREFIXLEN) : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
    1559            offset += PREFIXLEN; // NOSONAR - assignment as documentation
    1560            // SunOS tar -E does not add / to directory names, so fix
    1561            // up to be consistent
    1562            if (isDirectory() && !name.endsWith("/")) {
    1563                name += "/";
    1564            }
    1565            if (!prefix.isEmpty()) {
    1566                name = prefix + "/" + name;
    1567            }
    1568        }
    1569        }
    1570    }
    1571
    1572    /**
    1573     * Processes one pax header, using the entries extraPaxHeaders map as source for extra headers used when handling entries for sparse files.
    1574     *
    1575     * @param key
    1576     * @param val
    1577     * @since 1.15
    1578     */
    1579    private void processPaxHeader(final String key, final String val) throws IOException {
    1580        processPaxHeader(key, val, extraPaxHeaders);
    1581    }
    1582
    1583    /**
    1584     * Processes one pax header, using the supplied map as source for extra headers to be used when handling entries for sparse files
    1585     *
    1586     * @param key     the header name.
    1587     * @param val     the header value.
    1588     * @param headers map of headers used for dealing with sparse file.
    1589     * @throws NumberFormatException if encountered errors when parsing the numbers
    1590     * @since 1.15
    1591     */
    1592    private void processPaxHeader(final String key, final String val, final Map<String, String> headers) throws IOException {
    1593        /*
    1594         * The following headers are defined for Pax. charset: cannot use these without changing TarArchiveEntry fields mtime atime ctime
    1595         * LIBARCHIVE.creationtime comment gid, gname linkpath size uid,uname SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those
    1596         *
    1597         * 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.
    1598         *
    1599         * star files use additional members of which we use SCHILY.filetype in order to detect star sparse files.
    1600         *
    1601         * If called from addExtraPaxHeader, these additional headers must be already present .
    1602         */
    1603        switch (key) {
    1604        case "path":
    1605            setName(val);
    1606            break;
    1607        case "linkpath":
    1608            setLinkName(val);
    1609            break;
    1610        case "gid":
    1611            setGroupId(ParsingUtils.parseLongValue(val));
    1612            break;
    1613        case "gname":
    1614            setGroupName(val);
    1615            break;
    1616        case "uid":
    1617            setUserId(ParsingUtils.parseLongValue(val));
    1618            break;
    1619        case "uname":
    1620            setUserName(val);
    1621            break;
    1622        case "size":
    1623            final long size = ParsingUtils.parseLongValue(val);
    1624            if (size < 0) {
    1625                throw new IOException("Corrupted TAR archive. Entry size is negative");
    1626            }
    1627            setSize(size);
    1628            break;
    1629        case "mtime":
    1630            setLastModifiedTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
    1631            break;
    1632        case "atime":
    1633            setLastAccessTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
    1634            break;
    1635        case "ctime":
    1636            setStatusChangeTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
    1637            break;
    1638        case "LIBARCHIVE.creationtime":
    1639            setCreationTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
    1640            break;
    1641        case "SCHILY.devminor":
    1642            final int devMinor = ParsingUtils.parseIntValue(val);
    1643            if (devMinor < 0) {
    1644                throw new IOException("Corrupted TAR archive. Dev-Minor is negative");
    1645            }
    1646            setDevMinor(devMinor);
    1647            break;
    1648        case "SCHILY.devmajor":
    1649            final int devMajor = ParsingUtils.parseIntValue(val);
    1650            if (devMajor < 0) {
    1651                throw new IOException("Corrupted TAR archive. Dev-Major is negative");
    1652            }
    1653            setDevMajor(devMajor);
    1654            break;
    1655        case TarGnuSparseKeys.SIZE:
    1656            fillGNUSparse0xData(headers);
    1657            break;
    1658        case TarGnuSparseKeys.REALSIZE:
    1659            fillGNUSparse1xData(headers);
    1660            break;
    1661        case "SCHILY.filetype":
    1662            if ("sparse".equals(val)) {
    1663                fillStarSparseData(headers);
    1664            }
    1665            break;
    1666        default:
    1667            extraPaxHeaders.put(key, val);
    1668        }
    1669    }
    1670
    1671    private void readFileMode(final Path file, final String normalizedName, final LinkOption... options) throws IOException {
    1672        if (Files.isDirectory(file, options)) {
    1673            this.mode = DEFAULT_DIR_MODE;
    1674            this.linkFlag = LF_DIR;
    1675
    1676            final int nameLength = normalizedName.length();
    1677            if (nameLength == 0 || normalizedName.charAt(nameLength - 1) != '/') {
    1678                this.name = normalizedName + "/";
    1679            } else {
    1680                this.name = normalizedName;
    1681            }
    1682        } else {
    1683            this.mode = DEFAULT_FILE_MODE;
    1684            this.linkFlag = LF_NORMAL;
    1685            this.name = normalizedName;
    1686            this.size = Files.size(file);
    1687        }
    1688    }
    1689
    1690    private void readOsSpecificProperties(final Path file, final LinkOption... options) throws IOException {
    1691        final Set<String> availableAttributeViews = file.getFileSystem().supportedFileAttributeViews();
    1692        if (availableAttributeViews.contains("posix")) {
    1693            final PosixFileAttributes posixFileAttributes = Files.readAttributes(file, PosixFileAttributes.class, options);
    1694            setLastModifiedTime(posixFileAttributes.lastModifiedTime());
    1695            setCreationTime(posixFileAttributes.creationTime());
    1696            setLastAccessTime(posixFileAttributes.lastAccessTime());
    1697            this.userName = posixFileAttributes.owner().getName();
    1698            this.groupName = posixFileAttributes.group().getName();
    1699            if (availableAttributeViews.contains("unix")) {
    1700                this.userId = ((Number) Files.getAttribute(file, "unix:uid", options)).longValue();
    1701                this.groupId = ((Number) Files.getAttribute(file, "unix:gid", options)).longValue();
    1702                try {
    1703                    setStatusChangeTime((FileTime) Files.getAttribute(file, "unix:ctime", options));
    1704                } catch (final IllegalArgumentException ex) { // NOSONAR
    1705                    // ctime is not supported
    1706                }
    1707            }
    1708        } else {
    1709            if (availableAttributeViews.contains("dos")) {
    1710                final DosFileAttributes dosFileAttributes = Files.readAttributes(file, DosFileAttributes.class, options);
    1711                setLastModifiedTime(dosFileAttributes.lastModifiedTime());
    1712                setCreationTime(dosFileAttributes.creationTime());
    1713                setLastAccessTime(dosFileAttributes.lastAccessTime());
    1714            } else {
    1715                final BasicFileAttributes basicFileAttributes = Files.readAttributes(file, BasicFileAttributes.class, options);
    1716                setLastModifiedTime(basicFileAttributes.lastModifiedTime());
    1717                setCreationTime(basicFileAttributes.creationTime());
    1718                setLastAccessTime(basicFileAttributes.lastAccessTime());
    1719            }
    1720            this.userName = Files.getOwner(file, options).getName();
    1721        }
    1722    }
    1723
    1724    /**
    1725     * Sets this entry's creation time.
    1726     *
    1727     * @param time This entry's new creation time.
    1728     * @since 1.22
    1729     */
    1730    public void setCreationTime(final FileTime time) {
    1731        birthTime = time;
    1732    }
    1733
    1734    /**
    1735     * Sets the offset of the data for the tar entry.
    1736     *
    1737     * @param dataOffset the position of the data in the tar.
    1738     * @since 1.21
    1739     */
    1740    public void setDataOffset(final long dataOffset) {
    1741        if (dataOffset < 0) {
    1742            throw new IllegalArgumentException("The offset can not be smaller than 0");
    1743        }
    1744        this.dataOffset = dataOffset;
    1745    }
    1746
    1747    /**
    1748     * Sets this entry's major device number.
    1749     *
    1750     * @param devNo This entry's major device number.
    1751     * @throws IllegalArgumentException if the devNo is &lt; 0.
    1752     * @since 1.4
    1753     */
    1754    public void setDevMajor(final int devNo) {
    1755        if (devNo < 0) {
    1756            throw new IllegalArgumentException("Major device number is out of " + "range: " + devNo);
    1757        }
    1758        this.devMajor = devNo;
    1759    }
    1760
    1761    /**
    1762     * Sets this entry's minor device number.
    1763     *
    1764     * @param devNo This entry's minor device number.
    1765     * @throws IllegalArgumentException if the devNo is &lt; 0.
    1766     * @since 1.4
    1767     */
    1768    public void setDevMinor(final int devNo) {
    1769        if (devNo < 0) {
    1770            throw new IllegalArgumentException("Minor device number is out of " + "range: " + devNo);
    1771        }
    1772        this.devMinor = devNo;
    1773    }
    1774
    1775    /**
    1776     * Sets this entry's group id.
    1777     *
    1778     * @param groupId This entry's new group id.
    1779     */
    1780    public void setGroupId(final int groupId) {
    1781        setGroupId((long) groupId);
    1782    }
    1783
    1784    /**
    1785     * Sets this entry's group id.
    1786     *
    1787     * @since 1.10
    1788     * @param groupId This entry's new group id.
    1789     */
    1790    public void setGroupId(final long groupId) {
    1791        this.groupId = groupId;
    1792    }
    1793
    1794    /**
    1795     * Sets this entry's group name.
    1796     *
    1797     * @param groupName This entry's new group name.
    1798     */
    1799    public void setGroupName(final String groupName) {
    1800        this.groupName = groupName;
    1801    }
    1802
    1803    /**
    1804     * Convenience method to set this entry's group and user ids.
    1805     *
    1806     * @param userId  This entry's new user id.
    1807     * @param groupId This entry's new group id.
    1808     */
    1809    public void setIds(final int userId, final int groupId) {
    1810        setUserId(userId);
    1811        setGroupId(groupId);
    1812    }
    1813
    1814    /**
    1815     * Sets this entry's last access time.
    1816     *
    1817     * @param time This entry's new last access time.
    1818     * @since 1.22
    1819     */
    1820    public void setLastAccessTime(final FileTime time) {
    1821        aTime = time;
    1822    }
    1823
    1824    /**
    1825     * Sets this entry's modification time.
    1826     *
    1827     * @param time This entry's new modification time.
    1828     * @since 1.22
    1829     */
    1830    public void setLastModifiedTime(final FileTime time) {
    1831        mTime = Objects.requireNonNull(time, "Time must not be null");
    1832    }
    1833
    1834    /**
    1835     * Sets this entry's link name.
    1836     *
    1837     * @param link the link name to use.
    1838     *
    1839     * @since 1.1
    1840     */
    1841    public void setLinkName(final String link) {
    1842        this.linkName = link;
    1843    }
    1844
    1845    /**
    1846     * Sets the mode for this entry
    1847     *
    1848     * @param mode the mode for this entry
    1849     */
    1850    public void setMode(final int mode) {
    1851        this.mode = mode;
    1852    }
    1853
    1854    /**
    1855     * Sets this entry's modification time.
    1856     *
    1857     * @param time This entry's new modification time.
    1858     * @see TarArchiveEntry#setLastModifiedTime(FileTime)
    1859     */
    1860    public void setModTime(final Date time) {
    1861        setLastModifiedTime(FileTimes.toFileTime(time));
    1862    }
    1863
    1864    /**
    1865     * Sets this entry's modification time.
    1866     *
    1867     * @param time This entry's new modification time.
    1868     * @since 1.21
    1869     * @see TarArchiveEntry#setLastModifiedTime(FileTime)
    1870     */
    1871    public void setModTime(final FileTime time) {
    1872        setLastModifiedTime(time);
    1873    }
    1874
    1875    /**
    1876     * Sets this entry's modification time. The parameter passed to this method is in "Java time".
    1877     *
    1878     * @param time This entry's new modification time.
    1879     * @see TarArchiveEntry#setLastModifiedTime(FileTime)
    1880     */
    1881    public void setModTime(final long time) {
    1882        setLastModifiedTime(FileTime.fromMillis(time));
    1883    }
    1884
    1885    /**
    1886     * Sets this entry's name.
    1887     *
    1888     * @param name This entry's new name.
    1889     */
    1890    public void setName(final String name) {
    1891        this.name = normalizeFileName(name, this.preserveAbsolutePath);
    1892    }
    1893
    1894    /**
    1895     * Convenience method to set this entry's group and user names.
    1896     *
    1897     * @param userName  This entry's new user name.
    1898     * @param groupName This entry's new group name.
    1899     */
    1900    public void setNames(final String userName, final String groupName) {
    1901        setUserName(userName);
    1902        setGroupName(groupName);
    1903    }
    1904
    1905    /**
    1906     * Sets this entry's file size.
    1907     *
    1908     * @param size This entry's new file size.
    1909     * @throws IllegalArgumentException if the size is &lt; 0.
    1910     */
    1911    public void setSize(final long size) {
    1912        if (size < 0) {
    1913            throw new IllegalArgumentException("Size is out of range: " + size);
    1914        }
    1915        this.size = size;
    1916    }
    1917
    1918    /**
    1919     * Sets this entry's sparse headers
    1920     *
    1921     * @param sparseHeaders The new sparse headers
    1922     * @since 1.20
    1923     */
    1924    public void setSparseHeaders(final List<TarArchiveStructSparse> sparseHeaders) {
    1925        this.sparseHeaders = sparseHeaders;
    1926    }
    1927
    1928    /**
    1929     * Sets this entry's status change time.
    1930     *
    1931     * @param time This entry's new status change time.
    1932     * @since 1.22
    1933     */
    1934    public void setStatusChangeTime(final FileTime time) {
    1935        cTime = time;
    1936    }
    1937
    1938    /**
    1939     * Sets this entry's user id.
    1940     *
    1941     * @param userId This entry's new user id.
    1942     */
    1943    public void setUserId(final int userId) {
    1944        setUserId((long) userId);
    1945    }
    1946
    1947    /**
    1948     * Sets this entry's user id.
    1949     *
    1950     * @param userId This entry's new user id.
    1951     * @since 1.10
    1952     */
    1953    public void setUserId(final long userId) {
    1954        this.userId = userId;
    1955    }
    1956
    1957    /**
    1958     * Sets this entry's user name.
    1959     *
    1960     * @param userName This entry's new user name.
    1961     */
    1962    public void setUserName(final String userName) {
    1963        this.userName = userName;
    1964    }
    1965
    1966    /**
    1967     * Update the entry using a map of pax headers.
    1968     *
    1969     * @param headers
    1970     * @since 1.15
    1971     */
    1972    void updateEntryFromPaxHeaders(final Map<String, String> headers) throws IOException {
    1973        for (final Map.Entry<String, String> ent : headers.entrySet()) {
    1974            processPaxHeader(ent.getKey(), ent.getValue(), headers);
    1975        }
    1976    }
    1977
    1978    /**
    1979     * Writes an entry's header information to a header buffer.
    1980     * <p>
    1981     * This method does not use the star/GNU tar/BSD tar extensions.
    1982     * </p>
    1983     *
    1984     * @param outbuf The tar entry header buffer to fill in.
    1985     */
    1986    public void writeEntryHeader(final byte[] outbuf) {
    1987        try {
    1988            writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
    1989        } catch (final IOException ex) { // NOSONAR
    1990            try {
    1991                writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
    1992            } catch (final IOException ex2) {
    1993                // impossible
    1994                throw new UncheckedIOException(ex2); // NOSONAR
    1995            }
    1996        }
    1997    }
    1998
    1999    /**
    2000     * Writes an entry's header information to a header buffer.
    2001     *
    2002     * @param outbuf   The tar entry header buffer to fill in.
    2003     * @param encoding encoding to use when writing the file name.
    2004     * @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
    2005     *                 archives
    2006     * @since 1.4
    2007     * @throws IOException on error
    2008     */
    2009    public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding, final boolean starMode) throws IOException {
    2010        int offset = 0;
    2011
    2012        offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN, encoding);
    2013        offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
    2014        offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN, starMode);
    2015        offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN, starMode);
    2016        offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
    2017        offset = writeEntryHeaderField(TimeUtils.toUnixTime(mTime), outbuf, offset, MODTIMELEN, starMode);
    2018
    2019        final int csOffset = offset;
    2020
    2021        offset = fill((byte) ' ', offset, outbuf, CHKSUMLEN);
    2022
    2023        outbuf[offset++] = linkFlag;
    2024        offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN, encoding);
    2025        offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
    2026        offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
    2027        offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN, encoding);
    2028        offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN, encoding);
    2029        offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN, starMode);
    2030        offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN, starMode);
    2031
    2032        if (starMode) {
    2033            // skip prefix
    2034            offset = fill(0, offset, outbuf, PREFIXLEN_XSTAR);
    2035            offset = writeEntryHeaderOptionalTimeField(aTime, offset, outbuf, ATIMELEN_XSTAR);
    2036            offset = writeEntryHeaderOptionalTimeField(cTime, offset, outbuf, CTIMELEN_XSTAR);
    2037            // 8-byte fill
    2038            offset = fill(0, offset, outbuf, 8);
    2039            // Do not write MAGIC_XSTAR because it causes issues with some TAR tools
    2040            // This makes it effectively XUSTAR, which guarantees compatibility with USTAR
    2041            offset = fill(0, offset, outbuf, XSTAR_MAGIC_LEN);
    2042        }
    2043
    2044        offset = fill(0, offset, outbuf, outbuf.length - offset); // NOSONAR - assignment as documentation
    2045
    2046        final long chk = TarUtils.computeCheckSum(outbuf);
    2047
    2048        TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
    2049    }
    2050
    2051    private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset, final int length, final boolean starMode) {
    2052        if (!starMode && (value < 0 || value >= 1L << 3 * (length - 1))) {
    2053            // value doesn't fit into field when written as octal
    2054            // number, will be written to PAX header or causes an
    2055            // error
    2056            return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
    2057        }
    2058        return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset, length);
    2059    }
    2060
    2061    private int writeEntryHeaderOptionalTimeField(final FileTime time, int offset, final byte[] outbuf, final int fieldLength) {
    2062        if (time != null) {
    2063            offset = writeEntryHeaderField(TimeUtils.toUnixTime(time), outbuf, offset, fieldLength, true);
    2064        } else {
    2065            offset = fill(0, offset, outbuf, fieldLength);
    2066        }
    2067        return offset;
    2068    }
    2069
    2070}