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 & 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}