// package net.sf.zipme; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; /** * This is a FilterInputStream that reads the files in an zip archive * one after another. It has a special method to get the zip entry of * the next file. The zip entry contains information about the file name * size, compressed size, CRC, etc. * It includes support for STORED and DEFLATED entries. * @author Jochen Hoenicke */ public class ZipInputStream extends InflaterInputStream implements ZipConstants { private ZipEntry entry=null; private int csize; private int size; private int method; private int flags; private int avail; private boolean entryAtEOF; /** * Creates a new Zip input stream, reading a zip archive. */ public ZipInputStream( InputStream in){ super(in,new Inflater(true)); } private void fillBuf() throws IOException { avail=len=in.read(buf,0,buf.length); } private int readBuf( byte[] out, int offset, int length) throws IOException { if (avail <= 0) { fillBuf(); if (avail <= 0) return -1; } if (length > avail) length=avail; System.arraycopy(buf,len - avail,out,offset,length); avail-=length; return length; } private void readFully( byte[] out) throws IOException { int off=0; int len=out.length; while (len > 0) { int count=readBuf(out,off,len); if (count == -1) throw new EOFException(); off+=count; len-=count; } } private int readLeByte() throws IOException { if (avail <= 0) { fillBuf(); if (avail <= 0) throw new ZipException("EOF in header"); } return buf[len - avail--] & 0xff; } /** * Read an unsigned short in little endian byte order. */ private int readLeShort() throws IOException { return readLeByte() | (readLeByte() << 8); } /** * Read an int in little endian byte order. */ private int readLeInt() throws IOException { return readLeShort() | (readLeShort() << 16); } /** * Open the next entry from the zip archive, and return its description. * If the previous entry wasn't closed, this method will close it. */ public ZipEntry getNextEntry() throws IOException { if (entry != null) closeEntry(); int header=readLeInt(); if (header == CENSIG) { close(); return null; } if (header != LOCSIG) throw new ZipException("Wrong Local header signature: " + Integer.toHexString(header)); readLeShort(); flags=readLeShort(); method=readLeShort(); int dostime=readLeInt(); int crc=readLeInt(); csize=readLeInt(); size=readLeInt(); int nameLen=readLeShort(); int extraLen=readLeShort(); if (method == ZipOutputStream.STORED && csize != size) throw new ZipException("Stored, but compressed != uncompressed"); byte[] buffer=new byte[nameLen]; readFully(buffer); String name; try { name=new String(buffer,"UTF-8"); } catch ( UnsupportedEncodingException uee) { throw new Error(uee.toString()); } entry=createZipEntry(name); entryAtEOF=false; entry.setMethod(method); if ((flags & 8) == 0) { entry.setCrc(crc & 0xffffffffL); entry.setSize(size & 0xffffffffL); entry.setCompressedSize(csize & 0xffffffffL); } entry.setDOSTime(dostime); if (extraLen > 0) { byte[] extra=new byte[extraLen]; readFully(extra); entry.setExtra(extra); } if (method == ZipOutputStream.DEFLATED && avail > 0) { System.arraycopy(buf,len - avail,buf,0,avail); len=avail; avail=0; inf.setInput(buf,0,len); } return entry; } private void readDataDescr() throws IOException { if (readLeInt() != EXTSIG) throw new ZipException("Data descriptor signature not found"); entry.setCrc(readLeInt() & 0xffffffffL); csize=readLeInt(); size=readLeInt(); entry.setSize(size & 0xffffffffL); entry.setCompressedSize(csize & 0xffffffffL); } /** * Closes the current zip entry and moves to the next one. */ public void closeEntry() throws IOException { if (entry == null) return; if (method == ZipOutputStream.DEFLATED) { if ((flags & 8) != 0) { byte[] tmp=new byte[2048]; while (read(tmp) > 0) ; return; } csize-=inf.getTotalIn(); avail=inf.getRemaining(); } if (avail > csize && csize >= 0) avail-=csize; else { csize-=avail; avail=0; while (csize != 0) { long skipped=in.skip(csize & 0xffffffffL); if (skipped <= 0) throw new ZipException("zip archive ends early."); csize-=skipped; } } size=0; this.hook36(); if (method == ZipOutputStream.DEFLATED) inf.reset(); entry=null; entryAtEOF=true; } public int available() throws IOException { return entryAtEOF ? 0 : 1; } /** * Reads a byte from the current zip entry. * @return the byte or -1 on EOF. * @exception IOException if a i/o error occured. * @exception ZipException if the deflated stream is corrupted. */ public int read() throws IOException { byte[] b=new byte[1]; if (read(b,0,1) <= 0) return -1; return b[0] & 0xff; } /** * Reads a block of bytes from the current zip entry. * @return the number of bytes read (may be smaller, even before * EOF), or -1 on EOF. * @exception IOException if a i/o error occured. * @exception ZipException if the deflated stream is corrupted. */ public int read( byte[] b, int off, int len) throws IOException { if (len == 0) return 0; this.hook38(); if (entry == null) return -1; boolean finished=false; switch (method) { case ZipOutputStream.DEFLATED: len=super.read(b,off,len); if (len < 0) { if (!inf.finished()) throw new ZipException("Inflater not finished!?"); avail=inf.getRemaining(); if ((flags & 8) != 0) readDataDescr(); if (inf.getTotalIn() != csize || inf.getTotalOut() != size) throw new ZipException("size mismatch: " + csize + ";"+ size+ " <-> "+ inf.getTotalIn()+ ";"+ inf.getTotalOut()); inf.reset(); finished=true; } break; case ZipOutputStream.STORED: if (len > csize && csize >= 0) len=csize; len=readBuf(b,off,len); if (len > 0) { csize-=len; size-=len; } if (csize == 0) finished=true; else if (len < 0) throw new ZipException("EOF in stored block"); break; } this.hook37(b,off,len); if (finished) { this.hook39(); entry=null; entryAtEOF=true; } return len; } /** * Closes the zip file. * @exception IOException if a i/o error occured. */ public void close() throws IOException { super.close(); this.hook40(); entry=null; entryAtEOF=true; } /** * Creates a new zip entry for the given name. This is equivalent * to new ZipEntry(name). * @param name the name of the zip entry. */ protected ZipEntry createZipEntry(String name){ return new ZipEntry(name); } protected void hook36() throws IOException { } protected void hook37(byte[] b,int off,int len) throws IOException { } protected void hook38() throws IOException { } protected void hook39() throws IOException { } protected void hook40() throws IOException { } }