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 static org.apache.commons.compress.archivers.zip.ZipUtil.reverse;
022import static org.apache.commons.compress.archivers.zip.ZipUtil.signedByteToUnsignedInt;
023import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte;
024
025import java.io.Serializable;
026import java.math.BigInteger;
027import java.util.Arrays;
028import java.util.zip.ZipException;
029
030import org.apache.commons.compress.utils.ByteUtils;
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         *
087         * 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
088         * 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,
089         * 8 bits of zero) according to the spec.
090         *
091         * In the end I decided on MIN_LENGTH=1 for four reasons:
092         *
093         * 1.) We are adhering to the spec as far as I can tell, and so a consumer that cannot parse this is broken.
094         *
095         * 2.) Fundamentally, ZIP files are about shrinking things, so let's save a few bytes per entry while we can.
096         *
097         * 3.) Of all the people creating ZIP files using commons- compress, how many care about UNIX UID/GID attributes of the files they store? (e.g., I am
098         * probably thinking way too hard about this and no one cares!)
099         *
100         * 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
101         * ever restore UID/GID. unzip -X has no effect on my machine, even when run as root!!!!
102         *
103         * And thus it is decided: MIN_LENGTH=1.
104         *
105         * 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
106         * (requires changes to unit tests, though).
107         *
108         * And I am sorry that the time you spent reading this comment is now gone, and you can never have it back.
109         */
110        final int MIN_LENGTH = 1;
111
112        final byte[] trimmedArray = new byte[Math.max(MIN_LENGTH, array.length - pos)];
113        final int startPos = trimmedArray.length - (array.length - pos);
114        System.arraycopy(array, pos, trimmedArray, startPos, trimmedArray.length - startPos);
115        return trimmedArray;
116    }
117
118    private int version = 1; // always '1' according to current info-zip spec.
119    // BigInteger helps us with little-endian / big-endian conversions.
120    // (thanks to BigInteger.toByteArray() and a reverse() method we created).
121    // Also, the spec theoretically allows UID/GID up to 255 bytes long!
122    //
123    // NOTE: equals() and hashCode() currently assume these can never be null.
124    private BigInteger uid;
125
126    private BigInteger gid;
127
128    /**
129     * Constructor for X7875_NewUnix.
130     */
131    public X7875_NewUnix() {
132        reset();
133    }
134
135    @Override
136    public Object clone() throws CloneNotSupportedException {
137        return super.clone();
138    }
139
140    @Override
141    public boolean equals(final Object o) {
142        if (o instanceof X7875_NewUnix) {
143            final X7875_NewUnix xf = (X7875_NewUnix) o;
144            // We assume uid and gid can never be null.
145            return version == xf.version && uid.equals(xf.uid) && gid.equals(xf.gid);
146        }
147        return false;
148    }
149
150    /**
151     * The actual data to put into central directory data - without Header-ID or length specifier.
152     *
153     * @return get the data
154     */
155    @Override
156    public byte[] getCentralDirectoryData() {
157        return ByteUtils.EMPTY_BYTE_ARRAY;
158    }
159
160    /**
161     * Length of the extra field in the central directory data - without Header-ID or length specifier.
162     *
163     * @return a {@code ZipShort} for the length of the data of this extra field
164     */
165    @Override
166    public ZipShort getCentralDirectoryLength() {
167        return ZERO;
168    }
169
170    /**
171     * 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
172     * in case values above and including 2^31 are being used.
173     *
174     * @return the GID value.
175     */
176    public long getGID() {
177        return ZipUtil.bigToLong(gid);
178    }
179
180    /**
181     * The Header-ID.
182     *
183     * @return the value for the header id for this extrafield
184     */
185    @Override
186    public ZipShort getHeaderId() {
187        return HEADER_ID;
188    }
189
190    /**
191     * The actual data to put into local file data - without Header-ID or length specifier.
192     *
193     * @return get the data
194     */
195    @Override
196    public byte[] getLocalFileDataData() {
197        byte[] uidBytes = uid.toByteArray();
198        byte[] gidBytes = gid.toByteArray();
199
200        // BigInteger might prepend a leading-zero to force a positive representation
201        // (e.g., so that the sign-bit is set to zero). We need to remove that
202        // before sending the number over the wire.
203        uidBytes = trimLeadingZeroesForceMinLength(uidBytes);
204        final int uidBytesLen = uidBytes != null ? uidBytes.length : 0;
205        gidBytes = trimLeadingZeroesForceMinLength(gidBytes);
206        final int gidBytesLen = gidBytes != null ? gidBytes.length : 0;
207
208        // Couldn't bring myself to just call getLocalFileDataLength() when we've
209        // already got the arrays right here. Yeah, yeah, I know, premature
210        // optimization is the root of all...
211        //
212        // The 3 comes from: version=1 + uidsize=1 + gidsize=1
213        final byte[] data = new byte[3 + uidBytesLen + gidBytesLen];
214
215        // reverse() switches byte array from big-endian to little-endian.
216        if (uidBytes != null) {
217            reverse(uidBytes);
218        }
219        if (gidBytes != null) {
220            reverse(gidBytes);
221        }
222
223        int pos = 0;
224        data[pos++] = unsignedIntToSignedByte(version);
225        data[pos++] = unsignedIntToSignedByte(uidBytesLen);
226        if (uidBytes != null) {
227            System.arraycopy(uidBytes, 0, data, pos, uidBytesLen);
228        }
229        pos += uidBytesLen;
230        data[pos++] = unsignedIntToSignedByte(gidBytesLen);
231        if (gidBytes != null) {
232            System.arraycopy(gidBytes, 0, data, pos, gidBytesLen);
233        }
234        return data;
235    }
236
237    /**
238     * Length of the extra field in the local file data - without Header-ID or length specifier.
239     *
240     * @return a {@code ZipShort} for the length of the data of this extra field
241     */
242    @Override
243    public ZipShort getLocalFileDataLength() {
244        byte[] b = trimLeadingZeroesForceMinLength(uid.toByteArray());
245        final int uidSize = b == null ? 0 : b.length;
246        b = trimLeadingZeroesForceMinLength(gid.toByteArray());
247        final int gidSize = b == null ? 0 : b.length;
248
249        // The 3 comes from: version=1 + uidsize=1 + gidsize=1
250        return new ZipShort(3 + uidSize + gidSize);
251    }
252
253    /**
254     * 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
255     * in case values above and including 2^31 are being used.
256     *
257     * @return the UID value.
258     */
259    public long getUID() {
260        return ZipUtil.bigToLong(uid);
261    }
262
263    @Override
264    public int hashCode() {
265        int hc = -1234567 * version;
266        // Since most UIDs and GIDs are below 65,536, this is (hopefully!)
267        // a nice way to make sure typical UID and GID values impact the hash
268        // as much as possible.
269        hc ^= Integer.rotateLeft(uid.hashCode(), 16);
270        hc ^= gid.hashCode();
271        return hc;
272    }
273
274    /**
275     * Doesn't do anything since this class doesn't store anything inside the central directory.
276     */
277    @Override
278    public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
279    }
280
281    /**
282     * Populate data from this array as if it was in local file data.
283     *
284     * @param data   an array of bytes
285     * @param offset the start offset
286     * @param length the number of bytes in the array from offset
287     * @throws java.util.zip.ZipException on error
288     */
289    @Override
290    public void parseFromLocalFileData(final byte[] data, int offset, final int length) throws ZipException {
291        reset();
292        if (length < 3) {
293            throw new ZipException("X7875_NewUnix length is too short, only " + length + " bytes");
294        }
295        this.version = signedByteToUnsignedInt(data[offset++]);
296        final int uidSize = signedByteToUnsignedInt(data[offset++]);
297        if (uidSize + 3 > length) {
298            throw new ZipException("X7875_NewUnix invalid: uidSize " + uidSize + " doesn't fit into " + length + " bytes");
299        }
300        final byte[] uidBytes = Arrays.copyOfRange(data, offset, offset + uidSize);
301        offset += uidSize;
302        this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive
303
304        final int gidSize = signedByteToUnsignedInt(data[offset++]);
305        if (uidSize + 3 + gidSize > length) {
306            throw new ZipException("X7875_NewUnix invalid: gidSize " + gidSize + " doesn't fit into " + length + " bytes");
307        }
308        final byte[] gidBytes = Arrays.copyOfRange(data, offset, offset + gidSize);
309        this.gid = new BigInteger(1, reverse(gidBytes)); // sign-bit forced positive
310    }
311
312    /**
313     * Reset state back to newly constructed state. Helps us make sure parse() calls always generate clean results.
314     */
315    private void reset() {
316        // Typical UID/GID of the first non-root user created on a Unix system.
317        uid = ONE_THOUSAND;
318        gid = ONE_THOUSAND;
319    }
320
321    /**
322     * Sets the GID.
323     *
324     * @param l GID value to set on this extra field.
325     */
326    public void setGID(final long l) {
327        this.gid = ZipUtil.longToBig(l);
328    }
329
330    /**
331     * Sets the UID.
332     *
333     * @param l UID value to set on this extra field.
334     */
335    public void setUID(final long l) {
336        this.uid = ZipUtil.longToBig(l);
337    }
338
339    /**
340     * Returns a String representation of this class useful for debugging purposes.
341     *
342     * @return A String representation of this class useful for debugging purposes.
343     */
344    @Override
345    public String toString() {
346        return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid;
347    }
348}