478d4a8fbb3d1eaf3af3d1fbf30de476059b2dc0
[rocksndiamonds.git] / src / libgame / zip / miniunz.c
1 /* miniunz.c
2    Version 1.2.0, September 16th, 2017
3    sample part of the MiniZip project
4
5    Copyright (C) 2012-2017 Nathan Moinvaziri
6      https://github.com/nmoinvaz/minizip
7    Copyright (C) 2009-2010 Mathias Svensson
8      Modifications for Zip64 support
9      http://result42.com
10    Copyright (C) 2007-2008 Even Rouault
11      Modifications of Unzip for Zip64
12    Copyright (C) 1998-2010 Gilles Vollant
13      http://www.winimage.com/zLibDll/minizip.html
14
15    This program is distributed under the terms of the same license as zlib.
16    See the accompanying LICENSE file for the full text of the license.
17 */
18
19 #if defined(__linux__)
20 // Linux needs this to support file operation on files larger then 4+GB
21 #  ifndef __USE_FILE_OFFSET64
22 #  define __USE_FILE_OFFSET64
23 #  endif
24 #  ifndef __USE_LARGEFILE64
25 #  define __USE_LARGEFILE64
26 #  endif
27 #  ifndef _LARGEFILE64_SOURCE
28 #  define _LARGEFILE64_SOURCE
29 #  endif
30 #  ifndef _FILE_OFFSET_BIT
31 #  define _FILE_OFFSET_BIT 64
32 #  endif
33 #endif
34
35 #include <stdio.h>
36 #include <stdint.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <time.h>
40 #include <errno.h>
41 #include <fcntl.h>
42
43 #include <unistd.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46
47 #include "zlib.h"
48 #include "ioapi.h"
49
50 #ifdef _WIN32
51 #  include <direct.h>
52 #  include <io.h>
53 #else
54 #  include <utime.h>
55 #endif
56
57 #include "unzip.h"
58
59 #ifdef _WIN32
60 #  define USEWIN32IOAPI
61 #  include "iowin32.h"
62 #endif
63
64 #include "miniunz.h"
65
66 #ifdef _WIN32
67 #  define MKDIR(d) _mkdir(d)
68 #  define CHDIR(d) _chdir(d)
69 #else
70 #  define MKDIR(d) mkdir(d, 0775)
71 #  define CHDIR(d) chdir(d)
72 #endif
73
74
75 #define DEBUG_PRINTF                    0
76 #define debug_printf(...)               \
77     do { if (DEBUG_PRINTF) fprintf(stderr, __VA_ARGS__); } while (0)
78
79
80 static char **zip_entries = NULL;
81 static int num_zip_entries = 0;
82 static int max_zip_entries = 0;
83 static const int max_alloc_zip_entries = 256;
84
85 static void add_zip_entry(const char *s)
86 {
87     if (zip_entries == NULL)
88     {
89         max_zip_entries = max_alloc_zip_entries;
90
91         zip_entries = malloc(max_zip_entries * sizeof(char *));
92
93         if (zip_entries == NULL)
94             exit(1);
95
96         num_zip_entries = 0;
97     }
98
99     if (num_zip_entries >= max_zip_entries - 1)
100     {
101         max_zip_entries += max_alloc_zip_entries;
102
103         zip_entries = realloc(zip_entries, max_zip_entries * sizeof(char *));
104
105         if (zip_entries == NULL)
106             exit(1);
107     }
108
109     zip_entries[num_zip_entries++] = strdup(s);
110     zip_entries[num_zip_entries] = NULL;        // ensure NULL terminated list
111 }
112
113 static void free_zip_entries(void)
114 {
115     if (zip_entries == NULL)
116         return;
117
118     int i;
119
120     for (i = 0; i < num_zip_entries; i++)
121         free(zip_entries[i]);
122
123     num_zip_entries = 0;
124     max_zip_entries = 0;
125
126     free(zip_entries);
127
128     zip_entries = NULL;
129 }
130
131 static int invalid_date(const struct tm *ptm)
132 {
133 #define datevalue_in_range(min, max, value) ((min) <= (value) && (value) <= (max))
134     return (!datevalue_in_range(0, 207, ptm->tm_year) ||
135             !datevalue_in_range(0, 11, ptm->tm_mon) ||
136             !datevalue_in_range(1, 31, ptm->tm_mday) ||
137             !datevalue_in_range(0, 23, ptm->tm_hour) ||
138             !datevalue_in_range(0, 59, ptm->tm_min) ||
139             !datevalue_in_range(0, 59, ptm->tm_sec));
140 #undef datevalue_in_range
141 }
142
143 // Conversion without validation
144 static void dosdate_to_raw_tm(uint64_t dos_date, struct tm *ptm)
145 {
146     uint64_t date = (uint64_t)(dos_date >> 16);
147
148     ptm->tm_mday = (uint16_t)(date & 0x1f);
149     ptm->tm_mon = (uint16_t)(((date & 0x1E0) / 0x20) - 1);
150     ptm->tm_year = (uint16_t)(((date & 0x0FE00) / 0x0200) + 80);
151     ptm->tm_hour = (uint16_t)((dos_date & 0xF800) / 0x800);
152     ptm->tm_min = (uint16_t)((dos_date & 0x7E0) / 0x20);
153     ptm->tm_sec = (uint16_t)(2 * (dos_date & 0x1f));
154     ptm->tm_isdst = -1;
155 }
156
157 static int dosdate_to_tm(uint64_t dos_date, struct tm *ptm)
158 {
159     dosdate_to_raw_tm(dos_date, ptm);
160
161     if (invalid_date(ptm))
162     {
163         // Invalid date stored, so don't return it.
164         memset(ptm, 0, sizeof(struct tm));
165         return -1;
166     }
167     return 0;
168 }
169
170 #ifndef _WIN32
171 static time_t dosdate_to_time_t(uint64_t dos_date)
172 {
173     struct tm ptm;
174     dosdate_to_raw_tm(dos_date, &ptm);
175     return mktime(&ptm);
176 }
177 #endif
178
179 static void change_file_date(const char *path, uint32_t dos_date)
180 {
181 #ifdef _WIN32
182     HANDLE handle = NULL;
183     FILETIME ftm, ftm_local, ftm_create, ftm_access, ftm_modified;
184
185     handle = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
186     if (handle != INVALID_HANDLE_VALUE)
187     {
188         GetFileTime(handle, &ftm_create, &ftm_access, &ftm_modified);
189         DosDateTimeToFileTime((WORD)(dos_date >> 16), (WORD)dos_date, &ftm_local);
190         LocalFileTimeToFileTime(&ftm_local, &ftm);
191         SetFileTime(handle, &ftm, &ftm_access, &ftm);
192         CloseHandle(handle);
193     }
194 #else
195     struct utimbuf ut;
196     ut.actime = ut.modtime = dosdate_to_time_t(dos_date);
197     utime(path, &ut);
198 #endif
199 }
200
201 static int makedir(const char *newdir)
202 {
203     char *buffer = NULL;
204     char *p = NULL;
205     int len = (int)strlen(newdir);
206
207     if (len <= 0)
208         return 0;
209
210     buffer = (char*)malloc(len + 1);
211     if (buffer == NULL)
212     {
213         debug_printf("Error allocating memory\n");
214         return -1;
215     }
216
217     strcpy(buffer, newdir);
218
219     if (buffer[len - 1] == '/')
220         buffer[len - 1] = 0;
221
222     if (MKDIR(buffer) == 0)
223     {
224         free(buffer);
225         return 1;
226     }
227
228     p = buffer + 1;
229     while (1)
230     {
231         char hold;
232         while (*p && *p != '\\' && *p != '/')
233             p++;
234         hold = *p;
235         *p = 0;
236
237         if ((MKDIR(buffer) == -1) && (errno == ENOENT))
238         {
239             debug_printf("couldn't create directory %s (%d)\n", buffer, errno);
240             free(buffer);
241             return 0;
242         }
243
244         if (hold == 0)
245             break;
246
247         *p++ = hold;
248     }
249
250     free(buffer);
251     return 1;
252 }
253
254 static FILE *get_file_handle(const char *path)
255 {
256     FILE *handle = NULL;
257 #if defined(WIN32)
258     wchar_t *pathWide = NULL;
259     int pathLength = 0;
260
261     pathLength = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0) + 1;
262     pathWide = (wchar_t*)calloc(pathLength, sizeof(wchar_t));
263     MultiByteToWideChar(CP_UTF8, 0, path, -1, pathWide, pathLength);
264     handle = _wfopen((const wchar_t*)pathWide, L"rb");
265     free(pathWide);
266 #else
267     handle = fopen64(path, "rb");
268 #endif
269
270     return handle;
271 }
272
273 static int check_file_exists(const char *path)
274 {
275     FILE *handle = get_file_handle(path);
276     if (handle == NULL)
277         return 0;
278     fclose(handle);
279     return 1;
280 }
281
282 static void display_zpos64(uint64_t n, int size_char)
283 {
284     /* To avoid compatibility problem we do here the conversion */
285     char number[21] = { 0 };
286     int offset = 19;
287     int pos_string = 19;
288     int size_display_string = 19;
289
290     while (1)
291     {
292         number[offset] = (char)((n % 10) + '0');
293         if (number[offset] != '0')
294             pos_string = offset;
295         n /= 10;
296         if (offset == 0)
297             break;
298         offset--;
299     }
300
301     size_display_string -= pos_string;
302     while (size_char-- > size_display_string)
303         debug_printf(" ");
304     debug_printf("%s", &number[pos_string]);
305 }
306
307 static int miniunz_list(unzFile uf)
308 {
309     free_zip_entries();
310
311     int err = unzGoToFirstFile(uf);
312     if (err != UNZ_OK)
313     {
314         debug_printf("error %d with zipfile in unzGoToFirstFile\n", err);
315         return 1;
316     }
317
318     debug_printf("  Length  Method     Size Ratio   Date    Time   CRC-32     Name\n");
319     debug_printf("  ------  ------     ---- -----   ----    ----   ------     ----\n");
320
321     do
322     {
323         char filename_inzip[256] = {0};
324         unz_file_info64 file_info = {0};
325         uint32_t ratio = 0;
326         struct tm tmu_date = { 0 };
327         const char *string_method = NULL;
328         char char_crypt = ' ';
329
330         err = unzGetCurrentFileInfo64(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
331         if (err != UNZ_OK)
332         {
333             debug_printf("error %d with zipfile in unzGetCurrentFileInfo\n", err);
334             break;
335         }
336
337         add_zip_entry(filename_inzip);
338
339         if (file_info.uncompressed_size > 0)
340             ratio = (uint32_t)((file_info.compressed_size * 100) / file_info.uncompressed_size);
341
342         /* Display a '*' if the file is encrypted */
343         if ((file_info.flag & 1) != 0)
344             char_crypt = '*';
345
346         if (file_info.compression_method == 0)
347             string_method = "Stored";
348         else if (file_info.compression_method == Z_DEFLATED)
349         {
350             uint16_t level = (uint16_t)((file_info.flag & 0x6) / 2);
351             if (level == 0)
352                 string_method = "Defl:N";
353             else if (level == 1)
354                 string_method = "Defl:X";
355             else if ((level == 2) || (level == 3))
356                 string_method = "Defl:F"; /* 2:fast , 3 : extra fast*/
357             else
358                 string_method = "Unkn. ";
359         }
360         else if (file_info.compression_method == Z_BZIP2ED)
361         {
362             string_method = "BZip2 ";
363         }
364         else
365             string_method = "Unkn. ";
366
367         display_zpos64(file_info.uncompressed_size, 7);
368         debug_printf("  %6s%c", string_method, char_crypt);
369         display_zpos64(file_info.compressed_size, 7);
370
371         dosdate_to_tm(file_info.dos_date, &tmu_date);
372         debug_printf(" %3u%%  %2.2u-%2.2u-%2.2u  %2.2u:%2.2u  %8.8x   %s\n", ratio,
373             (uint32_t)tmu_date.tm_mon + 1, (uint32_t)tmu_date.tm_mday,
374             (uint32_t)tmu_date.tm_year % 100,
375             (uint32_t)tmu_date.tm_hour, (uint32_t)tmu_date.tm_min,
376             file_info.crc, filename_inzip);
377
378         err = unzGoToNextFile(uf);
379     }
380     while (err == UNZ_OK);
381
382     if (err != UNZ_END_OF_LIST_OF_FILE && err != UNZ_OK)
383     {
384         debug_printf("error %d with zipfile in unzGoToNextFile\n", err);
385         return err;
386     }
387
388     return 0;
389 }
390
391 static int miniunz_extract_currentfile(unzFile uf, int opt_extract_without_path, int *popt_overwrite, const char *password)
392 {
393     unz_file_info64 file_info = {0};
394     FILE* fout = NULL;
395     void* buf = NULL;
396     uint16_t size_buf = 8192;
397     int err = UNZ_OK;
398     int errclose = UNZ_OK;
399     int skip = 0;
400     char filename_inzip[256] = {0};
401     char *filename_withoutpath = NULL;
402     const char *write_filename = NULL;
403     char *p = NULL;
404
405     err = unzGetCurrentFileInfo64(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
406     if (err != UNZ_OK)
407     {
408         debug_printf("error %d with zipfile in unzGetCurrentFileInfo\n",err);
409         return err;
410     }
411
412     add_zip_entry(filename_inzip);
413
414     p = filename_withoutpath = filename_inzip;
415     while (*p != 0)
416     {
417         if ((*p == '/') || (*p == '\\'))
418             filename_withoutpath = p+1;
419         p++;
420     }
421
422     /* If zip entry is a directory then create it on disk */
423     if (*filename_withoutpath == 0)
424     {
425         if (opt_extract_without_path == 0)
426         {
427             debug_printf("creating directory: %s\n", filename_inzip);
428             MKDIR(filename_inzip);
429         }
430         return err;
431     }
432
433     buf = (void*)malloc(size_buf);
434     if (buf == NULL)
435     {
436         debug_printf("Error allocating memory\n");
437         return UNZ_INTERNALERROR;
438     }
439
440     err = unzOpenCurrentFilePassword(uf, password);
441     if (err != UNZ_OK)
442         debug_printf("error %d with zipfile in unzOpenCurrentFilePassword\n", err);
443
444     if (opt_extract_without_path)
445         write_filename = filename_withoutpath;
446     else
447         write_filename = filename_inzip;
448
449     /* Determine if the file should be overwritten or not and ask the user if needed */
450     if ((err == UNZ_OK) && (*popt_overwrite == 0) && (check_file_exists(write_filename)))
451     {
452         char rep = 0;
453         do
454         {
455             char answer[128];
456             debug_printf("The file %s exists. Overwrite ? [y]es, [n]o, [A]ll: ", write_filename);
457             if (scanf("%1s", answer) != 1)
458                 exit(EXIT_FAILURE);
459             rep = answer[0];
460             if ((rep >= 'a') && (rep <= 'z'))
461                 rep -= 0x20;
462         }
463         while ((rep != 'Y') && (rep != 'N') && (rep != 'A'));
464
465         if (rep == 'N')
466             skip = 1;
467         if (rep == 'A')
468             *popt_overwrite = 1;
469     }
470
471     /* Create the file on disk so we can unzip to it */
472     if ((skip == 0) && (err == UNZ_OK))
473     {
474         fout = fopen64(write_filename, "wb");
475         /* Some zips don't contain directory alone before file */
476         if ((fout == NULL) && (opt_extract_without_path == 0) &&
477             (filename_withoutpath != (char*)filename_inzip))
478         {
479             char c = *(filename_withoutpath-1);
480             *(filename_withoutpath-1) = 0;
481             makedir(write_filename);
482             *(filename_withoutpath-1) = c;
483             fout = fopen64(write_filename, "wb");
484         }
485         if (fout == NULL)
486             debug_printf("error opening %s\n", write_filename);
487     }
488
489     /* Read from the zip, unzip to buffer, and write to disk */
490     if (fout != NULL)
491     {
492         debug_printf(" extracting: %s\n", write_filename);
493
494         do
495         {
496             err = unzReadCurrentFile(uf, buf, size_buf);
497             if (err < 0)
498             {
499                 debug_printf("error %d with zipfile in unzReadCurrentFile\n", err);
500                 break;
501             }
502             if (err == 0)
503                 break;
504             if (fwrite(buf, err, 1, fout) != 1)
505             {
506                 debug_printf("error %d in writing extracted file\n", errno);
507                 err = UNZ_ERRNO;
508                 break;
509             }
510         }
511         while (err > 0);
512
513         if (fout)
514             fclose(fout);
515
516         /* Set the time of the file that has been unzipped */
517         if (err == 0)
518             change_file_date(write_filename, file_info.dos_date);
519     }
520
521     errclose = unzCloseCurrentFile(uf);
522     if (errclose != UNZ_OK)
523         debug_printf("error %d with zipfile in unzCloseCurrentFile\n", errclose);
524
525     free(buf);
526     return err;
527 }
528
529 static int miniunz_extract_all(unzFile uf, int opt_extract_without_path, int opt_overwrite, const char *password)
530 {
531     free_zip_entries();
532
533     int err = unzGoToFirstFile(uf);
534     if (err != UNZ_OK)
535     {
536         debug_printf("error %d with zipfile in unzGoToFirstFile\n", err);
537         return 1;
538     }
539
540     do
541     {
542         err = miniunz_extract_currentfile(uf, opt_extract_without_path, &opt_overwrite, password);
543         if (err != UNZ_OK)
544             break;
545         err = unzGoToNextFile(uf);
546     }
547     while (err == UNZ_OK);
548
549     if (err != UNZ_END_OF_LIST_OF_FILE)
550     {
551         debug_printf("error %d with zipfile in unzGoToNextFile\n", err);
552         return 1;
553     }
554     return 0;
555 }
556
557 char **zip_list(char *filename)
558 {
559     if (filename == NULL)
560     {
561         debug_printf("No filename specified!\n");
562
563         return NULL;
564     }
565
566 #ifdef USEWIN32IOAPI
567     zlib_filefunc64_def ffunc;
568     fill_win32_filefunc64A(&ffunc);
569     unzFile uf = unzOpen2_64(filename, &ffunc);
570 #else
571     unzFile uf = unzOpen64(filename);
572 #endif
573
574     if (uf == NULL)
575     {
576         debug_printf("Cannot open file '%s'!\n", filename);
577
578         return NULL;
579     }
580
581     debug_printf("File '%s' opened.\n", filename);
582
583     int success = (miniunz_list(uf) == UNZ_OK);
584
585     unzClose(uf);
586
587     if (!success)
588         return NULL;
589
590     return zip_entries;
591 }
592
593 char **zip_extract(char *filename, char *directory)
594 {
595     if (filename == NULL)
596     {
597         debug_printf("No zip filename specified!\n");
598
599         return NULL;
600     }
601
602     if (directory == NULL)
603     {
604         debug_printf("No target directory specified!\n");
605
606         return NULL;
607     }
608
609     struct stat file_status;
610
611     if (stat(directory, &file_status) != 0 || !S_ISDIR(file_status.st_mode))
612     {
613         debug_printf("Directory '%s' does not exist!\n", directory);
614
615         return NULL;
616     }
617
618 #ifdef USEWIN32IOAPI
619     zlib_filefunc64_def ffunc;
620     fill_win32_filefunc64A(&ffunc);
621     unzFile uf = unzOpen2_64(filename, &ffunc);
622 #else
623     unzFile uf = unzOpen64(filename);
624 #endif
625
626     if (uf == NULL)
627     {
628         debug_printf("Cannot open file '%s'!\n", filename);
629
630         return NULL;
631     }
632
633     debug_printf("File '%s' opened.\n", filename);
634
635     int max_directory_size = 1024;
636     char last_directory[max_directory_size];
637
638     if (getcwd(last_directory, max_directory_size) == NULL)
639     {
640         debug_printf("Cannot get current directory!\n");
641
642         unzClose(uf);
643
644         return NULL;
645     }
646
647     if (CHDIR(directory))       // change to target directory
648     {
649       debug_printf("Cannot change to directory '%s'!\n", directory);
650
651       unzClose(uf);
652
653       return NULL;
654     }
655
656     int success = (miniunz_extract_all(uf, 0, 1, NULL) == UNZ_OK);
657
658     CHDIR(last_directory);      // change back to previous directory
659
660     unzClose(uf);
661
662     if (!success)
663         return NULL;
664
665     return zip_entries;
666 }