Home | History | Annotate | Download | only in libzipfile
      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