Add support to gunzip for zip files that contain a single member.
authormillert <millert@openbsd.org>
Sat, 22 Oct 2022 14:41:27 +0000 (14:41 +0000)
committermillert <millert@openbsd.org>
Sat, 22 Oct 2022 14:41:27 +0000 (14:41 +0000)
This matches the behavior of GNU gzip and is most useful with "gunzip
-c" to pipe the uncompressed output.  It will not decompress a file
with more than one member unless in cat mode, in which case only
the first file is displayed.

To decompress a .zip file without the -c option, "-S .zip" must be
specified. The file name stored in the .zip file is not used unless
the -N option is specified. This is consistent with GNU gzip).

Does not increase the size of gzip on the install media.
OK jmc@ for documentation.

usr.bin/compress/Makefile
usr.bin/compress/compress.1
usr.bin/compress/compress.h
usr.bin/compress/gzip.1
usr.bin/compress/main.c
usr.bin/compress/zipopen.c [new file with mode: 0644]

index 357ff5d..5e14336 100644 (file)
@@ -1,7 +1,7 @@
-#      $OpenBSD: Makefile,v 1.22 2016/03/30 06:38:45 jmc Exp $
+#      $OpenBSD: Makefile,v 1.23 2022/10/22 14:41:27 millert Exp $
 
 PROG=  compress
-SRCS=  main.c zopen.c gzopen.c nullopen.c
+SRCS=  main.c zopen.c gzopen.c zipopen.c nullopen.c
 MAN=   compress.1 gzexe.1 gzip.1 zdiff.1 zforce.1 zmore.1 znew.1
 LINKS= ${BINDIR}/compress ${BINDIR}/uncompress \
        ${BINDIR}/compress ${BINDIR}/zcat \
index cf84be1..2a90a53 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: compress.1,v 1.48 2014/03/17 14:23:50 jmc Exp $
+.\"    $OpenBSD: compress.1,v 1.49 2022/10/22 14:41:27 millert Exp $
 .\"    $NetBSD: compress.1,v 1.5 1995/03/26 09:44:34 glass Exp $
 .\"
 .\" Copyright (c) 1986, 1990, 1993
@@ -34,7 +34,7 @@
 .\"
 .\"     @(#)compress.1 8.2 (Berkeley) 4/18/94
 .\"
-.Dd $Mdocdate: March 17 2014 $
+.Dd $Mdocdate: October 22 2022 $
 .Dt COMPRESS 1
 .Os
 .Sh NAME
@@ -84,10 +84,11 @@ utility restores compressed files to their original form, renaming the
 files by removing the extension (or by using the stored name if the
 .Fl N
 flag is specified).
-It has the ability to restore files compressed by both
-.Nm
-and
+It has the ability to restore files compressed by
+.Nm ,
 .Xr gzip 1 ,
+and
+.Xr zip 1 ,
 recognising the following extensions:
 .Dq .Z ,
 .Dq -Z ,
@@ -102,12 +103,17 @@ recognising the following extensions:
 .Dq -taz ,
 and
 .Dq _taz .
+The
+.Fl S
+option can be used to support other file extensions.
 Extensions ending in
 .Dq tgz
 and
 .Dq taz
 are not removed when decompressing, instead they are converted to
 .Dq tar .
+Files in zip format are only supported if they contain a single member
+either compressed with the deflate scheme or stored uncompressed.
 .Pp
 The
 .Nm zcat
@@ -245,7 +251,12 @@ Recursive mode:
 .Nm
 will descend into specified directories.
 .It Fl S Ar suffix
-Set the suffix for compressed files.
+When compressing, use the specified
+.Ar suffix
+as the extension when creating output files.
+When uncompressing, recognize file names with the specified
+.Ar suffix
+as compressed files.
 .It Fl t
 Test the integrity of each file leaving any files intact.
 .It Fl v
