001/* ZipInputStream.java --
002   Copyright (C) 2001, 2002, 2003, 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.util.zip;
040
041import java.io.EOFException;
042import java.io.IOException;
043import java.io.InputStream;
044import java.io.UnsupportedEncodingException;
045
046/**
047 * This is a FilterInputStream that reads the files in an zip archive
048 * one after another.  It has a special method to get the zip entry of
049 * the next file.  The zip entry contains information about the file name
050 * size, compressed size, CRC, etc.
051 *
052 * It includes support for STORED and DEFLATED entries.
053 *
054 * @author Jochen Hoenicke
055 */
056public class ZipInputStream extends InflaterInputStream implements ZipConstants
057{
058  private CRC32 crc = new CRC32();
059  private ZipEntry entry = null;
060
061  private int csize;
062  private int size;
063  private int method;
064  private int flags;
065  private int avail;
066  private boolean entryAtEOF;
067
068  /**
069   * Creates a new Zip input stream, reading a zip archive.
070   */
071  public ZipInputStream(InputStream in)
072  {
073    super(in, new Inflater(true));
074  }
075
076  private void fillBuf() throws IOException
077  {
078    avail = len = in.read(buf, 0, buf.length);
079  }
080
081  private int readBuf(byte[] out, int offset, int length) throws IOException
082  {
083    if (avail <= 0)
084      {
085        fillBuf();
086        if (avail <= 0)
087          return -1;
088      }
089    if (length > avail)
090      length = avail;
091    System.arraycopy(buf, len - avail, out, offset, length);
092    avail -= length;
093    return length;
094  }
095  
096  private void readFully(byte[] out) throws IOException
097  {
098    int off = 0;
099    int len = out.length;
100    while (len > 0)
101      {
102        int count = readBuf(out, off, len);
103        if (count == -1)
104          throw new EOFException();
105        off += count;
106        len -= count;
107      }
108  }
109  
110  private int readLeByte() throws IOException
111  {
112    if (avail <= 0)
113      {
114        fillBuf();
115        if (avail <= 0)
116          throw new ZipException("EOF in header");
117      }
118    return buf[len - avail--] & 0xff;
119  }
120
121  /**
122   * Read an unsigned short in little endian byte order.
123   */
124  private int readLeShort() throws IOException 
125  {
126    return readLeByte() | (readLeByte() << 8);
127  }
128
129  /**
130   * Read an int in little endian byte order.
131   */
132  private int readLeInt() throws IOException 
133  {
134    return readLeShort() | (readLeShort() << 16);
135  }
136
137  /**
138   * Open the next entry from the zip archive, and return its description.
139   * If the previous entry wasn't closed, this method will close it.
140   */
141  public ZipEntry getNextEntry() throws IOException
142  {
143    if (crc == null)
144      throw new IOException("Stream closed.");
145    if (entry != null)
146      closeEntry();
147
148    int header = readLeInt();
149    if (header == CENSIG)
150      {
151        /* Central Header reached. */
152        close();
153        return null;
154      }
155    if (header != LOCSIG)
156      throw new ZipException("Wrong Local header signature: "
157                             + Integer.toHexString(header));
158    /* skip version */
159    readLeShort();
160    flags = readLeShort();
161    method = readLeShort();
162    int dostime = readLeInt();
163    int crc = readLeInt();
164    csize = readLeInt();
165    size = readLeInt();
166    int nameLen = readLeShort();
167    int extraLen = readLeShort();
168
169    if (method == ZipOutputStream.STORED && csize != size)
170      throw new ZipException("Stored, but compressed != uncompressed");
171
172
173    byte[] buffer = new byte[nameLen];
174    readFully(buffer);
175    String name;
176    try
177      {
178        name = new String(buffer, "UTF-8");
179      }
180    catch (UnsupportedEncodingException uee)
181      {
182        throw new AssertionError(uee);
183      }
184    
185    entry = createZipEntry(name);
186    entryAtEOF = false;
187    entry.setMethod(method);
188    if ((flags & 8) == 0)
189      {
190        entry.setCrc(crc & 0xffffffffL);
191        entry.setSize(size & 0xffffffffL);
192        entry.setCompressedSize(csize & 0xffffffffL);
193      }
194    entry.setDOSTime(dostime);
195    if (extraLen > 0)
196      {
197        byte[] extra = new byte[extraLen];
198        readFully(extra);
199        entry.setExtra(extra);
200      }
201
202    if (method == ZipOutputStream.DEFLATED && avail > 0)
203      {
204        System.arraycopy(buf, len - avail, buf, 0, avail);
205        len = avail;
206        avail = 0;
207        inf.setInput(buf, 0, len);
208      }
209    return entry;
210  }
211
212  private void readDataDescr() throws IOException
213  {
214    if (readLeInt() != EXTSIG)
215      throw new ZipException("Data descriptor signature not found");
216    entry.setCrc(readLeInt() & 0xffffffffL);
217    csize = readLeInt();
218    size = readLeInt();
219    entry.setSize(size & 0xffffffffL);
220    entry.setCompressedSize(csize & 0xffffffffL);
221  }
222
223  /**
224   * Closes the current zip entry and moves to the next one.
225   */
226  public void closeEntry() throws IOException
227  {
228    if (crc == null)
229      throw new IOException("Stream closed.");
230    if (entry == null)
231      return;
232
233    if (method == ZipOutputStream.DEFLATED)
234      {
235        if ((flags & 8) != 0)
236          {
237            /* We don't know how much we must skip, read until end. */
238            byte[] tmp = new byte[2048];
239            while (read(tmp) > 0)
240              ;
241            
242            /* read will close this entry */
243            return;
244          }
245        csize -= inf.getTotalIn();
246        avail = inf.getRemaining();
247      }
248
249    if (avail > csize && csize >= 0)
250      avail -= csize;
251    else
252      {
253        csize -= avail;
254        avail = 0;
255        while (csize != 0)
256          {
257            long skipped = in.skip(csize & 0xffffffffL);
258            if (skipped <= 0)
259              throw new ZipException("zip archive ends early.");
260            csize -= skipped;
261          }
262      }
263
264    size = 0;
265    crc.reset();
266    if (method == ZipOutputStream.DEFLATED)
267      inf.reset();
268    entry = null;
269    entryAtEOF = true;
270  }
271
272  public int available() throws IOException
273  {
274    return entryAtEOF ? 0 : 1;
275  }
276
277  /**
278   * Reads a byte from the current zip entry.
279   * @return the byte or -1 on EOF.
280   * @exception IOException if a i/o error occured.
281   * @exception ZipException if the deflated stream is corrupted.
282   */
283  public int read() throws IOException
284  {
285    byte[] b = new byte[1];
286    if (read(b, 0, 1) <= 0)
287      return -1;
288    return b[0] & 0xff;
289  }
290
291  /**
292   * Reads a block of bytes from the current zip entry.
293   * @return the number of bytes read (may be smaller, even before
294   * EOF), or -1 on EOF.
295   * @exception IOException if a i/o error occured.
296   * @exception ZipException if the deflated stream is corrupted.
297   */
298  public int read(byte[] b, int off, int len) throws IOException
299  {
300    if (len == 0)
301      return 0;
302    if (crc == null)
303      throw new IOException("Stream closed.");
304    if (entry == null)
305      return -1;
306    boolean finished = false;
307    switch (method)
308      {
309      case ZipOutputStream.DEFLATED:
310        len = super.read(b, off, len);
311        if (len < 0)
312          {
313            if (!inf.finished())
314              throw new ZipException("Inflater not finished!?");
315            avail = inf.getRemaining();
316            if ((flags & 8) != 0)
317              readDataDescr();
318
319            if (inf.getTotalIn() != csize
320                || inf.getTotalOut() != size)
321              throw new ZipException("size mismatch: "+csize+";"+size+" <-> "+inf.getTotalIn()+";"+inf.getTotalOut());
322            inf.reset();
323            finished = true;
324          }
325        break;
326        
327      case ZipOutputStream.STORED:
328
329        if (len > csize && csize >= 0)
330          len = csize;
331        
332        len = readBuf(b, off, len);
333        if (len > 0)
334          {
335            csize -= len;
336            size -= len;
337          }
338
339        if (csize == 0)
340          finished = true;
341        else if (len < 0)
342          throw new ZipException("EOF in stored block");
343        break;
344      }
345
346    if (len > 0)
347      crc.update(b, off, len);
348
349    if (finished)
350      {
351        if ((crc.getValue() & 0xffffffffL) != entry.getCrc())
352          throw new ZipException("CRC mismatch");
353        crc.reset();
354        entry = null;
355        entryAtEOF = true;
356      }
357    return len;
358  }
359
360  /**
361   * Closes the zip file.
362   * @exception IOException if a i/o error occured.
363   */
364  public void close() throws IOException
365  {
366    super.close();
367    crc = null;
368    entry = null;
369    entryAtEOF = true;
370  }
371
372  /**
373   * Creates a new zip entry for the given name.  This is equivalent
374   * to new ZipEntry(name).
375   * @param name the name of the zip entry.
376   */
377  protected ZipEntry createZipEntry(String name) 
378  {
379    return new ZipEntry(name);
380  }
381}