• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
read_le_int(const unsigned char * buf)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
read_le_short(const unsigned char * buf)30 read_le_short(const unsigned char* buf)
31 {
32     return buf[0] | (buf[1] << 8);
33 }
34 
35 static int
read_central_dir_values(Zipfile * file,const unsigned char * buf,int len)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
read_central_directory_entry(Zipfile * file,Zipentry * entry,const unsigned char ** buf,ssize_t * len)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
read_central_dir(Zipfile * file)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