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