Subversion Repositories SvarDOS

Rev

Rev 1965 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
219 mateuszvis 1
/*
268 mateuszvis 2
 * This file is part of pkg (SvarDOS)
1965 mateusz.vi 3
 * Copyright (C) 2012-2024 Mateusz Viste.
219 mateuszvis 4
 *
5
 * Simple library providing functions to unzip files from zip archives.
6
 */
7
 
8
#include <stdio.h>     /* printf(), FILE, fclose()... */
9
#include <stdlib.h>    /* NULL */
10
#include <string.h>    /* memset() */
11
#include <time.h>      /* mktime() */
12
#include <utime.h>     /* utime() */
13
#include <unistd.h>   /* unlink() */
14
 
15
#include "crc32.h"
1963 mateusz.vi 16
#include "helpers.h"
268 mateuszvis 17
#include "inf.h"   /* INFLATE support */
1965 mateusz.vi 18
#include "svarlang.lib\svarlang.h"
219 mateuszvis 19
 
20
#include "libunzip.h"  /* include self for control */
21
 
22
 
23
/* converts a "DOS format" timestamp into unix timestamp. The DOS timestamp is constructed an array of 4 bytes, that contains following data at the bit level:
24
 * HHHHHMMM MMMSSSSS YYYYYYYM MMMDDDDD
25
 *  where:
26
 * day of month is always within 1-31 range;
27
 * month is always within 1-12 range;
28
 * year starts from 1980 and continues for 127 years
29
 * seconds are actually not 0-59 but rather 0-29 as there are only 32 possible values – to get actual seconds multiply this field by 2;
30
 * minutes are always within 0-59 range;
31
 * hours are always within 0-23 range.     */
268 mateuszvis 32
static time_t dostime2unix(const unsigned char *buff) {
219 mateuszvis 33
  struct tm curtime;
34
  time_t result;
35
  memset(&curtime, 0, sizeof(curtime)); /* make sure to set everything in curtime to 0's */
36
  curtime.tm_sec = (buff[0] & 31) << 1; /* seconds (0..60) */
37
  curtime.tm_min = (((buff[1] << 8) | buff[0]) >> 5) & 63 ; /* minutes after the hour (0..59) */
38
  curtime.tm_hour = (buff[1] >> 3); /* hours since midnight (0..23) */
39
  curtime.tm_mday = buff[2] & 31; /* day of the month (1..31) */
40
  curtime.tm_mon = ((((buff[3] << 8) | buff[2]) >> 5) & 15) - 1; /* months since January (0, 11) */
41
  curtime.tm_year = (buff[3] >> 1) + 80; /* years since 1900 */
42
  curtime.tm_wday = 0; /* days since Sunday (0..6) - leave 0, mktime() will set it */
43
  curtime.tm_yday = 0; /* days since January 1 (0..365]) - leave 0, mktime() will set it */
44
  curtime.tm_isdst = -1; /* Daylight Saving Time flag. Positive if DST is in effect, zero if not and negative if no information is available */
45
  result = mktime(&curtime);
46
  if (result == (time_t)-1) return(0);
47
  return(result);
48
}
49
 
50
 
51
/* opens a zip file and provides the list of files in the archive.
52
   returns a pointer to a ziplist (linked list) with all records, or NULL on error.
53
   The ziplist is allocated automatically, and must be freed via zip_freelist. */
