/* The code here is based on code by Mark Adler et al. which is Copyright (c) 1990-2005 Info-ZIP. Specifically, it derives from zip version 2.31. Modifications are by Allin Cottrell, March, 2006. Please see the included file "LICENSE" which contains the Info-ZIP license information. */ #include #include #include #include #include #include "zunz_private.h" #include "zlib.h" /* Read a new block from the current input file; update the crc and input file size; check for over-sized files. */ static guint32 file_read_chunk (FILE *fin, guchar *buf, unsigned size, guint32 *crc, guint32 *isize, int *err) { guint32 len; len = fread(buf, 1, size, fin); if (len == 0) { return len; } *crc = crc32(*crc, buf, len); *isize += len; if ((*isize & (guint32) 0xffffffffL) < len) { ziperr(ZE_BIG, "file exceeds Zip's 4GB uncompressed size limit"); *err = ZE_BIG; } return len; } static int zlib_check_version (void) { int err = 0; if (zlib_version[0] != ZLIB_VERSION[0]) { err = ziperr(ZE_LOGIC, "incompatible zlib version (expected %s, found %s)", ZLIB_VERSION, zlib_version); } else if (strcmp(zlib_version, ZLIB_VERSION) != 0) { fprintf(stderr, "warning: different zlib version (expected %s, using %s)\n", ZLIB_VERSION, zlib_version); } return err; } /* Convert from zlib error code Z_* to zip error code ZE_*: we're standardizing on (an extended version of) ZE_* for reporting errors from this library. */ static int translate_zlib_error (int zerr) { int err = ZE_LOGIC; if (zerr == Z_DATA_ERROR) { err = ZE_DATA; } else if (zerr == Z_MEM_ERROR) { err = ZE_MEM; } return err; } /* Re. the negative values for the windowBits argument to inflateInit2 and deflateInit2 value below: giving the negative is the special code needed to get zlib to treat the data as a raw deflate stream, not zlib-wrapped (see zlib.h). */ static int zlib_inflate_init (z_stream *strm) { int windowBits = 15; int err; err = zlib_check_version(); if (err) { return err; } strm->zalloc = Z_NULL; strm->zfree = Z_NULL; strm->opaque = Z_NULL; strm->avail_in = 0; strm->next_in = Z_NULL; err = inflateInit2(strm, -windowBits); if (err != Z_OK) { err = translate_zlib_error(err); ziperr(err, "zlib inflateInit2 failure"); } return err; } static int zlib_deflate_init (z_stream *strm, int level) { int windowBits = 15; int err = 0; err = zlib_check_version(); if (err) { return err; } strm->zalloc = Z_NULL; strm->zfree = Z_NULL; err = deflateInit2(strm, level, Z_DEFLATED, -windowBits, 8, 0); if (err != Z_OK) { err = translate_zlib_error(err); ziperr(err, "zlib deflateInit2 failure"); } return err; } static guint32 compress_file (zfile *zf, zlist *z_entry, FILE *fin, guint32 *crc, guint32 *isize, int *method, int *err) { guchar inbuf[WSIZE]; guchar outbuf[WSIZE]; int maybe_stored = 0; guint32 csize = 0; if (!zf->strm_initted) { *err = zlib_deflate_init(&zf->strm, zf->level); if (*err) { return 0; } zf->strm_initted = 1; } if (zf->level <= 2) { z_entry->flags |= 4; } else if (zf->level >= 8) { z_entry->flags |= 2; } zf->strm.next_in = inbuf; zf->strm.avail_in = file_read_chunk(fin, zf->strm.next_in, WSIZE, crc, isize, err); if (*err) { return 0; } if (zf->strm.avail_in < WSIZE) { size_t more = file_read_chunk(fin, zf->strm.next_in + zf->strm.avail_in, (WSIZE - zf->strm.avail_in), crc, isize, err); if (*err) { return 0; } if (more == EOF || more == 0) { maybe_stored = 1; } else { zf->strm.avail_in += more; } } zf->strm.next_out = outbuf; zf->strm.avail_out = WSIZE; trace(3, "compress_file: maybe_stored = %d\n", maybe_stored); if (!maybe_stored) { while (zf->strm.avail_in != 0 && zf->strm.avail_in != EOF) { *err = deflate(&zf->strm, Z_NO_FLUSH); if (*err != Z_OK && *err != Z_STREAM_END) { *err = translate_zlib_error(*err); return 0; } if (zf->strm.avail_out == 0) { if (fwrite(outbuf, 1, WSIZE, zf->fp) != WSIZE) { *err = ZE_TEMP; return 0; } zf->strm.next_out = outbuf; zf->strm.avail_out = WSIZE; } if (zf->strm.avail_in == 0) { zf->strm.next_in = inbuf; zf->strm.avail_in = file_read_chunk(fin, zf->strm.next_in, WSIZE, crc, isize, err); if (*err) { return 0; } } } } do { *err = deflate(&zf->strm, Z_FINISH); if (maybe_stored) { if (*err == Z_STREAM_END && zf->strm.total_out >= zf->strm.total_in) { unsigned len_out = (unsigned) zf->strm.total_in; trace(2, "deflation does not reduce size, switch to STORE\n"); if (fwrite(inbuf, 1, len_out, zf->fp) != len_out) { *err = ZE_TEMP; return 0; } zf->strm.total_out = (guint32) len_out; *method = STORE; break; } else { maybe_stored = 0; } } if (zf->strm.avail_out < WSIZE) { unsigned len_out = WSIZE - zf->strm.avail_out; if (fwrite(outbuf, 1, len_out, zf->fp) != len_out) { *err = ZE_TEMP; return 0; } zf->strm.next_out = outbuf; zf->strm.avail_out = WSIZE; } } while (*err == Z_OK); if (*err != Z_STREAM_END) { *err = translate_zlib_error(*err); return 0; } if (z_entry->att == (guint16) UNKNOWN) { z_entry->att = (guint16) (zf->strm.data_type == Z_ASCII ? ASCII : BINARY); } csize = zf->strm.total_out; *err = deflateReset(&zf->strm); if (*err != Z_OK) { *err = translate_zlib_error(*err); return 0; } return csize; } static void free_old_extra_fields (zlist *z) { if (z->extlen) { free(z->extra); } if (z->cextlen && z->extra != z->cextra) { free(z->cextra); } z->extra = z->cextra = NULL; z->extlen = z->cextlen = 0; } /* Compress the file z->name into the zip entry described by *z and write it to the file zf->fp. Return an error code in the ZE_ class. Also, update zf->tempzn by the number of bytes written. Note: a zip "entry" includes a local header (which includes the file name), an encryption header if encrypting, the compressed data and possibly an extended local header. */ int zipup (zfile *zf, zlist *z) { guchar b[WSIZE]; /* read buffer */ FILE *fin = NULL; /* input file pointer */ iztimes f_utim; /* UNIX timestamps, filled by file_mod_time() */ guint32 ftime; /* time returned by file_mod_time() */ guint32 attr = 0L; /* attributes from file_mod_time() */ long fsize = -3L; /* size returned by file_mod_time */ size_t k = 0; /* result of read */ int method; /* compression method for this entry */ guint32 o, p; /* offsets in zip file */ guint32 s = 0L; /* size of compressed data */ int isdir; /* set for a directory name */ int islink = 0; /* set for a symbolic link */ int set_type = 0; /* set if file type (ascii/binary) unknown */ guint32 isize = 0; /* length of input file */ guint32 crc = 0; /* CRC for input data */ int err = 0; trace(3, "at top of 'zipup': tempzn = %d\n", (int) zf->tempzn); z->namelen = strlen(z->iname); isdir = z->iname[z->namelen-1] == '/'; ftime = file_mod_time(z->name, &attr, &fsize, &f_utim, zf); if (ftime == 0 || fsize == -3L) { return ZE_OPEN; } /* fsize is set to -1 if the input file is a device, -2 for a volume label */ if (fsize == -2L) { isdir = 1; fsize = 0; } else if (isdir != ((attr & MSDOS_DIR_ATTR) != 0)) { /* don't overwrite a directory with a file and vice-versa */ return ZE_MISS; } z->att = (guint16) UNKNOWN; /* will be changed later */ z->atx = 0; /* may be changed by set_extra_field() */ free_old_extra_fields(z); /* initialize method based on the global method */ method = zf->method; /* create extra field and change z->att and z->atx */ set_extra_field(zf, z, &f_utim); /* Open input file to be zipped up */ islink = is_symlink(attr); if (islink) { trace(2, "'%s': is symlink, using STORE\n", z->name); method = STORE; } else if (isdir) { trace(2, "'%s': is directory, using STORE\n", z->name); method = STORE; fsize = 0; } else { trace(2, "'%s': is regular file, trying DEFLATE\n", z->name); fin = gretl_fopen(z->name, "rb"); if (fin == NULL) { return ZE_OPEN; } } z->time = ftime; if (fsize == 0) { method = STORE; } if (method == BEST) { method = DEFLATE; } /* Fill in header information and write local header to zip file. This header will later be re-written since compressed length and crc are not yet known. */ /* (Assume ext, cext, com, and zname already filled in.) */ z->version_made = (guint16) (OS_CODE + Z_MAJORVER * 10 + Z_MINORVER); z->version_extract = (guint16) (method == STORE ? 10 : 20); z->crc = 0; /* to be updated later */ /* Assume first that we will need an extended local header */ z->flags = 8; z->lflags = z->flags; z->method = (guint16) method; z->csize = (guint32) (method == STORE && fsize >= 0 ? fsize : 0); z->usize = (guint32) (fsize != -1L ? fsize : 0); z->dsk = 0; /* ?? */ if (z->att == (guint16) UNKNOWN) { z->att = BINARY; /* set sensible value in header */ set_type = 1; } /* Attributes from filetime(), flag bits from set_extra_field() */ #ifdef WIN32 z->atx = (z->dosflag)? attr & 0xff : attr | (z->atx & 0x0000ff00); #else z->atx = attr | (z->atx & 0x0000ff00); #endif /* WIN32 */ z->off = zf->tempzn; err = put_local_header(z, zf->fp); if (err) { if (fin != NULL) { fclose(fin); } return err; } zf->tempzn += 4 + LOCHEAD + z->namelen + z->extlen; trace(3, "before compressing, zf->tempzn = %d\n", (int) zf->tempzn); if (ferror(zf->fp)) { if (fin != NULL) { fclose(fin); } return ziperr(ZE_WRITE, "unexpected error on zip file"); } o = ftell(zf->fp); trace(3, "before compressing, ftell(zf->fp) gave o = %d\n", (int) o); if (ferror(zf->fp)) { clearerr(zf->fp); } /* Write stored or deflated file to zip file */ isize = 0L; crc = 0L; if (method == DEFLATE) { if (set_type) { z->att = (guint16) UNKNOWN; /* finally set in filecompress() */ } s = compress_file(zf, z, fin, &crc, &isize, &method, &err); trace(1, "compress_file returned size s = %d\n", (int) s); } else if (!isdir) { if (islink) { k = read_symlink(z->name, (char *) b, WSIZE); crc = crc32(crc, (guchar *) b, k); if (fwrite(b, 1, k, zf->fp) != k) { return ZE_TEMP; } isize = k; } else { while ((k = file_read_chunk(fin, b, WSIZE, &crc, &isize, &err)) > 0 && k != (size_t) EOF) { if (fwrite(b, 1, k, zf->fp) != k) { if (fin != NULL) { fclose(fin); } return ZE_TEMP; } } } s = isize; } if (fin != NULL && ferror(fin)) { perror("\nzip warning"); clearerr(fin); } if (fin != NULL) { fclose(fin); } zf->tempzn += s; p = zf->tempzn; /* save for future fseek() */ trace(2, "after compressing, p = zf->tempzn = %d\n", (int) p); if (fsize != -1L && isize != (guint32) fsize) { trace(2, " isize=%lu, fsize=%lu\n", isize, fsize); } /* now rewrite the local header with correct information */ z->crc = crc; z->csize = s; z->usize = isize; if (fseek(zf->fp, z->off, SEEK_SET)) { if (z->method != (guint16) method) { return ziperr(1, "can't rewrite method"); } if (method == STORE && fsize < 0) { return ziperr(ZE_PARMS, "zip -0 not supported " "for I/O on pipes or devices"); } err = put_extended_header(z, zf->fp); if (err) { return err; } zf->tempzn += 16L; z->flags = z->lflags; /* if flags modified by inflate */ } else { /* seek ok, ftell() should work, check compressed size */ if (p - o != s) { fprintf(stderr, " s=%ld, actual=%ld ", (glong) s, (glong) (p-o)); return ziperr(ZE_FORM, "incorrect compressed size"); } z->method = (guint16) method; /* Need PKUNZIP 2.0 unless STORED */ z->version_extract = (guint16) (method == STORE ? 10 : 20); if ((z->flags & 1) == 0) { z->flags &= ~8; /* clear the extended local header flag */ } z->lflags = z->flags; /* rewrite the local header */ err = put_local_header(z, zf->fp); if (err) { return err; } if (fseek(zf->fp, p, SEEK_SET)) { return ZE_READ; } if ((z->flags & 1) != 0) { /* encrypted file, extended header still required (??) */ if ((err = put_extended_header(z, zf->fp)) != ZE_OK) { return err; } zf->tempzn += 16L; } } /* Free the local extra field, no longer needed */ if (z->extlen) { if (z->extra != z->cextra) { free(z->extra); z->extra = NULL; } z->extlen = 0; } return ZE_OK; } void zlib_deflate_free (zfile *zf) { int err; if (zf->strm_initted) { err = deflateEnd(&zf->strm); if (err != Z_OK && err !=Z_DATA_ERROR) { ziperr(ZE_LOGIC, "zlib deflateEnd failed"); } } } /* now stuff pertaining to unzipping */ static int make_dirs_in_path (const char *fname) { const char *p = fname; char *dirname = NULL; DIR *dir; int len = 0; int err = 0; errno = 0; trace(2, "doing make_dirs_in_path for '%s'\n", fname); while (strchr(p, G_DIR_SEPARATOR) && !err) { len += strcspn(p, G_DIR_SEPARATOR_S); dirname = g_strndup(fname, len); if (dirname == NULL) { err = ZE_MEM; break; } trace(2, "got dirname = '%s'\n", dirname); dir = opendir(dirname); if (dir != NULL) { closedir(dir); } else if (errno == ENOENT) { #ifdef WIN32 if (mkdir(dirname)) { err = ZE_CREAT; } #else if (mkdir(dirname, 0755)) { err = ZE_CREAT; } #endif } else { err = ZE_READ; } g_free(dirname); if (!err) { p = fname + len; while (*p == G_DIR_SEPARATOR) { p++; len++; } } } if (err) { ziperr(err, "trying to create or open directory"); } return err; } static int zip_inflate (FILE *src, FILE *dest, z_stream *strm, int *initted, guint32 *crc) { guchar inbuf[WSIZE]; guchar outbuf[WSIZE]; unsigned have; int zret = Z_OK; int err = 0; if (!*initted) { err = zlib_inflate_init(strm); if (err) { return err; } *initted = 1; } /* decompress until z stream ends or end of file */ do { strm->avail_in = fread(inbuf, 1, WSIZE, src); if (ferror(src)) { return ZE_READ; } if (strm->avail_in == 0) { break; } strm->next_in = inbuf; /* run inflate() on input until output buffer not full */ do { strm->avail_out = WSIZE; strm->next_out = outbuf; zret = inflate(strm, Z_NO_FLUSH); if (zret == Z_NEED_DICT || zret == Z_DATA_ERROR || zret == Z_MEM_ERROR) { err = translate_zlib_error(zret); return err; } have = WSIZE - strm->avail_out; if (fwrite(outbuf, 1, have, dest) != have || ferror(dest)) { return ZE_WRITE; } *crc = crc32(*crc, outbuf, have); } while (strm->avail_out == 0); } while (zret != Z_STREAM_END); inflateReset(strm); if (zret == Z_DATA_ERROR) { err = translate_zlib_error(zret); } return err; } /* extract an uncompressed archive member */ static int zip_unstore (FILE *src, FILE *dest, guint32 usize, guint32 *crc) { guchar buf[WSIZE]; guint32 b, rem = usize; int err = 0; while (rem > 0 && !err) { b = fread(buf, 1, (rem > WSIZE)? WSIZE : rem, src); if (ferror(src)) { err = ZE_READ; } else if (b > 0) { *crc = crc32(*crc, buf, b); if (fwrite(buf, 1, b, dest) != b) { err = ZE_WRITE; } else { rem -= b; } } } return err; } #ifndef WIN32 /* recreate a symbolic link */ static int zip_relink (FILE *src, const char *targ, guint32 usize) { char *lname; int err = 0; lname = calloc(usize + 1, 1); if (lname == NULL) { return ZE_MEM; } if (fread(lname, 1, usize, src) != usize) { err = ZE_READ; } else { remove(targ); if (symlink(lname, targ)) { err = ziperr(ZE_CREAT, targ); } } free(lname); return err; } #endif /* driver for zlib decompression or simple extraction */ int decompress_to_file (zfile *zf, zlist *z, long offset) { #ifndef WIN32 unsigned xattr = (unsigned) (z->atx >> 16) & 0xFFFF; #endif FILE *fout = NULL; int islink = 0; guint32 crc = 0; int err; if (z->flags & 1) { /* encrypted: not handled */ return ziperr(ZE_CRYPT, NULL); } err = make_dirs_in_path(z->zname); if (err) { return err; } if (z->iname[strlen(z->iname) - 1] == '/') { /* the stored item is a directory */ trace(2, "'%s' is a directory, skipping decompression\n", z->iname); return 0; } #ifndef WIN32 islink = (xattr & S_IFMT) == S_IFLNK; #endif /* overwriting existing file(s)? */ if (!islink) { fout = gretl_fopen(z->name, "wb"); if (fout == NULL) { err = ZE_CREAT; } } if (!err) { fseek(zf->fp, offset, SEEK_SET); if (z->method == STORE) { if (islink) { trace(1, "'%s' is a symlink, re-linking\n", z->iname); #ifndef WIN32 err = zip_relink(zf->fp, z->name, z->usize); #endif } else { trace(1, "extracting %s at offset %d\n", z->name, (int) offset); err = zip_unstore(zf->fp, fout, z->usize, &crc); } } else { trace(1, "decompressing %s at offset %d\n", z->name, (int) offset); err = zip_inflate(zf->fp, fout, &zf->strm, &zf->strm_initted, &crc); } if (fout != NULL) { fclose(fout); } } if (!err && !islink) { trace(2, "crc: original = %u, extracted = %u\n", z->crc, crc); if (crc != z->crc) { err = ZE_CRC; } } if (!err && !islink) { unsigned attr = (unsigned) (z->atx >> 16); if (attr == 0) { attr = get_ef_mode(z); } time_stamp_file(z->name, z->time); if (attr) { /* set permissions from zipfile */ chmod(z->name, attr); } } return err; }