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 16 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; 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; 86 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]); 107 entry->compressedSize = read_le_int(&p[0x14]); 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; 142 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]); 149 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) { 195 fprintf(stderr, "Length is %zd -- too small\n", bufsize); 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)); 236 memset(entry, 0, sizeof(Zipentry)); 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 } 244 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 } 254