001    /* ZipInputStream.java --
002       Copyright (C) 2001, 2002, 2003, 2004, 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.util.zip;
040    
041    import java.io.EOFException;
042    import java.io.IOException;
043    import java.io.InputStream;
044    import 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     */
056    public 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    }