1 #include "private.h"
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdlib.h>
5
6 #include <utils/Compat.h>
7
8 enum {
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
23 unsigned int
read_le_int(const unsigned char * buf)24 read_le_int(const unsigned char* buf)
25 {
26 return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
27 }
28
29 unsigned int
read_le_short(const unsigned char * buf)30 read_le_short(const unsigned char* buf)
31 {
32 return buf[0] | (buf[1] << 8);
33 }
34
35 static int
read_central_dir_values(Zipfile * file,const unsigned char * buf,int len)36 read_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
65 static int
read_central_directory_entry(Zipfile * file,Zipentry * entry,const unsigned char ** buf,ssize_t * len)66 read_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 */
149 int
read_central_dir(Zipfile * file)150 read_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;
220 bail:
221 return -1;
222 }
223