1/*
2  zipnote.c - Zip 3
3
4  Copyright (c) 1990-2008 Info-ZIP.  All rights reserved.
5
6  See the accompanying file LICENSE, version 2007-Mar-4 or later
7  (the contents of which are also included in zip.h) for terms of use.
8  If, for some reason, all these files are missing, the Info-ZIP license
9  also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html
10*/
11/*
12 *  zipnote.c by Mark Adler.
13 */
14#define __ZIPNOTE_C
15
16#ifndef UTIL
17#define UTIL
18#endif
19#include "zip.h"
20#define DEFCPYRT        /* main module: enable copyright string defines! */
21#include "revision.h"
22#include <signal.h>
23
24/* Calculate size of static line buffer used in write (-w) mode. */
25#define WRBUFSIZ 2047
26/* The line buffer size should be at least as large as FNMAX. */
27#if FNMAX > WRBUFSIZ
28#  undef WRBUFSIZ
29#  define WRBUFSIZ FNMAX
30#endif
31
32/* Character to mark zip entry names in the comment file */
33#define MARK '@'
34#define MARKE " (comment above this line)"
35#define MARKZ " (zip file comment below this line)"
36
37/* Temporary zip file pointer */
38local FILE *tempzf;
39
40
41/* Local functions */
42local void handler OF((int));
43local void license OF((void));
44local void help OF((void));
45local void version_info OF((void));
46local void putclean OF((char *, extent));
47/* getline name conflicts with GNU getline() function */
48local char *zgetline OF((char *, extent));
49local int catalloc OF((char * far *, char *));
50int main OF((int, char **));
51
52/* keep compiler happy until implement long options - 11/4/2003 EG */
53struct option_struct far options[] = {
54  /* short longopt        value_type        negatable        ID    name */
55    {"h",  "help",        o_NO_VALUE,       o_NOT_NEGATABLE, 'h',  "help"},
56    /* the end of the list */
57    {NULL, NULL,          o_NO_VALUE,       o_NOT_NEGATABLE, 0,    NULL} /* end has option_ID = 0 */
58  };
59
60#ifdef MACOS
61#define ziperr(c, h)    zipnoteerr(c, h)
62#define zipwarn(a, b)   zipnotewarn(a, b)
63
64void zipnoteerr(int c, ZCONST char *h);
65void zipnotewarn(ZCONST char *a, ZCONST char *b);
66#endif
67
68#ifdef QDOS
69#define exit(p1) QDOSexit()
70#endif
71
72int set_filetype(out_path)
73  char *out_path;
74{
75#ifdef __BEOS__
76  /* Set the filetype of the zipfile to "application/zip" */
77  setfiletype( out_path, "application/zip" );
78#endif
79
80#ifdef __ATHEOS__
81  /* Set the filetype of the zipfile to "application/x-zip" */
82  setfiletype(out_path, "application/x-zip");
83#endif
84
85#ifdef MACOS
86  /* Set the Creator/Type of the zipfile to 'IZip' and 'ZIP ' */
87  setfiletype(out_path, 'IZip', 'ZIP ');
88#endif
89
90#ifdef RISCOS
91  /* Set the filetype of the zipfile to &DDC */
92  setfiletype(out_path, 0xDDC);
93#endif
94  return ZE_OK;
95}
96
97/* rename a split
98 * A split has a tempfile name until it is closed, then
99 * here rename it as out_path the final name for the split.
100 */
101int rename_split(temp_name, out_path)
102  char *temp_name;
103  char *out_path;
104{
105  int r;
106  /* Replace old zip file with new zip file, leaving only the new one */
107  if ((r = replace(out_path, temp_name)) != ZE_OK)
108  {
109    zipwarn("new zip file left as: ", temp_name);
110    free((zvoid *)tempzip);
111    tempzip = NULL;
112    ZIPERR(r, "was replacing split file");
113  }
114  if (zip_attributes) {
115    setfileattr(out_path, zip_attributes);
116  }
117  return ZE_OK;
118}
119
120void zipmessage_nl(a, nl)
121ZCONST char *a;     /* message string to output */
122int nl;             /* 1 = add nl to end */
123/* If nl false, print a message to mesg without new line.
124   If nl true, print and add new line.  If logfile is
125   open then also write message to log file. */
126{
127  if (noisy) {
128    fprintf(mesg, "%s", a);
129    if (nl) {
130      fprintf(mesg, "\n");
131      mesg_line_started = 0;
132    } else {
133      mesg_line_started = 1;
134    }
135    fflush(mesg);
136  }
137}
138
139void zipmessage(a, b)
140ZCONST char *a, *b;     /* message strings juxtaposed in output */
141/* Print a message to mesg and flush.  Also write to log file if
142   open.  Write new line first if current line has output already. */
143{
144  if (noisy) {
145    if (mesg_line_started)
146      fprintf(mesg, "\n");
147    fprintf(mesg, "%s%s\n", a, b);
148    mesg_line_started = 0;
149    fflush(mesg);
150  }
151}
152
153void ziperr(c, h)
154int c;                  /* error code from the ZE_ class */
155ZCONST char *h;         /* message about how it happened */
156/* Issue a message for the error, clean up files and memory, and exit. */
157{
158  if (PERR(c))
159    perror("zipnote error");
160  fprintf(mesg, "zipnote error: %s (%s)\n", ZIPERRORS(c), h);
161  if (tempzf != NULL)
162    fclose(tempzf);
163  if (tempzip != NULL)
164  {
165    destroy(tempzip);
166    free((zvoid *)tempzip);
167  }
168  if (zipfile != NULL)
169    free((zvoid *)zipfile);
170  EXIT(c);
171}
172
173
174local void handler(s)
175int s;                  /* signal number (ignored) */
176/* Upon getting a user interrupt, abort cleanly using ziperr(). */
177{
178#ifndef MSDOS
179  putc('\n', mesg);
180#endif /* !MSDOS */
181  ziperr(ZE_ABORT, "aborting");
182  s++;                                  /* keep some compilers happy */
183}
184
185
186void zipwarn(a, b)
187ZCONST char *a, *b;     /* message strings juxtaposed in output */
188/* Print a warning message to mesg (usually stderr) and return. */
189{
190  fprintf(mesg, "zipnote warning: %s%s\n", a, b);
191}
192
193
194local void license()
195/* Print license information to stdout. */
196{
197  extent i;             /* counter for copyright array */
198
199  for (i = 0; i < sizeof(swlicense)/sizeof(char *); i++)
200    puts(swlicense[i]);
201}
202
203
204local void help()
205/* Print help (along with license info) to stdout. */
206{
207  extent i;             /* counter for help array */
208
209  /* help array */
210  static ZCONST char *text[] = {
211"",
212"ZipNote %s (%s)",
213#ifdef VM_CMS
214"Usage:  zipnote [-w] [-q] [-b fm] zipfile",
215#else
216"Usage:  zipnote [-w] [-q] [-b path] zipfile",
217#endif
218"  the default action is to write the comments in zipfile to stdout",
219"  -w   write the zipfile comments from stdin",
220#ifdef VM_CMS
221"  -b   use \"fm\" as the filemode for the temporary zip file",
222#else
223"  -b   use \"path\" for the temporary zip file",
224#endif
225"  -q   quieter operation, suppress some informational messages",
226"  -h   show this help    -v   show version info    -L   show software license",
227"",
228"Example:",
229#ifdef VMS
230"     define/user sys$output foo.tmp",
231"     zipnote foo.zip",
232"     edit foo.tmp",
233"     ... then you edit the comments, save, and exit ...",
234"     define/user sys$input foo.tmp",
235"     zipnote -w foo.zip",
236#else
237#ifdef RISCOS
238"     zipnote foo/zip > foo/tmp",
239"     <!Edit> foo/tmp",
240"     ... then you edit the comments, save, and exit ...",
241"     zipnote -w foo/zip < foo/tmp",
242#else
243#ifdef VM_CMS
244"     zipnote foo.zip > foo.tmp",
245"     xedit foo tmp",
246"     ... then you edit the comments, save, and exit ...",
247"     zipnote -w foo.zip < foo.tmp",
248#else
249"     zipnote foo.zip > foo.tmp",
250"     ed foo.tmp",
251"     ... then you edit the comments, save, and exit ...",
252"     zipnote -w foo.zip < foo.tmp",
253#endif /* VM_CMS */
254#endif /* RISCOS */
255#endif /* VMS */
256"",
257"  \"@ name\" can be followed by an \"@=newname\" line to change the name"
258  };
259
260  for (i = 0; i < sizeof(copyright)/sizeof(char *); i++) {
261    printf(copyright[i], "zipnote");
262    putchar('\n');
263  }
264  for (i = 0; i < sizeof(text)/sizeof(char *); i++)
265  {
266    printf(text[i], VERSION, REVDATE);
267    putchar('\n');
268  }
269}
270
271/*
272 * XXX put this in version.c
273 */
274
275local void version_info()
276/* Print verbose info about program version and compile time options
277   to stdout. */
278{
279  extent i;             /* counter in text arrays */
280
281  /* Options info array */
282  static ZCONST char *comp_opts[] = {
283#ifdef DEBUG
284    "DEBUG",
285#endif
286    NULL
287  };
288
289  for (i = 0; i < sizeof(copyright)/sizeof(char *); i++)
290  {
291    printf(copyright[i], "zipnote");
292    putchar('\n');
293  }
294
295  for (i = 0; i < sizeof(versinfolines)/sizeof(char *); i++)
296  {
297    printf(versinfolines[i], "ZipNote", VERSION, REVDATE);
298    putchar('\n');
299  }
300
301  version_local();
302
303  puts("ZipNote special compilation options:");
304  for (i = 0; (int)i < (int)(sizeof(comp_opts)/sizeof(char *) - 1); i++)
305  {
306    printf("\t%s\n",comp_opts[i]);
307  }
308  if (i == 0)
309      puts("\t[none]");
310}
311
312
313local void putclean(s, n)
314char *s;                /* string to write to stdout */
315extent n;               /* length of string */
316/* Write the string s to stdout, filtering out control characters that are
317   not tab or newline (mainly to remove carriage returns), and prefix MARK's
318   and backslashes with a backslash.  Also, terminate with a newline if
319   needed. */
320{
321  int c;                /* next character in string */
322  int e;                /* last character written */
323
324  e = '\n';                     /* if empty, write nothing */
325  while (n--)
326  {
327    c = *(uch *)s++;
328    if (c == MARK || c == '\\')
329      putchar('\\');
330    if (c >= ' ' || c == '\t' || c == '\n')
331      { e=c; putchar(e); }
332  }
333  if (e != '\n')
334    putchar('\n');
335}
336
337
338local char *zgetline(buf, size)
339char *buf;
340extent size;
341/* Read a line of text from stdin into string buffer 'buf' of size 'size'.
342   In case of buffer overflow or EOF, a NULL pointer is returned. */
343{
344    char *line;
345    unsigned len;
346
347    line = fgets(buf, size, stdin);
348    if (line != NULL && (len = strlen(line)) > 0) {
349        if (len == size-1 && line[len-1] != '\n') {
350            /* buffer is full and record delimiter not seen -> overflow */
351            line = NULL;
352        } else {
353            /* delete trailing record delimiter */
354            if (line[len-1] == '\n') line[len-1] = '\0';
355        }
356    }
357    return line;
358}
359
360
361local int catalloc(a, s)
362char * far *a;          /* pointer to a pointer to a malloc'ed string */
363char *s;                /* string to concatenate on a */
364/* Concatentate the string s to the malloc'ed string pointed to by a.
365   Preprocess s by removing backslash escape characters. */
366{
367  char *p;              /* temporary pointer */
368  char *q;              /* temporary pointer */
369
370  for (p = q = s; *q; *p++ = *q++)
371    if (*q == '\\' && *(q+1))
372      q++;
373  *p = 0;
374  if ((p = malloc(strlen(*a) + strlen(s) + 3)) == NULL)
375    return ZE_MEM;
376  strcat(strcat(strcpy(p, *a), **a ? "\r\n" : ""), s);
377  free((zvoid *)*a);
378  *a = p;
379  return ZE_OK;
380}
381
382
383#ifndef USE_ZIPNOTEMAIN
384int main(argc, argv)
385#else
386int zipnotemain(argc, argv)
387#endif
388int argc;               /* number of tokens in command line */
389char **argv;            /* command line tokens */
390/* Write the comments in the zipfile to stdout, or read them from stdin. */
391{
392  char abf[WRBUFSIZ+1]; /* input line buffer */
393  char *a;              /* pointer to line buffer or NULL */
394  zoff_t c;             /* start of central directory */
395  int k;                /* next argument type */
396  char *q;              /* steps through option arguments */
397  int r;                /* arg counter, temporary variable */
398  zoff_t s;             /* length of central directory */
399  int t;                /* attributes of zip file */
400  int w;                /* true if updating zip file from stdin */
401  FILE *x;              /* input file for testing if can write it */
402  struct zlist far *z;  /* steps through zfiles linked list */
403
404#ifdef THEOS
405  setlocale(LC_CTYPE, "I");
406#endif
407
408#ifdef UNICODE_SUPPORT
409# ifdef UNIX
410  /* For Unix, set the locale to UTF-8.  Any UTF-8 locale is
411     OK and they should all be the same.  This allows seeing,
412     writing, and displaying (if the fonts are loaded) all
413     characters in UTF-8. */
414  {
415    char *loc;
416
417    /*
418      loc = setlocale(LC_CTYPE, NULL);
419      printf("  Initial language locale = '%s'\n", loc);
420    */
421
422    loc = setlocale(LC_CTYPE, "en_US.UTF-8");
423
424    /*
425      printf("langinfo %s\n", nl_langinfo(CODESET));
426    */
427
428    if (loc != NULL) {
429      /* using UTF-8 character set so can set UTF-8 GPBF bit 11 */
430      using_utf8 = 1;
431      /*
432        printf("  Locale set to %s\n", loc);
433      */
434    } else {
435      /*
436        printf("  Could not set Unicode UTF-8 locale\n");
437      */
438    }
439  }
440# endif
441#endif
442
443  /* If no args, show help */
444  if (argc == 1)
445  {
446    help();
447    EXIT(ZE_OK);
448  }
449
450  /* Direct info messages to stderr; stdout is used for data output. */
451  mesg = stderr;
452
453  init_upper();           /* build case map table */
454
455  /* Go through args */
456  zipfile = tempzip = NULL;
457  tempzf = NULL;
458  signal(SIGINT, handler);
459#ifdef SIGTERM              /* AMIGA has no SIGTERM */
460  signal(SIGTERM, handler);
461#endif
462#ifdef SIGABRT
463  signal(SIGABRT, handler);
464#endif
465#ifdef SIGBREAK
466  signal(SIGBREAK, handler);
467#endif
468#ifdef SIGBUS
469  signal(SIGBUS, handler);
470#endif
471#ifdef SIGILL
472  signal(SIGILL, handler);
473#endif
474#ifdef SIGSEGV
475  signal(SIGSEGV, handler);
476#endif
477  k = w = 0;
478  for (r = 1; r < argc; r++)
479    if (*argv[r] == '-') {
480      if (argv[r][1])
481        for (q = argv[r]+1; *q; q++)
482          switch (*q)
483          {
484            case 'b':   /* Specify path for temporary file */
485              if (k)
486                ziperr(ZE_PARMS, "use -b before zip file name");
487              else
488                k = 1;          /* Next non-option is path */
489              break;
490            case 'h':   /* Show help */
491              help();  EXIT(ZE_OK);
492            case 'l':  case 'L':  /* Show copyright and disclaimer */
493              license();  EXIT(ZE_OK);
494            case 'q':   /* Quiet operation, suppress info messages */
495              noisy = 0;  break;
496            case 'v':   /* Show version info */
497              version_info();  EXIT(ZE_OK);
498            case 'w':
499              w = 1;  break;
500            default:
501              ziperr(ZE_PARMS, "unknown option");
502          }
503      else
504        ziperr(ZE_PARMS, "zip file cannot be stdin");
505    } else
506      if (k == 0)
507      {
508        if (zipfile == NULL)
509        {
510          if ((zipfile = ziptyp(argv[r])) == NULL)
511            ziperr(ZE_MEM, "was processing arguments");
512        }
513        else
514          ziperr(ZE_PARMS, "can only specify one zip file");
515      }
516      else
517      {
518        tempath = argv[r];
519        k = 0;
520      }
521  if (zipfile == NULL)
522    ziperr(ZE_PARMS, "need to specify zip file");
523
524  if ((in_path = malloc(strlen(zipfile) + 1)) == NULL) {
525    ziperr(ZE_MEM, "input");
526  }
527  strcpy(in_path, zipfile);
528
529  /* Read zip file */
530  if ((r = readzipfile()) != ZE_OK)
531    ziperr(r, zipfile);
532  if (zfiles == NULL)
533    ziperr(ZE_NAME, zipfile);
534
535  /* Put comments to stdout, if not -w */
536  if (!w)
537  {
538    for (z = zfiles; z != NULL; z = z->nxt)
539    {
540      printf("%c %s\n", MARK, z->zname);
541      putclean(z->comment, z->com);
542      printf("%c%s\n", MARK, MARKE);
543    }
544    printf("%c%s\n", MARK, MARKZ);
545    putclean(zcomment, zcomlen);
546    EXIT(ZE_OK);
547  }
548
549  /* If updating comments, make sure zip file is writeable */
550  if ((x = fopen(zipfile, "a")) == NULL)
551    ziperr(ZE_CREAT, zipfile);
552  fclose(x);
553  t = getfileattr(zipfile);
554
555  /* Process stdin, replacing comments */
556  z = zfiles;
557  while ((a = zgetline(abf, WRBUFSIZ+1)) != NULL &&
558         (a[0] != MARK || strcmp(a + 1, MARKZ)))
559  {                                     /* while input and not file comment */
560    if (a[0] != MARK || a[1] != ' ')    /* better be "@ name" */
561      ziperr(ZE_NOTE, "unexpected input");
562    while (z != NULL && strcmp(a + 2, z->zname))
563      z = z->nxt;                       /* allow missing entries in order */
564    if (z == NULL)
565      ziperr(ZE_NOTE, "unknown entry name");
566    if ((a = zgetline(abf, WRBUFSIZ+1)) != NULL && a[0] == MARK && a[1] == '=')
567    {
568      if (z->name != z->iname)
569        free((zvoid *)z->iname);
570      if ((z->iname = malloc(strlen(a+1))) == NULL)
571        ziperr(ZE_MEM, "was changing name");
572#ifdef EBCDIC
573      strtoasc(z->iname, a+2);
574#else
575      strcpy(z->iname, a+2);
576#endif
577
578/*
579 * Don't update z->nam here, we need the old value a little later.....
580 * The update is handled in zipcopy().
581 */
582      a = zgetline(abf, WRBUFSIZ+1);
583    }
584    if (z->com)                         /* change zip entry comment */
585      free((zvoid *)z->comment);
586    z->comment = malloc(1);  *(z->comment) = 0;
587    while (a != NULL && *a != MARK)
588    {
589      if ((r = catalloc(&(z->comment), a)) != ZE_OK)
590        ziperr(r, "was building new zipentry comments");
591      a = zgetline(abf, WRBUFSIZ+1);
592    }
593    z->com = strlen(z->comment);
594    z = z->nxt;                         /* point to next entry */
595  }
596  if (a != NULL)                        /* change zip file comment */
597  {
598    zcomment = malloc(1);  *zcomment = 0;
599    while ((a = zgetline(abf, WRBUFSIZ+1)) != NULL)
600      if ((r = catalloc(&zcomment, a)) != ZE_OK)
601        ziperr(r, "was building new zipfile comment");
602    zcomlen = strlen(zcomment);
603  }
604
605  /* Open output zip file for writing */
606#if defined(UNIX) && !defined(NO_MKSTEMP)
607  {
608    int yd;
609    int i;
610
611    /* use mkstemp to avoid race condition and compiler warning */
612
613    if (tempath != NULL)
614    {
615      /* if -b used to set temp file dir use that for split temp */
616      if ((tempzip = malloc(strlen(tempath) + 12)) == NULL) {
617        ZIPERR(ZE_MEM, "allocating temp filename");
618      }
619      strcpy(tempzip, tempath);
620      if (lastchar(tempzip) != '/')
621        strcat(tempzip, "/");
622    }
623    else
624    {
625      /* create path by stripping name and appending template */
626      if ((tempzip = malloc(strlen(zipfile) + 12)) == NULL) {
627      ZIPERR(ZE_MEM, "allocating temp filename");
628      }
629      strcpy(tempzip, zipfile);
630      for(i = strlen(tempzip); i > 0; i--) {
631        if (tempzip[i - 1] == '/')
632          break;
633      }
634      tempzip[i] = '\0';
635    }
636    strcat(tempzip, "ziXXXXXX");
637
638    if ((yd = mkstemp(tempzip)) == EOF) {
639      ZIPERR(ZE_TEMP, tempzip);
640    }
641    if ((tempzf = y = fdopen(yd, FOPW)) == NULL) {
642      ZIPERR(ZE_TEMP, tempzip);
643    }
644  }
645#else
646  if ((tempzf = y = fopen(tempzip = tempname(zipfile), FOPW)) == NULL)
647    ziperr(ZE_TEMP, tempzip);
648#endif
649
650  /* Open input zip file again, copy preamble if any */
651  if ((in_file = fopen(zipfile, FOPR)) == NULL)
652    ziperr(ZE_NAME, zipfile);
653
654  if (zipbeg && (r = bfcopy(zipbeg)) != ZE_OK)
655    ziperr(r, r == ZE_TEMP ? tempzip : zipfile);
656  tempzn = zipbeg;
657
658  /* Go through local entries, copying them over as is */
659  fix = 3; /* needed for zipcopy if name changed */
660  for (z = zfiles; z != NULL; z = z->nxt) {
661    if ((r = zipcopy(z)) != ZE_OK)
662      ziperr(r, "was copying an entry");
663  }
664  fclose(x);
665
666  /* Write central directory and end of central directory with new comments */
667  if ((c = zftello(y)) == (zoff_t)-1)    /* get start of central */
668    ziperr(ZE_TEMP, tempzip);
669  for (z = zfiles; z != NULL; z = z->nxt)
670    if ((r = putcentral(z)) != ZE_OK)
671      ziperr(r, tempzip);
672  if ((s = zftello(y)) == (zoff_t)-1)    /* get end of central */
673    ziperr(ZE_TEMP, tempzip);
674  s -= c;                       /* compute length of central */
675  if ((r = putend((zoff_t)zcount, s, c, zcomlen, zcomment)) != ZE_OK)
676    ziperr(r, tempzip);
677  tempzf = NULL;
678  if (fclose(y))
679    ziperr(ZE_TEMP, tempzip);
680  if ((r = replace(zipfile, tempzip)) != ZE_OK)
681  {
682    zipwarn("new zip file left as: ", tempzip);
683    free((zvoid *)tempzip);
684    tempzip = NULL;
685    ziperr(r, "was replacing the original zip file");
686  }
687  free((zvoid *)tempzip);
688  tempzip = NULL;
689  setfileattr(zipfile, t);
690#ifdef RISCOS
691  /* Set the filetype of the zipfile to &DDC */
692  setfiletype(zipfile,0xDDC);
693#endif
694  free((zvoid *)zipfile);
695  zipfile = NULL;
696
697  /* Done! */
698  RETURN(0);
699}
700