diff options
author | Ron Yorston <rmy@pobox.com> | 2017-07-24 11:40:28 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2017-07-24 12:03:28 +0100 |
commit | 60411636073cdc08e8005f0de00098e6dd00eaf5 (patch) | |
tree | 58673b64af1e663280be81f798c8f66ce116d1dd /archival/unzip.c | |
parent | eeceafbc5c4caf513c6d92b7d71ecb0ccd89a3f8 (diff) | |
parent | b72f1ef17b97802d33f0ac522f64bea0f65442c5 (diff) | |
download | busybox-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.c | 205 |
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 | ||
324 | static 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 | |||
323 | static void unzip_skip(off_t skip) | 330 | static 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 | ||
348 | static 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 | |||
340 | static void unzip_extract(zip_header_t *zip, int dst_fd) | 380 | static 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 | ||
451 | static 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 | |||
417 | int unzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | 464 | int unzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
418 | int unzip_main(int argc, char **argv) | 465 | int 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 | // "--------- ---------- ----- ----" |