54
struct ziplist *zip_listfiles(FILE *fd) {
55
  struct ziplist *reslist = NULL;
56
  struct ziplist *newentry;
57
  unsigned long entrysig;
58
  unsigned short filenamelen, extrafieldlen, filecommentlen;
59
  unsigned long compfilelen;
60
  int centraldirectoryfound = 0;
61
  unsigned int ux;
62
  unsigned char hdrbuff[64];
63
 
64
  rewind(fd);  /* make sure the file cursor is at the very beginning of the file */
65
 
66
  for (;;) { /* read entry after entry */
67
    int x, eofflag;
68
    long longbuff;
69
    entrysig = 0;
70
    eofflag = 0;
71
    /* read the entry signature first */
72
    for (x = 0; x < 32; x += 8) {
73
      if ((longbuff = fgetc(fd)) == EOF) {
74
        eofflag = 1;
75
        break;
76
      }
77
      entrysig |= (longbuff << x);
78
    }
79
    if (eofflag != 0) break;
80
    /* printf("sig: 0x%08x\n", entrysig); */
81
    if (entrysig == 0x04034b50ul) { /* local file */
82
      unsigned int generalpurposeflags;
83
      /* read and parse the zip header */
84
      fread(hdrbuff, 1, 26, fd);
85
      /* read filename's length so I can allocate the proper amound of mem */
86
      filenamelen = hdrbuff[23];
87
      filenamelen <<= 8;
88
      filenamelen |= hdrbuff[22];
89
      /* create new entry and link it into the list */
90
      newentry = calloc(sizeof(struct ziplist) + filenamelen, 1);
91
      if (newentry == NULL) {
1965 mateusz.vi 92
        outputnl(svarlang_str(2,14)); /* Out of memory! */
219 mateuszvis 93
        zip_freelist(&reslist);
94
        break;
95
      }
96
      newentry->nextfile = reslist;
97
      newentry->flags = 0;
98
      reslist = newentry;
99
      /* read further areas of the header, and fill zip entry */
100
      generalpurposeflags = hdrbuff[3];  /* parse the general */
101
      generalpurposeflags <<= 8;         /* purpose flags and */
102
      generalpurposeflags |= hdrbuff[2]; /* save them for later */
103
      newentry->compmethod = hdrbuff[4] | (hdrbuff[5] << 8);
104
      newentry->timestamp = dostime2unix(&hdrbuff[6]);
105
      newentry->crc32 = 0;
106
      for (x = 13; x >= 10; x--) {
107
        newentry->crc32 <<= 8;
108
        newentry->crc32 |= hdrbuff[x];
109
      }
110
      newentry->compressedfilelen = 0;
111
      for (x = 17; x >= 14; x--) {
112
        newentry->compressedfilelen <<= 8;
113
        newentry->compressedfilelen |= hdrbuff[x];
114
      }
115
      newentry->filelen = 0;
116
      for (x = 21; x >= 18; x--) {
117
        newentry->filelen <<= 8;
118
        newentry->filelen |= hdrbuff[x];
119
      }
120
      extrafieldlen = hdrbuff[25];
121
      extrafieldlen <<= 8;
122
      extrafieldlen |= hdrbuff[24];
123
      /* printf("Filename len: %d / extrafield len: %d / compfile len: %ld / filelen: %ld\n", filenamelen, extrafieldlen, newentry->compressedfilelen, newentry->filelen); */
124
      /* check general purpose flags */
125
      if ((generalpurposeflags & 1) != 0) newentry->flags |= ZIP_FLAG_ENCRYPTED;
126
      /* parse the filename */
127
      for (ux = 0; ux < filenamelen; ux++) newentry->filename[ux] = fgetc(fd); /* store filename */
128
      if (newentry->filename[filenamelen - 1] == '/') newentry->flags |= ZIP_FLAG_ISADIR; /* if filename ends with / it's a dir. Note that ZIP forbids the usage of '\' in ZIP paths anyway */
129
      /* printf("Filename: %s (%ld bytes compressed)\n", newentry->filename, newentry->compressedfilelen); */
130
      newentry->dataoffset = ftell(fd) + extrafieldlen;
131
      /* skip rest of fields and data */
132
      fseek(fd, (extrafieldlen + newentry->compressedfilelen), SEEK_CUR);
133
    } else if (entrysig == 0x02014b50ul) { /* central directory */
134
      centraldirectoryfound = 1;
135
      /* parse header now */
136
      fread(hdrbuff, 1, 42, fd);
137
      filenamelen = hdrbuff[22] | (hdrbuff[23] << 8);
138
      extrafieldlen = hdrbuff[24] | (hdrbuff[25] << 8);
139
      filecommentlen = hdrbuff[26] | (hdrbuff[27] << 8);
140
      compfilelen = 0;
141
      for (x = 17; x >= 14; x--) {
142
        compfilelen <<= 8;
143
        compfilelen |= hdrbuff[x];
144
      }
145
      /* printf("central dir\n"); */
146
      /* skip rest of fields and data */
147
      fseek(fd, (filenamelen + extrafieldlen + compfilelen + filecommentlen), SEEK_CUR);
148
    } else if (entrysig == 0x08074b50ul) { /* Data descriptor header */
149
      /* no need to read the header we just have to skip it */
150
      fseek(fd, 12, SEEK_CUR); /* the header is 3x4 bytes (CRC + compressed len + uncompressed len) */
151
    } else { /* unknown sig */
152
      zip_freelist(&reslist);
153
      break;
154
    }
155
  }
156
  /* if we got no central directory record, the file is incomplete */
157
  if (centraldirectoryfound == 0) zip_freelist(&reslist);
158
  return(reslist);
159
}
160
 
