support for zipped ISOs
[picodrive.git] / unzip / unzip.c
CommitLineData
cc68a136 1#include "unzip.h"\r
2\r
3#include <stdlib.h>\r
4#include <string.h>\r
5#include <ctype.h>\r
6#include <assert.h>\r
7\r
8#ifdef __SYMBIAN32__\r
9#include <ezlib.h>\r
10#else\r
11#include "zlib/zlib.h"\r
12#endif\r
13\r
14/* public globals */\r
15//int gUnzipQuiet = 0; /* flag controls error messages */\r
16\r
17#define ERROR_CORRUPT "The zipfile seems to be corrupt, please check it"\r
18#define ERROR_FILESYSTEM "Your filesystem seems to be corrupt, please check it"\r
19#define ERROR_UNSUPPORTED "The format of this zipfile is not supported, please recompress it"\r
20\r
21#define INFLATE_INPUT_BUFFER_MAX 16384\r
22#ifndef MIN\r
23#define MIN(x,y) ((x)<(y)?(x):(y))\r
24#endif\r
25\r
26\r
27// notaz\r
83bd0b76 28#if 1 //def __DEBUG_PRINT\r
29#define logerror printf\r
30#define errormsg(str1,def,fname) printf("%s: " #def ": " str1 "\n", fname);\r
cc68a136 31#else\r
32#define logerror(x...)\r
33#define errormsg(x...)\r
34#endif\r
35\r
36/* Print a error message */\r
37//void errormsg(const char* extmsg, const char* usermsg, const char* zipname) {\r
38 /* Output to the user with no internal detail */\r
39// if (!gUnzipQuiet)\r
40// printf("Error in zipfile %s\n%s\n", zipname, usermsg);\r
41 /* Output to log file with all informations */\r
42// logerror("Error in zipfile %s: %s\n", zipname, extmsg);\r
43// printf("Error in zipfile %s: %s\n", zipname, extmsg);\r
44//}\r
45\r
46/* -------------------------------------------------------------------------\r
47 Unzip support\r
48 ------------------------------------------------------------------------- */\r
49\r
50/* Use these to avoid structure padding and byte-ordering problems */\r
51static UINT16 read_word (char *buf) {\r
52 unsigned char *ubuf = (unsigned char *) buf;\r
53\r
54 return ((UINT16)ubuf[1] << 8) | (UINT16)ubuf[0];\r
55}\r
56\r
57/* Use these to avoid structure padding and byte-ordering problems */\r
58static UINT32 read_dword (char *buf) {\r
59 unsigned char *ubuf = (unsigned char *) buf;\r
60\r
61 return ((UINT32)ubuf[3] << 24) | ((UINT32)ubuf[2] << 16) | ((UINT32)ubuf[1] << 8) | (UINT32)ubuf[0];\r
62}\r
63\r
64/* Locate end-of-central-dir sig in buffer and return offset\r
65 out:\r
66 *offset offset of cent dir start in buffer\r
67 return:\r
68 ==0 not found\r
69 !=0 found, *offset valid\r
70*/\r
71static int ecd_find_sig (char *buffer, int buflen, int *offset)\r
72{\r
73 static char ecdsig[] = { 'P', 'K', 0x05, 0x06 };\r
74 int i;\r
75 for (i=buflen-22; i>=0; i--) {\r
76 if (memcmp(buffer+i, ecdsig, 4) == 0) {\r
77 *offset = i;\r
78 return 1;\r
79 }\r
80 }\r
81 return 0;\r
82}\r
83\r
84/* Read ecd data in zip structure\r
85 in:\r
86 zip->fp, zip->length zip file\r
87 out:\r
88 zip->ecd, zip->ecd_length ecd data\r
89*/\r
90static int ecd_read(ZIP* zip) {\r
91 char* buf;\r
92 int buf_length = 1024; /* initial buffer length */\r
93\r
94 while (1) {\r
95 int offset;\r
96\r
97 if (buf_length > zip->length)\r
98 buf_length = zip->length;\r
99\r
100 if (fseek(zip->fp, zip->length - buf_length, SEEK_SET) != 0) {\r
101 return -1;\r
102 }\r
103\r
104 /* allocate buffer */\r
105 buf = (char*)malloc( buf_length );\r
106 if (!buf) {\r
107 return -1;\r
108 }\r
109\r
110 if (fread( buf, buf_length, 1, zip->fp ) != 1) {\r
111 free(buf);\r
112 return -1;\r
113 }\r
114\r
115 if (ecd_find_sig(buf, buf_length, &offset)) {\r
116 zip->ecd_length = buf_length - offset;\r
117\r
118 zip->ecd = (char*)malloc( zip->ecd_length );\r
119 if (!zip->ecd) {\r
120 free(buf);\r
121 return -1;\r
122 }\r
123\r
124 memcpy(zip->ecd, buf + offset, zip->ecd_length);\r
125\r
126 free(buf);\r
127 return 0;\r
128 }\r
129\r
130 free(buf);\r
131\r
132 if (buf_length < zip->length) {\r
133 /* double buffer */\r
134 buf_length = 2*buf_length;\r
135\r
136 logerror("Retry reading of zip ecd for %d bytes\n",buf_length);\r
137\r
138 } else {\r
139 return -1;\r
140 }\r
141 }\r
142}\r
143\r
144/* offsets in end of central directory structure */\r
145#define ZIPESIG 0x00\r
146#define ZIPEDSK 0x04\r
147#define ZIPECEN 0x06\r
148#define ZIPENUM 0x08\r
149#define ZIPECENN 0x0a\r
150#define ZIPECSZ 0x0c\r
151#define ZIPEOFST 0x10\r
152#define ZIPECOML 0x14\r
153#define ZIPECOM 0x16\r
154\r
155/* offsets in central directory entry structure */\r
156#define ZIPCENSIG 0x0\r
157#define ZIPCVER 0x4\r
158#define ZIPCOS 0x5\r
159#define ZIPCVXT 0x6\r
160#define ZIPCEXOS 0x7\r
161#define ZIPCFLG 0x8\r
162#define ZIPCMTHD 0xa\r
163#define ZIPCTIM 0xc\r
164#define ZIPCDAT 0xe\r
165#define ZIPCCRC 0x10\r
166#define ZIPCSIZ 0x14\r
167#define ZIPCUNC 0x18\r
168#define ZIPCFNL 0x1c\r
169#define ZIPCXTL 0x1e\r
170#define ZIPCCML 0x20\r
171#define ZIPDSK 0x22\r
172#define ZIPINT 0x24\r
173#define ZIPEXT 0x26\r
174#define ZIPOFST 0x2a\r
175#define ZIPCFN 0x2e\r
176\r
177/* offsets in local file header structure */\r
178#define ZIPLOCSIG 0x00\r
179#define ZIPVER 0x04\r
180#define ZIPGENFLG 0x06\r
181#define ZIPMTHD 0x08\r
182#define ZIPTIME 0x0a\r
183#define ZIPDATE 0x0c\r
184#define ZIPCRC 0x0e\r
185#define ZIPSIZE 0x12\r
186#define ZIPUNCMP 0x16\r
187#define ZIPFNLN 0x1a\r
188#define ZIPXTRALN 0x1c\r
189#define ZIPNAME 0x1e\r
190\r
191/* Opens a zip stream for reading\r
192 return:\r
193 !=0 success, zip stream\r
194 ==0 error\r
195*/\r
196ZIP* openzip(const char* zipfile) {\r
197 /* allocate */\r
198 ZIP* zip = (ZIP*)malloc( sizeof(ZIP) );\r
199 if (!zip) {\r
200 return 0;\r
201 }\r
202\r
203 /* open */\r
204 zip->fp = fopen(zipfile, "rb");\r
205 if (!zip->fp) {\r
206 errormsg ("Opening for reading", ERROR_FILESYSTEM, zipfile);\r
207 free(zip);\r
208 return 0;\r
209 }\r
210\r
211 /* go to end */\r
212 if (fseek(zip->fp, 0L, SEEK_END) != 0) {\r
213 errormsg ("Seeking to end", ERROR_FILESYSTEM, zipfile);\r
214 fclose(zip->fp);\r
215 free(zip);\r
216 return 0;\r
217 }\r
218\r
219 /* get length */\r
220 zip->length = ftell(zip->fp);\r
221 if (zip->length < 0) {\r
222 errormsg ("Get file size", ERROR_FILESYSTEM, zipfile);\r
223 fclose(zip->fp);\r
224 free(zip);\r
225 return 0;\r
226 }\r
227 if (zip->length == 0) {\r
228 errormsg ("Empty file", ERROR_CORRUPT, zipfile);\r
229 fclose(zip->fp);\r
230 free(zip);\r
231 return 0;\r
232 }\r
233\r
234 /* read ecd data */\r
235 if (ecd_read(zip)!=0) {\r
236 errormsg ("Reading ECD (end of central directory)", ERROR_CORRUPT, zipfile);\r
237 fclose(zip->fp);\r
238 free(zip);\r
239 return 0;\r
240 }\r
241\r
242 /* compile ecd info */\r
243 zip->end_of_cent_dir_sig = read_dword (zip->ecd+ZIPESIG);\r
244 zip->number_of_this_disk = read_word (zip->ecd+ZIPEDSK);\r
245 zip->number_of_disk_start_cent_dir = read_word (zip->ecd+ZIPECEN);\r
246 zip->total_entries_cent_dir_this_disk = read_word (zip->ecd+ZIPENUM);\r
247 zip->total_entries_cent_dir = read_word (zip->ecd+ZIPECENN);\r
248 zip->size_of_cent_dir = read_dword (zip->ecd+ZIPECSZ);\r
249 zip->offset_to_start_of_cent_dir = read_dword (zip->ecd+ZIPEOFST);\r
250 zip->zipfile_comment_length = read_word (zip->ecd+ZIPECOML);\r
251 zip->zipfile_comment = zip->ecd+ZIPECOM;\r
252\r
253 /* verify that we can work with this zipfile (no disk spanning allowed) */\r
254 if ((zip->number_of_this_disk != zip->number_of_disk_start_cent_dir) ||\r
255 (zip->total_entries_cent_dir_this_disk != zip->total_entries_cent_dir) ||\r
256 (zip->total_entries_cent_dir < 1)) {\r
257 errormsg("Cannot span disks", ERROR_UNSUPPORTED, zipfile);\r
258 free(zip->ecd);\r
259 fclose(zip->fp);\r
260 free(zip);\r
261 return 0;\r
262 }\r
263\r
264 if (fseek(zip->fp, zip->offset_to_start_of_cent_dir, SEEK_SET)!=0) {\r
265 errormsg ("Seeking to central directory", ERROR_CORRUPT, zipfile);\r
266 free(zip->ecd);\r
267 fclose(zip->fp);\r
268 free(zip);\r
269 return 0;\r
270 }\r
271\r
272 /* read from start of central directory */\r
273 zip->cd = (char*)malloc( zip->size_of_cent_dir );\r
274 if (!zip->cd) {\r
275 free(zip->ecd);\r
276 fclose(zip->fp);\r
277 free(zip);\r
278 return 0;\r
279 }\r
280\r
281 if (fread(zip->cd, zip->size_of_cent_dir, 1, zip->fp)!=1) {\r
282 errormsg ("Reading central directory", ERROR_CORRUPT, zipfile);\r
283 free(zip->cd);\r
284 free(zip->ecd);\r
285 fclose(zip->fp);\r
286 free(zip);\r
287 return 0;\r
288 }\r
289\r
290 /* reset ent */\r
291 zip->ent.name = 0;\r
292\r
293 /* rewind */\r
294 zip->cd_pos = 0;\r
295\r
296 /* file name */\r
297 zip->zip = (char*)malloc(strlen(zipfile)+1);\r
298 if (!zip->zip) {\r
299 free(zip->cd);\r
300 free(zip->ecd);\r
301 fclose(zip->fp);\r
302 free(zip);\r
303 return 0;\r
304 }\r
305 strcpy(zip->zip, zipfile);\r
306\r
307 return zip;\r
308}\r
309\r
310/* Reads the current entry from a zip stream\r
311 in:\r
312 zip opened zip\r
313 return:\r
314 !=0 success\r
315 ==0 error\r
316*/\r
317struct zipent* readzip(ZIP* zip) {\r
318\r
319 /* end of directory */\r
320 if (zip->cd_pos >= zip->size_of_cent_dir)\r
321 return 0;\r
322\r
323 /* compile zipent info */\r
324 zip->ent.cent_file_header_sig = read_dword (zip->cd+zip->cd_pos+ZIPCENSIG);\r
325 zip->ent.version_made_by = *(zip->cd+zip->cd_pos+ZIPCVER);\r
326 zip->ent.host_os = *(zip->cd+zip->cd_pos+ZIPCOS);\r
327 zip->ent.version_needed_to_extract = *(zip->cd+zip->cd_pos+ZIPCVXT);\r
328 zip->ent.os_needed_to_extract = *(zip->cd+zip->cd_pos+ZIPCEXOS);\r
329 zip->ent.general_purpose_bit_flag = read_word (zip->cd+zip->cd_pos+ZIPCFLG);\r
330 zip->ent.compression_method = read_word (zip->cd+zip->cd_pos+ZIPCMTHD);\r
331 zip->ent.last_mod_file_time = read_word (zip->cd+zip->cd_pos+ZIPCTIM);\r
332 zip->ent.last_mod_file_date = read_word (zip->cd+zip->cd_pos+ZIPCDAT);\r
333 zip->ent.crc32 = read_dword (zip->cd+zip->cd_pos+ZIPCCRC);\r
334 zip->ent.compressed_size = read_dword (zip->cd+zip->cd_pos+ZIPCSIZ);\r
335 zip->ent.uncompressed_size = read_dword (zip->cd+zip->cd_pos+ZIPCUNC);\r
336 zip->ent.filename_length = read_word (zip->cd+zip->cd_pos+ZIPCFNL);\r
337 zip->ent.extra_field_length = read_word (zip->cd+zip->cd_pos+ZIPCXTL);\r
338 zip->ent.file_comment_length = read_word (zip->cd+zip->cd_pos+ZIPCCML);\r
339 zip->ent.disk_number_start = read_word (zip->cd+zip->cd_pos+ZIPDSK);\r
340 zip->ent.internal_file_attrib = read_word (zip->cd+zip->cd_pos+ZIPINT);\r
341 zip->ent.external_file_attrib = read_dword (zip->cd+zip->cd_pos+ZIPEXT);\r
342 zip->ent.offset_lcl_hdr_frm_frst_disk = read_dword (zip->cd+zip->cd_pos+ZIPOFST);\r
343\r
344 /* check to see if filename length is illegally long (past the size of this directory\r
345 entry) */\r
346 if (zip->cd_pos + ZIPCFN + zip->ent.filename_length > zip->size_of_cent_dir)\r
347 {\r
348 errormsg("Invalid filename length in directory", ERROR_CORRUPT,zip->zip);\r
349 return 0;\r
350 }\r
351\r
352 /* copy filename */\r
353 free(zip->ent.name);\r
354 zip->ent.name = (char*)malloc(zip->ent.filename_length + 1);\r
355 memcpy(zip->ent.name, zip->cd+zip->cd_pos+ZIPCFN, zip->ent.filename_length);\r
356 zip->ent.name[zip->ent.filename_length] = 0;\r
357\r
358 /* skip to next entry in central dir */\r
359 zip->cd_pos += ZIPCFN + zip->ent.filename_length + zip->ent.extra_field_length + zip->ent.file_comment_length;\r
360\r
361 return &zip->ent;\r
362}\r
363\r
364/* Closes a zip stream */\r
365void closezip(ZIP* zip) {\r
366 /* release all */\r
367 free(zip->ent.name);\r
368 free(zip->cd);\r
369 free(zip->ecd);\r
370 /* only if not suspended */\r
371 if (zip->fp)\r
372 fclose(zip->fp);\r
373 free(zip->zip);\r
374 free(zip);\r
375}\r
376\r
377/* Suspend access to a zip file (release file handler)\r
378 in:\r
379 zip opened zip\r
380 note:\r
381 A suspended zip is automatically reopened at first call of\r
382 readuncompressd() or readcompressed() functions\r
383*/\r
384void suspendzip(ZIP* zip) {\r
385 if (zip->fp) {\r
386 fclose(zip->fp);\r
387 zip->fp = 0;\r
388 }\r
389}\r
390\r
391/* Revive a suspended zip file (reopen file handler)\r
392 in:\r
393 zip suspended zip\r
394 return:\r
395 zip success\r
396 ==0 error (zip must be closed with closezip)\r
397*/\r
398static ZIP* revivezip(ZIP* zip) {\r
399 if (!zip->fp) {\r
400 zip->fp = fopen(zip->zip, "rb");\r
401 if (!zip->fp) {\r
402 return 0;\r
403 }\r
404 }\r
405 return zip;\r
406\r
407}\r
408\r
409/* Reset a zip stream to the first entry\r
410 in:\r
411 zip opened zip\r
412 note:\r
413 ZIP file must be opened and not suspended\r
414*/\r
415void rewindzip(ZIP* zip) {\r
416 zip->cd_pos = 0;\r
417}\r
418\r
419/* Seek zip->fp to compressed data\r
420 return:\r
421 ==0 success\r
422 <0 error\r
423*/\r
424int seekcompresszip(ZIP* zip, struct zipent* ent) {\r
425 char buf[ZIPNAME];\r
426 long offset;\r
427\r
428 if (!zip->fp) {\r
429 if (!revivezip(zip))\r
430 return -1;\r
431 }\r
432\r
433 if (fseek(zip->fp, ent->offset_lcl_hdr_frm_frst_disk, SEEK_SET)!=0) {\r
434 errormsg ("Seeking to header", ERROR_CORRUPT, zip->zip);\r
435 return -1;\r
436 }\r
437\r
438 if (fread(buf, ZIPNAME, 1, zip->fp)!=1) {\r
439 errormsg ("Reading header", ERROR_CORRUPT, zip->zip);\r
440 return -1;\r
441 }\r
442\r
443 {\r
444 UINT16 filename_length = read_word (buf+ZIPFNLN);\r
445 UINT16 extra_field_length = read_word (buf+ZIPXTRALN);\r
446\r
447 /* calculate offset to data and fseek() there */\r
448 offset = ent->offset_lcl_hdr_frm_frst_disk + ZIPNAME + filename_length + extra_field_length;\r
449\r
450 if (fseek(zip->fp, offset, SEEK_SET) != 0) {\r
451 errormsg ("Seeking to compressed data", ERROR_CORRUPT, zip->zip);\r
452 return -1;\r
453 }\r
454\r
455 }\r
456\r
457 return 0;\r
458}\r
459\r
460/* Inflate a file\r
461 in:\r
462 in_file stream to inflate\r
463 in_size size of the compressed data to read\r
464 out_size size of decompressed data\r
465 out:\r
466 out_data buffer for decompressed data\r
467 return:\r
468 ==0 ok\r
469\r
470 990525 rewritten for use with zlib MLR\r
471*/\r
472static int inflate_file(FILE* in_file, unsigned in_size, unsigned char* out_data, unsigned out_size)\r
473{\r
474 int err;\r
475 unsigned char* in_buffer;\r
476 z_stream d_stream; /* decompression stream */\r
477\r
478 d_stream.zalloc = 0;\r
479 d_stream.zfree = 0;\r
480 d_stream.opaque = 0;\r
481\r
482 d_stream.next_in = 0;\r
483 d_stream.avail_in = 0;\r
484 d_stream.next_out = out_data;\r
485 d_stream.avail_out = out_size;\r
486\r
487 err = inflateInit2(&d_stream, -MAX_WBITS);\r
488 /* windowBits is passed < 0 to tell that there is no zlib header.\r
489 * Note that in this case inflate *requires* an extra "dummy" byte\r
490 * after the compressed stream in order to complete decompression and\r
491 * return Z_STREAM_END.\r
492 */\r
493 if (err != Z_OK)\r
494 {\r
495 logerror("inflateInit error: %d\n", err);\r
496 return -1;\r
497 }\r
498\r
499 in_buffer = (unsigned char*)malloc(INFLATE_INPUT_BUFFER_MAX+1);\r
500 if (!in_buffer)\r
501 return -1;\r
502\r
503 for (;;)\r
504 {\r
505 if (in_size <= 0)\r
506 {\r
507 logerror("inflate error: compressed size too small\n");\r
508 free (in_buffer);\r
509 return -1;\r
510 }\r
511 d_stream.next_in = in_buffer;\r
512 d_stream.avail_in = fread (in_buffer, 1, MIN(in_size, INFLATE_INPUT_BUFFER_MAX), in_file);\r
513 in_size -= d_stream.avail_in;\r
514 if (in_size == 0)\r
515 d_stream.avail_in++; /* add dummy byte at end of compressed data */\r
516\r
517 err = inflate(&d_stream, Z_NO_FLUSH);\r
518 if (err == Z_STREAM_END)\r
519 break;\r
520 if (err != Z_OK)\r
521 {\r
522 logerror("inflate error: %d\n", err);\r
523 free (in_buffer);\r
524 return -1;\r
525 }\r
526 }\r
527\r
528 err = inflateEnd(&d_stream);\r
529 if (err != Z_OK)\r
530 {\r
531 logerror("inflateEnd error: %d\n", err);\r
532 free (in_buffer);\r
533 return -1;\r
534 }\r
535\r
536 free (in_buffer);\r
537\r
538 if ((d_stream.avail_out > 0) || (in_size > 0))\r
539 {\r
540 logerror("zip size mismatch. %i\n", in_size);\r
541 return -1;\r
542 }\r
543\r
544 return 0;\r
545}\r
546\r
547/* Read compressed data\r
548 out:\r
549 data compressed data read\r
550 return:\r
551 ==0 success\r
552 <0 error\r
553*/\r
554int readcompresszip(ZIP* zip, struct zipent* ent, char* data) {\r
555 int err = seekcompresszip(zip,ent);\r
556 if (err!=0)\r
557 return err;\r
558\r
559 if (fread(data, ent->compressed_size, 1, zip->fp)!=1) {\r
560 errormsg ("Reading compressed data", ERROR_CORRUPT, zip->zip);\r
561 return -1;\r
562 }\r
563\r
564 return 0;\r
565}\r
566\r
567/* Read UNcompressed data\r
568 out:\r
569 data UNcompressed data\r
570 return:\r
571 ==0 success\r
572 <0 error\r
573*/\r
574int readuncompresszip(ZIP* zip, struct zipent* ent, char* data) {\r
575 if (ent->compression_method == 0x0000) {\r
576 /* file is not compressed, simply stored */\r
577\r
578 /* check if size are equal */\r
579 if (ent->compressed_size != ent->uncompressed_size) {\r
580 errormsg("Wrong uncompressed size in store compression", ERROR_CORRUPT,zip->zip);\r
581 return -3;\r
582 }\r
583\r
584 return readcompresszip(zip,ent,data);\r
585 } else if (ent->compression_method == 0x0008) {\r
586 /* file is compressed using "Deflate" method */\r
587 if (ent->version_needed_to_extract > 0x14) {\r
588 errormsg("Version too new", ERROR_UNSUPPORTED,zip->zip);\r
589 return -2;\r
590 }\r
591\r
592 if (ent->os_needed_to_extract != 0x00) {\r
593 errormsg("OS not supported", ERROR_UNSUPPORTED,zip->zip);\r
594 return -2;\r
595 }\r
596\r
597 if (ent->disk_number_start != zip->number_of_this_disk) {\r
598 errormsg("Cannot span disks", ERROR_UNSUPPORTED,zip->zip);\r
599 return -2;\r
600 }\r
601\r
602 /* read compressed data */\r
603 if (seekcompresszip(zip,ent)!=0) {\r
604 return -1;\r
605 }\r
606\r
607 /* configure inflate */\r
608 if (inflate_file( zip->fp, ent->compressed_size, (unsigned char*)data, ent->uncompressed_size))\r
609 {\r
610 errormsg("Inflating compressed data", ERROR_CORRUPT, zip->zip);\r
611 return -3;\r
612 }\r
613\r
614 return 0;\r
615 } else {\r
616 errormsg("Compression method unsupported", ERROR_UNSUPPORTED, zip->zip);\r
617 return -2;\r
618 }\r
619}\r