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 | * Project provides new get methods that return copies instead of the | ||||
| live maps of task and type definitions, references and targets. | 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 | Changes from Ant 1.8.0RC1 TO Ant 1.8.0 | ||||
| ====================================== | ====================================== | ||||
| @@ -1026,7 +1026,7 @@ public class Zip extends MatchingTask { | |||||
| try { | try { | ||||
| is = zf.getInputStream(ze); | is = zf.getInputStream(ze); | ||||
| zipFile(is, zOut, prefix + name, ze.getTime(), | zipFile(is, zOut, prefix + name, ze.getTime(), | ||||
| fromArchive, mode, ze.getExtraFields()); | |||||
| fromArchive, mode, ze.getExtraFields(true)); | |||||
| } finally { | } finally { | ||||
| doCompress = oldCompress; | doCompress = oldCompress; | ||||
| FileUtils.close(is); | FileUtils.close(is); | ||||
| @@ -219,7 +219,7 @@ public class ZipResource extends ArchiveResource { | |||||
| setDirectory(e.isDirectory()); | setDirectory(e.isDirectory()); | ||||
| setSize(e.getSize()); | setSize(e.getSize()); | ||||
| setMode(e.getUnixMode()); | setMode(e.getUnixMode()); | ||||
| extras = e.getExtraFields(); | |||||
| extras = e.getExtraFields(true); | |||||
| method = e.getMethod(); | method = e.getMethod(); | ||||
| } | } | ||||
| @@ -92,18 +92,19 @@ public class ExtraFieldUtils { | |||||
| /** | /** | ||||
| * Split the array into ExtraFields and populate them with the | * 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 | * @param data an array of bytes as it appears in local file data | ||||
| * @return an array of ExtraFields | * @return an array of ExtraFields | ||||
| * @throws ZipException on error | * @throws ZipException on error | ||||
| */ | */ | ||||
| public static ZipExtraField[] parse(byte[] data) throws ZipException { | 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 | * 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 data an array of bytes | ||||
| * @param local whether data originates from the local file data | * @param local whether data originates from the local file data | ||||
| * or the central directory | * or the central directory | ||||
| @@ -113,14 +114,60 @@ public class ExtraFieldUtils { | |||||
| */ | */ | ||||
| public static ZipExtraField[] parse(byte[] data, boolean local) | public static ZipExtraField[] parse(byte[] data, boolean local) | ||||
| throws ZipException { | 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(); | List v = new ArrayList(); | ||||
| int start = 0; | int start = 0; | ||||
| LOOP: | |||||
| while (start <= data.length - WORD) { | while (start <= data.length - WORD) { | ||||
| ZipShort headerId = new ZipShort(data, start); | ZipShort headerId = new ZipShort(data, start); | ||||
| int length = (new ZipShort(data, start + 2)).getValue(); | int length = (new ZipShort(data, start + 2)).getValue(); | ||||
| if (start + WORD + length > data.length) { | 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 { | try { | ||||
| ZipExtraField ze = createExtraField(headerId); | ZipExtraField ze = createExtraField(headerId); | ||||
| @@ -152,13 +199,19 @@ public class ExtraFieldUtils { | |||||
| * @since 1.1 | * @since 1.1 | ||||
| */ | */ | ||||
| public static byte[] mergeLocalFileDataData(ZipExtraField[] data) { | 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++) { | for (int i = 0; i < data.length; i++) { | ||||
| sum += data[i].getLocalFileDataLength().getValue(); | sum += data[i].getLocalFileDataLength().getValue(); | ||||
| } | } | ||||
| byte[] result = new byte[sum]; | byte[] result = new byte[sum]; | ||||
| int start = 0; | int start = 0; | ||||
| for (int i = 0; i < data.length; i++) { | |||||
| for (int i = 0; i < regularExtraFieldCount; i++) { | |||||
| System.arraycopy(data[i].getHeaderId().getBytes(), | System.arraycopy(data[i].getHeaderId().getBytes(), | ||||
| 0, result, start, 2); | 0, result, start, 2); | ||||
| System.arraycopy(data[i].getLocalFileDataLength().getBytes(), | System.arraycopy(data[i].getLocalFileDataLength().getBytes(), | ||||
| @@ -167,6 +220,10 @@ public class ExtraFieldUtils { | |||||
| System.arraycopy(local, 0, result, start + WORD, local.length); | System.arraycopy(local, 0, result, start + WORD, local.length); | ||||
| start += (local.length + WORD); | start += (local.length + WORD); | ||||
| } | } | ||||
| if (lastIsUnparseableHolder) { | |||||
| byte[] local = data[data.length - 1].getLocalFileDataData(); | |||||
| System.arraycopy(local, 0, result, start, local.length); | |||||
| } | |||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -177,13 +234,18 @@ public class ExtraFieldUtils { | |||||
| * @since 1.1 | * @since 1.1 | ||||
| */ | */ | ||||
| public static byte[] mergeCentralDirectoryData(ZipExtraField[] data) { | 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++) { | for (int i = 0; i < data.length; i++) { | ||||
| sum += data[i].getCentralDirectoryLength().getValue(); | sum += data[i].getCentralDirectoryLength().getValue(); | ||||
| } | } | ||||
| byte[] result = new byte[sum]; | byte[] result = new byte[sum]; | ||||
| int start = 0; | int start = 0; | ||||
| for (int i = 0; i < data.length; i++) { | |||||
| for (int i = 0; i < regularExtraFieldCount; i++) { | |||||
| System.arraycopy(data[i].getHeaderId().getBytes(), | System.arraycopy(data[i].getHeaderId().getBytes(), | ||||
| 0, result, start, 2); | 0, result, start, 2); | ||||
| System.arraycopy(data[i].getCentralDirectoryLength().getBytes(), | System.arraycopy(data[i].getCentralDirectoryLength().getBytes(), | ||||
| @@ -192,6 +254,60 @@ public class ExtraFieldUtils { | |||||
| System.arraycopy(local, 0, result, start + WORD, local.length); | System.arraycopy(local, 0, result, start + WORD, local.length); | ||||
| start += (local.length + WORD); | start += (local.length + WORD); | ||||
| } | } | ||||
| if (lastIsUnparseableHolder) { | |||||
| byte[] local = data[data.length - 1].getCentralDirectoryData(); | |||||
| System.arraycopy(local, 0, result, start, local.length); | |||||
| } | |||||
| return result; | 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 | * @param data the field data to use | ||||
| */ | */ | ||||
| public void setLocalFileDataData(byte[] data) { | public void setLocalFileDataData(byte[] data) { | ||||
| localData = copy(data); | |||||
| localData = ZipUtil.copy(data); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -82,7 +82,7 @@ public class UnrecognizedExtraField | |||||
| * @return the local data | * @return the local data | ||||
| */ | */ | ||||
| public byte[] getLocalFileDataData() { | public byte[] getLocalFileDataData() { | ||||
| return copy(localData); | |||||
| return ZipUtil.copy(localData); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -98,7 +98,7 @@ public class UnrecognizedExtraField | |||||
| * @param data the data to use | * @param data the data to use | ||||
| */ | */ | ||||
| public void setCentralDirectoryData(byte[] data) { | public void setCentralDirectoryData(byte[] data) { | ||||
| centralData = copy(data); | |||||
| centralData = ZipUtil.copy(data); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -119,7 +119,7 @@ public class UnrecognizedExtraField | |||||
| */ | */ | ||||
| public byte[] getCentralDirectoryData() { | public byte[] getCentralDirectoryData() { | ||||
| if (centralData != null) { | if (centralData != null) { | ||||
| return copy(centralData); | |||||
| return ZipUtil.copy(centralData); | |||||
| } | } | ||||
| return getLocalFileDataData(); | 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; | package org.apache.tools.zip; | ||||
| import java.util.ArrayList; | |||||
| import java.util.Arrays; | |||||
| import java.util.LinkedHashMap; | import java.util.LinkedHashMap; | ||||
| import java.util.List; | |||||
| import java.util.zip.ZipException; | import java.util.zip.ZipException; | ||||
| /** | /** | ||||
| * Extension that adds better handling of extra fields and provides | * Extension that adds better handling of extra fields and provides | ||||
| * access to the internal and external file attributes. | * 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 { | 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 int platform = PLATFORM_FAT; | ||||
| private long externalAttributes = 0; | private long externalAttributes = 0; | ||||
| private LinkedHashMap/*<ZipShort, ZipExtraField>*/ extraFields = null; | private LinkedHashMap/*<ZipShort, ZipExtraField>*/ extraFields = null; | ||||
| private UnparseableExtraFieldData unparseableExtra = null; | |||||
| private String name = null; | private String name = null; | ||||
| /** | /** | ||||
| @@ -58,7 +78,9 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
| super(entry); | super(entry); | ||||
| byte[] extra = entry.getExtra(); | byte[] extra = entry.getExtra(); | ||||
| if (extra != null) { | if (extra != null) { | ||||
| setExtraFields(ExtraFieldUtils.parse(extra)); | |||||
| setExtraFields(ExtraFieldUtils.parse(extra, true, | |||||
| ExtraFieldUtils | |||||
| .UnparseableExtraField.READ)); | |||||
| } else { | } else { | ||||
| // initializes extra data to an empty byte array | // initializes extra data to an empty byte array | ||||
| setExtra(); | setExtra(); | ||||
| @@ -75,7 +97,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
| this((java.util.zip.ZipEntry) entry); | this((java.util.zip.ZipEntry) entry); | ||||
| setInternalAttributes(entry.getInternalAttributes()); | setInternalAttributes(entry.getInternalAttributes()); | ||||
| setExternalAttributes(entry.getExternalAttributes()); | 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() { | public Object clone() { | ||||
| ZipEntry e = (ZipEntry) super.clone(); | ZipEntry e = (ZipEntry) super.clone(); | ||||
| e.extraFields = extraFields != null ? (LinkedHashMap) extraFields.clone() : null; | |||||
| e.setInternalAttributes(getInternalAttributes()); | e.setInternalAttributes(getInternalAttributes()); | ||||
| e.setExternalAttributes(getExternalAttributes()); | e.setExternalAttributes(getExternalAttributes()); | ||||
| e.setExtraFields(getExtraFields()); | |||||
| e.setExtraFields(getExtraFields(true)); | |||||
| return e; | return e; | ||||
| } | } | ||||
| @@ -194,26 +215,46 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
| public void setExtraFields(ZipExtraField[] fields) { | public void setExtraFields(ZipExtraField[] fields) { | ||||
| extraFields = new LinkedHashMap(); | extraFields = new LinkedHashMap(); | ||||
| for (int i = 0; i < fields.length; i++) { | 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(); | 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. | * 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 | * @return an array of the extra fields | ||||
| * @since 1.1 | * @since 1.1 | ||||
| */ | */ | ||||
| public ZipExtraField[] getExtraFields() { | |||||
| public ZipExtraField[] getExtraFields(boolean includeUnparseable) { | |||||
| if (extraFields == null) { | 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. | * of the same type. | ||||
| * | * | ||||
| * <p>If no extra field of the same type exists, the field will be | * <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 | * @since 1.1 | ||||
| */ | */ | ||||
| public void addExtraField(ZipExtraField ze) { | 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(); | 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. | * of the same type. | ||||
| * | * | ||||
| * <p>The new extra field will be the first one.</p> | * <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 | * @since 1.1 | ||||
| */ | */ | ||||
| public void addAsFirstExtraField(ZipExtraField ze) { | 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(); | setExtra(); | ||||
| } | } | ||||
| /** | /** | ||||
| * Remove an extra fields. | |||||
| * Remove an extra field. | |||||
| * @param type the type of extra field to remove | * @param type the type of extra field to remove | ||||
| * @since 1.1 | * @since 1.1 | ||||
| */ | */ | ||||
| @@ -263,6 +312,17 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
| setExtra(); | 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. | * 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 | * @param extra an array of bytes to be parsed into extra fields | ||||
| * @throws RuntimeException if the bytes cannot be parsed | * @throws RuntimeException if the bytes cannot be parsed | ||||
| * @since 1.1 | * @since 1.1 | ||||
| @@ -284,10 +355,14 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
| */ | */ | ||||
| public void setExtra(byte[] extra) throws RuntimeException { | public void setExtra(byte[] extra) throws RuntimeException { | ||||
| try { | try { | ||||
| ZipExtraField[] local = ExtraFieldUtils.parse(extra, true); | |||||
| ZipExtraField[] local = | |||||
| ExtraFieldUtils.parse(extra, true, | |||||
| ExtraFieldUtils.UnparseableExtraField.READ); | |||||
| mergeExtraFields(local, true); | mergeExtraFields(local, true); | ||||
| } catch (Exception e) { | } 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 | * @since 1.1 | ||||
| */ | */ | ||||
| protected void setExtra() { | 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) { | public void setCentralDirectoryExtra(byte[] b) { | ||||
| try { | try { | ||||
| ZipExtraField[] central = ExtraFieldUtils.parse(b, false); | |||||
| ZipExtraField[] central = | |||||
| ExtraFieldUtils.parse(b, false, | |||||
| ExtraFieldUtils.UnparseableExtraField.READ); | |||||
| mergeExtraFields(central, false); | mergeExtraFields(central, false); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| throw new RuntimeException(e.getMessage(), e); | throw new RuntimeException(e.getMessage(), e); | ||||
| @@ -331,7 +408,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
| * @since 1.1 | * @since 1.1 | ||||
| */ | */ | ||||
| public byte[] getCentralDirectoryExtra() { | 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); | setExtraFields(f); | ||||
| } else { | } else { | ||||
| for (int i = 0; i < f.length; i++) { | 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) { | if (existing == null) { | ||||
| addExtraField(f[i]); | addExtraField(f[i]); | ||||
| } else { | } 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="${input}"/> | ||||
| <mkdir dir="${output}"/> | <mkdir dir="${output}"/> | ||||
| <copy file="zip/Bugzilla-46559.zip" tofile="${input}/test.zip"/> | |||||
| <copy file="broken_cd.zip" tofile="${input}/test.zip"/> | |||||
| <au:expectfailure> | <au:expectfailure> | ||||
| <unzip src="${input}/test.zip" dest="${output}"/> | <unzip src="${input}/test.zip" dest="${output}"/> | ||||
| </au:expectfailure> | </au:expectfailure> | ||||
| @@ -18,6 +18,7 @@ | |||||
| package org.apache.tools.zip; | package org.apache.tools.zip; | ||||
| import java.util.Arrays; | |||||
| import junit.framework.TestCase; | import junit.framework.TestCase; | ||||
| /** | /** | ||||
| @@ -78,11 +79,65 @@ public class ExtraFieldUtilsTest extends TestCase implements UnixStat { | |||||
| fail("data should be invalid"); | fail("data should be invalid"); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| assertEquals("message", | 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()); | 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 | * 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]); | |||||
| } | |||||
| } | |||||
| } | } | ||||