1 #include "private.h" 2 #include <stdio.h> 3 #include <string.h> 4 #include <stdlib.h> 5 6 #include <utils/Compat.h> 7 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 18 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 71 unsigned short extraFieldLength; 72 unsigned short fileCommentLength; 73 unsigned long localHeaderRelOffset; 74 unsigned int dataOffset; 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 88 entry->compressionMethod = read_le_short(&p[0x0a]); 89 entry->compressedSize = read_le_int(&p[0x14]); 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]); 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 107 p += extraFieldLength; 108 109 // comment, if any 110 p += fileCommentLength; 111 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]); 118 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; 155 ZD_TYPE bufsize = file->bufsize; 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) { 164 fprintf(stderr, "Length is " ZD " -- too small\n", bufsize); 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)); 205 memset(entry, 0, sizeof(Zipentry)); 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 } 213 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 } 223