blob: 0c2a09f21a46a257a80a863e688f68bfbcd13334 [file]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "zip.h"
#include "zipint.h"
static struct zf *_zip_readcdir(FILE *fp, unsigned char *buf,
unsigned char *eocd, int buflen);
static int _zip_read2(unsigned char **a);
static int _zip_read4(unsigned char **a);
static char *_zip_readstr(unsigned char **buf, int len, int nulp);
static char *_zip_readfpstr(FILE *fp, int len, int nulp);
static int _zip_checkcons(FILE *fp, struct zf *zf);
static int _zip_headercomp(struct zf_entry *h1, int local1p, struct zf_entry *h2,
int local2p);
/* zip_open:
Tries to open the file 'fn' as a zipfile. If flags & ZIP_CHECKCONS,
also does some consistency checks (comparing local headers to
central directory entries). If flags & ZIP_CREATE, make a new file
(if flags & ZIP_EXCL, error if already existing). Returns a
zipfile struct, or NULL if unsuccessful, setting zip_err. */
struct zf *
zip_open(char *fn, int flags)
{
FILE *fp;
unsigned char *buf, *match;
int a, i, buflen, best;
struct zf *cdir, *cdirnew;
long len;
struct stat st;
if (fn == NULL)
return NULL;
if (stat(fn, &st) != 0) {
if (flags & ZIP_CREATE) {
cdir = _zip_zf_new();
cdir->zn = strdup(fn);
if (!cdir->zn) {
zip_err = ZERR_MEMORY;
return NULL;
}
return cdir;
} else {
zip_err = ZERR_FILENEXISTS;
return NULL;
}
} else if ((flags & ZIP_EXCL)) {
zip_err = ZERR_FILEEXISTS;
return NULL;
}
/* ZIP_CREAT gets ignored if file exists and not ZIP_EXCL,
just like open() */
if ((fp=fopen(fn, "rb"))==NULL) {
zip_err = ZERR_OPEN;
return NULL;
}
clearerr(fp);
fseek(fp, 0, SEEK_END);
len = ftell(fp);
i = fseek(fp, -(len < BUFSIZE ? len : BUFSIZE), SEEK_END);
if (i == -1 && errno != EFBIG) {
/* seek before start of file on my machine */
fclose(fp);
return NULL;
}
buf = (unsigned char *)malloc(BUFSIZE);
if (!buf) {
zip_err = ZERR_MEMORY;
return NULL;
}
clearerr(fp);
buflen = fread(buf, 1, BUFSIZE, fp);
if (ferror(fp)) {
/* read error */
free(buf);
fclose(fp);
return NULL;
}
best = -1;
cdir = NULL;
match = buf;
while ((match=memmem(match, buflen-(match-buf)-18, EOCD_MAGIC, 4))!=NULL) {
/* found match -- check, if good */
/* to avoid finding the same match all over again */
match++;
if ((cdirnew=_zip_readcdir(fp, buf, match-1, buflen)) == NULL)
continue;
if (cdir) {
if (best <= 0)
best = _zip_checkcons(fp, cdir);
a = _zip_checkcons(fp, cdirnew);
if (best < a) {
_zip_zf_free(cdir);
cdir = cdirnew;
best = a;
}
else
_zip_zf_free(cdirnew);
}
else {
cdir = cdirnew;
if (flags & ZIP_CHECKCONS)
best = _zip_checkcons(fp, cdir);
else
best = 0;
}
cdirnew = NULL;
}
if (best < 0) {
/* no consistent eocd found */
free(buf);
_zip_zf_free(cdir);
fclose(fp);
return NULL;
}
free(buf);
cdir->zn = strdup(fn);
if (!cdir->zn) {
zip_err = ZERR_MEMORY;
return NULL;
}
cdir->zp = fp;
return cdir;
}
/* _zip_readcdir:
tries to find a valid end-of-central-directory at the beginning of
buf, and then the corresponding central directory entries.
Returns a zipfile struct which contains the central directory
entries, or NULL if unsuccessful. */
static struct zf *
_zip_readcdir(FILE *fp, unsigned char *buf, unsigned char *eocd, int buflen)
{
struct zf *zf;
unsigned char *cdp;
int i, comlen, readp;
comlen = buf + buflen - eocd - EOCDLEN;
if (comlen < 0) {
/* not enough bytes left for comment */
return NULL;
}
/* check for end-of-central-dir magic */
if (memcmp(eocd, EOCD_MAGIC, 4) != 0)
return NULL;
if (memcmp(eocd+4, "\0\0\0\0", 4) != 0) {
zip_err = ZERR_MULTIDISK;
return NULL;
}
if ((zf=_zip_zf_new()) == NULL) {
zip_err = ZERR_MEMORY;
return NULL;
}
cdp = eocd + 8;
/* number of cdir-entries on this disk */
i = _zip_read2(&cdp);
/* number of cdir-entries */
zf->nentry = zf->nentry_alloc = _zip_read2(&cdp);
zf->cd_size = _zip_read4(&cdp);
zf->cd_offset = _zip_read4(&cdp);
zf->comlen = _zip_read2(&cdp);
zf->entry = NULL;
if ((zf->comlen != comlen) || (zf->nentry != i)) {
/* comment size wrong -- too few or too many left after central dir */
/* or number of cdir-entries on this disk != number of cdir-entries */
_zip_zf_free(zf);
return NULL;
}
zf->com = (unsigned char *)memdup(eocd+EOCDLEN, zf->comlen);
cdp = eocd;
if (zf->cd_size < eocd-buf) {
/* if buffer already read in, use it */
readp = 0;
cdp = eocd - zf->cd_size;
}
else {
/* go to start of cdir and read it entry by entry */
readp = 1;
clearerr(fp);
fseek(fp, -(zf->cd_size+zf->comlen+EOCDLEN), SEEK_END);
if (ferror(fp) || (ftell(fp) != zf->cd_offset)) {
/* seek error or offset of cdir wrong */
_zip_zf_free(zf);
return NULL;
}
}
zf->entry = (struct zf_entry *)malloc(sizeof(struct zf_entry)
*zf->nentry);
if (!zf->entry) {
zip_err = ZERR_MEMORY;
return NULL;
}
for (i=0; i<zf->nentry; i++) {
zf->entry[i].fn = NULL;
zf->entry[i].ef = NULL;
zf->entry[i].fcom = NULL;
zip_entry_init(zf, i);
}
for (i=0; i<zf->nentry; i++) {
if ((_zip_readcdentry(fp, zf->entry+i, &cdp, eocd-cdp, readp, 0)) < 0) {
/* i entries have already been filled, tell _zip_zf_free
how many to free */
_zip_zf_free(zf);
return NULL;
}
}
return zf;
}
/* _zip_readcdentry:
fills the zipfile entry zfe with data from the buffer *cdpp, not reading
more than 'left' bytes from it; if readp != 0, it also reads more data
from fp, if necessary. If localp != 0, it reads a local header instead
of a central directory entry. Returns 0 if successful, -1 if not,
advancing *cdpp for each byte read. */
int
_zip_readcdentry(FILE *fp, struct zf_entry *zfe, unsigned char **cdpp,
int left, int readp, int localp)
{
unsigned char buf[CDENTRYSIZE];
unsigned char *cur;
int size;
if (localp)
size = LENTRYSIZE;
else
size = CDENTRYSIZE;
if (readp) {
/* read entry from disk */
if ((fread(buf, 1, size, fp)<size))
return -1;
left = size;
cur = buf;
}
else {
cur = *cdpp;
if (left < size)
return -1;
}
if (localp) {
if (memcmp(cur, LOCAL_MAGIC, 4)!=0)
return -1;
}
else
if (memcmp(cur, CENTRAL_MAGIC, 4)!=0)
return -1;
cur += 4;
/* convert buffercontents to zf_entry */
if (!localp)
zfe->version_made = _zip_read2(&cur);
else
zfe->version_made = 0;
zfe->version_need = _zip_read2(&cur);
zfe->bitflags = _zip_read2(&cur);
zfe->comp_meth = _zip_read2(&cur);
zfe->lmtime = _zip_read2(&cur);
zfe->lmdate = _zip_read2(&cur);
zfe->crc = _zip_read4(&cur);
zfe->comp_size = _zip_read4(&cur);
zfe->uncomp_size = _zip_read4(&cur);
zfe->fnlen = _zip_read2(&cur);
zfe->eflen = _zip_read2(&cur);
if (!localp) {
zfe->fcomlen = _zip_read2(&cur);
zfe->disknrstart = _zip_read2(&cur);
zfe->intatt = _zip_read2(&cur);
zfe->extatt = _zip_read4(&cur);
zfe->local_offset = _zip_read4(&cur);
}
else {
zfe->fcomlen = zfe->disknrstart = zfe->intatt = 0;
zfe->extatt = zfe->local_offset = 0;
}
if (left < CDENTRYSIZE+zfe->fnlen+zfe->eflen+zfe->fcomlen) {
if (readp) {
if (zfe->fnlen)
zfe->fn = _zip_readfpstr(fp, zfe->fnlen, 1);
else {
zfe->fn = strdup("");
if (!zfe->fn) {
zip_err = ZERR_MEMORY;
return -1;
}
}
if (zfe->eflen)
zfe->ef = _zip_readfpstr(fp, zfe->eflen, 0);
/* XXX: really null-terminate comment? */
if (zfe->fcomlen)
zfe->fcom = _zip_readfpstr(fp, zfe->fcomlen, 1);
}
else {
/* can't get more bytes if not allowed to read */
return -1;
}
}
else {
if (zfe->fnlen)
zfe->fn = _zip_readstr(&cur, zfe->fnlen, 1);
if (zfe->eflen)
zfe->ef = _zip_readstr(&cur, zfe->eflen, 0);
if (zfe->fcomlen)
zfe->fcom = _zip_readstr(&cur, zfe->fcomlen, 1);
}
if (!readp)
*cdpp = cur;
/* XXX */
zfe->ch_data_fp = NULL;
zfe->ch_data_buf = NULL;
zfe->ch_data_offset = 0;
zfe->ch_data_len = 0;
return 0;
}
static int
_zip_read2(unsigned char **a)
{
int ret;
ret = (*a)[0]+((*a)[1]<<8);
*a += 2;
return ret;
}
static int
_zip_read4(unsigned char **a)
{
int ret;
ret = ((((((*a)[3]<<8)+(*a)[2])<<8)+(*a)[1])<<8)+(*a)[0];
*a += 4;
return ret;
}
static char *
_zip_readstr(unsigned char **buf, int len, int nulp)
{
char *r;
r = (char *)malloc(nulp?len+1:len);
if (!r) {
zip_err = ZERR_MEMORY;
return NULL;
}
memcpy(r, *buf, len);
*buf += len;
if (nulp)
r[len] = 0;
return r;
}
static char *
_zip_readfpstr(FILE *fp, int len, int nullp)
{
char *r;
r = (char *)malloc(nullp?len+1:len);
if (!r) {
zip_err = ZERR_MEMORY;
return NULL;
}
if (fread(r, 1, len, fp)<len) {
free(r);
return NULL;
}
if (nullp)
r[len] = 0;
return r;
}
/* _zip_checkcons:
Checks the consistency of the central directory by comparing central
directory entries with local headers and checking for plausible
file and header offsets. Returns -1 if not plausible, else the
difference between the lowest and the highest fileposition reached */
static int
_zip_checkcons(FILE *fp, struct zf *zf)
{
int min, max, i, j;
struct zf_entry temp;
unsigned char *buf;
buf = NULL;
if (zf->nentry) {
max = zf->entry[0].local_offset;
min = zf->entry[0].local_offset;
}
for (i=0; i<zf->nentry; i++) {
if (zf->entry[i].local_offset < min)
min = zf->entry[i].local_offset;
if (min < 0)
return -1;
j = zf->entry[i].local_offset + zf->entry[i].comp_size
+ zf->entry[i].fnlen + zf->entry[i].eflen
+ zf->entry[i].fcomlen + LENTRYSIZE;
if (j > max)
max = j;
if (max > zf->cd_offset)
return -1;
if (fseek(fp, zf->entry[i].local_offset, SEEK_SET) != 0) {
zip_err = ZERR_SEEK;
return -1;
}
_zip_readcdentry(fp, &temp, &buf, 0, 1, 1);
if (_zip_headercomp(zf->entry+i, 0, &temp, 1) != 0)
return -1;
}
return max - min;
}
/* _zip_headercomp:
compares two headers h1 and h2; if they are local headers, set
local1p or local2p respectively to 1, else 0. Return 0 if they
are identical, -1 if not. */
static int
_zip_headercomp(struct zf_entry *h1, int local1p, struct zf_entry *h2,
int local2p)
{
if ((h1->version_need != h2->version_need)
|| (h1->bitflags != h2->bitflags)
|| (h1->comp_meth != h2->comp_meth)
|| (h1->lmtime != h2->lmtime)
|| (h1->lmdate != h2->lmdate)
|| (h1->fnlen != h2->fnlen)
|| (h1->crc != h2->crc)
|| (h1->comp_size != h2->comp_size)
|| (h1->uncomp_size != h2->uncomp_size)
|| (h1->fnlen && memcmp(h1->fn, h2->fn, h1->fnlen)))
return -1;
/* if they are different type, nothing more to check */
if (local1p != local2p)
return 0;
if ((h1->version_made != h2->version_made)
|| (h1->disknrstart != h2->disknrstart)
|| (h1->intatt != h2->intatt)
|| (h1->extatt != h2->extatt)
|| (h1->local_offset != h2->local_offset)
|| (h1->eflen != h2->eflen)
|| (h1->eflen && memcmp(h1->fn, h2->fn, h1->fnlen))
|| (h1->fcomlen != h2->fcomlen)
|| (h1->fcomlen && memcmp(h1->fcom, h2->fcom, h1->fcomlen))) {
return -1;
}
return 0;
}