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