Milán Major

netscape-revival

1 branch
Code

sun-java/runtime/zip.c

/*
 * @(#)zip.c	1.5 95/10/22 David Connelly
 *
 * Copyright (c) 1995 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies. Please refer to the file "copyright.html"
 * for further important copyright and licensing information.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 */

/*
 * Routines for reading "zip" file format. Currently, there is no support
 * for compressed or encrypted zip file members.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "sys_api.h"
#include "io_md.h"
#include "bool.h"
#include "zip.h"

#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif

#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif

/*
 * Header signatures
 */
#define SIGSIZ 4
#define LOCSIG "PK\003\004"
#define CENSIG "PK\001\002"
#define ENDSIG "PK\005\006"

/*
 * Header sizes including signatures
 */
#define LOCHDRSIZ 30
#define CENHDRSIZ 46
#define ENDHDRSIZ 22

/*
 * Header field access macros
 */
#define CH(b, n) ((unsigned short)(((unsigned char *)(b))[n]))
#define SH(b, n) ((unsigned long)(CH(b, n) | (CH(b, n+1) << 8)))
#define LG(b, n) (SH(b, n) | (SH(b, n+2) << 16))

/*
 * Macros for getting local file header (LOC) fields
 */
#define LOCFLG(b) SH(b, 6)	    /* encrypt flags */
#define LOCHOW(b) SH(b, 8)	    /* compression method */
#define LOCCRC(b) LG(b, 14)	    /* uncompressed file crc-32 value */
#define LOCSIZ(b) LG(b, 18)	    /* compressed size */
#define LOCLEN(b) LG(b, 22)	    /* uncompressed size */
#define LOCNAM(b) SH(b, 26)	    /* filename size */
#define LOCEXT(b) SH(b, 28)	    /* extra field size */

/*
 * Macros for getting central directory header (CEN) fields
 */
#define CENTIM(b) LG(b, 12)	    /* file modification time (DOS format) */
#define CENSIZ(b) LG(b, 20)	    /* compressed size */
#define CENLEN(b) LG(b, 24)	    /* uncompressed size */
#define CENNAM(b) SH(b, 28)	    /* length of filename */
#define CENEXT(b) SH(b, 30)	    /* length of extra field */
#define CENCOM(b) SH(b, 32)	    /* file comment length */
#define CENOFF(b) LG(b, 42)	    /* offset of local header */

/*
 * Macros for getting end of central directory header (END) fields
 */
#define ENDSUB(b) SH(b, 8)	    /* number of entries on this disk */
#define ENDTOT(b) SH(b, 10)	    /* total number of entries */
#define ENDSIZ(b) LG(b, 12)	    /* central directory size */
#define ENDOFF(b) LG(b, 16)	    /* central directory offset */
#define ENDCOM(b) SH(b, 20)	    /* size of zip file comment */

static void
errmsg(const char *msg)
{
#ifdef DEBUG
    sysWrite(2, msg, strlen(msg));
#endif
}

static void
ziperr(zip_t *zip, const char *msg)
{
#ifdef DEBUG
    errmsg("Zip Error: ");
    errmsg(zip->fn);
    errmsg(": ");
    errmsg(msg);
    errmsg("\n");
#endif
}

/*
 * Read exactly 'len' bytes of data into 'buf'. Return TRUE if all bytes
 * could be read, otherwise return FALSE.
 */
static bool_t
readFully(int fd, void *buf, int32_t len)
{
    char *bp = buf;

    while (len > 0) {
	int32_t rqst;
	int32_t n;
	
	rqst = len;
#if defined(XP_PC) && !defined(_WIN32)
    if (rqst > 32767)
    	rqst = 32767;
#endif
	n = sysRead(fd, bp, rqst);
	if (n <= 0) {
	    return FALSE;
	}
	bp += n;
	len -= n;
    }
    return TRUE;
}

#define INBUFSIZ 64

/*
 * Find end of central directory (END) header in zip file and set the file
 * pointer to start of header. Return FALSE if the END header could not be
 * found, otherwise return TRUE.
 */
