| 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 |  | 
| Mark Salyzyn | ab88674 | 2014-05-01 07:27:02 -0700 | [diff] [blame] | 6 | #include <utils/Compat.h> | 
|  | 7 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 8 | enum { | 
|  | 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 Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 18 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 19 | // local file header | 
|  | 20 | LFH_SIZE = 30, | 
|  | 21 | }; | 
|  | 22 |  | 
|  | 23 | unsigned int | 
|  | 24 | read_le_int(const unsigned char* buf) | 
|  | 25 | { | 
|  | 26 | return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); | 
|  | 27 | } | 
|  | 28 |  | 
|  | 29 | unsigned int | 
|  | 30 | read_le_short(const unsigned char* buf) | 
|  | 31 | { | 
|  | 32 | return buf[0] | (buf[1] << 8); | 
|  | 33 | } | 
|  | 34 |  | 
|  | 35 | static int | 
|  | 36 | read_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 |  | 
|  | 65 | static int | 
|  | 66 | read_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 Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 71 | unsigned short  extraFieldLength; | 
|  | 72 | unsigned short  fileCommentLength; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 73 | unsigned long   localHeaderRelOffset; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 74 | unsigned int dataOffset; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 75 |  | 
|  | 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 Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 88 | entry->compressionMethod = read_le_short(&p[0x0a]); | 
| Doug Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 89 | entry->compressedSize = read_le_int(&p[0x14]); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 90 | 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 Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 94 | 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 Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 107 | p += extraFieldLength; | 
|  | 108 |  | 
|  | 109 | // comment, if any | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 110 | p += fileCommentLength; | 
| Doug Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 111 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 112 | *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 Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 118 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 119 | 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 | */ | 
|  | 149 | int | 
|  | 150 | read_central_dir(Zipfile *file) | 
|  | 151 | { | 
|  | 152 | int err; | 
|  | 153 |  | 
|  | 154 | const unsigned char* buf = file->buf; | 
| Mark Salyzyn | ab88674 | 2014-05-01 07:27:02 -0700 | [diff] [blame] | 155 | ZD_TYPE bufsize = file->bufsize; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 156 | 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 Salyzyn | ab88674 | 2014-05-01 07:27:02 -0700 | [diff] [blame] | 164 | fprintf(stderr, "Length is " ZD " -- too small\n", bufsize); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 165 | 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 Hughes | 90764cf | 2009-09-03 11:52:31 -0700 | [diff] [blame] | 205 | memset(entry, 0, sizeof(Zipentry)); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 206 |  | 
|  | 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 Zongker | 287c71c | 2009-06-16 17:36:04 -0700 | [diff] [blame] | 213 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 214 | // add it to our list | 
|  | 215 | entry->next = file->entries; | 
|  | 216 | file->entries = entry; | 
|  | 217 | } | 
|  | 218 |  | 
|  | 219 | return 0; | 
|  | 220 | bail: | 
|  | 221 | return -1; | 
|  | 222 | } |