mass rename and rebranding from xwt to ibex - fixed to use ixt files
[org.ibex.core.git] / src / org / ibex / util / CAB.java
diff --git a/src/org/ibex/util/CAB.java b/src/org/ibex/util/CAB.java
new file mode 100644 (file)
index 0000000..96e0947
--- /dev/null
@@ -0,0 +1,505 @@
+// Copyright (C) 2003 Adam Megacz <adam@ibex.org> all rights reserved.
+//
+// You may modify, copy, and redistribute this code under the terms of
+// the GNU Library Public License version 2.1, with the exception of
+// the portion of clause 6a after the semicolon (aka the "obnoxious
+// relink clause")
+
+package org.ibex.util;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.*;
+
+/** Reads a CAB file structure */
+public class CAB {
+
+    /** reads a CAB file, parses it, and returns an InputStream representing the named file */
+    public static InputStream getFileInputStream(InputStream is, String fileName) throws IOException, EOFException {
+      return getFileInputStream(is, 0, fileName);
+    }
+
+    public static InputStream getFileInputStream(InputStream is, int skipHeaders, String fileName) throws IOException, EOFException {
+        DataInputStream dis = new DataInputStream(is);
+        CFHEADER h = new CFHEADER();
+
+        while (skipHeaders > 0) { CFHEADER.seekMSCF(dis); skipHeaders--; }
+
+        try {
+         h.read(dis);
+        } catch (CFHEADER.BogusHeaderException bhe) {
+         throw new EOFException();
+        }
+
+        for(int i=0; i<h.folders.length; i++) {
+            CFFOLDER f = new CFFOLDER(h);
+            try {
+               f.read(dis);
+            } catch (CFFOLDER.UnsupportedCompressionTypeException ucte) {
+               throw ucte;
+            }
+        }
+
+        for(int i=0; i<h.files.length; i++) {
+            CFFILE f = new CFFILE(h);
+            f.read(dis);
+        }
+        
+        for(int i=0; i<h.folders.length; i++) {
+            InputStream is2 = new CFFOLDERInputStream(h.folders[i], dis);
+            for(int j=0; j<h.folders[i].files.size(); j++) {
+                CFFILE file = (CFFILE)h.folders[i].files.elementAt(j);
+                if (file.fileName.equals(fileName)) return new LimitStream(is2, file.fileSize);
+                byte[] b = new byte[file.fileSize];
+                int read = is2.read(b);
+            }
+        }
+
+        return null;
+    }
+
+    private static class LimitStream extends FilterInputStream {
+        int limit;
+        public LimitStream(InputStream is, int limit) {
+            super(is);
+            this.limit = limit;
+        }
+        public int read() throws IOException {
+            if (limit == 0) return -1;
+            int ret = super.read();
+            if (ret != -1) limit--;
+            return ret;
+        }
+        public int read(byte[] b, int off, int len) throws IOException {
+            if (len > limit) len = limit;
+            if (limit == 0) return -1;
+            int ret = super.read(b, off, len);
+            limit -= ret;
+            return ret;
+        }
+    }
+    
+    /** Encapsulates a CFHEADER entry */
+    public static class CFHEADER {
+        byte[]   reserved1 = new byte[4];       // reserved
+        int      fileSize = 0;                  // size of this cabinet file in bytes
+        byte[]   reserved2 = new byte[4];       // reserved
+        int      offsetOfFirstCFFILEEntry;      // offset of the first CFFILE entry
+        byte[]   reserved3 = new byte[4];       // reserved
+        byte     versionMinor = 3;              // cabinet file format version, minor
+        byte     versionMajor = 1;              // cabinet file format version, major
+        boolean  prevCAB = false;               // true iff there is a cabinet before this one in a sequence
+        boolean  nextCAB = false;               // true iff there is a cabinet after this one in a sequence
+        boolean  hasReserved = false;           // true iff the cab has per-{cabinet, folder, block} reserved areas
+        int      setID = 0;                     // must be the same for all cabinets in a set
+        int      indexInCabinetSet = 0;         // number of this cabinet file in a set
+        byte     perCFFOLDERReservedSize = 0;   // (optional) size of per-folder reserved area
+        byte     perDatablockReservedSize = 0;  // (optional) size of per-datablock reserved area
+        byte[]   perCabinetReservedArea = null; // per-cabinet reserved area
+        String   previousCabinet = null;        // name of previous cabinet file in a set
+        String   previousDisk = null;           // name of previous disk in a set
+        String   nextCabinet = null;            // name of next cabinet in a set
+        String   nextDisk = null;               // name of next disk in a set
+
+        CFFOLDER[] folders = new CFFOLDER[0];
+        CFFILE[] files = new CFFILE[0];
+
+        int readCFFOLDERs = 0;                    // the number of folders read in so far
+        int readCFFILEs = 0;                      // the number of folders read in so far
+
+        public CFHEADER() { }
+
+        public void print(PrintStream ps) {
+            ps.println("CAB CFFILE CFHEADER v" + ((int)versionMajor) + "." + ((int)versionMinor));
+            ps.println("    total file size               = " + fileSize);
+            ps.println("    offset of first file          = " + offsetOfFirstCFFILEEntry);
+            ps.println("    total folders                 = " + folders.length);
+            ps.println("    total files                   = " + files.length);
+            ps.println("    flags                         = 0x" +
+                       Integer.toString((prevCAB ? 0x1 : 0x0) |
+                                        (nextCAB ? 0x2 : 0x0) |
+                                        (hasReserved ? 0x4 : 0x0), 16) + " [ " +
+                       (prevCAB ? "prev " : "") + 
+                       (nextCAB ? "next " : "") + 
+                       (hasReserved ? "reserve_present " : "") + "]");
+            ps.println("    set id                        = " + setID);
+            ps.println("    index in set                  = " + indexInCabinetSet);
+            ps.println("    header reserved area #1       =" +
+                       " 0x" + Integer.toString(reserved1[0], 16) +
+                       " 0x" + Integer.toString(reserved1[1], 16) +
+                       " 0x" + Integer.toString(reserved1[2], 16) +
+                       " 0x" + Integer.toString(reserved1[3], 16));
+            ps.println("    header reserved area #2       =" +
+                       " 0x" + Integer.toString(reserved2[0], 16) +
+                       " 0x" + Integer.toString(reserved2[1], 16) +
+                       " 0x" + Integer.toString(reserved2[2], 16) +
+                       " 0x" + Integer.toString(reserved2[3], 16));
+            ps.println("    header reserved area #3       =" +
+                       " 0x" + Integer.toString(reserved3[0], 16) +
+                       " 0x" + Integer.toString(reserved3[1], 16) +
+                       " 0x" + Integer.toString(reserved3[2], 16) +
+                       " 0x" + Integer.toString(reserved3[3], 16));
+            if (hasReserved) {
+                if (perCabinetReservedArea != null) {
+                    ps.print("    per-cabinet reserved area     = ");
+                    for(int i=0; i<perCabinetReservedArea.length; i++)
+                        ps.print(((perCabinetReservedArea[i] & 0xff) < 16 ? "0" : "") +
+                                 Integer.toString(perCabinetReservedArea[i] & 0xff, 16) + " ");
+                    ps.println();
+                }
+                ps.println("    per folder  reserved area     = " + perCFFOLDERReservedSize + " bytes");
+                ps.println("    per block   reserved area     = " + perDatablockReservedSize + " bytes");
+            }
+        }
+
+        public String toString() {
+            return
+                "[ CAB CFFILE CFHEADER v" +
+                ((int)versionMajor) + "." + ((int)versionMinor) + ", " +
+                fileSize + " bytes, " +
+                folders.length + " folders, " +
+                files.length + " files] ";
+        }
+
+        /** fills in all fields in the header and positions the stream at the first folder */
+        public void read(DataInputStream dis) throws IOException, BogusHeaderException {
+            seekMSCF(dis);
+            dis.readFully(reserved1);
+
+            byte[] headerHashable = new byte[28];
+            dis.readFully(headerHashable);
+            DataInputStream hhis = new DataInputStream(new ByteArrayInputStream(headerHashable));
+
+            fileSize = readLittleInt(hhis);
+            hhis.readFully(reserved2);
+            offsetOfFirstCFFILEEntry = readLittleInt(hhis);
+            hhis.readFully(reserved3);
+            versionMinor = hhis.readByte();
+            versionMajor = hhis.readByte();
+            folders = new CFFOLDER[readLittleShort(hhis)];
+            files = new CFFILE[readLittleShort(hhis)];
+            int flags = readLittleShort(hhis);
+            prevCAB = (flags & 0x0001) != 0;
+            nextCAB = (flags & 0x0002) != 0;
+            hasReserved = (flags & 0x0004) != 0;
+            setID = readLittleShort(hhis);
+            indexInCabinetSet = readLittleShort(hhis);
+
+            if (offsetOfFirstCFFILEEntry < 0 || fileSize < 0) {
+               throw new BogusHeaderException();
+            }
+
+            if (hasReserved) {
+                perCabinetReservedArea = new byte[readLittleShort(dis)];
+                perCFFOLDERReservedSize = dis.readByte();
+                perDatablockReservedSize = dis.readByte();
+                if (perCabinetReservedArea.length > 0)
+                    dis.readFully(perCabinetReservedArea);
+            }
+
+            try {
+               if (prevCAB) {
+                   previousCabinet = readZeroTerminatedString(dis);
+                   previousDisk = readZeroTerminatedString(dis);
+               }
+               if (nextCAB) {
+                   nextCabinet = readZeroTerminatedString(dis);
+                   nextDisk = readZeroTerminatedString(dis);
+               }
+            } catch (ArrayIndexOutOfBoundsException e) {
+               throw new BogusHeaderException();
+            }
+        }
+
+        public static void seekMSCF(DataInputStream dis) throws EOFException, IOException
+        {
+           int state;
+           // skip up to and including the 'MSCF' signature
+           state = 0;
+           while (state != 4) {
+              // M
+              while (state == 0 && dis.readByte() != 0x4D) { }
+              state = 1;
+              // S
+              switch (dis.readByte()) {
+                 case 0x53 : state = 2; break;
+                 case 0x4D : state = 1; continue;
+                 default :   state = 0; continue;
+              }
+              // C
+              if (dis.readByte() == 0x43) { state = 3; }
+              else { state = 0; continue; }
+              // F
+              if (dis.readByte() == 0x46) { state = 4; }
+              else { state = 0; }
+           }
+        }
+
+        public static class BogusHeaderException extends IOException {}
+    }
+
+    /** Encapsulates a CFFOLDER entry */
+    public static class CFFOLDER {
+        public static final int COMPRESSION_NONE      = 0;
+        public static final int COMPRESSION_MSZIP     = 1;
+        public static final int COMPRESSION_QUANTUM   = 2;
+        public static final int COMPRESSION_LZX       = 3;
+
+        int      firstBlockOffset = 0;          // offset of first data block within this folder
+        int      numBlocks = 0;                 // number of data blocks
+        int      compressionType = 0;           // compression type for this folder
+        byte[]   reservedArea = null;           // per-folder reserved area
+        int      indexInCFHEADER = 0;           // our index in CFHEADER.folders
+        Vector   files = new Vector();
+
+        private CFHEADER header = null;
+
+        public CFFOLDER(CFHEADER header) { this.header = header; }
+
+        public String toString() {
+            return "[ CAB CFFOLDER, " + numBlocks + " data blocks, compression type " +
+                compressionName(compressionType) +
+                ", " + reservedArea.length + " bytes of reserved data ]";
+        }
+
+        public void read(DataInputStream dis) throws IOException, UnsupportedCompressionTypeException {
+            firstBlockOffset = readLittleInt(dis);
+            numBlocks = readLittleShort(dis);
+            compressionType = readLittleShort(dis) & 0x000F;
+            if (compressionType != COMPRESSION_MSZIP) {
+               throw new UnsupportedCompressionTypeException(compressionType);
+            }
+            reservedArea = new byte[header.perCFFOLDERReservedSize];
+            if (reservedArea.length > 0) dis.readFully(reservedArea);
+            indexInCFHEADER = header.readCFFOLDERs++;
+            header.folders[indexInCFHEADER] = this;
+        }
+
+        public static String compressionName(int type) {
+            switch (type) {
+               case COMPRESSION_NONE:
+                  return "NONE";
+               case COMPRESSION_MSZIP:
+                  return "MSZIP";
+               case COMPRESSION_QUANTUM:
+                  return "QUANTUM";
+               case COMPRESSION_LZX:
+                  return "LZX";
+               default:
+                  return "<Unknown type " + type + ">";
+            }
+        }
+
+        public static class UnsupportedCompressionTypeException extends IOException {
+            private int compressionType;
+
+            UnsupportedCompressionTypeException(int type) {
+               compressionType = type;
+            }
+            public String toString() {
+               return "UnsupportedCompressionTypeException: no support for compression type " + compressionName(compressionType);
+            }
+        }
+    }
+
+    /** Encapsulates a CFFILE entry */
+    public static class CFFILE {
+        int fileSize = 0;                       // size of this file
+        int uncompressedOffsetInCFFOLDER = 0;   // offset of this file within the folder, not accounting for compression
+        int folderIndex = 0;                    // index of the CFFOLDER we belong to
+        Date date = null;                       // modification date
+        int attrs = 0;                          // attrs
+        boolean readOnly = false;               // read-only flag
+        boolean hidden = false;                 // hidden flag
+        boolean system = false;                 // system flag
+        boolean arch = false;                   // archive flag
+        boolean runAfterExec = false;           // true if file should be run during extraction
+        boolean UTFfileName = false;            // true if filename is UTF-encoded
+        String fileName = null;                 // filename
+        int indexInCFHEADER = 0;                // our index in CFHEADER.files
+        CFFOLDER folder = null;                 // the folder we belong to
+        private CFHEADER header = null;
+        File myFile;
+
+        public CFFILE(CFHEADER header) { this.header = header; }
+
+        public CFFILE(File f, String pathName) throws IOException {
+            fileSize = (int)f.length();
+            folderIndex = 0;
+            date = new java.util.Date(f.lastModified());
+            fileName = pathName;
+            myFile = f;
+        }
+
+        public String toString() {
+            return "[ CAB CFFILE: " + fileName + ", " + fileSize + " bytes [ " +
+                (readOnly ? "readonly " : "") +
+                (system ? "system " : "") +
+                (hidden ? "hidden " : "") +
+                (arch ? "arch " : "") +
+                (runAfterExec ? "run_after_exec " : "") +
+                (UTFfileName ? "UTF_filename " : "") +
+                "]";
+        }
+
+        public void read(DataInputStream dis) throws IOException {
+            fileSize = readLittleInt(dis);
+            uncompressedOffsetInCFFOLDER = readLittleInt(dis);
+            folderIndex = readLittleShort(dis);
+            readLittleShort(dis);   // date
+            readLittleShort(dis);   // time
+            attrs = readLittleShort(dis);
+            readOnly = (attrs & 0x1) != 0;
+            hidden = (attrs & 0x2) != 0;
+            system = (attrs & 0x4) != 0;
+            arch = (attrs & 0x20) != 0;
+            runAfterExec = (attrs & 0x40) != 0;
+            UTFfileName = (attrs & 0x80) != 0;
+            fileName = readZeroTerminatedString(dis);
+
+            indexInCFHEADER = header.readCFFILEs++;
+            header.files[indexInCFHEADER] = this;
+            folder = header.folders[folderIndex];
+            folder.files.addElement(this);
+        }
+    }
+
+
+
+
+    // Compressing Input and Output Streams ///////////////////////////////////////////////
+
+    /** an InputStream that decodes CFDATA blocks belonging to a CFFOLDER */
+    private static class CFFOLDERInputStream extends InputStream {
+        CFFOLDER folder;
+        DataInputStream dis;
+        InputStream iis = null;
+
+        byte[] compressed = new byte[128 * 1024];
+        byte[] uncompressed = new byte[256 * 1024];
+
+        public CFFOLDERInputStream(CFFOLDER f, DataInputStream dis) {
+            this.folder = f;
+            this.dis = dis;
+        }
+
+        InputStream readBlock() throws IOException {
+            int checksum = readLittleInt(dis);
+            int compressedBytes = readLittleShort(dis);
+            int unCompressedBytes = readLittleShort(dis);
+            byte[] reserved = new byte[/*folder.header.perDatablockReservedSize*/0];
+            if (reserved.length > 0) dis.readFully(reserved);
+            if (dis.readByte() != 0x43) throw new CABException("malformed block header");
+            if (dis.readByte() != 0x4B) throw new CABException("malformed block header");
+
+            dis.readFully(compressed, 0, compressedBytes - 2);
+
+            Inflater i = new Inflater(true);
+            i.setInput(compressed, 0, compressedBytes - 2);
+            
+            if (unCompressedBytes > uncompressed.length) uncompressed = new byte[unCompressedBytes];
+            try { i.inflate(uncompressed, 0, uncompressed.length);
+            } catch (DataFormatException dfe) {
+                dfe.printStackTrace();
+                throw new CABException(dfe.toString());
+            }
+            return new ByteArrayInputStream(uncompressed, 0, unCompressedBytes);
+        }
+
+        public int available() throws IOException { return iis == null ? 0 : iis.available(); }
+        public void close() throws IOException { iis.close(); }
+        public void mark(int i) { }
+        public boolean markSupported() { return false; }
+        public void reset() { }
+
+        public long skip(long l) throws IOException {
+            if (iis == null) iis = readBlock();
+            int ret = 0;
+            while (l > ret) {
+                long numread = iis.skip(l - ret);
+                if (numread == 0 || numread == -1) iis = readBlock();
+                else ret += numread;
+            }
+            return ret;
+        }
+        
+        public int read(byte[] b, int off, int len) throws IOException {
+            if (iis == null) iis = readBlock();
+            int ret = 0;
+            while (len > ret) {
+                int numread = iis.read(b, off + ret, len - ret);
+                if (numread == 0 || numread == -1) iis = readBlock();
+                else ret += numread;
+            }
+            return ret;
+        }
+
+        public int read() throws IOException {
+            if (iis == null) iis = readBlock();
+            int ret = iis.read();
+            if (ret == -1) {
+                iis = readBlock();
+                ret = iis.read();
+            }
+            return ret;
+        }
+    }
+
+
+
+    // Misc Stuff //////////////////////////////////////////////////////////////
+
+    public static String readZeroTerminatedString(DataInputStream dis) throws IOException {
+        int numBytes = 0;
+        byte[] b = new byte[256];
+        while(true) {
+            byte next = dis.readByte();
+            if (next == 0x0) return new String(b, 0, numBytes);
+            b[numBytes++] = next;
+        }
+    }
+    
+    public static int readLittleInt(DataInputStream dis) throws IOException {
+        int lowest = (int)(dis.readByte() & 0xff);
+        int low = (int)(dis.readByte() & 0xff);
+        int high = (int)(dis.readByte() & 0xff);
+        int highest = (int)(dis.readByte() & 0xff);
+        return (highest << 24) | (high << 16) | (low << 8) | lowest;
+    }
+
+    public static int readLittleShort(DataInputStream dis) throws IOException {
+        int low = (int)(dis.readByte() & 0xff);
+        int high = (int)(dis.readByte() & 0xff);
+        return (high << 8) | low;
+    }
+
+    public static class CABException extends IOException {
+        public CABException(String s) { super(s); }
+    }
+
+
+    /** scratch space for isToByteArray() */
+    static byte[] workspace = new byte[16 * 1024];
+
+    /** Trivial method to completely read an InputStream */
+    public static synchronized byte[] isToByteArray(InputStream is) throws IOException {
+        int pos = 0;
+        while (true) {
+            int numread = is.read(workspace, pos, workspace.length - pos);
+            if (numread == -1) break;
+            else if (pos + numread < workspace.length) pos += numread;
+            else {
+                pos += numread;
+                byte[] temp = new byte[workspace.length * 2];
+                System.arraycopy(workspace, 0, temp, 0, workspace.length);
+                workspace = temp;
+            }
+        }
+        byte[] ret = new byte[pos];
+        System.arraycopy(workspace, 0, ret, 0, pos);
+        return ret;
+    }
+
+
+}
+