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