diff --git a/WHATSNEW b/WHATSNEW
index 1771d11a5..f6fe64fa0 100644
--- a/WHATSNEW
+++ b/WHATSNEW
@@ -10,6 +10,9 @@ Changes that could break older environments:
rmic to use a new compiler a lot easier but may break custom
versions of this task that rely on the old implementation.
+* several Zip methods have changed their signature as we now use a Zip
+ package of our own that handles Unix permissions for directories.
+
Other changes:
--------------
diff --git a/src/main/org/apache/tools/ant/taskdefs/Ear.java b/src/main/org/apache/tools/ant/taskdefs/Ear.java
index a52e6b676..cac185cab 100644
--- a/src/main/org/apache/tools/ant/taskdefs/Ear.java
+++ b/src/main/org/apache/tools/ant/taskdefs/Ear.java
@@ -55,10 +55,10 @@ package org.apache.tools.ant.taskdefs;
import org.apache.tools.ant.*;
import org.apache.tools.ant.types.ZipFileSet;
+import org.apache.tools.zip.*;
import java.io.*;
import java.util.Vector;
-import java.util.zip.*;
/**
* Creates a EAR archive. Based on WAR task
diff --git a/src/main/org/apache/tools/ant/taskdefs/Jar.java b/src/main/org/apache/tools/ant/taskdefs/Jar.java
index d69dd4289..8134e6661 100644
--- a/src/main/org/apache/tools/ant/taskdefs/Jar.java
+++ b/src/main/org/apache/tools/ant/taskdefs/Jar.java
@@ -56,9 +56,9 @@ package org.apache.tools.ant.taskdefs;
import org.apache.tools.ant.*;
import org.apache.tools.ant.types.ZipFileSet;
+import org.apache.tools.zip.*;
import java.io.*;
-import java.util.zip.*;
/**
* Creates a JAR archive.
diff --git a/src/main/org/apache/tools/ant/taskdefs/War.java b/src/main/org/apache/tools/ant/taskdefs/War.java
index 98f23bcde..7e12d545e 100644
--- a/src/main/org/apache/tools/ant/taskdefs/War.java
+++ b/src/main/org/apache/tools/ant/taskdefs/War.java
@@ -56,10 +56,10 @@ package org.apache.tools.ant.taskdefs;
import org.apache.tools.ant.*;
import org.apache.tools.ant.types.ZipFileSet;
+import org.apache.tools.zip.*;
import java.io.*;
import java.util.Vector;
-import java.util.zip.*;
/**
* Creates a WAR archive.
diff --git a/src/main/org/apache/tools/ant/taskdefs/Zip.java b/src/main/org/apache/tools/ant/taskdefs/Zip.java
index 2acbf56d6..4536406e4 100644
--- a/src/main/org/apache/tools/ant/taskdefs/Zip.java
+++ b/src/main/org/apache/tools/ant/taskdefs/Zip.java
@@ -59,10 +59,12 @@ import java.util.Hashtable;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Vector;
-import java.util.zip.*;
+import java.util.zip.CRC32;
+import java.util.zip.ZipInputStream;
import org.apache.tools.ant.*;
import org.apache.tools.ant.types.*;
import org.apache.tools.ant.util.*;
+import org.apache.tools.zip.*;
/**
* Create a ZIP archive.
@@ -280,7 +282,7 @@ public class Zip extends MatchingTask {
try {
in = new ZipInputStream(new FileInputStream(zipSrc));
- while ((entry = in.getNextEntry()) != null) {
+ while ((entry = new ZipEntry(in.getNextEntry())) != null) {
String vPath = entry.getName();
if (zipScanner.match(vPath)) {
addParentDirs(null, vPath, zOut, prefix);
@@ -414,6 +416,10 @@ public class Zip extends MatchingTask {
ze.setMethod (ZipEntry.STORED);
// This is faintly ridiculous:
ze.setCrc (emptyCrc);
+
+ // this is 040775 | MS-DOS directory flag in reverse byte order
+ ze.setExternalAttributes(0x41FD0010L);
+
zOut.putNextEntry (ze);
}
diff --git a/src/main/org/apache/tools/zip/AsiExtraField.java b/src/main/org/apache/tools/zip/AsiExtraField.java
new file mode 100644
index 000000000..111c6d905
--- /dev/null
+++ b/src/main/org/apache/tools/zip/AsiExtraField.java
@@ -0,0 +1,366 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2001 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Ant", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ *
This class uses the ASi extra field in the format: + *
+ * 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 + *+ * taken from appnote.iz (Info-ZIP note, 981119) found at ftp://ftp.uu.net/pub/archiving/zip/doc/ + + * + *
Short is two bytes and Long is four bytes in big endian byte and + * word order, device numbers are currently not supported.
+ * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { + + private final static 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. + * + *empty string - if entry is not a symbolic link.
+ * + * @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(); + + public AsiExtraField() { + } + + /** + * The Header-ID. + * + * @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. + * + * @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. + * + * @since 1.1 + */ + public ZipShort getCentralDirectoryLength() { + return getLocalFileDataLength(); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @since 1.1 + */ + public byte[] getLocalFileDataData() { + // CRC will be added later + byte[] data = new byte[getLocalFileDataLength().getValue() - 4]; + System.arraycopy((new ZipShort(getMode())).getBytes(), 0, data, 0, 2); + + byte[] linkArray = getLinkedFile().getBytes(); + System.arraycopy((new ZipLong(linkArray.length)).getBytes(), + 0, data, 2, 4); + + System.arraycopy((new ZipShort(getUserId())).getBytes(), + 0, data, 6, 2); + System.arraycopy((new ZipShort(getGroupId())).getBytes(), + 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((new ZipLong(checksum)).getBytes(), 0, result, 0, 4); + System.arraycopy(data, 0, result, 4, data.length); + return result; + } + + /** + * Delegate to local file data. + * + * @since 1.1 + */ + public byte[] getCentralDirectoryData() { + return getLocalFileDataData(); + } + + /** + * Set the user id. + * + * @since 1.1 + */ + public void setUserId(int uid) { + this.uid = uid; + } + + /** + * Get the user id. + * + * @since 1.1 + */ + public int getUserId() { + return uid; + } + + /** + * Set the group id. + * + * @since 1.1 + */ + public void setGroupId(int gid) { + this.gid = gid; + } + + /** + * Get 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? + * + * @since 1.1 + */ + public boolean isLink() { + return getLinkedFile().length() != 0; + } + + /** + * File mode of this file. + * + * @since 1.1 + */ + public void setMode(int mode) { + this.mode = getMode(mode); + } + + /** + * File mode of this file. + * + * @since 1.1 + */ + public int getMode() { + return mode; + } + + /** + * Indicate whether this entry is a directory. + * + * @since 1.1 + */ + public void setDirectory(boolean dirFlag) { + this.dirFlag = dirFlag; + mode = getMode(mode); + } + + /** + * Is this entry a directory? + * + * @since 1.1 + */ + public boolean isDirectory() { + return dirFlag && !isLink(); + } + + /** + * Populate data from this array as if it was in local file data. + * + * @since 1.1 + */ + public void parseFromLocalFileData(byte[] data, int offset, int length) + throws ZipException { + + long givenChecksum = (new ZipLong(data, offset)).getValue(); + 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 = (new ZipShort(tmp, 0)).getValue(); + byte[] linkArray = new byte[(int) (new ZipLong(tmp, 2)).getValue()]; + uid = (new ZipShort(tmp, 6)).getValue(); + gid = (new ZipShort(tmp, 8)).getValue(); + + 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. + * + * @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); + } + +} diff --git a/src/main/org/apache/tools/zip/ExtraFieldUtils.java b/src/main/org/apache/tools/zip/ExtraFieldUtils.java new file mode 100644 index 000000000..005e98809 --- /dev/null +++ b/src/main/org/apache/tools/zip/ExtraFieldUtils.java @@ -0,0 +1,203 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + *The given class must have a no-arg constructor and implement + * the {@link ZipExtraField ZipExtraField interface}.
+ * + * @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}. + * + * @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. + * + * @since 1.1 + */ + 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. + * + * @since 1.1 + */ + public static byte[] mergeLocalFileDataData(ZipExtraField[] data) { + int sum = 4*data.length; + for (int i=0; iAssumes local file data and central directory entries are + * identical - unless told the opposite.
+ * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class UnrecognizedExtraField implements ZipExtraField { + + /** + * The Header-ID. + * + * @since 1.1 + */ + private ZipShort headerId; + + public void setHeaderId(ZipShort headerId) { + this.headerId = headerId; + } + + public ZipShort getHeaderId() {return headerId;} + + /** + * Extra field data in local file data - without + * Header-ID or length specifier. + * + * @since 1.1 + */ + private byte[] localData; + + public void setLocalFileDataData(byte[] data) { + localData = data; + } + + public ZipShort getLocalFileDataLength() { + return new ZipShort(localData.length); + } + + public byte[] getLocalFileDataData() {return localData;} + + /** + * Extra field data in central directory - without + * Header-ID or length specifier. + * + * @since 1.1 + */ + private byte[] centralData; + + public void setCentralDirectoryData(byte[] data) { + centralData = data; + } + + public ZipShort getCentralDirectoryLength() { + if (centralData != null) { + return new ZipShort(centralData.length); + } + return getLocalFileDataLength(); + } + + public byte[] getCentralDirectoryData() { + if (centralData != null) { + return centralData; + } + return getLocalFileDataData(); + } + + public void parseFromLocalFileData(byte[] data, int offset, int length) { + byte[] tmp = new byte[length]; + System.arraycopy(data, offset, tmp, 0, length); + setLocalFileDataData(tmp); + } +} diff --git a/src/main/org/apache/tools/zip/ZipEntry.java b/src/main/org/apache/tools/zip/ZipEntry.java new file mode 100644 index 000000000..46bae0be3 --- /dev/null +++ b/src/main/org/apache/tools/zip/ZipEntry.java @@ -0,0 +1,272 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + *Extra fields usually apper 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 write the local file data at both places.
+ * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface ZipExtraField { + + /** + * The Header-ID. + * + * @since 1.1 + */ + public ZipShort getHeaderId(); + + /** + * Length of the extra field in the local file data - without + * Header-ID or length specifier. + * + * @since 1.1 + */ + public ZipShort getLocalFileDataLength(); + + /** + * Length of the extra field in the central directory - without + * Header-ID or length specifier. + * + * @since 1.1 + */ + public ZipShort getCentralDirectoryLength(); + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * + * @since 1.1 + */ + public byte[] getLocalFileDataData(); + + /** + * The actual data to put central directory - without Header-ID or + * length specifier. + * + * @since 1.1 + */ + public byte[] getCentralDirectoryData(); + + /** + * Populate data from this array as if it was in local file data. + * + * @since 1.1 + */ + public void parseFromLocalFileData(byte[] data, int offset, int length) + throws ZipException; +} diff --git a/src/main/org/apache/tools/zip/ZipLong.java b/src/main/org/apache/tools/zip/ZipLong.java new file mode 100644 index 000000000..565bf03d7 --- /dev/null +++ b/src/main/org/apache/tools/zip/ZipLong.java @@ -0,0 +1,142 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + *This implementation will use a Data Descriptor to store size and + * CRC information for DEFLATED entries, this means, you don't need to + * calculate them yourself. Unfortunately this is not possible for + * the STORED method, here setting the CRC and uncompressed size + * information is required before {@link #putNextEntry putNextEntry} + * will be called.
+ * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipOutputStream extends DeflaterOutputStream { + + /** + * 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 = Deflater.DEFAULT_COMPRESSION; + + /** + * Default compression method for next entry. + * + * @since 1.1 + */ + private int method = 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 current entry started here. + * + * @since 1.1 + */ + private long dataStart = 0; + + /** + * Start of central directory. + * + * @since 1.1 + */ + private ZipLong cdOffset = new ZipLong(0); + + /** + * Length of central directory. + * + * @since 1.1 + */ + private ZipLong cdLength = new ZipLong(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(); + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public static final int DEFLATED = ZipEntry.DEFLATED; + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public static final int STORED = ZipEntry.STORED; + + /** + * Creates a new ZIP OutputStream filtering the underlying stream. + * + * @since 1.1 + */ + public ZipOutputStream(OutputStream out) { + super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); + } + + /* + * Found out by experiment, that DeflaterOutputStream.close() + * will call finish() - so we don't need to override close + * ourselves. + */ + + /** + * Finishs writing the contents and closes this as well as the + * underlying stream. + * + * @since 1.1 + */ + public void finish() throws IOException { + closeEntry(); + cdOffset = new ZipLong(written); + for (int i=0; iDefault is DEFLATED.
+ * + * @since 1.1 + */ + public void setMethod(int method) { + this.method = method; + } + + /** + * Writes bytes to ZIP entry. + * + *Override is necessary to support STORED entries, as well as + * calculationg CRC automatically for DEFLATED entries.
+ */ + public void write(byte[] b, int offset, int length) throws IOException { + if (entry.getMethod() == DEFLATED) { + super.write(b, offset, length); + } else { + out.write(b, offset, length); + written += length; + } + crc.update(b, offset, length); + } + + /* + * Various ZIP constants + */ + /** + * local file header signature + * + * @since 1.1 + */ + protected static final ZipLong LFH_SIG = new ZipLong(0X04034B50L); + /** + * data descriptor signature + * + * @since 1.1 + */ + protected static final ZipLong DD_SIG = new ZipLong(0X08074B50L); + /** + * central file header signature + * + * @since 1.1 + */ + protected static final ZipLong CFH_SIG = new ZipLong(0X02014B50L); + /** + * end of central dir signature + * + * @since 1.1 + */ + protected static final ZipLong EOCD_SIG = new ZipLong(0X06054B50L); + + /** + * Writes the local file header entry + * + * @since 1.1 + */ + protected void writeLocalFileHeader(ZipEntry ze) throws IOException { + offsets.put(ze, new ZipLong(written)); + + out.write(LFH_SIG.getBytes()); + written += 4; + + // version needed to extract + // general purpose bit flag + if (ze.getMethod() == DEFLATED) { + // requires version 2 as we are going to store length info + // in the data descriptor + out.write((new ZipShort(20)).getBytes()); + + // bit3 set to signal, we use a data descriptor + out.write((new ZipShort(8)).getBytes()); + } else { + out.write((new ZipShort(10)).getBytes()); + out.write(ZERO); + } + written += 4; + + // compression method + out.write((new ZipShort(ze.getMethod())).getBytes()); + written += 2; + + // last mod. time and date + out.write(toDosTime(new Date(ze.getTime())).getBytes()); + written += 4; + + // CRC + // compressed length + // uncompressed length + if (ze.getMethod() == DEFLATED) { + out.write(LZERO); + out.write(LZERO); + out.write(LZERO); + } else { + out.write((new ZipLong(ze.getCrc())).getBytes()); + out.write((new ZipLong(ze.getSize())).getBytes()); + out.write((new ZipLong(ze.getSize())).getBytes()); + } + written += 12; + + // file name length + byte[] name = ze.getName().getBytes(); + out.write((new ZipShort(name.length)).getBytes()); + written += 2; + + // extra field length + byte[] extra = ze.getLocalFileDataExtra(); + out.write((new ZipShort(extra.length)).getBytes()); + written += 2; + + // file name + out.write(name); + written += name.length; + + // extra field + out.write(extra); + written += extra.length; + + dataStart = written; + } + + /** + * Writes the data descriptor entry + * + * @since 1.1 + */ + protected void writeDataDescriptor(ZipEntry ze) throws IOException { + if (ze.getMethod() != DEFLATED) { + return; + } + out.write(DD_SIG.getBytes()); + out.write((new ZipLong(entry.getCrc())).getBytes()); + out.write((new ZipLong(entry.getCompressedSize())).getBytes()); + out.write((new ZipLong(entry.getSize())).getBytes()); + written += 16; + } + + /** + * Writes the central file header entry + * + * @since 1.1 + */ + protected void writeCentralFileHeader(ZipEntry ze) throws IOException { + out.write(CFH_SIG.getBytes()); + written += 4; + + // version made by + out.write((new ZipShort(20)).getBytes()); + written += 2; + + // version needed to extract + // general purpose bit flag + if (ze.getMethod() == DEFLATED) { + // requires version 2 as we are going to store length info + // in the data descriptor + out.write((new ZipShort(20)).getBytes()); + + // bit3 set to signal, we use a data descriptor + out.write((new ZipShort(8)).getBytes()); + } else { + out.write((new ZipShort(10)).getBytes()); + out.write(ZERO); + } + written += 4; + + // compression method + out.write((new ZipShort(ze.getMethod())).getBytes()); + written += 2; + + // last mod. time and date + out.write(toDosTime(new Date(ze.getTime())).getBytes()); + written += 4; + + // CRC + // compressed length + // uncompressed length + out.write((new ZipLong(ze.getCrc())).getBytes()); + out.write((new ZipLong(ze.getCompressedSize())).getBytes()); + out.write((new ZipLong(ze.getSize())).getBytes()); + written += 12; + + // file name length + byte[] name = ze.getName().getBytes(); + out.write((new ZipShort(name.length)).getBytes()); + written += 2; + + // extra field length + byte[] extra = ze.getCentralDirectoryExtra(); + out.write((new ZipShort(extra.length)).getBytes()); + written += 2; + + // file comment length + String comm = ze.getComment(); + if (comm == null) { + comm = ""; + } + byte[] comment = comm.getBytes(); + out.write((new ZipShort(comment.length)).getBytes()); + written += 2; + + // disk number start + out.write(ZERO); + written += 2; + + // internal file attributes + out.write((new ZipShort(ze.getInternalAttributes())).getBytes()); + written += 2; + + // external file attributes + out.write((new ZipLong(ze.getExternalAttributes())).getBytes()); + written += 4; + + // relative offset of LFH + out.write(((ZipLong) offsets.get(ze)).getBytes()); + written += 4; + + // file name + out.write(name); + written += name.length; + + // extra field + out.write(extra); + written += extra.length; + + // file comment + out.write(comment); + written += comment.length; + } + + /** + * Writes the "End of central dir record" + * + * @since 1.1 + */ + protected void writeCentralDirectoryEnd() throws IOException { + out.write(EOCD_SIG.getBytes()); + + // disk numbers + out.write(ZERO); + out.write(ZERO); + + // number of entries + byte[] num = (new ZipShort(entries.size())).getBytes(); + out.write(num); + out.write(num); + + // length and location of CD + out.write(cdLength.getBytes()); + out.write(cdOffset.getBytes()); + + // ZIP file comment + byte[] data = comment.getBytes(); + out.write((new ZipShort(data.length)).getBytes()); + out.write(data); + } + + /** + * Smallest date/time ZIP can handle. + * + * @since 1.1 + */ + private static final ZipLong DOS_TIME_MIN = new ZipLong(0x00002100L); + + /** + * Convert a Date object to a DOS date/time field. + * + *Stolen from InfoZip's fileio.c