xref: /netbsd/usr.bin/unzip/unzip.c (revision 6a8b577c)
1*6a8b577cSrillig /* $NetBSD: unzip.c,v 1.28 2021/09/10 21:52:18 rillig Exp $ */
2a0922fbdSjoerg 
3a0922fbdSjoerg /*-
4ee86edd5Sjoerg  * Copyright (c) 2009, 2010 Joerg Sonnenberger <joerg@NetBSD.org>
5a0922fbdSjoerg  * Copyright (c) 2007-2008 Dag-Erling Co�dan Sm�rgrav
6a0922fbdSjoerg  * All rights reserved.
7a0922fbdSjoerg  *
8a0922fbdSjoerg  * Redistribution and use in source and binary forms, with or without
9a0922fbdSjoerg  * modification, are permitted provided that the following conditions
10a0922fbdSjoerg  * are met:
11a0922fbdSjoerg  * 1. Redistributions of source code must retain the above copyright
12a0922fbdSjoerg  *    notice, this list of conditions and the following disclaimer
13a0922fbdSjoerg  *    in this position and unchanged.
14a0922fbdSjoerg  * 2. Redistributions in binary form must reproduce the above copyright
15a0922fbdSjoerg  *    notice, this list of conditions and the following disclaimer in the
16a0922fbdSjoerg  *    documentation and/or other materials provided with the distribution.
17a0922fbdSjoerg  *
18a0922fbdSjoerg  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19a0922fbdSjoerg  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20a0922fbdSjoerg  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21a0922fbdSjoerg  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22a0922fbdSjoerg  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23a0922fbdSjoerg  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24a0922fbdSjoerg  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25a0922fbdSjoerg  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26a0922fbdSjoerg  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27a0922fbdSjoerg  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28a0922fbdSjoerg  * SUCH DAMAGE.
29a0922fbdSjoerg  *
30a0922fbdSjoerg  * $FreeBSD: revision 180124$
31a0922fbdSjoerg  *
32a0922fbdSjoerg  * This file would be much shorter if we didn't care about command-line
33a0922fbdSjoerg  * compatibility with Info-ZIP's UnZip, which requires us to duplicate
34a0922fbdSjoerg  * parts of libarchive in order to gain more detailed control of its
35a0922fbdSjoerg  * behaviour for the purpose of implementing the -n, -o, -L and -a
36a0922fbdSjoerg  * options.
37a0922fbdSjoerg  */
38a0922fbdSjoerg 
39a0922fbdSjoerg #include <sys/cdefs.h>
40*6a8b577cSrillig __RCSID("$NetBSD: unzip.c,v 1.28 2021/09/10 21:52:18 rillig Exp $");
41bd0f5c16Schristos 
42bd0f5c16Schristos #ifdef __GLIBC__
43bd0f5c16Schristos #define _GNU_SOURCE
4409f8ff84Schristos #define explicit_memset memset_s
45bd0f5c16Schristos #endif
46a0922fbdSjoerg 
47a0922fbdSjoerg #include <sys/queue.h>
48a0922fbdSjoerg #include <sys/stat.h>
49a0922fbdSjoerg 
50a0922fbdSjoerg #include <ctype.h>
51a0922fbdSjoerg #include <errno.h>
52a0922fbdSjoerg #include <fcntl.h>
53a0922fbdSjoerg #include <fnmatch.h>
54a0922fbdSjoerg #include <stdarg.h>
55a0922fbdSjoerg #include <stdio.h>
56a0922fbdSjoerg #include <stdlib.h>
57a0922fbdSjoerg #include <string.h>
58a0922fbdSjoerg #include <unistd.h>
59a0922fbdSjoerg 
60a0922fbdSjoerg #include <archive.h>
61a0922fbdSjoerg #include <archive_entry.h>
6209f8ff84Schristos #ifdef __GLIBC__
6309f8ff84Schristos #include <readpassphrase.h>
6409f8ff84Schristos #endif
65a0922fbdSjoerg 
66a0922fbdSjoerg /* command-line options */
67a0922fbdSjoerg static int		 a_opt;		/* convert EOL */
685705f526Swiz static int		 C_opt;		/* match case-insensitively */
69f3d0c4e7Swiz static int		 c_opt;		/* extract to stdout */
70a0922fbdSjoerg static const char	*d_arg;		/* directory */
71a0922fbdSjoerg static int		 f_opt;		/* update existing files only */
72a0922fbdSjoerg static int		 j_opt;		/* junk directories */
73a0922fbdSjoerg static int		 L_opt;		/* lowercase names */
74a0922fbdSjoerg static int		 n_opt;		/* never overwrite */
75a0922fbdSjoerg static int		 o_opt;		/* always overwrite */
7644b11f39Sjoerg static int		 p_opt;		/* extract to stdout, quiet */
77a0922fbdSjoerg static int		 q_opt;		/* quiet */
7809f8ff84Schristos static char		*P_arg;		/* passphrase */
79a0922fbdSjoerg static int		 t_opt;		/* test */
80a0922fbdSjoerg static int		 u_opt;		/* update */
8144b11f39Sjoerg static int		 v_opt;		/* verbose/list */
82a8ab691eSchristos static const char *	 y_str = "";	/* 4 digit year */
83a0922fbdSjoerg 
84a0922fbdSjoerg /* time when unzip started */
85a0922fbdSjoerg static time_t		 now;
86a0922fbdSjoerg 
87a0922fbdSjoerg /* debug flag */
88a0922fbdSjoerg static int		 unzip_debug;
89a0922fbdSjoerg 
90a0922fbdSjoerg /* running on tty? */
91a0922fbdSjoerg static int		 tty;
92a0922fbdSjoerg 
93a0922fbdSjoerg /* convenience macro */
94a0922fbdSjoerg /* XXX should differentiate between ARCHIVE_{WARN,FAIL,RETRY} */
95a0922fbdSjoerg #define ac(call)						\
96a0922fbdSjoerg 	do {							\
97a0922fbdSjoerg 		int acret = (call);				\
98a0922fbdSjoerg 		if (acret != ARCHIVE_OK)			\
99a0922fbdSjoerg 			errorx("%s", archive_error_string(a));	\
100*6a8b577cSrillig 	} while (0)
101a0922fbdSjoerg 
102a0922fbdSjoerg /*
103a0922fbdSjoerg  * Indicates that last info() did not end with EOL.  This helps error() et
104a0922fbdSjoerg  * al. avoid printing an error message on the same line as an incomplete
105a0922fbdSjoerg  * informational message.
106a0922fbdSjoerg  */
107a0922fbdSjoerg static int noeol;
108a0922fbdSjoerg 
10909f8ff84Schristos /* for an interactive passphrase input */
11009f8ff84Schristos static char passbuf[1024];
11109f8ff84Schristos 
112a0922fbdSjoerg /* fatal error message + errno */
113c317344fSjoerg __dead __printflike(1, 2) static void
error(const char * fmt,...)114a0922fbdSjoerg error(const char *fmt, ...)
115a0922fbdSjoerg {
116a0922fbdSjoerg 	va_list ap;
117a0922fbdSjoerg 
118a0922fbdSjoerg 	if (noeol)
119a0922fbdSjoerg 		fprintf(stdout, "\n");
120a0922fbdSjoerg 	fflush(stdout);
12109f8ff84Schristos 	fprintf(stderr, "%s: ", getprogname());
122a0922fbdSjoerg 	va_start(ap, fmt);
123a0922fbdSjoerg 	vfprintf(stderr, fmt, ap);
124a0922fbdSjoerg 	va_end(ap);
125a0922fbdSjoerg 	fprintf(stderr, ": %s\n", strerror(errno));
12609f8ff84Schristos 	exit(EXIT_FAILURE);
127a0922fbdSjoerg }
128a0922fbdSjoerg 
129a0922fbdSjoerg /* fatal error message, no errno */
130c317344fSjoerg __dead __printflike(1, 2) static void
errorx(const char * fmt,...)131a0922fbdSjoerg errorx(const char *fmt, ...)
132a0922fbdSjoerg {
133a0922fbdSjoerg 	va_list ap;
134a0922fbdSjoerg 
135a0922fbdSjoerg 	if (noeol)
136a0922fbdSjoerg 		fprintf(stdout, "\n");
137a0922fbdSjoerg 	fflush(stdout);
13809f8ff84Schristos 	fprintf(stderr, "%s: ", getprogname());
139a0922fbdSjoerg 	va_start(ap, fmt);
140a0922fbdSjoerg 	vfprintf(stderr, fmt, ap);
141a0922fbdSjoerg 	va_end(ap);
142a0922fbdSjoerg 	fprintf(stderr, "\n");
14309f8ff84Schristos 	exit(EXIT_FAILURE);
144a0922fbdSjoerg }
145a0922fbdSjoerg 
146a0922fbdSjoerg /* non-fatal error message + errno */
147c317344fSjoerg __printflike(1, 2) static void
warning(const char * fmt,...)148a0922fbdSjoerg warning(const char *fmt, ...)
149a0922fbdSjoerg {
150a0922fbdSjoerg 	va_list ap;
151a0922fbdSjoerg 
152a0922fbdSjoerg 	if (noeol)
153a0922fbdSjoerg 		fprintf(stdout, "\n");
154a0922fbdSjoerg 	fflush(stdout);
155a0922fbdSjoerg 	fprintf(stderr, "unzip: ");
156a0922fbdSjoerg 	va_start(ap, fmt);
157a0922fbdSjoerg 	vfprintf(stderr, fmt, ap);
158a0922fbdSjoerg 	va_end(ap);
159a0922fbdSjoerg 	fprintf(stderr, ": %s\n", strerror(errno));
160a0922fbdSjoerg }
161e91e7747Schristos 
162a0922fbdSjoerg /* non-fatal error message, no errno */
163c317344fSjoerg __printflike(1, 2) static void
warningx(const char * fmt,...)164a0922fbdSjoerg warningx(const char *fmt, ...)
165a0922fbdSjoerg {
166a0922fbdSjoerg 	va_list ap;
167a0922fbdSjoerg 
168a0922fbdSjoerg 	if (noeol)
169a0922fbdSjoerg 		fprintf(stdout, "\n");
170a0922fbdSjoerg 	fflush(stdout);
171a0922fbdSjoerg 	fprintf(stderr, "unzip: ");
172a0922fbdSjoerg 	va_start(ap, fmt);
173a0922fbdSjoerg 	vfprintf(stderr, fmt, ap);
174a0922fbdSjoerg 	va_end(ap);
175a0922fbdSjoerg 	fprintf(stderr, "\n");
176a0922fbdSjoerg }
177a0922fbdSjoerg 
178a0922fbdSjoerg /* informational message (if not -q) */
179c317344fSjoerg __printflike(1, 2) static void
info(const char * fmt,...)180a0922fbdSjoerg info(const char *fmt, ...)
181a0922fbdSjoerg {
182a0922fbdSjoerg 	va_list ap;
183a0922fbdSjoerg 
184a0922fbdSjoerg 	if (q_opt && !unzip_debug)
185a0922fbdSjoerg 		return;
186a0922fbdSjoerg 	va_start(ap, fmt);
187a0922fbdSjoerg 	vfprintf(stdout, fmt, ap);
188a0922fbdSjoerg 	va_end(ap);
189a0922fbdSjoerg 	fflush(stdout);
190a0922fbdSjoerg 
191a0922fbdSjoerg 	if (*fmt == '\0')
192a0922fbdSjoerg 		noeol = 1;
193a0922fbdSjoerg 	else
194a0922fbdSjoerg 		noeol = fmt[strlen(fmt) - 1] != '\n';
195a0922fbdSjoerg }
196a0922fbdSjoerg 
197a0922fbdSjoerg /* debug message (if unzip_debug) */
198c317344fSjoerg __printflike(1, 2) static void
debug(const char * fmt,...)199a0922fbdSjoerg debug(const char *fmt, ...)
200a0922fbdSjoerg {
201a0922fbdSjoerg 	va_list ap;
202a0922fbdSjoerg 
203a0922fbdSjoerg 	if (!unzip_debug)
204a0922fbdSjoerg 		return;
205a0922fbdSjoerg 	va_start(ap, fmt);
206a0922fbdSjoerg 	vfprintf(stderr, fmt, ap);
207a0922fbdSjoerg 	va_end(ap);
208a0922fbdSjoerg 	fflush(stderr);
209a0922fbdSjoerg 
210a0922fbdSjoerg 	if (*fmt == '\0')
211a0922fbdSjoerg 		noeol = 1;
212a0922fbdSjoerg 	else
213a0922fbdSjoerg 		noeol = fmt[strlen(fmt) - 1] != '\n';
214a0922fbdSjoerg }
215a0922fbdSjoerg 
216a0922fbdSjoerg /* duplicate a path name, possibly converting to lower case */
217a0922fbdSjoerg static char *
pathdup(const char * path)218a0922fbdSjoerg pathdup(const char *path)
219a0922fbdSjoerg {
220a0922fbdSjoerg 	char *str;
221a0922fbdSjoerg 	size_t i, len;
222a0922fbdSjoerg 
223a0922fbdSjoerg 	len = strlen(path);
224a0922fbdSjoerg 	while (len && path[len - 1] == '/')
225a0922fbdSjoerg 		len--;
226a0922fbdSjoerg 	if ((str = malloc(len + 1)) == NULL) {
227a0922fbdSjoerg 		errno = ENOMEM;
228a0922fbdSjoerg 		error("malloc()");
229a0922fbdSjoerg 	}
230a0922fbdSjoerg 	if (L_opt) {
231a0922fbdSjoerg 		for (i = 0; i < len; ++i)
232a0922fbdSjoerg 			str[i] = tolower((unsigned char)path[i]);
233a0922fbdSjoerg 	} else {
234a0922fbdSjoerg 		memcpy(str, path, len);
235a0922fbdSjoerg 	}
236a0922fbdSjoerg 	str[len] = '\0';
237a0922fbdSjoerg 
23809f8ff84Schristos 	return str;
239a0922fbdSjoerg }
240a0922fbdSjoerg 
241a0922fbdSjoerg /* concatenate two path names */
242a0922fbdSjoerg static char *
pathcat(const char * prefix,const char * path)243a0922fbdSjoerg pathcat(const char *prefix, const char *path)
244a0922fbdSjoerg {
245a0922fbdSjoerg 	char *str;
246a0922fbdSjoerg 	size_t prelen, len;
247a0922fbdSjoerg 
248a0922fbdSjoerg 	prelen = prefix ? strlen(prefix) + 1 : 0;
249a0922fbdSjoerg 	len = strlen(path) + 1;
250a0922fbdSjoerg 	if ((str = malloc(prelen + len)) == NULL) {
251a0922fbdSjoerg 		errno = ENOMEM;
252a0922fbdSjoerg 		error("malloc()");
253a0922fbdSjoerg 	}
254a0922fbdSjoerg 	if (prefix) {
255a0922fbdSjoerg 		memcpy(str, prefix, prelen);	/* includes zero */
256a0922fbdSjoerg 		str[prelen - 1] = '/';		/* splat zero */
257a0922fbdSjoerg 	}
258a0922fbdSjoerg 	memcpy(str + prelen, path, len);	/* includes zero */
259a0922fbdSjoerg 
26009f8ff84Schristos 	return str;
261a0922fbdSjoerg }
262a0922fbdSjoerg 
263a0922fbdSjoerg /*
264a0922fbdSjoerg  * Pattern lists for include / exclude processing
265a0922fbdSjoerg  */
266a0922fbdSjoerg struct pattern {
267a0922fbdSjoerg 	STAILQ_ENTRY(pattern) link;
268a0922fbdSjoerg 	char pattern[];
269a0922fbdSjoerg };
270a0922fbdSjoerg 
271a0922fbdSjoerg STAILQ_HEAD(pattern_list, pattern);
272a0922fbdSjoerg static struct pattern_list include = STAILQ_HEAD_INITIALIZER(include);
273a0922fbdSjoerg static struct pattern_list exclude = STAILQ_HEAD_INITIALIZER(exclude);
274a0922fbdSjoerg 
275a0922fbdSjoerg /*
276a0922fbdSjoerg  * Add an entry to a pattern list
277a0922fbdSjoerg  */
278a0922fbdSjoerg static void
add_pattern(struct pattern_list * list,const char * pattern)279a0922fbdSjoerg add_pattern(struct pattern_list *list, const char *pattern)
280a0922fbdSjoerg {
281a0922fbdSjoerg 	struct pattern *entry;
282a0922fbdSjoerg 	size_t len;
283a0922fbdSjoerg 
284a0922fbdSjoerg 	debug("adding pattern '%s'\n", pattern);
285a0922fbdSjoerg 	len = strlen(pattern);
286a0922fbdSjoerg 	if ((entry = malloc(sizeof *entry + len + 1)) == NULL) {
287a0922fbdSjoerg 		errno = ENOMEM;
288a0922fbdSjoerg 		error("malloc()");
289a0922fbdSjoerg 	}
290a0922fbdSjoerg 	memcpy(entry->pattern, pattern, len + 1);
291a0922fbdSjoerg 	STAILQ_INSERT_TAIL(list, entry, link);
292a0922fbdSjoerg }
293a0922fbdSjoerg 
294a0922fbdSjoerg /*
295a0922fbdSjoerg  * Match a string against a list of patterns
296a0922fbdSjoerg  */
297a0922fbdSjoerg static int
match_pattern(struct pattern_list * list,const char * str)298a0922fbdSjoerg match_pattern(struct pattern_list *list, const char *str)
299a0922fbdSjoerg {
300a0922fbdSjoerg 	struct pattern *entry;
301a0922fbdSjoerg 
302a0922fbdSjoerg 	STAILQ_FOREACH(entry, list, link) {
3035705f526Swiz 		if (fnmatch(entry->pattern, str, C_opt ? FNM_CASEFOLD : 0) == 0)
30409f8ff84Schristos 			return 1;
305a0922fbdSjoerg 	}
30609f8ff84Schristos 	return 0;
307a0922fbdSjoerg }
308a0922fbdSjoerg 
309a0922fbdSjoerg /*
310a0922fbdSjoerg  * Verify that a given pathname is in the include list and not in the
311a0922fbdSjoerg  * exclude list.
312a0922fbdSjoerg  */
313a0922fbdSjoerg static int
accept_pathname(const char * pathname)314a0922fbdSjoerg accept_pathname(const char *pathname)
315a0922fbdSjoerg {
316a0922fbdSjoerg 
317a0922fbdSjoerg 	if (!STAILQ_EMPTY(&include) && !match_pattern(&include, pathname))
31809f8ff84Schristos 		return 0;
319a0922fbdSjoerg 	if (!STAILQ_EMPTY(&exclude) && match_pattern(&exclude, pathname))
32009f8ff84Schristos 		return 0;
32109f8ff84Schristos 	return 1;
322a0922fbdSjoerg }
323a0922fbdSjoerg 
324a0922fbdSjoerg /*
325a0922fbdSjoerg  * Create the specified directory with the specified mode, taking certain
326a0922fbdSjoerg  * precautions on they way.
327a0922fbdSjoerg  */
328a0922fbdSjoerg static void
make_dir(const char * path,int mode)329a0922fbdSjoerg make_dir(const char *path, int mode)
330a0922fbdSjoerg {
331a0922fbdSjoerg 	struct stat sb;
332a0922fbdSjoerg 
333a0922fbdSjoerg 	if (lstat(path, &sb) == 0) {
334a0922fbdSjoerg 		if (S_ISDIR(sb.st_mode))
335a0922fbdSjoerg 			return;
336a0922fbdSjoerg 		/*
337a0922fbdSjoerg 		 * Normally, we should either ask the user about removing
338a0922fbdSjoerg 		 * the non-directory of the same name as a directory we
339a0922fbdSjoerg 		 * wish to create, or respect the -n or -o command-line
340a0922fbdSjoerg 		 * options.  However, this may lead to a later failure or
341a0922fbdSjoerg 		 * even compromise (if this non-directory happens to be a
342a0922fbdSjoerg 		 * symlink to somewhere unsafe), so we don't.
343a0922fbdSjoerg 		 */
344a0922fbdSjoerg 
345a0922fbdSjoerg 		/*
346a0922fbdSjoerg 		 * Don't check unlink() result; failure will cause mkdir()
347a0922fbdSjoerg 		 * to fail later, which we will catch.
348a0922fbdSjoerg 		 */
349a0922fbdSjoerg 		(void)unlink(path);
350a0922fbdSjoerg 	}
351a0922fbdSjoerg 	if (mkdir(path, mode) != 0 && errno != EEXIST)
352a0922fbdSjoerg 		error("mkdir('%s')", path);
353a0922fbdSjoerg }
354a0922fbdSjoerg 
355a0922fbdSjoerg /*
356a0922fbdSjoerg  * Ensure that all directories leading up to (but not including) the
357a0922fbdSjoerg  * specified path exist.
358a0922fbdSjoerg  *
359a0922fbdSjoerg  * XXX inefficient + modifies the file in-place
360a0922fbdSjoerg  */
361a0922fbdSjoerg static void
make_parent(char * path)362a0922fbdSjoerg make_parent(char *path)
363a0922fbdSjoerg {
364a0922fbdSjoerg 	struct stat sb;
365a0922fbdSjoerg 	char *sep;
366a0922fbdSjoerg 
367a0922fbdSjoerg 	sep = strrchr(path, '/');
368a0922fbdSjoerg 	if (sep == NULL || sep == path)
369a0922fbdSjoerg 		return;
370a0922fbdSjoerg 	*sep = '\0';
371a0922fbdSjoerg 	if (lstat(path, &sb) == 0) {
372a0922fbdSjoerg 		if (S_ISDIR(sb.st_mode)) {
373a0922fbdSjoerg 			*sep = '/';
374a0922fbdSjoerg 			return;
375a0922fbdSjoerg 		}
376a0922fbdSjoerg 		unlink(path);
377a0922fbdSjoerg 	}
378a0922fbdSjoerg 	make_parent(path);
379a0922fbdSjoerg 	mkdir(path, 0755);
380a0922fbdSjoerg 	*sep = '/';
381a0922fbdSjoerg 
382a0922fbdSjoerg #if 0
383a0922fbdSjoerg 	for (sep = path; (sep = strchr(sep, '/')) != NULL; sep++) {
384a0922fbdSjoerg 		/* root in case of absolute d_arg */
385a0922fbdSjoerg 		if (sep == path)
386a0922fbdSjoerg 			continue;
387a0922fbdSjoerg 		*sep = '\0';
388a0922fbdSjoerg 		make_dir(path, 0755);
389a0922fbdSjoerg 		*sep = '/';
390a0922fbdSjoerg 	}
391a0922fbdSjoerg #endif
392a0922fbdSjoerg }
393a0922fbdSjoerg 
394a0922fbdSjoerg /*
395a0922fbdSjoerg  * Extract a directory.
396a0922fbdSjoerg  */
397a0922fbdSjoerg static void
extract_dir(struct archive * a,struct archive_entry * e,const char * path)398a0922fbdSjoerg extract_dir(struct archive *a, struct archive_entry *e, const char *path)
399a0922fbdSjoerg {
400a0922fbdSjoerg 	int mode;
401a0922fbdSjoerg 
402cb22283dSjoerg 	/*
403cb22283dSjoerg 	 * Dropbox likes to create '/' directory entries, just ignore
404cb22283dSjoerg 	 * such junk.
405cb22283dSjoerg 	 */
406cb22283dSjoerg 	if (*path == '\0')
407cb22283dSjoerg 		return;
408cb22283dSjoerg 
409f5e83071Smbalmer 	mode = archive_entry_mode(e) & 0777;
410a0922fbdSjoerg 	if (mode == 0)
411a0922fbdSjoerg 		mode = 0755;
412a0922fbdSjoerg 
413a0922fbdSjoerg 	/*
414a0922fbdSjoerg 	 * Some zipfiles contain directories with weird permissions such
415a0922fbdSjoerg 	 * as 0644 or 0444.  This can cause strange issues such as being
416a0922fbdSjoerg 	 * unable to extract files into the directory we just created, or
417a0922fbdSjoerg 	 * the user being unable to remove the directory later without
418a0922fbdSjoerg 	 * first manually changing its permissions.  Therefore, we whack
419a0922fbdSjoerg 	 * the permissions into shape, assuming that the user wants full
420a0922fbdSjoerg 	 * access and that anyone who gets read access also gets execute
421a0922fbdSjoerg 	 * access.
422a0922fbdSjoerg 	 */
423a0922fbdSjoerg 	mode |= 0700;
424a0922fbdSjoerg 	if (mode & 0040)
425a0922fbdSjoerg 		mode |= 0010;
426a0922fbdSjoerg 	if (mode & 0004)
427a0922fbdSjoerg 		mode |= 0001;
428a0922fbdSjoerg 
42950a251ceSwiz 	info("   creating: %s/\n", path);
430a0922fbdSjoerg 	make_dir(path, mode);
431a0922fbdSjoerg 	ac(archive_read_data_skip(a));
432a0922fbdSjoerg }
433a0922fbdSjoerg 
434a0922fbdSjoerg static unsigned char buffer[8192];
435a0922fbdSjoerg static char spinner[] = { '|', '/', '-', '\\' };
436a0922fbdSjoerg 
4371d5ec6daSjoerg static int
handle_existing_file(char ** path)4381d5ec6daSjoerg handle_existing_file(char **path)
4391d5ec6daSjoerg {
4401d5ec6daSjoerg 	size_t alen;
4411d5ec6daSjoerg 	ssize_t len;
4421d5ec6daSjoerg 	char buf[4];
4431d5ec6daSjoerg 
4441d5ec6daSjoerg 	for (;;) {
4451d5ec6daSjoerg 		fprintf(stderr,
4461d5ec6daSjoerg 		    "replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ",
4471d5ec6daSjoerg 		    *path);
4481d5ec6daSjoerg 		fgets(buf, 4, stdin);
4491d5ec6daSjoerg 		switch (*buf) {
4501d5ec6daSjoerg 		case 'A':
4511d5ec6daSjoerg 			o_opt = 1;
4521d5ec6daSjoerg 			/* FALL THROUGH */
4531d5ec6daSjoerg 		case 'y':
4541d5ec6daSjoerg 		case 'Y':
4551d5ec6daSjoerg 			(void)unlink(*path);
4561d5ec6daSjoerg 			return 1;
4571d5ec6daSjoerg 		case 'N':
4581d5ec6daSjoerg 			n_opt = 1;
4591d5ec6daSjoerg 			/* FALL THROUGH */
4601d5ec6daSjoerg 		case 'n':
4611d5ec6daSjoerg 			return -1;
4621d5ec6daSjoerg 		case 'r':
4631d5ec6daSjoerg 		case 'R':
4641d5ec6daSjoerg 			printf("New name: ");
4651d5ec6daSjoerg 			fflush(stdout);
4661d5ec6daSjoerg 			free(*path);
4671d5ec6daSjoerg 			*path = NULL;
4681d5ec6daSjoerg 			alen = 0;
4691d5ec6daSjoerg 			len = getline(path, &alen, stdin);
470baeba406Swiz 			if ((*path)[len - 1] == '\n')
4711d5ec6daSjoerg 				(*path)[len - 1] = '\0';
4721d5ec6daSjoerg 			return 0;
4731d5ec6daSjoerg 		default:
4741d5ec6daSjoerg 			break;
4751d5ec6daSjoerg 		}
4761d5ec6daSjoerg 	}
4771d5ec6daSjoerg }
4781d5ec6daSjoerg 
479a0922fbdSjoerg /*
480ee86edd5Sjoerg  * Detect binary files by a combination of character white list and
481ee86edd5Sjoerg  * black list. NUL bytes and other control codes without use in text files
482ee86edd5Sjoerg  * result directly in switching the file to binary mode. Otherwise, at least
483ee86edd5Sjoerg  * one white-listed byte has to be found.
484ee86edd5Sjoerg  *
485ee86edd5Sjoerg  * Black-listed: 0..6, 14..25, 28..31
486ee86edd5Sjoerg  * White-listed: 9..10, 13, >= 32
487ee86edd5Sjoerg  *
488ee86edd5Sjoerg  * See the proginfo/txtvsbin.txt in the zip sources for a detailed discussion.
489ee86edd5Sjoerg  */
490ee86edd5Sjoerg #define BYTE_IS_BINARY(x)	((x) < 32 && (0xf3ffc07fU & (1U << (x))))
491ee86edd5Sjoerg #define	BYTE_IS_TEXT(x)		((x) >= 32 || (0x00002600U & (1U << (x))))
492ee86edd5Sjoerg 
493ee86edd5Sjoerg static int
check_binary(const unsigned char * buf,size_t len)494ee86edd5Sjoerg check_binary(const unsigned char *buf, size_t len)
495ee86edd5Sjoerg {
496ee86edd5Sjoerg 	int rv;
497ee86edd5Sjoerg 	for (rv = 1; len--; ++buf) {
498ee86edd5Sjoerg 		if (BYTE_IS_BINARY(*buf))
499ee86edd5Sjoerg 			return 1;
500ee86edd5Sjoerg 		if (BYTE_IS_TEXT(*buf))
501ee86edd5Sjoerg 			rv = 0;
502ee86edd5Sjoerg 	}
503ee86edd5Sjoerg 
504ee86edd5Sjoerg 	return rv;
505ee86edd5Sjoerg }
506ee86edd5Sjoerg 
507ee86edd5Sjoerg /*
508e601e1c3Schristos  * Extract to a file descriptor
509e601e1c3Schristos  */
510e601e1c3Schristos static int
extract2fd(struct archive * a,char * pathname,int fd)511e601e1c3Schristos extract2fd(struct archive *a, char *pathname, int fd)
512e601e1c3Schristos {
513e601e1c3Schristos 	int cr, text, warn;
514e601e1c3Schristos 	ssize_t len;
515e601e1c3Schristos 	unsigned char *p, *q, *end;
516e601e1c3Schristos 
517e601e1c3Schristos 	text = a_opt;
518e601e1c3Schristos 	warn = 0;
519e601e1c3Schristos 	cr = 0;
520e601e1c3Schristos 
521e601e1c3Schristos 	/* loop over file contents and write to fd */
522e601e1c3Schristos 	for (int n = 0; ; n++) {
523e601e1c3Schristos 		if (fd != STDOUT_FILENO)
524e601e1c3Schristos 			if (tty && (n % 4) == 0)
525e601e1c3Schristos 				info(" %c\b\b", spinner[(n / 4) % sizeof spinner]);
526e601e1c3Schristos 
527e601e1c3Schristos 		len = archive_read_data(a, buffer, sizeof buffer);
528e601e1c3Schristos 
529e601e1c3Schristos 		if (len < 0)
530e601e1c3Schristos 			ac(len);
531e601e1c3Schristos 
532e601e1c3Schristos 		/* left over CR from previous buffer */
533e601e1c3Schristos 		if (a_opt && cr) {
534e601e1c3Schristos 			if (len == 0 || buffer[0] != '\n')
535e601e1c3Schristos 				if (write(fd, "\r", 1) != 1)
536e601e1c3Schristos 					error("write('%s')", pathname);
537e601e1c3Schristos 			cr = 0;
538e601e1c3Schristos 		}
539e601e1c3Schristos 
540e601e1c3Schristos 		/* EOF */
541e601e1c3Schristos 		if (len == 0)
542e601e1c3Schristos 			break;
543e601e1c3Schristos 		end = buffer + len;
544e601e1c3Schristos 
545e601e1c3Schristos 		/*
546e601e1c3Schristos 		 * Detect whether this is a text file.  The correct way to
547e601e1c3Schristos 		 * do this is to check the least significant bit of the
548e601e1c3Schristos 		 * "internal file attributes" field of the corresponding
549e601e1c3Schristos 		 * file header in the central directory, but libarchive
550e601e1c3Schristos 		 * does not provide access to this field, so we have to
551e601e1c3Schristos 		 * guess by looking for non-ASCII characters in the
552e601e1c3Schristos 		 * buffer.  Hopefully we won't guess wrong.  If we do
553e601e1c3Schristos 		 * guess wrong, we print a warning message later.
554e601e1c3Schristos 		 */
555e601e1c3Schristos 		if (a_opt && n == 0) {
556e601e1c3Schristos 			if (check_binary(buffer, len))
557e601e1c3Schristos 				text = 0;
558e601e1c3Schristos 		}
559e601e1c3Schristos 
560e601e1c3Schristos 		/* simple case */
561e601e1c3Schristos 		if (!a_opt || !text) {
562e601e1c3Schristos 			if (write(fd, buffer, len) != len)
563e601e1c3Schristos 				error("write('%s')", pathname);
564e601e1c3Schristos 			continue;
565e601e1c3Schristos 		}
566e601e1c3Schristos 
567e601e1c3Schristos 		/* hard case: convert \r\n to \n (sigh...) */
568e601e1c3Schristos 		for (p = buffer; p < end; p = q + 1) {
569e601e1c3Schristos 			for (q = p; q < end; q++) {
570e601e1c3Schristos 				if (!warn && BYTE_IS_BINARY(*q)) {
571e601e1c3Schristos 					warningx("%s may be corrupted due"
572e601e1c3Schristos 					    " to weak text file detection"
573e601e1c3Schristos 					    " heuristic", pathname);
574e601e1c3Schristos 					warn = 1;
575e601e1c3Schristos 				}
576e601e1c3Schristos 				if (q[0] != '\r')
577e601e1c3Schristos 					continue;
578e601e1c3Schristos 				if (&q[1] == end) {
579e601e1c3Schristos 					cr = 1;
580e601e1c3Schristos 					break;
581e601e1c3Schristos 				}
582e601e1c3Schristos 				if (q[1] == '\n')
583e601e1c3Schristos 					break;
584e601e1c3Schristos 			}
585e601e1c3Schristos 			if (write(fd, p, q - p) != q - p)
586e601e1c3Schristos 				error("write('%s')", pathname);
587e601e1c3Schristos 		}
588e601e1c3Schristos 	}
589e601e1c3Schristos 
590e601e1c3Schristos 	return text;
591e601e1c3Schristos }
592e601e1c3Schristos 
593e601e1c3Schristos /*
594a0922fbdSjoerg  * Extract a regular file.
595a0922fbdSjoerg  */
596a0922fbdSjoerg static void
extract_file(struct archive * a,struct archive_entry * e,char ** path)5971d5ec6daSjoerg extract_file(struct archive *a, struct archive_entry *e, char **path)
598a0922fbdSjoerg {
599a0922fbdSjoerg 	int mode;
600a0922fbdSjoerg 	time_t mtime;
601a0922fbdSjoerg 	struct stat sb;
602a0922fbdSjoerg 	struct timeval tv[2];
603e601e1c3Schristos 	int fd, check, text;
604e91e7747Schristos 	const char *linkname;
605a0922fbdSjoerg 
606f5e83071Smbalmer 	mode = archive_entry_mode(e) & 0777;
607a0922fbdSjoerg 	if (mode == 0)
608a0922fbdSjoerg 		mode = 0644;
609a0922fbdSjoerg 	mtime = archive_entry_mtime(e);
610a0922fbdSjoerg 
611a0922fbdSjoerg 	/* look for existing file of same name */
6121d5ec6daSjoerg recheck:
6131d5ec6daSjoerg 	if (lstat(*path, &sb) == 0) {
614a0922fbdSjoerg 		if (u_opt || f_opt) {
615a0922fbdSjoerg 			/* check if up-to-date */
616a0922fbdSjoerg 			if (S_ISREG(sb.st_mode) && sb.st_mtime >= mtime)
617a0922fbdSjoerg 				return;
6181d5ec6daSjoerg 			(void)unlink(*path);
619a0922fbdSjoerg 		} else if (o_opt) {
620a0922fbdSjoerg 			/* overwrite */
6211d5ec6daSjoerg 			(void)unlink(*path);
622a0922fbdSjoerg 		} else if (n_opt) {
623a0922fbdSjoerg 			/* do not overwrite */
624a0922fbdSjoerg 			return;
625a0922fbdSjoerg 		} else {
6261d5ec6daSjoerg 			check = handle_existing_file(path);
6271d5ec6daSjoerg 			if (check == 0)
6281d5ec6daSjoerg 				goto recheck;
6291d5ec6daSjoerg 			if (check == -1)
6301d5ec6daSjoerg 				return; /* do not overwrite */
631a0922fbdSjoerg 		}
632a0922fbdSjoerg 	} else {
633a0922fbdSjoerg 		if (f_opt)
634a0922fbdSjoerg 			return;
635a0922fbdSjoerg 	}
636a0922fbdSjoerg 
6379ca6b5e9Schristos 	tv[0].tv_sec = now;
6389ca6b5e9Schristos 	tv[0].tv_usec = 0;
6399ca6b5e9Schristos 	tv[1].tv_sec = mtime;
6409ca6b5e9Schristos 	tv[1].tv_usec = 0;
6419ca6b5e9Schristos 
642e91e7747Schristos 	/* process symlinks */
643e91e7747Schristos 	linkname = archive_entry_symlink(e);
644e91e7747Schristos 	if (linkname != NULL) {
645e91e7747Schristos 		if (symlink(linkname, *path) == -1)
646e91e7747Schristos 			error("symlink('%s', '%s')", linkname, *path);
647e91e7747Schristos 		info(" extracting: %s -> %s\n", *path, linkname);
648e91e7747Schristos 		if (lchmod(*path, mode) == -1)
649e91e7747Schristos 			warning("Cannot set mode for '%s'", *path);
650e91e7747Schristos 		if (lutimes(*path, tv) == -1)
651e91e7747Schristos 			warning("utimes('%s')", *path);
652e91e7747Schristos 		return;
653e91e7747Schristos 	}
654e91e7747Schristos 
655e91e7747Schristos 	/* process hardlinks */
656e91e7747Schristos 	linkname = archive_entry_hardlink(e);
657e91e7747Schristos 	if (linkname != NULL) {
658e91e7747Schristos 		if (link(linkname, *path) == -1)
659e91e7747Schristos 			error("link('%s', '%s')", linkname, *path);
660e91e7747Schristos 		info(" extracting: %s link to %s\n", *path, linkname);
661e91e7747Schristos 		return;
662e91e7747Schristos 	}
663e91e7747Schristos 
6641d5ec6daSjoerg 	if ((fd = open(*path, O_RDWR|O_CREAT|O_TRUNC, mode)) < 0)
6651d5ec6daSjoerg 		error("open('%s')", *path);
666a0922fbdSjoerg 
6671d5ec6daSjoerg 	info(" extracting: %s", *path);
668a0922fbdSjoerg 
669e601e1c3Schristos 	text = extract2fd(a, *path, fd);
670a0922fbdSjoerg 
671a0922fbdSjoerg 	if (tty)
672a0922fbdSjoerg 		info("  \b\b");
673a0922fbdSjoerg 	if (text)
674a0922fbdSjoerg 		info(" (text)");
675a0922fbdSjoerg 	info("\n");
676a0922fbdSjoerg 
677a0922fbdSjoerg 	/* set access and modification time */
678a0922fbdSjoerg 	if (futimes(fd, tv) != 0)
6791d5ec6daSjoerg 		error("utimes('%s')", *path);
680a0922fbdSjoerg 	if (close(fd) != 0)
6811d5ec6daSjoerg 		error("close('%s')", *path);
682a0922fbdSjoerg }
683a0922fbdSjoerg 
684a0922fbdSjoerg /*
685a0922fbdSjoerg  * Extract a zipfile entry: first perform some sanity checks to ensure
686a0922fbdSjoerg  * that it is either a directory or a regular file and that the path is
687a0922fbdSjoerg  * not absolute and does not try to break out of the current directory;
688a0922fbdSjoerg  * then call either extract_dir() or extract_file() as appropriate.
689a0922fbdSjoerg  *
690a0922fbdSjoerg  * This is complicated a bit by the various ways in which we need to
691a0922fbdSjoerg  * manipulate the path name.  Case conversion (if requested by the -L
692a0922fbdSjoerg  * option) happens first, but the include / exclude patterns are applied
693a0922fbdSjoerg  * to the full converted path name, before the directory part of the path
694a0922fbdSjoerg  * is removed in accordance with the -j option.  Sanity checks are
695a0922fbdSjoerg  * intentionally done earlier than they need to be, so the user will get a
696a0922fbdSjoerg  * warning about insecure paths even for files or directories which
697a0922fbdSjoerg  * wouldn't be extracted anyway.
698a0922fbdSjoerg  */
699a0922fbdSjoerg static void
extract(struct archive * a,struct archive_entry * e)700a0922fbdSjoerg extract(struct archive *a, struct archive_entry *e)
701a0922fbdSjoerg {
702a0922fbdSjoerg 	char *pathname, *realpathname;
703a0922fbdSjoerg 	mode_t filetype;
704a0922fbdSjoerg 	char *p, *q;
705a0922fbdSjoerg 
706a0922fbdSjoerg 	pathname = pathdup(archive_entry_pathname(e));
707a0922fbdSjoerg 	filetype = archive_entry_filetype(e);
708a0922fbdSjoerg 
709a0922fbdSjoerg 	/* sanity checks */
710a0922fbdSjoerg 	if (pathname[0] == '/' ||
711a0922fbdSjoerg 	    strncmp(pathname, "../", 3) == 0 ||
712a0922fbdSjoerg 	    strstr(pathname, "/../") != NULL) {
713a0922fbdSjoerg 		warningx("skipping insecure entry '%s'", pathname);
714a0922fbdSjoerg 		ac(archive_read_data_skip(a));
715a0922fbdSjoerg 		free(pathname);
716a0922fbdSjoerg 		return;
717a0922fbdSjoerg 	}
718a0922fbdSjoerg 
719a0922fbdSjoerg 	/* I don't think this can happen in a zipfile.. */
720e91e7747Schristos 	if (!S_ISDIR(filetype) && !S_ISREG(filetype) && !S_ISLNK(filetype)) {
721a0922fbdSjoerg 		warningx("skipping non-regular entry '%s'", pathname);
722a0922fbdSjoerg 		ac(archive_read_data_skip(a));
723a0922fbdSjoerg 		free(pathname);
724a0922fbdSjoerg 		return;
725a0922fbdSjoerg 	}
726a0922fbdSjoerg 
727a0922fbdSjoerg 	/* skip directories in -j case */
728a0922fbdSjoerg 	if (S_ISDIR(filetype) && j_opt) {
729a0922fbdSjoerg 		ac(archive_read_data_skip(a));
730a0922fbdSjoerg 		free(pathname);
731a0922fbdSjoerg 		return;
732a0922fbdSjoerg 	}
733a0922fbdSjoerg 
734a0922fbdSjoerg 	/* apply include / exclude patterns */
735a0922fbdSjoerg 	if (!accept_pathname(pathname)) {
736a0922fbdSjoerg 		ac(archive_read_data_skip(a));
737a0922fbdSjoerg 		free(pathname);
738a0922fbdSjoerg 		return;
739a0922fbdSjoerg 	}
740a0922fbdSjoerg 
741a0922fbdSjoerg 	/* apply -j and -d */
742a0922fbdSjoerg 	if (j_opt) {
743a0922fbdSjoerg 		for (p = q = pathname; *p; ++p)
744a0922fbdSjoerg 			if (*p == '/')
745a0922fbdSjoerg 				q = p + 1;
746a0922fbdSjoerg 		realpathname = pathcat(d_arg, q);
747a0922fbdSjoerg 	} else {
748a0922fbdSjoerg 		realpathname = pathcat(d_arg, pathname);
749a0922fbdSjoerg 	}
750a0922fbdSjoerg 
751a0922fbdSjoerg 	/* ensure that parent directory exists */
752a0922fbdSjoerg 	make_parent(realpathname);
753a0922fbdSjoerg 
754a0922fbdSjoerg 	if (S_ISDIR(filetype))
755a0922fbdSjoerg 		extract_dir(a, e, realpathname);
756a0922fbdSjoerg 	else
7571d5ec6daSjoerg 		extract_file(a, e, &realpathname);
758a0922fbdSjoerg 
759a0922fbdSjoerg 	free(realpathname);
760a0922fbdSjoerg 	free(pathname);
761a0922fbdSjoerg }
762a0922fbdSjoerg 
7635e2ef53fSjoerg static void
extract_stdout(struct archive * a,struct archive_entry * e)7645e2ef53fSjoerg extract_stdout(struct archive *a, struct archive_entry *e)
7655e2ef53fSjoerg {
7665e2ef53fSjoerg 	char *pathname;
7675e2ef53fSjoerg 	mode_t filetype;
7685e2ef53fSjoerg 
7695e2ef53fSjoerg 	pathname = pathdup(archive_entry_pathname(e));
7705e2ef53fSjoerg 	filetype = archive_entry_filetype(e);
7715e2ef53fSjoerg 
7725e2ef53fSjoerg 	/* I don't think this can happen in a zipfile.. */
773e91e7747Schristos 	if (!S_ISDIR(filetype) && !S_ISREG(filetype) && !S_ISLNK(filetype)) {
7745e2ef53fSjoerg 		warningx("skipping non-regular entry '%s'", pathname);
7755e2ef53fSjoerg 		ac(archive_read_data_skip(a));
7765e2ef53fSjoerg 		free(pathname);
7775e2ef53fSjoerg 		return;
7785e2ef53fSjoerg 	}
7795e2ef53fSjoerg 
7805e2ef53fSjoerg 	/* skip directories in -j case */
7815e2ef53fSjoerg 	if (S_ISDIR(filetype)) {
7825e2ef53fSjoerg 		ac(archive_read_data_skip(a));
7835e2ef53fSjoerg 		free(pathname);
7845e2ef53fSjoerg 		return;
7855e2ef53fSjoerg 	}
7865e2ef53fSjoerg 
7875e2ef53fSjoerg 	/* apply include / exclude patterns */
7885e2ef53fSjoerg 	if (!accept_pathname(pathname)) {
7895e2ef53fSjoerg 		ac(archive_read_data_skip(a));
7905e2ef53fSjoerg 		free(pathname);
7915e2ef53fSjoerg 		return;
7925e2ef53fSjoerg 	}
7935e2ef53fSjoerg 
79444b11f39Sjoerg 	if (c_opt)
79544b11f39Sjoerg 		info("x %s\n", pathname);
79644b11f39Sjoerg 
797e601e1c3Schristos 	(void)extract2fd(a, pathname, STDOUT_FILENO);
7985e2ef53fSjoerg 
7995e2ef53fSjoerg 	free(pathname);
8005e2ef53fSjoerg }
8015e2ef53fSjoerg 
802a0922fbdSjoerg /*
803a0922fbdSjoerg  * Print the name of an entry to stdout.
804a0922fbdSjoerg  */
805a0922fbdSjoerg static void
list(struct archive * a,struct archive_entry * e)806a0922fbdSjoerg list(struct archive *a, struct archive_entry *e)
807a0922fbdSjoerg {
8085e2ef53fSjoerg 	char buf[20];
8095e2ef53fSjoerg 	time_t mtime;
810a8ab691eSchristos 	struct tm *tm;
811a0922fbdSjoerg 
8125e2ef53fSjoerg 	mtime = archive_entry_mtime(e);
813a8ab691eSchristos 	tm = localtime(&mtime);
814a8ab691eSchristos 	if (*y_str)
815a8ab691eSchristos 		strftime(buf, sizeof(buf), "%m-%d-%G %R", tm);
816a8ab691eSchristos 	else
817a8ab691eSchristos 		strftime(buf, sizeof(buf), "%m-%d-%g %R", tm);
81844b11f39Sjoerg 
81944b11f39Sjoerg 	if (v_opt == 1) {
82044b11f39Sjoerg 		printf(" %8ju  %s   %s\n",
82144b11f39Sjoerg 		    (uintmax_t)archive_entry_size(e),
82244b11f39Sjoerg 		    buf, archive_entry_pathname(e));
82344b11f39Sjoerg 	} else if (v_opt == 2) {
8245e2ef53fSjoerg 		printf("%8ju  Stored  %7ju   0%%  %s  %08x  %s\n",
8255e2ef53fSjoerg 		    (uintmax_t)archive_entry_size(e),
8265e2ef53fSjoerg 		    (uintmax_t)archive_entry_size(e),
8275e2ef53fSjoerg 		    buf,
8285e2ef53fSjoerg 		    0U,
8295e2ef53fSjoerg 		    archive_entry_pathname(e));
8305e2ef53fSjoerg 	}
831a0922fbdSjoerg 	ac(archive_read_data_skip(a));
832a0922fbdSjoerg }
833a0922fbdSjoerg 
834a0922fbdSjoerg /*
835a0922fbdSjoerg  * Extract to memory to check CRC
836a0922fbdSjoerg  */
8375705f526Swiz static int
test(struct archive * a,struct archive_entry * e)838a0922fbdSjoerg test(struct archive *a, struct archive_entry *e)
839a0922fbdSjoerg {
840a0922fbdSjoerg 	ssize_t len;
8415705f526Swiz 	int error_count;
842a0922fbdSjoerg 
8435705f526Swiz 	error_count = 0;
844a0922fbdSjoerg 	if (S_ISDIR(archive_entry_filetype(e)))
8455705f526Swiz 		return 0;
846a0922fbdSjoerg 
8475705f526Swiz 	info("    testing: %s\t", archive_entry_pathname(e));
848a0922fbdSjoerg 	while ((len = archive_read_data(a, buffer, sizeof buffer)) > 0)
849a0922fbdSjoerg 		/* nothing */;
850a0922fbdSjoerg 	if (len < 0) {
851a0922fbdSjoerg 		info(" %s\n", archive_error_string(a));
8525705f526Swiz 		++error_count;
853a0922fbdSjoerg 	} else {
854a0922fbdSjoerg 		info(" OK\n");
855a0922fbdSjoerg 	}
856a0922fbdSjoerg 
857a0922fbdSjoerg 	/* shouldn't be necessary, but it doesn't hurt */
858a0922fbdSjoerg 	ac(archive_read_data_skip(a));
8595705f526Swiz 
8605705f526Swiz 	return error_count;
861a0922fbdSjoerg }
862a0922fbdSjoerg 
863a0922fbdSjoerg /*
86409f8ff84Schristos  * Callback function for reading passphrase.
86509f8ff84Schristos  * Originally from cpio.c and passphrase.c, libarchive.
86609f8ff84Schristos  */
86709f8ff84Schristos static const char *
passphrase_callback(struct archive * a,void * client_data)86809f8ff84Schristos passphrase_callback(struct archive *a, void *client_data)
86909f8ff84Schristos {
87009f8ff84Schristos 	char *p;
87109f8ff84Schristos 	static const char prompt[] = "\nEnter passphrase:";
87209f8ff84Schristos 
87309f8ff84Schristos 	(void)a; /* UNUSED */
87409f8ff84Schristos 	(void)client_data; /* UNUSED */
87509f8ff84Schristos 
87609f8ff84Schristos #if defined(RPP_ECHO_OFF)
87709f8ff84Schristos 	p = readpassphrase(prompt, passbuf, sizeof(passbuf), RPP_ECHO_OFF);
87809f8ff84Schristos #elif defined(GETPASS_NEED_TTY)
87909f8ff84Schristos 	p = getpass_r(prompt, passbuf, sizeof(passbuf));
88009f8ff84Schristos #else
88109f8ff84Schristos 	p = getpass(prompt);
88209f8ff84Schristos 	if (p != NULL)
88309f8ff84Schristos 		strlcpy(passbuf, p, sizeof(passbuf));
88409f8ff84Schristos #endif
88509f8ff84Schristos 	if (p == NULL && errno != EINTR)
88609f8ff84Schristos 		error("Error reading password");
88709f8ff84Schristos 
88809f8ff84Schristos 	return p;
88909f8ff84Schristos }
89009f8ff84Schristos 
89109f8ff84Schristos /*
892a0922fbdSjoerg  * Main loop: open the zipfile, iterate over its contents and decide what
893a0922fbdSjoerg  * to do with each entry.
894a0922fbdSjoerg  */
895a0922fbdSjoerg static void
unzip(const char * fn)896a0922fbdSjoerg unzip(const char *fn)
897a0922fbdSjoerg {
898a0922fbdSjoerg 	struct archive *a;
899a0922fbdSjoerg 	struct archive_entry *e;
900e601e1c3Schristos 	int ret;
9015705f526Swiz 	uintmax_t total_size, file_count, error_count;
902a0922fbdSjoerg 
903e601e1c3Schristos 	if ((a = archive_read_new()) == NULL)
904e601e1c3Schristos 		error("archive_read_new failed");
905a0922fbdSjoerg 
906a0922fbdSjoerg 	ac(archive_read_support_format_zip(a));
90709f8ff84Schristos 
90809f8ff84Schristos 	if (P_arg)
90909f8ff84Schristos 		archive_read_add_passphrase(a, P_arg);
91009f8ff84Schristos 	else
91109f8ff84Schristos 		archive_read_set_passphrase_callback(a, passbuf, &passphrase_callback);
91209f8ff84Schristos 
913e601e1c3Schristos 	ac(archive_read_open_filename(a, fn, 8192));
914a0922fbdSjoerg 
915149eb584Sjoerg 	if (!q_opt && !p_opt)
916a3879acfSwiz 	    printf("Archive:  %s\n", fn);
9175746a940Swiz 
91844b11f39Sjoerg 	if (v_opt == 1) {
919a8ab691eSchristos 		printf("  Length     %sDate   Time    Name\n", y_str);
920a8ab691eSchristos 		printf(" --------    %s----   ----    ----\n", y_str);
92144b11f39Sjoerg 	} else if (v_opt == 2) {
922a8ab691eSchristos 		printf(" Length   Method    Size  Ratio   %sDate   Time   CRC-32    Name\n", y_str);
923a8ab691eSchristos 		printf("--------  ------  ------- -----   %s----   ----   ------    ----\n", y_str);
92444b11f39Sjoerg 	}
92544b11f39Sjoerg 
92644b11f39Sjoerg 	total_size = 0;
92744b11f39Sjoerg 	file_count = 0;
9285705f526Swiz 	error_count = 0;
929a0922fbdSjoerg 	for (;;) {
930a0922fbdSjoerg 		ret = archive_read_next_header(a, &e);
931a0922fbdSjoerg 		if (ret == ARCHIVE_EOF)
932a0922fbdSjoerg 			break;
933a0922fbdSjoerg 		ac(ret);
934a0922fbdSjoerg 		if (t_opt)
9355705f526Swiz 			error_count += test(a, e);
9365e2ef53fSjoerg 		else if (v_opt)
937a0922fbdSjoerg 			list(a, e);
93844b11f39Sjoerg 		else if (p_opt || c_opt)
9395e2ef53fSjoerg 			extract_stdout(a, e);
940a0922fbdSjoerg 		else
941a0922fbdSjoerg 			extract(a, e);
94244b11f39Sjoerg 
94344b11f39Sjoerg 		total_size += archive_entry_size(e);
94444b11f39Sjoerg 		++file_count;
94544b11f39Sjoerg 	}
94644b11f39Sjoerg 
94744b11f39Sjoerg 	if (v_opt == 1) {
948a8ab691eSchristos 		printf(" --------                   %s-------\n", y_str);
949a8ab691eSchristos 		printf(" %8ju                   %s%ju file%s\n",
950a8ab691eSchristos 		    total_size, y_str, file_count, file_count != 1 ? "s" : "");
95144b11f39Sjoerg 	} else if (v_opt == 2) {
952a8ab691eSchristos 		printf("--------          -------  ---                            %s-------\n", y_str);
953a8ab691eSchristos 		printf("%8ju          %7ju   0%%                            %s%ju file%s\n",
954a8ab691eSchristos 		    total_size, total_size, y_str, file_count,
95544b11f39Sjoerg 		    file_count != 1 ? "s" : "");
956a0922fbdSjoerg 	}
957a0922fbdSjoerg 
9580a49f30eSjoerg 	ac(archive_read_free(a));
9595705f526Swiz 
96009f8ff84Schristos 	explicit_memset(passbuf, 0, sizeof(passbuf));
9615705f526Swiz 	if (t_opt) {
9625705f526Swiz 		if (error_count > 0) {
963c538b3a2Sjoerg 			errorx("%ju checksum error(s) found.", error_count);
9645705f526Swiz 		}
9655705f526Swiz 		else {
9665705f526Swiz 			printf("No errors detected in compressed data of %s.\n",
9675705f526Swiz 			       fn);
9685705f526Swiz 		}
9695705f526Swiz 	}
970a0922fbdSjoerg }
971a0922fbdSjoerg 
972a8ab691eSchristos static void __dead
usage(void)973a0922fbdSjoerg usage(void)
974a0922fbdSjoerg {
975a0922fbdSjoerg 
97609f8ff84Schristos 	fprintf(stderr, "Usage: %s [-aCcfjLlnopqtuvy] [-d <dir>] "
97709f8ff84Schristos 	    "[-x <pattern>] [-P <passphrase>] <zipfile>\n", getprogname());
97809f8ff84Schristos 	exit(EXIT_FAILURE);
979a0922fbdSjoerg }
980a0922fbdSjoerg 
981a0922fbdSjoerg static int
getopts(int argc,char * argv[])982a0922fbdSjoerg getopts(int argc, char *argv[])
983a0922fbdSjoerg {
984a0922fbdSjoerg 	int opt;
98509f8ff84Schristos 	size_t len;
986a0922fbdSjoerg 
987bd0f5c16Schristos #ifdef __GLIBC__
988bd0f5c16Schristos 	optind = 0;
989bd0f5c16Schristos #else
990a0922fbdSjoerg  	optreset = optind = 1;
991bd0f5c16Schristos #endif
992bd0f5c16Schristos 
993bd0f5c16Schristos  	while ((opt = getopt(argc, argv, "aCcd:fjLlnopP:qtuvyx:")) != -1)
994a0922fbdSjoerg 		switch (opt) {
995a0922fbdSjoerg 		case 'a':
996a0922fbdSjoerg 			a_opt = 1;
997a0922fbdSjoerg 			break;
9985705f526Swiz 		case 'C':
9995705f526Swiz 			C_opt = 1;
10005705f526Swiz 			break;
100144b11f39Sjoerg 		case 'c':
100244b11f39Sjoerg 			c_opt = 1;
100344b11f39Sjoerg 			break;
1004a0922fbdSjoerg 		case 'd':
1005a0922fbdSjoerg 			d_arg = optarg;
1006a0922fbdSjoerg 			break;
1007a0922fbdSjoerg 		case 'f':
1008a0922fbdSjoerg 			f_opt = 1;
1009a0922fbdSjoerg 			break;
1010a0922fbdSjoerg 		case 'j':
1011a0922fbdSjoerg 			j_opt = 1;
1012a0922fbdSjoerg 			break;
1013a0922fbdSjoerg 		case 'L':
1014a0922fbdSjoerg 			L_opt = 1;
1015a0922fbdSjoerg 			break;
1016a0922fbdSjoerg 		case 'l':
10175e2ef53fSjoerg 			if (v_opt == 0)
10185e2ef53fSjoerg 				v_opt = 1;
1019a0922fbdSjoerg 			break;
1020a0922fbdSjoerg 		case 'n':
1021a0922fbdSjoerg 			n_opt = 1;
1022a0922fbdSjoerg 			break;
1023a0922fbdSjoerg 		case 'o':
1024a0922fbdSjoerg 			o_opt = 1;
10255e2ef53fSjoerg 			q_opt = 1;
10265e2ef53fSjoerg 			break;
10275e2ef53fSjoerg 		case 'p':
10285e2ef53fSjoerg 			p_opt = 1;
1029a0922fbdSjoerg 			break;
103009f8ff84Schristos 		case 'P':
103109f8ff84Schristos 			len = strlcpy(passbuf, optarg, sizeof(passbuf));
103209f8ff84Schristos 			memset(optarg, '*', len);
103309f8ff84Schristos 			P_arg = passbuf;
103409f8ff84Schristos 			break;
1035a0922fbdSjoerg 		case 'q':
1036a0922fbdSjoerg 			q_opt = 1;
1037a0922fbdSjoerg 			break;
1038a0922fbdSjoerg 		case 't':
1039a0922fbdSjoerg 			t_opt = 1;
1040a0922fbdSjoerg 			break;
1041a0922fbdSjoerg 		case 'u':
1042a0922fbdSjoerg 			u_opt = 1;
1043a0922fbdSjoerg 			break;
10445e2ef53fSjoerg 		case 'v':
10455e2ef53fSjoerg 			v_opt = 2;
10465e2ef53fSjoerg 			break;
1047a0922fbdSjoerg 		case 'x':
1048a0922fbdSjoerg 			add_pattern(&exclude, optarg);
1049a0922fbdSjoerg 			break;
1050a8ab691eSchristos 		case 'y':
1051a8ab691eSchristos 			y_str = "  ";
1052a8ab691eSchristos 			break;
1053a0922fbdSjoerg 		default:
1054a0922fbdSjoerg 			usage();
1055a0922fbdSjoerg 		}
1056a0922fbdSjoerg 
105709f8ff84Schristos 	return optind;
1058a0922fbdSjoerg }
1059a0922fbdSjoerg 
1060a0922fbdSjoerg int
main(int argc,char * argv[])1061a0922fbdSjoerg main(int argc, char *argv[])
1062a0922fbdSjoerg {
1063a0922fbdSjoerg 	const char *zipfile;
1064a0922fbdSjoerg 	int nopts;
1065a0922fbdSjoerg 
1066a0922fbdSjoerg 	if (isatty(STDOUT_FILENO))
1067a0922fbdSjoerg 		tty = 1;
1068a0922fbdSjoerg 
1069a0922fbdSjoerg 	if (getenv("UNZIP_DEBUG") != NULL)
1070a0922fbdSjoerg 		unzip_debug = 1;
1071a0922fbdSjoerg 	for (int i = 0; i < argc; ++i)
1072a0922fbdSjoerg 		debug("%s%c", argv[i], (i < argc - 1) ? ' ' : '\n');
1073a0922fbdSjoerg 
1074a0922fbdSjoerg 	/*
1075a0922fbdSjoerg 	 * Info-ZIP's unzip(1) expects certain options to come before the
1076a0922fbdSjoerg 	 * zipfile name, and others to come after - though it does not
1077a0922fbdSjoerg 	 * enforce this.  For simplicity, we accept *all* options both
1078a0922fbdSjoerg 	 * before and after the zipfile name.
1079a0922fbdSjoerg 	 */
1080a0922fbdSjoerg 	nopts = getopts(argc, argv);
1081a0922fbdSjoerg 
1082a0922fbdSjoerg 	if (argc <= nopts)
1083a0922fbdSjoerg 		usage();
1084a0922fbdSjoerg 	zipfile = argv[nopts++];
1085a0922fbdSjoerg 
1086e601e1c3Schristos 	if (strcmp(zipfile, "-") == 0)
1087e601e1c3Schristos 		zipfile = NULL; /* STDIN */
1088e601e1c3Schristos 
1089a0922fbdSjoerg 	while (nopts < argc && *argv[nopts] != '-')
1090a0922fbdSjoerg 		add_pattern(&include, argv[nopts++]);
1091a0922fbdSjoerg 
1092a0922fbdSjoerg 	nopts--; /* fake argv[0] */
1093a0922fbdSjoerg 	nopts += getopts(argc - nopts, argv + nopts);
1094a0922fbdSjoerg 
1095a0922fbdSjoerg 	if (n_opt + o_opt + u_opt > 1)
1096a0922fbdSjoerg 		errorx("-n, -o and -u are contradictory");
1097a0922fbdSjoerg 
1098a0922fbdSjoerg 	time(&now);
1099a0922fbdSjoerg 
1100a0922fbdSjoerg 	unzip(zipfile);
1101a0922fbdSjoerg 
110209f8ff84Schristos 	exit(EXIT_SUCCESS);
1103a0922fbdSjoerg }
1104