1
2
3
4
5
6
7
8 package org.xwt.util;
9
10 import java.io.*;
11 import java.util.*;
12 import java.util.zip.*;
13 import java.math.*;
14
15
16 public class CAB {
17
18
19 public static InputStream getFileInputStream(InputStream is, String fileName) throws IOException, EOFException {
20 return getFileInputStream(is, 0, fileName);
21 }
22
23 public static InputStream getFileInputStream(InputStream is, int skipHeaders, String fileName) throws IOException, EOFException {
24 DataInputStream dis = new DataInputStream(is);
25 CFHEADER h = new CFHEADER();
26
27 while (skipHeaders > 0) { CFHEADER.seekMSCF(dis); skipHeaders--; }
28
29 try {
30 h.read(dis);
31 } catch (CFHEADER.BogusHeaderException bhe) {
32 throw new EOFException();
33 }
34
35 for(int i=0; i<h.folders.length; i++) {
36 CFFOLDER f = new CFFOLDER(h);
37 try {
38 f.read(dis);
39 } catch (CFFOLDER.UnsupportedCompressionTypeException ucte) {
40 throw ucte;
41 }
42 }
43
44 for(int i=0; i<h.files.length; i++) {
45 CFFILE f = new CFFILE(h);
46 f.read(dis);
47 }
48
49 for(int i=0; i<h.folders.length; i++) {
50 InputStream is2 = new CFFOLDERInputStream(h.folders[i], dis);
51 for(int j=0; j<h.folders[i].files.size(); j++) {
52 CFFILE file = (CFFILE)h.folders[i].files.elementAt(j);
53 if (file.fileName.equals(fileName)) return new LimitStream(is2, file.fileSize);
54 byte[] b = new byte[file.fileSize];
55 int read = is2.read(b);
56 }
57 }
58
59 return null;
60 }
61
62 private static class LimitStream extends FilterInputStream {
63 int limit;
64 public LimitStream(InputStream is, int limit) {
65 super(is);
66 this.limit = limit;
67 }
68 public int read() throws IOException {
69 if (limit == 0) return -1;
70 int ret = super.read();
71 if (ret != -1) limit--;
72 return ret;
73 }
74 public int read(byte[] b, int off, int len) throws IOException {
75 if (len > limit) len = limit;
76 if (limit == 0) return -1;
77 int ret = super.read(b, off, len);
78 limit -= ret;
79 return ret;
80 }
81 }
82
83
84 public static class CFHEADER {
85 byte[] reserved1 = new byte[4];
86 int fileSize = 0;
87 byte[] reserved2 = new byte[4];
88 int offsetOfFirstCFFILEEntry;
89 byte[] reserved3 = new byte[4];
90 byte versionMinor = 3;
91 byte versionMajor = 1;
92 boolean prevCAB = false;
93 boolean nextCAB = false;
94 boolean hasReserved = false;
95 int setID = 0;
96 int indexInCabinetSet = 0;
97 byte perCFFOLDERReservedSize = 0;
98 byte perDatablockReservedSize = 0;
99 byte[] perCabinetReservedArea = null;
100 String previousCabinet = null;
101 String previousDisk = null;
102 String nextCabinet = null;
103 String nextDisk = null;
104
105 CFFOLDER[] folders = new CFFOLDER[0];
106 CFFILE[] files = new CFFILE[0];
107
108 int readCFFOLDERs = 0;
109 int readCFFILEs = 0;
110
111 public CFHEADER() { }
112
113 public void print(PrintStream ps) {
114 ps.println("CAB CFFILE CFHEADER v" + ((int)versionMajor) + "." + ((int)versionMinor));
115 ps.println(" total file size = " + fileSize);
116 ps.println(" offset of first file = " + offsetOfFirstCFFILEEntry);
117 ps.println(" total folders = " + folders.length);
118 ps.println(" total files = " + files.length);
119 ps.println(" flags = 0x" +
120 Integer.toString((prevCAB ? 0x1 : 0x0) |
121 (nextCAB ? 0x2 : 0x0) |
122 (hasReserved ? 0x4 : 0x0), 16) + " [ " +
123 (prevCAB ? "prev " : "") +
124 (nextCAB ? "next " : "") +
125 (hasReserved ? "reserve_present " : "") + "]");
126 ps.println(" set id = " + setID);
127 ps.println(" index in set = " + indexInCabinetSet);
128 ps.println(" header reserved area #1 =" +
129 " 0x" + Integer.toString(reserved1[0], 16) +
130 " 0x" + Integer.toString(reserved1[1], 16) +
131 " 0x" + Integer.toString(reserved1[2], 16) +
132 " 0x" + Integer.toString(reserved1[3], 16));
133 ps.println(" header reserved area #2 =" +
134 " 0x" + Integer.toString(reserved2[0], 16) +
135 " 0x" + Integer.toString(reserved2[1], 16) +
136 " 0x" + Integer.toString(reserved2[2], 16) +
137 " 0x" + Integer.toString(reserved2[3], 16));
138 ps.println(" header reserved area #3 =" +
139 " 0x" + Integer.toString(reserved3[0], 16) +
140 " 0x" + Integer.toString(reserved3[1], 16) +
141 " 0x" + Integer.toString(reserved3[2], 16) +
142 " 0x" + Integer.toString(reserved3[3], 16));
143 if (hasReserved) {
144 if (perCabinetReservedArea != null) {
145 ps.print(" per-cabinet reserved area = ");
146 for(int i=0; i<perCabinetReservedArea.length; i++)
147 ps.print(((perCabinetReservedArea[i] & 0xff) < 16 ? "0" : "") +
148 Integer.toString(perCabinetReservedArea[i] & 0xff, 16) + " ");
149 ps.println();
150 }
151 ps.println(" per folder reserved area = " + perCFFOLDERReservedSize + " bytes");
152 ps.println(" per block reserved area = " + perDatablockReservedSize + " bytes");
153 }
154 }
155
156 public String toString() {
157 return
158 "[ CAB CFFILE CFHEADER v" +
159 ((int)versionMajor) + "." + ((int)versionMinor) + ", " +
160 fileSize + " bytes, " +
161 folders.length + " folders, " +
162 files.length + " files] ";
163 }
164
165
166 public void read(DataInputStream dis) throws IOException, BogusHeaderException {
167 seekMSCF(dis);
168 dis.readFully(reserved1);
169
170 byte[] headerHashable = new byte[28];
171 dis.readFully(headerHashable);
172 DataInputStream hhis = new DataInputStream(new ByteArrayInputStream(headerHashable));
173
174 fileSize = readLittleInt(hhis);
175 hhis.readFully(reserved2);
176 offsetOfFirstCFFILEEntry = readLittleInt(hhis);
177 hhis.readFully(reserved3);
178 versionMinor = hhis.readByte();
179 versionMajor = hhis.readByte();
180 folders = new CFFOLDER[readLittleShort(hhis)];
181 files = new CFFILE[readLittleShort(hhis)];
182 int flags = readLittleShort(hhis);
183 prevCAB = (flags & 0x0001) != 0;
184 nextCAB = (flags & 0x0002) != 0;
185 hasReserved = (flags & 0x0004) != 0;
186 setID = readLittleShort(hhis);
187 indexInCabinetSet = readLittleShort(hhis);
188
189 if (offsetOfFirstCFFILEEntry < 0 || fileSize < 0) {
190 throw new BogusHeaderException();
191 }
192
193 if (hasReserved) {
194 perCabinetReservedArea = new byte[readLittleShort(dis)];
195 perCFFOLDERReservedSize = dis.readByte();
196 perDatablockReservedSize = dis.readByte();
197 if (perCabinetReservedArea.length > 0)
198 dis.readFully(perCabinetReservedArea);
199 }
200
201 try {
202 if (prevCAB) {
203 previousCabinet = readZeroTerminatedString(dis);
204 previousDisk = readZeroTerminatedString(dis);
205 }
206 if (nextCAB) {
207 nextCabinet = readZeroTerminatedString(dis);
208 nextDisk = readZeroTerminatedString(dis);
209 }
210 } catch (ArrayIndexOutOfBoundsException e) {
211 throw new BogusHeaderException();
212 }
213 }
214
215 public static void seekMSCF(DataInputStream dis) throws EOFException, IOException
216 {
217 int state;
218
219 state = 0;
220 while (state != 4) {
221
222 while (state == 0 && dis.readByte() != 0x4D) { }
223 state = 1;
224
225 switch (dis.readByte()) {
226 case 0x53 : state = 2; break;
227 case 0x4D : state = 1; continue;
228 default : state = 0; continue;
229 }
230
231 if (dis.readByte() == 0x43) { state = 3; }
232 else { state = 0; continue; }
233
234 if (dis.readByte() == 0x46) { state = 4; }
235 else { state = 0; }
236 }
237 }
238
239 public static class BogusHeaderException extends IOException {}
240 }
241
242
243 public static class CFFOLDER {
244 public static final int COMPRESSION_NONE = 0;
245 public static final int COMPRESSION_MSZIP = 1;
246 public static final int COMPRESSION_QUANTUM = 2;
247 public static final int COMPRESSION_LZX = 3;
248
249 int firstBlockOffset = 0;
250 int numBlocks = 0;
251 int compressionType = 0;
252 byte[] reservedArea = null;
253 int indexInCFHEADER = 0;
254 Vector files = new Vector();
255
256 private CFHEADER header = null;
257
258 public CFFOLDER(CFHEADER header) { this.header = header; }
259
260 public String toString() {
261 return "[ CAB CFFOLDER, " + numBlocks + " data blocks, compression type " +
262 compressionName(compressionType) +
263 ", " + reservedArea.length + " bytes of reserved data ]";
264 }
265
266 public void read(DataInputStream dis) throws IOException, UnsupportedCompressionTypeException {
267 firstBlockOffset = readLittleInt(dis);
268 numBlocks = readLittleShort(dis);
269 compressionType = readLittleShort(dis) & 0x000F;
270 if (compressionType != COMPRESSION_MSZIP) {
271 throw new UnsupportedCompressionTypeException(compressionType);
272 }
273 reservedArea = new byte[header.perCFFOLDERReservedSize];
274 if (reservedArea.length > 0) dis.readFully(reservedArea);
275 indexInCFHEADER = header.readCFFOLDERs++;
276 header.folders[indexInCFHEADER] = this;
277 }
278
279 public static String compressionName(int type) {
280 switch (type) {
281 case COMPRESSION_NONE:
282 return "NONE";
283 case COMPRESSION_MSZIP:
284 return "MSZIP";
285 case COMPRESSION_QUANTUM:
286 return "QUANTUM";
287 case COMPRESSION_LZX:
288 return "LZX";
289 default:
290 return "<Unknown type " + type + ">";
291 }
292 }
293
294 public static class UnsupportedCompressionTypeException extends IOException {
295 private int compressionType;
296
297 UnsupportedCompressionTypeException(int type) {
298 compressionType = type;
299 }
300 public String toString() {
301 return "UnsupportedCompressionTypeException: no support for compression type " + compressionName(compressionType);
302 }
303 }
304 }
305
306
307 public static class CFFILE {
308 int fileSize = 0;
309 int uncompressedOffsetInCFFOLDER = 0;
310 int folderIndex = 0;
311 Date date = null;
312 int attrs = 0;
313 boolean readOnly = false;
314 boolean hidden = false;
315 boolean system = false;
316 boolean arch = false;
317 boolean runAfterExec = false;
318 boolean UTFfileName = false;
319 String fileName = null;
320 int indexInCFHEADER = 0;
321 CFFOLDER folder = null;
322 private CFHEADER header = null;
323 File myFile;
324
325 public CFFILE(CFHEADER header) { this.header = header; }
326
327 public CFFILE(File f, String pathName) throws IOException {
328 fileSize = (int)f.length();
329 folderIndex = 0;
330 date = new java.util.Date(f.lastModified());
331 fileName = pathName;
332 myFile = f;
333 }
334
335 public String toString() {
336 return "[ CAB CFFILE: " + fileName + ", " + fileSize + " bytes [ " +
337 (readOnly ? "readonly " : "") +
338 (system ? "system " : "") +
339 (hidden ? "hidden " : "") +
340 (arch ? "arch " : "") +
341 (runAfterExec ? "run_after_exec " : "") +
342 (UTFfileName ? "UTF_filename " : "") +
343 "]";
344 }
345
346 public void read(DataInputStream dis) throws IOException {
347 fileSize = readLittleInt(dis);
348 uncompressedOffsetInCFFOLDER = readLittleInt(dis);
349 folderIndex = readLittleShort(dis);
350 readLittleShort(dis);
351 readLittleShort(dis);
352 attrs = readLittleShort(dis);
353 readOnly = (attrs & 0x1) != 0;
354 hidden = (attrs & 0x2) != 0;
355 system = (attrs & 0x4) != 0;
356 arch = (attrs & 0x20) != 0;
357 runAfterExec = (attrs & 0x40) != 0;
358 UTFfileName = (attrs & 0x80) != 0;
359 fileName = readZeroTerminatedString(dis);
360
361 indexInCFHEADER = header.readCFFILEs++;
362 header.files[indexInCFHEADER] = this;
363 folder = header.folders[folderIndex];
364 folder.files.addElement(this);
365 }
366 }
367
368
369
370
371
372
373
374 private static class CFFOLDERInputStream extends InputStream {
375 CFFOLDER folder;
376 DataInputStream dis;
377 InputStream iis = null;
378
379 byte[] compressed = new byte[128 * 1024];
380 byte[] uncompressed = new byte[256 * 1024];
381
382 public CFFOLDERInputStream(CFFOLDER f, DataInputStream dis) {
383 this.folder = f;
384 this.dis = dis;
385 }
386
387 InputStream readBlock() throws IOException {
388 int checksum = readLittleInt(dis);
389 int compressedBytes = readLittleShort(dis);
390 int unCompressedBytes = readLittleShort(dis);
391 byte[] reserved = new byte[0];
392 if (reserved.length > 0) dis.readFully(reserved);
393 if (dis.readByte() != 0x43) throw new CABException("malformed block header");
394 if (dis.readByte() != 0x4B) throw new CABException("malformed block header");
395
396 dis.readFully(compressed, 0, compressedBytes - 2);
397
398 Inflater i = new Inflater(true);
399 i.setInput(compressed, 0, compressedBytes - 2);
400
401 if (unCompressedBytes > uncompressed.length) uncompressed = new byte[unCompressedBytes];
402 try { i.inflate(uncompressed, 0, uncompressed.length);
403 } catch (DataFormatException dfe) {
404 dfe.printStackTrace();
405 throw new CABException(dfe.toString());
406 }
407 return new ByteArrayInputStream(uncompressed, 0, unCompressedBytes);
408 }
409
410 public int available() throws IOException { return iis == null ? 0 : iis.available(); }
411 public void close() throws IOException { iis.close(); }
412 public void mark(int i) { }
413 public boolean markSupported() { return false; }
414 public void reset() { }
415
416 public long skip(long l) throws IOException {
417 if (iis == null) iis = readBlock();
418 int ret = 0;
419 while (l > ret) {
420 long numread = iis.skip(l - ret);
421 if (numread == 0 || numread == -1) iis = readBlock();
422 else ret += numread;
423 }
424 return ret;
425 }
426
427 public int read(byte[] b, int off, int len) throws IOException {
428 if (iis == null) iis = readBlock();
429 int ret = 0;
430 while (len > ret) {
431 int numread = iis.read(b, off + ret, len - ret);
432 if (numread == 0 || numread == -1) iis = readBlock();
433 else ret += numread;
434 }
435 return ret;
436 }
437
438 public int read() throws IOException {
439 if (iis == null) iis = readBlock();
440 int ret = iis.read();
441 if (ret == -1) {
442 iis = readBlock();
443 ret = iis.read();
444 }
445 return ret;
446 }
447 }
448
449
450
451
452
453 public static String readZeroTerminatedString(DataInputStream dis) throws IOException {
454 int numBytes = 0;
455 byte[] b = new byte[256];
456 while(true) {
457 byte next = dis.readByte();
458 if (next == 0x0) return new String(b, 0, numBytes);
459 b[numBytes++] = next;
460 }
461 }
462
463 public static int readLittleInt(DataInputStream dis) throws IOException {
464 int lowest = (int)(dis.readByte() & 0xff);
465 int low = (int)(dis.readByte() & 0xff);
466 int high = (int)(dis.readByte() & 0xff);
467 int highest = (int)(dis.readByte() & 0xff);
468 return (highest << 24) | (high << 16) | (low << 8) | lowest;
469 }
470
471 public static int readLittleShort(DataInputStream dis) throws IOException {
472 int low = (int)(dis.readByte() & 0xff);
473 int high = (int)(dis.readByte() & 0xff);
474 return (high << 8) | low;
475 }
476
477 public static class CABException extends IOException {
478 public CABException(String s) { super(s); }
479 }
480
481
482
483 static byte[] workspace = new byte[16 * 1024];
484
485
486 public static synchronized byte[] isToByteArray(InputStream is) throws IOException {
487 int pos = 0;
488 while (true) {
489 int numread = is.read(workspace, pos, workspace.length - pos);
490 if (numread == -1) break;
491 else if (pos + numread < workspace.length) pos += numread;
492 else {
493 pos += numread;
494 byte[] temp = new byte[workspace.length * 2];
495 System.arraycopy(workspace, 0, temp, 0, workspace.length);
496 workspace = temp;
497 }
498 }
499 byte[] ret = new byte[pos];
500 System.arraycopy(workspace, 0, ret, 0, pos);
501 return ret;
502 }
503
504
505 }
506
507