Author: bodewig Date: Tue Feb 16 13:59:03 2010 New Revision: 910519 URL: http://svn.apache.org/viewvc?rev=910519&view=rev Log: Allow extra fields to violate the recommended structure. COMPRESS-73
Modified: commons/proper/compress/trunk/src/changes/changes.xml commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java commons/proper/compress/trunk/src/site/xdoc/zip.xml Modified: commons/proper/compress/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/changes/changes.xml?rev=910519&r1=910518&r2=910519&view=diff ============================================================================== --- commons/proper/compress/trunk/src/changes/changes.xml (original) +++ commons/proper/compress/trunk/src/changes/changes.xml Tue Feb 16 13:59:03 2010 @@ -23,6 +23,10 @@ </properties> <body> <release version="1.1" date="as in SVN" description="Release 1.1"> + <action type="fix" date="2010-02-16" issue="COMPRESS-73"> + ZipArchiveEntry, ZipFile and ZipArchiveInputStream are now + more lenient when parsing extra fields. + </action> <action type="fix" date="2010-02-12"> Improved exception message if the extra field data in ZIP archives cannot be parsed. Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java?rev=910519&r1=910518&r2=910519&view=diff ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java (original) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java Tue Feb 16 13:59:03 2010 @@ -18,9 +18,11 @@ package org.apache.commons.compress.archivers.zip; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; +import java.util.List; import java.util.zip.ZipException; import org.apache.commons.compress.archivers.ArchiveEntry; @@ -28,6 +30,22 @@ * Extension that adds better handling of extra fields and provides * access to the internal and external file attributes. * + * <p>The extra data is expected to follow the recommendation of + * {...@link http://www.pkware.com/documents/casestudies/APPNOTE.TXT + * APPNOTE.txt}:</p> + * <ul> + * <li>the extra byte array consists of a sequence of extra fields</li> + * <li>each extra fields starts by a two byte header id followed by + * a two byte sequence holding the length of the remainder of + * data.</li> + * </ul> + * + * <p>Any extra data that cannot be parsed by the rules above will be + * consumed as "unparseable" extra data and treated differently by the + * methods of this class. Versions prior to Apache Commons Compress + * 1.1 would have thrown an exception if any attempt was made to read + * or write extra data not conforming to the recommendation.</p> + * * @NotThreadSafe */ public class ZipArchiveEntry extends java.util.zip.ZipEntry @@ -54,6 +72,7 @@ private int platform = PLATFORM_FAT; private long externalAttributes = 0; private LinkedHashMap/*<ZipShort, ZipExtraField>*/ extraFields = null; + private UnparseableExtraFieldData unparseableExtra = null; private String name = null; /** @@ -75,7 +94,9 @@ setName(entry.getName()); byte[] extra = entry.getExtra(); if (extra != null) { - setExtraFields(ExtraFieldUtils.parse(extra)); + setExtraFields(ExtraFieldUtils.parse(extra, true, + ExtraFieldUtils + .UnparseableExtraField.READ)); } else { // initializes extra data to an empty byte array setExtra(); @@ -92,7 +113,7 @@ this((java.util.zip.ZipEntry) entry); setInternalAttributes(entry.getInternalAttributes()); setExternalAttributes(entry.getExternalAttributes()); - setExtraFields(entry.getExtraFields()); + setExtraFields(entry.getExtraFields(true)); } /** @@ -118,10 +139,9 @@ public Object clone() { ZipArchiveEntry e = (ZipArchiveEntry) super.clone(); - e.extraFields = extraFields != null ? (LinkedHashMap) extraFields.clone() : null; e.setInternalAttributes(getInternalAttributes()); e.setExternalAttributes(getExternalAttributes()); - e.setExtraFields(getExtraFields()); + e.setExtraFields(getExtraFields(true)); return e; } @@ -246,25 +266,45 @@ public void setExtraFields(ZipExtraField[] fields) { extraFields = new LinkedHashMap(); for (int i = 0; i < fields.length; i++) { + if (fields[i] instanceof UnparseableExtraFieldData) { + unparseableExtra = (UnparseableExtraFieldData) fields[i]; + } else { extraFields.put(fields[i].getHeaderId(), fields[i]); + } } setExtra(); } /** - * Retrieves extra fields. + * Retrieves all extra fields that have been parsed successfully. * @return an array of the extra fields */ public ZipExtraField[] getExtraFields() { + return getExtraFields(false); + } + + /** + * Retrieves extra fields. + * @param includeUnparseable whether to also return unparseable + * extra fields as {...@link UnparseableExtraFieldData} if such data + * exists. + * @return an array of the extra fields + */ + public ZipExtraField[] getExtraFields(boolean includeUnparseable) { if (extraFields == null) { - return new ZipExtraField[0]; + return !includeUnparseable || unparseableExtra == null + ? new ZipExtraField[0] + : new ZipExtraField[] { unparseableExtra }; } - ZipExtraField[] result = new ZipExtraField[extraFields.size()]; - return (ZipExtraField[]) extraFields.values().toArray(result); + List result = new ArrayList(extraFields.values()); + if (includeUnparseable && unparseableExtra != null) { + result.add(unparseableExtra); + } + return (ZipExtraField[]) result.toArray(new ZipExtraField[0]); } /** - * Adds an extra fields - replacing an already present extra field + * Adds an extra field - replacing an already present extra field * of the same type. * * <p>If no extra field of the same type exists, the field will be @@ -272,21 +312,28 @@ * @param ze an extra field */ public void addExtraField(ZipExtraField ze) { + if (ze instanceof UnparseableExtraFieldData) { + unparseableExtra = (UnparseableExtraFieldData) ze; + } else { if (extraFields == null) { extraFields = new LinkedHashMap(); } extraFields.put(ze.getHeaderId(), ze); + } setExtra(); } /** - * Adds an extra fields - replacing an already present extra field + * Adds an extra field - replacing an already present extra field * of the same type. * * <p>The new extra field will be the first one.</p> * @param ze an extra field */ public void addAsFirstExtraField(ZipExtraField ze) { + if (ze instanceof UnparseableExtraFieldData) { + unparseableExtra = (UnparseableExtraFieldData) ze; + } else { LinkedHashMap copy = extraFields; extraFields = new LinkedHashMap(); extraFields.put(ze.getHeaderId(), ze); @@ -294,11 +341,12 @@ copy.remove(ze.getHeaderId()); extraFields.putAll(copy); } + } setExtra(); } /** - * Remove an extra fields. + * Remove an extra field. * @param type the type of extra field to remove */ public void removeExtraField(ZipShort type) { @@ -312,6 +360,17 @@ } /** + * Removes unparseable extra field data. + */ + public void removeUnparseableExtraFieldData() { + if (unparseableExtra == null) { + throw new java.util.NoSuchElementException(); + } + unparseableExtra = null; + setExtra(); + } + + /** * Looks up an extra field by its header id. * * @return null if no such field exists. @@ -324,16 +383,30 @@ } /** - * Throws an Exception if extra data cannot be parsed into extra fields. + * Looks up extra field data that couldn't be parsed correctly. + * + * @return null if no such field exists. + */ + public UnparseableExtraFieldData getUnparseableExtraFieldData() { + return unparseableExtra; + } + + /** + * Parses the given bytes as extra field data and consumes any + * unparseable data as an {...@link UnparseableExtraFieldData} + * instance. * @param extra an array of bytes to be parsed into extra fields * @throws RuntimeException if the bytes cannot be parsed * @throws RuntimeException on error */ public void setExtra(byte[] extra) throws RuntimeException { try { - ZipExtraField[] local = ExtraFieldUtils.parse(extra, true); + ZipExtraField[] local = + ExtraFieldUtils.parse(extra, true, + ExtraFieldUtils.UnparseableExtraField.READ); mergeExtraFields(local, true); } catch (ZipException e) { + // actually this is not be possible as of Commons Compress 1.1 throw new RuntimeException("Error parsing extra fields for entry: " + getName() + " - " + e.getMessage(), e); } @@ -346,7 +419,7 @@ * modify super's data directly. */ protected void setExtra() { - super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields())); + super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields(true))); } /** @@ -354,7 +427,9 @@ */ public void setCentralDirectoryExtra(byte[] b) { try { - ZipExtraField[] central = ExtraFieldUtils.parse(b, false); + ZipExtraField[] central = + ExtraFieldUtils.parse(b, false, + ExtraFieldUtils.UnparseableExtraField.READ); mergeExtraFields(central, false); } catch (ZipException e) { throw new RuntimeException(e.getMessage(), e); @@ -375,7 +450,7 @@ * @return the central directory extra data */ public byte[] getCentralDirectoryExtra() { - return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields()); + return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields(true)); } /** @@ -429,7 +504,12 @@ setExtraFields(f); } else { for (int i = 0; i < f.length; i++) { - ZipExtraField existing = getExtraField(f[i].getHeaderId()); + ZipExtraField existing; + if (f[i] instanceof UnparseableExtraFieldData) { + existing = unparseableExtra; + } else { + existing = getExtraField(f[i].getHeaderId()); + } if (existing == null) { addExtraField(f[i]); } else { Modified: commons/proper/compress/trunk/src/site/xdoc/zip.xml URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/site/xdoc/zip.xml?rev=910519&r1=910518&r2=910519&view=diff ============================================================================== --- commons/proper/compress/trunk/src/site/xdoc/zip.xml (original) +++ commons/proper/compress/trunk/src/site/xdoc/zip.xml Tue Feb 16 13:59:03 2010 @@ -99,6 +99,13 @@ the package, any other extra field will be stored as <code>UnrecognizedExtraField</code>.</p> + <p>Prior to version 1.1 of this library trying to read an + archive with extra fields that didn't follow the recommended + structure for those fields would cause Compress to throw an + exception. Starting with version 1.1 these extra fields + will now be read + as <code>UnparseableExtraFieldData</code>.</p> + </subsection> <subsection name="Encoding" id="encoding">