From ec5e77ad322f8dc0d29990bd0dbe9375a71082f7 Mon Sep 17 00:00:00 2001 From: Stefan Bodewig Date: Wed, 2 Jul 2003 14:36:14 +0000 Subject: [PATCH] Initial support for reading of ZIP files - uncomplete as you cannot get an InputStream to the ZipEntrys yet. Will be needed to fix PR 10504 as well as to preserve permissions when updating an archive (no PR yet). git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@274742 13f79535-47bb-0310-9956-ffa450edef68 --- src/main/org/apache/tools/zip/ZipEntry.java | 66 +++- src/main/org/apache/tools/zip/ZipFile.java | 417 ++++++++++++++++++++ 2 files changed, 473 insertions(+), 10 deletions(-) create mode 100644 src/main/org/apache/tools/zip/ZipFile.java 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()); + } + } + } + +}