Author: bodewig Date: Thu Jul 21 12:11:06 2011 New Revision: 1149139 URL: http://svn.apache.org/viewvc?rev=1149139&view=rev Log: very initial implementation of the extra field that holds Zip64 data. Many question marks. We'll likely be forced to change the way we deal with extra fields specifically for this one because of its parsing logic of optional data. COMPRESS-36
Added: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraField.java (with props) commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraFieldTest.java (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/ZipEightByteInteger.java commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipShort.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=1149139&r1=1149138&r2=1149139&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 Thu Jul 21 12:11:06 2011 @@ -43,6 +43,7 @@ public class ExtraFieldUtils { register(JarMarker.class); register(UnicodePathExtraField.class); register(UnicodeCommentExtraField.class); + register(Zip64ExtendedInformationExtraField.class); } /** Added: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraField.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraField.java?rev=1149139&view=auto ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraField.java (added) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraField.java Thu Jul 21 12:11:06 2011 @@ -0,0 +1,241 @@ +/* + * 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.util.zip.ZipException; + +/** + * Holds size and other extended information for entries that use Zip64 + * features. + * + * <p>From {@link http://www.pkware.com/documents/casestudies/APPNOTE.TXT PKWARE's APPNOTE.TXT} + * <pre> + * Zip64 Extended Information Extra Field (0x0001): + * + * The following is the layout of the zip64 extended + * information "extra" block. If one of the size or + * offset fields in the Local or Central directory + * record is too small to hold the required data, + * a Zip64 extended information record is created. + * The order of the fields in the zip64 extended + * information record is fixed, but the fields will + * only appear if the corresponding Local or Central + * directory record field is set to 0xFFFF or 0xFFFFFFFF. + * + * Note: all fields stored in Intel low-byte/high-byte order. + * + * Value Size Description + * ----- ---- ----------- + * (ZIP64) 0x0001 2 bytes Tag for this "extra" block type + * Size 2 bytes Size of this "extra" block + * Original + * Size 8 bytes Original uncompressed file size + * Compressed + * Size 8 bytes Size of compressed data + * Relative Header + * Offset 8 bytes Offset of local header record + * Disk Start + * Number 4 bytes Number of the disk on which + * this file starts + * + * This entry in the Local header must include BOTH original + * and compressed file size fields. If encrypting the + * central directory and bit 13 of the general purpose bit + * flag is set indicating masking, the value stored in the + * Local Header for the original file size will be zero. + * </pre></p> + * + * <p>Currently Commons Compress doesn't support encrypting the + * central directory so the not about masking doesn't apply.</p> + * + * <p>The implementation relies on data being read from the local file + * header and assumes that both size values are always present.</p> + * + * @since Apache Commons Compress 1.2 + * @NotThreadSafe + */ +public class Zip64ExtendedInformationExtraField implements ZipExtraField { + // TODO: the LFH should probably not contain relativeHeaderOffset + // and diskStart but then ZipArchivePOutputStream won't write it to + // the CD either - need to test interop with other implementations + // to see whether they do have a problem with the extraneous + // information inside the LFH + + private static final ZipShort HEADER_ID = new ZipShort(0x0001); + + private static final int WORD = 4, DWORD = 8; + + private ZipEightByteInteger size, compressedSize, relativeHeaderOffset; + private ZipLong diskStart; + + /** + * This constructor should only be used by the code that reads + * archives inside of Commons Compress. + */ + public Zip64ExtendedInformationExtraField() { } + + /** + * Creates an extra field based on the original and compressed size. + * + * @param size the entry's original size + * @param compressedSize the entry's compressed size + * + * @throws IllegalArgumentException if size or compressedSize is null + */ + public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, + ZipEightByteInteger compressedSize) { + this(size, compressedSize, null, null); + } + + /** + * Creates an extra field based on all four possible values. + * + * @param size the entry's original size + * @param compressedSize the entry's compressed size + * + * @throws IllegalArgumentException if size or compressedSize is null + */ + public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, + ZipEightByteInteger compressedSize, + ZipEightByteInteger relativeHeaderOffset, + ZipLong diskStart) { + if (size == null) { + throw new IllegalArgumentException("size must not be null"); + } + if (compressedSize == null) { + throw new IllegalArgumentException("compressedSize must not be null"); + } + this.size = size; + this.compressedSize = compressedSize; + this.relativeHeaderOffset = relativeHeaderOffset; + this.diskStart = diskStart; + } + + /** {@inheritDoc} */ + public ZipShort getHeaderId() { + return HEADER_ID; + } + + /** {@inheritDoc} */ + public ZipShort getLocalFileDataLength() { + return getCentralDirectoryLength(); + } + + /** {@inheritDoc} */ + public ZipShort getCentralDirectoryLength() { + return new ZipShort(2 * DWORD // both size fields + + (relativeHeaderOffset != null ? DWORD : 0) + + (diskStart != null ? WORD : 0)); + } + + /** {@inheritDoc} */ + public byte[] getLocalFileDataData() { + return getCentralDirectoryData(); + } + + /** {@inheritDoc} */ + public byte[] getCentralDirectoryData() { + byte[] data = new byte[getCentralDirectoryLength().getValue()]; + addSizes(data); + int off = 2 * DWORD; + if (relativeHeaderOffset != null) { + System.arraycopy(relativeHeaderOffset.getBytes(), 0, data, off, DWORD); + off += DWORD; + } + if (diskStart != null) { + System.arraycopy(diskStart.getBytes(), 0, data, off, WORD); + off += WORD; + } + return data; + } + + /** {@inheritDoc} */ + public void parseFromLocalFileData(byte[] buffer, int offset, int length) + throws ZipException { + if (length < 2 * DWORD) { + throw new ZipException("Zip64 extended information must contain" + + " both size values in the local file" + + " header."); + } + size = new ZipEightByteInteger(buffer, offset); + offset += DWORD; + compressedSize = new ZipEightByteInteger(buffer, offset); + offset += DWORD; + int remaining = length - 2 * DWORD; + if (remaining >= DWORD) { + relativeHeaderOffset = new ZipEightByteInteger(buffer, offset); + offset += DWORD; + remaining -= DWORD; + } + if (remaining >= WORD) { + diskStart = new ZipLong(buffer, offset); + offset += WORD; + remaining -= WORD; + } + } + + /** {@inheritDoc} */ + public void parseFromCentralDirectoryData(byte[] buffer, int offset, + int length) + throws ZipException { + // if there is no size information in here, we are screwed and + // can only hope things will get resolved by LFH data later + // But there are some cases that can be detected + // * all data is there + // * length % 8 == 4 -> at least we can identify the diskStart field + if (length >= 3 * DWORD + WORD) { + parseFromLocalFileData(buffer, offset, length); + } else if (length % DWORD == WORD) { + diskStart = new ZipLong(buffer, offset + length - WORD); + } + } + + /** + * The uncompressed size stored in this extra field. + */ + public ZipEightByteInteger getSize() { + return size; + } + + /** + * The compressed size stored in this extra field. + */ + public ZipEightByteInteger getCompressedSize() { + return compressedSize; + } + + /** + * The relative header offset stored in this extra field. + */ + public ZipEightByteInteger getRelativeHeaderOffset() { + return relativeHeaderOffset; + } + + /** + * The disk start number stored in this extra field. + */ + public ZipLong getDiskStartNumber() { + return diskStart; + } + + private void addSizes(byte[] data) { + System.arraycopy(size.getBytes(), 0, data, 0, DWORD); + System.arraycopy(compressedSize.getBytes(), 0, data, DWORD, DWORD); + } +} \ No newline at end of file Propchange: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraField.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipEightByteInteger.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipEightByteInteger.java?rev=1149139&r1=1149138&r2=1149139&view=diff ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipEightByteInteger.java (original) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipEightByteInteger.java Thu Jul 21 12:11:06 2011 @@ -220,4 +220,8 @@ public final class ZipEightByteInteger { public int hashCode() { return value.hashCode(); } + + public String toString() { + return "ZipEightByteInteger value: " + value; + } } Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java?rev=1149139&r1=1149138&r2=1149139&view=diff ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java (original) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipLong.java Thu Jul 21 12:11:06 2011 @@ -160,4 +160,8 @@ public final class ZipLong implements Cl throw new RuntimeException(cnfe); } } + + public String toString() { + return "ZipLong value: " + value; + } } Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipShort.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipShort.java?rev=1149139&r1=1149138&r2=1149139&view=diff ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipShort.java (original) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipShort.java Thu Jul 21 12:11:06 2011 @@ -134,4 +134,8 @@ public final class ZipShort implements C throw new RuntimeException(cnfe); } } + + public String toString() { + return "ZipShort value: " + value; + } } Added: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraFieldTest.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraFieldTest.java?rev=1149139&view=auto ============================================================================== --- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraFieldTest.java (added) +++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraFieldTest.java Thu Jul 21 12:11:06 2011 @@ -0,0 +1,200 @@ +/* + * 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.math.BigInteger; +import java.util.zip.ZipException; +import junit.framework.TestCase; + +public class Zip64ExtendedInformationExtraFieldTest extends TestCase { + public Zip64ExtendedInformationExtraFieldTest(String name) { + super(name); + } + + private static final ZipEightByteInteger SIZE = + new ZipEightByteInteger(0x12345678); + private static final ZipEightByteInteger CSIZE = + new ZipEightByteInteger(0x9ABCDEF); + private static final ZipEightByteInteger OFF = + new ZipEightByteInteger(BigInteger.valueOf(0xABCDEF091234567l) + .shiftLeft(4) + .setBit(3)); + private static final ZipLong DISK = new ZipLong(0x12); + + public void testWriteCDOnlySizes() { + Zip64ExtendedInformationExtraField f = + new Zip64ExtendedInformationExtraField(SIZE, CSIZE); + assertEquals(new ZipShort(16), f.getCentralDirectoryLength()); + byte[] b = f.getCentralDirectoryData(); + assertEquals(16, b.length); + checkSizes(b); + } + + public void testWriteCDSizeAndOffset() { + Zip64ExtendedInformationExtraField f = + new Zip64ExtendedInformationExtraField(SIZE, CSIZE, OFF, null); + assertEquals(new ZipShort(24), f.getCentralDirectoryLength()); + byte[] b = f.getCentralDirectoryData(); + assertEquals(24, b.length); + checkSizes(b); + checkOffset(b, 16); + } + + public void testWriteCDSizeOffsetAndDisk() { + Zip64ExtendedInformationExtraField f = + new Zip64ExtendedInformationExtraField(SIZE, CSIZE, OFF, DISK); + assertEquals(new ZipShort(28), f.getCentralDirectoryLength()); + byte[] b = f.getCentralDirectoryData(); + assertEquals(28, b.length); + checkSizes(b); + checkOffset(b, 16); + checkDisk(b, 24); + } + + public void testWriteCDSizeAndDisk() { + Zip64ExtendedInformationExtraField f = + new Zip64ExtendedInformationExtraField(SIZE, CSIZE, null, DISK); + assertEquals(new ZipShort(20), f.getCentralDirectoryLength()); + byte[] b = f.getCentralDirectoryData(); + assertEquals(20, b.length); + checkSizes(b); + checkDisk(b, 16); + } + + public void testReadLFHSizesOnly() throws ZipException { + Zip64ExtendedInformationExtraField f = + new Zip64ExtendedInformationExtraField(); + byte[] b = new byte[16]; + System.arraycopy(SIZE.getBytes(), 0, b, 0, 8); + System.arraycopy(CSIZE.getBytes(), 0, b, 8, 8); + f.parseFromLocalFileData(b, 0, b.length); + assertEquals(SIZE, f.getSize()); + assertEquals(CSIZE, f.getCompressedSize()); + assertNull(f.getRelativeHeaderOffset()); + assertNull(f.getDiskStartNumber()); + } + + public void testReadLFHSizesAndOffset() throws ZipException { + Zip64ExtendedInformationExtraField f = + new Zip64ExtendedInformationExtraField(); + byte[] b = new byte[24]; + System.arraycopy(SIZE.getBytes(), 0, b, 0, 8); + System.arraycopy(CSIZE.getBytes(), 0, b, 8, 8); + System.arraycopy(OFF.getBytes(), 0, b, 16, 8); + f.parseFromLocalFileData(b, 0, b.length); + assertEquals(SIZE, f.getSize()); + assertEquals(CSIZE, f.getCompressedSize()); + assertEquals(OFF, f.getRelativeHeaderOffset()); + assertNull(f.getDiskStartNumber()); + } + + public void testReadLFHSizesOffsetAndDisk() throws ZipException { + Zip64ExtendedInformationExtraField f = + new Zip64ExtendedInformationExtraField(); + byte[] b = new byte[28]; + System.arraycopy(SIZE.getBytes(), 0, b, 0, 8); + System.arraycopy(CSIZE.getBytes(), 0, b, 8, 8); + System.arraycopy(OFF.getBytes(), 0, b, 16, 8); + System.arraycopy(DISK.getBytes(), 0, b, 24, 4); + f.parseFromLocalFileData(b, 0, b.length); + assertEquals(SIZE, f.getSize()); + assertEquals(CSIZE, f.getCompressedSize()); + assertEquals(OFF, f.getRelativeHeaderOffset()); + assertEquals(DISK, f.getDiskStartNumber()); + } + + public void testReadLFHSizesAndDisk() throws ZipException { + Zip64ExtendedInformationExtraField f = + new Zip64ExtendedInformationExtraField(); + byte[] b = new byte[20]; + System.arraycopy(SIZE.getBytes(), 0, b, 0, 8); + System.arraycopy(CSIZE.getBytes(), 0, b, 8, 8); + System.arraycopy(DISK.getBytes(), 0, b, 16, 4); + f.parseFromLocalFileData(b, 0, b.length); + assertEquals(SIZE, f.getSize()); + assertEquals(CSIZE, f.getCompressedSize()); + assertNull(f.getRelativeHeaderOffset()); + assertEquals(DISK, f.getDiskStartNumber()); + } + + public void testReadCDSizesOffsetAndDisk() throws ZipException { + Zip64ExtendedInformationExtraField f = + new Zip64ExtendedInformationExtraField(); + byte[] b = new byte[28]; + System.arraycopy(SIZE.getBytes(), 0, b, 0, 8); + System.arraycopy(CSIZE.getBytes(), 0, b, 8, 8); + System.arraycopy(OFF.getBytes(), 0, b, 16, 8); + System.arraycopy(DISK.getBytes(), 0, b, 24, 4); + f.parseFromCentralDirectoryData(b, 0, b.length); + assertEquals(SIZE, f.getSize()); + assertEquals(CSIZE, f.getCompressedSize()); + assertEquals(OFF, f.getRelativeHeaderOffset()); + assertEquals(DISK, f.getDiskStartNumber()); + } + + public void testReadCDSomethingAndDisk() throws ZipException { + Zip64ExtendedInformationExtraField f = + new Zip64ExtendedInformationExtraField(); + byte[] b = new byte[12]; + System.arraycopy(SIZE.getBytes(), 0, b, 0, 8); + System.arraycopy(DISK.getBytes(), 0, b, 8, 4); + f.parseFromCentralDirectoryData(b, 0, b.length); + assertNull(f.getSize()); + assertNull(f.getCompressedSize()); + assertNull(f.getRelativeHeaderOffset()); + assertEquals(DISK, f.getDiskStartNumber()); + } + + private static void checkSizes(byte[] b) { + assertEquals(0x78, b[0]); + assertEquals(0x56, b[1]); + assertEquals(0x34, b[2]); + assertEquals(0x12, b[3]); + assertEquals(0x00, b[4]); + assertEquals(0x00, b[5]); + assertEquals(0x00, b[6]); + assertEquals(0x00, b[7]); + assertEquals((byte) 0xEF, b[8]); + assertEquals((byte) 0xCD, b[9]); + assertEquals((byte) 0xAB, b[10]); + assertEquals(0x09, b[11]); + assertEquals(0x00, b[12]); + assertEquals(0x00, b[13]); + assertEquals(0x00, b[14]); + assertEquals(0x00, b[15]); + } + + private static void checkOffset(byte[] b, int off) { + assertEquals(0x78, b[0 + off]); + assertEquals(0x56, b[1 + off]); + assertEquals(0x34, b[2 + off]); + assertEquals(0x12, b[3 + off]); + assertEquals((byte) 0x09, b[4 + off]); + assertEquals((byte) 0xEF, b[5 + off]); + assertEquals((byte) 0xCD, b[6 + off]); + assertEquals((byte) 0xAB, b[7 + off]); + } + + private static void checkDisk(byte[] b, int off) { + assertEquals(0x12, b[0 + off]); + assertEquals(0x00, b[1 + off]); + assertEquals(0x00, b[2 + off]); + assertEquals(0x00, b[3 + off]); + } +} \ No newline at end of file Propchange: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/Zip64ExtendedInformationExtraFieldTest.java ------------------------------------------------------------------------------ svn:eol-style = native