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(4);
265: int flags = inp.readLeShort();
266: if ((flags & 1) != 0)
267: throw new ZipException("invalid CEN header (encrypted entry)");
268: int method = inp.readLeShort();
269: int dostime = inp.readLeInt();
270: int crc = inp.readLeInt();
271: int csize = inp.readLeInt();
272: int size = inp.readLeInt();
273: int nameLen = inp.readLeShort();
274: int extraLen = inp.readLeShort();
275: int commentLen = inp.readLeShort();
276: inp.skip(8);
277: int offset = inp.readLeInt();
278: String name = inp.readString(nameLen);
279:
280: ZipEntry entry = new ZipEntry(name);
281: entry.setMethod(method);
282: entry.setCrc(crc & 0xffffffffL);
283: entry.setSize(size & 0xffffffffL);
284: entry.setCompressedSize(csize & 0xffffffffL);
285: entry.setDOSTime(dostime);
286: if (extraLen > 0)
287: {
288: byte[] extra = new byte[extraLen];
289: inp.readFully(extra);
290: entry.setExtra(extra);
291: }
292: if (commentLen > 0)
293: {
294: entry.setComment(inp.readString(commentLen));
295: }
296: entry.offset = offset;
297: entries.put(name, entry);
298: }
299: }
300:
301:
308: public void close() throws IOException
309: {
310: RandomAccessFile raf = this.raf;
311: if (raf == null)
312: return;
313:
314: synchronized (raf)
315: {
316: closed = true;
317: entries = null;
318: raf.close();
319: }
320: }
321:
322:
326: protected void finalize() throws IOException
327: {
328: if (!closed && raf != null) close();
329: }
330:
331:
336: public Enumeration<? extends ZipEntry> entries()
337: {
338: checkClosed();
339:
340: try
341: {
342: return new ZipEntryEnumeration(getEntries().values().iterator());
343: }
344: catch (IOException ioe)
345: {
346: return new EmptyEnumeration<ZipEntry>();
347: }
348: }
349:
350:
356: private LinkedHashMap<String, ZipEntry> getEntries() throws IOException
357: {
358: synchronized(raf)
359: {
360: checkClosed();
361:
362: if (entries == null)
363: readEntries();
364:
365: return entries;
366: }
367: }
368:
369:
378: public ZipEntry getEntry(String name)
379: {
380: checkClosed();
381:
382: try
383: {
384: LinkedHashMap<String, ZipEntry> entries = getEntries();
385: ZipEntry entry = entries.get(name);
386:
387: if (entry == null && !name.endsWith("/"))
388: entry = entries.get(name + '/');
389: return entry != null ? new ZipEntry(entry, name) : null;
390: }
391: catch (IOException ioe)
392: {
393: return null;
394: }
395: }
396:
397:
419: public InputStream getInputStream(ZipEntry entry) throws IOException
420: {
421: checkClosed();
422:
423: LinkedHashMap<String, ZipEntry> entries = getEntries();
424: String name = entry.getName();
425: ZipEntry zipEntry = entries.get(name);
426: if (zipEntry == null)
427: return null;
428:
429: PartialInputStream inp = new PartialInputStream(raf, 1024);
430: inp.seek(zipEntry.offset);
431:
432: if (inp.readLeInt() != LOCSIG)
433: throw new ZipException("Wrong Local header signature: " + name);
434:
435: inp.skip(4);
436:
437: if (zipEntry.getMethod() != inp.readLeShort())
438: throw new ZipException("Compression method mismatch: " + name);
439:
440: inp.skip(16);
441:
442: int nameLen = inp.readLeShort();
443: int extraLen = inp.readLeShort();
444: inp.skip(nameLen + extraLen);
445:
446: inp.setLength(zipEntry.getCompressedSize());
447:
448: int method = zipEntry.getMethod();
449: switch (method)
450: {
451: case ZipOutputStream.STORED:
452: return inp;
453: case ZipOutputStream.DEFLATED:
454: inp.addDummyByte();
455: final Inflater inf = new Inflater(true);
456: final int sz = (int) entry.getSize();
457: return new InflaterInputStream(inp, inf)
458: {
459: public int available() throws IOException
460: {
461: if (sz == -1)
462: return super.available();
463: if (super.available() != 0)
464: return sz - inf.getTotalOut();
465: return 0;
466: }
467: };
468: default:
469: throw new ZipException("Unknown compression method " + method);
470: }
471: }
472:
473:
476: public String getName()
477: {
478: return name;
479: }
480:
481:
486: public int size()
487: {
488: checkClosed();
489:
490: try
491: {
492: return getEntries().size();
493: }
494: catch (IOException ioe)
495: {
496: return 0;
497: }
498: }
499:
500: private static class ZipEntryEnumeration implements Enumeration<ZipEntry>
501: {
502: private final Iterator<ZipEntry> elements;
503:
504: public ZipEntryEnumeration(Iterator<ZipEntry> elements)
505: {
506: this.elements = elements;
507: }
508:
509: public boolean hasMoreElements()
510: {
511: return elements.hasNext();
512: }
513:
514: public ZipEntry nextElement()
515: {
516:
519: return (ZipEntry) (elements.next().clone());
520: }
521: }
522:
523: private static final class PartialInputStream extends InputStream
524: {
525:
528: private static final Charset UTF8CHARSET = Charset.forName("UTF-8");
529:
530:
533: private CharsetDecoder utf8Decoder;
534:
535: private final RandomAccessFile raf;
536: private final byte[] buffer;
537: private long bufferOffset;
538: private int pos;
539: private long end;
540:
541:
542:
543:
544: private int dummyByteCount;
545:
546: public PartialInputStream(RandomAccessFile raf, int bufferSize)
547: throws IOException
548: {
549: this.raf = raf;
550: buffer = new byte[bufferSize];
551: bufferOffset = -buffer.length;
552: pos = buffer.length;
553: end = raf.length();
554: }
555:
556: void setLength(long length)
557: {
558: end = bufferOffset + pos + length;
559: }
560:
561: private void fillBuffer() throws IOException
562: {
563: synchronized (raf)
564: {
565: long len = end - bufferOffset;
566: if (len == 0 && dummyByteCount > 0)
567: {
568: buffer[0] = 0;
569: dummyByteCount = 0;
570: }
571: else
572: {
573: raf.seek(bufferOffset);
574: raf.readFully(buffer, 0, (int) Math.min(buffer.length, len));
575: }
576: }
577: }
578:
579: public int available()
580: {
581: long amount = end - (bufferOffset + pos);
582: if (amount > Integer.MAX_VALUE)
583: return Integer.MAX_VALUE;
584: return (int) amount;
585: }
586:
587: public int read() throws IOException
588: {
589: if (bufferOffset + pos >= end + dummyByteCount)
590: return -1;
591: if (pos == buffer.length)
592: {
593: bufferOffset += buffer.length;
594: pos = 0;
595: fillBuffer();
596: }
597:
598: return buffer[pos++] & 0xFF;
599: }
600:
601: public int read(byte[] b, int off, int len) throws IOException
602: {
603: if (len > end + dummyByteCount - (bufferOffset + pos))
604: {
605: len = (int) (end + dummyByteCount - (bufferOffset + pos));
606: if (len == 0)
607: return -1;
608: }
609:
610: int totalBytesRead = Math.min(buffer.length - pos, len);
611: System.arraycopy(buffer, pos, b, off, totalBytesRead);
612: pos += totalBytesRead;
613: off += totalBytesRead;
614: len -= totalBytesRead;
615:
616: while (len > 0)
617: {
618: bufferOffset += buffer.length;
619: pos = 0;
620: fillBuffer();
621: int remain = Math.min(buffer.length, len);
622: System.arraycopy(buffer, pos, b, off, remain);
623: pos += remain;
624: off += remain;
625: len -= remain;
626: totalBytesRead += remain;
627: }
628:
629: return totalBytesRead;
630: }
631:
632: public long skip(long amount) throws IOException
633: {
634: if (amount < 0)
635: return 0;
636: if (amount > end - (bufferOffset + pos))
637: amount = end - (bufferOffset + pos);
638: seek(bufferOffset + pos + amount);
639: return amount;
640: }
641:
642: void seek(long newpos) throws IOException
643: {
644: long offset = newpos - bufferOffset;
645: if (offset >= 0 && offset <= buffer.length)
646: {
647: pos = (int) offset;
648: }
649: else
650: {
651: bufferOffset = newpos;
652: pos = 0;
653: fillBuffer();
654: }
655: }
656:
657: void readFully(byte[] buf) throws IOException
658: {
659: if (read(buf, 0, buf.length) != buf.length)
660: throw new EOFException();
661: }
662:
663: void readFully(byte[] buf, int off, int len) throws IOException
664: {
665: if (read(buf, off, len) != len)
666: throw new EOFException();
667: }
668:
669: int readLeShort() throws IOException
670: {
671: int result;
672: if(pos + 1 < buffer.length)
673: {
674: result = ((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8);
675: pos += 2;
676: }
677: else
678: {
679: int b0 = read();
680: int b1 = read();
681: if (b1 == -1)
682: throw new EOFException();
683: result = (b0 & 0xff) | (b1 & 0xff) << 8;
684: }
685: return result;
686: }
687:
688: int readLeInt() throws IOException
689: {
690: int result;
691: if(pos + 3 < buffer.length)
692: {
693: result = (((buffer[pos + 0] & 0xff) | (buffer[pos + 1] & 0xff) << 8)
694: | ((buffer[pos + 2] & 0xff)
695: | (buffer[pos + 3] & 0xff) << 8) << 16);
696: pos += 4;
697: }
698: else
699: {
700: int b0 = read();
701: int b1 = read();
702: int b2 = read();
703: int b3 = read();
704: if (b3 == -1)
705: throw new EOFException();
706: result = (((b0 & 0xff) | (b1 & 0xff) << 8) | ((b2 & 0xff)
707: | (b3 & 0xff) << 8) << 16);
708: }
709: return result;
710: }
711:
712:
728: private String decodeChars(byte[] buffer, int pos, int length)
729: throws IOException
730: {
731: String result;
732: int i=length - 1;
733: while ((i >= 0) && (buffer[i] <= 0x7f))
734: {
735: i--;
736: }
737: if (i < 0)
738: {
739: result = new String(buffer, 0, pos, length);
740: }
741: else
742: {
743: ByteBuffer bufferBuffer = ByteBuffer.wrap(buffer, pos, length);
744: if (utf8Decoder == null)
745: utf8Decoder = UTF8CHARSET.newDecoder();
746: utf8Decoder.reset();
747: char [] characters = utf8Decoder.decode(bufferBuffer).array();
748: result = String.valueOf(characters);
749: }
750: return result;
751: }
752:
753: String readString(int length) throws IOException
754: {
755: if (length > end - (bufferOffset + pos))
756: throw new EOFException();
757:
758: String result = null;
759: try
760: {
761: if (buffer.length - pos >= length)
762: {
763: result = decodeChars(buffer, pos, length);
764: pos += length;
765: }
766: else
767: {
768: byte[] b = new byte[length];
769: readFully(b);
770: result = decodeChars(b, 0, length);
771: }
772: }
773: catch (UnsupportedEncodingException uee)
774: {
775: throw new AssertionError(uee);
776: }
777: return result;
778: }
779:
780: public void addDummyByte()
781: {
782: dummyByteCount = 1;
783: }
784: }
785: }