The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 1 | #include "private.h"
|
| 2 | #include <stdio.h>
|
| 3 | #include <string.h>
|
| 4 | #include <stdlib.h>
|
| 5 |
|
| 6 | enum {
|
| 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 Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 16 |
|
The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 17 | // local file header
|
| 18 | LFH_SIZE = 30,
|
| 19 | };
|
| 20 |
|
| 21 | unsigned int
|
| 22 | read_le_int(const unsigned char* buf)
|
| 23 | {
|
| 24 | return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
|
| 25 | }
|
| 26 |
|
| 27 | unsigned int
|
| 28 | read_le_short(const unsigned char* buf)
|
| 29 | {
|
| 30 | return buf[0] | (buf[1] << 8);
|
| 31 | }
|
| 32 |
|
| 33 | static int
|
| 34 | read_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 |
|
| 63 | static int
|
| 64 | read_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 Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 76 | 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 Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 86 |
|
The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 87 |
|
| 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 Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 107 | entry->compressedSize = read_le_int(&p[0x14]);
|
The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 108 | 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 Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 142 |
|
The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 143 | *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 Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 149 |
|
The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 150 | 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 | */
|
| 180 | int
|
| 181 | read_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) {
|
Edwin Vane | 8d9aa37 | 2012-07-26 15:44:23 -0400 | [diff] [blame] | 195 | fprintf(stderr, "Length is %zd -- too small\n", bufsize);
|
The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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 Hughes | 90764cf | 2009-09-03 11:52:31 -0700 | [diff] [blame] | 236 | memset(entry, 0, sizeof(Zipentry));
|
The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 237 |
|
| 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 Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 244 |
|
The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 245 | // add it to our list
|
| 246 | entry->next = file->entries;
|
| 247 | file->entries = entry;
|
| 248 | }
|
| 249 |
|
| 250 | return 0;
|
| 251 | bail:
|
| 252 | return -1;
|
| 253 | }
|