You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ju...@apache.org on 2013/01/08 01:13:18 UTC

svn commit: r1430102 - in /commons/proper/compress/trunk/src: main/java/org/apache/commons/compress/archivers/zip/ test/java/org/apache/commons/compress/archivers/zip/ test/resources/

Author: julius
Date: Tue Jan  8 00:13:18 2013
New Revision: 1430102

URL: http://svn.apache.org/viewvc?rev=1430102&view=rev
Log:
COMPRESS-211 - handle zip extra field 0x7875 - Info Zip New Unix Extra Field

Added:
    commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/X7875_NewUnix.java
    commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/X7875_NewUnixTest.java
    commons/proper/compress/trunk/src/test/resources/COMPRESS-211_uid_gid_zip_test.zip   (with props)
Modified:
    commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
    commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipUtil.java
    commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipUtilTest.java

Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java?rev=1430102&r1=1430101&r2=1430102&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java (original)
+++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtils.java Tue Jan  8 00:13:18 2013
@@ -40,6 +40,7 @@ public class ExtraFieldUtils {
     static {
         implementations = new ConcurrentHashMap<ZipShort, Class<?>>();
         register(AsiExtraField.class);
+        register(X7875_NewUnix.class);
         register(JarMarker.class);
         register(UnicodePathExtraField.class);
         register(UnicodeCommentExtraField.class);

Added: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/X7875_NewUnix.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/X7875_NewUnix.java?rev=1430102&view=auto
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/X7875_NewUnix.java (added)
+++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/X7875_NewUnix.java Tue Jan  8 00:13:18 2013
@@ -0,0 +1,339 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.compress.archivers.zip;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.util.zip.ZipException;
+
+import static org.apache.commons.compress.archivers.zip.ZipUtil.reverse;
+import static org.apache.commons.compress.archivers.zip.ZipUtil.signedByteToUnsignedInt;
+import static org.apache.commons.compress.archivers.zip.ZipUtil.unsignedIntToSignedByte;
+
+/**
+ * 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 archive:
+ * zip-3.0.tar.gz/proginfo/extrafld.txt
+ * <p/>
+ * <pre>
+ * Value         Size        Description
+ * -----         ----        -----------
+ * 0x7875        Short       tag for this extra block type ("ux")
+ * TSize         Short       total data size for this block
+ * Version       1 byte      version of this extra field, currently 1
+ * UIDSize       1 byte      Size of UID field
+ * UID           Variable    UID for this entry (little endian)
+ * GIDSize       1 byte      Size of GID field
+ * GID           Variable    GID for this entry (little endian)
+ * </pre>
+ */
+public class X7875_NewUnix implements ZipExtraField, Cloneable, Serializable {
+    private static final ZipShort HEADER_ID = new ZipShort(0x7875);
+    private static final BigInteger ONE_THOUSAND = BigInteger.valueOf(1000);
+    private static final long serialVersionUID = 1L;
+
+    private int version = 1; // always '1' according to current info-zip spec.
+
+    // BigInteger helps us with little-endian / big-endian conversions.
+    // (thanks to BigInteger.toByteArray() and a reverse() method we created).
+    // Also, the spec theoretically allows UID/GID up to 255 bytes long!
+    private BigInteger uid;
+    private BigInteger gid;
+
+    /**
+     * Constructor for X7875_NewUnix.
+     */
+    public X7875_NewUnix() {
+        reset();
+    }
+
+    /**
+     * The Header-ID.
+     *
+     * @return the value for the header id for this extrafield
+     */
+    public ZipShort getHeaderId() {
+        return HEADER_ID;
+    }
+
+    /**
+     * 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 in case values above
+     * and including 2^31 are being used.
+     *
+     * @return the UID value.
+     */
+    public long getUID() { return ZipUtil.bigToLong(uid); }
+
+    /**
+     * 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 in case values above
+     * and including 2^31 are being used.
+     *
+     * @return the GID value.
+     */
+    public long getGID() { return ZipUtil.bigToLong(gid); }
+
+    /**
+     * Sets the UID.
+     *
+     * @param l UID value to set on this extra field.
+     */
+    public void setUID(long l) {
+        this.uid = ZipUtil.longToBig(l);
+    }
+
+    /**
+     * Sets the GID.
+     *
+     * @param l GID value to set on this extra field.
+     */
+    public void setGID(long l) {
+        this.gid = ZipUtil.longToBig(l);
+    }
+
+    /**
+     * Length of the extra field in the local file data - without
+     * Header-ID or length specifier.
+     *
+     * @return a <code>ZipShort</code> for the length of the data of this extra field
+     */
+    public ZipShort getLocalFileDataLength() {
+        int uidSize = trimLeadingZeroesForceMinLength(uid.toByteArray()).length;
+        int gidSize = trimLeadingZeroesForceMinLength(gid.toByteArray()).length;
+
+        // The 3 comes from:  version=1 + uidsize=1 + gidsize=1
+        return new ZipShort(3 + uidSize + gidSize);
+    }
+
+    /**
+     * Length of the extra field in the central directory data - without
+     * Header-ID or length specifier.
+     *
+     * @return a <code>ZipShort</code> for the length of the data of this extra field
+     */
+    public ZipShort getCentralDirectoryLength() {
+        return getLocalFileDataLength();  // No different than local version.
+    }
+
+    /**
+     * The actual data to put into local file data - without Header-ID
+     * or length specifier.
+     *
+     * @return get the data
+     */
+    public byte[] getLocalFileDataData() {
+        byte[] uidBytes = uid.toByteArray();
+        byte[] gidBytes = gid.toByteArray();
+
+        // BigInteger might prepend a leading-zero to force a positive representation
+        // (e.g., so that the sign-bit is set to zero).  We need to remove that
+        // before sending the number over the wire.
+        uidBytes = trimLeadingZeroesForceMinLength(uidBytes);
+        gidBytes = trimLeadingZeroesForceMinLength(gidBytes);
+
+        // Couldn't bring myself to just call getLocalFileDataLength() when we've
+        // already got the arrays right here.  Yeah, yeah, I know, premature
+        // optimization is the root of all...
+        //
+        // The 3 comes from:  version=1 + uidsize=1 + gidsize=1
+        byte[] data = new byte[3 + uidBytes.length + gidBytes.length];
+
+        // reverse() switches byte array from big-endian to little-endian.
+        reverse(uidBytes);
+        reverse(gidBytes);
+
+        int pos = 0;
+        data[pos++] = unsignedIntToSignedByte(version);
+        data[pos++] = unsignedIntToSignedByte(uidBytes.length);
+        System.arraycopy(uidBytes, 0, data, pos, uidBytes.length);
+        pos += uidBytes.length;
+        data[pos++] = unsignedIntToSignedByte(gidBytes.length);
+        System.arraycopy(gidBytes, 0, data, pos, gidBytes.length);
+        return data;
+    }
+
+    /**
+     * The actual data to put into central directory data - without Header-ID
+     * or length specifier.
+     *
+     * @return get the data
+     */
+    public byte[] getCentralDirectoryData() {
+        return getLocalFileDataData();
+    }
+
+    /**
+     * Populate data from this array as if it was in local file data.
+     *
+     * @param data   an array of bytes
+     * @param offset the start offset
+     * @param length the number of bytes in the array from offset
+     * @throws java.util.zip.ZipException on error
+     */
+    public void parseFromLocalFileData(
+            byte[] data, int offset, int length
+    ) throws ZipException {
+        reset();
+        this.version = signedByteToUnsignedInt(data[offset++]);
+        int uidSize = signedByteToUnsignedInt(data[offset++]);
+        byte[] uidBytes = new byte[uidSize];
+        System.arraycopy(data, offset, uidBytes, 0, uidSize);
+        offset += uidSize;
+        this.uid = new BigInteger(1, reverse(uidBytes)); // sign-bit forced positive
+
+        int gidSize = signedByteToUnsignedInt(data[offset++]);
+        byte[] gidBytes = new byte[gidSize];
+        System.arraycopy(data, offset, gidBytes, 0, gidSize);
+        this.gid = new BigInteger(1, reverse(gidBytes)); // sign-bit forced positive
+    }
+
+    /**
+     * Doesn't do anything special since this class always uses the
+     * same data in central directory and local file data.
+     */
+    public void parseFromCentralDirectoryData(
+            byte[] buffer, int offset, int length
+    ) throws ZipException {
+        reset();
+        parseFromLocalFileData(buffer, offset, length);
+    }
+
+    /**
+     * Reset state back to newly constructed state.  Helps us make sure
+     * parse() calls always generate clean results.
+     */
+    private void reset() {
+        // Typical UID/GID of the first non-root user created on a unix system.
+        uid = ONE_THOUSAND;
+        gid = ONE_THOUSAND;
+    }
+
+    /**
+     * Returns a String representation of this class useful for
+     * debugging purposes.
+     *
+     * @return A String representation of this class useful for
+     *         debugging purposes.
+     */
+    public String toString() {
+        return "0x7875 Zip Extra Field: UID=" + uid + " GID=" + gid;
+    }
+
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+        return super.clone();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof X7875_NewUnix) {
+            X7875_NewUnix xf = (X7875_NewUnix) o;
+            if (version == xf.version) {
+                // The BigInteger==BigInteger clause handles the case where both are null.
+                if (uid == xf.uid || (uid != null && uid.equals(xf.uid))) {
+                    return gid == xf.gid || (gid != null && gid.equals(xf.gid));
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int hc = (-1234567 * version);
+        if (uid != null) {
+            hc ^= uid.hashCode();
+        }
+        if (gid != null) {
+            hc ^= gid.hashCode();
+        }
+        return hc;
+    }
+
+    /**
+     * 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 length, and thus it really trims AND pads
+     * at the same time.
+     *
+     * @param array byte[] array to trim & pad.
+     * @return trimmed & padded byte[] array.
+     */
+    static byte[] trimLeadingZeroesForceMinLength(byte[] array) {
+        if (array == null) {
+            return array;
+        }
+
+        int pos = 0;
+        for (byte b : array) {
+            if (b == 0) {
+                pos++;
+            } else {
+                break;
+            }
+        }
+
+        /*
+
+        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 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, 8 bits of zero) according to
+        the spec.
+
+        In the end I decided on MIN_LENGTH=1 for four reasons:
+
+        1.)  We are adhering to the spec as far as I can tell, and so
+             a consumer that cannot parse this is broken.
+
+        2.)  Fundamentally, zip files are about shrinking things, so
+             let's save a few bytes per entry while we can.
+
+        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 probably thinking
+             way too hard about this and no one cares!)
+
+        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 ever restore UID/GID.
+             unzip -X has no effect on my machine, even when run as root!!!!
+
+        And thus it is decided:  MIN_LENGTH=1.
+
+        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 (requires changes to unit tests, though).
+
+        And I am sorry that the time you spent reading this comment is now
+        gone and you can never have it back.
+
+        */
+        final int MIN_LENGTH = 1;
+
+        byte[] trimmedArray = new byte[Math.max(MIN_LENGTH, array.length - pos)];
+        int startPos = trimmedArray.length - (array.length - pos);
+        System.arraycopy(array, pos, trimmedArray, startPos, trimmedArray.length - startPos);
+        return trimmedArray;
+    }
+}

Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipUtil.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipUtil.java?rev=1430102&r1=1430101&r2=1430102&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipUtil.java (original)
+++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipUtil.java Tue Jan  8 00:13:18 2013
@@ -18,6 +18,7 @@
 package org.apache.commons.compress.archivers.zip;
 
 import java.io.IOException;
+import java.math.BigInteger;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.zip.CRC32;
@@ -71,9 +72,6 @@ public abstract class ZipUtil {
      * Assumes a negative integer really is a positive integer that
      * has wrapped around and re-creates the original value.
      *
-     * <p>This methods is no longer used as of Apache Commons Compress
-     * 1.3</p>
-     *
      * @param i the value to treat as unsigned int.
      * @return the unsigned int as a long.
      */
@@ -86,6 +84,96 @@ public abstract class ZipUtil {
     }
 
     /**
+     * Reverses a byte[] array.  Reverses in-place (thus provided array is
+     * mutated), but also returns same for convenience.
+     *
+     * @param array to reverse (mutated in-place, but also returned for
+     *        convenience).
+     *
+     * @return the reversed array (mutated in-place, but also returned for
+     *        convenience).
+     */
+    public static byte[] reverse(final byte[] array) {
+        final int z = array.length - 1; // position of last element
+        for (int i = 0; i < array.length / 2; i++) {
+            byte x = array[i];
+            array[i] = array[z - i];
+            array[z - i] = x;
+        }
+        return array;
+    }
+
+    /**
+     * Converts a BigInteger into a long, and blows up
+     * (NumberFormatException) if the BigInteger is too big.
+     *
+     * @param big BigInteger to convert.
+     * @return long representation of the BigInteger.
+     */
+    static long bigToLong(BigInteger big) {
+        if (big.bitLength() <= 63) { // bitLength() doesn't count the sign bit.
+            return big.longValue();
+        } else {
+            throw new NumberFormatException("The BigInteger cannot fit inside a 64 bit java long: [" + big + "]");
+        }
+    }
+
+    /**
+     * <p>
+     * Converts a long into a BigInteger.  Negative numbers between -1 and
+     * -2^31 are treated as unsigned 32 bit (e.g., positive) integers.
+     * Negative numbers below -2^31 cause an IllegalArgumentException
+     * to be thrown.
+     * </p>
+     *
+     * @param l long to convert to BigInteger.
+     * @return BigInteger representation of the provided long.
+     */
+    static BigInteger longToBig(long l) {
+        if (l < Integer.MIN_VALUE) {
+            throw new IllegalArgumentException("Negative longs < -2^31 not permitted: [" + l + "]");
+        } else if (l < 0 && l >= Integer.MIN_VALUE) {
+            // If someone passes in a -2, they probably mean 4294967294
+            // (For example, Unix UID/GID's are 32 bit unsigned.)
+            l = ZipUtil.adjustToLong((int) l);
+        }
+        return BigInteger.valueOf(l);
+    }
+
+    /**
+     * Converts a signed byte into an unsigned integer representation
+     * (e.g., -1 becomes 255).
+     *
+     * @param b byte to convert to int
+     * @return int representation of the provided byte
+     */
+    public static int signedByteToUnsignedInt(byte b) {
+        if (b >= 0) {
+            return b;
+        } else {
+            return 256 + b;
+        }
+    }
+
+    /**
+     * Converts an unsigned integer to a signed byte (e.g., 255 becomes -1).
+     *
+     * @param i integer to convert to byte
+     * @return byte representation of the provided int
+     * @throws IllegalArgumentException if the provided integer is not inside the range [0,255].
+     */
+    public static byte unsignedIntToSignedByte(int i) {
+        if (i > 255 || i < 0) {
+            throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]");
+        }
+        if (i < 128) {
+            return (byte) i;
+        } else {
+            return (byte) (i - 256);
+        }
+    }
+
+    /**
      * Convert a DOS date/time field to a Date object.
      *
      * @param zipDosTime contains the stored DOS time.

Added: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/X7875_NewUnixTest.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/X7875_NewUnixTest.java?rev=1430102&view=auto
==============================================================================
--- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/X7875_NewUnixTest.java (added)
+++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/X7875_NewUnixTest.java Tue Jan  8 00:13:18 2013
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.commons.compress.archivers.zip;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.zip.ZipException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class X7875_NewUnixTest {
+
+    private final static ZipShort X7875 = new ZipShort(0x7875);
+
+    private X7875_NewUnix xf;
+
+    @Before
+    public void before() {
+        xf = new X7875_NewUnix();
+    }
+
+
+    @Test
+    public void testSampleFile() throws Exception {
+        URL zip = getClass().getResource("/COMPRESS-211_uid_gid_zip_test.zip");
+        File archive = new File(new URI(zip.toString()));
+        ZipFile zf = null;
+
+        try {
+            zf = new ZipFile(archive);
+            Enumeration<ZipArchiveEntry> en = zf.getEntries();
+
+            // We expect EVERY entry of this zip file (dir & file) to
+            // contain extra field 0x7875.
+            while (en.hasMoreElements()) {
+
+                ZipArchiveEntry zae = en.nextElement();
+                String name = zae.getName();
+                X7875_NewUnix xf = (X7875_NewUnix) zae.getExtraField(X7875);
+
+                // The directory entry in the test zip file is uid/gid 1000.
+                long expected = 1000;
+                if (name.contains("uid555_gid555")) {
+                    expected = 555;
+                } else if (name.contains("uid5555_gid5555")) {
+                    expected = 5555;
+                } else if (name.contains("uid55555_gid55555")) {
+                    expected = 55555;
+                } else if (name.contains("uid555555_gid555555")) {
+                    expected = 555555;
+                } else if (name.contains("min_unix")) {
+                    expected = 0;
+                } else if (name.contains("max_unix")) {
+                    // 2^32-2 was the biggest UID/GID I could create on my linux!
+                    // (December 2012, linux kernel 3.4)
+                    expected = 0x100000000L - 2;
+                }
+                assertEquals(expected, xf.getUID());
+                assertEquals(expected, xf.getGID());
+            }
+        } finally {
+            if (zf != null) {
+                zf.close();
+            }
+        }
+    }
+
+    @Test
+    public void testGetHeaderId() {
+        assertEquals(X7875, xf.getHeaderId());
+    }
+
+    @Test
+    public void testMisc() throws Exception {
+        assertTrue(xf.toString().startsWith("0x7875 Zip Extra Field"));
+        Object o = xf.clone();
+        assertEquals(o.hashCode(), xf.hashCode());
+        assertTrue(xf.equals(o));
+    }
+
+    @Test
+    public void testTrimLeadingZeroesForceMinLength4() throws ZipException {
+        final byte[] NULL = null;
+        final byte[] EMPTY = new byte[0];
+        final byte[] ONE_ZERO = {0};
+        final byte[] TWO_ZEROES = {0, 0};
+        final byte[] FOUR_ZEROES = {0, 0, 0, 0};
+        final byte[] SEQUENCE = {1, 2, 3};
+        final byte[] SEQUENCE_LEADING_ZERO = {0, 1, 2, 3};
+        final byte[] SEQUENCE_LEADING_ZEROES = {0, 0, 0, 0, 0, 0, 0, 1, 2, 3};
+        final byte[] TRAILING_ZERO = {1, 2, 3, 0};
+        final byte[] PADDING_ZERO = {0, 1, 2, 3, 0};
+        final byte[] SEQUENCE6 = {1, 2, 3, 4, 5, 6};
+        final byte[] SEQUENCE6_LEADING_ZERO = {0, 1, 2, 3, 4, 5, 6};
+
+        assertTrue(NULL == trimTest(NULL));
+        assertTrue(Arrays.equals(ONE_ZERO, trimTest(EMPTY)));
+        assertTrue(Arrays.equals(ONE_ZERO, trimTest(ONE_ZERO)));
+        assertTrue(Arrays.equals(ONE_ZERO, trimTest(TWO_ZEROES)));
+        assertTrue(Arrays.equals(ONE_ZERO, trimTest(FOUR_ZEROES)));
+        assertTrue(Arrays.equals(SEQUENCE, trimTest(SEQUENCE)));
+        assertTrue(Arrays.equals(SEQUENCE, trimTest(SEQUENCE_LEADING_ZERO)));
+        assertTrue(Arrays.equals(SEQUENCE, trimTest(SEQUENCE_LEADING_ZEROES)));
+        assertTrue(Arrays.equals(TRAILING_ZERO, trimTest(TRAILING_ZERO)));
+        assertTrue(Arrays.equals(TRAILING_ZERO, trimTest(PADDING_ZERO)));
+        assertTrue(Arrays.equals(SEQUENCE6, trimTest(SEQUENCE6)));
+        assertTrue(Arrays.equals(SEQUENCE6, trimTest(SEQUENCE6_LEADING_ZERO)));
+    }
+
+    private static byte[] trimTest(byte[] b) { return X7875_NewUnix.trimLeadingZeroesForceMinLength(b); }
+
+    @Test
+    public void testParseReparse() throws ZipException {
+
+        // Version=1, Len=1, zero, Len=1, zero.
+        final byte[] ZERO_UID_GID = {1, 1, 0, 1, 0};
+
+        // Version=1, Len=1, one, Len=1, one
+        final byte[] ONE_UID_GID = {1, 1, 1, 1, 1};
+
+        // Version=1, Len=2, one thousand, Len=2, one thousand
+        final byte[] ONE_THOUSAND_UID_GID = {1, 2, -24, 3, 2, -24, 3};
+
+        // (2^32 - 2).   I guess they avoid (2^32 - 1) since it's identical to -1 in
+        // two's complement, and -1 often has a special meaning.
+        final byte[] UNIX_MAX_UID_GID = {1, 4, -2, -1, -1, -1, 4, -2, -1, -1, -1};
+
+        // Version=1, Len=5, 2^32, Len=5, 2^32 + 1
+        // Esoteric test:  can we handle 40 bit numbers?
+        final byte[] LENGTH_5 = {1, 5, 0, 0, 0, 0, 1, 5, 1, 0, 0, 0, 1};
+
+        // Version=1, Len=8, 2^63 - 2, Len=8, 2^63 - 1
+        // Esoteric test:  can we handle 64 bit numbers?
+        final byte[] LENGTH_8 = {1, 8, -2, -1, -1, -1, -1, -1, -1, 127, 8, -1, -1, -1, -1, -1, -1, -1, 127};
+
+        final long TWO_TO_32 = 0x100000000L;
+        final long MAX = TWO_TO_32 - 2;
+
+        parseReparse(0, 0, ZERO_UID_GID, 0, 0);
+        parseReparse(1, 1, ONE_UID_GID, 1, 1);
+        parseReparse(1000, 1000, ONE_THOUSAND_UID_GID, 1000, 1000);
+        parseReparse(MAX, MAX, UNIX_MAX_UID_GID, MAX, MAX);
+        parseReparse(-2, -2, UNIX_MAX_UID_GID, MAX, MAX);
+        parseReparse(TWO_TO_32, TWO_TO_32 + 1, LENGTH_5, TWO_TO_32, TWO_TO_32 + 1);
+        parseReparse(Long.MAX_VALUE - 1, Long.MAX_VALUE, LENGTH_8, Long.MAX_VALUE - 1, Long.MAX_VALUE);
+
+        // We never emit this, but we should be able to parse it:
+        final byte[] SPURIOUS_ZEROES_1 = {1, 4, -1, 0, 0, 0, 4, -128, 0, 0, 0};
+        final byte[] EXPECTED_1 = {1, 1, -1, 1, -128};
+        xf.parseFromLocalFileData(SPURIOUS_ZEROES_1, 0, SPURIOUS_ZEROES_1.length);
+
+        assertEquals(255, xf.getUID());
+        assertEquals(128, xf.getGID());
+        assertTrue(Arrays.equals(EXPECTED_1, xf.getLocalFileDataData()));
+
+        final byte[] SPURIOUS_ZEROES_2 = {1, 4, -1, -1, 0, 0, 4, 1, 2, 0, 0};
+        final byte[] EXPECTED_2 = {1, 2, -1, -1, 2, 1, 2};
+        xf.parseFromLocalFileData(SPURIOUS_ZEROES_2, 0, SPURIOUS_ZEROES_2.length);
+
+        assertEquals(65535, xf.getUID());
+        assertEquals(513, xf.getGID());
+        assertTrue(Arrays.equals(EXPECTED_2, xf.getLocalFileDataData()));
+    }
+
+
+    private void parseReparse(
+            final long uid,
+            final long gid,
+            final byte[] expected,
+            final long expectedUID,
+            final long expectedGID
+    ) throws ZipException {
+        xf.setUID(uid);
+        xf.setGID(gid);
+        assertEquals(expected.length, xf.getLocalFileDataLength().getValue());
+        byte[] result = xf.getLocalFileDataData();
+        assertTrue(Arrays.equals(expected, result));
+
+        // And now we re-parse:
+        xf.parseFromLocalFileData(result, 0, result.length);
+
+        // Did uid/gid change from re-parse?  They shouldn't!
+        assertEquals(expectedUID, xf.getUID());
+        assertEquals(expectedGID, xf.getGID());
+
+        // Do the same as above, but with Central Directory data:
+        xf.setUID(uid);
+        xf.setGID(gid);
+        assertEquals(expected.length, xf.getCentralDirectoryLength().getValue());
+        result = xf.getCentralDirectoryData();
+        assertTrue(Arrays.equals(expected, result));
+
+        // And now we re-parse:
+        xf.parseFromCentralDirectoryData(result, 0, result.length);
+
+        // Did uid/gid change from 2nd re-parse?  They shouldn't!
+        assertEquals(expectedUID, xf.getUID());
+        assertEquals(expectedGID, xf.getGID());
+    }
+}

Modified: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipUtilTest.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipUtilTest.java?rev=1430102&r1=1430101&r2=1430102&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipUtilTest.java (original)
+++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipUtilTest.java Tue Jan  8 00:13:18 2013
@@ -18,11 +18,13 @@
 
 package org.apache.commons.compress.archivers.zip;
 
+import junit.framework.TestCase;
+
+import java.math.BigInteger;
+import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
 
-import junit.framework.TestCase;
-
 public class ZipUtilTest extends TestCase {
 
     private Date time;
@@ -83,4 +85,126 @@ public class ZipUtilTest extends TestCas
         byte[] b2 = ZipUtil.toDosTime(0); // get the same time
         assertEquals(b10,b2[0]); // first byte should still be the same
     }
+
+    public void testReverse() {
+        byte[][] bTest = new byte[6][];
+        bTest[0] = new byte[]{};
+        bTest[1] = new byte[]{1};
+        bTest[2] = new byte[]{1, 2};
+        bTest[3] = new byte[]{1, 2, 3};
+        bTest[4] = new byte[]{1, 2, 3, 4};
+        bTest[5] = new byte[]{1, 2, 3, 4, 5};
+
+        byte[][] rTest = new byte[6][];
+        rTest[0] = new byte[]{};
+        rTest[1] = new byte[]{1};
+        rTest[2] = new byte[]{2, 1};
+        rTest[3] = new byte[]{3, 2, 1};
+        rTest[4] = new byte[]{4, 3, 2, 1};
+        rTest[5] = new byte[]{5, 4, 3, 2, 1};
+
+        assertEquals("test and result arrays are same length", bTest.length, rTest.length);
+
+        for (int i = 0; i < bTest.length; i++) {
+            byte[] result = ZipUtil.reverse(bTest[i]);
+            assertTrue("reverse mutates in-place", bTest[i] == result);
+            assertTrue("reverse actually reverses", Arrays.equals(rTest[i], result));
+        }
+    }
+
+    public void testBigToLong() {
+        BigInteger big1 = BigInteger.valueOf(1);
+        BigInteger big2 = BigInteger.valueOf(Long.MAX_VALUE);
+        BigInteger big3 = BigInteger.valueOf(Long.MIN_VALUE);
+
+        assertEquals(1L, ZipUtil.bigToLong(big1));
+        assertEquals(Long.MAX_VALUE, ZipUtil.bigToLong(big2));
+        assertEquals(Long.MIN_VALUE, ZipUtil.bigToLong(big3));
+
+        BigInteger big4 = big2.add(big1);
+        try {
+            ZipUtil.bigToLong(big4);
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // All is good.
+        }
+
+        BigInteger big5 = big3.subtract(big1);
+        try {
+            ZipUtil.bigToLong(big5);
+            fail("ZipUtil.bigToLong(BigInteger) should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // All is good.
+        }
+    }
+
+    public void testLongToBig() {
+        long l0 = 0;
+        long l1 = 1;
+        long l2 = -1;
+        long l3 = Integer.MIN_VALUE;
+        long l4 = Long.MAX_VALUE;
+        long l5 = Long.MIN_VALUE;
+
+        BigInteger big0 = ZipUtil.longToBig(l0);
+        BigInteger big1 = ZipUtil.longToBig(l1);
+        BigInteger big2 = ZipUtil.longToBig(l2);
+        BigInteger big3 = ZipUtil.longToBig(l3);
+        BigInteger big4 = ZipUtil.longToBig(l4);
+
+        assertEquals(0, big0.longValue());
+        assertEquals(1, big1.longValue());
+        assertEquals(0xFFFFFFFFL, big2.longValue());
+        assertEquals(0x80000000L, big3.longValue());
+        assertEquals(Long.MAX_VALUE, big4.longValue());
+
+        try {
+            ZipUtil.longToBig(l5);
+            fail("ZipUtil.longToBig(long) should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+
+        }
+    }
+
+    public void testSignedByteToUnsignedInt() {
+        // Yay, we can completely test all possible input values in this case!
+        int expectedVal = 128;
+        for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++) {
+            byte b = (byte) i;
+            assertEquals(expectedVal, ZipUtil.signedByteToUnsignedInt(b));
+            expectedVal++;
+            if (expectedVal == 256) {
+                expectedVal = 0;
+            }
+        }
+    }
+
+    public void testUnsignedIntToSignedByte() {
+        int unsignedVal = 128;
+        for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++) {
+            byte expectedVal = (byte) i;
+            assertEquals(expectedVal, ZipUtil.unsignedIntToSignedByte(unsignedVal));
+            unsignedVal++;
+            if (unsignedVal == 256) {
+                unsignedVal = 0;
+            }
+        }
+
+        try {
+            ZipUtil.unsignedIntToSignedByte(-1);
+            fail("ZipUtil.unsignedIntToSignedByte(-1) should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // All is good.
+        }
+
+        try {
+            ZipUtil.unsignedIntToSignedByte(256);
+            fail("ZipUtil.unsignedIntToSignedByte(256) should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // All is good.
+        }
+
+    }
+
+
 }

Added: commons/proper/compress/trunk/src/test/resources/COMPRESS-211_uid_gid_zip_test.zip
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/resources/COMPRESS-211_uid_gid_zip_test.zip?rev=1430102&view=auto
==============================================================================
Binary file - no diff available.

Propchange: commons/proper/compress/trunk/src/test/resources/COMPRESS-211_uid_gid_zip_test.zip
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream