git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@910537 13f79535-47bb-0310-9956-ffa450edef68master
@@ -31,6 +31,9 @@ Other changes: | |||
* Project provides new get methods that return copies instead of the | |||
live maps of task and type definitions, references and targets. | |||
* Ant is now more lenient with ZIP extra fields and will be able to | |||
read archives that it failed to read in earlier versions. | |||
Changes from Ant 1.8.0RC1 TO Ant 1.8.0 | |||
====================================== | |||
@@ -1026,7 +1026,7 @@ public class Zip extends MatchingTask { | |||
try { | |||
is = zf.getInputStream(ze); | |||
zipFile(is, zOut, prefix + name, ze.getTime(), | |||
fromArchive, mode, ze.getExtraFields()); | |||
fromArchive, mode, ze.getExtraFields(true)); | |||
} finally { | |||
doCompress = oldCompress; | |||
FileUtils.close(is); | |||
@@ -219,7 +219,7 @@ public class ZipResource extends ArchiveResource { | |||
setDirectory(e.isDirectory()); | |||
setSize(e.getSize()); | |||
setMode(e.getUnixMode()); | |||
extras = e.getExtraFields(); | |||
extras = e.getExtraFields(true); | |||
method = e.getMethod(); | |||
} | |||
@@ -92,18 +92,19 @@ public class ExtraFieldUtils { | |||
/** | |||
* 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 | |||
@@ -113,14 +114,60 @@ public class ExtraFieldUtils { | |||
*/ | |||
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 | |||
* @since Ant 1.8.1 | |||
*/ | |||
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("data starting at " + start | |||
+ " is in unknown format"); | |||
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); | |||
@@ -152,13 +199,19 @@ public class ExtraFieldUtils { | |||
* @since 1.1 | |||
*/ | |||
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(), | |||
@@ -167,6 +220,10 @@ public class ExtraFieldUtils { | |||
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; | |||
} | |||
@@ -177,13 +234,18 @@ public class ExtraFieldUtils { | |||
* @since 1.1 | |||
*/ | |||
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(), | |||
@@ -192,6 +254,60 @@ public class ExtraFieldUtils { | |||
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; } | |||
} | |||
} |
@@ -0,0 +1,115 @@ | |||
/* | |||
* 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.tools.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> | |||
* @since Ant 1.8.1 | |||
*/ | |||
public final class UnparseableExtraFieldData | |||
implements CentralDirectoryParsingZipExtraField { | |||
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); | |||
} | |||
} | |||
} |
@@ -66,7 +66,7 @@ public class UnrecognizedExtraField | |||
* @param data the field data to use | |||
*/ | |||
public void setLocalFileDataData(byte[] data) { | |||
localData = copy(data); | |||
localData = ZipUtil.copy(data); | |||
} | |||
/** | |||
@@ -82,7 +82,7 @@ public class UnrecognizedExtraField | |||
* @return the local data | |||
*/ | |||
public byte[] getLocalFileDataData() { | |||
return copy(localData); | |||
return ZipUtil.copy(localData); | |||
} | |||
/** | |||
@@ -98,7 +98,7 @@ public class UnrecognizedExtraField | |||
* @param data the data to use | |||
*/ | |||
public void setCentralDirectoryData(byte[] data) { | |||
centralData = copy(data); | |||
centralData = ZipUtil.copy(data); | |||
} | |||
/** | |||
@@ -119,7 +119,7 @@ public class UnrecognizedExtraField | |||
*/ | |||
public byte[] getCentralDirectoryData() { | |||
if (centralData != null) { | |||
return copy(centralData); | |||
return ZipUtil.copy(centralData); | |||
} | |||
return getLocalFileDataData(); | |||
} | |||
@@ -151,12 +151,4 @@ public class UnrecognizedExtraField | |||
} | |||
} | |||
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; | |||
} | |||
} |
@@ -18,13 +18,32 @@ | |||
package org.apache.tools.zip; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.LinkedHashMap; | |||
import java.util.List; | |||
import java.util.zip.ZipException; | |||
/** | |||
* 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> | |||
* | |||
*/ | |||
public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
@@ -37,6 +56,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
private int platform = PLATFORM_FAT; | |||
private long externalAttributes = 0; | |||
private LinkedHashMap/*<ZipShort, ZipExtraField>*/ extraFields = null; | |||
private UnparseableExtraFieldData unparseableExtra = null; | |||
private String name = null; | |||
/** | |||
@@ -58,7 +78,9 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
super(entry); | |||
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(); | |||
@@ -75,7 +97,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
this((java.util.zip.ZipEntry) entry); | |||
setInternalAttributes(entry.getInternalAttributes()); | |||
setExternalAttributes(entry.getExternalAttributes()); | |||
setExtraFields(entry.getExtraFields()); | |||
setExtraFields(entry.getExtraFields(true)); | |||
} | |||
/** | |||
@@ -93,10 +115,9 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
public Object clone() { | |||
ZipEntry e = (ZipEntry) 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; | |||
} | |||
@@ -194,26 +215,46 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
public void setExtraFields(ZipExtraField[] fields) { | |||
extraFields = new LinkedHashMap(); | |||
for (int i = 0; i < fields.length; i++) { | |||
extraFields.put(fields[i].getHeaderId(), fields[i]); | |||
if (fields[i] instanceof UnparseableExtraFieldData) { | |||
unparseableExtra = (UnparseableExtraFieldData) fields[i]; | |||
} else { | |||
extraFields.put(fields[i].getHeaderId(), fields[i]); | |||
} | |||
} | |||
setExtra(); | |||
} | |||
/** | |||
* 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 | |||
* @since 1.1 | |||
*/ | |||
public ZipExtraField[] getExtraFields() { | |||
public ZipExtraField[] getExtraFields(boolean includeUnparseable) { | |||
if (extraFields == null) { | |||
return new ZipExtraField[0]; | |||
return !includeUnparseable || unparseableExtra == null | |||
? new ZipExtraField[0] | |||
: new ZipExtraField[] { unparseableExtra }; | |||
} | |||
List result = new ArrayList(extraFields.values()); | |||
if (includeUnparseable && unparseableExtra != null) { | |||
result.add(unparseableExtra); | |||
} | |||
ZipExtraField[] result = new ZipExtraField[extraFields.size()]; | |||
return (ZipExtraField[]) extraFields.values().toArray(result); | |||
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 | |||
@@ -222,15 +263,19 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @since 1.1 | |||
*/ | |||
public void addExtraField(ZipExtraField ze) { | |||
if (extraFields == null) { | |||
extraFields = new LinkedHashMap(); | |||
if (ze instanceof UnparseableExtraFieldData) { | |||
unparseableExtra = (UnparseableExtraFieldData) ze; | |||
} else { | |||
if (extraFields == null) { | |||
extraFields = new LinkedHashMap(); | |||
} | |||
extraFields.put(ze.getHeaderId(), ze); | |||
} | |||
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> | |||
@@ -238,18 +283,22 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @since 1.1 | |||
*/ | |||
public void addAsFirstExtraField(ZipExtraField ze) { | |||
LinkedHashMap copy = extraFields; | |||
extraFields = new LinkedHashMap(); | |||
extraFields.put(ze.getHeaderId(), ze); | |||
if (copy != null) { | |||
copy.remove(ze.getHeaderId()); | |||
extraFields.putAll(copy); | |||
if (ze instanceof UnparseableExtraFieldData) { | |||
unparseableExtra = (UnparseableExtraFieldData) ze; | |||
} else { | |||
LinkedHashMap copy = extraFields; | |||
extraFields = new LinkedHashMap(); | |||
extraFields.put(ze.getHeaderId(), ze); | |||
if (copy != null) { | |||
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 | |||
* @since 1.1 | |||
*/ | |||
@@ -263,6 +312,17 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
setExtra(); | |||
} | |||
/** | |||
* 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. | |||
* | |||
@@ -276,7 +336,18 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
} | |||
/** | |||
* 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 | |||
* @since 1.1 | |||
@@ -284,10 +355,14 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
*/ | |||
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 (Exception e) { | |||
throw new RuntimeException(e.getMessage(), e); | |||
// actually this is not be possible as of Ant 1.8.1 | |||
throw new RuntimeException("Error parsing extra fields for entry: " | |||
+ getName() + " - " + e.getMessage(), e); | |||
} | |||
} | |||
@@ -300,7 +375,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @since 1.1 | |||
*/ | |||
protected void setExtra() { | |||
super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields())); | |||
super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields(true))); | |||
} | |||
/** | |||
@@ -308,7 +383,9 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
*/ | |||
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 (Exception e) { | |||
throw new RuntimeException(e.getMessage(), e); | |||
@@ -331,7 +408,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @since 1.1 | |||
*/ | |||
public byte[] getCentralDirectoryExtra() { | |||
return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields()); | |||
return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields(true)); | |||
} | |||
/** | |||
@@ -413,7 +490,12 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
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 { | |||
@@ -0,0 +1,38 @@ | |||
/* | |||
* 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.tools.zip; | |||
/** | |||
* Utility class for handling DOS and Java time conversions. | |||
* @since Ant 1.8.1 | |||
*/ | |||
public abstract class ZipUtil { | |||
/** | |||
* 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; | |||
} | |||
} |
@@ -59,7 +59,7 @@ | |||
> | |||
<mkdir dir="${input}"/> | |||
<mkdir dir="${output}"/> | |||
<copy file="zip/Bugzilla-46559.zip" tofile="${input}/test.zip"/> | |||
<copy file="broken_cd.zip" tofile="${input}/test.zip"/> | |||
<au:expectfailure> | |||
<unzip src="${input}/test.zip" dest="${output}"/> | |||
</au:expectfailure> | |||
@@ -18,6 +18,7 @@ | |||
package org.apache.tools.zip; | |||
import java.util.Arrays; | |||
import junit.framework.TestCase; | |||
/** | |||
@@ -78,11 +79,65 @@ public class ExtraFieldUtilsTest extends TestCase implements UnixStat { | |||
fail("data should be invalid"); | |||
} catch (Exception e) { | |||
assertEquals("message", | |||
"data starting at "+(4+aLocal.length)+" is in unknown format", | |||
"bad extra field starting at "+(4 + aLocal.length) | |||
+ ". Block length of 1 bytes exceeds remaining data of 0 bytes.", | |||
e.getMessage()); | |||
} | |||
} | |||
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 | |||
*/ | |||
@@ -111,4 +166,30 @@ public class ExtraFieldUtilsTest extends TestCase implements UnixStat { | |||
} | |||
} | |||
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]); | |||
} | |||
} | |||
} |