Path: blob/main/contrib/libarchive/unzip/bsdunzip.c
39536 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2009, 2010 Joerg Sonnenberger <[email protected]>4* Copyright (c) 2007-2008 Dag-Erling Smørgrav5* All rights reserved.6*/78#include "bsdunzip_platform.h"910#include "la_queue.h"11#include "la_getline.h"12#ifdef HAVE_SYS_STAT_H13#include <sys/stat.h>14#endif1516#ifdef HAVE_CTYPE_H17#include <ctype.h>18#endif19#ifdef HAVE_ERRNO_H20#include <errno.h>21#endif22#ifdef HAVE_FCNTL_H23#include <fcntl.h>24#endif25#ifdef HAVE_FNMATCH_H26#include <fnmatch.h>27#endif28#ifdef HAVE_LOCALE_H29#include <locale.h>30#endif31#ifdef HAVE_STDARG_H32#include <stdarg.h>33#endif34#include <stdio.h>35#ifdef HAVE_STDLIB_H36#include <stdlib.h>37#endif38#ifdef HAVE_STRING_H39#include <string.h>40#endif41#ifdef HAVE_UNISTD_H42#include <unistd.h>43#endif44#if ((!defined(HAVE_UTIMENSAT) && defined(HAVE_LUTIMES)) || \45(!defined(HAVE_FUTIMENS) && defined(HAVE_FUTIMES)))46#ifdef HAVE_SYS_TIME_H47#include <sys/time.h>48#endif49#endif50#ifdef HAVE_GETOPT_OPTRESET51#include <getopt.h>52#endif5354#include "bsdunzip.h"55#include "passphrase.h"56#include "err.h"5758/* command-line options */59static int a_opt; /* convert EOL */60static int C_opt; /* match case-insensitively */61static int c_opt; /* extract to stdout */62static const char *d_arg; /* directory */63static int f_opt; /* update existing files only */64static const char *O_arg; /* encoding */65static int j_opt; /* junk directories */66static int L_opt; /* lowercase names */67static int n_opt; /* never overwrite */68static int o_opt; /* always overwrite */69static int p_opt; /* extract to stdout, quiet */70static const char *P_arg; /* passphrase */71static int q_opt; /* quiet */72static int t_opt; /* test */73static int u_opt; /* update */74static int v_opt; /* verbose/list */75static const char *y_str = ""; /* 4 digit year */76static int Z1_opt; /* zipinfo mode list files only */77static int version_opt; /* version string */7879/* debug flag */80static int unzip_debug;8182/* zipinfo mode */83static int zipinfo_mode;8485/* running on tty? */86static int tty;8788/* processing exclude list */89static int unzip_exclude_mode = 0;9091int bsdunzip_optind;9293/* convenience macro */94/* XXX should differentiate between ARCHIVE_{WARN,FAIL,RETRY} */95#define ac(call) \96do { \97int acret = (call); \98if (acret != ARCHIVE_OK) \99errorx("%s", archive_error_string(a)); \100} while (0)101102/*103* Indicates that last info() did not end with EOL. This helps error() et104* al. avoid printing an error message on the same line as an incomplete105* informational message.106*/107static int noeol;108109/* for an interactive passphrase input */110static char *passphrase_buf;111112/* fatal error message + errno */113static void __LA_NORETURN114error(const char *fmt, ...)115{116va_list ap;117118if (noeol)119fprintf(stdout, "\n");120fflush(stdout);121fprintf(stderr, "unzip: ");122va_start(ap, fmt);123vfprintf(stderr, fmt, ap);124va_end(ap);125fprintf(stderr, ": %s\n", strerror(errno));126exit(EXIT_FAILURE);127}128129/* fatal error message, no errno */130static void __LA_NORETURN131errorx(const char *fmt, ...)132{133va_list ap;134135if (noeol)136fprintf(stdout, "\n");137fflush(stdout);138fprintf(stderr, "unzip: ");139va_start(ap, fmt);140vfprintf(stderr, fmt, ap);141va_end(ap);142fprintf(stderr, "\n");143exit(EXIT_FAILURE);144}145146/* non-fatal error message + errno */147static void148warning(const char *fmt, ...)149{150va_list ap;151152if (noeol)153fprintf(stdout, "\n");154fflush(stdout);155fprintf(stderr, "unzip: ");156va_start(ap, fmt);157vfprintf(stderr, fmt, ap);158va_end(ap);159fprintf(stderr, ": %s\n", strerror(errno));160}161162/* non-fatal error message, no errno */163static void164warningx(const char *fmt, ...)165{166va_list ap;167168if (noeol)169fprintf(stdout, "\n");170fflush(stdout);171fprintf(stderr, "unzip: ");172va_start(ap, fmt);173vfprintf(stderr, fmt, ap);174va_end(ap);175fprintf(stderr, "\n");176}177178/* informational message (if not -q) */179static void180info(const char *fmt, ...)181{182va_list ap;183184if (q_opt && !unzip_debug)185return;186va_start(ap, fmt);187vfprintf(stdout, fmt, ap);188va_end(ap);189fflush(stdout);190191if (*fmt == '\0')192noeol = 1;193else194noeol = fmt[strlen(fmt) - 1] != '\n';195}196197/* debug message (if unzip_debug) */198static void199debug(const char *fmt, ...)200{201va_list ap;202203if (!unzip_debug)204return;205va_start(ap, fmt);206vfprintf(stderr, fmt, ap);207va_end(ap);208fflush(stderr);209210if (*fmt == '\0')211noeol = 1;212else213noeol = fmt[strlen(fmt) - 1] != '\n';214}215216/* duplicate a path name, possibly converting to lower case */217static char *218pathdup(const char *path)219{220char *str;221size_t i, len;222223if (path == NULL || path[0] == '\0')224return (NULL);225226len = strlen(path);227while (len && path[len - 1] == '/')228len--;229if ((str = malloc(len + 1)) == NULL) {230errno = ENOMEM;231error("malloc()");232}233if (L_opt) {234for (i = 0; i < len; ++i)235str[i] = (char)tolower((unsigned char)path[i]);236} else {237memcpy(str, path, len);238}239str[len] = '\0';240241return (str);242}243244/* concatenate two path names */245static char *246pathcat(const char *prefix, const char *path)247{248char *str;249size_t prelen, len;250251prelen = prefix ? strlen(prefix) + 1 : 0;252len = strlen(path) + 1;253if ((str = malloc(prelen + len)) == NULL) {254errno = ENOMEM;255error("malloc()");256}257if (prefix) {258memcpy(str, prefix, prelen); /* includes zero */259str[prelen - 1] = '/'; /* splat zero */260}261memcpy(str + prelen, path, len); /* includes zero */262263return (str);264}265266/*267* Pattern lists for include / exclude processing268*/269struct pattern {270STAILQ_ENTRY(pattern) link;271char pattern[];272};273274STAILQ_HEAD(pattern_list, pattern);275static struct pattern_list include = STAILQ_HEAD_INITIALIZER(include);276static struct pattern_list exclude = STAILQ_HEAD_INITIALIZER(exclude);277278/*279* Add an entry to a pattern list280*/281static void282add_pattern(struct pattern_list *list, const char *pattern)283{284struct pattern *entry;285size_t len;286287debug("adding pattern '%s'\n", pattern);288len = strlen(pattern);289if ((entry = malloc(sizeof *entry + len + 1)) == NULL) {290errno = ENOMEM;291error("malloc()");292}293memcpy(entry->pattern, pattern, len + 1);294STAILQ_INSERT_TAIL(list, entry, link);295}296297/*298* Match a string against a list of patterns299*/300static int301match_pattern(struct pattern_list *list, const char *str)302{303struct pattern *entry;304305STAILQ_FOREACH(entry, list, link) {306#ifdef HAVE_FNMATCH307if (fnmatch(entry->pattern, str, C_opt ? FNM_CASEFOLD : 0) == 0)308return (1);309#else310#error "Unsupported platform: fnmatch() is required"311#endif312}313return (0);314}315316/*317* Verify that a given pathname is in the include list and not in the318* exclude list.319*/320static int321accept_pathname(const char *pathname)322{323324if (!STAILQ_EMPTY(&include) && !match_pattern(&include, pathname))325return (0);326if (!STAILQ_EMPTY(&exclude) && match_pattern(&exclude, pathname))327return (0);328return (1);329}330331/*332* Create the specified directory with the specified mode, taking certain333* precautions on they way.334*/335static void336make_dir(const char *path, int mode)337{338struct stat sb;339340if (lstat(path, &sb) == 0) {341if (S_ISDIR(sb.st_mode))342return;343/*344* Normally, we should either ask the user about removing345* the non-directory of the same name as a directory we346* wish to create, or respect the -n or -o command-line347* options. However, this may lead to a later failure or348* even compromise (if this non-directory happens to be a349* symlink to somewhere unsafe), so we don't.350*/351352/*353* Don't check unlink() result; failure will cause mkdir()354* to fail later, which we will catch.355*/356(void)unlink(path);357}358if (mkdir(path, (mode_t)mode) != 0 && errno != EEXIST)359error("mkdir('%s')", path);360}361362/*363* Ensure that all directories leading up to (but not including) the364* specified path exist.365*366* XXX inefficient + modifies the file in-place367*/368static void369make_parent(char *path)370{371struct stat sb;372char *sep;373374sep = strrchr(path, '/');375if (sep == NULL || sep == path)376return;377*sep = '\0';378if (lstat(path, &sb) == 0) {379if (S_ISDIR(sb.st_mode)) {380*sep = '/';381return;382}383unlink(path);384}385make_parent(path);386mkdir(path, 0755);387*sep = '/';388389#if 0390for (sep = path; (sep = strchr(sep, '/')) != NULL; sep++) {391/* root in case of absolute d_arg */392if (sep == path)393continue;394*sep = '\0';395make_dir(path, 0755);396*sep = '/';397}398#endif399}400401/*402* Extract a directory.403*/404static void405extract_dir(struct archive *a, struct archive_entry *e, const char *path)406{407int mode;408409/*410* Dropbox likes to create '/' directory entries, just ignore411* such junk.412*/413if (*path == '\0')414return;415416mode = archive_entry_mode(e) & 0777;417if (mode == 0)418mode = 0755;419420/*421* Some zipfiles contain directories with weird permissions such422* as 0644 or 0444. This can cause strange issues such as being423* unable to extract files into the directory we just created, or424* the user being unable to remove the directory later without425* first manually changing its permissions. Therefore, we whack426* the permissions into shape, assuming that the user wants full427* access and that anyone who gets read access also gets execute428* access.429*/430mode |= 0700;431if (mode & 0040)432mode |= 0010;433if (mode & 0004)434mode |= 0001;435436info(" creating: %s/\n", path);437make_dir(path, mode);438ac(archive_read_data_skip(a));439}440441static unsigned char buffer[8192];442static char spinner[] = { '|', '/', '-', '\\' };443444static int445handle_existing_file(char **path)446{447size_t alen;448ssize_t len;449char buf[4];450451for (;;) {452fprintf(stderr,453"replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ",454*path);455if (fgets(buf, sizeof(buf), stdin) == NULL)456goto stdin_err;457switch (*buf) {458case 'A':459o_opt = 1;460/* FALLTHROUGH */461case 'y':462case 'Y':463(void)unlink(*path);464return 1;465case 'N':466n_opt = 1;467/* FALLTHROUGH */468case 'n':469return -1;470case 'r':471case 'R':472printf("New name: ");473fflush(stdout);474free(*path);475*path = NULL;476alen = 0;477len = getline(path, &alen, stdin);478if (len < 1)479goto stdin_err;480if ((*path)[len - 1] == '\n')481(*path)[len - 1] = '\0';482return 0;483default:484break;485}486}487stdin_err:488clearerr(stdin);489printf("NULL\n(EOF or read error, "490"treating as \"[N]one\"...)\n");491n_opt = 1;492return -1;493}494495/*496* Detect binary files by a combination of character white list and497* black list. NUL bytes and other control codes without use in text files498* result directly in switching the file to binary mode. Otherwise, at least499* one white-listed byte has to be found.500*501* Black-listed: 0..6, 14..25, 28..31502* 0xf3ffc07f = 11110011111111111100000001111111b503* White-listed: 9..10, 13, >= 32504* 0x00002600 = 00000000000000000010011000000000b505*506* See the proginfo/txtvsbin.txt in the zip sources for a detailed discussion.507*/508#define BYTE_IS_BINARY(x) ((x) < 32 && (0xf3ffc07fU & (1U << (x))))509#define BYTE_IS_TEXT(x) ((x) >= 32 || (0x00002600U & (1U << (x))))510511static int512check_binary(const unsigned char *buf, size_t len)513{514int rv;515for (rv = 1; len--; ++buf) {516if (BYTE_IS_BINARY(*buf))517return 1;518if (BYTE_IS_TEXT(*buf))519rv = 0;520}521522return rv;523}524525/*526* Extract to a file descriptor527*/528static int529extract2fd(struct archive *a, char *pathname, int fd)530{531int cr, text, warn;532ssize_t len;533unsigned char *p, *q, *end;534535text = a_opt;536warn = 0;537cr = 0;538539/* loop over file contents and write to fd */540for (int n = 0; ; n++) {541if (fd != STDOUT_FILENO)542if (tty && (n % 4) == 0)543info(" %c\b\b", spinner[(n / 4) % sizeof spinner]);544545len = archive_read_data(a, buffer, sizeof buffer);546547if (len < 0)548ac(len);549550/* left over CR from previous buffer */551if (a_opt && cr) {552if (len == 0 || buffer[0] != '\n')553if (write(fd, "\r", 1) != 1)554error("write('%s')", pathname);555cr = 0;556}557558/* EOF */559if (len == 0)560break;561end = buffer + len;562563/*564* Detect whether this is a text file. The correct way to565* do this is to check the least significant bit of the566* "internal file attributes" field of the corresponding567* file header in the central directory, but libarchive568* does not provide access to this field, so we have to569* guess by looking for non-ASCII characters in the570* buffer. Hopefully we won't guess wrong. If we do571* guess wrong, we print a warning message later.572*/573if (a_opt && n == 0) {574if (check_binary(buffer, len))575text = 0;576}577578/* simple case */579if (!a_opt || !text) {580if (write(fd, buffer, len) != len)581error("write('%s')", pathname);582continue;583}584585/* hard case: convert \r\n to \n (sigh...) */586for (p = buffer; p < end; p = q + 1) {587for (q = p; q < end; q++) {588if (!warn && BYTE_IS_BINARY(*q)) {589warningx("%s may be corrupted due"590" to weak text file detection"591" heuristic", pathname);592warn = 1;593}594if (q[0] != '\r')595continue;596if (&q[1] == end) {597cr = 1;598break;599}600if (q[1] == '\n')601break;602}603if (write(fd, p, q - p) != q - p)604error("write('%s')", pathname);605}606}607608return text;609}610611/*612* Extract a regular file.613*/614static void615extract_file(struct archive *a, struct archive_entry *e, char **path)616{617int mode;618struct timespec mtime;619struct stat sb;620int fd, check, text;621const char *linkname;622#if defined(HAVE_UTIMENSAT) || defined(HAVE_FUTIMENS)623struct timespec ts[2];624#endif625#if ((!defined(HAVE_UTIMENSAT) && defined(HAVE_LUTIMES)) || \626(!defined(HAVE_FUTIMENS) && defined(HAVE_FUTIMES)))627struct timeval times[2];628#endif629630mode = archive_entry_mode(e) & 0777;631if (mode == 0)632mode = 0644;633mtime.tv_sec = archive_entry_mtime(e);634mtime.tv_nsec = archive_entry_mtime_nsec(e);635636/* look for existing file of same name */637recheck:638if (lstat(*path, &sb) == 0) {639if (u_opt || f_opt) {640/* check if up-to-date */641if (S_ISREG(sb.st_mode) && (642#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC643sb.st_mtimespec.tv_sec > mtime.tv_sec ||644(sb.st_mtimespec.tv_sec == mtime.tv_sec &&645sb.st_mtimespec.tv_nsec >= mtime.tv_nsec)646#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC647sb.st_mtim.tv_sec > mtime.tv_sec ||648(sb.st_mtim.tv_sec == mtime.tv_sec &&649sb.st_mtim.tv_nsec >= mtime.tv_nsec)650#elif HAVE_STRUCT_STAT_ST_MTIME_N651sb.st_mtime > mtime.tv_sec ||652(sb.st_mtime == mtime.tv_sec &&653sb.st_mtime_n => mtime.tv_nsec)654#elif HAVE_STRUCT_STAT_ST_MTIME_USEC655sb.st_mtime > mtime.tv_sec ||656(sb.st_mtime == mtime.tv_sec &&657sb.st_mtime_usec => mtime.tv_nsec / 1000)658#else659sb.st_mtime > mtime.tv_sec660#endif661))662return;663(void)unlink(*path);664} else if (o_opt) {665/* overwrite */666(void)unlink(*path);667} else if (n_opt) {668/* do not overwrite */669return;670} else {671check = handle_existing_file(path);672if (check == 0)673goto recheck;674if (check == -1)675return; /* do not overwrite */676}677} else {678if (f_opt)679return;680}681682#if defined(HAVE_UTIMENSAT) || defined(HAVE_FUTIMENS)683ts[0].tv_sec = 0;684ts[0].tv_nsec = UTIME_NOW;685ts[1] = mtime;686#endif687#if ((!defined(HAVE_UTIMENSAT) && defined(HAVE_LUTIMES)) || \688(!defined(HAVE_FUTIMENS) && defined(HAVE_FUTIMES)))689times[0].tv_sec = 0;690times[0].tv_usec = -1;691times[1].tv_sec = mtime.tv_sec;692times[1].tv_usec = mtime.tv_nsec / 1000;693#endif694695/* process symlinks */696linkname = archive_entry_symlink(e);697if (linkname != NULL) {698if (symlink(linkname, *path) != 0)699error("symlink('%s')", *path);700info(" extracting: %s -> %s\n", *path, linkname);701#ifdef HAVE_LCHMOD702if (lchmod(*path, (mode_t)mode) != 0)703warning("Cannot set mode for '%s'", *path);704#endif705/* set access and modification time */706#if defined(HAVE_UTIMENSAT)707if (utimensat(AT_FDCWD, *path, ts, AT_SYMLINK_NOFOLLOW) != 0)708warning("utimensat('%s')", *path);709#elif defined(HAVE_LUTIMES)710gettimeofday(×[0], NULL);711if (lutimes(*path, times) != 0)712warning("lutimes('%s')", *path);713#endif714return;715}716717if ((fd = open(*path, O_RDWR|O_CREAT|O_TRUNC, mode)) < 0)718error("open('%s')", *path);719720info(" extracting: %s", *path);721722text = extract2fd(a, *path, fd);723724if (tty)725info(" \b\b");726if (text)727info(" (text)");728info("\n");729730/* set access and modification time */731#if defined(HAVE_FUTIMENS)732if (futimens(fd, ts) != 0)733error("futimens('%s')", *path);734#elif defined(HAVE_FUTIMES)735gettimeofday(×[0], NULL);736if (futimes(fd, times) != 0)737error("futimes('%s')", *path);738#endif739if (close(fd) != 0)740error("close('%s')", *path);741}742743/*744* Extract a zipfile entry: first perform some sanity checks to ensure745* that it is either a directory or a regular file and that the path is746* not absolute and does not try to break out of the current directory;747* then call either extract_dir() or extract_file() as appropriate.748*749* This is complicated a bit by the various ways in which we need to750* manipulate the path name. Case conversion (if requested by the -L751* option) happens first, but the include / exclude patterns are applied752* to the full converted path name, before the directory part of the path753* is removed in accordance with the -j option. Sanity checks are754* intentionally done earlier than they need to be, so the user will get a755* warning about insecure paths even for files or directories which756* wouldn't be extracted anyway.757*/758static void759extract(struct archive *a, struct archive_entry *e)760{761char *pathname, *realpathname;762mode_t filetype;763char *p, *q;764765if ((pathname = pathdup(archive_entry_pathname(e))) == NULL) {766warningx("skipping empty or unreadable filename entry");767ac(archive_read_data_skip(a));768return;769}770filetype = archive_entry_filetype(e);771772/* sanity checks */773if (pathname[0] == '/' ||774strncmp(pathname, "../", 3) == 0 ||775strstr(pathname, "/../") != NULL) {776warningx("skipping insecure entry '%s'", pathname);777ac(archive_read_data_skip(a));778free(pathname);779return;780}781782/* I don't think this can happen in a zipfile.. */783if (!S_ISDIR(filetype) && !S_ISREG(filetype) && !S_ISLNK(filetype)) {784warningx("skipping non-regular entry '%s'", pathname);785ac(archive_read_data_skip(a));786free(pathname);787return;788}789790/* skip directories in -j case */791if (S_ISDIR(filetype) && j_opt) {792ac(archive_read_data_skip(a));793free(pathname);794return;795}796797/* apply include / exclude patterns */798if (!accept_pathname(pathname)) {799ac(archive_read_data_skip(a));800free(pathname);801return;802}803804/* apply -j and -d */805if (j_opt) {806for (p = q = pathname; *p; ++p)807if (*p == '/')808q = p + 1;809realpathname = pathcat(d_arg, q);810} else {811realpathname = pathcat(d_arg, pathname);812}813814/* ensure that parent directory exists */815make_parent(realpathname);816817if (S_ISDIR(filetype))818extract_dir(a, e, realpathname);819else820extract_file(a, e, &realpathname);821822free(realpathname);823free(pathname);824}825826static void827extract_stdout(struct archive *a, struct archive_entry *e)828{829char *pathname;830mode_t filetype;831832if ((pathname = pathdup(archive_entry_pathname(e))) == NULL) {833warningx("skipping empty or unreadable filename entry");834ac(archive_read_data_skip(a));835return;836}837filetype = archive_entry_filetype(e);838839/* I don't think this can happen in a zipfile.. */840if (!S_ISDIR(filetype) && !S_ISREG(filetype) && !S_ISLNK(filetype)) {841warningx("skipping non-regular entry '%s'", pathname);842ac(archive_read_data_skip(a));843free(pathname);844return;845}846847/* skip directories in -j case */848if (S_ISDIR(filetype)) {849ac(archive_read_data_skip(a));850free(pathname);851return;852}853854/* apply include / exclude patterns */855if (!accept_pathname(pathname)) {856ac(archive_read_data_skip(a));857free(pathname);858return;859}860861if (c_opt)862info("x %s\n", pathname);863864(void)extract2fd(a, pathname, STDOUT_FILENO);865866free(pathname);867}868869/*870* Print the name of an entry to stdout.871*/872static void873list(struct archive *a, struct archive_entry *e)874{875char buf[20];876time_t mtime;877struct tm *tm;878const char *pathname;879880mtime = archive_entry_mtime(e);881tm = localtime(&mtime);882if (*y_str)883strftime(buf, sizeof(buf), "%m-%d-%G %R", tm);884else885strftime(buf, sizeof(buf), "%m-%d-%g %R", tm);886887pathname = archive_entry_pathname(e);888if (!pathname)889pathname = "";890if (!zipinfo_mode) {891if (v_opt == 1) {892printf(" %8ju %s %s\n",893(uintmax_t)archive_entry_size(e),894buf, pathname);895} else if (v_opt == 2) {896printf("%8ju Stored %7ju 0%% %s %08x %s\n",897(uintmax_t)archive_entry_size(e),898(uintmax_t)archive_entry_size(e),899buf,9000U,901pathname);902}903} else {904if (Z1_opt)905printf("%s\n", pathname);906}907ac(archive_read_data_skip(a));908}909910/*911* Extract to memory to check CRC912*/913static int914test(struct archive *a, struct archive_entry *e)915{916ssize_t len;917int error_count;918919error_count = 0;920if (S_ISDIR(archive_entry_filetype(e)))921return 0;922923info(" testing: %s\t", archive_entry_pathname(e));924while ((len = archive_read_data(a, buffer, sizeof buffer)) > 0)925/* nothing */;926if (len < 0) {927info(" %s\n", archive_error_string(a));928++error_count;929} else {930info(" OK\n");931}932933/* shouldn't be necessary, but it doesn't hurt */934ac(archive_read_data_skip(a));935936return error_count;937}938939/*940* Callback function for reading passphrase.941* Originally from cpio.c and passphrase.c, libarchive.942*/943#define PPBUFF_SIZE 1024944static const char *945passphrase_callback(struct archive *a, void *_client_data)946{947char *p;948949(void)a; /* UNUSED */950(void)_client_data; /* UNUSED */951952if (passphrase_buf == NULL) {953passphrase_buf = malloc(PPBUFF_SIZE);954if (passphrase_buf == NULL) {955errno = ENOMEM;956error("malloc()");957}958}959960p = lafe_readpassphrase("\nEnter password: ", passphrase_buf,961PPBUFF_SIZE);962963if (p == NULL && errno != EINTR)964error("Error reading password");965966return p;967}968969/*970* Main loop: open the zipfile, iterate over its contents and decide what971* to do with each entry.972*/973static void974unzip(const char *fn)975{976struct archive *a;977struct archive_entry *e;978int ret;979uintmax_t total_size, file_count, error_count;980981if ((a = archive_read_new()) == NULL)982error("archive_read_new failed");983984ac(archive_read_support_format_zip(a));985986if (O_arg)987ac(archive_read_set_format_option(a, "zip", "hdrcharset", O_arg));988989if (P_arg)990archive_read_add_passphrase(a, P_arg);991else992archive_read_set_passphrase_callback(a, NULL,993&passphrase_callback);994995ac(archive_read_open_filename(a, fn, 8192));996997if (!zipinfo_mode) {998if (!p_opt && !q_opt)999printf("Archive: %s\n", fn);1000if (v_opt == 1) {1001printf(" Length %sDate Time Name\n", y_str);1002printf(" -------- %s---- ---- ----\n", y_str);1003} else if (v_opt == 2) {1004printf(" Length Method Size Ratio %sDate Time CRC-32 Name\n", y_str);1005printf("-------- ------ ------- ----- %s---- ---- ------ ----\n", y_str);1006}1007}10081009total_size = 0;1010file_count = 0;1011error_count = 0;1012for (;;) {1013ret = archive_read_next_header(a, &e);1014if (ret == ARCHIVE_EOF)1015break;1016ac(ret);1017if (!zipinfo_mode) {1018if (t_opt)1019error_count += test(a, e);1020else if (v_opt)1021list(a, e);1022else if (p_opt || c_opt)1023extract_stdout(a, e);1024else1025extract(a, e);1026} else {1027if (Z1_opt)1028list(a, e);1029}10301031total_size += archive_entry_size(e);1032++file_count;1033}10341035if (zipinfo_mode) {1036if (v_opt == 1) {1037printf(" -------- %s-------\n", y_str);1038printf(" %8ju %s%ju file%s\n",1039total_size, y_str, file_count, file_count != 1 ? "s" : "");1040} else if (v_opt == 2) {1041printf("-------- ------- --- %s-------\n", y_str);1042printf("%8ju %7ju 0%% %s%ju file%s\n",1043total_size, total_size, y_str, file_count,1044file_count != 1 ? "s" : "");1045}1046}10471048ac(archive_read_free(a));10491050if (passphrase_buf != NULL) {1051memset(passphrase_buf, 0, PPBUFF_SIZE);1052free(passphrase_buf);1053}10541055if (t_opt) {1056if (error_count > 0) {1057errorx("%ju checksum error(s) found.", error_count);1058}1059else {1060printf("No errors detected in compressed data of %s.\n",1061fn);1062}1063}1064}10651066static void1067usage(void)1068{10691070fprintf(stderr,1071"Usage: unzip [-aCcfjLlnopqtuvyZ1] [{-O|-I} encoding] [-d dir] [-x pattern] [-P password] zipfile\n"1072" [member ...]\n");1073exit(EXIT_FAILURE);1074}10751076static void1077version(void)1078{1079printf("bsdunzip %s - %s \n",1080BSDUNZIP_VERSION_STRING,1081archive_version_details());1082exit(0);1083}10841085static int1086getopts(int argc, char *argv[])1087{1088struct bsdunzip *bsdunzip, bsdunzip_storage;1089int opt;1090bsdunzip_optind = 1;10911092bsdunzip = &bsdunzip_storage;1093memset(bsdunzip, 0, sizeof(*bsdunzip));10941095bsdunzip->argv = argv;1096bsdunzip->argc = argc;10971098while ((opt = bsdunzip_getopt(bsdunzip)) != -1) {1099unzip_exclude_mode = 0;1100switch (opt) {1101case 'a':1102a_opt = 1;1103break;1104case 'C':1105C_opt = 1;1106break;1107case 'c':1108c_opt = 1;1109break;1110case 'd':1111d_arg = bsdunzip->argument;1112break;1113case 'f':1114f_opt = 1;1115break;1116case 'I':1117case 'O':1118O_arg = bsdunzip->argument;1119break;1120case 'j':1121j_opt = 1;1122break;1123case 'L':1124L_opt = 1;1125break;1126case 'l':1127if (v_opt == 0)1128v_opt = 1;1129break;1130case 'n':1131n_opt = 1;1132break;1133case 'o':1134o_opt = 1;1135q_opt = 1;1136break;1137case 'p':1138p_opt = 1;1139break;1140case 'P':1141P_arg = bsdunzip->argument;1142break;1143case 'q':1144q_opt = 1;1145break;1146case 't':1147t_opt = 1;1148break;1149case 'u':1150u_opt = 1;1151break;1152case 'v':1153v_opt = 2;1154break;1155case 'x':1156add_pattern(&exclude, bsdunzip->argument);1157unzip_exclude_mode = 1;1158break;1159case 'y':1160y_str = " ";1161break;1162case 'Z':1163zipinfo_mode = 1;1164if (bsdunzip->argument != NULL &&1165strcmp(bsdunzip->argument, "1") == 0) {1166Z1_opt = 1;1167}1168break;1169case OPTION_VERSION:1170version_opt = 1;1171break;1172case OPTION_NONE:1173break;1174default:1175usage();1176}1177if (opt == OPTION_NONE)1178break;1179}1180return (bsdunzip_optind);1181}11821183int1184main(int argc, char *argv[])1185{1186const char *zipfile;1187int nopts;11881189lafe_setprogname(*argv, "bsdunzip");11901191#if HAVE_SETLOCALE1192if (setlocale(LC_ALL, "") == NULL)1193lafe_warnc(0, "Failed to set default locale");1194#endif11951196if (isatty(STDOUT_FILENO))1197tty = 1;11981199if (getenv("UNZIP_DEBUG") != NULL)1200unzip_debug = 1;1201for (int i = 0; i < argc; ++i)1202debug("%s%c", argv[i], (i < argc - 1) ? ' ' : '\n');12031204#ifdef __GLIBC__1205/* Prevent GNU getopt(3) from rearranging options. */1206setenv("POSIXLY_CORRECT", "", 1);1207#endif1208/*1209* Info-ZIP's unzip(1) expects certain options to come before the1210* zipfile name, and others to come after - though it does not1211* enforce this. For simplicity, we accept *all* options both1212* before and after the zipfile name.1213*/1214nopts = getopts(argc, argv);12151216if (version_opt == 1)1217version();12181219/*1220* When more of the zipinfo mode options are implemented, this1221* will need to change.1222*/1223if (zipinfo_mode && !Z1_opt) {1224printf("Zipinfo mode needs additional options\n");1225exit(EXIT_FAILURE);1226}12271228if (argc <= nopts)1229usage();1230zipfile = argv[nopts++];12311232if (strcmp(zipfile, "-") == 0)1233zipfile = NULL; /* STDIN */12341235unzip_exclude_mode = 0;12361237while (nopts < argc && *argv[nopts] != '-')1238add_pattern(&include, argv[nopts++]);12391240nopts--; /* fake argv[0] */1241nopts += getopts(argc - nopts, argv + nopts);12421243/*1244* For compatibility with Info-ZIP's unzip(1) we need to treat1245* non-option arguments following an -x after the zipfile as1246* exclude list members.1247*/1248if (unzip_exclude_mode) {1249while (nopts < argc && *argv[nopts] != '-')1250add_pattern(&exclude, argv[nopts++]);1251nopts--; /* fake argv[0] */1252nopts += getopts(argc - nopts, argv + nopts);1253}12541255/* There may be residual arguments if we encountered -- */1256while (nopts < argc)1257add_pattern(&include, argv[nopts++]);12581259if (n_opt + o_opt + u_opt > 1)1260errorx("-n, -o and -u are contradictory");12611262unzip(zipfile);12631264exit(EXIT_SUCCESS);1265}126612671268