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