blob: 1cde28d273cac51ae8823f52ca42cc1ca364b514
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * Mini unzip implementation for busybox |
4 | * |
5 | * Copyright (C) 2004 by Ed Clark |
6 | * |
7 | * Loosely based on original busybox unzip applet by Laurence Anderson. |
8 | * All options and features should work in this version. |
9 | * |
10 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
11 | */ |
12 | /* For reference see |
13 | * http://www.pkware.com/company/standards/appnote/ |
14 | * http://www.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip |
15 | * |
16 | * TODO |
17 | * Zip64 + other methods |
18 | */ |
19 | |
20 | //config:config UNZIP |
21 | //config: bool "unzip" |
22 | //config: default y |
23 | //config: help |
24 | //config: unzip will list or extract files from a ZIP archive, |
25 | //config: commonly found on DOS/WIN systems. The default behavior |
26 | //config: (with no options) is to extract the archive into the |
27 | //config: current directory. Use the `-d' option to extract to a |
28 | //config: directory of your choice. |
29 | |
30 | //applet:IF_UNZIP(APPLET(unzip, BB_DIR_USR_BIN, BB_SUID_DROP)) |
31 | //kbuild:lib-$(CONFIG_UNZIP) += unzip.o |
32 | |
33 | //usage:#define unzip_trivial_usage |
34 | //usage: "[-lnopq] FILE[.zip] [FILE]... [-x FILE...] [-d DIR]" |
35 | //usage:#define unzip_full_usage "\n\n" |
36 | //usage: "Extract FILEs from ZIP archive\n" |
37 | //usage: "\n -l List contents (with -q for short form)" |
38 | //usage: "\n -n Never overwrite files (default: ask)" |
39 | //usage: "\n -o Overwrite" |
40 | //usage: "\n -p Print to stdout" |
41 | //usage: "\n -q Quiet" |
42 | //usage: "\n -x FILE Exclude FILEs" |
43 | //usage: "\n -d DIR Extract into DIR" |
44 | |
45 | #include "libbb.h" |
46 | #include "bb_archive.h" |
47 | |
48 | #if defined(ANDROID) || defined(__ANDROID__) |
49 | /* Needed to ensure we can extract signed Android OTA packages */ |
50 | #undef ENABLE_DESKTOP |
51 | #define ENABLE_DESKTOP 1 |
52 | #endif |
53 | |
54 | enum { |
55 | #if BB_BIG_ENDIAN |
56 | ZIP_FILEHEADER_MAGIC = 0x504b0304, |
57 | ZIP_CDF_MAGIC = 0x504b0102, /* central directory's file header */ |
58 | ZIP_CDE_MAGIC = 0x504b0506, /* "end of central directory" record */ |
59 | ZIP_DD_MAGIC = 0x504b0708, |
60 | #else |
61 | ZIP_FILEHEADER_MAGIC = 0x04034b50, |
62 | ZIP_CDF_MAGIC = 0x02014b50, |
63 | ZIP_CDE_MAGIC = 0x06054b50, |
64 | ZIP_DD_MAGIC = 0x08074b50, |
65 | #endif |
66 | }; |
67 | |
68 | #define ZIP_HEADER_LEN 26 |
69 | |
70 | typedef union { |
71 | uint8_t raw[ZIP_HEADER_LEN]; |
72 | struct { |
73 | uint16_t version; /* 0-1 */ |
74 | uint16_t zip_flags; /* 2-3 */ |
75 | uint16_t method; /* 4-5 */ |
76 | uint16_t modtime; /* 6-7 */ |
77 | uint16_t moddate; /* 8-9 */ |
78 | uint32_t crc32 PACKED; /* 10-13 */ |
79 | uint32_t cmpsize PACKED; /* 14-17 */ |
80 | uint32_t ucmpsize PACKED; /* 18-21 */ |
81 | uint16_t filename_len; /* 22-23 */ |
82 | uint16_t extra_len; /* 24-25 */ |
83 | } formatted PACKED; |
84 | } zip_header_t; /* PACKED - gcc 4.2.1 doesn't like it (spews warning) */ |
85 | |
86 | /* Check the offset of the last element, not the length. This leniency |
87 | * allows for poor packing, whereby the overall struct may be too long, |
88 | * even though the elements are all in the right place. |
89 | */ |
90 | struct BUG_zip_header_must_be_26_bytes { |
91 | char BUG_zip_header_must_be_26_bytes[ |
92 | offsetof(zip_header_t, formatted.extra_len) + 2 |
93 | == ZIP_HEADER_LEN ? 1 : -1]; |
94 | }; |
95 | |
96 | #define FIX_ENDIANNESS_ZIP(zip_header) do { \ |
97 | (zip_header).formatted.version = SWAP_LE16((zip_header).formatted.version ); \ |
98 | (zip_header).formatted.method = SWAP_LE16((zip_header).formatted.method ); \ |
99 | (zip_header).formatted.modtime = SWAP_LE16((zip_header).formatted.modtime ); \ |
100 | (zip_header).formatted.moddate = SWAP_LE16((zip_header).formatted.moddate ); \ |
101 | (zip_header).formatted.crc32 = SWAP_LE32((zip_header).formatted.crc32 ); \ |
102 | (zip_header).formatted.cmpsize = SWAP_LE32((zip_header).formatted.cmpsize ); \ |
103 | (zip_header).formatted.ucmpsize = SWAP_LE32((zip_header).formatted.ucmpsize ); \ |
104 | (zip_header).formatted.filename_len = SWAP_LE16((zip_header).formatted.filename_len); \ |
105 | (zip_header).formatted.extra_len = SWAP_LE16((zip_header).formatted.extra_len ); \ |
106 | } while (0) |
107 | |
108 | #define CDF_HEADER_LEN 42 |
109 | |
110 | typedef union { |
111 | uint8_t raw[CDF_HEADER_LEN]; |
112 | struct { |
113 | /* uint32_t signature; 50 4b 01 02 */ |
114 | uint16_t version_made_by; /* 0-1 */ |
115 | uint16_t version_needed; /* 2-3 */ |
116 | uint16_t cdf_flags; /* 4-5 */ |
117 | uint16_t method; /* 6-7 */ |
118 | uint16_t mtime; /* 8-9 */ |
119 | uint16_t mdate; /* 10-11 */ |
120 | uint32_t crc32; /* 12-15 */ |
121 | uint32_t cmpsize; /* 16-19 */ |
122 | uint32_t ucmpsize; /* 20-23 */ |
123 | uint16_t file_name_length; /* 24-25 */ |
124 | uint16_t extra_field_length; /* 26-27 */ |
125 | uint16_t file_comment_length; /* 28-29 */ |
126 | uint16_t disk_number_start; /* 30-31 */ |
127 | uint16_t internal_file_attributes; /* 32-33 */ |
128 | uint32_t external_file_attributes PACKED; /* 34-37 */ |
129 | uint32_t relative_offset_of_local_header PACKED; /* 38-41 */ |
130 | } formatted PACKED; |
131 | } cdf_header_t; |
132 | |
133 | struct BUG_cdf_header_must_be_42_bytes { |
134 | char BUG_cdf_header_must_be_42_bytes[ |
135 | offsetof(cdf_header_t, formatted.relative_offset_of_local_header) + 4 |
136 | == CDF_HEADER_LEN ? 1 : -1]; |
137 | }; |
138 | |
139 | #define FIX_ENDIANNESS_CDF(cdf_header) do { \ |
140 | (cdf_header).formatted.crc32 = SWAP_LE32((cdf_header).formatted.crc32 ); \ |
141 | (cdf_header).formatted.cmpsize = SWAP_LE32((cdf_header).formatted.cmpsize ); \ |
142 | (cdf_header).formatted.ucmpsize = SWAP_LE32((cdf_header).formatted.ucmpsize ); \ |
143 | (cdf_header).formatted.file_name_length = SWAP_LE16((cdf_header).formatted.file_name_length); \ |
144 | (cdf_header).formatted.extra_field_length = SWAP_LE16((cdf_header).formatted.extra_field_length); \ |
145 | (cdf_header).formatted.file_comment_length = SWAP_LE16((cdf_header).formatted.file_comment_length); \ |
146 | IF_DESKTOP( \ |
147 | (cdf_header).formatted.version_made_by = SWAP_LE16((cdf_header).formatted.version_made_by); \ |
148 | (cdf_header).formatted.external_file_attributes = SWAP_LE32((cdf_header).formatted.external_file_attributes); \ |
149 | ) \ |
150 | } while (0) |
151 | |
152 | #define CDE_HEADER_LEN 16 |
153 | |
154 | typedef union { |
155 | uint8_t raw[CDE_HEADER_LEN]; |
156 | struct { |
157 | /* uint32_t signature; 50 4b 05 06 */ |
158 | uint16_t this_disk_no; |
159 | uint16_t disk_with_cdf_no; |
160 | uint16_t cdf_entries_on_this_disk; |
161 | uint16_t cdf_entries_total; |
162 | uint32_t cdf_size; |
163 | uint32_t cdf_offset; |
164 | /* uint16_t file_comment_length; */ |
165 | /* .ZIP file comment (variable size) */ |
166 | } formatted PACKED; |
167 | } cde_header_t; |
168 | |
169 | struct BUG_cde_header_must_be_16_bytes { |
170 | char BUG_cde_header_must_be_16_bytes[ |
171 | sizeof(cde_header_t) == CDE_HEADER_LEN ? 1 : -1]; |
172 | }; |
173 | |
174 | #define FIX_ENDIANNESS_CDE(cde_header) do { \ |
175 | (cde_header).formatted.cdf_offset = SWAP_LE32((cde_header).formatted.cdf_offset); \ |
176 | } while (0) |
177 | |
178 | enum { zip_fd = 3 }; |
179 | |
180 | |
181 | #if ENABLE_DESKTOP |
182 | |
183 | /* Seen in the wild: |
184 | * Self-extracting PRO2K3XP_32.exe contains 19078464 byte zip archive, |
185 | * where CDE was nearly 48 kbytes before EOF. |
186 | * (Surprisingly, it also apparently has *another* CDE structure |
187 | * closer to the end, with bogus cdf_offset). |
188 | * To make extraction work, bumped PEEK_FROM_END from 16k to 64k. |
189 | */ |
190 | #define PEEK_FROM_END (64*1024) |
191 | |
192 | /* This value means that we failed to find CDF */ |
193 | #define BAD_CDF_OFFSET ((uint32_t)0xffffffff) |
194 | |
195 | /* NB: does not preserve file position! */ |
196 | static uint32_t find_cdf_offset(void) |
197 | { |
198 | cde_header_t cde_header; |
199 | unsigned char *p; |
200 | off_t end; |
201 | unsigned char *buf = xzalloc(PEEK_FROM_END); |
202 | |
203 | end = xlseek(zip_fd, 0, SEEK_END); |
204 | end -= PEEK_FROM_END; |
205 | if (end < 0) |
206 | end = 0; |
207 | xlseek(zip_fd, end, SEEK_SET); |
208 | full_read(zip_fd, buf, PEEK_FROM_END); |
209 | |
210 | cde_header.formatted.cdf_offset = BAD_CDF_OFFSET; |
211 | p = buf; |
212 | while (p <= buf + PEEK_FROM_END - CDE_HEADER_LEN - 4) { |
213 | if (*p != 'P') { |
214 | p++; |
215 | continue; |
216 | } |
217 | if (*++p != 'K') |
218 | continue; |
219 | if (*++p != 5) |
220 | continue; |
221 | if (*++p != 6) |
222 | continue; |
223 | /* we found CDE! */ |
224 | memcpy(cde_header.raw, p + 1, CDE_HEADER_LEN); |
225 | FIX_ENDIANNESS_CDE(cde_header); |
226 | /* |
227 | * I've seen .ZIP files with seemingly valid CDEs |
228 | * where cdf_offset points past EOF - ?? |
229 | * Ignore such CDEs: |
230 | */ |
231 | if ((int64_t) cde_header.formatted.cdf_offset < end + (p - buf)) |
232 | break; |
233 | cde_header.formatted.cdf_offset = BAD_CDF_OFFSET; |
234 | } |
235 | free(buf); |
236 | return cde_header.formatted.cdf_offset; |
237 | }; |
238 | |
239 | static uint32_t read_next_cdf(uint32_t cdf_offset, cdf_header_t *cdf_ptr) |
240 | { |
241 | off_t org; |
242 | |
243 | org = xlseek(zip_fd, 0, SEEK_CUR); |
244 | |
245 | if (!cdf_offset) |
246 | cdf_offset = find_cdf_offset(); |
247 | |
248 | if (cdf_offset != BAD_CDF_OFFSET) { |
249 | xlseek(zip_fd, cdf_offset + 4, SEEK_SET); |
250 | xread(zip_fd, cdf_ptr->raw, CDF_HEADER_LEN); |
251 | FIX_ENDIANNESS_CDF(*cdf_ptr); |
252 | cdf_offset += 4 + CDF_HEADER_LEN |
253 | + cdf_ptr->formatted.file_name_length |
254 | + cdf_ptr->formatted.extra_field_length |
255 | + cdf_ptr->formatted.file_comment_length; |
256 | } |
257 | |
258 | xlseek(zip_fd, org, SEEK_SET); |
259 | return cdf_offset; |
260 | }; |
261 | #endif |
262 | |
263 | static void unzip_skip(off_t skip) |
264 | { |
265 | if (skip != 0) |
266 | if (lseek(zip_fd, skip, SEEK_CUR) == (off_t)-1) |
267 | bb_copyfd_exact_size(zip_fd, -1, skip); |
268 | } |
269 | |
270 | static void unzip_create_leading_dirs(const char *fn) |
271 | { |
272 | /* Create all leading directories */ |
273 | char *name = xstrdup(fn); |
274 | if (bb_make_directory(dirname(name), 0777, FILEUTILS_RECUR)) { |
275 | xfunc_die(); /* bb_make_directory is noisy */ |
276 | } |
277 | free(name); |
278 | } |
279 | |
280 | static void unzip_extract(zip_header_t *zip_header, int dst_fd) |
281 | { |
282 | if (zip_header->formatted.method == 0) { |
283 | /* Method 0 - stored (not compressed) */ |
284 | off_t size = zip_header->formatted.ucmpsize; |
285 | if (size) |
286 | bb_copyfd_exact_size(zip_fd, dst_fd, size); |
287 | } else { |
288 | /* Method 8 - inflate */ |
289 | transformer_aux_data_t aux; |
290 | init_transformer_aux_data(&aux); |
291 | aux.bytes_in = zip_header->formatted.cmpsize; |
292 | if (inflate_unzip(&aux, zip_fd, dst_fd) < 0) |
293 | bb_error_msg_and_die("inflate error"); |
294 | /* Validate decompression - crc */ |
295 | if (zip_header->formatted.crc32 != (aux.crc32 ^ 0xffffffffL)) { |
296 | bb_error_msg_and_die("crc error"); |
297 | } |
298 | /* Validate decompression - size */ |
299 | if (zip_header->formatted.ucmpsize != (uint32_t) aux.bytes_out) { |
300 | /* Don't die. Who knows, maybe len calculation |
301 | * was botched somewhere. After all, crc matched! */ |
302 | bb_error_msg("bad length"); |
303 | } |
304 | } |
305 | } |
306 | |
307 | static void my_fgets80(char *buf80) |
308 | { |
309 | fflush_all(); |
310 | if (!fgets(buf80, 80, stdin)) { |
311 | bb_perror_msg_and_die("can't read standard input"); |
312 | } |
313 | } |
314 | |
315 | int unzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
316 | int unzip_main(int argc, char **argv) |
317 | { |
318 | enum { O_PROMPT, O_NEVER, O_ALWAYS }; |
319 | |
320 | zip_header_t zip_header; |
321 | smallint quiet = 0; |
322 | IF_NOT_DESKTOP(const) smallint verbose = 0; |
323 | smallint listing = 0; |
324 | smallint overwrite = O_PROMPT; |
325 | smallint x_opt_seen; |
326 | #if ENABLE_DESKTOP |
327 | uint32_t cdf_offset; |
328 | #endif |
329 | unsigned long total_usize; |
330 | unsigned long total_size; |
331 | unsigned total_entries; |
332 | int dst_fd = -1; |
333 | char *src_fn = NULL; |
334 | char *dst_fn = NULL; |
335 | llist_t *zaccept = NULL; |
336 | llist_t *zreject = NULL; |
337 | char *base_dir = NULL; |
338 | int i, opt; |
339 | char key_buf[80]; /* must match size used by my_fgets80 */ |
340 | struct stat stat_buf; |
341 | |
342 | /* -q, -l and -v: UnZip 5.52 of 28 February 2005, by Info-ZIP: |
343 | * |
344 | * # /usr/bin/unzip -qq -v decompress_unlzma.i.zip |
345 | * 204372 Defl:N 35278 83% 09-06-09 14:23 0d056252 decompress_unlzma.i |
346 | * # /usr/bin/unzip -q -v decompress_unlzma.i.zip |
347 | * Length Method Size Ratio Date Time CRC-32 Name |
348 | * -------- ------ ------- ----- ---- ---- ------ ---- |
349 | * 204372 Defl:N 35278 83% 09-06-09 14:23 0d056252 decompress_unlzma.i |
350 | * -------- ------- --- ------- |
351 | * 204372 35278 83% 1 file |
352 | * # /usr/bin/unzip -v decompress_unlzma.i.zip |
353 | * Archive: decompress_unlzma.i.zip |
354 | * Length Method Size Ratio Date Time CRC-32 Name |
355 | * -------- ------ ------- ----- ---- ---- ------ ---- |
356 | * 204372 Defl:N 35278 83% 09-06-09 14:23 0d056252 decompress_unlzma.i |
357 | * -------- ------- --- ------- |
358 | * 204372 35278 83% 1 file |
359 | * # unzip -v decompress_unlzma.i.zip |
360 | * Archive: decompress_unlzma.i.zip |
361 | * Length Date Time Name |
362 | * -------- ---- ---- ---- |
363 | * 204372 09-06-09 14:23 decompress_unlzma.i |
364 | * -------- ------- |
365 | * 204372 1 files |
366 | * # /usr/bin/unzip -l -qq decompress_unlzma.i.zip |
367 | * 204372 09-06-09 14:23 decompress_unlzma.i |
368 | * # /usr/bin/unzip -l -q decompress_unlzma.i.zip |
369 | * Length Date Time Name |
370 | * -------- ---- ---- ---- |
371 | * 204372 09-06-09 14:23 decompress_unlzma.i |
372 | * -------- ------- |
373 | * 204372 1 file |
374 | * # /usr/bin/unzip -l decompress_unlzma.i.zip |
375 | * Archive: decompress_unlzma.i.zip |
376 | * Length Date Time Name |
377 | * -------- ---- ---- ---- |
378 | * 204372 09-06-09 14:23 decompress_unlzma.i |
379 | * -------- ------- |
380 | * 204372 1 file |
381 | */ |
382 | |
383 | x_opt_seen = 0; |
384 | /* '-' makes getopt return 1 for non-options */ |
385 | while ((opt = getopt(argc, argv, "-d:lnopqxv")) != -1) { |
386 | switch (opt) { |
387 | case 'd': /* Extract to base directory */ |
388 | base_dir = optarg; |
389 | break; |
390 | |
391 | case 'l': /* List */ |
392 | listing = 1; |
393 | break; |
394 | |
395 | case 'n': /* Never overwrite existing files */ |
396 | overwrite = O_NEVER; |
397 | break; |
398 | |
399 | case 'o': /* Always overwrite existing files */ |
400 | overwrite = O_ALWAYS; |
401 | break; |
402 | |
403 | case 'p': /* Extract files to stdout and fall through to set verbosity */ |
404 | dst_fd = STDOUT_FILENO; |
405 | |
406 | case 'q': /* Be quiet */ |
407 | quiet++; |
408 | break; |
409 | |
410 | case 'v': /* Verbose list */ |
411 | IF_DESKTOP(verbose++;) |
412 | listing = 1; |
413 | break; |
414 | |
415 | case 'x': |
416 | x_opt_seen = 1; |
417 | break; |
418 | |
419 | case 1: |
420 | if (!src_fn) { |
421 | /* The zip file */ |
422 | /* +5: space for ".zip" and NUL */ |
423 | src_fn = xmalloc(strlen(optarg) + 5); |
424 | strcpy(src_fn, optarg); |
425 | } else if (!x_opt_seen) { |
426 | /* Include files */ |
427 | llist_add_to(&zaccept, optarg); |
428 | } else { |
429 | /* Exclude files */ |
430 | llist_add_to(&zreject, optarg); |
431 | } |
432 | break; |
433 | |
434 | default: |
435 | bb_show_usage(); |
436 | } |
437 | } |
438 | |
439 | #ifndef __GLIBC__ |
440 | /* |
441 | * This code is needed for non-GNU getopt |
442 | * which doesn't understand "-" in option string. |
443 | * The -x option won't work properly in this case: |
444 | * "unzip a.zip q -x w e" will be interpreted as |
445 | * "unzip a.zip q w e -x" = "unzip a.zip q w e" |
446 | */ |
447 | argv += optind; |
448 | if (argv[0]) { |
449 | /* +5: space for ".zip" and NUL */ |
450 | src_fn = xmalloc(strlen(argv[0]) + 5); |
451 | strcpy(src_fn, argv[0]); |
452 | while (*++argv) |
453 | llist_add_to(&zaccept, *argv); |
454 | } |
455 | #endif |
456 | |
457 | if (!src_fn) { |
458 | bb_show_usage(); |
459 | } |
460 | |
461 | /* Open input file */ |
462 | if (LONE_DASH(src_fn)) { |
463 | xdup2(STDIN_FILENO, zip_fd); |
464 | /* Cannot use prompt mode since zip data is arriving on STDIN */ |
465 | if (overwrite == O_PROMPT) |
466 | overwrite = O_NEVER; |
467 | } else { |
468 | static const char extn[][5] = { ".zip", ".ZIP" }; |
469 | char *ext = src_fn + strlen(src_fn); |
470 | int src_fd; |
471 | |
472 | i = 0; |
473 | for (;;) { |
474 | src_fd = open(src_fn, O_RDONLY); |
475 | if (src_fd >= 0) |
476 | break; |
477 | if (++i > 2) { |
478 | *ext = '\0'; |
479 | bb_error_msg_and_die("can't open %s[.zip]", src_fn); |
480 | } |
481 | strcpy(ext, extn[i - 1]); |
482 | } |
483 | xmove_fd(src_fd, zip_fd); |
484 | } |
485 | |
486 | /* Change dir if necessary */ |
487 | if (base_dir) |
488 | xchdir(base_dir); |
489 | |
490 | if (quiet <= 1) { /* not -qq */ |
491 | if (quiet == 0) |
492 | printf("Archive: %s\n", src_fn); |
493 | if (listing) { |
494 | puts(verbose ? |
495 | " Length Method Size Ratio Date Time CRC-32 Name\n" |
496 | "-------- ------ ------- ----- ---- ---- ------ ----" |
497 | : |
498 | " Length Date Time Name\n" |
499 | " -------- ---- ---- ----" |
500 | ); |
501 | } |
502 | } |
503 | |
504 | /* Example of an archive with one 0-byte long file named 'z' |
505 | * created by Zip 2.31 on Unix: |
506 | * 0000 [50 4b]03 04 0a 00 00 00 00 00 42 1a b8 3c 00 00 |PK........B..<..| |
507 | * sig........ vneed flags compr mtime mdate crc32> |
508 | * 0010 00 00 00 00 00 00 00 00 00 00 01 00 15 00 7a 55 |..............zU| |
509 | * >..... csize...... usize...... fnlen exlen fn ex> |
510 | * 0020 54 09 00 03 cc d3 f9 4b cc d3 f9 4b 55 78 04 00 |T......K...KUx..| |
511 | * >tra_field...................................... |
512 | * 0030 00 00 00 00[50 4b]01 02 17 03 0a 00 00 00 00 00 |....PK..........| |
513 | * ........... sig........ vmade vneed flags compr |
514 | * 0040 42 1a b8 3c 00 00 00 00 00 00 00 00 00 00 00 00 |B..<............| |
515 | * mtime mdate crc32...... csize...... usize...... |
516 | * 0050 01 00 0d 00 00 00 00 00 00 00 00 00 a4 81 00 00 |................| |
517 | * fnlen exlen clen. dnum. iattr eattr...... relofs> (eattr = rw-r--r--) |
518 | * 0060 00 00 7a 55 54 05 00 03 cc d3 f9 4b 55 78 00 00 |..zUT......KUx..| |
519 | * >..... fn extra_field........................... |
520 | * 0070 [50 4b]05 06 00 00 00 00 01 00 01 00 3c 00 00 00 |PK..........<...| |
521 | * 0080 34 00 00 00 00 00 |4.....| |
522 | */ |
523 | total_usize = 0; |
524 | total_size = 0; |
525 | total_entries = 0; |
526 | #if ENABLE_DESKTOP |
527 | cdf_offset = 0; |
528 | #endif |
529 | while (1) { |
530 | uint32_t magic; |
531 | mode_t dir_mode = 0777; |
532 | #if ENABLE_DESKTOP |
533 | mode_t file_mode = 0666; |
534 | #endif |
535 | |
536 | /* Check magic number */ |
537 | xread(zip_fd, &magic, 4); |
538 | /* Central directory? It's at the end, so exit */ |
539 | if (magic == ZIP_CDF_MAGIC) |
540 | break; |
541 | #if ENABLE_DESKTOP |
542 | /* Data descriptor? It was a streaming file, go on */ |
543 | if (magic == ZIP_DD_MAGIC) { |
544 | /* skip over duplicate crc32, cmpsize and ucmpsize */ |
545 | unzip_skip(3 * 4); |
546 | continue; |
547 | } |
548 | #endif |
549 | if (magic != ZIP_FILEHEADER_MAGIC) |
550 | bb_error_msg_and_die("invalid zip magic %08X", (int)magic); |
551 | |
552 | /* Read the file header */ |
553 | xread(zip_fd, zip_header.raw, ZIP_HEADER_LEN); |
554 | FIX_ENDIANNESS_ZIP(zip_header); |
555 | if ((zip_header.formatted.method != 0) && (zip_header.formatted.method != 8)) { |
556 | bb_error_msg_and_die("unsupported method %d", zip_header.formatted.method); |
557 | } |
558 | #if !ENABLE_DESKTOP |
559 | if (zip_header.formatted.zip_flags & SWAP_LE16(0x0009)) { |
560 | bb_error_msg_and_die("zip flags 1 and 8 are not supported"); |
561 | } |
562 | #else |
563 | if (zip_header.formatted.zip_flags & SWAP_LE16(0x0001)) { |
564 | /* 0x0001 - encrypted */ |
565 | bb_error_msg_and_die("zip flag 1 (encryption) is not supported"); |
566 | } |
567 | |
568 | if (cdf_offset != BAD_CDF_OFFSET) { |
569 | cdf_header_t cdf_header; |
570 | cdf_offset = read_next_cdf(cdf_offset, &cdf_header); |
571 | /* |
572 | * Note: cdf_offset can become BAD_CDF_OFFSET after the above call. |
573 | */ |
574 | if (zip_header.formatted.zip_flags & SWAP_LE16(0x0008)) { |
575 | /* 0x0008 - streaming. [u]cmpsize can be reliably gotten |
576 | * only from Central Directory. See unzip_doc.txt |
577 | */ |
578 | zip_header.formatted.crc32 = cdf_header.formatted.crc32; |
579 | zip_header.formatted.cmpsize = cdf_header.formatted.cmpsize; |
580 | zip_header.formatted.ucmpsize = cdf_header.formatted.ucmpsize; |
581 | } |
582 | if ((cdf_header.formatted.version_made_by >> 8) == 3) { |
583 | /* This archive is created on Unix */ |
584 | dir_mode = file_mode = (cdf_header.formatted.external_file_attributes >> 16); |
585 | } |
586 | } |
587 | if (cdf_offset == BAD_CDF_OFFSET |
588 | && (zip_header.formatted.zip_flags & SWAP_LE16(0x0008)) |
589 | ) { |
590 | /* If it's a streaming zip, we _require_ CDF */ |
591 | bb_error_msg_and_die("can't find file table"); |
592 | } |
593 | #endif |
594 | |
595 | /* Read filename */ |
596 | free(dst_fn); |
597 | dst_fn = xzalloc(zip_header.formatted.filename_len + 1); |
598 | xread(zip_fd, dst_fn, zip_header.formatted.filename_len); |
599 | |
600 | /* Skip extra header bytes */ |
601 | unzip_skip(zip_header.formatted.extra_len); |
602 | |
603 | /* Filter zip entries */ |
604 | if (find_list_entry(zreject, dst_fn) |
605 | || (zaccept && !find_list_entry(zaccept, dst_fn)) |
606 | ) { /* Skip entry */ |
607 | i = 'n'; |
608 | |
609 | } else { /* Extract entry */ |
610 | if (listing) { /* List entry */ |
611 | unsigned dostime = zip_header.formatted.modtime | (zip_header.formatted.moddate << 16); |
612 | if (!verbose) { |
613 | // " Length Date Time Name\n" |
614 | // " -------- ---- ---- ----" |
615 | printf( "%9u %02u-%02u-%02u %02u:%02u %s\n", |
616 | (unsigned)zip_header.formatted.ucmpsize, |
617 | (dostime & 0x01e00000) >> 21, |
618 | (dostime & 0x001f0000) >> 16, |
619 | (((dostime & 0xfe000000) >> 25) + 1980) % 100, |
620 | (dostime & 0x0000f800) >> 11, |
621 | (dostime & 0x000007e0) >> 5, |
622 | dst_fn); |
623 | total_usize += zip_header.formatted.ucmpsize; |
624 | } else { |
625 | unsigned long percents = zip_header.formatted.ucmpsize - zip_header.formatted.cmpsize; |
626 | percents = percents * 100; |
627 | if (zip_header.formatted.ucmpsize) |
628 | percents /= zip_header.formatted.ucmpsize; |
629 | // " Length Method Size Ratio Date Time CRC-32 Name\n" |
630 | // "-------- ------ ------- ----- ---- ---- ------ ----" |
631 | printf( "%8u Defl:N" "%9u%4u%% %02u-%02u-%02u %02u:%02u %08x %s\n", |
632 | (unsigned)zip_header.formatted.ucmpsize, |
633 | (unsigned)zip_header.formatted.cmpsize, |
634 | (unsigned)percents, |
635 | (dostime & 0x01e00000) >> 21, |
636 | (dostime & 0x001f0000) >> 16, |
637 | (((dostime & 0xfe000000) >> 25) + 1980) % 100, |
638 | (dostime & 0x0000f800) >> 11, |
639 | (dostime & 0x000007e0) >> 5, |
640 | zip_header.formatted.crc32, |
641 | dst_fn); |
642 | total_usize += zip_header.formatted.ucmpsize; |
643 | total_size += zip_header.formatted.cmpsize; |
644 | } |
645 | i = 'n'; |
646 | } else if (dst_fd == STDOUT_FILENO) { /* Extracting to STDOUT */ |
647 | i = -1; |
648 | } else if (last_char_is(dst_fn, '/')) { /* Extract directory */ |
649 | if (stat(dst_fn, &stat_buf) == -1) { |
650 | if (errno != ENOENT) { |
651 | bb_perror_msg_and_die("can't stat '%s'", dst_fn); |
652 | } |
653 | if (!quiet) { |
654 | printf(" creating: %s\n", dst_fn); |
655 | } |
656 | unzip_create_leading_dirs(dst_fn); |
657 | if (bb_make_directory(dst_fn, dir_mode, FILEUTILS_IGNORE_CHMOD_ERR)) { |
658 | xfunc_die(); |
659 | } |
660 | } else { |
661 | if (!S_ISDIR(stat_buf.st_mode)) { |
662 | bb_error_msg_and_die("'%s' exists but is not directory", dst_fn); |
663 | } |
664 | } |
665 | i = 'n'; |
666 | |
667 | } else { /* Extract file */ |
668 | check_file: |
669 | if (stat(dst_fn, &stat_buf) == -1) { /* File does not exist */ |
670 | if (errno != ENOENT) { |
671 | bb_perror_msg_and_die("can't stat '%s'", dst_fn); |
672 | } |
673 | i = 'y'; |
674 | } else { /* File already exists */ |
675 | if (overwrite == O_NEVER) { |
676 | i = 'n'; |
677 | } else if (S_ISREG(stat_buf.st_mode)) { /* File is regular file */ |
678 | if (overwrite == O_ALWAYS) { |
679 | i = 'y'; |
680 | } else { |
681 | printf("replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", dst_fn); |
682 | my_fgets80(key_buf); |
683 | i = key_buf[0]; |
684 | } |
685 | } else { /* File is not regular file */ |
686 | bb_error_msg_and_die("'%s' exists but is not regular file", dst_fn); |
687 | } |
688 | } |
689 | } |
690 | } |
691 | |
692 | switch (i) { |
693 | case 'A': |
694 | overwrite = O_ALWAYS; |
695 | case 'y': /* Open file and fall into unzip */ |
696 | unzip_create_leading_dirs(dst_fn); |
697 | #if ENABLE_DESKTOP |
698 | dst_fd = xopen3(dst_fn, O_WRONLY | O_CREAT | O_TRUNC, file_mode); |
699 | #else |
700 | dst_fd = xopen(dst_fn, O_WRONLY | O_CREAT | O_TRUNC); |
701 | #endif |
702 | case -1: /* Unzip */ |
703 | if (!quiet) { |
704 | printf(" inflating: %s\n", dst_fn); |
705 | } |
706 | unzip_extract(&zip_header, dst_fd); |
707 | if (dst_fd != STDOUT_FILENO) { |
708 | /* closing STDOUT is potentially bad for future business */ |
709 | close(dst_fd); |
710 | } |
711 | break; |
712 | |
713 | case 'N': |
714 | overwrite = O_NEVER; |
715 | case 'n': |
716 | /* Skip entry data */ |
717 | unzip_skip(zip_header.formatted.cmpsize); |
718 | break; |
719 | |
720 | case 'r': |
721 | /* Prompt for new name */ |
722 | printf("new name: "); |
723 | my_fgets80(key_buf); |
724 | free(dst_fn); |
725 | dst_fn = xstrdup(key_buf); |
726 | chomp(dst_fn); |
727 | goto check_file; |
728 | |
729 | default: |
730 | printf("error: invalid response [%c]\n", (char)i); |
731 | goto check_file; |
732 | } |
733 | |
734 | total_entries++; |
735 | } |
736 | |
737 | if (listing && quiet <= 1) { |
738 | if (!verbose) { |
739 | // " Length Date Time Name\n" |
740 | // " -------- ---- ---- ----" |
741 | printf( " -------- -------\n" |
742 | "%9lu" " %u files\n", |
743 | total_usize, total_entries); |
744 | } else { |
745 | unsigned long percents = total_usize - total_size; |
746 | percents = percents * 100; |
747 | if (total_usize) |
748 | percents /= total_usize; |
749 | // " Length Method Size Ratio Date Time CRC-32 Name\n" |
750 | // "-------- ------ ------- ----- ---- ---- ------ ----" |
751 | printf( "-------- ------- --- -------\n" |
752 | "%8lu" "%17lu%4u%% %u files\n", |
753 | total_usize, total_size, (unsigned)percents, |
754 | total_entries); |
755 | } |
756 | } |
757 | |
758 | return 0; |
759 | } |
760 |