Author: bodewig Date: Tue Feb 16 11:37:44 2010 New Revision: 910483 URL: http://svn.apache.org/viewvc?rev=910483&view=rev Log: allow different strategies when parsing extra fields. COMPRESS-73
Added: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/UnparseableExtraFieldData.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/UnrecognizedExtraField.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/ExtraFieldUtilsTest.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=910483&r1=910482&r2=910483&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 Feb 16 11:37:44 2010 @@ -86,18 +86,19 @@ /** * Split the array into ExtraFields and populate them with the - * given data as local file data. + * given data as local file data, throwing an exception if the + * data cannot be parsed. * @param data an array of bytes as it appears in local file data * @return an array of ExtraFields * @throws ZipException on error */ public static ZipExtraField[] parse(byte[] data) throws ZipException { - return parse(data, true); + return parse(data, true, UnparseableExtraField.THROW); } /** * Split the array into ExtraFields and populate them with the - * given data. + * given data, throwing an exception if the data cannot be parsed. * @param data an array of bytes * @param local whether data originates from the local file data * or the central directory @@ -106,18 +107,59 @@ */ public static ZipExtraField[] parse(byte[] data, boolean local) throws ZipException { + return parse(data, local, UnparseableExtraField.THROW); + } + + /** + * Split the array into ExtraFields and populate them with the + * given data. + * @param data an array of bytes + * @param local whether data originates from the local file data + * or the central directory + * @param onUnparseableData what to do if the extra field data + * cannot be parsed. + * @return an array of ExtraFields + * @throws ZipException on error + */ + public static ZipExtraField[] parse(byte[] data, boolean local, + UnparseableExtraField onUnparseableData) + throws ZipException { List v = new ArrayList(); int start = 0; + LOOP: while (start <= data.length - WORD) { ZipShort headerId = new ZipShort(data, start); int length = (new ZipShort(data, start + 2)).getValue(); if (start + WORD + length > data.length) { - throw new ZipException("bad extra field starting at " - + start + ". Block length of " - + length + " bytes exceeds remaining" - + " data of " - + (data.length - start - WORD) - + " bytes."); + switch(onUnparseableData.getKey()) { + case UnparseableExtraField.THROW_KEY: + throw new ZipException("bad extra field starting at " + + start + ". Block length of " + + length + " bytes exceeds remaining" + + " data of " + + (data.length - start - WORD) + + " bytes."); + case UnparseableExtraField.READ_KEY: + UnparseableExtraFieldData field = + new UnparseableExtraFieldData(); + if (local) { + field.parseFromLocalFileData(data, start, + data.length - start); + } else { + field.parseFromCentralDirectoryData(data, start, + data.length - start); + } + v.add(field); + /*FALLTHROUGH*/ + case UnparseableExtraField.SKIP_KEY: + // since we cannot parse the data we must assume + // the extra field consumes the whole rest of the + // available data + break LOOP; + default: + throw new ZipException("unknown UnparseableExtraField key: " + + onUnparseableData.getKey()); + } } try { ZipExtraField ze = createExtraField(headerId); @@ -146,13 +188,19 @@ * @return an array of bytes */ public static byte[] mergeLocalFileDataData(ZipExtraField[] data) { - int sum = WORD * data.length; + final boolean lastIsUnparseableHolder = data.length > 0 + && data[data.length - 1] instanceof UnparseableExtraFieldData; + int regularExtraFieldCount = + lastIsUnparseableHolder ? data.length - 1 : data.length; + + int sum = WORD * regularExtraFieldCount; for (int i = 0; i < data.length; i++) { sum += data[i].getLocalFileDataLength().getValue(); } + byte[] result = new byte[sum]; int start = 0; - for (int i = 0; i < data.length; i++) { + for (int i = 0; i < regularExtraFieldCount; i++) { System.arraycopy(data[i].getHeaderId().getBytes(), 0, result, start, 2); System.arraycopy(data[i].getLocalFileDataLength().getBytes(), @@ -161,6 +209,10 @@ System.arraycopy(local, 0, result, start + WORD, local.length); start += (local.length + WORD); } + if (lastIsUnparseableHolder) { + byte[] local = data[data.length - 1].getLocalFileDataData(); + System.arraycopy(local, 0, result, start, local.length); + } return result; } @@ -170,13 +222,18 @@ * @return an array of bytes */ public static byte[] mergeCentralDirectoryData(ZipExtraField[] data) { - int sum = WORD * data.length; + final boolean lastIsUnparseableHolder = data.length > 0 + && data[data.length - 1] instanceof UnparseableExtraFieldData; + int regularExtraFieldCount = + lastIsUnparseableHolder ? data.length - 1 : data.length; + + int sum = WORD * regularExtraFieldCount; for (int i = 0; i < data.length; i++) { sum += data[i].getCentralDirectoryLength().getValue(); } byte[] result = new byte[sum]; int start = 0; - for (int i = 0; i < data.length; i++) { + for (int i = 0; i < regularExtraFieldCount; i++) { System.arraycopy(data[i].getHeaderId().getBytes(), 0, result, start, 2); System.arraycopy(data[i].getCentralDirectoryLength().getBytes(), @@ -185,6 +242,60 @@ System.arraycopy(local, 0, result, start + WORD, local.length); start += (local.length + WORD); } + if (lastIsUnparseableHolder) { + byte[] local = data[data.length - 1].getCentralDirectoryData(); + System.arraycopy(local, 0, result, start, local.length); + } return result; } + + /** + * "enum" for the possible actions to take if the extra field + * cannot be parsed. + */ + public static final class UnparseableExtraField { + /** + * Key for "throw an exception" action. + */ + public static final int THROW_KEY = 0; + /** + * Key for "skip" action. + */ + public static final int SKIP_KEY = 1; + /** + * Key for "read" action. + */ + public static final int READ_KEY = 2; + + /** + * Throw an exception if field cannot be parsed. + */ + public static final UnparseableExtraField THROW + = new UnparseableExtraField(THROW_KEY); + + /** + * Skip the extra field entirely and don't make its data + * available - effectively removing the extra field data. + */ + public static final UnparseableExtraField SKIP + = new UnparseableExtraField(SKIP_KEY); + + /** + * Read the extra field data into an instance of {...@link + * UnparseableExtraFieldData UnparseableExtraFieldData}. + */ + public static final UnparseableExtraField READ + = new UnparseableExtraField(READ_KEY); + + private final int key; + + private UnparseableExtraField(int k) { + key = k; + } + + /** + * Key of the action to take. + */ + public int getKey() { return key; } + } } Added: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/UnparseableExtraFieldData.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/UnparseableExtraFieldData.java?rev=910483&view=auto ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/UnparseableExtraFieldData.java (added) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/UnparseableExtraFieldData.java Tue Feb 16 11:37:44 2010 @@ -0,0 +1,113 @@ +/* + * 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; + +/** + * Wrapper for extra field data that doesn't conform to the recommended format of header-tag + size + data. + * + * <p>The header-id is artificial (and not listed as a know ID in + * {...@link http://www.pkware.com/documents/casestudies/APPNOTE.TXT + * APPNOTE.TXT}. Since it isn't used anywhere except to satisfy the + * ZipExtraField contract it shouldn't matter anyway.</p> + * @NotThreadSafe + */ +public final class UnparseableExtraFieldData implements ZipExtraField { + private static final ZipShort HEADER_ID = new ZipShort(0xACC1); + + private byte[] localFileData; + private byte[] centralDirectoryData; + + /** + * The Header-ID. + * + * @return a completely arbitrary value that should be ignored. + */ + public ZipShort getHeaderId() { + return HEADER_ID; + } + + /** + * Length of the complete extra field in the local file data. + * + * @return The LocalFileDataLength value + */ + public ZipShort getLocalFileDataLength() { + return new ZipShort(localFileData == null ? 0 : localFileData.length); + } + + /** + * Length of the complete extra field in the central directory. + * + * @return The CentralDirectoryLength value + */ + public ZipShort getCentralDirectoryLength() { + return centralDirectoryData == null + ? getLocalFileDataLength() + : new ZipShort(centralDirectoryData.length); + } + + /** + * The actual data to put into local file data. + * + * @return The LocalFileDataData value + */ + public byte[] getLocalFileDataData() { + return ZipUtil.copy(localFileData); + } + + /** + * The actual data to put into central directory. + * + * @return The CentralDirectoryData value + */ + public byte[] getCentralDirectoryData() { + return centralDirectoryData == null + ? getLocalFileDataData() : ZipUtil.copy(centralDirectoryData); + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param buffer the buffer to read data from + * @param offset offset into buffer to read data + * @param length the length of data + */ + public void parseFromLocalFileData(byte[] buffer, int offset, int length) { + localFileData = new byte[length]; + System.arraycopy(buffer, offset, localFileData, 0, length); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param buffer the buffer to read data from + * @param offset offset into buffer to read data + * @param length the length of data + * @exception ZipException on error + */ + public void parseFromCentralDirectoryData(byte[] buffer, int offset, + int length) { + centralDirectoryData = new byte[length]; + System.arraycopy(buffer, offset, centralDirectoryData, 0, length); + if (localFileData == null) { + parseFromLocalFileData(buffer, offset, length); + } + } + +} Propchange: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/UnparseableExtraFieldData.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/UnrecognizedExtraField.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/UnrecognizedExtraField.java?rev=910483&r1=910482&r2=910483&view=diff ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/UnrecognizedExtraField.java (original) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/UnrecognizedExtraField.java Tue Feb 16 11:37:44 2010 @@ -61,7 +61,7 @@ * @param data the field data to use */ public void setLocalFileDataData(byte[] data) { - localData = copy(data); + localData = ZipUtil.copy(data); } /** @@ -77,7 +77,7 @@ * @return the local data */ public byte[] getLocalFileDataData() { - return copy(localData); + return ZipUtil.copy(localData); } /** @@ -91,7 +91,7 @@ * @param data the data to use */ public void setCentralDirectoryData(byte[] data) { - centralData = copy(data); + centralData = ZipUtil.copy(data); } /** @@ -112,7 +112,7 @@ */ public byte[] getCentralDirectoryData() { if (centralData != null) { - return copy(centralData); + return ZipUtil.copy(centralData); } return getLocalFileDataData(); } @@ -145,12 +145,4 @@ } } - private static byte[] copy(byte[] from) { - if (from != null) { - byte[] to = new byte[from.length]; - System.arraycopy(from, 0, to, 0, to.length); - return to; - } - return null; - } } 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=910483&r1=910482&r2=910483&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 Feb 16 11:37:44 2010 @@ -167,5 +167,17 @@ return null; } + /** + * Create a copy of the given array - or return null if the + * argument is null. + */ + static byte[] copy(byte[] from) { + if (from != null) { + byte[] to = new byte[from.length]; + System.arraycopy(from, 0, to, 0, to.length); + return to; + } + return null; + } } \ No newline at end of file Modified: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtilsTest.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtilsTest.java?rev=910483&r1=910482&r2=910483&view=diff ============================================================================== --- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtilsTest.java (original) +++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ExtraFieldUtilsTest.java Tue Feb 16 11:37:44 2010 @@ -18,6 +18,7 @@ package org.apache.commons.compress.archivers.zip; +import java.util.Arrays; import junit.framework.TestCase; /** @@ -84,6 +85,59 @@ } } + public void testParseWithRead() throws Exception { + ZipExtraField[] ze = + ExtraFieldUtils.parse(data, true, + ExtraFieldUtils.UnparseableExtraField.READ); + assertEquals("number of fields", 2, ze.length); + assertTrue("type field 1", ze[0] instanceof AsiExtraField); + assertEquals("mode field 1", 040755, + ((AsiExtraField) ze[0]).getMode()); + assertTrue("type field 2", ze[1] instanceof UnrecognizedExtraField); + assertEquals("data length field 2", 1, + ze[1].getLocalFileDataLength().getValue()); + + byte[] data2 = new byte[data.length-1]; + System.arraycopy(data, 0, data2, 0, data2.length); + ze = ExtraFieldUtils.parse(data2, true, + ExtraFieldUtils.UnparseableExtraField.READ); + assertEquals("number of fields", 2, ze.length); + assertTrue("type field 1", ze[0] instanceof AsiExtraField); + assertEquals("mode field 1", 040755, + ((AsiExtraField) ze[0]).getMode()); + assertTrue("type field 2", ze[1] instanceof UnparseableExtraFieldData); + assertEquals("data length field 2", 4, + ze[1].getLocalFileDataLength().getValue()); + byte[] expectedData = new byte[4]; + for (int i = 0; i < 4; i++) { + assertEquals("byte number " + i, + data2[data.length - 5 + i], + ze[1].getLocalFileDataData()[i]); + } + } + + public void testParseWithSkip() throws Exception { + ZipExtraField[] ze = + ExtraFieldUtils.parse(data, true, + ExtraFieldUtils.UnparseableExtraField.SKIP); + assertEquals("number of fields", 2, ze.length); + assertTrue("type field 1", ze[0] instanceof AsiExtraField); + assertEquals("mode field 1", 040755, + ((AsiExtraField) ze[0]).getMode()); + assertTrue("type field 2", ze[1] instanceof UnrecognizedExtraField); + assertEquals("data length field 2", 1, + ze[1].getLocalFileDataLength().getValue()); + + byte[] data2 = new byte[data.length-1]; + System.arraycopy(data, 0, data2, 0, data2.length); + ze = ExtraFieldUtils.parse(data2, true, + ExtraFieldUtils.UnparseableExtraField.SKIP); + assertEquals("number of fields", 1, ze.length); + assertTrue("type field 1", ze[0] instanceof AsiExtraField); + assertEquals("mode field 1", 040755, + ((AsiExtraField) ze[0]).getMode()); + } + /** * Test merge methods */ @@ -112,4 +166,30 @@ } } + + public void testMergeWithUnparseableData() throws Exception { + ZipExtraField d = new UnparseableExtraFieldData(); + d.parseFromLocalFileData(new byte[] {1, 0, 1, 0}, 0, 4); + byte[] local = + ExtraFieldUtils.mergeLocalFileDataData(new ZipExtraField[] {a, d}); + assertEquals("local length", data.length - 1, local.length); + for (int i = 0; i < local.length; i++) { + assertEquals("local byte " + i, data[i], local[i]); + } + + byte[] dCentral = d.getCentralDirectoryData(); + byte[] data2 = new byte[4 + aLocal.length + dCentral.length]; + System.arraycopy(data, 0, data2, 0, 4 + aLocal.length + 2); + System.arraycopy(dCentral, 0, data2, + 4 + aLocal.length, dCentral.length); + + + byte[] central = + ExtraFieldUtils.mergeCentralDirectoryData(new ZipExtraField[] {a, d}); + assertEquals("central length", data2.length, central.length); + for (int i = 0; i < central.length; i++) { + assertEquals("central byte " + i, data2[i], central[i]); + } + + } }