05dbc72dd9765389469c522bae8bc5e5192d263c
[oweals/busybox.git] / archival / unzip.c
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  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23  *
24  */
25
26 /* For reference see
27  * http://www.pkware.com/company/standards/appnote/
28  * http://www.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip
29  */
30
31 /* TODO
32  * Endian issues
33  * Zip64 + other methods
34  * Improve handling of zip format, ie.
35  * - deferred CRC, comp. & uncomp. lengths (zip header flags bit 3)
36  * - unix file permissions, etc.
37  * - central directory
38  */
39
40 #include <fcntl.h>
41 #include <getopt.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <errno.h>
46 #include "unarchive.h"
47 #include "busybox.h"
48
49 #if (BYTE_ORDER == BIG_ENDIAN)
50 static inline unsigned short
51 __swap16(unsigned short x) {
52         return (((uint16_t)(x) & 0xFF) << 8) | (((uint16_t)(x) & 0xFF00) >> 8);
53 }
54
55 static inline uint32_t
56 __swap32(uint32_t x) {
57          return (((x & 0xFF) << 24) |
58                 ((x & 0xFF00) << 8) |
59                 ((x & 0xFF0000) >> 8) |
60                 ((x & 0xFF000000) >> 24));
61 }
62 #else
63 #define __swap16(x) (x)
64 #define __swap32(x) (x)
65 #endif
66
67 #define ZIP_FILEHEADER_MAGIC            __swap32(0x04034b50)
68 #define ZIP_CDS_MAGIC                   __swap32(0x02014b50)
69 #define ZIP_CDS_END_MAGIC               __swap32(0x06054b50)
70 #define ZIP_DD_MAGIC                    __swap32(0x08074b50)
71
72 extern unsigned int gunzip_crc;
73 extern unsigned int gunzip_bytes_out;
74
75 typedef union {
76         unsigned char raw[26];
77         struct {
78                 unsigned short version; /* 0-1 */
79                 unsigned short flags;   /* 2-3 */
80                 unsigned short method;  /* 4-5 */
81                 unsigned short modtime; /* 6-7 */
82                 unsigned short moddate; /* 8-9 */
83                 unsigned int crc32 __attribute__ ((packed));    /* 10-13 */
84                 unsigned int cmpsize __attribute__ ((packed));  /* 14-17 */
85                 unsigned int ucmpsize __attribute__ ((packed)); /* 18-21 */
86                 unsigned short filename_len;    /* 22-23 */
87                 unsigned short extra_len;               /* 24-25 */
88         } formated __attribute__ ((packed));
89 } zip_header_t;
90
91 static void unzip_skip(int fd, off_t skip)
92 {
93         if (lseek(fd, skip, SEEK_CUR) == (off_t)-1) {
94                 if ((errno != ESPIPE) || (bb_copyfd_size(fd, -1, skip) != skip)) {
95                         bb_error_msg_and_die("Seek failure");
96                 }
97         }
98 }
99
100 static void unzip_read(int fd, void *buf, size_t count)
101 {
102         if (bb_xread(fd, buf, count) != count) {
103                 bb_error_msg_and_die("Read failure");
104         }
105 }
106
107 static void unzip_create_leading_dirs(char *fn)
108 {
109         /* Create all leading directories */
110         char *name = bb_xstrdup(fn);
111         if (bb_make_directory(dirname(name), 0777, FILEUTILS_RECUR)) {
112                 bb_error_msg_and_die("Failed to create directory");
113         }
114         free(name);
115 }
116
117 static void unzip_extract(zip_header_t *zip_header, int src_fd, int dst_fd)
118 {
119         if (zip_header->formated.method == 0) {
120                 /* Method 0 - stored (not compressed) */
121                 int size = zip_header->formated.ucmpsize;
122                 if (size && (bb_copyfd_size(src_fd, dst_fd, size) != size)) {
123                         bb_error_msg_and_die("Cannot complete extraction");
124                 }
125
126         } else {
127                 /* Method 8 - inflate */
128                 inflate_init(zip_header->formated.cmpsize);
129                 inflate_unzip(src_fd, dst_fd);
130                 inflate_cleanup();
131                 /* Validate decompression - crc */
132                 if (zip_header->formated.crc32 != (gunzip_crc ^ 0xffffffffL)) {
133                         bb_error_msg("Invalid compressed data--crc error");
134                 }
135                 /* Validate decompression - size */
136                 if (zip_header->formated.ucmpsize != gunzip_bytes_out) {
137                         bb_error_msg("Invalid compressed data--length error");
138                 }
139         }
140 }
141
142 extern int unzip_main(int argc, char **argv)
143 {
144         zip_header_t zip_header;
145         enum {v_silent, v_normal, v_list} verbosity = v_normal;
146         enum {o_prompt, o_never, o_always} overwrite = o_prompt;
147         unsigned int total_size = 0;
148         unsigned int total_entries = 0;
149         int src_fd = -1, dst_fd = -1;
150         char *src_fn = NULL, *dst_fn = NULL;
151         llist_t *accept = NULL;
152         llist_t *reject = NULL;
153         char *base_dir = NULL;
154         int i, opt, opt_range = 0, list_header_done = 0;
155         char key_buf[512];
156         struct stat stat_buf;
157
158         while((opt = getopt(argc, argv, "-d:lnopqx")) != -1) {
159                 switch(opt_range) {
160                 case 0: /* Options */
161                         switch(opt) {
162                         case 'l': /* List */
163                                 verbosity = v_list;
164                                 break;
165
166                         case 'n': /* Never overwrite existing files */
167                                 overwrite = o_never;
168                                 break;
169
170                         case 'o': /* Always overwrite existing files */
171                                 overwrite = o_always;
172                                 break;
173
174                         case 'p': /* Extract files to stdout and fall through to set verbosity */
175                                 dst_fd = STDOUT_FILENO;
176
177                         case 'q': /* Be quiet */
178                                 verbosity = (verbosity == v_normal) ? v_silent : verbosity;
179                                 break;
180
181                         case 1 : /* The zip file */
182                                 src_fn = bb_xstrndup(optarg, strlen(optarg)+4);
183                                 opt_range++;
184                                 break;
185
186                         default:
187                                 bb_show_usage();
188
189                         }
190                         break;
191
192                 case 1: /* Include files */
193                         if (opt == 1) {
194                                 accept = llist_add_to(accept, optarg);
195
196                         } else if (opt == 'd') {
197                                 base_dir = optarg;
198                                 opt_range += 2;
199
200                         } else if (opt == 'x') {
201                                 opt_range++;
202
203                         } else {
204                                 bb_show_usage();
205                         }
206                         break;
207
208                 case 2 : /* Exclude files */
209                         if (opt == 1) {
210                                 reject = llist_add_to(reject, optarg);
211
212                         } else if (opt == 'd') { /* Extract to base directory */
213                                 base_dir = optarg;
214                                 opt_range++;
215
216                         } else {
217                                 bb_show_usage();
218                         }
219                         break;
220
221                 default:
222                         bb_show_usage();
223                 }
224         }
225
226         if (src_fn == NULL) {
227                 bb_show_usage();
228         }
229
230         /* Open input file */
231         if (strcmp("-", src_fn) == 0) {
232                 src_fd = STDIN_FILENO;
233                 /* Cannot use prompt mode since zip data is arriving on STDIN */
234                 overwrite = (overwrite == o_prompt) ? o_never : overwrite;
235
236         } else {
237                 char *extn[] = {"", ".zip", ".ZIP"};
238                 int orig_src_fn_len = strlen(src_fn);
239                 for(i = 0; (i < 3) && (src_fd == -1); i++) {
240                         strcpy(src_fn + orig_src_fn_len, extn[i]);
241                         src_fd = open(src_fn, O_RDONLY);
242                 }
243                 if (src_fd == -1) {
244                         src_fn[orig_src_fn_len] = 0;
245                         bb_error_msg_and_die("Cannot open %s, %s.zip, %s.ZIP", src_fn, src_fn, src_fn);
246                 }
247         }
248
249         /* Change dir if necessary */
250         if (base_dir && chdir(base_dir)) {
251                 bb_perror_msg_and_die("Cannot chdir");
252         }
253
254         if (verbosity != v_silent)
255                 printf("Archive:  %s\n", src_fn);
256
257         while (1) {
258                 unsigned int magic;
259
260                 /* Check magic number */
261                 unzip_read(src_fd, &magic, 4);
262                 if (magic == ZIP_CDS_MAGIC) {
263                         break;
264                 } else if (magic != ZIP_FILEHEADER_MAGIC) {
265                         bb_error_msg_and_die("Invalid zip magic %08X", magic);
266                 }
267
268                 /* Read the file header */
269                 unzip_read(src_fd, zip_header.raw, 26);
270 #if (BYTE_ORDER == BIG_ENDIAN)
271                 zip_header.formated.version = __swap16(zip_header.formated.version);
272                 zip_header.formated.flags = __swap16(zip_header.formated.flags);
273                 zip_header.formated.method = __swap16(zip_header.formated.method);
274                 zip_header.formated.modtime = __swap16(zip_header.formated.modtime);
275                 zip_header.formated.moddate = __swap16(zip_header.formated.moddate);
276                 zip_header.formated.crc32 = __swap32(zip_header.formated.crc32);
277                 zip_header.formated.cmpsize = __swap32(zip_header.formated.cmpsize);
278                 zip_header.formated.ucmpsize = __swap32(zip_header.formated.ucmpsize);
279                 zip_header.formated.filename_len = __swap16(zip_header.formated.filename_len);
280                 zip_header.formated.extra_len = __swap16(zip_header.formated.extra_len);
281 #endif
282                 if ((zip_header.formated.method != 0) && (zip_header.formated.method != 8)) {
283                         bb_error_msg_and_die("Unsupported compression method %d", zip_header.formated.method);
284                 }
285
286                 /* Read filename */
287                 free(dst_fn);
288                 dst_fn = xmalloc(zip_header.formated.filename_len + 1);
289                 unzip_read(src_fd, dst_fn, zip_header.formated.filename_len);
290                 dst_fn[zip_header.formated.filename_len] = 0;
291
292                 /* Skip extra header bytes */
293                 unzip_skip(src_fd, zip_header.formated.extra_len);
294
295                 if ((verbosity == v_list) && !list_header_done){
296                         printf("  Length     Date   Time    Name\n");
297                         printf(" --------    ----   ----    ----\n");
298                         list_header_done = 1;
299                 }
300
301                 /* Filter zip entries */
302                 if (find_list_entry(reject, dst_fn) ||
303                         (accept && !find_list_entry(accept, dst_fn))) { /* Skip entry */
304                         i = 'n';
305
306                 } else { /* Extract entry */
307                         total_size += zip_header.formated.ucmpsize;
308
309                         if (verbosity == v_list) { /* List entry */
310                                 unsigned int dostime = zip_header.formated.modtime | (zip_header.formated.moddate << 16);
311                                 printf("%9u  %02u-%02u-%02u %02u:%02u   %s\n",
312                                            zip_header.formated.ucmpsize,
313                                            (dostime & 0x01e00000) >> 21,
314                                            (dostime & 0x001f0000) >> 16,
315                                            (((dostime & 0xfe000000) >> 25) + 1980) % 100,
316                                            (dostime & 0x0000f800) >> 11,
317                                            (dostime & 0x000007e0) >> 5,
318                                            dst_fn);
319                                 total_entries++;
320                                 i = 'n';
321
322                         } else if (dst_fd == STDOUT_FILENO) { /* Extracting to STDOUT */
323                                 i = -1;
324
325                         } else if (last_char_is(dst_fn, '/')) { /* Extract directory */
326                                 if (stat(dst_fn, &stat_buf) == -1) {
327                                         if (errno != ENOENT) {
328                                                 bb_perror_msg_and_die("Cannot stat '%s'",dst_fn);
329                                         }
330                                         if (verbosity == v_normal) {
331                                                 printf("   creating: %s\n", dst_fn);
332                                         }
333                                         unzip_create_leading_dirs(dst_fn);
334                                         if (bb_make_directory(dst_fn, 0777, 0)) {
335                                                 bb_error_msg_and_die("Failed to create directory");
336                                         }
337                                 } else {
338                                         if (!S_ISDIR(stat_buf.st_mode)) {
339                                                 bb_error_msg_and_die("'%s' exists but is not directory", dst_fn);
340                                         }
341                                 }
342                                 i = 'n';
343
344                         } else {  /* Extract file */
345                         _check_file:
346                                 if (stat(dst_fn, &stat_buf) == -1) { /* File does not exist */
347                                         if (errno != ENOENT) {
348                                                 bb_perror_msg_and_die("Cannot stat '%s'",dst_fn);
349                                         }
350                                         i = 'y';
351
352                                 } else { /* File already exists */
353                                         if (overwrite == o_never) {
354                                                 i = 'n';
355
356                                         } else if (S_ISREG(stat_buf.st_mode)) { /* File is regular file */
357                                                 if (overwrite == o_always) {
358                                                         i = 'y';
359                                                 } else {
360                                                         printf("replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", dst_fn);
361                                                         if (!fgets(key_buf, 512, stdin)) {
362                                                                 bb_perror_msg_and_die("Cannot read input");
363                                                         }
364                                                         i = key_buf[0];
365                                                 }
366
367                                         } else { /* File is not regular file */
368                                                 bb_error_msg_and_die("'%s' exists but is not regular file",dst_fn);
369                                         }
370                                 }
371                         }
372                 }
373
374                 switch (i) {
375                 case 'A':
376                         overwrite = o_always;
377                 case 'y': /* Open file and fall into unzip */
378                         unzip_create_leading_dirs(dst_fn);
379                         dst_fd = bb_xopen(dst_fn, O_WRONLY | O_CREAT);
380                 case -1: /* Unzip */
381                         if (verbosity == v_normal) {
382                                 printf("  inflating: %s\n", dst_fn);
383                         }
384                         unzip_extract(&zip_header, src_fd, dst_fd);
385                         if (dst_fd != STDOUT_FILENO) {
386                                 /* closing STDOUT is potentially bad for future business */
387                                 close(dst_fd);
388                         }
389                         break;
390
391                 case 'N':
392                         overwrite = o_never;
393                 case 'n':
394                         /* Skip entry data */
395                         unzip_skip(src_fd, zip_header.formated.cmpsize);
396                         break;
397
398                 case 'r':
399                         /* Prompt for new name */
400                         printf("new name: ");
401                         if (!fgets(key_buf, 512, stdin)) {
402                                 bb_perror_msg_and_die("Cannot read input");
403                         }
404                         free(dst_fn);
405                         dst_fn = bb_xstrdup(key_buf);
406                         chomp(dst_fn);
407                         goto _check_file;
408
409                 default:
410                         printf("error:  invalid response [%c]\n",(char)i);
411                         goto _check_file;
412                 }
413
414                 /* Data descriptor section */
415                 if (zip_header.formated.flags & 4) {
416                         /* skip over duplicate crc, compressed size and uncompressed size */
417                         unzip_skip(src_fd, 12);
418                 }
419         }
420
421         if (verbosity == v_list) {
422                 printf(" --------                   -------\n");
423                 printf("%9d                   %d files\n", total_size, total_entries);
424         }
425
426         return(EXIT_SUCCESS);
427 }
428
429 /* END CODE */
430 /*
431 Local Variables:
432 c-file-style: "linux"
433 c-basic-offset: 4
434 tab-width: 4
435 End:
436 */