161
 
162
 
163
/* unzips a file. zipfd points to the open zip file, curzipnode to the entry to extract, and fulldestfilename is the destination file where to unzip it. returns 0 on success, non-zero otherwise. */
1964 mateusz.vi 164
int zip_unzip(FILE *zipfd, struct ziplist *curzipnode, const char *fulldestfilename, unsigned char *buff15k) {
165
  #define buffsize (15 * 1024) /* bigger buffer is better, but pkg has to work on a 256K PC so let's not get too crazy with RAM */
219 mateuszvis 166
  FILE *filefd;
167
  unsigned long cksum;
168
  int extract_res;
169
  struct utimbuf filetimestamp;
170
 
171
  /* first of all, check we support the compression method */
172
  switch (curzipnode->compmethod) {
295 mateuszvis 173
    case ZIP_METH_STORE:
174
    case ZIP_METH_DEFLATE:
219 mateuszvis 175
      break;
176
    default: /* unsupported compression method, sorry */
177
      return(-1);
178
      break;
179
  }
180
 
181
  /* open the dst file */
182
  filefd = fopen(fulldestfilename, "wb");
183
  if (filefd == NULL) return(-2);  /* failed to open the dst file */
184
 
185
  if (fseek(zipfd, curzipnode->dataoffset, SEEK_SET) != 0) { /* set the reading position inside the zip file */
186
    fclose(filefd);
187
    unlink(fulldestfilename); /* remove the failed file once it is closed */
188
    return(-7);
189
  }
190
  extract_res = -255;
191
 
1975 mateusz.vi 192
  cksum = CRC32_INITVAL; /* init the crc32 */
219 mateuszvis 193
 
194
  if (curzipnode->compmethod == 0) { /* if the file is stored, copy it over */
195
    long i, toread;
196
    extract_res = 0;   /* assume we will succeed */
197
    for (i = 0; i < curzipnode->filelen;) {
198
      toread = curzipnode->filelen - i;
199
      if (toread > buffsize) toread = buffsize;
1964 mateusz.vi 200
      if (fread(buff15k, toread, 1, zipfd) != 1) extract_res = -3;   /* read a chunk of data */
201
      crc32_feed(&cksum, buff15k, toread); /* update the crc32 checksum */
202
      if (fwrite(buff15k, toread, 1, filefd) != 1) extract_res = -4; /* write data chunk to dst file */
219 mateuszvis 203
      i += toread;
204
    }
205
  } else if (curzipnode->compmethod == 8) {  /* if the file is deflated, inflate it */
1609 mateusz.vi 206
    /* use 1/3 of my buffer as input and 2/3 as output */
1964 mateusz.vi 207
    extract_res = inf(zipfd, filefd, buff15k, buffsize / 3, buff15k + (buffsize / 3), buffsize / 3 * 2, &cksum, curzipnode->compressedfilelen);
219 mateuszvis 208
  }
209
 
210
  /* clean up memory, close the dst file and terminates crc32 */
211
  fclose(filefd);   /* close the dst file */
212
  crc32_finish(&cksum);
213
 
214
  /* printf("extract_res=%d / cksum_expected=%08lX / cksum_obtained=%08lX\n", extract_res, curzipnode->crc32, cksum); */
215
  if (extract_res != 0) {  /* was the extraction process successful? */
216
    unlink(fulldestfilename); /* remove the failed file */
217
    return(extract_res);
218
  }
219
  if (cksum != curzipnode->crc32) { /* is the crc32 ok after extraction? */
220
    unlink(fulldestfilename); /* remove the failed file */
221
    return(-9);
222
  }
223
  /* Set the timestamp of the new file to what was set in the zip file */
224
  filetimestamp.actime  = curzipnode->timestamp;
225
  filetimestamp.modtime = curzipnode->timestamp;
226
  utime(fulldestfilename, &filetimestamp);
227
  return(0);
1609 mateusz.vi 228
#undef buffsize
219 mateuszvis 229
}
230
 
231
 
232
 
233
/* Call this to free a ziplist computed by zip_listfiles() */
234
void zip_freelist(struct ziplist **ziplist) {
235
  struct ziplist *zipentrytobefreed;
236
  while (*ziplist != NULL) { /* iterate through the linked list and free all nodes */
237
    zipentrytobefreed = *ziplist;
238
    *ziplist = zipentrytobefreed->nextfile;
239
    /* free the node entry */
240
    free(zipentrytobefreed);
241
  }
242
  *ziplist = NULL;
243
}