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