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: raf.read(magicBuf);
148:
149: if (readLeInt(magicBuf, 0) != LOCSIG)
150: {
151: raf.close();
152: throw new ZipException("Not a valid zip file");
153: }
154: }
155:
156:
159: private void checkClosed()
160: {
161: if (closed)
162: throw new IllegalStateException("ZipFile has closed: " + name);
163: }
164:
165:
176: private int readLeShort(DataInput di, byte[] b) throws IOException
177: {
178: di.readFully(b, 0, 2);
179: return (b[0] & 0xff) | (b[1] & 0xff) << 8;
180: }
181:
182:
193: private int readLeInt(DataInput di, byte[] b) throws IOException
194: {
195: di.readFully(b, 0, 4);
196: return ((b[0] & 0xff) | (b[1] & 0xff) << 8)
197: | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16;
198: }
199:
200:
208: private int readLeShort(byte[] b, int off)
209: {
210: return (b[off] & 0xff) | (b[off+1] & 0xff) << 8;
211: }
212:
213:
221: private int readLeInt(byte[] b, int off)
222: {
223: return ((b[off] & 0xff) | (b[off+1] & 0xff) << 8)
224: | ((b[off+2] & 0xff) | (b[off+3] & 0xff) << 8) << 16;
225: }
226:
227:
228:
236: private void readEntries() throws ZipException, IOException
237: {
238:
243: long pos = raf.length() - ENDHDR;
244: byte[] ebs = new byte[CENHDR];
245:
246: do
247: {
248: if (pos < 0)
249: throw new ZipException
250: ("central directory not found, probably not a zip file: " + name);
251: raf.seek(pos--);
252: }
253: while (readLeInt(raf, ebs) != ENDSIG);
254:
255: if (raf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
256: throw new EOFException(name);
257: int count = readLeShort(raf, ebs);
258: if (raf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
259: throw new EOFException(name);
260: int centralOffset = readLeInt(raf, ebs);
261:
262: entries = new HashMap(count+count/2);
263: raf.seek(centralOffset);
264:
265: byte[] buffer = new byte[16];
266: for (int i = 0; i < count; i++)
267: {
268: raf.readFully(ebs);
269: if (readLeInt(ebs, 0) != CENSIG)
270: throw new ZipException("Wrong Central Directory signature: " + name);
271:
272: int method = readLeShort(ebs, CENHOW);
273: int dostime = readLeInt(ebs, CENTIM);
274: int crc = readLeInt(ebs, CENCRC);
275: int csize = readLeInt(ebs, CENSIZ);
276: int size = readLeInt(ebs, CENLEN);
277: int nameLen = readLeShort(ebs, CENNAM);
278: int extraLen = readLeShort(ebs, CENEXT);
279: int commentLen = readLeShort(ebs, CENCOM);
280:
281: int offset = readLeInt(ebs, CENOFF);
282:
283: int needBuffer = Math.max(nameLen, commentLen);
284: if (buffer.length < needBuffer)
285: buffer = new byte[needBuffer];
286:
287: raf.readFully(buffer, 0, nameLen);
288: String name;
289: try
290: {
291: name = new String(buffer, 0, nameLen, "UTF-8");
292: }
293: catch (UnsupportedEncodingException uee)
294: {
295: throw new AssertionError(uee);
296: }
297:
298: ZipEntry entry = new ZipEntry(name);
299: entry.setMethod(method);
300: entry.setCrc(crc & 0xffffffffL);
301: entry.setSize(size & 0xffffffffL);
302: entry.setCompressedSize(csize & 0xffffffffL);
303: entry.setDOSTime(dostime);
304: if (extraLen > 0)
305: {
306: byte[] extra = new byte[extraLen];
307: raf.readFully(extra);
308: entry.setExtra(extra);
309: }
310: if (commentLen > 0)
311: {
312: raf.readFully(buffer, 0, commentLen);
313: try
314: {
315: entry.setComment(new String(buffer, 0, commentLen, "UTF-8"));
316: }
317: catch (UnsupportedEncodingException uee)
318: {
319: throw new AssertionError(uee);
320: }
321: }
322: entry.offset = offset;
323: entries.put(name, entry);
324: }
325: }
326:
327:
334: public void close() throws IOException
335: {
336: RandomAccessFile raf = this.raf;
337: if (raf == null)
338: return;
339:
340: synchronized (raf)
341: {
342: closed = true;
343: entries = null;
344: raf.close();
345: }
346: }
347:
348:
352: protected void finalize() throws IOException
353: {
354: if (!closed && raf != null) close();
355: }
356:
357:
362: public Enumeration entries()
363: {
364: checkClosed();
365:
366: try
367: {
368: return new ZipEntryEnumeration(getEntries().values().iterator());
369: }
370: catch (IOException ioe)
371: {
372: return EmptyEnumeration.getInstance();
373: }
374: }
375:
376:
382: private HashMap getEntries() throws IOException
383: {
384: synchronized(raf)
385: {
386: checkClosed();
387:
388: if (entries == null)
389: readEntries();
390:
391: return entries;
392: }
393: }
394:
395:
404: public ZipEntry getEntry(String name)
405: {
406: checkClosed();
407:
408: try
409: {
410: HashMap entries = getEntries();
411: ZipEntry entry = (ZipEntry) entries.get(name);
412:
413: if (entry == null && !name.endsWith("/"))
414: entry = (ZipEntry) entries.get(name + '/');
415: return entry != null ? new ZipEntry(entry, name) : null;
416: }
417: catch (IOException ioe)
418: {
419: return null;
420: }
421: }
422:
423:
424:
425: private byte[] locBuf = new byte[LOCHDR];
426:
427:
438: private long checkLocalHeader(ZipEntry entry) throws IOException
439: {
440: synchronized (raf)
441: {
442: raf.seek(entry.offset);
443: raf.readFully(locBuf);
444:
445: if (readLeInt(locBuf, 0) != LOCSIG)
446: throw new ZipException("Wrong Local header signature: " + name);
447:
448: if (entry.getMethod() != readLeShort(locBuf, LOCHOW))
449: throw new ZipException("Compression method mismatch: " + name);
450:
451: if (entry.getName().length() != readLeShort(locBuf, LOCNAM))
452: throw new ZipException("file name length mismatch: " + name);
453:
454: int extraLen = entry.getName().length() + readLeShort(locBuf, LOCEXT);
455: return entry.offset + LOCHDR + extraLen;
456: }
457: }
458:
459:
481: public InputStream getInputStream(ZipEntry entry) throws IOException
482: {
483: checkClosed();
484:
485: HashMap entries = getEntries();
486: String name = entry.getName();
487: ZipEntry zipEntry = (ZipEntry) entries.get(name);
488: if (zipEntry == null)
489: return null;
490:
491: long start = checkLocalHeader(zipEntry);
492: int method = zipEntry.getMethod();
493: InputStream is = new BufferedInputStream(new PartialInputStream
494: (raf, start, zipEntry.getCompressedSize()));
495: switch (method)
496: {
497: case ZipOutputStream.STORED:
498: return is;
499: case ZipOutputStream.DEFLATED:
500: return new InflaterInputStream(is, new Inflater(true));
501: default:
502: throw new ZipException("Unknown compression method " + method);
503: }
504: }
505:
506:
509: public String getName()
510: {
511: return name;
512: }
513:
514:
519: public int size()
520: {
521: checkClosed();
522:
523: try
524: {
525: return getEntries().size();
526: }
527: catch (IOException ioe)
528: {
529: return 0;
530: }
531: }
532:
533: private static class ZipEntryEnumeration implements Enumeration
534: {
535: private final Iterator elements;
536:
537: public ZipEntryEnumeration(Iterator elements)
538: {
539: this.elements = elements;
540: }
541:
542: public boolean hasMoreElements()
543: {
544: return elements.hasNext();
545: }
546:
547: public Object nextElement()
548: {
549:
552: return ((ZipEntry)elements.next()).clone();
553: }
554: }
555:
556: private static class PartialInputStream extends InputStream
557: {
558: private final RandomAccessFile raf;
559: long filepos, end;
560:
561: public PartialInputStream(RandomAccessFile raf, long start, long len)
562: {
563: this.raf = raf;
564: filepos = start;
565: end = start + len;
566: }
567:
568: public int available()
569: {
570: long amount = end - filepos;
571: if (amount > Integer.MAX_VALUE)
572: return Integer.MAX_VALUE;
573: return (int) amount;
574: }
575:
576: public int read() throws IOException
577: {
578: if (filepos == end)
579: return -1;
580: synchronized (raf)
581: {
582: raf.seek(filepos++);
583: return raf.read();
584: }
585: }
586:
587: public int read(byte[] b, int off, int len) throws IOException
588: {
589: if (len > end - filepos)
590: {
591: len = (int) (end - filepos);
592: if (len == 0)
593: return -1;
594: }
595: synchronized (raf)
596: {
597: raf.seek(filepos);
598: int count = raf.read(b, off, len);
599: if (count > 0)
600: filepos += len;
601: return count;
602: }
603: }
604:
605: public long skip(long amount)
606: {
607: if (amount < 0)
608: throw new IllegalArgumentException();
609: if (amount > end - filepos)
610: amount = end - filepos;
611: filepos += amount;
612: return amount;
613: }
614: }
615: }