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