blob: 2ce73e8ee252969482119c4d28119daf6d6d5c4b [file] [log] [blame]
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -08001#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
Doug Zongker287c71c2009-06-16 17:36:04 -070016
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080017 // 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
Narayan Kamatha1ec2362016-09-13 15:31:56 +010063static const int kCompressionStored = 0x0;
64static const int kCompressionDeflate = 0x8;
65
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080066static int
67read_central_directory_entry(Zipfile* file, Zipentry* entry,
68 const unsigned char** buf, ssize_t* len)
69{
70 const unsigned char* p;
Narayan Kamatha1ec2362016-09-13 15:31:56 +010071 size_t remaining;
72 const unsigned char* bufLimit;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080073
74 unsigned short versionMadeBy;
75 unsigned short versionToExtract;
76 unsigned short gpBitFlag;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080077 unsigned short lastModFileTime;
78 unsigned short lastModFileDate;
79 unsigned long crc32;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080080 unsigned short extraFieldLength;
81 unsigned short fileCommentLength;
82 unsigned short diskNumberStart;
83 unsigned short internalAttrs;
84 unsigned long externalAttrs;
85 unsigned long localHeaderRelOffset;
86 const unsigned char* extraField;
87 const unsigned char* fileComment;
88 unsigned int dataOffset;
89 unsigned short lfhExtraFieldSize;
Doug Zongker287c71c2009-06-16 17:36:04 -070090
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080091 p = *buf;
Narayan Kamatha1ec2362016-09-13 15:31:56 +010092 remaining = *len;
93 bufLimit = file->buf + file->bufsize;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080094
95 if (*len < ENTRY_LEN) {
96 fprintf(stderr, "cde entry not large enough\n");
97 return -1;
98 }
99
100 if (read_le_int(&p[0x00]) != ENTRY_SIGNATURE) {
101 fprintf(stderr, "Whoops: didn't find expected signature\n");
102 return -1;
103 }
104
105 versionMadeBy = read_le_short(&p[0x04]);
106 versionToExtract = read_le_short(&p[0x06]);
107 gpBitFlag = read_le_short(&p[0x08]);
108 entry->compressionMethod = read_le_short(&p[0x0a]);
109 lastModFileTime = read_le_short(&p[0x0c]);
110 lastModFileDate = read_le_short(&p[0x0e]);
111 crc32 = read_le_int(&p[0x10]);
Doug Zongker287c71c2009-06-16 17:36:04 -0700112 entry->compressedSize = read_le_int(&p[0x14]);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800113 entry->uncompressedSize = read_le_int(&p[0x18]);
114 entry->fileNameLength = read_le_short(&p[0x1c]);
115 extraFieldLength = read_le_short(&p[0x1e]);
116 fileCommentLength = read_le_short(&p[0x20]);
117 diskNumberStart = read_le_short(&p[0x22]);
118 internalAttrs = read_le_short(&p[0x24]);
119 externalAttrs = read_le_int(&p[0x26]);
120 localHeaderRelOffset = read_le_int(&p[0x2a]);
121
122 p += ENTRY_LEN;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100123 remaining -= ENTRY_LEN;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800124
125 // filename
126 if (entry->fileNameLength != 0) {
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100127 if (entry->fileNameLength > remaining) {
128 fprintf(stderr, "cde entry not large enough for file name.\n");
129 return 1;
130 }
131
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800132 entry->fileName = p;
133 } else {
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100134 fprintf(stderr, "cde entry does not contain a file name.\n");
135 return 1;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800136 }
137 p += entry->fileNameLength;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100138 remaining -= entry->fileNameLength;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800139
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100140 // extra field, if any
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800141 if (extraFieldLength != 0) {
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100142 if (extraFieldLength > remaining) {
143 fprintf(stderr, "cde entry not large enough for extra field.\n");
144 return 1;
145 }
146
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800147 extraField = p;
148 } else {
149 extraField = NULL;
150 }
151 p += extraFieldLength;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100152 remaining -= extraFieldLength;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800153
154 // comment, if any
155 if (fileCommentLength != 0) {
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100156 if (fileCommentLength > remaining) {
157 fprintf(stderr, "cde entry not large enough for file comment.\n");
158 return 1;
159 }
160
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800161 fileComment = p;
162 } else {
163 fileComment = NULL;
164 }
165 p += fileCommentLength;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100166 remaining -= fileCommentLength;
Doug Zongker287c71c2009-06-16 17:36:04 -0700167
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800168 *buf = p;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100169 *len = remaining;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800170
171 // the size of the extraField in the central dir is how much data there is,
172 // but the one in the local file header also contains some padding.
173 p = file->buf + localHeaderRelOffset;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100174 if (p >= bufLimit) {
175 fprintf(stderr, "Invalid local header offset for entry.\n");
176 return 1;
177 }
178
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800179 extraFieldLength = read_le_short(&p[0x1c]);
Doug Zongker287c71c2009-06-16 17:36:04 -0700180
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800181 dataOffset = localHeaderRelOffset + LFH_SIZE
182 + entry->fileNameLength + extraFieldLength;
183 entry->data = file->buf + dataOffset;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100184
185 // Sanity check: make sure that the start of the entry data is within
186 // our allocated buffer.
187 if ((entry->data < file->buf) || (entry->data >= bufLimit)) {
188 fprintf(stderr, "Invalid data offset for entry.\n");
189 return 1;
190 }
191
192 // Sanity check: make sure that the end of the entry data is within
193 // our allocated buffer. We need to look at the uncompressedSize for
194 // stored entries and the compressed size for deflated entries.
195 if ((entry->compressionMethod == kCompressionStored) &&
196 (entry->uncompressedSize > (unsigned int) (bufLimit - entry->data))) {
197 fprintf(stderr, "Invalid uncompressed size for stored entry.\n");
198 return 1;
199 }
200 if ((entry->compressionMethod == kCompressionDeflate) &&
201 (entry->compressedSize > (unsigned int) (bufLimit - entry->data))) {
202 fprintf(stderr, "Invalid uncompressed size for deflated entry.\n");
203 return 1;
204 }
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800205#if 0
206 printf("file->buf=%p entry->data=%p dataOffset=%x localHeaderRelOffset=%d "
207 "entry->fileNameLength=%d extraFieldLength=%d\n",
208 file->buf, entry->data, dataOffset, localHeaderRelOffset,
209 entry->fileNameLength, extraFieldLength);
210#endif
211 return 0;
212}
213
214/*
215 * Find the central directory and read the contents.
216 *
217 * The fun thing about ZIP archives is that they may or may not be
218 * readable from start to end. In some cases, notably for archives
219 * that were written to stdout, the only length information is in the
220 * central directory at the end of the file.
221 *
222 * Of course, the central directory can be followed by a variable-length
223 * comment field, so we have to scan through it backwards. The comment
224 * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
225 * itself, plus apparently sometimes people throw random junk on the end
226 * just for the fun of it.
227 *
228 * This is all a little wobbly. If the wrong value ends up in the EOCD
229 * area, we're hosed. This appears to be the way that everbody handles
230 * it though, so we're in pretty good company if this fails.
231 */
232int
233read_central_dir(Zipfile *file)
234{
235 int err;
236
237 const unsigned char* buf = file->buf;
238 ssize_t bufsize = file->bufsize;
239 const unsigned char* eocd;
240 const unsigned char* p;
241 const unsigned char* start;
242 ssize_t len;
243 int i;
244
245 // too small to be a ZIP archive?
246 if (bufsize < EOCD_LEN) {
Edwin Vane8d9aa372012-07-26 15:44:23 -0400247 fprintf(stderr, "Length is %zd -- too small\n", bufsize);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800248 goto bail;
249 }
250
251 // find the end-of-central-dir magic
252 if (bufsize > MAX_EOCD_SEARCH) {
253 start = buf + bufsize - MAX_EOCD_SEARCH;
254 } else {
255 start = buf;
256 }
257 p = buf + bufsize - 4;
258 while (p >= start) {
259 if (*p == 0x50 && read_le_int(p) == CD_SIGNATURE) {
260 eocd = p;
261 break;
262 }
263 p--;
264 }
265 if (p < start) {
266 fprintf(stderr, "EOCD not found, not Zip\n");
267 goto bail;
268 }
269
270 // extract eocd values
271 err = read_central_dir_values(file, eocd, (buf+bufsize)-eocd);
272 if (err != 0) {
273 goto bail;
274 }
275
276 if (file->disknum != 0
277 || file->diskWithCentralDir != 0
278 || file->entryCount != file->totalEntryCount) {
279 fprintf(stderr, "Archive spanning not supported\n");
280 goto bail;
281 }
282
283 // Loop through and read the central dir entries.
284 p = buf + file->centralDirOffest;
285 len = (buf+bufsize)-p;
286 for (i=0; i < file->totalEntryCount; i++) {
287 Zipentry* entry = malloc(sizeof(Zipentry));
Elliott Hughes90764cf2009-09-03 11:52:31 -0700288 memset(entry, 0, sizeof(Zipentry));
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800289
290 err = read_central_directory_entry(file, entry, &p, &len);
291 if (err != 0) {
292 fprintf(stderr, "read_central_directory_entry failed\n");
293 free(entry);
294 goto bail;
295 }
Doug Zongker287c71c2009-06-16 17:36:04 -0700296
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800297 // add it to our list
298 entry->next = file->entries;
299 file->entries = entry;
300 }
301
302 return 0;
303bail:
304 return -1;
305}