    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 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     * @return This entry's file.
    655     */
    656    public File getFile() {
    657        return file;
    658    }
    659
    660    /**
    661     * Get this entry's mode.
    662     *
    663     * @return This entry's mode.
    664     */
    665    public int getMode() {
    666        return mode;
    667    }
    668
    669    /**
    670     * Get this entry's file size.
    671     *
    672     * @return This entry's file size.
    673     */
    674    @Override
    675    public long getSize() {
    676        return size;
    677    }
    678
    679    /**
    680     * Set this entry's file size.
    681     *
    682     * @param size This entry's new file size.
    683     * @throws IllegalArgumentException if the size is &lt; 0.
    684     */
    685    public void setSize(final long size) {
    686        if (size < 0){
    687            throw new IllegalArgumentException("Size is out of range: "+size);
    688        }
    689        this.size = size;
    690    }
    691
    692    /**
    693     * Get this entry's major device number.
    694     *
    695     * @return This entry's major device number.
    696     * @since 1.4
    697     */
    698    public int getDevMajor() {
    699        return devMajor;
    700    }
    701
    702    /**
    703     * Set this entry's major device number.
    704     *
    705     * @param devNo This entry's major device number.
    706     * @throws IllegalArgumentException if the devNo is &lt; 0.
    707     * @since 1.4
    708     */
    709    public void setDevMajor(final int devNo) {
    710        if (devNo < 0){
    711            throw new IllegalArgumentException("Major device number is out of "
    712                                               + "range: " + devNo);
    713        }
    714        this.devMajor = devNo;
    715    }
    716
    717    /**
    718     * Get this entry's minor device number.
    719     *
    720     * @return This entry's minor device number.
    721     * @since 1.4
    722     */
    723    public int getDevMinor() {
    724        return devMinor;
    725    }
    726
    727    /**
    728     * Set this entry's minor device number.
    729     *
    730     * @param devNo This entry's minor device number.
    731     * @throws IllegalArgumentException if the devNo is &lt; 0.
    732     * @since 1.4
    733     */
    734    public void setDevMinor(final int devNo) {
    735        if (devNo < 0){
    736            throw new IllegalArgumentException("Minor device number is out of "
    737                                               + "range: " + devNo);
    738        }
    739        this.devMinor = devNo;
    740    }
    741
    742    /**
    743     * Indicates in case of an oldgnu sparse file if an extension
    744     * sparse header follows.
    745     *
    746     * @return true if an extension oldgnu sparse header follows.
    747     */
    748    public boolean isExtended() {
    749        return isExtended;
    750    }
    751
    752    /**
    753     * Get this entry's real file size in case of a sparse file.
    754     *
    755     * @return This entry's real file size.
    756     */
    757    public long getRealSize() {
    758        return realSize;
    759    }
    760
    761    /**
    762     * Indicate if this entry is a GNU sparse block.
    763     *
    764     * @return true if this is a sparse extension provided by GNU tar
    765     */
    766    public boolean isGNUSparse() {
    767        return isOldGNUSparse() || isPaxGNUSparse();
    768    }
    769
    770    /**
    771     * Indicate if this entry is a GNU or star sparse block using the
    772     * oldgnu format.
    773     *
    774     * @return true if this is a sparse extension provided by GNU tar or star
    775     * @since 1.11
    776     */
    777    public boolean isOldGNUSparse() {
    778        return linkFlag == LF_GNUTYPE_SPARSE;
    779    }
    780
    781    /**
    782     * Indicate if this entry is a GNU sparse block using one of the
    783     * PAX formats.
    784     *
    785     * @return true if this is a sparse extension provided by GNU tar
    786     * @since 1.11
    787     */
    788    public boolean isPaxGNUSparse() {
    789        return paxGNUSparse;
    790    }
    791
    792    /**
    793     * Indicate if this entry is a star sparse block using PAX headers.
    794     *
    795     * @return true if this is a sparse extension provided by star
    796     * @since 1.11
    797     */
    798    public boolean isStarSparse() {
    799        return starSparse;
    800    }
    801
    802    /**
    803     * Indicate if this entry is a GNU long linkname block
    804     *
    805     * @return true if this is a long name extension provided by GNU tar
    806     */
    807    public boolean isGNULongLinkEntry() {
    808        return linkFlag == LF_GNUTYPE_LONGLINK;
    809    }
    810
    811    /**
    812     * Indicate if this entry is a GNU long name block
    813     *
    814     * @return true if this is a long name extension provided by GNU tar
    815     */
    816    public boolean isGNULongNameEntry() {
    817        return linkFlag == LF_GNUTYPE_LONGNAME;
    818    }
    819
    820    /**
    821     * Check if this is a Pax header.
    822     *
    823     * @return {@code true} if this is a Pax header.
    824     *
    825     * @since 1.1
    826     *
    827     */
    828    public boolean isPaxHeader() {
    829        return linkFlag == LF_PAX_EXTENDED_HEADER_LC
    830            || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
    831    }
    832
    833    /**
    834     * Check if this is a Pax header.
    835     *
    836     * @return {@code true} if this is a Pax header.
    837     *
    838     * @since 1.1
    839     */
    840    public boolean isGlobalPaxHeader() {
    841        return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
    842    }
    843
    844    /**
    845     * Return whether or not this entry represents a directory.
    846     *
    847     * @return True if this entry is a directory.
    848     */
    849    @Override
    850    public boolean isDirectory() {
    851        if (file != null) {
    852            return file.isDirectory();
    853        }
    854
    855        if (linkFlag == LF_DIR) {
    856            return true;
    857        }
    858
    859        if (!isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/")) {
    860            return true;
    861        }
    862
    863        return false;
    864    }
    865
    866    /**
    867     * Check if this is a "normal file"
    868     *
    869     * @since 1.2
    870     * @return whether this is a "normal file"
    871     */
    872    public boolean isFile() {
    873        if (file != null) {
    874            return file.isFile();
    875        }
    876        if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
    877            return true;
    878        }
    879        return !getName().endsWith("/");
    880    }
    881
    882    /**
    883     * Check if this is a symbolic link entry.
    884     *
    885     * @since 1.2
    886     * @return whether this is a symbolic link
    887     */
    888    public boolean isSymbolicLink() {
    889        return linkFlag == LF_SYMLINK;
    890    }
    891
    892    /**
    893     * Check if this is a link entry.
    894     *
    895     * @since 1.2
    896     * @return whether this is a link entry
    897     */
    898    public boolean isLink() {
    899        return linkFlag == LF_LINK;
    900    }
    901
    902    /**
    903     * Check if this is a character device entry.
    904     *
    905     * @since 1.2
    906     * @return whether this is a character device
    907     */
    908    public boolean isCharacterDevice() {
    909        return linkFlag == LF_CHR;
    910    }
    911
    912    /**
    913     * Check if this is a block device entry.
    914     *
    915     * @since 1.2
    916     * @return whether this is a block device
    917     */
    918    public boolean isBlockDevice() {
    919        return linkFlag == LF_BLK;
    920    }
    921
    922    /**
    923     * Check if this is a FIFO (pipe) entry.
    924     *
    925     * @since 1.2
    926     * @return whether this is a FIFO entry
    927     */
    928    public boolean isFIFO() {
    929        return linkFlag == LF_FIFO;
    930    }
    931
    932    /**
    933     * Check whether this is a sparse entry.
    934     *
    935     * @return whether this is a sparse entry
    936     * @since 1.11
    937     */
    938    public boolean isSparse() {
    939        return isGNUSparse() || isStarSparse();
    940    }
    941
    942    /**
    943     * If this entry represents a file, and the file is a directory, return
    944     * an array of TarEntries for this entry's children.
    945     *
    946     * @return An array of TarEntry's for this entry's children.
    947     */
    948    public TarArchiveEntry[] getDirectoryEntries() {
    949        if (file == null || !file.isDirectory()) {
    950            return EMPTY_TAR_ARCHIVE_ENTRIES;
    951        }
    952
    953        final String[] list = file.list();
    954        if (list == null) {
    955            return EMPTY_TAR_ARCHIVE_ENTRIES;
    956        }
    957        final TarArchiveEntry[] result = new TarArchiveEntry[list.length];
    958
    959        for (int i = 0; i < result.length; ++i) {
    960            result[i] = new TarArchiveEntry(new File(file, list[i]));
    961        }
    962
    963        return result;
    964    }
    965
    966    /**
    967     * Write an entry's header information to a header buffer.
    968     *
    969     * <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
    970     *
    971     * @param outbuf The tar entry header buffer to fill in.
    972     */
    973    public void writeEntryHeader(final byte[] outbuf) {
    974        try {
    975            writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
    976        } catch (final IOException ex) {
    977            try {
    978                writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
    979            } catch (final IOException ex2) {
    980                // impossible
    981                throw new RuntimeException(ex2);
    982            }
    983        }
    984    }
    985
    986    /**
    987     * Write an entry's header information to a header buffer.
    988     *
    989     * @param outbuf The tar entry header buffer to fill in.
    990     * @param encoding encoding to use when writing the file name.
    991     * @param starMode whether to use the star/GNU tar/BSD tar
    992     * extension for numeric fields if their value doesn't fit in the
    993     * maximum size of standard tar archives
    994     * @since 1.4
    995     * @throws IOException on error
    996     */
    997    public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding,
    998                                 final boolean starMode) throws IOException {
    999        int offset = 0;
    1000
    1001        offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN,
    1002                                          encoding);
    1003        offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
    1004        offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN,
    1005                                       starMode);
    1006        offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN,
    1007                                       starMode);
    1008        offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
    1009        offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN,
    1010                                       starMode);
    1011
    1012        final int csOffset = offset;
    1013
    1014        for (int c = 0; c < CHKSUMLEN; ++c) {
    1015            outbuf[offset++] = (byte) ' ';
    1016        }
    1017
    1018        outbuf[offset++] = linkFlag;
    1019        offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN,
    1020                                          encoding);
    1021        offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
    1022        offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
    1023        offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN,
    1024                                          encoding);
    1025        offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN,
    1026                                          encoding);
    1027        offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN,
    1028                                       starMode);
    1029        offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN,
    1030                                       starMode);
    1031
    1032        while (offset < outbuf.length) {
    1033            outbuf[offset++] = 0;
    1034        }
    1035
    1036        final long chk = TarUtils.computeCheckSum(outbuf);
    1037
    1038        TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
    1039    }
    1040
    1041    private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset,
    1042                                      final int length, final boolean starMode) {
    1043        if (!starMode && (value < 0
    1044                          || value >= 1l << 3 * (length - 1))) {
    1045            // value doesn't fit into field when written as octal
    1046            // number, will be written to PAX header or causes an
    1047            // error
    1048            return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
    1049        }
    1050        return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset,
    1051                                                     length);
    1052    }
    1053
    1054    /**
    1055     * Parse an entry's header information from a header buffer.
    1056     *
    1057     * @param header The tar entry header buffer to get information from.
    1058     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
    1059     */
    1060    public void parseTarHeader(final byte[] header) {
    1061        try {
    1062            parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
    1063        } catch (final IOException ex) {
    1064            try {
    1065                parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true);
    1066            } catch (final IOException ex2) {
    1067                // not really possible
    1068                throw new RuntimeException(ex2);
    1069            }
    1070        }
    1071    }
    1072
    1073    /**
    1074     * Parse an entry's header information from a header buffer.
    1075     *
    1076     * @param header The tar entry header buffer to get information from.
    1077     * @param encoding encoding to use for file names
    1078     * @since 1.4
    1079     * @throws IllegalArgumentException if any of the numeric fields
    1080     * have an invalid format
    1081     * @throws IOException on error
    1082     */
    1083    public void parseTarHeader(final byte[] header, final ZipEncoding encoding)
    1084        throws IOException {
    1085        parseTarHeader(header, encoding, false);
    1086    }
    1087
    1088    private void parseTarHeader(final byte[] header, final ZipEncoding encoding,
    1089                                final boolean oldStyle)
    1090        throws IOException {
    1091        int offset = 0;
    1092
    1093        name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
    1094            : TarUtils.parseName(header, offset, NAMELEN, encoding);
    1095        offset += NAMELEN;
    1096        mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
    1097        offset += MODELEN;
    1098        userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
    1099        offset += UIDLEN;
    1100        groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
    1101        offset += GIDLEN;
    1102        size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
    1103        offset += SIZELEN;
    1104        modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
    1105        offset += MODTIMELEN;
    1106        checkSumOK = TarUtils.verifyCheckSum(header);
    1107        offset += CHKSUMLEN;
    1108        linkFlag = header[offset++];
    1109        linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
    1110            : TarUtils.parseName(header, offset, NAMELEN, encoding);
    1111        offset += NAMELEN;
    1112        magic = TarUtils.parseName(header, offset, MAGICLEN);
    1113        offset += MAGICLEN;
    1114        version = TarUtils.parseName(header, offset, VERSIONLEN);
    1115        offset += VERSIONLEN;
    1116        userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN)
    1117            : TarUtils.parseName(header, offset, UNAMELEN, encoding);
    1118        offset += UNAMELEN;
    1119        groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
    1120            : TarUtils.parseName(header, offset, GNAMELEN, encoding);
    1121        offset += GNAMELEN;
    1122        devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
    1123        offset += DEVLEN;
    1124        devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
    1125        offset += DEVLEN;
    1126
    1127        final int type = evaluateType(header);
    1128        switch (type) {
    1129        case FORMAT_OLDGNU: {
    1130            offset += ATIMELEN_GNU;
    1131            offset += CTIMELEN_GNU;
    1132            offset += OFFSETLEN_GNU;
    1133            offset += LONGNAMESLEN_GNU;
    1134            offset += PAD2LEN_GNU;
    1135            offset += SPARSELEN_GNU;
    1136            isExtended = TarUtils.parseBoolean(header, offset);
    1137            offset += ISEXTENDEDLEN_GNU;
    1138            realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
    1139            offset += REALSIZELEN_GNU;
    1140            break;
    1141        }
    1142        case FORMAT_XSTAR: {
    1143            final String xstarPrefix = oldStyle
    1144                ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR)
    1145                : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding);
    1146            if (xstarPrefix.length() > 0) {
    1147                name = xstarPrefix + "/" + name;
    1148            }
    1149            break;
    1150        }
    1151        case FORMAT_POSIX:
    1152        default: {
    1153            final String prefix = oldStyle
    1154                ? TarUtils.parseName(header, offset, PREFIXLEN)
    1155                : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
    1156            // SunOS tar -E does not add / to directory names, so fix
    1157            // up to be consistent
    1158            if (isDirectory() && !name.endsWith("/")){
    1159                name = name + "/";
    1160            }
    1161            if (prefix.length() > 0){
    1162                name = prefix + "/" + name;
    1163            }
    1164        }
    1165        }
    1166    }
    1167
    1168    /**
    1169     * Strips Windows' drive letter as well as any leading slashes,
    1170     * turns path separators into forward slahes.
    1171     */
    1172    private static String normalizeFileName(String fileName,
    1173                                            final boolean preserveLeadingSlashes) {
    1174        final String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
    1175
    1176        if (osname != null) {
    1177
    1178            // Strip off drive letters!
    1179            // REVIEW Would a better check be "(File.separator == '\')"?
    1180
    1181            if (osname.startsWith("windows")) {
    1182                if (fileName.length() > 2) {
    1183                    final char ch1 = fileName.charAt(0);
    1184                    final char ch2 = fileName.charAt(1);
    1185
    1186                    if (ch2 == ':'
    1187                        && (ch1 >= 'a' && ch1 <= 'z'
    1188                            || ch1 >= 'A' && ch1 <= 'Z')) {
    1189                        fileName = fileName.substring(2);
    1190                    }
    1191                }
    1192            } else if (osname.contains("netware")) {
    1193                final int colon = fileName.indexOf(':');
    1194                if (colon != -1) {
    1195                    fileName = fileName.substring(colon + 1);
    1196                }
    1197            }
    1198        }
    1199
    1200        fileName = fileName.replace(File.separatorChar, '/');
    1201
    1202        // No absolute pathnames
    1203        // Windows (and Posix?) paths can start with "\\NetworkDrive\",
    1204        // so we loop on starting /'s.
    1205        while (!preserveLeadingSlashes && fileName.startsWith("/")) {
    1206            fileName = fileName.substring(1);
    1207        }
    1208        return fileName;
    1209    }
    1210
    1211    /**
    1212     * Evaluate an entry's header format from a header buffer.
    1213     *
    1214     * @param header The tar entry header buffer to evaluate the format for.
    1215     * @return format type
    1216     */
    1217    private int evaluateType(final byte[] header) {
    1218        if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) {
    1219            return FORMAT_OLDGNU;
    1220        }
    1221        if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) {
    1222            if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET,
    1223                                              XSTAR_MAGIC_LEN)) {
    1224                return FORMAT_XSTAR;
    1225            }
    1226            return FORMAT_POSIX;
    1227        }
    1228        return 0;
    1229    }
    1230
    1231    void fillGNUSparse0xData(final Map<String, String> headers) {
    1232        paxGNUSparse = true;
    1233        realSize = Integer.parseInt(headers.get("GNU.sparse.size"));
    1234        if (headers.containsKey("GNU.sparse.name")) {
    1235            // version 0.1
    1236            name = headers.get("GNU.sparse.name");
    1237        }
    1238    }
    1239
    1240    void fillGNUSparse1xData(final Map<String, String> headers) {
    1241        paxGNUSparse = true;
    1242        realSize = Integer.parseInt(headers.get("GNU.sparse.realsize"));
    1243        name = headers.get("GNU.sparse.name");
    1244    }
    1245
    1246    void fillStarSparseData(final Map<String, String> headers) {
    1247        starSparse = true;
    1248        if (headers.containsKey("SCHILY.realsize")) {
    1249            realSize = Long.parseLong(headers.get("SCHILY.realsize"));
    1250        }
    1251    }
    1252}
    1253