static bool_t
findEnd(zip_t *zip)
{
    char buf[INBUFSIZ+SIGSIZ], *bp;
    int32_t len, off, mark;

    /* Need to search backwards from end of file */
    if ((len = lseek(zip->fd, 0, SEEK_END)) == -1)
	return FALSE;
    
    /*
     * Set limit on how far back we need to search. The END header must be
     * located within the last 64K bytes of the file.
     */
    mark = max(0, len - 0xffff);
    /*
     * Search backwards INBUFSIZ bytes at a time from end of file stopping
     * when the END header signature has been found. Since the signature
     * may straddle a buffer boundary, we need to stash the first SIGSIZ-1
     * bytes of the previous record at the tail of the current record so
     * that the search can overlap.
     */
    memset(buf, 0, SIGSIZ);
    for (off = len; off > mark; ) {
	long n = min(off - mark, INBUFSIZ);
	memcpy(buf + n, buf, SIGSIZ);
	if (lseek(zip->fd, off -= n, SEEK_SET) == -1) {
	    return FALSE;
	}
	if (!readFully(zip->fd, buf, n)) {
	    ziperr(zip, "Fatal read error while searching for END record");
	    return FALSE;
	}
	for (bp = buf + n - 1; bp >= buf; --bp) {
	    if (strncmp(bp, ENDSIG, SIGSIZ) == 0) {
		char endbuf[ENDHDRSIZ];
		long endoff = off + (bp - buf);
		if (len - endoff < ENDHDRSIZ) {
		    continue;
		}
		if (lseek(zip->fd, endoff, SEEK_SET) == -1) {
		}
		if (!readFully(zip->fd, endbuf, ENDHDRSIZ)) {
		    ziperr(zip, "Read error while searching for END record");
		    return FALSE;
		}
		if (endoff + ENDHDRSIZ + ENDCOM(endbuf) != len) {
		    continue;
		}
		if (lseek(zip->fd, endoff, SEEK_SET) == -1) {
		    return FALSE;
		}
		zip->endoff = endoff;
		return TRUE;
	    }
	}
    }
    return FALSE;
}

/*
 * Convert DOS time to UNIX time
 */
static time_t
unixtime(int dostime)
{
    struct tm tm;

    tm.tm_sec = (dostime << 1) & 0x3e;
    tm.tm_min = (dostime >> 5) & 0x3f;
    tm.tm_hour = (dostime >> 11) & 0x1f;
    tm.tm_mday = (dostime >> 16) & 0x1f;
    tm.tm_mon = ((dostime >> 21) & 0xf) - 1;
    tm.tm_year = ((dostime >> 25) & 0x7f) + 1980;
    return mktime(&tm);
}

static int
direlcmp(const void *d1, const void *d2)
{

    return strcmp(((direl_t *)d1)->fn, ((direl_t *)d2)->fn);
}

/*
 * Initialize zip file reader, read in central directory and construct the
 * lookup table for locating zip file members.
 */
static bool_t
initReader(zip_t *zip)
{
    char endbuf[ENDHDRSIZ];
    char *cenbuf, *cp;
    uint32_t i, cenlen;

    /* Seek to END header */
    if (!findEnd(zip)) {
	ziperr(zip, "Unable to locate end-of-central-directory record");
	return FALSE;
    }
    /* Read END header */
    if (!readFully(zip->fd, endbuf, ENDHDRSIZ)) {
	ziperr(zip, "Fatal error while reading END header");
	return FALSE;
    }
    /* Verify END signature */
    if (strncmp(endbuf, ENDSIG, SIGSIZ) != 0) {
	ziperr(zip, "Invalid END signature");
	return FALSE;
    }
    /* Get offset of central directory */
    zip->cenoff = ENDOFF(endbuf);
    /* Get length of central directory */
    cenlen = ENDSIZ(endbuf);
    /* Check offset and length */
    if (zip->cenoff + cenlen != zip->endoff) {
	ziperr(zip, "Invalid end-of-central-directory header");
	return FALSE;
    }
    /* Get total number of central directory entries */
    zip->nel = ENDTOT(endbuf);
    /* Check entry count */
    if (zip->nel * CENHDRSIZ > cenlen) {
	ziperr(zip, "Invalid end-of-central-directory header");
	return FALSE;
    }
    /* Verify that zip file contains only one drive */
    if (ENDSUB(endbuf) != zip->nel) {
	ziperr(zip, "Cannot contain more than one drive");
	return FALSE;
    }
    /* Seek to first CEN header */
    if (lseek(zip->fd, zip->cenoff, SEEK_SET) == -1) {
	return FALSE;
    }
    /* Allocate file lookup table */
    if ((zip->dir = sysMalloc((size_t)(zip->nel * sizeof(direl_t)))) == 0) {
	ziperr(zip, "Out of memory allocating lookup table");
	return FALSE;
    }
    /* Allocate temporary buffer for contents of central directory */
    if ((cenbuf = sysMalloc((size_t)cenlen)) == 0) {
	ziperr(zip, "Out of memory allocating central directory buffer");
	return FALSE;
    }
    /* Read central directory */
    if (!readFully(zip->fd, cenbuf, cenlen)) {
	ziperr(zip, "Fatal error while reading central directory");
	sysFree(cenbuf);
	return FALSE;
    }
    /* Scan each header in central directory */
    for (i = 0, cp = cenbuf; i < zip->nel; i++) {
	direl_t *dp = &zip->dir[i];
	uint32_t n;
	/* Verify signature of CEN header */
	if (strncmp(cp, CENSIG, SIGSIZ) != 0) {
	    ziperr(zip, "Invalid central directory header signature");
	    sysFree(cenbuf);
	    return FALSE;
	}
	/* Get file name */
	n = CENNAM(cp);
	if ((dp->fn = sysMalloc((size_t)n + 1)) == 0) {
	    ziperr(zip, "Out of memory reading CEN header file name");
	    sysFree(cenbuf);
	    return FALSE;
	}
	strncpy(dp->fn, cp + CENHDRSIZ, (size_t) n);
	dp->fn[n] = '\0';
	/* Get offset of LOC header */
	dp->off = CENOFF(cp);
	/* Get uncompressed file size */
	dp->len = CENLEN(cp);
	/* Check offset and size */
	if (dp->off + CENSIZ(cp) > zip->cenoff) {
	    ziperr(zip, "Invalid CEN header");
	    return FALSE;
	}
	/* Get file modification time */
	dp->mod = (int) CENTIM(cp);
	/* Skip to next CEN header */
	cp += CENHDRSIZ + CENNAM(cp) + CENEXT(cp) + CENCOM(cp);
	if (cp > cenbuf + cenlen) {
	    ziperr(zip, "Invalid CEN header");
	    return FALSE;
	}
    }
    /* Free temporary buffer */
    sysFree(cenbuf);
    /* Sort directory elements by name */
    qsort(zip->dir, (size_t) zip->nel, sizeof(direl_t), direlcmp);
    return TRUE;
}

