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