    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.io.Serializable;
    022import java.math.BigInteger;
    023import java.util.Arrays;
    024import java.util.zip.ZipException;
    025
    026import org.apache.commons.compress.utils.ByteUtils;
    027
    028import static org.apache.commons.compress.archivers.zip.ZipUtil.reverse;
    029import static org.apache.commons.compress.archivers.zip.ZipUtil.signedByteToUnsignedInt;
    030import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte;
    031
    032/**
    033 * An extra field that stores UNIX UID/GID data (owner &amp; group ownership) for a given
    034 * zip entry.  We're using the field definition given in Info-Zip's source archive:
    035 * zip-3.0.tar.gz/proginfo/extrafld.txt
    036 *
    037 * <pre>
    038 * Local-header version:
    039 *
    040 * Value         Size        Description
    041 * -----         ----        -----------
    042 * 0x7875        Short       tag for this extra block type ("ux")
    043 * TSize         Short       total data size for this block
    044 * Version       1 byte      version of this extra field, currently 1
    045 * UIDSize       1 byte      Size of UID field
    046 * UID           Variable    UID for this entry (little endian)
    047 * GIDSize       1 byte      Size of GID field
    048 * GID           Variable    GID for this entry (little endian)
    049 *
    050 * Central-header version:
    051 *
    052 * Value         Size        Description
    053 * -----         ----        -----------
    054 * 0x7855        Short       tag for this extra block type ("Ux")
    055 * TSize         Short       total data size for this block (0)
    056 * </pre>
    057 * @since 1.5
    058 */
    059public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable {
    060    private static final ZipShort HEADER_ID = new ZipShort(0x7875);
    061    private static final ZipShort ZERO = new ZipShort(0);
    062    private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000);
    063    private static final long serialVersionUID = 1L;
    064
    065    private int version = 1; // always '1' according to current info-zip spec.
    066
    067    // BigInteger helps us with little-endian / big-endian conversions.
    068    // (thanks to BigInteger.toByteArray() and a reverse() method we created).
    069    // Also, the spec theoretically allows UID/GID up to 255 bytes long!
    070    //
    071    // NOTE:  equals() and hashCode() currently assume these can never be null.
    072    private BigInteger uid;
    073    private BigInteger gid;
    074
    075    /**
    076     * Constructor for X7875_NewUnix.
    077     */
    078    public X7875_NewUnix() {
    079        reset();
    080    }
    081
    082    /**
    083     * The Header-ID.
    084     *
    085     * @return the value for the header id for this extrafield
    086     */
    087    @Override
    088    public ZipShort getHeaderId() {
    089        return HEADER_ID;
    090    }
    091
    092    /**
    093     * Gets the UID as a long.  UID is typically a 32 bit unsigned
    094     * value on most UNIX systems, so we return a long to avoid
    095     * integer overflow into the negatives in case values above
    096     * and including 2^31 are being used.
    097     *
    098     * @return the UID value.
    099     */
    100    public long getUID() { return ZipUtil.bigToLong(uid); }
    101
    102    /**
    103     * Gets the GID as a long.  GID is typically a 32 bit unsigned
    104     * value on most UNIX systems, so we return a long to avoid
    105     * integer overflow into the negatives in case values above
    106     * and including 2^31 are being used.
    107     *
    108     * @return the GID value.
    109     */
    110    public long getGID() { return ZipUtil.bigToLong(gid); }
    111
    112    /**
    113     * Sets the UID.
    114     *
    115     * @param l UID value to set on this extra field.
    116     */
    117    public void setUID(final long l) {
    118        this.uid = ZipUtil.longToBig(l);
    119    }
    120
    121    /**
    122     * Sets the GID.
    123     *
    124     * @param l GID value to set on this extra field.
    125     */
    126    public void setGID(final long l) {
    127        this.gid = ZipUtil.longToBig(l);
    128    }
    129
    130    /**
    131     * Length of the extra field in the local file data - without
    132     * Header-ID or length specifier.
    133     *
    134     * @return a <code>ZipShort</code> for the length of the data of this extra field
    135     */
    136    @Override
    137    public ZipShort getLocalFileDataLength() {
    138        byte[] b = trimLeadingZeroesForceMinLength(uid.toByteArray());
    139        final int uidSize = b == null ? 0 : b.length;
    140        b = trimLeadingZeroesForceMinLength(gid.toByteArray());
    141        final int gidSize = b == null ? 0 : b.length;
    142
    143        // The 3 comes from:  version=1 + uidsize=1 + gidsize=1
    144        return new ZipShort(3 + uidSize + gidSize);
    145    }
    146
    147    /**
    148     * Length of the extra field in the central directory data - without
    149     * Header-ID or length specifier.
    150     *
    151     * @return a <code>ZipShort</code> for the length of the data of this extra field
    152     */
    153    @Override
    154    public ZipShort getCentralDirectoryLength() {
    155        return ZERO;
    156    }
    157
    158    /**
    159     * The actual data to put into local file data - without Header-ID
    160     * or length specifier.
    161     *
    162     * @return get the data
    163     */
    164    @Override
    165    public byte[] getLocalFileDataData() {
    166        byte[] uidBytes = uid.toByteArray();
    167        byte[] gidBytes = gid.toByteArray();
    168
    169        // BigInteger might prepend a leading-zero to force a positive representation
    170        // (e.g., so that the sign-bit is set to zero).  We need to remove that
    171        // before sending the number over the wire.
    172        uidBytes = trimLeadingZeroesForceMinLength(uidBytes);
    173        final int uidBytesLen = uidBytes != null ? uidBytes.length : 0;
    174        gidBytes = trimLeadingZeroesForceMinLength(gidBytes);
    175        final int gidBytesLen = gidBytes != null ? gidBytes.length : 0;
    176
    177        // Couldn't bring myself to just call getLocalFileDataLength() when we've
    178        // already got the arrays right here.  Yeah, yeah, I know, premature
    179        // optimization is the root of all...
    180        //
    181        // The 3 comes from:  version=1 + uidsize=1 + gidsize=1
    182        final byte[] data = new byte[3 + uidBytesLen + gidBytesLen];
    183
    184        // reverse() switches byte array from big-endian to little-endian.
    185        if (uidBytes != null) {
    186            reverse(uidBytes);
    187        }
    188        if (gidBytes != null) {
    189            reverse(gidBytes);
    190        }
    191
    192        int pos = 0;
    193        data[pos++] = unsignedIntToSignedByte(version);
    194        data[pos++] = unsignedIntToSignedByte(uidBytesLen);
    195        if (uidBytes != null) {
    196            System.arraycopy(uidBytes, 0, data, pos, uidBytesLen);
    197        }
    198        pos += uidBytesLen;
    199        data[pos++] = unsignedIntToSignedByte(gidBytesLen);
    200        if (gidBytes != null) {
    201            System.arraycopy(gidBytes, 0, data, pos, gidBytesLen);
    202        }
    203        return data;
    204    }
    205
    206    /**
    207     * The actual data to put into central directory data - without Header-ID
    208     * or length specifier.
    209     *
    210     * @return get the data
    211     */
    212    @Override
    213    public byte[] getCentralDirectoryData() {
    214        return ByteUtils.EMPTY_BYTE_ARRAY;
    215    }
    216
    217    /**
    218     * Populate data from this array as if it was in local file data.
    219     *
    220     * @param data   an array of bytes
    221     * @param offset the start offset
    222     * @param length the number of bytes in the array from offset
    223     * @throws java.util.zip.ZipException on error
    224     */
    225    @Override
    226    public void parseFromLocalFileData(
    227            final byte[] data, int offset, final int length
    228    ) throws ZipException {
    229        reset();
    230        if (length < 3) {
    231            throw new ZipException("X7875_NewUnix length is too short, only "
    232                + length + " bytes");
    233        }
    234        this.version = signedByteToUnsignedInt(data[offset++]);
    235        final int uidSize = signedByteToUnsignedInt(data[offset++]);
    236        if (uidSize + 3 > length) {
    237            throw new ZipException("X7875_NewUnix invalid: uidSize " + uidSize
    238                + " doesn't fit into " + length + " bytes");
    239        }
    240        final byte[] uidBytes = Arrays.copyOfRange(data, offset, offset + uidSize);
    241        offset += uidSize;
    242        this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive
    243
    244        final int gidSize = signedByteToUnsignedInt(data[offset++]);
    245        if (uidSize + 3 + gidSize > length) {
    246            throw new ZipException("X7875_NewUnix invalid: gidSize " + gidSize
    247                + " doesn't fit into " + length + " bytes");
    248        }
    249        final byte[] gidBytes = Arrays.copyOfRange(data, offset, offset + gidSize);
    250        this.gid = new BigInteger(1, reverse(gidBytes)); // sign-bit forced positive
    251    }
    252
    253    /**
    254     * Doesn't do anything since this class doesn't store anything
    255     * inside the central directory.
    256     */
    257    @Override
    258    public void parseFromCentralDirectoryData(
    259            final byte[] buffer, final int offset, final int length
    260    ) throws ZipException {
    261    }
    262
    263    /**
    264     * Reset state back to newly constructed state.  Helps us make sure
    265     * parse() calls always generate clean results.
    266     */
    267    private void reset() {
    268        // Typical UID/GID of the first non-root user created on a unix system.
    269        uid = ONE_THOUSAND;
    270        gid = ONE_THOUSAND;
    271    }
    272
    273    /**
    274     * Returns a String representation of this class useful for
    275     * debugging purposes.
    276     *
    277     * @return A String representation of this class useful for
    278     *         debugging purposes.
    279     */
    280    @Override
    281    public String toString() {
    282        return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid;
    283    }
    284
    285    @Override
    286    public Object clone() throws CloneNotSupportedException {
    287        return super.clone();
    288    }
    289
    290    @Override
    291    public boolean equals(final Object o) {
    292        if (o instanceof X7875_NewUnix) {
    293            final X7875_NewUnix xf = (X7875_NewUnix) o;
    294            // We assume uid and gid can never be null.
    295            return version == xf.version && uid.equals(xf.uid) && gid.equals(xf.gid);
    296        }
    297        return false;
    298    }
    299
    300    @Override
    301    public int hashCode() {
    302        int hc = -1234567 * version;
    303        // Since most UID's and GID's are below 65,536, this is (hopefully!)
    304        // a nice way to make sure typical UID and GID values impact the hash
    305        // as much as possible.
    306        hc ^= Integer.rotateLeft(uid.hashCode(), 16);
    307        hc ^= gid.hashCode();
    308        return hc;
    309    }
    310
    311    /**
    312     * Not really for external usage, but marked "package" visibility
    313     * to help us JUnit it.   Trims a byte array of leading zeroes while
    314     * also enforcing a minimum length, and thus it really trims AND pads
    315     * at the same time.
    316     *
    317     * @param array byte[] array to trim & pad.
    318     * @return trimmed & padded byte[] array.
    319     */
    320    static byte[] trimLeadingZeroesForceMinLength(final byte[] array) {
    321        if (array == null) {
    322            return array;
    323        }
    324
    325        int pos = 0;
    326        for (final byte b : array) {
    327            if (b != 0) {
    328                break;
    329            }
    330            pos++;
    331        }
    332
    333        /*
    334
    335        I agonized over my choice of MIN_LENGTH=1.  Here's the situation:
    336        InfoZip (the tool I am using to test interop) always sets these
    337        to length=4.  And so a UID of 0 (typically root) for example is
    338        encoded as {4,0,0,0,0} (len=4, 32 bits of zero), when it could just
    339        as easily be encoded as {1,0} (len=1, 8 bits of zero) according to
    340        the spec.
    341
    342        In the end I decided on MIN_LENGTH=1 for four reasons:
    343
    344        1.)  We are adhering to the spec as far as I can tell, and so
    345             a consumer that cannot parse this is broken.
    346
    347        2.)  Fundamentally, zip files are about shrinking things, so
    348             let's save a few bytes per entry while we can.
    349
    350        3.)  Of all the people creating zip files using commons-
    351             compress, how many care about UNIX UID/GID attributes
    352             of the files they store?   (e.g., I am probably thinking
    353             way too hard about this and no one cares!)
    354
    355        4.)  InfoZip's tool, even though it carefully stores every UID/GID
    356             for every file zipped on a unix machine (by default) currently
    357             appears unable to ever restore UID/GID.
    358             unzip -X has no effect on my machine, even when run as root!!!!
    359
    360        And thus it is decided:  MIN_LENGTH=1.
    361
    362        If anyone runs into interop problems from this, feel free to set
    363        it to MIN_LENGTH=4 at some future time, and then we will behave
    364        exactly like InfoZip (requires changes to unit tests, though).
    365
    366        And I am sorry that the time you spent reading this comment is now
    367        gone and you can never have it back.
    368
    369        */
    370        final int MIN_LENGTH = 1;
    371
    372        final byte[] trimmedArray = new byte[Math.max(MIN_LENGTH, array.length - pos)];
    373        final int startPos = trimmedArray.length - (array.length - pos);
    374        System.arraycopy(array, pos, trimmedArray, startPos, trimmedArray.length - startPos);
    375        return trimmedArray;
    376    }
    377}