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