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