    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.zip;
    020
    021import java.util.zip.CRC32;
    022import java.util.zip.ZipException;
    023
    024/**
    025 * Adds Unix file permission and UID/GID fields as well as symbolic
    026 * link handling.
    027 *
    028 * <p>This class uses the ASi extra field in the format:</p>
    029 * <pre>
    030 *         Value         Size            Description
    031 *         -----         ----            -----------
    032 * (Unix3) 0x756e        Short           tag for this extra block type
    033 *         TSize         Short           total data size for this block
    034 *         CRC           Long            CRC-32 of the remaining data
    035 *         Mode          Short           file permissions
    036 *         SizDev        Long            symlink'd size OR major/minor dev num
    037 *         UID           Short           user ID
    038 *         GID           Short           group ID
    039 *         (var.)        variable        symbolic link filename
    040 * </pre>
    041 * <p>taken from appnote.iz (Info-ZIP note, 981119) found at <a
    042 * href="ftp://ftp.uu.net/pub/archiving/zip/doc/">ftp://ftp.uu.net/pub/archiving/zip/doc/</a></p>
    043 *
    044 * <p>Short is two bytes and Long is four bytes in big endian byte and
    045 * word order, device numbers are currently not supported.</p>
    046 * @NotThreadSafe
    047 *
    048 * <p>Since the documentation this class is based upon doesn't mention
    049 * the character encoding of the file name at all, it is assumed that
    050 * it uses the current platform's default encoding.</p>
    051 */
    052public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable {
    053
    054    private static final ZipShort HEADER_ID = new ZipShort(0x756E);
    055    private static final int      WORD = 4;
    056    /**
    057     * Standard Unix stat(2) file mode.
    058     */
    059    private int mode = 0;
    060    /**
    061     * User ID.
    062     */
    063    private int uid = 0;
    064    /**
    065     * Group ID.
    066     */
    067    private int gid = 0;
    068    /**
    069     * File this entry points to, if it is a symbolic link.
    070     *
    071     * <p>empty string - if entry is not a symbolic link.</p>
    072     */
    073    private String link = "";
    074    /**
    075     * Is this an entry for a directory?
    076     */
    077    private boolean dirFlag = false;
    078
    079    /**
    080     * Instance used to calculate checksums.
    081     */
    082    private CRC32 crc = new CRC32();
    083
    084    /** Constructor for AsiExtraField. */
    085    public AsiExtraField() {
    086    }
    087
    088    /**
    089     * The Header-ID.
    090     * @return the value for the header id for this extrafield
    091     */
    092    @Override
    093    public ZipShort getHeaderId() {
    094        return HEADER_ID;
    095    }
    096
    097    /**
    098     * Length of the extra field in the local file data - without
    099     * Header-ID or length specifier.
    100     * @return a <code>ZipShort</code> for the length of the data of this extra field
    101     */
    102    @Override
    103    public ZipShort getLocalFileDataLength() {
    104        return new ZipShort(WORD         // CRC
    105                          + 2         // Mode
    106                          + WORD         // SizDev
    107                          + 2         // UID
    108                          + 2         // GID
    109                          + getLinkedFile().getBytes().length);
    110                          // Uses default charset - see class Javadoc
    111    }
    112
    113    /**
    114     * Delegate to local file data.
    115     * @return the centralDirectory length
    116     */
    117    @Override
    118    public ZipShort getCentralDirectoryLength() {
    119        return getLocalFileDataLength();
    120    }
    121
    122    /**
    123     * The actual data to put into local file data - without Header-ID
    124     * or length specifier.
    125     * @return get the data
    126     */
    127    @Override
    128    public byte[] getLocalFileDataData() {
    129        // CRC will be added later
    130        final byte[] data = new byte[getLocalFileDataLength().getValue() - WORD];
    131        System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2);
    132
    133        final byte[] linkArray = getLinkedFile().getBytes(); // Uses default charset - see class Javadoc
    134        // CheckStyle:MagicNumber OFF
    135        System.arraycopy(ZipLong.getBytes(linkArray.length),
    136                         0, data, 2, WORD);
    137
    138        System.arraycopy(ZipShort.getBytes(getUserId()),
    139                         0, data, 6, 2);
    140        System.arraycopy(ZipShort.getBytes(getGroupId()),
    141                         0, data, 8, 2);
    142
    143        System.arraycopy(linkArray, 0, data, 10, linkArray.length);
    144        // CheckStyle:MagicNumber ON
    145
    146        crc.reset();
    147        crc.update(data);
    148        final long checksum = crc.getValue();
    149
    150        final byte[] result = new byte[data.length + WORD];
    151        System.arraycopy(ZipLong.getBytes(checksum), 0, result, 0, WORD);
    152        System.arraycopy(data, 0, result, WORD, data.length);
    153        return result;
    154    }
    155
    156    /**
    157     * Delegate to local file data.
    158     * @return the local file data
    159     */
    160    @Override
    161    public byte[] getCentralDirectoryData() {
    162        return getLocalFileDataData();
    163    }
    164
    165    /**
    166     * Set the user id.
    167     * @param uid the user id
    168     */
    169    public void setUserId(final int uid) {
    170        this.uid = uid;
    171    }
    172
    173    /**
    174     * Get the user id.
    175     * @return the user id
    176     */
    177    public int getUserId() {
    178        return uid;
    179    }
    180
    181    /**
    182     * Set the group id.
    183     * @param gid the group id
    184     */
    185    public void setGroupId(final int gid) {
    186        this.gid = gid;
    187    }
    188
    189    /**
    190     * Get the group id.
    191     * @return the group id
    192     */
    193    public int getGroupId() {
    194        return gid;
    195    }
    196
    197    /**
    198     * Indicate that this entry is a symbolic link to the given filename.
    199     *
    200     * @param name Name of the file this entry links to, empty String
    201     *             if it is not a symbolic link.
    202     */
    203    public void setLinkedFile(final String name) {
    204        link = name;
    205        mode = getMode(mode);
    206    }
    207
    208    /**
    209     * Name of linked file
    210     *
    211     * @return name of the file this entry links to if it is a
    212     *         symbolic link, the empty string otherwise.
    213     */
    214    public String getLinkedFile() {
    215        return link;
    216    }
    217
    218    /**
    219     * Is this entry a symbolic link?
    220     * @return true if this is a symbolic link
    221     */
    222    public boolean isLink() {
    223        return getLinkedFile().length() != 0;
    224    }
    225
    226    /**
    227     * File mode of this file.
    228     * @param mode the file mode
    229     */
    230    public void setMode(final int mode) {
    231        this.mode = getMode(mode);
    232    }
    233
    234    /**
    235     * File mode of this file.
    236     * @return the file mode
    237     */
    238    public int getMode() {
    239        return mode;
    240    }
    241
    242    /**
    243     * Indicate whether this entry is a directory.
    244     * @param dirFlag if true, this entry is a directory
    245     */
    246    public void setDirectory(final boolean dirFlag) {
    247        this.dirFlag = dirFlag;
    248        mode = getMode(mode);
    249    }
    250
    251    /**
    252     * Is this entry a directory?
    253     * @return true if this entry is a directory
    254     */
    255    public boolean isDirectory() {
    256        return dirFlag && !isLink();
    257    }
    258
    259    /**
    260     * Populate data from this array as if it was in local file data.
    261     * @param data an array of bytes
    262     * @param offset the start offset
    263     * @param length the number of bytes in the array from offset
    264     * @throws ZipException on error
    265     */
    266    @Override
    267    public void parseFromLocalFileData(final byte[] data, final int offset, final int length)
    268        throws ZipException {
    269
    270        final long givenChecksum = ZipLong.getValue(data, offset);
    271        final byte[] tmp = new byte[length - WORD];
    272        System.arraycopy(data, offset + WORD, tmp, 0, length - WORD);
    273        crc.reset();
    274        crc.update(tmp);
    275        final long realChecksum = crc.getValue();
    276        if (givenChecksum != realChecksum) {
    277            throw new ZipException("bad CRC checksum "
    278                                   + Long.toHexString(givenChecksum)
    279                                   + " instead of "
    280                                   + Long.toHexString(realChecksum));
    281        }
    282
    283        final int newMode = ZipShort.getValue(tmp, 0);
    284        // CheckStyle:MagicNumber OFF
    285        final byte[] linkArray = new byte[(int) ZipLong.getValue(tmp, 2)];
    286        uid = ZipShort.getValue(tmp, 6);
    287        gid = ZipShort.getValue(tmp, 8);
    288
    289        if (linkArray.length == 0) {
    290            link = "";
    291        } else {
    292            System.arraycopy(tmp, 10, linkArray, 0, linkArray.length);
    293            link = new String(linkArray); // Uses default charset - see class Javadoc
    294        }
    295        // CheckStyle:MagicNumber ON
    296        setDirectory((newMode & DIR_FLAG) != 0);
    297        setMode(newMode);
    298    }
    299
    300    /**
    301     * Doesn't do anything special since this class always uses the
    302     * same data in central directory and local file data.
    303     */
    304    @Override
    305    public void parseFromCentralDirectoryData(final byte[] buffer, final int offset,
    306                                              final int length)
    307        throws ZipException {
    308        parseFromLocalFileData(buffer, offset, length);
    309    }
    310
    311    /**
    312     * Get the file mode for given permissions with the correct file type.
    313     * @param mode the mode
    314     * @return the type with the mode
    315     */
    316    protected int getMode(final int mode) {
    317        int type = FILE_FLAG;
    318        if (isLink()) {
    319            type = LINK_FLAG;
    320        } else if (isDirectory()) {
    321            type = DIR_FLAG;
    322        }
    323        return type | (mode & PERM_MASK);
    324    }
    325
    326    @Override
    327    public Object clone() {
    328        try {
    329            final AsiExtraField cloned = (AsiExtraField) super.clone();
    330            cloned.crc = new CRC32();
    331            return cloned;
    332        } catch (final CloneNotSupportedException cnfe) {
    333            // impossible
    334            throw new RuntimeException(cnfe);
    335        }
    336    }
    337}