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, boolean mscfAlreadyConsumed) throws IOException {
+ 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();
- h.read(dis, mscfAlreadyConsumed);
+
+ 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);
- f.read(dis);
+ try {
+ f.read(dis);
+ } catch (CFFOLDER.UnsupportedCompressionTypeException ucte) {
+ throw new IOException(ucte.toString());
+ }
}
for(int i=0; i<h.files.length; i++) {
}
/** fills in all fields in the header and positions the stream at the first folder */
- public void read(DataInputStream dis, boolean mscfAlreadyConsumed) throws IOException {
- if (!mscfAlreadyConsumed) {
- if (dis.readByte() != 0x4d) throw new CABException("First byte of header signature malformed");
- if (dis.readByte() != 0x53) throw new CABException("Second byte of header signature malformed");
- if (dis.readByte() != 0x43) throw new CABException("Third byte of header signature malformed");
- if (dis.readByte() != 0x46) throw new CABException("Fourth byte of header signature malformed");
- }
+ public void read(DataInputStream dis) throws IOException, BogusHeaderException {
+ seekMSCF(dis);
dis.readFully(reserved1);
byte[] headerHashable = new byte[28];
setID = readLittleShort(hhis);
indexInCabinetSet = readLittleShort(hhis);
+ if (offsetOfFirstCFFILEEntry < 0 || fileSize < 0) {
+ throw new BogusHeaderException();
+ }
+
if (hasReserved) {
perCabinetReservedArea = new byte[readLittleShort(dis)];
perCFFOLDERReservedSize = dis.readByte();
if (perCabinetReservedArea.length > 0)
dis.readFully(perCabinetReservedArea);
}
- if (prevCAB) {
- previousCabinet = readZeroTerminatedString(dis);
- previousDisk = readZeroTerminatedString(dis);
- }
- if (nextCAB) {
- nextCabinet = readZeroTerminatedString(dis);
- nextDisk = readZeroTerminatedString(dis);
+
+ 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 Exception {}
}
/** 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
public String toString() {
return "[ CAB CFFOLDER, " + numBlocks + " data blocks, compression type " +
- (compressionType == 1 ? "MSZIP" : "unknown") +
+ compressionName(compressionType) +
", " + reservedArea.length + " bytes of reserved data ]";
}
- public void read(DataInputStream dis) throws IOException {
+ public void read(DataInputStream dis) throws IOException, UnsupportedCompressionTypeException {
firstBlockOffset = readLittleInt(dis);
numBlocks = readLittleShort(dis);
- compressionType = 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 Exception {
+ private int compressionType;
+
+ UnsupportedCompressionTypeException(int type) {
+ compressionType = type;
+ }
+ public String toString() {
+ return "UnsupportedCompressionTypeException: no support for compression type " + compressionName(compressionType);
+ }
+ }
}
/** Encapsulates a CFFILE entry */