OSDN Git Service

Should not create symlinks in `lha p' command.
[lha/lha.git] / src / lhext.c
1 /* ------------------------------------------------------------------------ */
2 /* LHa for UNIX                                                             */
3 /*              lhext.c -- LHarc extract                                    */
4 /*                                                                          */
5 /*      Copyright (C) MCMLXXXIX Yooichi.Tagawa                              */
6 /*      Modified                Nobutaka Watazaki                           */
7 /*                                                                          */
8 /*  Ver. 0.00  Original                             1988.05.23  Y.Tagawa    */
9 /*  Ver. 1.00  Fixed                                1989.09.22  Y.Tagawa    */
10 /*  Ver. 0.03  LHa for UNIX                         1991.12.17  M.Oki       */
11 /*  Ver. 1.12  LHa for UNIX                         1993.10.01  N.Watazaki  */
12 /*  Ver. 1.13b Symbolic Link Update Bug Fix         1994.06.21  N.Watazaki  */
13 /*  Ver. 1.14  Source All chagned                   1995.01.14  N.Watazaki  */
14 /*  Ver. 1.14e bugfix                               1999.04.30  T.Okamoto   */
15 /* ------------------------------------------------------------------------ */
16 #include "lha.h"
17 /* ------------------------------------------------------------------------ */
18 static int      skip_flg = FALSE;   /* FALSE..No Skip , TRUE..Skip */
19 static char    *methods[] =
20 {
21     LZHUFF0_METHOD, LZHUFF1_METHOD, LZHUFF2_METHOD, LZHUFF3_METHOD,
22     LZHUFF4_METHOD, LZHUFF5_METHOD, LZHUFF6_METHOD, LZHUFF7_METHOD,
23     LARC_METHOD, LARC5_METHOD, LARC4_METHOD,
24     LZHDIRS_METHOD,
25     PMARC0_METHOD, PMARC2_METHOD,
26     NULL
27 };
28
29 static void add_dirinfo(char* name, LzHeader* hdr);
30 static void adjust_dirinfo();
31
32 #ifdef HAVE_LIBAPPLEFILE
33 static boolean decode_macbinary(FILE *ofp, size_t size, const char *outPath);
34 #endif
35
36 /* ------------------------------------------------------------------------ */
37 static          boolean
38 inquire_extract(name)
39     char           *name;
40 {
41     struct stat     stbuf;
42
43     skip_flg = FALSE;
44     if (stat(name, &stbuf) >= 0) {
45         if (!is_regularfile(&stbuf)) {
46             error("\"%s\" already exists (not a file)", name);
47             return FALSE;
48         }
49
50         if (noexec) {
51             printf("EXTRACT %s but file is exist.\n", name);
52             return FALSE;
53         }
54         else if (!force) {
55             if (!isatty(0)) {
56                 warning("skip to extract %s.", name);
57                 return FALSE;
58             }
59
60             switch (inquire("OverWrite ?(Yes/[No]/All/Skip)", name, "YyNnAaSs\n")) {
61             case 0:
62             case 1:/* Y/y */
63                 break;
64             case 2:
65             case 3:/* N/n */
66             case 8:/* Return */
67                 return FALSE;
68             case 4:
69             case 5:/* A/a */
70                 force = TRUE;
71                 break;
72             case 6:
73             case 7:/* S/s */
74                 skip_flg = TRUE;
75                 break;
76             }
77         }
78     }
79
80     if (noexec)
81         printf("EXTRACT %s\n", name);
82
83     return TRUE;
84 }
85
86 static boolean
87 make_name_with_pathcheck(char *name, size_t namesz, const char *q)
88 {
89     int offset = 0;
90     const char *p;
91     int sz;
92     struct stat stbuf;
93
94     if (extract_directory) {
95         sz = xsnprintf(name, namesz, "%s/", extract_directory);
96         if (sz == -1) {
97             return FALSE;
98         }
99         offset += sz;
100     }
101
102 #ifdef S_IFLNK
103     while ((p = strchr(q, '/')) != NULL) {
104         if (namesz - offset < (p - q) + 2) {
105             return FALSE;
106         }
107         memcpy(name + offset, q, (p - q));
108         name[offset + (p - q)] = 0;
109
110         offset += (p - q);
111         q = p + 1;
112
113         if (lstat(name, &stbuf) < 0) {
114             name[offset++] = '/';
115             break;
116         }
117         if (is_symlink(&stbuf)) {
118             return FALSE;
119         }
120         name[offset++] = '/';
121     }
122 #endif
123
124     str_safe_copy(name + offset, q, namesz - offset);
125
126     return TRUE;
127 }
128
129 /* ------------------------------------------------------------------------ */
130 static          boolean
131 make_parent_path(name)
132     char           *name;
133 {
134     char            path[FILENAME_LENGTH];
135     struct stat     stbuf;
136     register char  *p;
137
138     /* make parent directory name into PATH for recursive call */
139     str_safe_copy(path, name, sizeof(path));
140     for (p = path + strlen(path); p > path; p--)
141         if (p[-1] == '/') {
142             *--p = '\0';
143             break;
144         }
145
146     if (p == path) {
147         message("invalid path name \"%s\"", name);
148         return FALSE;   /* no more parent. */
149     }
150
151     if (GETSTAT(path, &stbuf) >= 0) {
152         if (is_directory(&stbuf))
153             return TRUE;
154     }
155
156     if (verbose)
157         message("Making directory \"%s\".", path);
158
159 #if defined __MINGW32__
160     if (mkdir(path) >= 0)
161         return TRUE;
162 #else
163     if (mkdir(path, 0777) >= 0) /* try */
164         return TRUE;    /* successful done. */
165 #endif
166
167     if (!make_parent_path(path))
168         return FALSE;
169
170 #if defined __MINGW32__
171     if (mkdir(path) < 0) {      /* try again */
172         error("Cannot make directory \"%s\"", path);
173         return FALSE;
174     }
175 #else
176     if (mkdir(path, 0777) < 0) {    /* try again */
177         error("Cannot make directory \"%s\"", path);
178         return FALSE;
179     }
180 #endif
181
182     return TRUE;
183 }
184
185 /* ------------------------------------------------------------------------ */
186 static FILE    *
187 open_with_make_path(name)
188     char           *name;
189 {
190     FILE           *fp;
191
192     if ((fp = fopen(name, WRITE_BINARY)) == NULL) {
193         if (!make_parent_path(name) ||
194             (fp = fopen(name, WRITE_BINARY)) == NULL)
195             error("Cannot extract a file \"%s\"", name);
196     }
197     return fp;
198 }
199
200 /* ------------------------------------------------------------------------ */
201 static void
202 adjust_info(name, hdr)
203     char           *name;
204     LzHeader       *hdr;
205 {
206     struct utimbuf utimebuf;
207
208     /* adjust file stamp */
209     utimebuf.actime = utimebuf.modtime = hdr->unix_last_modified_stamp;
210
211     if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) != UNIX_FILE_SYMLINK)
212         utime(name, &utimebuf);
213
214     if (hdr->extend_type == EXTEND_UNIX
215         || hdr->extend_type == EXTEND_OS68K
216         || hdr->extend_type == EXTEND_XOSK) {
217
218         if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) != UNIX_FILE_SYMLINK) {
219             chmod(name, hdr->unix_mode);
220         }
221
222         if (!getuid()){
223             uid_t uid = hdr->unix_uid;
224             gid_t gid = hdr->unix_gid;
225
226 #if HAVE_GETPWNAM && HAVE_GETGRNAM
227             if (hdr->user[0]) {
228                 struct passwd *ent = getpwnam(hdr->user);
229                 if (ent) uid = ent->pw_uid;
230             }
231             if (hdr->group[0]) {
232                 struct group *ent = getgrnam(hdr->group);
233                 if (ent) gid = ent->gr_gid;
234             }
235 #endif
236
237 #if HAVE_LCHOWN
238             if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_SYMLINK)
239                 lchown(name, uid, gid);
240             else
241 #endif /* HAVE_LCHWON */
242                 chown(name, uid, gid);
243         }
244     }
245 #if __CYGWIN__
246     else {
247         /* On Cygwin, execute permission should be set for .exe or .dll. */
248         mode_t m;
249
250         umask(m = umask(0));    /* get current umask */
251         chmod(name, 0777 & ~m);
252     }
253 #endif
254 }
255
256 /* ------------------------------------------------------------------------ */
257 static off_t
258 extract_one(afp, hdr)
259     FILE           *afp;    /* archive file */
260     LzHeader       *hdr;
261 {
262     FILE           *fp; /* output file */
263 #if HAVE_LIBAPPLEFILE
264     FILE           *tfp; /* temporary output file */
265 #endif
266     struct stat     stbuf;
267     char            name[FILENAME_LENGTH];
268     unsigned int crc;
269     int             method;
270     boolean         save_quiet, save_verbose, up_flag;
271     char           *q = hdr->name, c;
272     off_t read_size = 0;
273
274     if (ignore_directory && strrchr(hdr->name, '/')) {
275         q = (char *) strrchr(hdr->name, '/') + 1;
276     }
277     else {
278         if (is_directory_traversal(q)) {
279             error("Possible directory traversal hack attempt in %s", q);
280             exit(1);
281         }
282
283         if (*q == '/') {
284             while (*q == '/') { q++; }
285
286             /*
287              * if OSK then strip device name
288              */
289             if (hdr->extend_type == EXTEND_OS68K
290                 || hdr->extend_type == EXTEND_XOSK) {
291                 do
292                     c = (*q++);
293                 while (c && c != '/');
294                 if (!c || !*q)
295                     q = ".";    /* if device name only */
296             }
297         }
298     }
299
300     if (!make_name_with_pathcheck(name, sizeof(name), q)) {
301         error("Possible symlink traversal hack attempt in %s", q);
302         exit(1);
303     }
304
305     /* LZHDIRS_METHODを持つヘッダをチェックする */
306     /* 1999.4.30 t.okamoto */
307     for (method = 0;; method++) {
308         if (methods[method] == NULL) {
309             error("Unknown method \"%.*s\"; \"%s\" will be skipped ...",
310                   5, hdr->method, name);
311             return read_size;
312         }
313         if (memcmp(hdr->method, methods[method], 5) == 0)
314             break;
315     }
316
317     if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_REGULAR
318         && method != LZHDIRS_METHOD_NUM) {
319     extract_regular:
320 #if 0
321         for (method = 0;; method++) {
322             if (methods[method] == NULL) {
323                 error("Unknown method \"%.*s\"; \"%s\" will be skipped ...",
324                       5, hdr->method, name);
325                 return read_size;
326             }
327             if (memcmp(hdr->method, methods[method], 5) == 0)
328                 break;
329         }
330 #endif
331
332         reading_filename = archive_name;
333         writing_filename = name;
334         if (output_to_stdout || verify_mode) {
335             /* "Icon\r" should be a resource fork file encoded in MacBinary
336                format, so that it should be skipped. */
337             if (hdr->extend_type == EXTEND_MACOS
338                 && strcmp(basename(name), "Icon\r") == 0
339                 && decode_macbinary_contents) {
340                 return read_size;
341             }
342
343             if (noexec) {
344                 printf("%s %s\n", verify_mode ? "VERIFY" : "EXTRACT", name);
345                 return read_size;
346             }
347
348             save_quiet = quiet;
349             save_verbose = verbose;
350             if (!quiet && output_to_stdout) {
351                 printf("::::::::\n%s\n::::::::\n", name);
352                 quiet = TRUE;
353                 verbose = FALSE;
354             }
355             else if (verify_mode) {
356                 quiet = FALSE;
357                 verbose = TRUE;
358             }
359
360 #if defined(__MINGW32__) || defined(__DJGPP__)
361             {
362                 int old_mode;
363                 fflush(stdout);
364                 old_mode = setmode(fileno(stdout), O_BINARY);
365 #endif
366
367 #if HAVE_LIBAPPLEFILE
368             /* On default, MacLHA encodes into MacBinary. */
369             if (hdr->extend_type == EXTEND_MACOS && !verify_mode && decode_macbinary_contents) {
370                 /* build temporary file */
371                 tfp = NULL; /* avoid compiler warnings `uninitialized' */
372                 tfp = build_temporary_file();
373
374                 crc = decode_lzhuf(afp, tfp,
375                                    hdr->original_size, hdr->packed_size,
376                                    name, method, &read_size);
377                 fclose(tfp);
378                 decode_macbinary(stdout, hdr->original_size, name);
379                 unlink(temporary_name);
380             } else {
381                 crc = decode_lzhuf(afp, stdout,
382                                    hdr->original_size, hdr->packed_size,
383                                    name, method, &read_size);
384             }
385 #else
386             crc = decode_lzhuf(afp, stdout,
387                                hdr->original_size, hdr->packed_size,
388                                name, method, &read_size);
389 #endif /* HAVE_LIBAPPLEFILE */
390 #if defined(__MINGW32__) || defined(__DJGPP__)
391                 fflush(stdout);
392                 setmode(fileno(stdout), old_mode);
393             }
394 #endif
395             quiet = save_quiet;
396             verbose = save_verbose;
397         }
398         else {
399 #ifndef __APPLE__
400             /* "Icon\r" should be a resource fork of parent folder's icon,
401                so that it can be skipped when system is not Mac OS X. */
402             if (hdr->extend_type == EXTEND_MACOS
403                 && strcmp(basename(name), "Icon\r") == 0
404                 && decode_macbinary_contents) {
405                 make_parent_path(name); /* create directory only */
406                 return read_size;
407             }
408 #endif /* __APPLE__ */
409             if (skip_flg == FALSE)  {
410                 up_flag = inquire_extract(name);
411                 if (up_flag == FALSE && force == FALSE) {
412                     return read_size;
413                 }
414             }
415
416             if (skip_flg == TRUE) { /* if skip_flg */
417                 if (stat(name, &stbuf) == 0 && force != TRUE) {
418                     if (stbuf.st_mtime >= hdr->unix_last_modified_stamp) {
419                         if (quiet != TRUE)
420                             printf("%s : Skipped...\n", name);
421                         return read_size;
422                     }
423                 }
424             }
425             if (noexec) {
426                 return read_size;
427             }
428
429             signal(SIGINT, interrupt);
430 #ifdef SIGHUP
431             signal(SIGHUP, interrupt);
432 #endif
433
434             unlink(name);
435             remove_extracting_file_when_interrupt = TRUE;
436
437             if ((fp = open_with_make_path(name)) != NULL) {
438 #if HAVE_LIBAPPLEFILE
439                 if (hdr->extend_type == EXTEND_MACOS && !verify_mode && decode_macbinary_contents) {
440                     /* build temporary file */
441                     tfp = NULL; /* avoid compiler warnings `uninitialized' */
442                     tfp = build_temporary_file();
443
444                     crc = decode_lzhuf(afp, tfp,
445                                        hdr->original_size, hdr->packed_size,
446                                        name, method, &read_size);
447                     fclose(tfp);
448                     decode_macbinary(fp, hdr->original_size, name);
449 #ifdef __APPLE__
450                     /* TODO: set resource fork */
451                     /* after processing, "Icon\r" is not needed. */
452                     if (strcmp(basename(name), "Icon\r") == 0) {
453                         unlink(name);
454                     }
455 #endif /* __APPLE__ */
456                     unlink(temporary_name);
457                 } else {
458                     crc = decode_lzhuf(afp, fp,
459                                        hdr->original_size, hdr->packed_size,
460                                        name, method, &read_size);
461                 }
462 #else /* HAVE_LIBAPPLEFILE */
463                 crc = decode_lzhuf(afp, fp,
464                                    hdr->original_size, hdr->packed_size,
465                                    name, method, &read_size);
466 #endif /* HAVE_LIBAPPLEFILE */
467                 fclose(fp);
468             }
469             remove_extracting_file_when_interrupt = FALSE;
470             signal(SIGINT, SIG_DFL);
471 #ifdef SIGHUP
472             signal(SIGHUP, SIG_DFL);
473 #endif
474             if (!fp)
475                 return read_size;
476         }
477
478         if (hdr->has_crc && crc != hdr->crc)
479             error("CRC error: \"%s\"", name);
480     }
481     else if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_DIRECTORY
482              || (hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_SYMLINK
483              || method == LZHDIRS_METHOD_NUM) {
484         /* ↑これで、Symbolic Link は、大丈夫か? */
485         if (!ignore_directory && !verify_mode && !output_to_stdout) {
486             if (noexec) {
487                 if (quiet != TRUE)
488                     printf("EXTRACT %s (directory)\n", name);
489                 return read_size;
490             }
491             /* NAME has trailing SLASH '/', (^_^) */
492             if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_SYMLINK) {
493                 int             l_code;
494
495 #ifdef S_IFLNK
496                 if (skip_flg == FALSE)  {
497                     up_flag = inquire_extract(name);
498                     if (up_flag == FALSE && force == FALSE) {
499                         return read_size;
500                     }
501                 } else {
502                     if (GETSTAT(name, &stbuf) == 0 && force != TRUE) {
503                         if (stbuf.st_mtime >= hdr->unix_last_modified_stamp) {
504                             if (quiet != TRUE)
505                                 printf("%s : Skipped...\n", name);
506                             return read_size;
507                         }
508                     }
509                 }
510
511                 unlink(name);
512                 make_parent_path(name);
513                 l_code = symlink(hdr->realname, name);
514                 if (l_code < 0) {
515                     if (quiet != TRUE)
516                         warning("Can't make Symbolic Link \"%s\" -> \"%s\"",
517                                 name, hdr->realname);
518                 }
519                 if (quiet != TRUE) {
520                     message("Symbolic Link %s -> %s",
521                             name, hdr->realname);
522                 }
523 #else
524                 warning("Can't make Symbolic Link %s -> %s",
525                         name, hdr->realname);
526                 return read_size;
527 #endif
528             }
529             else { /* make directory */
530                 if (!make_parent_path(name))
531                     return read_size;
532                 /* save directory information */
533                 add_dirinfo(name, hdr);
534             }
535         }
536     }
537     else {
538         if (force)              /* force extract */
539             goto extract_regular;
540         else
541             error("Unknown file type: \"%s\". use `f' option to force extract.", name);
542     }
543
544     if (!output_to_stdout && !verify_mode) {
545         if ((hdr->unix_mode & UNIX_FILE_TYPEMASK) != UNIX_FILE_DIRECTORY)
546             adjust_info(name, hdr);
547     }
548
549     return read_size;
550 }
551
552 /* ------------------------------------------------------------------------ */
553 /* EXTRACT COMMAND MAIN                                                     */
554 /* ------------------------------------------------------------------------ */
555 void
556 cmd_extract()
557 {
558     LzHeader        hdr;
559     off_t           pos;
560     FILE           *afp;
561     off_t read_size;
562
563     /* open archive file */
564     if ((afp = open_old_archive()) == NULL)
565         fatal_error("Cannot open archive file \"%s\"", archive_name);
566
567     if (archive_is_msdos_sfx1(archive_name))
568         seek_lha_header(afp);
569
570     /* extract each files */
571     while (get_header(afp, &hdr)) {
572         if (need_file(hdr.name)) {
573             pos = ftello(afp);
574             read_size = extract_one(afp, &hdr);
575             if (read_size != hdr.packed_size) {
576                 /* when error occurred in extract_one(), should adjust
577                    point of file stream */
578                 if (pos != -1 && afp != stdin)
579                     fseeko(afp, pos + hdr.packed_size, SEEK_SET);
580                 else {
581                     off_t i = hdr.packed_size - read_size;
582                     while (i--) fgetc(afp);
583                 }
584             }
585         } else {
586             if (afp != stdin)
587                 fseeko(afp, hdr.packed_size, SEEK_CUR);
588             else {
589                 off_t i = hdr.packed_size;
590                 while (i--) fgetc(afp);
591             }
592         }
593     }
594
595     /* close archive file */
596     fclose(afp);
597
598     /* adjust directory information */
599     adjust_dirinfo();
600
601     return;
602 }
603
604 int
605 is_directory_traversal(char *path)
606 {
607     int state = 0;
608
609     for (; *path; path++) {
610         switch (state) {
611         case 0:
612             if (*path == '.') state = 1;
613             else state = 3;
614             break;
615         case 1:
616             if (*path == '.') state = 2;
617             else if (*path == '/') state = 0;
618             else state = 3;
619             break;
620         case 2:
621             if (*path == '/') return 1;
622             else state = 3;
623             break;
624         case 3:
625             if (*path == '/') state = 0;
626             break;
627         }
628     }
629
630     return state == 2;
631 }
632
633 /*
634  * restore directory information (timestamp, permission and uid/gid).
635  * added by A.Iriyama  2003.12.12
636  */
637
638 typedef struct LzHeaderList_t {
639     struct LzHeaderList_t *next;
640     LzHeader hdr;
641 } LzHeaderList;
642
643 static LzHeaderList *dirinfo;
644
645 static void add_dirinfo(char *name, LzHeader *hdr)
646 {
647     LzHeaderList *p, *tmp, top;
648
649     if (memcmp(hdr->method, LZHDIRS_METHOD, 5) != 0)
650         return;
651
652     p = xmalloc(sizeof(LzHeaderList));
653
654     memcpy(&p->hdr, hdr, sizeof(LzHeader));
655     strncpy(p->hdr.name, name, sizeof(p->hdr.name));
656     p->hdr.name[sizeof(p->hdr.name)-1] = 0;
657
658 #if 0
659     /* push front */
660     {
661         tmp = dirinfo;
662         dirinfo = p;
663         dirinfo->next = tmp;
664     }
665 #else
666
667     /*
668       reverse sorted by pathname order
669
670          p->hdr.name = "a"
671
672          dirinfo->hdr.name             = "a/b/d"
673          dirinfo->next->hdr.name       = "a/b/c"
674          dirinfo->next->next->hdr.name = "a/b"
675
676        result:
677
678          dirinfo->hdr.name                   = "a/b/d"
679          dirinfo->next->hdr.name             = "a/b/c"
680          dirinfo->next->next->hdr.name       = "a/b"
681          dirinfo->next->next->next->hdr.name = "a"
682     */
683
684     top.next = dirinfo;
685
686     for (tmp = &top; tmp->next; tmp = tmp->next) {
687         if (strcmp(p->hdr.name, tmp->next->hdr.name) > 0) {
688             p->next = tmp->next;
689             tmp->next = p;
690             break;
691         }
692     }
693     if (tmp->next == NULL) {
694         p->next = NULL;
695         tmp->next = p;
696     }
697
698     dirinfo = top.next;
699 #endif
700 }
701
702 static void adjust_dirinfo()
703 {
704     while (dirinfo) {
705         /* message("adjusting [%s]", dirinfo->hdr.name); */
706         adjust_info(dirinfo->hdr.name, &dirinfo->hdr);
707
708         {
709             LzHeaderList *tmp = dirinfo;
710             dirinfo = dirinfo->next;
711             free(tmp);
712         }
713     }
714 }
715
716 #if HAVE_LIBAPPLEFILE
717 static boolean
718 decode_macbinary(ofp, size, outPath)
719     FILE *ofp;
720     off_t size;
721     const char *outPath;
722 {
723     af_file_t *afp = NULL;
724     FILE *ifp = NULL;
725     unsigned char *datap;
726     off_t dlen;
727
728     if ((afp = af_open(temporary_name)) != NULL) {
729         /* fetch datafork */
730         datap = af_data(afp, &dlen);
731         fwrite(datap, sizeof(unsigned char), dlen, ofp);
732         af_close(afp);
733         return TRUE;
734     } else { /* it may be not encoded in MacBinary */
735         /* try to copy */
736         if ((ifp = fopen(temporary_name, READ_BINARY)) == NULL) {
737             error("Cannot open a temporary file \"%s\"", temporary_name);
738             return FALSE;
739         }
740         copyfile(ifp, ofp, size, 0, 0);
741         fclose(ifp);
742         return TRUE;
743     }
744
745     return FALSE;
746 }
747 #endif /* HAVE_LIBAPPLEFILE */