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 | 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. | EOL ASIS will not insert a newline even if fixlast is set to true. | ||||
Bugzilla report 53036 | Bugzilla report 53036 | ||||
Fixed bugs: | Fixed bugs: | ||||
----------- | ----------- | ||||
@@ -34,6 +33,10 @@ Fixed bugs: | |||||
Other changes: | 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 | 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. | * @return The utf-8 encoded name. | ||||
*/ | */ | ||||
public byte[] getUnicodeName() { | 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. | * @param unicodeName The utf-8 encoded name to set. | ||||
*/ | */ | ||||
public void setUnicodeName(byte[] unicodeName) { | 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; | data = null; | ||||
} | } | ||||
/** {@inheritDoc} */ | |||||
public byte[] getCentralDirectoryData() { | public byte[] getCentralDirectoryData() { | ||||
if (data == null) { | if (data == null) { | ||||
this.assembleData(); | 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() { | public ZipShort getCentralDirectoryLength() { | ||||
if (data == null) { | if (data == null) { | ||||
assembleData(); | assembleData(); | ||||
@@ -130,14 +148,17 @@ public abstract class AbstractUnicodeExtraField implements ZipExtraField { | |||||
return new ZipShort(data.length); | return new ZipShort(data.length); | ||||
} | } | ||||
/** {@inheritDoc} */ | |||||
public byte[] getLocalFileDataData() { | public byte[] getLocalFileDataData() { | ||||
return getCentralDirectoryData(); | return getCentralDirectoryData(); | ||||
} | } | ||||
/** {@inheritDoc} */ | |||||
public ZipShort getLocalFileDataLength() { | public ZipShort getLocalFileDataLength() { | ||||
return getCentralDirectoryLength(); | return getCentralDirectoryLength(); | ||||
} | } | ||||
/** {@inheritDoc} */ | |||||
public void parseFromLocalFileData(byte[] buffer, int offset, int length) | public void parseFromLocalFileData(byte[] buffer, int offset, int length) | ||||
throws ZipException { | 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 | * <p>Short is two bytes and Long is four bytes in big endian byte and | ||||
* word order, device numbers are currently not supported.</p> | * 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 { | public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { | ||||
@@ -116,6 +119,7 @@ public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { | |||||
+ 2 // UID | + 2 // UID | ||||
+ 2 // GID | + 2 // GID | ||||
+ getLinkedFile().getBytes().length); | + 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]; | byte[] data = new byte[getLocalFileDataLength().getValue() - WORD]; | ||||
System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2); | 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 | // CheckStyle:MagicNumber OFF | ||||
System.arraycopy(ZipLong.getBytes(linkArray.length), | System.arraycopy(ZipLong.getBytes(linkArray.length), | ||||
0, data, 2, WORD); | 0, data, 2, WORD); | ||||
@@ -311,7 +315,7 @@ public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { | |||||
link = ""; | link = ""; | ||||
} else { | } else { | ||||
System.arraycopy(tmp, 10, linkArray, 0, linkArray.length); | 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 | // CheckStyle:MagicNumber ON | ||||
setDirectory((newMode & DIR_FLAG) != 0); | setDirectory((newMode & DIR_FLAG) != 0); | ||||
@@ -334,6 +338,7 @@ public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { | |||||
return type | (mode & PERM_MASK); | return type | (mode & PERM_MASK); | ||||
} | } | ||||
@Override | |||||
public Object clone() { | public Object clone() { | ||||
try { | try { | ||||
AsiExtraField cloned = (AsiExtraField) super.clone(); | AsiExtraField cloned = (AsiExtraField) super.clone(); | ||||
@@ -38,14 +38,15 @@ public class ExtraFieldUtils { | |||||
* | * | ||||
* @since 1.1 | * @since 1.1 | ||||
*/ | */ | ||||
private static final Map implementations; | |||||
private static final Map<ZipShort, Class<?>> implementations; | |||||
static { | static { | ||||
implementations = new HashMap(); | |||||
implementations = new HashMap<ZipShort, Class<?>>(); | |||||
register(AsiExtraField.class); | register(AsiExtraField.class); | ||||
register(JarMarker.class); | register(JarMarker.class); | ||||
register(UnicodePathExtraField.class); | register(UnicodePathExtraField.class); | ||||
register(UnicodeCommentExtraField.class); | register(UnicodeCommentExtraField.class); | ||||
register(Zip64ExtendedInformationExtraField.class); | |||||
} | } | ||||
/** | /** | ||||
@@ -57,7 +58,7 @@ public class ExtraFieldUtils { | |||||
* | * | ||||
* @since 1.1 | * @since 1.1 | ||||
*/ | */ | ||||
public static void register(Class c) { | |||||
public static void register(Class<?> c) { | |||||
try { | try { | ||||
ZipExtraField ze = (ZipExtraField) c.newInstance(); | ZipExtraField ze = (ZipExtraField) c.newInstance(); | ||||
implementations.put(ze.getHeaderId(), c); | implementations.put(ze.getHeaderId(), c); | ||||
@@ -81,7 +82,7 @@ public class ExtraFieldUtils { | |||||
*/ | */ | ||||
public static ZipExtraField createExtraField(ZipShort headerId) | public static ZipExtraField createExtraField(ZipShort headerId) | ||||
throws InstantiationException, IllegalAccessException { | throws InstantiationException, IllegalAccessException { | ||||
Class c = (Class) implementations.get(headerId); | |||||
Class<?> c = implementations.get(headerId); | |||||
if (c != null) { | if (c != null) { | ||||
return (ZipExtraField) c.newInstance(); | return (ZipExtraField) c.newInstance(); | ||||
} | } | ||||
@@ -132,7 +133,7 @@ public class ExtraFieldUtils { | |||||
public static ZipExtraField[] parse(byte[] data, boolean local, | public static ZipExtraField[] parse(byte[] data, boolean local, | ||||
UnparseableExtraField onUnparseableData) | UnparseableExtraField onUnparseableData) | ||||
throws ZipException { | throws ZipException { | ||||
List v = new ArrayList(); | |||||
List<ZipExtraField> v = new ArrayList<ZipExtraField>(); | |||||
int start = 0; | int start = 0; | ||||
LOOP: | LOOP: | ||||
while (start <= data.length - WORD) { | while (start <= data.length - WORD) { | ||||
@@ -158,7 +159,7 @@ public class ExtraFieldUtils { | |||||
data.length - start); | data.length - start); | ||||
} | } | ||||
v.add(field); | v.add(field); | ||||
/*FALLTHROUGH*/ | |||||
//$FALL-THROUGH$ | |||||
case UnparseableExtraField.SKIP_KEY: | case UnparseableExtraField.SKIP_KEY: | ||||
// since we cannot parse the data we must assume | // since we cannot parse the data we must assume | ||||
// the extra field consumes the whole rest of the | // the extra field consumes the whole rest of the | ||||
@@ -189,7 +190,7 @@ public class ExtraFieldUtils { | |||||
} | } | ||||
ZipExtraField[] result = new ZipExtraField[v.size()]; | 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; | lastIsUnparseableHolder ? data.length - 1 : data.length; | ||||
int sum = WORD * regularExtraFieldCount; | 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]; | byte[] result = new byte[sum]; | ||||
@@ -240,8 +241,8 @@ public class ExtraFieldUtils { | |||||
lastIsUnparseableHolder ? data.length - 1 : data.length; | lastIsUnparseableHolder ? data.length - 1 : data.length; | ||||
int sum = WORD * regularExtraFieldCount; | 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]; | byte[] result = new byte[sum]; | ||||
int start = 0; | int start = 0; | ||||
@@ -30,7 +30,7 @@ import java.nio.ByteBuffer; | |||||
* marks leading to unreadable ZIP entries on some operating | * marks leading to unreadable ZIP entries on some operating | ||||
* systems.</p> | * 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> | * given name can be safely encoded or not.</p> | ||||
* | * | ||||
* <p>This implementation acts as a last resort implementation, when | * <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. | * 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. | * the platform's default character set. | ||||
*/ | */ | ||||
public FallbackZipEncoding(String charset) { | public FallbackZipEncoding(String charset) { | ||||
@@ -73,7 +73,7 @@ class FallbackZipEncoding implements ZipEncoding { | |||||
* org.apache.tools.zip.ZipEncoding#encode(java.lang.String) | * org.apache.tools.zip.ZipEncoding#encode(java.lang.String) | ||||
*/ | */ | ||||
public ByteBuffer encode(String name) throws IOException { | 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()); | return ByteBuffer.wrap(name.getBytes()); | ||||
} else { | } else { | ||||
return ByteBuffer.wrap(name.getBytes(this.charset)); | return ByteBuffer.wrap(name.getBytes(this.charset)); | ||||
@@ -85,7 +85,7 @@ class FallbackZipEncoding implements ZipEncoding { | |||||
* org.apache.tools.zip.ZipEncoding#decode(byte[]) | * org.apache.tools.zip.ZipEncoding#decode(byte[]) | ||||
*/ | */ | ||||
public String decode(byte[] data) throws IOException { | 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); | return new String(data); | ||||
} else { | } else { | ||||
return new String(data,this.charset); | 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 | * A character entity, which is put to the reverse mapping table | ||||
* of a simple encoding. | * 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 char unicode; | ||||
public final byte code; | public final byte code; | ||||
@@ -58,15 +58,28 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||||
this.unicode = unicode; | this.unicode = unicode; | ||||
} | } | ||||
public int compareTo(Object o) { | |||||
Simple8BitChar a = (Simple8BitChar) o; | |||||
public int compareTo(Simple8BitChar a) { | |||||
return this.unicode - a.unicode; | return this.unicode - a.unicode; | ||||
} | } | ||||
@Override | |||||
public String toString() { | 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 | * field. This list is used to binary search reverse mapping of | ||||
* unicode characters with a character code greater than 127. | * 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 | * @param highChars The characters for byte values of 128 to 255 | ||||
* stored as an array of 128 chars. | * stored as an array of 128 chars. | ||||
*/ | */ | ||||
public Simple8BitZipEncoding(char[] highChars) { | 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; | byte code = 127; | ||||
for (int i = 0; i < this.highChars.length; ++i) { | 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 | // 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 bb The byte buffer to write to. | ||||
* @param c The character to encode. | * @param c The character to encode. | ||||
* @return Whether the given unicode character is covered by this encoding. | * @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. | * byte buffer. | ||||
*/ | */ | ||||
public boolean pushEncodedChar(ByteBuffer bb, char c) { | 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 | * @param c A unicode character in the range from 0x0080 to 0x7f00 | ||||
* @return A Simple8BitChar, if this character is covered by this encoding. | * @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. | * covered by this encoding. | ||||
*/ | */ | ||||
private Simple8BitChar encodeHighChar(char c) { | private Simple8BitChar encodeHighChar(char c) { | ||||
@@ -171,7 +185,7 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||||
int i = i0 + (i1 - i0) / 2; | int i = i0 + (i1 - i0) / 2; | ||||
Simple8BitChar m = (Simple8BitChar) this.reverseMapping.get(i); | |||||
Simple8BitChar m = this.reverseMapping.get(i); | |||||
if (m.unicode == c) { | if (m.unicode == c) { | ||||
return m; | return m; | ||||
@@ -188,7 +202,7 @@ class Simple8BitZipEncoding implements ZipEncoding { | |||||
return null; | return null; | ||||
} | } | ||||
Simple8BitChar r = (Simple8BitChar) this.reverseMapping.get(i0); | |||||
Simple8BitChar r = this.reverseMapping.get(i0); | |||||
if (r.unicode != c) { | if (r.unicode != c) { | ||||
return null; | return null; | ||||
@@ -67,6 +67,7 @@ public class UnicodeCommentExtraField extends AbstractUnicodeExtraField { | |||||
super(comment, bytes); | super(comment, bytes); | ||||
} | } | ||||
/** {@inheritDoc} */ | |||||
public ZipShort getHeaderId() { | public ZipShort getHeaderId() { | ||||
return UCOM_ID; | return UCOM_ID; | ||||
} | } | ||||
@@ -66,6 +66,7 @@ public class UnicodePathExtraField extends AbstractUnicodeExtraField { | |||||
super(name, bytes); | super(name, bytes); | ||||
} | } | ||||
/** {@inheritDoc} */ | |||||
public ZipShort getHeaderId() { | public ZipShort getHeaderId() { | ||||
return UPATH_ID; | 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. | * 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> | * 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 | * @since Ant 1.8.1 | ||||
*/ | */ | ||||
public final class UnparseableExtraFieldData | public final class UnparseableExtraFieldData | ||||
@@ -103,7 +102,6 @@ public final class UnparseableExtraFieldData | |||||
* @param buffer the buffer to read data from | * @param buffer the buffer to read data from | ||||
* @param offset offset into buffer to read data | * @param offset offset into buffer to read data | ||||
* @param length the length of data | * @param length the length of data | ||||
* @exception ZipException on error | |||||
*/ | */ | ||||
public void parseFromCentralDirectoryData(byte[] buffer, int offset, | public void parseFromCentralDirectoryData(byte[] buffer, int offset, | ||||
int length) { | 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 | * character sequences are mapped to a sequence of utf-16 | ||||
* words encoded in the format <code>%Uxxxx</code>. It is | * words encoded in the format <code>%Uxxxx</code>. It is | ||||
* assumed, that the byte buffer is positioned at the | * 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 | * backing array and the limit of the byte buffer points | ||||
* to the end of the encoded result. | * to the end of the encoded result. | ||||
* @throws IOException | * @throws IOException | ||||
@@ -62,10 +62,10 @@ abstract class ZipEncodingHelper { | |||||
} | } | ||||
} | } | ||||
private static final Map simpleEncodings; | |||||
private static final Map<String, SimpleEncodingHolder> simpleEncodings; | |||||
static { | static { | ||||
simpleEncodings = new HashMap(); | |||||
simpleEncodings = new HashMap<String, SimpleEncodingHolder>(); | |||||
char[] cp437_high_chars = | char[] cp437_high_chars = | ||||
new char[] { 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, | new char[] { 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, | ||||
@@ -203,7 +203,7 @@ abstract class ZipEncodingHelper { | |||||
/** | /** | ||||
* Instantiates a zip encoding. | * 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. | * the platform's default encoding. | ||||
* @return A zip encoding for the given encoding name. | * @return A zip encoding for the given encoding name. | ||||
*/ | */ | ||||
@@ -218,8 +218,7 @@ abstract class ZipEncodingHelper { | |||||
return new FallbackZipEncoding(); | return new FallbackZipEncoding(); | ||||
} | } | ||||
SimpleEncodingHolder h = | |||||
(SimpleEncodingHolder) simpleEncodings.get(name); | |||||
SimpleEncodingHolder h = simpleEncodings.get(name); | |||||
if (h!=null) { | if (h!=null) { | ||||
return h.getEncoding(); | return h.getEncoding(); | ||||
@@ -18,7 +18,10 @@ | |||||
package org.apache.tools.zip; | package org.apache.tools.zip; | ||||
import java.io.File; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | |||||
import java.util.Date; | |||||
import java.util.LinkedHashMap; | import java.util.LinkedHashMap; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.zip.ZipException; | import java.util.zip.ZipException; | ||||
@@ -28,7 +31,8 @@ import java.util.zip.ZipException; | |||||
* access to the internal and external file attributes. | * access to the internal and external file attributes. | ||||
* | * | ||||
* <p>The extra data is expected to follow the recommendation of | * <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> | * <ul> | ||||
* <li>the extra byte array consists of a sequence of extra fields</li> | * <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 | * <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 | * <p>Any extra data that cannot be parsed by the rules above will be | ||||
* consumed as "unparseable" extra data and treated differently by the | * 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 { | 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_MASK = 0xFFFF; | ||||
private static final int SHORT_SHIFT = 16; | 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 internalAttributes = 0; | ||||
private int platform = PLATFORM_FAT; | private int platform = PLATFORM_FAT; | ||||
private long externalAttributes = 0; | private long externalAttributes = 0; | ||||
private LinkedHashMap/*<ZipShort, ZipExtraField>*/ extraFields = null; | |||||
private LinkedHashMap<ZipShort, ZipExtraField> extraFields = null; | |||||
private UnparseableExtraFieldData unparseableExtra = null; | private UnparseableExtraFieldData unparseableExtra = null; | ||||
private String name = null; | private String name = null; | ||||
private byte[] rawName = null; | |||||
private GeneralPurposeBit gpb = new GeneralPurposeBit(); | |||||
/** | /** | ||||
* Creates a new zip entry with the specified name. | * 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 | * @param name the name of the entry | ||||
* @since 1.1 | * @since 1.1 | ||||
*/ | */ | ||||
public ZipEntry(String name) { | public ZipEntry(String name) { | ||||
super(name); | super(name); | ||||
setName(name); | |||||
} | } | ||||
/** | /** | ||||
* Creates a new zip entry with fields taken from the specified zip entry. | * 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 | * @param entry the entry to get fields from | ||||
* @since 1.1 | * @since 1.1 | ||||
* @throws ZipException on error | * @throws ZipException on error | ||||
*/ | */ | ||||
public ZipEntry(java.util.zip.ZipEntry entry) throws ZipException { | public ZipEntry(java.util.zip.ZipEntry entry) throws ZipException { | ||||
super(entry); | super(entry); | ||||
setName(entry.getName()); | |||||
byte[] extra = entry.getExtra(); | byte[] extra = entry.getExtra(); | ||||
if (extra != null) { | if (extra != null) { | ||||
setExtraFields(ExtraFieldUtils.parse(extra, true, | 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 | // initializes extra data to an empty byte array | ||||
setExtra(); | setExtra(); | ||||
} | } | ||||
setMethod(entry.getMethod()); | |||||
this.size = entry.getSize(); | |||||
} | } | ||||
/** | /** | ||||
* Creates a new zip entry with fields taken from the specified zip entry. | * 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 | * @param entry the entry to get fields from | ||||
* @throws ZipException on error | * @throws ZipException on error | ||||
* @since 1.1 | * @since 1.1 | ||||
@@ -104,7 +141,26 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
* @since 1.9 | * @since 1.9 | ||||
*/ | */ | ||||
protected ZipEntry() { | 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 | * @return a cloned copy of this ZipEntry | ||||
* @since 1.1 | * @since 1.1 | ||||
*/ | */ | ||||
@Override | |||||
public Object clone() { | public Object clone() { | ||||
ZipEntry e = (ZipEntry) super.clone(); | ZipEntry e = (ZipEntry) super.clone(); | ||||
@@ -121,6 +178,31 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
return e; | 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. | * Retrieves the internal file attributes. | ||||
* | * | ||||
@@ -213,12 +295,12 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
* @since 1.1 | * @since 1.1 | ||||
*/ | */ | ||||
public void setExtraFields(ZipExtraField[] fields) { | 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 { | } else { | ||||
extraFields.put(fields[i].getHeaderId(), fields[i]); | |||||
extraFields.put(field.getHeaderId(), field); | |||||
} | } | ||||
} | } | ||||
setExtra(); | setExtra(); | ||||
@@ -246,11 +328,12 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
? new ZipExtraField[0] | ? new ZipExtraField[0] | ||||
: new ZipExtraField[] { unparseableExtra }; | : new ZipExtraField[] { unparseableExtra }; | ||||
} | } | ||||
List result = new ArrayList(extraFields.values()); | |||||
List<ZipExtraField> result = | |||||
new ArrayList<ZipExtraField>(extraFields.values()); | |||||
if (includeUnparseable && unparseableExtra != null) { | if (includeUnparseable && unparseableExtra != null) { | ||||
result.add(unparseableExtra); | 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; | unparseableExtra = (UnparseableExtraFieldData) ze; | ||||
} else { | } else { | ||||
if (extraFields == null) { | if (extraFields == null) { | ||||
extraFields = new LinkedHashMap(); | |||||
extraFields = new LinkedHashMap<ZipShort, ZipExtraField>(); | |||||
} | } | ||||
extraFields.put(ze.getHeaderId(), ze); | extraFields.put(ze.getHeaderId(), ze); | ||||
} | } | ||||
@@ -286,8 +369,8 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
if (ze instanceof UnparseableExtraFieldData) { | if (ze instanceof UnparseableExtraFieldData) { | ||||
unparseableExtra = (UnparseableExtraFieldData) ze; | unparseableExtra = (UnparseableExtraFieldData) ze; | ||||
} else { | } else { | ||||
LinkedHashMap copy = extraFields; | |||||
extraFields = new LinkedHashMap(); | |||||
LinkedHashMap<ZipShort, ZipExtraField> copy = extraFields; | |||||
extraFields = new LinkedHashMap<ZipShort, ZipExtraField>(); | |||||
extraFields.put(ze.getHeaderId(), ze); | extraFields.put(ze.getHeaderId(), ze); | ||||
if (copy != null) { | if (copy != null) { | ||||
copy.remove(ze.getHeaderId()); | copy.remove(ze.getHeaderId()); | ||||
@@ -330,7 +413,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
*/ | */ | ||||
public ZipExtraField getExtraField(ZipShort type) { | public ZipExtraField getExtraField(ZipShort type) { | ||||
if (extraFields != null) { | if (extraFields != null) { | ||||
return (ZipExtraField) extraFields.get(type); | |||||
return extraFields.get(type); | |||||
} | } | ||||
return null; | return null; | ||||
} | } | ||||
@@ -353,13 +436,14 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
* @since 1.1 | * @since 1.1 | ||||
* @throws RuntimeException on error | * @throws RuntimeException on error | ||||
*/ | */ | ||||
@Override | |||||
public void setExtra(byte[] extra) throws RuntimeException { | public void setExtra(byte[] extra) throws RuntimeException { | ||||
try { | try { | ||||
ZipExtraField[] local = | ZipExtraField[] local = | ||||
ExtraFieldUtils.parse(extra, true, | ExtraFieldUtils.parse(extra, true, | ||||
ExtraFieldUtils.UnparseableExtraField.READ); | ExtraFieldUtils.UnparseableExtraField.READ); | ||||
mergeExtraFields(local, true); | mergeExtraFields(local, true); | ||||
} catch (Exception e) { | |||||
} catch (ZipException e) { | |||||
// actually this is not be possible as of Ant 1.8.1 | // actually this is not be possible as of Ant 1.8.1 | ||||
throw new RuntimeException("Error parsing extra fields for entry: " | throw new RuntimeException("Error parsing extra fields for entry: " | ||||
+ getName() + " - " + e.getMessage(), e); | + getName() + " - " + e.getMessage(), e); | ||||
@@ -387,7 +471,7 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
ExtraFieldUtils.parse(b, false, | ExtraFieldUtils.parse(b, false, | ||||
ExtraFieldUtils.UnparseableExtraField.READ); | ExtraFieldUtils.UnparseableExtraField.READ); | ||||
mergeExtraFields(central, false); | mergeExtraFields(central, false); | ||||
} catch (Exception e) { | |||||
} catch (ZipException e) { | |||||
throw new RuntimeException(e.getMessage(), 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 | * @return the entry name | ||||
* @since 1.9 | * @since 1.9 | ||||
*/ | */ | ||||
@Override | |||||
public String getName() { | public String getName() { | ||||
return name == null ? super.getName() : name; | 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 | * @return true if the entry is a directory | ||||
* @since 1.10 | * @since 1.10 | ||||
*/ | */ | ||||
@Override | |||||
public boolean isDirectory() { | public boolean isDirectory() { | ||||
return getName().endsWith("/"); | return getName().endsWith("/"); | ||||
} | } | ||||
@@ -448,15 +534,72 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
* @param name the name to use | * @param name the name to use | ||||
*/ | */ | ||||
protected void setName(String name) { | protected void setName(String name) { | ||||
if (name != null && getPlatform() == PLATFORM_FAT | |||||
&& name.indexOf("/") == -1) { | |||||
name = name.replace('\\', '/'); | |||||
} | |||||
this.name = name; | 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. | * Get the hashCode of the entry. | ||||
* This uses the name as the hashcode. | * This uses the name as the hashcode. | ||||
* @return a hashcode. | * @return a hashcode. | ||||
* @since Ant 1.7 | * @since Ant 1.7 | ||||
*/ | */ | ||||
@Override | |||||
public int hashCode() { | public int hashCode() { | ||||
// this method has severe consequences on performance. We cannot rely | // this method has severe consequences on performance. We cannot rely | ||||
// on the super.hashCode() method since super.getName() always return | // 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) { | if (extraFields == null) { | ||||
setExtraFields(f); | setExtraFields(f); | ||||
} else { | } else { | ||||
for (int i = 0; i < f.length; i++) { | |||||
for (ZipExtraField element : f) { | |||||
ZipExtraField existing; | ZipExtraField existing; | ||||
if (f[i] instanceof UnparseableExtraFieldData) { | |||||
if (element instanceof UnparseableExtraFieldData) { | |||||
existing = unparseableExtra; | existing = unparseableExtra; | ||||
} else { | } else { | ||||
existing = getExtraField(f[i].getHeaderId()); | |||||
existing = getExtraField(element.getHeaderId()); | |||||
} | } | ||||
if (existing == null) { | if (existing == null) { | ||||
addExtraField(f[i]); | |||||
addExtraField(element); | |||||
} else { | } else { | ||||
if (local | if (local | ||||
|| !(existing | || !(existing | ||||
instanceof CentralDirectoryParsingZipExtraField)) { | instanceof CentralDirectoryParsingZipExtraField)) { | ||||
byte[] b = f[i].getLocalFileDataData(); | |||||
byte[] b = element.getLocalFileDataData(); | |||||
existing.parseFromLocalFileData(b, 0, b.length); | existing.parseFromLocalFileData(b, 0, b.length); | ||||
} else { | } else { | ||||
byte[] b = f[i].getCentralDirectoryData(); | |||||
byte[] b = element.getCentralDirectoryData(); | |||||
((CentralDirectoryParsingZipExtraField) existing) | ((CentralDirectoryParsingZipExtraField) existing) | ||||
.parseFromCentralDirectoryData(b, 0, b.length); | .parseFromCentralDirectoryData(b, 0, b.length); | ||||
} | } | ||||
@@ -514,4 +660,54 @@ public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { | |||||
setExtra(); | 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; | package org.apache.tools.zip; | ||||
import java.io.EOFException; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.io.RandomAccessFile; | import java.io.RandomAccessFile; | ||||
import java.util.Calendar; | |||||
import java.util.Arrays; | |||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.Date; | |||||
import java.util.Comparator; | |||||
import java.util.Enumeration; | import java.util.Enumeration; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.HashSet; | |||||
import java.util.LinkedHashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.zip.CRC32; | |||||
import java.util.zip.Inflater; | import java.util.zip.Inflater; | ||||
import java.util.zip.InflaterInputStream; | import java.util.zip.InflaterInputStream; | ||||
import java.util.zip.ZipException; | 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>. | * 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 | * <p>It doesn't extend <code>java.util.zip.ZipFile</code> as it would | ||||
* have to reimplement all methods anyway. Like | * have to reimplement all methods anyway. Like | ||||
* <code>java.util.ZipFile</code>, it uses RandomAccessFile under the | * <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 | * <p>The method signatures mimic the ones of | ||||
* <code>java.util.zip.ZipFile</code>, with a couple of exceptions: | * <code>java.util.zip.ZipFile</code>, with a couple of exceptions: | ||||
@@ -63,25 +72,25 @@ import java.util.zip.ZipException; | |||||
*/ | */ | ||||
public class ZipFile { | public class ZipFile { | ||||
private static final int HASH_SIZE = 509; | 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_0 = 0; | ||||
private static final int POS_1 = 1; | private static final int POS_1 = 1; | ||||
private static final int POS_2 = 2; | private static final int POS_2 = 2; | ||||
private static final int POS_3 = 3; | 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. | * 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 static final class OffsetEntry { | ||||
private long headerOffset = -1; | 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>. | * 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> | * 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. | * The zip encoding to use for filenames and the file comment. | ||||
*/ | */ | ||||
private final ZipEncoding zipEncoding; | private final ZipEncoding zipEncoding; | ||||
/** | |||||
* File name of actual source. | |||||
*/ | |||||
private final String archiveName; | |||||
/** | /** | ||||
* The actual data source. | * The actual data source. | ||||
*/ | */ | ||||
private RandomAccessFile archive; | |||||
private final RandomAccessFile archive; | |||||
/** | /** | ||||
* Whether to look for and use Unicode extra fields. | * Whether to look for and use Unicode extra fields. | ||||
*/ | */ | ||||
private final boolean useUnicodeExtraFields; | private final boolean useUnicodeExtraFields; | ||||
/** | |||||
* Whether the file is closed. | |||||
*/ | |||||
private boolean closed; | |||||
/** | /** | ||||
* Opens the given file for reading, assuming the platform's | * Opens the given file for reading, assuming the platform's | ||||
* native encoding for file names. | * native encoding for file names. | ||||
@@ -141,7 +160,8 @@ public class ZipFile { | |||||
* encoding for file names, scanning unicode extra fields. | * encoding for file names, scanning unicode extra fields. | ||||
* | * | ||||
* @param name name of the archive. | * @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. | * @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) | public ZipFile(File f, String encoding, boolean useUnicodeExtraFields) | ||||
throws IOException { | throws IOException { | ||||
this.archiveName = f.getAbsolutePath(); | |||||
this.encoding = encoding; | this.encoding = encoding; | ||||
this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); | this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); | ||||
this.useUnicodeExtraFields = useUnicodeExtraFields; | this.useUnicodeExtraFields = useUnicodeExtraFields; | ||||
archive = new RandomAccessFile(f, "r"); | archive = new RandomAccessFile(f, "r"); | ||||
boolean success = false; | boolean success = false; | ||||
try { | try { | ||||
Map entriesWithoutUTF8Flag = populateFromCentralDirectory(); | |||||
Map<ZipEntry, NameAndComment> entriesWithoutUTF8Flag = | |||||
populateFromCentralDirectory(); | |||||
resolveLocalFileHeaderData(entriesWithoutUTF8Flag); | resolveLocalFileHeaderData(entriesWithoutUTF8Flag); | ||||
success = true; | success = true; | ||||
} finally { | } finally { | ||||
if (!success) { | if (!success) { | ||||
try { | try { | ||||
closed = true; | |||||
archive.close(); | archive.close(); | ||||
} catch (IOException e2) { | } catch (IOException e2) { | ||||
// swallow, throw the original exception instead | // swallow, throw the original exception instead | ||||
@@ -211,6 +234,11 @@ public class ZipFile { | |||||
* @throws IOException if an error occurs closing the archive. | * @throws IOException if an error occurs closing the archive. | ||||
*/ | */ | ||||
public void close() throws IOException { | 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(); | archive.close(); | ||||
} | } | ||||
@@ -231,37 +259,69 @@ public class ZipFile { | |||||
/** | /** | ||||
* Returns all entries. | * 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 | * @return all entries as {@link ZipEntry} instances | ||||
*/ | */ | ||||
public Enumeration getEntries() { | |||||
public Enumeration<ZipEntry> getEntries() { | |||||
return Collections.enumeration(entries.keySet()); | 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. | * that name exists. | ||||
* @param name name of the entry. | * @param name name of the entry. | ||||
* @return the ZipEntry corresponding to the given name - or | * @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) { | 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. | * Returns an InputStream for reading the contents of the given entry. | ||||
* | |||||
* @param ze the entry to get the stream for. | * @param ze the entry to get the stream for. | ||||
* @return a stream to read the entry from. | * @return a stream to read the entry from. | ||||
* @throws IOException if unable to create an input stream from the zipentry | * @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) | public InputStream getInputStream(ZipEntry ze) | ||||
throws IOException, ZipException { | throws IOException, ZipException { | ||||
OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze); | |||||
OffsetEntry offsetEntry = entries.get(ze); | |||||
if (offsetEntry == null) { | if (offsetEntry == null) { | ||||
return null; | return null; | ||||
} | } | ||||
ZipUtil.checkRequestedFeatures(ze); | |||||
long start = offsetEntry.dataOffset; | long start = offsetEntry.dataOffset; | ||||
BoundedInputStream bis = | BoundedInputStream bis = | ||||
new BoundedInputStream(start, ze.getCompressedSize()); | new BoundedInputStream(start, ze.getCompressedSize()); | ||||
@@ -272,6 +332,7 @@ public class ZipFile { | |||||
bis.addDummy(); | bis.addDummy(); | ||||
final Inflater inflater = new Inflater(true); | final Inflater inflater = new Inflater(true); | ||||
return new InflaterInputStream(bis, inflater) { | return new InflaterInputStream(bis, inflater) { | ||||
@Override | |||||
public void close() throws IOException { | public void close() throws IOException { | ||||
super.close(); | super.close(); | ||||
inflater.end(); | 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 = | private static final int CFH_LEN = | ||||
/* version made by */ SHORT | /* version made by */ SHORT | ||||
/* version needed to extract */ + SHORT | /* version needed to extract */ + SHORT | ||||
@@ -301,6 +384,9 @@ public class ZipFile { | |||||
/* external file attributes */ + WORD | /* external file attributes */ + WORD | ||||
/* relative offset of local header */ + 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 | * Reads the central directory of the given archive and populates | ||||
* the internal tables with ZipEntry instances. | * the internal tables with ZipEntry instances. | ||||
@@ -309,111 +395,179 @@ public class ZipFile { | |||||
* the central directory alone, but not the data that requires the | * the central directory alone, but not the data that requires the | ||||
* local file header or additional data to be read.</p> | * 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 { | throws IOException { | ||||
HashMap noUTF8Flag = new HashMap(); | |||||
HashMap<ZipEntry, NameAndComment> noUTF8Flag = | |||||
new HashMap<ZipEntry, NameAndComment>(); | |||||
positionAtCentralDirectory(); | positionAtCentralDirectory(); | ||||
byte[] cfh = new byte[CFH_LEN]; | |||||
byte[] signatureBytes = new byte[WORD]; | byte[] signatureBytes = new byte[WORD]; | ||||
archive.readFully(signatureBytes); | archive.readFully(signatureBytes); | ||||
long sig = ZipLong.getValue(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" | throw new IOException("central directory is empty, can't expand" | ||||
+ " corrupt archive."); | + " 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 = | private static final int MIN_EOCD_SIZE = | ||||
/* end of central dir signature */ WORD | /* end of central dir signature */ WORD | ||||
/* number of this disk */ + SHORT | /* number of this disk */ + SHORT | ||||
@@ -429,9 +583,19 @@ public class ZipFile { | |||||
/* the starting disk number */ + WORD | /* the starting disk number */ + WORD | ||||
/* zipfile comment length */ + SHORT; | /* 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 | 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 = | private static final int CFD_LOCATOR_OFFSET = | ||||
/* end of central dir signature */ WORD | /* end of central dir signature */ WORD | ||||
/* number of this disk */ + SHORT | /* number of this disk */ + SHORT | ||||
@@ -444,18 +608,133 @@ public class ZipFile { | |||||
/* size of the central directory */ + WORD; | /* 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 | * it and positions the stream at the first central directory | ||||
* record. | * record. | ||||
*/ | */ | ||||
private void positionAtCentralDirectory() | private void positionAtCentralDirectory() | ||||
throws IOException { | 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; | boolean found = false; | ||||
long off = archive.length() - MIN_EOCD_SIZE; | |||||
long off = archive.length() - minDistanceFromEnd; | |||||
final long stopSearching = | final long stopSearching = | ||||
Math.max(0L, archive.length() - MAX_EOCD_SIZE); | |||||
Math.max(0L, archive.length() - maxDistanceFromEnd); | |||||
if (off >= 0) { | if (off >= 0) { | ||||
final byte[] sig = ZipOutputStream.EOCD_SIG; | |||||
for (; off >= stopSearching; off--) { | for (; off >= stopSearching; off--) { | ||||
archive.seek(off); | archive.seek(off); | ||||
int curr = archive.read(); | 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 | * <p>Also records the offsets for the data to read from the | ||||
* entries.</p> | * entries.</p> | ||||
*/ | */ | ||||
private void resolveLocalFileHeaderData(Map entriesWithoutUTF8Flag) | |||||
private void resolveLocalFileHeaderData(Map<ZipEntry, NameAndComment> | |||||
entriesWithoutUTF8Flag) | |||||
throws IOException { | 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; | long offset = offsetEntry.headerOffset; | ||||
archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH); | archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH); | ||||
byte[] b = new byte[SHORT]; | byte[] b = new byte[SHORT]; | ||||
@@ -525,75 +823,28 @@ public class ZipFile { | |||||
while (lenToSkip > 0) { | while (lenToSkip > 0) { | ||||
int skipped = archive.skipBytes(lenToSkip); | int skipped = archive.skipBytes(lenToSkip); | ||||
if (skipped <= 0) { | 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; | lenToSkip -= skipped; | ||||
} | |||||
} | |||||
byte[] localExtraData = new byte[extraFieldLen]; | byte[] localExtraData = new byte[extraFieldLen]; | ||||
archive.readFully(localExtraData); | archive.readFully(localExtraData); | ||||
ze.setExtra(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 | offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH | ||||
+ SHORT + SHORT + fileNameLen + extraFieldLen; | + SHORT + SHORT + fileNameLen + extraFieldLen; | ||||
if (entriesWithoutUTF8Flag.containsKey(ze)) { | 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; | 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 | * InputStream that delegates requests to the underlying | ||||
* RandomAccessFile, making sure that only bytes from a certain | * RandomAccessFile, making sure that only bytes from a certain | ||||
@@ -686,6 +879,7 @@ public class ZipFile { | |||||
loc = start; | loc = start; | ||||
} | } | ||||
@Override | |||||
public int read() throws IOException { | public int read() throws IOException { | ||||
if (remaining-- <= 0) { | if (remaining-- <= 0) { | ||||
if (addDummyByte) { | if (addDummyByte) { | ||||
@@ -700,6 +894,7 @@ public class ZipFile { | |||||
} | } | ||||
} | } | ||||
@Override | |||||
public int read(byte[] b, int off, int len) throws IOException { | public int read(byte[] b, int off, int len) throws IOException { | ||||
if (remaining <= 0) { | if (remaining <= 0) { | ||||
if (addDummyByte) { | if (addDummyByte) { | ||||
@@ -746,4 +941,32 @@ public class ZipFile { | |||||
this.comment = comment; | 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; | 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 | * Utility class that represents a four byte integer with conversion | ||||
* rules for the big endian byte order of ZIP files. | * 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 { | 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 = 1; | ||||
private static final int BYTE_1_MASK = 0xFF00; | 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 long BYTE_3_MASK = 0xFF000000L; | ||||
private static final int BYTE_3_SHIFT = 24; | 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. | * 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 | * 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 bytes the array of bytes | ||||
* @param offset the offset to start | * @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) { | public static long getValue(byte[] bytes, int offset) { | ||||
long value = (bytes[offset + BYTE_3] << BYTE_3_SHIFT) & BYTE_3_MASK; | 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 | * Helper method to get the value as a Java long from a four-byte array | ||||
* @param bytes the array of bytes | * @param bytes the array of bytes | ||||
* @return the correspondanding Java long value | |||||
* @return the corresponding Java long value | |||||
*/ | */ | ||||
public static long getValue(byte[] bytes) { | public static long getValue(byte[] bytes) { | ||||
return getValue(bytes, 0); | return getValue(bytes, 0); | ||||
@@ -131,6 +152,7 @@ public final class ZipLong implements Cloneable { | |||||
* @return true if the objects are equal | * @return true if the objects are equal | ||||
* @since 1.1 | * @since 1.1 | ||||
*/ | */ | ||||
@Override | |||||
public boolean equals(Object o) { | public boolean equals(Object o) { | ||||
if (o == null || !(o instanceof ZipLong)) { | if (o == null || !(o instanceof ZipLong)) { | ||||
return false; | return false; | ||||
@@ -143,10 +165,12 @@ public final class ZipLong implements Cloneable { | |||||
* @return the value stored in the ZipLong | * @return the value stored in the ZipLong | ||||
* @since 1.1 | * @since 1.1 | ||||
*/ | */ | ||||
@Override | |||||
public int hashCode() { | public int hashCode() { | ||||
return (int) value; | return (int) value; | ||||
} | } | ||||
@Override | |||||
public Object clone() { | public Object clone() { | ||||
try { | try { | ||||
return super.clone(); | return super.clone(); | ||||
@@ -155,4 +179,9 @@ public final class ZipLong implements Cloneable { | |||||
throw new RuntimeException(cnfe); | throw new RuntimeException(cnfe); | ||||
} | } | ||||
} | } | ||||
@Override | |||||
public String toString() { | |||||
return "ZipLong value: " + value; | |||||
} | |||||
} | } |
@@ -18,17 +18,18 @@ | |||||
package org.apache.tools.zip; | package org.apache.tools.zip; | ||||
import static org.apache.tools.zip.ZipConstants.BYTE_MASK; | |||||
/** | /** | ||||
* Utility class that represents a two byte integer with conversion | * Utility class that represents a two byte integer with conversion | ||||
* rules for the big endian byte order of ZIP files. | * rules for the big endian byte order of ZIP files. | ||||
* | * | ||||
*/ | */ | ||||
public final class ZipShort implements Cloneable { | 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_MASK = 0xFF00; | ||||
private static final int BYTE_1_SHIFT = 8; | private static final int BYTE_1_SHIFT = 8; | ||||
private int value; | |||||
private final int value; | |||||
/** | /** | ||||
* Create instance from a number. | * 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 | * 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 bytes the array of bytes | ||||
* @param offset the offset to start | * @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) { | public static int getValue(byte[] bytes, int offset) { | ||||
int value = (bytes[offset + 1] << BYTE_1_SHIFT) & BYTE_1_MASK; | 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 | * Helper method to get the value as a java int from a two-byte array | ||||
* @param bytes the array of bytes | * @param bytes the array of bytes | ||||
* @return the correspondanding java int value | |||||
* @return the corresponding java int value | |||||
*/ | */ | ||||
public static int getValue(byte[] bytes) { | public static int getValue(byte[] bytes) { | ||||
return getValue(bytes, 0); | return getValue(bytes, 0); | ||||
@@ -118,6 +119,7 @@ public final class ZipShort implements Cloneable { | |||||
* @return true if the objects are equal | * @return true if the objects are equal | ||||
* @since 1.1 | * @since 1.1 | ||||
*/ | */ | ||||
@Override | |||||
public boolean equals(Object o) { | public boolean equals(Object o) { | ||||
if (o == null || !(o instanceof ZipShort)) { | if (o == null || !(o instanceof ZipShort)) { | ||||
return false; | return false; | ||||
@@ -130,10 +132,12 @@ public final class ZipShort implements Cloneable { | |||||
* @return the value stored in the ZipShort | * @return the value stored in the ZipShort | ||||
* @since 1.1 | * @since 1.1 | ||||
*/ | */ | ||||
@Override | |||||
public int hashCode() { | public int hashCode() { | ||||
return value; | return value; | ||||
} | } | ||||
@Override | |||||
public Object clone() { | public Object clone() { | ||||
try { | try { | ||||
return super.clone(); | return super.clone(); | ||||
@@ -142,4 +146,9 @@ public final class ZipShort implements Cloneable { | |||||
throw new RuntimeException(cnfe); | throw new RuntimeException(cnfe); | ||||
} | } | ||||
} | } | ||||
@Override | |||||
public String toString() { | |||||
return "ZipShort value: " + value; | |||||
} | |||||
} | } |
@@ -17,11 +17,159 @@ | |||||
*/ | */ | ||||
package org.apache.tools.zip; | 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. | * Utility class for handling DOS and Java time conversions. | ||||
* @since Ant 1.8.1 | * @since Ant 1.8.1 | ||||
*/ | */ | ||||
public abstract class ZipUtil { | 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 | * Create a copy of the given array - or return null if the | ||||
* argument is null. | * argument is null. | ||||
@@ -35,4 +183,49 @@ public abstract class ZipUtil { | |||||
return null; | 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.ResourceCollection; | ||||
import org.apache.tools.ant.types.resources.ZipResource; | import org.apache.tools.ant.types.resources.ZipResource; | ||||
import org.apache.tools.zip.JarMarker; | import org.apache.tools.zip.JarMarker; | ||||
import org.apache.tools.zip.Zip64ExtendedInformationExtraField; | |||||
import org.apache.tools.zip.ZipEntry; | import org.apache.tools.zip.ZipEntry; | ||||
import org.apache.tools.zip.ZipExtraField; | import org.apache.tools.zip.ZipExtraField; | ||||
import org.apache.tools.zip.ZipFile; | import org.apache.tools.zip.ZipFile; | ||||
@@ -79,8 +80,10 @@ public class ZipExtraFieldTest extends TestCase { | |||||
zf = new ZipFile(f); | zf = new ZipFile(f); | ||||
ZipEntry ze = zf.getEntry("x"); | ZipEntry ze = zf.getEntry("x"); | ||||
assertNotNull(ze); | assertNotNull(ze); | ||||
assertEquals(1, ze.getExtraFields().length); | |||||
assertEquals(2, ze.getExtraFields().length); | |||||
assertTrue(ze.getExtraFields()[0] instanceof JarMarker); | assertTrue(ze.getExtraFields()[0] instanceof JarMarker); | ||||
assertTrue(ze.getExtraFields()[1] | |||||
instanceof Zip64ExtendedInformationExtraField); | |||||
} finally { | } finally { | ||||
ZipFile.closeQuietly(zf); | ZipFile.closeQuietly(zf); | ||||
if (f.exists()) { | if (f.exists()) { | ||||
@@ -29,17 +29,26 @@ public class ExtraFieldUtilsTest extends TestCase implements UnixStat { | |||||
super(name); | 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 AsiExtraField a; | ||||
private UnrecognizedExtraField dummy; | private UnrecognizedExtraField dummy; | ||||
private byte[] data; | private byte[] data; | ||||
private byte[] aLocal; | private byte[] aLocal; | ||||
@Override | |||||
public void setUp() { | public void setUp() { | ||||
a = new AsiExtraField(); | a = new AsiExtraField(); | ||||
a.setMode(0755); | a.setMode(0755); | ||||
a.setDirectory(true); | a.setDirectory(true); | ||||
dummy = new UnrecognizedExtraField(); | dummy = new UnrecognizedExtraField(); | ||||
dummy.setHeaderId(new ZipShort(1)); | |||||
dummy.setHeaderId(UNRECOGNIZED_HEADER); | |||||
dummy.setLocalFileDataData(new byte[] {0}); | dummy.setLocalFileDataData(new byte[] {0}); | ||||
dummy.setCentralDirectoryData(new byte[] {0}); | dummy.setCentralDirectoryData(new byte[] {0}); | ||||
@@ -167,7 +176,8 @@ public class ExtraFieldUtilsTest extends TestCase implements UnixStat { | |||||
public void testMergeWithUnparseableData() throws Exception { | public void testMergeWithUnparseableData() throws Exception { | ||||
ZipExtraField d = new UnparseableExtraFieldData(); | 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 = | byte[] local = | ||||
ExtraFieldUtils.mergeLocalFileDataData(new ZipExtraField[] {a, d}); | ExtraFieldUtils.mergeLocalFileDataData(new ZipExtraField[] {a, d}); | ||||
assertEquals("local length", data.length - 1, local.length); | assertEquals("local length", data.length - 1, local.length); | ||||
@@ -38,7 +38,7 @@ public class ZipEntryTest extends TestCase { | |||||
a.setDirectory(true); | a.setDirectory(true); | ||||
a.setMode(0755); | a.setMode(0755); | ||||
UnrecognizedExtraField u = new UnrecognizedExtraField(); | UnrecognizedExtraField u = new UnrecognizedExtraField(); | ||||
u.setHeaderId(new ZipShort(1)); | |||||
u.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||||
u.setLocalFileDataData(new byte[0]); | u.setLocalFileDataData(new byte[0]); | ||||
ZipEntry ze = new ZipEntry("test/"); | ZipEntry ze = new ZipEntry("test/"); | ||||
@@ -50,7 +50,7 @@ public class ZipEntryTest extends TestCase { | |||||
assertSame(u, result[1]); | assertSame(u, result[1]); | ||||
UnrecognizedExtraField u2 = new UnrecognizedExtraField(); | UnrecognizedExtraField u2 = new UnrecognizedExtraField(); | ||||
u2.setHeaderId(new ZipShort(1)); | |||||
u2.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||||
u2.setLocalFileDataData(new byte[] {1}); | u2.setLocalFileDataData(new byte[] {1}); | ||||
ze.addExtraField(u2); | ze.addExtraField(u2); | ||||
@@ -68,7 +68,7 @@ public class ZipEntryTest extends TestCase { | |||||
result = ze.getExtraFields(); | result = ze.getExtraFields(); | ||||
assertEquals("third pass", 3, result.length); | assertEquals("third pass", 3, result.length); | ||||
ze.removeExtraField(new ZipShort(1)); | |||||
ze.removeExtraField(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||||
byte[] data3 = ze.getExtra(); | byte[] data3 = ze.getExtra(); | ||||
result = ze.getExtraFields(); | result = ze.getExtraFields(); | ||||
assertEquals("fourth pass", 2, result.length); | assertEquals("fourth pass", 2, result.length); | ||||
@@ -77,7 +77,7 @@ public class ZipEntryTest extends TestCase { | |||||
assertEquals("length fourth pass", data2.length, data3.length); | assertEquals("length fourth pass", data2.length, data3.length); | ||||
try { | try { | ||||
ze.removeExtraField(new ZipShort(1)); | |||||
ze.removeExtraField(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||||
fail("should be no such element"); | fail("should be no such element"); | ||||
} catch (java.util.NoSuchElementException nse) { | } catch (java.util.NoSuchElementException nse) { | ||||
} | } | ||||
@@ -91,7 +91,7 @@ public class ZipEntryTest extends TestCase { | |||||
a.setDirectory(true); | a.setDirectory(true); | ||||
a.setMode(0755); | a.setMode(0755); | ||||
UnrecognizedExtraField u = new UnrecognizedExtraField(); | UnrecognizedExtraField u = new UnrecognizedExtraField(); | ||||
u.setHeaderId(new ZipShort(1)); | |||||
u.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||||
u.setLocalFileDataData(new byte[0]); | u.setLocalFileDataData(new byte[0]); | ||||
ZipEntry ze = new ZipEntry("test/"); | ZipEntry ze = new ZipEntry("test/"); | ||||
@@ -99,12 +99,14 @@ public class ZipEntryTest extends TestCase { | |||||
// merge | // merge | ||||
// Header-ID 1 + length 1 + one byte of data | // 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(); | ZipExtraField[] result = ze.getExtraFields(); | ||||
assertEquals("first pass", 2, result.length); | assertEquals("first pass", 2, result.length); | ||||
assertSame(a, result[0]); | 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(0), result[1].getLocalFileDataLength()); | ||||
assertEquals(new ZipShort(1), result[1].getCentralDirectoryLength()); | assertEquals(new ZipShort(1), result[1].getCentralDirectoryLength()); | ||||
@@ -135,7 +137,7 @@ public class ZipEntryTest extends TestCase { | |||||
a.setDirectory(true); | a.setDirectory(true); | ||||
a.setMode(0755); | a.setMode(0755); | ||||
UnrecognizedExtraField u = new UnrecognizedExtraField(); | UnrecognizedExtraField u = new UnrecognizedExtraField(); | ||||
u.setHeaderId(new ZipShort(1)); | |||||
u.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||||
u.setLocalFileDataData(new byte[0]); | u.setLocalFileDataData(new byte[0]); | ||||
ZipEntry ze = new ZipEntry("test/"); | ZipEntry ze = new ZipEntry("test/"); | ||||
@@ -143,7 +145,7 @@ public class ZipEntryTest extends TestCase { | |||||
byte[] data1 = ze.getExtra(); | byte[] data1 = ze.getExtra(); | ||||
UnrecognizedExtraField u2 = new UnrecognizedExtraField(); | UnrecognizedExtraField u2 = new UnrecognizedExtraField(); | ||||
u2.setHeaderId(new ZipShort(1)); | |||||
u2.setHeaderId(ExtraFieldUtilsTest.UNRECOGNIZED_HEADER); | |||||
u2.setLocalFileDataData(new byte[] {1}); | u2.setLocalFileDataData(new byte[] {1}); | ||||
ze.addAsFirstExtraField(u2); | ze.addAsFirstExtraField(u2); | ||||