1 |
/* java.util.zip.ZipFile |
/* java.util.zip.ZipFile |
2 |
Copyright (C) 2001, 2002 Free Software Foundation, Inc. |
Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. |
3 |
|
|
4 |
This file is part of GNU Classpath. |
This file is part of GNU Classpath. |
5 |
|
|
37 |
|
|
38 |
package java.util.zip; |
package java.util.zip; |
39 |
|
|
40 |
|
import java.io.BufferedInputStream; |
41 |
import java.io.ByteArrayInputStream; |
import java.io.ByteArrayInputStream; |
42 |
import java.io.DataInput; |
import java.io.DataInput; |
43 |
import java.io.DataInputStream; |
import java.io.DataInputStream; |
47 |
import java.io.EOFException; |
import java.io.EOFException; |
48 |
import java.io.RandomAccessFile; |
import java.io.RandomAccessFile; |
49 |
import java.util.Enumeration; |
import java.util.Enumeration; |
50 |
import java.util.Hashtable; |
import java.util.HashMap; |
51 |
|
import java.util.Iterator; |
52 |
import java.util.NoSuchElementException; |
import java.util.NoSuchElementException; |
53 |
|
|
54 |
/** |
/** |
60 |
* entries in different threads. |
* entries in different threads. |
61 |
* |
* |
62 |
* @author Jochen Hoenicke |
* @author Jochen Hoenicke |
63 |
|
* @author Artur Biesiadowski |
64 |
*/ |
*/ |
65 |
public class ZipFile implements ZipConstants |
public class ZipFile implements ZipConstants |
66 |
{ |
{ |
82 |
private final RandomAccessFile raf; |
private final RandomAccessFile raf; |
83 |
|
|
84 |
// The entries of this zip file when initialized and not yet closed. |
// The entries of this zip file when initialized and not yet closed. |
85 |
private Hashtable entries; |
private HashMap entries; |
86 |
|
|
87 |
private boolean closed = false; |
private boolean closed = false; |
88 |
|
|
140 |
} |
} |
141 |
|
|
142 |
/** |
/** |
143 |
* Read an unsigned short in little endian byte order. |
* Read an unsigned short in little endian byte order from the given |
144 |
|
* DataInput stream using the given byte buffer. |
145 |
|
* |
146 |
|
* @param di DataInput stream to read from. |
147 |
|
* @param b the byte buffer to read in (must be at least 2 bytes long). |
148 |
|
* @return The value read. |
149 |
|
* |
150 |
* @exception IOException if a i/o error occured. |
* @exception IOException if a i/o error occured. |
151 |
* @exception EOFException if the file ends prematurely |
* @exception EOFException if the file ends prematurely |
152 |
*/ |
*/ |
153 |
private final int readLeShort(DataInput di) throws IOException |
private final int readLeShort(DataInput di, byte[] b) throws IOException |
154 |
{ |
{ |
155 |
byte[] b = new byte[2]; |
di.readFully(b, 0, 2); |
|
di.readFully(b); |
|
156 |
return (b[0] & 0xff) | (b[1] & 0xff) << 8; |
return (b[0] & 0xff) | (b[1] & 0xff) << 8; |
157 |
} |
} |
158 |
|
|
159 |
/** |
/** |
160 |
* Read an int in little endian byte order. |
* Read an int in little endian byte order from the given |
161 |
|
* DataInput stream using the given byte buffer. |
162 |
|
* |
163 |
|
* @param di DataInput stream to read from. |
164 |
|
* @param b the byte buffer to read in (must be at least 4 bytes long). |
165 |
|
* @return The value read. |
166 |
|
* |
167 |
* @exception IOException if a i/o error occured. |
* @exception IOException if a i/o error occured. |
168 |
* @exception EOFException if the file ends prematurely |
* @exception EOFException if the file ends prematurely |
169 |
*/ |
*/ |
170 |
private final int readLeInt(DataInput di) throws IOException |
private final int readLeInt(DataInput di, byte[] b) throws IOException |
171 |
{ |
{ |
172 |
byte[] b = new byte[4]; |
di.readFully(b, 0, 4); |
|
di.readFully(b); |
|
173 |
return ((b[0] & 0xff) | (b[1] & 0xff) << 8) |
return ((b[0] & 0xff) | (b[1] & 0xff) << 8) |
174 |
| ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16; |
| ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16; |
175 |
} |
} |
176 |
|
|
177 |
|
|
178 |
|
/** |
179 |
|
* Read an unsigned short in little endian byte order from the given |
180 |
|
* byte buffer at the given offset. |
181 |
|
* |
182 |
|
* @param b the byte array to read from. |
183 |
|
* @param off the offset to read from. |
184 |
|
* @return The value read. |
185 |
|
*/ |
186 |
|
private final int readLeShort(byte[] b, int off) |
187 |
|
{ |
188 |
|
return (b[off] & 0xff) | (b[off+1] & 0xff) << 8; |
189 |
|
} |
190 |
|
|
191 |
|
/** |
192 |
|
* Read an int in little endian byte order from the given |
193 |
|
* byte buffer at the given offset. |
194 |
|
* |
195 |
|
* @param b the byte array to read from. |
196 |
|
* @param off the offset to read from. |
197 |
|
* @return The value read. |
198 |
|
*/ |
199 |
|
private final int readLeInt(byte[] b, int off) |
200 |
|
{ |
201 |
|
return ((b[off] & 0xff) | (b[off+1] & 0xff) << 8) |
202 |
|
| ((b[off+2] & 0xff) | (b[off+3] & 0xff) << 8) << 16; |
203 |
|
} |
204 |
|
|
205 |
|
|
206 |
/** |
/** |
207 |
* Read the central directory of a zip file and fill the entries |
* Read the central directory of a zip file and fill the entries |
208 |
* array. This is called exactly once when first needed. |
* array. This is called exactly once when first needed. It is called |
209 |
|
* while holding the lock on <code>raf</code>. |
210 |
|
* |
211 |
* @exception IOException if a i/o error occured. |
* @exception IOException if a i/o error occured. |
212 |
* @exception ZipException if the central directory is malformed |
* @exception ZipException if the central directory is malformed |
213 |
*/ |
*/ |
219 |
* file isn't a zip file. |
* file isn't a zip file. |
220 |
*/ |
*/ |
221 |
long pos = raf.length() - ENDHDR; |
long pos = raf.length() - ENDHDR; |
222 |
|
byte[] ebs = new byte[CENHDR]; |
223 |
|
|
224 |
do |
do |
225 |
{ |
{ |
226 |
if (pos < 0) |
if (pos < 0) |
228 |
("central directory not found, probably not a zip file: " + name); |
("central directory not found, probably not a zip file: " + name); |
229 |
raf.seek(pos--); |
raf.seek(pos--); |
230 |
} |
} |
231 |
while (readLeInt(raf) != ENDSIG); |
while (readLeInt(raf, ebs) != ENDSIG); |
232 |
|
|
233 |
if (raf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD) |
if (raf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD) |
234 |
throw new EOFException(name); |
throw new EOFException(name); |
235 |
int count = readLeShort(raf); |
int count = readLeShort(raf, ebs); |
236 |
if (raf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ) |
if (raf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ) |
237 |
throw new EOFException(name); |
throw new EOFException(name); |
238 |
int centralOffset = readLeInt(raf); |
int centralOffset = readLeInt(raf, ebs); |
239 |
|
|
240 |
entries = new Hashtable(count); |
entries = new HashMap(count+count/2); |
241 |
raf.seek(centralOffset); |
raf.seek(centralOffset); |
242 |
byte[] ebs = new byte[24]; |
|
243 |
ByteArrayInputStream ebais = new ByteArrayInputStream(ebs); |
byte[] buffer = new byte[16]; |
|
DataInputStream edip = new DataInputStream(ebais); |
|
244 |
for (int i = 0; i < count; i++) |
for (int i = 0; i < count; i++) |
245 |
{ |
{ |
246 |
if (readLeInt(raf) != CENSIG) |
raf.readFully(ebs); |
247 |
|
if (readLeInt(ebs, 0) != CENSIG) |
248 |
throw new ZipException("Wrong Central Directory signature: " + name); |
throw new ZipException("Wrong Central Directory signature: " + name); |
|
if (raf.skipBytes(CENHOW - CENVEM) != CENHOW - CENVEM) |
|
|
throw new EOFException(name); |
|
|
|
|
|
raf.readFully(ebs); |
|
|
ebais.reset(); |
|
|
int method = readLeShort(edip); |
|
|
int dostime = readLeInt(edip); |
|
|
int crc = readLeInt(edip); |
|
|
int csize = readLeInt(edip); |
|
|
int size = readLeInt(edip); |
|
|
int nameLen = readLeShort(edip); |
|
|
int extraLen = readLeShort(edip); |
|
|
int commentLen = readLeShort(edip); |
|
|
|
|
|
if (raf.skipBytes(CENOFF - CENDSK) != CENOFF - CENDSK) |
|
|
throw new EOFException(name); |
|
|
int offset = readLeInt(raf); |
|
249 |
|
|
250 |
byte[] buffer = new byte[Math.max(nameLen, commentLen)]; |
int method = readLeShort(ebs, CENHOW); |
251 |
|
int dostime = readLeInt(ebs, CENTIM); |
252 |
|
int crc = readLeInt(ebs, CENCRC); |
253 |
|
int csize = readLeInt(ebs, CENSIZ); |
254 |
|
int size = readLeInt(ebs, CENLEN); |
255 |
|
int nameLen = readLeShort(ebs, CENNAM); |
256 |
|
int extraLen = readLeShort(ebs, CENEXT); |
257 |
|
int commentLen = readLeShort(ebs, CENCOM); |
258 |
|
|
259 |
|
int offset = readLeInt(ebs, CENOFF); |
260 |
|
|
261 |
|
int needBuffer = Math.max(nameLen, commentLen); |
262 |
|
if (buffer.length < needBuffer) |
263 |
|
buffer = new byte[needBuffer]; |
264 |
|
|
265 |
raf.readFully(buffer, 0, nameLen); |
raf.readFully(buffer, 0, nameLen); |
266 |
String name = new String(buffer, 0, nameLen); |
String name = new String(buffer, 0, 0, nameLen); |
267 |
|
|
268 |
ZipEntry entry = new ZipEntry(name); |
ZipEntry entry = new ZipEntry(name); |
269 |
entry.setMethod(method); |
entry.setMethod(method); |
291 |
* Closes the ZipFile. This also closes all input streams given by |
* Closes the ZipFile. This also closes all input streams given by |
292 |
* this class. After this is called, no further method should be |
* this class. After this is called, no further method should be |
293 |
* called. |
* called. |
294 |
|
* |
295 |
* @exception IOException if a i/o error occured. |
* @exception IOException if a i/o error occured. |
296 |
*/ |
*/ |
297 |
public void close() throws IOException |
public void close() throws IOException |
311 |
{ |
{ |
312 |
try |
try |
313 |
{ |
{ |
314 |
return new ZipEntryEnumeration(getEntries().elements()); |
return new ZipEntryEnumeration(getEntries().values().iterator()); |
315 |
} |
} |
316 |
catch (IOException ioe) |
catch (IOException ioe) |
317 |
{ |
{ |
325 |
* @exception IllegalStateException when the ZipFile has already been closed. |
* @exception IllegalStateException when the ZipFile has already been closed. |
326 |
* @exception IOEexception when the entries could not be read. |
* @exception IOEexception when the entries could not be read. |
327 |
*/ |
*/ |
328 |
private Hashtable getEntries() throws IOException |
private HashMap getEntries() throws IOException |
329 |
{ |
{ |
330 |
synchronized(raf) |
synchronized(raf) |
331 |
{ |
{ |
341 |
|
|
342 |
/** |
/** |
343 |
* Searches for a zip entry in this archive with the given name. |
* Searches for a zip entry in this archive with the given name. |
344 |
|
* |
345 |
* @param the name. May contain directory components separated by |
* @param the name. May contain directory components separated by |
346 |
* slashes ('/'). |
* slashes ('/'). |
347 |
* @return the zip entry, or null if no entry with that name exists. |
* @return the zip entry, or null if no entry with that name exists. |
348 |
* @see #entries */ |
*/ |
349 |
public ZipEntry getEntry(String name) |
public ZipEntry getEntry(String name) |
350 |
{ |
{ |
351 |
try |
try |
352 |
{ |
{ |
353 |
Hashtable entries = getEntries(); |
HashMap entries = getEntries(); |
354 |
ZipEntry entry = (ZipEntry) entries.get(name); |
ZipEntry entry = (ZipEntry) entries.get(name); |
355 |
return entry != null ? (ZipEntry) entry.clone() : null; |
return entry != null ? (ZipEntry) entry.clone() : null; |
356 |
} |
} |
360 |
} |
} |
361 |
} |
} |
362 |
|
|
363 |
|
|
364 |
|
//access should be protected by synchronized(raf) |
365 |
|
private byte[] locBuf = new byte[LOCHDR]; |
366 |
|
|
367 |
/** |
/** |
368 |
* Checks, if the local header of the entry at index i matches the |
* Checks, if the local header of the entry at index i matches the |
369 |
* central directory, and returns the offset to the data. |
* central directory, and returns the offset to the data. |
370 |
|
* |
371 |
|
* @param entry to check. |
372 |
* @return the start offset of the (compressed) data. |
* @return the start offset of the (compressed) data. |
373 |
|
* |
374 |
* @exception IOException if a i/o error occured. |
* @exception IOException if a i/o error occured. |
375 |
* @exception ZipException if the local header doesn't match the |
* @exception ZipException if the local header doesn't match the |
376 |
* central directory header |
* central directory header |
380 |
synchronized (raf) |
synchronized (raf) |
381 |
{ |
{ |
382 |
raf.seek(entry.offset); |
raf.seek(entry.offset); |
383 |
if (readLeInt(raf) != LOCSIG) |
raf.readFully(locBuf); |
384 |
|
|
385 |
|
if (readLeInt(locBuf, 0) != LOCSIG) |
386 |
throw new ZipException("Wrong Local header signature: " + name); |
throw new ZipException("Wrong Local header signature: " + name); |
387 |
|
|
388 |
/* skip version and flags */ |
if (entry.getMethod() != readLeShort(locBuf, LOCHOW)) |
|
if (raf.skipBytes(LOCHOW - LOCVER) != LOCHOW - LOCVER) |
|
|
throw new EOFException(name); |
|
|
|
|
|
if (entry.getMethod() != readLeShort(raf)) |
|
389 |
throw new ZipException("Compression method mismatch: " + name); |
throw new ZipException("Compression method mismatch: " + name); |
390 |
|
|
391 |
/* Skip time, crc, size and csize */ |
if (entry.getName().length() != readLeShort(locBuf, LOCNAM)) |
|
if (raf.skipBytes(LOCNAM - LOCTIM) != LOCNAM - LOCTIM) |
|
|
throw new EOFException(name); |
|
|
|
|
|
if (entry.getName().length() != readLeShort(raf)) |
|
392 |
throw new ZipException("file name length mismatch: " + name); |
throw new ZipException("file name length mismatch: " + name); |
393 |
|
|
394 |
int extraLen = entry.getName().length() + readLeShort(raf); |
int extraLen = entry.getName().length() + readLeShort(locBuf, LOCEXT); |
395 |
return entry.offset + LOCHDR + extraLen; |
return entry.offset + LOCHDR + extraLen; |
396 |
} |
} |
397 |
} |
} |
400 |
* Creates an input stream reading the given zip entry as |
* Creates an input stream reading the given zip entry as |
401 |
* uncompressed data. Normally zip entry should be an entry |
* uncompressed data. Normally zip entry should be an entry |
402 |
* returned by getEntry() or entries(). |
* returned by getEntry() or entries(). |
403 |
|
* |
404 |
|
* @param entry the entry to create an InputStream for. |
405 |
* @return the input stream. |
* @return the input stream. |
406 |
|
* |
407 |
* @exception IOException if a i/o error occured. |
* @exception IOException if a i/o error occured. |
408 |
* @exception ZipException if the Zip archive is malformed. |
* @exception ZipException if the Zip archive is malformed. |
409 |
*/ |
*/ |
410 |
public InputStream getInputStream(ZipEntry entry) throws IOException |
public InputStream getInputStream(ZipEntry entry) throws IOException |
411 |
{ |
{ |
412 |
Hashtable entries = getEntries(); |
HashMap entries = getEntries(); |
413 |
String name = entry.getName(); |
String name = entry.getName(); |
414 |
ZipEntry zipEntry = (ZipEntry) entries.get(name); |
ZipEntry zipEntry = (ZipEntry) entries.get(name); |
415 |
if (zipEntry == null) |
if (zipEntry == null) |
417 |
|
|
418 |
long start = checkLocalHeader(zipEntry); |
long start = checkLocalHeader(zipEntry); |
419 |
int method = zipEntry.getMethod(); |
int method = zipEntry.getMethod(); |
420 |
InputStream is = new PartialInputStream |
InputStream is = new BufferedInputStream(new PartialInputStream |
421 |
(raf, start, zipEntry.getCompressedSize()); |
(raf, start, zipEntry.getCompressedSize())); |
422 |
switch (method) |
switch (method) |
423 |
{ |
{ |
424 |
case ZipOutputStream.STORED: |
case ZipOutputStream.STORED: |
455 |
|
|
456 |
private static class ZipEntryEnumeration implements Enumeration |
private static class ZipEntryEnumeration implements Enumeration |
457 |
{ |
{ |
458 |
private final Enumeration elements; |
private final Iterator elements; |
459 |
|
|
460 |
public ZipEntryEnumeration(Enumeration elements) |
public ZipEntryEnumeration(Iterator elements) |
461 |
{ |
{ |
462 |
this.elements = elements; |
this.elements = elements; |
463 |
} |
} |
464 |
|
|
465 |
public boolean hasMoreElements() |
public boolean hasMoreElements() |
466 |
{ |
{ |
467 |
return elements.hasMoreElements(); |
return elements.hasNext(); |
468 |
} |
} |
469 |
|
|
470 |
public Object nextElement() |
public Object nextElement() |
472 |
/* We return a clone, just to be safe that the user doesn't |
/* We return a clone, just to be safe that the user doesn't |
473 |
* change the entry. |
* change the entry. |
474 |
*/ |
*/ |
475 |
return ((ZipEntry)elements.nextElement()).clone(); |
return ((ZipEntry)elements.next()).clone(); |
476 |
} |
} |
477 |
} |
} |
478 |
|
|
479 |
private static class PartialInputStream extends InputStream |
private static class PartialInputStream extends InputStream |
480 |
{ |
{ |
481 |
RandomAccessFile raf; |
private final RandomAccessFile raf; |
482 |
long filepos, end; |
long filepos, end; |
483 |
|
|
484 |
public PartialInputStream(RandomAccessFile raf, long start, long len) |
public PartialInputStream(RandomAccessFile raf, long start, long len) |