Download
jcs
/subtext
/zip.c
(View History)
jcs zip: Make zip_read_file return an error code depending on failure | Latest amendment: 455 on 2023-03-27 |
1 | /* |
2 | * Basic PKZIP parser |
3 | * https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT |
4 | * https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html |
5 | * |
6 | * Copyright (c) 2023 joshua stein <jcs@jcs.org> |
7 | * |
8 | * Permission to use, copy, modify, and distribute this software for any |
9 | * purpose with or without fee is hereby granted, provided that the above |
10 | * copyright notice and this permission notice appear in all copies. |
11 | * |
12 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
13 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
14 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
15 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
16 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
17 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
18 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
19 | */ |
20 | |
21 | #include <fcntl.h> |
22 | #include <stdio.h> |
23 | #include <stdlib.h> |
24 | #include <string.h> |
25 | |
26 | #include "logger.h" |
27 | |
28 | #include "puff.h" |
29 | #include "util.h" |
30 | #include "zip.h" |
31 | |
32 | static unsigned char zip_file_magic[] = { 0x50, 0x4b, 0x03, 0x04 }; |
33 | static unsigned char zip_dir_magic[] = { 0x50, 0x4b, 0x01, 0x02 }; |
34 | |
35 | size_t zip_read(short frefnum, void *buf, size_t len); |
36 | |
37 | size_t |
38 | zip_read(short frefnum, void *buf, size_t len) |
39 | { |
40 | short error; |
41 | long rlen = len; |
42 | |
43 | error = FSRead(frefnum, &rlen, buf); |
44 | if (error) { |
45 | logger_printf("[zip] error reading zip: %d", error); |
46 | if (error != eofErr) |
47 | warn("error reading zip: %d", error); |
48 | return 0; |
49 | } |
50 | if (rlen != len) { |
51 | logger_printf("[zip] short read: got %ld wanted %ld", rlen, len); |
52 | return 0; |
53 | } |
54 | return rlen; |
55 | } |
56 | |
57 | bool |
58 | zip_is_zip_file(Str255 path) |
59 | { |
60 | char buf[32]; |
61 | short error, frefnum; |
62 | bool ret = false; |
63 | |
64 | error = FSOpen(path, 0, &frefnum); |
65 | if (error) { |
66 | warn("failed opening %s: %d", PtoCstr(path), error); |
67 | CtoPstr(path); |
68 | return false; |
69 | } |
70 | |
71 | if (zip_read(frefnum, &buf, 4) == 4 && |
72 | memcmp(buf, &zip_file_magic, 4) == 0) |
73 | ret = true; |
74 | |
75 | read_done: |
76 | FSClose(frefnum); |
77 | return ret; |
78 | |
79 | } |
80 | |
81 | short |
82 | zip_read_file(Str255 path, zip_extract_decider *decider, |
83 | zip_extract_processor *processor) |
84 | { |
85 | char buf[32]; |
86 | char filename[256]; |
87 | unsigned char *comp, *uncomp; |
88 | unsigned long comp_len, uncomp_len; |
89 | u_int16_t t, fn_len, ex_len; |
90 | short ret, fret, error, frefnum; |
91 | |
92 | error = FSOpen(path, 0, &frefnum); |
93 | if (error) { |
94 | warn("failed opening %s: %d", PtoCstr(path), error); |
95 | CtoPstr(path); |
96 | return ZIP_FAILED_OPEN; |
97 | } |
98 | |
99 | for (;;) { |
100 | fret = 0; |
101 | |
102 | if (zip_read(frefnum, &buf, 4) != 4) { |
103 | fret = ZIP_SHORT_READ; |
104 | goto read_fail; |
105 | } |
106 | if (memcmp(buf, &zip_dir_magic, 4) == 0) |
107 | break; |
108 | if (memcmp(buf, &zip_file_magic, 4) != 0) { |
109 | fret = ZIP_BAD_FILE; |
110 | goto read_fail; |
111 | } |
112 | |
113 | /* version */ |
114 | if (zip_read(frefnum, &buf, 2) != 2) { |
115 | fret = ZIP_UNSUPPORTED; |
116 | goto read_fail; |
117 | } |
118 | |
119 | /* flags */ |
120 | if (zip_read(frefnum, &buf, 2) != 2) { |
121 | fret = ZIP_SHORT_READ; |
122 | goto read_fail; |
123 | } |
124 | t = GET_U16(&buf); |
125 | if (t & (1 << 0)) { |
126 | logger_printf("[zip] Encryption not supported"); |
127 | fret = ZIP_UNSUPPORTED; |
128 | goto read_fail; |
129 | } |
130 | |
131 | /* compression */ |
132 | if (zip_read(frefnum, &buf, 2) != 2) { |
133 | fret = ZIP_SHORT_READ; |
134 | goto read_fail; |
135 | } |
136 | t = GET_U16(&buf); |
137 | if (t != 8) { |
138 | logger_printf("[zip] Compression method %d not supported", t); |
139 | fret = ZIP_UNSUPPORTED; |
140 | goto read_fail; |
141 | } |
142 | |
143 | /* mod time/date */ |
144 | if (zip_read(frefnum, &buf, 4) != 4) { |
145 | fret = ZIP_SHORT_READ; |
146 | goto read_fail; |
147 | } |
148 | |
149 | /* crc32 */ |
150 | if (zip_read(frefnum, &buf, 4) != 4) { |
151 | fret = ZIP_SHORT_READ; |
152 | goto read_fail; |
153 | } |
154 | |
155 | /* compressed size */ |
156 | if (zip_read(frefnum, &buf, 4) != 4) { |
157 | fret = ZIP_SHORT_READ; |
158 | goto read_fail; |
159 | } |
160 | comp_len = GET_U32(&buf); |
161 | if (comp_len == 0xffffffff) { |
162 | logger_printf("[zip] ZIP64 not supported"); |
163 | fret = ZIP_UNSUPPORTED; |
164 | goto read_fail; |
165 | } |
166 | if ((signed long)comp_len < 0) { |
167 | logger_printf("[zip] Bogus compressed length (%lu)", comp_len); |
168 | fret = ZIP_BAD_FILE; |
169 | goto read_fail; |
170 | } |
171 | if (comp_len == 0) { |
172 | logger_printf("[zip] Data descriptor not supported"); |
173 | fret = ZIP_UNSUPPORTED; |
174 | goto read_fail; |
175 | } |
176 | |
177 | /* uncompressed size */ |
178 | if (zip_read(frefnum, &buf, 4) != 4) { |
179 | fret = ZIP_SHORT_READ; |
180 | goto read_fail; |
181 | } |
182 | uncomp_len = GET_U32(&buf); |
183 | if (uncomp_len == 0xffffffff) { |
184 | logger_printf("[zip] ZIP64 not supported"); |
185 | fret = ZIP_UNSUPPORTED; |
186 | goto read_fail; |
187 | } |
188 | if ((signed long)uncomp_len < 0) { |
189 | logger_printf("[zip] Bogus uncompressed length (%lu)", |
190 | uncomp_len); |
191 | fret = ZIP_BAD_FILE; |
192 | goto read_fail; |
193 | } |
194 | if (uncomp_len == 0) { |
195 | logger_printf("[zip] Data descriptor not supported"); |
196 | fret = ZIP_UNSUPPORTED; |
197 | goto read_fail; |
198 | } |
199 | |
200 | /* file name len */ |
201 | if (zip_read(frefnum, &buf, 2) != 2) { |
202 | fret = ZIP_SHORT_READ; |
203 | goto read_fail; |
204 | } |
205 | fn_len = GET_U16(&buf); |
206 | if (fn_len >= sizeof(buf)) { |
207 | logger_printf("[zip] Filename len %d too big", fn_len); |
208 | fret = ZIP_UNSUPPORTED; |
209 | goto read_fail; |
210 | } |
211 | |
212 | /* extra field len */ |
213 | if (zip_read(frefnum, &buf, 2) != 2) { |
214 | fret = ZIP_SHORT_READ; |
215 | goto read_fail; |
216 | } |
217 | ex_len = GET_U16(&buf); |
218 | |
219 | /* filename */ |
220 | if (fn_len > sizeof(filename)) { |
221 | logger_printf("[zip] Filename too long (%ld)", fn_len); |
222 | fret = ZIP_UNSUPPORTED; |
223 | goto read_fail; |
224 | } |
225 | |
226 | if (zip_read(frefnum, &filename, fn_len) != fn_len) { |
227 | fret = ZIP_SHORT_READ; |
228 | goto read_fail; |
229 | } |
230 | filename[fn_len] = '\0'; |
231 | |
232 | /* extra field */ |
233 | if (zip_read(frefnum, &buf, ex_len) != ex_len) { |
234 | fret = ZIP_SHORT_READ; |
235 | goto read_fail; |
236 | } |
237 | |
238 | if (decider(filename, uncomp_len)) { |
239 | comp = xmalloc(comp_len); |
240 | if (comp == NULL) { |
241 | logger_printf("[zip] Failed to malloc(%ld) for " |
242 | "compressed data", comp_len); |
243 | fret = ZIP_NO_MEMORY; |
244 | goto read_fail; |
245 | } |
246 | |
247 | /* read compressed data */ |
248 | if (zip_read(frefnum, comp, comp_len) != comp_len) { |
249 | xfree(&comp); |
250 | fret = ZIP_SHORT_READ; |
251 | goto read_fail; |
252 | } |
253 | |
254 | uncomp = xmalloc(uncomp_len); |
255 | if (uncomp == NULL) { |
256 | logger_printf("[zip] Failed to malloc(%ld) for " |
257 | "uncompressed data", uncomp_len); |
258 | fret = ZIP_NO_MEMORY; |
259 | xfree(&comp); |
260 | goto read_fail; |
261 | } |
262 | |
263 | ret = puff(uncomp, &uncomp_len, comp, &comp_len); |
264 | if (ret != 0) { |
265 | logger_printf("[zip] Unzip failed: %d", ret); |
266 | xfree(&comp); |
267 | xfree(&uncomp); |
268 | fret = ZIP_BAD_FILE; |
269 | goto read_fail; |
270 | } |
271 | xfree(&comp); |
272 | |
273 | processor(filename, uncomp, uncomp_len); |
274 | xfree(&uncomp); |
275 | } else { |
276 | /* skip over it */ |
277 | SetFPos(frefnum, fsFromMark, comp_len); |
278 | } |
279 | } |
280 | |
281 | FSClose(frefnum); |
282 | return ZIP_OK; |
283 | |
284 | read_fail: |
285 | FSClose(frefnum); |
286 | if (fret == 0) |
287 | fret = ZIP_BAD_FILE; |
288 | return fret; |
289 | |
290 | } |