git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@1346025 13f79535-47bb-0310-9956-ffa450edef68master
@@ -9,7 +9,6 @@ Changes that could break older environments: | |||
the task now really leaves the EOL characters alone. This also implies that | |||
EOL ASIS will not insert a newline even if fixlast is set to true. | |||
Bugzilla report 53036 | |||
Fixed bugs: | |||
----------- | |||
@@ -34,6 +33,10 @@ Fixed bugs: | |||
Other changes: | |||
-------------- | |||
* merged the ZIP package from Commons Compress, it can now read | |||
archives using Zip64 extensions (files and archives bigger that 4GB | |||
and with more that 64k entries). | |||
Changes from Ant 1.8.3 TO Ant 1.8.4 | |||
=================================== | |||
@@ -105,24 +105,42 @@ public abstract class AbstractUnicodeExtraField implements ZipExtraField { | |||
* @return The utf-8 encoded name. | |||
*/ | |||
public byte[] getUnicodeName() { | |||
return unicodeName; | |||
byte[] b = null; | |||
if (unicodeName != null) { | |||
b = new byte[unicodeName.length]; | |||
System.arraycopy(unicodeName, 0, b, 0, b.length); | |||
} | |||
return b; | |||
} | |||
/** | |||
* @param unicodeName The utf-8 encoded name to set. | |||
*/ | |||
public void setUnicodeName(byte[] unicodeName) { | |||
this.unicodeName = unicodeName; | |||
if (unicodeName != null) { | |||
this.unicodeName = new byte[unicodeName.length]; | |||
System.arraycopy(unicodeName, 0, this.unicodeName, 0, | |||
unicodeName.length); | |||
} else { | |||
this.unicodeName = null; | |||
} | |||
data = null; | |||
} | |||
/** {@inheritDoc} */ | |||
public byte[] getCentralDirectoryData() { | |||
if (data == null) { | |||
this.assembleData(); | |||
} | |||
return data; | |||
byte[] b = null; | |||
if (data != null) { | |||
b = new byte[data.length]; | |||
System.arraycopy(data, 0, b, 0, b.length); | |||
} | |||
return b; | |||
} | |||
/** {@inheritDoc} */ | |||
public ZipShort getCentralDirectoryLength() { | |||
if (data == null) { | |||
assembleData(); | |||
@@ -130,14 +148,17 @@ public abstract class AbstractUnicodeExtraField implements ZipExtraField { | |||
return new ZipShort(data.length); | |||
} | |||
/** {@inheritDoc} */ | |||
public byte[] getLocalFileDataData() { | |||
return getCentralDirectoryData(); | |||
} | |||
/** {@inheritDoc} */ | |||
public ZipShort getLocalFileDataLength() { | |||
return getCentralDirectoryLength(); | |||
} | |||
/** {@inheritDoc} */ | |||
public void parseFromLocalFileData(byte[] buffer, int offset, int length) | |||
throws ZipException { | |||
@@ -45,6 +45,9 @@ import java.util.zip.ZipException; | |||
* <p>Short is two bytes and Long is four bytes in big endian byte and | |||
* word order, device numbers are currently not supported.</p> | |||
* | |||
* <p>Since the documentation this class is based upon doesn't mention | |||
* the character encoding of the file name at all, it is assumed that | |||
* it uses the current platform's default encoding.</p> | |||
*/ | |||
public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { | |||
@@ -116,6 +119,7 @@ public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { | |||
+ 2 // UID | |||
+ 2 // GID | |||
+ getLinkedFile().getBytes().length); | |||
// Uses default charset - see class Javadoc | |||
} | |||
/** | |||
@@ -138,7 +142,7 @@ public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { | |||
byte[] data = new byte[getLocalFileDataLength().getValue() - WORD]; | |||
System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2); | |||
byte[] linkArray = getLinkedFile().getBytes(); | |||
byte[] linkArray = getLinkedFile().getBytes(); // Uses default charset - see class Javadoc | |||
// CheckStyle:MagicNumber OFF | |||
System.arraycopy(ZipLong.getBytes(linkArray.length), | |||
0, data, 2, WORD); | |||
@@ -311,7 +315,7 @@ public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { | |||
link = ""; | |||
} else { | |||
System.arraycopy(tmp, 10, linkArray, 0, linkArray.length); | |||
link = new String(linkArray); | |||
link = new String(linkArray); // Uses default charset - see class Javadoc | |||
} | |||
// CheckStyle:MagicNumber ON | |||
setDirectory((newMode & DIR_FLAG) != 0); | |||
@@ -334,6 +338,7 @@ public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { | |||
return type | (mode & PERM_MASK); | |||
} | |||
@Override | |||
public Object clone() { | |||
try { | |||
AsiExtraField cloned = (AsiExtraField) super.clone(); | |||
@@ -38,14 +38,15 @@ public class ExtraFieldUtils { | |||
* | |||
* @since 1.1 | |||
*/ | |||
private static final Map implementations; | |||
private static final Map<ZipShort, Class<?>> implementations; | |||
static { | |||
implementations = new HashMap(); | |||
implementations = new HashMap<ZipShort, Class<?>>(); | |||
register(AsiExtraField.class); | |||
register(JarMarker.class); | |||
register(UnicodePathExtraField.class); | |||
register(UnicodeCommentExtraField.class); | |||
register(Zip64ExtendedInformationExtraField.class); | |||
} | |||
/** | |||
@@ -57,7 +58,7 @@ public class ExtraFieldUtils { | |||
* | |||
* @since 1.1 | |||
*/ | |||
public static void register(Class c) { | |||
public static void register(Class<?> c) { | |||
try { | |||
ZipExtraField ze = (ZipExtraField) c.newInstance(); | |||
implementations.put(ze.getHeaderId(), c); | |||
@@ -81,7 +82,7 @@ public class ExtraFieldUtils { | |||
*/ | |||
public static ZipExtraField createExtraField(ZipShort headerId) | |||
throws InstantiationException, IllegalAccessException { | |||
Class c = (Class) implementations.get(headerId); | |||
Class<?> c = implementations.get(headerId); | |||
if (c != null) { | |||
return (ZipExtraField) c.newInstance(); | |||
} | |||
@@ -132,7 +133,7 @@ public class ExtraFieldUtils { | |||
public static ZipExtraField[] parse(byte[] data, boolean local, | |||
UnparseableExtraField onUnparseableData) | |||
throws ZipException { | |||
List v = new ArrayList(); | |||
List<ZipExtraField> v = new ArrayList<ZipExtraField>(); | |||
int start = 0; | |||
LOOP: | |||
while (start <= data.length - WORD) { | |||
@@ -158,7 +159,7 @@ public class ExtraFieldUtils { | |||
data.length - start); | |||
} | |||
v.add(field); | |||
/*FALLTHROUGH*/ | |||
//$FALL-THROUGH$ | |||
case UnparseableExtraField.SKIP_KEY: | |||
// since we cannot parse the data we must assume | |||
// the extra field consumes the whole rest of the | |||
@@ -189,7 +190,7 @@ public class ExtraFieldUtils { | |||
} | |||
ZipExtraField[] result = new ZipExtraField[v.size()]; | |||
return (ZipExtraField[]) v.toArray(result); | |||
return v.toArray(result); | |||
} | |||
/** | |||
@@ -205,8 +206,8 @@ public class ExtraFieldUtils { | |||
lastIsUnparseableHolder ? data.length - 1 : data.length; | |||
int sum = WORD * regularExtraFieldCount; | |||
for (int i = 0; i < data.length; i++) { | |||
sum += data[i].getLocalFileDataLength().getValue(); | |||
for (ZipExtraField element : data) { | |||
sum += element.getLocalFileDataLength().getValue(); | |||
} | |||
byte[] result = new byte[sum]; | |||
@@ -240,8 +241,8 @@ public class ExtraFieldUtils { | |||
lastIsUnparseableHolder ? data.length - 1 : data.length; | |||
int sum = WORD * regularExtraFieldCount; | |||
for (int i = 0; i < data.length; i++) { | |||
sum += data[i].getCentralDirectoryLength().getValue(); | |||
for (ZipExtraField element : data) { | |||
sum += element.getCentralDirectoryLength().getValue(); | |||
} | |||
byte[] result = new byte[sum]; | |||
int start = 0; | |||
@@ -30,7 +30,7 @@ import java.nio.ByteBuffer; | |||
* marks leading to unreadable ZIP entries on some operating | |||
* systems.</p> | |||
* | |||
* <p>Furthermore this implementation is unable to tell, whether a | |||
* <p>Furthermore this implementation is unable to tell whether a | |||
* given name can be safely encoded or not.</p> | |||
* | |||
* <p>This implementation acts as a last resort implementation, when | |||
@@ -53,7 +53,7 @@ class FallbackZipEncoding implements ZipEncoding { | |||
/** | |||
* Construct a fallback zip encoding, which uses the given charset. | |||
* | |||
* @param charset The name of the charset or <code>null</code> for | |||
* @param charset The name of the charset or {@code null} for | |||
* the platform's default character set. | |||
*/ | |||
public FallbackZipEncoding(String charset) { | |||
@@ -73,7 +73,7 @@ class FallbackZipEncoding implements ZipEncoding { | |||
* org.apache.tools.zip.ZipEncoding#encode(java.lang.String) | |||
*/ | |||
public ByteBuffer encode(String name) throws IOException { | |||
if (this.charset == null) { | |||
if (this.charset == null) { // i.e. use default charset, see no-args constructor | |||
return ByteBuffer.wrap(name.getBytes()); | |||
} else { | |||
return ByteBuffer.wrap(name.getBytes(this.charset)); | |||
@@ -85,7 +85,7 @@ class FallbackZipEncoding implements ZipEncoding { | |||
* org.apache.tools.zip.ZipEncoding#decode(byte[]) | |||
*/ | |||
public String decode(byte[] data) throws IOException { | |||
if (this.charset == null) { | |||
if (this.charset == null) { // i.e. use default charset, see no-args constructor | |||
return new String(data); | |||
} else { | |||
return new String(data,this.charset); | |||
@@ -0,0 +1,171 @@ | |||
/* | |||
* 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; | |||
/** | |||
* Parser/encoder for the "general purpose bit" field in ZIP's local | |||
* file and central directory headers. | |||
* | |||
* @since Ant 1.9.0 | |||
*/ | |||
public final class GeneralPurposeBit { | |||
/** | |||
* Indicates that the file is encrypted. | |||
*/ | |||
private static final int ENCRYPTION_FLAG = 1 << 0; | |||
/** | |||
* Indicates that a data descriptor stored after the file contents | |||
* will hold CRC and size information. | |||
*/ | |||
private static final int DATA_DESCRIPTOR_FLAG = 1 << 3; | |||
/** | |||
* Indicates strong encryption. | |||
*/ | |||
private static final int STRONG_ENCRYPTION_FLAG = 1 << 6; | |||
/** | |||
* Indicates that filenames are written in utf-8. | |||
* | |||
* <p>The only reason this is public is that {@link | |||
* ZipOutputStream#EFS_FLAG} was public in several versions of | |||
* Apache Ant and we needed a substitute for it.</p> | |||
*/ | |||
public static final int UFT8_NAMES_FLAG = 1 << 11; | |||
private boolean languageEncodingFlag = false; | |||
private boolean dataDescriptorFlag = false; | |||
private boolean encryptionFlag = false; | |||
private boolean strongEncryptionFlag = false; | |||
public GeneralPurposeBit() { | |||
} | |||
/** | |||
* whether the current entry uses UTF8 for file name and comment. | |||
*/ | |||
public boolean usesUTF8ForNames() { | |||
return languageEncodingFlag; | |||
} | |||
/** | |||
* whether the current entry will use UTF8 for file name and comment. | |||
*/ | |||
public void useUTF8ForNames(boolean b) { | |||
languageEncodingFlag = b; | |||
} | |||
/** | |||
* whether the current entry uses the data descriptor to store CRC | |||
* and size information | |||
*/ | |||
public boolean usesDataDescriptor() { | |||
return dataDescriptorFlag; | |||
} | |||
/** | |||
* whether the current entry will use the data descriptor to store | |||
* CRC and size information | |||
*/ | |||
public void useDataDescriptor(boolean b) { | |||
dataDescriptorFlag = b; | |||
} | |||
/** | |||
* whether the current entry is encrypted | |||
*/ | |||
public boolean usesEncryption() { | |||
return encryptionFlag; | |||
} | |||
/** | |||
* whether the current entry will be encrypted | |||
*/ | |||
public void useEncryption(boolean b) { | |||
encryptionFlag = b; | |||
} | |||
/** | |||
* whether the current entry is encrypted using strong encryption | |||
*/ | |||
public boolean usesStrongEncryption() { | |||
return encryptionFlag && strongEncryptionFlag; | |||
} | |||
/** | |||
* whether the current entry will be encrypted using strong encryption | |||
*/ | |||
public void useStrongEncryption(boolean b) { | |||
strongEncryptionFlag = b; | |||
if (b) { | |||
useEncryption(true); | |||
} | |||
} | |||
/** | |||
* Encodes the set bits in a form suitable for ZIP archives. | |||
*/ | |||
public byte[] encode() { | |||
return | |||
ZipShort.getBytes((dataDescriptorFlag ? DATA_DESCRIPTOR_FLAG : 0) | |||
| | |||
(languageEncodingFlag ? UFT8_NAMES_FLAG : 0) | |||
| | |||
(encryptionFlag ? ENCRYPTION_FLAG : 0) | |||
| | |||
(strongEncryptionFlag ? STRONG_ENCRYPTION_FLAG : 0) | |||
); | |||
} | |||
/** | |||
* Parses the supported flags from the given archive data. | |||
* @param data local file header or a central directory entry. | |||
* @param offset offset at which the general purpose bit starts | |||
*/ | |||
public static GeneralPurposeBit parse(final byte[] data, final int offset) { | |||
final int generalPurposeFlag = ZipShort.getValue(data, offset); | |||
GeneralPurposeBit b = new GeneralPurposeBit(); | |||
b.useDataDescriptor((generalPurposeFlag & DATA_DESCRIPTOR_FLAG) != 0); | |||
b.useUTF8ForNames((generalPurposeFlag & UFT8_NAMES_FLAG) != 0); | |||
b.useStrongEncryption((generalPurposeFlag & STRONG_ENCRYPTION_FLAG) | |||
!= 0); | |||
b.useEncryption((generalPurposeFlag & ENCRYPTION_FLAG) != 0); | |||
return b; | |||
} | |||
@Override | |||
public int hashCode() { | |||
return 3 * (7 * (13 * (17 * (encryptionFlag ? 1 : 0) | |||
+ (strongEncryptionFlag ? 1 : 0)) | |||
+ (languageEncodingFlag ? 1 : 0)) | |||
+ (dataDescriptorFlag ? 1 : 0)); | |||
} | |||
@Override | |||
public boolean equals(Object o) { | |||
if (!(o instanceof GeneralPurposeBit)) { | |||
return false; | |||
} | |||
GeneralPurposeBit g = (GeneralPurposeBit) o; | |||
return g.encryptionFlag == encryptionFlag | |||
&& g.strongEncryptionFlag == strongEncryptionFlag | |||
&& g.languageEncodingFlag == languageEncodingFlag | |||
&& g.dataDescriptorFlag == dataDescriptorFlag; | |||
} | |||
} |
@@ -49,7 +49,7 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||
* A character entity, which is put to the reverse mapping table | |||
* of a simple encoding. | |||
*/ | |||
private static final class Simple8BitChar implements Comparable { | |||
private static final class Simple8BitChar implements Comparable<Simple8BitChar> { | |||
public final char unicode; | |||
public final byte code; | |||
@@ -58,15 +58,28 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||
this.unicode = unicode; | |||
} | |||
public int compareTo(Object o) { | |||
Simple8BitChar a = (Simple8BitChar) o; | |||
public int compareTo(Simple8BitChar a) { | |||
return this.unicode - a.unicode; | |||
} | |||
@Override | |||
public String toString() { | |||
return "0x" + Integer.toHexString(0xffff & (int) unicode) | |||
+ "->0x" + Integer.toHexString(0xff & (int) code); | |||
return "0x" + Integer.toHexString(0xffff & unicode) | |||
+ "->0x" + Integer.toHexString(0xff & code); | |||
} | |||
@Override | |||
public boolean equals(Object o) { | |||
if (o instanceof Simple8BitChar) { | |||
Simple8BitChar other = (Simple8BitChar) o; | |||
return unicode == other.unicode && code == other.code; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public int hashCode() { | |||
return unicode; | |||
} | |||
} | |||
@@ -81,24 +94,25 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||
* field. This list is used to binary search reverse mapping of | |||
* unicode characters with a character code greater than 127. | |||
*/ | |||
private final List reverseMapping; | |||
private final List<Simple8BitChar> reverseMapping; | |||
/** | |||
* @param highChars The characters for byte values of 128 to 255 | |||
* stored as an array of 128 chars. | |||
*/ | |||
public Simple8BitZipEncoding(char[] highChars) { | |||
this.highChars = highChars; | |||
this.reverseMapping = new ArrayList(this.highChars.length); | |||
this.highChars = highChars.clone(); | |||
List<Simple8BitChar> temp = | |||
new ArrayList<Simple8BitChar>(this.highChars.length); | |||
byte code = 127; | |||
for (int i = 0; i < this.highChars.length; ++i) { | |||
this.reverseMapping.add(new Simple8BitChar(++code, | |||
this.highChars[i])); | |||
temp.add(new Simple8BitChar(++code, this.highChars[i])); | |||
} | |||
Collections.sort(this.reverseMapping); | |||
Collections.sort(temp); | |||
this.reverseMapping = Collections.unmodifiableList(temp); | |||
} | |||
/** | |||
@@ -114,7 +128,7 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||
} | |||
// byte is signed, so 128 == -128 and 255 == -1 | |||
return this.highChars[128 + (int) b]; | |||
return this.highChars[128 + b]; | |||
} | |||
/** | |||
@@ -137,7 +151,7 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||
* @param bb The byte buffer to write to. | |||
* @param c The character to encode. | |||
* @return Whether the given unicode character is covered by this encoding. | |||
* If <code>false</code> is returned, nothing is pushed to the | |||
* If {@code false} is returned, nothing is pushed to the | |||
* byte buffer. | |||
*/ | |||
public boolean pushEncodedChar(ByteBuffer bb, char c) { | |||
@@ -158,7 +172,7 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||
/** | |||
* @param c A unicode character in the range from 0x0080 to 0x7f00 | |||
* @return A Simple8BitChar, if this character is covered by this encoding. | |||
* A <code>null</code> value is returned, if this character is not | |||
* A {@code null} value is returned, if this character is not | |||
* covered by this encoding. | |||
*/ | |||
private Simple8BitChar encodeHighChar(char c) { | |||
@@ -171,7 +185,7 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||
int i = i0 + (i1 - i0) / 2; | |||
Simple8BitChar m = (Simple8BitChar) this.reverseMapping.get(i); | |||
Simple8BitChar m = this.reverseMapping.get(i); | |||
if (m.unicode == c) { | |||
return m; | |||
@@ -188,7 +202,7 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||
return null; | |||
} | |||
Simple8BitChar r = (Simple8BitChar) this.reverseMapping.get(i0); | |||
Simple8BitChar r = this.reverseMapping.get(i0); | |||
if (r.unicode != c) { | |||
return null; | |||
@@ -67,6 +67,7 @@ public class UnicodeCommentExtraField extends AbstractUnicodeExtraField { | |||
super(comment, bytes); | |||
} | |||
/** {@inheritDoc} */ | |||
public ZipShort getHeaderId() { | |||
return UCOM_ID; | |||
} | |||
@@ -66,6 +66,7 @@ public class UnicodePathExtraField extends AbstractUnicodeExtraField { | |||
super(name, bytes); | |||
} | |||
/** {@inheritDoc} */ | |||
public ZipShort getHeaderId() { | |||
return UPATH_ID; | |||
} | |||
@@ -21,12 +21,11 @@ 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 | |||
* the .ZIP File Format Specification). | |||
* Since it isn't used anywhere except to satisfy the | |||
* <p>The header-id is artificial (and not listed as a known ID in | |||
* {@link <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT"> | |||
* APPNOTE.TXT</a>}). Since it isn't used anywhere except to satisfy the | |||
* ZipExtraField contract it shouldn't matter anyway.</p> | |||
* @see <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT | |||
* APPNOTE.TXT">.ZIP File Format Specification</a> | |||
* | |||
* @since Ant 1.8.1 | |||
*/ | |||
public final class UnparseableExtraFieldData | |||
@@ -103,7 +102,6 @@ public final class UnparseableExtraFieldData | |||
* @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) { | |||
@@ -0,0 +1,89 @@ | |||
/* | |||
* 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; | |||
import java.util.zip.ZipException; | |||
/** | |||
* Exception thrown when attempting to read or write data for a zip | |||
* entry that uses ZIP features not supported by this library. | |||
* @since Ant 1.9.0 | |||
*/ | |||
public class UnsupportedZipFeatureException extends ZipException { | |||
private final Feature reason; | |||
private final ZipEntry entry; | |||
private static final long serialVersionUID = 4430521921766595597L; | |||
/** | |||
* Creates an exception. | |||
* @param reason the feature that is not supported | |||
* @param entry the entry using the feature | |||
*/ | |||
public UnsupportedZipFeatureException(Feature reason, | |||
ZipEntry entry) { | |||
super("unsupported feature " + reason + " used in entry " | |||
+ entry.getName()); | |||
this.reason = reason; | |||
this.entry = entry; | |||
} | |||
/** | |||
* The unsupported feature that has been used. | |||
*/ | |||
public Feature getFeature() { | |||
return reason; | |||
} | |||
/** | |||
* The entry using the unsupported feature. | |||
*/ | |||
public ZipEntry getEntry() { | |||
return entry; | |||
} | |||
/** | |||
* ZIP Features that may or may not be supported. | |||
*/ | |||
public static class Feature { | |||
/** | |||
* The entry is encrypted. | |||
*/ | |||
public static final Feature ENCRYPTION = new Feature("encryption"); | |||
/** | |||
* The entry used an unsupported compression method. | |||
*/ | |||
public static final Feature METHOD = new Feature("compression method"); | |||
/** | |||
* The entry uses a data descriptor. | |||
*/ | |||
public static final Feature DATA_DESCRIPTOR = new Feature("data descriptor"); | |||
private final String name; | |||
private Feature(String name) { | |||
this.name = name; | |||
} | |||
@Override | |||
public String toString() { | |||
return name; | |||
} | |||
} | |||
} |
@@ -0,0 +1,354 @@ | |||
/* | |||
* 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; | |||
import java.util.zip.ZipException; | |||
import static org.apache.tools.zip.ZipConstants.DWORD; | |||
import static org.apache.tools.zip.ZipConstants.WORD; | |||
/** | |||
* Holds size and other extended information for entries that use Zip64 | |||
* features. | |||
* | |||
* <p>From {@link "http://www.pkware.com/documents/casestudies/APPNOTE.TXT PKWARE's APPNOTE.TXT"} | |||
* <pre> | |||
* Zip64 Extended Information Extra Field (0x0001): | |||
* | |||
* The following is the layout of the zip64 extended | |||
* information "extra" block. If one of the size or | |||
* offset fields in the Local or Central directory | |||
* record is too small to hold the required data, | |||
* a Zip64 extended information record is created. | |||
* The order of the fields in the zip64 extended | |||
* information record is fixed, but the fields will | |||
* only appear if the corresponding Local or Central | |||
* directory record field is set to 0xFFFF or 0xFFFFFFFF. | |||
* | |||
* Note: all fields stored in Intel low-byte/high-byte order. | |||
* | |||
* Value Size Description | |||
* ----- ---- ----------- | |||
* (ZIP64) 0x0001 2 bytes Tag for this "extra" block type | |||
* Size 2 bytes Size of this "extra" block | |||
* Original | |||
* Size 8 bytes Original uncompressed file size | |||
* Compressed | |||
* Size 8 bytes Size of compressed data | |||
* Relative Header | |||
* Offset 8 bytes Offset of local header record | |||
* Disk Start | |||
* Number 4 bytes Number of the disk on which | |||
* this file starts | |||
* | |||
* This entry in the Local header must include BOTH original | |||
* and compressed file size fields. If encrypting the | |||
* central directory and bit 13 of the general purpose bit | |||
* flag is set indicating masking, the value stored in the | |||
* Local Header for the original file size will be zero. | |||
* </pre></p> | |||
* | |||
* <p>Currently Ant doesn't support encrypting the | |||
* central directory so the note about masking doesn't apply.</p> | |||
* | |||
* <p>The implementation relies on data being read from the local file | |||
* header and assumes that both size values are always present.</p> | |||
* | |||
* @since Ant 1.9.0 | |||
*/ | |||
public class Zip64ExtendedInformationExtraField | |||
implements CentralDirectoryParsingZipExtraField { | |||
static final ZipShort HEADER_ID = new ZipShort(0x0001); | |||
private static final String LFH_MUST_HAVE_BOTH_SIZES_MSG = | |||
"Zip64 extended information must contain" | |||
+ " both size values in the local file header."; | |||
private ZipEightByteInteger size, compressedSize, relativeHeaderOffset; | |||
private ZipLong diskStart; | |||
/** | |||
* Stored in {@link #parseFromCentralDirectoryData | |||
* parseFromCentralDirectoryData} so it can be reused when ZipFile | |||
* calls {@link #reparseCentralDirectoryData | |||
* reparseCentralDirectoryData}. | |||
* | |||
* <p>Not used for anything else</p> | |||
*/ | |||
private byte[] rawCentralDirectoryData; | |||
/** | |||
* This constructor should only be used by the code that reads | |||
* archives inside of Ant. | |||
*/ | |||
public Zip64ExtendedInformationExtraField() { } | |||
/** | |||
* Creates an extra field based on the original and compressed size. | |||
* | |||
* @param size the entry's original size | |||
* @param compressedSize the entry's compressed size | |||
* | |||
* @throws IllegalArgumentException if size or compressedSize is null | |||
*/ | |||
public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, | |||
ZipEightByteInteger compressedSize) { | |||
this(size, compressedSize, null, null); | |||
} | |||
/** | |||
* Creates an extra field based on all four possible values. | |||
* | |||
* @param size the entry's original size | |||
* @param compressedSize the entry's compressed size | |||
* | |||
* @throws IllegalArgumentException if size or compressedSize is null | |||
*/ | |||
public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, | |||
ZipEightByteInteger compressedSize, | |||
ZipEightByteInteger relativeHeaderOffset, | |||
ZipLong diskStart) { | |||
this.size = size; | |||
this.compressedSize = compressedSize; | |||
this.relativeHeaderOffset = relativeHeaderOffset; | |||
this.diskStart = diskStart; | |||
} | |||
/** {@inheritDoc} */ | |||
public ZipShort getHeaderId() { | |||
return HEADER_ID; | |||
} | |||
/** {@inheritDoc} */ | |||
public ZipShort getLocalFileDataLength() { | |||
return new ZipShort(size != null ? 2 * DWORD : 0); | |||
} | |||
/** {@inheritDoc} */ | |||
public ZipShort getCentralDirectoryLength() { | |||
return new ZipShort((size != null ? DWORD : 0) | |||
+ (compressedSize != null ? DWORD : 0) | |||
+ (relativeHeaderOffset != null ? DWORD : 0) | |||
+ (diskStart != null ? WORD : 0)); | |||
} | |||
/** {@inheritDoc} */ | |||
public byte[] getLocalFileDataData() { | |||
if (size != null || compressedSize != null) { | |||
if (size == null || compressedSize == null) { | |||
throw new IllegalArgumentException(LFH_MUST_HAVE_BOTH_SIZES_MSG); | |||
} | |||
byte[] data = new byte[2 * DWORD]; | |||
addSizes(data); | |||
return data; | |||
} | |||
return new byte[0]; | |||
} | |||
/** {@inheritDoc} */ | |||
public byte[] getCentralDirectoryData() { | |||
byte[] data = new byte[getCentralDirectoryLength().getValue()]; | |||
int off = addSizes(data); | |||
if (relativeHeaderOffset != null) { | |||
System.arraycopy(relativeHeaderOffset.getBytes(), 0, data, off, DWORD); | |||
off += DWORD; | |||
} | |||
if (diskStart != null) { | |||
System.arraycopy(diskStart.getBytes(), 0, data, off, WORD); | |||
off += WORD; | |||
} | |||
return data; | |||
} | |||
/** {@inheritDoc} */ | |||
public void parseFromLocalFileData(byte[] buffer, int offset, int length) | |||
throws ZipException { | |||
if (length == 0) { | |||
// no local file data at all, may happen if an archive | |||
// only holds a ZIP64 extended information extra field | |||
// inside the central directory but not inside the local | |||
// file header | |||
return; | |||
} | |||
if (length < 2 * DWORD) { | |||
throw new ZipException(LFH_MUST_HAVE_BOTH_SIZES_MSG); | |||
} | |||
size = new ZipEightByteInteger(buffer, offset); | |||
offset += DWORD; | |||
compressedSize = new ZipEightByteInteger(buffer, offset); | |||
offset += DWORD; | |||
int remaining = length - 2 * DWORD; | |||
if (remaining >= DWORD) { | |||
relativeHeaderOffset = new ZipEightByteInteger(buffer, offset); | |||
offset += DWORD; | |||
remaining -= DWORD; | |||
} | |||
if (remaining >= WORD) { | |||
diskStart = new ZipLong(buffer, offset); | |||
offset += WORD; | |||
remaining -= WORD; | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
public void parseFromCentralDirectoryData(byte[] buffer, int offset, | |||
int length) | |||
throws ZipException { | |||
// store for processing in reparseCentralDirectoryData | |||
rawCentralDirectoryData = new byte[length]; | |||
System.arraycopy(buffer, offset, rawCentralDirectoryData, 0, length); | |||
// if there is no size information in here, we are screwed and | |||
// can only hope things will get resolved by LFH data later | |||
// But there are some cases that can be detected | |||
// * all data is there | |||
// * length == 24 -> both sizes and offset | |||
// * length % 8 == 4 -> at least we can identify the diskStart field | |||
if (length >= 3 * DWORD + WORD) { | |||
parseFromLocalFileData(buffer, offset, length); | |||
} else if (length == 3 * DWORD) { | |||
size = new ZipEightByteInteger(buffer, offset); | |||
offset += DWORD; | |||
compressedSize = new ZipEightByteInteger(buffer, offset); | |||
offset += DWORD; | |||
relativeHeaderOffset = new ZipEightByteInteger(buffer, offset); | |||
} else if (length % DWORD == WORD) { | |||
diskStart = new ZipLong(buffer, offset + length - WORD); | |||
} | |||
} | |||
/** | |||
* Parses the raw bytes read from the central directory extra | |||
* field with knowledge which fields are expected to be there. | |||
* | |||
* <p>All four fields inside the zip64 extended information extra | |||
* field are optional and only present if their corresponding | |||
* entry inside the central directory contains the correct magic | |||
* value.</p> | |||
*/ | |||
public void reparseCentralDirectoryData(boolean hasUncompressedSize, | |||
boolean hasCompressedSize, | |||
boolean hasRelativeHeaderOffset, | |||
boolean hasDiskStart) | |||
throws ZipException { | |||
if (rawCentralDirectoryData != null) { | |||
int expectedLength = (hasUncompressedSize ? DWORD : 0) | |||
+ (hasCompressedSize ? DWORD : 0) | |||
+ (hasRelativeHeaderOffset ? DWORD : 0) | |||
+ (hasDiskStart ? WORD : 0); | |||
if (rawCentralDirectoryData.length != expectedLength) { | |||
throw new ZipException("central directory zip64 extended" | |||
+ " information extra field's length" | |||
+ " doesn't match central directory" | |||
+ " data. Expected length " | |||
+ expectedLength + " but is " | |||
+ rawCentralDirectoryData.length); | |||
} | |||
int offset = 0; | |||
if (hasUncompressedSize) { | |||
size = new ZipEightByteInteger(rawCentralDirectoryData, offset); | |||
offset += DWORD; | |||
} | |||
if (hasCompressedSize) { | |||
compressedSize = new ZipEightByteInteger(rawCentralDirectoryData, | |||
offset); | |||
offset += DWORD; | |||
} | |||
if (hasRelativeHeaderOffset) { | |||
relativeHeaderOffset = | |||
new ZipEightByteInteger(rawCentralDirectoryData, offset); | |||
offset += DWORD; | |||
} | |||
if (hasDiskStart) { | |||
diskStart = new ZipLong(rawCentralDirectoryData, offset); | |||
offset += WORD; | |||
} | |||
} | |||
} | |||
/** | |||
* The uncompressed size stored in this extra field. | |||
*/ | |||
public ZipEightByteInteger getSize() { | |||
return size; | |||
} | |||
/** | |||
* The uncompressed size stored in this extra field. | |||
*/ | |||
public void setSize(ZipEightByteInteger size) { | |||
this.size = size; | |||
} | |||
/** | |||
* The compressed size stored in this extra field. | |||
*/ | |||
public ZipEightByteInteger getCompressedSize() { | |||
return compressedSize; | |||
} | |||
/** | |||
* The uncompressed size stored in this extra field. | |||
*/ | |||
public void setCompressedSize(ZipEightByteInteger compressedSize) { | |||
this.compressedSize = compressedSize; | |||
} | |||
/** | |||
* The relative header offset stored in this extra field. | |||
*/ | |||
public ZipEightByteInteger getRelativeHeaderOffset() { | |||
return relativeHeaderOffset; | |||
} | |||
/** | |||
* The relative header offset stored in this extra field. | |||
*/ | |||
public void setRelativeHeaderOffset(ZipEightByteInteger rho) { | |||
relativeHeaderOffset = rho; | |||
} | |||
/** | |||
* The disk start number stored in this extra field. | |||
*/ | |||
public ZipLong getDiskStartNumber() { | |||
return diskStart; | |||
} | |||
/** | |||
* The disk start number stored in this extra field. | |||
*/ | |||
public void setDiskStartNumber(ZipLong ds) { | |||
diskStart = ds; | |||
} | |||
private int addSizes(byte[] data) { | |||
int off = 0; | |||
if (size != null) { | |||
System.arraycopy(size.getBytes(), 0, data, 0, DWORD); | |||
off += DWORD; | |||
} | |||
if (compressedSize != null) { | |||
System.arraycopy(compressedSize.getBytes(), 0, data, off, DWORD); | |||
off += DWORD; | |||
} | |||
return off; | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
/* | |||
* 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; | |||
/** | |||
* The different modes {@link ZipOutputStream} can operate in. | |||
* | |||
* @see ZipOutputStream#setUseZip64 | |||
* | |||
* @since Ant 1.9.0 | |||
*/ | |||
public enum Zip64Mode { | |||
/** | |||
* Use Zip64 extensions for all entries, even if it is clear it is | |||
* not required. | |||
*/ | |||
Always, | |||
/** | |||
* Don't use Zip64 extensions for any entries. | |||
* | |||
* <p>This will cause a {@link Zip64RequiredException} to be | |||
* thrown if {@link ZipOutputStream} detects it needs Zip64 | |||
* support.</p> | |||
*/ | |||
Never, | |||
/** | |||
* Use Zip64 extensions for all entries where they are required, | |||
* don't use them for entries that clearly don't require them. | |||
*/ | |||
AsNeeded | |||
} |
@@ -0,0 +1,49 @@ | |||
/* | |||
* 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; | |||
import java.util.zip.ZipException; | |||
/** | |||
* Exception thrown when attempting to write data that requires Zip64 | |||
* support to an archive and {@link ZipOutputStream#setUseZip64 | |||
* UseZip64} has been set to {@link Zip64Mode#Never Never}. | |||
* @since Ant 1.9.0 | |||
*/ | |||
public class Zip64RequiredException extends ZipException { | |||
private static final long serialVersionUID = 20110809L; | |||
/** | |||
* Helper to format "entry too big" messages. | |||
*/ | |||
static String getEntryTooBigMessage(ZipEntry ze) { | |||
return ze.getName() + "'s size exceeds the limit of 4GByte."; | |||
} | |||
static final String ARCHIVE_TOO_BIG_MESSAGE = | |||
"archive's size exceeds the limit of 4GByte."; | |||
static final String TOO_MANY_ENTRIES_MESSAGE = | |||
"archive contains more than 65535 entries."; | |||
public Zip64RequiredException(String reason) { | |||
super(reason); | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
* 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; | |||
/** | |||
* Various constants used throughout the package. | |||
*/ | |||
final class ZipConstants { | |||
private ZipConstants() { } | |||
/** Masks last eight bits */ | |||
static final int BYTE_MASK = 0xFF; | |||
/** length of a ZipShort in bytes */ | |||
static final int SHORT = 2; | |||
/** length of a ZipLong in bytes */ | |||
static final int WORD = 4; | |||
/** length of a ZipEightByteInteger in bytes */ | |||
static final int DWORD = 8; | |||
/** Initial ZIP specification version */ | |||
static final int INITIAL_VERSION = 10; | |||
/** ZIP specification version that introduced data descriptor method */ | |||
static final int DATA_DESCRIPTOR_MIN_VERSION = 20; | |||
/** ZIP specification version that introduced ZIP64 */ | |||
static final int ZIP64_MIN_VERSION = 45; | |||
/** | |||
* Value stored in two-byte size and similar fields if ZIP64 | |||
* extensions are used. | |||
*/ | |||
static final int ZIP64_MAGIC_SHORT = 0xFFFF; | |||
/** | |||
* Value stored in four-byte size and similar fields if ZIP64 | |||
* extensions are used. | |||
*/ | |||
static final long ZIP64_MAGIC = 0xFFFFFFFFL; | |||
} |
@@ -0,0 +1,229 @@ | |||
/* | |||
* 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; | |||
import java.math.BigInteger; | |||
import static org.apache.tools.zip.ZipConstants.BYTE_MASK; | |||
/** | |||
* Utility class that represents an eight byte integer with conversion | |||
* rules for the big endian byte order of ZIP files. | |||
*/ | |||
public final class ZipEightByteInteger { | |||
private static final int BYTE_1 = 1; | |||
private static final int BYTE_1_MASK = 0xFF00; | |||
private static final int BYTE_1_SHIFT = 8; | |||
private static final int BYTE_2 = 2; | |||
private static final int BYTE_2_MASK = 0xFF0000; | |||
private static final int BYTE_2_SHIFT = 16; | |||
private static final int BYTE_3 = 3; | |||
private static final long BYTE_3_MASK = 0xFF000000L; | |||
private static final int BYTE_3_SHIFT = 24; | |||
private static final int BYTE_4 = 4; | |||
private static final long BYTE_4_MASK = 0xFF00000000L; | |||
private static final int BYTE_4_SHIFT = 32; | |||
private static final int BYTE_5 = 5; | |||
private static final long BYTE_5_MASK = 0xFF0000000000L; | |||
private static final int BYTE_5_SHIFT = 40; | |||
private static final int BYTE_6 = 6; | |||
private static final long BYTE_6_MASK = 0xFF000000000000L; | |||
private static final int BYTE_6_SHIFT = 48; | |||
private static final int BYTE_7 = 7; | |||
private static final long BYTE_7_MASK = 0x7F00000000000000L; | |||
private static final int BYTE_7_SHIFT = 56; | |||
private static final int LEFTMOST_BIT_SHIFT = 63; | |||
private static final byte LEFTMOST_BIT = (byte) 0x80; | |||
private final BigInteger value; | |||
public static final ZipEightByteInteger ZERO = new ZipEightByteInteger(0); | |||
/** | |||
* Create instance from a number. | |||
* @param value the long to store as a ZipEightByteInteger | |||
*/ | |||
public ZipEightByteInteger(long value) { | |||
this(BigInteger.valueOf(value)); | |||
} | |||
/** | |||
* Create instance from a number. | |||
* @param value the BigInteger to store as a ZipEightByteInteger | |||
*/ | |||
public ZipEightByteInteger(BigInteger value) { | |||
this.value = value; | |||
} | |||
/** | |||
* Create instance from bytes. | |||
* @param bytes the bytes to store as a ZipEightByteInteger | |||
*/ | |||
public ZipEightByteInteger (byte[] bytes) { | |||
this(bytes, 0); | |||
} | |||
/** | |||
* Create instance from the eight bytes starting at offset. | |||
* @param bytes the bytes to store as a ZipEightByteInteger | |||
* @param offset the offset to start | |||
*/ | |||
public ZipEightByteInteger (byte[] bytes, int offset) { | |||
value = ZipEightByteInteger.getValue(bytes, offset); | |||
} | |||
/** | |||
* Get value as eight bytes in big endian byte order. | |||
* @return value as eight bytes in big endian order | |||
*/ | |||
public byte[] getBytes() { | |||
return ZipEightByteInteger.getBytes(value); | |||
} | |||
/** | |||
* Get value as Java long. | |||
* @return value as a long | |||
*/ | |||
public long getLongValue() { | |||
return value.longValue(); | |||
} | |||
/** | |||
* Get value as Java long. | |||
* @return value as a long | |||
*/ | |||
public BigInteger getValue() { | |||
return value; | |||
} | |||
/** | |||
* Get value as eight bytes in big endian byte order. | |||
* @param value the value to convert | |||
* @return value as eight bytes in big endian byte order | |||
*/ | |||
public static byte[] getBytes(long value) { | |||
return getBytes(BigInteger.valueOf(value)); | |||
} | |||
/** | |||
* Get value as eight bytes in big endian byte order. | |||
* @param value the value to convert | |||
* @return value as eight bytes in big endian byte order | |||
*/ | |||
public static byte[] getBytes(BigInteger value) { | |||
byte[] result = new byte[8]; | |||
long val = value.longValue(); | |||
result[0] = (byte) ((val & BYTE_MASK)); | |||
result[BYTE_1] = (byte) ((val & BYTE_1_MASK) >> BYTE_1_SHIFT); | |||
result[BYTE_2] = (byte) ((val & BYTE_2_MASK) >> BYTE_2_SHIFT); | |||
result[BYTE_3] = (byte) ((val & BYTE_3_MASK) >> BYTE_3_SHIFT); | |||
result[BYTE_4] = (byte) ((val & BYTE_4_MASK) >> BYTE_4_SHIFT); | |||
result[BYTE_5] = (byte) ((val & BYTE_5_MASK) >> BYTE_5_SHIFT); | |||
result[BYTE_6] = (byte) ((val & BYTE_6_MASK) >> BYTE_6_SHIFT); | |||
result[BYTE_7] = (byte) ((val & BYTE_7_MASK) >> BYTE_7_SHIFT); | |||
if (value.testBit(LEFTMOST_BIT_SHIFT)) { | |||
result[BYTE_7] |= LEFTMOST_BIT; | |||
} | |||
return result; | |||
} | |||
/** | |||
* Helper method to get the value as a Java long from eight bytes | |||
* starting at given array offset | |||
* @param bytes the array of bytes | |||
* @param offset the offset to start | |||
* @return the corresponding Java long value | |||
*/ | |||
public static long getLongValue(byte[] bytes, int offset) { | |||
return getValue(bytes, offset).longValue(); | |||
} | |||
/** | |||
* Helper method to get the value as a Java BigInteger from eight | |||
* bytes starting at given array offset | |||
* @param bytes the array of bytes | |||
* @param offset the offset to start | |||
* @return the corresponding Java BigInteger value | |||
*/ | |||
public static BigInteger getValue(byte[] bytes, int offset) { | |||
long value = ((long) bytes[offset + BYTE_7] << BYTE_7_SHIFT) & BYTE_7_MASK; | |||
value += ((long) bytes[offset + BYTE_6] << BYTE_6_SHIFT) & BYTE_6_MASK; | |||
value += ((long) bytes[offset + BYTE_5] << BYTE_5_SHIFT) & BYTE_5_MASK; | |||
value += ((long) bytes[offset + BYTE_4] << BYTE_4_SHIFT) & BYTE_4_MASK; | |||
value += ((long) bytes[offset + BYTE_3] << BYTE_3_SHIFT) & BYTE_3_MASK; | |||
value += ((long) bytes[offset + BYTE_2] << BYTE_2_SHIFT) & BYTE_2_MASK; | |||
value += ((long) bytes[offset + BYTE_1] << BYTE_1_SHIFT) & BYTE_1_MASK; | |||
value += ((long) bytes[offset] & BYTE_MASK); | |||
BigInteger val = BigInteger.valueOf(value); | |||
return (bytes[offset + BYTE_7] & LEFTMOST_BIT) == LEFTMOST_BIT | |||
? val.setBit(LEFTMOST_BIT_SHIFT) : val; | |||
} | |||
/** | |||
* Helper method to get the value as a Java long from an eight-byte array | |||
* @param bytes the array of bytes | |||
* @return the corresponding Java long value | |||
*/ | |||
public static long getLongValue(byte[] bytes) { | |||
return getLongValue(bytes, 0); | |||
} | |||
/** | |||
* Helper method to get the value as a Java long from an eight-byte array | |||
* @param bytes the array of bytes | |||
* @return the corresponding Java BigInteger value | |||
*/ | |||
public static BigInteger getValue(byte[] bytes) { | |||
return getValue(bytes, 0); | |||
} | |||
/** | |||
* Override to make two instances with same value equal. | |||
* @param o an object to compare | |||
* @return true if the objects are equal | |||
*/ | |||
@Override | |||
public boolean equals(Object o) { | |||
if (o == null || !(o instanceof ZipEightByteInteger)) { | |||
return false; | |||
} | |||
return value.equals(((ZipEightByteInteger) o).getValue()); | |||
} | |||
/** | |||
* Override to make two instances with same value equal. | |||
* @return the hashCode of the value stored in the ZipEightByteInteger | |||
*/ | |||
@Override | |||
public int hashCode() { | |||
return value.hashCode(); | |||
} | |||
@Override | |||
public String toString() { | |||
return "ZipEightByteInteger value: " + value; | |||
} | |||
} |
@@ -68,7 +68,7 @@ interface ZipEncoding { | |||
* character sequences are mapped to a sequence of utf-16 | |||
* words encoded in the format <code>%Uxxxx</code>. It is | |||
* assumed, that the byte buffer is positioned at the | |||
* beinning of the encoded result, the byte buffer has a | |||
* beginning of the encoded result, the byte buffer has a | |||
* backing array and the limit of the byte buffer points | |||
* to the end of the encoded result. | |||
* @throws IOException | |||
@@ -62,10 +62,10 @@ abstract class ZipEncodingHelper { | |||
} | |||
} | |||
private static final Map simpleEncodings; | |||
private static final Map<String, SimpleEncodingHolder> simpleEncodings; | |||
static { | |||
simpleEncodings = new HashMap(); | |||
simpleEncodings = new HashMap<String, SimpleEncodingHolder>(); | |||
char[] cp437_high_chars = | |||
new char[] { 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, | |||
@@ -203,7 +203,7 @@ abstract class ZipEncodingHelper { | |||
/** | |||
* Instantiates a zip encoding. | |||
* | |||
* @param name The name of the zip encoding. Specify <code>null</code> for | |||
* @param name The name of the zip encoding. Specify {@code null} for | |||
* the platform's default encoding. | |||
* @return A zip encoding for the given encoding name. | |||
*/ | |||
@@ -218,8 +218,7 @@ abstract class ZipEncodingHelper { | |||
return new FallbackZipEncoding(); | |||
} | |||
SimpleEncodingHolder h = | |||
(SimpleEncodingHolder) simpleEncodings.get(name); | |||
SimpleEncodingHolder h = simpleEncodings.get(name); | |||
if (h!=null) { | |||
return h.getEncoding(); | |||
@@ -18,7 +18,10 @@ | |||
package org.apache.tools.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; | |||
@@ -28,7 +31,8 @@ import java.util.zip.ZipException; | |||
* access to the internal and external file attributes. | |||
* | |||
* <p>The extra data is expected to follow the recommendation of | |||
* the .ZIP File Format Specification created by PKWARE Inc. :</p> | |||
* {@link <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT"> | |||
* APPNOTE.txt</a>}:</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 | |||
@@ -38,11 +42,9 @@ import java.util.zip.ZipException; | |||
* | |||
* <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> | |||
* @see <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT"> | |||
* .ZIP File Format Specification</a> | |||
* methods of this class. Older versions 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 { | |||
@@ -52,30 +54,59 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
private static final int SHORT_MASK = 0xFFFF; | |||
private static final int SHORT_SHIFT = 16; | |||
/** | |||
* The {@link java.util.zip.ZipEntry} base class only supports | |||
* the compression methods STORED and DEFLATED. We override the | |||
* field so that any compression methods can be used. | |||
* <p> | |||
* The default value -1 means that the method has not been specified. | |||
*/ | |||
private int method = -1; | |||
/** | |||
* The {@link java.util.zip.ZipEntry#setSize} method in the base | |||
* class throws an IllegalArgumentException if the size is bigger | |||
* than 2GB for Java versions < 7. Need to keep our own size | |||
* information for Zip64 support. | |||
*/ | |||
private long size = -1; | |||
private int internalAttributes = 0; | |||
private int platform = PLATFORM_FAT; | |||
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 byte[] rawName = null; | |||
private GeneralPurposeBit gpb = new GeneralPurposeBit(); | |||
/** | |||
* Creates a new zip entry with the specified name. | |||
* | |||
* <p>Assumes the entry represents a directory if and only if the | |||
* name ends with a forward slash "/".</p> | |||
* | |||
* @param name the name of the entry | |||
* @since 1.1 | |||
*/ | |||
public ZipEntry(String name) { | |||
super(name); | |||
setName(name); | |||
} | |||
/** | |||
* Creates a new zip entry with fields taken from the specified zip entry. | |||
* | |||
* <p>Assumes the entry represents a directory if and only if the | |||
* name ends with a forward slash "/".</p> | |||
* | |||
* @param entry the entry to get fields from | |||
* @since 1.1 | |||
* @throws ZipException on error | |||
*/ | |||
public ZipEntry(java.util.zip.ZipEntry entry) throws ZipException { | |||
super(entry); | |||
setName(entry.getName()); | |||
byte[] extra = entry.getExtra(); | |||
if (extra != null) { | |||
setExtraFields(ExtraFieldUtils.parse(extra, true, | |||
@@ -85,10 +116,16 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
// initializes extra data to an empty byte array | |||
setExtra(); | |||
} | |||
setMethod(entry.getMethod()); | |||
this.size = entry.getSize(); | |||
} | |||
/** | |||
* Creates a new zip entry with fields taken from the specified zip entry. | |||
* | |||
* <p>Assumes the entry represents a directory if and only if the | |||
* name ends with a forward slash "/".</p> | |||
* | |||
* @param entry the entry to get fields from | |||
* @throws ZipException on error | |||
* @since 1.1 | |||
@@ -104,7 +141,26 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @since 1.9 | |||
*/ | |||
protected ZipEntry() { | |||
super(""); | |||
this(""); | |||
} | |||
/** | |||
* Creates a new zip entry taking some information from the given | |||
* file and using the provided name. | |||
* | |||
* <p>The name will be adjusted to end with a forward slash "/" if | |||
* the file is a directory. If the file is not a directory a | |||
* potential trailing forward slash will be stripped from the | |||
* entry name.</p> | |||
*/ | |||
public ZipEntry(File inputFile, String entryName) { | |||
this(inputFile.isDirectory() && !entryName.endsWith("/") ? | |||
entryName + "/" : entryName); | |||
if (inputFile.isFile()){ | |||
setSize(inputFile.length()); | |||
} | |||
setTime(inputFile.lastModified()); | |||
// TODO are there any other fields we can set here? | |||
} | |||
/** | |||
@@ -112,6 +168,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @return a cloned copy of this ZipEntry | |||
* @since 1.1 | |||
*/ | |||
@Override | |||
public Object clone() { | |||
ZipEntry e = (ZipEntry) super.clone(); | |||
@@ -121,6 +178,31 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
return e; | |||
} | |||
/** | |||
* Returns the compression method of this entry, or -1 if the | |||
* compression method has not been specified. | |||
* | |||
* @return compression method | |||
*/ | |||
@Override | |||
public int getMethod() { | |||
return method; | |||
} | |||
/** | |||
* Sets the compression method of this entry. | |||
* | |||
* @param method compression method | |||
*/ | |||
@Override | |||
public void setMethod(int method) { | |||
if (method < 0) { | |||
throw new IllegalArgumentException( | |||
"ZIP compression method can not be negative: " + method); | |||
} | |||
this.method = method; | |||
} | |||
/** | |||
* Retrieves the internal file attributes. | |||
* | |||
@@ -213,12 +295,12 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @since 1.1 | |||
*/ | |||
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]; | |||
extraFields = new LinkedHashMap<ZipShort, ZipExtraField>(); | |||
for (ZipExtraField field : fields) { | |||
if (field instanceof UnparseableExtraFieldData) { | |||
unparseableExtra = (UnparseableExtraFieldData) field; | |||
} else { | |||
extraFields.put(fields[i].getHeaderId(), fields[i]); | |||
extraFields.put(field.getHeaderId(), field); | |||
} | |||
} | |||
setExtra(); | |||
@@ -246,11 +328,12 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
? new ZipExtraField[0] | |||
: new ZipExtraField[] { unparseableExtra }; | |||
} | |||
List result = new ArrayList(extraFields.values()); | |||
List<ZipExtraField> result = | |||
new ArrayList<ZipExtraField>(extraFields.values()); | |||
if (includeUnparseable && unparseableExtra != null) { | |||
result.add(unparseableExtra); | |||
} | |||
return (ZipExtraField[]) result.toArray(new ZipExtraField[0]); | |||
return result.toArray(new ZipExtraField[0]); | |||
} | |||
/** | |||
@@ -267,7 +350,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
unparseableExtra = (UnparseableExtraFieldData) ze; | |||
} else { | |||
if (extraFields == null) { | |||
extraFields = new LinkedHashMap(); | |||
extraFields = new LinkedHashMap<ZipShort, ZipExtraField>(); | |||
} | |||
extraFields.put(ze.getHeaderId(), ze); | |||
} | |||
@@ -286,8 +369,8 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
if (ze instanceof UnparseableExtraFieldData) { | |||
unparseableExtra = (UnparseableExtraFieldData) ze; | |||
} else { | |||
LinkedHashMap copy = extraFields; | |||
extraFields = new LinkedHashMap(); | |||
LinkedHashMap<ZipShort, ZipExtraField> copy = extraFields; | |||
extraFields = new LinkedHashMap<ZipShort, ZipExtraField>(); | |||
extraFields.put(ze.getHeaderId(), ze); | |||
if (copy != null) { | |||
copy.remove(ze.getHeaderId()); | |||
@@ -330,7 +413,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
*/ | |||
public ZipExtraField getExtraField(ZipShort type) { | |||
if (extraFields != null) { | |||
return (ZipExtraField) extraFields.get(type); | |||
return extraFields.get(type); | |||
} | |||
return null; | |||
} | |||
@@ -353,13 +436,14 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @since 1.1 | |||
* @throws RuntimeException on error | |||
*/ | |||
@Override | |||
public void setExtra(byte[] extra) throws RuntimeException { | |||
try { | |||
ZipExtraField[] local = | |||
ExtraFieldUtils.parse(extra, true, | |||
ExtraFieldUtils.UnparseableExtraField.READ); | |||
mergeExtraFields(local, true); | |||
} catch (Exception e) { | |||
} catch (ZipException 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); | |||
@@ -387,7 +471,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
ExtraFieldUtils.parse(b, false, | |||
ExtraFieldUtils.UnparseableExtraField.READ); | |||
mergeExtraFields(central, false); | |||
} catch (Exception e) { | |||
} catch (ZipException e) { | |||
throw new RuntimeException(e.getMessage(), e); | |||
} | |||
} | |||
@@ -430,6 +514,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @return the entry name | |||
* @since 1.9 | |||
*/ | |||
@Override | |||
public String getName() { | |||
return name == null ? super.getName() : name; | |||
} | |||
@@ -439,6 +524,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @return true if the entry is a directory | |||
* @since 1.10 | |||
*/ | |||
@Override | |||
public boolean isDirectory() { | |||
return getName().endsWith("/"); | |||
} | |||
@@ -448,15 +534,72 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
* @param name the name to use | |||
*/ | |||
protected void setName(String name) { | |||
if (name != null && getPlatform() == PLATFORM_FAT | |||
&& name.indexOf("/") == -1) { | |||
name = name.replace('\\', '/'); | |||
} | |||
this.name = name; | |||
} | |||
/** | |||
* Gets the uncompressed size of the entry data. | |||
* @return the entry size | |||
*/ | |||
@Override | |||
public long getSize() { | |||
return size; | |||
} | |||
/** | |||
* Sets the uncompressed size of the entry data. | |||
* @param size the uncompressed size in bytes | |||
* @exception IllegalArgumentException if the specified size is less | |||
* than 0 | |||
*/ | |||
@Override | |||
public void setSize(long size) { | |||
if (size < 0) { | |||
throw new IllegalArgumentException("invalid entry size"); | |||
} | |||
this.size = size; | |||
} | |||
/** | |||
* Sets the name using the raw bytes and the string created from | |||
* it by guessing or using the configured encoding. | |||
* @param name the name to use created from the raw bytes using | |||
* the guessed or configured encoding | |||
* @param rawName the bytes originally read as name from the | |||
* archive | |||
*/ | |||
protected void setName(String name, byte[] rawName) { | |||
setName(name); | |||
this.rawName = rawName; | |||
} | |||
/** | |||
* Returns the raw bytes that made up the name before it has been | |||
* converted using the configured or guessed encoding. | |||
* | |||
* <p>This method will return null if this instance has not been | |||
* read from an archive.</p> | |||
*/ | |||
public byte[] getRawName() { | |||
if (rawName != null) { | |||
byte[] b = new byte[rawName.length]; | |||
System.arraycopy(rawName, 0, b, 0, rawName.length); | |||
return b; | |||
} | |||
return null; | |||
} | |||
/** | |||
* Get the hashCode of the entry. | |||
* This uses the name as the hashcode. | |||
* @return a hashcode. | |||
* @since Ant 1.7 | |||
*/ | |||
@Override | |||
public int hashCode() { | |||
// this method has severe consequences on performance. We cannot rely | |||
// on the super.hashCode() method since super.getName() always return | |||
@@ -466,14 +609,17 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
} | |||
/** | |||
* The equality method. In this case, the implementation returns 'this == o' | |||
* which is basically the equals method of the Object class. | |||
* @param o the object to compare to | |||
* @return true if this object is the same as <code>o</code> | |||
* @since Ant 1.7 | |||
* The "general purpose bit" field. | |||
*/ | |||
public GeneralPurposeBit getGeneralPurposeBit() { | |||
return gpb; | |||
} | |||
/** | |||
* The "general purpose bit" field. | |||
*/ | |||
public boolean equals(Object o) { | |||
return (this == o); | |||
public void setGeneralPurposeBit(GeneralPurposeBit b) { | |||
gpb = b; | |||
} | |||
/** | |||
@@ -489,23 +635,23 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
if (extraFields == null) { | |||
setExtraFields(f); | |||
} else { | |||
for (int i = 0; i < f.length; i++) { | |||
for (ZipExtraField element : f) { | |||
ZipExtraField existing; | |||
if (f[i] instanceof UnparseableExtraFieldData) { | |||
if (element instanceof UnparseableExtraFieldData) { | |||
existing = unparseableExtra; | |||
} else { | |||
existing = getExtraField(f[i].getHeaderId()); | |||
existing = getExtraField(element.getHeaderId()); | |||
} | |||
if (existing == null) { | |||
addExtraField(f[i]); | |||
addExtraField(element); | |||
} else { | |||
if (local | |||
|| !(existing | |||
instanceof CentralDirectoryParsingZipExtraField)) { | |||
byte[] b = f[i].getLocalFileDataData(); | |||
byte[] b = element.getLocalFileDataData(); | |||
existing.parseFromLocalFileData(b, 0, b.length); | |||
} else { | |||
byte[] b = f[i].getCentralDirectoryData(); | |||
byte[] b = element.getCentralDirectoryData(); | |||
((CentralDirectoryParsingZipExtraField) existing) | |||
.parseFromCentralDirectoryData(b, 0, b.length); | |||
} | |||
@@ -514,4 +660,54 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||
setExtra(); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
public Date getLastModifiedDate() { | |||
return new Date(getTime()); | |||
} | |||
/* (non-Javadoc) | |||
* @see java.lang.Object#equals(java.lang.Object) | |||
*/ | |||
@Override | |||
public boolean equals(Object obj) { | |||
if (this == obj) { | |||
return true; | |||
} | |||
if (obj == null || getClass() != obj.getClass()) { | |||
return false; | |||
} | |||
ZipEntry other = (ZipEntry) obj; | |||
String myName = getName(); | |||
String otherName = other.getName(); | |||
if (myName == null) { | |||
if (otherName != null) { | |||
return false; | |||
} | |||
} else if (!myName.equals(otherName)) { | |||
return false; | |||
} | |||
String myComment = getComment(); | |||
String otherComment = other.getComment(); | |||
if (myComment == null) { | |||
myComment = ""; | |||
} | |||
if (otherComment == null) { | |||
otherComment = ""; | |||
} | |||
return getTime() == other.getTime() | |||
&& myComment.equals(otherComment) | |||
&& getInternalAttributes() == other.getInternalAttributes() | |||
&& getPlatform() == other.getPlatform() | |||
&& getExternalAttributes() == other.getExternalAttributes() | |||
&& getMethod() == other.getMethod() | |||
&& getSize() == other.getSize() | |||
&& getCrc() == other.getCrc() | |||
&& getCompressedSize() == other.getCompressedSize() | |||
&& Arrays.equals(getCentralDirectoryExtra(), | |||
other.getCentralDirectoryExtra()) | |||
&& Arrays.equals(getLocalFileDataExtra(), | |||
other.getLocalFileDataExtra()) | |||
&& gpb.equals(other.gpb); | |||
} | |||
} |
@@ -18,22 +18,28 @@ | |||
package org.apache.tools.zip; | |||
import java.io.EOFException; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.RandomAccessFile; | |||
import java.util.Calendar; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.Date; | |||
import java.util.Comparator; | |||
import java.util.Enumeration; | |||
import java.util.HashMap; | |||
import java.util.HashSet; | |||
import java.util.LinkedHashMap; | |||
import java.util.Map; | |||
import java.util.zip.CRC32; | |||
import java.util.zip.Inflater; | |||
import java.util.zip.InflaterInputStream; | |||
import java.util.zip.ZipException; | |||
import static org.apache.tools.zip.ZipConstants.DWORD; | |||
import static org.apache.tools.zip.ZipConstants.SHORT; | |||
import static org.apache.tools.zip.ZipConstants.WORD; | |||
import static org.apache.tools.zip.ZipConstants.ZIP64_MAGIC; | |||
import static org.apache.tools.zip.ZipConstants.ZIP64_MAGIC_SHORT; | |||
/** | |||
* Replacement for <code>java.util.ZipFile</code>. | |||
* | |||
@@ -47,7 +53,10 @@ import java.util.zip.ZipException; | |||
* <p>It doesn't extend <code>java.util.zip.ZipFile</code> as it would | |||
* have to reimplement all methods anyway. Like | |||
* <code>java.util.ZipFile</code>, it uses RandomAccessFile under the | |||
* covers and supports compressed and uncompressed entries.</p> | |||
* covers and supports compressed and uncompressed entries. As of | |||
* Apache Ant 1.9.0 it also transparently supports Zip64 | |||
* extensions and thus individual entries and archives larger than 4 | |||
* GB or with more than 65536 entries.</p> | |||
* | |||
* <p>The method signatures mimic the ones of | |||
* <code>java.util.zip.ZipFile</code>, with a couple of exceptions: | |||
@@ -63,25 +72,25 @@ import java.util.zip.ZipException; | |||
*/ | |||
public class ZipFile { | |||
private static final int HASH_SIZE = 509; | |||
private static final int SHORT = 2; | |||
private static final int WORD = 4; | |||
private static final int NIBLET_MASK = 0x0f; | |||
private static final int BYTE_SHIFT = 8; | |||
static final int NIBLET_MASK = 0x0f; | |||
static final int BYTE_SHIFT = 8; | |||
private static final int POS_0 = 0; | |||
private static final int POS_1 = 1; | |||
private static final int POS_2 = 2; | |||
private static final int POS_3 = 3; | |||
/** | |||
* Maps ZipEntrys to Longs, recording the offsets of the local | |||
* file headers. | |||
* Maps ZipEntrys to two longs, recording the offsets of | |||
* the local file headers and the start of entry data. | |||
*/ | |||
private final Map entries = new HashMap(HASH_SIZE); | |||
private final Map<ZipEntry, OffsetEntry> entries = | |||
new LinkedHashMap<ZipEntry, OffsetEntry>(HASH_SIZE); | |||
/** | |||
* Maps String to ZipEntrys, name -> actual entry. | |||
*/ | |||
private final Map nameMap = new HashMap(HASH_SIZE); | |||
private final Map<String, ZipEntry> nameMap = | |||
new HashMap<String, ZipEntry>(HASH_SIZE); | |||
private static final class OffsetEntry { | |||
private long headerOffset = -1; | |||
@@ -95,23 +104,33 @@ public class ZipFile { | |||
* href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. | |||
* Defaults to the platform's default character encoding.</p> | |||
*/ | |||
private String encoding = null; | |||
private final String encoding; | |||
/** | |||
* The zip encoding to use for filenames and the file comment. | |||
*/ | |||
private final ZipEncoding zipEncoding; | |||
/** | |||
* File name of actual source. | |||
*/ | |||
private final String archiveName; | |||
/** | |||
* The actual data source. | |||
*/ | |||
private RandomAccessFile archive; | |||
private final RandomAccessFile archive; | |||
/** | |||
* Whether to look for and use Unicode extra fields. | |||
*/ | |||
private final boolean useUnicodeExtraFields; | |||
/** | |||
* Whether the file is closed. | |||
*/ | |||
private boolean closed; | |||
/** | |||
* Opens the given file for reading, assuming the platform's | |||
* native encoding for file names. | |||
@@ -141,7 +160,8 @@ public class ZipFile { | |||
* encoding for file names, scanning unicode extra fields. | |||
* | |||
* @param name name of the archive. | |||
* @param encoding the encoding to use for file names | |||
* @param encoding the encoding to use for file names, use null | |||
* for the platform's default encoding | |||
* | |||
* @throws IOException if an error occurs while reading the file. | |||
*/ | |||
@@ -177,18 +197,21 @@ public class ZipFile { | |||
*/ | |||
public ZipFile(File f, String encoding, boolean useUnicodeExtraFields) | |||
throws IOException { | |||
this.archiveName = f.getAbsolutePath(); | |||
this.encoding = encoding; | |||
this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); | |||
this.useUnicodeExtraFields = useUnicodeExtraFields; | |||
archive = new RandomAccessFile(f, "r"); | |||
boolean success = false; | |||
try { | |||
Map entriesWithoutUTF8Flag = populateFromCentralDirectory(); | |||
Map<ZipEntry, NameAndComment> entriesWithoutUTF8Flag = | |||
populateFromCentralDirectory(); | |||
resolveLocalFileHeaderData(entriesWithoutUTF8Flag); | |||
success = true; | |||
} finally { | |||
if (!success) { | |||
try { | |||
closed = true; | |||
archive.close(); | |||
} catch (IOException e2) { | |||
// swallow, throw the original exception instead | |||
@@ -211,6 +234,11 @@ public class ZipFile { | |||
* @throws IOException if an error occurs closing the archive. | |||
*/ | |||
public void close() throws IOException { | |||
// this flag is only written here and read in finalize() which | |||
// can never be run in parallel. | |||
// no synchronization needed. | |||
closed = true; | |||
archive.close(); | |||
} | |||
@@ -231,37 +259,69 @@ public class ZipFile { | |||
/** | |||
* Returns all entries. | |||
* | |||
* <p>Entries will be returned in the same order they appear | |||
* within the archive's central directory.</p> | |||
* | |||
* @return all entries as {@link ZipEntry} instances | |||
*/ | |||
public Enumeration getEntries() { | |||
public Enumeration<ZipEntry> getEntries() { | |||
return Collections.enumeration(entries.keySet()); | |||
} | |||
/** | |||
* Returns a named entry - or <code>null</code> if no entry by | |||
* Returns all entries in physical order. | |||
* | |||
* <p>Entries will be returned in the same order their contents | |||
* appear within the archive.</p> | |||
* | |||
* @return all entries as {@link ZipEntry} instances | |||
* | |||
* @since Ant 1.9.0 | |||
*/ | |||
public Enumeration<ZipEntry> getEntriesInPhysicalOrder() { | |||
ZipEntry[] allEntries = | |||
entries.keySet().toArray(new ZipEntry[0]); | |||
Arrays.sort(allEntries, OFFSET_COMPARATOR); | |||
return Collections.enumeration(Arrays.asList(allEntries)); | |||
} | |||
/** | |||
* Returns a named entry - or {@code null} if no entry by | |||
* that name exists. | |||
* @param name name of the entry. | |||
* @return the ZipEntry corresponding to the given name - or | |||
* <code>null</code> if not present. | |||
* {@code null} if not present. | |||
*/ | |||
public ZipEntry getEntry(String name) { | |||
return (ZipEntry) nameMap.get(name); | |||
return nameMap.get(name); | |||
} | |||
/** | |||
* Whether this class is able to read the given entry. | |||
* | |||
* <p>May return false if it is set up to use encryption or a | |||
* compression method that hasn't been implemented yet.</p> | |||
*/ | |||
public boolean canReadEntryData(ZipEntry ze) { | |||
return ZipUtil.canHandleEntryData(ze); | |||
} | |||
/** | |||
* Returns an InputStream for reading the contents of the given entry. | |||
* | |||
* @param ze the entry to get the stream for. | |||
* @return a stream to read the entry from. | |||
* @throws IOException if unable to create an input stream from the zipentry | |||
* @throws ZipException if the zipentry has an unsupported | |||
* compression method | |||
* @throws ZipException if the zipentry uses an unsupported feature | |||
*/ | |||
public InputStream getInputStream(ZipEntry ze) | |||
throws IOException, ZipException { | |||
OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze); | |||
OffsetEntry offsetEntry = entries.get(ze); | |||
if (offsetEntry == null) { | |||
return null; | |||
} | |||
ZipUtil.checkRequestedFeatures(ze); | |||
long start = offsetEntry.dataOffset; | |||
BoundedInputStream bis = | |||
new BoundedInputStream(start, ze.getCompressedSize()); | |||
@@ -272,6 +332,7 @@ public class ZipFile { | |||
bis.addDummy(); | |||
final Inflater inflater = new Inflater(true); | |||
return new InflaterInputStream(bis, inflater) { | |||
@Override | |||
public void close() throws IOException { | |||
super.close(); | |||
inflater.end(); | |||
@@ -283,6 +344,28 @@ public class ZipFile { | |||
} | |||
} | |||
/** | |||
* Ensures that the close method of this zipfile is called when | |||
* there are no more references to it. | |||
* @see #close() | |||
*/ | |||
@Override | |||
protected void finalize() throws Throwable { | |||
try { | |||
if (!closed) { | |||
System.err.println("Cleaning up unclosed ZipFile for archive " | |||
+ archiveName); | |||
close(); | |||
} | |||
} finally { | |||
super.finalize(); | |||
} | |||
} | |||
/** | |||
* Length of a "central directory" entry structure without file | |||
* name, extra fields or comment. | |||
*/ | |||
private static final int CFH_LEN = | |||
/* version made by */ SHORT | |||
/* version needed to extract */ + SHORT | |||
@@ -301,6 +384,9 @@ public class ZipFile { | |||
/* external file attributes */ + WORD | |||
/* relative offset of local header */ + WORD; | |||
private static final long CFH_SIG = | |||
ZipLong.getValue(ZipOutputStream.CFH_SIG); | |||
/** | |||
* Reads the central directory of the given archive and populates | |||
* the internal tables with ZipEntry instances. | |||
@@ -309,111 +395,179 @@ public class ZipFile { | |||
* the central directory alone, but not the data that requires the | |||
* local file header or additional data to be read.</p> | |||
* | |||
* @return a Map<ZipEntry, NameAndComment>> of | |||
* zipentries that didn't have the language encoding flag set when | |||
* read. | |||
* @return a map of zipentries that didn't have the language | |||
* encoding flag set when read. | |||
*/ | |||
private Map populateFromCentralDirectory() | |||
private Map<ZipEntry, NameAndComment> populateFromCentralDirectory() | |||
throws IOException { | |||
HashMap noUTF8Flag = new HashMap(); | |||
HashMap<ZipEntry, NameAndComment> noUTF8Flag = | |||
new HashMap<ZipEntry, NameAndComment>(); | |||
positionAtCentralDirectory(); | |||
byte[] cfh = new byte[CFH_LEN]; | |||
byte[] signatureBytes = new byte[WORD]; | |||
archive.readFully(signatureBytes); | |||
long sig = ZipLong.getValue(signatureBytes); | |||
final long cfhSig = ZipLong.getValue(ZipOutputStream.CFH_SIG); | |||
if (sig != cfhSig && startsWithLocalFileHeader()) { | |||
if (sig != CFH_SIG && startsWithLocalFileHeader()) { | |||
throw new IOException("central directory is empty, can't expand" | |||
+ " corrupt archive."); | |||
} | |||
while (sig == cfhSig) { | |||
archive.readFully(cfh); | |||
int off = 0; | |||
ZipEntry ze = new ZipEntry(); | |||
int versionMadeBy = ZipShort.getValue(cfh, off); | |||
off += SHORT; | |||
ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK); | |||
while (sig == CFH_SIG) { | |||
readCentralDirectoryEntry(noUTF8Flag); | |||
archive.readFully(signatureBytes); | |||
sig = ZipLong.getValue(signatureBytes); | |||
} | |||
return noUTF8Flag; | |||
} | |||
/** | |||
* Reads an individual entry of the central directory, creats an | |||
* ZipEntry from it and adds it to the global maps. | |||
* | |||
* @param noUTF8Flag map used to collect entries that don't have | |||
* their UTF-8 flag set and whose name will be set by data read | |||
* from the local file header later. The current entry may be | |||
* added to this map. | |||
*/ | |||
private void | |||
readCentralDirectoryEntry(Map<ZipEntry, NameAndComment> noUTF8Flag) | |||
throws IOException { | |||
byte[] cfh = new byte[CFH_LEN]; | |||
off += SHORT; // skip version info | |||
archive.readFully(cfh); | |||
int off = 0; | |||
ZipEntry ze = new ZipEntry(); | |||
final int generalPurposeFlag = ZipShort.getValue(cfh, off); | |||
final boolean hasUTF8Flag = | |||
(generalPurposeFlag & ZipOutputStream.UFT8_NAMES_FLAG) != 0; | |||
final ZipEncoding entryEncoding = | |||
hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; | |||
int versionMadeBy = ZipShort.getValue(cfh, off); | |||
off += SHORT; | |||
ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK); | |||
off += SHORT; | |||
off += SHORT; // skip version info | |||
ze.setMethod(ZipShort.getValue(cfh, off)); | |||
off += SHORT; | |||
final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(cfh, off); | |||
final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames(); | |||
final ZipEncoding entryEncoding = | |||
hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; | |||
ze.setGeneralPurposeBit(gpFlag); | |||
// FIXME this is actually not very cpu cycles friendly as we are converting from | |||
// dos to java while the underlying Sun implementation will convert | |||
// from java to dos time for internal storage... | |||
long time = dosToJavaTime(ZipLong.getValue(cfh, off)); | |||
ze.setTime(time); | |||
off += WORD; | |||
off += SHORT; | |||
ze.setCrc(ZipLong.getValue(cfh, off)); | |||
off += WORD; | |||
ze.setMethod(ZipShort.getValue(cfh, off)); | |||
off += SHORT; | |||
ze.setCompressedSize(ZipLong.getValue(cfh, off)); | |||
off += WORD; | |||
long time = ZipUtil.dosToJavaTime(ZipLong.getValue(cfh, off)); | |||
ze.setTime(time); | |||
off += WORD; | |||
ze.setSize(ZipLong.getValue(cfh, off)); | |||
off += WORD; | |||
ze.setCrc(ZipLong.getValue(cfh, off)); | |||
off += WORD; | |||
int fileNameLen = ZipShort.getValue(cfh, off); | |||
off += SHORT; | |||
ze.setCompressedSize(ZipLong.getValue(cfh, off)); | |||
off += WORD; | |||
int extraLen = ZipShort.getValue(cfh, off); | |||
off += SHORT; | |||
ze.setSize(ZipLong.getValue(cfh, off)); | |||
off += WORD; | |||
int commentLen = ZipShort.getValue(cfh, off); | |||
off += SHORT; | |||
int fileNameLen = ZipShort.getValue(cfh, off); | |||
off += SHORT; | |||
off += SHORT; // disk number | |||
int extraLen = ZipShort.getValue(cfh, off); | |||
off += SHORT; | |||
ze.setInternalAttributes(ZipShort.getValue(cfh, off)); | |||
off += SHORT; | |||
int commentLen = ZipShort.getValue(cfh, off); | |||
off += SHORT; | |||
ze.setExternalAttributes(ZipLong.getValue(cfh, off)); | |||
off += WORD; | |||
int diskStart = ZipShort.getValue(cfh, off); | |||
off += SHORT; | |||
byte[] fileName = new byte[fileNameLen]; | |||
archive.readFully(fileName); | |||
ze.setName(entryEncoding.decode(fileName)); | |||
ze.setInternalAttributes(ZipShort.getValue(cfh, off)); | |||
off += SHORT; | |||
// LFH offset, | |||
OffsetEntry offset = new OffsetEntry(); | |||
offset.headerOffset = ZipLong.getValue(cfh, off); | |||
// data offset will be filled later | |||
entries.put(ze, offset); | |||
ze.setExternalAttributes(ZipLong.getValue(cfh, off)); | |||
off += WORD; | |||
nameMap.put(ze.getName(), ze); | |||
byte[] fileName = new byte[fileNameLen]; | |||
archive.readFully(fileName); | |||
ze.setName(entryEncoding.decode(fileName), fileName); | |||
byte[] cdExtraData = new byte[extraLen]; | |||
archive.readFully(cdExtraData); | |||
ze.setCentralDirectoryExtra(cdExtraData); | |||
// LFH offset, | |||
OffsetEntry offset = new OffsetEntry(); | |||
offset.headerOffset = ZipLong.getValue(cfh, off); | |||
// data offset will be filled later | |||
entries.put(ze, offset); | |||
byte[] comment = new byte[commentLen]; | |||
archive.readFully(comment); | |||
ze.setComment(entryEncoding.decode(comment)); | |||
nameMap.put(ze.getName(), ze); | |||
archive.readFully(signatureBytes); | |||
sig = ZipLong.getValue(signatureBytes); | |||
byte[] cdExtraData = new byte[extraLen]; | |||
archive.readFully(cdExtraData); | |||
ze.setCentralDirectoryExtra(cdExtraData); | |||
setSizesAndOffsetFromZip64Extra(ze, offset, diskStart); | |||
byte[] comment = new byte[commentLen]; | |||
archive.readFully(comment); | |||
ze.setComment(entryEncoding.decode(comment)); | |||
if (!hasUTF8Flag && useUnicodeExtraFields) { | |||
noUTF8Flag.put(ze, new NameAndComment(fileName, comment)); | |||
} | |||
} | |||
/** | |||
* If the entry holds a Zip64 extended information extra field, | |||
* read sizes from there if the entry's sizes are set to | |||
* 0xFFFFFFFFF, do the same for the offset of the local file | |||
* header. | |||
* | |||
* <p>Ensures the Zip64 extra either knows both compressed and | |||
* uncompressed size or neither of both as the internal logic in | |||
* ExtraFieldUtils forces the field to create local header data | |||
* even if they are never used - and here a field with only one | |||
* size would be invalid.</p> | |||
*/ | |||
private void setSizesAndOffsetFromZip64Extra(ZipEntry ze, | |||
OffsetEntry offset, | |||
int diskStart) | |||
throws IOException { | |||
Zip64ExtendedInformationExtraField z64 = | |||
(Zip64ExtendedInformationExtraField) | |||
ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID); | |||
if (z64 != null) { | |||
boolean hasUncompressedSize = ze.getSize() == ZIP64_MAGIC; | |||
boolean hasCompressedSize = ze.getCompressedSize() == ZIP64_MAGIC; | |||
boolean hasRelativeHeaderOffset = | |||
offset.headerOffset == ZIP64_MAGIC; | |||
z64.reparseCentralDirectoryData(hasUncompressedSize, | |||
hasCompressedSize, | |||
hasRelativeHeaderOffset, | |||
diskStart == ZIP64_MAGIC_SHORT); | |||
if (hasUncompressedSize) { | |||
ze.setSize(z64.getSize().getLongValue()); | |||
} else if (hasCompressedSize) { | |||
z64.setSize(new ZipEightByteInteger(ze.getSize())); | |||
} | |||
if (hasCompressedSize) { | |||
ze.setCompressedSize(z64.getCompressedSize().getLongValue()); | |||
} else if (hasUncompressedSize) { | |||
z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize())); | |||
} | |||
if (!hasUTF8Flag && useUnicodeExtraFields) { | |||
noUTF8Flag.put(ze, new NameAndComment(fileName, comment)); | |||
if (hasRelativeHeaderOffset) { | |||
offset.headerOffset = | |||
z64.getRelativeHeaderOffset().getLongValue(); | |||
} | |||
} | |||
return noUTF8Flag; | |||
} | |||
/** | |||
* Length of the "End of central directory record" - which is | |||
* supposed to be the last structure of the archive - without file | |||
* comment. | |||
*/ | |||
private static final int MIN_EOCD_SIZE = | |||
/* end of central dir signature */ WORD | |||
/* number of this disk */ + SHORT | |||
@@ -429,9 +583,19 @@ public class ZipFile { | |||
/* the starting disk number */ + WORD | |||
/* zipfile comment length */ + SHORT; | |||
/** | |||
* Maximum length of the "End of central directory record" with a | |||
* file comment. | |||
*/ | |||
private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE | |||
/* maximum length of zipfile comment */ + 0xFFFF; | |||
/* maximum length of zipfile comment */ + ZIP64_MAGIC_SHORT; | |||
/** | |||
* Offset of the field that holds the location of the first | |||
* central directory entry inside the "End of central directory | |||
* record" relative to the start of the "End of central directory | |||
* record". | |||
*/ | |||
private static final int CFD_LOCATOR_OFFSET = | |||
/* end of central dir signature */ WORD | |||
/* number of this disk */ + SHORT | |||
@@ -444,18 +608,133 @@ public class ZipFile { | |||
/* size of the central directory */ + WORD; | |||
/** | |||
* Searches for the "End of central dir record", parses | |||
* Length of the "Zip64 end of central directory locator" - which | |||
* should be right in front of the "end of central directory | |||
* record" if one is present at all. | |||
*/ | |||
private static final int ZIP64_EOCDL_LENGTH = | |||
/* zip64 end of central dir locator sig */ WORD | |||
/* number of the disk with the start */ | |||
/* start of the zip64 end of */ | |||
/* central directory */ + WORD | |||
/* relative offset of the zip64 */ | |||
/* end of central directory record */ + DWORD | |||
/* total number of disks */ + WORD; | |||
/** | |||
* Offset of the field that holds the location of the "Zip64 end | |||
* of central directory record" inside the "Zip64 end of central | |||
* directory locator" relative to the start of the "Zip64 end of | |||
* central directory locator". | |||
*/ | |||
private static final int ZIP64_EOCDL_LOCATOR_OFFSET = | |||
/* zip64 end of central dir locator sig */ WORD | |||
/* number of the disk with the start */ | |||
/* start of the zip64 end of */ | |||
/* central directory */ + WORD; | |||
/** | |||
* Offset of the field that holds the location of the first | |||
* central directory entry inside the "Zip64 end of central | |||
* directory record" relative to the start of the "Zip64 end of | |||
* central directory record". | |||
*/ | |||
private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET = | |||
/* zip64 end of central dir */ | |||
/* signature */ WORD | |||
/* size of zip64 end of central */ | |||
/* directory record */ + DWORD | |||
/* version made by */ + SHORT | |||
/* version needed to extract */ + SHORT | |||
/* number of this disk */ + WORD | |||
/* number of the disk with the */ | |||
/* start of the central directory */ + WORD | |||
/* total number of entries in the */ | |||
/* central directory on this disk */ + DWORD | |||
/* total number of entries in the */ | |||
/* central directory */ + DWORD | |||
/* size of the central directory */ + DWORD; | |||
/** | |||
* Searches for either the "Zip64 end of central directory | |||
* locator" or the "End of central dir record", parses | |||
* it and positions the stream at the first central directory | |||
* record. | |||
*/ | |||
private void positionAtCentralDirectory() | |||
throws IOException { | |||
boolean found = tryToLocateSignature(MIN_EOCD_SIZE + ZIP64_EOCDL_LENGTH, | |||
MAX_EOCD_SIZE + ZIP64_EOCDL_LENGTH, | |||
ZipOutputStream | |||
.ZIP64_EOCD_LOC_SIG); | |||
if (!found) { | |||
// not a ZIP64 archive | |||
positionAtCentralDirectory32(); | |||
} else { | |||
positionAtCentralDirectory64(); | |||
} | |||
} | |||
/** | |||
* Parses the "Zip64 end of central directory locator", | |||
* finds the "Zip64 end of central directory record" using the | |||
* parsed information, parses that and positions the stream at the | |||
* first central directory record. | |||
*/ | |||
private void positionAtCentralDirectory64() | |||
throws IOException { | |||
skipBytes(ZIP64_EOCDL_LOCATOR_OFFSET); | |||
byte[] zip64EocdOffset = new byte[DWORD]; | |||
archive.readFully(zip64EocdOffset); | |||
archive.seek(ZipEightByteInteger.getLongValue(zip64EocdOffset)); | |||
byte[] sig = new byte[WORD]; | |||
archive.readFully(sig); | |||
if (sig[POS_0] != ZipOutputStream.ZIP64_EOCD_SIG[POS_0] | |||
|| sig[POS_1] != ZipOutputStream.ZIP64_EOCD_SIG[POS_1] | |||
|| sig[POS_2] != ZipOutputStream.ZIP64_EOCD_SIG[POS_2] | |||
|| sig[POS_3] != ZipOutputStream.ZIP64_EOCD_SIG[POS_3] | |||
) { | |||
throw new ZipException("archive's ZIP64 end of central " | |||
+ "directory locator is corrupt."); | |||
} | |||
skipBytes(ZIP64_EOCD_CFD_LOCATOR_OFFSET | |||
- WORD /* signature has already been read */); | |||
byte[] cfdOffset = new byte[DWORD]; | |||
archive.readFully(cfdOffset); | |||
archive.seek(ZipEightByteInteger.getLongValue(cfdOffset)); | |||
} | |||
/** | |||
* Searches for the "End of central dir record", parses | |||
* it and positions the stream at the first central directory | |||
* record. | |||
*/ | |||
private void positionAtCentralDirectory32() | |||
throws IOException { | |||
boolean found = tryToLocateSignature(MIN_EOCD_SIZE, MAX_EOCD_SIZE, | |||
ZipOutputStream.EOCD_SIG); | |||
if (!found) { | |||
throw new ZipException("archive is not a ZIP archive"); | |||
} | |||
skipBytes(CFD_LOCATOR_OFFSET); | |||
byte[] cfdOffset = new byte[WORD]; | |||
archive.readFully(cfdOffset); | |||
archive.seek(ZipLong.getValue(cfdOffset)); | |||
} | |||
/** | |||
* Searches the archive backwards from minDistance to maxDistance | |||
* for the given signature, positions the RandomaccessFile right | |||
* at the signature if it has been found. | |||
*/ | |||
private boolean tryToLocateSignature(long minDistanceFromEnd, | |||
long maxDistanceFromEnd, | |||
byte[] sig) throws IOException { | |||
boolean found = false; | |||
long off = archive.length() - MIN_EOCD_SIZE; | |||
long off = archive.length() - minDistanceFromEnd; | |||
final long stopSearching = | |||
Math.max(0L, archive.length() - MAX_EOCD_SIZE); | |||
Math.max(0L, archive.length() - maxDistanceFromEnd); | |||
if (off >= 0) { | |||
final byte[] sig = ZipOutputStream.EOCD_SIG; | |||
for (; off >= stopSearching; off--) { | |||
archive.seek(off); | |||
int curr = archive.read(); | |||
@@ -477,13 +756,25 @@ public class ZipFile { | |||
} | |||
} | |||
} | |||
if (!found) { | |||
throw new ZipException("archive is not a ZIP archive"); | |||
if (found) { | |||
archive.seek(off); | |||
} | |||
return found; | |||
} | |||
/** | |||
* Skips the given number of bytes or throws an EOFException if | |||
* skipping failed. | |||
*/ | |||
private void skipBytes(final int count) throws IOException { | |||
int totalSkipped = 0; | |||
while (totalSkipped < count) { | |||
int skippedNow = archive.skipBytes(count - totalSkipped); | |||
if (skippedNow <= 0) { | |||
throw new EOFException(); | |||
} | |||
totalSkipped += skippedNow; | |||
} | |||
archive.seek(off + CFD_LOCATOR_OFFSET); | |||
byte[] cfdOffset = new byte[WORD]; | |||
archive.readFully(cfdOffset); | |||
archive.seek(ZipLong.getValue(cfdOffset)); | |||
} | |||
/** | |||
@@ -508,12 +799,19 @@ public class ZipFile { | |||
* <p>Also records the offsets for the data to read from the | |||
* entries.</p> | |||
*/ | |||
private void resolveLocalFileHeaderData(Map entriesWithoutUTF8Flag) | |||
private void resolveLocalFileHeaderData(Map<ZipEntry, NameAndComment> | |||
entriesWithoutUTF8Flag) | |||
throws IOException { | |||
Enumeration e = Collections.enumeration(new HashSet(entries.keySet())); | |||
while (e.hasMoreElements()) { | |||
ZipEntry ze = (ZipEntry) e.nextElement(); | |||
OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze); | |||
// changing the name of a ZipEntry is going to change | |||
// the hashcode - see COMPRESS-164 | |||
// Map needs to be reconstructed in order to keep central | |||
// directory order | |||
Map<ZipEntry, OffsetEntry> origMap = | |||
new LinkedHashMap<ZipEntry, OffsetEntry>(entries); | |||
entries.clear(); | |||
for (Map.Entry<ZipEntry, OffsetEntry> ent : origMap.entrySet()) { | |||
ZipEntry ze = ent.getKey(); | |||
OffsetEntry offsetEntry = ent.getValue(); | |||
long offset = offsetEntry.headerOffset; | |||
archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH); | |||
byte[] b = new byte[SHORT]; | |||
@@ -525,75 +823,28 @@ public class ZipFile { | |||
while (lenToSkip > 0) { | |||
int skipped = archive.skipBytes(lenToSkip); | |||
if (skipped <= 0) { | |||
throw new RuntimeException("failed to skip file name in" | |||
+ " local file header"); | |||
throw new IOException("failed to skip file name in" | |||
+ " local file header"); | |||
} | |||
lenToSkip -= skipped; | |||
} | |||
} | |||
byte[] localExtraData = new byte[extraFieldLen]; | |||
archive.readFully(localExtraData); | |||
ze.setExtra(localExtraData); | |||
/*dataOffsets.put(ze, | |||
new Long(offset + LFH_OFFSET_FOR_FILENAME_LENGTH | |||
+ SHORT + SHORT + fileNameLen + extraFieldLen)); | |||
*/ | |||
offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH | |||
+ SHORT + SHORT + fileNameLen + extraFieldLen; | |||
if (entriesWithoutUTF8Flag.containsKey(ze)) { | |||
// changing the name of a ZipEntry is going to change | |||
// the hashcode | |||
// - see https://issues.apache.org/jira/browse/COMPRESS-164 | |||
entries.remove(ze); | |||
setNameAndCommentFromExtraFields(ze, | |||
(NameAndComment) | |||
entriesWithoutUTF8Flag.get(ze)); | |||
entries.put(ze, offsetEntry); | |||
String orig = ze.getName(); | |||
NameAndComment nc = entriesWithoutUTF8Flag.get(ze); | |||
ZipUtil.setNameAndCommentFromExtraFields(ze, nc.name, | |||
nc.comment); | |||
if (!orig.equals(ze.getName())) { | |||
nameMap.remove(orig); | |||
nameMap.put(ze.getName(), ze); | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Convert a DOS date/time field to a Date object. | |||
* | |||
* @param zipDosTime contains the stored DOS time. | |||
* @return a Date instance corresponding to the given time. | |||
*/ | |||
protected static Date fromDosTime(ZipLong zipDosTime) { | |||
long dosTime = zipDosTime.getValue(); | |||
return new Date(dosToJavaTime(dosTime)); | |||
} | |||
/* | |||
* Converts DOS time to Java time (number of milliseconds since epoch). | |||
*/ | |||
private static long dosToJavaTime(long dosTime) { | |||
Calendar cal = Calendar.getInstance(); | |||
// CheckStyle:MagicNumberCheck OFF - no point | |||
cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980); | |||
cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1); | |||
cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f); | |||
cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f); | |||
cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f); | |||
cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e); | |||
// CheckStyle:MagicNumberCheck ON | |||
return cal.getTime().getTime(); | |||
} | |||
/** | |||
* Retrieve a String from the given bytes using the encoding set | |||
* for this ZipFile. | |||
* | |||
* @param bytes the byte array to transform | |||
* @return String obtained by using the given encoding | |||
* @throws ZipException if the encoding cannot be recognized. | |||
*/ | |||
protected String getString(byte[] bytes) throws ZipException { | |||
try { | |||
return ZipEncodingHelper.getZipEncoding(encoding).decode(bytes); | |||
} catch (IOException ex) { | |||
throw new ZipException("Failed to decode name: " + ex.getMessage()); | |||
entries.put(ze, offsetEntry); | |||
} | |||
} | |||
@@ -613,64 +864,6 @@ public class ZipFile { | |||
return true; | |||
} | |||
/** | |||
* If the entry has Unicode*ExtraFields and the CRCs of the | |||
* names/comments match those of the extra fields, transfer the | |||
* known Unicode values from the extra field. | |||
*/ | |||
private void setNameAndCommentFromExtraFields(ZipEntry ze, | |||
NameAndComment nc) { | |||
UnicodePathExtraField name = (UnicodePathExtraField) | |||
ze.getExtraField(UnicodePathExtraField.UPATH_ID); | |||
String originalName = ze.getName(); | |||
String newName = getUnicodeStringIfOriginalMatches(name, nc.name); | |||
if (newName != null && !originalName.equals(newName)) { | |||
ze.setName(newName); | |||
nameMap.remove(originalName); | |||
nameMap.put(newName, ze); | |||
} | |||
if (nc.comment != null && nc.comment.length > 0) { | |||
UnicodeCommentExtraField cmt = (UnicodeCommentExtraField) | |||
ze.getExtraField(UnicodeCommentExtraField.UCOM_ID); | |||
String newComment = | |||
getUnicodeStringIfOriginalMatches(cmt, nc.comment); | |||
if (newComment != null) { | |||
ze.setComment(newComment); | |||
} | |||
} | |||
} | |||
/** | |||
* If the stored CRC matches the one of the given name, return the | |||
* Unicode name of the given field. | |||
* | |||
* <p>If the field is null or the CRCs don't match, return null | |||
* instead.</p> | |||
*/ | |||
private String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f, | |||
byte[] orig) { | |||
if (f != null) { | |||
CRC32 crc32 = new CRC32(); | |||
crc32.update(orig); | |||
long origCRC32 = crc32.getValue(); | |||
if (origCRC32 == f.getNameCRC32()) { | |||
try { | |||
return ZipEncodingHelper | |||
.UTF8_ZIP_ENCODING.decode(f.getUnicodeName()); | |||
} catch (IOException ex) { | |||
// UTF-8 unsupported? should be impossible the | |||
// Unicode*ExtraField must contain some bad bytes | |||
// TODO log this anywhere? | |||
return null; | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* InputStream that delegates requests to the underlying | |||
* RandomAccessFile, making sure that only bytes from a certain | |||
@@ -686,6 +879,7 @@ public class ZipFile { | |||
loc = start; | |||
} | |||
@Override | |||
public int read() throws IOException { | |||
if (remaining-- <= 0) { | |||
if (addDummyByte) { | |||
@@ -700,6 +894,7 @@ public class ZipFile { | |||
} | |||
} | |||
@Override | |||
public int read(byte[] b, int off, int len) throws IOException { | |||
if (remaining <= 0) { | |||
if (addDummyByte) { | |||
@@ -746,4 +941,32 @@ public class ZipFile { | |||
this.comment = comment; | |||
} | |||
} | |||
/** | |||
* Compares two ZipEntries based on their offset within the archive. | |||
* | |||
* <p>Won't return any meaningful results if one of the entries | |||
* isn't part of the archive at all.</p> | |||
* | |||
* @since Ant 1.9.0 | |||
*/ | |||
private final Comparator<ZipEntry> OFFSET_COMPARATOR = | |||
new Comparator<ZipEntry>() { | |||
public int compare(ZipEntry e1, ZipEntry e2) { | |||
if (e1 == e2) { | |||
return 0; | |||
} | |||
OffsetEntry off1 = entries.get(e1); | |||
OffsetEntry off2 = entries.get(e2); | |||
if (off1 == null) { | |||
return 1; | |||
} | |||
if (off2 == null) { | |||
return -1; | |||
} | |||
long val = (off1.headerOffset - off2.headerOffset); | |||
return val == 0 ? 0 : val < 0 ? -1 : +1; | |||
} | |||
}; | |||
} |
@@ -18,6 +18,9 @@ | |||
package org.apache.tools.zip; | |||
import static org.apache.tools.zip.ZipConstants.BYTE_MASK; | |||
import static org.apache.tools.zip.ZipConstants.WORD; | |||
/** | |||
* Utility class that represents a four byte integer with conversion | |||
* rules for the big endian byte order of ZIP files. | |||
@@ -25,8 +28,7 @@ package org.apache.tools.zip; | |||
*/ | |||
public final class ZipLong implements Cloneable { | |||
private static final int WORD = 4; | |||
private static final int BYTE_MASK = 0xFF; | |||
//private static final int BYTE_BIT_SIZE = 8; | |||
private static final int BYTE_1 = 1; | |||
private static final int BYTE_1_MASK = 0xFF00; | |||
@@ -40,7 +42,26 @@ public final class ZipLong implements Cloneable { | |||
private static final long BYTE_3_MASK = 0xFF000000L; | |||
private static final int BYTE_3_SHIFT = 24; | |||
private long value; | |||
private final long value; | |||
/** Central File Header Signature */ | |||
public static final ZipLong CFH_SIG = new ZipLong(0X02014B50L); | |||
/** Local File Header Signature */ | |||
public static final ZipLong LFH_SIG = new ZipLong(0X04034B50L); | |||
/** | |||
* Data Descriptor signature | |||
* @since 1.1 | |||
*/ | |||
public static final ZipLong DD_SIG = new ZipLong(0X08074B50L); | |||
/** | |||
* Value stored in size and similar fields if ZIP64 extensions are | |||
* used. | |||
* @since 1.3 | |||
*/ | |||
static final ZipLong ZIP64_MAGIC = new ZipLong(ZipConstants.ZIP64_MAGIC); | |||
/** | |||
* Create instance from a number. | |||
@@ -106,7 +127,7 @@ public final class ZipLong implements Cloneable { | |||
* Helper method to get the value as a Java long from four bytes starting at given array offset | |||
* @param bytes the array of bytes | |||
* @param offset the offset to start | |||
* @return the correspondanding Java long value | |||
* @return the corresponding Java long value | |||
*/ | |||
public static long getValue(byte[] bytes, int offset) { | |||
long value = (bytes[offset + BYTE_3] << BYTE_3_SHIFT) & BYTE_3_MASK; | |||
@@ -119,7 +140,7 @@ public final class ZipLong implements Cloneable { | |||
/** | |||
* Helper method to get the value as a Java long from a four-byte array | |||
* @param bytes the array of bytes | |||
* @return the correspondanding Java long value | |||
* @return the corresponding Java long value | |||
*/ | |||
public static long getValue(byte[] bytes) { | |||
return getValue(bytes, 0); | |||
@@ -131,6 +152,7 @@ public final class ZipLong implements Cloneable { | |||
* @return true if the objects are equal | |||
* @since 1.1 | |||
*/ | |||
@Override | |||
public boolean equals(Object o) { | |||
if (o == null || !(o instanceof ZipLong)) { | |||
return false; | |||
@@ -143,10 +165,12 @@ public final class ZipLong implements Cloneable { | |||
* @return the value stored in the ZipLong | |||
* @since 1.1 | |||
*/ | |||
@Override | |||
public int hashCode() { | |||
return (int) value; | |||
} | |||
@Override | |||
public Object clone() { | |||
try { | |||
return super.clone(); | |||
@@ -155,4 +179,9 @@ public final class ZipLong implements Cloneable { | |||
throw new RuntimeException(cnfe); | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return "ZipLong value: " + value; | |||
} | |||
} |
@@ -18,17 +18,18 @@ | |||
package org.apache.tools.zip; | |||
import static org.apache.tools.zip.ZipConstants.BYTE_MASK; | |||
/** | |||
* Utility class that represents a two byte integer with conversion | |||
* rules for the big endian byte order of ZIP files. | |||
* | |||
*/ | |||
public final class ZipShort implements Cloneable { | |||
private static final int BYTE_MASK = 0xFF; | |||
private static final int BYTE_1_MASK = 0xFF00; | |||
private static final int BYTE_1_SHIFT = 8; | |||
private int value; | |||
private final int value; | |||
/** | |||
* Create instance from a number. | |||
@@ -95,7 +96,7 @@ public final class ZipShort implements Cloneable { | |||
* Helper method to get the value as a java int from two bytes starting at given array offset | |||
* @param bytes the array of bytes | |||
* @param offset the offset to start | |||
* @return the correspondanding java int value | |||
* @return the corresponding java int value | |||
*/ | |||
public static int getValue(byte[] bytes, int offset) { | |||
int value = (bytes[offset + 1] << BYTE_1_SHIFT) & BYTE_1_MASK; | |||
@@ -106,7 +107,7 @@ public final class ZipShort implements Cloneable { | |||
/** | |||
* Helper method to get the value as a java int from a two-byte array | |||
* @param bytes the array of bytes | |||
* @return the correspondanding java int value | |||
* @return the corresponding java int value | |||
*/ | |||
public static int getValue(byte[] bytes) { | |||
return getValue(bytes, 0); | |||
@@ -118,6 +119,7 @@ public final class ZipShort implements Cloneable { | |||
* @return true if the objects are equal | |||
* @since 1.1 | |||
*/ | |||
@Override | |||
public boolean equals(Object o) { | |||
if (o == null || !(o instanceof ZipShort)) { | |||
return false; | |||
@@ -130,10 +132,12 @@ public final class ZipShort implements Cloneable { | |||
* @return the value stored in the ZipShort | |||
* @since 1.1 | |||
*/ | |||
@Override | |||
public int hashCode() { | |||
return value; | |||
} | |||
@Override | |||
public Object clone() { | |||
try { | |||
return super.clone(); | |||
@@ -142,4 +146,9 @@ public final class ZipShort implements Cloneable { | |||
throw new RuntimeException(cnfe); | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return "ZipShort value: " + value; | |||
} | |||
} |
@@ -17,11 +17,159 @@ | |||
*/ | |||
package org.apache.tools.zip; | |||
import java.io.IOException; | |||
import java.util.Calendar; | |||
import java.util.Date; | |||
import java.util.zip.CRC32; | |||
/** | |||
* Utility class for handling DOS and Java time conversions. | |||
* @since Ant 1.8.1 | |||
*/ | |||
public abstract class ZipUtil { | |||
/** | |||
* Smallest date/time ZIP can handle. | |||
*/ | |||
private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L); | |||
/** | |||
* Convert a Date object to a DOS date/time field. | |||
* @param time the <code>Date</code> to convert | |||
* @return the date as a <code>ZipLong</code> | |||
*/ | |||
public static ZipLong toDosTime(Date time) { | |||
return new ZipLong(toDosTime(time.getTime())); | |||
} | |||
/** | |||
* Convert a Date object to a DOS date/time field. | |||
* | |||
* <p>Stolen from InfoZip's <code>fileio.c</code></p> | |||
* @param t number of milliseconds since the epoch | |||
* @return the date as a byte array | |||
*/ | |||
public static byte[] toDosTime(long t) { | |||
Calendar c = Calendar.getInstance(); | |||
c.setTimeInMillis(t); | |||
int year = c.get(Calendar.YEAR); | |||
if (year < 1980) { | |||
return copy(DOS_TIME_MIN); // stop callers from changing the array | |||
} | |||
int month = c.get(Calendar.MONTH) + 1; | |||
long value = ((year - 1980) << 25) | |||
| (month << 21) | |||
| (c.get(Calendar.DAY_OF_MONTH) << 16) | |||
| (c.get(Calendar.HOUR_OF_DAY) << 11) | |||
| (c.get(Calendar.MINUTE) << 5) | |||
| (c.get(Calendar.SECOND) >> 1); | |||
return ZipLong.getBytes(value); | |||
} | |||
/** | |||
* Assumes a negative integer really is a positive integer that | |||
* has wrapped around and re-creates the original value. | |||
* | |||
* <p>This methods is no longer used as of Apache Ant 1.9.0</p> | |||
* | |||
* @param i the value to treat as unsigned int. | |||
* @return the unsigned int as a long. | |||
*/ | |||
public static long adjustToLong(int i) { | |||
if (i < 0) { | |||
return 2 * ((long) Integer.MAX_VALUE) + 2 + i; | |||
} else { | |||
return i; | |||
} | |||
} | |||
/** | |||
* Convert a DOS date/time field to a Date object. | |||
* | |||
* @param zipDosTime contains the stored DOS time. | |||
* @return a Date instance corresponding to the given time. | |||
*/ | |||
public static Date fromDosTime(ZipLong zipDosTime) { | |||
long dosTime = zipDosTime.getValue(); | |||
return new Date(dosToJavaTime(dosTime)); | |||
} | |||
/** | |||
* Converts DOS time to Java time (number of milliseconds since | |||
* epoch). | |||
*/ | |||
public static long dosToJavaTime(long dosTime) { | |||
Calendar cal = Calendar.getInstance(); | |||
// CheckStyle:MagicNumberCheck OFF - no point | |||
cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980); | |||
cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1); | |||
cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f); | |||
cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f); | |||
cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f); | |||
cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e); | |||
// CheckStyle:MagicNumberCheck ON | |||
return cal.getTime().getTime(); | |||
} | |||
/** | |||
* If the entry has Unicode*ExtraFields and the CRCs of the | |||
* names/comments match those of the extra fields, transfer the | |||
* known Unicode values from the extra field. | |||
*/ | |||
static void setNameAndCommentFromExtraFields(ZipEntry ze, | |||
byte[] originalNameBytes, | |||
byte[] commentBytes) { | |||
UnicodePathExtraField name = (UnicodePathExtraField) | |||
ze.getExtraField(UnicodePathExtraField.UPATH_ID); | |||
String originalName = ze.getName(); | |||
String newName = getUnicodeStringIfOriginalMatches(name, | |||
originalNameBytes); | |||
if (newName != null && !originalName.equals(newName)) { | |||
ze.setName(newName); | |||
} | |||
if (commentBytes != null && commentBytes.length > 0) { | |||
UnicodeCommentExtraField cmt = (UnicodeCommentExtraField) | |||
ze.getExtraField(UnicodeCommentExtraField.UCOM_ID); | |||
String newComment = | |||
getUnicodeStringIfOriginalMatches(cmt, commentBytes); | |||
if (newComment != null) { | |||
ze.setComment(newComment); | |||
} | |||
} | |||
} | |||
/** | |||
* If the stored CRC matches the one of the given name, return the | |||
* Unicode name of the given field. | |||
* | |||
* <p>If the field is null or the CRCs don't match, return null | |||
* instead.</p> | |||
*/ | |||
private static | |||
String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f, | |||
byte[] orig) { | |||
if (f != null) { | |||
CRC32 crc32 = new CRC32(); | |||
crc32.update(orig); | |||
long origCRC32 = crc32.getValue(); | |||
if (origCRC32 == f.getNameCRC32()) { | |||
try { | |||
return ZipEncodingHelper | |||
.UTF8_ZIP_ENCODING.decode(f.getUnicodeName()); | |||
} catch (IOException ex) { | |||
// UTF-8 unsupported? should be impossible the | |||
// Unicode*ExtraField must contain some bad bytes | |||
// TODO log this anywhere? | |||
return null; | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* Create a copy of the given array - or return null if the | |||
* argument is null. | |||
@@ -35,4 +183,49 @@ public abstract class ZipUtil { | |||
return null; | |||
} | |||
/** | |||
* Whether this library is able to read or write the given entry. | |||
*/ | |||
static boolean canHandleEntryData(ZipEntry entry) { | |||
return supportsEncryptionOf(entry) && supportsMethodOf(entry); | |||
} | |||
/** | |||
* Whether this library supports the encryption used by the given | |||
* entry. | |||
* | |||
* @return true if the entry isn't encrypted at all | |||
*/ | |||
private static boolean supportsEncryptionOf(ZipEntry entry) { | |||
return !entry.getGeneralPurposeBit().usesEncryption(); | |||
} | |||
/** | |||
* Whether this library supports the compression method used by | |||
* the given entry. | |||
* | |||
* @return true if the compression method is STORED or DEFLATED | |||
*/ | |||
private static boolean supportsMethodOf(ZipEntry entry) { | |||
return entry.getMethod() == ZipEntry.STORED | |||
|| entry.getMethod() == ZipEntry.DEFLATED; | |||
} | |||
/** | |||
* Checks whether the entry requires features not (yet) supported | |||
* by the library and throws an exception if it does. | |||
*/ | |||
static void checkRequestedFeatures(ZipEntry ze) | |||
throws UnsupportedZipFeatureException { | |||
if (!supportsEncryptionOf(ze)) { | |||
throw | |||
new UnsupportedZipFeatureException(UnsupportedZipFeatureException | |||
.Feature.ENCRYPTION, ze); | |||
} | |||
if (!supportsMethodOf(ze)) { | |||
throw | |||
new UnsupportedZipFeatureException(UnsupportedZipFeatureException | |||
.Feature.METHOD, ze); | |||
} | |||
} | |||
} |
@@ -32,6 +32,7 @@ import org.apache.tools.ant.types.Resource; | |||
import org.apache.tools.ant.types.ResourceCollection; | |||
import org.apache.tools.ant.types.resources.ZipResource; | |||
import org.apache.tools.zip.JarMarker; | |||
import org.apache.tools.zip.Zip64ExtendedInformationExtraField; | |||
import org.apache.tools.zip.ZipEntry; | |||
import org.apache.tools.zip.ZipExtraField; | |||
import org.apache.tools.zip.ZipFile; | |||
@@ -79,8 +80,10 @@ public class ZipExtraFieldTest extends TestCase { | |||
zf = new ZipFile(f); | |||
ZipEntry ze = zf.getEntry("x"); | |||
assertNotNull(ze); | |||
assertEquals(1, ze.getExtraFields().length); | |||
assertEquals(2, ze.getExtraFields().length); | |||
assertTrue(ze.getExtraFields()[0] instanceof JarMarker); | |||
assertTrue(ze.getExtraFields()[1] | |||
instanceof Zip64ExtendedInformationExtraField); | |||
} finally { | |||
ZipFile.closeQuietly(zf); | |||
if (f.exists()) { | |||
@@ -29,17 +29,26 @@ public class ExtraFieldUtilsTest extends TestCase implements UnixStat { | |||
super(name); | |||
} | |||
/** | |||
* Header-ID of a ZipExtraField not supported by Ant. | |||
* | |||
* <p>Used to be ZipShort(1) but this is the ID of the Zip64 extra | |||
* field.</p> | |||
*/ | |||
static final ZipShort UNRECOGNIZED_HEADER = new ZipShort(0x5555); | |||
private AsiExtraField a; | |||
private UnrecognizedExtraField dummy; | |||
private byte[] data; | |||
private byte[] aLocal; | |||
@Override | |||
public void setUp() { | |||
a = new AsiExtraField(); | |||
a.setMode(0755); | |||
a.setDirectory(true); | |||
dummy = new UnrecognizedExtraField(); | |||
dummy.setHeaderId(new ZipShort(1)); | |||
dummy.setHeaderId(UNRECOGNIZED_HEADER); | |||
dummy.setLocalFileDataData(new byte[] {0}); | |||
dummy.setCentralDirectoryData(new byte[] {0}); | |||
@@ -167,7 +176,8 @@ 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[] b = UNRECOGNIZED_HEADER.getBytes(); | |||
d.parseFromLocalFileData(new byte[] {b[0], b[1], 1, 0}, 0, 4); | |||
byte[] local = | |||
ExtraFieldUtils.mergeLocalFileDataData(new ZipExtraField[] {a, d}); | |||
assertEquals("local length", data.length - 1, local.length); | |||
@@ -38,7 +38,7 @@ public class ZipEntryTest extends TestCase { | |||
a.setDirectory(true); | |||
a.setMode(0755); | |||
UnrecognizedExtraField u = new UnrecognizedExtraField(); | |||
u.setHeaderId(new ZipShort(1)); | |||
u.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||
u.setLocalFileDataData(new byte[0]); | |||
ZipEntry ze = new ZipEntry("test/"); | |||
@@ -50,7 +50,7 @@ public class ZipEntryTest extends TestCase { | |||
assertSame(u, result[1]); | |||
UnrecognizedExtraField u2 = new UnrecognizedExtraField(); | |||
u2.setHeaderId(new ZipShort(1)); | |||
u2.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||
u2.setLocalFileDataData(new byte[] {1}); | |||
ze.addExtraField(u2); | |||
@@ -68,7 +68,7 @@ public class ZipEntryTest extends TestCase { | |||
result = ze.getExtraFields(); | |||
assertEquals("third pass", 3, result.length); | |||
ze.removeExtraField(new ZipShort(1)); | |||
ze.removeExtraField(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||
byte[] data3 = ze.getExtra(); | |||
result = ze.getExtraFields(); | |||
assertEquals("fourth pass", 2, result.length); | |||
@@ -77,7 +77,7 @@ public class ZipEntryTest extends TestCase { | |||
assertEquals("length fourth pass", data2.length, data3.length); | |||
try { | |||
ze.removeExtraField(new ZipShort(1)); | |||
ze.removeExtraField(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||
fail("should be no such element"); | |||
} catch (java.util.NoSuchElementException nse) { | |||
} | |||
@@ -91,7 +91,7 @@ public class ZipEntryTest extends TestCase { | |||
a.setDirectory(true); | |||
a.setMode(0755); | |||
UnrecognizedExtraField u = new UnrecognizedExtraField(); | |||
u.setHeaderId(new ZipShort(1)); | |||
u.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||
u.setLocalFileDataData(new byte[0]); | |||
ZipEntry ze = new ZipEntry("test/"); | |||
@@ -99,12 +99,14 @@ public class ZipEntryTest extends TestCase { | |||
// merge | |||
// Header-ID 1 + length 1 + one byte of data | |||
ze.setCentralDirectoryExtra(new byte[] {1, 0, 1, 0, 127}); | |||
byte[] b = ExtraFieldUtilsTest.UNRECOGNIZED_HEADER.getBytes(); | |||
ze.setCentralDirectoryExtra(new byte[] {b[0], b[1], 1, 0, 127}); | |||
ZipExtraField[] result = ze.getExtraFields(); | |||
assertEquals("first pass", 2, result.length); | |||
assertSame(a, result[0]); | |||
assertEquals(new ZipShort(1), result[1].getHeaderId()); | |||
assertEquals(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER, | |||
result[1].getHeaderId()); | |||
assertEquals(new ZipShort(0), result[1].getLocalFileDataLength()); | |||
assertEquals(new ZipShort(1), result[1].getCentralDirectoryLength()); | |||
@@ -135,7 +137,7 @@ public class ZipEntryTest extends TestCase { | |||
a.setDirectory(true); | |||
a.setMode(0755); | |||
UnrecognizedExtraField u = new UnrecognizedExtraField(); | |||
u.setHeaderId(new ZipShort(1)); | |||
u.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||
u.setLocalFileDataData(new byte[0]); | |||
ZipEntry ze = new ZipEntry("test/"); | |||
@@ -143,7 +145,7 @@ public class ZipEntryTest extends TestCase { | |||
byte[] data1 = ze.getExtra(); | |||
UnrecognizedExtraField u2 = new UnrecognizedExtraField(); | |||
u2.setHeaderId(new ZipShort(1)); | |||
u2.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||
u2.setLocalFileDataData(new byte[] {1}); | |||
ze.addAsFirstExtraField(u2); | |||