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