main
zip.c
1#include <stdio.h>
2#include <stdlib.h>
3#include <stddef.h>
4#include <string.h>
5#include "util.h"
6#include "zip.h"
7#include <zlib.h>
8
9static const char cdfd_magic[4] = {'P', 'K', 1, 2};
10static const char eocd_magic[4] = {'P', 'K', 5, 6};
11#define MIN_EOCD_SIZE 22
12#define MIN_CDFD_SIZE 46
13#define ZIP_MAX_EOCD_OFFSET (64*1024+MIN_EOCD_SIZE)
14
15enum {
16 ZIP_STORE = 0,
17 ZIP_DEFLATE = 8
18};
19
20zip_file *zip_open(const char *filename)
21{
22 FILE *f = fopen(filename, "rb");
23 if (!f) {
24 return NULL;
25 }
26 long fsize = file_size(f);
27 if (fsize < MIN_EOCD_SIZE) {
28 //too small to be a zip file
29 goto fail;
30 }
31
32 long max_offset = fsize > ZIP_MAX_EOCD_OFFSET ? ZIP_MAX_EOCD_OFFSET : fsize;
33 fseek(f, -max_offset, SEEK_END);
34 uint8_t *buf = malloc(max_offset);
35 if (max_offset != fread(buf, 1, max_offset, f)) {
36 goto fail;
37 }
38
39 long current_offset;
40 uint32_t cd_start, cd_size;
41 uint16_t cd_count;
42 for (current_offset = max_offset - MIN_EOCD_SIZE; current_offset >= 0; current_offset--)
43 {
44 if (memcmp(eocd_magic, buf + current_offset, sizeof(eocd_magic))) {
45 continue;
46 }
47 uint16_t comment_size = buf[current_offset + 20] | buf[current_offset + 21] << 8;
48 if (comment_size != (max_offset - current_offset - MIN_EOCD_SIZE)) {
49 continue;
50 }
51 cd_start = buf[current_offset + 16] | buf[current_offset + 17] << 8
52 | buf[current_offset + 18] << 16 | buf[current_offset + 19] << 24;
53 if (cd_start > (fsize - (max_offset - current_offset))) {
54 continue;
55 }
56 cd_size = buf[current_offset + 12] | buf[current_offset + 13] << 8
57 | buf[current_offset + 14] << 16 | buf[current_offset + 15] << 24;
58 if ((cd_start + cd_size) > (fsize - (max_offset - current_offset))) {
59 continue;
60 }
61 cd_count = buf[current_offset + 10] | buf[current_offset + 11] << 8;
62 break;
63 }
64 free(buf);
65 if (current_offset < 0) {
66 //failed to find EOCD
67 goto fail;
68 }
69 buf = malloc(cd_size);
70 fseek(f, cd_start, SEEK_SET);
71 if (cd_size != fread(buf, 1, cd_size, f)) {
72 goto fail_free;
73 }
74 zip_entry *entries = calloc(cd_count, sizeof(zip_entry));
75 uint32_t cd_max_last = cd_size - MIN_CDFD_SIZE;
76 zip_entry *cur_entry = entries;
77 for (uint32_t off = 0; cd_count && off <= cd_max_last; cur_entry++, cd_count--)
78 {
79 if (memcmp(buf + off, cdfd_magic, sizeof(cdfd_magic))) {
80 goto fail_entries;
81 }
82 uint32_t name_length = buf[off + 28] | buf[off + 29] << 8;
83 uint32_t extra_length = buf[off + 30] | buf[off + 31] << 8;
84 //TODO: verify name length doesn't go past end of CD
85
86 cur_entry->name = malloc(name_length + 1);
87 memcpy(cur_entry->name, buf + off + MIN_CDFD_SIZE, name_length);
88 cur_entry->name[name_length] = 0;
89
90 cur_entry->compressed_size = buf[off + 20] | buf[off + 21] << 8
91 | buf[off + 22] << 16 | buf[off + 23] << 24;
92 cur_entry->size = buf[off + 24] | buf[off + 25] << 8
93 | buf[off + 26] << 16 | buf[off + 27] << 24;
94
95 cur_entry->local_header_off = buf[off + 42] | buf[off + 43] << 8
96 | buf[off + 44] << 16 | buf[off + 45] << 24;
97
98 cur_entry->compression_method = buf[off + 10] | buf[off + 11] << 8;
99
100 off += name_length + extra_length + MIN_CDFD_SIZE;
101 }
102
103 zip_file *z = malloc(sizeof(zip_file));
104 z->entries = entries;
105 z->file = f;
106 z->num_entries = cur_entry - entries;
107 return z;
108
109fail_entries:
110 for (cur_entry--; cur_entry >= entries; cur_entry--)
111 {
112 free(cur_entry->name);
113 }
114 free(entries);
115fail_free:
116 free(buf);
117fail:
118 fclose(f);
119 return NULL;
120}
121
122uint8_t *zip_read(zip_file *f, uint32_t index, size_t *out_size)
123{
124
125 fseek(f->file, f->entries[index].local_header_off + 26, SEEK_SET);
126 uint8_t tmp[4];
127 if (sizeof(tmp) != fread(tmp, 1, sizeof(tmp), f->file)) {
128 return NULL;
129 }
130 uint32_t local_variable = (tmp[0] | tmp[1] << 8) + (tmp[2] | tmp[3] << 8);
131 fseek(f->file, f->entries[index].local_header_off + local_variable + 30, SEEK_SET);
132
133 size_t int_size;
134 if (!out_size) {
135 out_size = &int_size;
136 int_size = f->entries[index].size;
137 }
138
139 uint8_t *buf = malloc(*out_size);
140 if (*out_size > f->entries[index].size) {
141 *out_size = f->entries[index].size;
142 }
143 switch(f->entries[index].compression_method)
144 {
145 case ZIP_STORE:
146 if (*out_size != fread(buf, 1, *out_size, f->file)) {
147 free(buf);
148 return NULL;
149 }
150 break;
151 case ZIP_DEFLATE: {
152 //note in unzip.c in zlib/contrib suggests a dummy byte is needed, so we allocate an extra byte here
153 uint8_t *src_buf = malloc(f->entries[index].compressed_size + 1);
154 if (f->entries[index].compressed_size != fread(src_buf, 1, f->entries[index].compressed_size, f->file)) {
155 free(src_buf);
156 return NULL;
157 }
158 uLongf destLen = *out_size;
159 z_stream stream;
160 memset(&stream, 0, sizeof(stream));
161 stream.avail_in = f->entries[index].compressed_size + 1;
162 stream.next_in = src_buf;
163 stream.next_out = buf;
164 stream.avail_out = *out_size;
165 if (Z_OK == inflateInit2(&stream, -15)) {
166 int result = inflate(&stream, Z_FINISH);
167 *out_size = stream.total_out;
168 free(src_buf);
169 inflateEnd(&stream);
170 if (result != Z_OK && result != Z_STREAM_END && result != Z_BUF_ERROR) {
171 free(buf);
172 return NULL;
173 }
174 }
175 break;
176 }
177 default:
178 free(buf);
179 return NULL;
180 }
181
182 return buf;
183}
184
185void zip_close(zip_file *f)
186{
187 fclose(f->file);
188 for (uint32_t i = 0; i < f->num_entries; i++)
189 {
190 free(f->entries[i].name);
191 }
192 free(f->entries);
193 free(f);
194}
195
196