/*
 * Open zip_t file and initialize file lookup table.
 */
zip_t *
zip_open(const char *fn)
{
    int fd = sysOpen(fn, O_RDONLY, 0);
    zip_t *zip;

    if (fd == -1) {
	return 0;
    }
    if ((zip = sysMalloc(sizeof(zip_t))) == 0) {
	errmsg("Out of memory");
	return 0;
    }
    if ((zip->fn = strdup(fn)) == 0) {
	errmsg("Out of memory");
	return 0;
    }
    zip->fd = fd;
    if (!initReader(zip)) {
	sysFree(zip->fn);
	sysFree(zip);
	sysClose(fd);
	return 0;
    }
    return zip;
}

/*
 * Close zip file and free lookup table
 */
void
zip_close(zip_t *zip)
{
    uint32_t i;

    sysFree(zip->fn);
    close(zip->fd);
    for (i = 0; i < zip->nel; i++) {
	sysFree(zip->dir[i].fn);
    }
    sysFree(zip->dir);
    sysFree(zip);
}

static direl_t *
lookup(zip_t *zip, const char *fn)
{
    direl_t key;
    key.fn = (char *)fn;
    return bsearch(&key, zip->dir, (size_t) zip->nel, sizeof(direl_t), direlcmp);
}

/*
 * Return file status for zip_t file member
 */
bool_t
zip_stat(zip_t *zip, const char *fn, struct stat *sbuf)
{
    direl_t *dp = lookup(zip, fn);

    if (dp == 0) {
	return FALSE;
    }
    memset(sbuf, 0, sizeof(struct stat));
    sbuf->st_mode = 444;
    sbuf->st_size = dp->len;
    sbuf->st_mtime = unixtime(dp->mod);
    sbuf->st_atime = sbuf->st_mtime;
    sbuf->st_ctime = sbuf->st_mtime;
    return TRUE;
}

/*
 * Read contents of zip file member into buffer 'buf'. If the size of the
 * data should exceed 'len' then nothing is read and FALSE is returned.
 */
bool_t
zip_get(zip_t *zip, const char *fn, void *buf, int32_t len)
{
    char locbuf[LOCHDRSIZ];
    long off;
    direl_t *dp = lookup(zip, fn);

    /* Look up file member */
    if (dp == 0) {
	return FALSE;
    }
    /* Check buffer length */
    if (len != (int32_t) dp->len) {
	return FALSE;
    }
    /* Seek to beginning of LOC header */
    if (lseek(zip->fd, dp->off, SEEK_SET) == -1) {
	return FALSE;
    }
    /* Read LOC header */
    if (!readFully(zip->fd, locbuf, LOCHDRSIZ)) {
	ziperr(zip, "Fatal error while reading LOC header");
	return FALSE;
    }
    /* Verify signature */
    if (strncmp(locbuf, LOCSIG, SIGSIZ) != 0) {
	ziperr(zip, "Invalid LOC header signature");
	return FALSE;
    }
    /* Make sure file is not compressed */
    if (LOCHOW(locbuf) != 0) {
	ziperr(zip, "Member is compressed");
	return FALSE;
    }
    /* Make sure file is not encrypted */
    if ((LOCFLG(locbuf) & 1) == 1) {
	ziperr(zip, "Member is encrypted");
	return FALSE;
    }
    /* Get offset of file data */
    off = dp->off + LOCHDRSIZ + LOCNAM(locbuf) + LOCEXT(locbuf);
    if (off + dp->len > zip->cenoff) {
	ziperr(zip, "Invalid LOC header");
	return FALSE;
    }
    /* Seek to file data */
    if (lseek(zip->fd, off, SEEK_SET) == -1) {
	return FALSE;
    }
    /* Read file data */
    if (!readFully(zip->fd, buf, dp->len)) {
	ziperr(zip, "Fatal error while reading LOC data");
	return FALSE;
    }
    return TRUE;
}