diff --git a/src/main/org/apache/tools/zip/ZipEntry.java b/src/main/org/apache/tools/zip/ZipEntry.java
index bd5ded6ae..672ad5e7c 100644
--- a/src/main/org/apache/tools/zip/ZipEntry.java
+++ b/src/main/org/apache/tools/zip/ZipEntry.java
@@ -66,7 +66,7 @@ import java.util.zip.ZipException;
* @author Stefan Bodewig
* @version $Revision$
*/
-public class ZipEntry extends java.util.zip.ZipEntry {
+public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable {
private static final int PLATFORM_UNIX = 3;
private static final int PLATFORM_FAT = 0;
@@ -75,6 +75,7 @@ public class ZipEntry extends java.util.zip.ZipEntry {
private int platform = PLATFORM_FAT;
private long externalAttributes = 0;
private Vector extraFields = new Vector();
+ private String name = null;
/**
* Creates a new zip entry with the specified name.
@@ -135,23 +136,50 @@ public class ZipEntry extends java.util.zip.ZipEntry {
setExtraFields(entry.getExtraFields());
}
+ /**
+ * @since 1.9
+ */
+ protected ZipEntry() {
+ super("");
+ }
+
/**
* Overwrite clone
*
* @since 1.1
*/
public Object clone() {
- ZipEntry e = null;
try {
- e = new ZipEntry((java.util.zip.ZipEntry) super.clone());
- } catch (Exception ex) {
- // impossible as extra data is in correct format
- ex.printStackTrace();
+ ZipEntry e = (ZipEntry) super.clone();
+
+ e.setName(getName());
+ e.setComment(getComment());
+ e.setMethod(getMethod());
+ e.setTime(getTime());
+ long size = getSize();
+ if (size > 0) {
+ e.setSize(size);
+ }
+ long cSize = getCompressedSize();
+ if (cSize > 0) {
+ e.setComprSize(cSize);
+ }
+ long crc = getCrc();
+ if (crc > 0) {
+ e.setCrc(crc);
+ }
+
+ e.extraFields = (Vector) extraFields.clone();
+ e.setInternalAttributes(getInternalAttributes());
+ e.setExternalAttributes(getExternalAttributes());
+ e.setExtraFields(getExtraFields());
+ return e;
+ } catch (Throwable t) {
+ // in JDK 1.1 ZipEntry is not Cloneable, so super.clone declares
+ // to throw CloneNotSupported - since JDK 1.2 it is overridden to
+ // not throw that exception
+ return null;
}
- e.setInternalAttributes(getInternalAttributes());
- e.setExternalAttributes(getExternalAttributes());
- e.setExtraFields(getExtraFields());
- return e;
}
/**
@@ -218,6 +246,13 @@ public class ZipEntry extends java.util.zip.ZipEntry {
return platform;
}
+ /**
+ * @since 1.9
+ */
+ protected void setPlatform(int platform) {
+ this.platform = platform;
+ }
+
/**
* Replaces all currently attached extra fields with the new array.
*
@@ -362,6 +397,17 @@ public class ZipEntry extends java.util.zip.ZipEntry {
return super.getCompressedSize();
}
+ /**
+ * @since 1.9
+ */
+ public String getName() {
+ return name == null ? super.getName() : name;
+ }
+
+ protected void setName(String name) {
+ this.name = name;
+ }
+
/**
* Helper for JDK 1.1
*
diff --git a/src/main/org/apache/tools/zip/ZipFile.java b/src/main/org/apache/tools/zip/ZipFile.java
new file mode 100644
index 000000000..29ed4f21e
--- /dev/null
+++ b/src/main/org/apache/tools/zip/ZipFile.java
@@ -0,0 +1,417 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2003 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 "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
+ * java.util.ZipFile
.
+ *
+ *
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
+ * org.apache.tools.zip.ZipEntry
instead of
+ * java.util.zip.ZipEntry
.
It doesn't extend java.util.zip.ZipFile
as it would
+ * have to reimplement all methods anyway. Like
+ * java.util.ZipFile
, it uses RandomAccessFile under the
+ * covers and supports compressed and uncompressed entries.
The method signatures mimic the ones of
+ * java.util.zip.ZipFile
, with a couple of exceptions:
+ *
+ *
org.apache.tools.zip.ZipEntry
instances.For a list of possible values see http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html. + * Defaults to the platform's default character encoding.
+ */ + 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. + */ + public ZipFile(File f) throws IOException, ZipException { + this(f, null); + } + + /** + * Opens the given file for reading, assuming the platform's + * native encoding for file names. + */ + public ZipFile(String name) throws IOException, ZipException { + this(new File(name), null); + } + + /** + * Opens the given file for reading, assuming the specified + * encoding for file names. + */ + public ZipFile(String name, String encoding) + throws IOException, ZipException { + this(new File(name), encoding); + } + + /** + * Opens the given file for reading, assuming the specified + * encoding for file names. + */ + public ZipFile(File f, String encoding) + throws IOException, ZipException { + this.encoding = encoding; + archive = new RandomAccessFile(f, "r"); + populateFromCentralDirectory(); + resolveLocalFileHeaderData(); + } + + /** + * 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. + */ + public void close() throws IOException { + archive.close(); + } + + /** + * Returns all entries as {@link org.apache.tools.ant.ZipEntry + * ZipEntry} instances. + */ + public Enumeration getEntries() { + return entries.keys(); + } + + /** + * Returns a named entry - ornull
if no entry by
+ * that name exists.
+ */
+ public ZipEntry getEntry(String name) {
+ return (ZipEntry) nameMap.get(name);
+ }
+
+ /**
+ * Returns an InputStream for reading the contents of the given entry.
+ */
+ public InputStream getInputStream(ZipEntry ze)
+ throws IOException, ZipException {
+ return null;
+ }
+
+ 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 interal tables with ZipEntry instances.
+ *
+ * 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.
+ */ + private void populateFromCentralDirectory() + throws IOException, ZipException { + positionAtCentralDirectory(); + + byte[] cfh = new byte[CFH_LEN]; + + byte[] signatureBytes = new byte[4]; + archive.readFully(signatureBytes); + ZipLong sig = new ZipLong(signatureBytes); + while (sig.equals(ZipOutputStream.CFH_SIG)) { + archive.readFully(cfh); + int off = 0; + ZipEntry ze = new ZipEntry(); + + ZipShort versionMadeBy = new ZipShort(cfh, off); + off += 2; + ze.setPlatform((versionMadeBy.getValue() >> 8) & 0x0F); + + off += 4;// skip version info and general purpose byte + + ze.setMethod((new ZipShort(cfh, off)).getValue()); + off += 2; + + ze.setTime(fromDosTime(new ZipLong(cfh, off)).getTime()); + off += 4; + + ze.setCrc((new ZipLong(cfh, off)).getValue()); + off += 4; + + ze.setCompressedSize((new ZipLong(cfh, off)).getValue()); + off += 4; + + ze.setSize((new ZipLong(cfh, off)).getValue()); + off += 4; + + int fileNameLen = (new ZipShort(cfh, off)).getValue(); + off += 2; + + int extraLen = (new ZipShort(cfh, off)).getValue(); + off += 2; + + int commentLen = (new ZipShort(cfh, off)).getValue(); + off += 2; + + off += 2; // disk number + + ze.setInternalAttributes((new ZipShort(cfh, off)).getValue()); + off += 2; + + ze.setExternalAttributes((new ZipLong(cfh, off)).getValue()); + off += 4; + + // LFH offset + entries.put(ze, new Long((new ZipLong(cfh, off)).getValue())); + + byte[] fileName = new byte[fileNameLen]; + archive.readFully(fileName); + ze.setName(getString(fileName)); + + nameMap.put(ze.getName(), ze); + + archive.skipBytes(extraLen); + + byte[] comment = new byte[commentLen]; + archive.readFully(comment); + ze.setComment(getString(comment)); + + archive.readFully(signatureBytes); + sig = new ZipLong(signatureBytes); + } + } + + /** + * Searches for the first occurence of the central file header + * signature. + */ + private void positionAtCentralDirectory() + throws IOException, ZipException { + archive.seek(0); + int off = 0; + byte[] sig = ZipOutputStream.CFH_SIG.getBytes(); + int curr = archive.read(); + boolean found = false; + 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); + } else { + off++; + } + curr = archive.read(); + } + if (!found) { + throw new ZipException("archive is not a ZIP archive"); + } + archive.seek(off); + } + + /** + * 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. + * + *Also records the offsets for the data to read from the + * entries.
+ */ + private void resolveLocalFileHeaderData() + throws IOException { + Enumeration enum = getEntries(); + while (enum.hasMoreElements()) { + ZipEntry ze = (ZipEntry) enum.nextElement(); + long offset = ((Long) entries.get(ze)).longValue(); + archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH); + byte[] b = new byte[2]; + archive.readFully(b); + int fileNameLen = (new ZipShort(b)).getValue(); + archive.readFully(b); + int extraFieldLen = (new ZipShort(b)).getValue(); + 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)); + } + } + + /** + * Convert a DOS date/time field to a Date object. + */ + protected static Date fromDosTime(ZipLong l) { + long dosTime = l.getValue(); + 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, (int) (dosTime >> 11) & 0x1f); + cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f); + cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e); + return cal.getTime(); + } + + /** + * Retrieve a String from the given bytes using the encoding set + * for this ZipFile. + */ + 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()); + } + } + } + +}