1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
6 import java.util.zip.*;
9 /** Reads a CAB file structure */
12 /** reads a CAB file, parses it, and returns an InputStream representing the named file */
13 public static InputStream getFileInputStream(InputStream is, String fileName, boolean mscfAlreadyConsumed) throws IOException {
14 DataInputStream dis = new DataInputStream(is);
15 CFHEADER h = new CFHEADER();
16 h.read(dis, mscfAlreadyConsumed);
18 for(int i=0; i<h.folders.length; i++) {
19 CFFOLDER f = new CFFOLDER(h);
23 for(int i=0; i<h.files.length; i++) {
24 CFFILE f = new CFFILE(h);
28 for(int i=0; i<h.folders.length; i++) {
29 InputStream is2 = new CFFOLDERInputStream(h.folders[i], dis);
30 for(int j=0; j<h.folders[i].files.size(); j++) {
31 CFFILE file = (CFFILE)h.folders[i].files.elementAt(j);
32 if (file.fileName.equals(fileName)) return new LimitStream(is2, file.fileSize);
33 byte[] b = new byte[file.fileSize];
34 int read = is2.read(b);
41 private static class LimitStream extends FilterInputStream {
43 public LimitStream(InputStream is, int limit) {
47 public int read() throws IOException {
48 if (limit == 0) return -1;
49 int ret = super.read();
50 if (ret != -1) limit--;
53 public int read(byte[] b, int off, int len) throws IOException {
54 if (len > limit) len = limit;
55 if (limit == 0) return -1;
56 int ret = super.read(b, off, len);
62 /** Encapsulates a CFHEADER entry */
63 public static class CFHEADER {
64 byte[] reserved1 = new byte[4]; // reserved
65 int fileSize = 0; // size of this cabinet file in bytes
66 byte[] reserved2 = new byte[4]; // reserved
67 int offsetOfFirstCFFILEEntry; // offset of the first CFFILE entry
68 byte[] reserved3 = new byte[4]; // reserved
69 byte versionMinor = 3; // cabinet file format version, minor
70 byte versionMajor = 1; // cabinet file format version, major
71 boolean prevCAB = false; // true iff there is a cabinet before this one in a sequence
72 boolean nextCAB = false; // true iff there is a cabinet after this one in a sequence
73 boolean hasReserved = false; // true iff the cab has per-{cabinet, folder, block} reserved areas
74 int setID = 0; // must be the same for all cabinets in a set
75 int indexInCabinetSet = 0; // number of this cabinet file in a set
76 byte perCFFOLDERReservedSize = 0; // (optional) size of per-folder reserved area
77 byte perDatablockReservedSize = 0; // (optional) size of per-datablock reserved area
78 byte[] perCabinetReservedArea = null; // per-cabinet reserved area
79 String previousCabinet = null; // name of previous cabinet file in a set
80 String previousDisk = null; // name of previous disk in a set
81 String nextCabinet = null; // name of next cabinet in a set
82 String nextDisk = null; // name of next disk in a set
84 CFFOLDER[] folders = new CFFOLDER[0];
85 CFFILE[] files = new CFFILE[0];
87 int readCFFOLDERs = 0; // the number of folders read in so far
88 int readCFFILEs = 0; // the number of folders read in so far
92 public void print(PrintStream ps) {
93 ps.println("CAB CFFILE CFHEADER v" + ((int)versionMajor) + "." + ((int)versionMinor));
94 ps.println(" total file size = " + fileSize);
95 ps.println(" offset of first file = " + offsetOfFirstCFFILEEntry);
96 ps.println(" total folders = " + folders.length);
97 ps.println(" total files = " + files.length);
98 ps.println(" flags = 0x" +
99 Integer.toString((prevCAB ? 0x1 : 0x0) |
100 (nextCAB ? 0x2 : 0x0) |
101 (hasReserved ? 0x4 : 0x0), 16) + " [ " +
102 (prevCAB ? "prev " : "") +
103 (nextCAB ? "next " : "") +
104 (hasReserved ? "reserve_present " : "") + "]");
105 ps.println(" set id = " + setID);
106 ps.println(" index in set = " + indexInCabinetSet);
107 ps.println(" header reserved area #1 =" +
108 " 0x" + Integer.toString(reserved1[0], 16) +
109 " 0x" + Integer.toString(reserved1[1], 16) +
110 " 0x" + Integer.toString(reserved1[2], 16) +
111 " 0x" + Integer.toString(reserved1[3], 16));
112 ps.println(" header reserved area #2 =" +
113 " 0x" + Integer.toString(reserved2[0], 16) +
114 " 0x" + Integer.toString(reserved2[1], 16) +
115 " 0x" + Integer.toString(reserved2[2], 16) +
116 " 0x" + Integer.toString(reserved2[3], 16));
117 ps.println(" header reserved area #3 =" +
118 " 0x" + Integer.toString(reserved3[0], 16) +
119 " 0x" + Integer.toString(reserved3[1], 16) +
120 " 0x" + Integer.toString(reserved3[2], 16) +
121 " 0x" + Integer.toString(reserved3[3], 16));
123 if (perCabinetReservedArea != null) {
124 ps.print(" per-cabinet reserved area = ");
125 for(int i=0; i<perCabinetReservedArea.length; i++)
126 ps.print(((perCabinetReservedArea[i] & 0xff) < 16 ? "0" : "") +
127 Integer.toString(perCabinetReservedArea[i] & 0xff, 16) + " ");
130 ps.println(" per folder reserved area = " + perCFFOLDERReservedSize + " bytes");
131 ps.println(" per block reserved area = " + perDatablockReservedSize + " bytes");
135 public String toString() {
137 "[ CAB CFFILE CFHEADER v" +
138 ((int)versionMajor) + "." + ((int)versionMinor) + ", " +
139 fileSize + " bytes, " +
140 folders.length + " folders, " +
141 files.length + " files] ";
144 /** fills in all fields in the header and positions the stream at the first folder */
145 public void read(DataInputStream dis, boolean mscfAlreadyConsumed) throws IOException {
146 if (!mscfAlreadyConsumed) {
147 if (dis.readByte() != 0x4d) throw new CABException("First byte of header signature malformed");
148 if (dis.readByte() != 0x53) throw new CABException("Second byte of header signature malformed");
149 if (dis.readByte() != 0x43) throw new CABException("Third byte of header signature malformed");
150 if (dis.readByte() != 0x46) throw new CABException("Fourth byte of header signature malformed");
152 dis.readFully(reserved1);
154 byte[] headerHashable = new byte[28];
155 dis.readFully(headerHashable);
156 DataInputStream hhis = new DataInputStream(new ByteArrayInputStream(headerHashable));
158 fileSize = readLittleInt(hhis);
159 hhis.readFully(reserved2);
160 offsetOfFirstCFFILEEntry = readLittleInt(hhis);
161 hhis.readFully(reserved3);
162 versionMinor = hhis.readByte();
163 versionMajor = hhis.readByte();
164 folders = new CFFOLDER[readLittleShort(hhis)];
165 files = new CFFILE[readLittleShort(hhis)];
166 int flags = readLittleShort(hhis);
167 prevCAB = (flags & 0x0001) != 0;
168 nextCAB = (flags & 0x0002) != 0;
169 hasReserved = (flags & 0x0004) != 0;
170 setID = readLittleShort(hhis);
171 indexInCabinetSet = readLittleShort(hhis);
174 perCabinetReservedArea = new byte[readLittleShort(dis)];
175 perCFFOLDERReservedSize = dis.readByte();
176 perDatablockReservedSize = dis.readByte();
177 if (perCabinetReservedArea.length > 0)
178 dis.readFully(perCabinetReservedArea);
181 previousCabinet = readZeroTerminatedString(dis);
182 previousDisk = readZeroTerminatedString(dis);
185 nextCabinet = readZeroTerminatedString(dis);
186 nextDisk = readZeroTerminatedString(dis);
191 /** Encapsulates a CFFOLDER entry */
192 public static class CFFOLDER {
193 int firstBlockOffset = 0; // offset of first data block within this folder
194 int numBlocks = 0; // number of data blocks
195 int compressionType = 0; // compression type for this folder
196 byte[] reservedArea = null; // per-folder reserved area
197 int indexInCFHEADER = 0; // our index in CFHEADER.folders
198 Vector files = new Vector();
200 private CFHEADER header = null;
202 public CFFOLDER(CFHEADER header) { this.header = header; }
204 public String toString() {
205 return "[ CAB CFFOLDER, " + numBlocks + " data blocks, compression type " +
206 (compressionType == 1 ? "MSZIP" : "unknown") +
207 ", " + reservedArea.length + " bytes of reserved data ]";
210 public void read(DataInputStream dis) throws IOException {
211 firstBlockOffset = readLittleInt(dis);
212 numBlocks = readLittleShort(dis);
213 compressionType = readLittleShort(dis);
214 reservedArea = new byte[header.perCFFOLDERReservedSize];
215 if (reservedArea.length > 0) dis.readFully(reservedArea);
216 indexInCFHEADER = header.readCFFOLDERs++;
217 header.folders[indexInCFHEADER] = this;
221 /** Encapsulates a CFFILE entry */
222 public static class CFFILE {
223 int fileSize = 0; // size of this file
224 int uncompressedOffsetInCFFOLDER = 0; // offset of this file within the folder, not accounting for compression
225 int folderIndex = 0; // index of the CFFOLDER we belong to
226 Date date = null; // modification date
227 int attrs = 0; // attrs
228 boolean readOnly = false; // read-only flag
229 boolean hidden = false; // hidden flag
230 boolean system = false; // system flag
231 boolean arch = false; // archive flag
232 boolean runAfterExec = false; // true if file should be run during extraction
233 boolean UTFfileName = false; // true if filename is UTF-encoded
234 String fileName = null; // filename
235 int indexInCFHEADER = 0; // our index in CFHEADER.files
236 CFFOLDER folder = null; // the folder we belong to
237 private CFHEADER header = null;
240 public CFFILE(CFHEADER header) { this.header = header; }
242 public CFFILE(File f, String pathName) throws IOException {
243 fileSize = (int)f.length();
245 date = new java.util.Date(f.lastModified());
250 public String toString() {
251 return "[ CAB CFFILE: " + fileName + ", " + fileSize + " bytes [ " +
252 (readOnly ? "readonly " : "") +
253 (system ? "system " : "") +
254 (hidden ? "hidden " : "") +
255 (arch ? "arch " : "") +
256 (runAfterExec ? "run_after_exec " : "") +
257 (UTFfileName ? "UTF_filename " : "") +
261 public void read(DataInputStream dis) throws IOException {
262 fileSize = readLittleInt(dis);
263 uncompressedOffsetInCFFOLDER = readLittleInt(dis);
264 folderIndex = readLittleShort(dis);
265 readLittleShort(dis); // date
266 readLittleShort(dis); // time
267 attrs = readLittleShort(dis);
268 readOnly = (attrs & 0x1) != 0;
269 hidden = (attrs & 0x2) != 0;
270 system = (attrs & 0x4) != 0;
271 arch = (attrs & 0x20) != 0;
272 runAfterExec = (attrs & 0x40) != 0;
273 UTFfileName = (attrs & 0x80) != 0;
274 fileName = readZeroTerminatedString(dis);
276 indexInCFHEADER = header.readCFFILEs++;
277 header.files[indexInCFHEADER] = this;
278 folder = header.folders[folderIndex];
279 folder.files.addElement(this);
286 // Compressing Input and Output Streams ///////////////////////////////////////////////
288 /** an InputStream that decodes CFDATA blocks belonging to a CFFOLDER */
289 private static class CFFOLDERInputStream extends InputStream {
292 InputStream iis = null;
294 byte[] compressed = new byte[128 * 1024];
295 byte[] uncompressed = new byte[256 * 1024];
297 public CFFOLDERInputStream(CFFOLDER f, DataInputStream dis) {
302 InputStream readBlock() throws IOException {
303 int checksum = readLittleInt(dis);
304 int compressedBytes = readLittleShort(dis);
305 int unCompressedBytes = readLittleShort(dis);
306 byte[] reserved = new byte[/*folder.header.perDatablockReservedSize*/0];
307 if (reserved.length > 0) dis.readFully(reserved);
308 if (dis.readByte() != 0x43) throw new CABException("malformed block header");
309 if (dis.readByte() != 0x4B) throw new CABException("malformed block header");
311 dis.readFully(compressed, 0, compressedBytes - 2);
313 Inflater i = new Inflater(true);
314 i.setInput(compressed, 0, compressedBytes - 2);
316 if (unCompressedBytes > uncompressed.length) uncompressed = new byte[unCompressedBytes];
317 try { i.inflate(uncompressed, 0, uncompressed.length);
318 } catch (DataFormatException dfe) {
319 dfe.printStackTrace();
320 throw new CABException(dfe.toString());
322 return new ByteArrayInputStream(uncompressed, 0, unCompressedBytes);
325 public int available() throws IOException { return iis == null ? 0 : iis.available(); }
326 public void close() throws IOException { iis.close(); }
327 public void mark(int i) { }
328 public boolean markSupported() { return false; }
329 public void reset() { }
331 public long skip(long l) throws IOException {
332 if (iis == null) iis = readBlock();
335 long numread = iis.skip(l - ret);
336 if (numread == 0 || numread == -1) iis = readBlock();
342 public int read(byte[] b, int off, int len) throws IOException {
343 if (iis == null) iis = readBlock();
346 int numread = iis.read(b, off + ret, len - ret);
347 if (numread == 0 || numread == -1) iis = readBlock();
353 public int read() throws IOException {
354 if (iis == null) iis = readBlock();
355 int ret = iis.read();
366 // Misc Stuff //////////////////////////////////////////////////////////////
368 public static String readZeroTerminatedString(DataInputStream dis) throws IOException {
370 byte[] b = new byte[256];
372 byte next = dis.readByte();
373 if (next == 0x0) return new String(b, 0, numBytes);
374 b[numBytes++] = next;
378 public static int readLittleInt(DataInputStream dis) throws IOException {
379 int lowest = (int)(dis.readByte() & 0xff);
380 int low = (int)(dis.readByte() & 0xff);
381 int high = (int)(dis.readByte() & 0xff);
382 int highest = (int)(dis.readByte() & 0xff);
383 return (highest << 24) | (high << 16) | (low << 8) | lowest;
386 public static int readLittleShort(DataInputStream dis) throws IOException {
387 int low = (int)(dis.readByte() & 0xff);
388 int high = (int)(dis.readByte() & 0xff);
389 return (high << 8) | low;
392 public static class CABException extends IOException {
393 public CABException(String s) { super(s); }
397 /** scratch space for isToByteArray() */
398 static byte[] workspace = new byte[16 * 1024];
400 /** Trivial method to completely read an InputStream */
401 public static synchronized byte[] isToByteArray(InputStream is) throws IOException {
404 int numread = is.read(workspace, pos, workspace.length - pos);
405 if (numread == -1) break;
406 else if (pos + numread < workspace.length) pos += numread;
409 byte[] temp = new byte[workspace.length * 2];
410 System.arraycopy(workspace, 0, temp, 0, workspace.length);
414 byte[] ret = new byte[pos];
415 System.arraycopy(workspace, 0, ret, 0, pos);