• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  #include "private.h"
2  #include <stdio.h>
3  #include <string.h>
4  #include <stdlib.h>
5  
6  enum {
7      // finding the directory
8      CD_SIGNATURE = 0x06054b50,
9      EOCD_LEN     = 22,        // EndOfCentralDir len, excl. comment
10      MAX_COMMENT_LEN = 65535,
11      MAX_EOCD_SEARCH = MAX_COMMENT_LEN + EOCD_LEN,
12  
13      // central directory entries
14      ENTRY_SIGNATURE = 0x02014b50,
15      ENTRY_LEN = 46,          // CentralDirEnt len, excl. var fields
16  
17      // local file header
18      LFH_SIZE = 30,
19  };
20  
21  unsigned int
read_le_int(const unsigned char * buf)22  read_le_int(const unsigned char* buf)
23  {
24      return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
25  }
26  
27  unsigned int
read_le_short(const unsigned char * buf)28  read_le_short(const unsigned char* buf)
29  {
30      return buf[0] | (buf[1] << 8);
31  }
32  
33  static int
read_central_dir_values(Zipfile * file,const unsigned char * buf,int len)34  read_central_dir_values(Zipfile* file, const unsigned char* buf, int len)
35  {
36      if (len < EOCD_LEN) {
37          // looks like ZIP file got truncated
38          fprintf(stderr, " Zip EOCD: expected >= %d bytes, found %d\n",
39                  EOCD_LEN, len);
40          return -1;
41      }
42  
43      file->disknum = read_le_short(&buf[0x04]);
44      file->diskWithCentralDir = read_le_short(&buf[0x06]);
45      file->entryCount = read_le_short(&buf[0x08]);
46      file->totalEntryCount = read_le_short(&buf[0x0a]);
47      file->centralDirSize = read_le_int(&buf[0x0c]);
48      file->centralDirOffest = read_le_int(&buf[0x10]);
49      file->commentLen = read_le_short(&buf[0x14]);
50  
51      if (file->commentLen > 0) {
52          if (EOCD_LEN + file->commentLen > len) {
53              fprintf(stderr, "EOCD(%d) + comment(%d) exceeds len (%d)\n",
54                      EOCD_LEN, file->commentLen, len);
55              return -1;
56          }
57          file->comment = buf + EOCD_LEN;
58      }
59  
60      return 0;
61  }
62  
63  static int
read_central_directory_entry(Zipfile * file,Zipentry * entry,const unsigned char ** buf,ssize_t * len)64  read_central_directory_entry(Zipfile* file, Zipentry* entry,
65                  const unsigned char** buf, ssize_t* len)
66  {
67      const unsigned char* p;
68  
69      unsigned short  versionMadeBy;
70      unsigned short  versionToExtract;
71      unsigned short  gpBitFlag;
72      unsigned short  compressionMethod;
73      unsigned short  lastModFileTime;
74      unsigned short  lastModFileDate;
75      unsigned long   crc32;
76      unsigned short  extraFieldLength;
77      unsigned short  fileCommentLength;
78      unsigned short  diskNumberStart;
79      unsigned short  internalAttrs;
80      unsigned long   externalAttrs;
81      unsigned long   localHeaderRelOffset;
82      const unsigned char*  extraField;
83      const unsigned char*  fileComment;
84      unsigned int dataOffset;
85      unsigned short lfhExtraFieldSize;
86  
87  
88      p = *buf;
89  
90      if (*len < ENTRY_LEN) {
91          fprintf(stderr, "cde entry not large enough\n");
92          return -1;
93      }
94  
95      if (read_le_int(&p[0x00]) != ENTRY_SIGNATURE) {
96          fprintf(stderr, "Whoops: didn't find expected signature\n");
97          return -1;
98      }
99  
100      versionMadeBy = read_le_short(&p[0x04]);
101      versionToExtract = read_le_short(&p[0x06]);
102      gpBitFlag = read_le_short(&p[0x08]);
103      entry->compressionMethod = read_le_short(&p[0x0a]);
104      lastModFileTime = read_le_short(&p[0x0c]);
105      lastModFileDate = read_le_short(&p[0x0e]);
106      crc32 = read_le_int(&p[0x10]);
107      entry->compressedSize = read_le_int(&p[0x14]);
108      entry->uncompressedSize = read_le_int(&p[0x18]);
109      entry->fileNameLength = read_le_short(&p[0x1c]);
110      extraFieldLength = read_le_short(&p[0x1e]);
111      fileCommentLength = read_le_short(&p[0x20]);
112      diskNumberStart = read_le_short(&p[0x22]);
113      internalAttrs = read_le_short(&p[0x24]);
114      externalAttrs = read_le_int(&p[0x26]);
115      localHeaderRelOffset = read_le_int(&p[0x2a]);
116  
117      p += ENTRY_LEN;
118  
119      // filename
120      if (entry->fileNameLength != 0) {
121          entry->fileName = p;
122      } else {
123          entry->fileName = NULL;
124      }
125      p += entry->fileNameLength;
126  
127      // extra field
128      if (extraFieldLength != 0) {
129          extraField = p;
130      } else {
131          extraField = NULL;
132      }
133      p += extraFieldLength;
134  
135      // comment, if any
136      if (fileCommentLength != 0) {
137          fileComment = p;
138      } else {
139          fileComment = NULL;
140      }
141      p += fileCommentLength;
142  
143      *buf = p;
144  
145      // the size of the extraField in the central dir is how much data there is,
146      // but the one in the local file header also contains some padding.
147      p = file->buf + localHeaderRelOffset;
148      extraFieldLength = read_le_short(&p[0x1c]);
149  
150      dataOffset = localHeaderRelOffset + LFH_SIZE
151          + entry->fileNameLength + extraFieldLength;
152      entry->data = file->buf + dataOffset;
153  #if 0
154      printf("file->buf=%p entry->data=%p dataOffset=%x localHeaderRelOffset=%d "
155             "entry->fileNameLength=%d extraFieldLength=%d\n",
156             file->buf, entry->data, dataOffset, localHeaderRelOffset,
157             entry->fileNameLength, extraFieldLength);
158  #endif
159      return 0;
160  }
161  
162  /*
163   * Find the central directory and read the contents.
164   *
165   * The fun thing about ZIP archives is that they may or may not be
166   * readable from start to end.  In some cases, notably for archives
167   * that were written to stdout, the only length information is in the
168   * central directory at the end of the file.
169   *
170   * Of course, the central directory can be followed by a variable-length
171   * comment field, so we have to scan through it backwards.  The comment
172   * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
173   * itself, plus apparently sometimes people throw random junk on the end
174   * just for the fun of it.
175   *
176   * This is all a little wobbly.  If the wrong value ends up in the EOCD
177   * area, we're hosed.  This appears to be the way that everbody handles
178   * it though, so we're in pretty good company if this fails.
179   */
180  int
read_central_dir(Zipfile * file)181  read_central_dir(Zipfile *file)
182  {
183      int err;
184  
185      const unsigned char* buf = file->buf;
186      ssize_t bufsize = file->bufsize;
187      const unsigned char* eocd;
188      const unsigned char* p;
189      const unsigned char* start;
190      ssize_t len;
191      int i;
192  
193      // too small to be a ZIP archive?
194      if (bufsize < EOCD_LEN) {
195          fprintf(stderr, "Length is %zd -- too small\n", bufsize);
196          goto bail;
197      }
198  
199      // find the end-of-central-dir magic
200      if (bufsize > MAX_EOCD_SEARCH) {
201          start = buf + bufsize - MAX_EOCD_SEARCH;
202      } else {
203          start = buf;
204      }
205      p = buf + bufsize - 4;
206      while (p >= start) {
207          if (*p == 0x50 && read_le_int(p) == CD_SIGNATURE) {
208              eocd = p;
209              break;
210          }
211          p--;
212      }
213      if (p < start) {
214          fprintf(stderr, "EOCD not found, not Zip\n");
215          goto bail;
216      }
217  
218      // extract eocd values
219      err = read_central_dir_values(file, eocd, (buf+bufsize)-eocd);
220      if (err != 0) {
221          goto bail;
222      }
223  
224      if (file->disknum != 0
225            || file->diskWithCentralDir != 0
226            || file->entryCount != file->totalEntryCount) {
227          fprintf(stderr, "Archive spanning not supported\n");
228          goto bail;
229      }
230  
231      // Loop through and read the central dir entries.
232      p = buf + file->centralDirOffest;
233      len = (buf+bufsize)-p;
234      for (i=0; i < file->totalEntryCount; i++) {
235          Zipentry* entry = malloc(sizeof(Zipentry));
236          memset(entry, 0, sizeof(Zipentry));
237  
238          err = read_central_directory_entry(file, entry, &p, &len);
239          if (err != 0) {
240              fprintf(stderr, "read_central_directory_entry failed\n");
241              free(entry);
242              goto bail;
243          }
244  
245          // add it to our list
246          entry->next = file->entries;
247          file->entries = entry;
248      }
249  
250      return 0;
251  bail:
252      return -1;
253  }
254