-
Notifications
You must be signed in to change notification settings - Fork 17
/
junzip.c
309 lines (246 loc) · 9.38 KB
/
junzip.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// JUnzip library by Joonas Pihlajamaa. See junzip.h for license and details.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "junzip.h"
unsigned char jzBuffer[JZ_BUFFER_SIZE]; // limits maximum zip descriptor size
// Read ZIP file end record. Will move within file.
int jzReadEndRecord(JZFile *zip, JZEndRecord *endRecord) {
long fileSize, readBytes, i;
JZEndRecord *er;
if(zip->seek(zip, 0, SEEK_END)) {
fprintf(stderr, "Couldn't go to end of zip file!");
return Z_ERRNO;
}
if((fileSize = zip->tell(zip)) <= sizeof(JZEndRecord)) {
fprintf(stderr, "Too small file to be a zip!");
return Z_ERRNO;
}
readBytes = (fileSize < sizeof(jzBuffer)) ? fileSize : sizeof(jzBuffer);
if(zip->seek(zip, fileSize - readBytes, SEEK_SET)) {
fprintf(stderr, "Cannot seek in zip file!");
return Z_ERRNO;
}
if(zip->read(zip, jzBuffer, readBytes) < readBytes) {
fprintf(stderr, "Couldn't read end of zip file!");
return Z_ERRNO;
}
// Naively assume signature can only be found in one place...
for(i = readBytes - sizeof(JZEndRecord); i >= 0; i--) {
er = (JZEndRecord *)(jzBuffer + i);
if(er->signature == 0x06054B50)
break;
}
if(i < 0) {
fprintf(stderr, "End record signature not found in zip!");
return Z_ERRNO;
}
memcpy(endRecord, er, sizeof(JZEndRecord));
if(endRecord->diskNumber || endRecord->centralDirectoryDiskNumber ||
endRecord->numEntries != endRecord->numEntriesThisDisk) {
fprintf(stderr, "Multifile zips not supported!");
return Z_ERRNO;
}
return Z_OK;
}
// Read ZIP file global directory. Will move within file.
int jzReadCentralDirectory(JZFile *zip, JZEndRecord *endRecord,
JZRecordCallback callback, void *user_data) {
JZGlobalFileHeader fileHeader;
JZFileHeader header;
int i;
if(zip->seek(zip, endRecord->centralDirectoryOffset, SEEK_SET)) {
fprintf(stderr, "Cannot seek in zip file!");
return Z_ERRNO;
}
for(i=0; i<endRecord->numEntries; i++) {
if(zip->read(zip, &fileHeader, sizeof(JZGlobalFileHeader)) <
sizeof(JZGlobalFileHeader)) {
fprintf(stderr, "Couldn't read file header %d!", i);
return Z_ERRNO;
}
if(fileHeader.signature != 0x02014B50) {
fprintf(stderr, "Invalid file header signature %d!", i);
return Z_ERRNO;
}
if(fileHeader.fileNameLength + 1 >= JZ_BUFFER_SIZE) {
fprintf(stderr, "Too long file name %d!", i);
return Z_ERRNO;
}
if(zip->read(zip, jzBuffer, fileHeader.fileNameLength) <
fileHeader.fileNameLength) {
fprintf(stderr, "Couldn't read filename %d!", i);
return Z_ERRNO;
}
jzBuffer[fileHeader.fileNameLength] = '\0'; // NULL terminate
if(zip->seek(zip, fileHeader.extraFieldLength, SEEK_CUR) ||
zip->seek(zip, fileHeader.fileCommentLength, SEEK_CUR)) {
fprintf(stderr, "Couldn't skip extra field or file comment %d", i);
return Z_ERRNO;
}
// Construct JZFileHeader from global file header
memcpy(&header, &fileHeader.compressionMethod, sizeof(header));
header.offset = fileHeader.relativeOffsetOflocalHeader;
if(!callback(zip, i, &header, (char *)jzBuffer, user_data))
break; // end if callback returns zero
}
return Z_OK;
}
// Read local ZIP file header. Silent on errors so optimistic reading possible.
int jzReadLocalFileHeaderRaw(JZFile *zip, JZLocalFileHeader *header,
char *filename, int len) {
if(zip->read(zip, header, sizeof(JZLocalFileHeader)) <
sizeof(JZLocalFileHeader))
return Z_ERRNO;
if(header->signature != 0x04034B50)
return Z_ERRNO;
if(len) { // read filename
if(header->fileNameLength >= len)
return Z_ERRNO; // filename cannot fit
if(zip->read(zip, filename, header->fileNameLength) <
header->fileNameLength)
return Z_ERRNO; // read fail
filename[header->fileNameLength] = '\0'; // NULL terminate
} else { // skip filename
if(zip->seek(zip, header->fileNameLength, SEEK_CUR))
return Z_ERRNO;
}
if(header->extraFieldLength) {
if(zip->seek(zip, header->extraFieldLength, SEEK_CUR))
return Z_ERRNO;
}
// For now, silently ignore bit flags and hope ZLIB can uncompress
// if(header->generalPurposeBitFlag)
// return Z_ERRNO; // Flags not supported
if(header->compressionMethod == 0 &&
(header->compressedSize != header->uncompressedSize))
return Z_ERRNO; // Method is "store" but sizes indicate otherwise, abort
return Z_OK;
}
int jzReadLocalFileHeader(JZFile *zip, JZFileHeader *header,
char *filename, int len) {
JZLocalFileHeader localHeader;
if(jzReadLocalFileHeaderRaw(zip, &localHeader, filename, len) != Z_OK)
return Z_ERRNO;
memcpy(header, &localHeader.compressionMethod, sizeof(JZFileHeader));
header->offset = 0; // not used in local context
return Z_OK;
}
// Read data from file stream, described by header, to preallocated buffer
int jzReadData(JZFile *zip, JZFileHeader *header, void *buffer) {
#ifdef HAVE_ZLIB
unsigned char *bytes = (unsigned char *)buffer; // cast
long compressedLeft, uncompressedLeft;
int ret;
z_stream strm;
#endif
if(header->compressionMethod == 0) { // Store - just read it
if(zip->read(zip, buffer, header->uncompressedSize) <
header->uncompressedSize || zip->error(zip))
return Z_ERRNO;
#ifdef HAVE_ZLIB
} else if(header->compressionMethod == 8) { // Deflate - using zlib
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
// Use inflateInit2 with negative window bits to indicate raw data
if((ret = inflateInit2(&strm, -MAX_WBITS)) != Z_OK)
return ret; // Zlib errors are negative
// Inflate compressed data
for(compressedLeft = header->compressedSize,
uncompressedLeft = header->uncompressedSize;
compressedLeft && uncompressedLeft && ret != Z_STREAM_END;
compressedLeft -= strm.avail_in) {
// Read next chunk
strm.avail_in = zip->read(zip, jzBuffer,
(sizeof(jzBuffer) < compressedLeft) ?
sizeof(jzBuffer) : compressedLeft);
if(strm.avail_in == 0 || zip->error(zip)) {
inflateEnd(&strm);
return Z_ERRNO;
}
strm.next_in = jzBuffer;
strm.avail_out = uncompressedLeft;
strm.next_out = bytes;
compressedLeft -= strm.avail_in; // inflate will change avail_in
ret = inflate(&strm, Z_NO_FLUSH);
if(ret == Z_STREAM_ERROR) return ret; // shouldn't happen
switch (ret) {
case Z_NEED_DICT:
ret = Z_DATA_ERROR; /* and fall through */
case Z_DATA_ERROR: case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return ret;
}
bytes += uncompressedLeft - strm.avail_out; // bytes uncompressed
uncompressedLeft = strm.avail_out;
}
inflateEnd(&strm);
#else
#ifdef HAVE_PUFF
} else if(header->compressionMethod == 8) { // Deflate - using puff()
unsigned long destlen = header->uncompressedSize,
sourcelen = header->compressedSize;
unsigned char *comp = (unsigned char *)malloc(sourcelen);
if(comp == NULL) return Z_ERRNO; // couldn't allocate
unsigned long read = zip->read(zip, comp, sourcelen);
if(read != sourcelen) return Z_ERRNO; // TODO: more robust read loop
int ret = puff((unsigned char *)buffer, &destlen, comp, &sourcelen);
free(comp);
if(ret) return Z_ERRNO; // something went wrong
#endif // HAVE_PUFF
#endif
} else {
return Z_ERRNO;
}
return Z_OK;
}
typedef struct {
JZFile handle;
FILE *fp;
} StdioJZFile;
static size_t
stdio_read_file_handle_read(JZFile *file, void *buf, size_t size)
{
StdioJZFile *handle = (StdioJZFile *)file;
return fread(buf, 1, size, handle->fp);
}
static size_t
stdio_read_file_handle_tell(JZFile *file)
{
StdioJZFile *handle = (StdioJZFile *)file;
return ftell(handle->fp);
}
static int
stdio_read_file_handle_seek(JZFile *file, size_t offset, int whence)
{
StdioJZFile *handle = (StdioJZFile *)file;
return fseek(handle->fp, offset, whence);
}
static int
stdio_read_file_handle_error(JZFile *file)
{
StdioJZFile *handle = (StdioJZFile *)file;
return ferror(handle->fp);
}
static void
stdio_read_file_handle_close(JZFile *file)
{
StdioJZFile *handle = (StdioJZFile *)file;
fclose(handle->fp);
free(file);
}
JZFile *
jzfile_from_stdio_file(FILE *fp)
{
StdioJZFile *handle = (StdioJZFile *)malloc(sizeof(StdioJZFile));
handle->handle.read = stdio_read_file_handle_read;
handle->handle.tell = stdio_read_file_handle_tell;
handle->handle.seek = stdio_read_file_handle_seek;
handle->handle.error = stdio_read_file_handle_error;
handle->handle.close = stdio_read_file_handle_close;
handle->fp = fp;
return &(handle->handle);
}