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 + * . + */ + +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.ZipException; + +/** + * Replacement for 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: + * + *

+ * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipFile { + + /** + * Maps ZipEntrys to Longs, recording the offsets of the local + * file headers. + */ + private Hashtable entries = new Hashtable(); + + /** + * Maps String to ZipEntrys, name -> actual entry. + */ + private Hashtable nameMap = new Hashtable(); + + /** + * Maps ZipEntrys to Longs, recording the offsets of the actual file data. + */ + private Hashtable dataOffsets = new Hashtable(); + + /** + * The encoding to use for filenames and the file comment. + * + *

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 - or null 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()); + } + } + } + +}