blob: 69cf47a1908ee94b5a835c83b3073daaf28c1d7d [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
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
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080071 unsigned short extraFieldLength;
72 unsigned short fileCommentLength;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080073 unsigned long localHeaderRelOffset;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080074 unsigned int dataOffset;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080075
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
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080088 entry->compressionMethod = read_le_short(&p[0x0a]);
Doug Zongker287c71c2009-06-16 17:36:04 -070089 entry->compressedSize = read_le_int(&p[0x14]);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080090 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]);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080094 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
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800107 p += extraFieldLength;
108
109 // comment, if any
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800110 p += fileCommentLength;
Doug Zongker287c71c2009-06-16 17:36:04 -0700111
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800112 *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]);
Doug Zongker287c71c2009-06-16 17:36:04 -0700118
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800119 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;
Mark Salyzynab886742014-05-01 07:27:02 -0700155 ZD_TYPE bufsize = file->bufsize;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800156 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) {
Mark Salyzynab886742014-05-01 07:27:02 -0700164 fprintf(stderr, "Length is " ZD " -- too small\n", bufsize);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800165 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));
Elliott Hughes90764cf2009-09-03 11:52:31 -0700205 memset(entry, 0, sizeof(Zipentry));
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800206
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 }
Doug Zongker287c71c2009-06-16 17:36:04 -0700213
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800214 // add it to our list
215 entry->next = file->entries;
216 file->entries = entry;
217 }
218
219 return 0;
220bail:
221 return -1;
222}