File: [local] / src / usr.bin / compress / zipopen.c (download)
Revision 1.1, Sat Oct 22 14:41:27 2022 UTC (18 months, 3 weeks ago) by millert
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, HEAD
Add support to gunzip for zip files that contain a single member.
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.
|
/* $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;
}