index 56910c3..35f08fa 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: compress.h,v 1.14 2021/01/18 00:46:58 mortimer Exp $  */
+/*     $OpenBSD: compress.h,v 1.15 2022/10/22 14:41:27 millert Exp $   */
 
 /*
  * Copyright (c) 1997 Michael Shalayeff
@@ -65,7 +65,6 @@ extern int zread(void *, char *, int);
 extern int zwrite(void *, const char *, int);
 extern int z_close(void *, struct z_info *, const char *, struct stat *);
 
-
 extern void *gz_ropen(int, char *, int);
 extern void *gz_wopen(int, char *, int, u_int32_t);
 extern int gz_read(void *, char *, int);
@@ -73,6 +72,10 @@ extern int gz_write(void *, const char *, int);
 extern int gz_close(void *, struct z_info *, const char *, struct stat *);
 extern int gz_flush(void *, int);
 
+extern void *zip_ropen(int, char *, int);
+extern int zip_read(void *, char *, int);
+extern int zip_close(void *, struct z_info *, const char *, struct stat *);
+
 extern void *null_ropen(int, char *, int);
 extern void *null_wopen(int, char *, int, u_int32_t);
 extern int null_read(void *, char *, int);
index 39d5dbe..b162d8a 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: gzip.1,v 1.15 2022/03/14 21:52:08 solene Exp $
+.\"    $OpenBSD: gzip.1,v 1.16 2022/10/22 14:41:27 millert Exp $
 .\"
 .\" Copyright (c) 1986, 1990, 1993
 .\"    The Regents of the University of California.  All rights reserved.
@@ -33,7 +33,7 @@
 .\"
 .\"     @(#)compress.1 8.2 (Berkeley) 4/18/94
 .\"
-.Dd $Mdocdate: March 14 2022 $
+.Dd $Mdocdate: October 22 2022 $
 .Dt GZIP 1
 .Os
 .Sh NAME
@@ -83,10 +83,11 @@ utility restores compressed files to their original form, renaming the
 files by removing the extension (or by using the stored name if the
 .Fl N
 flag is specified).
-It has the ability to restore files compressed by both
-.Nm
-and
+It has the ability to restore files compressed by
+.Nm ,
 .Xr compress 1 ,
+and
+.Xr zip 1 ,
 recognising the following extensions:
 .Dq .Z ,
 .Dq -Z ,
@@ -101,12 +102,17 @@ recognising the following extensions:
 .Dq -taz ,
 and
 .Dq _taz .
+The
+.Fl S
+option can be used to support other file extensions.
 Extensions ending in
 .Dq tgz
 and
 .Dq taz
 are not removed when decompressing, instead they are converted to
 .Dq tar .
+Files in zip format are only supported if they contain a single member
+either compressed with the deflate scheme or stored uncompressed.
 .Pp
 The
 .Nm gzcat
@@ -242,7 +248,12 @@ Recursive mode:
 .Nm
 will descend into specified directories.
 .It Fl S Ar suffix
-Set the suffix for compressed files.
+When compressing, use the specified
+.Ar suffix
+as the extension when creating output files.
+When uncompressing, recognize file names with the specified
+.Ar suffix
+as compressed files.
 .It Fl t
 Test the integrity of each file leaving any files intact.
 .It Fl V
index 81d7bd5..09be764 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: main.c,v 1.101 2022/08/29 19:42:01 tb Exp $   */
+/*     $OpenBSD: main.c,v 1.102 2022/10/22 14:41:27 millert Exp $      */
 
 /*
  * Copyright (c) 1992, 1993
@@ -52,8 +52,9 @@
 
 enum program_mode pmode;
 
-int cat, decomp, pipin, force, verbose, testmode, list, recurse, storename;
-int kflag;
+static int cat, decomp, kflag, pipin, force, verbose, testmode, list, recurse;
+static int storename;
+static char suffix[16];
 extern char *__progname;
 
 const struct compressor {
@@ -102,6 +103,20 @@ const struct compressor {
                zwrite,
                z_close
        },
+#define M_UNZIP (&c_table[2])
+       {
+               "unzip",
+               ".zip",
+               "PK",
+               NULL,
+               "cfhkLlNno:qrtVv",
+               "fhqr",
+               zip_ropen,
+               zip_read,
+               NULL,
+               NULL,
+               zip_close
+       },
 #endif /* SMALL */
   { NULL }
 };
@@ -167,7 +182,7 @@ main(int argc, char *argv[])
        const struct compressor *method;
        const char *optstr, *s;
        char *p, *infile;
-       char outfile[PATH_MAX], _infile[PATH_MAX], suffix[16];
+       char outfile[PATH_MAX], _infile[PATH_MAX];
        int bits, ch, error, rc, cflag, oflag;
 
        if (pledge("stdio rpath wpath cpath fattr chown", NULL) == -1)
@@ -312,7 +327,6 @@ main(int argc, char *argv[])
                        if (optarg[0] != '.')
                                *p++ = '.';
                        strlcpy(p, optarg, sizeof(suffix) - (p - suffix));
-                       p = optarg;
                        break;
                case 't':
                        testmode = 1;
