--- /dev/null
+/*
+ * 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>
+ *
+ */
+public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable {
+
+ private static final ZipShort HEADER_ID = new ZipShort(0x756E);
+
+ /**
+ * 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(4 // CRC
+ + 2 // Mode
+ + 4 // SizDev
+ + 2 // UID
+ + 2 // GID
+ + getLinkedFile().getBytes().length);
+ }
+
+ /**
+ * 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() - 4];
+ System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2);
+
+ byte[] linkArray = getLinkedFile().getBytes();
+ System.arraycopy(ZipLong.getBytes(linkArray.length),
+ 0, data, 2, 4);
+
+ 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);
+
+ crc.reset();
+ crc.update(data);
+ long checksum = crc.getValue();
+
+ byte[] result = new byte[data.length + 4];
+ System.arraycopy(ZipLong.getBytes(checksum), 0, result, 0, 4);
+ System.arraycopy(data, 0, result, 4, 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 - 4];
+ System.arraycopy(data, offset + 4, tmp, 0, length - 4);
+ 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);
+ 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);
+ }
+ 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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.Hashtable;
+import java.util.Vector;
+import java.util.zip.ZipException;
+
+/**
+ * ZipExtraField related methods
+ *
+ */
+public class ExtraFieldUtils {
+
+ /**
+ * Static registry of known extra fields.
+ *
+ * @since 1.1
+ */
+ private static Hashtable implementations;
+
+ static {
+ implementations = new Hashtable();
+ register(AsiExtraField.class);
+ register(JarMarker.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 approriate ExtraField, falls back to
+ * {@link UnrecognizedExtraField UnrecognizedExtraField}.
+ * @param headerId the header identifier
+ * @return an instance of the appropiate ExtraField
+ * @exception InstantiationException if unable to instantiate the class
+ * @exception IllegalAccessException if not allowed to instatiate the class
+ * @since 1.1
+ */
+ public static ZipExtraField createExtraField(ZipShort headerId)
+ throws InstantiationException, IllegalAccessException {
+ Class c = (Class) 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
+ * give data.
+ * @param data an array of bytes
+ * @return an array of ExtraFields
+ * @since 1.1
+ * @throws ZipException on error
+ */
+ public static ZipExtraField[] parse(byte[] data) throws ZipException {
+ Vector v = new Vector();
+ int start = 0;
+ while (start <= data.length - 4) {
+ ZipShort headerId = new ZipShort(data, start);
+ int length = (new ZipShort(data, start + 2)).getValue();
+ if (start + 4 + length > data.length) {
+ throw new ZipException("data starting at " + start
+ + " is in unknown format");
+ }
+ try {
+ ZipExtraField ze = createExtraField(headerId);
+ ze.parseFromLocalFileData(data, start + 4, length);
+ v.addElement(ze);
+ } catch (InstantiationException ie) {
+ throw new ZipException(ie.getMessage());
+ } catch (IllegalAccessException iae) {
+ throw new ZipException(iae.getMessage());
+ }
+ start += (length + 4);
+ }
+ if (start != data.length) { // array not exhausted
+ throw new ZipException("data starting at " + start
+ + " is in unknown format");
+ }
+
+ ZipExtraField[] result = new ZipExtraField[v.size()];
+ v.copyInto(result);
+ return 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) {
+ int sum = 4 * data.length;
+ for (int i = 0; i < data.length; i++) {
+ sum += data[i].getLocalFileDataLength().getValue();
+ }
+ byte[] result = new byte[sum];
+ int start = 0;
+ for (int i = 0; i < data.length; 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 + 4, local.length);
+ start += (local.length + 4);
+ }
+ 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) {
+ int sum = 4 * data.length;
+ for (int i = 0; i < data.length; i++) {
+ sum += data[i].getCentralDirectoryLength().getValue();
+ }
+ byte[] result = new byte[sum];
+ int start = 0;
+ for (int i = 0; i < data.length; 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 + 4, local.length);
+ start += (local.length + 4);
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * 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");
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.
+ *
+ */
+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;
+}
--- /dev/null
+/*
+ * 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 ZipExtraField {
+
+ /**
+ * 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 = 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 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 = 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 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);
+ }
+}
--- /dev/null
+/*
+ * 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.Vector;
+import java.util.zip.ZipException;
+
+/**
+ * Extension that adds better handling of extra fields and provides
+ * access to the internal and external file attributes.
+ *
+ */
+public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable {
+
+ private static final int PLATFORM_UNIX = 3;
+ private static final int PLATFORM_FAT = 0;
+
+ private int internalAttributes = 0;
+ private int platform = PLATFORM_FAT;
+ private long externalAttributes = 0;
+ private Vector/*<ZipExtraField>*/ extraFields = null;
+ private String name = null;
+
+ /**
+ * Creates a new zip entry with the specified name.
+ * @param name the name of the entry
+ * @since 1.1
+ */
+ public ZipEntry(String name) {
+ super(name);
+ }
+
+ /**
+ * Creates a new zip entry with fields taken from the specified zip entry.
+ * @param entry the entry to get fields from
+ * @since 1.1
+ * @throws ZipException on error
+ */
+ public ZipEntry(java.util.zip.ZipEntry entry) throws ZipException {
+ super(entry);
+ byte[] extra = entry.getExtra();
+ if (extra != null) {
+ setExtraFields(ExtraFieldUtils.parse(extra));
+ } else {
+ // initializes extra data to an empty byte array
+ setExtra();
+ }
+ }
+
+ /**
+ * Creates a new zip entry with fields taken from the specified zip entry.
+ * @param entry the entry to get fields from
+ * @throws ZipException on error
+ * @since 1.1
+ */
+ public ZipEntry(ZipEntry entry) throws ZipException {
+ this((java.util.zip.ZipEntry) entry);
+ setInternalAttributes(entry.getInternalAttributes());
+ setExternalAttributes(entry.getExternalAttributes());
+ setExtraFields(entry.getExtraFields());
+ }
+
+ /**
+ * @since 1.9
+ */
+ protected ZipEntry() {
+ super("");
+ }
+
+ /**
+ * Overwrite clone.
+ * @return a cloned copy of this ZipEntry
+ * @since 1.1
+ */
+ public Object clone() {
+ ZipEntry e = (ZipEntry) super.clone();
+
+ e.extraFields = extraFields != null ? (Vector) extraFields.clone() : null;
+ e.setInternalAttributes(getInternalAttributes());
+ e.setExternalAttributes(getExternalAttributes());
+ e.setExtraFields(getExtraFields());
+ return e;
+ }
+
+ /**
+ * 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(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(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(int mode) {
+ setExternalAttributes((mode << 16)
+ // MS-DOS read-only attribute
+ | ((mode & 0200) == 0 ? 1 : 0)
+ // MS-DOS directory flag
+ | (isDirectory() ? 0x10 : 0));
+ platform = PLATFORM_UNIX;
+ }
+
+ /**
+ * Unix permission.
+ * @return the unix permissions
+ * @since Ant 1.6
+ */
+ public int getUnixMode() {
+ return (int) ((getExternalAttributes() >> 16) & 0xFFFF);
+ }
+
+ /**
+ * Platform specification to put into the "version made
+ * by" part of the central file header.
+ *
+ * @return 0 (MS-DOS FAT) unless {@link #setUnixMode setUnixMode}
+ * has been called, in which case 3 (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(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(ZipExtraField[] fields) {
+ extraFields = new Vector();
+ for (int i = 0; i < fields.length; i++) {
+ extraFields.addElement(fields[i]);
+ }
+ setExtra();
+ }
+
+ /**
+ * Retrieves extra fields.
+ * @return an array of the extra fields
+ * @since 1.1
+ */
+ public ZipExtraField[] getExtraFields() {
+ if (extraFields == null) {
+ return new ZipExtraField[0];
+ }
+ ZipExtraField[] result = new ZipExtraField[extraFields.size()];
+ extraFields.copyInto(result);
+ return result;
+ }
+
+ /**
+ * Adds an extra fields - replacing an already present extra field
+ * of the same type.
+ * @param ze an extra field
+ * @since 1.1
+ */
+ public void addExtraField(ZipExtraField ze) {
+ if (extraFields == null) {
+ extraFields = new Vector();
+ }
+ ZipShort type = ze.getHeaderId();
+ boolean done = false;
+ for (int i = 0, fieldsSize = extraFields.size(); !done && i < fieldsSize; i++) {
+ if (((ZipExtraField) extraFields.elementAt(i)).getHeaderId().equals(type)) {
+ extraFields.setElementAt(ze, i);
+ done = true;
+ }
+ }
+ if (!done) {
+ extraFields.addElement(ze);
+ }
+ setExtra();
+ }
+
+ /**
+ * Remove an extra fields.
+ * @param type the type of extra field to remove
+ * @since 1.1
+ */
+ public void removeExtraField(ZipShort type) {
+ if (extraFields == null) {
+ extraFields = new Vector();
+ }
+ boolean done = false;
+ for (int i = 0, fieldsSize = extraFields.size(); !done && i < fieldsSize; i++) {
+ if (((ZipExtraField) extraFields.elementAt(i)).getHeaderId().equals(type)) {
+ extraFields.removeElementAt(i);
+ done = true;
+ }
+ }
+ if (!done) {
+ throw new java.util.NoSuchElementException();
+ }
+ setExtra();
+ }
+
+ /**
+ * Throws an Exception if extra data cannot be parsed into extra fields.
+ * @param extra an array of bytes to be parsed into extra fields
+ * @throws RuntimeException if the bytes cannot be parsed
+ * @since 1.1
+ * @throws RuntimeException on error
+ */
+ public void setExtra(byte[] extra) throws RuntimeException {
+ try {
+ setExtraFields(ExtraFieldUtils.parse(extra));
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ /**
+ * 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()));
+ }
+
+ /**
+ * Retrieves the extra data for the local file data.
+ * @return the extra data for local file
+ * @since 1.1
+ */
+ public byte[] getLocalFileDataExtra() {
+ byte[] extra = getExtra();
+ return extra != null ? extra : new byte[0];
+ }
+
+ /**
+ * Retrieves the extra data for the central directory.
+ * @return the central directory extra data
+ * @since 1.1
+ */
+ public byte[] getCentralDirectoryExtra() {
+ return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields());
+ }
+
+ /**
+ * 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
+ */
+ public void setComprSize(long size) {
+ setCompressedSize(size);
+ }
+
+ /**
+ * Get the name of the entry.
+ * @return the entry name
+ * @since 1.9
+ */
+ 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
+ */
+ public boolean isDirectory() {
+ return getName().endsWith("/");
+ }
+
+ /**
+ * Set the name of the entry.
+ * @param name the name to use
+ */
+ protected void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Get the hashCode of the entry.
+ * This uses the name as the hashcode.
+ * @return a hashcode.
+ * @since Ant 1.7
+ */
+ 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 equality method. In this case, the implementation returns 'this == o'
+ * which is basically the equals method of the Object class.
+ * @param o the object to compare to
+ * @return true if this object is the same as <code>o</code>
+ * @since Ant 1.7
+ */
+ public boolean equals(Object o) {
+ return (this == o);
+ }
+
+}
--- /dev/null
+/*
+ * 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 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;
+}
--- /dev/null
+/*
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+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.</p>
+ * <p>For the VAMSAS library, a new constructor was added to pass in
+ * existing RandomAccessFile directly. This allows the ZIPped data input
+ * from a file locked under the <code>java.nio.FileChannel.Lock</code>
+ * mechanism.</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 {
+
+ /**
+ * Maps ZipEntrys to Longs, recording the offsets of the local
+ * file headers.
+ */
+ private Hashtable entries = new Hashtable(509);
+
+ /**
+ * Maps String to ZipEntrys, name -> actual entry.
+ */
+ private Hashtable nameMap = new Hashtable(509);
+
+ 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 String encoding = null;
+
+ /**
+ * The actual data source.
+ */
+ private RandomAccessFile archive;
+
+ /**
+ * 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(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(String name) throws IOException {
+ this(new File(name), null);
+ }
+
+ /**
+ * Opens the given file for reading, assuming the specified
+ * encoding for file names.
+ *
+ * @param name name of the archive.
+ * @param encoding the encoding to use for file names
+ *
+ * @throws IOException if an error occurs while reading the file.
+ */
+ public ZipFile(String name, String encoding) throws IOException {
+ this(new File(name), encoding);
+ }
+
+ /**
+ * 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
+ *
+ * @throws IOException if an error occurs while reading the file.
+ */
+ public ZipFile(File f, String encoding) throws IOException {
+ this(new RandomAccessFile(f, "r"), encoding);
+ }
+ /**
+ * Read an archive from the given random access file
+ *
+ * @param rafile the archive as a readable random access file
+ * @throws IOException
+ */
+ public ZipFile(RandomAccessFile rafile) throws IOException
+ {
+ this (rafile, null);
+ }
+ /**
+ * Read an archive from the given random access file, assuming the specified
+ * encoding for file names.
+ *
+ * @param readablearchive the archive opened as a readable random access file
+ * @param encoding the encoding to use for file names
+ *
+ * @throws IOException if an error occurs while reading the file.
+ */
+ public ZipFile(RandomAccessFile readablearchive, String encoding) throws IOException
+ {
+ this.encoding = encoding;
+ archive = readablearchive;
+ try {
+ populateFromCentralDirectory();
+ resolveLocalFileHeaderData();
+ } catch (IOException e) {
+ try {
+ archive.close();
+ } catch (IOException e2) {
+ // swallow, throw the original exception instead
+ }
+ throw e;
+ }
+ }
+ /**
+ * 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 {
+ 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(ZipFile zipfile) {
+ if (zipfile != null) {
+ try {
+ zipfile.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+ }
+
+ /**
+ * Returns all entries.
+ * @return all entries as {@link ZipEntry} instances
+ */
+ public Enumeration getEntries() {
+ return entries.keys();
+ }
+
+ /**
+ * Returns a named entry - or <code>null</code> if no entry by
+ * that name exists.
+ * @param name name of the entry.
+ * @return the ZipEntry corresponding to the given name - or
+ * <code>null</code> if not present.
+ */
+ public ZipEntry getEntry(String name) {
+ return (ZipEntry) nameMap.get(name);
+ }
+
+ /**
+ * 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 zipenty
+ * @throws ZipException if the zipentry has an unsupported compression method
+ */
+ public InputStream getInputStream(ZipEntry ze)
+ throws IOException, ZipException {
+ OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze);
+ if (offsetEntry == null) {
+ return null;
+ }
+ long start = offsetEntry.dataOffset;
+ BoundedInputStream bis =
+ new BoundedInputStream(start, ze.getCompressedSize());
+ switch (ze.getMethod()) {
+ case ZipEntry.STORED:
+ return bis;
+ case ZipEntry.DEFLATED:
+ bis.addDummy();
+ return new InflaterInputStream(bis, new Inflater(true));
+ default:
+ throw new ZipException("Found unsupported compression method "
+ + ze.getMethod());
+ }
+ }
+
+ private static final int CFH_LEN =
+ /* version made by */ 2
+ /* version needed to extract */ + 2
+ /* general purpose bit flag */ + 2
+ /* compression method */ + 2
+ /* last mod file time */ + 2
+ /* last mod file date */ + 2
+ /* crc-32 */ + 4
+ /* compressed size */ + 4
+ /* uncompressed size */ + 4
+ /* filename length */ + 2
+ /* extra field length */ + 2
+ /* file comment length */ + 2
+ /* disk number start */ + 2
+ /* internal file attributes */ + 2
+ /* external file attributes */ + 4
+ /* relative offset of local header */ + 4;
+
+ /**
+ * 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>
+ */
+ private void populateFromCentralDirectory()
+ throws IOException {
+ positionAtCentralDirectory();
+
+ byte[] cfh = new byte[CFH_LEN];
+
+ byte[] signatureBytes = new byte[4];
+ archive.readFully(signatureBytes);
+ long sig = ZipLong.getValue(signatureBytes);
+ final long cfhSig = ZipLong.getValue(ZipOutputStream.CFH_SIG);
+ while (sig == cfhSig) {
+ archive.readFully(cfh);
+ int off = 0;
+ ZipEntry ze = new ZipEntry();
+
+ int versionMadeBy = ZipShort.getValue(cfh, off);
+ off += 2;
+ ze.setPlatform((versionMadeBy >> 8) & 0x0F);
+
+ off += 4; // skip version info and general purpose byte
+
+ ze.setMethod(ZipShort.getValue(cfh, off));
+ off += 2;
+
+ // FIXME this is actually not very cpu cycles friendly as we are converting from
+ // dos to java while the underlying Sun implementation will convert
+ // from java to dos time for internal storage...
+ long time = dosToJavaTime(ZipLong.getValue(cfh, off));
+ ze.setTime(time);
+ off += 4;
+
+ ze.setCrc(ZipLong.getValue(cfh, off));
+ off += 4;
+
+ ze.setCompressedSize(ZipLong.getValue(cfh, off));
+ off += 4;
+
+ ze.setSize(ZipLong.getValue(cfh, off));
+ off += 4;
+
+ int fileNameLen = ZipShort.getValue(cfh, off);
+ off += 2;
+
+ int extraLen = ZipShort.getValue(cfh, off);
+ off += 2;
+
+ int commentLen = ZipShort.getValue(cfh, off);
+ off += 2;
+
+ off += 2; // disk number
+
+ ze.setInternalAttributes(ZipShort.getValue(cfh, off));
+ off += 2;
+
+ ze.setExternalAttributes(ZipLong.getValue(cfh, off));
+ off += 4;
+
+ byte[] fileName = new byte[fileNameLen];
+ archive.readFully(fileName);
+ ze.setName(getString(fileName));
+
+
+ // LFH offset,
+ OffsetEntry offset = new OffsetEntry();
+ offset.headerOffset = ZipLong.getValue(cfh, off);
+ // data offset will be filled later
+ entries.put(ze, offset);
+
+ nameMap.put(ze.getName(), ze);
+
+ archive.skipBytes(extraLen);
+
+ byte[] comment = new byte[commentLen];
+ archive.readFully(comment);
+ ze.setComment(getString(comment));
+
+ archive.readFully(signatureBytes);
+ sig = ZipLong.getValue(signatureBytes);
+ }
+ }
+
+ private static final int MIN_EOCD_SIZE =
+ /* end of central dir signature */ 4
+ /* number of this disk */ + 2
+ /* number of the disk with the */
+ /* start of the central directory */ + 2
+ /* total number of entries in */
+ /* the central dir on this disk */ + 2
+ /* total number of entries in */
+ /* the central dir */ + 2
+ /* size of the central directory */ + 4
+ /* offset of start of central */
+ /* directory with respect to */
+ /* the starting disk number */ + 4
+ /* zipfile comment length */ + 2;
+
+ private static final int CFD_LOCATOR_OFFSET =
+ /* end of central dir signature */ 4
+ /* number of this disk */ + 2
+ /* number of the disk with the */
+ /* start of the central directory */ + 2
+ /* total number of entries in */
+ /* the central dir on this disk */ + 2
+ /* total number of entries in */
+ /* the central dir */ + 2
+ /* size of the central directory */ + 4;
+
+ /**
+ * Searches for the "End of central dir record", parses
+ * it and positions the stream at the first central directory
+ * record.
+ */
+ private void positionAtCentralDirectory()
+ throws IOException {
+ boolean found = false;
+ long off = archive.length() - MIN_EOCD_SIZE;
+ if (off >= 0) {
+ archive.seek(off);
+ byte[] sig = ZipOutputStream.EOCD_SIG;
+ int curr = archive.read();
+ while (curr != -1) {
+ if (curr == sig[0]) {
+ curr = archive.read();
+ if (curr == sig[1]) {
+ curr = archive.read();
+ if (curr == sig[2]) {
+ curr = archive.read();
+ if (curr == sig[3]) {
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+ archive.seek(--off);
+ curr = archive.read();
+ }
+ }
+ if (!found) {
+ throw new ZipException("archive is not a ZIP archive");
+ }
+ archive.seek(off + CFD_LOCATOR_OFFSET);
+ byte[] cfdOffset = new byte[4];
+ archive.readFully(cfdOffset);
+ archive.seek(ZipLong.getValue(cfdOffset));
+ }
+
+ /**
+ * 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 */ 4
+ /* version needed to extract */ + 2
+ /* general purpose bit flag */ + 2
+ /* compression method */ + 2
+ /* last mod file time */ + 2
+ /* last mod file date */ + 2
+ /* crc-32 */ + 4
+ /* compressed size */ + 4
+ /* uncompressed size */ + 4;
+
+ /**
+ * 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()
+ throws IOException {
+ Enumeration e = getEntries();
+ while (e.hasMoreElements()) {
+ ZipEntry ze = (ZipEntry) e.nextElement();
+ OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze);
+ long offset = offsetEntry.headerOffset;
+ archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
+ byte[] b = new byte[2];
+ archive.readFully(b);
+ int fileNameLen = ZipShort.getValue(b);
+ archive.readFully(b);
+ int extraFieldLen = ZipShort.getValue(b);
+ archive.skipBytes(fileNameLen);
+ byte[] localExtraData = new byte[extraFieldLen];
+ archive.readFully(localExtraData);
+ ze.setExtra(localExtraData);
+ /*dataOffsets.put(ze,
+ new Long(offset + LFH_OFFSET_FOR_FILENAME_LENGTH
+ + 2 + 2 + fileNameLen + extraFieldLen));
+ */
+ offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH
+ + 2 + 2 + fileNameLen + extraFieldLen;
+ }
+ }
+
+ /**
+ * Convert a DOS date/time field to a Date object.
+ *
+ * @param zipDosTime contains the stored DOS time.
+ * @return a Date instance corresponding to the given time.
+ */
+ protected static Date fromDosTime(ZipLong zipDosTime) {
+ long dosTime = zipDosTime.getValue();
+ return new Date(dosToJavaTime(dosTime));
+ }
+
+ /*
+ * Converts DOS time to Java time (number of milliseconds since epoch).
+ */
+ private static long dosToJavaTime(long dosTime) {
+ Calendar cal = Calendar.getInstance();
+ 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);
+ return cal.getTime().getTime();
+ }
+
+
+ /**
+ * Retrieve a String from the given bytes using the encoding set
+ * for this ZipFile.
+ *
+ * @param bytes the byte array to transform
+ * @return String obtained by using the given encoding
+ * @throws ZipException if the encoding cannot be recognized.
+ */
+ protected String getString(byte[] bytes) throws ZipException {
+ if (encoding == null) {
+ return new String(bytes);
+ } else {
+ try {
+ return new String(bytes, encoding);
+ } catch (UnsupportedEncodingException uee) {
+ throw new ZipException(uee.getMessage());
+ }
+ }
+ }
+
+ /**
+ * 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(long start, long remaining) {
+ this.remaining = remaining;
+ loc = start;
+ }
+
+ public int read() throws IOException {
+ if (remaining-- <= 0) {
+ if (addDummyByte) {
+ addDummyByte = false;
+ return 0;
+ }
+ return -1;
+ }
+ synchronized (archive) {
+ archive.seek(loc++);
+ return archive.read();
+ }
+ }
+
+ public int read(byte[] b, 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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.tools.zip;
+
+/**
+ * Utility class 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 long value;
+
+ /**
+ * 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[4];
+ result[0] = (byte) ((value & 0xFF));
+ result[1] = (byte) ((value & 0xFF00) >> 8);
+ result[2] = (byte) ((value & 0xFF0000) >> 16);
+ result[3] = (byte) ((value & 0xFF000000L) >> 24);
+ return result;
+ }
+
+ /**
+ * Helper method to get the value as a Java long from four bytes starting at given array offset
+ * @param bytes the array of bytes
+ * @param offset the offset to start
+ * @return the correspondanding Java long value
+ */
+ public static long getValue(byte[] bytes, int offset) {
+ long value = (bytes[offset + 3] << 24) & 0xFF000000L;
+ value += (bytes[offset + 2] << 16) & 0xFF0000;
+ value += (bytes[offset + 1] << 8) & 0xFF00;
+ value += (bytes[offset] & 0xFF);
+ 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 correspondanding 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
+ */
+ 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
+ */
+ public int hashCode() {
+ return (int) value;
+ }
+}
--- /dev/null
+/*
+ * 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.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Vector;
+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>
+ *
+ */
+public class ZipOutputStream extends FilterOutputStream {
+
+ /**
+ * 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;
+
+ /**
+ * Current entry.
+ *
+ * @since 1.1
+ */
+ private ZipEntry 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 Vector entries = new Vector();
+
+ /**
+ * CRC instance to avoid parsing DEFLATED data twice.
+ *
+ * @since 1.1
+ */
+ private CRC32 crc = new CRC32();
+
+ /**
+ * Count the bytes written to out.
+ *
+ * @since 1.1
+ */
+ private long written = 0;
+
+ /**
+ * Data for local header data
+ *
+ * @since 1.1
+ */
+ private long dataStart = 0;
+
+ /**
+ * Offset for CRC entry in the local file header data for the
+ * current entry starts here.
+ *
+ * @since 1.15
+ */
+ private long localDataStart = 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};
+
+ /**
+ * Holds the offsets of the LFH starts for each entry.
+ *
+ * @since 1.1
+ */
+ private Hashtable offsets = new Hashtable();
+
+ /**
+ * 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;
+
+ // CheckStyle:VisibilityModifier OFF - bc
+
+ /**
+ * This Deflater object is used for output.
+ *
+ * <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 Deflater def = new Deflater(level, true);
+
+ /**
+ * This buffer servers 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[512];
+
+ // CheckStyle:VisibilityModifier ON
+
+ /**
+ * Optional random access output.
+ *
+ * @since 1.14
+ */
+ private RandomAccessFile raf = null;
+
+ /**
+ * Creates a new ZIP OutputStream filtering the underlying stream.
+ * @param out the outputstream to zip
+ * @since 1.1
+ */
+ public ZipOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ /**
+ * 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);
+
+ try {
+ raf = new RandomAccessFile(file, "rw");
+ raf.setLength(0);
+ } catch (IOException e) {
+ if (raf != null) {
+ try {
+ raf.close();
+ } catch (IOException inner) {
+ // ignore
+ }
+ raf = null;
+ }
+ out = new FileOutputStream(file);
+ }
+ }
+
+ /**
+ * 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(String encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Finishs writing the contents and closes this as well as the
+ * underlying stream.
+ *
+ * @since 1.1
+ * @throws IOException on error
+ */
+ public void finish() throws IOException {
+ closeEntry();
+ cdOffset = written;
+ for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) {
+ writeCentralFileHeader((ZipEntry) entries.elementAt(i));
+ }
+ cdLength = written - cdOffset;
+ writeCentralDirectoryEnd();
+ offsets.clear();
+ entries.removeAllElements();
+ }
+
+ /**
+ * Writes all necessary data for this entry.
+ *
+ * @since 1.1
+ * @throws IOException on error
+ */
+ public void closeEntry() throws IOException {
+ if (entry == null) {
+ return;
+ }
+
+ long realCrc = crc.getValue();
+ crc.reset();
+
+ if (entry.getMethod() == DEFLATED) {
+ def.finish();
+ while (!def.finished()) {
+ deflate();
+ }
+
+ entry.setSize(adjustToLong(def.getTotalIn()));
+ entry.setCompressedSize(adjustToLong(def.getTotalOut()));
+ entry.setCrc(realCrc);
+
+ def.reset();
+
+ written += entry.getCompressedSize();
+ } else if (raf == null) {
+ if (entry.getCrc() != realCrc) {
+ throw new ZipException("bad CRC checksum for entry "
+ + entry.getName() + ": "
+ + Long.toHexString(entry.getCrc())
+ + " instead of "
+ + Long.toHexString(realCrc));
+ }
+
+ if (entry.getSize() != written - dataStart) {
+ throw new ZipException("bad size for entry "
+ + entry.getName() + ": "
+ + entry.getSize()
+ + " instead of "
+ + (written - dataStart));
+ }
+ } else { /* method is STORED and we used RandomAccessFile */
+ long size = written - dataStart;
+
+ entry.setSize(size);
+ entry.setCompressedSize(size);
+ entry.setCrc(realCrc);
+ }
+
+ // If random access output, write the local file header containing
+ // the correct CRC and compressed/uncompressed sizes
+ if (raf != null) {
+ long save = raf.getFilePointer();
+
+ raf.seek(localDataStart);
+ writeOut(ZipLong.getBytes(entry.getCrc()));
+ writeOut(ZipLong.getBytes(entry.getCompressedSize()));
+ writeOut(ZipLong.getBytes(entry.getSize()));
+ raf.seek(save);
+ }
+
+ writeDataDescriptor(entry);
+ entry = null;
+ }
+
+ /**
+ * Begin writing next entry.
+ * @param ze the entry to write
+ * @since 1.1
+ * @throws IOException on error
+ */
+ public void putNextEntry(ZipEntry ze) throws IOException {
+ closeEntry();
+
+ entry = ze;
+ entries.addElement(entry);
+
+ if (entry.getMethod() == -1) { // not specified
+ entry.setMethod(method);
+ }
+
+ if (entry.getTime() == -1) { // not specified
+ entry.setTime(System.currentTimeMillis());
+ }
+
+ // Size/CRC not required if RandomAccessFile is used
+ if (entry.getMethod() == STORED && raf == null) {
+ if (entry.getSize() == -1) {
+ throw new ZipException("uncompressed size is required for"
+ + " STORED method when not writing to a"
+ + " file");
+ }
+ if (entry.getCrc() == -1) {
+ throw new ZipException("crc checksum is required for STORED"
+ + " method when not writing to a file");
+ }
+ entry.setCompressedSize(entry.getSize());
+ }
+
+ if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
+ def.setLevel(level);
+ hasCompressionLevelChanged = false;
+ }
+ writeLocalFileHeader(entry);
+ }
+
+ /**
+ * Set the file comment.
+ * @param comment the comment
+ * @since 1.1
+ */
+ 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;
+ }
+
+ /**
+ * 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
+ */
+ public void write(byte[] b, int offset, int length) throws IOException {
+ if (entry.getMethod() == DEFLATED) {
+ if (length > 0) {
+ if (!def.finished()) {
+ def.setInput(b, offset, length);
+ while (!def.needsInput()) {
+ deflate();
+ }
+ }
+ }
+ } else {
+ writeOut(b, offset, length);
+ written += length;
+ }
+ crc.update(b, offset, length);
+ }
+
+ /**
+ * Writes a single byte to ZIP entry.
+ *
+ * <p>Delegates to the three arg method.</p>
+ * @param b the byte to write
+ * @since 1.14
+ * @throws IOException on error
+ */
+ public void write(int b) throws IOException {
+ byte[] buff = new byte[1];
+ buff[0] = (byte) (b & 0xff);
+ write(buff, 0, 1);
+ }
+
+ /**
+ * Closes this output stream and releases any system resources
+ * associated with the stream.
+ *
+ * @exception IOException if an I/O error occurs.
+ * @since 1.14
+ */
+ public void close() throws IOException {
+ finish();
+
+ if (raf != null) {
+ raf.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+ }
+
+ /**
+ * 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.
+ * @since 1.14
+ */
+ 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.getBytes(0X04034B50L);
+ /**
+ * data descriptor signature
+ *
+ * @since 1.1
+ */
+ protected static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L);
+ /**
+ * central file header signature
+ *
+ * @since 1.1
+ */
+ protected static final byte[] CFH_SIG = ZipLong.getBytes(0X02014B50L);
+ /**
+ * end of central dir signature
+ *
+ * @since 1.1
+ */
+ protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
+
+ /**
+ * 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) {
+ writeOut(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 {
+ offsets.put(ze, ZipLong.getBytes(written));
+
+ writeOut(LFH_SIG);
+ written += 4;
+
+ //store method in local variable to prevent multiple method calls
+ final int zipMethod = ze.getMethod();
+
+ // version needed to extract
+ // general purpose bit flag
+ if (zipMethod == DEFLATED && raf == null) {
+ // requires version 2 as we are going to store length info
+ // in the data descriptor
+ writeOut(ZipShort.getBytes(20));
+
+ // bit3 set to signal, we use a data descriptor
+ writeOut(ZipShort.getBytes(8));
+ } else {
+ writeOut(ZipShort.getBytes(10));
+ writeOut(ZERO);
+ }
+ written += 4;
+
+ // compression method
+ writeOut(ZipShort.getBytes(zipMethod));
+ written += 2;
+
+ // last mod. time and date
+ writeOut(toDosTime(ze.getTime()));
+ written += 4;
+
+ // CRC
+ // compressed length
+ // uncompressed length
+ localDataStart = written;
+ if (zipMethod == DEFLATED || raf != null) {
+ writeOut(LZERO);
+ writeOut(LZERO);
+ writeOut(LZERO);
+ } else {
+ writeOut(ZipLong.getBytes(ze.getCrc()));
+ writeOut(ZipLong.getBytes(ze.getSize()));
+ writeOut(ZipLong.getBytes(ze.getSize()));
+ }
+ written += 12;
+
+ // file name length
+ byte[] name = getBytes(ze.getName());
+ writeOut(ZipShort.getBytes(name.length));
+ written += 2;
+
+ // extra field length
+ byte[] extra = ze.getLocalFileDataExtra();
+ writeOut(ZipShort.getBytes(extra.length));
+ written += 2;
+
+ // file name
+ writeOut(name);
+ written += name.length;
+
+ // extra field
+ writeOut(extra);
+ written += extra.length;
+
+ dataStart = written;
+ }
+
+ /**
+ * 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;
+ }
+ writeOut(DD_SIG);
+ writeOut(ZipLong.getBytes(entry.getCrc()));
+ writeOut(ZipLong.getBytes(entry.getCompressedSize()));
+ writeOut(ZipLong.getBytes(entry.getSize()));
+ written += 16;
+ }
+
+ /**
+ * Writes the central file header entry.
+ * @param ze the entry to write
+ * @throws IOException on error
+ *
+ * @since 1.1
+ */
+ protected void writeCentralFileHeader(ZipEntry ze) throws IOException {
+ writeOut(CFH_SIG);
+ written += 4;
+
+ // version made by
+ writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
+ written += 2;
+
+ // version needed to extract
+ // general purpose bit flag
+ if (ze.getMethod() == DEFLATED && raf == null) {
+ // requires version 2 as we are going to store length info
+ // in the data descriptor
+ writeOut(ZipShort.getBytes(20));
+
+ // bit3 set to signal, we use a data descriptor
+ writeOut(ZipShort.getBytes(8));
+ } else {
+ writeOut(ZipShort.getBytes(10));
+ writeOut(ZERO);
+ }
+ written += 4;
+
+ // compression method
+ writeOut(ZipShort.getBytes(ze.getMethod()));
+ written += 2;
+
+ // last mod. time and date
+ writeOut(toDosTime(ze.getTime()));
+ written += 4;
+
+ // CRC
+ // compressed length
+ // uncompressed length
+ writeOut(ZipLong.getBytes(ze.getCrc()));
+ writeOut(ZipLong.getBytes(ze.getCompressedSize()));
+ writeOut(ZipLong.getBytes(ze.getSize()));
+ written += 12;
+
+ // file name length
+ byte[] name = getBytes(ze.getName());
+ writeOut(ZipShort.getBytes(name.length));
+ written += 2;
+
+ // extra field length
+ byte[] extra = ze.getCentralDirectoryExtra();
+ writeOut(ZipShort.getBytes(extra.length));
+ written += 2;
+
+ // file comment length
+ String comm = ze.getComment();
+ if (comm == null) {
+ comm = "";
+ }
+ byte[] commentB = getBytes(comm);
+ writeOut(ZipShort.getBytes(commentB.length));
+ written += 2;
+
+ // disk number start
+ writeOut(ZERO);
+ written += 2;
+
+ // internal file attributes
+ writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
+ written += 2;
+
+ // external file attributes
+ writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
+ written += 4;
+
+ // relative offset of LFH
+ writeOut((byte[]) offsets.get(ze));
+ written += 4;
+
+ // file name
+ writeOut(name);
+ written += name.length;
+
+ // extra field
+ writeOut(extra);
+ written += extra.length;
+
+ // file comment
+ writeOut(commentB);
+ written += commentB.length;
+ }
+
+ /**
+ * Writes the "End of central dir record".
+ * @throws IOException on error
+ *
+ * @since 1.1
+ */
+ protected void writeCentralDirectoryEnd() throws IOException {
+ writeOut(EOCD_SIG);
+
+ // disk numbers
+ writeOut(ZERO);
+ writeOut(ZERO);
+
+ // number of entries
+ byte[] num = ZipShort.getBytes(entries.size());
+ writeOut(num);
+ writeOut(num);
+
+ // length and location of CD
+ writeOut(ZipLong.getBytes(cdLength));
+ writeOut(ZipLong.getBytes(cdOffset));
+
+ // ZIP file comment
+ byte[] data = getBytes(comment);
+ writeOut(ZipShort.getBytes(data.length));
+ writeOut(data);
+ }
+
+ /**
+ * Smallest date/time ZIP can handle.
+ *
+ * @since 1.1
+ */
+ 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>
+ * @since 1.1
+ */
+ protected 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
+ * @since 1.26
+ */
+ protected static byte[] toDosTime(long t) {
+ Date time = new Date(t);
+ int year = time.getYear() + 1900;
+ if (year < 1980) {
+ return DOS_TIME_MIN;
+ }
+ int month = time.getMonth() + 1;
+ long value = ((year - 1980) << 25)
+ | (month << 21)
+ | (time.getDate() << 16)
+ | (time.getHours() << 11)
+ | (time.getMinutes() << 5)
+ | (time.getSeconds() >> 1);
+ return ZipLong.getBytes(value);
+ }
+
+ /**
+ * 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 {
+ if (encoding == null) {
+ return name.getBytes();
+ } else {
+ try {
+ return name.getBytes(encoding);
+ } catch (UnsupportedEncodingException uee) {
+ throw new ZipException(uee.getMessage());
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ protected static long adjustToLong(int i) {
+ if (i < 0) {
+ return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
+ } else {
+ return i;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.tools.zip;
+
+/**
+ * Utility class 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 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];
+ result[0] = (byte) (value & 0xFF);
+ result[1] = (byte) ((value & 0xFF00) >> 8);
+ return result;
+ }
+
+ /**
+ * 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 & 0xFF);
+ result[1] = (byte) ((value & 0xFF00) >> 8);
+ 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 correspondanding java int value
+ */
+ public static int getValue(byte[] bytes, int offset) {
+ int value = (bytes[offset + 1] << 8) & 0xFF00;
+ value += (bytes[offset] & 0xFF);
+ 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 correspondanding 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
+ */
+ 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
+ */
+ public int hashCode() {
+ return value;
+ }
+}