blob: 98eef73e004190e31d9824997fd15cbf3ff9e220 [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
Mark Salyzynab886742014-05-01 07:27:02 -07006#include <utils/Compat.h>
7
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -08008enum {
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
Doug Zongker287c71c2009-06-16 17:36:04 -070018
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080019 // 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
Narayan Kamatha1ec2362016-09-13 15:31:56 +010065static const int kCompressionStored = 0x0;
66static const int kCompressionDeflate = 0x8;
67
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080068static int
69read_central_directory_entry(Zipfile* file, Zipentry* entry,
70 const unsigned char** buf, ssize_t* len)
71{
72 const unsigned char* p;
Narayan Kamatha1ec2362016-09-13 15:31:56 +010073 size_t remaining;
74 const unsigned char* bufLimit;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080075
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080076 unsigned short extraFieldLength;
77 unsigned short fileCommentLength;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080078 unsigned long localHeaderRelOffset;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080079 unsigned int dataOffset;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080080
81 p = *buf;
Narayan Kamatha1ec2362016-09-13 15:31:56 +010082 remaining = *len;
83 bufLimit = file->buf + file->bufsize;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080084
85 if (*len < ENTRY_LEN) {
86 fprintf(stderr, "cde entry not large enough\n");
87 return -1;
88 }
89
90 if (read_le_int(&p[0x00]) != ENTRY_SIGNATURE) {
91 fprintf(stderr, "Whoops: didn't find expected signature\n");
92 return -1;
93 }
94
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080095 entry->compressionMethod = read_le_short(&p[0x0a]);
Doug Zongker287c71c2009-06-16 17:36:04 -070096 entry->compressedSize = read_le_int(&p[0x14]);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080097 entry->uncompressedSize = read_le_int(&p[0x18]);
98 entry->fileNameLength = read_le_short(&p[0x1c]);
99 extraFieldLength = read_le_short(&p[0x1e]);
100 fileCommentLength = read_le_short(&p[0x20]);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800101 localHeaderRelOffset = read_le_int(&p[0x2a]);
102
103 p += ENTRY_LEN;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100104 remaining -= ENTRY_LEN;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800105
106 // filename
107 if (entry->fileNameLength != 0) {
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100108 if (entry->fileNameLength > remaining) {
109 fprintf(stderr, "cde entry not large enough for file name.\n");
110 return 1;
111 }
112
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800113 entry->fileName = p;
114 } else {
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100115 fprintf(stderr, "cde entry does not contain a file name.\n");
116 return 1;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800117 }
118 p += entry->fileNameLength;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100119 remaining -= entry->fileNameLength;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800120
121 // extra field
Narayan Kamath61f27552016-09-21 09:48:28 +0100122 if (extraFieldLength > remaining) {
123 fprintf(stderr, "cde entry not large enough for extra field.\n");
124 return 1;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800125 }
126 p += extraFieldLength;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100127 remaining -= extraFieldLength;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800128
129 // comment, if any
Narayan Kamath61f27552016-09-21 09:48:28 +0100130 if (fileCommentLength > remaining) {
131 fprintf(stderr, "cde entry not large enough for file comment.\n");
132 return 1;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800133 }
Narayan Kamath61f27552016-09-21 09:48:28 +0100134
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800135 p += fileCommentLength;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100136 remaining -= fileCommentLength;
Doug Zongker287c71c2009-06-16 17:36:04 -0700137
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800138 *buf = p;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100139 *len = remaining;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800140
141 // the size of the extraField in the central dir is how much data there is,
142 // but the one in the local file header also contains some padding.
143 p = file->buf + localHeaderRelOffset;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100144 if (p >= bufLimit) {
145 fprintf(stderr, "Invalid local header offset for entry.\n");
146 return 1;
147 }
148
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800149 extraFieldLength = read_le_short(&p[0x1c]);
Doug Zongker287c71c2009-06-16 17:36:04 -0700150
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800151 dataOffset = localHeaderRelOffset + LFH_SIZE
152 + entry->fileNameLength + extraFieldLength;
153 entry->data = file->buf + dataOffset;
Narayan Kamatha1ec2362016-09-13 15:31:56 +0100154
155 // Sanity check: make sure that the start of the entry data is within
156 // our allocated buffer.
157 if ((entry->data < file->buf) || (entry->data >= bufLimit)) {
158 fprintf(stderr, "Invalid data offset for entry.\n");
159 return 1;
160 }
161
162 // Sanity check: make sure that the end of the entry data is within
163 // our allocated buffer. We need to look at the uncompressedSize for
164 // stored entries and the compressed size for deflated entries.
165 if ((entry->compressionMethod == kCompressionStored) &&
166 (entry->uncompressedSize > (unsigned int) (bufLimit - entry->data))) {
167 fprintf(stderr, "Invalid uncompressed size for stored entry.\n");
168 return 1;
169 }
170 if ((entry->compressionMethod == kCompressionDeflate) &&
171 (entry->compressedSize > (unsigned int) (bufLimit - entry->data))) {
172 fprintf(stderr, "Invalid uncompressed size for deflated entry.\n");
173 return 1;
174 }
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800175#if 0
176 printf("file->buf=%p entry->data=%p dataOffset=%x localHeaderRelOffset=%d "
177 "entry->fileNameLength=%d extraFieldLength=%d\n",
178 file->buf, entry->data, dataOffset, localHeaderRelOffset,
179 entry->fileNameLength, extraFieldLength);
180#endif
181 return 0;
182}
183
184/*
185 * Find the central directory and read the contents.
186 *
187 * The fun thing about ZIP archives is that they may or may not be
188 * readable from start to end. In some cases, notably for archives
189 * that were written to stdout, the only length information is in the
190 * central directory at the end of the file.
191 *
192 * Of course, the central directory can be followed by a variable-length
193 * comment field, so we have to scan through it backwards. The comment
194 * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
195 * itself, plus apparently sometimes people throw random junk on the end
196 * just for the fun of it.
197 *
198 * This is all a little wobbly. If the wrong value ends up in the EOCD
199 * area, we're hosed. This appears to be the way that everbody handles
200 * it though, so we're in pretty good company if this fails.
201 */
202int
203read_central_dir(Zipfile *file)
204{
205 int err;
206
207 const unsigned char* buf = file->buf;
Mark Salyzynab886742014-05-01 07:27:02 -0700208 ZD_TYPE bufsize = file->bufsize;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800209 const unsigned char* eocd;
210 const unsigned char* p;
211 const unsigned char* start;
212 ssize_t len;
213 int i;
214
215 // too small to be a ZIP archive?
216 if (bufsize < EOCD_LEN) {
Mark Salyzynab886742014-05-01 07:27:02 -0700217 fprintf(stderr, "Length is " ZD " -- too small\n", bufsize);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800218 goto bail;
219 }
220
221 // find the end-of-central-dir magic
222 if (bufsize > MAX_EOCD_SEARCH) {
223 start = buf + bufsize - MAX_EOCD_SEARCH;
224 } else {
225 start = buf;
226 }
227 p = buf + bufsize - 4;
228 while (p >= start) {
229 if (*p == 0x50 && read_le_int(p) == CD_SIGNATURE) {
230 eocd = p;
231 break;
232 }
233 p--;
234 }
235 if (p < start) {
236 fprintf(stderr, "EOCD not found, not Zip\n");
237 goto bail;
238 }
239
240 // extract eocd values
241 err = read_central_dir_values(file, eocd, (buf+bufsize)-eocd);
242 if (err != 0) {
243 goto bail;
244 }
245
246 if (file->disknum != 0
247 || file->diskWithCentralDir != 0
248 || file->entryCount != file->totalEntryCount) {
249 fprintf(stderr, "Archive spanning not supported\n");
250 goto bail;
251 }
252
253 // Loop through and read the central dir entries.
254 p = buf + file->centralDirOffest;
255 len = (buf+bufsize)-p;
256 for (i=0; i < file->totalEntryCount; i++) {
257 Zipentry* entry = malloc(sizeof(Zipentry));
Elliott Hughes90764cf2009-09-03 11:52:31 -0700258 memset(entry, 0, sizeof(Zipentry));
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800259
260 err = read_central_directory_entry(file, entry, &p, &len);
261 if (err != 0) {
262 fprintf(stderr, "read_central_directory_entry failed\n");
263 free(entry);
264 goto bail;
265 }
Doug Zongker287c71c2009-06-16 17:36:04 -0700266
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800267 // add it to our list
268 entry->next = file->entries;
269 file->entries = entry;
270 }
271
272 return 0;
273bail:
274 return -1;
275}