/* * net.sf.jazzlib.ZipOutputStream Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU * Classpath. GNU Classpath is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2, or (at your * option) any later version. GNU Classpath is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. You should have received a copy of the GNU General Public * License along with GNU Classpath; see the file COPYING. If not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. Linking this library statically or dynamically * with other modules is making a combined work based on this library. Thus, the terms and conditions of the * GNU General Public License cover the whole combination. As a special exception, the copyright holders of * this library give you permission to link this library with independent modules to produce an executable, * regardless of the license terms of these independent modules, and to copy and distribute the resulting * executable under terms of your choice, provided that you also meet, for each linked independent module, the * terms and conditions of the license of that module. An independent module is a module which is not derived * from or based on this library. If you modify this library, you may extend this exception to your version of * the library, but you are not obligated to do so. If you do not wish to do so, delete this exception * statement from your version. */ package ilarkesto.io.zip; import java.io.IOException; import java.io.OutputStream; import java.util.Enumeration; import java.util.Vector; /** * This is a FilterOutputStream that writes the files into a zip archive one after another. It has a special * method to start a new zip entry. The zip entries contains information about the file name size, compressed * size, CRC, etc. It includes support for STORED and DEFLATED entries. This class is not thread safe. * * @author Jochen Hoenicke */ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { private Vector entries = new Vector(); private CRC32 crc = new CRC32(); private ZipEntry curEntry = null; private int curMethod; private int size; private int offset = 0; private byte[] zipComment = new byte[0]; private int defaultMethod = DEFLATED; /** * Our Zip version is hard coded to 1.0 resp. 2.0 */ private final static int ZIP_STORED_VERSION = 10; private final static int ZIP_DEFLATED_VERSION = 20; /** * Compression method. This method doesn't compress at all. */ public final static int STORED = 0; /** * Compression method. This method uses the Deflater. */ public final static int DEFLATED = 8; /** * Creates a new Zip output stream, writing a zip archive. * * @param out * the output stream to which the zip archive is written. */ public ZipOutputStream(OutputStream out) { super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); } /** * Set the zip file comment. * * @param comment * the comment. * @exception IllegalArgumentException * if encoding of comment is longer than 0xffff bytes. */ public void setComment(String comment) { byte[] commentBytes; commentBytes = comment.getBytes(); if (commentBytes.length > 0xffff) throw new IllegalArgumentException("Comment too long."); zipComment = commentBytes; } /** * Sets default compression method. If the Zip entry specifies another method its method takes precedence. * * @param method * the method. * @exception IllegalArgumentException * if method is not supported. * @see #STORED * @see #DEFLATED */ public void setMethod(int method) { if (method != STORED && method != DEFLATED) throw new IllegalArgumentException("Method not supported."); defaultMethod = method; } /** * Sets default compression level. The new level will be activated immediately. * * @exception IllegalArgumentException * if level is not supported. * @see Deflater */ public void setLevel(int level) { def.setLevel(level); } /** * Write an unsigned short in little endian byte order. */ private final void writeLeShort(int value) throws IOException { out.write(value & 0xff); out.write((value >> 8) & 0xff); } /** * Write an int in little endian byte order. */ private final void writeLeInt(int value) throws IOException { writeLeShort(value); writeLeShort(value >> 16); } /** * Starts a new Zip entry. It automatically closes the previous entry if present. If the compression * method is stored, the entry must have a valid size and crc, otherwise all elements (except name) are * optional, but must be correct if present. If the time is not set in the entry, the current time is * used. * * @param entry * the entry. * @exception IOException * if an I/O error occured. * @exception ZipException * if stream was finished. */ public void putNextEntry(ZipEntry entry) throws IOException { if (entries == null) throw new ZipException("ZipOutputStream was finished"); int method = entry.getMethod(); int flags = 0; if (method == -1) method = defaultMethod; if (method == STORED) { if (entry.getCompressedSize() >= 0) { if (entry.getSize() < 0) entry.setSize(entry.getCompressedSize()); else if (entry.getSize() != entry.getCompressedSize()) throw new ZipException("Method STORED, but compressed size != size"); } else entry.setCompressedSize(entry.getSize()); if (entry.getSize() < 0) throw new ZipException("Method STORED, but size not set"); if (entry.getCrc() < 0) throw new ZipException("Method STORED, but crc not set"); } else if (method == DEFLATED) { if (entry.getCompressedSize() < 0 || entry.getSize() < 0 || entry.getCrc() < 0) flags |= 8; } if (curEntry != null) closeEntry(); if (entry.getTime() < 0) entry.setTime(System.currentTimeMillis()); entry.flags = flags; entry.offset = offset; entry.setMethod(method); curMethod = method; /* Write the local file header */ writeLeInt(LOCSIG); writeLeShort(method == STORED ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION); writeLeShort(flags); writeLeShort(method); writeLeInt(entry.getDOSTime()); if ((flags & 8) == 0) { writeLeInt((int) entry.getCrc()); writeLeInt((int) entry.getCompressedSize()); writeLeInt((int) entry.getSize()); } else { writeLeInt(0); writeLeInt(0); writeLeInt(0); } byte[] name = entry.getName().getBytes(); if (name.length > 0xffff) throw new ZipException("Name too long."); byte[] extra = entry.getExtra(); if (extra == null) extra = new byte[0]; writeLeShort(name.length); writeLeShort(extra.length); out.write(name); out.write(extra); offset += LOCHDR + name.length + extra.length; /* Activate the entry. */ curEntry = entry; crc.reset(); if (method == DEFLATED) def.reset(); size = 0; } /** * Closes the current entry. * * @exception IOException * if an I/O error occured. * @exception ZipException * if no entry is active. */ public void closeEntry() throws IOException { if (curEntry == null) throw new ZipException("No open entry"); /* First finish the deflater, if appropriate */ if (curMethod == DEFLATED) super.finish(); int csize = curMethod == DEFLATED ? def.getTotalOut() : size; if (curEntry.getSize() < 0) curEntry.setSize(size); else if (curEntry.getSize() != size) throw new ZipException("size was " + size + ", but I expected " + curEntry.getSize()); if (curEntry.getCompressedSize() < 0) curEntry.setCompressedSize(csize); else if (curEntry.getCompressedSize() != csize) throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.getSize()); if (curEntry.getCrc() < 0) curEntry.setCrc(crc.getValue()); else if (curEntry.getCrc() != crc.getValue()) throw new ZipException("crc was " + Long.toHexString(crc.getValue()) + ", but I expected " + Long.toHexString(curEntry.getCrc())); offset += csize; /* Now write the data descriptor entry if needed. */ if (curMethod == DEFLATED && (curEntry.flags & 8) != 0) { writeLeInt(EXTSIG); writeLeInt((int) curEntry.getCrc()); writeLeInt((int) curEntry.getCompressedSize()); writeLeInt((int) curEntry.getSize()); offset += EXTHDR; } entries.addElement(curEntry); curEntry = null; } /** * Writes the given buffer to the current entry. * * @exception IOException * if an I/O error occured. * @exception ZipException * if no entry is active. */ public void write(byte[] b, int off, int len) throws IOException { if (curEntry == null) throw new ZipException("No open entry."); switch (curMethod) { case DEFLATED: super.write(b, off, len); break; case STORED: out.write(b, off, len); break; } crc.update(b, off, len); size += len; } /** * Finishes the stream. This will write the central directory at the end of the zip file and flush the * stream. * * @exception IOException * if an I/O error occured. */ public void finish() throws IOException { if (entries == null) return; if (curEntry != null) closeEntry(); int numEntries = 0; int sizeEntries = 0; Enumeration enume = entries.elements(); while (enume.hasMoreElements()) { ZipEntry entry = (ZipEntry) enume.nextElement(); int method = entry.getMethod(); writeLeInt(CENSIG); writeLeShort(method == STORED ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION); writeLeShort(method == STORED ? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION); writeLeShort(entry.flags); writeLeShort(method); writeLeInt(entry.getDOSTime()); writeLeInt((int) entry.getCrc()); writeLeInt((int) entry.getCompressedSize()); writeLeInt((int) entry.getSize()); byte[] name = entry.getName().getBytes(); if (name.length > 0xffff) throw new ZipException("Name too long."); byte[] extra = entry.getExtra(); if (extra == null) extra = new byte[0]; String strComment = entry.getComment(); byte[] comment = strComment != null ? strComment.getBytes() : new byte[0]; if (comment.length > 0xffff) throw new ZipException("Comment too long."); writeLeShort(name.length); writeLeShort(extra.length); writeLeShort(comment.length); writeLeShort(0); /* disk number */ writeLeShort(0); /* internal file attr */ writeLeInt(0); /* external file attr */ writeLeInt(entry.offset); out.write(name); out.write(extra); out.write(comment); numEntries++; sizeEntries += CENHDR + name.length + extra.length + comment.length; } writeLeInt(ENDSIG); writeLeShort(0); /* disk number */ writeLeShort(0); /* disk with start of central dir */ writeLeShort(numEntries); writeLeShort(numEntries); writeLeInt(sizeEntries); writeLeInt(offset); writeLeShort(zipComment.length); out.write(zipComment); out.flush(); entries = null; } }