Removed
Link Here
|
1 |
/*- |
2 |
* SPDX-License-Identifier: BSD-2-Clause |
3 |
* |
4 |
* Copyright (c) 2009, 2010 Joerg Sonnenberger <joerg@NetBSD.org> |
5 |
* Copyright (c) 2007-2008 Dag-Erling Smørgrav |
6 |
* All rights reserved. |
7 |
* |
8 |
* Redistribution and use in source and binary forms, with or without |
9 |
* modification, are permitted provided that the following conditions |
10 |
* are met: |
11 |
* 1. Redistributions of source code must retain the above copyright |
12 |
* notice, this list of conditions and the following disclaimer |
13 |
* in this position and unchanged. |
14 |
* 2. Redistributions in binary form must reproduce the above copyright |
15 |
* notice, this list of conditions and the following disclaimer in the |
16 |
* documentation and/or other materials provided with the distribution. |
17 |
* |
18 |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
19 |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
20 |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
21 |
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
22 |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
23 |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
24 |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
25 |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
26 |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
27 |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
28 |
* SUCH DAMAGE. |
29 |
* |
30 |
* $FreeBSD$ |
31 |
* |
32 |
* This file would be much shorter if we didn't care about command-line |
33 |
* compatibility with Info-ZIP's UnZip, which requires us to duplicate |
34 |
* parts of libarchive in order to gain more detailed control of its |
35 |
* behaviour for the purpose of implementing the -n, -o, -L and -a |
36 |
* options. |
37 |
*/ |
38 |
|
39 |
#include <sys/queue.h> |
40 |
#include <sys/stat.h> |
41 |
|
42 |
#include <ctype.h> |
43 |
#include <errno.h> |
44 |
#include <fcntl.h> |
45 |
#include <fnmatch.h> |
46 |
#include <stdarg.h> |
47 |
#include <stdio.h> |
48 |
#include <stdlib.h> |
49 |
#include <string.h> |
50 |
#include <unistd.h> |
51 |
|
52 |
#include <archive.h> |
53 |
#include <archive_entry.h> |
54 |
#include <readpassphrase.h> |
55 |
|
56 |
/* command-line options */ |
57 |
static int a_opt; /* convert EOL */ |
58 |
static int C_opt; /* match case-insensitively */ |
59 |
static int c_opt; /* extract to stdout */ |
60 |
static const char *d_arg; /* directory */ |
61 |
static int f_opt; /* update existing files only */ |
62 |
static char *O_arg; /* encoding */ |
63 |
static int j_opt; /* junk directories */ |
64 |
static int L_opt; /* lowercase names */ |
65 |
static int n_opt; /* never overwrite */ |
66 |
static int o_opt; /* always overwrite */ |
67 |
static int p_opt; /* extract to stdout, quiet */ |
68 |
static char *P_arg; /* passphrase */ |
69 |
static int q_opt; /* quiet */ |
70 |
static int t_opt; /* test */ |
71 |
static int u_opt; /* update */ |
72 |
static int v_opt; /* verbose/list */ |
73 |
static const char *y_str = ""; /* 4 digit year */ |
74 |
static int Z1_opt; /* zipinfo mode list files only */ |
75 |
|
76 |
/* debug flag */ |
77 |
static int unzip_debug; |
78 |
|
79 |
/* zipinfo mode */ |
80 |
static int zipinfo_mode; |
81 |
|
82 |
/* running on tty? */ |
83 |
static int tty; |
84 |
|
85 |
/* convenience macro */ |
86 |
/* XXX should differentiate between ARCHIVE_{WARN,FAIL,RETRY} */ |
87 |
#define ac(call) \ |
88 |
do { \ |
89 |
int acret = (call); \ |
90 |
if (acret != ARCHIVE_OK) \ |
91 |
errorx("%s", archive_error_string(a)); \ |
92 |
} while (0) |
93 |
|
94 |
/* |
95 |
* Indicates that last info() did not end with EOL. This helps error() et |
96 |
* al. avoid printing an error message on the same line as an incomplete |
97 |
* informational message. |
98 |
*/ |
99 |
static int noeol; |
100 |
|
101 |
/* for an interactive passphrase input */ |
102 |
static char *passphrase_buf; |
103 |
|
104 |
/* fatal error message + errno */ |
105 |
static void |
106 |
error(const char *fmt, ...) |
107 |
{ |
108 |
va_list ap; |
109 |
|
110 |
if (noeol) |
111 |
fprintf(stdout, "\n"); |
112 |
fflush(stdout); |
113 |
fprintf(stderr, "unzip: "); |
114 |
va_start(ap, fmt); |
115 |
vfprintf(stderr, fmt, ap); |
116 |
va_end(ap); |
117 |
fprintf(stderr, ": %s\n", strerror(errno)); |
118 |
exit(EXIT_FAILURE); |
119 |
} |
120 |
|
121 |
/* fatal error message, no errno */ |
122 |
static void |
123 |
errorx(const char *fmt, ...) |
124 |
{ |
125 |
va_list ap; |
126 |
|
127 |
if (noeol) |
128 |
fprintf(stdout, "\n"); |
129 |
fflush(stdout); |
130 |
fprintf(stderr, "unzip: "); |
131 |
va_start(ap, fmt); |
132 |
vfprintf(stderr, fmt, ap); |
133 |
va_end(ap); |
134 |
fprintf(stderr, "\n"); |
135 |
exit(EXIT_FAILURE); |
136 |
} |
137 |
|
138 |
/* non-fatal error message + errno */ |
139 |
static void |
140 |
warning(const char *fmt, ...) |
141 |
{ |
142 |
va_list ap; |
143 |
|
144 |
if (noeol) |
145 |
fprintf(stdout, "\n"); |
146 |
fflush(stdout); |
147 |
fprintf(stderr, "unzip: "); |
148 |
va_start(ap, fmt); |
149 |
vfprintf(stderr, fmt, ap); |
150 |
va_end(ap); |
151 |
fprintf(stderr, ": %s\n", strerror(errno)); |
152 |
} |
153 |
|
154 |
/* non-fatal error message, no errno */ |
155 |
static void |
156 |
warningx(const char *fmt, ...) |
157 |
{ |
158 |
va_list ap; |
159 |
|
160 |
if (noeol) |
161 |
fprintf(stdout, "\n"); |
162 |
fflush(stdout); |
163 |
fprintf(stderr, "unzip: "); |
164 |
va_start(ap, fmt); |
165 |
vfprintf(stderr, fmt, ap); |
166 |
va_end(ap); |
167 |
fprintf(stderr, "\n"); |
168 |
} |
169 |
|
170 |
/* informational message (if not -q) */ |
171 |
static void |
172 |
info(const char *fmt, ...) |
173 |
{ |
174 |
va_list ap; |
175 |
|
176 |
if (q_opt && !unzip_debug) |
177 |
return; |
178 |
va_start(ap, fmt); |
179 |
vfprintf(stdout, fmt, ap); |
180 |
va_end(ap); |
181 |
fflush(stdout); |
182 |
|
183 |
if (*fmt == '\0') |
184 |
noeol = 1; |
185 |
else |
186 |
noeol = fmt[strlen(fmt) - 1] != '\n'; |
187 |
} |
188 |
|
189 |
/* debug message (if unzip_debug) */ |
190 |
static void |
191 |
debug(const char *fmt, ...) |
192 |
{ |
193 |
va_list ap; |
194 |
|
195 |
if (!unzip_debug) |
196 |
return; |
197 |
va_start(ap, fmt); |
198 |
vfprintf(stderr, fmt, ap); |
199 |
va_end(ap); |
200 |
fflush(stderr); |
201 |
|
202 |
if (*fmt == '\0') |
203 |
noeol = 1; |
204 |
else |
205 |
noeol = fmt[strlen(fmt) - 1] != '\n'; |
206 |
} |
207 |
|
208 |
/* duplicate a path name, possibly converting to lower case */ |
209 |
static char * |
210 |
pathdup(const char *path) |
211 |
{ |
212 |
char *str; |
213 |
size_t i, len; |
214 |
|
215 |
if (path == NULL || path[0] == '\0') |
216 |
return (NULL); |
217 |
|
218 |
len = strlen(path); |
219 |
while (len && path[len - 1] == '/') |
220 |
len--; |
221 |
if ((str = malloc(len + 1)) == NULL) { |
222 |
errno = ENOMEM; |
223 |
error("malloc()"); |
224 |
} |
225 |
if (L_opt) { |
226 |
for (i = 0; i < len; ++i) |
227 |
str[i] = tolower((unsigned char)path[i]); |
228 |
} else { |
229 |
memcpy(str, path, len); |
230 |
} |
231 |
str[len] = '\0'; |
232 |
|
233 |
return (str); |
234 |
} |
235 |
|
236 |
/* concatenate two path names */ |
237 |
static char * |
238 |
pathcat(const char *prefix, const char *path) |
239 |
{ |
240 |
char *str; |
241 |
size_t prelen, len; |
242 |
|
243 |
prelen = prefix ? strlen(prefix) + 1 : 0; |
244 |
len = strlen(path) + 1; |
245 |
if ((str = malloc(prelen + len)) == NULL) { |
246 |
errno = ENOMEM; |
247 |
error("malloc()"); |
248 |
} |
249 |
if (prefix) { |
250 |
memcpy(str, prefix, prelen); /* includes zero */ |
251 |
str[prelen - 1] = '/'; /* splat zero */ |
252 |
} |
253 |
memcpy(str + prelen, path, len); /* includes zero */ |
254 |
|
255 |
return (str); |
256 |
} |
257 |
|
258 |
/* |
259 |
* Pattern lists for include / exclude processing |
260 |
*/ |
261 |
struct pattern { |
262 |
STAILQ_ENTRY(pattern) link; |
263 |
char pattern[]; |
264 |
}; |
265 |
|
266 |
STAILQ_HEAD(pattern_list, pattern); |
267 |
static struct pattern_list include = STAILQ_HEAD_INITIALIZER(include); |
268 |
static struct pattern_list exclude = STAILQ_HEAD_INITIALIZER(exclude); |
269 |
|
270 |
/* |
271 |
* Add an entry to a pattern list |
272 |
*/ |
273 |
static void |
274 |
add_pattern(struct pattern_list *list, const char *pattern) |
275 |
{ |
276 |
struct pattern *entry; |
277 |
size_t len; |
278 |
|
279 |
debug("adding pattern '%s'\n", pattern); |
280 |
len = strlen(pattern); |
281 |
if ((entry = malloc(sizeof *entry + len + 1)) == NULL) { |
282 |
errno = ENOMEM; |
283 |
error("malloc()"); |
284 |
} |
285 |
memcpy(entry->pattern, pattern, len + 1); |
286 |
STAILQ_INSERT_TAIL(list, entry, link); |
287 |
} |
288 |
|
289 |
/* |
290 |
* Match a string against a list of patterns |
291 |
*/ |
292 |
static int |
293 |
match_pattern(struct pattern_list *list, const char *str) |
294 |
{ |
295 |
struct pattern *entry; |
296 |
|
297 |
STAILQ_FOREACH(entry, list, link) { |
298 |
if (fnmatch(entry->pattern, str, C_opt ? FNM_CASEFOLD : 0) == 0) |
299 |
return (1); |
300 |
} |
301 |
return (0); |
302 |
} |
303 |
|
304 |
/* |
305 |
* Verify that a given pathname is in the include list and not in the |
306 |
* exclude list. |
307 |
*/ |
308 |
static int |
309 |
accept_pathname(const char *pathname) |
310 |
{ |
311 |
|
312 |
if (!STAILQ_EMPTY(&include) && !match_pattern(&include, pathname)) |
313 |
return (0); |
314 |
if (!STAILQ_EMPTY(&exclude) && match_pattern(&exclude, pathname)) |
315 |
return (0); |
316 |
return (1); |
317 |
} |
318 |
|
319 |
/* |
320 |
* Create the specified directory with the specified mode, taking certain |
321 |
* precautions on they way. |
322 |
*/ |
323 |
static void |
324 |
make_dir(const char *path, int mode) |
325 |
{ |
326 |
struct stat sb; |
327 |
|
328 |
if (lstat(path, &sb) == 0) { |
329 |
if (S_ISDIR(sb.st_mode)) |
330 |
return; |
331 |
/* |
332 |
* Normally, we should either ask the user about removing |
333 |
* the non-directory of the same name as a directory we |
334 |
* wish to create, or respect the -n or -o command-line |
335 |
* options. However, this may lead to a later failure or |
336 |
* even compromise (if this non-directory happens to be a |
337 |
* symlink to somewhere unsafe), so we don't. |
338 |
*/ |
339 |
|
340 |
/* |
341 |
* Don't check unlink() result; failure will cause mkdir() |
342 |
* to fail later, which we will catch. |
343 |
*/ |
344 |
(void)unlink(path); |
345 |
} |
346 |
if (mkdir(path, mode) != 0 && errno != EEXIST) |
347 |
error("mkdir('%s')", path); |
348 |
} |
349 |
|
350 |
/* |
351 |
* Ensure that all directories leading up to (but not including) the |
352 |
* specified path exist. |
353 |
* |
354 |
* XXX inefficient + modifies the file in-place |
355 |
*/ |
356 |
static void |
357 |
make_parent(char *path) |
358 |
{ |
359 |
struct stat sb; |
360 |
char *sep; |
361 |
|
362 |
sep = strrchr(path, '/'); |
363 |
if (sep == NULL || sep == path) |
364 |
return; |
365 |
*sep = '\0'; |
366 |
if (lstat(path, &sb) == 0) { |
367 |
if (S_ISDIR(sb.st_mode)) { |
368 |
*sep = '/'; |
369 |
return; |
370 |
} |
371 |
unlink(path); |
372 |
} |
373 |
make_parent(path); |
374 |
mkdir(path, 0755); |
375 |
*sep = '/'; |
376 |
|
377 |
#if 0 |
378 |
for (sep = path; (sep = strchr(sep, '/')) != NULL; sep++) { |
379 |
/* root in case of absolute d_arg */ |
380 |
if (sep == path) |
381 |
continue; |
382 |
*sep = '\0'; |
383 |
make_dir(path, 0755); |
384 |
*sep = '/'; |
385 |
} |
386 |
#endif |
387 |
} |
388 |
|
389 |
/* |
390 |
* Extract a directory. |
391 |
*/ |
392 |
static void |
393 |
extract_dir(struct archive *a, struct archive_entry *e, const char *path) |
394 |
{ |
395 |
int mode; |
396 |
|
397 |
/* |
398 |
* Dropbox likes to create '/' directory entries, just ignore |
399 |
* such junk. |
400 |
*/ |
401 |
if (*path == '\0') |
402 |
return; |
403 |
|
404 |
mode = archive_entry_mode(e) & 0777; |
405 |
if (mode == 0) |
406 |
mode = 0755; |
407 |
|
408 |
/* |
409 |
* Some zipfiles contain directories with weird permissions such |
410 |
* as 0644 or 0444. This can cause strange issues such as being |
411 |
* unable to extract files into the directory we just created, or |
412 |
* the user being unable to remove the directory later without |
413 |
* first manually changing its permissions. Therefore, we whack |
414 |
* the permissions into shape, assuming that the user wants full |
415 |
* access and that anyone who gets read access also gets execute |
416 |
* access. |
417 |
*/ |
418 |
mode |= 0700; |
419 |
if (mode & 0040) |
420 |
mode |= 0010; |
421 |
if (mode & 0004) |
422 |
mode |= 0001; |
423 |
|
424 |
info(" creating: %s/\n", path); |
425 |
make_dir(path, mode); |
426 |
ac(archive_read_data_skip(a)); |
427 |
} |
428 |
|
429 |
static unsigned char buffer[8192]; |
430 |
static char spinner[] = { '|', '/', '-', '\\' }; |
431 |
|
432 |
static int |
433 |
handle_existing_file(char **path) |
434 |
{ |
435 |
size_t alen; |
436 |
ssize_t len; |
437 |
char buf[4]; |
438 |
|
439 |
for (;;) { |
440 |
fprintf(stderr, |
441 |
"replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", |
442 |
*path); |
443 |
if (fgets(buf, sizeof(buf), stdin) == NULL) { |
444 |
clearerr(stdin); |
445 |
printf("NULL\n(EOF or read error, " |
446 |
"treating as \"[N]one\"...)\n"); |
447 |
n_opt = 1; |
448 |
return -1; |
449 |
} |
450 |
switch (*buf) { |
451 |
case 'A': |
452 |
o_opt = 1; |
453 |
/* FALLTHROUGH */ |
454 |
case 'y': |
455 |
case 'Y': |
456 |
(void)unlink(*path); |
457 |
return 1; |
458 |
case 'N': |
459 |
n_opt = 1; |
460 |
/* FALLTHROUGH */ |
461 |
case 'n': |
462 |
return -1; |
463 |
case 'r': |
464 |
case 'R': |
465 |
printf("New name: "); |
466 |
fflush(stdout); |
467 |
free(*path); |
468 |
*path = NULL; |
469 |
alen = 0; |
470 |
len = getline(path, &alen, stdin); |
471 |
if ((*path)[len - 1] == '\n') |
472 |
(*path)[len - 1] = '\0'; |
473 |
return 0; |
474 |
default: |
475 |
break; |
476 |
} |
477 |
} |
478 |
} |
479 |
|
480 |
/* |
481 |
* Detect binary files by a combination of character white list and |
482 |
* black list. NUL bytes and other control codes without use in text files |
483 |
* result directly in switching the file to binary mode. Otherwise, at least |
484 |
* one white-listed byte has to be found. |
485 |
* |
486 |
* Black-listed: 0..6, 14..25, 28..31 |
487 |
* 0xf3ffc07f = 11110011111111111100000001111111b |
488 |
* White-listed: 9..10, 13, >= 32 |
489 |
* 0x00002600 = 00000000000000000010011000000000b |
490 |
* |
491 |
* See the proginfo/txtvsbin.txt in the zip sources for a detailed discussion. |
492 |
*/ |
493 |
#define BYTE_IS_BINARY(x) ((x) < 32 && (0xf3ffc07fU & (1U << (x)))) |
494 |
#define BYTE_IS_TEXT(x) ((x) >= 32 || (0x00002600U & (1U << (x)))) |
495 |
|
496 |
static int |
497 |
check_binary(const unsigned char *buf, size_t len) |
498 |
{ |
499 |
int rv; |
500 |
for (rv = 1; len--; ++buf) { |
501 |
if (BYTE_IS_BINARY(*buf)) |
502 |
return 1; |
503 |
if (BYTE_IS_TEXT(*buf)) |
504 |
rv = 0; |
505 |
} |
506 |
|
507 |
return rv; |
508 |
} |
509 |
|
510 |
/* |
511 |
* Extract to a file descriptor |
512 |
*/ |
513 |
static int |
514 |
extract2fd(struct archive *a, char *pathname, int fd) |
515 |
{ |
516 |
int cr, text, warn; |
517 |
ssize_t len; |
518 |
unsigned char *p, *q, *end; |
519 |
|
520 |
text = a_opt; |
521 |
warn = 0; |
522 |
cr = 0; |
523 |
|
524 |
/* loop over file contents and write to fd */ |
525 |
for (int n = 0; ; n++) { |
526 |
if (fd != STDOUT_FILENO) |
527 |
if (tty && (n % 4) == 0) |
528 |
info(" %c\b\b", spinner[(n / 4) % sizeof spinner]); |
529 |
|
530 |
len = archive_read_data(a, buffer, sizeof buffer); |
531 |
|
532 |
if (len < 0) |
533 |
ac(len); |
534 |
|
535 |
/* left over CR from previous buffer */ |
536 |
if (a_opt && cr) { |
537 |
if (len == 0 || buffer[0] != '\n') |
538 |
if (write(fd, "\r", 1) != 1) |
539 |
error("write('%s')", pathname); |
540 |
cr = 0; |
541 |
} |
542 |
|
543 |
/* EOF */ |
544 |
if (len == 0) |
545 |
break; |
546 |
end = buffer + len; |
547 |
|
548 |
/* |
549 |
* Detect whether this is a text file. The correct way to |
550 |
* do this is to check the least significant bit of the |
551 |
* "internal file attributes" field of the corresponding |
552 |
* file header in the central directory, but libarchive |
553 |
* does not provide access to this field, so we have to |
554 |
* guess by looking for non-ASCII characters in the |
555 |
* buffer. Hopefully we won't guess wrong. If we do |
556 |
* guess wrong, we print a warning message later. |
557 |
*/ |
558 |
if (a_opt && n == 0) { |
559 |
if (check_binary(buffer, len)) |
560 |
text = 0; |
561 |
} |
562 |
|
563 |
/* simple case */ |
564 |
if (!a_opt || !text) { |
565 |
if (write(fd, buffer, len) != len) |
566 |
error("write('%s')", pathname); |
567 |
continue; |
568 |
} |
569 |
|
570 |
/* hard case: convert \r\n to \n (sigh...) */ |
571 |
for (p = buffer; p < end; p = q + 1) { |
572 |
for (q = p; q < end; q++) { |
573 |
if (!warn && BYTE_IS_BINARY(*q)) { |
574 |
warningx("%s may be corrupted due" |
575 |
" to weak text file detection" |
576 |
" heuristic", pathname); |
577 |
warn = 1; |
578 |
} |
579 |
if (q[0] != '\r') |
580 |
continue; |
581 |
if (&q[1] == end) { |
582 |
cr = 1; |
583 |
break; |
584 |
} |
585 |
if (q[1] == '\n') |
586 |
break; |
587 |
} |
588 |
if (write(fd, p, q - p) != q - p) |
589 |
error("write('%s')", pathname); |
590 |
} |
591 |
} |
592 |
|
593 |
return text; |
594 |
} |
595 |
|
596 |
/* |
597 |
* Extract a regular file. |
598 |
*/ |
599 |
static void |
600 |
extract_file(struct archive *a, struct archive_entry *e, char **path) |
601 |
{ |
602 |
int mode; |
603 |
struct timespec mtime; |
604 |
struct stat sb; |
605 |
struct timespec ts[2]; |
606 |
int fd, check, text; |
607 |
const char *linkname; |
608 |
|
609 |
mode = archive_entry_mode(e) & 0777; |
610 |
if (mode == 0) |
611 |
mode = 0644; |
612 |
mtime.tv_sec = archive_entry_mtime(e); |
613 |
mtime.tv_nsec = archive_entry_mtime_nsec(e); |
614 |
|
615 |
/* look for existing file of same name */ |
616 |
recheck: |
617 |
if (lstat(*path, &sb) == 0) { |
618 |
if (u_opt || f_opt) { |
619 |
/* check if up-to-date */ |
620 |
if (S_ISREG(sb.st_mode) && |
621 |
(sb.st_mtim.tv_sec > mtime.tv_sec || |
622 |
(sb.st_mtim.tv_sec == mtime.tv_sec && |
623 |
sb.st_mtim.tv_nsec >= mtime.tv_nsec))) |
624 |
return; |
625 |
(void)unlink(*path); |
626 |
} else if (o_opt) { |
627 |
/* overwrite */ |
628 |
(void)unlink(*path); |
629 |
} else if (n_opt) { |
630 |
/* do not overwrite */ |
631 |
return; |
632 |
} else { |
633 |
check = handle_existing_file(path); |
634 |
if (check == 0) |
635 |
goto recheck; |
636 |
if (check == -1) |
637 |
return; /* do not overwrite */ |
638 |
} |
639 |
} else { |
640 |
if (f_opt) |
641 |
return; |
642 |
} |
643 |
|
644 |
ts[0].tv_sec = 0; |
645 |
ts[0].tv_nsec = UTIME_NOW; |
646 |
ts[1] = mtime; |
647 |
|
648 |
/* process symlinks */ |
649 |
linkname = archive_entry_symlink(e); |
650 |
if (linkname != NULL) { |
651 |
if (symlink(linkname, *path) != 0) |
652 |
error("symlink('%s')", *path); |
653 |
info(" extracting: %s -> %s\n", *path, linkname); |
654 |
if (lchmod(*path, mode) != 0) |
655 |
warning("Cannot set mode for '%s'", *path); |
656 |
/* set access and modification time */ |
657 |
if (utimensat(AT_FDCWD, *path, ts, AT_SYMLINK_NOFOLLOW) != 0) |
658 |
warning("utimensat('%s')", *path); |
659 |
return; |
660 |
} |
661 |
|
662 |
if ((fd = open(*path, O_RDWR|O_CREAT|O_TRUNC, mode)) < 0) |
663 |
error("open('%s')", *path); |
664 |
|
665 |
info(" extracting: %s", *path); |
666 |
|
667 |
text = extract2fd(a, *path, fd); |
668 |
|
669 |
if (tty) |
670 |
info(" \b\b"); |
671 |
if (text) |
672 |
info(" (text)"); |
673 |
info("\n"); |
674 |
|
675 |
/* set access and modification time */ |
676 |
if (futimens(fd, ts) != 0) |
677 |
error("futimens('%s')", *path); |
678 |
if (close(fd) != 0) |
679 |
error("close('%s')", *path); |
680 |
} |
681 |
|
682 |
/* |
683 |
* Extract a zipfile entry: first perform some sanity checks to ensure |
684 |
* that it is either a directory or a regular file and that the path is |
685 |
* not absolute and does not try to break out of the current directory; |
686 |
* then call either extract_dir() or extract_file() as appropriate. |
687 |
* |
688 |
* This is complicated a bit by the various ways in which we need to |
689 |
* manipulate the path name. Case conversion (if requested by the -L |
690 |
* option) happens first, but the include / exclude patterns are applied |
691 |
* to the full converted path name, before the directory part of the path |
692 |
* is removed in accordance with the -j option. Sanity checks are |
693 |
* intentionally done earlier than they need to be, so the user will get a |
694 |
* warning about insecure paths even for files or directories which |
695 |
* wouldn't be extracted anyway. |
696 |
*/ |
697 |
static void |
698 |
extract(struct archive *a, struct archive_entry *e) |
699 |
{ |
700 |
char *pathname, *realpathname; |
701 |
mode_t filetype; |
702 |
char *p, *q; |
703 |
|
704 |
if ((pathname = pathdup(archive_entry_pathname(e))) == NULL) { |
705 |
warningx("skipping empty or unreadable filename entry"); |
706 |
ac(archive_read_data_skip(a)); |
707 |
return; |
708 |
} |
709 |
filetype = archive_entry_filetype(e); |
710 |
|
711 |
/* sanity checks */ |
712 |
if (pathname[0] == '/' || |
713 |
strncmp(pathname, "../", 3) == 0 || |
714 |
strstr(pathname, "/../") != NULL) { |
715 |
warningx("skipping insecure entry '%s'", pathname); |
716 |
ac(archive_read_data_skip(a)); |
717 |
free(pathname); |
718 |
return; |
719 |
} |
720 |
|
721 |
/* I don't think this can happen in a zipfile.. */ |
722 |
if (!S_ISDIR(filetype) && !S_ISREG(filetype) && !S_ISLNK(filetype)) { |
723 |
warningx("skipping non-regular entry '%s'", pathname); |
724 |
ac(archive_read_data_skip(a)); |
725 |
free(pathname); |
726 |
return; |
727 |
} |
728 |
|
729 |
/* skip directories in -j case */ |
730 |
if (S_ISDIR(filetype) && j_opt) { |
731 |
ac(archive_read_data_skip(a)); |
732 |
free(pathname); |
733 |
return; |
734 |
} |
735 |
|
736 |
/* apply include / exclude patterns */ |
737 |
if (!accept_pathname(pathname)) { |
738 |
ac(archive_read_data_skip(a)); |
739 |
free(pathname); |
740 |
return; |
741 |
} |
742 |
|
743 |
/* apply -j and -d */ |
744 |
if (j_opt) { |
745 |
for (p = q = pathname; *p; ++p) |
746 |
if (*p == '/') |
747 |
q = p + 1; |
748 |
realpathname = pathcat(d_arg, q); |
749 |
} else { |
750 |
realpathname = pathcat(d_arg, pathname); |
751 |
} |
752 |
|
753 |
/* ensure that parent directory exists */ |
754 |
make_parent(realpathname); |
755 |
|
756 |
if (S_ISDIR(filetype)) |
757 |
extract_dir(a, e, realpathname); |
758 |
else |
759 |
extract_file(a, e, &realpathname); |
760 |
|
761 |
free(realpathname); |
762 |
free(pathname); |
763 |
} |
764 |
|
765 |
static void |
766 |
extract_stdout(struct archive *a, struct archive_entry *e) |
767 |
{ |
768 |
char *pathname; |
769 |
mode_t filetype; |
770 |
|
771 |
if ((pathname = pathdup(archive_entry_pathname(e))) == NULL) { |
772 |
warningx("skipping empty or unreadable filename entry"); |
773 |
ac(archive_read_data_skip(a)); |
774 |
return; |
775 |
} |
776 |
filetype = archive_entry_filetype(e); |
777 |
|
778 |
/* I don't think this can happen in a zipfile.. */ |
779 |
if (!S_ISDIR(filetype) && !S_ISREG(filetype) && !S_ISLNK(filetype)) { |
780 |
warningx("skipping non-regular entry '%s'", pathname); |
781 |
ac(archive_read_data_skip(a)); |
782 |
free(pathname); |
783 |
return; |
784 |
} |
785 |
|
786 |
/* skip directories in -j case */ |
787 |
if (S_ISDIR(filetype)) { |
788 |
ac(archive_read_data_skip(a)); |
789 |
free(pathname); |
790 |
return; |
791 |
} |
792 |
|
793 |
/* apply include / exclude patterns */ |
794 |
if (!accept_pathname(pathname)) { |
795 |
ac(archive_read_data_skip(a)); |
796 |
free(pathname); |
797 |
return; |
798 |
} |
799 |
|
800 |
if (c_opt) |
801 |
info("x %s\n", pathname); |
802 |
|
803 |
(void)extract2fd(a, pathname, STDOUT_FILENO); |
804 |
|
805 |
free(pathname); |
806 |
} |
807 |
|
808 |
/* |
809 |
* Print the name of an entry to stdout. |
810 |
*/ |
811 |
static void |
812 |
list(struct archive *a, struct archive_entry *e) |
813 |
{ |
814 |
char buf[20]; |
815 |
time_t mtime; |
816 |
struct tm *tm; |
817 |
|
818 |
mtime = archive_entry_mtime(e); |
819 |
tm = localtime(&mtime); |
820 |
if (*y_str) |
821 |
strftime(buf, sizeof(buf), "%m-%d-%G %R", tm); |
822 |
else |
823 |
strftime(buf, sizeof(buf), "%m-%d-%g %R", tm); |
824 |
|
825 |
if (!zipinfo_mode) { |
826 |
if (v_opt == 1) { |
827 |
printf(" %8ju %s %s\n", |
828 |
(uintmax_t)archive_entry_size(e), |
829 |
buf, archive_entry_pathname(e)); |
830 |
} else if (v_opt == 2) { |
831 |
printf("%8ju Stored %7ju 0%% %s %08x %s\n", |
832 |
(uintmax_t)archive_entry_size(e), |
833 |
(uintmax_t)archive_entry_size(e), |
834 |
buf, |
835 |
0U, |
836 |
archive_entry_pathname(e)); |
837 |
} |
838 |
} else { |
839 |
if (Z1_opt) |
840 |
printf("%s\n",archive_entry_pathname(e)); |
841 |
} |
842 |
ac(archive_read_data_skip(a)); |
843 |
} |
844 |
|
845 |
/* |
846 |
* Extract to memory to check CRC |
847 |
*/ |
848 |
static int |
849 |
test(struct archive *a, struct archive_entry *e) |
850 |
{ |
851 |
ssize_t len; |
852 |
int error_count; |
853 |
|
854 |
error_count = 0; |
855 |
if (S_ISDIR(archive_entry_filetype(e))) |
856 |
return 0; |
857 |
|
858 |
info(" testing: %s\t", archive_entry_pathname(e)); |
859 |
while ((len = archive_read_data(a, buffer, sizeof buffer)) > 0) |
860 |
/* nothing */; |
861 |
if (len < 0) { |
862 |
info(" %s\n", archive_error_string(a)); |
863 |
++error_count; |
864 |
} else { |
865 |
info(" OK\n"); |
866 |
} |
867 |
|
868 |
/* shouldn't be necessary, but it doesn't hurt */ |
869 |
ac(archive_read_data_skip(a)); |
870 |
|
871 |
return error_count; |
872 |
} |
873 |
|
874 |
/* |
875 |
* Callback function for reading passphrase. |
876 |
* Originally from cpio.c and passphrase.c, libarchive. |
877 |
*/ |
878 |
#define PPBUFF_SIZE 1024 |
879 |
static const char * |
880 |
passphrase_callback(struct archive *a, void *_client_data) |
881 |
{ |
882 |
char *p; |
883 |
|
884 |
(void)a; /* UNUSED */ |
885 |
(void)_client_data; /* UNUSED */ |
886 |
|
887 |
if (passphrase_buf == NULL) { |
888 |
passphrase_buf = malloc(PPBUFF_SIZE); |
889 |
if (passphrase_buf == NULL) { |
890 |
errno = ENOMEM; |
891 |
error("malloc()"); |
892 |
} |
893 |
} |
894 |
|
895 |
p = readpassphrase("\nEnter password: ", passphrase_buf, |
896 |
PPBUFF_SIZE, RPP_ECHO_OFF); |
897 |
|
898 |
if (p == NULL && errno != EINTR) |
899 |
error("Error reading password"); |
900 |
|
901 |
return p; |
902 |
} |
903 |
|
904 |
/* |
905 |
* Main loop: open the zipfile, iterate over its contents and decide what |
906 |
* to do with each entry. |
907 |
*/ |
908 |
static void |
909 |
unzip(const char *fn) |
910 |
{ |
911 |
struct archive *a; |
912 |
struct archive_entry *e; |
913 |
int ret; |
914 |
uintmax_t total_size, file_count, error_count; |
915 |
|
916 |
if ((a = archive_read_new()) == NULL) |
917 |
error("archive_read_new failed"); |
918 |
|
919 |
ac(archive_read_support_format_zip(a)); |
920 |
|
921 |
if (O_arg) |
922 |
ac(archive_read_set_format_option(a, "zip", "hdrcharset", O_arg)); |
923 |
|
924 |
if (P_arg) |
925 |
archive_read_add_passphrase(a, P_arg); |
926 |
else |
927 |
archive_read_set_passphrase_callback(a, NULL, |
928 |
&passphrase_callback); |
929 |
|
930 |
ac(archive_read_open_filename(a, fn, 8192)); |
931 |
|
932 |
if (!zipinfo_mode) { |
933 |
if (!p_opt && !q_opt) |
934 |
printf("Archive: %s\n", fn); |
935 |
if (v_opt == 1) { |
936 |
printf(" Length %sDate Time Name\n", y_str); |
937 |
printf(" -------- %s---- ---- ----\n", y_str); |
938 |
} else if (v_opt == 2) { |
939 |
printf(" Length Method Size Ratio %sDate Time CRC-32 Name\n", y_str); |
940 |
printf("-------- ------ ------- ----- %s---- ---- ------ ----\n", y_str); |
941 |
} |
942 |
} |
943 |
|
944 |
total_size = 0; |
945 |
file_count = 0; |
946 |
error_count = 0; |
947 |
for (;;) { |
948 |
ret = archive_read_next_header(a, &e); |
949 |
if (ret == ARCHIVE_EOF) |
950 |
break; |
951 |
ac(ret); |
952 |
if (!zipinfo_mode) { |
953 |
if (t_opt) |
954 |
error_count += test(a, e); |
955 |
else if (v_opt) |
956 |
list(a, e); |
957 |
else if (p_opt || c_opt) |
958 |
extract_stdout(a, e); |
959 |
else |
960 |
extract(a, e); |
961 |
} else { |
962 |
if (Z1_opt) |
963 |
list(a, e); |
964 |
} |
965 |
|
966 |
total_size += archive_entry_size(e); |
967 |
++file_count; |
968 |
} |
969 |
|
970 |
if (zipinfo_mode) { |
971 |
if (v_opt == 1) { |
972 |
printf(" -------- %s-------\n", y_str); |
973 |
printf(" %8ju %s%ju file%s\n", |
974 |
total_size, y_str, file_count, file_count != 1 ? "s" : ""); |
975 |
} else if (v_opt == 2) { |
976 |
printf("-------- ------- --- %s-------\n", y_str); |
977 |
printf("%8ju %7ju 0%% %s%ju file%s\n", |
978 |
total_size, total_size, y_str, file_count, |
979 |
file_count != 1 ? "s" : ""); |
980 |
} |
981 |
} |
982 |
|
983 |
ac(archive_read_free(a)); |
984 |
|
985 |
if (passphrase_buf != NULL) { |
986 |
memset_s(passphrase_buf, PPBUFF_SIZE, 0, PPBUFF_SIZE); |
987 |
free(passphrase_buf); |
988 |
} |
989 |
|
990 |
if (t_opt) { |
991 |
if (error_count > 0) { |
992 |
errorx("%ju checksum error(s) found.", error_count); |
993 |
} |
994 |
else { |
995 |
printf("No errors detected in compressed data of %s.\n", |
996 |
fn); |
997 |
} |
998 |
} |
999 |
} |
1000 |
|
1001 |
static void |
1002 |
usage(void) |
1003 |
{ |
1004 |
|
1005 |
fprintf(stderr, |
1006 |
"Usage: unzip [-aCcfjLlnopqtuvyZ1] [{-O|-I} encoding] [-d dir] [-x pattern] [-P password] zipfile\n" |
1007 |
" [member ...]\n"); |
1008 |
exit(EXIT_FAILURE); |
1009 |
} |
1010 |
|
1011 |
static int |
1012 |
getopts(int argc, char *argv[]) |
1013 |
{ |
1014 |
int opt; |
1015 |
|
1016 |
optreset = optind = 1; |
1017 |
while ((opt = getopt(argc, argv, "aCcd:fI:jLlnO:opP:qtuvx:yZ1")) != -1) |
1018 |
switch (opt) { |
1019 |
case '1': |
1020 |
Z1_opt = 1; |
1021 |
break; |
1022 |
case 'a': |
1023 |
a_opt = 1; |
1024 |
break; |
1025 |
case 'C': |
1026 |
C_opt = 1; |
1027 |
break; |
1028 |
case 'c': |
1029 |
c_opt = 1; |
1030 |
break; |
1031 |
case 'd': |
1032 |
d_arg = optarg; |
1033 |
break; |
1034 |
case 'f': |
1035 |
f_opt = 1; |
1036 |
break; |
1037 |
case 'I': |
1038 |
case 'O': |
1039 |
O_arg = optarg; |
1040 |
case 'j': |
1041 |
j_opt = 1; |
1042 |
break; |
1043 |
case 'L': |
1044 |
L_opt = 1; |
1045 |
break; |
1046 |
case 'l': |
1047 |
if (v_opt == 0) |
1048 |
v_opt = 1; |
1049 |
break; |
1050 |
case 'n': |
1051 |
n_opt = 1; |
1052 |
break; |
1053 |
case 'o': |
1054 |
o_opt = 1; |
1055 |
q_opt = 1; |
1056 |
break; |
1057 |
case 'p': |
1058 |
p_opt = 1; |
1059 |
break; |
1060 |
case 'P': |
1061 |
P_arg = optarg; |
1062 |
break; |
1063 |
case 'q': |
1064 |
q_opt = 1; |
1065 |
break; |
1066 |
case 't': |
1067 |
t_opt = 1; |
1068 |
break; |
1069 |
case 'u': |
1070 |
u_opt = 1; |
1071 |
break; |
1072 |
case 'v': |
1073 |
v_opt = 2; |
1074 |
break; |
1075 |
case 'x': |
1076 |
add_pattern(&exclude, optarg); |
1077 |
break; |
1078 |
case 'y': |
1079 |
y_str = " "; |
1080 |
break; |
1081 |
case 'Z': |
1082 |
zipinfo_mode = 1; |
1083 |
break; |
1084 |
default: |
1085 |
usage(); |
1086 |
} |
1087 |
|
1088 |
return (optind); |
1089 |
} |
1090 |
|
1091 |
int |
1092 |
main(int argc, char *argv[]) |
1093 |
{ |
1094 |
const char *zipfile; |
1095 |
int nopts; |
1096 |
|
1097 |
if (isatty(STDOUT_FILENO)) |
1098 |
tty = 1; |
1099 |
|
1100 |
if (getenv("UNZIP_DEBUG") != NULL) |
1101 |
unzip_debug = 1; |
1102 |
for (int i = 0; i < argc; ++i) |
1103 |
debug("%s%c", argv[i], (i < argc - 1) ? ' ' : '\n'); |
1104 |
|
1105 |
/* |
1106 |
* Info-ZIP's unzip(1) expects certain options to come before the |
1107 |
* zipfile name, and others to come after - though it does not |
1108 |
* enforce this. For simplicity, we accept *all* options both |
1109 |
* before and after the zipfile name. |
1110 |
*/ |
1111 |
nopts = getopts(argc, argv); |
1112 |
|
1113 |
/* |
1114 |
* When more of the zipinfo mode options are implemented, this |
1115 |
* will need to change. |
1116 |
*/ |
1117 |
if (zipinfo_mode && !Z1_opt) { |
1118 |
printf("Zipinfo mode needs additional options\n"); |
1119 |
exit(EXIT_FAILURE); |
1120 |
} |
1121 |
|
1122 |
if (argc <= nopts) |
1123 |
usage(); |
1124 |
zipfile = argv[nopts++]; |
1125 |
|
1126 |
if (strcmp(zipfile, "-") == 0) |
1127 |
zipfile = NULL; /* STDIN */ |
1128 |
|
1129 |
while (nopts < argc && *argv[nopts] != '-') |
1130 |
add_pattern(&include, argv[nopts++]); |
1131 |
|
1132 |
nopts--; /* fake argv[0] */ |
1133 |
nopts += getopts(argc - nopts, argv + nopts); |
1134 |
|
1135 |
if (n_opt + o_opt + u_opt > 1) |
1136 |
errorx("-n, -o and -u are contradictory"); |
1137 |
|
1138 |
unzip(zipfile); |
1139 |
|
1140 |
exit(EXIT_SUCCESS); |
1141 |
} |