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