centraldir.c revision dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0
1#include "private.h" 2#include <stdio.h> 3#include <string.h> 4#include <stdlib.h> 5 6enum { 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 21unsigned int 22read_le_int(const unsigned char* buf) 23{ 24 return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); 25} 26 27unsigned int 28read_le_short(const unsigned char* buf) 29{ 30 return buf[0] | (buf[1] << 8); 31} 32 33static int 34read_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 63static int 64read_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 long compressedSize; 77 unsigned long uncompressedSize; 78 unsigned short extraFieldLength; 79 unsigned short fileCommentLength; 80 unsigned short diskNumberStart; 81 unsigned short internalAttrs; 82 unsigned long externalAttrs; 83 unsigned long localHeaderRelOffset; 84 const unsigned char* extraField; 85 const unsigned char* fileComment; 86 unsigned int dataOffset; 87 unsigned short lfhExtraFieldSize; 88 89 90 p = *buf; 91 92 if (*len < ENTRY_LEN) { 93 fprintf(stderr, "cde entry not large enough\n"); 94 return -1; 95 } 96 97 if (read_le_int(&p[0x00]) != ENTRY_SIGNATURE) { 98 fprintf(stderr, "Whoops: didn't find expected signature\n"); 99 return -1; 100 } 101 102 versionMadeBy = read_le_short(&p[0x04]); 103 versionToExtract = read_le_short(&p[0x06]); 104 gpBitFlag = read_le_short(&p[0x08]); 105 entry->compressionMethod = read_le_short(&p[0x0a]); 106 lastModFileTime = read_le_short(&p[0x0c]); 107 lastModFileDate = read_le_short(&p[0x0e]); 108 crc32 = read_le_int(&p[0x10]); 109 compressedSize = read_le_int(&p[0x14]); 110 entry->uncompressedSize = read_le_int(&p[0x18]); 111 entry->fileNameLength = read_le_short(&p[0x1c]); 112 extraFieldLength = read_le_short(&p[0x1e]); 113 fileCommentLength = read_le_short(&p[0x20]); 114 diskNumberStart = read_le_short(&p[0x22]); 115 internalAttrs = read_le_short(&p[0x24]); 116 externalAttrs = read_le_int(&p[0x26]); 117 localHeaderRelOffset = read_le_int(&p[0x2a]); 118 119 p += ENTRY_LEN; 120 121 // filename 122 if (entry->fileNameLength != 0) { 123 entry->fileName = p; 124 } else { 125 entry->fileName = NULL; 126 } 127 p += entry->fileNameLength; 128 129 // extra field 130 if (extraFieldLength != 0) { 131 extraField = p; 132 } else { 133 extraField = NULL; 134 } 135 p += extraFieldLength; 136 137 // comment, if any 138 if (fileCommentLength != 0) { 139 fileComment = p; 140 } else { 141 fileComment = NULL; 142 } 143 p += fileCommentLength; 144 145 *buf = p; 146 147 // the size of the extraField in the central dir is how much data there is, 148 // but the one in the local file header also contains some padding. 149 p = file->buf + localHeaderRelOffset; 150 extraFieldLength = read_le_short(&p[0x1c]); 151 152 dataOffset = localHeaderRelOffset + LFH_SIZE 153 + entry->fileNameLength + extraFieldLength; 154 entry->data = file->buf + dataOffset; 155#if 0 156 printf("file->buf=%p entry->data=%p dataOffset=%x localHeaderRelOffset=%d " 157 "entry->fileNameLength=%d extraFieldLength=%d\n", 158 file->buf, entry->data, dataOffset, localHeaderRelOffset, 159 entry->fileNameLength, extraFieldLength); 160#endif 161 return 0; 162} 163 164/* 165 * Find the central directory and read the contents. 166 * 167 * The fun thing about ZIP archives is that they may or may not be 168 * readable from start to end. In some cases, notably for archives 169 * that were written to stdout, the only length information is in the 170 * central directory at the end of the file. 171 * 172 * Of course, the central directory can be followed by a variable-length 173 * comment field, so we have to scan through it backwards. The comment 174 * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff 175 * itself, plus apparently sometimes people throw random junk on the end 176 * just for the fun of it. 177 * 178 * This is all a little wobbly. If the wrong value ends up in the EOCD 179 * area, we're hosed. This appears to be the way that everbody handles 180 * it though, so we're in pretty good company if this fails. 181 */ 182int 183read_central_dir(Zipfile *file) 184{ 185 int err; 186 187 const unsigned char* buf = file->buf; 188 ssize_t bufsize = file->bufsize; 189 const unsigned char* eocd; 190 const unsigned char* p; 191 const unsigned char* start; 192 ssize_t len; 193 int i; 194 195 // too small to be a ZIP archive? 196 if (bufsize < EOCD_LEN) { 197 fprintf(stderr, "Length is %d -- too small\n", bufsize); 198 goto bail; 199 } 200 201 // find the end-of-central-dir magic 202 if (bufsize > MAX_EOCD_SEARCH) { 203 start = buf + bufsize - MAX_EOCD_SEARCH; 204 } else { 205 start = buf; 206 } 207 p = buf + bufsize - 4; 208 while (p >= start) { 209 if (*p == 0x50 && read_le_int(p) == CD_SIGNATURE) { 210 eocd = p; 211 break; 212 } 213 p--; 214 } 215 if (p < start) { 216 fprintf(stderr, "EOCD not found, not Zip\n"); 217 goto bail; 218 } 219 220 // extract eocd values 221 err = read_central_dir_values(file, eocd, (buf+bufsize)-eocd); 222 if (err != 0) { 223 goto bail; 224 } 225 226 if (file->disknum != 0 227 || file->diskWithCentralDir != 0 228 || file->entryCount != file->totalEntryCount) { 229 fprintf(stderr, "Archive spanning not supported\n"); 230 goto bail; 231 } 232 233 // Loop through and read the central dir entries. 234 p = buf + file->centralDirOffest; 235 len = (buf+bufsize)-p; 236 for (i=0; i < file->totalEntryCount; i++) { 237 Zipentry* entry = malloc(sizeof(Zipentry)); 238 memset(entry, sizeof(Zipentry), 0); 239 240 err = read_central_directory_entry(file, entry, &p, &len); 241 if (err != 0) { 242 fprintf(stderr, "read_central_directory_entry failed\n"); 243 free(entry); 244 goto bail; 245 } 246 247 // add it to our list 248 entry->next = file->entries; 249 file->entries = entry; 250 } 251 252 return 0; 253bail: 254 return -1; 255} 256 257