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