aboutsummaryrefslogtreecommitdiff
path: root/archival/unzip.c
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2017-07-24 11:40:28 +0100
committerRon Yorston <rmy@pobox.com>2017-07-24 12:03:28 +0100
commit60411636073cdc08e8005f0de00098e6dd00eaf5 (patch)
tree58673b64af1e663280be81f798c8f66ce116d1dd /archival/unzip.c
parenteeceafbc5c4caf513c6d92b7d71ecb0ccd89a3f8 (diff)
parentb72f1ef17b97802d33f0ac522f64bea0f65442c5 (diff)
downloadbusybox-w32-60411636073cdc08e8005f0de00098e6dd00eaf5.tar.gz
busybox-w32-60411636073cdc08e8005f0de00098e6dd00eaf5.tar.bz2
busybox-w32-60411636073cdc08e8005f0de00098e6dd00eaf5.zip
Merge branch 'busybox' into merge
Diffstat (limited to 'archival/unzip.c')
-rw-r--r--archival/unzip.c205
1 files changed, 141 insertions, 64 deletions
diff --git a/archival/unzip.c b/archival/unzip.c
index 8dfc4e678..4c4feda82 100644
--- a/archival/unzip.c
+++ b/archival/unzip.c
@@ -17,23 +17,23 @@
17 * Zip64 + other methods 17 * Zip64 + other methods
18 */ 18 */
19//config:config UNZIP 19//config:config UNZIP
20//config: bool "unzip" 20//config: bool "unzip (24 kb)"
21//config: default y 21//config: default y
22//config: help 22//config: help
23//config: unzip will list or extract files from a ZIP archive, 23//config: unzip will list or extract files from a ZIP archive,
24//config: commonly found on DOS/WIN systems. The default behavior 24//config: commonly found on DOS/WIN systems. The default behavior
25//config: (with no options) is to extract the archive into the 25//config: (with no options) is to extract the archive into the
26//config: current directory. 26//config: current directory.
27//config: 27//config:
28//config:config FEATURE_UNZIP_CDF 28//config:config FEATURE_UNZIP_CDF
29//config: bool "Read and use Central Directory data" 29//config: bool "Read and use Central Directory data"
30//config: default y 30//config: default y
31//config: depends on UNZIP 31//config: depends on UNZIP
32//config: help 32//config: help
33//config: If you know that you only need to deal with simple 33//config: If you know that you only need to deal with simple
34//config: ZIP files without deleted/updated files, SFX archives etc, 34//config: ZIP files without deleted/updated files, SFX archives etc,
35//config: you can reduce code size by unselecting this option. 35//config: you can reduce code size by unselecting this option.
36//config: To support less trivial ZIPs, say Y. 36//config: To support less trivial ZIPs, say Y.
37//config: 37//config:
38//config:config FEATURE_UNZIP_BZIP2 38//config:config FEATURE_UNZIP_BZIP2
39//config: bool "Support compression method 12 (bzip2)" 39//config: bool "Support compression method 12 (bzip2)"
@@ -62,6 +62,7 @@
62//usage: "\n -l List contents (with -q for short form)" 62//usage: "\n -l List contents (with -q for short form)"
63//usage: "\n -n Never overwrite files (default: ask)" 63//usage: "\n -n Never overwrite files (default: ask)"
64//usage: "\n -o Overwrite" 64//usage: "\n -o Overwrite"
65//usage: "\n -j Do not restore paths"
65//usage: "\n -p Print to stdout" 66//usage: "\n -p Print to stdout"
66//usage: "\n -q Quiet" 67//usage: "\n -q Quiet"
67//usage: "\n -x FILE Exclude FILEs" 68//usage: "\n -x FILE Exclude FILEs"
@@ -320,6 +321,12 @@ static uint32_t read_next_cdf(uint32_t cdf_offset, cdf_header_t *cdf)
320}; 321};
321#endif 322#endif
322 323
324static void die_if_bad_fnamesize(unsigned sz)
325{
326 if (sz > 0xfff) /* more than 4k?! no funny business please */
327 bb_error_msg_and_die("bad archive");
328}
329
323static void unzip_skip(off_t skip) 330static void unzip_skip(off_t skip)
324{ 331{
325 if (skip != 0) 332 if (skip != 0)
@@ -337,6 +344,39 @@ static void unzip_create_leading_dirs(const char *fn)
337 free(name); 344 free(name);
338} 345}
339 346
347#if ENABLE_FEATURE_UNZIP_CDF
348static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn)
349{
350 char *target;
351
352 die_if_bad_fnamesize(zip->fmt.ucmpsize);
353
354 if (zip->fmt.method == 0) {
355 /* Method 0 - stored (not compressed) */
356 target = xzalloc(zip->fmt.ucmpsize + 1);
357 xread(zip_fd, target, zip->fmt.ucmpsize);
358 } else {
359#if 1
360 bb_error_msg_and_die("compressed symlink is not supported");
361#else
362 transformer_state_t xstate;
363 init_transformer_state(&xstate);
364 xstate.mem_output_size_max = zip->fmt.ucmpsize;
365 /* ...unpack... */
366 if (!xstate.mem_output_buf)
367 WTF();
368 target = xstate.mem_output_buf;
369 target = xrealloc(target, xstate.mem_output_size + 1);
370 target[xstate.mem_output_size] = '\0';
371#endif
372 }
373//TODO: libbb candidate
374 if (symlink(target, dst_fn))
375 bb_perror_msg_and_die("can't create symlink '%s'", dst_fn);
376 free(target);
377}
378#endif
379
340static void unzip_extract(zip_header_t *zip, int dst_fd) 380static void unzip_extract(zip_header_t *zip, int dst_fd)
341{ 381{
342 transformer_state_t xstate; 382 transformer_state_t xstate;
@@ -349,12 +389,6 @@ static void unzip_extract(zip_header_t *zip, int dst_fd)
349 return; 389 return;
350 } 390 }
351 391
352// NB: to support symlinks, need to extract symlink target. A-la:
353// xstate.mem_output_size_max = zip->fmt.ucmpsize;
354// ...unpack...
355// if (xstate.mem_output_buf) { success, xstate.mem_output_size is the size }
356// Although archives I've seen have fmt.method == 0 for symlinks.
357
358 init_transformer_state(&xstate); 392 init_transformer_state(&xstate);
359 xstate.bytes_in = zip->fmt.cmpsize; 393 xstate.bytes_in = zip->fmt.cmpsize;
360 xstate.src_fd = zip_fd; 394 xstate.src_fd = zip_fd;
@@ -414,16 +448,32 @@ static void my_fgets80(char *buf80)
414 } 448 }
415} 449}
416 450
451static int get_lstat_mode(const char *dst_fn)
452{
453 struct stat stat_buf;
454 if (lstat(dst_fn, &stat_buf) == -1) {
455 if (errno != ENOENT) {
456 bb_perror_msg_and_die("can't stat '%s'", dst_fn);
457 }
458 /* File does not exist */
459 return -1;
460 }
461 return stat_buf.st_mode;
462}
463
417int unzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 464int unzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
418int unzip_main(int argc, char **argv) 465int unzip_main(int argc, char **argv)
419{ 466{
420 enum { O_PROMPT, O_NEVER, O_ALWAYS }; 467 enum {
421 468 OPT_l = (1 << 0),
469 OPT_x = (1 << 1),
470 OPT_j = (1 << 2),
471 };
472 unsigned opts;
422 smallint quiet = 0; 473 smallint quiet = 0;
423 IF_NOT_FEATURE_UNZIP_CDF(const) smallint verbose = 0; 474 IF_NOT_FEATURE_UNZIP_CDF(const) smallint verbose = 0;
424 smallint listing = 0; 475 enum { O_PROMPT, O_NEVER, O_ALWAYS };
425 smallint overwrite = O_PROMPT; 476 smallint overwrite = O_PROMPT;
426 smallint x_opt_seen;
427 uint32_t cdf_offset; 477 uint32_t cdf_offset;
428 unsigned long total_usize; 478 unsigned long total_usize;
429 unsigned long total_size; 479 unsigned long total_size;
@@ -434,9 +484,8 @@ int unzip_main(int argc, char **argv)
434 llist_t *zaccept = NULL; 484 llist_t *zaccept = NULL;
435 llist_t *zreject = NULL; 485 llist_t *zreject = NULL;
436 char *base_dir = NULL; 486 char *base_dir = NULL;
437 int i, opt; 487 int i;
438 char key_buf[80]; /* must match size used by my_fgets80 */ 488 char key_buf[80]; /* must match size used by my_fgets80 */
439 struct stat stat_buf;
440 489
441/* -q, -l and -v: UnZip 5.52 of 28 February 2005, by Info-ZIP: 490/* -q, -l and -v: UnZip 5.52 of 28 February 2005, by Info-ZIP:
442 * 491 *
@@ -479,16 +528,16 @@ int unzip_main(int argc, char **argv)
479 * 204372 1 file 528 * 204372 1 file
480 */ 529 */
481 530
482 x_opt_seen = 0; 531 opts = 0;
483 /* '-' makes getopt return 1 for non-options */ 532 /* '-' makes getopt return 1 for non-options */
484 while ((opt = getopt(argc, argv, "-d:lnopqxv")) != -1) { 533 while ((i = getopt(argc, argv, "-d:lnopqxjv")) != -1) {
485 switch (opt) { 534 switch (i) {
486 case 'd': /* Extract to base directory */ 535 case 'd': /* Extract to base directory */
487 base_dir = optarg; 536 base_dir = optarg;
488 break; 537 break;
489 538
490 case 'l': /* List */ 539 case 'l': /* List */
491 listing = 1; 540 opts |= OPT_l;
492 break; 541 break;
493 542
494 case 'n': /* Never overwrite existing files */ 543 case 'n': /* Never overwrite existing files */
@@ -508,11 +557,15 @@ int unzip_main(int argc, char **argv)
508 557
509 case 'v': /* Verbose list */ 558 case 'v': /* Verbose list */
510 IF_FEATURE_UNZIP_CDF(verbose++;) 559 IF_FEATURE_UNZIP_CDF(verbose++;)
511 listing = 1; 560 opts |= OPT_l;
512 break; 561 break;
513 562
514 case 'x': 563 case 'x':
515 x_opt_seen = 1; 564 opts |= OPT_x;
565 break;
566
567 case 'j':
568 opts |= OPT_j;
516 break; 569 break;
517 570
518 case 1: 571 case 1:
@@ -521,7 +574,7 @@ int unzip_main(int argc, char **argv)
521 /* +5: space for ".zip" and NUL */ 574 /* +5: space for ".zip" and NUL */
522 src_fn = xmalloc(strlen(optarg) + 5); 575 src_fn = xmalloc(strlen(optarg) + 5);
523 strcpy(src_fn, optarg); 576 strcpy(src_fn, optarg);
524 } else if (!x_opt_seen) { 577 } else if (!(opts & OPT_x)) {
525 /* Include files */ 578 /* Include files */
526 llist_add_to(&zaccept, optarg); 579 llist_add_to(&zaccept, optarg);
527 } else { 580 } else {
@@ -589,7 +642,7 @@ int unzip_main(int argc, char **argv)
589 if (quiet <= 1) { /* not -qq */ 642 if (quiet <= 1) { /* not -qq */
590 if (quiet == 0) 643 if (quiet == 0)
591 printf("Archive: %s\n", src_fn); 644 printf("Archive: %s\n", src_fn);
592 if (listing) { 645 if (opts & OPT_l) {
593 puts(verbose ? 646 puts(verbose ?
594 " Length Method Size Cmpr Date Time CRC-32 Name\n" 647 " Length Method Size Cmpr Date Time CRC-32 Name\n"
595 "-------- ------ ------- ---- ---------- ----- -------- ----" 648 "-------- ------ ------- ---- ---------- ----- -------- ----"
@@ -722,7 +775,6 @@ int unzip_main(int argc, char **argv)
722 if ((cdf.fmt.version_made_by >> 8) == 3) { 775 if ((cdf.fmt.version_made_by >> 8) == 3) {
723 /* This archive is created on Unix */ 776 /* This archive is created on Unix */
724 dir_mode = file_mode = (cdf.fmt.external_attributes >> 16); 777 dir_mode = file_mode = (cdf.fmt.external_attributes >> 16);
725//TODO: if (S_ISLNK(file_mode)) this is a symlink
726 } 778 }
727 } 779 }
728#endif 780#endif
@@ -740,15 +792,22 @@ int unzip_main(int argc, char **argv)
740 792
741 /* Read filename */ 793 /* Read filename */
742 free(dst_fn); 794 free(dst_fn);
795 die_if_bad_fnamesize(zip.fmt.filename_len);
743 dst_fn = xzalloc(zip.fmt.filename_len + 1); 796 dst_fn = xzalloc(zip.fmt.filename_len + 1);
744 xread(zip_fd, dst_fn, zip.fmt.filename_len); 797 xread(zip_fd, dst_fn, zip.fmt.filename_len);
745
746 /* Skip extra header bytes */ 798 /* Skip extra header bytes */
747 unzip_skip(zip.fmt.extra_len); 799 unzip_skip(zip.fmt.extra_len);
748 800
749 /* Guard against "/abspath", "/../" and similar attacks */ 801 /* Guard against "/abspath", "/../" and similar attacks */
750 overlapping_strcpy(dst_fn, strip_unsafe_prefix(dst_fn)); 802 overlapping_strcpy(dst_fn, strip_unsafe_prefix(dst_fn));
751 803
804 if (opts & OPT_j) /* Strip paths? */
805 overlapping_strcpy(dst_fn, bb_basename(dst_fn));
806
807 /* Did this strip everything ("DIR/" case)? Then skip */
808 if (!dst_fn[0])
809 goto skip_cmpsize;
810
752 /* Filter zip entries */ 811 /* Filter zip entries */
753 if (find_list_entry(zreject, dst_fn) 812 if (find_list_entry(zreject, dst_fn)
754 || (zaccept && !find_list_entry(zaccept, dst_fn)) 813 || (zaccept && !find_list_entry(zaccept, dst_fn))
@@ -756,7 +815,7 @@ int unzip_main(int argc, char **argv)
756 goto skip_cmpsize; 815 goto skip_cmpsize;
757 } 816 }
758 817
759 if (listing) { 818 if (opts & OPT_l) {
760 /* List entry */ 819 /* List entry */
761 char dtbuf[sizeof("mm-dd-yyyy hh:mm")]; 820 char dtbuf[sizeof("mm-dd-yyyy hh:mm")];
762 sprintf(dtbuf, "%02u-%02u-%04u %02u:%02u", 821 sprintf(dtbuf, "%02u-%02u-%04u %02u:%02u",
@@ -814,11 +873,11 @@ int unzip_main(int argc, char **argv)
814 goto do_extract; 873 goto do_extract;
815 } 874 }
816 if (last_char_is(dst_fn, '/')) { 875 if (last_char_is(dst_fn, '/')) {
876 int mode;
877
817 /* Extract directory */ 878 /* Extract directory */
818 if (stat(dst_fn, &stat_buf) == -1) { 879 mode = get_lstat_mode(dst_fn);
819 if (errno != ENOENT) { 880 if (mode == -1) { /* ENOENT */
820 bb_perror_msg_and_die("can't stat '%s'", dst_fn);
821 }
822 if (!quiet) { 881 if (!quiet) {
823 printf(" creating: %s\n", dst_fn); 882 printf(" creating: %s\n", dst_fn);
824 } 883 }
@@ -827,7 +886,7 @@ int unzip_main(int argc, char **argv)
827 xfunc_die(); 886 xfunc_die();
828 } 887 }
829 } else { 888 } else {
830 if (!S_ISDIR(stat_buf.st_mode)) { 889 if (!S_ISDIR(mode)) {
831 bb_error_msg_and_die("'%s' exists but is not a %s", 890 bb_error_msg_and_die("'%s' exists but is not a %s",
832 dst_fn, "directory"); 891 dst_fn, "directory");
833 } 892 }
@@ -835,29 +894,33 @@ int unzip_main(int argc, char **argv)
835 goto skip_cmpsize; 894 goto skip_cmpsize;
836 } 895 }
837 check_file: 896 check_file:
838 /* Extract file */ 897 /* Does target file already exist? */
839 if (stat(dst_fn, &stat_buf) == -1) { 898 {
840 /* File does not exist */ 899 int mode = get_lstat_mode(dst_fn);
841 if (errno != ENOENT) { 900 if (mode == -1) {
842 bb_perror_msg_and_die("can't stat '%s'", dst_fn); 901 /* ENOENT: does not exist */
902 goto do_open_and_extract;
843 } 903 }
844 goto do_open_and_extract; 904 if (overwrite == O_NEVER) {
845 } 905 goto skip_cmpsize;
846 /* File already exists */ 906 }
847 if (overwrite == O_NEVER) { 907 if (!S_ISREG(mode)) {
848 goto skip_cmpsize; 908 fishy:
849 } 909 bb_error_msg_and_die("'%s' exists but is not a %s",
850 if (!S_ISREG(stat_buf.st_mode)) { 910 dst_fn, "regular file");
851 /* File is not regular file */ 911 }
852 bb_error_msg_and_die("'%s' exists but is not a %s", 912 if (overwrite == O_ALWAYS) {
853 dst_fn, "regular file"); 913 goto do_open_and_extract;
914 }
915 printf("replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", dst_fn);
916 my_fgets80(key_buf);
917 /* User input could take a long time. Is it still a regular file? */
918 mode = get_lstat_mode(dst_fn);
919 if (!S_ISREG(mode))
920 goto fishy;
854 } 921 }
855 /* File is regular file */
856 if (overwrite == O_ALWAYS)
857 goto do_open_and_extract;
858 printf("replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", dst_fn);
859 my_fgets80(key_buf);
860 922
923 /* Extract (or skip) it */
861 switch (key_buf[0]) { 924 switch (key_buf[0]) {
862 case 'A': 925 case 'A':
863 overwrite = O_ALWAYS; 926 overwrite = O_ALWAYS;
@@ -865,9 +928,15 @@ int unzip_main(int argc, char **argv)
865 do_open_and_extract: 928 do_open_and_extract:
866 unzip_create_leading_dirs(dst_fn); 929 unzip_create_leading_dirs(dst_fn);
867#if ENABLE_FEATURE_UNZIP_CDF 930#if ENABLE_FEATURE_UNZIP_CDF
868 dst_fd = xopen3(dst_fn, O_WRONLY | O_CREAT | O_TRUNC, file_mode); 931 dst_fd = -1;
932 if (!S_ISLNK(file_mode)) {
933 dst_fd = xopen3(dst_fn,
934 O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW,
935 file_mode);
936 }
869#else 937#else
870 dst_fd = xopen(dst_fn, O_WRONLY | O_CREAT | O_TRUNC); 938 /* O_NOFOLLOW defends against symlink attacks */
939 dst_fd = xopen(dst_fn, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW);
871#endif 940#endif
872 do_extract: 941 do_extract:
873 if (!quiet) { 942 if (!quiet) {
@@ -875,10 +944,18 @@ int unzip_main(int argc, char **argv)
875 ? " extracting: %s\n" 944 ? " extracting: %s\n"
876 : */ " inflating: %s\n", dst_fn); 945 : */ " inflating: %s\n", dst_fn);
877 } 946 }
878 unzip_extract(&zip, dst_fd); 947#if ENABLE_FEATURE_UNZIP_CDF
879 if (dst_fd != STDOUT_FILENO) { 948 if (S_ISLNK(file_mode)) {
880 /* closing STDOUT is potentially bad for future business */ 949 if (dst_fd != STDOUT_FILENO) /* not -p? */
881 close(dst_fd); 950 unzip_extract_symlink(&zip, dst_fn);
951 } else
952#endif
953 {
954 unzip_extract(&zip, dst_fd);
955 if (dst_fd != STDOUT_FILENO) {
956 /* closing STDOUT is potentially bad for future business */
957 close(dst_fd);
958 }
882 } 959 }
883 break; 960 break;
884 961
@@ -906,7 +983,7 @@ int unzip_main(int argc, char **argv)
906 total_entries++; 983 total_entries++;
907 } 984 }
908 985
909 if (listing && quiet <= 1) { 986 if ((opts & OPT_l) && quiet <= 1) {
910 if (!verbose) { 987 if (!verbose) {
911 // " Length Date Time Name\n" 988 // " Length Date Time Name\n"
912 // "--------- ---------- ----- ----" 989 // "--------- ---------- ----- ----"