• 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