1 /* ZipInputStream.java --
2    Copyright (C) 2001, 2002, 2003, 2004, 2005  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package java.util.zip;
40 
41 import java.io.EOFException;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.UnsupportedEncodingException;
45 
46 /**
47  * This is a FilterInputStream that reads the files in an zip archive
48  * one after another.  It has a special method to get the zip entry of
49  * the next file.  The zip entry contains information about the file name
50  * size, compressed size, CRC, etc.
51  *
52  * It includes support for STORED and DEFLATED entries.
53  *
54  * @author Jochen Hoenicke
55  */
56 public class ZipInputStream extends InflaterInputStream implements ZipConstants
57 {
58   private CRC32 crc = new CRC32();
59   private ZipEntry entry = null;
60 
61   private int csize;
62   private int size;
63   private int method;
64   private int flags;
65   private int avail;
66   private boolean entryAtEOF;
67 
68   /**
69    * Creates a new Zip input stream, reading a zip archive.
70    */
ZipInputStream(InputStream in)71   public ZipInputStream(InputStream in)
72   {
73     super(in, new Inflater(true));
74   }
75 
fillBuf()76   private void fillBuf() throws IOException
77   {
78     avail = len = in.read(buf, 0, buf.length);
79   }
80 
readBuf(byte[] out, int offset, int length)81   private int readBuf(byte[] out, int offset, int length) throws IOException
82   {
83     if (avail <= 0)
84       {
85 	fillBuf();
86 	if (avail <= 0)
87 	  return -1;
88       }
89     if (length > avail)
90       length = avail;
91     System.arraycopy(buf, len - avail, out, offset, length);
92     avail -= length;
93     return length;
94   }
95 
readFully(byte[] out)96   private void readFully(byte[] out) throws IOException
97   {
98     int off = 0;
99     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 
readLeByte()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    */
readLeShort()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    */
readLeInt()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    */
getNextEntry()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 
readDataDescr()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    */
closeEntry()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 	    /* read will close this entry */
242 	    return;
243 	  }
244 	csize -= inf.getTotalIn();
245 	avail = inf.getRemaining();
246       }
247 
248     if (avail > csize && csize >= 0)
249       avail -= csize;
250     else
251       {
252 	csize -= avail;
253 	avail = 0;
254 	while (csize != 0)
255 	  {
256 	    long skipped = in.skip(csize & 0xffffffffL);
257 	    if (skipped <= 0)
258 	      throw new ZipException("zip archive ends early.");
259 	    csize -= skipped;
260 	  }
261       }
262 
263     size = 0;
264     crc.reset();
265     if (method == ZipOutputStream.DEFLATED)
266       inf.reset();
267     entry = null;
268     entryAtEOF = true;
269   }
270 
available()271   public int available() throws IOException
272   {
273     return entryAtEOF ? 0 : 1;
274   }
275 
276   /**
277    * Reads a byte from the current zip entry.
278    * @return the byte or -1 on EOF.
279    * @exception IOException if a i/o error occured.
280    * @exception ZipException if the deflated stream is corrupted.
281    */
read()282   public int read() throws IOException
283   {
284     byte[] b = new byte[1];
285     if (read(b, 0, 1) <= 0)
286       return -1;
287     return b[0] & 0xff;
288   }
289 
290   /**
291    * Reads a block of bytes from the current zip entry.
292    * @return the number of bytes read (may be smaller, even before
293    * EOF), or -1 on EOF.
294    * @exception IOException if a i/o error occured.
295    * @exception ZipException if the deflated stream is corrupted.
296    */
read(byte[] b, int off, int len)297   public int read(byte[] b, int off, int len) throws IOException
298   {
299     if (len == 0)
300       return 0;
301     if (crc == null)
302       throw new IOException("Stream closed.");
303     if (entry == null)
304       return -1;
305     boolean finished = false;
306     switch (method)
307       {
308       case ZipOutputStream.DEFLATED:
309 	len = super.read(b, off, len);
310 	if (len < 0)
311 	  {
312 	    if (!inf.finished())
313 	      throw new ZipException("Inflater not finished!?");
314 	    avail = inf.getRemaining();
315 	    if ((flags & 8) != 0)
316 	      readDataDescr();
317 
318 	    if (inf.getTotalIn() != csize
319 		|| inf.getTotalOut() != size)
320 	      throw new ZipException("size mismatch: "+csize+";"+size+" <-> "+inf.getTotalIn()+";"+inf.getTotalOut());
321 	    inf.reset();
322 	    finished = true;
323 	  }
324 	break;
325 
326       case ZipOutputStream.STORED:
327 
328 	if (len > csize && csize >= 0)
329 	  len = csize;
330 
331 	len = readBuf(b, off, len);
332 	if (len > 0)
333 	  {
334 	    csize -= len;
335 	    size -= len;
336 	  }
337 
338 	if (csize == 0)
339 	  finished = true;
340 	else if (len < 0)
341 	  throw new ZipException("EOF in stored block");
342 	break;
343       }
344 
345     if (len > 0)
346       crc.update(b, off, len);
347 
348     if (finished)
349       {
350 	if ((crc.getValue() & 0xffffffffL) != entry.getCrc())
351 	  throw new ZipException("CRC mismatch");
352 	crc.reset();
353 	entry = null;
354 	entryAtEOF = true;
355       }
356     return len;
357   }
358 
359   /**
360    * Closes the zip file.
361    * @exception IOException if a i/o error occured.
362    */
close()363   public void close() throws IOException
364   {
365     super.close();
366     crc = null;
367     entry = null;
368     entryAtEOF = true;
369   }
370 
371   /**
372    * Creates a new zip entry for the given name.  This is equivalent
373    * to new ZipEntry(name).
374    * @param name the name of the zip entry.
375    */
createZipEntry(String name)376   protected ZipEntry createZipEntry(String name)
377   {
378     return new ZipEntry(name);
379   }
380 }
381