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