/************************************************************************** * Copyright (c) 2001 by Punch Telematix. All rights reserved. * * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * 1. Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * 3. Neither the name of Punch Telematix nor the names of * * other contributors may be used to endorse or promote products * * derived from this software without specific prior written permission.* * * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * * IN NO EVENT SHALL PUNCH TELEMATIX OR OTHER CONTRIBUTORS BE LIABLE * * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **************************************************************************/ /** * $Id: ZipFile.java,v 1.4 2006/06/09 09:43:34 cvs Exp $ */ package java.util.zip; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.util.Enumeration; /** ** Class for manipulating zip files. ** IMPORTANT: if you add any set...() methods to this class, be sure to ** override them in wonka.net.jar.ImmutableJarFile! */ public class ZipFile implements ZipConstants { public static final int OPEN_READ = 1; public static final int OPEN_DELETE = 4; private final static byte CDS0 = endCenDirS[0]; private final static byte CDS1 = endCenDirS[1]; private final static byte CDS2 = endCenDirS[2]; private final static byte CDS3 = endCenDirS[3]; private final static byte FHS0 = cenFileHeaderS[0]; private final static byte FHS1 = cenFileHeaderS[1]; private final static byte FHS2 = cenFileHeaderS[2]; private final static byte FHS3 = cenFileHeaderS[3]; private RandomAccessFile raf; private String name; private int nrEntries; String[] strings; long[] longs; int capacity; // static { //only to avoid bootstrapping problems ... // new java.util.jar.JarEntry(""); // } public ZipFile (String name) throws IOException { this.name = name; this.raf = new RandomAccessFile(name, "r"); getEntries(); if (strings == null){ throw new ZipException("Bad or No ZipFile "); } } public ZipFile (File file) throws ZipException, IOException { this.name = file.getPath(); this.raf = new RandomAccessFile(file, "r"); getEntries(); if (strings == null){ throw new ZipException("Bad or No ZipFile "); } } public ZipFile (File file, int mode) throws ZipException, IOException { this(file); if(mode == OPEN_DELETE){ throw new IllegalArgumentException("OPEN_DELELTE: Unsupported feature"); } } public void close() throws IOException { nrEntries = 0; capacity = 1; strings = new String[1]; longs = null; } public Enumeration entries(){ return new Enum(); } protected void finalize() throws IOException { close(); } public ZipEntry getEntry(String zname) { int hash = zname.hashCode() % capacity; while(true) { if(hash < 0){ hash += capacity; } if(strings[hash] == null) { return null; } else if(zname.equals(strings[hash])) { break; } hash--; } try { return new ZipEntry(zname, longs[hash], this); } catch(Exception e){} return null; } public String getName(){ return name; } public InputStream getInputStream(ZipEntry ze) throws IOException{ //check if entry belongs to this zipFile! if(ze.zipFile != this){ throw new IOException("entry " + ze + " doesn't not belong to file " + this); } if(ze.initPointer != 0) { createEntry(ze); } return new ZipByteArrayInputStream(checkHeader(ze)); } /** ** since jdk 1.2 ... */ public int size() { return nrEntries; } /** ** called from constructor ... (no synchronization needed on raf)! */ private void getEntries() throws IOException { //our file system uses buffers of at least 1024 bytes long pos = raf.length(); wonka.vm.Etc.woempa(7, "raf.length() = " + pos); int size = (1024 > pos ? (int)pos : 1024); byte [] bytes = new byte[size]; while (pos > 0) { pos -= size; wonka.vm.Etc.woempa(7, "seeking to offset " + pos + " and reading " + size + " bytes"); raf.seek(pos); raf.readFully(bytes,0,size); size = locateEndCD(bytes, size); wonka.vm.Etc.woempa(7, "read " + size + " bytes"); if (size == -1) { break; } if (size > 0) { System.arraycopy(bytes,0,bytes,bytes.length-size,size); } size = (1024 > pos ? (int)pos : 1024)-size; } } /** ** called from within constructor ... (no synchronization needed on raf)! */ private int locateEndCD(byte[] bytes, int size) throws IOException { //we start looking for endCenDirS for (int p=size ; --p > -1 ; ) { if (bytes[p] == CDS3) { //this might be the last byte of the signature! if (p >=3) { if (bytes[p-1] == CDS2 && bytes[p-2] == CDS1 && bytes[p-3] == CDS0) { //we have found a signature ... if(readEntries(bytes, size, p+1)){ return -1; } } } else { return p; } } } return 0; } /** ** called from constructor ... (no synchronization needed on raf)! */ private boolean readEntries(byte [] bytes, int size, int pos) throws IOException { int nrE = size - pos; nrE = (16 > nrE ? nrE : 16); byte [] b = new byte [16]; System.arraycopy(bytes, pos, b,0, nrE); if (nrE < 16){ raf.readFully(b,nrE,16-nrE); } //b should contain 16 bytes of the possible header nrE = (0x0ff&(char)b[6]) + (0x0ff &(char)b[7])*256; long stP = bytesToLong(b, 12); size = (int)bytesToLong(b, 8); if (nrE < 0 || (stP+size) > raf.length() || size < 0) { // we encountered a fony header return false; } raf.seek(stP); b = new byte[size]; raf.readFully(b); pos=0; //creating internal hastable structure. int capacity = ((int)(nrE/0.74f)) + 1; this.capacity = capacity; strings = new String[capacity]; longs = new long[capacity]; //end of creation. for (int i=0; i < nrE ; i++) { if (pos+46 > size) { throw new ZipException("too few bytes for header");//bad Header ... } if (FHS0 != b[pos] || FHS1 != b[pos+1] || FHS2 != b[pos+2] || FHS3 != b[pos+3]){ throw new ZipException("bad header in central file directory");//bad Header ... } long pointer = stP + pos; // we have found a correct header ... // lets skip useless bytes pos+=28; int len = (0x0ff&(char)b[pos]) + (0x0ff &(char)b[pos+1])*256; pos+=18; if (pos+len > size) { throw new ZipException("not enough bytes in header"); } String name = new String(b,pos,len); pos-=36; int hlp = (0x0ff&(char)b[pos]) + (0x0ff &(char)b[pos+1])*256; if (hlp != 0 && hlp != 8) { throw new ZipException("unknown store/zip method "+hlp); } pos += 20; hlp = (0x0ff&(char)b[pos]) + (0x0ff &(char)b[pos+1])*256; pos+=2; int com = (0x0ff&(char)b[pos]) + (0x0ff &(char)b[pos+1])*256; if(hlp < 0 || com < 0 || pos+14+len+hlp+com > size) { throw new ZipException("invalid header data"); } pos+=10; //inlined put method. int hashcode = name.hashCode() % capacity; while(true){ if(hashcode < 0){ hashcode += capacity; } if(strings[hashcode] == null) { strings[hashcode] = name; longs[hashcode] = pointer; break; } else if(name.equals(strings[hashcode])) { throw new IOException("duplicate entry found "+name); } hashcode--; } //end put method. pos+= 4+len; if (hlp > 0) { pos += hlp; } if (com > 0) { pos += com; } } nrEntries = nrE; return true; } private byte[] checkHeader(ZipEntry ze) throws IOException { if (ze.data != null) { return ze.data; } synchronized(raf){ raf.seek(ze.pointer); byte [] header = new byte [30]; raf.readFully(header,0,30); if ( header[0] != locFileHeaderS[0] || header[1] != locFileHeaderS[1] || header[2] != locFileHeaderS[2] || header[3] != locFileHeaderS[3]) { throw new ZipException("corrupt header found"); } // we have 30 bytes --> 4 are already checked Signature // version needed --> ... (2 bytes) // general purpose bytes (2) int mode = (0x0ff&(char)header[4]) + (0x0ff &(char)header[5])*256; if (mode > 20) { throw new ZipException("higher zip version needed"); } mode = (0x08 & header[6]); int fnlen = (0x0ff & header[26]) + (0x0ff & header[27])*256; byte [] fname = new byte[fnlen]; raf.readFully(fname,0, fnlen); String name = new String(fname, 0, fnlen); if (!ze.name.equals(name)) { throw new ZipException("entry pointed to wrong data '" + name + "' != '" + ze.name + "'"); } fnlen = (0x0ff&(char)header[28]) + (0x0ff &(char)header[29])*256; fname = new byte[fnlen]; if (fnlen != 0) { raf.readFully(fname,0, fnlen); } mode = (0x0ff&(char)header[8]) + (0x0ff &(char)header[9])*256; if (mode != 8 && mode != 0) { throw new ZipException("stream is corrupted"); } if (mode == 8) { header = new byte[(int) ze.compressedSize]; raf.readFully(header); header = quickInflate(header, (int)ze.size,(int)ze.crc); } else { header = new byte[(int) ze.size]; raf.readFully(header); } ze.data = header; return header; // the rest of the bytes are not verified } } void createEntry(ZipEntry ze) throws IOException { synchronized(raf){ raf.seek(ze.initPointer); int pos = 0; byte[] b = new byte[46]; raf.readFully(b); if (FHS0 != b[pos] || FHS1 != b[pos+1] || FHS2 != b[pos+2] || FHS3 != b[pos+3]){ throw new ZipException("bad header in central file directory");//bad Header ... } // we have found a correct header ... // lets skip useless bytes pos+=28; int len = (0x0ff&(char)b[pos]) + (0x0ff &(char)b[pos+1])*256; pos+=18; byte[] name = new byte[len]; raf.readFully(name); pos-=36; int hlp = (0x0ff&(char)b[pos]) + (0x0ff &(char)b[pos+1])*256; if (hlp != 0 && hlp != 8) { throw new ZipException("unknown store/zip method "+hlp); } ze.compressionMethod = hlp; pos+=2; ze.time = getDate(b,pos);; pos+=4; ze.crc = bytesToLong(b,pos); pos+=4; ze.compressedSize = bytesToLong(b,pos); pos+=4; ze.size = bytesToLong(b,pos); pos+=6; hlp = (0x0ff&(char)b[pos]) + (0x0ff &(char)b[pos+1])*256; pos+=2; int com = (0x0ff&(char)b[pos]) + (0x0ff &(char)b[pos+1])*256; if(hlp < 0 || com < 0) { throw new ZipException("invalid header data"); } pos += 10; ze.pointer = bytesToLong(b,pos); if (hlp > 0) { byte [] extra = new byte[hlp]; raf.readFully(extra); ze.extra = extra; } if (com > 0) { byte[] comment = new byte[com]; raf.readFully(comment); ze.comment = new String(comment); } ze.initPointer = 0; } } private class Enum implements Enumeration { int index = -1; Enum(){ for(int i=0 ; i < capacity ; i++){ if(strings[i] != null){ index = i; break; } } } public boolean hasMoreElements() { return index != -1; } public Object nextElement(){ if(index == -1){ throw new java.util.NoSuchElementException(); } String ename = strings[index]; int i=index+1; index = -1; for( ; i < capacity ; i++){ if(strings[i] != null){ index = i; break; } } try { return getEntry(ename); } catch(Exception e){ return null; } } } static native byte[] quickInflate(byte[] cData, int uSize, int CRC) throws ZipException; /** ** these methods are static (default acces) so ZipInputStream is able call it */ native static long bytesToLong(byte [] header, int offset); native static long getDate(byte [] header, int offset); }