blob: 0e264a3437095efcd6a54ebe1083be7bf90db717 [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
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;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080076 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;
Doug Zongker287c71c2009-06-16 17:36:04 -070086
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080087
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]);
Doug Zongker287c71c2009-06-16 17:36:04 -0700107 entry->compressedSize = read_le_int(&p[0x14]);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800108 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;
Doug Zongker287c71c2009-06-16 17:36:04 -0700142
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800143 *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]);
Doug Zongker287c71c2009-06-16 17:36:04 -0700149
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800150 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 */
180int
181read_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 %d -- 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));
Elliott Hughes90764cf2009-09-03 11:52:31 -0700236 memset(entry, 0, sizeof(Zipentry));
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800237
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 }
Doug Zongker287c71c2009-06-16 17:36:04 -0700244
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800245 // add it to our list
246 entry->next = file->entries;
247 file->entries = entry;
248 }
249
250 return 0;
251bail:
252 return -1;
253}