ref: 649e276d63d7a15613b96eb32d89f91dba4b46a4
dir: /zipio.c/
/* * zipio.c - stdio emulation library for reading zip files * * Version 1.1.1 */ /* * Copyright (c) 1995, Edward B. Hamrick * * Permission to use, copy, modify, distribute, and sell this software and * its documentation for any purpose is hereby granted without fee, provided * that * * (i) the above copyright notice and the text in this "C" comment block * appear in all copies of the software and related documentation, and * * (ii) any modifications to this source file must be sent, via e-mail * to the copyright owner (currently hamrick@primenet.com) within * 30 days of such modification. * * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. * * IN NO EVENT SHALL EDWARD B. HAMRICK BE LIABLE FOR ANY SPECIAL, INCIDENTAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Changes from 1.1 to 1.1.1: * Changed "z*" functions to "Z*" to avoid namespace pollution. * Added "zungetc" macro. * Added definitions of SEEK_SET, SEEK_CUR, SEEK_END for the Posixly challenged * John Cowan <cowan@ccil.org> */ /* * Refer to zipio.h for a description of this package. */ /* * The .zip file header is described below. It consists of * 30 fixed bytes, followed by two variable length fields * whose length is contained in the first 30 bytes. After this * header, the data is stored (in deflate format if the compression * method is 8). * * The crc-32 field is the crc on the uncompressed data. * * .zip file header: * * local file header signature 4 bytes (0x04034b50) * version needed to extract 2 bytes * general purpose bit flag 2 bytes * compression method 2 bytes * last mod file time 2 bytes * last mod file date 2 bytes * crc-32 4 bytes * compressed size 4 bytes * uncompressed size 4 bytes * filename length 2 bytes * extra field length 2 bytes * * filename (variable size) * extra field (variable size) * * These fields are described in more detail in appnote.txt * in the pkzip 1.93 distribution. */ #include <u.h> #include <libc.h> #include <stdio.h> #ifdef MEMCPY #include <mem.h> #endif #include "zipio.h" #include "inflate.h" #include "crc.h" /* * Macros for constants */ #ifndef NULL #define NULL ((void *) 0) #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #ifndef ZIPSIGNATURE #define ZIPSIGNATURE 0x04034b50L #endif #ifndef SEEK_SET #define SEEK_SET 0 #endif #ifndef SEEK_CUR #define SEEK_CUR 1 #endif #ifndef SEEK_END #define SEEK_END 2 #endif /* * Buffer size macros * * The following constants are optimized for large-model * (but not flat model) Windows with virtual memory. It * will work fine on unix and flat model Windows as well. * * The constant BUFFERTHRESHOLD determines when memory * buffering changes to file buffering. * * Assumptions: * * 1) INPBUFSIZE + OUTBUFSIZE + sizeof(void *) * PTRBUFSIZE + delta < 64K * * 2) OUTBUFSIZE = 32K * N (related to inflate's 32K window size) * * 2) Max in-memory file size is OUTBUFSIZE * PTRBUFSIZE * which is 64 MBytes by default (32K * 2K). * */ #ifndef BUFFERTHRESHOLD #define BUFFERTHRESHOLD (256 * 1024L) #endif #ifndef INPBUFSIZE #define INPBUFSIZE ( 8 * 1024 ) #endif #ifndef PTRBUFSIZE #define PTRBUFSIZE ( 2 * 1024 ) #endif #ifndef OUTBUFSIZE #define OUTBUFSIZE ((unsigned int) ( 32 * 1024L)) #endif #define MAXFILESIZE (OUTBUFSIZE * (long) PTRBUFSIZE) /* * Macro for short-hand reference to ZipioState (from ZFILE *) */ #define ZS ((struct ZipioState *) stream) /* * Macro to manipulate Zgetc() cache */ #define CACHEINIT \ zs->ptr = NULL; \ zs->len = 0; #define CACHEUPDATE \ if (ZS->ptr) \ { \ ZS->fileposition &= ~((long) (OUTBUFSIZE-1)); \ ZS->fileposition += ZS->ptr - ZS->getbuf; \ ZS->ptr = NULL; \ } \ ZS->len = 0; /* * Macros for run-time type identification */ #ifndef RUNTIMEENABLE #define ZIPIOSTATETYPE 0x0110f00fL #define RUNTIMEINIT \ zs->runtimetypeid1 = ZIPIOSTATETYPE; \ zs->runtimetypeid2 = ZIPIOSTATETYPE; #define RUNTIMECHECK \ if (!ZS || (ZS->runtimetypeid1 != ZIPIOSTATETYPE) \ || (ZS->runtimetypeid2 != ZIPIOSTATETYPE)) return -1; #else #define RUNTIMEINIT #define RUNTIMECHECK #endif /* * Macros for converting bytes to unsigned integers */ #define GETUINT4(ptr, i4) \ { \ i4 = (((unsigned long) *(((unsigned char *) (ptr)) + 0)) ) | \ (((unsigned long) *(((unsigned char *) (ptr)) + 1)) << 8) | \ (((unsigned long) *(((unsigned char *) (ptr)) + 2)) << 16) | \ (((unsigned long) *(((unsigned char *) (ptr)) + 3)) << 24) ; \ } #define GETUINT2(ptr, i2) \ { \ i2 = (((unsigned int) *(((unsigned char *) (ptr)) + 0)) ) | \ (((unsigned int) *(((unsigned char *) (ptr)) + 1)) << 8) ; \ } /* Structure to hold state for decoding zip files */ struct ZipioState { /* Fields overlaid with ZFILE structure */ int len; /* length of Zgetc cache */ unsigned char *ptr; /* pointer to Zgetc cache */ /* Fields invisible to users of ZFILE structure */ unsigned long runtimetypeid1; /* to detect run-time errors */ int errorencountered; /* error encountered flag */ /* Buffering state */ unsigned char inpbuf[INPBUFSIZE]; /* inp buffer from zip file */ unsigned char *ptrbuf[PTRBUFSIZE]; /* pointers to in-memory bufs */ unsigned char getbuf[OUTBUFSIZE]; /* buffer for use by Zgetc */ long getoff; /* starting offset of getbuf */ FILE *tmpfil; /* file ptr to temp file */ /* Amount of input data inflated */ unsigned long inpinf; unsigned long outinf; /* Zip file header */ unsigned long sign; /* local file header signature (0x04034b50) */ unsigned int vers; /* version needed to extract 2 bytes */ unsigned int flag; /* general purpose bit flag 2 bytes */ unsigned int comp; /* compression method 2 bytes */ unsigned int mtim; /* last mod file time 2 bytes */ unsigned int mdat; /* last mod file date 2 bytes */ unsigned long crc3; /* crc-32 4 bytes */ unsigned long csiz; /* compressed size 4 bytes */ unsigned long usiz; /* uncompressed size 4 bytes */ unsigned int flen; /* filename length 2 bytes */ unsigned int elen; /* extra field length 2 bytes */ /* Application state */ FILE *OpenFile; /* currently open file */ void *inflatestate; /* current state for inflate */ unsigned long fileposition; /* current file position */ unsigned long filecrc; /* current crc */ unsigned long runtimetypeid2; /* to detect run-time errors */ }; /* * Utility routines to handle uncompressed file buffers */ /* Initialize buffering */ static void BufferInitialize( struct ZipioState *zs, int doinflate ) { zs->getoff = -1; zs->tmpfil = NULL; /* * If not inflating, use the input file */ if (!doinflate) { zs->tmpfil = zs->OpenFile; /* Get the uncompressed file size */ fseek(zs->tmpfil, 0, SEEK_END); zs->usiz = ftell(zs->tmpfil); zs->outinf = zs->usiz; /* Start at the beginning */ fseek(zs->tmpfil, 0, SEEK_SET); } /* If there's no file open, see if it's big enough for temp file */ if (!zs->tmpfil) { if (zs->usiz >= BUFFERTHRESHOLD) zs->tmpfil = tmpfile(); } /* If there's no file open, then use memory buffering */ if (!zs->tmpfil) { int i; for (i=0; i<PTRBUFSIZE; i++) zs->ptrbuf[i] = NULL; } } /* pump data till length bytes of file are inflated or error encountered */ static int BufferPump(struct ZipioState *zs, long length) { ulong inplen, ret; /* Check to see if the length is valid */ if (length > zs->usiz) return TRUE; /* Loop till enough data is pumped */ while (!zs->errorencountered && (zs->outinf < length)) { /* Compute how much data to read */ if ((zs->csiz - zs->inpinf) < INPBUFSIZE) inplen = (ulong) (zs->csiz - zs->inpinf); else inplen = INPBUFSIZE; if (inplen <= 0) return TRUE; /* Read some data from the file */ ret = fread(zs->inpbuf, 1, inplen, zs->OpenFile); if (ret != inplen) return TRUE; /* Update how much data has been read from the file */ zs->inpinf += inplen; /* Pump this data into the decompressor */ if (InflatePutBuffer(zs->inflatestate, zs->inpbuf, inplen)) return TRUE; } return FALSE; } /* Read from the buffer */ static int BufferRead( struct ZipioState *zs, long offset, unsigned char *buffer, long length ) { /* * Make sure enough bytes have been inflated * Note that the correction for reading past EOF has to * be done before calling this routine */ if (BufferPump(zs, offset+length)) return TRUE; /* If using file buffering, just get the data from the file */ if (zs->tmpfil) { if (fseek(zs->tmpfil, offset, SEEK_SET)) return TRUE; if (fread(buffer, 1, length, zs->tmpfil) != length) return TRUE; } /* If no temp file, use memory buffering */ else { unsigned int i; unsigned int off, len; unsigned char *ptr; long tmpoff; unsigned char *tmpbuf; long tmplen; /* Save copies of offset, buffer and length for the loop */ tmpoff = offset; tmpbuf = buffer; tmplen = length; /* Validate the transfer */ if (tmpoff+tmplen > MAXFILESIZE) return TRUE; /* Loop till done */ while (tmplen) { /* Get a pointer to the next block */ i = (unsigned int) (tmpoff / OUTBUFSIZE); ptr = zs->ptrbuf[i]; if (!ptr) return TRUE; /* Get the offset,length for this block */ off = (unsigned int) (tmpoff & (OUTBUFSIZE-1)); len = OUTBUFSIZE - off; if (len > tmplen) len = (unsigned int) tmplen; /* Get the starting pointer for the transfer */ ptr += off; /* Copy the data for this block */ #ifdef MEMCPY memcpy(tmpbuf, ptr, len); #else for (i=0; i<len; i++) tmpbuf[i] = ptr[i]; #endif /* Update the offset, buffer, and length */ tmpoff += len; tmpbuf += len; tmplen -= len; } } /* return success */ return FALSE; } /* Append to the buffer */ static int BufferAppend( struct ZipioState *zs, unsigned char *buffer, long length ) { /* If using file buffering, just append the data from the file */ if (zs->tmpfil) { if (fseek(zs->tmpfil, zs->outinf, SEEK_SET)) return TRUE; if (fwrite(buffer, 1, length, zs->tmpfil) != length) return TRUE; } /* If no temp file, use memory buffering */ else { unsigned int i; unsigned int off, len; unsigned char *ptr; long tmpoff; unsigned char *tmpbuf; long tmplen; /* Save copies of outinf, buffer and length for the loop */ tmpoff = zs->outinf; tmpbuf = buffer; tmplen = length; /* Validate the transfer */ if (tmpoff+tmplen > MAXFILESIZE) return TRUE; /* Loop till done */ while (tmplen) { /* Get a pointer to the next block */ i = (unsigned int) (tmpoff / OUTBUFSIZE); ptr = zs->ptrbuf[i]; if (!ptr) { ptr = (unsigned char *) malloc(OUTBUFSIZE); if (!ptr) return TRUE; zs->ptrbuf[i] = ptr; } /* Get the offset,length for this block */ off = (unsigned int) (tmpoff & (OUTBUFSIZE-1)); len = OUTBUFSIZE - off; if (len > tmplen) len = (unsigned int) tmplen; /* Get the starting pointer for the transfer */ ptr += off; /* Copy the data for this block */ #ifdef MEMCPY memcpy(ptr, tmpbuf, len); #else for (i=0; i<len; i++) ptr[i] = tmpbuf[i]; #endif /* Update the offset, buffer, and length */ tmpoff += len; tmpbuf += len; tmplen -= len; } } /* Update the output buffer length */ zs->outinf += length; /* return success */ return FALSE; } /* Terminate buffering */ static void BufferTerminate( struct ZipioState *zs ) { /* If reading directly from the uncompressed file, just mark with NULL */ if (zs->tmpfil == zs->OpenFile) { zs->tmpfil = NULL; } /* If using the a temporary file, close it */ else if (zs->tmpfil) { fclose(zs->tmpfil); zs->tmpfil = NULL; } /* If doing memory buffering, free the buffers */ else { int i; for (i=0; i<PTRBUFSIZE; i++) if (zs->ptrbuf[i]) free(zs->ptrbuf[i]); } } /* * callout routines for InflateInitialize */ static int inflate_putbuffer( /* returns 0 on success */ void *stream, /* opaque ptr from Initialize */ unsigned char *buffer, /* buffer to put */ long length /* length of buffer */ ) { RUNTIMECHECK; /* If the write will go past the end of file, return an error */ if (ZS->outinf + length > ZS->usiz) return TRUE; /* Update the CRC */ ZS->filecrc = CrcUpdate(ZS->filecrc, buffer, length); /* Append to the buffer */ if (BufferAppend(ZS, buffer, length)) return TRUE; /* Return success */ return FALSE; } static void *inflate_malloc(long length) { return malloc((ulong) length); } static void inflate_free(void *buffer) { free(buffer); } ZFILE *Zopen(const char *path, const char *mode) { struct ZipioState *zs; long inplen; /* Allocate the ZipioState memory area */ zs = (struct ZipioState *) malloc(sizeof(struct ZipioState)); if (!zs) return NULL; /* Set up the initial values of the inflate state */ CACHEINIT; RUNTIMEINIT; zs->errorencountered = FALSE; zs->inpinf = 0; zs->outinf = 0; zs->fileposition = 0; zs->filecrc = 0xffffffffL; /* Open the real file */ zs->OpenFile = fopen(path, mode); if (!zs->OpenFile) { free(zs); return NULL; } /* Read the first input buffer */ if ((inplen = (long) fread(zs->inpbuf, 1, INPBUFSIZE, zs->OpenFile)) >= 30) { GETUINT4(zs->inpbuf+ 0, zs->sign); GETUINT2(zs->inpbuf+ 4, zs->vers); GETUINT2(zs->inpbuf+ 6, zs->flag); GETUINT2(zs->inpbuf+ 8, zs->comp); GETUINT2(zs->inpbuf+10, zs->mtim); GETUINT2(zs->inpbuf+12, zs->mdat); GETUINT4(zs->inpbuf+14, zs->crc3); GETUINT4(zs->inpbuf+18, zs->csiz); GETUINT4(zs->inpbuf+22, zs->usiz); GETUINT2(zs->inpbuf+26, zs->flen); GETUINT2(zs->inpbuf+28, zs->elen); #ifdef PRINTZIPHEADER fprintf(stderr, "local file header signature hex %8lx\n", zs->sign); fprintf(stderr, "version needed to extract %8d\n" , zs->vers); fprintf(stderr, "general purpose bit flag hex %8x\n" , zs->flag); fprintf(stderr, "compression method %8d\n" , zs->comp); fprintf(stderr, "last mod file time %8d\n" , zs->mtim); fprintf(stderr, "last mod file date %8d\n" , zs->mdat); fprintf(stderr, "crc-32 hex %8lx\n", zs->crc3); fprintf(stderr, "compressed size %8ld\n", zs->csiz); fprintf(stderr, "uncompressed size %8ld\n", zs->usiz); fprintf(stderr, "filename length %8d\n" , zs->flen); fprintf(stderr, "extra field length %8d\n" , zs->elen); #endif } else { zs->sign = 0; } /* * If the file isn't a zip file, set up to read it normally */ if ((zs->sign != ZIPSIGNATURE) || (zs->flag & 1) || (zs->comp != 8) || (inplen <= 30 + zs->flen + zs->elen) ) { /* Initialize buffering */ BufferInitialize(zs, FALSE); zs->inflatestate = NULL; } else { /* Initialize buffering */ BufferInitialize(zs, TRUE); zs->inflatestate = InflateInitialize( (void *) zs, inflate_putbuffer, inflate_malloc, inflate_free ); if (InflatePutBuffer(zs->inflatestate, zs->inpbuf+30+zs->flen+zs->elen, inplen-30-zs->flen-zs->elen ) ) zs->errorencountered = TRUE; zs->inpinf += inplen-30-zs->flen-zs->elen; } /* Return this state info to the caller */ return (ZFILE *) zs; } int _Zgetc(ZFILE *stream) { long offset, length; int off; RUNTIMECHECK; if (ZS->errorencountered) return -1; CACHEUPDATE; /* If already at EOF, return */ if (ZS->fileposition >= ZS->usiz) return -1; /* If data isn't in current outbuf, get it */ offset = ZS->fileposition & ~((long) (OUTBUFSIZE-1)); if (ZS->getoff != offset) { length = ZS->usiz - offset; if (length > OUTBUFSIZE) length = OUTBUFSIZE; if (BufferRead(ZS, offset, ZS->getbuf, length)) return -1; ZS->getoff = offset; } /* Set up the cache */ off = (int) (ZS->fileposition & (OUTBUFSIZE-1)); ZS->len = (int) (length - off); ZS->ptr = ZS->getbuf + off; /* Return the character */ ZS->len--; return *(ZS->ptr++); } long Zread(void *ptr, long size, long n, ZFILE *stream) { long length; RUNTIMECHECK; if (ZS->errorencountered) return 0; CACHEUPDATE; /* Compute the length requested */ length = size * (long) n; /* Adjust the length to account for premature EOF */ if (ZS->fileposition+length > ZS->usiz) length = ZS->usiz - ZS->fileposition; /* If the length is zero, then just return an EOF error */ if (length <= 0) return 0; /* Make the length a multiple of size */ length /= size; length *= size; /* If the length is zero, then just return an EOF error */ if (length <= 0) return 0; /* Read from the buffer */ if (BufferRead(ZS, ZS->fileposition, (unsigned char *) ptr, length)) return 0; /* Update the file position */ ZS->fileposition += length; /* Return the number of items transferred */ return (length / size); } int Zseek(ZFILE *stream, long offset, int whence) { long newoffset; RUNTIMECHECK; if (ZS->errorencountered) return -1; CACHEUPDATE; if (whence == SEEK_SET) { newoffset = offset; } else if (whence == SEEK_CUR) { newoffset = ZS->fileposition + offset; } else if (whence == SEEK_END) { newoffset = ZS->fileposition + ZS->usiz; } else { return -1; } if ((newoffset < 0) || (newoffset > ZS->usiz)) return -1; ZS->fileposition = newoffset; return 0; } long Ztell(ZFILE *stream) { RUNTIMECHECK; if (ZS->errorencountered) return -1; CACHEUPDATE; return ZS->fileposition; } int Zclose(ZFILE *stream) { int ret; RUNTIMECHECK; CACHEUPDATE; /* terminate the inflate routines, and check for errors */ if (ZS->inflatestate) { if (InflateTerminate(ZS->inflatestate)) ZS->errorencountered = TRUE; /* Check that the CRC is OK */ if (ZS->filecrc != (ZS->crc3 ^ 0xffffffffL)) ZS->errorencountered = TRUE; } /* save the final error status */ ret = ZS->errorencountered; /* terminate the buffering */ BufferTerminate(ZS); /* free the ZipioState structure */ free(ZS); /* return the final error status */ return ret; }