@@ -700,6 +714,8 @@ dodecompress(const char *in, char *out, struct stat *sb)
                                }
                        }
                        ofd = open(out, O_WRONLY|O_CREAT|O_TRUNC, S_IWUSR);
+                       if (ofd != -1)
+                               oreg = 1;
                }
                if (ofd == -1) {
                        if (verbose >= 0)
@@ -725,10 +741,21 @@ dodecompress(const char *in, char *out, struct stat *sb)
                error = errno == EINVAL ? WARNING : FAILURE;
        }
 
-       if (method->close(cookie, &info, NULL, NULL)) {
-               if (!error && verbose >= 0)
-                       warnx("%s", in);
-               error = FAILURE;
+       if (method->close(cookie, &info, NULL, NULL) && !error) {
+#ifdef M_UNZIP
+               if (errno == EEXIST) {
+                       if (verbose >= 0) {
+                               warnx("more than one entry in %s: %s", in,
+                                   cat ? "ignoring the rest" : "unchanged");
+                       }
+                       error = cat ? WARNING : FAILURE;
+               } else
+#endif
+               {
+                       if (verbose >= 0)
+                               warn("%s", in);
+                       error = FAILURE;
+               }
        }
        if (storename && !cat) {
                if (info.mtime != 0) {
@@ -736,10 +763,9 @@ dodecompress(const char *in, char *out, struct stat *sb)
                            sb->st_atimespec.tv_sec = info.mtime;
                        sb->st_mtimespec.tv_nsec =
                            sb->st_atimespec.tv_nsec = 0;
-               } else
-                       storename = 0;          /* no timestamp to restore */
+               }
        }
-       if (error == SUCCESS)
+       if (error != FAILURE)
                setfile(out, ofd, sb);
 
        if (ofd != -1 && close(ofd)) {
@@ -748,7 +774,7 @@ dodecompress(const char *in, char *out, struct stat *sb)
                error = FAILURE;
        }
 
