diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip')
29 files changed, 7506 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/AbstractUnicodeExtraField.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/AbstractUnicodeExtraField.java new file mode 100644 index 00000000..1014787d --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/AbstractUnicodeExtraField.java @@ -0,0 +1,183 @@ +/* + * 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.io.UnsupportedEncodingException; +import java.util.zip.CRC32; +import java.util.zip.ZipException; + +/** + * A common base class for Unicode extra information extra fields. + */ +public abstract class AbstractUnicodeExtraField implements ZipExtraField { + private long nameCRC32; + private byte[] unicodeName; + private byte[] data; + + protected AbstractUnicodeExtraField() { + } + + /** + * Assemble as unicode extension from the name/comment and + * encoding of the original zip entry. + * + * @param text The file name or comment. + * @param bytes The encoded of the filename or comment in the zip + * file. + * @param off The offset of the encoded filename or comment in + * <code>bytes</code>. + * @param len The length of the encoded filename or commentin + * <code>bytes</code>. + */ + protected AbstractUnicodeExtraField(final String text, final byte[] bytes, final int off, + final int len) { + final CRC32 crc32 = new CRC32(); + crc32.update(bytes, off, len); + nameCRC32 = crc32.getValue(); + + try { + unicodeName = text.getBytes("UTF-8"); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException("FATAL: UTF-8 encoding not supported.", + e); + } + } + + /** + * Assemble as unicode extension from the name/comment and + * encoding of the original zip entry. + * + * @param text The file name or comment. + * @param bytes The encoded of the filename or comment in the zip + * file. + */ + protected AbstractUnicodeExtraField(final String text, final byte[] bytes) { + + this(text, bytes, 0, bytes.length); + } + + private void assembleData() { + if (unicodeName == null) { + return; + } + + data = new byte[5 + unicodeName.length]; + // version 1 + data[0] = 0x01; + System.arraycopy(ZipLong.getBytes(nameCRC32), 0, data, 1, 4); + System.arraycopy(unicodeName, 0, data, 5, unicodeName.length); + } + + /** + * @return The CRC32 checksum of the filename or comment as + * encoded in the central directory of the zip file. + */ + public long getNameCRC32() { + return nameCRC32; + } + + /** + * @param nameCRC32 The CRC32 checksum of the filename as encoded + * in the central directory of the zip file to set. + */ + public void setNameCRC32(final long nameCRC32) { + this.nameCRC32 = nameCRC32; + data = null; + } + + /** + * @return The utf-8 encoded name. + */ + public byte[] getUnicodeName() { + 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(final byte[] 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(); + } + 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(); + } + return new ZipShort(data.length); + } + + /** {@inheritDoc} */ + public byte[] getLocalFileDataData() { + return getCentralDirectoryData(); + } + + /** {@inheritDoc} */ + public ZipShort getLocalFileDataLength() { + return getCentralDirectoryLength(); + } + + /** {@inheritDoc} */ + public void parseFromLocalFileData(final byte[] buffer, final int offset, final int length) + throws ZipException { + + if (length < 5) { + throw new ZipException("UniCode path extra data must have at least" + + " 5 bytes."); + } + + final int version = buffer[offset]; + + if (version != 0x01) { + throw new ZipException("Unsupported version [" + version + + "] for UniCode path extra data."); + } + + nameCRC32 = ZipLong.getValue(buffer, offset + 1); + unicodeName = new byte[length - 5]; + System.arraycopy(buffer, offset + 5, unicodeName, 0, length - 5); + data = null; + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/AsiExtraField.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/AsiExtraField.java new file mode 100644 index 00000000..d2ca6910 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/AsiExtraField.java @@ -0,0 +1,352 @@ +/* + * 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.CRC32; +import java.util.zip.ZipException; + +/** + * Adds Unix file permission and UID/GID fields as well as symbolic + * link handling. + * + * <p>This class uses the ASi extra field in the format: + * <pre> + * Value Size Description + * ----- ---- ----------- + * (Unix3) 0x756e Short tag for this extra block type + * TSize Short total data size for this block + * CRC Long CRC-32 of the remaining data + * Mode Short file permissions + * SizDev Long symlink'd size OR major/minor dev num + * UID Short user ID + * GID Short group ID + * (var.) variable symbolic link filename + * </pre> + * taken from appnote.iz (Info-ZIP note, 981119) found at <a + * href="ftp://ftp.uu.net/pub/archiving/zip/doc/">ftp://ftp.uu.net/pub/archiving/zip/doc/</a></p> + + * + * <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 { + + private static final ZipShort HEADER_ID = new ZipShort(0x756E); + private static final int WORD = 4; + /** + * Standard Unix stat(2) file mode. + * + * @since 1.1 + */ + private int mode = 0; + /** + * User ID. + * + * @since 1.1 + */ + private int uid = 0; + /** + * Group ID. + * + * @since 1.1 + */ + private int gid = 0; + /** + * File this entry points to, if it is a symbolic link. + * + * <p>empty string - if entry is not a symbolic link.</p> + * + * @since 1.1 + */ + private String link = ""; + /** + * Is this an entry for a directory? + * + * @since 1.1 + */ + private boolean dirFlag = false; + + /** + * Instance used to calculate checksums. + * + * @since 1.1 + */ + private CRC32 crc = new CRC32(); + + /** Constructor for AsiExtraField. */ + public AsiExtraField() { + } + + /** + * The Header-ID. + * @return the value for the header id for this extrafield + * @since 1.1 + */ + public ZipShort getHeaderId() { + return HEADER_ID; + } + + /** + * Length of the extra field in the local file data - without + * Header-ID or length specifier. + * @return a <code>ZipShort</code> for the length of the data of this extra field + * @since 1.1 + */ + public ZipShort getLocalFileDataLength() { + return new ZipShort(WORD // CRC + + 2 // Mode + + WORD // SizDev + + 2 // UID + + 2 // GID + + getLinkedFile().getBytes().length); + // Uses default charset - see class Javadoc + } + + /** + * Delegate to local file data. + * @return the centralDirectory length + * @since 1.1 + */ + public ZipShort getCentralDirectoryLength() { + return getLocalFileDataLength(); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * @return get the data + * @since 1.1 + */ + public byte[] getLocalFileDataData() { + // CRC will be added later + byte[] data = new byte[getLocalFileDataLength().getValue() - WORD]; + System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2); + + byte[] linkArray = getLinkedFile().getBytes(); // Uses default charset - see class Javadoc + // CheckStyle:MagicNumber OFF + System.arraycopy(ZipLong.getBytes(linkArray.length), + 0, data, 2, WORD); + + System.arraycopy(ZipShort.getBytes(getUserId()), + 0, data, 6, 2); + System.arraycopy(ZipShort.getBytes(getGroupId()), + 0, data, 8, 2); + + System.arraycopy(linkArray, 0, data, 10, linkArray.length); + // CheckStyle:MagicNumber ON + + crc.reset(); + crc.update(data); + long checksum = crc.getValue(); + + byte[] result = new byte[data.length + WORD]; + System.arraycopy(ZipLong.getBytes(checksum), 0, result, 0, WORD); + System.arraycopy(data, 0, result, WORD, data.length); + return result; + } + + /** + * Delegate to local file data. + * @return the local file data + * @since 1.1 + */ + public byte[] getCentralDirectoryData() { + return getLocalFileDataData(); + } + + /** + * Set the user id. + * @param uid the user id + * @since 1.1 + */ + public void setUserId(int uid) { + this.uid = uid; + } + + /** + * Get the user id. + * @return the user id + * @since 1.1 + */ + public int getUserId() { + return uid; + } + + /** + * Set the group id. + * @param gid the group id + * @since 1.1 + */ + public void setGroupId(int gid) { + this.gid = gid; + } + + /** + * Get the group id. + * @return the group id + * @since 1.1 + */ + public int getGroupId() { + return gid; + } + + /** + * Indicate that this entry is a symbolic link to the given filename. + * + * @param name Name of the file this entry links to, empty String + * if it is not a symbolic link. + * + * @since 1.1 + */ + public void setLinkedFile(String name) { + link = name; + mode = getMode(mode); + } + + /** + * Name of linked file + * + * @return name of the file this entry links to if it is a + * symbolic link, the empty string otherwise. + * + * @since 1.1 + */ + public String getLinkedFile() { + return link; + } + + /** + * Is this entry a symbolic link? + * @return true if this is a symbolic link + * @since 1.1 + */ + public boolean isLink() { + return getLinkedFile().length() != 0; + } + + /** + * File mode of this file. + * @param mode the file mode + * @since 1.1 + */ + public void setMode(int mode) { + this.mode = getMode(mode); + } + + /** + * File mode of this file. + * @return the file mode + * @since 1.1 + */ + public int getMode() { + return mode; + } + + /** + * Indicate whether this entry is a directory. + * @param dirFlag if true, this entry is a directory + * @since 1.1 + */ + public void setDirectory(boolean dirFlag) { + this.dirFlag = dirFlag; + mode = getMode(mode); + } + + /** + * Is this entry a directory? + * @return true if this entry is a directory + * @since 1.1 + */ + public boolean isDirectory() { + return dirFlag && !isLink(); + } + + /** + * Populate data from this array as if it was in local file data. + * @param data an array of bytes + * @param offset the start offset + * @param length the number of bytes in the array from offset + * @since 1.1 + * @throws ZipException on error + */ + public void parseFromLocalFileData(byte[] data, int offset, int length) + throws ZipException { + + long givenChecksum = ZipLong.getValue(data, offset); + byte[] tmp = new byte[length - WORD]; + System.arraycopy(data, offset + WORD, tmp, 0, length - WORD); + crc.reset(); + crc.update(tmp); + long realChecksum = crc.getValue(); + if (givenChecksum != realChecksum) { + throw new ZipException("bad CRC checksum " + + Long.toHexString(givenChecksum) + + " instead of " + + Long.toHexString(realChecksum)); + } + + int newMode = ZipShort.getValue(tmp, 0); + // CheckStyle:MagicNumber OFF + byte[] linkArray = new byte[(int) ZipLong.getValue(tmp, 2)]; + uid = ZipShort.getValue(tmp, 6); + gid = ZipShort.getValue(tmp, 8); + + if (linkArray.length == 0) { + link = ""; + } else { + System.arraycopy(tmp, 10, linkArray, 0, linkArray.length); + link = new String(linkArray); // Uses default charset - see class Javadoc + } + // CheckStyle:MagicNumber ON + setDirectory((newMode & DIR_FLAG) != 0); + setMode(newMode); + } + + /** + * Get the file mode for given permissions with the correct file type. + * @param mode the mode + * @return the type with the mode + * @since 1.1 + */ + protected int getMode(int mode) { + int type = FILE_FLAG; + if (isLink()) { + type = LINK_FLAG; + } else if (isDirectory()) { + type = DIR_FLAG; + } + return type | (mode & PERM_MASK); + } + + @Override + public Object clone() { + try { + AsiExtraField cloned = (AsiExtraField) super.clone(); + cloned.crc = new CRC32(); + return cloned; + } catch (CloneNotSupportedException cnfe) { + // impossible + throw new RuntimeException(cnfe); + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/CentralDirectoryParsingZipExtraField.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/CentralDirectoryParsingZipExtraField.java new file mode 100644 index 00000000..738ed625 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/CentralDirectoryParsingZipExtraField.java @@ -0,0 +1,40 @@ +/* + * 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; + +/** + * {@link ZipExtraField ZipExtraField} that knows how to parse central + * directory data. + * + * @since Ant 1.8.0 + */ +public interface CentralDirectoryParsingZipExtraField extends ZipExtraField { + /** + * Populate data from this array as if it was in central directory data. + * @param data an array of bytes + * @param offset the start offset + * @param length the number of bytes in the array from offset + * + * @throws ZipException on error + */ + void parseFromCentralDirectoryData(byte[] data, int offset, int length) + throws ZipException; +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ExtraFieldUtils.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ExtraFieldUtils.java new file mode 100644 index 00000000..a6c0118f --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ExtraFieldUtils.java @@ -0,0 +1,314 @@ +/* + * 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.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.ZipException; + +/** + * ZipExtraField related methods + * + */ +// CheckStyle:HideUtilityClassConstructorCheck OFF (bc) +public class ExtraFieldUtils { + + private static final int WORD = 4; + + /** + * Static registry of known extra fields. + * + * @since 1.1 + */ + private static final Map<ZipShort, Class<?>> implementations; + + static { + implementations = new ConcurrentHashMap<ZipShort, Class<?>>(); + register(AsiExtraField.class); + register(JarMarker.class); + register(UnicodePathExtraField.class); + register(UnicodeCommentExtraField.class); + register(Zip64ExtendedInformationExtraField.class); + } + + /** + * Register a ZipExtraField implementation. + * + * <p>The given class must have a no-arg constructor and implement + * the {@link ZipExtraField ZipExtraField interface}.</p> + * @param c the class to register + * + * @since 1.1 + */ + public static void register(Class<?> c) { + try { + ZipExtraField ze = (ZipExtraField) c.newInstance(); + implementations.put(ze.getHeaderId(), c); + } catch (ClassCastException cc) { + throw new RuntimeException(c + " doesn\'t implement ZipExtraField"); + } catch (InstantiationException ie) { + throw new RuntimeException(c + " is not a concrete class"); + } catch (IllegalAccessException ie) { + throw new RuntimeException(c + "\'s no-arg constructor is not public"); + } + } + + /** + * Create an instance of the appropriate ExtraField, falls back to + * {@link UnrecognizedExtraField UnrecognizedExtraField}. + * @param headerId the header identifier + * @return an instance of the appropriate ExtraField + * @exception InstantiationException if unable to instantiate the class + * @exception IllegalAccessException if not allowed to instantiate the class + * @since 1.1 + */ + public static ZipExtraField createExtraField(ZipShort headerId) + throws InstantiationException, IllegalAccessException { + Class<?> c = implementations.get(headerId); + if (c != null) { + return (ZipExtraField) c.newInstance(); + } + UnrecognizedExtraField u = new UnrecognizedExtraField(); + u.setHeaderId(headerId); + return u; + } + + /** + * Split the array into ExtraFields and populate them with the + * given data as local file data, throwing an exception if the + * data cannot be parsed. + * @param data an array of bytes as it appears in local file data + * @return an array of ExtraFields + * @throws ZipException on error + */ + public static ZipExtraField[] parse(byte[] data) throws ZipException { + return parse(data, true, UnparseableExtraField.THROW); + } + + /** + * Split the array into ExtraFields and populate them with the + * given data, throwing an exception if the data cannot be parsed. + * @param data an array of bytes + * @param local whether data originates from the local file data + * or the central directory + * @return an array of ExtraFields + * @since 1.1 + * @throws ZipException on error + */ + public static ZipExtraField[] parse(byte[] data, boolean local) + throws ZipException { + return parse(data, local, UnparseableExtraField.THROW); + } + + /** + * Split the array into ExtraFields and populate them with the + * given data. + * @param data an array of bytes + * @param local whether data originates from the local file data + * or the central directory + * @param onUnparseableData what to do if the extra field data + * cannot be parsed. + * @return an array of ExtraFields + * @throws ZipException on error + * @since Ant 1.8.1 + */ + public static ZipExtraField[] parse(byte[] data, boolean local, + UnparseableExtraField onUnparseableData) + throws ZipException { + List<ZipExtraField> v = new ArrayList<ZipExtraField>(); + int start = 0; + LOOP: + while (start <= data.length - WORD) { + ZipShort headerId = new ZipShort(data, start); + int length = (new ZipShort(data, start + 2)).getValue(); + if (start + WORD + length > data.length) { + switch(onUnparseableData.getKey()) { + case UnparseableExtraField.THROW_KEY: + throw new ZipException("bad extra field starting at " + + start + ". Block length of " + + length + " bytes exceeds remaining" + + " data of " + + (data.length - start - WORD) + + " bytes."); + case UnparseableExtraField.READ_KEY: + UnparseableExtraFieldData field = + new UnparseableExtraFieldData(); + if (local) { + field.parseFromLocalFileData(data, start, + data.length - start); + } else { + field.parseFromCentralDirectoryData(data, start, + data.length - start); + } + v.add(field); + //$FALL-THROUGH$ + case UnparseableExtraField.SKIP_KEY: + // since we cannot parse the data we must assume + // the extra field consumes the whole rest of the + // available data + break LOOP; + default: + throw new ZipException("unknown UnparseableExtraField key: " + + onUnparseableData.getKey()); + } + } + try { + ZipExtraField ze = createExtraField(headerId); + if (local + || !(ze instanceof CentralDirectoryParsingZipExtraField)) { + ze.parseFromLocalFileData(data, start + WORD, length); + } else { + ((CentralDirectoryParsingZipExtraField) ze) + .parseFromCentralDirectoryData(data, start + WORD, + length); + } + v.add(ze); + } catch (InstantiationException ie) { + throw new ZipException(ie.getMessage()); + } catch (IllegalAccessException iae) { + throw new ZipException(iae.getMessage()); + } + start += (length + WORD); + } + + ZipExtraField[] result = new ZipExtraField[v.size()]; + return v.toArray(result); + } + + /** + * Merges the local file data fields of the given ZipExtraFields. + * @param data an array of ExtraFiles + * @return an array of bytes + * @since 1.1 + */ + public static byte[] mergeLocalFileDataData(ZipExtraField[] data) { + final boolean lastIsUnparseableHolder = data.length > 0 + && data[data.length - 1] instanceof UnparseableExtraFieldData; + int regularExtraFieldCount = + lastIsUnparseableHolder ? data.length - 1 : data.length; + + int sum = WORD * regularExtraFieldCount; + for (ZipExtraField element : data) { + sum += element.getLocalFileDataLength().getValue(); + } + + byte[] result = new byte[sum]; + int start = 0; + for (int i = 0; i < regularExtraFieldCount; i++) { + System.arraycopy(data[i].getHeaderId().getBytes(), + 0, result, start, 2); + System.arraycopy(data[i].getLocalFileDataLength().getBytes(), + 0, result, start + 2, 2); + byte[] local = data[i].getLocalFileDataData(); + System.arraycopy(local, 0, result, start + WORD, local.length); + start += (local.length + WORD); + } + if (lastIsUnparseableHolder) { + byte[] local = data[data.length - 1].getLocalFileDataData(); + System.arraycopy(local, 0, result, start, local.length); + } + return result; + } + + /** + * Merges the central directory fields of the given ZipExtraFields. + * @param data an array of ExtraFields + * @return an array of bytes + * @since 1.1 + */ + public static byte[] mergeCentralDirectoryData(ZipExtraField[] data) { + final boolean lastIsUnparseableHolder = data.length > 0 + && data[data.length - 1] instanceof UnparseableExtraFieldData; + int regularExtraFieldCount = + lastIsUnparseableHolder ? data.length - 1 : data.length; + + int sum = WORD * regularExtraFieldCount; + for (ZipExtraField element : data) { + sum += element.getCentralDirectoryLength().getValue(); + } + byte[] result = new byte[sum]; + int start = 0; + for (int i = 0; i < regularExtraFieldCount; i++) { + System.arraycopy(data[i].getHeaderId().getBytes(), + 0, result, start, 2); + System.arraycopy(data[i].getCentralDirectoryLength().getBytes(), + 0, result, start + 2, 2); + byte[] local = data[i].getCentralDirectoryData(); + System.arraycopy(local, 0, result, start + WORD, local.length); + start += (local.length + WORD); + } + if (lastIsUnparseableHolder) { + byte[] local = data[data.length - 1].getCentralDirectoryData(); + System.arraycopy(local, 0, result, start, local.length); + } + return result; + } + + /** + * "enum" for the possible actions to take if the extra field + * cannot be parsed. + */ + public static final class UnparseableExtraField { + /** + * Key for "throw an exception" action. + */ + public static final int THROW_KEY = 0; + /** + * Key for "skip" action. + */ + public static final int SKIP_KEY = 1; + /** + * Key for "read" action. + */ + public static final int READ_KEY = 2; + + /** + * Throw an exception if field cannot be parsed. + */ + public static final UnparseableExtraField THROW + = new UnparseableExtraField(THROW_KEY); + + /** + * Skip the extra field entirely and don't make its data + * available - effectively removing the extra field data. + */ + public static final UnparseableExtraField SKIP + = new UnparseableExtraField(SKIP_KEY); + + /** + * Read the extra field data into an instance of {@link + * UnparseableExtraFieldData UnparseableExtraFieldData}. + */ + public static final UnparseableExtraField READ + = new UnparseableExtraField(READ_KEY); + + private final int key; + + private UnparseableExtraField(int k) { + key = k; + } + + /** + * Key of the action to take. + */ + public int getKey() { return key; } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/FallbackZipEncoding.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/FallbackZipEncoding.java new file mode 100644 index 00000000..3edbb8e1 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/FallbackZipEncoding.java @@ -0,0 +1,94 @@ +/* + * 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.io.IOException; +import java.nio.ByteBuffer; + +/** + * A fallback ZipEncoding, which uses a java.io means to encode names. + * + * <p>This implementation is not favorable for encodings other than + * utf-8, because java.io encodes unmappable character as question + * marks leading to unreadable ZIP entries on some operating + * systems.</p> + * + * <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 + * neither {@link Simple8BitZipEnoding} nor {@link NioZipEncoding} is + * available.</p> + * + * <p>The methods of this class are reentrant.</p> + */ +class FallbackZipEncoding implements ZipEncoding { + private final String charset; + + /** + * Construct a fallback zip encoding, which uses the platform's + * default charset. + */ + public FallbackZipEncoding() { + this.charset = null; + } + + /** + * Construct a fallback zip encoding, which uses the given charset. + * + * @param charset The name of the charset or {@code null} for + * the platform's default character set. + */ + public FallbackZipEncoding(final String charset) { + this.charset = charset; + } + + /** + * @see + * org.apache.tools.zip.ZipEncoding#canEncode(java.lang.String) + */ + public boolean canEncode(final String name) { + return true; + } + + /** + * @see + * org.apache.tools.zip.ZipEncoding#encode(java.lang.String) + */ + public ByteBuffer encode(final String name) throws IOException { + 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)); + } + } + + /** + * @see + * org.apache.tools.zip.ZipEncoding#decode(byte[]) + */ + public String decode(final byte[] data) throws IOException { + if (this.charset == null) { // i.e. use default charset, see no-args constructor + return new String(data); + } else { + return new String(data,this.charset); + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/GeneralPurposeBit.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/GeneralPurposeBit.java new file mode 100644 index 00000000..1d2255fa --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/GeneralPurposeBit.java @@ -0,0 +1,194 @@ +/* + * 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 implements Cloneable { + /** + * 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() { + byte[] result = new byte[2]; + encode(result, 0); + return result; + } + + /** + * Encodes the set bits in a form suitable for ZIP archives. + * + * @param buf the output buffer + * @param offset + * The offset within the output buffer of the first byte to be written. + * must be non-negative and no larger than <tt>buf.length-2</tt> + */ + public void encode(byte[] buf, int offset) { + ZipShort.putShort((dataDescriptorFlag ? DATA_DESCRIPTOR_FLAG : 0) + | + (languageEncodingFlag ? UFT8_NAMES_FLAG : 0) + | + (encryptionFlag ? ENCRYPTION_FLAG : 0) + | + (strongEncryptionFlag ? STRONG_ENCRYPTION_FLAG : 0) + , buf, offset); + } + + /** + * 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; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ex) { + // impossible + throw new RuntimeException("GeneralPurposeBit is not Cloneable?", ex); + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/JarMarker.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/JarMarker.java new file mode 100644 index 00000000..c0633539 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/JarMarker.java @@ -0,0 +1,108 @@ +/* + * 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; + +/** + * If this extra field is added as the very first extra field of the + * archive, Solaris will consider it an executable jar file. + * + * @since Ant 1.6.3 + */ +public final class JarMarker implements ZipExtraField { + + private static final ZipShort ID = new ZipShort(0xCAFE); + private static final ZipShort NULL = new ZipShort(0); + private static final byte[] NO_BYTES = new byte[0]; + private static final JarMarker DEFAULT = new JarMarker(); + + /** No-arg constructor */ + public JarMarker() { + // empty + } + + /** + * Since JarMarker is stateless we can always use the same instance. + * @return the DEFAULT jarmaker. + */ + public static JarMarker getInstance() { + return DEFAULT; + } + + /** + * The Header-ID. + * @return the header id + */ + public ZipShort getHeaderId() { + return ID; + } + + /** + * Length of the extra field in the local file data - without + * Header-ID or length specifier. + * @return 0 + */ + public ZipShort getLocalFileDataLength() { + return NULL; + } + + /** + * Length of the extra field in the central directory - without + * Header-ID or length specifier. + * @return 0 + */ + public ZipShort getCentralDirectoryLength() { + return NULL; + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * @return the data + * @since 1.1 + */ + public byte[] getLocalFileDataData() { + return NO_BYTES; + } + + /** + * The actual data to put central directory - without Header-ID or + * length specifier. + * @return the data + */ + public byte[] getCentralDirectoryData() { + return NO_BYTES; + } + + /** + * Populate data from this array as if it was in local file data. + * @param data an array of bytes + * @param offset the start offset + * @param length the number of bytes in the array from offset + * + * @throws ZipException on error + */ + public void parseFromLocalFileData(byte[] data, int offset, int length) + throws ZipException { + if (length != 0) { + throw new ZipException("JarMarker doesn't expect any data"); + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/NioZipEncoding.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/NioZipEncoding.java new file mode 100644 index 00000000..63d33ff5 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/NioZipEncoding.java @@ -0,0 +1,122 @@ +/* + * 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.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; + +/** + * A ZipEncoding, which uses a java.nio {@link + * java.nio.charset.Charset Charset} to encode names. + * + * <p>This implementation works for all cases under java-1.5 or + * later. However, in java-1.4, some charsets don't have a java.nio + * implementation, most notably the default ZIP encoding Cp437.</p> + * + * <p>The methods of this class are reentrant.</p> + */ +class NioZipEncoding implements ZipEncoding { + private final Charset charset; + + /** + * Construct an NIO based zip encoding, which wraps the given + * charset. + * + * @param charset The NIO charset to wrap. + */ + public NioZipEncoding(final Charset charset) { + this.charset = charset; + } + + /** + * @see + * org.apache.tools.zip.ZipEncoding#canEncode(java.lang.String) + */ + public boolean canEncode(final String name) { + final CharsetEncoder enc = this.charset.newEncoder(); + enc.onMalformedInput(CodingErrorAction.REPORT); + enc.onUnmappableCharacter(CodingErrorAction.REPORT); + + return enc.canEncode(name); + } + + /** + * @see + * org.apache.tools.zip.ZipEncoding#encode(java.lang.String) + */ + public ByteBuffer encode(final String name) { + final CharsetEncoder enc = this.charset.newEncoder(); + + enc.onMalformedInput(CodingErrorAction.REPORT); + enc.onUnmappableCharacter(CodingErrorAction.REPORT); + + final CharBuffer cb = CharBuffer.wrap(name); + ByteBuffer out = ByteBuffer.allocate(name.length() + + (name.length() + 1) / 2); + + while (cb.remaining() > 0) { + final CoderResult res = enc.encode(cb, out,true); + + if (res.isUnmappable() || res.isMalformed()) { + + // write the unmappable characters in utf-16 + // pseudo-URL encoding style to ByteBuffer. + if (res.length() * 6 > out.remaining()) { + out = ZipEncodingHelper.growBuffer(out, out.position() + + res.length() * 6); + } + + for (int i=0; i<res.length(); ++i) { + ZipEncodingHelper.appendSurrogate(out,cb.get()); + } + + } else if (res.isOverflow()) { + + out = ZipEncodingHelper.growBuffer(out, 0); + + } else if (res.isUnderflow()) { + + enc.flush(out); + break; + + } + } + + out.limit(out.position()); + out.rewind(); + return out; + } + + /** + * @see + * org.apache.tools.zip.ZipEncoding#decode(byte[]) + */ + public String decode(final byte[] data) throws IOException { + return this.charset.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(data)).toString(); + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Simple8BitZipEncoding.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Simple8BitZipEncoding.java new file mode 100644 index 00000000..57a838ca --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Simple8BitZipEncoding.java @@ -0,0 +1,274 @@ +/* + * 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.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This ZipEncoding implementation implements a simple 8bit character + * set, which mets the following restrictions: + * + * <ul> + * <li>Characters 0x0000 to 0x007f are encoded as the corresponding + * byte values 0x00 to 0x7f.</li> + * <li>All byte codes from 0x80 to 0xff are mapped to a unique unicode + * character in the range 0x0080 to 0x7fff. (No support for + * UTF-16 surrogates) + * </ul> + * + * <p>These restrictions most notably apply to the most prominent + * omissions of java-1.4's {@link java.nio.charset.Charset Charset} + * implementation, Cp437 and Cp850.</p> + * + * <p>The methods of this class are reentrant.</p> + */ +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<Simple8BitChar> { + public final char unicode; + public final byte code; + + Simple8BitChar(final byte code, final char unicode) { + this.code = code; + this.unicode = unicode; + } + + public int compareTo(final Simple8BitChar a) { + return this.unicode - a.unicode; + } + + @Override + public String toString() { + return "0x" + Integer.toHexString(0xffff & unicode) + + "->0x" + Integer.toHexString(0xff & code); + } + + @Override + public boolean equals(final Object o) { + if (o instanceof Simple8BitChar) { + final Simple8BitChar other = (Simple8BitChar) o; + return unicode == other.unicode && code == other.code; + } + return false; + } + + @Override + public int hashCode() { + return unicode; + } + } + + /** + * The characters for byte values of 128 to 255 stored as an array of + * 128 chars. + */ + private final char[] highChars; + + /** + * A list of {@link Simple8BitChar} objects sorted by the unicode + * field. This list is used to binary search reverse mapping of + * unicode characters with a character code greater than 127. + */ + 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(final char[] highChars) { + this.highChars = highChars.clone(); + final List<Simple8BitChar> temp = + new ArrayList<Simple8BitChar>(this.highChars.length); + + byte code = 127; + + for (int i = 0; i < this.highChars.length; ++i) { + temp.add(new Simple8BitChar(++code, this.highChars[i])); + } + + Collections.sort(temp); + this.reverseMapping = Collections.unmodifiableList(temp); + } + + /** + * Return the character code for a given encoded byte. + * + * @param b The byte to decode. + * @return The associated character value. + */ + public char decodeByte(final byte b) { + // code 0-127 + if (b >= 0) { + return (char) b; + } + + // byte is signed, so 128 == -128 and 255 == -1 + return this.highChars[128 + b]; + } + + /** + * @param c The character to encode. + * @return Whether the given unicode character is covered by this encoding. + */ + public boolean canEncodeChar(final char c) { + + if (c >= 0 && c < 128) { + return true; + } + + final Simple8BitChar r = this.encodeHighChar(c); + return r != null; + } + + /** + * Pushes the encoded form of the given character to the given byte buffer. + * + * @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} is returned, nothing is pushed to the + * byte buffer. + */ + public boolean pushEncodedChar(final ByteBuffer bb, final char c) { + + if (c >= 0 && c < 128) { + bb.put((byte) c); + return true; + } + + final Simple8BitChar r = this.encodeHighChar(c); + if (r == null) { + return false; + } + bb.put(r.code); + return true; + } + + /** + * @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} value is returned, if this character is not + * covered by this encoding. + */ + private Simple8BitChar encodeHighChar(final char c) { + // for performance an simplicity, yet another reincarnation of + // binary search... + int i0 = 0; + int i1 = this.reverseMapping.size(); + + while (i1 > i0) { + + final int i = i0 + (i1 - i0) / 2; + + final Simple8BitChar m = this.reverseMapping.get(i); + + if (m.unicode == c) { + return m; + } + + if (m.unicode < c) { + i0 = i + 1; + } else { + i1 = i; + } + } + + if (i0 >= this.reverseMapping.size()) { + return null; + } + + final Simple8BitChar r = this.reverseMapping.get(i0); + + if (r.unicode != c) { + return null; + } + + return r; + } + + /** + * @see + * org.apache.tools.zip.ZipEncoding#canEncode(java.lang.String) + */ + public boolean canEncode(final String name) { + + for (int i=0;i<name.length();++i) { + + final char c = name.charAt(i); + + if (!this.canEncodeChar(c)) { + return false; + } + } + + return true; + } + + /** + * @see + * org.apache.tools.zip.ZipEncoding#encode(java.lang.String) + */ + public ByteBuffer encode(final String name) { + ByteBuffer out = ByteBuffer.allocate(name.length() + + 6 + (name.length() + 1) / 2); + + for (int i=0;i<name.length();++i) { + + final char c = name.charAt(i); + + if (out.remaining() < 6) { + out = ZipEncodingHelper.growBuffer(out,out.position() + 6); + } + + if (!this.pushEncodedChar(out,c)) { + + ZipEncodingHelper.appendSurrogate(out,c); + } + } + + out.limit(out.position()); + out.rewind(); + return out; + } + + /** + * @see + * org.apache.tools.zip.ZipEncoding#decode(byte[]) + */ + public String decode(final byte[] data) throws IOException { + final char [] ret = new char[data.length]; + + for (int i=0;i<data.length;++i) { + ret[i] = this.decodeByte(data[i]); + } + + return new String(ret); + } + + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnicodeCommentExtraField.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnicodeCommentExtraField.java new file mode 100644 index 00000000..51550fe7 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnicodeCommentExtraField.java @@ -0,0 +1,70 @@ +/* + * 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; + +/** + * Info-ZIP Unicode Comment Extra Field (0x6375): + * + * <p>Stores the UTF-8 version of the file comment as stored in the + * central directory header.</p> + * + * <p>See {@link + * "http://www.pkware.com/documents/casestudies/APPNOTE.TXT PKWARE's + * APPNOTE.TXT, section 4.6.8"}.</p> + * + */ +public class UnicodeCommentExtraField extends AbstractUnicodeExtraField { + + public static final ZipShort UCOM_ID = new ZipShort(0x6375); + + public UnicodeCommentExtraField () { + } + + /** + * Assemble as unicode comment extension from the name given as + * text as well as the encoded bytes actually written to the archive. + * + * @param text The file name + * @param bytes the bytes actually written to the archive + * @param off The offset of the encoded comment in <code>bytes</code>. + * @param len The length of the encoded comment or comment in + * <code>bytes</code>. + */ + public UnicodeCommentExtraField(final String text, final byte[] bytes, final int off, + final int len) { + super(text, bytes, off, len); + } + + /** + * Assemble as unicode comment extension from the comment given as + * text as well as the bytes actually written to the archive. + * + * @param comment The file comment + * @param bytes the bytes actually written to the archive + */ + public UnicodeCommentExtraField(final String comment, final byte[] bytes) { + super(comment, bytes); + } + + /** {@inheritDoc} */ + public ZipShort getHeaderId() { + return UCOM_ID; + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnicodePathExtraField.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnicodePathExtraField.java new file mode 100644 index 00000000..8b045a1e --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnicodePathExtraField.java @@ -0,0 +1,67 @@ +/* + * 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; + +/** + * Info-ZIP Unicode Path Extra Field (0x7075): + * + * <p>Stores the UTF-8 version of the file name field as stored in the + * local header and central directory header.</p> + * + * <p>See {@link + * "http://www.pkware.com/documents/casestudies/APPNOTE.TXT PKWARE's + * APPNOTE.TXT, section 4.6.9"}.</p> + */ +public class UnicodePathExtraField extends AbstractUnicodeExtraField { + + public static final ZipShort UPATH_ID = new ZipShort(0x7075); + + public UnicodePathExtraField () { + } + + /** + * Assemble as unicode path extension from the name given as + * text as well as the encoded bytes actually written to the archive. + * + * @param text The file name + * @param bytes the bytes actually written to the archive + * @param off The offset of the encoded filename in <code>bytes</code>. + * @param len The length of the encoded filename or comment in + * <code>bytes</code>. + */ + public UnicodePathExtraField(final String text, final byte[] bytes, final int off, final int len) { + super(text, bytes, off, len); + } + + /** + * Assemble as unicode path extension from the name given as + * text as well as the encoded bytes actually written to the archive. + * + * @param name The file name + * @param bytes the bytes actually written to the archive + */ + public UnicodePathExtraField(final String name, final byte[] bytes) { + super(name, bytes); + } + + /** {@inheritDoc} */ + public ZipShort getHeaderId() { + return UPATH_ID; + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnixStat.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnixStat.java new file mode 100644 index 00000000..2ca0674b --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnixStat.java @@ -0,0 +1,76 @@ +/* + * 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; + +/** + * Constants from stat.h on Unix systems. + * + */ +// CheckStyle:InterfaceIsTypeCheck OFF - backward compatible +public interface UnixStat { + + /** + * Bits used for permissions (and sticky bit) + * + * @since 1.1 + */ + int PERM_MASK = 07777; + /** + * Indicates symbolic links. + * + * @since 1.1 + */ + int LINK_FLAG = 0120000; + /** + * Indicates plain files. + * + * @since 1.1 + */ + int FILE_FLAG = 0100000; + /** + * Indicates directories. + * + * @since 1.1 + */ + int DIR_FLAG = 040000; + + // ---------------------------------------------------------- + // somewhat arbitrary choices that are quite common for shared + // installations + // ----------------------------------------------------------- + + /** + * Default permissions for symbolic links. + * + * @since 1.1 + */ + int DEFAULT_LINK_PERM = 0777; + /** + * Default permissions for directories. + * + * @since 1.1 + */ + int DEFAULT_DIR_PERM = 0755; + /** + * Default permissions for plain files. + * + * @since 1.1 + */ + int DEFAULT_FILE_PERM = 0644; +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnparseableExtraFieldData.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnparseableExtraFieldData.java new file mode 100644 index 00000000..92d30020 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnparseableExtraFieldData.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tools.zip; + +/** + * Wrapper for extra field data that doesn't conform to the recommended format of header-tag + size + data. + * + * <p>The header-id is artificial (and not listed as a 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> + * + * @since Ant 1.8.1 + */ +public final class UnparseableExtraFieldData + implements CentralDirectoryParsingZipExtraField { + + private static final ZipShort HEADER_ID = new ZipShort(0xACC1); + + private byte[] localFileData; + private byte[] centralDirectoryData; + + /** + * The Header-ID. + * + * @return a completely arbitrary value that should be ignored. + */ + public ZipShort getHeaderId() { + return HEADER_ID; + } + + /** + * Length of the complete extra field in the local file data. + * + * @return The LocalFileDataLength value + */ + public ZipShort getLocalFileDataLength() { + return new ZipShort(localFileData == null ? 0 : localFileData.length); + } + + /** + * Length of the complete extra field in the central directory. + * + * @return The CentralDirectoryLength value + */ + public ZipShort getCentralDirectoryLength() { + return centralDirectoryData == null + ? getLocalFileDataLength() + : new ZipShort(centralDirectoryData.length); + } + + /** + * The actual data to put into local file data. + * + * @return The LocalFileDataData value + */ + public byte[] getLocalFileDataData() { + return ZipUtil.copy(localFileData); + } + + /** + * The actual data to put into central directory. + * + * @return The CentralDirectoryData value + */ + public byte[] getCentralDirectoryData() { + return centralDirectoryData == null + ? getLocalFileDataData() : ZipUtil.copy(centralDirectoryData); + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param buffer the buffer to read data from + * @param offset offset into buffer to read data + * @param length the length of data + */ + public void parseFromLocalFileData(byte[] buffer, int offset, int length) { + localFileData = new byte[length]; + System.arraycopy(buffer, offset, localFileData, 0, length); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param buffer the buffer to read data from + * @param offset offset into buffer to read data + * @param length the length of data + */ + public void parseFromCentralDirectoryData(byte[] buffer, int offset, + int length) { + centralDirectoryData = new byte[length]; + System.arraycopy(buffer, offset, centralDirectoryData, 0, length); + if (localFileData == null) { + parseFromLocalFileData(buffer, offset, length); + } + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnrecognizedExtraField.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnrecognizedExtraField.java new file mode 100644 index 00000000..0e4262de --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnrecognizedExtraField.java @@ -0,0 +1,154 @@ +/* + * 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; + +/** + * Simple placeholder for all those extra fields we don't want to deal + * with. + * + * <p>Assumes local file data and central directory entries are + * identical - unless told the opposite.</p> + * + */ +public class UnrecognizedExtraField + implements CentralDirectoryParsingZipExtraField { + + /** + * The Header-ID. + * + * @since 1.1 + */ + private ZipShort headerId; + + /** + * Set the header id. + * @param headerId the header id to use + */ + public void setHeaderId(ZipShort headerId) { + this.headerId = headerId; + } + + /** + * Get the header id. + * @return the header id + */ + public ZipShort getHeaderId() { + return headerId; + } + + /** + * Extra field data in local file data - without + * Header-ID or length specifier. + * + * @since 1.1 + */ + private byte[] localData; + + /** + * Set the extra field data in the local file data - + * without Header-ID or length specifier. + * @param data the field data to use + */ + public void setLocalFileDataData(byte[] data) { + localData = ZipUtil.copy(data); + } + + /** + * Get the length of the local data. + * @return the length of the local data + */ + public ZipShort getLocalFileDataLength() { + return new ZipShort(localData.length); + } + + /** + * Get the local data. + * @return the local data + */ + public byte[] getLocalFileDataData() { + return ZipUtil.copy(localData); + } + + /** + * Extra field data in central directory - without + * Header-ID or length specifier. + * + * @since 1.1 + */ + private byte[] centralData; + + /** + * Set the extra field data in central directory. + * @param data the data to use + */ + public void setCentralDirectoryData(byte[] data) { + centralData = ZipUtil.copy(data); + } + + /** + * Get the central data length. + * If there is no central data, get the local file data length. + * @return the central data length + */ + public ZipShort getCentralDirectoryLength() { + if (centralData != null) { + return new ZipShort(centralData.length); + } + return getLocalFileDataLength(); + } + + /** + * Get the central data. + * @return the central data if present, else return the local file data + */ + public byte[] getCentralDirectoryData() { + if (centralData != null) { + return ZipUtil.copy(centralData); + } + return getLocalFileDataData(); + } + + /** + * @param data the array of bytes. + * @param offset the source location in the data array. + * @param length the number of bytes to use in the data array. + * @see ZipExtraField#parseFromLocalFileData(byte[], int, int) + */ + public void parseFromLocalFileData(byte[] data, int offset, int length) { + byte[] tmp = new byte[length]; + System.arraycopy(data, offset, tmp, 0, length); + setLocalFileDataData(tmp); + } + + /** + * @param data the array of bytes. + * @param offset the source location in the data array. + * @param length the number of bytes to use in the data array. + */ + public void parseFromCentralDirectoryData(byte[] data, int offset, + int length) { + byte[] tmp = new byte[length]; + System.arraycopy(data, offset, tmp, 0, length); + setCentralDirectoryData(tmp); + if (localData == null) { + setLocalFileDataData(tmp); + } + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnsupportedZipFeatureException.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnsupportedZipFeatureException.java new file mode 100644 index 00000000..534bb165 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/UnsupportedZipFeatureException.java @@ -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; + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Zip64ExtendedInformationExtraField.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Zip64ExtendedInformationExtraField.java new file mode 100644 index 00000000..16502ac6 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Zip64ExtendedInformationExtraField.java @@ -0,0 +1,322 @@ +/* + * 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 static org.apache.tools.zip.ZipConstants.DWORD; +import static org.apache.tools.zip.ZipConstants.WORD; + +import java.util.zip.ZipException; + +/** + * Holds size and other extended information for entries that use Zip64 + * features. + * + * <p>See {@link + * "http://www.pkware.com/documents/casestudies/APPNOTE.TXT PKWARE's + * APPNOTE.TXT, section 4.5.3"}.</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 static final byte[] EMPTY = new byte[0]; + + 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 EMPTY; + } + + /** {@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 must only be 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; + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Zip64Mode.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Zip64Mode.java new file mode 100644 index 00000000..30d9f1cb --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Zip64Mode.java @@ -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 +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Zip64RequiredException.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Zip64RequiredException.java new file mode 100644 index 00000000..f7fb7790 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/Zip64RequiredException.java @@ -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); + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipConstants.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipConstants.java new file mode 100644 index 00000000..83ae9569 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipConstants.java @@ -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; + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEightByteInteger.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEightByteInteger.java new file mode 100644 index 00000000..8d582dd8 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEightByteInteger.java @@ -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 static org.apache.tools.zip.ZipConstants.BYTE_MASK; + +import java.math.BigInteger; + +/** + * 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; + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEncoding.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEncoding.java new file mode 100644 index 00000000..72653834 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEncoding.java @@ -0,0 +1,84 @@ +/* + * 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.io.IOException; +import java.nio.ByteBuffer; + +/** + * An interface for encoders that do a pretty encoding of ZIP + * filenames. + * + * <p>There are mostly two implementations, one that uses java.nio + * {@link java.nio.charset.Charset Charset} and one implementation, + * which copes with simple 8 bit charsets, because java-1.4 did not + * support Cp437 in java.nio.</p> + * + * <p>The main reason for defining an own encoding layer comes from + * the problems with {@link java.lang.String#getBytes(String) + * String.getBytes}, which encodes unknown characters as ASCII + * quotation marks ('?'). Quotation marks are per definition an + * invalid filename on some operating systems like Windows, which + * leads to ignored ZIP entries.</p> + * + * <p>All implementations should implement this interface in a + * reentrant way.</p> + */ +public interface ZipEncoding { + /** + * Check, whether the given string may be losslessly encoded using this + * encoding. + * + * @param name A filename or ZIP comment. + * @return Whether the given name may be encoded with out any losses. + */ + boolean canEncode(String name); + + /** + * Encode a filename or a comment to a byte array suitable for + * storing it to a serialized zip entry. + * + * <p>Examples for CP 437 (in pseudo-notation, right hand side is + * C-style notation):</p> + * <pre> + * encode("\u20AC_for_Dollar.txt") = "%U20AC_for_Dollar.txt" + * encode("\u00D6lf\u00E4sser.txt") = "\231lf\204sser.txt" + * </pre> + * + * @param name A filename or ZIP comment. + * @return A byte buffer with a backing array containing the + * encoded name. Unmappable characters or malformed + * 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 + * 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 + */ + ByteBuffer encode(String name) throws IOException; + + /** + * @param data The byte values to decode. + * @return The decoded string. + * @throws IOException + */ + String decode(byte [] data) throws IOException; +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEncodingHelper.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEncodingHelper.java new file mode 100644 index 00000000..4d3dab95 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEncodingHelper.java @@ -0,0 +1,252 @@ +/* + * 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.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Static helper functions for robustly encoding filenames in zip files. + */ +public abstract class ZipEncodingHelper { + + /** + * A class, which holds the high characters of a simple encoding + * and lazily instantiates a Simple8BitZipEncoding instance in a + * thread-safe manner. + */ + private static class SimpleEncodingHolder { + + private final char [] highChars; + private Simple8BitZipEncoding encoding; + + /** + * Instantiate a simple encoding holder. + * + * @param highChars The characters for byte codes 128 to 255. + * + * @see Simple8BitZipEncoding#Simple8BitZipEncoding(char[]) + */ + SimpleEncodingHolder(final char [] highChars) { + this.highChars = highChars; + } + + /** + * @return The associated {@link Simple8BitZipEncoding}, which + * is instantiated if not done so far. + */ + public synchronized Simple8BitZipEncoding getEncoding() { + if (this.encoding == null) { + this.encoding = new Simple8BitZipEncoding(this.highChars); + } + return this.encoding; + } + } + + private static final Map<String, SimpleEncodingHolder> simpleEncodings; + + static { + final Map<String, SimpleEncodingHolder> se = + new HashMap<String, SimpleEncodingHolder>(); + + final char[] cp437_high_chars = + new char[] {0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, + 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, + 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, + 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, + 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, + 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, + 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, + 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, + 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, + 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, + 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, + 0x256c, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, + 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, + 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, + 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, + 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, + 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1, + 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, + 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, + 0x25a0, 0x00a0}; + + final SimpleEncodingHolder cp437 = new SimpleEncodingHolder(cp437_high_chars); + + se.put("CP437", cp437); + se.put("Cp437", cp437); + se.put("cp437", cp437); + se.put("IBM437", cp437); + se.put("ibm437", cp437); + + final char[] cp850_high_chars = + new char[] {0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, + 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, + 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, + 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, + 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, + 0x00d7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, + 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00ae, + 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, + 0x00c2, 0x00c0, 0x00a9, 0x2563, 0x2551, 0x2557, + 0x255d, 0x00a2, 0x00a5, 0x2510, 0x2514, 0x2534, + 0x252c, 0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, + 0x256c, 0x00a4, 0x00f0, 0x00d0, 0x00ca, 0x00cb, + 0x00c8, 0x0131, 0x00cd, 0x00ce, 0x00cf, 0x2518, + 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, + 0x00d3, 0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, + 0x00b5, 0x00fe, 0x00de, 0x00da, 0x00db, 0x00d9, + 0x00fd, 0x00dd, 0x00af, 0x00b4, 0x00ad, 0x00b1, + 0x2017, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, + 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, + 0x25a0, 0x00a0}; + + final SimpleEncodingHolder cp850 = new SimpleEncodingHolder(cp850_high_chars); + + se.put("CP850", cp850); + se.put("Cp850", cp850); + se.put("cp850", cp850); + se.put("IBM850", cp850); + se.put("ibm850", cp850); + simpleEncodings = Collections.unmodifiableMap(se); + } + + /** + * Grow a byte buffer, so it has a minimal capacity or at least + * the double capacity of the original buffer + * + * @param b The original buffer. + * @param newCapacity The minimal requested new capacity. + * @return A byte buffer <code>r</code> with + * <code>r.capacity() = max(b.capacity()*2,newCapacity)</code> and + * all the data contained in <code>b</code> copied to the beginning + * of <code>r</code>. + * + */ + static ByteBuffer growBuffer(final ByteBuffer b, final int newCapacity) { + b.limit(b.position()); + b.rewind(); + + final int c2 = b.capacity() * 2; + final ByteBuffer on = ByteBuffer.allocate(c2 < newCapacity ? newCapacity : c2); + + on.put(b); + return on; + } + + + /** + * The hexadecimal digits <code>0,...,9,A,...,F</code> encoded as + * ASCII bytes. + */ + private static final byte[] HEX_DIGITS = + new byte [] { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46 + }; + + /** + * Append <code>%Uxxxx</code> to the given byte buffer. + * The caller must assure, that <code>bb.remaining()>=6</code>. + * + * @param bb The byte buffer to write to. + * @param c The character to write. + */ + static void appendSurrogate(final ByteBuffer bb, final char c) { + + bb.put((byte) '%'); + bb.put((byte) 'U'); + + bb.put(HEX_DIGITS[(c >> 12)&0x0f]); + bb.put(HEX_DIGITS[(c >> 8)&0x0f]); + bb.put(HEX_DIGITS[(c >> 4)&0x0f]); + bb.put(HEX_DIGITS[c & 0x0f]); + } + + + /** + * name of the encoding UTF-8 + */ + static final String UTF8 = "UTF8"; + + /** + * variant name of the encoding UTF-8 used for comparisons. + */ + private static final String UTF_DASH_8 = "utf-8"; + + /** + * name of the encoding UTF-8 + */ + static final ZipEncoding UTF8_ZIP_ENCODING = new FallbackZipEncoding(UTF8); + + /** + * Instantiates a zip encoding. + * + * @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. + */ + public static ZipEncoding getZipEncoding(final String name) { + + // fallback encoding is good enough for utf-8. + if (isUTF8(name)) { + return UTF8_ZIP_ENCODING; + } + + if (name == null) { + return new FallbackZipEncoding(); + } + + final SimpleEncodingHolder h = simpleEncodings.get(name); + + if (h!=null) { + return h.getEncoding(); + } + + try { + + final Charset cs = Charset.forName(name); + return new NioZipEncoding(cs); + + } catch (final UnsupportedCharsetException e) { + return new FallbackZipEncoding(name); + } + } + + /** + * Whether a given encoding - or the platform's default encoding + * if the parameter is null - is UTF-8. + */ + static boolean isUTF8(String encoding) { + if (encoding == null) { + // check platform's default encoding + encoding = System.getProperty("file.encoding"); + } + return UTF8.equalsIgnoreCase(encoding) + || UTF_DASH_8.equalsIgnoreCase(encoding); + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEntry.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEntry.java new file mode 100644 index 00000000..30a8155b --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipEntry.java @@ -0,0 +1,786 @@ +/* + * 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.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.zip.ZipException; + +/** + * Extension that adds better handling of extra fields and provides + * access to the internal and external file attributes. + * + * <p>The extra data is expected to follow the recommendation of + * {@link <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 + * a two byte sequence holding the length of the remainder of + * data.</li> + * </ul> + * + * <p>Any extra data that cannot be parsed by the rules above will be + * consumed as "unparseable" extra data and treated differently by the + * methods of this class. Older versions would have thrown an + * exception if any attempt was made to read or write extra data not + * conforming to the recommendation.</p> + * + */ +public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { + + public static final int PLATFORM_UNIX = 3; + public static final int PLATFORM_FAT = 0; + public static final int CRC_UNKNOWN = -1; + private static final int SHORT_MASK = 0xFFFF; + private static final int SHORT_SHIFT = 16; + private static final byte[] EMPTY = new byte[0]; + + /** + * 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 ZipExtraField[] extraFields; + private UnparseableExtraFieldData unparseableExtra = null; + private String name = null; + private byte[] rawName = null; + private GeneralPurposeBit gpb = new GeneralPurposeBit(); + private static final ZipExtraField[] noExtraFields = new ZipExtraField[0]; + + /** + * 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(final 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(final java.util.zip.ZipEntry entry) throws ZipException { + super(entry); + setName(entry.getName()); + final byte[] extra = entry.getExtra(); + if (extra != null) { + setExtraFields(ExtraFieldUtils.parse(extra, true, + ExtraFieldUtils + .UnparseableExtraField.READ)); + } else { + // 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 + */ + public ZipEntry(final ZipEntry entry) throws ZipException { + this((java.util.zip.ZipEntry) entry); + setInternalAttributes(entry.getInternalAttributes()); + setExternalAttributes(entry.getExternalAttributes()); + setExtraFields(getAllExtraFieldsNoCopy()); + setPlatform(entry.getPlatform()); + GeneralPurposeBit other = entry.getGeneralPurposeBit(); + setGeneralPurposeBit(other == null ? null : + (GeneralPurposeBit) other.clone()); + } + + /** + * @since 1.9 + */ + protected ZipEntry() { + 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(final File inputFile, final 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? + } + + /** + * Overwrite clone. + * @return a cloned copy of this ZipEntry + * @since 1.1 + */ + @Override + public Object clone() { + final ZipEntry e = (ZipEntry) super.clone(); + + e.setInternalAttributes(getInternalAttributes()); + e.setExternalAttributes(getExternalAttributes()); + e.setExtraFields(getAllExtraFieldsNoCopy()); + 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(final int method) { + if (method < 0) { + throw new IllegalArgumentException( + "ZIP compression method can not be negative: " + method); + } + this.method = method; + } + + /** + * Retrieves the internal file attributes. + * + * @return the internal file attributes + * @since 1.1 + */ + public int getInternalAttributes() { + return internalAttributes; + } + + /** + * Sets the internal file attributes. + * @param value an <code>int</code> value + * @since 1.1 + */ + public void setInternalAttributes(final int value) { + internalAttributes = value; + } + + /** + * Retrieves the external file attributes. + * @return the external file attributes + * @since 1.1 + */ + public long getExternalAttributes() { + return externalAttributes; + } + + /** + * Sets the external file attributes. + * @param value an <code>long</code> value + * @since 1.1 + */ + public void setExternalAttributes(final long value) { + externalAttributes = value; + } + + /** + * Sets Unix permissions in a way that is understood by Info-Zip's + * unzip command. + * @param mode an <code>int</code> value + * @since Ant 1.5.2 + */ + public void setUnixMode(final int mode) { + // CheckStyle:MagicNumberCheck OFF - no point + setExternalAttributes((mode << SHORT_SHIFT) + // MS-DOS read-only attribute + | ((mode & 0200) == 0 ? 1 : 0) + // MS-DOS directory flag + | (isDirectory() ? 0x10 : 0)); + // CheckStyle:MagicNumberCheck ON + platform = PLATFORM_UNIX; + } + + /** + * Unix permission. + * @return the unix permissions + * @since Ant 1.6 + */ + public int getUnixMode() { + return platform != PLATFORM_UNIX ? 0 : + (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK); + } + + /** + * Platform specification to put into the "version made + * by" part of the central file header. + * + * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode} + * has been called, in which case PLATFORM_UNIX will be returned. + * + * @since Ant 1.5.2 + */ + public int getPlatform() { + return platform; + } + + /** + * Set the platform (UNIX or FAT). + * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX + * @since 1.9 + */ + protected void setPlatform(final int platform) { + this.platform = platform; + } + + /** + * Replaces all currently attached extra fields with the new array. + * @param fields an array of extra fields + * @since 1.1 + */ + public void setExtraFields(final ZipExtraField[] fields) { + List<ZipExtraField> newFields = new ArrayList<ZipExtraField>(); + for (ZipExtraField field : fields) { + if (field instanceof UnparseableExtraFieldData) { + unparseableExtra = (UnparseableExtraFieldData) field; + } else { + newFields.add( field); + } + } + extraFields = newFields.toArray(new ZipExtraField[newFields.size()]); + setExtra(); + } + + /** + * Retrieves all extra fields that have been parsed successfully. + * @return an array of the extra fields + */ + public ZipExtraField[] getExtraFields() { + return getParseableExtraFields(); + } + + /** + * Retrieves extra fields. + * @param includeUnparseable whether to also return unparseable + * extra fields as {@link UnparseableExtraFieldData} if such data + * exists. + * @return an array of the extra fields + * @since 1.1 + */ + public ZipExtraField[] getExtraFields(final boolean includeUnparseable) { + return includeUnparseable ? + getAllExtraFields() : + getParseableExtraFields(); + } + + private ZipExtraField[] getParseableExtraFieldsNoCopy() { + if (extraFields == null) { + return noExtraFields; + } + return extraFields; + } + + private ZipExtraField[] getParseableExtraFields() { + final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy(); + return (parseableExtraFields == extraFields) + ? copyOf(parseableExtraFields) : parseableExtraFields; + } + + private ZipExtraField[] copyOf(ZipExtraField[] src){ + return copyOf(src, src.length); + } + + private ZipExtraField[] copyOf(ZipExtraField[] src, int length){ + ZipExtraField[] cpy = new ZipExtraField[length]; + System.arraycopy(src, 0, cpy, 0, Math.min(src.length, length)); + return cpy; + } + + private ZipExtraField[] getMergedFields() { + final ZipExtraField[] zipExtraFields = + copyOf(extraFields, extraFields.length + 1); + zipExtraFields[extraFields.length] = unparseableExtra; + return zipExtraFields; + } + + private ZipExtraField[] getUnparseableOnly() { + return unparseableExtra == null + ? noExtraFields : new ZipExtraField[] { unparseableExtra }; + } + + private ZipExtraField[] getAllExtraFields() { + final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy(); + return (allExtraFieldsNoCopy == extraFields) + ? copyOf(allExtraFieldsNoCopy) : allExtraFieldsNoCopy; + } + + /** + * Get all extra fields, including unparseable ones. + * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method + */ + private ZipExtraField[] getAllExtraFieldsNoCopy() { + if (extraFields == null) { + return getUnparseableOnly(); + } + return unparseableExtra != null ? getMergedFields() : extraFields; + } + + /** + * Adds an extra field - replacing an already present extra field + * of the same type. + * + * <p>If no extra field of the same type exists, the field will be + * added as last field.</p> + * @param ze an extra field + * @since 1.1 + */ + public void addExtraField(final ZipExtraField ze) { + if (ze instanceof UnparseableExtraFieldData) { + unparseableExtra = (UnparseableExtraFieldData) ze; + } else { + if (extraFields == null) { + extraFields = new ZipExtraField[] {ze}; + } else { + if (getExtraField(ze.getHeaderId()) != null){ + removeExtraField(ze.getHeaderId()); + } + final ZipExtraField[] zipExtraFields = + copyOf(extraFields, extraFields.length + 1); + zipExtraFields[extraFields.length] = ze; + extraFields = zipExtraFields; + } + } + setExtra(); + } + + /** + * Adds an extra field - replacing an already present extra field + * of the same type. + * + * <p>The new extra field will be the first one.</p> + * @param ze an extra field + * @since 1.1 + */ + public void addAsFirstExtraField(final ZipExtraField ze) { + if (ze instanceof UnparseableExtraFieldData) { + unparseableExtra = (UnparseableExtraFieldData) ze; + } else { + if (getExtraField(ze.getHeaderId()) != null){ + removeExtraField(ze.getHeaderId()); + } + ZipExtraField[] copy = extraFields; + int newLen = extraFields != null ? extraFields.length + 1: 1; + extraFields = new ZipExtraField[newLen]; + extraFields[0] = ze; + if (copy != null){ + System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1); + } + } + setExtra(); + } + + /** + * Remove an extra field. + * @param type the type of extra field to remove + * @since 1.1 + */ + public void removeExtraField(final ZipShort type) { + if (extraFields == null) { + throw new java.util.NoSuchElementException(); + } + List<ZipExtraField> newResult = new ArrayList<ZipExtraField>(); + for (ZipExtraField extraField : extraFields) { + if (!type.equals(extraField.getHeaderId())){ + newResult.add(extraField); + } + } + if (extraFields.length == newResult.size()) { + throw new java.util.NoSuchElementException(); + } + extraFields = newResult.toArray(new ZipExtraField[newResult.size()]); + setExtra(); + } + + /** + * Removes unparseable extra field data. + */ + public void removeUnparseableExtraFieldData() { + if (unparseableExtra == null) { + throw new java.util.NoSuchElementException(); + } + unparseableExtra = null; + setExtra(); + } + + /** + * Looks up an extra field by its header id. + * + * @return null if no such field exists. + */ + public ZipExtraField getExtraField(final ZipShort type) { + if (extraFields != null) { + for (ZipExtraField extraField : extraFields) { + if (type.equals(extraField.getHeaderId())) { + return extraField; + } + } + } + return null; + } + + /** + * Looks up extra field data that couldn't be parsed correctly. + * + * @return null if no such field exists. + */ + public UnparseableExtraFieldData getUnparseableExtraFieldData() { + return unparseableExtra; + } + + /** + * Parses the given bytes as extra field data and consumes any + * unparseable data as an {@link UnparseableExtraFieldData} + * instance. + * @param extra an array of bytes to be parsed into extra fields + * @throws RuntimeException if the bytes cannot be parsed + * @since 1.1 + * @throws RuntimeException on error + */ + @Override + public void setExtra(final byte[] extra) throws RuntimeException { + try { + final ZipExtraField[] local = + ExtraFieldUtils.parse(extra, true, + ExtraFieldUtils.UnparseableExtraField.READ); + mergeExtraFields(local, true); + } catch (final 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); + } + } + + /** + * Unfortunately {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} seems to access the extra data + * directly, so overriding getExtra doesn't help - we need to + * modify super's data directly. + * + * @since 1.1 + */ + protected void setExtra() { + super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields(true))); + } + + /** + * Sets the central directory part of extra fields. + */ + public void setCentralDirectoryExtra(final byte[] b) { + try { + final ZipExtraField[] central = + ExtraFieldUtils.parse(b, false, + ExtraFieldUtils.UnparseableExtraField.READ); + mergeExtraFields(central, false); + } catch (final ZipException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + /** + * Retrieves the extra data for the local file data. + * @return the extra data for local file + * @since 1.1 + */ + public byte[] getLocalFileDataExtra() { + final byte[] extra = getExtra(); + return extra != null ? extra : EMPTY; + } + + /** + * Retrieves the extra data for the central directory. + * @return the central directory extra data + * @since 1.1 + */ + public byte[] getCentralDirectoryExtra() { + return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields(true)); + } + + /** + * Make this class work in JDK 1.1 like a 1.2 class. + * + * <p>This either stores the size for later usage or invokes + * setCompressedSize via reflection.</p> + * @param size the size to use + * @deprecated since 1.7. + * Use setCompressedSize directly. + * @since 1.2 + */ + @Deprecated + public void setComprSize(final long size) { + setCompressedSize(size); + } + + /** + * Get the name of the entry. + * @return the entry name + * @since 1.9 + */ + @Override + public String getName() { + return name == null ? super.getName() : name; + } + + /** + * Is this entry a directory? + * @return true if the entry is a directory + * @since 1.10 + */ + @Override + public boolean isDirectory() { + return getName().endsWith("/"); + } + + /** + * Set the name of the entry. + * @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(final 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(final String name, final 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) { + final 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 + // the empty string in the current implemention (there's no setter) + // so it is basically draining the performance of a hashmap lookup + return getName().hashCode(); + } + + /** + * The "general purpose bit" field. + */ + public GeneralPurposeBit getGeneralPurposeBit() { + return gpb; + } + + /** + * The "general purpose bit" field. + */ + public void setGeneralPurposeBit(final GeneralPurposeBit b) { + gpb = b; + } + + /** + * If there are no extra fields, use the given fields as new extra + * data - otherwise merge the fields assuming the existing fields + * and the new fields stem from different locations inside the + * archive. + * @param f the extra fields to merge + * @param local whether the new fields originate from local data + */ + private void mergeExtraFields(final ZipExtraField[] f, final boolean local) + throws ZipException { + if (extraFields == null) { + setExtraFields(f); + } else { + for (final ZipExtraField element : f) { + ZipExtraField existing; + if (element instanceof UnparseableExtraFieldData) { + existing = unparseableExtra; + } else { + existing = getExtraField(element.getHeaderId()); + } + if (existing == null) { + addExtraField(element); + } else { + if (local + || !(existing + instanceof CentralDirectoryParsingZipExtraField)) { + final byte[] b = element.getLocalFileDataData(); + existing.parseFromLocalFileData(b, 0, b.length); + } else { + final byte[] b = element.getCentralDirectoryData(); + ((CentralDirectoryParsingZipExtraField) existing) + .parseFromCentralDirectoryData(b, 0, b.length); + } + } + } + setExtra(); + } + } + + /** {@inheritDoc} */ + public Date getLastModifiedDate() { + return new Date(getTime()); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final ZipEntry other = (ZipEntry) obj; + final String myName = getName(); + final 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); + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipExtraField.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipExtraField.java new file mode 100644 index 00000000..649fca00 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipExtraField.java @@ -0,0 +1,85 @@ +/* + * 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; + +/** + * General format of extra field data. + * + * <p>Extra fields usually appear twice per file, once in the local + * file data and once in the central directory. Usually they are the + * same, but they don't have to be. {@link + * java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} will + * only use the local file data in both places.</p> + * + */ +public interface ZipExtraField { + + /** + * The Header-ID. + * @return the header id + * @since 1.1 + */ + ZipShort getHeaderId(); + + /** + * Length of the extra field in the local file data - without + * Header-ID or length specifier. + * @return the length of the field in the local file data + * @since 1.1 + */ + ZipShort getLocalFileDataLength(); + + /** + * Length of the extra field in the central directory - without + * Header-ID or length specifier. + * @return the length of the field in the central directory + * @since 1.1 + */ + ZipShort getCentralDirectoryLength(); + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * @return the data + * @since 1.1 + */ + byte[] getLocalFileDataData(); + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * @return the data + * @since 1.1 + */ + byte[] getCentralDirectoryData(); + + /** + * Populate data from this array as if it was in local file data. + * @param data an array of bytes + * @param offset the start offset + * @param length the number of bytes in the array from offset + * + * @since 1.1 + * @throws ZipException on error + */ + void parseFromLocalFileData(byte[] data, int offset, int length) + throws ZipException; +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipFile.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipFile.java new file mode 100644 index 00000000..7a2c9926 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipFile.java @@ -0,0 +1,1048 @@ +/* + * 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 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; + +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; +import java.util.zip.ZipException; + +/** + * Replacement for <code>java.util.ZipFile</code>. + * + * <p>This class adds support for file name encodings other than UTF-8 + * (which is required to work on ZIP files created by native zip tools + * and is able to skip a preamble like the one found in self + * extracting archives. Furthermore it returns instances of + * <code>org.apache.tools.zip.ZipEntry</code> instead of + * <code>java.util.zip.ZipEntry</code>.</p> + * + * <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. 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: + * + * <ul> + * <li>There is no getName method.</li> + * <li>entries has been renamed to getEntries.</li> + * <li>getEntries and getEntry return + * <code>org.apache.tools.zip.ZipEntry</code> instances.</li> + * <li>close is allowed to throw IOException.</li> + * </ul> + * + */ +public class ZipFile { + private static final int HASH_SIZE = 509; + 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; + + /** + * List of entries in the order they appear inside the central + * directory. + */ + private final List<ZipEntry> entries = new LinkedList<ZipEntry>(); + + /** + * Maps String to list of ZipEntrys, name -> actual entries. + */ + private final Map<String, LinkedList<ZipEntry>> nameMap = + new HashMap<String, LinkedList<ZipEntry>>(HASH_SIZE); + + private static final class OffsetEntry { + private long headerOffset = -1; + private long dataOffset = -1; + } + + /** + * The encoding to use for filenames and the file comment. + * + * <p>For a list of possible values see <a + * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. + * Defaults to the platform's default character encoding.</p> + */ + 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 final RandomAccessFile archive; + + /** + * Whether to look for and use Unicode extra fields. + */ + private final boolean useUnicodeExtraFields; + + /** + * Whether the file is closed. + */ + private volatile boolean closed; + + // cached buffers + private final byte[] DWORD_BUF = new byte[DWORD]; + private final byte[] WORD_BUF = new byte[WORD]; + private final byte[] CFH_BUF = new byte[CFH_LEN]; + private final byte[] SHORT_BUF = new byte[SHORT]; + + /** + * Opens the given file for reading, assuming the platform's + * native encoding for file names. + * + * @param f the archive. + * + * @throws IOException if an error occurs while reading the file. + */ + public ZipFile(final File f) throws IOException { + this(f, null); + } + + /** + * Opens the given file for reading, assuming the platform's + * native encoding for file names. + * + * @param name name of the archive. + * + * @throws IOException if an error occurs while reading the file. + */ + public ZipFile(final String name) throws IOException { + this(new File(name), null); + } + + /** + * Opens the given file for reading, assuming the specified + * encoding for file names, scanning unicode extra fields. + * + * @param name name of the archive. + * @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. + */ + public ZipFile(final String name, final String encoding) throws IOException { + this(new File(name), encoding, true); + } + + /** + * Opens the given file for reading, assuming the specified + * encoding for file names and scanning for unicode extra fields. + * + * @param f the archive. + * @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. + */ + public ZipFile(final File f, final String encoding) throws IOException { + this(f, encoding, true); + } + + /** + * Opens the given file for reading, assuming the specified + * encoding for file names. + * + * @param f the archive. + * @param encoding the encoding to use for file names, use null + * for the platform's default encoding + * @param useUnicodeExtraFields whether to use InfoZIP Unicode + * Extra Fields (if present) to set the file names. + * + * @throws IOException if an error occurs while reading the file. + */ + public ZipFile(final File f, final String encoding, final 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 { + final Map<ZipEntry, NameAndComment> entriesWithoutUTF8Flag = + populateFromCentralDirectory(); + resolveLocalFileHeaderData(entriesWithoutUTF8Flag); + success = true; + } finally { + closed = !success; + if (!success) { + try { + archive.close(); + } catch (final IOException e2) { + // swallow, throw the original exception instead + } + } + } + } + + /** + * The encoding to use for filenames and the file comment. + * + * @return null if using the platform's default character encoding. + */ + public String getEncoding() { + return encoding; + } + + /** + * Closes the archive. + * @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(); + } + + /** + * close a zipfile quietly; throw no io fault, do nothing + * on a null parameter + * @param zipfile file to close, can be null + */ + public static void closeQuietly(final ZipFile zipfile) { + if (zipfile != null) { + try { + zipfile.close(); + } catch (final IOException e) { + //ignore + } + } + } + + /** + * 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<ZipEntry> getEntries() { + return Collections.enumeration(entries); + } + + /** + * 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() { + final ZipEntry[] allEntries = entries.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. + * + * <p>If multiple entries with the same name exist the first entry + * in the archive's central directory by that name is + * returned.</p> + * + * @param name name of the entry. + * @return the ZipEntry corresponding to the given name - or + * {@code null} if not present. + */ + public ZipEntry getEntry(final String name) { + final LinkedList<ZipEntry> entriesOfThatName = nameMap.get(name); + return entriesOfThatName != null ? entriesOfThatName.getFirst() : null; + } + + /** + * Returns all named entries in the same order they appear within + * the archive's central directory. + * + * @param name name of the entry. + * @return the Iterable<ZipEntry> corresponding to the + * given name + * @since 1.9.2 + */ + public Iterable<ZipEntry> getEntries(final String name) { + final List<ZipEntry> entriesOfThatName = nameMap.get(name); + return entriesOfThatName != null ? entriesOfThatName + : Collections.<ZipEntry>emptyList(); + } + + /** + * Returns all named entries in the same order their contents + * appear within the archive. + * + * @param name name of the entry. + * @return the Iterable<ZipEntry> corresponding to the + * given name + * @since 1.9.2 + */ + public Iterable<ZipEntry> getEntriesInPhysicalOrder(final String name) { + ZipEntry[] entriesOfThatName = new ZipEntry[0]; + if (nameMap.containsKey(name)) { + entriesOfThatName = nameMap.get(name).toArray(entriesOfThatName); + Arrays.sort(entriesOfThatName, OFFSET_COMPARATOR); + } + return Arrays.asList(entriesOfThatName); + } + + /** + * 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(final 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 uses an unsupported feature + */ + public InputStream getInputStream(final ZipEntry ze) + throws IOException, ZipException { + if (!(ze instanceof Entry)) { + return null; + } + // cast valididty is checked just above + final OffsetEntry offsetEntry = ((Entry) ze).getOffsetEntry(); + ZipUtil.checkRequestedFeatures(ze); + final long start = offsetEntry.dataOffset; + final BoundedInputStream bis = + new BoundedInputStream(start, ze.getCompressedSize()); + switch (ze.getMethod()) { + case ZipEntry.STORED: + return bis; + case ZipEntry.DEFLATED: + bis.addDummy(); + final Inflater inflater = new Inflater(true); + return new InflaterInputStream(bis, inflater) { + @Override + public void close() throws IOException { + super.close(); + inflater.end(); + } + }; + default: + throw new ZipException("Found unsupported compression method " + + ze.getMethod()); + } + } + + /** + * 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 + /* general purpose bit flag */ + SHORT + /* compression method */ + SHORT + /* last mod file time */ + SHORT + /* last mod file date */ + SHORT + /* crc-32 */ + WORD + /* compressed size */ + WORD + /* uncompressed size */ + WORD + /* filename length */ + SHORT + /* extra field length */ + SHORT + /* file comment length */ + SHORT + /* disk number start */ + SHORT + /* internal file attributes */ + SHORT + /* 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. + * + * <p>The ZipEntrys will know all data that can be obtained from + * the central directory alone, but not the data that requires the + * local file header or additional data to be read.</p> + * + * @return a map of zipentries that didn't have the language + * encoding flag set when read. + */ + private Map<ZipEntry, NameAndComment> populateFromCentralDirectory() + throws IOException { + final HashMap<ZipEntry, NameAndComment> noUTF8Flag = + new HashMap<ZipEntry, NameAndComment>(); + + positionAtCentralDirectory(); + + archive.readFully(WORD_BUF); + long sig = ZipLong.getValue(WORD_BUF); + + if (sig != CFH_SIG && startsWithLocalFileHeader()) { + throw new IOException("central directory is empty, can't expand" + + " corrupt archive."); + } + + while (sig == CFH_SIG) { + readCentralDirectoryEntry(noUTF8Flag); + archive.readFully(WORD_BUF); + sig = ZipLong.getValue(WORD_BUF); + } + 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(final Map<ZipEntry, NameAndComment> noUTF8Flag) + throws IOException { + archive.readFully(CFH_BUF); + int off = 0; + final OffsetEntry offset = new OffsetEntry(); + final Entry ze = new Entry(offset); + + final int versionMadeBy = ZipShort.getValue(CFH_BUF, off); + off += SHORT; + ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK); + + off += SHORT; // skip version info + + final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(CFH_BUF, off); + final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames(); + final ZipEncoding entryEncoding = + hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; + ze.setGeneralPurposeBit(gpFlag); + + off += SHORT; + + ze.setMethod(ZipShort.getValue(CFH_BUF, off)); + off += SHORT; + + final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(CFH_BUF, off)); + ze.setTime(time); + off += WORD; + + ze.setCrc(ZipLong.getValue(CFH_BUF, off)); + off += WORD; + + ze.setCompressedSize(ZipLong.getValue(CFH_BUF, off)); + off += WORD; + + ze.setSize(ZipLong.getValue(CFH_BUF, off)); + off += WORD; + + final int fileNameLen = ZipShort.getValue(CFH_BUF, off); + off += SHORT; + + final int extraLen = ZipShort.getValue(CFH_BUF, off); + off += SHORT; + + final int commentLen = ZipShort.getValue(CFH_BUF, off); + off += SHORT; + + final int diskStart = ZipShort.getValue(CFH_BUF, off); + off += SHORT; + + ze.setInternalAttributes(ZipShort.getValue(CFH_BUF, off)); + off += SHORT; + + ze.setExternalAttributes(ZipLong.getValue(CFH_BUF, off)); + off += WORD; + + final byte[] fileName = new byte[fileNameLen]; + archive.readFully(fileName); + ze.setName(entryEncoding.decode(fileName), fileName); + + // LFH offset, + offset.headerOffset = ZipLong.getValue(CFH_BUF, off); + // data offset will be filled later + entries.add(ze); + + final byte[] cdExtraData = new byte[extraLen]; + archive.readFully(cdExtraData); + ze.setCentralDirectoryExtra(cdExtraData); + + setSizesAndOffsetFromZip64Extra(ze, offset, diskStart); + + final 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(final ZipEntry ze, + final OffsetEntry offset, + final int diskStart) + throws IOException { + final Zip64ExtendedInformationExtraField z64 = + (Zip64ExtendedInformationExtraField) + ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID); + if (z64 != null) { + final boolean hasUncompressedSize = ze.getSize() == ZIP64_MAGIC; + final boolean hasCompressedSize = ze.getCompressedSize() == ZIP64_MAGIC; + final 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 (hasRelativeHeaderOffset) { + offset.headerOffset = + z64.getRelativeHeaderOffset().getLongValue(); + } + } + } + + /** + * 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 + /* number of the disk with the */ + /* start of the central directory */ + SHORT + /* total number of entries in */ + /* the central dir on this disk */ + SHORT + /* total number of entries in */ + /* the central dir */ + SHORT + /* size of the central directory */ + WORD + /* offset of start of central */ + /* directory with respect to */ + /* 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 */ + 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 + /* number of the disk with the */ + /* start of the central directory */ + SHORT + /* total number of entries in */ + /* the central dir on this disk */ + SHORT + /* total number of entries in */ + /* the central dir */ + SHORT + /* size of the central directory */ + WORD; + + /** + * 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 { + positionAtEndOfCentralDirectoryRecord(); + boolean found = false; + final boolean searchedForZip64EOCD = + archive.getFilePointer() > ZIP64_EOCDL_LENGTH; + if (searchedForZip64EOCD) { + archive.seek(archive.getFilePointer() - ZIP64_EOCDL_LENGTH); + archive.readFully(WORD_BUF); + found = Arrays.equals(ZipOutputStream.ZIP64_EOCD_LOC_SIG, WORD_BUF); + } + if (!found) { + // not a ZIP64 archive + if (searchedForZip64EOCD) { + skipBytes(ZIP64_EOCDL_LENGTH - WORD); + } + 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 + - WORD /* signature has already been read */); + archive.readFully(DWORD_BUF); + archive.seek(ZipEightByteInteger.getLongValue(DWORD_BUF)); + archive.readFully(WORD_BUF); + if (!Arrays.equals(WORD_BUF, ZipOutputStream.ZIP64_EOCD_SIG)) { + 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 */); + archive.readFully(DWORD_BUF); + archive.seek(ZipEightByteInteger.getLongValue(DWORD_BUF)); + } + + /** + * 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 { + skipBytes(CFD_LOCATOR_OFFSET); + archive.readFully(WORD_BUF); + archive.seek(ZipLong.getValue(WORD_BUF)); + } + + /** + * Searches for the and positions the stream at the start of the + * "End of central dir record". + */ + private void positionAtEndOfCentralDirectoryRecord() + throws IOException { + final boolean found = tryToLocateSignature(MIN_EOCD_SIZE, MAX_EOCD_SIZE, + ZipOutputStream.EOCD_SIG); + if (!found) { + throw new ZipException("archive is not a ZIP archive"); + } + } + + /** + * 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(final long minDistanceFromEnd, + final long maxDistanceFromEnd, + final byte[] sig) throws IOException { + boolean found = false; + long off = archive.length() - minDistanceFromEnd; + final long stopSearching = + Math.max(0L, archive.length() - maxDistanceFromEnd); + if (off >= 0) { + for (; off >= stopSearching; off--) { + archive.seek(off); + int curr = archive.read(); + if (curr == -1) { + break; + } + if (curr == sig[POS_0]) { + curr = archive.read(); + if (curr == sig[POS_1]) { + curr = archive.read(); + if (curr == sig[POS_2]) { + curr = archive.read(); + if (curr == sig[POS_3]) { + found = true; + break; + } + } + } + } + } + } + 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) { + final int skippedNow = archive.skipBytes(count - totalSkipped); + if (skippedNow <= 0) { + throw new EOFException(); + } + totalSkipped += skippedNow; + } + } + + /** + * Number of bytes in local file header up to the "length of + * filename" entry. + */ + private static final long LFH_OFFSET_FOR_FILENAME_LENGTH = + /* local file header signature */ WORD + /* version needed to extract */ + SHORT + /* general purpose bit flag */ + SHORT + /* compression method */ + SHORT + /* last mod file time */ + SHORT + /* last mod file date */ + SHORT + /* crc-32 */ + WORD + /* compressed size */ + WORD + /* uncompressed size */ + WORD; + + /** + * Walks through all recorded entries and adds the data available + * from the local file header. + * + * <p>Also records the offsets for the data to read from the + * entries.</p> + */ + private void resolveLocalFileHeaderData(final Map<ZipEntry, NameAndComment> + entriesWithoutUTF8Flag) + throws IOException { + for (final Iterator<ZipEntry> it = entries.iterator(); it.hasNext();) { + // entries is filled in populateFromCentralDirectory and + // never modified + final Entry ze = (Entry) it.next(); + final OffsetEntry offsetEntry = ze.getOffsetEntry(); + final long offset = offsetEntry.headerOffset; + archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH); + archive.readFully(SHORT_BUF); + final int fileNameLen = ZipShort.getValue(SHORT_BUF); + archive.readFully(SHORT_BUF); + final int extraFieldLen = ZipShort.getValue(SHORT_BUF); + int lenToSkip = fileNameLen; + while (lenToSkip > 0) { + final int skipped = archive.skipBytes(lenToSkip); + if (skipped <= 0) { + throw new IOException("failed to skip file name in" + + " local file header"); + } + lenToSkip -= skipped; + } + final byte[] localExtraData = new byte[extraFieldLen]; + archive.readFully(localExtraData); + ze.setExtra(localExtraData); + offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH + + SHORT + SHORT + fileNameLen + extraFieldLen; + + if (entriesWithoutUTF8Flag.containsKey(ze)) { + final NameAndComment nc = entriesWithoutUTF8Flag.get(ze); + ZipUtil.setNameAndCommentFromExtraFields(ze, nc.name, + nc.comment); + } + + final String name = ze.getName(); + LinkedList<ZipEntry> entriesOfThatName = nameMap.get(name); + if (entriesOfThatName == null) { + entriesOfThatName = new LinkedList<ZipEntry>(); + nameMap.put(name, entriesOfThatName); + } + entriesOfThatName.addLast(ze); + } + } + + /** + * Checks whether the archive starts with a LFH. If it doesn't, + * it may be an empty archive. + */ + private boolean startsWithLocalFileHeader() throws IOException { + archive.seek(0); + archive.readFully(WORD_BUF); + return Arrays.equals(WORD_BUF, ZipOutputStream.LFH_SIG); + } + + /** + * InputStream that delegates requests to the underlying + * RandomAccessFile, making sure that only bytes from a certain + * range can be read. + */ + private class BoundedInputStream extends InputStream { + private long remaining; + private long loc; + private boolean addDummyByte = false; + + BoundedInputStream(final long start, final long remaining) { + this.remaining = remaining; + loc = start; + } + + @Override + public int read() throws IOException { + if (remaining-- <= 0) { + if (addDummyByte) { + addDummyByte = false; + return 0; + } + return -1; + } + synchronized (archive) { + archive.seek(loc++); + return archive.read(); + } + } + + @Override + public int read(final byte[] b, final int off, int len) throws IOException { + if (remaining <= 0) { + if (addDummyByte) { + addDummyByte = false; + b[off] = 0; + return 1; + } + return -1; + } + + if (len <= 0) { + return 0; + } + + if (len > remaining) { + len = (int) remaining; + } + int ret = -1; + synchronized (archive) { + archive.seek(loc); + ret = archive.read(b, off, len); + } + if (ret > 0) { + loc += ret; + remaining -= ret; + } + return ret; + } + + /** + * Inflater needs an extra dummy byte for nowrap - see + * Inflater's javadocs. + */ + void addDummy() { + addDummyByte = true; + } + } + + private static final class NameAndComment { + private final byte[] name; + private final byte[] comment; + private NameAndComment(final byte[] name, final byte[] comment) { + this.name = name; + 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(final ZipEntry e1, final ZipEntry e2) { + if (e1 == e2) { + return 0; + } + + final Entry ent1 = e1 instanceof Entry ? (Entry) e1 : null; + final Entry ent2 = e2 instanceof Entry ? (Entry) e2 : null; + if (ent1 == null) { + return 1; + } + if (ent2 == null) { + return -1; + } + final long val = (ent1.getOffsetEntry().headerOffset + - ent2.getOffsetEntry().headerOffset); + return val == 0 ? 0 : val < 0 ? -1 : +1; + } + }; + + /** + * Extends ZipEntry to store the offset within the archive. + */ + private static class Entry extends ZipEntry { + + private final OffsetEntry offsetEntry; + + Entry(final OffsetEntry offset) { + this.offsetEntry = offset; + } + + OffsetEntry getOffsetEntry() { + return offsetEntry; + } + + @Override + public int hashCode() { + return 3 * super.hashCode() + + (int) (offsetEntry.headerOffset % Integer.MAX_VALUE); + } + + @Override + public boolean equals(final Object other) { + if (super.equals(other)) { + // super.equals would return false if other were not an Entry + final Entry otherEntry = (Entry) other; + return offsetEntry.headerOffset + == otherEntry.offsetEntry.headerOffset + && offsetEntry.dataOffset + == otherEntry.offsetEntry.dataOffset; + } + return false; + } + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipLong.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipLong.java new file mode 100644 index 00000000..72af84db --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipLong.java @@ -0,0 +1,201 @@ +/* + * 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 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. + * + */ +public final class ZipLong implements Cloneable { + + //private static final int BYTE_BIT_SIZE = 8; + + 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 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 + */ + public static final ZipLong DD_SIG = new ZipLong(0X08074B50L); + + /** + * Value stored in size and similar fields if ZIP64 extensions are + * used. + */ + static final ZipLong ZIP64_MAGIC = new ZipLong(ZipConstants.ZIP64_MAGIC); + + /** + * Create instance from a number. + * @param value the long to store as a ZipLong + * @since 1.1 + */ + public ZipLong(long value) { + this.value = value; + } + + /** + * Create instance from bytes. + * @param bytes the bytes to store as a ZipLong + * @since 1.1 + */ + public ZipLong (byte[] bytes) { + this(bytes, 0); + } + + /** + * Create instance from the four bytes starting at offset. + * @param bytes the bytes to store as a ZipLong + * @param offset the offset to start + * @since 1.1 + */ + public ZipLong (byte[] bytes, int offset) { + value = ZipLong.getValue(bytes, offset); + } + + /** + * Get value as four bytes in big endian byte order. + * @since 1.1 + * @return value as four bytes in big endian order + */ + public byte[] getBytes() { + return ZipLong.getBytes(value); + } + + /** + * Get value as Java long. + * @since 1.1 + * @return value as a long + */ + public long getValue() { + return value; + } + + /** + * Get value as four bytes in big endian byte order. + * @param value the value to convert + * @return value as four bytes in big endian byte order + */ + public static byte[] getBytes(long value) { + byte[] result = new byte[WORD]; + putLong(value, result, 0); + return result; + } + + /** + * put the value as four bytes in big endian byte order. + * @param value the Java long to convert to bytes + * @param buf the output buffer + * @param offset + * The offset within the output buffer of the first byte to be written. + * must be non-negative and no larger than <tt>buf.length-4</tt> + */ + public static void putLong(long value, byte[] buf, int offset) { + buf[offset++] = (byte) ((value & BYTE_MASK)); + buf[offset++] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); + buf[offset++] = (byte) ((value & BYTE_2_MASK) >> BYTE_2_SHIFT); + buf[offset] = (byte) ((value & BYTE_3_MASK) >> BYTE_3_SHIFT); + } + + public void putLong(byte[] buf, int offset) { + putLong(value, buf, offset); + } + + /** + * Helper method to get the value as a Java long from four bytes starting at given array offset + * @param bytes the array of bytes + * @param offset the offset to start + * @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; + value += (bytes[offset + BYTE_2] << BYTE_2_SHIFT) & BYTE_2_MASK; + value += (bytes[offset + BYTE_1] << BYTE_1_SHIFT) & BYTE_1_MASK; + value += (bytes[offset] & BYTE_MASK); + return value; + } + + /** + * Helper method to get the value as a Java long from a four-byte array + * @param bytes the array of bytes + * @return the corresponding Java long value + */ + public static long 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 + * @since 1.1 + */ + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof ZipLong)) { + return false; + } + return value == ((ZipLong) o).getValue(); + } + + /** + * Override to make two instances with same value equal. + * @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(); + } catch (CloneNotSupportedException cnfe) { + // impossible + throw new RuntimeException(cnfe); + } + } + + @Override + public String toString() { + return "ZipLong value: " + value; + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipOutputStream.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipOutputStream.java new file mode 100644 index 00000000..261c717e --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipOutputStream.java @@ -0,0 +1,1674 @@ +/* + * 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 static org.apache.tools.zip.ZipConstants.DATA_DESCRIPTOR_MIN_VERSION; +import static org.apache.tools.zip.ZipConstants.DWORD; +import static org.apache.tools.zip.ZipConstants.INITIAL_VERSION; +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; +import static org.apache.tools.zip.ZipConstants.ZIP64_MIN_VERSION; +import static org.apache.tools.zip.ZipLong.putLong; +import static org.apache.tools.zip.ZipShort.putShort; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.ZipException; + +/** + * Reimplementation of {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} that does handle the extended + * functionality of this package, especially internal/external file + * attributes and extra fields with different layouts for local file + * data and central directory entries. + * + * <p>This class will try to use {@link java.io.RandomAccessFile + * RandomAccessFile} when you know that the output is going to go to a + * file.</p> + * + * <p>If RandomAccessFile cannot be used, this implementation will use + * a Data Descriptor to store size and CRC information for {@link + * #DEFLATED DEFLATED} entries, this means, you don't need to + * calculate them yourself. Unfortunately this is not possible for + * the {@link #STORED STORED} method, here setting the CRC and + * uncompressed size information is required before {@link + * #putNextEntry putNextEntry} can be called.</p> + * + * <p>As of Apache Ant 1.9.0 it transparently supports Zip64 + * extensions and thus individual entries and archives larger than 4 + * GB or with more than 65536 entries in most cases but explicit + * control is provided via {@link #setUseZip64}. If the stream can not + * user RandomAccessFile and you try to write a ZipEntry of + * unknown size then Zip64 extensions will be disabled by default.</p> + */ +public class ZipOutputStream extends FilterOutputStream { + + private static final int BUFFER_SIZE = 512; + private static final int LFH_SIG_OFFSET = 0; + private static final int LFH_VERSION_NEEDED_OFFSET = 4; + private static final int LFH_GPB_OFFSET = 6; + private static final int LFH_METHOD_OFFSET = 8; + private static final int LFH_TIME_OFFSET = 10; + private static final int LFH_CRC_OFFSET = 14; + private static final int LFH_COMPRESSED_SIZE_OFFSET = 18; + private static final int LFH_ORIGINAL_SIZE_OFFSET = 22; + private static final int LFH_FILENAME_LENGTH_OFFSET = 26; + private static final int LFH_EXTRA_LENGTH_OFFSET = 28; + private static final int LFH_FILENAME_OFFSET = 30; + private static final int CFH_SIG_OFFSET = 0; + private static final int CFH_VERSION_MADE_BY_OFFSET = 4; + private static final int CFH_VERSION_NEEDED_OFFSET = 6; + private static final int CFH_GPB_OFFSET = 8; + private static final int CFH_METHOD_OFFSET = 10; + private static final int CFH_TIME_OFFSET = 12; + private static final int CFH_CRC_OFFSET = 16; + private static final int CFH_COMPRESSED_SIZE_OFFSET = 20; + private static final int CFH_ORIGINAL_SIZE_OFFSET = 24; + private static final int CFH_FILENAME_LENGTH_OFFSET = 28; + private static final int CFH_EXTRA_LENGTH_OFFSET = 30; + private static final int CFH_COMMENT_LENGTH_OFFSET = 32; + private static final int CFH_DISK_NUMBER_OFFSET = 34; + private static final int CFH_INTERNAL_ATTRIBUTES_OFFSET = 36; + private static final int CFH_EXTERNAL_ATTRIBUTES_OFFSET = 38; + private static final int CFH_LFH_OFFSET = 42; + private static final int CFH_FILENAME_OFFSET = 46; + + /** + * indicates if this archive is finished. + */ + private boolean finished = false; + + /* + * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs + * when it gets handed a really big buffer. See + * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396 + * + * Using a buffer size of 8 kB proved to be a good compromise + */ + private static final int DEFLATER_BLOCK_SIZE = 8192; + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED; + + /** + * Default compression level for deflated entries. + * + * @since Ant 1.7 + */ + public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION; + + /** + * Compression method for stored entries. + * + * @since 1.1 + */ + public static final int STORED = java.util.zip.ZipEntry.STORED; + + /** + * default encoding for file names and comment. + */ + static final String DEFAULT_ENCODING = null; + + /** + * General purpose flag, which indicates that filenames are + * written in utf-8. + * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead + */ + @Deprecated + public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG; + + private static final byte[] EMPTY = new byte[0]; + + /** + * Current entry. + * + * @since 1.1 + */ + private CurrentEntry entry; + + /** + * The file comment. + * + * @since 1.1 + */ + private String comment = ""; + + /** + * Compression level for next entry. + * + * @since 1.1 + */ + private int level = DEFAULT_COMPRESSION; + + /** + * Has the compression level changed when compared to the last + * entry? + * + * @since 1.5 + */ + private boolean hasCompressionLevelChanged = false; + + /** + * Default compression method for next entry. + * + * @since 1.1 + */ + private int method = java.util.zip.ZipEntry.DEFLATED; + + /** + * List of ZipEntries written so far. + * + * @since 1.1 + */ + private final List<ZipEntry> entries = new LinkedList<ZipEntry>(); + + /** + * CRC instance to avoid parsing DEFLATED data twice. + * + * @since 1.1 + */ + private final CRC32 crc = new CRC32(); + + /** + * Count the bytes written to out. + * + * @since 1.1 + */ + private long written = 0; + + /** + * Start of central directory. + * + * @since 1.1 + */ + private long cdOffset = 0; + + /** + * Length of central directory. + * + * @since 1.1 + */ + private long cdLength = 0; + + /** + * Helper, a 0 as ZipShort. + * + * @since 1.1 + */ + private static final byte[] ZERO = {0, 0}; + + /** + * Helper, a 0 as ZipLong. + * + * @since 1.1 + */ + private static final byte[] LZERO = {0, 0, 0, 0}; + + private static final byte[] ONE = ZipLong.getBytes(1L); + + /** + * Holds the offsets of the LFH starts for each entry. + * + * @since 1.1 + */ + private final Map<ZipEntry, Long> offsets = new HashMap<ZipEntry, Long>(); + + /** + * The encoding to use for filenames and the file comment. + * + * <p>For a list of possible values see <a + * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. + * Defaults to the platform's default character encoding.</p> + * + * @since 1.3 + */ + private String encoding = null; + + /** + * The zip encoding to use for filenames and the file comment. + * + * This field is of internal use and will be set in {@link + * #setEncoding(String)}. + */ + private ZipEncoding zipEncoding = + ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING); + + // CheckStyle:VisibilityModifier OFF - bc + + /** + * This Deflater object is used for output. + * + */ + protected final Deflater def = new Deflater(level, true); + + /** + * This buffer serves as a Deflater. + * + * <p>This attribute is only protected to provide a level of API + * backwards compatibility. This class used to extend {@link + * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to + * Revision 1.13.</p> + * + * @since 1.14 + */ + protected byte[] buf = new byte[BUFFER_SIZE]; + + // CheckStyle:VisibilityModifier ON + + /** + * Optional random access output. + * + * @since 1.14 + */ + private final RandomAccessFile raf; + + /** + * whether to use the general purpose bit flag when writing UTF-8 + * filenames or not. + */ + private boolean useUTF8Flag = true; + + /** + * Whether to encode non-encodable file names as UTF-8. + */ + private boolean fallbackToUTF8 = false; + + /** + * whether to create UnicodePathExtraField-s for each entry. + */ + private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER; + + /** + * Whether anything inside this archive has used a ZIP64 feature. + */ + private boolean hasUsedZip64 = false; + + private Zip64Mode zip64Mode = Zip64Mode.AsNeeded; + + private final Calendar calendarInstance = Calendar.getInstance(); + + /** + * Creates a new ZIP OutputStream filtering the underlying stream. + * @param out the outputstream to zip + * @since 1.1 + */ + public ZipOutputStream(OutputStream out) { + super(out); + this.raf = null; + } + + /** + * Creates a new ZIP OutputStream writing to a File. Will use + * random access if possible. + * @param file the file to zip to + * @since 1.14 + * @throws IOException on error + */ + public ZipOutputStream(File file) throws IOException { + super(null); + RandomAccessFile _raf = null; + try { + _raf = new RandomAccessFile(file, "rw"); + _raf.setLength(0); + } catch (IOException e) { + if (_raf != null) { + try { + _raf.close(); + } catch (IOException inner) { // NOPMD + // ignore + } + _raf = null; + } + out = new FileOutputStream(file); + } + raf = _raf; + } + + /** + * This method indicates whether this archive is writing to a + * seekable stream (i.e., to a random access file). + * + * <p>For seekable streams, you don't need to calculate the CRC or + * uncompressed size for {@link #STORED} entries before + * invoking {@link #putNextEntry}. + * @return true if seekable + * @since 1.17 + */ + public boolean isSeekable() { + return raf != null; + } + + /** + * The encoding to use for filenames and the file comment. + * + * <p>For a list of possible values see <a + * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. + * Defaults to the platform's default character encoding.</p> + * @param encoding the encoding value + * @since 1.3 + */ + public void setEncoding(final String encoding) { + this.encoding = encoding; + this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); + if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) { + useUTF8Flag = false; + } + } + + /** + * The encoding to use for filenames and the file comment. + * + * @return null if using the platform's default character encoding. + * + * @since 1.3 + */ + public String getEncoding() { + return encoding; + } + + /** + * Whether to set the language encoding flag if the file name + * encoding is UTF-8. + * + * <p>Defaults to true.</p> + */ + public void setUseLanguageEncodingFlag(boolean b) { + useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding); + } + + /** + * Whether to create Unicode Extra Fields. + * + * <p>Defaults to NEVER.</p> + */ + public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) { + createUnicodeExtraFields = b; + } + + /** + * Whether to fall back to UTF and the language encoding flag if + * the file name cannot be encoded using the specified encoding. + * + * <p>Defaults to false.</p> + */ + public void setFallbackToUTF8(boolean b) { + fallbackToUTF8 = b; + } + + /** + * Whether Zip64 extensions will be used. + * + * <p>When setting the mode to {@link Zip64Mode#Never Never}, + * {@link #putNextEntry}, {@link #closeEntry}, {@link + * #finish} or {@link #close} may throw a {@link + * Zip64RequiredException} if the entry's size or the total size + * of the archive exceeds 4GB or there are more than 65536 entries + * inside the archive. Any archive created in this mode will be + * readable by implementations that don't support Zip64.</p> + * + * <p>When setting the mode to {@link Zip64Mode#Always Always}, + * Zip64 extensions will be used for all entries. Any archive + * created in this mode may be unreadable by implementations that + * don't support Zip64 even if all its contents would be.</p> + * + * <p>When setting the mode to {@link Zip64Mode#AsNeeded + * AsNeeded}, Zip64 extensions will transparently be used for + * those entries that require them. This mode can only be used if + * the uncompressed size of the {@link ZipEntry} is known + * when calling {@link #putNextEntry} or the archive is written + * to a seekable output (i.e. you have used the {@link + * #ZipOutputStream(java.io.File) File-arg constructor}) - + * this mode is not valid when the output stream is not seekable + * and the uncompressed size is unknown when {@link + * #putNextEntry} is called.</p> + * + * <p>If no entry inside the resulting archive requires Zip64 + * extensions then {@link Zip64Mode#Never Never} will create the + * smallest archive. {@link Zip64Mode#AsNeeded AsNeeded} will + * create a slightly bigger archive if the uncompressed size of + * any entry has initially been unknown and create an archive + * identical to {@link Zip64Mode#Never Never} otherwise. {@link + * Zip64Mode#Always Always} will create an archive that is at + * least 24 bytes per entry bigger than the one {@link + * Zip64Mode#Never Never} would create.</p> + * + * <p>Defaults to {@link Zip64Mode#AsNeeded AsNeeded} unless + * {@link #putNextEntry} is called with an entry of unknown + * size and data is written to a non-seekable stream - in this + * case the default is {@link Zip64Mode#Never Never}.</p> + * + * @since 1.3 + */ + public void setUseZip64(Zip64Mode mode) { + zip64Mode = mode; + } + + /** + * {@inheritDoc} + * @throws Zip64RequiredException if the archive's size exceeds 4 + * GByte or there are more than 65535 entries inside the archive + * and {@link #setUseZip64} is {@link Zip64Mode#Never}. + */ + public void finish() throws IOException { + if (finished) { + throw new IOException("This archive has already been finished"); + } + + if (entry != null) { + closeEntry(); + } + + cdOffset = written; + writeCentralDirectoryInChunks(); + cdLength = written - cdOffset; + writeZip64CentralDirectory(); + writeCentralDirectoryEnd(); + offsets.clear(); + entries.clear(); + def.end(); + finished = true; + } + + private void writeCentralDirectoryInChunks() throws IOException { + final int NUM_PER_WRITE = 1000; + final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(70 * NUM_PER_WRITE); + int count = 0; + for (ZipEntry ze : entries) { + byteArrayOutputStream.write(createCentralFileHeader(ze)); + if (++count > NUM_PER_WRITE){ + writeCounted(byteArrayOutputStream.toByteArray()); + byteArrayOutputStream.reset(); + count = 0; + } + } + writeCounted(byteArrayOutputStream.toByteArray()); + } + + /** + * Writes all necessary data for this entry. + * + * @since 1.1 + * @throws IOException on error + * @throws Zip64RequiredException if the entry's uncompressed or + * compressed size exceeds 4 GByte and {@link #setUseZip64} + * is {@link Zip64Mode#Never}. + */ + public void closeEntry() throws IOException { + preClose(); + + flushDeflater(); + + final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); + long bytesWritten = written - entry.dataStart; + long realCrc = crc.getValue(); + crc.reset(); + + final boolean actuallyNeedsZip64 = + handleSizesAndCrc(bytesWritten, realCrc, effectiveMode); + + closeEntry(actuallyNeedsZip64); + } + + private void closeEntry(boolean actuallyNeedsZip64) throws IOException { + if (raf != null) { + rewriteSizesAndCrc(actuallyNeedsZip64); + } + + writeDataDescriptor(entry.entry); + entry = null; + } + + private void preClose() throws IOException { + if (finished) { + throw new IOException("Stream has already been finished"); + } + + if (entry == null) { + throw new IOException("No current entry to close"); + } + + if (!entry.hasWritten) { + write(EMPTY, 0, 0); + } + } + + /** + * Ensures all bytes sent to the deflater are written to the stream. + */ + private void flushDeflater() throws IOException { + if (entry.entry.getMethod() == DEFLATED) { + def.finish(); + while (!def.finished()) { + deflate(); + } + } + } + + /** + * Ensures the current entry's size and CRC information is set to + * the values just written, verifies it isn't too big in the + * Zip64Mode.Never case and returns whether the entry would + * require a Zip64 extra field. + */ + private boolean handleSizesAndCrc(long bytesWritten, long crc, + Zip64Mode effectiveMode) + throws ZipException { + if (entry.entry.getMethod() == DEFLATED) { + /* It turns out def.getBytesRead() returns wrong values if + * the size exceeds 4 GB on Java < Java7 + entry.entry.setSize(def.getBytesRead()); + */ + entry.entry.setSize(entry.bytesRead); + entry.entry.setCompressedSize(bytesWritten); + entry.entry.setCrc(crc); + + def.reset(); + } else if (raf == null) { + if (entry.entry.getCrc() != crc) { + throw new ZipException("bad CRC checksum for entry " + + entry.entry.getName() + ": " + + Long.toHexString(entry.entry.getCrc()) + + " instead of " + + Long.toHexString(crc)); + } + + if (entry.entry.getSize() != bytesWritten) { + throw new ZipException("bad size for entry " + + entry.entry.getName() + ": " + + entry.entry.getSize() + + " instead of " + + bytesWritten); + } + } else { /* method is STORED and we used RandomAccessFile */ + entry.entry.setSize(bytesWritten); + entry.entry.setCompressedSize(bytesWritten); + entry.entry.setCrc(crc); + } + + return checkIfNeedsZip64(effectiveMode); + } + + /** + * Ensures the current entry's size and CRC information is set to + * the values just written, verifies it isn't too big in the + * Zip64Mode.Never case and returns whether the entry would + * require a Zip64 extra field. + */ + private boolean checkIfNeedsZip64(Zip64Mode effectiveMode) + throws ZipException { + final boolean actuallyNeedsZip64 = isZip64Required(entry.entry, + effectiveMode); + if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) { + throw new Zip64RequiredException(Zip64RequiredException + .getEntryTooBigMessage(entry.entry)); + } + return actuallyNeedsZip64; + } + + private boolean isZip64Required(ZipEntry entry1, Zip64Mode requestedMode) { + return requestedMode == Zip64Mode.Always || isTooLageForZip32(entry1); + } + + private boolean isTooLageForZip32(ZipEntry zipArchiveEntry){ + return zipArchiveEntry.getSize() >= ZIP64_MAGIC + || zipArchiveEntry.getCompressedSize() >= ZIP64_MAGIC; + } + + /** + * When using random access output, write the local file header + * and potentiall the ZIP64 extra containing the correct CRC and + * compressed/uncompressed sizes. + */ + private void rewriteSizesAndCrc(boolean actuallyNeedsZip64) + throws IOException { + long save = raf.getFilePointer(); + + raf.seek(entry.localDataStart); + writeOut(ZipLong.getBytes(entry.entry.getCrc())); + if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) { + writeOut(ZipLong.getBytes(entry.entry.getCompressedSize())); + writeOut(ZipLong.getBytes(entry.entry.getSize())); + } else { + writeOut(ZipLong.ZIP64_MAGIC.getBytes()); + writeOut(ZipLong.ZIP64_MAGIC.getBytes()); + } + + if (hasZip64Extra(entry.entry)) { + // seek to ZIP64 extra, skip header and size information + raf.seek(entry.localDataStart + 3 * WORD + 2 * SHORT + + getName(entry.entry).limit() + 2 * SHORT); + // inside the ZIP64 extra uncompressed size comes + // first, unlike the LFH, CD or data descriptor + writeOut(ZipEightByteInteger.getBytes(entry.entry.getSize())); + writeOut(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize())); + + if (!actuallyNeedsZip64) { + // do some cleanup: + // * rewrite version needed to extract + raf.seek(entry.localDataStart - 5 * SHORT); + writeOut(ZipShort.getBytes(INITIAL_VERSION)); + + // * remove ZIP64 extra so it doesn't get written + // to the central directory + entry.entry.removeExtraField(Zip64ExtendedInformationExtraField + .HEADER_ID); + entry.entry.setExtra(); + + // * reset hasUsedZip64 if it has been set because + // of this entry + if (entry.causedUseOfZip64) { + hasUsedZip64 = false; + } + } + } + raf.seek(save); + } + + /** + * {@inheritDoc} + * @throws Zip64RequiredException if the entry's uncompressed or + * compressed size is known to exceed 4 GByte and {@link #setUseZip64} + * is {@link Zip64Mode#Never}. + */ + public void putNextEntry(ZipEntry archiveEntry) throws IOException { + if (finished) { + throw new IOException("Stream has already been finished"); + } + + if (entry != null) { + closeEntry(); + } + + entry = new CurrentEntry(archiveEntry); + entries.add(entry.entry); + + setDefaults(entry.entry); + + final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); + validateSizeInformation(effectiveMode); + + if (shouldAddZip64Extra(entry.entry, effectiveMode)) { + + Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry); + + // just a placeholder, real data will be in data + // descriptor or inserted later via RandomAccessFile + ZipEightByteInteger size = ZipEightByteInteger.ZERO; + ZipEightByteInteger compressedSize = ZipEightByteInteger.ZERO; + if (entry.entry.getMethod() == STORED + && entry.entry.getSize() != -1) { + // actually, we already know the sizes + size = new ZipEightByteInteger(entry.entry.getSize()); + compressedSize = size; + } + z64.setSize(size); + z64.setCompressedSize(compressedSize); + entry.entry.setExtra(); + } + + if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) { + def.setLevel(level); + hasCompressionLevelChanged = false; + } + writeLocalFileHeader(entry.entry); + } + + /** + * Provides default values for compression method and last + * modification time. + */ + private void setDefaults(ZipEntry entry) { + if (entry.getMethod() == -1) { // not specified + entry.setMethod(method); + } + + if (entry.getTime() == -1) { // not specified + entry.setTime(System.currentTimeMillis()); + } + } + + /** + * Throws an exception if the size is unknown for a stored entry + * that is written to a non-seekable output or the entry is too + * big to be written without Zip64 extra but the mode has been set + * to Never. + */ + private void validateSizeInformation(Zip64Mode effectiveMode) + throws ZipException { + // Size/CRC not required if RandomAccessFile is used + if (entry.entry.getMethod() == STORED && raf == null) { + if (entry.entry.getSize() == -1) { + throw new ZipException("uncompressed size is required for" + + " STORED method when not writing to a" + + " file"); + } + if (entry.entry.getCrc() == -1) { + throw new ZipException("crc checksum is required for STORED" + + " method when not writing to a file"); + } + entry.entry.setCompressedSize(entry.entry.getSize()); + } + + if ((entry.entry.getSize() >= ZIP64_MAGIC + || entry.entry.getCompressedSize() >= ZIP64_MAGIC) + && effectiveMode == Zip64Mode.Never) { + throw new Zip64RequiredException(Zip64RequiredException + .getEntryTooBigMessage(entry.entry)); + } + } + + /** + * Whether to addd a Zip64 extended information extra field to the + * local file header. + * + * <p>Returns true if</p> + * + * <ul> + * <li>mode is Always</li> + * <li>or we already know it is going to be needed</li> + * <li>or the size is unknown and we can ensure it won't hurt + * other implementations if we add it (i.e. we can erase its + * usage</li> + * </ul> + */ + private boolean shouldAddZip64Extra(ZipEntry entry, Zip64Mode mode) { + return mode == Zip64Mode.Always + || entry.getSize() >= ZIP64_MAGIC + || entry.getCompressedSize() >= ZIP64_MAGIC + || (entry.getSize() == -1 + && raf != null && mode != Zip64Mode.Never); + } + + /** + * Set the file comment. + * @param comment the comment + */ + public void setComment(String comment) { + this.comment = comment; + } + + /** + * Sets the compression level for subsequent entries. + * + * <p>Default is Deflater.DEFAULT_COMPRESSION.</p> + * @param level the compression level. + * @throws IllegalArgumentException if an invalid compression + * level is specified. + * @since 1.1 + */ + public void setLevel(int level) { + if (level < Deflater.DEFAULT_COMPRESSION + || level > Deflater.BEST_COMPRESSION) { + throw new IllegalArgumentException("Invalid compression level: " + + level); + } + hasCompressionLevelChanged = (this.level != level); + this.level = level; + } + + /** + * Sets the default compression method for subsequent entries. + * + * <p>Default is DEFLATED.</p> + * @param method an <code>int</code> from java.util.zip.ZipEntry + * @since 1.1 + */ + public void setMethod(int method) { + this.method = method; + } + + /** + * Whether this stream is able to write 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 canWriteEntryData(ZipEntry ae) { + return ZipUtil.canHandleEntryData(ae); + } + + /** + * Writes bytes to ZIP entry. + * @param b the byte array to write + * @param offset the start position to write from + * @param length the number of bytes to write + * @throws IOException on error + */ + @Override + public void write(byte[] b, int offset, int length) throws IOException { + if (entry == null) { + throw new IllegalStateException("No current entry"); + } + ZipUtil.checkRequestedFeatures(entry.entry); + entry.hasWritten = true; + if (entry.entry.getMethod() == DEFLATED) { + writeDeflated(b, offset, length); + } else { + writeCounted(b, offset, length); + } + crc.update(b, offset, length); + } + + /** + * Write bytes to output or random access file. + * @param data the byte array to write + * @throws IOException on error + */ + private void writeCounted(byte[] data) throws IOException { + writeCounted(data, 0, data.length); + } + + private void writeCounted(byte[] data, int offset, int length) throws IOException { + writeOut(data, offset, length); + written += length; + } + + /** + * write implementation for DEFLATED entries. + */ + private void writeDeflated(byte[]b, int offset, int length) + throws IOException { + if (length > 0 && !def.finished()) { + entry.bytesRead += length; + if (length <= DEFLATER_BLOCK_SIZE) { + def.setInput(b, offset, length); + deflateUntilInputIsNeeded(); + } else { + final int fullblocks = length / DEFLATER_BLOCK_SIZE; + for (int i = 0; i < fullblocks; i++) { + def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE, + DEFLATER_BLOCK_SIZE); + deflateUntilInputIsNeeded(); + } + final int done = fullblocks * DEFLATER_BLOCK_SIZE; + if (done < length) { + def.setInput(b, offset + done, length - done); + deflateUntilInputIsNeeded(); + } + } + } + } + + /** + * Closes this output stream and releases any system resources + * associated with the stream. + * + * @exception IOException if an I/O error occurs. + * @throws Zip64RequiredException if the archive's size exceeds 4 + * GByte or there are more than 65535 entries inside the archive + * and {@link #setUseZip64} is {@link Zip64Mode#Never}. + */ + @Override + public void close() throws IOException { + if (!finished) { + finish(); + } + destroy(); + } + + /** + * Flushes this output stream and forces any buffered output bytes + * to be written out to the stream. + * + * @exception IOException if an I/O error occurs. + */ + @Override + public void flush() throws IOException { + if (out != null) { + out.flush(); + } + } + + /* + * Various ZIP constants + */ + /** + * local file header signature + * + * @since 1.1 + */ + protected static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes(); + /** + * data descriptor signature + * + * @since 1.1 + */ + protected static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes(); + /** + * central file header signature + * + * @since 1.1 + */ + protected static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes(); + /** + * end of central dir signature + * + * @since 1.1 + */ + protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); + /** + * ZIP64 end of central dir signature + */ + static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L); + /** + * ZIP64 end of central dir locator signature + */ + static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L); + + /** + * Writes next block of compressed data to the output stream. + * @throws IOException on error + * + * @since 1.14 + */ + protected final void deflate() throws IOException { + int len = def.deflate(buf, 0, buf.length); + if (len > 0) { + writeCounted(buf, 0, len); + } + } + + /** + * Writes the local file header entry + * @param ze the entry to write + * @throws IOException on error + * + * @since 1.1 + */ + protected void writeLocalFileHeader(ZipEntry ze) throws IOException { + + boolean encodable = zipEncoding.canEncode(ze.getName()); + ByteBuffer name = getName(ze); + + if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) { + addUnicodeExtraFields(ze, encodable, name); + } + + final byte[] localHeader = createLocalFileHeader(ze, name, encodable); + final long localHeaderStart = written; + offsets.put(ze, localHeaderStart); + entry.localDataStart = localHeaderStart + LFH_CRC_OFFSET; // At crc offset + writeCounted(localHeader); + entry.dataStart = written; + } + + private byte[] createLocalFileHeader(ZipEntry ze, ByteBuffer name, boolean encodable) { + byte[] extra = ze.getLocalFileDataExtra(); + final int nameLen = name.limit() - name.position(); + int len= LFH_FILENAME_OFFSET + nameLen + extra.length; + byte[] buf = new byte[len]; + + System.arraycopy(LFH_SIG, 0, buf, LFH_SIG_OFFSET, WORD); + + //store method in local variable to prevent multiple method calls + final int zipMethod = ze.getMethod(); + + putShort(versionNeededToExtract(zipMethod, hasZip64Extra(ze)), + buf, LFH_VERSION_NEEDED_OFFSET); + + GeneralPurposeBit generalPurposeBit = + getGeneralPurposeBits(zipMethod, !encodable && fallbackToUTF8); + generalPurposeBit.encode(buf, LFH_GPB_OFFSET); + + // compression method + putShort(zipMethod, buf, LFH_METHOD_OFFSET); + + ZipUtil.toDosTime(calendarInstance, ze.getTime(), buf, LFH_TIME_OFFSET); + + // CRC + if (zipMethod == DEFLATED || raf != null) { + System.arraycopy(LZERO, 0, buf, LFH_CRC_OFFSET, WORD); + } else { + putLong(ze.getCrc(), buf, LFH_CRC_OFFSET); + } + + // compressed length + // uncompressed length + if (hasZip64Extra(entry.entry)){ + // point to ZIP64 extended information extra field for + // sizes, may get rewritten once sizes are known if + // stream is seekable + ZipLong.ZIP64_MAGIC.putLong(buf, LFH_COMPRESSED_SIZE_OFFSET); + ZipLong.ZIP64_MAGIC.putLong(buf, LFH_ORIGINAL_SIZE_OFFSET); + } else if (zipMethod == DEFLATED || raf != null) { + System.arraycopy(LZERO, 0, buf, LFH_COMPRESSED_SIZE_OFFSET, WORD); + System.arraycopy(LZERO, 0, buf, LFH_ORIGINAL_SIZE_OFFSET, WORD); + } else { // Stored + putLong(ze.getSize(), buf, LFH_COMPRESSED_SIZE_OFFSET); + putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET); + } + // file name length + putShort(nameLen, buf, LFH_FILENAME_LENGTH_OFFSET); + + // extra field length + putShort(extra.length, buf, LFH_EXTRA_LENGTH_OFFSET); + + // file name + System.arraycopy(name.array(), name.arrayOffset(), buf, + LFH_FILENAME_OFFSET, nameLen); + + System.arraycopy(extra, 0, buf, LFH_FILENAME_OFFSET + nameLen, extra.length); + return buf; + } + + /** + * Adds UnicodeExtra fields for name and file comment if mode is + * ALWAYS or the data cannot be encoded using the configured + * encoding. + */ + private void addUnicodeExtraFields(ZipEntry ze, boolean encodable, + ByteBuffer name) + throws IOException { + if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS + || !encodable) { + ze.addExtraField(new UnicodePathExtraField(ze.getName(), + name.array(), + name.arrayOffset(), + name.limit() + - name.position())); + } + + String comm = ze.getComment(); + if (comm != null && !"".equals(comm)) { + + boolean commentEncodable = zipEncoding.canEncode(comm); + + if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS + || !commentEncodable) { + ByteBuffer commentB = getEntryEncoding(ze).encode(comm); + ze.addExtraField(new UnicodeCommentExtraField(comm, + commentB.array(), + commentB.arrayOffset(), + commentB.limit() + - commentB.position()) + ); + } + } + } + + /** + * Writes the data descriptor entry. + * @param ze the entry to write + * @throws IOException on error + * + * @since 1.1 + */ + protected void writeDataDescriptor(ZipEntry ze) throws IOException { + if (ze.getMethod() != DEFLATED || raf != null) { + return; + } + writeCounted(DD_SIG); + writeCounted(ZipLong.getBytes(ze.getCrc())); + if (!hasZip64Extra(ze)) { + writeCounted(ZipLong.getBytes(ze.getCompressedSize())); + writeCounted(ZipLong.getBytes(ze.getSize())); + } else { + writeCounted(ZipEightByteInteger.getBytes(ze.getCompressedSize())); + writeCounted(ZipEightByteInteger.getBytes(ze.getSize())); + } + } + + /** + * Writes the central file header entry. + * @param ze the entry to write + * @throws IOException on error + * @throws Zip64RequiredException if the archive's size exceeds 4 + * GByte and {@link Zip64Mode #setUseZip64} is {@link + * Zip64Mode#Never}. + */ + protected void writeCentralFileHeader(ZipEntry ze) throws IOException { + byte[] centralFileHeader = createCentralFileHeader(ze); + writeCounted(centralFileHeader); + } + + private byte[] createCentralFileHeader(ZipEntry ze) throws IOException { + final long lfhOffset = offsets.get(ze); + final boolean needsZip64Extra = hasZip64Extra(ze) + || ze.getCompressedSize() >= ZIP64_MAGIC + || ze.getSize() >= ZIP64_MAGIC + || lfhOffset >= ZIP64_MAGIC; + + if (needsZip64Extra && zip64Mode == Zip64Mode.Never) { + // must be the offset that is too big, otherwise an + // exception would have been throw in putArchiveEntry or + // closeArchiveEntry + throw new Zip64RequiredException(Zip64RequiredException + .ARCHIVE_TOO_BIG_MESSAGE); + } + + + handleZip64Extra(ze, lfhOffset, needsZip64Extra); + + return createCentralFileHeader(ze, getName(ze), lfhOffset, needsZip64Extra); + } + + /** + * Writes the central file header entry. + * @param ze the entry to write + * @param name The encoded name + * @param lfhOffset Local file header offset for this file + * @throws IOException on error + */ + private byte[] createCentralFileHeader(ZipEntry ze, ByteBuffer name, long lfhOffset, + boolean needsZip64Extra) throws IOException { + byte[] extra = ze.getCentralDirectoryExtra(); + + // file comment length + String comm = ze.getComment(); + if (comm == null) { + comm = ""; + } + + ByteBuffer commentB = getEntryEncoding(ze).encode(comm); + final int nameLen = name.limit() - name.position(); + final int commentLen = commentB.limit() - commentB.position(); + int len= CFH_FILENAME_OFFSET + nameLen + extra.length + commentLen; + byte[] buf = new byte[len]; + + System.arraycopy(CFH_SIG, 0, buf, CFH_SIG_OFFSET, WORD); + + // version made by + // CheckStyle:MagicNumber OFF + putShort((ze.getPlatform() << 8) | (!hasUsedZip64 ? DATA_DESCRIPTOR_MIN_VERSION : ZIP64_MIN_VERSION), + buf, CFH_VERSION_MADE_BY_OFFSET); + + final int zipMethod = ze.getMethod(); + final boolean encodable = zipEncoding.canEncode(ze.getName()); + putShort(versionNeededToExtract(zipMethod, needsZip64Extra), buf, CFH_VERSION_NEEDED_OFFSET); + getGeneralPurposeBits(zipMethod, !encodable && fallbackToUTF8).encode(buf, CFH_GPB_OFFSET); + + // compression method + putShort(zipMethod, buf, CFH_METHOD_OFFSET); + + + // last mod. time and date + ZipUtil.toDosTime(calendarInstance, ze.getTime(), buf, CFH_TIME_OFFSET); + + // CRC + // compressed length + // uncompressed length + putLong(ze.getCrc(), buf, CFH_CRC_OFFSET); + if (ze.getCompressedSize() >= ZIP64_MAGIC + || ze.getSize() >= ZIP64_MAGIC) { + ZipLong.ZIP64_MAGIC.putLong(buf, CFH_COMPRESSED_SIZE_OFFSET); + ZipLong.ZIP64_MAGIC.putLong(buf, CFH_ORIGINAL_SIZE_OFFSET); + } else { + putLong(ze.getCompressedSize(), buf, CFH_COMPRESSED_SIZE_OFFSET); + putLong(ze.getSize(), buf, CFH_ORIGINAL_SIZE_OFFSET); + } + + putShort(nameLen, buf, CFH_FILENAME_LENGTH_OFFSET); + + // extra field length + putShort(extra.length, buf, CFH_EXTRA_LENGTH_OFFSET); + + putShort(commentLen, buf, CFH_COMMENT_LENGTH_OFFSET); + + // disk number start + System.arraycopy(ZERO, 0, buf, CFH_DISK_NUMBER_OFFSET, SHORT); + + // internal file attributes + putShort(ze.getInternalAttributes(), buf, CFH_INTERNAL_ATTRIBUTES_OFFSET); + + // external file attributes + putLong(ze.getExternalAttributes(), buf, CFH_EXTERNAL_ATTRIBUTES_OFFSET); + + // relative offset of LFH + putLong(Math.min(lfhOffset, ZIP64_MAGIC), buf, CFH_LFH_OFFSET); + + // file name + System.arraycopy(name.array(), name.arrayOffset(), buf, CFH_FILENAME_OFFSET, nameLen); + + int extraStart = CFH_FILENAME_OFFSET + nameLen; + System.arraycopy(extra, 0, buf, extraStart, extra.length); + + int commentStart = extraStart + commentLen; + + // file comment + System.arraycopy(commentB.array(), commentB.arrayOffset(), buf, commentStart, commentLen); + return buf; + } + + /** + * If the entry needs Zip64 extra information inside the central + * directory then configure its data. + */ + private void handleZip64Extra(ZipEntry ze, long lfhOffset, + boolean needsZip64Extra) { + if (needsZip64Extra) { + Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze); + if (ze.getCompressedSize() >= ZIP64_MAGIC + || ze.getSize() >= ZIP64_MAGIC) { + z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize())); + z64.setSize(new ZipEightByteInteger(ze.getSize())); + } else { + // reset value that may have been set for LFH + z64.setCompressedSize(null); + z64.setSize(null); + } + if (lfhOffset >= ZIP64_MAGIC) { + z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset)); + } + ze.setExtra(); + } + } + + /** + * Writes the "End of central dir record". + * @throws IOException on error + * @throws Zip64RequiredException if the archive's size exceeds 4 + * GByte or there are more than 65535 entries inside the archive + * and {@link Zip64Mode #setUseZip64} is {@link Zip64Mode#Never}. + */ + protected void writeCentralDirectoryEnd() throws IOException { + writeCounted(EOCD_SIG); + + // disk numbers + writeCounted(ZERO); + writeCounted(ZERO); + + // number of entries + int numberOfEntries = entries.size(); + if (numberOfEntries > ZIP64_MAGIC_SHORT + && zip64Mode == Zip64Mode.Never) { + throw new Zip64RequiredException(Zip64RequiredException + .TOO_MANY_ENTRIES_MESSAGE); + } + if (cdOffset > ZIP64_MAGIC && zip64Mode == Zip64Mode.Never) { + throw new Zip64RequiredException(Zip64RequiredException + .ARCHIVE_TOO_BIG_MESSAGE); + } + + byte[] num = ZipShort.getBytes(Math.min(numberOfEntries, + ZIP64_MAGIC_SHORT)); + writeCounted(num); + writeCounted(num); + + // length and location of CD + writeCounted(ZipLong.getBytes(Math.min(cdLength, ZIP64_MAGIC))); + writeCounted(ZipLong.getBytes(Math.min(cdOffset, ZIP64_MAGIC))); + + // ZIP file comment + ByteBuffer data = this.zipEncoding.encode(comment); + int dataLen = data.limit() - data.position(); + writeCounted(ZipShort.getBytes(dataLen)); + writeCounted(data.array(), data.arrayOffset(), dataLen); + } + + /** + * 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> + * @since 1.1 + * @deprecated use ZipUtil#toDosTime + */ + @Deprecated + protected static ZipLong toDosTime(Date time) { + return ZipUtil.toDosTime(time); + } + + /** + * 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 + * @since 1.26 + * @deprecated use ZipUtil#toDosTime + */ + @Deprecated + protected static byte[] toDosTime(long t) { + return ZipUtil.toDosTime(t); + } + + /** + * Retrieve the bytes for the given String in the encoding set for + * this Stream. + * @param name the string to get bytes from + * @return the bytes as a byte array + * @throws ZipException on error + * + * @since 1.3 + */ + protected byte[] getBytes(String name) throws ZipException { + try { + ByteBuffer b = + ZipEncodingHelper.getZipEncoding(encoding).encode(name); + byte[] result = new byte[b.limit()]; + System.arraycopy(b.array(), b.arrayOffset(), result, 0, + result.length); + return result; + } catch (IOException ex) { + throw new ZipException("Failed to encode name: " + ex.getMessage()); + } + } + + /** + * Writes the "ZIP64 End of central dir record" and + * "ZIP64 End of central dir locator". + * @throws IOException on error + */ + protected void writeZip64CentralDirectory() throws IOException { + if (zip64Mode == Zip64Mode.Never) { + return; + } + + if (!hasUsedZip64 + && (cdOffset >= ZIP64_MAGIC || cdLength >= ZIP64_MAGIC + || entries.size() >= ZIP64_MAGIC_SHORT)) { + // actually "will use" + hasUsedZip64 = true; + } + + if (!hasUsedZip64) { + return; + } + + long offset = written; + + writeOut(ZIP64_EOCD_SIG); + // size, we don't have any variable length as we don't support + // the extensible data sector, yet + writeOut(ZipEightByteInteger + .getBytes(SHORT /* version made by */ + + SHORT /* version needed to extract */ + + WORD /* disk number */ + + WORD /* disk with central directory */ + + DWORD /* number of entries in CD on this disk */ + + DWORD /* total number of entries */ + + DWORD /* size of CD */ + + DWORD /* offset of CD */ + )); + + // version made by and version needed to extract + writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION)); + writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION)); + + // disk numbers - four bytes this time + writeOut(LZERO); + writeOut(LZERO); + + // number of entries + byte[] num = ZipEightByteInteger.getBytes(entries.size()); + writeOut(num); + writeOut(num); + + // length and location of CD + writeOut(ZipEightByteInteger.getBytes(cdLength)); + writeOut(ZipEightByteInteger.getBytes(cdOffset)); + + // no "zip64 extensible data sector" for now + + // and now the "ZIP64 end of central directory locator" + writeOut(ZIP64_EOCD_LOC_SIG); + + // disk number holding the ZIP64 EOCD record + writeOut(LZERO); + // relative offset of ZIP64 EOCD record + writeOut(ZipEightByteInteger.getBytes(offset)); + // total number of disks + writeOut(ONE); + } + + /** + * Write bytes to output or random access file. + * @param data the byte array to write + * @throws IOException on error + * + * @since 1.14 + */ + protected final void writeOut(byte[] data) throws IOException { + writeOut(data, 0, data.length); + } + + /** + * Write bytes to output or random access file. + * @param data the byte array to write + * @param offset the start position to write from + * @param length the number of bytes to write + * @throws IOException on error + * + * @since 1.14 + */ + protected final void writeOut(byte[] data, int offset, int length) + throws IOException { + if (raf != null) { + raf.write(data, offset, length); + } else { + out.write(data, offset, length); + } + } + + /** + * Assumes a negative integer really is a positive integer that + * has wrapped around and re-creates the original value. + * @param i the value to treat as unsigned int. + * @return the unsigned int as a long. + * @since 1.34 + * @deprecated use ZipUtil#adjustToLong + */ + @Deprecated + protected static long adjustToLong(int i) { + return ZipUtil.adjustToLong(i); + } + + private void deflateUntilInputIsNeeded() throws IOException { + while (!def.needsInput()) { + deflate(); + } + } + + private GeneralPurposeBit getGeneralPurposeBits(final int zipMethod, final boolean utfFallback) { + GeneralPurposeBit b = new GeneralPurposeBit(); + b.useUTF8ForNames(useUTF8Flag || utfFallback); + if (isDeflatedToOutputStream(zipMethod)) { + b.useDataDescriptor(true); + } + return b; + } + + private int versionNeededToExtract(final int zipMethod, final boolean zip64) { + if (zip64) { + return ZIP64_MIN_VERSION; + } + // requires version 2 as we are going to store length info + // in the data descriptor + return (isDeflatedToOutputStream(zipMethod)) ? + DATA_DESCRIPTOR_MIN_VERSION : + INITIAL_VERSION; + } + + private boolean isDeflatedToOutputStream(int zipMethod) { + return zipMethod == DEFLATED && raf == null; + } + + /** + * Get the existing ZIP64 extended information extra field or + * create a new one and add it to the entry. + */ + private Zip64ExtendedInformationExtraField getZip64Extra(ZipEntry ze) { + if (entry != null) { + entry.causedUseOfZip64 = !hasUsedZip64; + } + hasUsedZip64 = true; + Zip64ExtendedInformationExtraField z64 = + (Zip64ExtendedInformationExtraField) + ze.getExtraField(Zip64ExtendedInformationExtraField + .HEADER_ID); + if (z64 == null) { + /* + System.err.println("Adding z64 for " + ze.getName() + + ", method: " + ze.getMethod() + + " (" + (ze.getMethod() == STORED) + ")" + + ", raf: " + (raf != null)); + */ + z64 = new Zip64ExtendedInformationExtraField(); + } + + // even if the field is there already, make sure it is the first one + ze.addAsFirstExtraField(z64); + + return z64; + } + + /** + * Is there a ZIP64 extended information extra field for the + * entry? + */ + private boolean hasZip64Extra(ZipEntry ze) { + return ze.getExtraField(Zip64ExtendedInformationExtraField + .HEADER_ID) + != null; + } + + /** + * If the mode is AsNeeded and the entry is a compressed entry of + * unknown size that gets written to a non-seekable stream the + * change the default to Never. + */ + private Zip64Mode getEffectiveZip64Mode(ZipEntry ze) { + if (zip64Mode != Zip64Mode.AsNeeded + || raf != null + || ze.getMethod() != DEFLATED + || ze.getSize() != -1) { + return zip64Mode; + } + return Zip64Mode.Never; + } + + private ZipEncoding getEntryEncoding(ZipEntry ze) { + boolean encodable = zipEncoding.canEncode(ze.getName()); + return !encodable && fallbackToUTF8 + ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; + } + + private ByteBuffer getName(ZipEntry ze) throws IOException { + return getEntryEncoding(ze).encode(ze.getName()); + } + + /** + * Closes the underlying stream/file without finishing the + * archive, the result will likely be a corrupt archive. + * + * <p>This method only exists to support tests that generate + * corrupt archives so they can clean up any temporary files.</p> + */ + void destroy() throws IOException { + if (raf != null) { + raf.close(); + } + if (out != null) { + out.close(); + } + } + + /** + * enum that represents the possible policies for creating Unicode + * extra fields. + */ + public static final class UnicodeExtraFieldPolicy { + /** + * Always create Unicode extra fields. + */ + public static final UnicodeExtraFieldPolicy ALWAYS = + new UnicodeExtraFieldPolicy("always"); + /** + * Never create Unicode extra fields. + */ + public static final UnicodeExtraFieldPolicy NEVER = + new UnicodeExtraFieldPolicy("never"); + /** + * Create Unicode extra fields for filenames that cannot be + * encoded using the specified encoding. + */ + public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = + new UnicodeExtraFieldPolicy("not encodeable"); + + private final String name; + private UnicodeExtraFieldPolicy(String n) { + name = n; + } + @Override + public String toString() { + return name; + } + } + + /** + * Structure collecting information for the entry that is + * currently being written. + */ + private static final class CurrentEntry { + private CurrentEntry(ZipEntry entry) { + this.entry = entry; + } + /** + * Current ZIP entry. + */ + private final ZipEntry entry; + /** + * Offset for CRC entry in the local file header data for the + * current entry starts here. + */ + private long localDataStart = 0; + /** + * Data for local header data + */ + private long dataStart = 0; + /** + * Number of bytes read for the current entry (can't rely on + * Deflater#getBytesRead) when using DEFLATED. + */ + private long bytesRead = 0; + /** + * Whether current entry was the first one using ZIP64 features. + */ + private boolean causedUseOfZip64 = false; + /** + * Whether write() has been called at all. + * + * <p>In order to create a valid archive {@link + * #closeEntry closeEntry} will write an empty + * array to get the CRC right if nothing has been written to + * the stream at all.</p> + */ + private boolean hasWritten; + } + +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipShort.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipShort.java new file mode 100644 index 00000000..e52c570d --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipShort.java @@ -0,0 +1,166 @@ +/* + * 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 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_1_MASK = 0xFF00; + private static final int BYTE_1_SHIFT = 8; + + private final int value; + + /** + * Create instance from a number. + * @param value the int to store as a ZipShort + * @since 1.1 + */ + public ZipShort (int value) { + this.value = value; + } + + /** + * Create instance from bytes. + * @param bytes the bytes to store as a ZipShort + * @since 1.1 + */ + public ZipShort (byte[] bytes) { + this(bytes, 0); + } + + /** + * Create instance from the two bytes starting at offset. + * @param bytes the bytes to store as a ZipShort + * @param offset the offset to start + * @since 1.1 + */ + public ZipShort (byte[] bytes, int offset) { + value = ZipShort.getValue(bytes, offset); + } + + /** + * Get value as two bytes in big endian byte order. + * @return the value as a a two byte array in big endian byte order + * @since 1.1 + */ + public byte[] getBytes() { + byte[] result = new byte[2]; + putShort(value, result, 0); + return result; + } + + /** + * put the value as two bytes in big endian byte order. + * @param value the Java int to convert to bytes + * @param buf the output buffer + * @param offset + * The offset within the output buffer of the first byte to be written. + * must be non-negative and no larger than <tt>buf.length-2</tt> + */ + public static void putShort(int value, byte[] buf, int offset) { + buf[offset] = (byte) (value & BYTE_MASK); + buf[offset+1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); + } + + /** + * Get value as Java int. + * @return value as a Java int + * @since 1.1 + */ + public int getValue() { + return value; + } + + /** + * Get value as two bytes in big endian byte order. + * @param value the Java int to convert to bytes + * @return the converted int as a byte array in big endian byte order + */ + public static byte[] getBytes(int value) { + byte[] result = new byte[2]; + result[0] = (byte) (value & BYTE_MASK); + result[1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); + return result; + } + + /** + * 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 corresponding java int value + */ + public static int getValue(byte[] bytes, int offset) { + int value = (bytes[offset + 1] << BYTE_1_SHIFT) & BYTE_1_MASK; + value += (bytes[offset] & BYTE_MASK); + return value; + } + + /** + * Helper method to get the value as a java int from a two-byte array + * @param bytes the array of bytes + * @return the corresponding java int value + */ + public static int 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 + * @since 1.1 + */ + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof ZipShort)) { + return false; + } + return value == ((ZipShort) o).getValue(); + } + + /** + * Override to make two instances with same value equal. + * @return the value stored in the ZipShort + * @since 1.1 + */ + @Override + public int hashCode() { + return value; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException cnfe) { + // impossible + throw new RuntimeException(cnfe); + } + } + + @Override + public String toString() { + return "ZipShort value: " + value; + } +} diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipUtil.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipUtil.java new file mode 100644 index 00000000..c25b8c7f --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/zip/ZipUtil.java @@ -0,0 +1,252 @@ +/* + * 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.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) { + byte[] result = new byte[4]; + toDosTime(t, result, 0); + return result; + } + + /** + * 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 + * @param buf the output buffer + * @param offset + * The offset within the output buffer of the first byte to be written. + * must be non-negative and no larger than <tt>buf.length-4</tt> + */ + public static void toDosTime(long t, byte[] buf, int offset) { + toDosTime(Calendar.getInstance(), t, buf, offset); + } + + static void toDosTime(Calendar c, long t, byte[] buf, int offset) { + c.setTimeInMillis(t); + + int year = c.get(Calendar.YEAR); + if (year < 1980) { + System.arraycopy(DOS_TIME_MIN, 0, buf, offset, DOS_TIME_MIN.length);// stop callers from changing the array + return; + } + 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); + ZipLong.putLong(value, buf, offset); + } + + /** + * 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); + cal.set(Calendar.MILLISECOND, 0); + // 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. + */ + static byte[] copy(byte[] from) { + if (from != null) { + byte[] to = new byte[from.length]; + System.arraycopy(from, 0, to, 0, to.length); + return to; + } + return null; + } + + /** + * 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); + } + } +} |