    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.util.Date;
    024import java.util.Locale;
    025import java.util.Map;
    026
    027import org.apache.commons.compress.archivers.ArchiveEntry;
    028import org.apache.commons.compress.archivers.zip.ZipEncoding;
    029import org.apache.commons.compress.utils.ArchiveUtils;
    030
    031/**
    032 * This class represents an entry in a Tar archive. It consists
    033 * of the entry's header, as well as the entry's File. Entries
    034 * can be instantiated in one of three ways, depending on how
    035 * they are to be used.
    036 * <p>
    037 * TarEntries that are created from the header bytes read from
    038 * an archive are instantiated with the TarEntry( byte[] )
    039 * constructor. These entries will be used when extracting from
    040 * or listing the contents of an archive. These entries have their
    041 * header filled in using the header bytes. They also set the File
    042 * to null, since they reference an archive entry not a file.
    043 * <p>
    044 * TarEntries that are created from Files that are to be written
    045 * into an archive are instantiated with the TarEntry( File )
    046 * constructor. These entries have their header filled in using
    047 * the File's information. They also keep a reference to the File
    048 * for convenience when writing entries.
    049 * <p>
    050 * Finally, TarEntries can be constructed from nothing but a name.
    051 * This allows the programmer to construct the entry by hand, for
    052 * instance when only an InputStream is available for writing to
    053 * the archive, and the header information is constructed from
    054 * other information. In this case the header fields are set to
    055 * defaults and the File is set to null.
    056 *
    057 * <p>
    058 * The C structure for a Tar Entry's header is:
    059 * <pre>
    060 * struct header {
    061 * char name[100];     // TarConstants.NAMELEN    - offset   0
    062 * char mode[8];       // TarConstants.MODELEN    - offset 100
    063 * char uid[8];        // TarConstants.UIDLEN     - offset 108
    064 * char gid[8];        // TarConstants.GIDLEN     - offset 116
    065 * char size[12];      // TarConstants.SIZELEN    - offset 124
    066 * char mtime[12];     // TarConstants.MODTIMELEN - offset 136
    067 * char chksum[8];     // TarConstants.CHKSUMLEN  - offset 148
    068 * char linkflag[1];   //                         - offset 156
    069 * char linkname[100]; // TarConstants.NAMELEN    - offset 157
    070 * The following fields are only present in new-style POSIX tar archives:
    071 * char magic[6];      // TarConstants.MAGICLEN   - offset 257
    072 * char version[2];    // TarConstants.VERSIONLEN - offset 263
    073 * char uname[32];     // TarConstants.UNAMELEN   - offset 265
    074 * char gname[32];     // TarConstants.GNAMELEN   - offset 297
    075 * char devmajor[8];   // TarConstants.DEVLEN     - offset 329
    076 * char devminor[8];   // TarConstants.DEVLEN     - offset 337
    077 * char prefix[155];   // TarConstants.PREFIXLEN  - offset 345
    078 * // Used if "name" field is not long enough to hold the path
    079 * char pad[12];       // NULs                    - offset 500
    080 * } header;
    081 * All unused bytes are set to null.
    082 * New-style GNU tar files are slightly different from the above.
    083 * For values of size larger than 077777777777L (11 7s)
    084 * or uid and gid larger than 07777777L (7 7s)
    085 * the sign bit of the first byte is set, and the rest of the
    086 * field is the binary representation of the number.
    087 * See TarUtils.parseOctalOrBinary.
    088 * </pre>
    089 *
    090 * <p>
    091 * The C structure for a old GNU Tar Entry's header is:
    092 * <pre>
    093 * struct oldgnu_header {
    094 * char unused_pad1[345]; // TarConstants.PAD1LEN_GNU       - offset 0
    095 * char atime[12];        // TarConstants.ATIMELEN_GNU      - offset 345
    096 * char ctime[12];        // TarConstants.CTIMELEN_GNU      - offset 357
    097 * char offset[12];       // TarConstants.OFFSETLEN_GNU     - offset 369
    098 * char longnames[4];     // TarConstants.LONGNAMESLEN_GNU  - offset 381
    099 * char unused_pad2;      // TarConstants.PAD2LEN_GNU       - offset 385
    100 * struct sparse sp[4];   // TarConstants.SPARSELEN_GNU     - offset 386
    101 * char isextended;       // TarConstants.ISEXTENDEDLEN_GNU - offset 482
    102 * char realsize[12];     // TarConstants.REALSIZELEN_GNU   - offset 483
    103 * char unused_pad[17];   // TarConstants.PAD3LEN_GNU       - offset 495
    104 * };
    105 * </pre>
    106 * Whereas, "struct sparse" is:
    107 * <pre>
    108 * struct sparse {
    109 * char offset[12];   // offset 0
    110 * char numbytes[12]; // offset 12
    111 * };
    112 * </pre>
    113 *
    114 * <p>
    115 * The C structure for a xstar (JÃ¶rg Schilling star) Tar Entry's header is:
    116 * <pre>
    117 * struct star_header {
    118 *  char name[100];             // offset   0
    119 *  char mode[8];               // offset 100
    120 *  char uid[8];                // offset 108
    121 *  char gid[8];                // offset 116
    122 *  char size[12];              // offset 124
    123 *  char mtime[12];             // offset 136
    124 *  char chksum[8];             // offset 148
    125 *  char typeflag;              // offset 156
    126 *  char linkname[100];         // offset 157
    127 *  char magic[6];              // offset 257
    128 *  char version[2];            // offset 263
    129 *  char uname[32];             // offset 265
    130 *  char gname[32];             // offset 297
    131 *  char devmajor[8];           // offset 329
    132 *  char devminor[8];           // offset 337
    133 *  char prefix[131];           // offset 345
    134 *  char atime[12];             // offset 476
    135 *  char ctime[12];             // offset 488
    136 *  char mfill[8];              // offset 500
    137 *  char xmagic[4];             // offset 508  "tar"
    138 * };
    139 * </pre>
    140 * <p>which is identical to new-style POSIX up to the first 130 bytes of the prefix.</p>
    141 *
    142 * @NotThreadSafe
    143 */
    144
    145public class TarArchiveEntry implements TarConstants, ArchiveEntry {
    146    private static final TarArchiveEntry[] EMPTY_TAR_ARCHIVE_ENTRIES = new TarArchiveEntry[0];
    147
    148    /** The entry's name. */
    149    private String name = "";
    150
    151    /** Whether to enforce leading slashes on the name */
    152    private boolean preserveLeadingSlashes;
    153
    154    /** The entry's permission mode. */
    155    private int mode;
    156
    157    /** The entry's user id. */
    158    private long userId = 0;
    159
    160    /** The entry's group id. */
    161    private long groupId = 0;
    162
    163    /** The entry's size. */
    164    private long size = 0;
    165
    166    /** The entry's modification time. */
    167    private long modTime;
    168
    169    /** If the header checksum is reasonably correct. */
    170    private boolean checkSumOK;
    171
    172    /** The entry's link flag. */
    173    private byte linkFlag;
    174
    175    /** The entry's link name. */
    176    private String linkName = "";
    177
    178    /** The entry's magic tag. */
    179    private String magic = MAGIC_POSIX;
    180    /** The version of the format */
    181    private String version = VERSION_POSIX;
    182
    183    /** The entry's user name. */
    184    private String userName;
    185
    186    /** The entry's group name. */
    187    private String groupName = "";
    188
    189    /** The entry's major device number. */
    190    private int devMajor = 0;
    191
    192    /** The entry's minor device number. */
    193    private int devMinor = 0;
    194
    195    /** If an extension sparse header follows. */
    196    private boolean isExtended;
    197
    198    /** The entry's real size in case of a sparse file. */
    199    private long realSize;
    200
    201    /** is this entry a GNU sparse entry using one of the PAX formats? */
    202    private boolean paxGNUSparse;
    203
    204    /** is this entry a star sparse entry using the PAX header? */
    205    private boolean starSparse;
    206
    207    /** The entry's file reference */
    208    private final File file;
    209
    210    /** Maximum length of a user's name in the tar file */
    211    public static final int MAX_NAMELEN = 31;
    212
    213    /** Default permissions bits for directories */
    214    public static final int DEFAULT_DIR_MODE = 040755;
    215
    216    /** Default permissions bits for files */
    217    public static final int DEFAULT_FILE_MODE = 0100644;
    218
    219    /** Convert millis to seconds */
    220    public static final int MILLIS_PER_SECOND = 1000;
    221
    222    /**
    223     * Construct an empty entry and prepares the header values.
    224     */
    225    private TarArchiveEntry() {
    226        String user = System.getProperty("user.name", "");
    227
    228        if (user.length() > MAX_NAMELEN) {
    229            user = user.substring(0, MAX_NAMELEN);
    230        }
    231
    232        this.userName = user;
    233        this.file = null;
    234    }
    235
    236    /**
    237     * Construct an entry with only a name. This allows the programmer
    238     * to construct the entry's header "by hand". File is set to null.
    239     *
    240     * @param name the entry name
    241     */
    242    public TarArchiveEntry(final String name) {
    243        this(name, false);
    244    }
    245
    246    /**
    247     * Construct an entry with only a name. This allows the programmer
    248     * to construct the entry's header "by hand". File is set to null.
    249     *
    250     * @param name the entry name
    251     * @param preserveLeadingSlashes whether to allow leading slashes
    252     * in the name.
    253     *
    254     * @since 1.1
    255     */
    256    public TarArchiveEntry(String name, final boolean preserveLeadingSlashes) {
    257        this();
    258
    259        this.preserveLeadingSlashes = preserveLeadingSlashes;
    260
    261        name = normalizeFileName(name, preserveLeadingSlashes);
    262        final boolean isDir = name.endsWith("/");
    263
    264        this.name = name;
    265        this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
    266        this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
    267        this.modTime = new Date().getTime() / MILLIS_PER_SECOND;
    268        this.userName = "";
    269    }
    270
    271    /**
    272     * Construct an entry with a name and a link flag.
    273     *
    274     * @param name the entry name
    275     * @param linkFlag the entry link flag.
    276     */
    277    public TarArchiveEntry(final String name, final byte linkFlag) {
    278        this(name, linkFlag, false);
    279    }
    280
    281    /**
    282     * Construct an entry with a name and a link flag.
    283     *
    284     * @param name the entry name
    285     * @param linkFlag the entry link flag.
    286     * @param preserveLeadingSlashes whether to allow leading slashes
    287     * in the name.
    288     *
    289     * @since 1.5
    290     */
    291    public TarArchiveEntry(final String name, final byte linkFlag, final boolean preserveLeadingSlashes) {
    292        this(name, preserveLeadingSlashes);
    293        this.linkFlag = linkFlag;
    294        if (linkFlag == LF_GNUTYPE_LONGNAME) {
    295            magic = MAGIC_GNU;
    296            version = VERSION_GNU_SPACE;
    297        }
    298    }
    299
    300    /**
    301     * Construct an entry for a file. File is set to file, and the
    302     * header is constructed from information from the file.
    303     * The name is set from the normalized file path.
    304     *
    305     * @param file The file that the entry represents.
    306     */
    307    public TarArchiveEntry(final File file) {
    308        this(file, file.getPath());
    309    }
    310
    311    /**
    312     * Construct an entry for a file. File is set to file, and the
    313     * header is constructed from information from the file.
    314     *
    315     * @param file The file that the entry represents.
    316     * @param fileName the name to be used for the entry.
    317     */
    318    public TarArchiveEntry(final File file, final String fileName) {
    319        final String normalizedName = normalizeFileName(fileName, false);
    320        this.file = file;
    321
    322        if (file.isDirectory()) {
    323            this.mode = DEFAULT_DIR_MODE;
    324            this.linkFlag = LF_DIR;
    325
    326            final int nameLength = normalizedName.length();
    327            if (nameLength == 0 || normalizedName.charAt(nameLength - 1) != '/') {
    328                this.name = normalizedName + "/";
    329            } else {
    330                this.name = normalizedName;
    331            }
    332        } else {
    333            this.mode = DEFAULT_FILE_MODE;
    334            this.linkFlag = LF_NORMAL;
    335            this.size = file.length();
    336            this.name = normalizedName;
    337        }
    338
    339        this.modTime = file.lastModified() / MILLIS_PER_SECOND;
    340        this.userName = "";
    341    }
    342
    343    /**
    344     * Construct an entry from an archive's header bytes. File is set
    345     * to null.
    346     *
    347     * @param headerBuf The header bytes from a tar archive entry.
    348     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    349     */
    350    public TarArchiveEntry(final byte[] headerBuf) {
    351        this();
    352        parseTarHeader(headerBuf);
    353    }
    354
    355    /**
    356     * Construct an entry from an archive's header bytes. File is set
    357     * to null.
    358     *
    359     * @param headerBuf The header bytes from a tar archive entry.
    360     * @param encoding encoding to use for file names
    361     * @since 1.4
    362     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    363     * @throws IOException on error
    364     */
    365    public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding)
    366        throws IOException {
    367        this();
    368        parseTarHeader(headerBuf, encoding);
    369    }
    370
    371    /**
    372     * Determine if the two entries are equal. Equality is determined
    373     * by the header names being equal.
    374     *
    375     * @param it Entry to be checked for equality.
    376     * @return True if the entries are equal.
    377     */
    378    public boolean equals(final TarArchiveEntry it) {
    379        return it != null && getName().equals(it.getName());
    380    }
    381
    382    /**
    383     * Determine if the two entries are equal. Equality is determined
    384     * by the header names being equal.
    385     *
    386     * @param it Entry to be checked for equality.
    387     * @return True if the entries are equal.
    388     */
    389    @Override
    390    public boolean equals(final Object it) {
    391        if (it == null || getClass() != it.getClass()) {
    392            return false;
    393        }
    394        return equals((TarArchiveEntry) it);
    395    }
    396
    397    /**
    398     * Hashcodes are based on entry names.
    399     *
    400     * @return the entry hashcode
    401     */
    402    @Override
    403    public int hashCode() {
    404        return getName().hashCode();
    405    }
    406
    407    /**
    408     * Determine if the given entry is a descendant of this entry.
    409     * Descendancy is determined by the name of the descendant
    410     * starting with this entry's name.
    411     *
    412     * @param desc Entry to be checked as a descendent of this.
    413     * @return True if entry is a descendant of this.
    414     */
    415    public boolean isDescendent(final TarArchiveEntry desc) {
    416        return desc.getName().startsWith(getName());
    417    }
    418
    419    /**
    420     * Get this entry's name.
    421     *
    422     * @return This entry's name.
    423     */
    424    @Override
    425    public String getName() {
    426        return name;
    427    }
    428
    429    /**
    430     * Set this entry's name.
    431     *
    432     * @param name This entry's new name.
    433     */
    434    public void setName(final String name) {
    435        this.name = normalizeFileName(name, this.preserveLeadingSlashes);
    436    }
    437
    438    /**
    439     * Set the mode for this entry
    440     *
    441     * @param mode the mode for this entry
    442     */
    443    public void setMode(final int mode) {
    444        this.mode = mode;
    445    }
    446
    447    /**
    448     * Get this entry's link name.
    449     *
    450     * @return This entry's link name.
    451     */
    452    public String getLinkName() {
    453        return linkName;
    454    }
    455
    456    /**
    457     * Set this entry's link name.
    458     *
    459     * @param link the link name to use.
    460     *
    461     * @since 1.1
    462     */
    463    public void setLinkName(final String link) {
    464        this.linkName = link;
    465    }
    466
    467    /**
    468     * Get this entry's user id.
    469     *
    470     * @return This entry's user id.
    471     * @deprecated use #getLongUserId instead as user ids can be
    472     * bigger than {@link Integer#MAX_VALUE}
    473     */
    474    @Deprecated
    475    public int getUserId() {
    476        return (int) (userId & 0xffffffff);
    477    }
    478
    479    /**
    480     * Set this entry's user id.
    481     *
    482     * @param userId This entry's new user id.
    483     */
    484    public void setUserId(final int userId) {
    485        setUserId((long) userId);
    486    }
    487
    488    /**
    489     * Get this entry's user id.
    490     *
    491     * @return This entry's user id.
    492     * @since 1.10
    493     */
    494    public long getLongUserId() {
    495        return userId;
    496    }
    497
    498    /**
    499     * Set this entry's user id.
    500     *
    501     * @param userId This entry's new user id.
    502     * @since 1.10
    503     */
    504    public void setUserId(final long userId) {
    505        this.userId = userId;
    506    }
    507
    508    /**
    509     * Get this entry's group id.
    510     *
    511     * @return This entry's group id.
    512     * @deprecated use #getLongGroupId instead as group ids can be
    513     * bigger than {@link Integer#MAX_VALUE}
    514     */
    515    @Deprecated
    516    public int getGroupId() {
    517        return (int) (groupId & 0xffffffff);
    518    }
    519
    520    /**
    521     * Set this entry's group id.
    522     *
    523     * @param groupId This entry's new group id.
    524     */
    525    public void setGroupId(final int groupId) {
    526        setGroupId((long) groupId);
    527    }
    528
    529    /**
    530     * Get this entry's group id.
    531     *
    532     * @since 1.10
    533     * @return This entry's group id.
    534     */
    535    public long getLongGroupId() {
    536        return groupId;
    537    }
    538
    539    /**
    540     * Set this entry's group id.
    541     *
    542     * @since 1.10
    543     * @param groupId This entry's new group id.
    544     */
    545    public void setGroupId(final long groupId) {
    546        this.groupId = groupId;
    547    }
    548
    549    /**
    550     * Get this entry's user name.
    551     *
    552     * @return This entry's user name.
    553     */
    554    public String getUserName() {
    555        return userName;
    556    }
    557
    558    /**
    559     * Set this entry's user name.
    560     *
    561     * @param userName This entry's new user name.
    562     */
    563    public void setUserName(final String userName) {
    564        this.userName = userName;
    565    }
    566
    567    /**
    568     * Get this entry's group name.
    569     *
    570     * @return This entry's group name.
    571     */
    572    public String getGroupName() {
    573        return groupName;
    574    }
    575
    576    /**
    577     * Set this entry's group name.
    578     *
    579     * @param groupName This entry's new group name.
    580     */
    581    public void setGroupName(final String groupName) {
    582        this.groupName = groupName;
    583    }
    584
    585    /**
    586     * Convenience method to set this entry's group and user ids.
    587     *
    588     * @param userId This entry's new user id.
    589     * @param groupId This entry's new group id.
    590     */
    591    public void setIds(final int userId, final int groupId) {
    592        setUserId(userId);
    593        setGroupId(groupId);
    594    }
    595
    596    /**
    597     * Convenience method to set this entry's group and user names.
    598     *
    599     * @param userName This entry's new user name.
    600     * @param groupName This entry's new group name.
    601     */
    602    public void setNames(final String userName, final String groupName) {
    603        setUserName(userName);
    604        setGroupName(groupName);
    605    }
    606
    607    /**
    608     * Set this entry's modification time. The parameter passed
    609     * to this method is in "Java time".
    610     *
    611     * @param time This entry's new modification time.
    612     */
    613    public void setModTime(final long time) {
    614        modTime = time / MILLIS_PER_SECOND;
    615    }
    616
    617    /**
    618     * Set this entry's modification time.
    619     *
    620     * @param time This entry's new modification time.
    621     */
    622    public void setModTime(final Date time) {
    623        modTime = time.getTime() / MILLIS_PER_SECOND;
    624    }
    625
    626    /**
    627     * Set this entry's modification time.
    628     *
    629     * @return time This entry's new modification time.
    630     */
    631    public Date getModTime() {
    632        return new Date(modTime * MILLIS_PER_SECOND);
    633    }
    634
    635    @Override
    636    public Date getLastModifiedDate() {
    637        return getModTime();
    638    }
    639
    640    /**
    641     * Get this entry's checksum status.
    642     *
    643     * @return if the header checksum is reasonably correct
    644     * @see TarUtils#verifyCheckSum(byte[])
    645     * @since 1.5
    646     */
    647    public boolean isCheckSumOK() {
    648        return checkSumOK;
    649    }
    650
    651    /**
    652     * Get this entry's file.
    653     *
    654     * <p>This method is only useful for entries created from a {@code
    655     * File} but not for entries read from an archive.</p>
    656     *
    657     * @return This entry's file.
    658     */
    659    public File getFile() {
    660        return file;
    661    }
    662
    663    /**
    664     * Get this entry's mode.
    665     *
    666     * @return This entry's mode.
    667     */
    668    public int getMode() {
    669        return mode;
    670    }
    671
    672    /**
    673     * Get this entry's file size.
    674     *
    675     * @return This entry's file size.
    676     */
    677    @Override
    678    public long getSize() {
    679        return size;
    680    }
    681
    682    /**
    683     * Set this entry's file size.
    684     *
    685     * @param size This entry's new file size.
    686     * @throws IllegalArgumentException if the size is &lt; 0.
    687     */
    688    public void setSize(final long size) {
    689        if (size < 0){
    690            throw new IllegalArgumentException("Size is out of range: "+size);
    691        }
    692        this.size = size;
    693    }
    694
    695    /**
    696     * Get this entry's major device number.
    697     *
    698     * @return This entry's major device number.
    699     * @since 1.4
    700     */
    701    public int getDevMajor() {
    702        return devMajor;
    703    }
    704
    705    /**
    706     * Set this entry's major device number.
    707     *
    708     * @param devNo This entry's major device number.
    709     * @throws IllegalArgumentException if the devNo is &lt; 0.
    710     * @since 1.4
    711     */
    712    public void setDevMajor(final int devNo) {
    713        if (devNo < 0){
    714            throw new IllegalArgumentException("Major device number is out of "
    715                                               + "range: " + devNo);
    716        }
    717        this.devMajor = devNo;
    718    }
    719
    720    /**
    721     * Get this entry's minor device number.
    722     *
    723     * @return This entry's minor device number.
    724     * @since 1.4
    725     */
    726    public int getDevMinor() {
    727        return devMinor;
    728    }
    729
    730    /**
    731     * Set this entry's minor device number.
    732     *
    733     * @param devNo This entry's minor device number.
    734     * @throws IllegalArgumentException if the devNo is &lt; 0.
    735     * @since 1.4
    736     */
    737    public void setDevMinor(final int devNo) {
    738        if (devNo < 0){
    739            throw new IllegalArgumentException("Minor device number is out of "
    740                                               + "range: " + devNo);
    741        }
    742        this.devMinor = devNo;
    743    }
    744
    745    /**
    746     * Indicates in case of an oldgnu sparse file if an extension
    747     * sparse header follows.
    748     *
    749     * @return true if an extension oldgnu sparse header follows.
    750     */
    751    public boolean isExtended() {
    752        return isExtended;
    753    }
    754
    755    /**
    756     * Get this entry's real file size in case of a sparse file.
    757     *
    758     * @return This entry's real file size.
    759     */
    760    public long getRealSize() {
    761        return realSize;
    762    }
    763
    764    /**
    765     * Indicate if this entry is a GNU sparse block.
    766     *
    767     * @return true if this is a sparse extension provided by GNU tar
    768     */
    769    public boolean isGNUSparse() {
    770        return isOldGNUSparse() || isPaxGNUSparse();
    771    }
    772
    773    /**
    774     * Indicate if this entry is a GNU or star sparse block using the
    775     * oldgnu format.
    776     *
    777     * @return true if this is a sparse extension provided by GNU tar or star
    778     * @since 1.11
    779     */
    780    public boolean isOldGNUSparse() {
    781        return linkFlag == LF_GNUTYPE_SPARSE;
    782    }
    783
    784    /**
    785     * Indicate if this entry is a GNU sparse block using one of the
    786     * PAX formats.
    787     *
    788     * @return true if this is a sparse extension provided by GNU tar
    789     * @since 1.11
    790     */
    791    public boolean isPaxGNUSparse() {
    792        return paxGNUSparse;
    793    }
    794
    795    /**
    796     * Indicate if this entry is a star sparse block using PAX headers.
    797     *
    798     * @return true if this is a sparse extension provided by star
    799     * @since 1.11
    800     */
    801    public boolean isStarSparse() {
    802        return starSparse;
    803    }
    804
    805    /**
    806     * Indicate if this entry is a GNU long linkname block
    807     *
    808     * @return true if this is a long name extension provided by GNU tar
    809     */
    810    public boolean isGNULongLinkEntry() {
    811        return linkFlag == LF_GNUTYPE_LONGLINK;
    812    }
    813
    814    /**
    815     * Indicate if this entry is a GNU long name block
    816     *
    817     * @return true if this is a long name extension provided by GNU tar
    818     */
    819    public boolean isGNULongNameEntry() {
    820        return linkFlag == LF_GNUTYPE_LONGNAME;
    821    }
    822
    823    /**
    824     * Check if this is a Pax header.
    825     *
    826     * @return {@code true} if this is a Pax header.
    827     *
    828     * @since 1.1
    829     *
    830     */
    831    public boolean isPaxHeader() {
    832        return linkFlag == LF_PAX_EXTENDED_HEADER_LC
    833            || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
    834    }
    835
    836    /**
    837     * Check if this is a Pax header.
    838     *
    839     * @return {@code true} if this is a Pax header.
    840     *
    841     * @since 1.1
    842     */
    843    public boolean isGlobalPaxHeader() {
    844        return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
    845    }
    846
    847    /**
    848     * Return whether or not this entry represents a directory.
    849     *
    850     * @return True if this entry is a directory.
    851     */
    852    @Override
    853    public boolean isDirectory() {
    854        if (file != null) {
    855            return file.isDirectory();
    856        }
    857
    858        if (linkFlag == LF_DIR) {
    859            return true;
    860        }
    861
    862        if (!isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/")) {
    863            return true;
    864        }
    865
    866        return false;
    867    }
    868
    869    /**
    870     * Check if this is a "normal file"
    871     *
    872     * @since 1.2
    873     * @return whether this is a "normal file"
    874     */
    875    public boolean isFile() {
    876        if (file != null) {
    877            return file.isFile();
    878        }
    879        if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
    880            return true;
    881        }
    882        return !getName().endsWith("/");
    883    }
    884
    885    /**
    886     * Check if this is a symbolic link entry.
    887     *
    888     * @since 1.2
    889     * @return whether this is a symbolic link
    890     */
    891    public boolean isSymbolicLink() {
    892        return linkFlag == LF_SYMLINK;
    893    }
    894
    895    /**
    896     * Check if this is a link entry.
    897     *
    898     * @since 1.2
    899     * @return whether this is a link entry
    900     */
    901    public boolean isLink() {
    902        return linkFlag == LF_LINK;
    903    }
    904
    905    /**
    906     * Check if this is a character device entry.
    907     *
    908     * @since 1.2
    909     * @return whether this is a character device
    910     */
    911    public boolean isCharacterDevice() {
    912        return linkFlag == LF_CHR;
    913    }
    914
    915    /**
    916     * Check if this is a block device entry.
    917     *
    918     * @since 1.2
    919     * @return whether this is a block device
    920     */
    921    public boolean isBlockDevice() {
    922        return linkFlag == LF_BLK;
    923    }
    924
    925    /**
    926     * Check if this is a FIFO (pipe) entry.
    927     *
    928     * @since 1.2
    929     * @return whether this is a FIFO entry
    930     */
    931    public boolean isFIFO() {
    932        return linkFlag == LF_FIFO;
    933    }
    934
    935    /**
    936     * Check whether this is a sparse entry.
    937     *
    938     * @return whether this is a sparse entry
    939     * @since 1.11
    940     */
    941    public boolean isSparse() {
    942        return isGNUSparse() || isStarSparse();
    943    }
    944
    945    /**
    946     * If this entry represents a file, and the file is a directory, return
    947     * an array of TarEntries for this entry's children.
    948     *
    949     * <p>This method is only useful for entries created from a {@code
    950     * File} but not for entries read from an archive.</p>
    951     *
    952     * @return An array of TarEntry's for this entry's children.
    953     */
    954    public TarArchiveEntry[] getDirectoryEntries() {
    955        if (file == null || !file.isDirectory()) {
    956            return EMPTY_TAR_ARCHIVE_ENTRIES;
    957        }
    958
    959        final String[] list = file.list();
    960        if (list == null) {
    961            return EMPTY_TAR_ARCHIVE_ENTRIES;
    962        }
    963        final TarArchiveEntry[] result = new TarArchiveEntry[list.length];
    964
    965        for (int i = 0; i < result.length; ++i) {
    966            result[i] = new TarArchiveEntry(new File(file, list[i]));
    967        }
    968
    969        return result;
    970    }
    971
    972    /**
    973     * Write an entry's header information to a header buffer.
    974     *
    975     * <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
    976     *
    977     * @param outbuf The tar entry header buffer to fill in.
    978     */
    979    public void writeEntryHeader(final byte[] outbuf) {
    980        try {
    981            writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
    982        } catch (final IOException ex) {
    983            try {
    984                writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
    985            } catch (final IOException ex2) {
    986                // impossible
    987                throw new RuntimeException(ex2); //NOSONAR
    988            }
    989        }
    990    }
    991
    992    /**
    993     * Write an entry's header information to a header buffer.
    994     *
    995     * @param outbuf The tar entry header buffer to fill in.
    996     * @param encoding encoding to use when writing the file name.
    997     * @param starMode whether to use the star/GNU tar/BSD tar
    998     * extension for numeric fields if their value doesn't fit in the
    999     * maximum size of standard tar archives
    1000     * @since 1.4
    1001     * @throws IOException on error
    1002     */
    1003    public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding,
    1004                                 final boolean starMode) throws IOException {
    1005        int offset = 0;
    1006
    1007        offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN,
    1008                                          encoding);
    1009        offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
    1010        offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN,
    1011                                       starMode);
    1012        offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN,
    1013                                       starMode);
    1014        offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
    1015        offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN,
    1016                                       starMode);
    1017
    1018        final int csOffset = offset;
    1019
    1020        for (int c = 0; c < CHKSUMLEN; ++c) {
    1021            outbuf[offset++] = (byte) ' ';
    1022        }
    1023
    1024        outbuf[offset++] = linkFlag;
    1025        offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN,
    1026                                          encoding);
    1027        offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
    1028        offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
    1029        offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN,
    1030                                          encoding);
    1031        offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN,
    1032                                          encoding);
    1033        offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN,
    1034                                       starMode);
    1035        offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN,
    1036                                       starMode);
    1037
    1038        while (offset < outbuf.length) {
    1039            outbuf[offset++] = 0;
    1040        }
    1041
    1042        final long chk = TarUtils.computeCheckSum(outbuf);
    1043
    1044        TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
    1045    }
    1046
    1047    private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset,
    1048                                      final int length, final boolean starMode) {
    1049        if (!starMode && (value < 0
    1050                          || value >= 1l << 3 * (length - 1))) {
    1051            // value doesn't fit into field when written as octal
    1052            // number, will be written to PAX header or causes an
    1053            // error
    1054            return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
    1055        }
    1056        return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset,
    1057                                                     length);
    1058    }
    1059
    1060    /**
    1061     * Parse an entry's header information from a header buffer.
    1062     *
    1063     * @param header The tar entry header buffer to get information from.
    1064     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    1065     */
    1066    public void parseTarHeader(final byte[] header) {
    1067        try {
    1068            parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
    1069        } catch (final IOException ex) {
    1070            try {
    1071                parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true);
    1072            } catch (final IOException ex2) {
    1073                // not really possible
    1074                throw new RuntimeException(ex2); //NOSONAR
    1075            }
    1076        }
    1077    }
    1078
    1079    /**
    1080     * Parse an entry's header information from a header buffer.
    1081     *
    1082     * @param header The tar entry header buffer to get information from.
    1083     * @param encoding encoding to use for file names
    1084     * @since 1.4
    1085     * @throws IllegalArgumentException if any of the numeric fields
    1086     * have an invalid format
    1087     * @throws IOException on error
    1088     */
    1089    public void parseTarHeader(final byte[] header, final ZipEncoding encoding)
    1090        throws IOException {
    1091        parseTarHeader(header, encoding, false);
    1092    }
    1093
    1094    private void parseTarHeader(final byte[] header, final ZipEncoding encoding,
    1095                                final boolean oldStyle)
    1096        throws IOException {
    1097        int offset = 0;
    1098
    1099        name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
    1100            : TarUtils.parseName(header, offset, NAMELEN, encoding);
    1101        offset += NAMELEN;
    1102        mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
    1103        offset += MODELEN;
    1104        userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
    1105        offset += UIDLEN;
    1106        groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
    1107        offset += GIDLEN;
    1108        size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
    1109        offset += SIZELEN;
    1110        modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
    1111        offset += MODTIMELEN;
    1112        checkSumOK = TarUtils.verifyCheckSum(header);
    1113        offset += CHKSUMLEN;
    1114        linkFlag = header[offset++];
    1115        linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
    1116            : TarUtils.parseName(header, offset, NAMELEN, encoding);
    1117        offset += NAMELEN;
    1118        magic = TarUtils.parseName(header, offset, MAGICLEN);
    1119        offset += MAGICLEN;
    1120        version = TarUtils.parseName(header, offset, VERSIONLEN);
    1121        offset += VERSIONLEN;
    1122        userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN)
    1123            : TarUtils.parseName(header, offset, UNAMELEN, encoding);
    1124        offset += UNAMELEN;
    1125        groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
    1126            : TarUtils.parseName(header, offset, GNAMELEN, encoding);
    1127        offset += GNAMELEN;
    1128        devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
    1129        offset += DEVLEN;
    1130        devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
    1131        offset += DEVLEN;
    1132
    1133        final int type = evaluateType(header);
    1134        switch (type) {
    1135        case FORMAT_OLDGNU: {
    1136            offset += ATIMELEN_GNU;
    1137            offset += CTIMELEN_GNU;
    1138            offset += OFFSETLEN_GNU;
    1139            offset += LONGNAMESLEN_GNU;
    1140            offset += PAD2LEN_GNU;
    1141            offset += SPARSELEN_GNU;
    1142            isExtended = TarUtils.parseBoolean(header, offset);
    1143            offset += ISEXTENDEDLEN_GNU;
    1144            realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
    1145            offset += REALSIZELEN_GNU;
    1146            break;
    1147        }
    1148        case FORMAT_XSTAR: {
    1149            final String xstarPrefix = oldStyle
    1150                ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR)
    1151                : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding);
    1152            if (xstarPrefix.length() > 0) {
    1153                name = xstarPrefix + "/" + name;
    1154            }
    1155            break;
    1156        }
    1157        case FORMAT_POSIX:
    1158        default: {
    1159            final String prefix = oldStyle
    1160                ? TarUtils.parseName(header, offset, PREFIXLEN)
    1161                : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
    1162            // SunOS tar -E does not add / to directory names, so fix
    1163            // up to be consistent
    1164            if (isDirectory() && !name.endsWith("/")){
    1165                name = name + "/";
    1166            }
    1167            if (prefix.length() > 0){
    1168                name = prefix + "/" + name;
    1169            }
    1170        }
    1171        }
    1172    }
    1173
    1174    /**
    1175     * Strips Windows' drive letter as well as any leading slashes,
    1176     * turns path separators into forward slahes.
    1177     */
    1178    private static String normalizeFileName(String fileName,
    1179                                            final boolean preserveLeadingSlashes) {
    1180        final String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
    1181
    1182        if (osname != null) {
    1183
    1184            // Strip off drive letters!
    1185            // REVIEW Would a better check be "(File.separator == '\')"?
    1186
    1187            if (osname.startsWith("windows")) {
    1188                if (fileName.length() > 2) {
    1189                    final char ch1 = fileName.charAt(0);
    1190                    final char ch2 = fileName.charAt(1);
    1191
    1192                    if (ch2 == ':'
    1193                        && (ch1 >= 'a' && ch1 <= 'z'
    1194                            || ch1 >= 'A' && ch1 <= 'Z')) {
    1195                        fileName = fileName.substring(2);
    1196                    }
    1197                }
    1198            } else if (osname.contains("netware")) {
    1199                final int colon = fileName.indexOf(':');
    1200                if (colon != -1) {
    1201                    fileName = fileName.substring(colon + 1);
    1202                }
    1203            }
    1204        }
    1205
    1206        fileName = fileName.replace(File.separatorChar, '/');
    1207
    1208        // No absolute pathnames
    1209        // Windows (and Posix?) paths can start with "\\NetworkDrive\",
    1210        // so we loop on starting /'s.
    1211        while (!preserveLeadingSlashes && fileName.startsWith("/")) {
    1212            fileName = fileName.substring(1);
    1213        }
    1214        return fileName;
    1215    }
    1216
    1217    /**
    1218     * Evaluate an entry's header format from a header buffer.
    1219     *
    1220     * @param header The tar entry header buffer to evaluate the format for.
    1221     * @return format type
    1222     */
    1223    private int evaluateType(final byte[] header) {
    1224        if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) {
    1225            return FORMAT_OLDGNU;
    1226        }
    1227        if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) {
    1228            if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET,
    1229                                              XSTAR_MAGIC_LEN)) {
    1230                return FORMAT_XSTAR;
    1231            }
    1232            return FORMAT_POSIX;
    1233        }
    1234        return 0;
    1235    }
    1236
    1237    void fillGNUSparse0xData(final Map<String, String> headers) {
    1238        paxGNUSparse = true;
    1239        realSize = Integer.parseInt(headers.get("GNU.sparse.size"));
    1240        if (headers.containsKey("GNU.sparse.name")) {
    1241            // version 0.1
    1242            name = headers.get("GNU.sparse.name");
    1243        }
    1244    }
    1245
    1246    void fillGNUSparse1xData(final Map<String, String> headers) {
    1247        paxGNUSparse = true;
    1248        realSize = Integer.parseInt(headers.get("GNU.sparse.realsize"));
    1249        name = headers.get("GNU.sparse.name");
    1250    }
    1251
    1252    void fillStarSparseData(final Map<String, String> headers) {
    1253        starSparse = true;
    1254        if (headers.containsKey("SCHILY.realsize")) {
    1255            realSize = Long.parseLong(headers.get("SCHILY.realsize"));
    1256        }
    1257    }
    1258}
    1259