-       if (!error) {
+       if (error != FAILURE) {
                if (list) {
                        if (info.mtime == 0)
                                info.mtime = (u_int32_t)sb->st_mtime;
@@ -760,7 +786,7 @@ dodecompress(const char *in, char *out, struct stat *sb)
        }
 
        /* On error, clean up the file we created but preserve errno. */
-       if (error && oreg)
+       if (error == FAILURE && oreg)
                unlink(out);
 
        return (error);
@@ -829,14 +855,17 @@ const char *
 check_suffix(const char *infile)
 {
        int i;
-       char *suf, *sep, *separators = ".-_";
-       static char *suffixes[] = { "Z", "gz", "z", "tgz", "taz", NULL };
+       const char *suf, *sep;
+       const char separators[] = ".-_";
+       const char *suffixes[] = { "Z", "gz", "z", "tgz", "taz", NULL };
 
        for (sep = separators; *sep != '\0'; sep++) {
                if ((suf = strrchr(infile, *sep)) == NULL)
                        continue;
                suf++;
 
+               if (strcmp(suf, suffix + 1) == 0)
+                       return (suf - 1);
                for (i = 0; suffixes[i] != NULL; i++) {
                        if (strcmp(suf, suffixes[i]) == 0)
                                return (suf - 1);
diff --git a/usr.bin/compress/zipopen.c b/usr.bin/compress/zipopen.c
new file mode 100644 (file)
index 0000000..fda7d9b
--- /dev/null
@@ -0,0 +1,454 @@
+/*     $OpenBSD: zipopen.c,v 1.1 2022/10/22 14:41:27 millert Exp $     */
+
+/*
+ * Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+#include <zlib.h>
+#include "compress.h"
+
+#define MINIMUM(a, b)  (((a) < (b)) ? (a) : (b))
+
+/* Signatures for zip file headers we use. */
+#define ZIPMAG 0x4b50          /* first two bytes of the zip signature */
+#define LOCREM 0x0403          /* remaining two bytes in zip signature */
+#define LOCSIG 0x04034b50      /* local file header signature */
+#define EXTSIG 0x08074b50      /* extended local header signature */
+
+/* Header sizes. */
+#define LOCHDR 30              /* size of local header, including signature */
+#define EXTHDR 16              /* size of extended local header, inc sig */
+
+/* General purpose flag bits. */
+#define CRPFLG 1               /* flag bit for encrypted entry */
+#define EXTFLG 8               /* flag bit for extended local header */
+
+/* Extra field definitions */
+#define        EF_ZIP64        0x0001  /* zip64 support */
+#define        EF_TIME         0x5455  /* mtime, atime, ctime in UTC ("UT") */
+#define EF_IZUNIX      0x5855  /* UNIX extra field ID ("UX") */
+
+#define Z_STORED 0             /* Stored uncompressed in .zip */
+
+struct zip_state {
+       z_stream z_stream;      /* libz stream */
+       uint8_t  z_buf[Z_BUFSIZE]; /* I/O buffer */
+       uint8_t  z_eof;         /* set if end of input file */
+       uint8_t  z_zip64;       /* 64-bit file sizes */
+       uint16_t z_method;      /* Z_DEFLATE or Z_STORED */
+       uint16_t z_flags;       /* general purpose flags */
+       int      z_fd;          /* zip file descriptor */
+       uint32_t z_time;        /* timestamp (mtime) */
+       uint32_t z_crc;         /* crc32 of uncompressed data */
+       uint32_t z_ocrc;        /* crc32 of uncompressed data (from header) */
+       uint32_t z_hlen;        /* length of the zip header */
+       uint64_t z_ulen;        /* uncompressed data length (from header) */
+       uint64_t z_total_in;    /* # bytes in */
+       uint64_t z_total_out;   /* # bytes out */
+};
+
+static int
+get_byte(struct zip_state *s)
+{
+       if (s->z_eof)
+               return EOF;
+
+       if (s->z_stream.avail_in == 0) {
+               ssize_t nread = read(s->z_fd, s->z_buf, Z_BUFSIZE);
+               if (nread <= 0) {
+                       s->z_eof = 1;
+                       return EOF;
+               }
+               s->z_stream.avail_in = nread;
+               s->z_stream.next_in = s->z_buf;
+       }
+       s->z_stream.avail_in--;
+       return *s->z_stream.next_in++;
+}
+
+static uint16_t
+get_uint16(struct zip_state *s)
+{
+       uint16_t x;
+
+       x  = ((uint16_t)(get_byte(s) & 0xff));
+       x |= ((uint16_t)(get_byte(s) & 0xff))<<8;
+       return x;
+}
+
+static uint32_t
+get_uint32(struct zip_state *s)
+{
+       uint32_t x;
+
+       x  = ((uint32_t)(get_byte(s) & 0xff));
+       x |= ((uint32_t)(get_byte(s) & 0xff))<<8;
+       x |= ((uint32_t)(get_byte(s) & 0xff))<<16;
+       x |= ((uint32_t)(get_byte(s) & 0xff))<<24;
+       return x;
+}
+
+static uint64_t
+get_uint64(struct zip_state *s)
+{
+       uint64_t x;
+
+       x  = ((uint64_t)(get_byte(s) & 0xff));
+       x |= ((uint64_t)(get_byte(s) & 0xff))<<8;
+       x |= ((uint64_t)(get_byte(s) & 0xff))<<16;
+       x |= ((uint64_t)(get_byte(s) & 0xff))<<24;
+       x |= ((uint64_t)(get_byte(s) & 0xff))<<32;
+       x |= ((uint64_t)(get_byte(s) & 0xff))<<40;
+       x |= ((uint64_t)(get_byte(s) & 0xff))<<48;
+       x |= ((uint64_t)(get_byte(s) & 0xff))<<56;
+       return x;
+}
+
+static int
+get_header(struct zip_state *s, char *name, int gotmagic)
+{
+       int c, got_mtime = 0;
+       uint16_t namelen, extlen;
+       uint32_t sig;
+
+       /* Check the zip local file header signature. */
+       if (!gotmagic) {
+               sig = get_uint32(s);
+               if (sig != LOCSIG) {
+                       errno = EFTYPE;
+                       return -1;
+               }
+       } else {
+               sig = get_uint16(s);
+               if (sig != LOCREM) {
+                       errno = EFTYPE;
+                       return -1;
+               }
+       }
+
+       /* Read the local header fields. */
+       get_uint16(s);                  /* min version */
+       s->z_flags = get_uint16(s);     /* general purpose flags */
+       s->z_method = get_uint16(s);    /* compression method */
+       get_uint32(s);                  /* DOS format mtime */
+       s->z_ocrc = get_uint32(s);      /* 32-bit CRC */
+       get_uint32(s);                  /* compressed size */
+       s->z_ulen = get_uint32(s);      /* uncompressed size */
+       namelen = get_uint16(s);        /* file name length */
+       extlen = get_uint16(s);         /* length of extra fields */
+       s->z_hlen = LOCHDR;
+
+       /* Encrypted files not supported. */
+       if (s->z_flags & CRPFLG) {
+               errno = EFTYPE;
+               return -1;
+       }
+
+       /* Supported compression methods are deflate and store. */
+       if (s->z_method != Z_DEFLATED && s->z_method != Z_STORED) {
+               errno = EFTYPE;
+               return -1;
+       }
+
+       /* Store the original file name if present. */
+       if (namelen != 0 && name != NULL) {
+               const char *ep = name + PATH_MAX - 1;
+               for (; namelen > 0; namelen--) {
+                       if ((c = get_byte(s)) == EOF)
+                               break;
+                       s->z_hlen++;
+                       if (c == '\0')
+                               break;
+                       if (name < ep)
+                               *name++ = c;
+               }
+               *name = '\0';
+       }
+
+       /* Parse extra fields, if any. */
+       while (extlen >= 4) {
+               uint16_t sig;
+               int fieldlen;
+
+               sig = get_uint16(s);
+               fieldlen = get_uint16(s);
+               s->z_hlen += 4;
+               extlen -= 4;
+
+               switch (sig) {
+               case EF_ZIP64:
+                       /* 64-bit file sizes */
+                       s->z_zip64 = 1;
+                       if (fieldlen >= 8) {
+                               s->z_ulen = get_uint64(s);
+                               s->z_hlen += 8;
+                               extlen -= 8;
+                               fieldlen -= 8;
+                       }
+                       break;
+               case EF_TIME:
+                       /* UTC timestamps */
+                       if ((c = get_byte(s)) == EOF)
+                               break;
+                       s->z_hlen++;
+                       extlen--;
+                       fieldlen--;
+                       if (c & 1) {
+                               got_mtime = 1;
+                               s->z_time = get_uint32(s);
+                               s->z_hlen += 4;
+                               extlen -= 4;
+                               fieldlen -= 4;
+                       }
+                       break;
+               case EF_IZUNIX:
+                       /* We prefer EF_TIME if it is present. */
+                       if (got_mtime)
+                               break;
+
+                       /* skip atime, store mtime. */
+                       (void)get_uint32(s);
+                       s->z_time = get_uint32(s);
+                       s->z_hlen += 8;
+                       extlen -= 8;
+                       fieldlen -= 8;
+                       break;
+               default:
+                       break;
+               }
+
+               /* Consume any unparsed bytes in the field. */
+               for (; fieldlen > 0; fieldlen--) {
+                       if (get_byte(s) == EOF)
+                               break;
+                       s->z_hlen++;
+                       extlen--;
+               }
+       }
+       for (; extlen > 0; extlen--) {
+               if (get_byte(s) == EOF)
+                       break;
+               s->z_hlen++;
+       }
+
+       return 0;
+}
+
+void *
+zip_ropen(int fd, char *name, int gotmagic)
+{
+       struct zip_state *s;
+
+       if (fd < 0)
+               return NULL;
+
+       if ((s = calloc(1, sizeof(*s))) == NULL)
+               return NULL;
+
+       s->z_fd = fd;
+       s->z_crc = crc32(0, NULL, 0);
+
+       /* Request a raw inflate, there is no zlib/gzip header present. */
+       if (inflateInit2(&s->z_stream, -MAX_WBITS) != Z_OK) {
+               free(s);
+               return NULL;
+       }
+       s->z_stream.next_in = s->z_buf;
+       s->z_stream.avail_out = sizeof(s->z_buf);
+
+       /* Read the zip header. */
+       if (get_header(s, name, gotmagic) != 0) {
+               zip_close(s, NULL, NULL, NULL);
+               s = NULL;
+       }
+
+       return s;
+}
+
+static int
+zip_store(struct zip_state *s)
+{
+       int error = Z_OK;
+       uLong copy_len;
+
+       if ((int)s->z_stream.avail_in <= 0)
+               return s->z_stream.avail_in == 0 ? Z_STREAM_END : Z_DATA_ERROR;
+
+       /* For stored files we rely on z_ulen being set. */
+       copy_len = MINIMUM(s->z_stream.avail_out, s->z_stream.avail_in);
+       if (copy_len >= s->z_ulen - s->z_total_out) {
+               /* Don't copy past the end of the file. */
+               copy_len = s->z_ulen - s->z_total_out;
+               error = Z_STREAM_END;
+       }
+
+       memcpy(s->z_stream.next_out, s->z_stream.next_in, copy_len);
+       s->z_stream.next_out += copy_len;
+       s->z_stream.avail_out -= copy_len;
+       s->z_stream.next_in += copy_len;
+       s->z_stream.avail_in -= copy_len;
+       s->z_total_in += copy_len;
+       s->z_total_out += copy_len;
+
+       return error;
+}
+
+int
+zip_read(void *cookie, char *buf, int len)
+{
+       struct zip_state *s = cookie;
+       Bytef *ubuf = buf;
+       int error = Z_OK;
+
+       s->z_stream.next_out = ubuf;
+       s->z_stream.avail_out = len;
+
+       while (error == Z_OK && !s->z_eof && s->z_stream.avail_out != 0) {
+               if (s->z_stream.avail_in == 0) {
+                       ssize_t nread = read(s->z_fd, s->z_buf, Z_BUFSIZE);
+                       switch (nread) {
+                       case -1:
+                               goto bad;
+                       case 0:
+                               s->z_eof = 1;
+                               continue;
+                       default:
+                               s->z_stream.avail_in = nread;
+                               s->z_stream.next_in = s->z_buf;
+                       }
+               }
+
+               if (s->z_method == Z_DEFLATED) {
+                       /*
+                        * Prevent overflow of z_stream.total_{in,out}
+                        * which may be 32-bit.
+                        */
+                       uLong prev_total_in = s->z_stream.total_in;
+                       uLong prev_total_out = s->z_stream.total_out;
+                       error = inflate(&s->z_stream, Z_NO_FLUSH);
+                       s->z_total_in += s->z_stream.total_in - prev_total_in;
+                       s->z_total_out += s->z_stream.total_out - prev_total_out;
+               } else {
+                       /* File stored uncompressed. */
+                       error = zip_store(s);
+               }
+       }
+
+       switch (error) {
+       case Z_OK:
+               s->z_crc = crc32(s->z_crc, ubuf,
+                   (uInt)(s->z_stream.next_out - ubuf));
+               break;
+       case Z_STREAM_END:
+               s->z_eof = 1;
+
+               /*
+                * Check CRC and original size.
+                * These may be found in the local header or, if
+                * EXTFLG is set, immediately following the file.
+                */
+               s->z_crc = crc32(s->z_crc, ubuf,
+                   (uInt)(s->z_stream.next_out - ubuf));
+
+               if (s->z_flags & EXTFLG) {
+                       /*
+                        * Read data descriptor:
+                        *  signature 0x08074b50: 4 bytes
+                        *  CRC-32: 4 bytes
+                        *  compressed size: 4 or 8 bytes
+                        *  uncompressed size: 4 or 8 bytes
+                        */
+                       get_uint32(s);
+                       s->z_ocrc = get_uint32(s);
+                       if (s->z_zip64) {
+                               get_uint64(s);
+                               s->z_ulen = get_uint64(s);
+                               s->z_hlen += 8;
+                       } else {
+                               get_uint32(s);
+                               s->z_ulen = get_uint32(s);
+                       }
+                       s->z_hlen += EXTHDR;
+               }
+               if (s->z_ulen != s->z_total_out) {
+                       errno = EIO;
+                       goto bad;
+               }
+               if (s->z_ocrc != s->z_crc) {
+                       errno = EINVAL;
+                       goto bad;
+               }
+               break;
+       case Z_DATA_ERROR:
+               errno = EINVAL;
+               goto bad;
+       case Z_BUF_ERROR:
+               errno = EIO;
+               goto bad;
+       default:
+               goto bad;
+       }
+
+       return len - s->z_stream.avail_out;
+bad:
+       return -1;
+}
+
+int
+zip_close(void *cookie, struct z_info *info, const char *name, struct stat *sb)
+{
+       struct zip_state *s = cookie;
+       int error = 0;
+
+       if (s == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (info != NULL) {
+               info->mtime = s->z_time;
+               info->crc = s->z_crc;
+               info->hlen = s->z_hlen;
+               info->total_in = s->z_total_in;
+               info->total_out = s->z_total_out;
+       }
+
+       if (s->z_stream.state != NULL) {
+               /* inflateEnd() overwrites errno. */
+               (void)inflateEnd(&s->z_stream);
+       }
+
+       /*
+        * Check for the presence of additional files in the .zip.
+        * Do not remove the original if we cannot extract all the files.
+        */
+       s->z_eof = 0;
+       if (get_header(s, NULL, 0) == 0) {
+               errno = EEXIST;
+               error = -1;
+       }
+
+       (void)close(s->z_fd);
+
+       free(s);
+
+       return error;
+}