X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Fibex%2Futil%2FCAB.java;fp=src%2Forg%2Fibex%2Futil%2FCAB.java;h=96e09476f98fac47455d17a1cb694f7fd41f749c;hb=3591b88b94a6bb378af3d4abe6eb5233ce583104;hp=0000000000000000000000000000000000000000;hpb=de378041d5ca2aca1a2b5a31ef15ae90a86c977f;p=org.ibex.core.git diff --git a/src/org/ibex/util/CAB.java b/src/org/ibex/util/CAB.java new file mode 100644 index 0000000..96e0947 --- /dev/null +++ b/src/org/ibex/util/CAB.java @@ -0,0 +1,505 @@ +// Copyright (C) 2003 Adam Megacz 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 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 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 ""; + } + } + + 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; + } + + +} +