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: import ;
56: import ;
57:
58:
69: public class ZipFile implements ZipConstants
70: {
71:
72:
75: public static final int OPEN_READ = 0x1;
76:
77:
80: public static final int OPEN_DELETE = 0x4;
81:
82:
85: static final int ENDNRD = 4;
86:
87:
88: private final String name;
89:
90:
91: private final RandomAccessFile raf;
92:
93:
94: private LinkedHashMap<String, ZipEntry> entries;
95:
96: private boolean closed = false;
97:
98:
99:
109: private RandomAccessFile openFile(String name,
110: File file)
111: throws ZipException, IOException
112: {
113: try
114: {
115: return
116: (name != null)
117: ? new RandomAccessFile(name, "r")
118: : new RandomAccessFile(file, "r");
119: }
120: catch (FileNotFoundException f)
121: {
122: ZipException ze = new ZipException(f.getMessage());
123: ze.initCause(f);
124: throw ze;
125: }
126: }
127:
128:
129:
135: public ZipFile(String name) throws ZipException, IOException
136: {
137: this.raf = openFile(name,null);
138: this.name = name;
139: checkZipFile();
140: }
141:
142:
148: public ZipFile(File file) throws ZipException, IOException
149: {
150: this.raf = openFile(null,file);
151: this.name = file.getPath();
152: checkZipFile();
153: }
154:
155:
171: public ZipFile(File file, int mode) throws ZipException, IOException
172: {
173: if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE))
174: throw new IllegalArgumentException("invalid mode");
175: if ((mode & OPEN_DELETE) != 0)
176: file.deleteOnExit();
177: this.raf = openFile(null,file);
178: this.name = file.getPath();
179: checkZipFile();
180: }
181:
182: private void checkZipFile() throws ZipException
183: {
184: boolean valid = false;
185:
186: try
187: {
188: byte[] buf = new byte[4];
189: raf.readFully(buf);
190: int sig = buf[0] & 0xFF
191: | ((buf[1] & 0xFF) << 8)
192: | ((buf[2] & 0xFF) << 16)
193: | ((buf[3] & 0xFF) << 24);
194: valid = sig == LOCSIG;
195: }
196: catch (IOException _)
197: {
198: }
199:
200: if (!valid)
201: {
202: try
203: {
204: raf.close();
205: }
206: catch (IOException _)
207: {
208: }
209: throw new ZipException("Not a valid zip file");
210: }
211: }
212:
213:
216: private void checkClosed()
217: {
218: if (closed)
219: throw new IllegalStateException("ZipFile has closed: " + name);
220: }
221:
222:
230: private void readEntries() throws ZipException, IOException
231: {
232:
237: PartialInputStream inp = new PartialInputStream(raf, 4096);
238: long pos = raf.length() - ENDHDR;
239: long top = Math.max(0, pos - 65536);
240: do
241: {
242: if (pos < top)
243: throw new ZipException
244: ("central directory not found, probably not a zip file: " + name);
245: inp.seek(pos--);
246: }
247: while (inp.readLeInt() != ENDSIG);
248:
249: if (inp.skip(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
250: throw new EOFException(name);
251: int count = inp.readLeShort();
252: if (inp.skip(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
253: throw new EOFException(name);
254: int centralOffset = inp.readLeInt();
255:
256: entries = new LinkedHashMap<String, ZipEntry> (count+count/2);
257: inp.seek(centralOffset);
258:
259: for (int i = 0; i < count; i++)
260: {
261: if (inp.readLeInt() != CENSIG)
262: throw new ZipException("Wrong Central Directory signature: " + name);
263:
264: inp.skip(6);
265: int method = inp.readLeShort();
266: int dostime = inp.readLeInt();
267: int crc = inp.readLeInt();
268: int csize = inp.readLeInt();
269: int size = inp.readLeInt();
270: int nameLen = inp.readLeShort();
271: int extraLen = inp.readLeShort();
272: int commentLen = inp.readLeShort();
273: inp.skip(8);
274: int offset = inp.readLeInt();
275: String name = inp.readString(nameLen);
276:
277: ZipEntry entry = new ZipEntry(name);
278: entry.setMethod(method);
279: entry.setCrc(crc & 0xffffffffL);
280: entry.setSize(size & 0xffffffffL);
281: entry.setCompressedSize(csize & 0xffffffffL);
282: entry.setDOSTime(dostime);
283: if (extraLen > 0)
284: {
285: byte[] extra = new byte[extraLen];
286: inp.readFully(extra);
287: entry.setExtra(extra);
288: }
289: if (commentLen > 0)
290: {
291: entry.setComment(inp.readString(commentLen));
292: }
293: entry.offset = offset;
294: entries.put(name, entry);
295: }
296: }
297:
298:
305: public void close() throws IOException
306: {
307: RandomAccessFile raf = this.raf;
308: if (raf == null)
309: return;
310:
311: synchronized (raf)
312: {
313: closed = true;
314: entries = null;
315: raf.close();
316: }
317: }
318:
319:
323: protected void finalize() throws IOException
324: {
325: if (!closed && raf != null) close();
326: }
327:
328:
333: public Enumeration<? extends ZipEntry> entries()
334: {
335: checkClosed();
336:
337: try
338: {
339: return new ZipEntryEnumeration(getEntries().values().iterator());
340: }
341: catch (IOException ioe)
342: {
343: return EmptyEnumeration.getInstance();
344: }
345: }
346:
347:
353: private LinkedHashMap<String, ZipEntry> getEntries() throws IOException
354: {
355: synchronized(raf)
356: {
357: checkClosed();
358:
359: if (entries == null)
360: readEntries();
361:
362: return entries;
363: }
364: }
365:
366:
375: public ZipEntry getEntry(String name)
376: {
377: checkClosed();
378:
379: try
380: {
381: LinkedHashMap<String, ZipEntry> entries = getEntries();
382: ZipEntry entry = entries.get(name);
383:
384: if (entry == null && !name.endsWith("/"))
385: entry = entries.get(name + '/');
386: return entry != null ? new ZipEntry(entry, name) : null;
387: }
388: catch (IOException ioe)
389: {
390: return null;
391: }
392: }
393:
394:
416: public InputStream getInputStream(ZipEntry entry) throws IOException
417: {
418: checkClosed();
419:
420: LinkedHashMap<String, ZipEntry> entries = getEntries();
421: String name = entry.getName();
422: ZipEntry zipEntry = entries.get(name);
423: if (zipEntry == null)
424: return null;
425:
426: PartialInputStream inp = new PartialInputStream(raf, 1024);
427: inp.seek(zipEntry.offset);
428:
429: if (inp.readLeInt() != LOCSIG)
430: throw new ZipException("Wrong Local header signature: " + name);
431:
432: inp.skip(4);
433:
434: if (zipEntry.getMethod() != inp.readLeShort())
435: throw new ZipException("Compression method mismatch: " + name);
436:
437: inp.skip(16);
438:
439: int nameLen = inp.readLeShort();
440: int extraLen = inp.readLeShort();
441: inp.skip(nameLen + extraLen);
442:
443: inp.setLength(zipEntry.getCompressedSize());
444:
445: int method = zipEntry.getMethod();
446: switch (method)
447: {
448: case ZipOutputStream.STORED:
449: return inp;
450: case ZipOutputStream.DEFLATED:
451: inp.addDummyByte();
452: final Inflater inf = new Inflater(true);
453: final int sz = (int) entry.getSize();
454: return new InflaterInputStream(inp, inf)
455: {
456: public int available() throws IOException
457: {
458: if (sz == -1)
459: return super.available();
460: if (super.available() != 0)
461: return sz - inf.getTotalOut();
462: return 0;
463: }
464: };
465: default:
466: throw new ZipException("Unknown compression method " + method);
467: }
468: }
469:
470:
473: public String getName()
474: {
475: return name;
476: }
477:
478:
483: public int size()
484: {
485: checkClosed();
486:
487: try
488: {
489: return getEntries().size();
490: }
491: catch (IOException ioe)
492: {
493: return 0;
494: }
495: }
496:
497: private static class ZipEntryEnumeration implements Enumeration<ZipEntry>
498: {
499: private final Iterator<ZipEntry> elements;
500:
501: public ZipEntryEnumeration(Iterator<ZipEntry> elements)
502: {
503: this.elements = elements;
504: }
505:
506: public boolean hasMoreElements()
507: {
508: return elements.hasNext();
509: }
510:
511: public ZipEntry nextElement()
512: {
513:
516: return (ZipEntry) (elements.next().clone());
517: }
518: }
519:
520: private static final class PartialInputStream extends InputStream
521: {
522:
525: private static final Charset UTF8CHARSET = Charset.forName("UTF-8");
526:
527:
530: private CharsetDecoder utf8Decoder;
531:
532: private final RandomAccessFile raf;
533: private final byte[] buffer;
534: private long bufferOffset;
535: private int pos;
536: private long end;
537:
538:
539:
540:
541: private int dummyByteCount;
542:
543: public PartialInputStream(RandomAccessFile raf, int bufferSize)
544: throws IOException
545: {
546: this.raf = raf;
547: buffer = new byte[bufferSize];
548: bufferOffset = -buffer.length;
549: pos = buffer.length;
550: end = raf.length();
551: }
552:
553: void setLength(long length)
554: {
555: end = bufferOffset + pos + length;
556: }
557:
558: private void fillBuffer() throws IOException
559: {
560: synchronized (raf)
561: {
562: long len = end - bufferOffset;
563: if (len == 0 && dummyByteCount > 0)
564: {
565: buffer[0] = 0;
566: dummyByteCount = 0;
567: }
568: else
569: {
570: raf.seek(bufferOffset);
571: raf.readFully(buffer, 0, (int) Math.min(buffer.length, len));
572: }
573: }
574: }
575:
576: public int available()
577: {
578: long amount = end - (bufferOffset + pos);
579: if (amount > Integer.MAX_VALUE)
580: return Integer.MAX_VALUE;
581: return (int) amount;
582: }
583:
584: public int read() throws IOException
585: {
586: if (bufferOffset + pos >= end + dummyByteCount)
587: return -1;
588: if (pos == buffer.length)
589: {
590: bufferOffset += buffer.length;
591: pos = 0;
592: fillBuffer();
593: }
594:
595: return buffer[pos++] & 0xFF;
596: }
597:
598: public int read(byte[] b, int off, int len) throws IOException
599: {
600: if (len > end + dummyByteCount - (bufferOffset + pos))
601: {
602: len = (int) (end + dummyByteCount - (bufferOffset + pos));
603: if (len == 0)
604: return -1;
605: }
606:
607: int totalBytesRead = Math.min(buffer.length - pos, len);
608: System.arraycopy(buffer, pos, b, off, totalBytesRead);
609: pos += totalBytesRead;
610: off += totalBytesRead;
611: len -= totalBytesRead;
612:
613: while (len > 0)
614: {
615: bufferOffset += buffer.length;
616: pos = 0;
617: fillBuffer();
618: int remain = Math.min(buffer.length, len);
619: System.arraycopy(buffer, pos, b, off, remain);
620: pos += remain;
621: off += remain;
622: len -= remain;
623: totalBytesRead += remain;
624: }
625:
626: return totalBytesRead;
627: }
628:
629: public long skip(long amount) throws IOException
630: {
631: if (amount < 0)
632: return 0;
633: if (amount > end - (bufferOffset + pos))
634: amount = end - (bufferOffset + pos);
635: seek(bufferOffset + pos + amount);
636: return amount;
637: }
638:
639: void seek(long newpos) throws IOException
640: {
641: long offset = newpos - bufferOffset;
642: if (offset >= 0 && offset <= buffer.length)
643: {
644: pos = (int) offset;
645: }
646: else
647: {
648: bufferOffset = newpos;
649: pos = 0;
650: fillBuffer();
651: }
652: }
653:
654: void readFully(byte[] buf) throws IOException
655: {
656: if (read(buf, 0, buf.length) != buf.length)
657: throw new EOFException();
658: }
659:
660: void readFully(byte[] buf, int off, int len) throws IOException
661: {
662: if (read(buf, off, len) != len)
663: throw new EOFException();
664: }
665:
666: int readLeShort() throws IOException
667: {
668: int result;
669: if(pos + 1 < buffer.length)
670: {
671: result = ((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8);
672: pos += 2;
673: }
674: else
675: {
676: int b0 = read();
677: int b1 = read();
678: if (b1 == -1)
679: throw new EOFException();
680: result = (b0 & 0xff) | (b1 & 0xff) << 8;
681: }
682: return result;
683: }
684:
685: int readLeInt() throws IOException
686: {
687: int result;
688: if(pos + 3 < buffer.length)
689: {
690: result = (((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8)
691: | ((buffer[pos + 2] & 0xff)
692: | (buffer[pos + 3] & 0xff) << 8) << 16);
693: pos += 4;
694: }
695: else
696: {
697: int b0 = read();
698: int b1 = read();
699: int b2 = read();
700: int b3 = read();
701: if (b3 == -1)
702: throw new EOFException();
703: result = (((b0 & 0xff) | (b1 & 0xff) << 8) | ((b2 & 0xff)
704: | (b3 & 0xff) << 8) << 16);
705: }
706: return result;
707: }
708:
709:
725: private String decodeChars(byte[] buffer, int pos, int length)
726: throws IOException
727: {
728: String result;
729: int i=length - 1;
730: while ((i >= 0) && (buffer[i] <= 0x7f))
731: {
732: i--;
733: }
734: if (i < 0)
735: {
736: result = new String(buffer, 0, pos, length);
737: }
738: else
739: {
740: ByteBuffer bufferBuffer = ByteBuffer.wrap(buffer, pos, length);
741: if (utf8Decoder == null)
742: utf8Decoder = UTF8CHARSET.newDecoder();
743: utf8Decoder.reset();
744: char [] characters = utf8Decoder.decode(bufferBuffer).array();
745: result = String.valueOf(characters);
746: }
747: return result;
748: }
749:
750: String readString(int length) throws IOException
751: {
752: if (length > end - (bufferOffset + pos))
753: throw new EOFException();
754:
755: String result = null;
756: try
757: {
758: if (buffer.length - pos >= length)
759: {
760: result = decodeChars(buffer, pos, length);
761: pos += length;
762: }
763: else
764: {
765: byte[] b = new byte[length];
766: readFully(b);
767: result = decodeChars(b, 0, length);
768: }
769: }
770: catch (UnsupportedEncodingException uee)
771: {
772: throw new AssertionError(uee);
773: }
774: return result;
775: }
776:
777: public void addDummyByte()
778: {
779: dummyByteCount = 1;
780: }
781: }
782: }