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 | }
|