Release: 1.1.7
[captive.git] / src / install / acquire / cabextract / cabinfo.c
1 /* cabinfo -- dumps useful information from cabinets
2  * (C) 2000 Stuart Caie <kyzer@4u.net>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 /* structure offsets */
24 #define cfhead_Signature         (0x00)
25 #define cfhead_CabinetSize       (0x08)
26 #define cfhead_FileOffset        (0x10)
27 #define cfhead_MinorVersion      (0x18)
28 #define cfhead_MajorVersion      (0x19)
29 #define cfhead_NumFolders        (0x1A)
30 #define cfhead_NumFiles          (0x1C)
31 #define cfhead_Flags             (0x1E)
32 #define cfhead_SetID             (0x20)
33 #define cfhead_CabinetIndex      (0x22)
34 #define cfhead_SIZEOF            (0x24)
35 #define cfheadext_HeaderReserved (0x00)
36 #define cfheadext_FolderReserved (0x02)
37 #define cfheadext_DataReserved   (0x03)
38 #define cfheadext_SIZEOF         (0x04)
39 #define cffold_DataOffset        (0x00)
40 #define cffold_NumBlocks         (0x04)
41 #define cffold_CompType          (0x06)
42 #define cffold_SIZEOF            (0x08)
43 #define cffile_UncompressedSize  (0x00)
44 #define cffile_FolderOffset      (0x04)
45 #define cffile_FolderIndex       (0x08)
46 #define cffile_Date              (0x0A)
47 #define cffile_Time              (0x0C)
48 #define cffile_Attribs           (0x0E)
49 #define cffile_SIZEOF            (0x10)
50 #define cfdata_CheckSum          (0x00)
51 #define cfdata_CompressedSize    (0x04)
52 #define cfdata_UncompressedSize  (0x06)
53 #define cfdata_SIZEOF            (0x08)
54
55 /* flags */
56 #define cffoldCOMPTYPE_MASK            (0x000f)
57 #define cffoldCOMPTYPE_NONE            (0x0000)
58 #define cffoldCOMPTYPE_MSZIP           (0x0001)
59 #define cffoldCOMPTYPE_QUANTUM         (0x0002)
60 #define cffoldCOMPTYPE_LZX             (0x0003)
61 #define cfheadPREV_CABINET             (0x0001)
62 #define cfheadNEXT_CABINET             (0x0002)
63 #define cfheadRESERVE_PRESENT          (0x0004)
64 #define cffileCONTINUED_FROM_PREV      (0xFFFD)
65 #define cffileCONTINUED_TO_NEXT        (0xFFFE)
66 #define cffileCONTINUED_PREV_AND_NEXT  (0xFFFF)
67 #define cffile_A_RDONLY                (0x01)
68 #define cffile_A_HIDDEN                (0x02)
69 #define cffile_A_SYSTEM                (0x04)
70 #define cffile_A_ARCH                  (0x20)
71 #define cffile_A_EXEC                  (0x40)
72 #define cffile_A_NAME_IS_UTF           (0x80)
73
74
75 FILE *fh;
76 char *filename;
77 unsigned long filelength;
78 void search();
79 void getinfo();
80
81 #define EndGetI32(a)  ((((a)[3])<<24)|(((a)[2])<<16)|(((a)[1])<<8)|((a)[0]))
82 #define EndGetI16(a)  ((((a)[1])<<8)|((a)[0]))
83 #define GETLONG(n) EndGetI32(&buf[n])
84 #define GETWORD(n) EndGetI16(&buf[n])
85 #define GETBYTE(n) ((int)buf[n])
86
87 #define GETOFFSET      (ftell(fh))
88 #define READ(buf,len)  if (myread((void *)(buf),(len))) return
89 #define SKIP(offset)   if (myseek((offset),SEEK_CUR)) return
90 #define SEEK(offset)   if (myseek((offset),SEEK_SET)) return
91
92
93
94 int myread(void *buf, int length) {
95   int remain = filelength - GETOFFSET;
96   if (length > remain) length = remain;
97   if (fread(buf, 1, length, fh) != length) {
98     perror(filename);
99     return 1;
100   }
101   return 0;
102 }
103
104 int myseek(unsigned long offset, int mode) {
105   if (fseek(fh, offset, mode) != 0) {
106     perror(filename);
107     return 1;
108   }
109   return 0;
110 }
111
112 int main(int argc, char *argv[]) {
113   printf("Cabinet information dumper by Stuart Caie <kyzer@4u.net>\n");
114
115   if (argc <= 1) {
116     printf("Usage: %s <file.cab>\n", argv[0]);
117     return 1;
118   }
119
120   if (!(fh = fopen((filename = argv[1]), "rb"))) {
121     perror(filename);
122     return 1;
123   }
124
125   if (fseek(fh, 0, SEEK_END) != 0) {
126     perror(filename);
127     fclose(fh);
128     return 1;
129   }
130
131   filelength = (unsigned long) ftell(fh);
132
133   if (fseek(fh, 0, SEEK_SET) != 0) {
134     perror(filename);
135     fclose(fh);
136     return 1;
137   }
138
139   printf("Examining file \"%s\" (%u bytes)...\n", filename, filelength);
140   search();
141   fclose(fh);
142   return 0;
143 }
144
145
146 #define SEARCH_SIZE (32*1024)
147 unsigned char search_buf[SEARCH_SIZE];
148
149 void search() {
150   unsigned char *pstart = &search_buf[0], *pend, *p;
151   unsigned long offset, caboff, cablen, foffset;
152   size_t length;
153   int state = 0;
154
155   for (offset = 0; offset < filelength; offset += length) {
156     /* search length is either the full length of the search buffer,
157      * or the amount of data remaining to the end of the file,
158      * whichever is less.
159      */
160     length = filelength - offset;
161     if (length > SEARCH_SIZE) length = SEARCH_SIZE;
162
163     /* fill the search buffer with data from disk */
164     SEEK(offset);
165     READ(&search_buf[0], length);
166     /* read through the entire buffer. */
167     p = pstart;
168     pend = &search_buf[length];
169     while (p < pend) {
170       switch (state) {
171         /* starting state */
172       case 0:
173         /* we spend most of our time in this while loop, looking for
174          * a leading 'M' of the 'MSCF' signature
175          */
176         while (*p++ != 0x4D && p < pend);
177         if (p < pend) state = 1; /* if we found tht 'M', advance state */
178         break;
179         
180         /* verify that the next 3 bytes are 'S', 'C' and 'F' */
181       case 1: state = (*p++ == 0x53) ? 2 : 0; break;
182       case 2: state = (*p++ == 0x43) ? 3 : 0; break;
183       case 3: state = (*p++ == 0x46) ? 4 : 0; break;
184         
185         /* we don't care about bytes 4-7 */
186         /* bytes 8-11 are the overall length of the cabinet */
187       case 8:  cablen  = *p++;       state++; break;
188       case 9:  cablen |= *p++ << 8;  state++; break;
189       case 10: cablen |= *p++ << 16; state++; break;
190       case 11: cablen |= *p++ << 24; state++; break;
191         
192         /* we don't care about bytes 12-15 */
193         /* bytes 16-19 are the offset within the cabinet of the filedata */
194       case 16: foffset  = *p++;       state++; break;
195       case 17: foffset |= *p++ << 8;  state++; break;
196       case 18: foffset |= *p++ << 16; state++; break;
197       case 19: foffset |= *p++ << 24;
198         /* now we have recieved 20 bytes of potential cab header. */
199         /* work out the offset in the file of this potential cabinet */
200         caboff = offset + (p-pstart) - 20;
201         /* check that the files offset is less than the alleged length
202          * of the cabinet, and that the offset + the alleged length are
203          * 'roughly' within the end of overall file length
204          */
205         if ((foffset < cablen) &&
206             ((caboff + foffset) < (filelength + 32)) &&
207             ((caboff + cablen) < (filelength + 32)) )
208         {
209           /* found a potential result - try loading it */
210           printf("Found cabinet header at offset %u\n", caboff);
211           SEEK(caboff);
212           getinfo();
213           offset = caboff + cablen;
214           length = 0;
215           p = pend;
216         }
217         state = 0;
218         break;
219
220       default:
221         p++, state++; break;
222       } /* switch state */
223     } /* while p < pend */
224   } /* while offset < filelength */
225 }
226
227
228
229
230
231 #define CAB_NAMEMAX (1024)
232
233 /* UTF translates two-byte unicode characters into 1, 2 or 3 bytes.
234  * %000000000xxxxxxx -> %0xxxxxxx
235  * %00000xxxxxyyyyyy -> %110xxxxx %10yyyyyy
236  * %xxxxyyyyyyzzzzzz -> %1110xxxx %10yyyyyy %10zzzzzz
237  *
238  * Therefore, the inverse is as follows:
239  * First char:
240  *  0x00 - 0x7F = one byte char
241  *  0x80 - 0xBF = invalid
242  *  0xC0 - 0xDF = 2 byte char (next char only 0x80-0xBF is valid)
243  *  0xE0 - 0xEF = 3 byte char (next 2 chars only 0x80-0xBF is valid)
244  *  0xF0 - 0xFF = invalid
245  */
246
247 /* translate UTF -> ASCII */
248 int convertUTF(unsigned char *in) {
249   unsigned char c, *out = in, *end = in + strlen(in) + 1;
250   unsigned int x;
251
252   do {
253     /* read unicode character */
254     if ((c = *in++) < 0x80) x = c;
255     else {
256       if (c < 0xC0) return 0;
257       else if (c < 0xE0) {
258         x = (c & 0x1F) << 6;
259         if ((c = *in++) < 0x80 || c > 0xBF) return 0; else x |= (c & 0x3F);
260       }
261       else if (c < 0xF0) {
262         x = (c & 0xF) << 12;
263         if ((c = *in++) < 0x80 || c > 0xBF) return 0; else x |= (c & 0x3F)<<6;
264         if ((c = *in++) < 0x80 || c > 0xBF) return 0; else x |= (c & 0x3F);
265       }
266       else return 0;
267     }
268
269     /* terrible unicode -> ASCII conversion */
270     if (x > 127) x = '_';
271
272     if (in > end) return 0; /* just in case */
273   } while ((*out++ = (unsigned char) x));
274   return 1;
275 }
276
277
278
279 void getinfo() {
280   unsigned char buf[64];
281   unsigned char namebuf[CAB_NAMEMAX];
282   char *name;
283
284   int num_folders, num_files, num_blocks = 0;
285   int header_res = 0, folder_res = 0, data_res = 0;
286   int i, x, offset, base_offset, files_offset, base;
287
288   base_offset = GETOFFSET;
289
290   READ(&buf, cfhead_SIZEOF);
291
292   x = GETWORD(cfhead_Flags);
293
294   printf(
295     "\n*** HEADER SECTION ***\n\n"
296     "Cabinet signature      = '%4.4s'\n"
297     "Cabinet size           = %u bytes\n"
298     "Offset of files        = %u\n"
299     "Cabinet format version = %d.%d\n"
300     "Number of folders      = %u\n"
301     "Number of files        = %u\n"
302     "Header flags           = 0x%04x%s%s%s\n"
303     "Set ID                 = %u\n"
304     "Cabinet set index      = %u\n",
305
306     buf,
307     GETLONG(cfhead_CabinetSize),
308     files_offset = (GETLONG(cfhead_FileOffset) + base_offset),
309     GETBYTE(cfhead_MajorVersion),
310     GETBYTE(cfhead_MinorVersion),
311     num_folders = GETWORD(cfhead_NumFolders),
312     num_files = GETWORD(cfhead_NumFiles),
313     x,
314     ((x & cfheadPREV_CABINET)    ? " PREV_CABINET"    : ""),
315     ((x & cfheadNEXT_CABINET)    ? " NEXT_CABINET"    : ""),
316     ((x & cfheadRESERVE_PRESENT) ? " RESERVE_PRESENT" : ""),
317     GETWORD(cfhead_SetID),
318     GETWORD(cfhead_CabinetIndex)
319   );
320
321   if (num_folders == 0) { printf("ERROR: no folders\n"); return; }
322   if (num_files == 0) { printf("ERROR: no files\n"); return; }
323
324   if (buf[0]!='M' || buf[1]!='S' || buf[2]!='C' || buf[3]!='F')
325     printf("WARNING: cabinet doesn't start with MSCF signature\n");
326
327   if (GETBYTE(cfhead_MajorVersion) > 1
328   || GETBYTE(cfhead_MinorVersion) > 3)
329     printf("WARNING: format version > 1.3\n");
330
331
332
333   if (x & cfheadRESERVE_PRESENT) {
334     READ(&buf, cfheadext_SIZEOF);
335     header_res = GETWORD(cfheadext_HeaderReserved);
336     folder_res = GETBYTE(cfheadext_FolderReserved);
337     data_res   = GETBYTE(cfheadext_DataReserved);
338   }
339
340   printf("Reserved header space  = %u\n", header_res);
341   printf("Reserved folder space  = %u\n", folder_res);
342   printf("Reserved datablk space = %u\n", data_res);
343
344   if (header_res > 60000)
345     printf("WARNING: header reserved space > 60000\n");
346
347   if (header_res) {
348     printf("[Reserved header: offset %lu, size %u]\n", GETOFFSET, header_res);
349     SKIP(header_res);
350   }
351
352   if (x & cfheadPREV_CABINET) {
353     base = GETOFFSET;
354     READ(&namebuf, CAB_NAMEMAX);
355     SEEK(base + strlen(namebuf) + 1);
356     printf("Previous cabinet file  = %s\n", namebuf);
357     if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n");
358
359     base = GETOFFSET;
360     READ(&namebuf, CAB_NAMEMAX);
361     SEEK(base + strlen(namebuf) + 1);
362     printf("Previous disk name     = %s\n", namebuf);
363     if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n");
364   }
365
366   if (x & cfheadNEXT_CABINET) {
367     base = GETOFFSET;
368     READ(&namebuf, CAB_NAMEMAX);
369     SEEK(base + strlen(namebuf) + 1);
370     printf("Next cabinet file      = %s\n", namebuf);
371     if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n");
372
373     base = GETOFFSET;
374     READ(&namebuf, CAB_NAMEMAX);
375     SEEK(base + strlen(namebuf) + 1);
376     printf("Next disk name         = %s\n", namebuf);
377     if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n");
378   }
379
380   printf("\n*** FOLDERS SECTION ***\n");
381
382   for (i = 0; i < num_folders; i++) {
383     offset = GETOFFSET;
384     READ(&buf, cffold_SIZEOF);
385
386     switch(GETWORD(cffold_CompType) & cffoldCOMPTYPE_MASK) {
387     case cffoldCOMPTYPE_NONE:    name = "stored";  break;
388     case cffoldCOMPTYPE_MSZIP:   name = "MSZIP";   break;
389     case cffoldCOMPTYPE_QUANTUM: name = "Quantum"; break;
390     case cffoldCOMPTYPE_LZX:     name = "LZX";     break;
391     default:                     name = "unknown"; break;
392     }
393
394     printf(
395       "\n[New folder at offset %u]\n"
396       "Offset of folder       = %u\n"
397       "Num. blocks in folder  = %u\n"
398       "Compression type       = 0x%04x [%s]\n",
399
400       offset,
401       base_offset + GETLONG(cffold_DataOffset),
402       GETWORD(cffold_NumBlocks),
403       GETWORD(cffold_CompType),
404       name
405     );
406
407     num_blocks += GETWORD(cffold_NumBlocks);
408
409     if (folder_res) {
410       printf("[Reserved folder: offset %lu, size %u]\n", GETOFFSET, folder_res);
411       SKIP(folder_res);
412     }
413   }
414
415   printf("\n*** FILES SECTION ***\n");
416
417   if (GETOFFSET != files_offset) {
418     printf("WARNING: weird file offset in header\n");
419     SEEK(files_offset);
420   }
421
422
423   for (i = 0; i < num_files; i++) {
424     offset = GETOFFSET;
425     READ(&buf, cffile_SIZEOF);
426
427     switch (GETWORD(cffile_FolderIndex)) {
428     case cffileCONTINUED_PREV_AND_NEXT:
429       name = "continued from previous and to next cabinet";
430       break;
431     case cffileCONTINUED_FROM_PREV:
432       name = "continued from previous cabinet";
433       break;
434     case cffileCONTINUED_TO_NEXT:
435       name = "continued to next cabinet";
436       break;
437     default:
438       name = "normal folder";
439       break;
440     }
441     
442     x = GETWORD(cffile_Attribs);
443     
444     base = GETOFFSET;
445     READ(&namebuf, CAB_NAMEMAX);
446     SEEK(base + strlen(namebuf) + 1);
447     if (strlen(namebuf) > 256) printf("WARNING: name length > 256\n");
448
449     /* convert filename */
450     if (x & cffile_A_NAME_IS_UTF) {
451       if (!convertUTF(namebuf)) printf("WARNING: invalid UTF filename");
452     }
453
454     printf(
455       "\n[New file at offset %u]\n"
456       "File name              = %s\n"
457       "File size              = %u bytes\n"
458       "Offset within folder   = %u\n"
459       "Folder index           = 0x%04x [%s]\n"
460       "Date / time            = %02d/%02d/%4d %02d:%02d:%02d\n"
461       "File attributes        = 0x%02x %s%s%s%s%s\n",
462       offset,
463       namebuf,
464       GETLONG(cffile_UncompressedSize),
465       GETLONG(cffile_FolderOffset),
466       GETWORD(cffile_FolderIndex),
467       name,
468       GETWORD(cffile_Date) & 0x1f,
469       (GETWORD(cffile_Date)>>5) & 0xf,
470       (GETWORD(cffile_Date)>>9) + 1980,
471       GETWORD(cffile_Time) >> 11,
472       (GETWORD(cffile_Time)>>5) & 0x3f,
473       (GETWORD(cffile_Time) << 1) & 0x3e,
474       x,
475       (x & cffile_A_RDONLY) ? "RDONLY " : "",
476       (x & cffile_A_HIDDEN) ? "HIDDEN " : "",
477       (x & cffile_A_SYSTEM) ? "SYSTEM " : "",
478       (x & cffile_A_ARCH)   ? "ARCH "   : "",
479       (x & cffile_A_EXEC)   ? "EXEC "   : ""
480     );
481   }
482
483   printf("\n*** DATABLOCKS SECTION ***\n");
484   printf("*** Note: offset is BLOCK offset. Add 8 for DATA offset! ***\n\n");
485
486   for (i = 0; i < num_blocks; i++) {
487     offset = GETOFFSET;
488     READ(&buf, cfdata_SIZEOF);
489     printf("Block %5d: offset %10d / csum %08x / c=%5d / u=%5d\n",
490       i, offset, GETLONG(cfdata_CheckSum),
491       x = GETWORD(cfdata_CompressedSize),
492       GETWORD(cfdata_UncompressedSize)
493     );
494     SKIP(x);
495   }
496
497 }