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