// package net.sf.zipme; class ZipOutputStream { private Vector entries=new Vector(); 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 static final int ZIP_STORED_VERSION=10; private static final int ZIP_DEFLATED_VERSION=20; /** * Table for calculating digits, used in Character, Long, and Integer. */ static final char[] digits={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; /** * 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; try { commentBytes=comment.getBytes("UTF-8"); } catch ( UnsupportedEncodingException uee) { throw new Error(uee.toString()); } 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 void writeLeShort( int value) throws IOException { out.write(value & 0xff); out.write((value >> 8) & 0xff); } /** * Write an int in little endian byte order. */ private void writeLeInt( int value) throws IOException { writeLeShort(value); writeLeShort(value >> 16); } /** * Write a long value as an int. Some of the zip constants * are declared as longs even though they fit perfectly well * into integers. */ private void writeLeInt( long value) throws IOException { writeLeInt((int)value); } /** * 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; 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; try { name=entry.getName().getBytes("UTF-8"); } catch ( UnsupportedEncodingException uee) { throw new Error(uee.toString()); } 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; curEntry=entry; this.hook41(); 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"); 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()); this.hook42(); offset+=csize; 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; } this.hook43(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 e=entries.elements(); while (e.hasMoreElements()) { ZipEntry entry=(ZipEntry)e.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; try { name=entry.getName().getBytes("UTF-8"); } catch (UnsupportedEncodingException uee) { throw new Error(uee.toString()); } if (name.length > 0xffff) throw new ZipException("Name too long."); byte[] extra=entry.getExtra(); if (extra == null) extra=new byte[0]; String str=entry.getComment(); byte[] comment; try { comment=str != null ? str.getBytes("UTF-8") : new byte[0]; } catch (UnsupportedEncodingException uee) { throw new Error(uee.toString()); } if (comment.length > 0xffff) throw new ZipException("Comment too long."); writeLeShort(name.length); writeLeShort(extra.length); writeLeShort(comment.length); writeLeShort(0); writeLeShort(0); writeLeInt(0); 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); writeLeShort(0); writeLeShort(numEntries); writeLeShort(numEntries); writeLeInt(sizeEntries); writeLeInt(offset); writeLeShort(zipComment.length); out.write(zipComment); out.flush(); entries=null; } /** * Converts the <code>long</code> to a <code>String</code> assuming it is * unsigned in base 16. * @param l the <code>long</code> to convert to <code>String</code> * @return the <code>String</code> representation of the argument */ private static String toHexString(long num){ if (num >= 0 && (int)num == num) return Integer.toHexString((int)num); int mask=(1 << 4) - 1; char[] buffer=new char[64]; int i=64; do { buffer[--i]=digits[(int)num & mask]; num>>>=4; } while (num != 0); return new String(buffer,i,64 - i); } protected void hook41() throws IOException { } protected void hook42() throws IOException { } protected void hook43(byte[] b,int off,int len) throws IOException { } }