1:
38:
39:
40: package ;
41:
42: import ;
43:
44: import ;
45: import ;
46: import ;
47: import ;
48: import ;
49: import ;
50: import ;
51: import ;
52: import ;
53: import ;
54: import ;
55:
56:
67: public class ZipFile implements ZipConstants
68: {
69:
70:
73: public static final int OPEN_READ = 0x1;
74:
75:
78: public static final int OPEN_DELETE = 0x4;
79:
80:
81: private final String name;
82:
83:
84: private final RandomAccessFile raf;
85:
86:
87: private HashMap entries;
88:
89: private boolean closed = false;
90:
91:
97: public ZipFile(String name) throws ZipException, IOException
98: {
99: this.raf = new RandomAccessFile(name, "r");
100: this.name = name;
101: checkZipFile();
102: }
103:
104:
110: public ZipFile(File file) throws ZipException, IOException
111: {
112: this.raf = new RandomAccessFile(file, "r");
113: this.name = file.getPath();
114: checkZipFile();
115: }
116:
117:
133: public ZipFile(File file, int mode) throws ZipException, IOException
134: {
135: if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE))
136: throw new IllegalArgumentException("invalid mode");
137: if ((mode & OPEN_DELETE) != 0)
138: file.deleteOnExit();
139: this.raf = new RandomAccessFile(file, "r");
140: this.name = file.getPath();
141: checkZipFile();
142: }
143:
144: private void checkZipFile() throws IOException, ZipException
145: {
146: byte[] magicBuf = new byte[4];
147: boolean validRead = true;
148:
149: try
150: {
151: raf.readFully(magicBuf);
152: }
153: catch (EOFException eof)
154: {
155: validRead = false;
156: }
157:
158: if (validRead == false || readLeInt(magicBuf, 0) != LOCSIG)
159: {
160: raf.close();
161: throw new ZipException("Not a valid zip file");
162: }
163: }
164:
165:
168: private void checkClosed()
169: {
170: if (closed)
171: throw new IllegalStateException("ZipFile has closed: " + name);
172: }
173:
174:
185: private int readLeShort(DataInput di, byte[] b) throws IOException
186: {
187: di.readFully(b, 0, 2);
188: return (b[0] & 0xff) | (b[1] & 0xff) << 8;
189: }
190:
191:
202: private int readLeInt(DataInput di, byte[] b) throws IOException
203: {
204: di.readFully(b, 0, 4);
205: return ((b[0] & 0xff) | (b[1] & 0xff) << 8)
206: | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16;
207: }
208:
209:
217: private int readLeShort(byte[] b, int off)
218: {
219: return (b[off] & 0xff) | (b[off+1] & 0xff) << 8;
220: }
221:
222:
230: private int readLeInt(byte[] b, int off)
231: {
232: return ((b[off] & 0xff) | (b[off+1] & 0xff) << 8)
233: | ((b[off+2] & 0xff) | (b[off+3] & 0xff) << 8) << 16;
234: }
235:
236:
237:
245: private void readEntries() throws ZipException, IOException
246: {
247:
252: long pos = raf.length() - ENDHDR;
253: byte[] ebs = new byte[CENHDR];
254:
255: do
256: {
257: if (pos < 0)
258: throw new ZipException
259: ("central directory not found, probably not a zip file: " + name);
260: raf.seek(pos--);
261: }
262: while (readLeInt(raf, ebs) != ENDSIG);
263:
264: if (raf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
265: throw new EOFException(name);
266: int count = readLeShort(raf, ebs);
267: if (raf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
268: throw new EOFException(name);
269: int centralOffset = readLeInt(raf, ebs);
270:
271: entries = new HashMap(count+count/2);
272: raf.seek(centralOffset);
273:
274: byte[] buffer = new byte[16];
275: for (int i = 0; i < count; i++)
276: {
277: raf.readFully(ebs);
278: if (readLeInt(ebs, 0) != CENSIG)
279: throw new ZipException("Wrong Central Directory signature: " + name);
280:
281: int method = readLeShort(ebs, CENHOW);
282: int dostime = readLeInt(ebs, CENTIM);
283: int crc = readLeInt(ebs, CENCRC);
284: int csize = readLeInt(ebs, CENSIZ);
285: int size = readLeInt(ebs, CENLEN);
286: int nameLen = readLeShort(ebs, CENNAM);
287: int extraLen = readLeShort(ebs, CENEXT);
288: int commentLen = readLeShort(ebs, CENCOM);
289:
290: int offset = readLeInt(ebs, CENOFF);
291:
292: int needBuffer = Math.max(nameLen, commentLen);
293: if (buffer.length < needBuffer)
294: buffer = new byte[needBuffer];
295:
296: raf.readFully(buffer, 0, nameLen);
297: String name;
298: try
299: {
300: name = new String(buffer, 0, nameLen, "UTF-8");
301: }
302: catch (UnsupportedEncodingException uee)
303: {
304: throw new AssertionError(uee);
305: }
306:
307: ZipEntry entry = new ZipEntry(name);
308: entry.setMethod(method);
309: entry.setCrc(crc & 0xffffffffL);
310: entry.setSize(size & 0xffffffffL);
311: entry.setCompressedSize(csize & 0xffffffffL);
312: entry.setDOSTime(dostime);
313: if (extraLen > 0)
314: {
315: byte[] extra = new byte[extraLen];
316: raf.readFully(extra);
317: entry.setExtra(extra);
318: }
319: if (commentLen > 0)
320: {
321: raf.readFully(buffer, 0, commentLen);
322: try
323: {
324: entry.setComment(new String(buffer, 0, commentLen, "UTF-8"));
325: }
326: catch (UnsupportedEncodingException uee)
327: {
328: throw new AssertionError(uee);
329: }
330: }
331: entry.offset = offset;
332: entries.put(name, entry);
333: }
334: }
335:
336:
343: public void close() throws IOException
344: {
345: RandomAccessFile raf = this.raf;
346: if (raf == null)
347: return;
348:
349: synchronized (raf)
350: {
351: closed = true;
352: entries = null;
353: raf.close();
354: }
355: }
356:
357:
361: protected void finalize() throws IOException
362: {
363: if (!closed && raf != null) close();
364: }
365:
366:
371: public Enumeration entries()
372: {
373: checkClosed();
374:
375: try
376: {
377: return new ZipEntryEnumeration(getEntries().values().iterator());
378: }
379: catch (IOException ioe)
380: {
381: return EmptyEnumeration.getInstance();
382: }
383: }
384:
385:
391: private HashMap getEntries() throws IOException
392: {
393: synchronized(raf)
394: {
395: checkClosed();
396:
397: if (entries == null)
398: readEntries();
399:
400: return entries;
401: }
402: }
403:
404:
413: public ZipEntry getEntry(String name)
414: {
415: checkClosed();
416:
417: try
418: {
419: HashMap entries = getEntries();
420: ZipEntry entry = (ZipEntry) entries.get(name);
421:
422: if (entry == null && !name.endsWith("/"))
423: entry = (ZipEntry) entries.get(name + '/');
424: return entry != null ? new ZipEntry(entry, name) : null;
425: }
426: catch (IOException ioe)
427: {
428: return null;
429: }
430: }
431:
432:
433:
434: private byte[] locBuf = new byte[LOCHDR];
435:
436:
447: private long checkLocalHeader(ZipEntry entry) throws IOException
448: {
449: synchronized (raf)
450: {
451: raf.seek(entry.offset);
452: raf.readFully(locBuf);
453:
454: if (readLeInt(locBuf, 0) != LOCSIG)
455: throw new ZipException("Wrong Local header signature: " + name);
456:
457: if (entry.getMethod() != readLeShort(locBuf, LOCHOW))
458: throw new ZipException("Compression method mismatch: " + name);
459:
460: if (entry.getName().length() != readLeShort(locBuf, LOCNAM))
461: throw new ZipException("file name length mismatch: " + name);
462:
463: int extraLen = entry.getName().length() + readLeShort(locBuf, LOCEXT);
464: return entry.offset + LOCHDR + extraLen;
465: }
466: }
467:
468:
490: public InputStream getInputStream(ZipEntry entry) throws IOException
491: {
492: checkClosed();
493:
494: HashMap entries = getEntries();
495: String name = entry.getName();
496: ZipEntry zipEntry = (ZipEntry) entries.get(name);
497: if (zipEntry == null)
498: return null;
499:
500: long start = checkLocalHeader(zipEntry);
501: int method = zipEntry.getMethod();
502: InputStream is = new BufferedInputStream(new PartialInputStream
503: (raf, start, zipEntry.getCompressedSize()));
504: switch (method)
505: {
506: case ZipOutputStream.STORED:
507: return is;
508: case ZipOutputStream.DEFLATED:
509: return new InflaterInputStream(is, new Inflater(true));
510: default:
511: throw new ZipException("Unknown compression method " + method);
512: }
513: }
514:
515:
518: public String getName()
519: {
520: return name;
521: }
522:
523:
528: public int size()
529: {
530: checkClosed();
531:
532: try
533: {
534: return getEntries().size();
535: }
536: catch (IOException ioe)
537: {
538: return 0;
539: }
540: }
541:
542: private static class ZipEntryEnumeration implements Enumeration
543: {
544: private final Iterator elements;
545:
546: public ZipEntryEnumeration(Iterator elements)
547: {
548: this.elements = elements;
549: }
550:
551: public boolean hasMoreElements()
552: {
553: return elements.hasNext();
554: }
555:
556: public Object nextElement()
557: {
558:
561: return ((ZipEntry)elements.next()).clone();
562: }
563: }
564:
565: private static class PartialInputStream extends InputStream
566: {
567: private final RandomAccessFile raf;
568: long filepos, end;
569:
570: public PartialInputStream(RandomAccessFile raf, long start, long len)
571: {
572: this.raf = raf;
573: filepos = start;
574: end = start + len;
575: }
576:
577: public int available()
578: {
579: long amount = end - filepos;
580: if (amount > Integer.MAX_VALUE)
581: return Integer.MAX_VALUE;
582: return (int) amount;
583: }
584:
585: public int read() throws IOException
586: {
587: if (filepos == end)
588: return -1;
589: synchronized (raf)
590: {
591: raf.seek(filepos++);
592: return raf.read();
593: }
594: }
595:
596: public int read(byte[] b, int off, int len) throws IOException
597: {
598: if (len > end - filepos)
599: {
600: len = (int) (end - filepos);
601: if (len == 0)
602: return -1;
603: }
604: synchronized (raf)
605: {
606: raf.seek(filepos);
607: int count = raf.read(b, off, len);
608: if (count > 0)
609: filepos += len;
610: return count;
611: }
612: }
613:
614: public long skip(long amount)
615: {
616: if (amount < 0)
617: throw new IllegalArgumentException();
618: if (amount > end - filepos)
619: amount = end - filepos;
620: filepos += amount;
621: return amount;
622: }
623: }
624: }